feat(firmware): 硬件联调引脚定义&远程启动充电和相关外设联动逻辑

This commit is contained in:
2026-04-19 20:20:03 +08:00
parent c7f9c959e0
commit c11c7f1a4a
2 changed files with 376 additions and 5 deletions

View 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

View File

@@ -1,5 +1,6 @@
#include <Arduino.h> #include <Arduino.h>
#include <Wire.h> #include <Wire.h>
#include <SPI.h>
#include <WiFiManager.h> #include <WiFiManager.h>
#include <Preferences.h> #include <Preferences.h>
#include <string.h> #include <string.h>
@@ -14,6 +15,7 @@
#include "esp_system.h" #include "esp_system.h"
#include "config.h" #include "config.h"
#include "pins.h"
/* LED State Enum */ /* LED State Enum */
enum LEDState 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 unsigned long s_blink_last_time = 0;
static volatile bool s_blink_on = false; static volatile bool s_blink_on = false;
static const unsigned long BLINK_INTERVAL = 200; // 200ms blink interval 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]; uint8_t mac[6];
char cpSerial[13]; char cpSerial[13];
@@ -62,6 +85,205 @@ Adafruit_SSD1306 display(128, 64, &Wire, -1);
#define LED_COUNT 1 #define LED_COUNT 1
SmartLed leds(LED_WS2812B, LED_COUNT, LED_PIN, 0, DoubleBuffer); 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 */ /* LED Control Functions */
void updateLED() void updateLED()
@@ -175,11 +397,38 @@ void setup()
s_blink_last_time = 0; s_blink_last_time = 0;
s_blink_on = false; 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[0] = Rgb{255, 255, 0};
leds.show(); leds.show();
// Initialize IIC OLED // Initialize I2C for OLED (from schematic pin map)
Wire.begin(4, 15); 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 // Load configuration from Preferences
Preferences 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)); 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) // For development/recovery: Set up BOOT button (GPIO 0)
pinMode(0, INPUT_PULLUP); pinMode(0, INPUT_PULLUP);
@@ -417,16 +740,27 @@ void setup()
setOnSendConf("RemoteStopTransaction", [](JsonObject payload) setOnSendConf("RemoteStopTransaction", [](JsonObject payload)
{ {
if (!strcmp(payload["status"], "Rejected")) { if (!strcmp(payload["status"], "Rejected")) {
Serial.println("[main] MicroOcpp rejected RemoteStopTransaction! Force overriding and stopping charging..."); unsigned int connectorId = payload["connectorId"] | 1;
endTransaction(nullptr, "Remote", 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() void loop()
{ {
updateConnectorPluggedState();
stopIfUnplugged();
pollRfidCard();
pollStartKeys();
mg_mgr_poll(&mgr, 10); mg_mgr_poll(&mgr, 10);
mocpp_loop(); mocpp_loop();
updateChargeActuators();
// Handle BOOT button (GPIO 0) interactions for recovery // Handle BOOT button (GPIO 0) interactions for recovery
bool is_btn_pressed = (digitalRead(0) == LOW); bool is_btn_pressed = (digitalRead(0) == LOW);
@@ -485,8 +819,9 @@ void loop()
} }
else if (held_time >= 3000) 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", 1);
endTransaction(nullptr, "PowerLoss", 2);
} }
boot_was_pressed = false; boot_was_pressed = false;
// Temporarily set to init so the logic below restores the actual network state accurately // Temporarily set to init so the logic below restores the actual network state accurately