Restructured Repo before adding Hardware-Files

This commit is contained in:
2022-11-19 02:16:46 +01:00
parent 030f010832
commit 0e352881d3
73 changed files with 8 additions and 6362 deletions

41
Software/src/common.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef _COMMON_H_
#define _COMMON_H_
#define Q(x) #x
#define QUOTE(x) Q(x)
// Module connection pins (ESP GPIO-Nums)
#define CLK 12
#define DIO_FAC_1_7SEG 13
#define DIO_FAC_2_7SEG 17
#define DIO_FAC_3_7SEG 21
#define DIO_FAC_1_TRG 36
#define FAC_1_TRG_PRESSED LOW
#define DIO_FAC_2_TRG 37
#define FAC_2_TRG_PRESSED LOW
#define DIO_FAC_3_TRG 38
#define FAC_3_TRG_PRESSED LOW
#ifndef HOST_NAME
#define HOST_NAME "DE_Timer_%06X" // Use printf-Formatting - Chip-ID (uin32_t) will be added
#endif
#ifndef OTA_DELAY
#define OTA_DELAY 50 // ticks -> 10ms / tick
#endif
#ifndef ADMIN_PASSWORD
#error "You need to define ADMIN_PASSWORD for OTA-Update"
#endif
#ifndef WIFI_PASSWORD
#error "You must define an WIFI_PASSWORD for OTA-Update"
#endif
#ifndef WIFI_SSID
#error "You must define an WIFI_SSID for OTA-Update"
#endif
#ifndef WIFI_AP_PASSWORD
#error "You must define an WIFI_AP_PASSWORD for Standalone AP-Mode"
#endif
#endif

261
Software/src/config.cpp Normal file
View File

