Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6888454727 | ||
|
|
91d91ebd08 | ||
|
|
37c5cfe5a9 |
@@ -398,7 +398,7 @@ export default function ChargePointsPage() {
|
|||||||
.filter((c) => c.connectorId > 0)
|
.filter((c) => c.connectorId > 0)
|
||||||
.sort((a, b) => a.connectorId - b.connectorId)
|
.sort((a, b) => a.connectorId - b.connectorId)
|
||||||
.map((conn) => {
|
.map((conn) => {
|
||||||
const url = `${qrOrigin}/charge?cpId=${qrTarget.id}&connector=${conn.connectorId}`;
|
const url = `${qrOrigin}/dashboard/charge?cpId=${qrTarget.id}&connector=${conn.connectorId}`;
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={conn.id}
|
key={conn.id}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFiManager.h>
|
#include <WiFiManager.h>
|
||||||
|
#include <Preferences.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
#include <MicroOcpp.h>
|
#include <MicroOcpp.h>
|
||||||
@@ -18,7 +19,9 @@ enum LEDState
|
|||||||
LED_INITIALIZING, // Blue blinking - Initialization and WiFi connecting
|
LED_INITIALIZING, // Blue blinking - Initialization and WiFi connecting
|
||||||
LED_WIFI_CONNECTED, // Blue solid - WiFi connected, connecting to OCPP server
|
LED_WIFI_CONNECTED, // Blue solid - WiFi connected, connecting to OCPP server
|
||||||
LED_OCPP_CONNECTED, // Green solid - Successfully connected to OCPP server
|
LED_OCPP_CONNECTED, // Green solid - Successfully connected to OCPP server
|
||||||
LED_ERROR // Red - Error state
|
LED_ERROR, // Red solid - Error state
|
||||||
|
LED_RESET_TX, // Yellow solid - 3s BOOT button hold (Ready to clear transaction)
|
||||||
|
LED_FACTORY_RESET // Magenta fast blink - 7s BOOT button hold (Ready to factory reset)
|
||||||
};
|
};
|
||||||
|
|
||||||
static int s_retry_num = 0;
|
static int s_retry_num = 0;
|
||||||
@@ -31,6 +34,19 @@ static const unsigned long BLINK_INTERVAL = 200; // 200ms blink interval
|
|||||||
uint8_t mac[6];
|
uint8_t mac[6];
|
||||||
char cpSerial[13];
|
char cpSerial[13];
|
||||||
|
|
||||||
|
// OCPP Configuration Variables
|
||||||
|
char ocpp_backend[128];
|
||||||
|
char cp_identifier[64];
|
||||||
|
char auth_key[64];
|
||||||
|
bool shouldSaveConfig = false;
|
||||||
|
|
||||||
|
// callback notifying us of the need to save config
|
||||||
|
void saveConfigCallback()
|
||||||
|
{
|
||||||
|
Serial.println("Should save config");
|
||||||
|
shouldSaveConfig = true;
|
||||||
|
}
|
||||||
|
|
||||||
struct mg_mgr mgr;
|
struct mg_mgr mgr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -86,6 +102,38 @@ void updateLED()
|
|||||||
leds[0] = Rgb{255, 0, 0}; // Red solid
|
leds[0] = Rgb{255, 0, 0}; // Red solid
|
||||||
leds.show();
|
leds.show();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case LED_RESET_TX:
|
||||||
|
// Yellow fast blink - Ready to clear transaction
|
||||||
|
if (current_time - s_blink_last_time >= 100)
|
||||||
|
{
|
||||||
|
s_blink_last_time = current_time;
|
||||||
|
s_blink_on = !s_blink_on;
|
||||||
|
|
||||||
|
if (s_blink_on)
|
||||||
|
leds[0] = Rgb{150, 150, 0}; // Yellow
|
||||||
|
else
|
||||||
|
leds[0] = Rgb{0, 0, 0};
|
||||||
|
|
||||||
|
leds.show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LED_FACTORY_RESET:
|
||||||
|
// Magenta fast blink - Ready to factory reset
|
||||||
|
if (current_time - s_blink_last_time >= 100)
|
||||||
|
{
|
||||||
|
s_blink_last_time = current_time;
|
||||||
|
s_blink_on = !s_blink_on;
|
||||||
|
|
||||||
|
if (s_blink_on)
|
||||||
|
leds[0] = Rgb{255, 0, 255}; // Magenta
|
||||||
|
else
|
||||||
|
leds[0] = Rgb{0, 0, 0};
|
||||||
|
|
||||||
|
leds.show();
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +144,10 @@ void setup()
|
|||||||
snprintf(cpSerial, sizeof(cpSerial),
|
snprintf(cpSerial, sizeof(cpSerial),
|
||||||
"%02X%02X%02X%02X%02X%02X",
|
"%02X%02X%02X%02X%02X%02X",
|
||||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
|
|
||||||
|
// Auto-generate Charge Point Identifier based on MAC (e.g. HLCP_A1B2C3)
|
||||||
|
snprintf(cp_identifier, sizeof(cp_identifier), "HLCP_%s", cpSerial + 6);
|
||||||
|
|
||||||
// reset LED
|
// reset LED
|
||||||
leds[0] = Rgb{0, 0, 0};
|
leds[0] = Rgb{0, 0, 0};
|
||||||
leds.show();
|
leds.show();
|
||||||
@@ -113,7 +165,30 @@ void setup()
|
|||||||
leds[0] = Rgb{255, 255, 0};
|
leds[0] = Rgb{255, 255, 0};
|
||||||
leds.show();
|
leds.show();
|
||||||
|
|
||||||
|
// Load configuration from Preferences
|
||||||
|
Preferences preferences;
|
||||||
|
preferences.begin("ocpp-config", false);
|
||||||
|
String b = preferences.getString("backend", CFG_OCPP_BACKEND);
|
||||||
|
String a = preferences.getString("auth_key", CFG_AUTHORIZATIONKEY ? CFG_AUTHORIZATIONKEY : "");
|
||||||
|
|
||||||
|
Serial.printf("\n[OCPP] Loaded Backend URL: %s\n", b.c_str());
|
||||||
|
Serial.printf("[OCPP] Loaded Auth Key length: %d\n", a.length());
|
||||||
|
|
||||||
|
strncpy(ocpp_backend, b.c_str(), sizeof(ocpp_backend));
|
||||||
|
strncpy(auth_key, a.c_str(), sizeof(auth_key));
|
||||||
|
|
||||||
WiFiManager wm;
|
WiFiManager wm;
|
||||||
|
wm.setSaveConfigCallback(saveConfigCallback);
|
||||||
|
wm.setSaveParamsCallback(saveConfigCallback);
|
||||||
|
wm.setParamsPage(true);
|
||||||
|
|
||||||
|
// Use autocomplete=off to prevent browsers from autofilling old URLs after a reset
|
||||||
|
WiFiManagerParameter custom_ocpp_backend("backend", "OCPP Backend URL", ocpp_backend, 128, "autocomplete=\"off\"");
|
||||||
|
WiFiManagerParameter custom_auth_key("auth_key", "Auth Key (leave blank if none)", auth_key, 64, "autocomplete=\"off\"");
|
||||||
|
|
||||||
|
wm.addParameter(&custom_ocpp_backend);
|
||||||
|
wm.addParameter(&custom_auth_key);
|
||||||
|
|
||||||
const char *customHeadElement = R"rawliteral(
|
const char *customHeadElement = R"rawliteral(
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
@@ -255,7 +330,19 @@ void setup()
|
|||||||
</style>
|
</style>
|
||||||
)rawliteral";
|
)rawliteral";
|
||||||
wm.setCustomHeadElement(customHeadElement);
|
wm.setCustomHeadElement(customHeadElement);
|
||||||
bool autoConnectRet = wm.autoConnect((String("HLCP_") + String(cpSerial).substring(String(cpSerial).length() - 6)).c_str(), cpSerial);
|
bool autoConnectRet = wm.autoConnect(cp_identifier, cpSerial);
|
||||||
|
|
||||||
|
if (shouldSaveConfig)
|
||||||
|
{
|
||||||
|
strncpy(ocpp_backend, custom_ocpp_backend.getValue(), sizeof(ocpp_backend));
|
||||||
|
strncpy(auth_key, custom_auth_key.getValue(), sizeof(auth_key));
|
||||||
|
|
||||||
|
preferences.putString("backend", ocpp_backend);
|
||||||
|
preferences.putString("auth_key", auth_key);
|
||||||
|
Serial.println("Saved new OCPP config to Preferences");
|
||||||
|
}
|
||||||
|
preferences.end();
|
||||||
|
|
||||||
if (!autoConnectRet)
|
if (!autoConnectRet)
|
||||||
{
|
{
|
||||||
Serial.println("Failed to connect and hit timeout");
|
Serial.println("Failed to connect and hit timeout");
|
||||||
@@ -267,8 +354,20 @@ void setup()
|
|||||||
s_led_state = LED_WIFI_CONNECTED;
|
s_led_state = LED_WIFI_CONNECTED;
|
||||||
|
|
||||||
mg_mgr_init(&mgr);
|
mg_mgr_init(&mgr);
|
||||||
MicroOcpp::MOcppMongooseClient *client = new MicroOcpp::MOcppMongooseClient(&mgr, CFG_OCPP_BACKEND, CFG_CP_IDENTIFIER, CFG_AUTHORIZATIONKEY, "", MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail), MicroOcpp::ProtocolVersion(1, 6));
|
const char *final_auth_key = (strlen(auth_key) > 0) ? auth_key : nullptr;
|
||||||
|
MicroOcpp::MOcppMongooseClient *client = new MicroOcpp::MOcppMongooseClient(&mgr, ocpp_backend, cp_identifier, final_auth_key, "", MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail), MicroOcpp::ProtocolVersion(1, 6));
|
||||||
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));
|
||||||
|
|
||||||
|
// For development/recovery: Set up BOOT button (GPIO 0)
|
||||||
|
pinMode(0, INPUT_PULLUP);
|
||||||
|
|
||||||
|
// Forcefully accept rejected RemoteStopTransaction (if hardware goes out of sync with CSMS)
|
||||||
|
setOnSendConf("RemoteStopTransaction", [](JsonObject payload)
|
||||||
|
{
|
||||||
|
if (!strcmp(payload["status"], "Rejected")) {
|
||||||
|
Serial.println("[main] MicroOcpp rejected RemoteStopTransaction! Force overriding and stopping charging...");
|
||||||
|
endTransaction(nullptr, "Remote", 1);
|
||||||
|
} });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,6 +376,75 @@ void loop()
|
|||||||
mg_mgr_poll(&mgr, 10);
|
mg_mgr_poll(&mgr, 10);
|
||||||
mocpp_loop();
|
mocpp_loop();
|
||||||
|
|
||||||
|
// Handle BOOT button (GPIO 0) interactions for recovery
|
||||||
|
bool is_btn_pressed = (digitalRead(0) == LOW);
|
||||||
|
static unsigned long boot_press_time = 0;
|
||||||
|
static bool boot_was_pressed = false;
|
||||||
|
|
||||||
|
if (is_btn_pressed)
|
||||||
|
{
|
||||||
|
if (!boot_was_pressed)
|
||||||
|
{
|
||||||
|
boot_was_pressed = true;
|
||||||
|
boot_press_time = millis();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long held_time = millis() - boot_press_time;
|
||||||
|
if (held_time >= 7000)
|
||||||
|
{
|
||||||
|
s_led_state = LED_FACTORY_RESET;
|
||||||
|
}
|
||||||
|
else if (held_time >= 3000)
|
||||||
|
{
|
||||||
|
s_led_state = LED_RESET_TX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (boot_was_pressed)
|
||||||
|
{
|
||||||
|
unsigned long held_time = millis() - boot_press_time;
|
||||||
|
if (held_time >= 7000)
|
||||||
|
{
|
||||||
|
Serial.println("BOOT button held for > 7s! Clearing WiFi and OCPP settings, then restarting...");
|
||||||
|
|
||||||
|
// Clear WiFi completely
|
||||||
|
WiFi.disconnect(true, true);
|
||||||
|
WiFiManager wm;
|
||||||
|
wm.resetSettings();
|
||||||
|
|
||||||
|
// Clear Preferences explicitely
|
||||||
|
Preferences preferences;
|
||||||
|
preferences.begin("ocpp-config", false);
|
||||||
|
preferences.remove("backend");
|
||||||
|
preferences.remove("auth_key");
|
||||||
|
preferences.clear();
|
||||||
|
preferences.end();
|
||||||
|
Serial.println("NVS ocpp-config cleared.");
|
||||||
|
|
||||||
|
// Clear MicroOcpp FS configs (this removes MO's cached URL)
|
||||||
|
auto fs = MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail);
|
||||||
|
fs->remove(MO_WSCONN_FN);
|
||||||
|
Serial.println("MicroOcpp config cache cleared.");
|
||||||
|
|
||||||
|
// Give time for serial to print and NVS to sync
|
||||||
|
delay(1000);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
else if (held_time >= 3000)
|
||||||
|
{
|
||||||
|
Serial.println("BOOT button held for > 3s! Forcefully ending dangling transaction...");
|
||||||
|
endTransaction(nullptr, "PowerLoss", 1);
|
||||||
|
}
|
||||||
|
boot_was_pressed = false;
|
||||||
|
// Temporarily set to init so the logic below restores the actual network state accurately
|
||||||
|
s_led_state = LED_INITIALIZING;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only update default LED states if button is not overriding them
|
||||||
|
if (!is_btn_pressed)
|
||||||
|
{
|
||||||
auto ctx = getOcppContext();
|
auto ctx = getOcppContext();
|
||||||
if (ctx && ctx->getConnection().isConnected())
|
if (ctx && ctx->getConnection().isConnected())
|
||||||
{
|
{
|
||||||
@@ -292,6 +460,7 @@ void loop()
|
|||||||
s_led_state = LED_WIFI_CONNECTED;
|
s_led_state = LED_WIFI_CONNECTED;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateLED();
|
updateLED();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user