648 lines
16 KiB
C++

#include <Arduino.h>
#include <TM1637.h>
#include <Ticker.h>
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESP8266mDNS.h>
#include <ArduinoOTA.h>
#include <PCF8574.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <ArduinoJson.h>
// local includes
#include "common.h"
#include "sanitycheck.h"
#include "defaults.h"
#include "webui.h"
#include "config.h"
#include "globals.h"
#include "dtc.h"
#include "debugger.h"
#ifdef FEATURE_ENABLE_LORA
#include "lora_net.h"
#endif
#ifdef FEATURE_ENABLE_WIFI_CLIENT
#include <ESP8266WiFiMulti.h>
const char *ssid = QUOTE(WIFI_SSID_CLIENT);
const char *password = QUOTE(WIFI_PASSWORD_CLIENT);
const uint32_t connectTimeoutMs = 5000;
ESP8266WiFiMulti wifiMulti;
#endif
PCF8574 i2c_io(I2C_IO_ADDRESS);
Adafruit_INA219 ina219;
TM1637 disp_FAC_1(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC1);
TM1637 disp_FAC_2(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC2);
TM1637 disp_FAC_3(GPIO_7SEG_CLK, GPIO_7SEG_EN_FAC3);
void SevenSeg_Output();
void toggleWiFiAP(boolean shutdown = false);
void SystemShutdown();
void SetBatteryType(batteryType_t type);
void ProcessKeyCombos(bool *btnState);
void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3);
void initGlobals();
void maintainSysStat();
#ifdef FEATURE_ENABLE_LORA
void setMPins_Helper(int pin, int status);
void tmrCallback_StatusSender();
Ticker tmrStatusSender(tmrCallback_StatusSender, 30000, 0, MILLIS);
#endif
void tmrCallback_PowerMonitor();
Ticker tmrPowerMonitor(tmrCallback_PowerMonitor, 10000, 0, MILLIS);
void tmrCallback_FactionTicker();
Ticker tmrFactionTicker(tmrCallback_FactionTicker, 1000, 0, MILLIS);
void tmrCallback_InputGetter();
Ticker tmrInputGetter(tmrCallback_InputGetter, 250, 0, MILLIS);
void tmrCallback_EEPROMCyclicPDS();
Ticker tmrEEPROMCyclicPDS(tmrCallback_EEPROMCyclicPDS, 60000, 0, MILLIS);
#ifdef FEATURE_ENABLE_WIFI_CLIENT
void tmrCallback_WiFiMaintainConnection();
Ticker tmrWiFiMaintainConnection(tmrCallback_WiFiMaintainConnection, 1000, 0, MILLIS);
#endif
uint32_t DisplayOverrideFlag = 0;
char DisplayOverrideValue[3][5] = {0};
#ifdef FEATURE_ENABLE_LORA
void setMPins_Helper(int pin, int status)
{
i2c_io.write(pin, status);
}
#endif
void setup()
{
system_update_cpu_freq(SYS_CPU_80MHZ);
strcpy(globals.DeviceName, DEVICE_NAME);
snprintf(globals.DeviceName_ID, 42, "%s_%08X", globals.DeviceName, ESP.getChipId());
WiFi.persistent(false);
WiFi.disconnect();
Serial.begin(115200);
Serial.setDebugOutput(false);
Serial.print("\n\n-------------------START-------------------\n");
Serial.print(globals.DeviceName);
Serial.print("\nby Hiabuto Defense\n");
ClearAllDTC(); // Init DTC-Storage
InitEEPROM();
GetConfig_EEPROM();
GetPersistence_EEPROM();
if (i2c_io.begin())
{
Serial.printf("Initialized PCF8574-GPIO at Address 0x%02X\n", I2C_IO_ADDRESS);
}
else
{
Serial.print("PCF8574-GPIO not Initialized\n");
}
if (ina219.begin())
{
Serial.printf("Initialized INA219-Powermonitor at Address 0x%02X\n", I2C_POWER_ADDRESS);
tmrPowerMonitor.start();
}
else
{
Serial.print("INA219 not Initialized\n");
}
#ifdef FEATURE_ENABLE_LORA
if (InitLoRa(&setMPins_Helper))
{
Serial.print("Initialized LoRa_Transceiver\n");
tmrStatusSender.start();
}
else
{
Serial.print("LoRa not Initialized\n");
}
#endif
#ifdef FEATURE_ENABLE_WIFI_CLIENT
WiFi.mode(WIFI_STA);
WiFi.setHostname(globals.DeviceName);
wifiMulti.addAP(QUOTE(WIFI_CLIENT_SSID), QUOTE(WIFI_CLIENT_PASSWORD));
tmrWiFiMaintainConnection.start();
Serial.print("WiFi-Client Initialized\n");
#else
WiFi.mode(WIFI_OFF);
#endif
ArduinoOTA.setPort(8266);
ArduinoOTA.setHostname(globals.DeviceName_ID);
ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD));
ArduinoOTA.onStart([]()
{
disp_FAC_1.setBrightness(5);
disp_FAC_2.setBrightness(5);
disp_FAC_3.setBrightness(5);
disp_FAC_1.display("OTA ");
disp_FAC_3.clearScreen();
if (ArduinoOTA.getCommand() == U_FLASH)
{
disp_FAC_2.display("FLSH");
}
else
{
disp_FAC_2.display("FILE");
LittleFS.end();
} });
ArduinoOTA.onEnd([]()
{
disp_FAC_1.display("DONE");
disp_FAC_2.clearScreen();
disp_FAC_3.clearScreen(); });
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
{ disp_FAC_3.display((progress / (total / 100))); });
ArduinoOTA.onError([](ota_error_t error)
{
Serial.printf("Error[%u]: ", error);
if (error == OTA_AUTH_ERROR)
Serial.println("Auth Failed");
else if (error == OTA_BEGIN_ERROR)
Serial.println("Begin Failed");
else if (error == OTA_CONNECT_ERROR)
Serial.println("Connect Failed");
else if (error == OTA_RECEIVE_ERROR)
Serial.println("Receive Failed");
else if (error == OTA_END_ERROR)
Serial.println("End Failed"); });
ArduinoOTA.begin();
Serial.print("\nOTA-Init done");
initWebUI();
Serial.print("\nWebUI-Init done");
initGlobals();
Serial.print("\nglobals-Init done");
#ifdef CAPTIVE
dnsServer.start(53, "*", WiFi.softAPIP());
#endif
disp_FAC_1.init();
disp_FAC_1.setBrightness(5);
disp_FAC_2.init();
disp_FAC_2.setBrightness(5);
disp_FAC_3.init();
disp_FAC_3.setBrightness(5);
tmrEEPROMCyclicPDS.start();
tmrFactionTicker.start();
tmrInputGetter.start();
if (ConfigData.active_faction_on_reboot == false)
PersistenceData.activeFaction = NONE;
Serial.print("\nSetup Done\n");
}
void loop()
{
maintainSysStat();
tmrEEPROMCyclicPDS.update();
tmrFactionTicker.update();
tmrInputGetter.update();
tmrPowerMonitor.update();
ArduinoOTA.handle();
SevenSeg_Output();
EEPROM_Process();
Webserver_Process();
DTC_Process();
Debug_Process();
#ifdef FEATURE_ENABLE_LORA
tmrStatusSender.update();
LoRa_Process();
#endif
#ifdef CAPTIVE
dnsServer.processNextRequest();
#endif
#ifdef FEATURE_ENABLE_WIFI_CLIENT
tmrWiFiMaintainConnection.update();
#endif
yield();
}
String macToString(const unsigned char *mac)
{
char buf[20];
snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
return String(buf);
}
void SevenSeg_Output()
{
char sevenSegBuff[5] = "";
if (DisplayOverrideFlag > millis())
{
disp_FAC_1.setBrightness(5);
disp_FAC_2.setBrightness(5);
disp_FAC_3.setBrightness(5);
disp_FAC_1.display(String(DisplayOverrideValue[0]));
disp_FAC_2.display(String(DisplayOverrideValue[1]));
disp_FAC_3.display(String(DisplayOverrideValue[2]));
}
else
{
if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000)
{
if (millis() % 3000 < 1500)
snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", globals.battery_level);
else
snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%3d.%1d", (globals.loadvoltage_mV / 1000), ((globals.loadvoltage_mV % 1000) / 100));
disp_FAC_1.setBrightness(1);
disp_FAC_1.display(" Bat");
disp_FAC_2.setBrightness(1);
disp_FAC_2.display("low ");
disp_FAC_3.setBrightness(1);
disp_FAC_3.display(String(sevenSegBuff));
}
else
{
disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 5 : 1);
disp_FAC_1.refresh();
snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_1_timer / 60);
disp_FAC_1.display(String(sevenSegBuff), false, false);
disp_FAC_1.setDp((PersistenceData.activeFaction == FACTION_1) && (millis() % 1000 > 500));
disp_FAC_2.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 5 : 1);
disp_FAC_2.refresh();
snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_2_timer / 60);
disp_FAC_2.display(String(sevenSegBuff), false, false);
disp_FAC_2.setDp((PersistenceData.activeFaction == FACTION_2) && (millis() % 1000 > 500));
disp_FAC_3.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 5 : 1);
disp_FAC_3.refresh();
snprintf(sevenSegBuff, sizeof(sevenSegBuff), "%4d", PersistenceData.faction_3_timer / 60);
disp_FAC_3.display(String(sevenSegBuff), false, false);
disp_FAC_3.setDp((PersistenceData.activeFaction == FACTION_3) && (millis() % 1000 > 500));
}
}
}
void tmrCallback_FactionTicker()
{
switch (PersistenceData.activeFaction)
{
case FACTION_1:
PersistenceData.faction_1_timer++;
break;
case FACTION_2:
PersistenceData.faction_2_timer++;
break;
case FACTION_3:
PersistenceData.faction_3_timer++;
break;
default:
break;
}
}
void tmrCallback_InputGetter()
{
static bool btnState[3];
uint8_t keysPressed = 0;
btnState[0] = i2c_io.readButton(I2C_IO_BTN_FAC1);
btnState[1] = i2c_io.readButton(I2C_IO_BTN_FAC2);
btnState[2] = i2c_io.readButton(I2C_IO_BTN_FAC3);
ProcessKeyCombos(btnState);
keysPressed = keysPressed + (btnState[0] == FAC_1_TRG_PRESSED ? 1 : 0);
keysPressed = keysPressed + (btnState[1] == FAC_2_TRG_PRESSED ? 1 : 0);
keysPressed = keysPressed + (btnState[2] == FAC_3_TRG_PRESSED ? 1 : 0);
if (keysPressed > 1)
{
Serial.println("ERROR: More than one Flag active - setting no Faction active");
PersistenceData.activeFaction = NONE;
return;
}
if (btnState[0] == FAC_1_TRG_PRESSED)
{
if (PersistenceData.activeFaction != FACTION_1)
{
Serial.println("Faction 1 captured !");
globals.requestEEAction = EE_PDS_SAVE;
}
PersistenceData.activeFaction = FACTION_1;
}
if (btnState[1] == FAC_2_TRG_PRESSED)
{
if (PersistenceData.activeFaction != FACTION_2)
{
Serial.println("Faction 2 captured !");
globals.requestEEAction = EE_PDS_SAVE;
}
PersistenceData.activeFaction = FACTION_2;
}
if (btnState[2] == FAC_3_TRG_PRESSED)
{
if (PersistenceData.activeFaction != FACTION_3)
{
Serial.println("Faction 3 captured !");
globals.requestEEAction = EE_PDS_SAVE;
}
PersistenceData.activeFaction = FACTION_3;
}
}
#ifdef FEATURE_ENABLE_LORA
void tmrCallback_StatusSender()
{
sendStatus_LoRa();
}
#endif
void tmrCallback_PowerMonitor()
{
// loadvoltage and percentage is global, because of battery Monitoring
const int bat_min_2s = 680;
const int bat_max_2s = 840;
const int bat_min_3s = 1020;
const int bat_max_3s = 1260;
float shuntvoltage = 0;
// float current_mA = 0;
float busvoltage = 0;
// float power_mW = 0;
int battery_level = 0;
shuntvoltage = ina219.getShuntVoltage_mV();
busvoltage = ina219.getBusVoltage_V();
// current_mA = ina219.getCurrent_mA();
// power_mW = ina219.getPower_mW();
globals.loadvoltage_mV = (busvoltage * 1000) + shuntvoltage;
switch (ConfigData.batteryType)
{
case BATTERY_LIPO_2S:
battery_level = map(globals.loadvoltage_mV / 10, bat_min_2s, bat_max_2s, 0, 100);
globals.battery_level = battery_level < 0 ? 0 : battery_level;
break;
case BATTERY_LIPO_3S:
battery_level = map(globals.loadvoltage_mV / 10, bat_min_3s, bat_max_3s, 0, 100);
globals.battery_level = battery_level < 0 ? 0 : battery_level;
break;
default:
globals.battery_level = -1;
break;
}
MaintainDTC(DTC_BAT_LOW, DTC_WARN, (battery_level < 15 ? true : false), battery_level);
MaintainDTC(DTC_BAT_CRITICAL, DTC_CRITICAL, (battery_level < 5 ? true : false), battery_level);
// Serial.printf("Battery Level: %d %%\n", globals.battery_level);
// Serial.printf("Bus Voltage: %f V\n", busvoltage);
// Serial.printf("Shunt Voltage: %f mV\n", shuntvoltage);
// Serial.printf("Load Voltage: %d mV\n", globals.loadvoltage_mV;
// Serial.printf("Current: %f mA\n", current_mA);
// Serial.printf("Power: %f mW\n", power_mW);
}
void tmrCallback_EEPROMCyclicPDS()
{
StorePersistence_EEPROM();
}
#ifdef FEATURE_ENABLE_WIFI_CLIENT
void tmrCallback_WiFiMaintainConnection()
{
static uint32_t WiFiFailCount = 0;
const uint32_t WiFiFailMax = 20;
if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED)
{
return;
}
else
{
if (WiFiFailCount < WiFiFailMax)
{
WiFiFailCount++;
}
else
{
Debug_pushMessage("WiFi not connected! - Start AP");
toggleWiFiAP();
}
}
}
#endif
void toggleWiFiAP(boolean shutdown)
{
if (WiFi.getMode() != WIFI_OFF)
{
WiFi.mode(WIFI_OFF);
Serial.println("WiFi turned off");
#ifdef FEATURE_ENABLE_WIFI_CLIENT
tmrWiFiMaintainConnection.stop();
#endif
}
else
{
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0));
WiFi.softAP(QUOTE(WIFI_AP_SSID), QUOTE(WIFI_AP_PASSWORD));
#ifdef FEATURE_ENABLE_WIFI_CLIENT
tmrWiFiMaintainConnection.stop();
Serial.println("WiFi AP started, stopped Maintain-Timer");
#else
Serial.println("WiFi AP started");
#endif
}
}
void SystemShutdown()
{
static uint32_t shutdown_delay = 0;
if (shutdown_delay == 0)
{
shutdown_delay = millis() + SHUTDOWN_DELAY_MS;
Serial.printf("Shutdown requested - Restarting in %d seconds\n", SHUTDOWN_DELAY_MS / 1000);
}
if (shutdown_delay < millis())
{
StoreConfig_EEPROM();
StorePersistence_EEPROM();
ESP.restart();
}
}
void SetBatteryType(batteryType_t type)
{
if (ConfigData.batteryType != type)
{
ConfigData.batteryType = type;
globals.requestEEAction = EE_CFG_SAVE;
Serial.printf("Set Batterytype to %s\n", type == BATTERY_LIPO_2S ? "2s Lipo" : "3s LiPo");
}
}
void OverrideDisplay(uint32_t time, const char *message1, const char *message2, const char *message3)
{
DisplayOverrideFlag = millis() + time;
strcpy(DisplayOverrideValue[0], message1);
strcpy(DisplayOverrideValue[1], message2);
strcpy(DisplayOverrideValue[2], message3);
}
void ProcessKeyCombos(bool *btnState)
{
typedef enum
{
KEY_PRESSED,
KEY_RELEASED
} keyStatus_t;
static keyStatus_t keyStatus_Fac1 = KEY_RELEASED;
static keyStatus_t keyStatus_Fac2 = KEY_RELEASED;
static uint8_t keyCount_Fac2 = 0;
static keyStatus_t keyStatus_Fac3 = KEY_RELEASED;
static uint8_t keyCount_Fac3 = 0;
if (btnState[0] == FAC_1_TRG_PRESSED)
{
keyStatus_Fac1 = KEY_PRESSED;
// Process FactionKey 2 ComboCounter
if (btnState[1] == FAC_2_TRG_PRESSED && keyStatus_Fac2 == KEY_RELEASED)
{
keyStatus_Fac2 = KEY_PRESSED;
keyCount_Fac2++;
}
if (btnState[1] != FAC_2_TRG_PRESSED)
keyStatus_Fac2 = KEY_RELEASED;
// Process FactionKey 3 ComboCounter
if (btnState[2] == FAC_3_TRG_PRESSED && keyStatus_Fac3 == KEY_RELEASED)
{
keyStatus_Fac3 = KEY_PRESSED;
keyCount_Fac3++;
}
if (btnState[2] != FAC_3_TRG_PRESSED)
keyStatus_Fac3 = KEY_RELEASED;
}
if (btnState[0] != FAC_1_TRG_PRESSED && keyStatus_Fac1 == KEY_PRESSED)
{
if (keyCount_Fac2 > 0 || keyCount_Fac3 > 0)
Serial.printf("KeyCombo 2: %d | 3: %d\n", keyCount_Fac2, keyCount_Fac3);
if (keyCount_Fac2 == 2 && keyCount_Fac3 == 0)
{
Serial.println("KeyCombo: WiFi AP ON");
OverrideDisplay(5000, "NET ", " ", " ");
toggleWiFiAP(false);
}
else if (keyCount_Fac2 == 4 && keyCount_Fac3 == 0)
{
Serial.printf("KeyCombo: Reset Timer\n");
if (globals.systemStatus == sysStat_Startup)
{
OverrideDisplay(5000, "RST ", " ", " ");
PersistenceData.faction_1_timer = 0;
PersistenceData.faction_2_timer = 0;
PersistenceData.faction_3_timer = 0;
PersistenceData.activeFaction = NONE;
globals.requestEEAction = EE_PDS_SAVE;
}
else
{
OverrideDisplay(5000, "ERR ", " ", " ");
Serial.printf("ERROR: only %d seconds after Startup!\n", STARTUP_DELAY_MS / 1000);
}
}
keyCount_Fac2 = 0;
keyCount_Fac3 = 0;
keyStatus_Fac1 = KEY_RELEASED;
keyStatus_Fac2 = KEY_RELEASED;
keyStatus_Fac3 = KEY_RELEASED;
}
}
void maintainSysStat()
{
static tSystem_Status lastStat = sysStat_null;
// system Status Transistions
switch (globals.systemStatus)
{
case sysStat_Shutdown:
SystemShutdown();
break;
case sysStat_Startup:
if (millis() > STARTUP_DELAY_MS)
globals.systemStatus = sysStat_Normal;
break;
case sysStat_Error:
case sysStat_Normal:
case sysStat_null:
default:
break;
}
// system Status Changed Actions
if (lastStat != globals.systemStatus)
{
switch (globals.systemStatus)
{
case sysStat_Shutdown:
OverrideDisplay(SHUTDOWN_DELAY_MS, " re", "boot", " ");
break;
case sysStat_Startup:
OverrideDisplay(STARTUP_DELAY_MS, "star", "t up", " ");
break;
case sysStat_Error:
case sysStat_Normal:
case sysStat_null:
default:
break;
}
lastStat = globals.systemStatus;
}
}