@@ -0,0 +1,261 @@
#include "config.h"
I2C_eeprom ee(0x50, EEPROM_SIZE_BYTES);
configData_t ConfigData;
persistenceData_t PersistenceData;
const uint16_t eeVersion = 1; // inc
boolean eeAvailable = false;
const uint16_t persistencemarker_Adress = 0; // sizeof 4
const uint16_t startofConfig_Adress = 16;
const uint16_t startofPersistence_Adress = startofConfig_Adress + sizeof(ConfigData) + (sizeof(ConfigData) % 16);
boolean checkEEPROMavailable();
void InitEEPROM()
{
ee.begin();
if (!checkEEPROMavailable())
{
globals.systemStatus = sysStat_Error;
MaintainDTC(DTC_NO_EEPROM_FOUND, true);
return;
}
GetConfig_EEPROM();
if (ConfigData.EEPROM_Version != eeVersion)
{
FormatConfig_EEPROM();
globals.systemStatus = sysStat_Error;
MaintainDTC(DTC_EEPROM_VERSION_BAD, true);
return;
}
if (getPersistanceAddress() > ee.getDeviceSize())
{
FormatPersistence_EEPROM();
globals.systemStatus = sysStat_Error;
MaintainDTC(DTC_EEPROM_PDS_MARKER_INVALID, true);
return;
}
GetPersistence_EEPROM();
}
void EEPROM_Process()
{
switch (globals.requestEEAction)
{
case EE_CFG_SAVE:
StoreConfig_EEPROM();
globals.requestEEAction = EE_IDLE;
break;
case EE_CFG_LOAD:
GetConfig_EEPROM();
globals.requestEEAction = EE_IDLE;
break;
case EE_PDS_SAVE:
StorePersistence_EEPROM();
globals.requestEEAction = EE_IDLE;
break;
case EE_PDS_LOAD:
GetPersistence_EEPROM();
globals.requestEEAction = EE_IDLE;
break;
case EE_IDLE:
default:
globals.requestEEAction = EE_IDLE;
}
}
void StoreConfig_EEPROM()
{
ConfigData.checksum = 0;
ConfigData.checksum = Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData));
if (!checkEEPROMavailable())
return;
ee.updateBlock(startofConfig_Adress, (uint8_t *)&ConfigData, sizeof(ConfigData));
}
void GetConfig_EEPROM()
{
if (!checkEEPROMavailable())
return;
ee.readBlock(startofConfig_Adress, (uint8_t *)&ConfigData, sizeof(ConfigData));
uint32_t checksum = ConfigData.checksum;
ConfigData.checksum = 0;
if (Checksum_EEPROM((uint8_t *)&ConfigData, sizeof(ConfigData)) != checksum)
{
MaintainDTC(DTC_EEPROM_CFG_BAD, true);
FormatConfig_EEPROM();
}
ConfigData.checksum = checksum;
}
uint32_t getPersistanceAddress()
{
uint32_t eePersistenceMarker;
ee.readBlock(persistencemarker_Adress, (uint8_t *)&eePersistenceMarker, sizeof(eePersistenceMarker));
return eePersistenceMarker;
}
void updatePersistanceAddress(uint32_t adress)
{
ee.updateBlock(persistencemarker_Adress, (uint8_t *)&adress, sizeof(adress));
}
void StorePersistence_EEPROM()
{
if (PersistenceData.writeCycleCounter >= EEPROM_ENDURANCE)
MovePersistencePage_EEPROM(false);
else
PersistenceData.writeCycleCounter++;
PersistenceData.checksum = 0;
PersistenceData.checksum = Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData));
if (!checkEEPROMavailable())
return;
ee.updateBlock(getPersistanceAddress(), (uint8_t *)&PersistenceData, sizeof(PersistenceData));
}
void GetPersistence_EEPROM()
{
if (!checkEEPROMavailable())
return;
ee.readBlock(getPersistanceAddress(), (uint8_t *)&PersistenceData, sizeof(PersistenceData));
uint32_t checksum = PersistenceData.checksum;
PersistenceData.checksum = 0;
if (Checksum_EEPROM((uint8_t *)&PersistenceData, sizeof(PersistenceData)) != checksum)
{
MaintainDTC(DTC_EEPROM_PDS_BAD, true);
FormatPersistence_EEPROM();
}
PersistenceData.checksum = checksum;
}
void FormatConfig_EEPROM()
{
configData_t defaults;
ConfigData = defaults;
ConfigData.EEPROM_Version = eeVersion;
StoreConfig_EEPROM();
}
void FormatPersistence_EEPROM()
{
persistenceData_t defaults;
PersistenceData = defaults;
updatePersistanceAddress(startofPersistence_Adress);
StorePersistence_EEPROM();
}
void MovePersistencePage_EEPROM(boolean reset)
{
if (!checkEEPROMavailable())
return;
if (reset)
{
updatePersistanceAddress(startofPersistence_Adress);
}
else
{
uint32_t newPersistenceMarker = getPersistanceAddress() + sizeof(PersistenceData);
// check if we reached the End of the EEPROM and Startover at the beginning
if ((newPersistenceMarker + sizeof(PersistenceData)) > ee.getDeviceSize())
{
MaintainDTC(DTC_EEPROM_WORNOUT, true);
return;
}
else
{
updatePersistanceAddress(newPersistenceMarker);
PersistenceData.writeCycleCounter = 0;
}
}
}
uint32_t GetEESize()
{
return ee.getDeviceSize();
}
uint32_t Checksum_EEPROM(uint8_t const *data, size_t len)
{
if (data == NULL)
return 0;
uint32_t crc, mask;
crc = 0xFFFFFFFF;
while (len--)
{
crc ^= *data++;
for (uint8_t k = 0; k < 8; k++)
{
mask = -(crc & 1);
crc = (crc >> 1) ^ (0xEDB88320 & mask);
}
}
return ~crc;
}
void dumpEEPROM(uint16_t memoryAddress, uint16_t length)
{
#define BLOCK_TO_LENGTH 16
if (length > ee.getDeviceSize())
length = ee.getDeviceSize();
if (memoryAddress + length > ee.getDeviceSize())
return;
if (!checkEEPROMavailable())
return;
char ascii_buf[BLOCK_TO_LENGTH + 1];
sprintf(ascii_buf, "%*s", BLOCK_TO_LENGTH, "ASCII");
Serial.print(PSTR("\nAddress "));
for (int x = 0; x < BLOCK_TO_LENGTH; x++)
Serial.printf("%3d", x);
memoryAddress = memoryAddress / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH;
length = (length + BLOCK_TO_LENGTH - 1) / BLOCK_TO_LENGTH * BLOCK_TO_LENGTH;
for (unsigned int i = 0; i < length; i++)
{
int blockpoint = memoryAddress % BLOCK_TO_LENGTH;
if (blockpoint == 0)
{
ascii_buf[BLOCK_TO_LENGTH] = 0;
Serial.printf(" %s", ascii_buf);
Serial.printf("\n0x%05X:", memoryAddress);
}
ascii_buf[blockpoint] = ee.readByte(memoryAddress);
Serial.printf(" %02X", ascii_buf[blockpoint]);
if (ascii_buf[blockpoint] < 0x20 || ascii_buf[blockpoint] > 0x7E)
ascii_buf[blockpoint] = '.';
memoryAddress++;
}
Serial.println();
}
boolean checkEEPROMavailable()
{
if (!ee.isConnected())
{
MaintainDTC(DTC_NO_EEPROM_FOUND, true);
return false;
}
return true;
}

69
Software/src/config.h Normal file
View File

@@ -0,0 +1,69 @@
#ifndef _CONFIG_H_
#define _CONFIG_H_
#include <Arduino.h>
#include <Wire.h>
#include <I2C_eeprom.h>
#include "globals.h"
#include "dtc.h"
#define EEPROM_SIZE_BYTES I2C_DEVICESIZE_24LC01
#define EEPROM_ENDURANCE 1000000
typedef enum
{
NONE,
FACTION_1,
FACTION_2,
FACTION_3
} factions_t;
typedef struct
{
uint32_t writeCycleCounter = 0;
uint32_t faction_1_timer = 0;
uint32_t faction_2_timer = 0;
uint32_t faction_3_timer = 0;
factions_t activeFaction = NONE;
uint32_t checksum = 0;
} persistenceData_t;
typedef enum
{
BATTERY_UNDEFINED,
BATTERY_LIPO_2S,
BATTERY_LIPO_3S
} batteryType_t;
const char BatteryString[][10]{
"Undefined",
"LiPo 2S",
"LiPo 3S"
};
typedef struct
{
uint8_t EEPROM_Version = 1;
batteryType_t batteryType = BATTERY_UNDEFINED;
uint32_t checksum = 0;
} configData_t;
void InitEEPROM();
void EEPROM_Process();
void StoreConfig_EEPROM();
void GetConfig_EEPROM();
void StorePersistence_EEPROM();
void GetPersistence_EEPROM();
void FormatConfig_EEPROM();
void FormatPersistence_EEPROM();
uint32_t Checksum_EEPROM(uint8_t const *data, size_t len);
void dumpEEPROM(uint16_t memoryAddress, uint16_t length);
void MovePersistencePage_EEPROM(boolean reset);
uint32_t getPersistanceAddress();
void updatePersistanceAddress(uint32_t adress);
uint32_t GetEESize();
extern configData_t ConfigData;
extern persistenceData_t PersistenceData;
#endif // _CONFIG_H_

