Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add webserver #98

Merged
merged 12 commits into from
Dec 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
# Ignore any .vscode folder
*.vscode/

# Ignore any files in the build folder
Software/build/
15 changes: 14 additions & 1 deletion Software/Software.ino
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
#include "src/lib/miwagner-ESP32-Arduino-CAN/CAN_config.h"
#include "src/lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"

#ifdef WEBSERVER
#include "src/devboard/webserver/webserver.h"
#endif

// Interval settings
int intervalUpdateValues = 4800; // Interval at which to update inverter values / Modbus registers
const int interval10 = 10; // Interval for 10ms tasks
Expand All @@ -36,7 +40,7 @@ static ACAN2515_Buffer16 gBuffer;
#define MB_RTU_NUM_VALUES 30000
#endif
#if defined(LUNA2000_MODBUS)
#define MB_RTU_NUM_VALUES 50000
#define MB_RTU_NUM_VALUES 30000
#endif
#if defined(BYD_MODBUS) || defined(LUNA2000_MODBUS)
uint16_t mbPV[MB_RTU_NUM_VALUES]; // Process variable memory
Expand Down Expand Up @@ -109,6 +113,10 @@ bool inverterAllowsContactorClosing = true;
void setup() {
init_serial();

#ifdef WEBSERVER
init_webserver();
#endif

init_CAN();

init_LED();
Expand All @@ -124,6 +132,11 @@ void setup() {

// Perform main program functions
void loop() {
#ifdef WEBSERVER
// Over-the-air updates by ElegantOTA
ElegantOTA.loop();
#endif

// Input
receive_can(); // Receive CAN messages. Runs as fast as possible
#ifdef DUAL_CAN
Expand Down
8 changes: 8 additions & 0 deletions Software/USER_SETTINGS.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#include "USER_SETTINGS.h"

#ifdef WEBSERVER
const char* ssid = "REPLACE_WITH_YOUR_SSID"; // maximum of 63 characters;
const char* password = "REPLACE_WITH_YOUR_PASSWORD"; // minimum of 8 characters;
const char* ssidAP = "Battery Emulator"; // maximum of 63 characters;
const char* passwordAP = "123456789"; // minimum of 8 characters; set to NULL if you want the access point to be open
#endif
1 change: 1 addition & 0 deletions Software/USER_SETTINGS.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,5 +42,6 @@
//#define CONTACTOR_CONTROL //Enable this line to have pins 25,32,33 handle automatic precharge/contactor+/contactor- closing sequence
//#define PWM_CONTACTOR_CONTROL //Enable this line to use PWM logic for contactors, which lower power consumption and heat generation
//#define DUAL_CAN //Enable this line to activate an isolated secondary CAN Bus using add-on MCP2515 controller (Needed for FoxESS inverters)
#define WEBSERVER //Enable this line to enable WiFi, and to run the webserver

#endif
39 changes: 39 additions & 0 deletions Software/src/devboard/webserver/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Battery Emulator Webserver
This webserver creates a WiFi access point. It also connects ot an existing network.
The webserver intends to display useful information to the user of the battery emulator
development board, without the need to physically connect to the board via USB.
The webserver implementation also provides the option to update the firmware of the board over the air.

To use the webserver, follow the following steps:
- Connect to the board via Serial, and boot up the board.
- The IP address of the WiFi access point is printed to Serial when the board boots up. Note this down.
- Connect to the access point created by board via a WiFi-capable device
- On that device, open a webbrowser and type the IP address of the WiFi access point.
- If the ssid and password of an existing WiFi network are provided, the board will also connect to this network. The IP address obtained on the existing network is shown in the webserver. Note this down.
- From this point onwards, any device connected to the existing WiFi network can access the webserver via a webbrowser. To do this:
- Connect your WiFi-capable device to the existing nwetork.
- Open a webbrowser and type the IP address obtained on the existing WiFi network.

To update the software over the air:
- In Arduino, go to `Sketch` > `Export Compiled Binary`. This will create the `.bin` file that you need to update the firmware. It is found in the folder `Software/build/`
- In your webbrowser, go to the url consisting of the IP address, followed by `/update`, for instance `http://192.168.0.224/update`.
- In the webbrowser, follow the steps to select the `.bin` file and to upload the file to the board.

## Future work
This section lists a number of features that can be implemented as part of the webserver in the future.

- TODO: display state of charge
- TODO: display battery hardware selected
- TODO: display emulated inverter communication protocol selected
- TODO: list all available ssids: scan WiFi Networks https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/
- TODO: add option to add/change ssid and password and save, connect to the new ssid (Option: save ssid and password using Preferences.h library https://randomnerdtutorials.com/esp32-save-data-permanently-preferences/)
- TODO: display WiFi connection strength (https://randomnerdtutorials.com/esp32-useful-wi-fi-functions-arduino/)
- TODO: display CAN state (indicate if there is a communication error)
- TODO: display battery errors in battery diagnosis tab
- TODO: display inverter errors in battery diagnosis tab
- TODO: add functionality to turn WiFi AP off
- TODO: fix IP address on home network (https://randomnerdtutorials.com/esp32-static-fixed-ip-address-arduino-ide/)
- TODO: set hostname (https://randomnerdtutorials.com/esp32-set-custom-hostname-arduino/)

# References
The code for this webserver is based on code provided by Rui Santos at https://randomnerdtutorials.com.
177 changes: 177 additions & 0 deletions Software/src/devboard/webserver/webserver.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#include "webserver.h"

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Measure OTA progress
unsigned long ota_progress_millis = 0;

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
<title>Battery Emulator Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="data:,">
<style>
html {font-family: Arial; display: inline-block; text-align: center;}
h2 {font-size: 3.0rem;}
p {font-size: 3.0rem;}
body {max-width: 600px; margin:0px auto; padding-bottom: 25px;}
.switch {position: relative; display: inline-block; width: 120px; height: 68px}
.switch input {display: none}
.slider {position: absolute; top: 0; left: 0; right: 0; bottom: 0; background-color: #ccc; border-radius: 6px}
.slider:before {position: absolute; content: ""; height: 52px; width: 52px; left: 8px; bottom: 8px; background-color: #fff; -webkit-transition: .4s; transition: .4s; border-radius: 3px}
input:checked+.slider {background-color: #b30000}
input:checked+.slider:before {-webkit-transform: translateX(52px); -ms-transform: translateX(52px); transform: translateX(52px)}
</style>
</head>
<body>
<h2>Battery Emulator Web Server</h2>
%PLACEHOLDER%
</script>
</body>
</html>
)rawliteral";

String wifi_state;
bool wifi_connected;

// Wifi connect time declarations and definition
unsigned long wifi_connect_start_time;
unsigned long wifi_connect_current_time;
const long wifi_connect_timeout = 5000; // Timeout for WiFi connect in milliseconds

void init_webserver() {
// Configure WiFi
WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi Access Point and WiFi STAtion
init_WiFi_AP();
init_WiFi_STA(ssid, password);

// Route for root / web page
server.on("/", HTTP_GET,
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, processor); });

// Send a GET request to <ESP_IP>/update
server.on("/debug", HTTP_GET,
[](AsyncWebServerRequest* request) { request->send(200, "text/plain", "Debug: all OK."); });

// Initialize ElegantOTA
init_ElegantOTA();

// Start server
server.begin();
}

void init_WiFi_AP() {
Serial.print("Creating Access Point: ");
Serial.println(ssidAP);
Serial.print("With password: ");
Serial.println(passwordAP);

WiFi.softAP(ssidAP, passwordAP);

IPAddress IP = WiFi.softAPIP();
Serial.println("Access Point created.");
Serial.print("IP address: ");
Serial.println(IP);
}

void init_WiFi_STA(const char* ssid, const char* password) {
// Connect to Wi-Fi network with SSID and password
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);

wifi_connect_start_time = millis();
wifi_connect_current_time = wifi_connect_start_time;
while ((wifi_connect_current_time - wifi_connect_start_time) <= wifi_connect_timeout &&
WiFi.status() != WL_CONNECTED) { // do this loop for up to 5000ms
// to break the loop when the connection is not established (wrong ssid or password).
delay(500);
Serial.print(".");
wifi_connect_current_time = millis();
}
if (WiFi.status() == WL_CONNECTED) { // WL_CONNECTED is assigned when connected to a WiFi network
wifi_connected = true;
wifi_state = "connected";
// Print local IP address and start web server
Serial.println("");
Serial.print("Connected to WiFi network: ");
Serial.println(ssid);
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
} else {
wifi_connected = false;
wifi_state = "not connected";
Serial.print("Not connected to WiFi network: ");
Serial.println(ssid);
Serial.println("Please check WiFi network name and password, and if WiFi network is available.");
}
}

void init_ElegantOTA() {
ElegantOTA.begin(&server); // Start ElegantOTA
// ElegantOTA callbacks
ElegantOTA.onStart(onOTAStart);
ElegantOTA.onProgress(onOTAProgress);
ElegantOTA.onEnd(onOTAEnd);
}

String processor(const String& var) {
if (var == "PLACEHOLDER") {
String content = "";
// Display LED color
content += "<h4>LED color: ";
switch (LEDcolor) {
case GREEN:
content += "GREEN</h4>";
break;
case YELLOW:
content += "YELLOW</h4>";
break;
case BLUE:
content += "BLUE</h4>";
break;
case RED:
content += "RED</h4>";
break;
case TEST_ALL_COLORS:
content += "RAINBOW</h4>";
break;
default:
break;
}
// Display ssid of network connected to and, if connected to the WiFi, its own IP
content += "<h4>SSID: " + String(ssid) + "</h4>";
content += "<h4>status: " + wifi_state + "</h4>";
if (wifi_connected == true) {
content += "<h4>IP: " + WiFi.localIP().toString() + "</h4>";
}
return content;
}
return String();
}

void onOTAStart() {
// Log when OTA has started
Serial.println("OTA update started!");
// <Add your own code here>
}

void onOTAProgress(size_t current, size_t final) {
// Log every 1 second
if (millis() - ota_progress_millis > 1000) {
ota_progress_millis = millis();
Serial.printf("OTA Progress Current: %u bytes, Final: %u bytes\n", current, final);
}
}

void onOTAEnd(bool success) {
// Log when OTA has finished
if (success) {
Serial.println("OTA update finished successfully!");
} else {
Serial.println("There was an error during OTA update!");
}
// <Add your own code here>
}
92 changes: 92 additions & 0 deletions Software/src/devboard/webserver/webserver.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#ifndef WEBSERVER_H
#define WEBSERVER_H

// Load Wi-Fi library
#include <WiFi.h>
#include "../../../USER_SETTINGS.h" // Needed for WiFi ssid and password
#include "../../lib/ayushsharma82-ElegantOTA/src/ElegantOTA.h"
#include "../../lib/me-no-dev-AsyncTCP/src/AsyncTCP.h"
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
#include "../config.h" // Needed for LED defines

extern uint8_t LEDcolor; // Enum, 0-10
extern const char* ssid;
extern const char* password;
extern const char* ssidAP;
extern const char* passwordAP;

/**
* @brief Initialization function for the webserver.
*
* @param[in] void
*
* @return void
*/
void init_webserver();

/**
* @brief Initialization function that creates a WiFi Access Point.
*
* @param[in] void
*
* @return void
*/
void init_WiFi_AP();

/**
* @brief Initialization function that connects to an existing network.
*
* @param[in] ssid WiFi network name
* @param[in] password WiFi network password
*
* @return void
*/
void init_WiFi_STA(const char* ssid, const char* password);

/**
* @brief Initialization function for ElegantOTA.
*
* @param[in] void
*
* @return void
*/
void init_ElegantOTA();

/**
* @brief Replaces placeholder with content section in web page
*
* @param[in] var
*
* @return String
*/
String processor(const String& var);

/**
* @brief Executes on OTA start
*
* @param[in] void
*
* @return void
*/
void onOTAStart();

/**
* @brief Executes on OTA progress
*
* @param[in] current Current bytes
* @param[in] final Final bytes
*
* @return void
*/
void onOTAProgress(size_t current, size_t final);

/**
* @brief Executes on OTA end
*
* @param[in] void
*
* @return bool success: success = true, failed = false
*/
void onOTAEnd(bool success);

#endif
2 changes: 1 addition & 1 deletion Software/src/inverter/LUNA2000-MODBUS.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#define LUNA2000_MODBUS_H
#include <Arduino.h>

#define MB_RTU_NUM_VALUES 50000
#define MB_RTU_NUM_VALUES 30000
// Definitions for BMS status
#define STANDBY 0
#define INACTIVE 1
Expand Down
2 changes: 2 additions & 0 deletions Software/src/lib/ayushsharma82-ElegantOTA/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
Loading