Merge pull request #770 from dalathegreat/improvement/sketch-size

Improvement: Reduce sketch size
This commit is contained in:
Daniel Öster 2025-01-10 10:14:48 +03:00 committed by GitHub
commit 4912c012a9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 108 additions and 1664 deletions

View file

@ -5,14 +5,8 @@
#endif
#include "../../../USER_SETTINGS.h"
#include "../../lib/YiannisBourkelis-Uptime-Library/src/uptime.h"
#include "timer.h"
// Time conversion macros
#define DAYS_TO_SECS 86400 // 24 * 60 * 60
#define HOURS_TO_SECS 3600 // 60 * 60
#define MINUTES_TO_SECS 60
#define EE_NOF_EVENT_ENTRIES 30
#define EE_EVENT_ENTRY_SIZE sizeof(EVENT_LOG_ENTRY_TYPE)
#define EE_WRITE_PERIOD_MINUTES 10
@ -70,10 +64,8 @@ static uint32_t lastMillis = millis();
static void set_event(EVENTS_ENUM_TYPE event, uint8_t data, bool latched);
static void update_event_level(void);
static void update_bms_status(void);
static void log_event(EVENTS_ENUM_TYPE event, uint8_t millisrolloverCount, uint32_t timestamp, uint8_t data);
static void print_event_log(void);
static void check_ee_write(void);
uint8_t millisrolloverCount = 0;
@ -87,8 +79,6 @@ void run_event_handling(void) {
}
lastMillis = currentMillis;
run_sequence_on_target();
//check_ee_write();
update_event_level();
}
@ -283,88 +273,88 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_CANFD_RX_FAILURE:
return "No CANFD communication detected for 60s. Shutting down battery control.";
case EVENT_CAN_RX_WARNING:
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
return "High amount of corrupted CAN messages detected. Check CAN wire shielding!";
case EVENT_CAN_TX_FAILURE:
return "ERROR: CAN messages failed to transmit, or no one on the bus to ACK the message!";
return "CAN messages failed to transmit, or no one on the bus to ACK the message!";
case EVENT_CAN_INVERTER_MISSING:
return "Warning: Inverter not sending messages on CAN bus. Check wiring!";
return "Inverter not sending messages on CAN bus. Check wiring!";
case EVENT_CONTACTOR_WELDED:
return "Warning: Contactors sticking/welded. Inspect battery with caution!";
return "Contactors sticking/welded. Inspect battery with caution!";
case EVENT_CHARGE_LIMIT_EXCEEDED:
return "Info: Inverter is charging faster than battery is allowing.";
return "Inverter is charging faster than battery is allowing.";
case EVENT_DISCHARGE_LIMIT_EXCEEDED:
return "Info: Inverter is discharging faster than battery is allowing.";
return "Inverter is discharging faster than battery is allowing.";
case EVENT_WATER_INGRESS:
return "Water leakage inside battery detected. Operation halted. Inspect battery!";
case EVENT_12V_LOW:
return "12V battery source below required voltage to safely close contactors. Inspect the supply/battery!";
case EVENT_SOC_PLAUSIBILITY_ERROR:
return "Warning: SOC reported by battery not plausible. Restart battery!";
return "SOC reported by battery not plausible. Restart battery!";
case EVENT_SOC_UNAVAILABLE:
return "Warning: SOC not sent by BMS. Calibrate BMS via app.";
return "SOC not sent by BMS. Calibrate BMS via app.";
case EVENT_KWH_PLAUSIBILITY_ERROR:
return "Info: kWh remaining reported by battery not plausible. Battery needs cycling.";
return "kWh remaining reported by battery not plausible. Battery needs cycling.";
case EVENT_BALANCING_START:
return "Info: Balancing has started";
return "Balancing has started";
case EVENT_BALANCING_END:
return "Info: Balancing has ended";
return "Balancing has ended";
case EVENT_BATTERY_EMPTY:
return "Info: Battery is completely discharged";
return "Battery is completely discharged";
case EVENT_BATTERY_FULL:
return "Info: Battery is fully charged";
return "Battery is fully charged";
case EVENT_BATTERY_FROZEN:
return "Info: Battery is too cold to operate optimally. Consider warming it up!";
return "Battery is too cold to operate optimally. Consider warming it up!";
case EVENT_BATTERY_CAUTION:
return "Info: Battery has raised a general caution flag. Might want to inspect it closely.";
return "Battery has raised a general caution flag. Might want to inspect it closely.";
case EVENT_BATTERY_CHG_STOP_REQ:
return "ERROR: Battery raised caution indicator AND requested charge stop. Inspect battery status!";
return "Battery raised caution indicator AND requested charge stop. Inspect battery status!";
case EVENT_BATTERY_DISCHG_STOP_REQ:
return "ERROR: Battery raised caution indicator AND requested discharge stop. Inspect battery status!";
return "Battery raised caution indicator AND requested discharge stop. Inspect battery status!";
case EVENT_BATTERY_CHG_DISCHG_STOP_REQ:
return "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!";
return "Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!";
case EVENT_BATTERY_REQUESTS_HEAT:
return "Info: COLD BATTERY! Battery requesting heating pads to activate!";
return "COLD BATTERY! Battery requesting heating pads to activate!";
case EVENT_BATTERY_WARMED_UP:
return "Info: Battery requesting heating pads to stop. The battery is now warm enough.";
return "Battery requesting heating pads to stop. The battery is now warm enough.";
case EVENT_BATTERY_OVERHEAT:
return "ERROR: Battery overheated. Shutting down to prevent thermal runaway!";
return "Battery overheated. Shutting down to prevent thermal runaway!";
case EVENT_BATTERY_OVERVOLTAGE:
return "Warning: Battery exceeding maximum design voltage. Discharge battery to prevent damage!";
return "Battery exceeding maximum design voltage. Discharge battery to prevent damage!";
case EVENT_BATTERY_UNDERVOLTAGE:
return "Warning: Battery under minimum design voltage. Charge battery to prevent damage!";
return "Battery under minimum design voltage. Charge battery to prevent damage!";
case EVENT_BATTERY_VALUE_UNAVAILABLE:
return "Warning: Battery measurement unavailable. Check 12V power supply and battery wiring!";
return "Battery measurement unavailable. Check 12V power supply and battery wiring!";
case EVENT_BATTERY_ISOLATION:
return "Warning: Battery reports isolation error. High voltage might be leaking to ground. Check battery!";
return "Battery reports isolation error. High voltage might be leaking to ground. Check battery!";
case EVENT_VOLTAGE_DIFFERENCE:
return "Info: Too large voltage diff between the batteries. Second battery cannot join the DC-link";
return "Too large voltage diff between the batteries. Second battery cannot join the DC-link";
case EVENT_SOH_DIFFERENCE:
return "Warning: Large deviation in State of health between packs. Inspect battery.";
return "Large deviation in State of health between packs. Inspect battery.";
case EVENT_SOH_LOW:
return "ERROR: State of health critically low. Battery internal resistance too high to continue. Recycle "
return "State of health critically low. Battery internal resistance too high to continue. Recycle "
"battery.";
case EVENT_HVIL_FAILURE:
return "ERROR: Battery interlock loop broken. Check that high voltage / low voltage connectors are seated. "
return "Battery interlock loop broken. Check that high voltage / low voltage connectors are seated. "
"Battery will be disabled!";
case EVENT_PRECHARGE_FAILURE:
return "Info: Battery failed to precharge. Check that capacitor is seated on high voltage output.";
return "Battery failed to precharge. Check that capacitor is seated on high voltage output.";
case EVENT_INTERNAL_OPEN_FAULT:
return "ERROR: High voltage cable removed while battery running. Opening contactors!";
return "High voltage cable removed while battery running. Opening contactors!";
case EVENT_INVERTER_OPEN_CONTACTOR:
return "Info: Inverter side opened contactors. Normal operation.";
return "Inverter side opened contactors. Normal operation.";
case EVENT_INTERFACE_MISSING:
return "Info: Configuration trying to use CAN interface not baked into the software. Recompile software!";
return "Configuration trying to use CAN interface not baked into the software. Recompile software!";
case EVENT_ERROR_OPEN_CONTACTOR:
return "Info: Too much time spent in error state. Opening contactors, not safe to continue charging. "
return "Too much time spent in error state. Opening contactors, not safe to continue charging. "
"Check other error code for reason!";
case EVENT_MODBUS_INVERTER_MISSING:
return "Info: Modbus inverter has not sent any data. Inspect communication wiring!";
return "Modbus inverter has not sent any data. Inspect communication wiring!";
case EVENT_CELL_UNDER_VOLTAGE:
return "ERROR: CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
return "CELL UNDERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
case EVENT_CELL_OVER_VOLTAGE:
return "ERROR: CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
return "CELL OVERVOLTAGE!!! Stopping battery charging and discharging. Inspect battery!";
case EVENT_CELL_DEVIATION_HIGH:
return "ERROR: HIGH CELL DEVIATION!!! Inspect battery!";
return "HIGH CELL DEVIATION!!! Inspect battery!";
case EVENT_UNKNOWN_EVENT_SET:
return "An unknown event was set! Review your code!";
case EVENT_DUMMY_INFO:
@ -376,7 +366,7 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_DUMMY_ERROR:
return "The dummy error event was set!"; // Don't change this event message!
case EVENT_PERSISTENT_SAVE_INFO:
return "Info: Failed to save user settings. Namespace full?";
return "Failed to save user settings. Namespace full?";
case EVENT_SERIAL_RX_WARNING:
return "Error in serial function: No data received for some time, see data for minutes";
case EVENT_SERIAL_RX_FAILURE:
@ -390,54 +380,54 @@ const char* get_event_message_string(EVENTS_ENUM_TYPE event) {
case EVENT_OTA_UPDATE_TIMEOUT:
return "OTA update timed out!";
case EVENT_EEPROM_WRITE:
return "Info: The EEPROM was written";
return "The EEPROM was written";
case EVENT_RESET_UNKNOWN:
return "Info: The board was reset unexpectedly, and reason can't be determined";
return "The board was reset unexpectedly, and reason can't be determined";
case EVENT_RESET_POWERON:
return "Info: The board was reset from a power-on event. Normal operation";
return "The board was reset from a power-on event. Normal operation";
case EVENT_RESET_EXT:
return "Info: The board was reset from an external pin";
return "The board was reset from an external pin";
case EVENT_RESET_SW:
return "Info: The board was reset via software, webserver or OTA. Normal operation";
return "The board was reset via software, webserver or OTA. Normal operation";
case EVENT_RESET_PANIC:
return "Warning: The board was reset due to an exception or panic. Inform developers!";
return "The board was reset due to an exception or panic. Inform developers!";
case EVENT_RESET_INT_WDT:
return "Warning: The board was reset due to an interrupt watchdog timeout. Inform developers!";
return "The board was reset due to an interrupt watchdog timeout. Inform developers!";
case EVENT_RESET_TASK_WDT:
return "Warning: The board was reset due to a task watchdog timeout. Inform developers!";
return "The board was reset due to a task watchdog timeout. Inform developers!";
case EVENT_RESET_WDT:
return "Warning: The board was reset due to other watchdog timeout. Inform developers!";
return "The board was reset due to other watchdog timeout. Inform developers!";
case EVENT_RESET_DEEPSLEEP:
return "Info: The board was reset after exiting deep sleep mode";
return "The board was reset after exiting deep sleep mode";
case EVENT_RESET_BROWNOUT:
return "Info: The board was reset due to a momentary low voltage condition. This is expected during certain "
return "The board was reset due to a momentary low voltage condition. This is expected during certain "
"operations like flashing via USB";
case EVENT_RESET_SDIO:
return "Info: The board was reset over SDIO";
return "The board was reset over SDIO";
case EVENT_RESET_USB:
return "Info: The board was reset by the USB peripheral";
return "The board was reset by the USB peripheral";
case EVENT_RESET_JTAG:
return "Info: The board was reset by JTAG";
return "The board was reset by JTAG";
case EVENT_RESET_EFUSE:
return "Info: The board was reset due to an efuse error";
return "The board was reset due to an efuse error";
case EVENT_RESET_PWR_GLITCH:
return "Info: The board was reset due to a detected power glitch";
return "The board was reset due to a detected power glitch";
case EVENT_RESET_CPU_LOCKUP:
return "Warning: The board was reset due to CPU lockup. Inform developers!";
return "The board was reset due to CPU lockup. Inform developers!";
case EVENT_PAUSE_BEGIN:
return "Warning: The emulator is trying to pause the battery.";
return "The emulator is trying to pause the battery.";
case EVENT_PAUSE_END:
return "Info: The emulator is attempting to resume battery operation from pause.";
return "The emulator is attempting to resume battery operation from pause.";
case EVENT_WIFI_CONNECT:
return "Info: Wifi connected.";
return "Wifi connected.";
case EVENT_WIFI_DISCONNECT:
return "Info: Wifi disconnected.";
return "Wifi disconnected.";
case EVENT_MQTT_CONNECT:
return "Info: MQTT connected.";
return "MQTT connected.";
case EVENT_MQTT_DISCONNECT:
return "Info: MQTT disconnected.";
return "MQTT disconnected.";
case EVENT_EQUIPMENT_STOP:
return "ERROR: EQUIPMENT STOP ACTIVATED!!!";
return "EQUIPMENT STOP ACTIVATED!!!";
default:
return "";
}
@ -603,19 +593,3 @@ static void print_event_log(void) {
}
}
}
static void check_ee_write(void) {
// Only actually write to flash emulated EEPROM every EE_WRITE_PERIOD_MINUTES minutes,
// and only if we've logged any events
if (events.ee_timer.elapsed() && (events.nof_logged_events > 0)) {
EEPROM.commit();
events.nof_eeprom_writes += (events.nof_eeprom_writes < 65535) ? 1 : 0;
events.nof_logged_events = 0;
// We want to know how many writes we have, and to increment the occurrence counter
// we need to clear it first. It's just the way life is. Using events is a smooth
// way to visualize it in the web UI
clear_event(EVENT_EEPROM_WRITE);
set_event(EVENT_EEPROM_WRITE, events.nof_eeprom_writes);
}
}