48
Software/src/defaults.h Normal file
View File

@@ -0,0 +1,48 @@
#ifndef _DEFAULTS_H_
#define _DEFAULTS_H_
#ifndef WIFI_CLIENT
#define WIFI_ACCESSPOINT
#endif
#if defined(WIFI_CLIENT) && defined(WIFI_ACCESSPOINT)
#error "You can't define AP and CLIENT at the same Time!"
#endif
#ifndef WIFI_SSID
#define WIFI_SSID "3 Factions CTF Timer"
#endif
#ifndef WIFI_PASS
#define WIFI_PASS "CaptureTheFlag"
#endif
#ifndef OTA_PASS
#define OTA_PASS "UploadTheFlag"
#endif
#ifndef OTA_HOST
#define OTA_HOST "ESP_OTA"
#endif
#ifndef DEVICE_NAME
#define DEVICE_NAME WIFI_SSID
#endif
#ifndef BAT_LOW_PERCENT
#define BAT_LOW_PERCENT 10
#endif
#ifndef FACTION_1_NAME
#define FACTION_1_NAME "Team A"
#endif
#ifndef FACTION_2_NAME
#define FACTION_2_NAME "Team B"
#endif
#ifndef FACTION_3_NAME
#define FACTION_3_NAME "Team C"
#endif
#endif

85
Software/src/dtc.cpp Normal file
View File

@@ -0,0 +1,85 @@
#include "dtc.h"
DTCEntry_s DTCStorage[MAX_DTC_STORAGE];
void MaintainDTC(DTCNums_t DTC_no, boolean active)
{
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number == DTC_no)
{
if (active && DTCStorage[i].active != DTC_ACTIVE)
{
Serial.printf("DTC gone active: %d", DTC_no);
DTCStorage[i].timestamp = millis();
DTCStorage[i].active = DTC_ACTIVE;
}
if (!active && DTCStorage[i].active == DTC_ACTIVE)
{
Serial.printf("DTC gone previous: %d", DTC_no);
DTCStorage[i].active = DTC_PREVIOUS;
}
return;
}
}
// DTC was not found with upper iteration, but is active
// so we need to look for free space to store DTC
if (active == true)
{
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number == DTC_LAST_DTC)
{
Serial.printf("new DTC registered: %d", DTC_no);
DTCStorage[i].Number = DTC_no;
DTCStorage[i].timestamp = millis();
DTCStorage[i].active = DTC_ACTIVE;
return;
}
}
}
}
void ClearDTC(DTCNums_t DTC_no)
{
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number == DTC_no)
{
DTCStorage[i].Number = DTC_LAST_DTC;
DTCStorage[i].active = DTC_NONE;
DTCStorage[i].timestamp = 0;
}
}
}
void ClearAllDTC()
{
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
DTCStorage[i].Number = DTC_LAST_DTC;
DTCStorage[i].active = DTC_NONE;
DTCStorage[i].timestamp = 0;
}
}
DTCNums_t getlastDTC(boolean only_active)
{
int8_t pointer = -1;
uint32_t lasttimestamp = 0;
for (int i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number > 0 && DTCStorage[i].timestamp > lasttimestamp)
{
if (only_active == false || DTCStorage[i].active == DTC_ACTIVE)
{
pointer = i;
lasttimestamp = DTCStorage[i].timestamp;
}
}
}
return pointer >= 0 ? DTCStorage[pointer].Number : DTC_LAST_DTC;
}

39
Software/src/dtc.h Normal file
View File

@@ -0,0 +1,39 @@
#ifndef _DTC_H_
#define _DTC_H_
#include <Arduino.h>
#define MAX_DTC_STORAGE 6
typedef enum DTCNums_e
{
DTC_NO_EEPROM_FOUND,
DTC_EEPROM_CFG_BAD,
DTC_EEPROM_PDS_BAD,
DTC_EEPROM_VERSION_BAD,
DTC_EEPROM_WORNOUT, // this will happen if the EEPROM-cells are all overwritten 1 million times!
DTC_EEPROM_PDS_MARKER_INVALID, // This happens if the Marker of the PersistanceData was pointing to an EE-Adress bigger than the used EEPROM-IC
DTC_LAST_DTC
} DTCNums_t;
typedef enum DTCActive_e
{
DTC_ACTIVE,
DTC_PREVIOUS,
DTC_NONE
} DTCActive_t;
typedef struct DTCEntry_s
{
DTCNums_t Number;
uint32_t timestamp;
DTCActive_t active;
} DTCEntry_t;
void MaintainDTC(DTCNums_t DTC_no, boolean active);
void ClearDTC(DTCNums_t DTC_no);
void ClearAllDTC();
DTCNums_t getlastDTC(boolean only_active);
extern DTCEntry_s DTCStorage[MAX_DTC_STORAGE];
#endif

