mirror of
https://github.com/dalathegreat/Battery-Emulator.git
synced 2025-10-04 18:29:48 +02:00
Merge branch 'main' into feature/event-log
This commit is contained in:
commit
25cdc7a98a
17 changed files with 400 additions and 231 deletions
6
.github/workflows/compile-all-batteries.yml
vendored
6
.github/workflows/compile-all-batteries.yml
vendored
|
@ -2,7 +2,11 @@
|
|||
name: Compile All Batteries
|
||||
|
||||
# Here we tell GitHub when to run the workflow.
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
# The workflow is run when a commit is pushed or for a
|
||||
# Pull Request.
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# This is the list of jobs that will be run concurrently.
|
||||
# Since we use a build matrix, the actual number of jobs
|
||||
|
|
6
.github/workflows/compile-all-inverters.yml
vendored
6
.github/workflows/compile-all-inverters.yml
vendored
|
@ -2,7 +2,11 @@
|
|||
name: Compile All Inverters
|
||||
|
||||
# Here we tell GitHub when to run the workflow.
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
# The workflow is run when a commit is pushed or for a
|
||||
# Pull Request.
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
# This is the list of jobs that will be run concurrently.
|
||||
# Since we use a build matrix, the actual number of jobs
|
||||
|
|
4
.github/workflows/run-pre-commit.yml
vendored
4
.github/workflows/run-pre-commit.yml
vendored
|
@ -2,7 +2,9 @@
|
|||
|
||||
name: Run pre-commit
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
- push
|
||||
- pull_request
|
||||
|
||||
jobs:
|
||||
pre-commit:
|
||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -3,9 +3,3 @@
|
|||
|
||||
# Ignore any files in the build folder
|
||||
Software/build/
|
||||
|
||||
# Ignore CMake build folder
|
||||
build/
|
||||
|
||||
# Ignore unit tests
|
||||
*.exe
|
|
@ -22,7 +22,7 @@
|
|||
#endif
|
||||
|
||||
Preferences settings; // Store user settings
|
||||
|
||||
const char* version_number = "5.2.0"; // The current software version, shown on webserver
|
||||
// Interval settings
|
||||
int intervalUpdateValues = 4800; // Interval at which to update inverter values / Modbus registers
|
||||
const int interval10 = 10; // Interval for 10ms tasks
|
||||
|
@ -130,7 +130,9 @@ void setup() {
|
|||
init_webserver();
|
||||
#endif
|
||||
|
||||
#ifdef EVENTLOGGING
|
||||
init_events();
|
||||
#endif
|
||||
|
||||
init_CAN();
|
||||
|
||||
|
@ -156,8 +158,8 @@ void loop() {
|
|||
|
||||
#ifdef WEBSERVER
|
||||
// Over-the-air updates by ElegantOTA
|
||||
wifi_monitor();
|
||||
ElegantOTA.loop();
|
||||
WiFi_monitor_loop();
|
||||
#ifdef MQTT
|
||||
mqtt_loop();
|
||||
#endif
|
||||
|
@ -186,8 +188,10 @@ void loop() {
|
|||
{
|
||||
previousMillisUpdateVal = millis();
|
||||
update_values(); // Update values heading towards inverter. Prepare for sending on CAN, or write directly to Modbus.
|
||||
if (DUMMY_EVENT_ENABLED) {
|
||||
set_event(EVENT_DUMMY, (uint8_t)millis());
|
||||
}
|
||||
}
|
||||
|
||||
// Output
|
||||
send_can(); // Send CAN messages
|
||||
|
|
|
@ -36,5 +36,5 @@ const char* ssid = "REPLACE_WITH_YOUR_SSID"; // Maximum of 63 character
|
|||
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
|
||||
const char* versionNumber = "5.0.1"; // The current software version, shown on webserver
|
||||
const uint8_t wifi_channel = 0; // set to 0 for automatic channel selection
|
||||
#endif
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
//#define CHADEMO_BATTERY
|
||||
//#define IMIEV_CZERO_ION_BATTERY
|
||||
//#define KIA_HYUNDAI_64_BATTERY
|
||||
#define NISSAN_LEAF_BATTERY
|
||||
//#define NISSAN_LEAF_BATTERY
|
||||
//#define RENAULT_KANGOO_BATTERY
|
||||
//#define RENAULT_ZOE_BATTERY
|
||||
//#define SANTA_FE_PHEV_BATTERY
|
||||
|
@ -21,7 +21,7 @@
|
|||
|
||||
/* Select inverter communication protocol. See Wiki for which to use with your inverter: https://github.com/dalathegreat/BYD-Battery-Emulator-For-Gen24/wiki */
|
||||
//#define BYD_CAN //Enable this line to emulate a "BYD Battery-Box Premium HVS" over CAN Bus
|
||||
#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
|
||||
//#define BYD_MODBUS //Enable this line to emulate a "BYD 11kWh HVM battery" over Modbus RTU
|
||||
//#define LUNA2000_MODBUS //Enable this line to emulate a "Luna2000 battery" over Modbus RTU
|
||||
//#define PYLON_CAN //Enable this line to emulate a "Pylontech battery" over CAN bus
|
||||
//#define SMA_CAN //Enable this line to emulate a "BYD Battery-Box H 8.9kWh, 7 mod" over CAN bus
|
||||
|
@ -46,6 +46,10 @@
|
|||
#define MQTT_SERVER "192.168.xxx.yyy"
|
||||
#define MQTT_PORT 1883
|
||||
|
||||
/* Event options*/
|
||||
#define EVENTLOGGING //Enable this line to log events to the event log
|
||||
#define DUMMY_EVENT_ENABLED false //Enable this line to have a dummy event that gets logged to test the interface
|
||||
|
||||
/* Select charger used (Optional) */
|
||||
//#define CHEVYVOLT_CHARGER //Enable this line to control a Chevrolet Volt charger connected to battery - for example, when generator charging or using an inverter without a charging function.
|
||||
//#define NISSANLEAF_CHARGER //Enable this line to control a Nissan LEAF PDM connected to battery - for example, when generator charging
|
||||
|
@ -71,4 +75,6 @@ extern volatile float CHARGER_END_A;
|
|||
extern bool charger_HV_enabled;
|
||||
extern bool charger_aux12V_enabled;
|
||||
|
||||
extern const uint8_t wifi_channel;
|
||||
|
||||
#endif
|
||||
|
|
|
@ -187,9 +187,9 @@ void update_values_kiaHyundai_64_battery() { //This function maps all the value
|
|||
|
||||
stat_batt_power = convertToUnsignedInt16(powerWatt); //Power in watts, Negative = charging batt
|
||||
|
||||
temperature_min = convertToUnsignedInt16(temperatureMin * 10); //Increase decimals, 17C -> 17.0C
|
||||
temperature_min = convertToUnsignedInt16((int8_t)temperatureMin * 10); //Increase decimals, 17C -> 17.0C
|
||||
|
||||
temperature_max = convertToUnsignedInt16(temperatureMax * 10); //Increase decimals, 18C -> 18.0C
|
||||
temperature_max = convertToUnsignedInt16((int8_t)temperatureMax * 10); //Increase decimals, 18C -> 18.0C
|
||||
|
||||
cell_max_voltage = CellVoltMax_mV;
|
||||
|
||||
|
|
|
@ -260,7 +260,13 @@ void update_values_tesla_model_3_battery() { //This function maps all the value
|
|||
}
|
||||
|
||||
//Check if BMS is in need of recalibration
|
||||
if (nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) {
|
||||
if (nominal_full_pack_energy > 1 && nominal_full_pack_energy < REASONABLE_ENERGYAMOUNT) {
|
||||
Serial.println("Warning: kWh remaining " + String(nominal_full_pack_energy) +
|
||||
" reported by battery not plausible. Battery needs cycling.");
|
||||
set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy);
|
||||
LEDcolor = YELLOW;
|
||||
} else if (nominal_full_pack_energy <= 1) {
|
||||
Serial.println("Info: kWh remaining battery is not reporting kWh remaining.");
|
||||
set_event(EVENT_KWH_PLAUSIBILITY_ERROR, nominal_full_pack_energy);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,22 +26,6 @@ static void publish_values(void) {
|
|||
publish_cell_voltages();
|
||||
}
|
||||
|
||||
static void publish_common_info(void) {
|
||||
snprintf(mqtt_msg, sizeof(mqtt_msg),
|
||||
"{\n"
|
||||
" \"SOC\": %.3f,\n"
|
||||
" \"StateOfHealth\": %.3f,\n"
|
||||
" \"temperature_min\": %.3f,\n"
|
||||
" \"temperature_max\": %.3f,\n"
|
||||
" \"cell_max_voltage\": %d,\n"
|
||||
" \"cell_min_voltage\": %d\n"
|
||||
"}\n",
|
||||
((float)SOC) / 100.0, ((float)StateOfHealth) / 100.0, ((float)((int16_t)temperature_min)) / 10.0,
|
||||
((float)((int16_t)temperature_max)) / 10.0, cell_max_voltage, cell_min_voltage);
|
||||
bool result = client.publish("battery/info", mqtt_msg, true);
|
||||
//Serial.println(mqtt_msg); // Uncomment to print the payload on serial
|
||||
}
|
||||
|
||||
static void publish_cell_voltages(void) {
|
||||
static bool mqtt_first_transmission = true;
|
||||
|
||||
|
@ -76,17 +60,17 @@ static void publish_cell_voltages(void) {
|
|||
"\"object_id\": \"sensor_battery_voltage_cell%d\","
|
||||
"\"origin\": {"
|
||||
"\"name\": \"BatteryEmulator\","
|
||||
"\"sw\": \"4.4.0-mqtt\","
|
||||
"\"sw\": \"%s-mqtt\","
|
||||
"\"url\": \"https://github.com/dalathegreat/Battery-Emulator\""
|
||||
"},"
|
||||
"\"state_class\": \"measurement\","
|
||||
"\"name\": \"Battery Cell Voltage %d\","
|
||||
"\"state_topic\": \"battery/spec_data\","
|
||||
"\"state_topic\": \"battery-emulator/spec_data\","
|
||||
"\"unique_id\": \"battery-emulator_battery_voltage_cell%d\","
|
||||
"\"unit_of_measurement\": \"V\","
|
||||
"\"value_template\": \"{{ value_json.cell_voltages[%d] }}\""
|
||||
"}",
|
||||
i + 1, i + 1, i + 1, i);
|
||||
i + 1, version_number, i + 1, i + 1, i);
|
||||
// End each discovery topic with cell number and '/config'
|
||||
String cell_topic = topic + String(i + 1) + "/config";
|
||||
mqtt_publish_retain(cell_topic.c_str());
|
||||
|
@ -99,7 +83,7 @@ static void publish_cell_voltages(void) {
|
|||
// is the string content
|
||||
|
||||
// If cell voltages haven't been populated...
|
||||
if (cellvoltages[0] == 0u) {
|
||||
if (cellvoltages[0] == 0u / 1000) { //cell voltage is in mV and homeassistant expects V
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -111,12 +95,101 @@ static void publish_cell_voltages(void) {
|
|||
snprintf(mqtt_msg + msg_length, sizeof(mqtt_msg) - msg_length, "]\n}\n");
|
||||
|
||||
// Publish and print error if not OK
|
||||
if (mqtt_publish_retain("battery/spec_data") == false) {
|
||||
if (mqtt_publish_retain("battery-emulator/spec_data") == false) {
|
||||
Serial.println("Cell voltage MQTT msg could not be sent");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct SensorConfig {
|
||||
const char* object_id;
|
||||
const char* topic;
|
||||
const char* name;
|
||||
const char* value_template;
|
||||
const char* unit;
|
||||
const char* device_class;
|
||||
};
|
||||
|
||||
SensorConfig sensorConfigs[] = {
|
||||
{"SOC", "homeassistant/sensor/battery-emulator/SOC/config", "Battery Emulator SOC", "{{ value_json.SOC }}", "%",
|
||||
"battery"},
|
||||
{"state_of_health", "homeassistant/sensor/battery-emulator/state_of_health/config",
|
||||
"Battery Emulator State Of Health", "{{ value_json.state_of_health }}", "%", "battery"},
|
||||
{"temperature_min", "homeassistant/sensor/battery-emulator/temperature_min/config",
|
||||
"Battery Emulator Temperature Min", "{{ value_json.temperature_min }}", "°C", "temperature"},
|
||||
{"temperature_max", "homeassistant/sensor/battery-emulator/temperature_max/config",
|
||||
"Battery Emulator Temperature Max", "{{ value_json.temperature_max }}", "°C", "temperature"},
|
||||
{"stat_batt_power", "homeassistant/sensor/battery-emulator/stat_batt_power/config",
|
||||
"Battery Emulator Stat Batt Power", "{{ value_json.stat_batt_power }}", "W", "power"},
|
||||
{"battery_current", "homeassistant/sensor/battery-emulator/battery_current/config",
|
||||
"Battery Emulator Battery Current", "{{ value_json.battery_current }}", "A", "current"},
|
||||
{"cell_max_voltage", "homeassistant/sensor/battery-emulator/cell_max_voltage/config",
|
||||
"Battery Emulator Cell Max Voltage", "{{ value_json.cell_max_voltage }}", "V", "voltage"},
|
||||
{"cell_min_voltage", "homeassistant/sensor/battery-emulator/cell_min_voltage/config",
|
||||
"Battery Emulator Cell Min Voltage", "{{ value_json.cell_min_voltage }}", "V", "voltage"},
|
||||
{"battery_voltage", "homeassistant/sensor/battery-emulator/battery_voltage/config",
|
||||
"Battery Emulator Battery Voltage", "{{ value_json.battery_voltage }}", "V", "voltage"},
|
||||
};
|
||||
|
||||
static void publish_common_info(void) {
|
||||
static bool mqtt_first_transmission = true;
|
||||
static char* state_topic = "battery-emulator/info";
|
||||
if (mqtt_first_transmission == true) {
|
||||
mqtt_first_transmission = false;
|
||||
for (int i = 0; i < sizeof(sensorConfigs) / sizeof(sensorConfigs[0]); i++) {
|
||||
SensorConfig& config = sensorConfigs[i];
|
||||
snprintf(mqtt_msg, sizeof(mqtt_msg),
|
||||
"{"
|
||||
"\"name\": \"%s\","
|
||||
"\"state_topic\": \"%s\","
|
||||
"\"unique_id\": \"battery-emulator_%s\","
|
||||
"\"object_id\": \"sensor_battery_%s\","
|
||||
"\"device\": {"
|
||||
"\"identifiers\": ["
|
||||
"\"battery-emulator\""
|
||||
"],"
|
||||
"\"manufacturer\": \"DalaTech\","
|
||||
"\"model\": \"BatteryEmulator\","
|
||||
"\"name\": \"BatteryEmulator\""
|
||||
"},"
|
||||
"\"origin\": {"
|
||||
"\"name\": \"BatteryEmulator\","
|
||||
"\"sw\": \"%s-mqtt\","
|
||||
"\"url\": \"https://github.com/dalathegreat/Battery-Emulator\""
|
||||
"},"
|
||||
"\"value_template\": \"%s\","
|
||||
"\"unit_of_measurement\": \"%s\","
|
||||
"\"device_class\": \"%s\","
|
||||
"\"enabled_by_default\": true,"
|
||||
"\"state_class\": \"measurement\""
|
||||
"}",
|
||||
config.name, state_topic, config.object_id, config.object_id, version_number, config.value_template,
|
||||
config.unit, config.device_class);
|
||||
mqtt_publish_retain(config.topic);
|
||||
}
|
||||
} else {
|
||||
snprintf(mqtt_msg, sizeof(mqtt_msg),
|
||||
"{\n"
|
||||
" \"SOC\": %.3f,\n"
|
||||
" \"state_of_health\": %.3f,\n"
|
||||
" \"temperature_min\": %.3f,\n"
|
||||
" \"temperature_max\": %.3f,\n"
|
||||
" \"stat_batt_power\": %.3f,\n"
|
||||
" \"battery_current\": %.3f,\n"
|
||||
" \"cell_max_voltage\": %d,\n"
|
||||
" \"cell_min_voltage\": %d,\n"
|
||||
" \"battery_voltage\": %d\n"
|
||||
"}\n",
|
||||
((float)SOC) / 100.0, ((float)StateOfHealth) / 100.0, ((float)((int16_t)temperature_min)) / 10.0,
|
||||
((float)((int16_t)temperature_max)) / 10.0, ((float)((int16_t)stat_batt_power)),
|
||||
((float)((int16_t)battery_current)) / 10.0, cell_max_voltage / 1000, cell_min_voltage / 1000,
|
||||
battery_voltage / 10.0);
|
||||
bool result = client.publish(state_topic, mqtt_msg, true);
|
||||
}
|
||||
|
||||
//Serial.println(mqtt_msg); // Uncomment to print the payload on serial
|
||||
}
|
||||
|
||||
/* This is called whenever a subscribed topic changes (hopefully) */
|
||||
static void callback(char* topic, byte* payload, unsigned int length) {
|
||||
Serial.print("Message arrived [");
|
||||
|
@ -132,9 +205,8 @@ static void callback(char* topic, byte* payload, unsigned int length) {
|
|||
static void reconnect() {
|
||||
// attempt one reconnection
|
||||
Serial.print("Attempting MQTT connection... ");
|
||||
// Create a random client ID
|
||||
String clientId = "LilyGoClient-";
|
||||
clientId += String(random(0xffff), HEX);
|
||||
const char* hostname = WiFi.getHostname();
|
||||
String clientId = "LilyGoClient-" + String(hostname);
|
||||
// Attempt to connect
|
||||
if (client.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
|
||||
Serial.println("connected");
|
||||
|
|
|
@ -39,6 +39,8 @@
|
|||
|
||||
#define MQTT_MSG_BUFFER_SIZE (1024)
|
||||
|
||||
extern const char* version_number; // The current software version, used for mqtt
|
||||
|
||||
extern uint16_t SOC;
|
||||
extern uint16_t StateOfHealth;
|
||||
extern uint16_t temperature_min; //C+1, Goes thru convert2unsignedint16 function (15.0C = 150, -15.0C = 65385)
|
||||
|
@ -47,6 +49,8 @@ extern uint16_t cell_max_voltage; //mV, 0-4350
|
|||
extern uint16_t cell_min_voltage; //mV, 0-4350
|
||||
extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
|
||||
extern uint8_t nof_cellvoltages; // Total number of cell voltages, set by each battery.
|
||||
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
|
||||
extern uint16_t battery_current; //A+1, Goes thru convert2unsignedint16 function (5.0A = 50, -5.0A = 65485)
|
||||
|
||||
extern const char* mqtt_user;
|
||||
extern const char* mqtt_password;
|
||||
|
|
|
@ -278,6 +278,62 @@ static void set_message(EVENTS_ENUM_TYPE event) {
|
|||
snprintf(events.message, sizeof(events.message), "The dummy event was set!"); // Don't change this event message!
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
const char* get_event_message(EVENTS_ENUM_TYPE event) {
|
||||
switch (event) {
|
||||
case EVENT_CAN_FAILURE:
|
||||
return "No CAN communication detected for 60s. Shutting down battery control.";
|
||||
case EVENT_CAN_WARNING:
|
||||
return "ERROR: High amount of corrupted CAN messages detected. Check CAN wire shielding!";
|
||||
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 "ERROR: SOC% reported by battery not plausible. Restart battery!";
|
||||
case EVENT_KWH_PLAUSIBILITY_ERROR:
|
||||
return "Warning: kWh remaining reported by battery not plausible. Battery needs cycling.";
|
||||
case EVENT_BATTERY_CHG_STOP_REQ:
|
||||
return "ERROR: 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!";
|
||||
case EVENT_BATTERY_CHG_DISCHG_STOP_REQ:
|
||||
return "ERROR: Battery raised caution indicator AND requested charge/discharge stop. Inspect battery status!";
|
||||
case EVENT_LOW_SOH:
|
||||
return "ERROR: 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 connectors are seated. Battery will be "
|
||||
"disabled!";
|
||||
case EVENT_INTERNAL_OPEN_FAULT:
|
||||
return "ERROR: High voltage cable removed while battery running. Opening contactors!";
|
||||
case EVENT_CELL_UNDER_VOLTAGE:
|
||||
return "ERROR: 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!";
|
||||
case EVENT_CELL_DEVIATION_HIGH:
|
||||
return "ERROR: HIGH CELL DEVIATION!!! Inspect battery!";
|
||||
case EVENT_UNKNOWN_EVENT_SET:
|
||||
return "An unknown event was set! Review your code!";
|
||||
case EVENT_DUMMY:
|
||||
return "The dummy event was set!"; // Don't change this event message!
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const char* get_event_enum_string(EVENTS_ENUM_TYPE event) {
|
||||
const char* fullString = EVENTS_ENUM_TYPE_STRING[event];
|
||||
if (strncmp(fullString, "EVENT_", 6) == 0) {
|
||||
return fullString + 6; // Skip the first 6 characters
|
||||
}
|
||||
return fullString;
|
||||
}
|
||||
|
||||
static void set_event_message(EVENTS_ENUM_TYPE event) {
|
||||
const char* message = get_event_message(event);
|
||||
snprintf(event_message, sizeof(event_message), "%s", message);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,32 @@
|
|||
#ifndef __EVENTS_H__
|
||||
#define __EVENTS_H__
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef UNIT_TEST
|
||||
#include <Arduino.h>
|
||||
extern unsigned long previous_millis;
|
||||
extern uint32_t time_seconds;
|
||||
#endif
|
||||
|
||||
#include <stdint.h>
|
||||
#define EVENTS_ENUM_TYPE(XX) \
|
||||
XX(EVENT_CAN_FAILURE) \
|
||||
XX(EVENT_CAN_WARNING) \
|
||||
XX(EVENT_WATER_INGRESS) \
|
||||
XX(EVENT_12V_LOW) \
|
||||
XX(EVENT_SOC_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_KWH_PLAUSIBILITY_ERROR) \
|
||||
XX(EVENT_BATTERY_CHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_BATTERY_CHG_DISCHG_STOP_REQ) \
|
||||
XX(EVENT_LOW_SOH) \
|
||||
XX(EVENT_HVIL_FAILURE) \
|
||||
XX(EVENT_INTERNAL_OPEN_FAULT) \
|
||||
XX(EVENT_CELL_UNDER_VOLTAGE) \
|
||||
XX(EVENT_CELL_OVER_VOLTAGE) \
|
||||
XX(EVENT_CELL_DEVIATION_HIGH) \
|
||||
XX(EVENT_UNKNOWN_EVENT_SET) \
|
||||
XX(EVENT_DUMMY) \
|
||||
XX(EVENT_NOF_EVENTS)
|
||||
|
||||
typedef enum {
|
||||
EVENT_CAN_FAILURE = 0u, // RED event
|
||||
|
@ -28,6 +49,18 @@ typedef enum {
|
|||
EVENT_DUMMY, // RED event
|
||||
EVENT_NOF_EVENTS // RED event
|
||||
} EVENTS_ENUM_TYPE;
|
||||
#define GENERATE_ENUM(ENUM) ENUM,
|
||||
#define GENERATE_STRING(STRING) #STRING,
|
||||
|
||||
typedef enum { EVENTS_ENUM_TYPE(GENERATE_ENUM) } EVENTS_ENUM_TYPE;
|
||||
|
||||
static const char* EVENTS_ENUM_TYPE_STRING[] = {EVENTS_ENUM_TYPE(GENERATE_STRING)};
|
||||
|
||||
const char* get_event_enum_string(EVENTS_ENUM_TYPE event);
|
||||
|
||||
const char* get_event_message(EVENTS_ENUM_TYPE event);
|
||||
|
||||
const char* get_led_color_display_text(u_int8_t led_color);
|
||||
|
||||
void init_events(void);
|
||||
void set_event_latched(EVENTS_ENUM_TYPE event, uint8_t data);
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
g++ events_test.cpp ../../../test/test_lib.cpp -o events_test.exe -DUNIT_TEST -I.
|
|
@ -37,32 +37,31 @@ const char index_html[] PROGMEM = R"rawliteral(
|
|||
</html>
|
||||
)rawliteral";
|
||||
|
||||
// Wifi connect time declarations and definition
|
||||
const unsigned long MAX_WIFI_RECONNECT_BACKOFF_TIME = 60000; // Maximum backoff time of 1 minute
|
||||
const unsigned long DEFAULT_WIFI_RECONNECT_BACKOFF_TIME =
|
||||
1000; // Default wifi reconnect backoff time. Start with 1 second
|
||||
const unsigned long WIFI_CONNECT_TIMEOUT = 10000; // Timeout for WiFi connect in milliseconds
|
||||
const unsigned long WIFI_MONITOR_LOOP_TIME =
|
||||
1000; // Will check if WiFi is connected and try reconnect every x milliseconds
|
||||
unsigned long last_wifi_monitor_run = 0;
|
||||
unsigned long wifi_connect_start_time;
|
||||
unsigned long wifi_reconnect_backoff_time = DEFAULT_WIFI_RECONNECT_BACKOFF_TIME;
|
||||
enum WifiState {
|
||||
INIT, //before connecting first time
|
||||
RECONNECTING, //we've connected before, but lost connection
|
||||
CONNECTED //we are connected
|
||||
};
|
||||
|
||||
enum WiFiState { DISCONNECTED, CONNECTING, CONNECTED };
|
||||
WifiState wifi_state = INIT;
|
||||
|
||||
WiFiState wifi_state =
|
||||
DISCONNECTED; //the esp library has no specific state to indicate if its connecting (only WL_IDLE_STATUS) so we keep track of it here
|
||||
unsigned const long WIFI_MONITOR_INTERVAL_TIME = 15000;
|
||||
unsigned const long INIT_WIFI_CONNECT_TIMEOUT = 8000; // Timeout for initial WiFi connect in milliseconds
|
||||
unsigned const long DEFAULT_WIFI_RECONNECT_INTERVAL = 1000; // Default WiFi reconnect interval in ms
|
||||
unsigned const long MAX_WIFI_RETRY_INTERVAL = 30000; // Maximum wifi retry interval in ms
|
||||
unsigned long last_wifi_monitor_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
unsigned long wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
unsigned long last_wifi_attempt_time = millis(); //init millis so wifi monitor doesn't run immediately
|
||||
|
||||
void init_webserver() {
|
||||
// Configure WiFi
|
||||
if (AccessPointEnabled) {
|
||||
WiFi.mode(WIFI_AP_STA); // Simultaneous WiFi AP and Router connection
|
||||
init_WiFi_AP();
|
||||
init_WiFi_STA(ssid, password);
|
||||
} else {
|
||||
WiFi.mode(WIFI_STA); // Only Router connection
|
||||
init_WiFi_STA(ssid, password);
|
||||
}
|
||||
init_WiFi_STA(ssid, password, wifi_channel);
|
||||
|
||||
// Route for root / web page
|
||||
server.on("/", HTTP_GET,
|
||||
|
@ -77,6 +76,11 @@ void init_webserver() {
|
|||
request->send_P(200, "text/html", index_html, cellmonitor_processor);
|
||||
});
|
||||
|
||||
#ifdef EVENTLOGGING
|
||||
server.on("/events", HTTP_GET,
|
||||
[](AsyncWebServerRequest* request) { request->send_P(200, "text/html", index_html, events_processor); });
|
||||
#endif
|
||||
|
||||
// Route for editing Wh
|
||||
server.on("/updateBatterySize", HTTP_GET, [](AsyncWebServerRequest* request) {
|
||||
if (request->hasParam("value")) {
|
||||
|
@ -256,120 +260,77 @@ void init_webserver() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void print_wifi_status_message(wl_status_t status) {
|
||||
switch (status) {
|
||||
case WL_CONNECTED:
|
||||
Serial.println("Connected to WiFi network: " + String(ssid));
|
||||
Serial.println("IP address: " + WiFi.localIP().toString());
|
||||
Serial.println("Signal Strength: " + String(WiFi.RSSI()) + " dBm");
|
||||
break;
|
||||
case WL_CONNECT_FAILED:
|
||||
Serial.println("Failed to connect to WiFi network: " + String(ssid));
|
||||
break;
|
||||
case WL_CONNECTION_LOST:
|
||||
Serial.println("Connection to WiFi network: " + String(ssid) + " lost");
|
||||
break;
|
||||
case WL_DISCONNECTED:
|
||||
Serial.println("Disconnected from WiFi network: " + String(ssid));
|
||||
break;
|
||||
case WL_NO_SSID_AVAIL:
|
||||
Serial.println("Could not find network with SSID: " + String(ssid));
|
||||
break;
|
||||
case WL_IDLE_STATUS:
|
||||
Serial.println("WiFi is in idle status. This can indicate it is currently trying to connect.");
|
||||
break;
|
||||
case WL_SCAN_COMPLETED:
|
||||
Serial.println("WiFi scan completed");
|
||||
break;
|
||||
case WL_NO_SHIELD:
|
||||
Serial.println("No WiFi shield detected");
|
||||
break;
|
||||
default:
|
||||
Serial.println("Unknown WiFi status: " + String(status));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle WiFi reconnection. Use some timeouts and backoffs here to avoid flooding reconnection attempts/spamming the serial console
|
||||
void handle_WiFi_reconnection(unsigned long currentMillis, wl_status_t status) {
|
||||
if (wifi_state == CONNECTING && currentMillis - wifi_connect_start_time > WIFI_CONNECT_TIMEOUT) {
|
||||
// we are here if we were trying to connect to wifi, but it took too long (more than configured timeout)
|
||||
Serial.println("Failed to connect to WiFi network before timeout");
|
||||
print_wifi_status_message(status);
|
||||
WiFi.disconnect(); //disconnect to clear any previous settings
|
||||
wifi_state = DISCONNECTED;
|
||||
wifi_connect_start_time = currentMillis; //reset the start time to now so backoff is respected on next try
|
||||
// We use a backoff time before trying to connect again. Increase backoff time, up to a maximum
|
||||
wifi_reconnect_backoff_time = min(wifi_reconnect_backoff_time * 2, MAX_WIFI_RECONNECT_BACKOFF_TIME);
|
||||
Serial.println("Will try again in " + String(wifi_reconnect_backoff_time / 1000) + " seconds.");
|
||||
} else if (wifi_state != CONNECTING && currentMillis - wifi_connect_start_time > wifi_reconnect_backoff_time) {
|
||||
// we are here if the connection failed for some reason and the backoff time has now passed
|
||||
print_wifi_status_message(status);
|
||||
init_WiFi_STA(ssid, password);
|
||||
}
|
||||
}
|
||||
|
||||
// Function to handle WiFi connection
|
||||
void WiFi_monitor_loop() {
|
||||
unsigned long currentMillis = millis();
|
||||
if (currentMillis - last_wifi_monitor_run > WIFI_MONITOR_LOOP_TIME) {
|
||||
last_wifi_monitor_run = currentMillis;
|
||||
wl_status_t status = WiFi.status();
|
||||
switch (status) {
|
||||
case WL_CONNECTED:
|
||||
if (wifi_state != CONNECTED) { //we need to update our own wifi state to indicate we are connected
|
||||
wifi_reconnect_backoff_time =
|
||||
DEFAULT_WIFI_RECONNECT_BACKOFF_TIME; // Reset backoff time after maintaining connection
|
||||
wifi_state = CONNECTED;
|
||||
print_wifi_status_message(status);
|
||||
}
|
||||
break;
|
||||
case WL_CONNECT_FAILED:
|
||||
case WL_CONNECTION_LOST:
|
||||
case WL_DISCONNECTED:
|
||||
case WL_NO_SSID_AVAIL:
|
||||
handle_WiFi_reconnection(currentMillis, status);
|
||||
break;
|
||||
case WL_IDLE_STATUS: //this means the wifi is not ready to process any commands (it's probably trying to connect). do nothing
|
||||
|
||||
case WL_SCAN_COMPLETED: //this will only be set when scanning for networks. We don't do that yet
|
||||
case WL_NO_SHIELD: //should not happen, this means no wifi chip detected, so we can't do much
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Function to initialize WiFi in Station Mode (i.e. connect to another access point)
|
||||
void init_WiFi_STA(const char* ssid, const char* password) {
|
||||
Serial.println("Connecting to: " + String(ssid));
|
||||
wifi_state = CONNECTING;
|
||||
WiFi.begin(ssid, password);
|
||||
WiFi.setAutoReconnect(true);
|
||||
wifi_connect_start_time = millis();
|
||||
}
|
||||
|
||||
// Function to convert WiFiState enum to String
|
||||
String wifi_state_to_string(WiFiState state) {
|
||||
switch (state) {
|
||||
case DISCONNECTED:
|
||||
return "Disconnected";
|
||||
case CONNECTING:
|
||||
return "Connecting";
|
||||
case CONNECTED:
|
||||
return "Connected";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// Function to initialize WiFi in Access Point Mode
|
||||
void init_WiFi_AP() {
|
||||
Serial.println("Creating Access Point: " + String(ssidAP));
|
||||
Serial.println("With password: " + String(passwordAP));
|
||||
WiFi.softAP(ssidAP, passwordAP);
|
||||
IPAddress IP = WiFi.softAPIP();
|
||||
Serial.println("Access Point created.");
|
||||
Serial.println("IP address: " + IP.toString());
|
||||
Serial.print("IP address: ");
|
||||
Serial.println(IP);
|
||||
}
|
||||
|
||||
String getConnectResultString(wl_status_t status) {
|
||||
switch (status) {
|
||||
case WL_CONNECTED:
|
||||
return "Connected";
|
||||
case WL_NO_SHIELD:
|
||||
return "No shield";
|
||||
case WL_IDLE_STATUS:
|
||||
return "Idle status";
|
||||
case WL_NO_SSID_AVAIL:
|
||||
return "No SSID available";
|
||||
case WL_SCAN_COMPLETED:
|
||||
return "Scan completed";
|
||||
case WL_CONNECT_FAILED:
|
||||
return "Connect failed";
|
||||
case WL_CONNECTION_LOST:
|
||||
return "Connection lost";
|
||||
case WL_DISCONNECTED:
|
||||
return "Disconnected";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void wifi_monitor() {
|
||||
unsigned long currentMillis = millis();
|
||||
if (currentMillis - last_wifi_monitor_time > WIFI_MONITOR_INTERVAL_TIME) {
|
||||
last_wifi_monitor_time = currentMillis;
|
||||
wl_status_t status = WiFi.status();
|
||||
if (status != WL_CONNECTED && status != WL_IDLE_STATUS) {
|
||||
Serial.println(getConnectResultString(status));
|
||||
if (wifi_state == INIT) { //we haven't been connected yet, try the init logic
|
||||
init_WiFi_STA(ssid, password, wifi_channel);
|
||||
} else { //we were connected before, try the reconnect logic
|
||||
if (currentMillis - last_wifi_attempt_time > wifi_reconnect_interval) {
|
||||
last_wifi_attempt_time = currentMillis;
|
||||
Serial.println("WiFi not connected, trying to reconnect...");
|
||||
wifi_state = RECONNECTING;
|
||||
WiFi.reconnect();
|
||||
wifi_reconnect_interval = min(wifi_reconnect_interval * 2, MAX_WIFI_RETRY_INTERVAL);
|
||||
}
|
||||
}
|
||||
} else if (status == WL_CONNECTED && wifi_state != CONNECTED) {
|
||||
wifi_state = CONNECTED;
|
||||
wifi_reconnect_interval = DEFAULT_WIFI_RECONNECT_INTERVAL;
|
||||
// Print local IP address and start web server
|
||||
Serial.print("Connected to WiFi network: " + String(ssid));
|
||||
Serial.print(" IP address: " + WiFi.localIP().toString());
|
||||
Serial.print(" Signal Strength: " + String(WiFi.RSSI()) + " dBm");
|
||||
Serial.println(" Channel: " + String(WiFi.channel()));
|
||||
Serial.println(" Hostname: " + String(WiFi.getHostname()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void init_WiFi_STA(const char* ssid, const char* password, const uint8_t wifi_channel) {
|
||||
// Connect to Wi-Fi network with SSID and password
|
||||
Serial.print("Connecting to ");
|
||||
Serial.println(ssid);
|
||||
WiFi.begin(ssid, password, wifi_channel);
|
||||
WiFi.setAutoReconnect(true); // Enable auto reconnect
|
||||
wl_status_t result = static_cast<wl_status_t>(WiFi.waitForConnectResult(INIT_WIFI_CONNECT_TIMEOUT));
|
||||
}
|
||||
|
||||
// Function to initialize ElegantOTA
|
||||
|
@ -393,7 +354,7 @@ String processor(const String& var) {
|
|||
content += "<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>";
|
||||
|
||||
// Show version number
|
||||
content += "<h4>Software version: " + String(versionNumber) + "</h4>";
|
||||
content += "<h4>Software version: " + String(version_number) + "</h4>";
|
||||
|
||||
// Display LED color
|
||||
content += "<h4>LED color: ";
|
||||
|
@ -416,13 +377,15 @@ String processor(const String& var) {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
wl_status_t status = WiFi.status();
|
||||
// Display ssid of network connected to and, if connected to the WiFi, its own IP
|
||||
content += "<h4>SSID: " + String(ssid) + "</h4>";
|
||||
content += "<h4>Wifi status: " + wifi_state_to_string(wifi_state) + "</h4>";
|
||||
if (WiFi.status() == WL_CONNECTED) {
|
||||
content += "<h4>Wifi status: " + getConnectResultString(status) + "</h4>";
|
||||
if (status == WL_CONNECTED) {
|
||||
content += "<h4>IP: " + WiFi.localIP().toString() + "</h4>";
|
||||
// Get and display the signal strength (RSSI)
|
||||
content += "<h4>Signal Strength: " + String(WiFi.RSSI()) + " dBm</h4>";
|
||||
content += "<h4>Channel: " + String(WiFi.channel()) + "</h4>";
|
||||
}
|
||||
// Close the block
|
||||
content += "</div>";
|
||||
|
@ -660,11 +623,16 @@ String processor(const String& var) {
|
|||
content += " ";
|
||||
content += "<button onclick='goToCellmonitorPage()'>Cellmonitor</button>";
|
||||
content += " ";
|
||||
content += "<button onclick='goToEventsPage()'>Events</button>";
|
||||
content += " ";
|
||||
content += "<button onclick='promptToReboot()'>Reboot Emulator</button>";
|
||||
content += "<script>";
|
||||
content += "function goToUpdatePage() { window.location.href = '/update'; }";
|
||||
content += "function goToCellmonitorPage() { window.location.href = '/cellmonitor'; }";
|
||||
content += "function goToSettingsPage() { window.location.href = '/settings'; }";
|
||||
#ifdef EVENTLOGGING
|
||||
content += "function goToEventsPage() { window.location.href = '/events'; }";
|
||||
#endif
|
||||
content +=
|
||||
"function promptToReboot() { if (window.confirm('Are you sure you want to reboot the emulator? NOTE: If "
|
||||
"emulator is handling contactors, they will open during reboot!')) { "
|
||||
|
@ -976,6 +944,58 @@ String cellmonitor_processor(const String& var) {
|
|||
return String();
|
||||
}
|
||||
|
||||
#ifdef EVENTLOGGING
|
||||
const char EVENTS_HTML_START[] PROGMEM = R"=====(
|
||||
<style>
|
||||
body { background-color: black; color: white; }
|
||||
.event-log { display: flex; flex-direction: column; }
|
||||
.event { display: flex; flex-wrap: wrap; border: 1px solid white; padding: 10px; }
|
||||
.event > div { flex: 1; min-width: 100px; max-width: 90%; word-break: break-word; }
|
||||
</style>
|
||||
<div style='background-color: #303E47; padding: 10px; margin-bottom: 10px;border-radius: 50px'>
|
||||
<h4 style='color: white;'>Event log:</h4>
|
||||
<div class="event-log">
|
||||
<div class="event">
|
||||
<div>Event Type</div><div>LED Color</div><div>Last Event (seconds ago)</div><div>Count</div><div>Data</div><div>Message</div>
|
||||
</div>
|
||||
)=====";
|
||||
const char EVENTS_HTML_END[] PROGMEM = R"=====(
|
||||
</div></div>
|
||||
<button onclick='goToMainPage()'>Back to main page</button>
|
||||
<script>
|
||||
function goToMainPage() {
|
||||
window.location.href = '/';
|
||||
}
|
||||
</script>
|
||||
)=====";
|
||||
|
||||
String events_processor(const String& var) {
|
||||
if (var == "PLACEHOLDER") {
|
||||
String content = "";
|
||||
content.reserve(5000);
|
||||
// Page format
|
||||
content.concat(FPSTR(EVENTS_HTML_START));
|
||||
for (int i = 0; i < EVENT_NOF_EVENTS; i++) {
|
||||
Serial.println("Event: " + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) +
|
||||
" count: " + String(entries[i].occurences) + " seconds: " + String(entries[i].timestamp) +
|
||||
" data: " + String(entries[i].data));
|
||||
if (entries[i].occurences > 0) {
|
||||
content.concat("<div class='event'>");
|
||||
content.concat("<div>" + String(get_event_enum_string(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
|
||||
content.concat("<div>" + String(get_led_color_display_text(entries[i].led_color)) + "</div>");
|
||||
content.concat("<div>" + String((millis() / 1000) - entries[i].timestamp) + "</div>");
|
||||
content.concat("<div>" + String(entries[i].occurences) + "</div>");
|
||||
content.concat("<div>" + String(entries[i].data) + "</div>");
|
||||
content.concat("<div>" + String(get_event_message(static_cast<EVENTS_ENUM_TYPE>(i))) + "</div>");
|
||||
content.concat("</div>"); // End of event row
|
||||
}
|
||||
}
|
||||
content.concat(FPSTR(EVENTS_HTML_END));
|
||||
return content;
|
||||
}
|
||||
return String();
|
||||
}
|
||||
#endif
|
||||
void onOTAStart() {
|
||||
// Log when OTA has started
|
||||
ESP32Can.CANStop();
|
||||
|
|
|
@ -12,10 +12,12 @@
|
|||
#include "../../lib/me-no-dev-ESPAsyncWebServer/src/ESPAsyncWebServer.h"
|
||||
#include "../../lib/miwagner-ESP32-Arduino-CAN/ESP32CAN.h"
|
||||
#include "../config.h" // Needed for LED defines
|
||||
#include "../utils/events.h"
|
||||
#ifdef MQTT
|
||||
#include "../mqtt/mqtt.h"
|
||||
#endif
|
||||
|
||||
extern const char* version_number; // The current software version, shown on webserver
|
||||
extern uint16_t SOC; //SOC%, 0-100.00 (0-10000)
|
||||
extern uint16_t StateOfHealth; //SOH%, 0-100.00 (0-10000)
|
||||
extern uint16_t battery_voltage; //V+1, 0-500.0 (0-5000)
|
||||
|
@ -35,12 +37,13 @@ extern uint16_t cellvoltages[120]; //mV 0-4350 per cell
|
|||
extern uint8_t LEDcolor; //Enum, 0-10
|
||||
extern bool batteryAllowsContactorClosing; //Bool, 1=true, 0=false
|
||||
extern bool inverterAllowsContactorClosing; //Bool, 1=true, 0=false
|
||||
extern EVENTS_STRUCT_TYPE entries[EVENT_NOF_EVENTS];
|
||||
|
||||
extern const char* ssid;
|
||||
extern const char* password;
|
||||
extern const uint8_t wifi_channel;
|
||||
extern const char* ssidAP;
|
||||
extern const char* passwordAP;
|
||||
extern const char* versionNumber;
|
||||
|
||||
// Common charger parameters
|
||||
extern float charger_stat_HVcur;
|
||||
|
@ -62,6 +65,15 @@ extern uint16_t OBC_Charge_Power;
|
|||
*/
|
||||
void init_webserver();
|
||||
|
||||
/**
|
||||
* @brief Monitoring loop for WiFi. Will attempt to reconnect to access point if the connection goes down.
|
||||
*
|
||||
* @param[in] void
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void wifi_monitor();
|
||||
|
||||
/**
|
||||
* @brief Initialization function that creates a WiFi Access Point.
|
||||
*
|
||||
|
@ -79,16 +91,7 @@ void init_WiFi_AP();
|
|||
*
|
||||
* @return void
|
||||
*/
|
||||
void init_WiFi_STA(const char* ssid, const char* password);
|
||||
|
||||
/**
|
||||
* @brief Monitoring loop for WiFi. Will attempt to reconnect to access point if the connection goes down.
|
||||
*
|
||||
* @param[in] void
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
void WiFi_monitor_loop();
|
||||
void init_WiFi_STA(const char* ssid, const char* password, const uint8_t channel);
|
||||
|
||||
// /**
|
||||
// * @brief Function to handle WiFi reconnection.
|
||||
|
@ -135,6 +138,15 @@ String settings_processor(const String& var);
|
|||
*/
|
||||
String cellmonitor_processor(const String& var);
|
||||
|
||||
/**
|
||||
* @brief Replaces placeholder with content section in web page
|
||||
*
|
||||
* @param[in] var
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
String events_processor(const String& var);
|
||||
|
||||
/**
|
||||
* @brief Executes on OTA start
|
||||
*
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
// The test library must be included first!
|
||||
#include "../test_lib.h"
|
||||
|
||||
#include "timer.cpp"
|
||||
|
||||
/* Helper functions */
|
||||
|
||||
/* Test functions */
|
||||
|
||||
TEST(timer_test) {
|
||||
unsigned long test_interval = 10;
|
||||
bool result;
|
||||
|
||||
// Create a timer, assert that it hasn't elapsed immediately
|
||||
testlib_millis = 30;
|
||||
MyTimer timer(test_interval);
|
||||
result = timer.elapsed();
|
||||
ASSERT_EQ(result, false);
|
||||
|
||||
// Test interval - 1, shouldn't have elapsed
|
||||
testlib_millis += test_interval - 1;
|
||||
result = timer.elapsed();
|
||||
ASSERT_EQ(result, false);
|
||||
|
||||
// Add 1, so now it should have elapsed
|
||||
testlib_millis += 1;
|
||||
result = timer.elapsed();
|
||||
ASSERT_EQ(result, true);
|
||||
|
||||
// The timer should have reset when it elapsed
|
||||
result = timer.elapsed();
|
||||
ASSERT_EQ(result, false);
|
||||
|
||||
// Test close to the next interval
|
||||
testlib_millis += test_interval - 1;
|
||||
result = timer.elapsed();
|
||||
ASSERT_EQ(result, false);
|
||||
|
||||
// Add 1, ensure that the timer elapses but only once
|
||||
testlib_millis += 1;
|
||||
result = timer.elapsed();
|
||||
ASSERT_EQ(result, true);
|
||||
result = timer.elapsed();
|
||||
ASSERT_EQ(result, false);
|
||||
}
|
||||
|
||||
TEST_MAIN();
|
Loading…
Add table
Add a link
Reference in a new issue