feat(firmware): 硬件联调引脚定义&远程启动充电和相关外设联动逻辑
This commit is contained in:
36
hardware/firmware/include/pins.h
Normal file
36
hardware/firmware/include/pins.h
Normal file
@@ -0,0 +1,36 @@
|
||||
#ifndef HELIOS_PINS_H
|
||||
#define HELIOS_PINS_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
// Panel switches and LEDs
|
||||
static const uint8_t PIN_CC1 = 39; // High-active switch
|
||||
static const uint8_t PIN_CC2 = 36; // High-active switch
|
||||
static const uint8_t PIN_LED1 = 32; // Low-active LED
|
||||
static const uint8_t PIN_LED2 = 33; // Low-active LED
|
||||
|
||||
// Key inputs
|
||||
static const uint8_t PIN_KEY1 = 34;
|
||||
static const uint8_t PIN_KEY2 = 35;
|
||||
|
||||
// Relay outputs
|
||||
static const uint8_t PIN_RELAY1 = 27;
|
||||
static const uint8_t PIN_RELAY2 = 14;
|
||||
|
||||
// I2C (OLED)
|
||||
static const uint8_t PIN_OLED_SCL = 22;
|
||||
static const uint8_t PIN_OLED_SDA = 21;
|
||||
|
||||
// SPI (RC522)
|
||||
static const uint8_t PIN_RC_MOSI = 23;
|
||||
static const uint8_t PIN_RC_MISO = 19;
|
||||
static const uint8_t PIN_RC_SCK = 18;
|
||||
static const uint8_t PIN_RC_CS = 5;
|
||||
static const uint8_t PIN_RC_RST = 4;
|
||||
|
||||
// UART2 <-> IM1281C (U2)
|
||||
static const uint8_t PIN_U2TXD = 26;
|
||||
static const uint8_t PIN_U2RXD = 25;
|
||||
static const uint32_t BAUD_IM1281C = 9600;
|
||||
|
||||
#endif // HELIOS_PINS_H
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <SPI.h>
|
||||
#include <WiFiManager.h>
|
||||
#include <Preferences.h>
|
||||
#include <string.h>
|
||||
@@ -14,6 +15,7 @@
|
||||
|
||||
#include "esp_system.h"
|
||||
#include "config.h"
|
||||
#include "pins.h"
|
||||
|
||||
/* LED State Enum */
|
||||
enum LEDState
|
||||
@@ -32,6 +34,27 @@ static volatile LEDState s_led_state = LED_INITIALIZING;
|
||||
static volatile unsigned long s_blink_last_time = 0;
|
||||
static volatile bool s_blink_on = false;
|
||||
static const unsigned long BLINK_INTERVAL = 200; // 200ms blink interval
|
||||
static const unsigned long CC_DEBOUNCE_MS = 30;
|
||||
static const unsigned long AUTH_WINDOW_MS = 30000;
|
||||
|
||||
static bool s_cc1_plugged = false;
|
||||
static bool s_cc2_plugged = false;
|
||||
static bool s_cc1_raw_last = false;
|
||||
static bool s_cc2_raw_last = false;
|
||||
static unsigned long s_cc1_last_change_ms = 0;
|
||||
static unsigned long s_cc2_last_change_ms = 0;
|
||||
static bool s_cc1_prev_plugged = false;
|
||||
static bool s_cc2_prev_plugged = false;
|
||||
|
||||
static bool s_key1_prev = false;
|
||||
static bool s_key2_prev = false;
|
||||
|
||||
static bool s_auth_in_progress = false;
|
||||
static bool s_auth_ok = false;
|
||||
static unsigned long s_auth_ok_at_ms = 0;
|
||||
static String s_auth_id_tag;
|
||||
|
||||
static bool s_remote_start_accepted = false;
|
||||
|
||||
uint8_t mac[6];
|
||||
char cpSerial[13];
|
||||
@@ -62,6 +85,205 @@ Adafruit_SSD1306 display(128, 64, &Wire, -1);
|
||||
#define LED_COUNT 1
|
||||
|
||||
SmartLed leds(LED_WS2812B, LED_COUNT, LED_PIN, 0, DoubleBuffer);
|
||||
MFRC522 rfid(PIN_RC_CS, PIN_RC_RST);
|
||||
|
||||
static bool isConnectorPlugged(unsigned int connectorId)
|
||||
{
|
||||
if (connectorId == 1)
|
||||
return s_cc1_plugged;
|
||||
if (connectorId == 2)
|
||||
return s_cc2_plugged;
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool isConnectorIdle(unsigned int connectorId)
|
||||
{
|
||||
return !isTransactionActive(connectorId) && !isTransactionRunning(connectorId) && !getTransaction(connectorId);
|
||||
}
|
||||
|
||||
static bool isConnectorStartReady(unsigned int connectorId)
|
||||
{
|
||||
return connectorId >= 1 && connectorId <= 2 && isOperative(connectorId) && isConnectorIdle(connectorId) && isConnectorPlugged(connectorId);
|
||||
}
|
||||
|
||||
static void updateConnectorPluggedState()
|
||||
{
|
||||
unsigned long now = millis();
|
||||
|
||||
const bool cc1_raw = (digitalRead(PIN_CC1) == HIGH);
|
||||
if (cc1_raw != s_cc1_raw_last)
|
||||
{
|
||||
s_cc1_raw_last = cc1_raw;
|
||||
s_cc1_last_change_ms = now;
|
||||
}
|
||||
if ((now - s_cc1_last_change_ms) >= CC_DEBOUNCE_MS)
|
||||
{
|
||||
s_cc1_plugged = s_cc1_raw_last;
|
||||
}
|
||||
|
||||
const bool cc2_raw = (digitalRead(PIN_CC2) == HIGH);
|
||||
if (cc2_raw != s_cc2_raw_last)
|
||||
{
|
||||
s_cc2_raw_last = cc2_raw;
|
||||
s_cc2_last_change_ms = now;
|
||||
}
|
||||
if ((now - s_cc2_last_change_ms) >= CC_DEBOUNCE_MS)
|
||||
{
|
||||
s_cc2_plugged = s_cc2_raw_last;
|
||||
}
|
||||
}
|
||||
|
||||
static void updatePanelLedsFromPlugState()
|
||||
{
|
||||
// Reserved for startup sync; runtime LED behavior is tied to charging permission.
|
||||
digitalWrite(PIN_LED1, HIGH);
|
||||
digitalWrite(PIN_LED2, HIGH);
|
||||
}
|
||||
|
||||
static void updateChargeActuators()
|
||||
{
|
||||
const bool chg1_on = ocppPermitsCharge(1);
|
||||
const bool chg2_on = ocppPermitsCharge(2);
|
||||
|
||||
// LEDs and relays are low-active
|
||||
digitalWrite(PIN_LED1, chg1_on ? LOW : HIGH);
|
||||
digitalWrite(PIN_LED2, chg2_on ? LOW : HIGH);
|
||||
digitalWrite(PIN_RELAY1, chg1_on ? LOW : HIGH);
|
||||
digitalWrite(PIN_RELAY2, chg2_on ? LOW : HIGH);
|
||||
}
|
||||
|
||||
static void stopIfUnplugged()
|
||||
{
|
||||
if (s_cc1_prev_plugged && !s_cc1_plugged && (isTransactionActive(1) || isTransactionRunning(1)))
|
||||
{
|
||||
Serial.println("[main] Connector 1 unplugged. Stop transaction immediately.");
|
||||
endTransaction(nullptr, "EVDisconnected", 1);
|
||||
}
|
||||
|
||||
if (s_cc2_prev_plugged && !s_cc2_plugged && (isTransactionActive(2) || isTransactionRunning(2)))
|
||||
{
|
||||
Serial.println("[main] Connector 2 unplugged. Stop transaction immediately.");
|
||||
endTransaction(nullptr, "EVDisconnected", 2);
|
||||
}
|
||||
|
||||
s_cc1_prev_plugged = s_cc1_plugged;
|
||||
s_cc2_prev_plugged = s_cc2_plugged;
|
||||
}
|
||||
|
||||
static bool authWindowValid()
|
||||
{
|
||||
return s_auth_ok && (millis() - s_auth_ok_at_ms) <= AUTH_WINDOW_MS && s_auth_id_tag.length() > 0;
|
||||
}
|
||||
|
||||
static void requestAuthorizeByCard(const String &idTag)
|
||||
{
|
||||
if (s_auth_in_progress)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
s_auth_in_progress = true;
|
||||
s_auth_ok = false;
|
||||
s_auth_id_tag = "";
|
||||
|
||||
Serial.printf("[main] Authorize idTag: %s\n", idTag.c_str());
|
||||
authorize(
|
||||
idTag.c_str(),
|
||||
[idTag](JsonObject payload)
|
||||
{
|
||||
s_auth_in_progress = false;
|
||||
const char *status = payload["idTagInfo"]["status"] | "";
|
||||
if (!strcmp(status, "Accepted"))
|
||||
{
|
||||
s_auth_ok = true;
|
||||
s_auth_ok_at_ms = millis();
|
||||
s_auth_id_tag = idTag;
|
||||
Serial.printf("[main] Authorize accepted for idTag %s\n", idTag.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
s_auth_ok = false;
|
||||
s_auth_id_tag = "";
|
||||
Serial.printf("[main] Authorize rejected, status=%s\n", status);
|
||||
}
|
||||
},
|
||||
[]()
|
||||
{
|
||||
s_auth_in_progress = false;
|
||||
s_auth_ok = false;
|
||||
s_auth_id_tag = "";
|
||||
Serial.println("[main] Authorize aborted");
|
||||
});
|
||||
}
|
||||
|
||||
static void pollRfidCard()
|
||||
{
|
||||
if (!rfid.PICC_IsNewCardPresent() || !rfid.PICC_ReadCardSerial())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
String idTag;
|
||||
for (byte i = 0; i < rfid.uid.size; i++)
|
||||
{
|
||||
if (rfid.uid.uidByte[i] < 0x10)
|
||||
{
|
||||
idTag += '0';
|
||||
}
|
||||
idTag += String(rfid.uid.uidByte[i], HEX);
|
||||
}
|
||||
idTag.toUpperCase();
|
||||
|
||||
rfid.PICC_HaltA();
|
||||
rfid.PCD_StopCrypto1();
|
||||
|
||||
requestAuthorizeByCard(idTag);
|
||||
}
|
||||
|
||||
static void tryStartByKey(unsigned int connectorId)
|
||||
{
|
||||
if (!authWindowValid())
|
||||
{
|
||||
Serial.println("[main] No valid authorization window. Swipe card first.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isConnectorStartReady(connectorId))
|
||||
{
|
||||
Serial.printf("[main] Connector %u not ready for start (needs idle + operative + plugged).\n", connectorId);
|
||||
return;
|
||||
}
|
||||
|
||||
auto tx = beginTransaction_authorized(s_auth_id_tag.c_str(), nullptr, connectorId);
|
||||
if (tx)
|
||||
{
|
||||
Serial.printf("[main] Local start accepted on connector %u for idTag %s\n", connectorId, s_auth_id_tag.c_str());
|
||||
s_auth_ok = false;
|
||||
s_auth_id_tag = "";
|
||||
}
|
||||
else
|
||||
{
|
||||
Serial.printf("[main] Local start failed on connector %u\n", connectorId);
|
||||
}
|
||||
}
|
||||
|
||||
static void pollStartKeys()
|
||||
{
|
||||
const bool key1 = (digitalRead(PIN_KEY1) == HIGH);
|
||||
const bool key2 = (digitalRead(PIN_KEY2) == HIGH);
|
||||
|
||||
if (key1 && !s_key1_prev)
|
||||
{
|
||||
tryStartByKey(1);
|
||||
}
|
||||
if (key2 && !s_key2_prev)
|
||||
{
|
||||
tryStartByKey(2);
|
||||
}
|
||||
|
||||
s_key1_prev = key1;
|
||||
s_key2_prev = key2;
|
||||
}
|
||||
|
||||
/* LED Control Functions */
|
||||
void updateLED()
|
||||
@@ -175,11 +397,38 @@ void setup()
|
||||
s_blink_last_time = 0;
|
||||
s_blink_on = false;
|
||||
|
||||
// Initialize CC switches (input) and panel LEDs (low-active output)
|
||||
pinMode(PIN_CC1, INPUT);
|
||||
pinMode(PIN_CC2, INPUT);
|
||||
pinMode(PIN_KEY1, INPUT);
|
||||
pinMode(PIN_KEY2, INPUT);
|
||||
pinMode(PIN_LED1, OUTPUT);
|
||||
pinMode(PIN_LED2, OUTPUT);
|
||||
pinMode(PIN_RELAY1, OUTPUT);
|
||||
pinMode(PIN_RELAY2, OUTPUT);
|
||||
digitalWrite(PIN_LED1, HIGH); // Off by default (low-active)
|
||||
digitalWrite(PIN_LED2, HIGH); // Off by default (low-active)
|
||||
digitalWrite(PIN_RELAY1, HIGH); // Off by default (low-active)
|
||||
digitalWrite(PIN_RELAY2, HIGH); // Off by default (low-active)
|
||||
updateConnectorPluggedState();
|
||||
updatePanelLedsFromPlugState();
|
||||
|
||||
leds[0] = Rgb{255, 255, 0};
|
||||
leds.show();
|
||||
|
||||
// Initialize IIC OLED
|
||||
Wire.begin(4, 15);
|
||||
// Initialize I2C for OLED (from schematic pin map)
|
||||
Wire.begin(PIN_OLED_SDA, PIN_OLED_SCL);
|
||||
|
||||
// Initialize UART2 for IM1281C (U2)
|
||||
Serial2.begin(BAUD_IM1281C, SERIAL_8N1, PIN_U2RXD, PIN_U2TXD);
|
||||
|
||||
// Initialize SPI bus for RC522
|
||||
SPI.begin(PIN_RC_SCK, PIN_RC_MISO, PIN_RC_MOSI, PIN_RC_CS);
|
||||
pinMode(PIN_RC_CS, OUTPUT);
|
||||
digitalWrite(PIN_RC_CS, HIGH);
|
||||
pinMode(PIN_RC_RST, OUTPUT);
|
||||
digitalWrite(PIN_RC_RST, HIGH);
|
||||
rfid.PCD_Init();
|
||||
|
||||
// Load configuration from Preferences
|
||||
Preferences preferences;
|
||||
@@ -410,6 +659,80 @@ void setup()
|
||||
|
||||
mocpp_initialize(*client, ChargerCredentials(CFG_CP_MODAL, CFG_CP_VENDOR, CFG_CP_FW_VERSION, cpSerial, nullptr, nullptr, CFG_CB_SERIAL, nullptr, nullptr), MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail));
|
||||
|
||||
// Expose both physical connectors to CSMS and feed live plug-state from CC switches.
|
||||
// connectorId 1 <-> CC1, connectorId 2 <-> CC2
|
||||
setConnectorPluggedInput([]()
|
||||
{ return s_cc1_plugged; },
|
||||
1);
|
||||
setConnectorPluggedInput([]()
|
||||
{ return s_cc2_plugged; },
|
||||
2);
|
||||
|
||||
// Occupied state drives StatusNotification (Available <-> Preparing/Finishing)
|
||||
// to report plug-in / unplug events even without an active transaction.
|
||||
setOccupiedInput([]()
|
||||
{ return s_cc1_plugged; },
|
||||
1);
|
||||
setOccupiedInput([]()
|
||||
{ return s_cc2_plugged; },
|
||||
2);
|
||||
|
||||
// Custom RemoteStartTransaction policy:
|
||||
// accept only when target connector is idle + operative + plugged.
|
||||
setRequestHandler(
|
||||
"RemoteStartTransaction",
|
||||
[](JsonObject payload)
|
||||
{
|
||||
s_remote_start_accepted = false;
|
||||
|
||||
const char *idTag = payload["idTag"] | "";
|
||||
if (!idTag || !*idTag)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
int reqConnectorId = payload["connectorId"] | -1;
|
||||
unsigned int targetConnector = 0;
|
||||
|
||||
if (reqConnectorId >= 1 && reqConnectorId <= 2)
|
||||
{
|
||||
if (isConnectorStartReady((unsigned int)reqConnectorId))
|
||||
{
|
||||
targetConnector = (unsigned int)reqConnectorId;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (unsigned int cid = 1; cid <= 2; cid++)
|
||||
{
|
||||
if (isConnectorStartReady(cid))
|
||||
{
|
||||
targetConnector = cid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (targetConnector == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto tx = beginTransaction_authorized(idTag, nullptr, targetConnector);
|
||||
s_remote_start_accepted = (tx != nullptr);
|
||||
if (s_remote_start_accepted)
|
||||
{
|
||||
Serial.printf("[main] Remote start accepted on connector %u\n", targetConnector);
|
||||
}
|
||||
},
|
||||
[]() -> std::unique_ptr<MicroOcpp::JsonDoc>
|
||||
{
|
||||
auto doc = std::unique_ptr<MicroOcpp::JsonDoc>(new MicroOcpp::JsonDoc(JSON_OBJECT_SIZE(1)));
|
||||
JsonObject payload = doc->to<JsonObject>();
|
||||
payload["status"] = s_remote_start_accepted ? "Accepted" : "Rejected";
|
||||
return doc;
|
||||
});
|
||||
|
||||
// For development/recovery: Set up BOOT button (GPIO 0)
|
||||
pinMode(0, INPUT_PULLUP);
|
||||
|
||||
@@ -417,16 +740,27 @@ void setup()
|
||||
setOnSendConf("RemoteStopTransaction", [](JsonObject payload)
|
||||
{
|
||||
if (!strcmp(payload["status"], "Rejected")) {
|
||||
Serial.println("[main] MicroOcpp rejected RemoteStopTransaction! Force overriding and stopping charging...");
|
||||
endTransaction(nullptr, "Remote", 1);
|
||||
unsigned int connectorId = payload["connectorId"] | 1;
|
||||
if (connectorId < 1 || connectorId > 2)
|
||||
{
|
||||
connectorId = 1;
|
||||
}
|
||||
Serial.printf("[main] MicroOcpp rejected RemoteStopTransaction on connector %u. Force overriding and stopping charging...\n", connectorId);
|
||||
endTransaction(nullptr, "Remote", connectorId);
|
||||
} });
|
||||
}
|
||||
}
|
||||
|
||||
void loop()
|
||||
{
|
||||
updateConnectorPluggedState();
|
||||
stopIfUnplugged();
|
||||
pollRfidCard();
|
||||
pollStartKeys();
|
||||
|
||||
mg_mgr_poll(&mgr, 10);
|
||||
mocpp_loop();
|
||||
updateChargeActuators();
|
||||
|
||||
// Handle BOOT button (GPIO 0) interactions for recovery
|
||||
bool is_btn_pressed = (digitalRead(0) == LOW);
|
||||
@@ -485,8 +819,9 @@ void loop()
|
||||
}
|
||||
else if (held_time >= 3000)
|
||||
{
|
||||
Serial.println("BOOT button held for > 3s! Forcefully ending dangling transaction...");
|
||||
Serial.println("BOOT button held for > 3s! Forcefully ending dangling transactions on connector 1 and 2...");
|
||||
endTransaction(nullptr, "PowerLoss", 1);
|
||||
endTransaction(nullptr, "PowerLoss", 2);
|
||||
}
|
||||
boot_was_pressed = false;
|
||||
// Temporarily set to init so the logic below restores the actual network state accurately
|
||||
|
||||
Reference in New Issue
Block a user