feat(firmware): implement smart configuration for WiFi setup and connection
This commit is contained in:
@@ -10,6 +10,7 @@
|
|||||||
#include <MFRC522.h>
|
#include <MFRC522.h>
|
||||||
|
|
||||||
#include "esp_system.h"
|
#include "esp_system.h"
|
||||||
|
#include "smartconfig.h"
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
|
|
||||||
/* LED State Enum */
|
/* LED State Enum */
|
||||||
@@ -30,6 +31,9 @@ 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
|
||||||
|
|
||||||
|
uint8_t mac[6];
|
||||||
|
char cpSerial[13];
|
||||||
|
|
||||||
struct mg_mgr mgr;
|
struct mg_mgr mgr;
|
||||||
// MicroOcpp::MOcppMongooseClient *client = nullptr;
|
// MicroOcpp::MOcppMongooseClient *client = nullptr;
|
||||||
|
|
||||||
@@ -68,6 +72,7 @@ static void WiFiEvent(WiFiEvent_t event)
|
|||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
case ARDUINO_EVENT_WIFI_STA_CONNECTED:
|
||||||
Serial.println("WiFi connected");
|
Serial.println("WiFi connected");
|
||||||
|
Serial.printf("- Hostname: %s\n", WiFi.getHostname());
|
||||||
break;
|
break;
|
||||||
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
case ARDUINO_EVENT_WIFI_STA_GOT_IP:
|
||||||
Serial.println("Got IP: " + WiFi.localIP().toString());
|
Serial.println("Got IP: " + WiFi.localIP().toString());
|
||||||
@@ -128,13 +133,19 @@ void updateLED()
|
|||||||
|
|
||||||
void setup()
|
void setup()
|
||||||
{
|
{
|
||||||
|
// Get MAC address and set as Charge Point Serial Number
|
||||||
|
esp_efuse_mac_get_default(mac);
|
||||||
|
snprintf(cpSerial, sizeof(cpSerial),
|
||||||
|
"%02X%02X%02X%02X%02X%02X",
|
||||||
|
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
||||||
// reset LED
|
// reset LED
|
||||||
leds[0] = Rgb{0, 0, 0};
|
leds[0] = Rgb{0, 0, 0};
|
||||||
leds.show();
|
leds.show();
|
||||||
// initialize Serial
|
// initialize Serial
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
delay(1000);
|
delay(1000);
|
||||||
Serial.println("\n\nInitializing firmware...");
|
Serial.printf("\n\n%s(%s) made by %s\n", CFG_CP_MODAL, cpSerial, CFG_CP_VENDOR);
|
||||||
|
Serial.println("Initializing firmware...\n");
|
||||||
|
|
||||||
// Initialize LED
|
// Initialize LED
|
||||||
s_led_state = LED_INITIALIZING;
|
s_led_state = LED_INITIALIZING;
|
||||||
@@ -142,73 +153,72 @@ void setup()
|
|||||||
s_blink_on = false;
|
s_blink_on = false;
|
||||||
|
|
||||||
// Initialize WiFi
|
// Initialize WiFi
|
||||||
WiFi.onEvent(WiFiEvent);
|
// WiFi.onEvent(WiFiEvent);
|
||||||
WiFi.mode(WIFI_STA);
|
// WiFi.mode(WIFI_STA);
|
||||||
WiFi.begin(CFG_WIFI_SSID, CFG_WIFI_PASS);
|
// WiFi.setHostname((CFG_CP_MODAL + String("_") + String(cpSerial).substring(String(cpSerial).length() - 6)).c_str());
|
||||||
Serial.println("WiFi connecting...");
|
// WiFi.begin(CFG_WIFI_SSID, CFG_WIFI_PASS);
|
||||||
|
|
||||||
// Wait for WiFi connection with LED updates
|
// Wait for WiFi connection with LED updates
|
||||||
int retry = 0;
|
// int retry = 0;
|
||||||
while (WiFi.status() != WL_CONNECTED && retry < 20)
|
// while (WiFi.status() != WL_CONNECTED && retry < 20)
|
||||||
{
|
// {
|
||||||
delay(200);
|
// delay(200);
|
||||||
updateLED(); // Update LED while waiting for WiFi
|
// updateLED(); // Update LED while waiting for WiFi
|
||||||
Serial.print(".");
|
// Serial.print(".");
|
||||||
retry++;
|
// retry++;
|
||||||
}
|
// }
|
||||||
Serial.println();
|
// Serial.println();
|
||||||
|
|
||||||
if (WiFi.status() == WL_CONNECTED)
|
if (!connectToSavedWiFi())
|
||||||
{
|
{
|
||||||
Serial.println("WiFi connected");
|
String ssidPrefix = CFG_CP_MODAL + String("_") + String(cpSerial).substring(String(cpSerial).length() - 6);
|
||||||
Serial.println("IP address: " + WiFi.localIP().toString());
|
char ssidPrefixBuffer[32];
|
||||||
s_led_state = LED_WIFI_CONNECTED;
|
strcpy(ssidPrefixBuffer, ssidPrefix.c_str());
|
||||||
}
|
startSmartConfig(ssidPrefixBuffer, cpSerial);
|
||||||
else
|
|
||||||
{
|
|
||||||
Serial.println("WiFi connection failed");
|
|
||||||
s_led_state = LED_ERROR;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
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));
|
||||||
|
|
||||||
uint8_t mac[6];
|
|
||||||
esp_efuse_mac_get_default(mac); // read hardware MAC from efuse
|
|
||||||
char cpSerial[13];
|
|
||||||
snprintf(cpSerial, sizeof(cpSerial),
|
|
||||||
"%02X%02X%02X%02X%02X%02X",
|
|
||||||
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
|
|
||||||
Serial.printf("Charge Point Serial Number: %s\n", cpSerial);
|
|
||||||
|
|
||||||
mocpp_initialize(*client, ChargerCredentials(CFG_CP_MODAL, CFG_CP_VENDOR, "1.0.0", 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, "1.0.0", cpSerial, nullptr, nullptr, CFG_CB_SERIAL, nullptr, nullptr), MicroOcpp::makeDefaultFilesystemAdapter(MicroOcpp::FilesystemOpt::Use_Mount_FormatOnFail));
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
|
if (!smartconfig_done)
|
||||||
|
{
|
||||||
|
dnsServer.processNextRequest();
|
||||||
|
server.handleClient();
|
||||||
|
}
|
||||||
|
if (should_reboot)
|
||||||
|
{
|
||||||
|
Serial.println("Rebooting...");
|
||||||
|
delay(1000);
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
|
||||||
mg_mgr_poll(&mgr, 10);
|
mg_mgr_poll(&mgr, 10);
|
||||||
mocpp_loop();
|
mocpp_loop();
|
||||||
|
|
||||||
// Check OCPP connection status
|
// Check OCPP connection status
|
||||||
if (s_wifi_connected)
|
// if (s_wifi_connected)
|
||||||
{
|
// {
|
||||||
auto ctx = getOcppContext();
|
// auto ctx = getOcppContext();
|
||||||
if (ctx && ctx->getConnection().isConnected())
|
// if (ctx && ctx->getConnection().isConnected())
|
||||||
{
|
// {
|
||||||
if (s_led_state != LED_OCPP_CONNECTED)
|
// if (s_led_state != LED_OCPP_CONNECTED)
|
||||||
{
|
// {
|
||||||
s_led_state = LED_OCPP_CONNECTED;
|
// s_led_state = LED_OCPP_CONNECTED;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
else
|
// else
|
||||||
{
|
// {
|
||||||
if (s_led_state != LED_WIFI_CONNECTED)
|
// if (s_led_state != LED_WIFI_CONNECTED)
|
||||||
{
|
// {
|
||||||
s_led_state = LED_WIFI_CONNECTED;
|
// s_led_state = LED_WIFI_CONNECTED;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
updateLED();
|
updateLED();
|
||||||
|
|
||||||
|
|||||||
152
hardware/firmware/src/smartconfig.cpp
Normal file
152
hardware/firmware/src/smartconfig.cpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#include "smartconfig.h"
|
||||||
|
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include <Preferences.h>
|
||||||
|
|
||||||
|
WebServer server(80);
|
||||||
|
DNSServer dnsServer;
|
||||||
|
Preferences preferences;
|
||||||
|
|
||||||
|
IPAddress apIP(192, 168, 4, 1);
|
||||||
|
|
||||||
|
bool should_reboot = false;
|
||||||
|
bool smartconfig_done = false;
|
||||||
|
|
||||||
|
const char *HTML_FORM = R"rawliteral(
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="zh-CN">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>Helios Charge Point 配网</title>
|
||||||
|
<style>
|
||||||
|
:root { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #1f2a37; }
|
||||||
|
body { margin: 0; background: #f4f6fb; display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 16px; }
|
||||||
|
.card { width: min(360px, 100%); background: #fff; border-radius: 16px; padding: 24px; box-shadow: 0 8px 24px rgba(15, 23, 42, 0.12); }
|
||||||
|
h2 { margin: 0 0 16px; font-size: 1.4rem; text-align: center; }
|
||||||
|
label { display: block; margin-bottom: 6px; font-weight: 600; }
|
||||||
|
input[type="text"], input[type="password"] {
|
||||||
|
width: 100%; padding: 12px; margin-bottom: 16px; border: 1px solid #d0d7e2; border-radius: 10px;
|
||||||
|
font-size: 1rem; transition: border 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
input:focus { outline: none; border-color: #2563eb; box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15); }
|
||||||
|
input[type="submit"] {
|
||||||
|
width: 100%; padding: 12px; border: none; border-radius: 10px; background: #2563eb; color: #fff;
|
||||||
|
font-size: 1rem; font-weight: 600; cursor: pointer; transition: background 0.2s;
|
||||||
|
}
|
||||||
|
input[type="submit"]:hover { background: #1d4ed8; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<h2>配置 WiFi</h2>
|
||||||
|
<form action="/save" method="POST">
|
||||||
|
<label for="ssid">SSID</label>
|
||||||
|
<input type="text" id="ssid" name="ssid" required>
|
||||||
|
<label for="password">密码</label>
|
||||||
|
<input type="password" id="password" name="password" required>
|
||||||
|
<input type="submit" value="保存并连接">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
)rawliteral";
|
||||||
|
|
||||||
|
void handleCaptivePortal()
|
||||||
|
{
|
||||||
|
String host = server.hostHeader();
|
||||||
|
server.sendHeader("Location", String("http://") + apIP.toString(), true);
|
||||||
|
server.send(302, "text/plain", "");
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleRoot()
|
||||||
|
{
|
||||||
|
server.send(200, "text/html", HTML_FORM);
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSave()
|
||||||
|
{
|
||||||
|
if (server.hasArg("ssid") && server.hasArg("password"))
|
||||||
|
{
|
||||||
|
String ssid = server.arg("ssid");
|
||||||
|
String password = server.arg("password");
|
||||||
|
|
||||||
|
Serial.printf("Received SSID: %s, PASSWORD: %s\n", ssid.c_str(), password.c_str());
|
||||||
|
|
||||||
|
// save to NVS
|
||||||
|
preferences.begin("wifi", false);
|
||||||
|
preferences.putString("ssid", ssid);
|
||||||
|
preferences.putString("password", password);
|
||||||
|
preferences.end();
|
||||||
|
|
||||||
|
String response = "<html><head><meta charset=\"UTF-8\"></head><body><h3>配置已保存,设备正在尝试连接网络...</h3></body></html>";
|
||||||
|
server.send(200, "text/html", response);
|
||||||
|
|
||||||
|
// flag to reboot
|
||||||
|
should_reboot = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
server.send(400, "text/plain", "缺少 SSID 或密码");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void startSmartConfig(char *ssid, char *psk)
|
||||||
|
{
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0));
|
||||||
|
WiFi.softAP(ssid, psk);
|
||||||
|
|
||||||
|
dnsServer.start(53, "*", apIP);
|
||||||
|
|
||||||
|
server.on("/", handleRoot);
|
||||||
|
server.on("/save", handleSave);
|
||||||
|
|
||||||
|
server.on("/generate_204", HTTP_GET, handleCaptivePortal); // Android
|
||||||
|
server.on("/ncsi.txt", HTTP_GET, handleCaptivePortal); // Windows
|
||||||
|
server.on("/hotspot-detect.html", HTTP_GET, handleCaptivePortal); // iOS
|
||||||
|
server.onNotFound(handleCaptivePortal);
|
||||||
|
|
||||||
|
server.begin();
|
||||||
|
Serial.println("HTTP server started");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool connectToSavedWiFi()
|
||||||
|
{
|
||||||
|
preferences.begin("wifi", true);
|
||||||
|
String ssid = preferences.getString("ssid", "");
|
||||||
|
String password = preferences.getString("password", "");
|
||||||
|
preferences.end();
|
||||||
|
|
||||||
|
if (ssid == "")
|
||||||
|
{
|
||||||
|
Serial.println("No saved WiFi credentials");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Serial.printf("Connecting to SSID: %s\n", ssid.c_str());
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.begin(ssid.c_str(), password.c_str());
|
||||||
|
|
||||||
|
unsigned long start = millis();
|
||||||
|
const unsigned long timeout = 15000; // 15 secs
|
||||||
|
|
||||||
|
while (WiFi.status() != WL_CONNECTED && millis() - start < timeout)
|
||||||
|
{
|
||||||
|
Serial.print(".");
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WiFi.status() == WL_CONNECTED)
|
||||||
|
{
|
||||||
|
Serial.println("\nWiFi connected!");
|
||||||
|
Serial.printf("IP address: %s\n", WiFi.localIP().toString().c_str());
|
||||||
|
smartconfig_done = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Serial.println("\nWiFi connect failed.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
hardware/firmware/src/smartconfig.h
Normal file
16
hardware/firmware/src/smartconfig.h
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
#if !defined(SMARTCONFIG_H)
|
||||||
|
#define SMARTCONFIG_H
|
||||||
|
|
||||||
|
#include <WebServer.h>
|
||||||
|
#include <DNSServer.h>
|
||||||
|
|
||||||
|
extern bool should_reboot;
|
||||||
|
extern WebServer server;
|
||||||
|
extern DNSServer dnsServer;
|
||||||
|
|
||||||
|
extern bool smartconfig_done;
|
||||||
|
|
||||||
|
void startSmartConfig(char *ssid, char *psk);
|
||||||
|
bool connectToSavedWiFi();
|
||||||
|
|
||||||
|
#endif // SMARTCONFIG_H
|
||||||
Reference in New Issue
Block a user