36
Software/src/globals.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef _GLOBALS_H_
#define _GLOBALS_H_
#include <Arduino.h>
typedef enum eSystem_Status
{
sysStat_Startup,
sysStat_Normal,
sysStat_Error,
sysStat_Shutdown
} tSystem_Status;
typedef enum eEERequest
{
EE_IDLE,
EE_CFG_SAVE,
EE_CFG_LOAD,
EE_PDS_SAVE,
EE_PDS_LOAD
} tEERequest;
typedef struct Globals_s
{
char DeviceName[33];
char DeviceName_ID[43];
tSystem_Status systemStatus = sysStat_Startup;
tSystem_Status resumeStatus = sysStat_Startup;
eEERequest requestEEAction = EE_IDLE;
float loadvoltage = 0;
int battery_level = 0;
} Globals_t;
extern Globals_t globals;
#endif

20
Software/src/lora_net.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include "lora_net.h"
uint8_t LoRa_Init()
{
uint8_t success = false;
// SPI LoRa pins
SPI.begin(LORA_SCK, LORA_MISO, LORA_MOSI, LORA_SS);
// setup LoRa transceiver module
LoRa.setPins(LORA_SS, LORA_RST, LORA_DIO0);
if (!LoRa.begin(LORA_BAND))
{
Serial.println("Starting LoRa failed!");
success = false;
}
Serial.println("LoRa Initializing OK!");
success = true;
return success;
}

23
Software/src/lora_net.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef _LORA_NET_H_
#define _LORA_NET_H_
#include <Arduino.h>
#include <SPI.h>
#include <LoRa.h>
// define the pins used by the LoRa transceiver module
#define LORA_SCK 5
#define LORA_MISO 19
#define LORA_MOSI 27
#define LORA_SS 18
#define LORA_RST 14
#define LORA_DIO0 26
// 433E6 for Asia
// 866E6 for Europe
// 915E6 for North America
#define LORA_BAND 8681E5
uint8_t LoRa_Init();
#endif

513
Software/src/main.cpp Normal file
View File