View file

@ -1,142 +0,0 @@
#include "events.h"
#include "timer.h"
typedef enum {
ETOT_INIT,
ETOT_FIRST_WAIT,
ETOT_INFO,
ETOT_INFO_CLEAR,
ETOT_DEBUG,
ETOT_DEBUG_CLEAR,
ETOT_WARNING,
ETOT_WARNING_CLEAR,
ETOT_ERROR,
ETOT_ERROR_CLEAR,
ETOT_ERROR_LATCHED,
ETOT_DONE
} ETOT_TYPE;
MyTimer timer(5000);
ETOT_TYPE events_test_state = ETOT_INIT;
void run_sequence_on_target(void) {
#ifdef INCLUDE_EVENTS_TEST
switch (events_test_state) {
case ETOT_INIT:
timer.set_interval(10000);
events_test_state = ETOT_FIRST_WAIT;
logging.println("Events test: initialized");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
break;
case ETOT_FIRST_WAIT:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_INFO;
set_event(EVENT_DUMMY_INFO, 123);
set_event(EVENT_DUMMY_INFO, 234); // 234 should show, occurrence 1
logging.println("Events test: info event set, data: 234");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_INFO:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_INFO);
events_test_state = ETOT_INFO_CLEAR;
logging.println("Events test : info event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_INFO_CLEAR:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_DEBUG;
set_event(EVENT_DUMMY_DEBUG, 111);
set_event(EVENT_DUMMY_DEBUG, 222); // 222 should show, occurrence 1
logging.println("Events test : debug event set, data: 222");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DEBUG:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_DEBUG);
events_test_state = ETOT_DEBUG_CLEAR;
logging.println("Events test : info event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DEBUG_CLEAR:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_WARNING;
set_event(EVENT_DUMMY_WARNING, 234);
set_event(EVENT_DUMMY_WARNING, 121); // 121 should show, occurrence 1
logging.println("Events test : warning event set, data: 121");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_WARNING:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_WARNING);
events_test_state = ETOT_WARNING_CLEAR;
logging.println("Events test : warning event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_WARNING_CLEAR:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_ERROR;
set_event(EVENT_DUMMY_ERROR, 221);
set_event(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1
logging.println("Events test : error event set, data: 133");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_ERROR);
events_test_state = ETOT_ERROR_CLEAR;
logging.println("Events test : error event cleared");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR_CLEAR:
if (timer.elapsed()) {
timer.set_interval(8000);
events_test_state = ETOT_ERROR_LATCHED;
set_event_latched(EVENT_DUMMY_ERROR, 221);
set_event_latched(EVENT_DUMMY_ERROR, 133); // 133 should show, occurrence 1
logging.println("Events test : latched error event set, data: 133");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_ERROR_LATCHED:
if (timer.elapsed()) {
timer.set_interval(8000);
clear_event(EVENT_DUMMY_ERROR);
events_test_state = ETOT_DONE;
logging.println("Events test : latched error event cleared?");
logging.print("datalayer.battery.status.bms_status: ");
logging.println(datalayer.battery.status.bms_status);
}
break;
case ETOT_DONE:
default:
break;
}
#endif
}

View file

@ -2,7 +2,6 @@
#include "../../datalayer/datalayer.h"
#include "../../include.h"
#include "events.h"
#include "timer.h"
#include "value_mapping.h"
#define COLOR_GREEN(x) (((uint32_t)0 << 16) | ((uint32_t)x << 8) | 0)
@ -30,58 +29,43 @@ led_color led_get_color() {
}
void LED::exe(void) {
// Don't run too often
if (!timer.elapsed()) {
return;
}
switch (state) {
// Update brightness
switch (mode) {
case led_mode::FLOW:
flow_run();
break;
case led_mode::HEARTBEAT:
heartbeat_run();
break;
case led_mode::CLASSIC:
default:
case LED_NORMAL:
// Update brightness
switch (mode) {
case led_mode::FLOW:
flow_run();
break;
case led_mode::HEARTBEAT:
heartbeat_run();
break;
case led_mode::CLASSIC:
default:
classic_run();
break;
}
// Set color
switch (get_event_level()) {
case EVENT_LEVEL_INFO:
color = led_color::GREEN;
pixels.setPixelColor(0, COLOR_GREEN(brightness)); // Green pulsing LED
break;
case EVENT_LEVEL_WARNING:
color = led_color::YELLOW;
pixels.setPixelColor(0, COLOR_YELLOW(brightness)); // Yellow pulsing LED
break;
case EVENT_LEVEL_DEBUG:
case EVENT_LEVEL_UPDATE:
color = led_color::BLUE;
pixels.setPixelColor(0, COLOR_BLUE(brightness)); // Blue pulsing LED
break;
case EVENT_LEVEL_ERROR:
color = led_color::RED;
pixels.setPixelColor(0, COLOR_RED(LED_MAX_BRIGHTNESS)); // Red LED full brightness
break;
default:
break;
}
break;
case LED_COMMAND:
break;
case LED_RGB:
rainbow_run();
classic_run();
break;
}
// Set color
switch (get_event_level()) {
case EVENT_LEVEL_INFO:
color = led_color::GREEN;
pixels.setPixelColor(0, COLOR_GREEN(brightness)); // Green pulsing LED
break;
case EVENT_LEVEL_WARNING:
color = led_color::YELLOW;
pixels.setPixelColor(0, COLOR_YELLOW(brightness)); // Yellow pulsing LED
break;
case EVENT_LEVEL_DEBUG:
case EVENT_LEVEL_UPDATE:
color = led_color::BLUE;
pixels.setPixelColor(0, COLOR_BLUE(brightness)); // Blue pulsing LED
break;
case EVENT_LEVEL_ERROR:
color = led_color::RED;
pixels.setPixelColor(0, COLOR_RED(LED_MAX_BRIGHTNESS)); // Red LED full brightness
break;
default:
break;
}
pixels.show(); // This sends the updated pixel color to the hardware.
}
@ -92,11 +76,10 @@ void LED::classic_run(void) {
void LED::flow_run(void) {
// Determine how bright the LED should be
int16_t power_W = datalayer.battery.status.active_power_W;
if (power_W < -50) {
if (datalayer.battery.status.active_power_W < -50) {
// Discharging
brightness = max_brightness - up_down(0.95);
} else if (power_W > 50) {
} else if (datalayer.battery.status.active_power_W > 50) {
// Charging
brightness = up_down(0.95);
} else {
@ -146,39 +129,6 @@ void LED::heartbeat_run(void) {
brightness = (uint8_t)(brightness_f * LED_MAX_BRIGHTNESS);
}
void LED::rainbow_run(void) {
brightness = LED_MAX_BRIGHTNESS / 2;
uint16_t ms = (uint16_t)(millis() % LED_PERIOD_MS);
float value = ((float)ms) / LED_PERIOD_MS;
// Clamp the input value to the range [0.0, 1.0]
value = value < 0.0f ? 0.0f : value;
value = value > 1.0f ? 1.0f : value;
uint8_t r = 0, g = 0, b = 0;
// Scale the value to the range [0, 3), which will be used to transition through the colors
float scaledValue = value * 3.0f;
if (scaledValue < 1.0f) {
// From red to green
r = static_cast<uint8_t>((1.0f - scaledValue) * brightness);
g = static_cast<uint8_t>((scaledValue - 0.0f) * brightness);
} else if (scaledValue < 2.0f) {
// From green to blue
g = static_cast<uint8_t>((2.0f - scaledValue) * brightness);
b = static_cast<uint8_t>((scaledValue - 1.0f) * brightness);
} else {
// From blue back to red
b = static_cast<uint8_t>((3.0f - scaledValue) * brightness);
r = static_cast<uint8_t>((scaledValue - 2.0f) * brightness);
}
// Assemble the color
pixels.setPixelColor(0, pixels.Color(r, g, b)); // RGB
}
uint8_t LED::up_down(float middle_point_f) {
// Determine how bright the LED should be
middle_point_f = CONSTRAIN(middle_point_f, 0.0f, 1.0f);

View file

@ -3,10 +3,8 @@
#include "../../include.h"
#include "../../lib/adafruit-Adafruit_NeoPixel/Adafruit_NeoPixel.h"
#include "timer.h"
enum led_mode { CLASSIC, FLOW, HEARTBEAT };
enum led_state { LED_NORMAL, LED_COMMAND, LED_RGB };
class LED {
public:
@ -16,17 +14,13 @@ class LED {
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
max_brightness(LED_MAX_BRIGHTNESS),
brightness(LED_MAX_BRIGHTNESS),
mode(led_mode::CLASSIC),
state(LED_NORMAL),
timer(LED_EXECUTION_FREQUENCY) {}
mode(led_mode::CLASSIC) {}
LED(led_mode mode)
: pixels(1, LED_PIN, NEO_GRB + NEO_KHZ800),
max_brightness(LED_MAX_BRIGHTNESS),
brightness(LED_MAX_BRIGHTNESS),
mode(mode),
state(LED_NORMAL),
timer(LED_EXECUTION_FREQUENCY) {}
mode(mode) {}
void exe(void);
void init(void) { pixels.begin(); }
@ -36,12 +30,9 @@ class LED {
uint8_t max_brightness;
uint8_t brightness;
led_mode mode;
led_state state = LED_NORMAL;
MyTimer timer;
void classic_run(void);
void flow_run(void);
void rainbow_run(void);
void heartbeat_run(void);
uint8_t up_down(float middle_point_f);

View file

@ -5,7 +5,7 @@
enum bms_status_enum { STANDBY = 0, INACTIVE = 1, DARKSTART = 2, ACTIVE = 3, FAULT = 4, UPDATING = 5 };
enum battery_chemistry_enum { NCA, NMC, LFP };
enum led_color { GREEN, YELLOW, RED, BLUE, RGB };
enum led_color { GREEN, YELLOW, RED, BLUE };
#define DISCHARGING 1
#define CHARGING 2

View file

@ -1,36 +0,0 @@
# 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.
Security Concerns
(https://randomnerdtutorials.com/esp32-esp8266-web-server-http-authentication/)
Authentication implemented here is meant to be used in your local network to protect from anyone just typing the ESP IP address and accessing the web server (like unauthorized family member or friend).
## Future work
This section lists a number of features that can be implemented as part of the webserver in the future.
- 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: 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.

View file

@ -823,7 +823,6 @@ String processor(const String& var) {
content += "#F5CC00;";
break;
case led_color::BLUE:
case led_color::RGB:
content += "#2B35AF;"; // Blue in test mode
break;
case led_color::RED:

View file

@ -1,35 +0,0 @@
/* ***********************************************************************
* Uptime library for Arduino boards and compatible systems
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
*
* This file is part of Uptime library for Arduino boards and compatible systems
*
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
* ***********************************************************************/
#include "uptime_formatter.h"
void setup() {
// connect at 115200 so we can read the uptime fast enough
Serial.begin(115200);
}
void loop() {
//uptime_formatter::get_uptime() returns a string
//containing the total device uptime since startup in days, hours, minutes and seconds
Serial.println("up " + uptime_formatter::getUptime());
//wait 1 second
delay(1000);
}

View file

@ -1,56 +0,0 @@
/* ***********************************************************************
* Uptime library for Arduino boards and compatible systems
* (C) 2019 by Yiannis Bourkelis (https://github.com/YiannisBourkelis/)
*
* This file is part of Uptime library for Arduino boards and compatible systems
*
* Uptime library for Arduino boards and compatible systems is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Uptime library for Arduino boards and compatible systems is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Uptime library for Arduino boards and compatible systems. If not, see <http://www.gnu.org/licenses/>.
* ***********************************************************************/
#include "uptime.h"
void setup() {
// connect at 115200 so we can read the uptime fast enough
Serial.begin(115200);
}
void loop() {
//If you do not want to use the string library
//you can get the total uptime variables
//and format the output the way you want.
//First call calculate_uptime() to calculate the uptime
//and then read the uptime variables.
uptime::calculateUptime();
Serial.print("days: ");
Serial.println(uptime::getDays());
Serial.print("hours: ");
Serial.println(uptime::getHours());
Serial.print("minutes: ");
Serial.println(uptime::getMinutes());
Serial.print("seconds: ");
Serial.println(uptime::getSeconds());
Serial.print("milliseconds: ");
Serial.println(uptime::getMilliseconds());
Serial.print("\n");
//wait 1 second
delay(1000);
}

View file

@ -59,19 +59,15 @@ unsigned long uptime::m_remaining_days = 0;
//private variables that in combination hold the actual time passed
//Use the coresponding uptime::get_.... to read these private variables
unsigned long uptime::m_mod_milliseconds;
unsigned long uptime::m_mod_seconds;
unsigned long uptime::m_mod_minutes;
unsigned long uptime::m_mod_hours;
uint8_t uptime::m_mod_seconds;
uint8_t uptime::m_mod_minutes;
uint8_t uptime::m_mod_hours;
uptime::uptime()
{
}
/**** get the actual time passed from device boot time ****/
unsigned long uptime::getMilliseconds()
{
return uptime::m_mod_milliseconds;
}
unsigned long uptime::getSeconds()
{
return uptime::m_mod_seconds;

View file

@ -47,7 +47,6 @@ class uptime
static void calculateUptime();
static unsigned long getMilliseconds();
static unsigned long getSeconds();
static unsigned long getMinutes();
static unsigned long getHours();
@ -61,9 +60,9 @@ class uptime
static unsigned long m_days;
static unsigned long m_mod_milliseconds;
static unsigned long m_mod_seconds;
static unsigned long m_mod_minutes;
static unsigned long m_mod_hours;
static uint8_t m_mod_seconds;
static uint8_t m_mod_minutes;
static uint8_t m_mod_hours;
static unsigned long m_last_milliseconds;
static unsigned long m_remaining_seconds;

View file

@ -36,11 +36,3 @@ String uptime_formatter::getUptime()
(String)(uptime::getMinutes()) + " minutes, " +
(String)(uptime::getSeconds()) + " seconds";
}
//returns the actual time passed since device boot
//in the format: x days, y hours, z minutes, s seconds, n milliseconds
String uptime_formatter::getUptimeWithMillis()
{
return uptime_formatter::getUptime() + ", " +
(String)(uptime::getMilliseconds()) + " milliseconds";
}

View file

@ -31,6 +31,5 @@ class uptime_formatter
uptime_formatter();
static String getUptime();
static String getUptimeWithMillis();
};
#endif

View file

@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

View file

@ -1,40 +0,0 @@
# Prerequisites
*.d
# Compiled Object files
*.slo
*.lo
*.o
*.obj
.docusaurus
# Precompiled Headers
*.gch
*.pch
# Compiled Dynamic libraries
*.so
*.dylib
*.dll
# Fortran module files
*.mod
*.smod
# Compiled Static libraries
*.lai
*.la
*.a
*.lib
# Executables
*.exe
*.out
*.app
node_modules
.DS_Store
.vscode
/build
/portal
.pio

View file

@ -1,30 +0,0 @@
Modifying HTML for the ElegantOTA Library
This guide provides the necessary steps to update and modify the HTML used in the ElegantOTA library.
Follow these steps carefully to ensure that your changes are properly integrated.
Steps to Modify the HTML:
1 - Edit the CurrentPlainHTML.txt:
Locate the file CurrentPlainHTML.txt in your project directory.
Modify the HTML content as needed. This file contains the plain HTML code that will be served through the OTA interface.
Convert the HTML to GZIP and Decimal Format:
Copy the content of the updated CurrentPlainHTML.txt.
Navigate to the CyberChef tool for encoding and compression.
Apply the following recipe:
Gzip with the "Dynamic Huffman Coding" option enabled.
Convert to Decimal with a comma separator.
Use this link for the process: https://gchq.github.io/CyberChef/#recipe=Gzip('Dynamic%20Huffman%20Coding','','',false)To_Decimal('Comma',false)
2 - Update the ELEGANT_HTML Array:
Copy the resulting decimal output from CyberChef.
Replace the existing content of the ELEGANT_HTML array in elop.cpp with the new decimal data from CyberChef.
3 - Adjust the ELEGANT_HTML Array Size:
After updating the ELEGANT_HTML array in both elop.h and elop.cpp, update the array size to match the length of the new output from CyberChef.
Ensure that the array size reflects the new length of the compressed HTML to avoid errors during compilation.

View file

@ -1,21 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#ifndef _MODBUS_BRIDGE_ETHERNET_H
#define _MODBUS_BRIDGE_ETHERNET_H
#include "options.h"
#if HAS_ETHERNET == 1
#include <Ethernet.h>
#include <SPI.h>
#undef SERVER_END
#define SERVER_END // NIL for Ethernet
#include "ModbusServerTCPtemp.h"
#include "ModbusBridgeTemp.h"
using ModbusBridgeEthernet = ModbusBridge<ModbusServerTCP<EthernetServer, EthernetClient>>;
#endif
#endif

View file

@ -1,14 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#ifndef _MODBUS_BRIDGE_RTU_H
#define _MODBUS_BRIDGE_RTU_H
#include "options.h"
#include "ModbusServerRTU.h"
#include "ModbusBridgeTemp.h"
#include "RTUutils.h"
using ModbusBridgeRTU = ModbusBridge<ModbusServerRTU>;
#endif

View file

@ -1,199 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#ifndef _MODBUS_BRIDGE_TEMP_H
#define _MODBUS_BRIDGE_TEMP_H
#include <map>
#include <functional>
#include "ModbusClient.h"
#include "ModbusClientTCP.h" // Needed for client.setTarget()
#include "RTUutils.h" // Needed for RTScallback
#undef LOCAL_LOG_LEVEL
#define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE
#include "Logging.h"
using std::bind;
using std::placeholders::_1;
// Known server types: TCP (client, host/port) and RTU (client)
enum ServerType : uint8_t { TCP_SERVER, RTU_SERVER };
// Bridge class template, takes one of ModbusServerRTU, ModbusServerWiFi, ModbusServerEthernet or ModbusServerTCPasync as parameter
template<typename SERVERCLASS>
class ModbusBridge : public SERVERCLASS {
public:
// Constructor for TCP server variants.
ModbusBridge();
// Constructors for the RTU variant. Parameters as are for ModbusServerRTU
ModbusBridge(uint32_t timeout, int rtsPin = -1);
ModbusBridge(uint32_t timeout, RTScallback rts);
// Destructor
~ModbusBridge();
// Method to link external servers to the bridge
bool attachServer(uint8_t aliasID, uint8_t serverID, uint8_t functionCode, ModbusClient *client, IPAddress host = IPAddress(0, 0, 0, 0), uint16_t port = 0);
// Link another function code to the server
bool addFunctionCode(uint8_t aliasID, uint8_t functionCode);
// Block a function code (respond with ILLEGAL_FUNCTION error)
bool denyFunctionCode(uint8_t aliasID, uint8_t functionCode);
protected:
// ServerData holds all data necessary to address a single server
struct ServerData {
uint8_t serverID; // External server id
ModbusClient *client; // client to be used to request the server
ServerType serverType; // TCP_SERVER or RTU_SERVER
IPAddress host; // TCP: host IP address, else 0.0.0.0
uint16_t port; // TCP: host port number, else 0
// RTU constructor
ServerData(uint8_t sid, ModbusClient *c) :
serverID(sid),
client(c),
serverType(RTU_SERVER),
host(IPAddress(0, 0, 0, 0)),
port(0) {}
// TCP constructor
ServerData(uint8_t sid, ModbusClient *c, IPAddress h, uint16_t p) :
serverID(sid),
client(c),
serverType(TCP_SERVER),
host(h),
port(p) {}
};
// Default worker functions
ModbusMessage bridgeWorker(ModbusMessage msg);
ModbusMessage bridgeDenyWorker(ModbusMessage msg);
// Map of servers attached
std::map<uint8_t, ServerData *> servers;
};
// Constructor for TCP variants
template<typename SERVERCLASS>
ModbusBridge<SERVERCLASS>::ModbusBridge() :
SERVERCLASS() { }
// Constructors for RTU variant
template<typename SERVERCLASS>
ModbusBridge<SERVERCLASS>::ModbusBridge(uint32_t timeout, int rtsPin) :
SERVERCLASS(timeout, rtsPin) { }
// Alternate constructors for RTU variant
template<typename SERVERCLASS>
ModbusBridge<SERVERCLASS>::ModbusBridge(uint32_t timeout, RTScallback rts) :
SERVERCLASS(timeout, rts) { }
// Destructor
template<typename SERVERCLASS>
ModbusBridge<SERVERCLASS>::~ModbusBridge() {
// Release ServerData storage in servers array
for (auto itr = servers.begin(); itr != servers.end(); itr++) {
delete (itr->second);
}
servers.clear();
}
// attachServer: memorize the access data for an external server with ID serverID under bridge ID aliasID
template<typename SERVERCLASS>
bool ModbusBridge<SERVERCLASS>::attachServer(uint8_t aliasID, uint8_t serverID, uint8_t functionCode, ModbusClient *client, IPAddress host, uint16_t port) {
// Is there already an entry for the aliasID?
if (servers.find(aliasID) == servers.end()) {
// No. Store server data in map.
// Do we have a port number?
if (port != 0) {
// Yes. Must be a TCP client
servers[aliasID] = new ServerData(serverID, static_cast<ModbusClient *>(client), host, port);
LOG_D("(TCP): %02X->%02X %d.%d.%d.%d:%d\n", aliasID, serverID, host[0], host[1], host[2], host[3], port);
} else {
// No - RTU client required
servers[aliasID] = new ServerData(serverID, static_cast<ModbusClient *>(client));
LOG_D("(RTU): %02X->%02X\n", aliasID, serverID);
}
}
// Register the server/FC combination for the bridgeWorker
addFunctionCode(aliasID, functionCode);
return true;
}
template<typename SERVERCLASS>
bool ModbusBridge<SERVERCLASS>::addFunctionCode(uint8_t aliasID, uint8_t functionCode) {
// Is there already an entry for the aliasID?
if (servers.find(aliasID) != servers.end()) {
// Yes. Link server to own worker function
this->registerWorker(aliasID, functionCode, std::bind(&ModbusBridge<SERVERCLASS>::bridgeWorker, this, std::placeholders::_1));
LOG_D("FC %02X added for server %02X\n", functionCode, aliasID);
} else {
LOG_E("Server %d not attached to bridge!\n", aliasID);
return false;
}
return true;
}
template<typename SERVERCLASS>
bool ModbusBridge<SERVERCLASS>::denyFunctionCode(uint8_t aliasID, uint8_t functionCode) {
// Is there already an entry for the aliasID?
if (servers.find(aliasID) != servers.end()) {
// Yes. Link server to own worker function
this->registerWorker(aliasID, functionCode, std::bind(&ModbusBridge<SERVERCLASS>::bridgeDenyWorker, this, std::placeholders::_1));
LOG_D("FC %02X blocked for server %02X\n", functionCode, aliasID);
} else {
LOG_E("Server %d not attached to bridge!\n", aliasID);
return false;
}
return true;
}
// bridgeWorker: default worker function to process bridge requests
template<typename SERVERCLASS>
ModbusMessage ModbusBridge<SERVERCLASS>::bridgeWorker(ModbusMessage msg) {
uint8_t aliasID = msg.getServerID();
uint8_t functionCode = msg.getFunctionCode();
ModbusMessage response;
// Find the (alias) serverID
if (servers.find(aliasID) != servers.end()) {
// Found it. We may use servers[aliasID] now without allocating a new map slot
// Set real target server ID
msg.setServerID(servers[aliasID]->serverID);
// Issue the request
LOG_D("Request (%02X/%02X) sent\n", servers[aliasID]->serverID, functionCode);
// TCP servers have a target host/port that needs to be set in the client
if (servers[aliasID]->serverType == TCP_SERVER) {
response = reinterpret_cast<ModbusClientTCP *>(servers[aliasID]->client)->syncRequestMT(msg, (uint32_t)millis(), servers[aliasID]->host, servers[aliasID]->port);
} else {
response = servers[aliasID]->client->syncRequestM(msg, (uint32_t)millis());
}
// Re-set the requested server ID
response.setServerID(aliasID);
} else {
// If we get here, something has gone wrong internally. We send back an error response anyway.
response.setError(aliasID, functionCode, INVALID_SERVER);
}
return response;
}
// bridgeDenyWorker: worker function to block function codes
template<typename SERVERCLASS>
ModbusMessage ModbusBridge<SERVERCLASS>::bridgeDenyWorker(ModbusMessage msg) {
ModbusMessage response;
response.setError(msg.getServerID(), msg.getFunctionCode(), ILLEGAL_FUNCTION);
return response;
}
#endif

View file

@ -1,18 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#ifndef _MODBUS_BRIDGE_WIFI_H
#define _MODBUS_BRIDGE_WIFI_H
#include "options.h"
#include <WiFi.h>
#undef SERVER_END
#define SERVER_END server.end();
#include "ModbusServerTCPtemp.h"
#include "ModbusBridgeTemp.h"
using ModbusBridgeWiFi = ModbusBridge<ModbusServerTCP<WiFiServer, WiFiClient>>;
#endif

View file

@ -1,103 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#include "ModbusClient.h"
#undef LOCAL_LOG_LEVEL
#include "Logging.h"
uint16_t ModbusClient::instanceCounter = 0;
// Default constructor: set the default timeout to 2000ms, zero out all other
ModbusClient::ModbusClient() :
messageCount(0),
errorCount(0),
#if HAS_FREERTOS
worker(NULL),
#elif IS_LINUX
worker(0),
#endif
onData(nullptr),
onError(nullptr),
onResponse(nullptr) { instanceCounter++; }
// onDataHandler: register callback for data responses
bool ModbusClient::onDataHandler(MBOnData handler) {
if (onData) {
LOG_W("onData handler was already claimed\n");
} else if (onResponse) {
LOG_E("onData handler is unavailable with an onResponse handler\n");
return false;
}
onData = handler;
return true;
}
// onErrorHandler: register callback for error responses
bool ModbusClient::onErrorHandler(MBOnError handler) {
if (onError) {
LOG_W("onError handler was already claimed\n");
} else if (onResponse) {
LOG_E("onError handler is unavailable with an onResponse handler\n");
return false;
}
onError = handler;
return true;
}
// onResponseHandler: register callback for error responses
bool ModbusClient::onResponseHandler(MBOnResponse handler) {
if (onError || onData) {
LOG_E("onResponse handler is unavailable with an onData or onError handler\n");
return false;
}
onResponse = handler;
return true;
}
// getMessageCount: return message counter value
uint32_t ModbusClient::getMessageCount() {
return messageCount;
}
// getErrorCount: return error counter value
uint32_t ModbusClient::getErrorCount() {
return errorCount;
}
// resetCounts: Set both message and error counts to zero
void ModbusClient::resetCounts() {
{
LOCK_GUARD(cntLock, countAccessM);
messageCount = 0;
errorCount = 0;
}
}
// waitSync: wait for response on syncRequest to arrive
ModbusMessage ModbusClient::waitSync(uint8_t serverID, uint8_t functionCode, uint32_t token) {
ModbusMessage response;
unsigned long lostPatience = millis();
// Default response is TIMEOUT
response.setError(serverID, functionCode, TIMEOUT);
// Loop 60 seconds, if unlucky
while (millis() - lostPatience < 60000) {
{
LOCK_GUARD(lg, syncRespM);
// Look for the token
auto sR = syncResponse.find(token);
// Is it there?
if (sR != syncResponse.end()) {
// Yes. get the response, delete it from the map and return
response = sR->second;
syncResponse.erase(sR);
break;
}
}
// Give the watchdog time to act
delay(10);
}
return response;
}

View file

@ -1,119 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#ifndef _MODBUS_CLIENT_H
#define _MODBUS_CLIENT_H
#include <functional>
#include <map>
#include "options.h"
#include "ModbusMessage.h"
#if HAS_FREERTOS
extern "C" {
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
}
#elif IS_LINUX
#include <pthread.h>
#endif
#if USE_MUTEX
#include <mutex> // NOLINT
using std::mutex;
using std::lock_guard;
#endif
typedef std::function<void(ModbusMessage msg, uint32_t token)> MBOnData;
typedef std::function<void(Modbus::Error errorCode, uint32_t token)> MBOnError;
typedef std::function<void(ModbusMessage msg, uint32_t token)> MBOnResponse;
class ModbusClient {
public:
bool onDataHandler(MBOnData handler); // Accept onData handler
bool onErrorHandler(MBOnError handler); // Accept onError handler
bool onResponseHandler(MBOnResponse handler); // Accept onResponse handler
uint32_t getMessageCount(); // Informative: return number of messages created
uint32_t getErrorCount(); // Informative: return number of errors received
void resetCounts(); // Set both message and error counts to zero
inline Error addRequest(ModbusMessage m, uint32_t token) { return addRequestM(m, token); }
inline ModbusMessage syncRequest(ModbusMessage m, uint32_t token) { return syncRequestM(m, token); }
// Template function to generate syncRequest functions as long as there is a
// matching ModbusMessage::setMessage() call
template <typename... Args>
ModbusMessage syncRequest(uint32_t token, Args&&... args) {
Error rc = SUCCESS;
// Create request, if valid
ModbusMessage m;
rc = m.setMessage(std::forward<Args>(args) ...);
// Add it to the queue and wait for a response, if valid
if (rc == SUCCESS) {
return syncRequestM(m, token);
}
// Else return the error as a message
return buildErrorMsg(rc, std::forward<Args>(args) ...);
}
// Template function to create an error response message from a variadic pattern
template <typename... Args>
ModbusMessage buildErrorMsg(Error e, uint8_t serverID, uint8_t functionCode, Args&&... args) {
ModbusMessage m;
m.setError(serverID, functionCode, e);
return m;
}
// Template function to generate addRequest functions as long as there is a
// matching ModbusMessage::setMessage() call
template <typename... Args>
Error addRequest(uint32_t token, Args&&... args) {
Error rc = SUCCESS; // Return value
// Create request, if valid
ModbusMessage m;
rc = m.setMessage(std::forward<Args>(args) ...);
// Add it to the queue, if valid
if (rc == SUCCESS) {
return addRequestM(m, token);
}
// Else return the error
return rc;
}
protected:
ModbusClient(); // Default constructor
virtual void isInstance() = 0; // Make class abstract
ModbusMessage waitSync(uint8_t serverID, uint8_t functionCode, uint32_t token); // wait for syncRequest response to arrive
// Virtual addRequest variant needed internally. All others done by template!
virtual Error addRequestM(ModbusMessage msg, uint32_t token) = 0;
// Virtual syncRequest variant following the same pattern
virtual ModbusMessage syncRequestM(ModbusMessage msg, uint32_t token) = 0;
// Prevent copy construction or assignment
ModbusClient(ModbusClient& other) = delete;
ModbusClient& operator=(ModbusClient& other) = delete;
uint32_t messageCount; // Number of requests generated. Used for transactionID in TCPhead
uint32_t errorCount; // Number of errors received
#if HAS_FREERTOS
TaskHandle_t worker; // Interface instance worker task
#elif IS_LINUX
pthread_t worker;
#endif
MBOnData onData; // Data response handler
MBOnError onError; // Error response handler
MBOnResponse onResponse; // Uniform response handler
static uint16_t instanceCounter; // Number of ModbusClients created
std::map<uint32_t, ModbusMessage> syncResponse; // Map to hold response messages on synchronous requests
#if USE_MUTEX
std::mutex syncRespM; // Mutex protecting syncResponse map against race conditions
std::mutex countAccessM; // Mutex protecting access to the message and error counts
#endif
// Let any ModbusBridge class use protected members
template<typename SERVERCLASS> friend class ModbusBridge;
};
#endif

View file

@ -1,354 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#include "ModbusClientRTU.h"
#if HAS_FREERTOS
#undef LOCAL_LOG_LEVEL
// #define LOCAL_LOG_LEVEL LOG_LEVEL_VERBOSE
#include "Logging.h"
// Constructor takes an optional DE/RE pin and queue size
ModbusClientRTU::ModbusClientRTU(int8_t rtsPin, uint16_t queueLimit) :
ModbusClient(),
MR_serial(nullptr),
MR_lastMicros(micros()),
MR_interval(2000),
MR_rtsPin(rtsPin),
MR_qLimit(queueLimit),
MR_timeoutValue(DEFAULTTIMEOUT),
MR_useASCII(false),
MR_skipLeadingZeroByte(false) {
if (MR_rtsPin >= 0) {
pinMode(MR_rtsPin, OUTPUT);
MTRSrts = [this](bool level) {
digitalWrite(MR_rtsPin, level);
};
MTRSrts(LOW);
} else {
MTRSrts = RTUutils::RTSauto;
}
}
// Alternative constructor takes an RTS callback function
ModbusClientRTU::ModbusClientRTU(RTScallback rts, uint16_t queueLimit) :
ModbusClient(),
MR_serial(nullptr),
MR_lastMicros(micros()),
MR_interval(2000),
MTRSrts(rts),
MR_qLimit(queueLimit),
MR_timeoutValue(DEFAULTTIMEOUT),
MR_useASCII(false),
MR_skipLeadingZeroByte(false) {
MR_rtsPin = -1;
MTRSrts(LOW);
}
// Destructor: clean up queue, task etc.
ModbusClientRTU::~ModbusClientRTU() {
// Kill worker task and clean up request queue
end();
}
// begin: start worker task - general version
void ModbusClientRTU::begin(Stream& serial, uint32_t baudRate, int coreID, uint32_t userInterval) {
MR_serial = &serial;
doBegin(baudRate, coreID, userInterval);
}
// begin: start worker task - HardwareSerial version
void ModbusClientRTU::begin(HardwareSerial& serial, int coreID, uint32_t userInterval) {
MR_serial = &serial;
uint32_t baudRate = serial.baudRate();
serial.setRxFIFOFull(1);
doBegin(baudRate, coreID, userInterval);
}
void ModbusClientRTU::doBegin(uint32_t baudRate, int coreID, uint32_t userInterval) {
// Task already running? End it in case
end();
// Pull down RTS toggle, if necessary
MTRSrts(LOW);
// Set minimum interval time
MR_interval = RTUutils::calculateInterval(baudRate);
// If user defined interval is longer, use that
if (MR_interval < userInterval) {
MR_interval = userInterval;
}
// Create unique task name
char taskName[18];
snprintf(taskName, 18, "Modbus%02XRTU", instanceCounter);
// Start task to handle the queue
xTaskCreatePinnedToCore((TaskFunction_t)&handleConnection, taskName, CLIENT_TASK_STACK, this, 6, &worker, coreID >= 0 ? coreID : NULL);
LOG_D("Client task %d started. Interval=%d\n", (uint32_t)worker, MR_interval);
}
// end: stop worker task
void ModbusClientRTU::end() {
if (worker) {
// Clean up queue
{
// Safely lock access
LOCK_GUARD(lockGuard, qLock);
// Get all queue entries one by one
while (!requests.empty()) {
// Remove front entry
requests.pop();
}
}
// Kill task
vTaskDelete(worker);
LOG_D("Client task %d killed.\n", (uint32_t)worker);
worker = nullptr;
}
}
// setTimeOut: set/change the default interface timeout
void ModbusClientRTU::setTimeout(uint32_t TOV) {
MR_timeoutValue = TOV;
LOG_D("Timeout set to %d\n", TOV);
}
// Toggle protocol to ModbusASCII
void ModbusClientRTU::useModbusASCII(unsigned long timeout) {
MR_useASCII = true;
MR_timeoutValue = timeout; // Switch timeout to ASCII's value
LOG_D("Protocol mode: ASCII\n");
}
// Toggle protocol to ModbusRTU
void ModbusClientRTU::useModbusRTU() {
MR_useASCII = false;
LOG_D("Protocol mode: RTU\n");
}
// Inquire protocol mode
bool ModbusClientRTU::isModbusASCII() {
return MR_useASCII;
}
// Toggle skipping of leading 0x00 byte
void ModbusClientRTU::skipLeading0x00(bool onOff) {
MR_skipLeadingZeroByte = onOff;
LOG_D("Skip leading 0x00 mode = %s\n", onOff ? "ON" : "OFF");
}
// Return number of unprocessed requests in queue
uint32_t ModbusClientRTU::pendingRequests() {
return requests.size();
}
// Remove all pending request from queue
void ModbusClientRTU::clearQueue()
{
std::queue<RequestEntry> empty;
LOCK_GUARD(lockGuard, qLock);
std::swap(requests, empty);
}
// Base addRequest taking a preformatted data buffer and length as parameters
Error ModbusClientRTU::addRequestM(ModbusMessage msg, uint32_t token) {
Error rc = SUCCESS; // Return value
LOG_D("request for %02X/%02X\n", msg.getServerID(), msg.getFunctionCode());
// Add it to the queue, if valid
if (msg) {
// Queue add successful?
if (!addToQueue(token, msg)) {
// No. Return error after deleting the allocated request.
rc = REQUEST_QUEUE_FULL;
}
}
LOG_D("RC=%02X\n", rc);
return rc;
}
// Base syncRequest follows the same pattern
ModbusMessage ModbusClientRTU::syncRequestM(ModbusMessage msg, uint32_t token) {
ModbusMessage response;
if (msg) {
// Queue add successful?
if (!addToQueue(token, msg, true)) {
// No. Return error after deleting the allocated request.
response.setError(msg.getServerID(), msg.getFunctionCode(), REQUEST_QUEUE_FULL);
} else {
// Request is queued - wait for the result.
response = waitSync(msg.getServerID(), msg.getFunctionCode(), token);
}
} else {
response.setError(msg.getServerID(), msg.getFunctionCode(), EMPTY_MESSAGE);
}
return response;
}
// addBroadcastMessage: create a fire-and-forget message to all servers on the RTU bus
Error ModbusClientRTU::addBroadcastMessage(const uint8_t *data, uint8_t len) {
Error rc = SUCCESS; // Return value
LOG_D("Broadcast request of length %d\n", len);
// We do only accept requests with data, 0 byte, data and CRC must fit into 256 bytes.
if (len && len < 254) {
// Create a "broadcast token"
uint32_t token = (millis() & 0xFFFFFF) | 0xBC000000;
ModbusMessage msg;
// Server ID is 0x00 for broadcast
msg.add((uint8_t)0x00);
// Append data
msg.add(data, len);
// Queue add successful?
if (!addToQueue(token, msg)) {
// No. Return error after deleting the allocated request.
rc = REQUEST_QUEUE_FULL;
}
} else {
rc = BROADCAST_ERROR;
}
LOG_D("RC=%02X\n", rc);
return rc;
}
// addToQueue: send freshly created request to queue
bool ModbusClientRTU::addToQueue(uint32_t token, ModbusMessage request, bool syncReq) {
bool rc = false;
// Did we get one?
if (request) {
RequestEntry re(token, request, syncReq);
if (requests.size()<MR_qLimit) {
// Yes. Safely lock queue and push request to queue
rc = true;
LOCK_GUARD(lockGuard, qLock);
requests.push(re);
}
{
LOCK_GUARD(cntLock, countAccessM);
messageCount++;
}
}
LOG_D("RC=%02X\n", rc);
return rc;
}
// handleConnection: worker task
// This was created in begin() to handle the queue entries
void ModbusClientRTU::handleConnection(ModbusClientRTU *instance) {
// initially clean the serial buffer
while (instance->MR_serial->available()) instance->MR_serial->read();
delay(100);
// Loop forever - or until task is killed
while (1) {
// Do we have a reuest in queue?
if (!instance->requests.empty()) {
// Yes. pull it.
RequestEntry request = instance->requests.front();
LOG_D("Pulled request from queue\n");
// Send it via Serial
RTUutils::send(*(instance->MR_serial), instance->MR_lastMicros, instance->MR_interval, instance->MTRSrts, request.msg, instance->MR_useASCII);
LOG_D("Request sent.\n");
// HEXDUMP_V("Data", request.msg.data(), request.msg.size());
// For a broadcast, we will not wait for a response
if (request.msg.getServerID() != 0 || ((request.token & 0xFF000000) != 0xBC000000)) {
// This is a regular request, Get the response - if any
ModbusMessage response = RTUutils::receive(
'C',
*(instance->MR_serial),
instance->MR_timeoutValue,
instance->MR_lastMicros,
instance->MR_interval,
instance->MR_useASCII,
instance->MR_skipLeadingZeroByte);
LOG_D("%s response (%d bytes) received.\n", response.size()>1 ? "Data" : "Error", response.size());
HEXDUMP_V("Data", response.data(), response.size());
// No error in receive()?
if (response.size() > 1) {
// No. Check message contents
// Does the serverID match the requested?
if (request.msg.getServerID() != response.getServerID()) {
// No. Return error response
response.setError(request.msg.getServerID(), request.msg.getFunctionCode(), SERVER_ID_MISMATCH);
// ServerID ok, but does the FC match as well?
} else if (request.msg.getFunctionCode() != (response.getFunctionCode() & 0x7F)) {
// No. Return error response
response.setError(request.msg.getServerID(), request.msg.getFunctionCode(), FC_MISMATCH);
}
} else {
// No, we got an error code from receive()
// Return it as error response
response.setError(request.msg.getServerID(), request.msg.getFunctionCode(), static_cast<Error>(response[0]));
}
LOG_D("Response generated.\n");
HEXDUMP_V("Response packet", response.data(), response.size());
// If we got an error, count it
if (response.getError() != SUCCESS) {
instance->errorCount++;
}
// Was it a synchronous request?
if (request.isSyncRequest) {
// Yes. Put it into the response map
{
LOCK_GUARD(sL, instance->syncRespM);
instance->syncResponse[request.token] = response;
}
// No, an async request. Do we have an onResponse handler?
} else if (instance->onResponse) {
// Yes. Call it
instance->onResponse(response, request.token);
} else {
// No, but we may have onData or onError handlers
// Did we get a normal response?
if (response.getError()==SUCCESS) {
// Yes. Do we have an onData handler registered?
if (instance->onData) {
// Yes. call it
instance->onData(response, request.token);
}
} else {
// No, something went wrong. All we have is an error
// Do we have an onError handler?
if (instance->onError) {
// Yes. Forward the error code to it
instance->onError(response.getError(), request.token);
}
}
}
}
// Clean-up time.
{
// Safely lock the queue
LOCK_GUARD(lockGuard, instance->qLock);
// Remove the front queue entry
instance->requests.pop();
}
} else {
delay(1);
}
}
}
#endif // HAS_FREERTOS

View file

@ -1,111 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#ifndef _MODBUS_CLIENT_RTU_H
#define _MODBUS_CLIENT_RTU_H
#include "options.h"
#if HAS_FREERTOS
#include "ModbusClient.h"
#include "Stream.h"
#include "RTUutils.h"
#include <queue>
#include <vector>
using std::queue;
#define DEFAULTTIMEOUT 2000
class ModbusClientRTU : public ModbusClient {
public:
// Constructor takes an optional DE/RE pin and queue limit
explicit ModbusClientRTU(int8_t rtsPin = -1, uint16_t queueLimit = 100);
// Alternative Constructor takes an RTS line toggle callback
explicit ModbusClientRTU(RTScallback rts, uint16_t queueLimit = 100);
// Destructor: clean up queue, task etc.
~ModbusClientRTU();
// begin: start worker task
void begin(Stream& serial, uint32_t baudrate, int coreID = -1, uint32_t userInterval = 0);
// Special variant for HardwareSerial
void begin(HardwareSerial& serial, int coreID = -1, uint32_t userInterval = 0);
// end: stop the worker
void end();
// Set default timeout value for interface
void setTimeout(uint32_t TOV);
// Toggle protocol to ModbusASCII
void useModbusASCII(unsigned long timeout = 1000);
// Toggle protocol to ModbusRTU
void useModbusRTU();
// Inquire protocol mode
bool isModbusASCII();
// Toggle skipping of leading 0x00 byte
void skipLeading0x00(bool onOff = true);
// Return number of unprocessed requests in queue
uint32_t pendingRequests();
// Remove all pending request from queue
void clearQueue();
// addBroadcastMessage: create a fire-and-forget message to all servers on the RTU bus
Error addBroadcastMessage(const uint8_t *data, uint8_t len);
protected:
struct RequestEntry {
uint32_t token;
ModbusMessage msg;
bool isSyncRequest;
RequestEntry(uint32_t t, ModbusMessage m, bool syncReq = false) :
token(t),
msg(m),
isSyncRequest(syncReq) {}
};
// Base addRequest and syncRequest must be present
Error addRequestM(ModbusMessage msg, uint32_t token);
ModbusMessage syncRequestM(ModbusMessage msg, uint32_t token);
// addToQueue: send freshly created request to queue
bool addToQueue(uint32_t token, ModbusMessage msg, bool syncReq = false);
// handleConnection: worker task method
static void handleConnection(ModbusClientRTU *instance);
// receive: get response via Serial
ModbusMessage receive(const ModbusMessage request);
// start background task
void doBegin(uint32_t baudRate, int coreID, uint32_t userInterval);
void isInstance() { return; } // make class instantiable
queue<RequestEntry> requests; // Queue to hold requests to be processed
#if USE_MUTEX
mutex qLock; // Mutex to protect queue
#endif
Stream *MR_serial; // Ptr to the serial interface used
unsigned long MR_lastMicros; // Microseconds since last bus activity
uint32_t MR_interval; // Modbus RTU bus quiet time
int8_t MR_rtsPin; // GPIO pin to toggle RS485 DE/RE line. -1 if none.
RTScallback MTRSrts; // RTS line callback function
uint16_t MR_qLimit; // Maximum number of requests to hold in the queue
uint32_t MR_timeoutValue; // Interface default timeout
bool MR_useASCII; // true=ModbusASCII, false=ModbusRTU
bool MR_skipLeadingZeroByte; // true=skip the first byte if it is 0x00, false=accept all bytes
};
#endif // HAS_FREERTOS
#endif // INCLUDE GUARD

View file

@ -1,16 +0,0 @@
// =================================================================================================
// eModbus: Copyright 2020 by Michael Harwerth, Bert Melis and the contributors to eModbus
// MIT license - see license.md for details
// =================================================================================================
#ifndef _MODBUS_SERVER_WIFI_H
#define _MODBUS_SERVER_WIFI_H
#include "options.h"
#include <WiFi.h>
#undef SERVER_END
#define SERVER_END server.end();
#include "ModbusServerTCPtemp.h"
using ModbusServerWiFi = ModbusServerTCP<WiFiServer, WiFiClient>;
#endif

View file

@ -1,2 +0,0 @@
.DS_Store

View file

@ -1,34 +0,0 @@
sudo: false
language: python
os:
- linux
git:
depth: false
stages:
- build
jobs:
include:
- name: "Arduino Build"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh
- name: "PlatformIO Build"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh 1 1
notifications:
email:
on_success: change
on_failure: change
webhooks:
urls:
- https://webhooks.gitter.im/e/60e65d0c78ea0a920347
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: false # default: false

View file

@ -1,15 +0,0 @@
set(COMPONENT_SRCDIRS
"src"
)
set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
)
register_component()
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View file

@ -1,30 +0,0 @@
menu "AsyncTCP Configuration"
choice ASYNC_TCP_RUNNING_CORE
bool "Core on which AsyncTCP's thread is running"
default ASYNC_TCP_RUN_CORE1
help
Select on which core AsyncTCP is running
config ASYNC_TCP_RUN_CORE0
bool "CORE 0"
config ASYNC_TCP_RUN_CORE1
bool "CORE 1"
config ASYNC_TCP_RUN_NO_AFFINITY
bool "BOTH"
endchoice
config ASYNC_TCP_RUNNING_CORE
int
default 0 if ASYNC_TCP_RUN_CORE0
default 1 if ASYNC_TCP_RUN_CORE1
default -1 if ASYNC_TCP_RUN_NO_AFFINITY
config ASYNC_TCP_USE_WDT
bool "Enable WDT for the AsyncTCP task"
default "y"
help
Enable WDT for the AsyncTCP task, so it will trigger if a handler is locking the thread.
endmenu

View file

@ -1,3 +0,0 @@
COMPONENT_ADD_INCLUDEDIRS := src
COMPONENT_SRCDIRS := src
CXXFLAGS += -fno-rtti

View file

@ -1,2 +0,0 @@
.vscode
.DS_Store

View file

@ -1,46 +0,0 @@
sudo: false
language: python
os:
- linux
git:
depth: false
stages:
- build
jobs:
include:
- name: "Build Arduino ESP32"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32
- name: "Build Arduino ESP8266"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266
- name: "Build Platformio ESP32"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp32 1 1
- name: "Build Platformio ESP8266"
if: tag IS blank AND (type = pull_request OR (type = push AND branch = master))
stage: build
script: bash $TRAVIS_BUILD_DIR/.github/scripts/on-push.sh esp8266 1 1
notifications:
email:
on_success: change
on_failure: change
webhooks:
urls:
- https://webhooks.gitter.im/e/60e65d0c78ea0a920347
on_success: change # options: [always|never|change] default: always
on_failure: always # options: [always|never|change] default: always
on_start: never # options: [always|never|change] default: always

View file

@ -1,17 +0,0 @@
set(COMPONENT_SRCDIRS
"src"
)
set(COMPONENT_ADD_INCLUDEDIRS
"src"
)
set(COMPONENT_REQUIRES
"arduino-esp32"
"AsyncTCP"
)
register_component()
target_compile_definitions(${COMPONENT_TARGET} PUBLIC -DESP32)
target_compile_options(${COMPONENT_TARGET} PRIVATE -fno-rtti)

View file

@ -1 +0,0 @@
theme: jekyll-theme-cayman

View file

@ -1,3 +0,0 @@
COMPONENT_ADD_INCLUDEDIRS := src
COMPONENT_SRCDIRS := src
CXXFLAGS += -fno-rtti

View file

@ -1,3 +0,0 @@
JsonArray KEYWORD1
add KEYWORD2
createArray KEYWORD3

View file

@ -50,14 +50,8 @@
* Description:
* The period of whatever LED mode is active. If CLASSIC, then a ramp up and ramp down will finish in
* LED_PERIOD_MS milliseconds
*
* Parameter: LED_EXECUTION_FREQUENCY
* Description:
* Defines how often the LED handling will run, basically the FPS. The animation will honor its overall
* frequency but the animation will be choppier
*/
#define LED_MODE_DEFAULT FLOW
#define LED_PERIOD_MS 3000
#define LED_EXECUTION_FREQUENCY 50
#endif