@@ -0,0 +1,513 @@
#include <Arduino.h>
#include <TM1637Display.h>
#include <Ticker.h>
#include <DNSServer.h>
#include <WiFi.h>
#include <AsyncTCP.h>
#include <ESPmDNS.h>
#include <ArduinoOTA.h>
#include <ESPAsyncWebServer.h>
#include <LittleFS.h>
#include <Wire.h>
#include <Adafruit_INA219.h>
#include <ArduinoJson.h>
// local includes
#include "defaults.h"
#include "webui.h"
#include "config.h"
#include "globals.h"
#include "dtc.h"
#include "common.h"
#include "lora_net.h"
#include "oled_display.h"
#ifdef WIFI_CLIENT
#include <WiFiMulti.h>
const char *ssid = QUOTE(WIFI_SSID);
const char *password = QUOTE(WIFI_PASSWORD);
const uint32_t connectTimeoutMs = 5000;
WiFiMulti wifiMulti;
#endif
void SevenSeg_Output();
void FactionTicker_callback();
void inputGetterTicker_callback();
void powerMonitorTicker_callback();
void EEPROMCyclicPDS_callback();
void toggleWiFiAP(boolean shutdown = false);
void SystemShutdown();
void SetBatteryType(batteryType_t type);
void ProcessKeyCombos();
void OverrideDisplay(const uint8_t *message, uint32_t time);
void LoRaPublish_callback();
#ifdef WIFI_CLIENT
void wifiMaintainConnectionTicker_callback();
Ticker WiFiMaintainConnectionTicker(wifiMaintainConnectionTicker_callback, 1000, 0, MILLIS);
#endif
uint32_t getESPChipID();
TM1637Display disp_FAC_1(CLK, DIO_FAC_1_7SEG);
TM1637Display disp_FAC_2(CLK, DIO_FAC_2_7SEG);
TM1637Display disp_FAC_3(CLK, DIO_FAC_3_7SEG);
Adafruit_INA219 ina219;
#ifdef CAPTIVE
DNSServer dnsServer;
#endif
AsyncWebServer server(80);
Ticker FactionTicker(FactionTicker_callback, 1000, 0, MILLIS);
Ticker InputGetterTicker(inputGetterTicker_callback, 250, 0, MILLIS);
Ticker PowerMonitorTicker(powerMonitorTicker_callback, 5000, 0, MILLIS);
Ticker EEPROMCyclicPDSTicker(EEPROMCyclicPDS_callback, 60000, 0, MILLIS);
Ticker LoRaPublishTicker(LoRaPublish_callback, 60000, 0, MILLIS);
uint8_t Faction_1_dot = 0;
uint8_t Faction_2_dot = 0;
uint8_t Faction_3_dot = 0;
uint32_t DisplayOverrideFlag = 0;
uint8_t *DisplayOverrideValue = 0;
Globals_t globals;
const uint8_t sevenSeg_bat[] = {0x00, 0b01111100, 0b01110111, 0b01111000};
const uint8_t sevenSeg_low[] = {0b00111000, 0b01011100, 0x00, 0x00};
const uint8_t sevenSeg_net[] = {0b01010100, 0b01111001, 0b01111000, 0x00};
const uint8_t sevenSeg_ota[] = {0x3F, 0x78, 0x77, 0x00};
const uint8_t sevenSeg_flsh[] = {0x71, 0x38, 0x6D, 0x76};
const uint8_t sevenSeg_file[] = {0x71, 0x30, 0x38, 0x79};
void setup()
{
setCpuFrequencyMhz(80);
WiFi.setAutoReconnect(false);
WiFi.persistent(false);
WiFi.disconnect();
Serial.begin(115200);
Serial.print("\n\n\n");
strcpy(globals.DeviceName, DEVICE_NAME);
snprintf(globals.DeviceName_ID, 42, "%s_%08X", globals.DeviceName, getESPChipID());
if (LoRa_Init())
LoRaPublishTicker.start();
OLED_Init();
pinMode(DIO_FAC_1_TRG, INPUT_PULLUP);
pinMode(DIO_FAC_2_TRG, INPUT_PULLUP);
pinMode(DIO_FAC_3_TRG, INPUT_PULLUP);
#ifdef SERIAL_DEBUG
Serial.setDebugOutput(true);
#endif
if (ina219.begin())
PowerMonitorTicker.start();
else
Serial.println("Failed to find INA219 chip");
LittleFS.begin();
#ifdef WIFI_CLIENT
WiFi.mode(WIFI_STA);
WiFi.setHostname(globals.DeviceName_ID);
wifiMulti.addAP(QUOTE(WIFI_SSID), QUOTE(WIFI_PASSWORD));
WiFiMaintainConnectionTicker.start();
#else
WiFi.mode(WIFI_AP);
WiFi.begin(QUOTE(DEVICE_NAME), QUOTE(WIFI_AP_PASSWORD));
WiFi.setSleep(true);
WiFi.mode(WIFI_OFF);
#endif
InitEEPROM();
ArduinoOTA.setPort(8266);
ArduinoOTA.setHostname(globals.DeviceName_ID);
ArduinoOTA.setPassword(QUOTE(ADMIN_PASSWORD));
ArduinoOTA.onStart([]()
{
disp_FAC_1.setBrightness(7);
disp_FAC_2.setBrightness(7);
disp_FAC_3.setBrightness(7);
disp_FAC_1.setSegments(sevenSeg_ota);
disp_FAC_3.clear();
if (ArduinoOTA.getCommand() == U_FLASH)
{
disp_FAC_2.setSegments(sevenSeg_flsh);
}
else
{
disp_FAC_2.setSegments(sevenSeg_file);
LittleFS.end();
} });
ArduinoOTA.onEnd([]()
{
const uint8_t seg_done[] = {0x5E, 0x3F, 0x54, 0x79};
disp_FAC_1.setSegments(seg_done);
disp_FAC_2.clear();
disp_FAC_3.clear(); });
ArduinoOTA.onProgress([](unsigned int progress, unsigned int total)
{ disp_FAC_3.showNumberDecEx((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();
#ifdef CAPTIVE
dnsServer.start(53, "*", WiFi.softAPIP());
#endif
initWebUI();
EEPROMCyclicPDSTicker.start();
FactionTicker.start();
InputGetterTicker.start();
Serial.println("Setup Done");
}
void loop()
{
EEPROMCyclicPDSTicker.update();
FactionTicker.update();
InputGetterTicker.update();
PowerMonitorTicker.update();
LoRaPublishTicker.update();
ArduinoOTA.handle();
SevenSeg_Output();
EEPROM_Process();
OLED_Process();
#ifdef CAPTIVE
dnsServer.processNextRequest();
#endif
#ifdef WIFI_CLIENT
WiFiMaintainConnectionTicker.update();
#endif
if (globals.systemStatus == sysStat_Shutdown)
SystemShutdown();
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()
{
if (DisplayOverrideFlag > millis())
{
disp_FAC_1.setBrightness(7);
disp_FAC_2.setBrightness(7);
disp_FAC_3.setBrightness(7);
disp_FAC_1.setSegments(DisplayOverrideValue);
disp_FAC_2.clear();
disp_FAC_3.clear();
}
else
{
if (globals.battery_level < BAT_LOW_PERCENT && millis() % 10000 > 7000)
{
disp_FAC_1.setBrightness(0);
disp_FAC_2.setBrightness(0);
disp_FAC_3.setBrightness(0);
disp_FAC_1.setSegments(sevenSeg_bat);
disp_FAC_2.setSegments(sevenSeg_low);
if (millis() % 3000 < 1500)
disp_FAC_3.showNumberDec(globals.battery_level);
else
disp_FAC_3.showNumberDecEx(globals.loadvoltage * 100, 0x40);
}
else
{
disp_FAC_1.setBrightness(PersistenceData.activeFaction == FACTION_1 ? 7 : 0);
disp_FAC_2.setBrightness(PersistenceData.activeFaction == FACTION_2 ? 7 : 0);
disp_FAC_3.setBrightness(PersistenceData.activeFaction == FACTION_3 ? 7 : 0);
disp_FAC_1.showNumberDecEx(PersistenceData.faction_1_timer / 60, Faction_1_dot, true, 4, 0);
disp_FAC_2.showNumberDecEx(PersistenceData.faction_2_timer / 60, Faction_2_dot, true, 4, 0);
disp_FAC_3.showNumberDecEx(PersistenceData.faction_3_timer / 60, Faction_3_dot, true, 4, 0);
}
}
}
void FactionTicker_callback()
{
switch (PersistenceData.activeFaction)
{
case FACTION_1:
PersistenceData.faction_1_timer++;
Faction_1_dot = Faction_1_dot == 0x80 || Faction_1_dot == 0x00 ? 0x10 : Faction_1_dot << 1;
Faction_2_dot = 0;
Faction_3_dot = 0;
break;
case FACTION_2:
PersistenceData.faction_2_timer++;
Faction_2_dot = Faction_2_dot == 0x80 || Faction_2_dot == 0x00 ? 0x10 : Faction_2_dot << 1;
Faction_1_dot = 0;
Faction_3_dot = 0;
break;
case FACTION_3:
PersistenceData.faction_3_timer++;
Faction_3_dot = Faction_3_dot == 0x80 || Faction_3_dot == 0x00 ? 0x10 : Faction_3_dot << 1;
Faction_1_dot = 0;
Faction_2_dot = 0;
break;
default:
break;
}
}
void inputGetterTicker_callback()
{
ProcessKeyCombos();
uint8_t keysPressed = 0;
keysPressed = +digitalRead(DIO_FAC_1_TRG) == FAC_1_TRG_PRESSED ? 1 : 0;
keysPressed = +digitalRead(DIO_FAC_2_TRG) == FAC_2_TRG_PRESSED ? 1 : 0;
keysPressed = +digitalRead(DIO_FAC_3_TRG) == 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 (digitalRead(DIO_FAC_1_TRG) == FAC_1_TRG_PRESSED)
PersistenceData.activeFaction = FACTION_1;
if (digitalRead(DIO_FAC_2_TRG) == FAC_2_TRG_PRESSED)
PersistenceData.activeFaction = FACTION_2;
if (digitalRead(DIO_FAC_3_TRG) == FAC_3_TRG_PRESSED)
PersistenceData.activeFaction = FACTION_3;
}
void powerMonitorTicker_callback()
{
// 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 = busvoltage + (shuntvoltage / 1000);
switch (ConfigData.batteryType)
{
case BATTERY_LIPO_2S:
battery_level = map(globals.loadvoltage * 100, 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 * 100, bat_min_3s, bat_max_3s, 0, 100);
globals.battery_level = battery_level < 0 ? 0 : battery_level;
break;
default:
globals.battery_level = -1;
break;
}
// 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: %f V\n", globals.loadvoltage);
// Serial.printf("Current: %f mA\n", current_mA);
// Serial.printf("Power: %f mW\n", power_mW);
}
void EEPROMCyclicPDS_callback()
{
StorePersistence_EEPROM();
}
#ifdef WIFI_CLIENT
void wifiMaintainConnectionTicker_callback()
{
static uint32_t WiFiFailCount = 0;
const uint32_t WiFiFailMax = 20;
if (wifiMulti.run(connectTimeoutMs) == WL_CONNECTED)
{
return;
}
else
{
if (WiFiFailCount < WiFiFailMax)
WiFiFailCount++;
else
toggleWiFiAP(false);
}
}
#endif
void toggleWiFiAP(boolean shutdown)
{
if (WiFi.getMode() != WIFI_OFF && shutdown == true)
{
WiFi.mode(WIFI_OFF);
#ifdef WIFI_CLIENT
WiFiMaintainConnectionTicker.stop();
#endif
}
else if (shutdown == false)
{
WiFi.mode(WIFI_AP);
WiFi.softAPConfig(IPAddress(WIFI_AP_IP_GW), IPAddress(WIFI_AP_IP_GW), IPAddress(255, 255, 255, 0));
WiFi.softAP(globals.DeviceName_ID, QUOTE(WIFI_AP_PASSWORD));
#ifdef WIFI_CLIENT
WiFiMaintainConnectionTicker.stop();
#endif
}
}
void SystemShutdown()
{
StoreConfig_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 ProcessKeyCombos()
{
typedef enum
{
KEY_PRESSED,
KEY_RELEASED
} keyStatus_t;
static keyStatus_t keyStatus_Fac1 = KEY_RELEASED;
static uint8_t keyCount_Fac1 = 0;
static keyStatus_t keyStatus_Fac2 = KEY_RELEASED;
static uint8_t keyCount_Fac2 = 0;
static keyStatus_t keyStatus_Fac3 = KEY_RELEASED;
if (digitalRead(DIO_FAC_3_TRG) == FAC_3_TRG_PRESSED)
{
keyStatus_Fac3 = KEY_PRESSED;
// Process FactionKey 1 ComboCounter
if (digitalRead(DIO_FAC_1_TRG) == FAC_1_TRG_PRESSED && keyStatus_Fac1 == KEY_RELEASED)
{
keyStatus_Fac1 = KEY_PRESSED;
keyCount_Fac1++;
}
if (digitalRead(DIO_FAC_1_TRG) != FAC_1_TRG_PRESSED)
keyStatus_Fac1 = KEY_RELEASED;
// Process FactionKey 2 ComboCounter
if (digitalRead(DIO_FAC_2_TRG) == FAC_2_TRG_PRESSED && keyStatus_Fac2 == KEY_RELEASED)
{
keyStatus_Fac2 = KEY_PRESSED;
keyCount_Fac2++;
}
if (digitalRead(DIO_FAC_2_TRG) != FAC_2_TRG_PRESSED)
keyStatus_Fac2 = KEY_RELEASED;
}
if (digitalRead(DIO_FAC_3_TRG) != FAC_3_TRG_PRESSED && keyStatus_Fac3 == KEY_PRESSED)
{
Serial.printf("KeyCombo 1: %d | 2: %d\n", keyCount_Fac1, keyCount_Fac2);
if (keyCount_Fac1 == 2 && keyCount_Fac2 == 2)
{
Serial.println("KeyCombo: WiFi AP ON");
OverrideDisplay(sevenSeg_net, 5000);
toggleWiFiAP(false);
}
keyCount_Fac1 = 0;
keyCount_Fac2 = 0;
keyStatus_Fac1 = KEY_RELEASED;
keyStatus_Fac2 = KEY_RELEASED;
keyStatus_Fac3 = KEY_RELEASED;
}
}
void OverrideDisplay(const uint8_t *message, uint32_t time)
{
DisplayOverrideFlag = millis() + time;
DisplayOverrideValue = (uint8_t *)message;
}
uint32_t getESPChipID()
{
uint32_t chipId;
for (int i = 0; i < 17; i = i + 8)
{
chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i;
}
return chipId;
}
#
void LoRaPublish_callback()
{
StaticJsonDocument<200> doc;
doc["lipo"] = globals.battery_level;
doc["afact"] = PersistenceData.activeFaction;
doc["fac1"] = PersistenceData.faction_1_timer;
doc["fac2"] = PersistenceData.faction_2_timer;
doc["fac3"] = PersistenceData.faction_3_timer;
LoRa.beginPacket();
serializeJson(doc, LoRa);
LoRa.endPacket();
Serial.printf("Sendet LoRa-Status Package\n");
}

View File

@@ -0,0 +1,42 @@
#include "oled_display.h"
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RST);
void OLED_Init()
{
// reset OLED display via software
pinMode(OLED_RST, OUTPUT);
digitalWrite(OLED_RST, LOW);
delay(20);
digitalWrite(OLED_RST, HIGH);
// initialize OLED
Wire.begin(OLED_SDA, OLED_SCL);
if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3c, false, false))
{ // Address 0x3C for 128x32
Serial.println(F("SSD1306 allocation failed"));
}
display.clearDisplay();
display.setTextColor(WHITE);
display.setTextSize(1);
display.setCursor(0, 0);
display.print("DISPLAY INIT");
display.display();
}
void OLED_Process()
{
display.clearDisplay();
display.setCursor(0, 0);
display.printf("LiPo: %d%%\n", globals.battery_level);
display.print(PersistenceData.activeFaction == FACTION_1 ? "> " : " ");
display.printf("%-5s: %02d:%02d:%02d\n", FACTION_1_NAME, PersistenceData.faction_1_timer / 3600, (PersistenceData.faction_1_timer / 60) % 60, PersistenceData.faction_1_timer % 60);
display.print(PersistenceData.activeFaction == FACTION_2 ? "> " : " ");
display.printf("%-5s: %02d:%02d:%02d\n", FACTION_2_NAME, PersistenceData.faction_2_timer / 3600, (PersistenceData.faction_2_timer / 60) % 60, PersistenceData.faction_2_timer % 60);
display.print(PersistenceData.activeFaction == FACTION_3 ? "> " : " ");
display.printf("%-5s: %02d:%02d:%02d\n", FACTION_3_NAME, PersistenceData.faction_3_timer / 3600, (PersistenceData.faction_3_timer / 60) % 60, PersistenceData.faction_3_timer % 60);
display.display();
}

View File

@@ -0,0 +1,20 @@
#ifndef _OLED_DISPLAY_H_
#define _OLED_DISPLAY_H_
#include <Arduino.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "globals.h"
#include "config.h"
#define OLED_SDA 4
#define OLED_SCL 15
#define OLED_RST 16
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
void OLED_Init();
void OLED_Process();
#endif

209
Software/src/webui.cpp Normal file
View File

@@ -0,0 +1,209 @@
#include "webui.h"
AsyncWebServer webServer(80);
typedef enum
{
RESPMSG_HIDE,
RESPMSG_SUCCESS,
RESPMSG_INFO,
RESPMSG_WARNING,
RESPMSG_DANGER
} statusResponseMessage_Type_t;
char StatusResponseMessage[64];
statusResponseMessage_Type_t StatusResponseMessage_Type = RESPMSG_INFO;
String processor(const String &var);
void WebserverPOST_Callback(AsyncWebServerRequest *request);
void WebserverNotFound_Callback(AsyncWebServerRequest *request);
void Webserver_Callback(AsyncWebServerRequest *request);
void WebserverCommands_Callback(String input);
void initWebUI()
{
if (!LittleFS.begin())
{
Serial.println("An Error has occurred while mounting LittleFS");
return;
}
webServer.serveStatic("/static/", LittleFS, "/static/").setCacheControl("max-age=360000");
webServer.on("/", HTTP_GET, [](AsyncWebServerRequest *request)
{ request->redirect("/index.htm"); });
webServer.onNotFound(WebserverNotFound_Callback);
webServer.on("/index.htm", HTTP_GET, Webserver_Callback);
webServer.on("/index.htm", HTTP_POST, WebserverPOST_Callback);
webServer.begin();
}
String processor(const String &var)
{
if (var == "SHOW_DTC_TABLE")
return globals.systemStatus == sysStat_Error ? "" : "hidden";
if (var == "SHOW_RESP_MESSAGE")
return StatusResponseMessage_Type != RESPMSG_HIDE ? "" : "hidden";
if (var == "RESP_MESSAGE_TYPE")
{
switch (StatusResponseMessage_Type)
{
case RESPMSG_SUCCESS:
return "success";
case RESPMSG_INFO:
return "info";
case RESPMSG_WARNING:
return "warning";
case RESPMSG_DANGER:
return "danger";
default:
return "info";
}
}
if (var == "RESP_MESSAGE")
return String(StatusResponseMessage);
if (var == "BAT_REMAIN_CAPACITY")
return String(globals.battery_level);
if (var == "DEVICE_NAME")
return String(globals.DeviceName);
if (var == "BAT_VOLTAGE")
return String(globals.loadvoltage);
if (var == "DTC_TABLE")
{
String temp;
char buff_timestamp[16]; // Format: DD-hh:mm:ss:xxx
for (uint32_t i = 0; i < MAX_DTC_STORAGE; i++)
{
if (DTCStorage[i].Number > 0)
{
sprintf(buff_timestamp, "%02d-%02d:%02d:%02d:%03d",
DTCStorage[i].timestamp / 86400000, // Days
DTCStorage[i].timestamp / 360000 % 24, // Hours
DTCStorage[i].timestamp / 60000 % 60, // Minutes
DTCStorage[i].timestamp / 1000 % 60, // Seconds
DTCStorage[i].timestamp % 1000); // milliseconds
temp = "<tr><td>" + String(buff_timestamp);
temp = temp + "</td><td>" + String(DTCStorage[i].Number) + "</td><td>";
if (DTCStorage[i].active == DTC_ACTIVE)
temp = temp + "active";
else if (DTCStorage[i].active == DTC_PREVIOUS)
temp = temp + "previous";
else
temp = temp + "none";
temp = temp + "</td></tr>";
}
}
return temp;
}
if (var == "PLACEHOLDER")
return "placeholder";
if (var == "POINTS_FAC_1")
{
char buff[12];
snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_1_timer / 3600, (PersistenceData.faction_1_timer / 60) % 60, PersistenceData.faction_1_timer % 60);
return String(buff);
}
if (var == "POINTS_FAC_2")
{
char buff[12];
snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_2_timer / 3600, (PersistenceData.faction_2_timer / 60) % 60, PersistenceData.faction_2_timer % 60);
return String(buff);
}
if (var == "POINTS_FAC_3")
{
char buff[12];
snprintf(buff, 12, "%3d:%02d:%02d", PersistenceData.faction_3_timer / 3600, (PersistenceData.faction_3_timer / 60) % 60, PersistenceData.faction_3_timer % 60);
return String(buff);
}
if (var == "STATUS_FAC_1")
return PersistenceData.activeFaction == FACTION_1 ? "ACTIVE" : "INACTIVE";
if (var == "STATUS_FAC_2")
return PersistenceData.activeFaction == FACTION_2 ? "ACTIVE" : "INACTIVE";
if (var == "STATUS_FAC_3")
return PersistenceData.activeFaction == FACTION_3 ? "ACTIVE" : "INACTIVE";
if (var == "NAME_FAC_1")
return FACTION_1_NAME;
if (var == "NAME_FAC_2")
return FACTION_2_NAME;
if (var == "NAME_FAC_3")
return FACTION_3_NAME;
if (var == "TITLE")
return DEVICE_NAME;
if (var == "BATTERY_LEVEL")
{
return String(globals.battery_level);
}
if (var == "BATTERY_TYPE")
{
return String(BatteryString[ConfigData.batteryType]);
}
if (var == "BATTERY_VOLTAGE")
{
return String(globals.loadvoltage);
}
return String();
}
void Webserver_Callback(AsyncWebServerRequest *request)
{
request->send(LittleFS, "/index.htm", "text/html", false, processor);
StatusResponseMessage_Type = RESPMSG_HIDE;
}
void WebserverPOST_Callback(AsyncWebServerRequest *request)
{
int paramsNr = request->params();
for (int i = 0; i < paramsNr; i++)
{
AsyncWebParameter *p = request->getParam(i);
if (p->name() == "commandInput")
WebserverCommands_Callback(p->value());
}
request->send(LittleFS, "/index.htm", "text/html", false, processor);
}
void WebserverNotFound_Callback(AsyncWebServerRequest *request)
{
request->send(404, "text/html", "Not found");
}
void WebserverCommands_Callback(String input)
{
String command = input.substring(0, input.indexOf(' '));
command.toUpperCase();
StatusResponseMessage_Type = RESPMSG_HIDE;
if (command == "RESET")
{
strcpy(StatusResponseMessage, "Counter Reset done");
StatusResponseMessage_Type = RESPMSG_SUCCESS;
PersistenceData.faction_1_timer = 0;
PersistenceData.faction_2_timer = 0;
PersistenceData.faction_3_timer = 0;
PersistenceData.activeFaction = NONE;
}
}

15
Software/src/webui.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef _WEBUI_H_
#define _WEBUI_H_
#include <Arduino.h>
#include <FS.h>
#include <LittleFS.h>
#include <AsyncTCP.h>
#include <ESPAsyncWebServer.h>
#include "config.h"
#include "globals.h"
#include "dtc.h"
void initWebUI();
#endif