From cbdea582a776e73891d419ac66eac5dbdac8dd1a Mon Sep 17 00:00:00 2001 From: Stuart Pittaway <1201909+stuartpittaway@users.noreply.github.com> Date: Thu, 11 Feb 2021 11:53:34 +0000 Subject: [PATCH] Merge ESP32 enhancements back into ESP8266 code (#102) * Back port of ESP32 controller code to ESP8266 * Comment out STORAGE option on ESP8266 * Remove some ESP32 code from ESP8266 * Output fuse settings as part of build filenames * Set ATTINY to 2Mhz * Format MQTT messages for emoncms * Resolve issue #95 * Keep attempting reconnect to WIFI AP after network loss --- .github/workflows/main.yml | 8 +- ATTINYCellModule/.travis.yml | 67 -- ATTINYCellModule/buildscript.py | 17 +- ATTINYCellModule/include/defines.h | 9 +- ATTINYCellModule/include/diybms_attiny841.h | 6 +- ATTINYCellModule/platformio.ini | 38 +- ATTINYCellModule/src/diybms_attiny841.cpp | 9 + ATTINYCellModule/src/main.cpp | 24 +- ATTINYCellModule/src/packet_processor.cpp | 46 +- ESPController/.gitignore | 1 + ESPController/buildscript_versioning.py | 11 + ESPController/include/DIYBMSServer.h | 40 +- ESPController/include/HAL_ESP32.h | 66 -- ESPController/include/Rules.h | 7 +- ESPController/include/defines.h | 43 +- ESPController/platformio.ini | 9 +- .../prebuild_generate_embedded_files.py | 16 +- ESPController/src/DIYBMSServer.cpp | 373 +++++---- ESPController/src/HAL_ESP32.cpp | 186 ----- ESPController/src/HAL_ESP8266.cpp | 3 - ESPController/src/Rules.cpp | 20 +- ESPController/src/SoftAP.cpp | 17 + ESPController/src/main.cpp | 760 +++++++++--------- ESPController/web_src/default.htm | 84 +- ESPController/web_src/pagecode.js | 110 ++- README.md | 21 +- 26 files changed, 958 insertions(+), 1033 deletions(-) delete mode 100644 ATTINYCellModule/.travis.yml delete mode 100644 ESPController/include/HAL_ESP32.h delete mode 100644 ESPController/src/HAL_ESP32.cpp diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47a5787..18b4177 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -38,9 +38,9 @@ jobs: with: name: DIYBMS-Compiled-Module path: | - ./ATTINYCellModule/.pio/build/attiny841_V*/*.hex - ./ATTINYCellModule/.pio/build/attiny841_V*/*.elf - if-no-files-found: error + ./ATTINYCellModule/.pio/build/V*/*.hex + ./ATTINYCellModule/.pio/build/V*/*.elf + if-no-files-found: error job_build_controller_esp8266: @@ -110,7 +110,7 @@ jobs: run: | zip --junk-paths release.zip ./esp8266_d1mini/diybms_controller_firmware_espressif8266_esp8266_d1mini.bin zip --junk-paths release.zip ./esp8266_d1mini/diybms_controller_filesystemimage_espressif8266_esp8266_d1mini.bin - zip --junk-paths release.zip ./attiny841_V*/*.hex + zip --junk-paths release.zip ./V*/*.hex - name: Get current date id: date diff --git a/ATTINYCellModule/.travis.yml b/ATTINYCellModule/.travis.yml deleted file mode 100644 index 7c486f1..0000000 --- a/ATTINYCellModule/.travis.yml +++ /dev/null @@ -1,67 +0,0 @@ -# Continuous Integration (CI) is the practice, in software -# engineering, of merging all developer working copies with a shared mainline -# several times a day < https://docs.platformio.org/page/ci/index.html > -# -# Documentation: -# -# * Travis CI Embedded Builds with PlatformIO -# < https://docs.travis-ci.com/user/integration/platformio/ > -# -# * PlatformIO integration with Travis CI -# < https://docs.platformio.org/page/ci/travis.html > -# -# * User Guide for `platformio ci` command -# < https://docs.platformio.org/page/userguide/cmd_ci.html > -# -# -# Please choose one of the following templates (proposed below) and uncomment -# it (remove "# " before each line) or use own configuration according to the -# Travis CI documentation (see above). -# - - -# -# Template #1: General project. Test it using existing `platformio.ini`. -# - -# language: python -# python: -# - "2.7" -# -# sudo: false -# cache: -# directories: -# - "~/.platformio" -# -# install: -# - pip install -U platformio -# - platformio update -# -# script: -# - platformio run - - -# -# Template #2: The project is intended to be used as a library with examples. -# - -# language: python -# python: -# - "2.7" -# -# sudo: false -# cache: -# directories: -# - "~/.platformio" -# -# env: -# - PLATFORMIO_CI_SRC=path/to/test/file.c -# - PLATFORMIO_CI_SRC=examples/file.ino -# - PLATFORMIO_CI_SRC=path/to/test/directory -# -# install: -# - pip install -U platformio -# - platformio update -# -# script: -# - platformio ci --lib="." --board=ID_1 --board=ID_2 --board=ID_N diff --git a/ATTINYCellModule/buildscript.py b/ATTINYCellModule/buildscript.py index c34b7e4..e8b8509 100644 --- a/ATTINYCellModule/buildscript.py +++ b/ATTINYCellModule/buildscript.py @@ -1,11 +1,18 @@ Import("env") -# access to global construction environment +platform = env.PioPlatform() + #print(env) -# access to project construction environment -#print(projenv) +#print(platform) + my_flags = env.ParseFlags(env['BUILD_FLAGS']) +#print(my_flags) defines = {k: v for (k, v) in my_flags.get("CPPDEFINES")} -#print(defines.get("DIYBMSMODULEVERSION")) +print(defines.get("DIYBMSMODULEVERSION")) + +efuse=hex(int(env.GetProjectOption("board_fuses.efuse"), 2)).upper()[2:4] +hfuse=hex(int(env.GetProjectOption("board_fuses.hfuse"), 2)).upper()[2:4] +lfuse=hex(int(env.GetProjectOption("board_fuses.lfuse"), 2)).upper()[2:4] + +env.Replace(PROGNAME="module_fw_%s_%s_%s%s_e%s_h%s_l%s" % (env["PIOENV"],env.get("BOARD"), defines.get("DIYBMSMODULEVERSION"), "_SWAPR19R20" if "SWAPR19R20" in defines else "", efuse, hfuse, lfuse )) -env.Replace(PROGNAME="module_firmware_%s_%s%s" % (env["PIOENV"], defines.get("DIYBMSMODULEVERSION") , "_SWAPR19R20" if "SWAPR19R20" in defines else "" )) diff --git a/ATTINYCellModule/include/defines.h b/ATTINYCellModule/include/defines.h index c246a25..eaeaed3 100644 --- a/ATTINYCellModule/include/defines.h +++ b/ATTINYCellModule/include/defines.h @@ -19,20 +19,13 @@ Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK #if (!defined(DIYBMSMODULEVERSION)) -#error You need to enable one of the DIYBMSMODULEVERSION define statements +#error You need to specify the DIYBMSMODULEVERSION define #endif #if defined(DIYBMSMODULEVERSION) && DIYBMSMODULEVERSION > 430 #error Incorrect value for DIYBMSMODULEVERSION #endif -#if defined(DIYBMSMODULEVERSION) && DIYBMSMODULEVERSION < 430 - #define COMMS_BAUD_RATE 2400 -#endif - -#if defined(DIYBMSMODULEVERSION) && DIYBMSMODULEVERSION == 430 - #define COMMS_BAUD_RATE 2400 -#endif //This is where the data begins in EEPROM #define EEPROM_CONFIG_ADDRESS 0 diff --git a/ATTINYCellModule/include/diybms_attiny841.h b/ATTINYCellModule/include/diybms_attiny841.h index 98f0107..63dfe38 100644 --- a/ATTINYCellModule/include/diybms_attiny841.h +++ b/ATTINYCellModule/include/diybms_attiny841.h @@ -48,9 +48,9 @@ class DiyBMSATTiny841 // II-WWCCC TCCR1B = B00001011; - // Prescaler 8Mhz/64 = 125000 counts per second - // Call ISR 1000 times per second - OCR1A = 125; + // Prescaler 8Mhz/64 = 125000 counts per second, call ISR 1000 times per second + // Prescaler 2Mhz/64 = 31250 counts per second, call ISR 1000 times per second - roughly, as rounding error of 31.25 + OCR1A = (F_CPU/64)/1000; ResumePWM(); } diff --git a/ATTINYCellModule/platformio.ini b/ATTINYCellModule/platformio.ini index 5db6e4f..25cb28e 100644 --- a/ATTINYCellModule/platformio.ini +++ b/ATTINYCellModule/platformio.ini @@ -28,13 +28,14 @@ ; [Blue LED removed, resetable fuse fitted] ;** DO NOT FLASH V430 TO AN OLDER BOARD - THE ATTINY WILL BECOME UNUSABLE ** [platformio] -default_envs = attiny841_V400, attiny841_V410, attiny841_V420, attiny841_V420_SWAPR19R20, attiny841_V421, attiny841_V421_LTO +default_envs = V400, V410, V420, V420_SWAPR19R20, V421, V421_LTO +description=DIYBMS Cell monitoring module code [env] platform = atmelavr board = attiny841 framework = arduino -board_build.f_cpu = 8000000L +board_build.f_cpu = 2000000L board_build.core = tinymodern ; Use my latest version of the ATTINYCORE rather than the old PlatformIO version @@ -48,41 +49,42 @@ lib_deps = https://github.com/mike-matera/FastPID.git https://github.com/stuartpittaway/SerialEncoder upload_protocol = usbasp -; efuse = 1111 0100 = Enables SPM instruction +; Fuses E:F4, H:D6, L:62 +; lfuse = 0110 0010 = CKDIV8 (enabled) & Calibrated Internal 8MHz Oscillator ; hfuse = 1101 0110 = EESAVE & 1.8V BOD detection level -; lfuse = 1110 0010 = CKDIV8 & Calibrated Internal 8MHz Oscillator -board_fuses.lfuse = 0b11100010 +; efuse = 1111 0100 = Enables SPM instruction +board_fuses.lfuse = 0b01100010 board_fuses.hfuse = 0b11010110 board_fuses.efuse = 0b11110100 ;-B16 option needed for my USBASP programmer to slow it down! upload_flags = -Pusb - -Ulfuse:w:0b11100010:m + -Ulfuse:w:0b01100010:m -Uhfuse:w:0b11010110:m -Uefuse:w:0b11110100:m -[env:attiny841_V400] +[env:V400] ; 8 balance resistors marked 2R2 -build_flags=-DDIYBMSMODULEVERSION=400 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4150 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.40 +build_flags=-DDIYBMSMODULEVERSION=400 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4150 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.40 -DBAUD=2400 -[env:attiny841_V410] +[env:V410] ; 8 balance resistors marked 2R0 -build_flags=-DDIYBMSMODULEVERSION=410 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4150 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.00 +build_flags=-DDIYBMSMODULEVERSION=410 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4150 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.00 -DBAUD=2400 -[env:attiny841_V420] +[env:V420] ; 20 balance resistors marked 6R2 (6.2ohm) -build_flags=-DDIYBMSMODULEVERSION=420 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4050 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.96 +build_flags=-DDIYBMSMODULEVERSION=420 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4050 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.96 -DBAUD=2400 -[env:attiny841_V420_SWAPR19R20] +[env:V420_SWAPR19R20] ; 20 balance resistors marked 6R2 (6.2ohm) -build_flags=-DDIYBMSMODULEVERSION=420 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4050 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.96 -DSWAPR19R20=1 +build_flags=-DDIYBMSMODULEVERSION=420 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4050 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.96 -DSWAPR19R20=1 -DBAUD=2400 -[env:attiny841_V421] +[env:V421] ; 20 balance resistors marked 6R2 (6.2ohm) -build_flags=-DDIYBMSMODULEVERSION=421 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4050 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.96 +build_flags=-DDIYBMSMODULEVERSION=421 -DMV_PER_ADC=2.00 -DINT_BCOEFFICIENT=4050 -DEXT_BCOEFFICIENT=4150 -DLOAD_RESISTANCE=4.96 -DBAUD=2400 -[env:attiny841_V421_LTO] +[env:V421_LTO] ;DIYBMS V4.21 Design For Lithium Titanate Battery (LTO) ;Round PCB with bolt hole. 10 balance resistors marked 6.2OHM ;Uses resistor divider circuit of 18.2K/13K ohms = 1.24V output for 3.00V input -build_flags=-DDIYBMSMODULEVERSION=421 -DMV_PER_ADC=1.00 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=2.48 +build_flags=-DDIYBMSMODULEVERSION=421 -DMV_PER_ADC=1.00 -DINT_BCOEFFICIENT=3950 -DEXT_BCOEFFICIENT=3950 -DLOAD_RESISTANCE=2.48 -DBAUD=2400 diff --git a/ATTINYCellModule/src/diybms_attiny841.cpp b/ATTINYCellModule/src/diybms_attiny841.cpp index 67f572e..e2711a9 100644 --- a/ATTINYCellModule/src/diybms_attiny841.cpp +++ b/ATTINYCellModule/src/diybms_attiny841.cpp @@ -139,8 +139,17 @@ void DiyBMSATTiny841::BeginADCReading() //ADCSRA – ADC Control and Status Register A //Consider ADC sleep conversion mode? + +#if !(F_CPU == 8000000) //prescaler of 64 = 8MHz/64 = 125KHz. ADCSRA |= _BV(ADPS2) | _BV(ADPS1); // | _BV(ADPS0); +#endif + +#if !(F_CPU == 2000000) + //prescaler of 16 = 2MHz/16 = 125000. + ADCSRA |= _BV(ADPS2); +#endif + //adc_enable(); //Bit 4 – ADIF: ADC Interrupt Flag diff --git a/ATTINYCellModule/src/main.cpp b/ATTINYCellModule/src/main.cpp index b95d7e8..24ff476 100644 --- a/ATTINYCellModule/src/main.cpp +++ b/ATTINYCellModule/src/main.cpp @@ -7,7 +7,7 @@ DIYBMS V4.0 CELL MODULE FOR ATTINY841 -(c)2019/2020 Stuart Pittaway +(c)2019 to 2021 Stuart Pittaway COMPILE THIS CODE USING PLATFORM.IO @@ -28,20 +28,28 @@ IMPORTANT You need to configure the correct DIYBMSMODULEVERSION in defines.h file to build for your module +ATTINY chip frequency dropped to 2Mhz to comply with datasheet at low voltages (<2V) + +https://trolsoft.ru/en/uart-calc */ #define RX_BUFFER_SIZE 64 #include -#if !(F_CPU == 8000000) -#error Processor speed should be 8 Mhz internal +#if !(F_CPU == 2000000) +#error Processor speed should be 2Mhz #endif #if !defined(ATTINY_CORE) #error Expected ATTINYCORE #endif +#if !defined(BAUD) +#error Expected BAUD define +#endif + + //Our project code includes #include "defines.h" #include "settings.h" @@ -197,6 +205,14 @@ void setup() wdt_disable(); wdt_reset(); + //Boot up will be in 1Mhz CKDIV8 mode, swap to /4 to change speed to 2Mhz + //CCP – Configuration Change Protection Register + CCP = 0xD8; + //CLKPR – Clock Prescale Register + CLKPR = _BV(CLKPS1); + + //below 2Mhz is required for running ATTINY at low voltages (less than 2V) + //8 second between watchdogs DiyBMSATTiny841::SetWatchdog8sec(); @@ -230,7 +246,7 @@ void setup() StopBalance(); //Set up data handler - Serial.begin(COMMS_BAUD_RATE, SERIAL_8N1); + Serial.begin(BAUD, SERIAL_8N1); myPacketSerial.begin(&Serial, &onPacketReceived, sizeof(PacketStruct), SerialPacketReceiveBuffer, sizeof(SerialPacketReceiveBuffer)); } diff --git a/ATTINYCellModule/src/packet_processor.cpp b/ATTINYCellModule/src/packet_processor.cpp index 1adce55..84085b8 100644 --- a/ATTINYCellModule/src/packet_processor.cpp +++ b/ATTINYCellModule/src/packet_processor.cpp @@ -26,6 +26,10 @@ Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK #include "packet_processor.h" +//Enable this for debug/testing a single module will pretend to be an entire bank of 16 modules +//you are likely to get OOS errors when these are running in a string as the timings will be wrong +//#define FAKE_16_CELLS + //Returns TRUE if the internal thermistor is hotter than the required setting (or over max limit) bool PacketProcessor::BypassOverheatCheck() { @@ -61,7 +65,8 @@ void PacketProcessor::ADCReading(uint16_t value) { #if (defined(DIYBMSMODULEVERSION) && (DIYBMSMODULEVERSION == 420 && defined(SWAPR19R20))) //R19 and R20 swapped on V4.2 board, invert the thermistor reading - raw_adc_onboard_temperature = 1225 - value; + //Reverted back to 1000 base value to fix issue https://github.com/stuartpittaway/diyBMSv4Code/issues/95 + raw_adc_onboard_temperature = 1000 - value; #elif (defined(DIYBMSMODULEVERSION) && (DIYBMSMODULEVERSION == 430 && defined(SWAPR19R20))) //R19 and R20 swapped on V4.3 board (never publically released), invert the thermistor reading raw_adc_onboard_temperature = 1000 - value; @@ -111,6 +116,7 @@ void PacketProcessor::TakeAnAnalogueReading(uint8_t mode) //Run when a new packet is received over serial bool PacketProcessor::onPacketReceived(PacketStruct *receivebuffer) { + bool commandProcessed = false; //Temporary debug counter, see where packets get lost PacketReceivedCounter++; @@ -119,27 +125,37 @@ bool PacketProcessor::onPacketReceived(PacketStruct *receivebuffer) if (validateCRC == receivebuffer->crc) { - //TODO: We can probably get rid of mymoduleaddress - mymoduleaddress = receivebuffer->hops; - bool isPacketForMe = receivebuffer->start_address <= mymoduleaddress && receivebuffer->end_address >= mymoduleaddress; +#if defined(FAKE_16_CELLS) + uint8_t start=receivebuffer->hops; + uint8_t end=start+16; + for (size_t i = start; i < end; i++) + { +#endif - //Increment the hops no matter what (on valid CRC) - receivebuffer->hops++; + //TODO: We can probably get rid of mymoduleaddress + mymoduleaddress = receivebuffer->hops; - bool commandProcessed = false; - //It's a good packet - if (isPacketForMe) - { - commandProcessed = processPacket(receivebuffer); + bool isPacketForMe = receivebuffer->start_address <= mymoduleaddress && receivebuffer->end_address >= mymoduleaddress; - if (commandProcessed) + //Increment the hops no matter what (on valid CRC) + receivebuffer->hops++; + + commandProcessed = false; + //It's a good packet + if (isPacketForMe) { - //Set flag to indicate we processed packet (other modules may also do this) - receivebuffer->command = receivebuffer->command | B10000000; + commandProcessed = processPacket(receivebuffer); + + if (commandProcessed) + { + //Set flag to indicate we processed packet (other modules may also do this) + receivebuffer->command = receivebuffer->command | B10000000; + } } +#if defined(FAKE_16_CELLS) } - +#endif //Calculate new checksum over whole buffer (as hops as increased) receivebuffer->crc = CRC16::CalculateArray((unsigned char *)receivebuffer, sizeof(PacketStruct) - 2); diff --git a/ESPController/.gitignore b/ESPController/.gitignore index 146221e..500a077 100644 --- a/ESPController/.gitignore +++ b/ESPController/.gitignore @@ -7,3 +7,4 @@ web_temp EmbeddedFiles_AutoGenerated.h EmbeddedFiles_AutoGenerated_Blobs.h EmbeddedFiles_Defines.h +./ESPController/data/avr/* diff --git a/ESPController/buildscript_versioning.py b/ESPController/buildscript_versioning.py index f4e9319..3e27e59 100644 --- a/ESPController/buildscript_versioning.py +++ b/ESPController/buildscript_versioning.py @@ -51,6 +51,13 @@ f.write("LocalCompile") f.write("\";\n\n") + f.write("static const char GIT_VERSION_SHORT[] = \"") + if (git_sha!=None): + f.write(git_sha[-8:]) + else: + f.write("LocalCompile") + f.write("\";\n\n") + f.write("static const uint16_t GIT_VERSION_B1 = 0x") if (git_sha!=None): f.write(git_sha[32:36]) @@ -72,4 +79,8 @@ f.write(datetime.datetime.utcnow().isoformat()[:-3]+'Z') f.write("\";\n\n") + f.write("static const char COMPILE_DATE_TIME_SHORT[] = \"") + f.write(datetime.datetime.utcnow().strftime("%d %b %Y %H:%M")) + f.write("\";\n\n") + f.write("#endif") diff --git a/ESPController/include/DIYBMSServer.h b/ESPController/include/DIYBMSServer.h index 95fed54..1d1521b 100644 --- a/ESPController/include/DIYBMSServer.h +++ b/ESPController/include/DIYBMSServer.h @@ -18,6 +18,7 @@ #include +#include "defines.h" #include "Rules.h" #include "settings.h" #include "ArduinoJson.h" @@ -27,7 +28,16 @@ class DIYBMSServer { public: - static void StartServer(AsyncWebServer *webserver); + static void StartServer(AsyncWebServer *webserver, + diybms_eeprom_settings *mysettings, + sdcard_info (*sdcardcallback)(), + PacketRequestGenerator *prg, + PacketReceiveProcessor *pktreceiveproc, + ControllerState *controlState, + Rules *rules, + void (*sdcardaction_callback)(uint8_t action) + ); + static void generateUUID(); static void clearModuleValues(uint8_t module); @@ -35,6 +45,20 @@ class DIYBMSServer static AsyncWebServer *_myserver; static String UUIDString; + //Pointers to other classes (not always a good idea in static classes) + static sdcard_info (*_sdcardcallback)(); + static void (*_sdcardaction_callback)(uint8_t action); + static PacketRequestGenerator *_prg; + static PacketReceiveProcessor *_receiveProc; + static diybms_eeprom_settings *_mysettings; + static Rules *_rules; + static ControllerState *_controlState; + + + static void saveConfiguration() + { + Settings::WriteConfigToEEPROM((char *)_mysettings, sizeof(diybms_eeprom_settings), EEPROM_SETTINGS_START_ADDRESS); + } static void PrintStreamComma(AsyncResponseStream *response,const __FlashStringHelper *ifsh, uint32_t value); static void handleNotFound(AsyncWebServerRequest *request); @@ -42,7 +66,6 @@ class DIYBMSServer static void monitor3(AsyncWebServerRequest *request); //static void monitor(AsyncWebServerRequest *request); static void modules(AsyncWebServerRequest *request); - static void integration(AsyncWebServerRequest *request); static void identifyModule(AsyncWebServerRequest *request); static void GetRules(AsyncWebServerRequest *request); @@ -53,6 +76,7 @@ class DIYBMSServer static void settings(AsyncWebServerRequest *request); static void resetCounters(AsyncWebServerRequest *request); static void handleRestartController(AsyncWebServerRequest *request); + static void storage(AsyncWebServerRequest *request); static void saveSetting(AsyncWebServerRequest *request); static void saveInfluxDBSetting(AsyncWebServerRequest *request); @@ -61,21 +85,19 @@ class DIYBMSServer static void saveBankConfiguration(AsyncWebServerRequest *request); static void saveRuleConfiguration(AsyncWebServerRequest *request); static void saveNTP(AsyncWebServerRequest *request); + static void saveStorage(AsyncWebServerRequest *request); static void saveDisplaySetting(AsyncWebServerRequest *request); + static void sdMount(AsyncWebServerRequest *request); + static void sdUnmount(AsyncWebServerRequest *request); + static String uuidToString(uint8_t *uuidLocation); static void SetCacheAndETagGzip(AsyncWebServerResponse *response, String ETag); static void SetCacheAndETag(AsyncWebServerResponse *response, String ETag); }; -//TODO: Mixing of classes, static and extern is not great -extern PacketRequestGenerator prg; -extern PacketReceiveProcessor receiveProc; -extern diybms_eeprom_settings mysettings; -extern uint16_t ConfigHasChanged; -extern Rules rules; -extern ControllerState ControlState; + extern bool OutputsEnabled; extern bool InputsEnabled; diff --git a/ESPController/include/HAL_ESP32.h b/ESPController/include/HAL_ESP32.h deleted file mode 100644 index 955d01e..0000000 --- a/ESPController/include/HAL_ESP32.h +++ /dev/null @@ -1,66 +0,0 @@ -/* -THIS IS THE HARDWARE ABSTRACT LAYER -FOR DIYBMS ESP32 CONTROLLER PCB - THIS IS THE LARGER -PCB WITH RS485/CANBUS/TFT DISPLAY -*/ -#if defined(ESP32) - -#include -#include "driver/i2c.h" -#include "esp32-hal-i2c.h" - -//#define GREEN_LED 2 - -//0 is the BOOT button -#define RESET_WIFI_PIN 0 -#define PFC_INTERRUPT_PIN 33 - -#ifndef HAL_ESP32_H_ -#define HAL_ESP32_H_ - -//GPIO34 (input only pin) -#define TCA9534A_INTERRUPT_PIN 34 -#define TCA9534APWR_ADDRESS 0x38 -#define TCA9534APWR_INPUT 0x00 -#define TCA9534APWR_OUTPUT 0x01 -#define TCA9534APWR_POLARITY_INVERSION 0x02 -#define TCA9534APWR_CONFIGURATION 0x03 -#define TCA9534APWR_INPUTMASK B11100000 - -//GPIO39 (input only pin) -#define TCA6408_INTERRUPT_PIN 39 -#define TCA6408_ADDRESS 0x20 -#define TCA6408_INPUT 0x00 -#define TCA6408_OUTPUT 0x01 -#define TCA6408_POLARITY_INVERSION 0x02 -#define TCA6408_CONFIGURATION 0x03 - -#define TCA6408_INPUTMASK B00000011 - -// Derived classes -class HAL_ESP32 -{ -public: - void ConfigureI2C(void (*TCA6408Interrupt)(void),void (*TCA9534AInterrupt)(void)); - void SetOutputState(uint8_t outputId, RelayState state); - uint8_t ReadTCA6408InputRegisters(); - uint8_t ReadTCA9534InputRegisters(); - bool OutputsEnabled = false; - bool InputsEnabled = false; - void Led(uint8_t bits); - void ConfigurePins(); - -private: - //Private constructor (static class) - - //Copy of pin state for TCA9534 - uint8_t TCA9534APWR_Value; - //Copy of pin state for TCA6408 - uint8_t TCA6408_Value; - - esp_err_t writeByte(i2c_port_t i2c_num,uint8_t dev, uint8_t reg, uint8_t data); - uint8_t readByte(i2c_port_t i2c_num,uint8_t dev, uint8_t reg); -}; - -#endif -#endif \ No newline at end of file diff --git a/ESPController/include/Rules.h b/ESPController/include/Rules.h index f018656..ce04791 100644 --- a/ESPController/include/Rules.h +++ b/ESPController/include/Rules.h @@ -12,8 +12,8 @@ enum Rule : uint8_t BMSError = 1, Individualcellovervoltage = 2, Individualcellundervoltage = 3, - IndividualcellovertemperatureInternal = 4, - IndividualcellundertemperatureInternal = 5, + ModuleOverTemperatureInternal = 4, + ModuleUnderTemperatureInternal = 5, IndividualcellovertemperatureExternal = 6, IndividualcellundertemperatureExternal = 7, PackOverVoltage = 8, @@ -29,7 +29,8 @@ enum InternalWarningCode : uint8_t ModuleInconsistantBypassVoltage = 1, ModuleInconsistantBypassTemperature = 2, ModuleInconsistantCodeVersion = 3, - ModuleInconsistantBoardRevision = 4 + ModuleInconsistantBoardRevision = 4, + LoggingEnabledNoSDCard=5 }; enum InternalErrorCode : uint8_t diff --git a/ESPController/include/defines.h b/ESPController/include/defines.h index 444c89f..e25adcc 100644 --- a/ESPController/include/defines.h +++ b/ESPController/include/defines.h @@ -7,36 +7,13 @@ #ifndef DIYBMS_DEFINES_H_ #define DIYBMS_DEFINES_H_ -#if defined(ESP32) -//Data uses Rx2/TX2 and debug logs go to serial0 - USB -#define SERIAL_DATA Serial2 -#define SERIAL_DEBUG Serial -//Total number of cells a single controler can handle (memory limitation) -#define maximum_controller_cell_modules 250 - -enum RGBLED : uint8_t -{ - OFF = 0, - Blue = B00000001, - Red = B00000010, - Purple = B00000011, - Green = B00000100, - Cyan = B00000101, - Yellow = B00000110, - White = B00000111 -}; -#endif - -#if defined(ESP8266) #define SERIAL_DATA Serial #define SERIAL_DEBUG Serial1 -//Total number of cells a single controler can handle (memory limitation) +//Total number of cells a single controller can handle (memory limitation) #define maximum_controller_cell_modules 100 -#endif - //Maximum of 16 cell modules (don't change this!) number of cells to process in a single packet of data #define maximum_cell_modules_per_packet 16 @@ -45,7 +22,7 @@ enum RGBLED : uint8_t #define maximum_number_of_banks 16 //Version 4.XX of DIYBMS modules operate at 2400 baud -#define COMMS_BAUD_RATE 2400 +//#define COMMS_BAUD_RATE 2400 #define EEPROM_SETTINGS_START_ADDRESS 256 @@ -111,6 +88,9 @@ struct diybms_eeprom_settings bool daylight; //=false; char ntpServer[64 + 1]; // = "time.google.com"; + bool loggingEnabled; + uint16_t loggingFrequencySeconds; + //NOTE this array is subject to buffer overflow vulnerabilities! bool mqtt_enabled; uint16_t mqtt_port; @@ -127,8 +107,7 @@ struct diybms_eeprom_settings char influxdb_password[32 + 1]; }; -typedef union -{ +typedef union { float number; uint8_t bytes[4]; uint16_t word[2]; @@ -217,6 +196,16 @@ enum ControllerState : uint8_t Running = 255, }; +struct sdcard_info +{ + bool available; + uint32_t totalkilobytes; + uint32_t usedkilobytes; + uint32_t flash_totalkilobytes; + uint32_t flash_usedkilobytes; + +}; + //This holds all the cell information in a large array array extern CellModuleInfo cmi[maximum_controller_cell_modules]; diff --git a/ESPController/platformio.ini b/ESPController/platformio.ini index 89f6bab..6da11ff 100644 --- a/ESPController/platformio.ini +++ b/ESPController/platformio.ini @@ -22,10 +22,6 @@ extra_scripts = pre:prebuild_generate_integrity_hash.py pre:prebuild_generate_embedded_files.py - - - - [env:esp8266_d1mini] ; ESP8266 D1 MINI 4M bytes Flash buy from here https://amzn.to/3i1gPIz ; The MINI PRO version also works but is no longer required @@ -52,7 +48,7 @@ build_flags = -D NDEBUG -fno-exceptions -DCORE_DEBUG_LEVEL=0 - + -DCOMMS_BAUD_RATE=2400 ; 4MB board board_build.ldscript = eagle.flash.4m3m.ld @@ -62,9 +58,8 @@ lib_deps = https://github.com/marvinroger/async-mqtt-client.git https://github.com/me-no-dev/ESPAsyncWebServer.git https://github.com/bblanchon/ArduinoJson.git + https://github.com/stuartpittaway/SerialEncoder.git https://github.com/WereCatf/PCF8574_ESP.git https://github.com/PaulStoffregen/Time ESPAsyncUDP https://github.com/gmag11/NtpClient.git - https://github.com/stuartpittaway/SerialEncoder - diff --git a/ESPController/prebuild_generate_embedded_files.py b/ESPController/prebuild_generate_embedded_files.py index 223e326..faa44e7 100644 --- a/ESPController/prebuild_generate_embedded_files.py +++ b/ESPController/prebuild_generate_embedded_files.py @@ -9,16 +9,13 @@ Import("env") -def prepare_embedded_files(): +def prepare_embedded_files(data_dir, include_dir, filenamePrefix): #This routine takes every file in the web_temp folder and converts #to a byte array suitable for embedding into flash to avoid using SPIFF or LITTLEFS #it also generates checksums and file length variables to improve caching and speed print('prebuild_generate_embedded_files.py') - data_dir = os.path.join(env.get('PROJECT_DIR'), 'web_temp') - include_dir = os.path.join(env.get('PROJECT_DIR'), 'include') - if (os.path.exists(data_dir)==False or os.path.exists(include_dir)==False): raise Exception("Missing project folder - data or include folder") @@ -26,12 +23,12 @@ def prepare_embedded_files(): sha1sum = hashlib.sha1() - with open(os.path.join(include_dir,'EmbeddedFiles_AutoGenerated_Blobs.h'), 'w') as blobs, open(os.path.join(include_dir,'EmbeddedFiles_AutoGenerated.h'), 'w') as f: + with open(os.path.join(include_dir,filenamePrefix+'_Blobs.h'), 'w') as blobs, open(os.path.join(include_dir,filenamePrefix+'.h'), 'w') as f: blobs.write("// This is an automatically generated file, any changes will be overwritten on compiliation!\n") f.write("// This is an automatically generated file, any changes will be overwritten on compiliation!\n") - f.write("\n\n#ifndef EmbeddedFiles_AutoGenerated_H\n#define EmbeddedFiles_AutoGenerated_H\n\n") - f.write("\n#include \"EmbeddedFiles_AutoGenerated_Blobs.h\"\n"); + f.write("\n\n#ifndef "+filenamePrefix+"_H\n#define "+filenamePrefix+"_H\n\n") + f.write("\n#include \""+filenamePrefix+"_Blobs.h\"\n"); for file in all_files: print("Embedding {}".format(file)) @@ -86,4 +83,7 @@ def prepare_embedded_files(): f.write("#endif") -prepare_embedded_files() +prepare_embedded_files(os.path.join(env.get('PROJECT_DIR'), 'web_temp'), os.path.join(env.get('PROJECT_DIR'), 'include'), "EmbeddedFiles_AutoGenerated") + + +#prepare_embedded_files(os.path.join(env.get('PROJECT_DIR'), 'avr_firmware'), os.path.join(env.get('PROJECT_DIR'), 'include'), "AVRFirmware_AutoGenerated") diff --git a/ESPController/src/DIYBMSServer.cpp b/ESPController/src/DIYBMSServer.cpp index f489b23..fd4cdd2 100644 --- a/ESPController/src/DIYBMSServer.cpp +++ b/ESPController/src/DIYBMSServer.cpp @@ -27,16 +27,11 @@ Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK #include #include "FS.h" -#if defined(ESP8266) + #include "ESP8266TrueRandom.h" #include #include -#endif -#if defined(ESP32) -#include -#include "time.h" -#endif #include "defines.h" #include "DIYBMSServer.h" @@ -48,6 +43,14 @@ Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales (CC BY-NC-SA 2.0 UK AsyncWebServer *DIYBMSServer::_myserver; String DIYBMSServer::UUIDString; +sdcard_info (*DIYBMSServer::_sdcardcallback)() = 0; +void (*DIYBMSServer::_sdcardaction_callback)(uint8_t action) = 0; +PacketRequestGenerator *DIYBMSServer::_prg = 0; +PacketReceiveProcessor *DIYBMSServer::_receiveProc = 0; +diybms_eeprom_settings *DIYBMSServer::_mysettings = 0; +Rules *DIYBMSServer::_rules = 0; +ControllerState *DIYBMSServer::_controlState = 0; + #define REBOOT_COUNT_DOWN 2000 String DIYBMSServer::uuidToString(uint8_t *uuidLocation) @@ -79,16 +82,10 @@ void DIYBMSServer::generateUUID() { //SERIAL_DEBUG.print("generateUUID="); byte uuidNumber[16]; // UUIDs in binary form are 16 bytes long -#if defined(ESP8266) + ESP8266TrueRandom.uuid(uuidNumber); -#endif -#if defined(ESP32) - //ESP32 has inbuilt random number generator - //https://techtutorialsx.com/2017/12/22/esp32-arduino-random-number-generation/ - for (uint8_t x = 0; x < 16; x--) - uuidNumber[x] = random(0xFF); -#endif + UUIDString = uuidToString(uuidNumber); } @@ -132,14 +129,33 @@ void DIYBMSServer::SendFailure(AsyncWebServerRequest *request) request->send(500, "text/plain", "Failed"); } +void DIYBMSServer::sdMount(AsyncWebServerRequest *request) +{ + if (!validateXSS(request)) + return; + + (*DIYBMSServer::_sdcardaction_callback)(1); + + SendSuccess(request); +} +void DIYBMSServer::sdUnmount(AsyncWebServerRequest *request) +{ + if (!validateXSS(request)) + return; + + (*DIYBMSServer::_sdcardaction_callback)(0); + + SendSuccess(request); +} + void DIYBMSServer::resetCounters(AsyncWebServerRequest *request) { if (!validateXSS(request)) return; //Ask modules to reset bad packet counters - prg.sendBadPacketCounterReset(); - prg.sendResetBalanceCurrentCounter(); + _prg->sendBadPacketCounterReset(); + _prg->sendResetBalanceCurrentCounter(); for (uint8_t i = 0; i < maximum_controller_cell_modules; i++) { @@ -148,9 +164,9 @@ void DIYBMSServer::resetCounters(AsyncWebServerRequest *request) } //Reset internal counters on CONTROLLER - receiveProc.ResetCounters(); + _receiveProc->ResetCounters(); - prg.packetsGenerated = 0; + _prg->packetsGenerated = 0; SendSuccess(request); } @@ -163,22 +179,22 @@ void DIYBMSServer::saveDisplaySetting(AsyncWebServerRequest *request) if (request->hasParam("VoltageHigh", true)) { AsyncWebParameter *p1 = request->getParam("VoltageHigh", true); - mysettings.graph_voltagehigh = p1->value().toFloat(); + _mysettings->graph_voltagehigh = p1->value().toFloat(); } if (request->hasParam("VoltageLow", true)) { AsyncWebParameter *p1 = request->getParam("VoltageLow", true); - mysettings.graph_voltagelow = p1->value().toFloat(); + _mysettings->graph_voltagelow = p1->value().toFloat(); } //Validate high is greater than low - if (mysettings.graph_voltagelow > mysettings.graph_voltagehigh) + if (_mysettings->graph_voltagelow > _mysettings->graph_voltagehigh) { - mysettings.graph_voltagelow = 0; + _mysettings->graph_voltagelow = 0; } - Settings::WriteConfigToEEPROM((char *)&mysettings, sizeof(mysettings), EEPROM_SETTINGS_START_ADDRESS); + saveConfiguration(); SendSuccess(request); } @@ -191,46 +207,46 @@ void DIYBMSServer::saveInfluxDBSetting(AsyncWebServerRequest *request) if (request->hasParam("influxEnabled", true)) { AsyncWebParameter *p1 = request->getParam("influxEnabled", true); - mysettings.influxdb_enabled = p1->value().equals("on") ? true : false; + _mysettings->influxdb_enabled = p1->value().equals("on") ? true : false; } else { - mysettings.influxdb_enabled = false; + _mysettings->influxdb_enabled = false; } if (request->hasParam("influxPort", true)) { AsyncWebParameter *p1 = request->getParam("influxPort", true); - mysettings.influxdb_httpPort = p1->value().toInt(); + _mysettings->influxdb_httpPort = p1->value().toInt(); } if (request->hasParam("influxServer", true)) { AsyncWebParameter *p1 = request->getParam("influxServer", true); - p1->value().toCharArray(mysettings.influxdb_host, sizeof(mysettings.influxdb_host)); + p1->value().toCharArray(_mysettings->influxdb_host, sizeof(_mysettings->influxdb_host)); } if (request->hasParam("influxDatabase", true)) { AsyncWebParameter *p1 = request->getParam("influxDatabase", true); - p1->value().toCharArray(mysettings.influxdb_database, sizeof(mysettings.influxdb_database)); + p1->value().toCharArray(_mysettings->influxdb_database, sizeof(_mysettings->influxdb_database)); } if (request->hasParam("influxUsername", true)) { AsyncWebParameter *p1 = request->getParam("influxUsername", true); - p1->value().toCharArray(mysettings.influxdb_user, sizeof(mysettings.influxdb_user)); + p1->value().toCharArray(_mysettings->influxdb_user, sizeof(_mysettings->influxdb_user)); } if (request->hasParam("influxPassword", true)) { AsyncWebParameter *p1 = request->getParam("influxPassword", true); - p1->value().toCharArray(mysettings.influxdb_password, sizeof(mysettings.influxdb_password)); + p1->value().toCharArray(_mysettings->influxdb_password, sizeof(_mysettings->influxdb_password)); } - Settings::WriteConfigToEEPROM((char *)&mysettings, sizeof(mysettings), EEPROM_SETTINGS_START_ADDRESS); + saveConfiguration(); - ConfigHasChanged = REBOOT_COUNT_DOWN; + //ConfigHasChanged = REBOOT_COUNT_DOWN; SendSuccess(request); } @@ -248,10 +264,10 @@ void DIYBMSServer::saveRuleConfiguration(AsyncWebServerRequest *request) { AsyncWebParameter *p1 = request->getParam(name.c_str(), true, false); //Default - mysettings.relaytype[i] = RelayType::RELAY_STANDARD; + _mysettings->relaytype[i] = RelayType::RELAY_STANDARD; if (p1->value().equals("Pulse")) { - mysettings.relaytype[i] = RelayType::RELAY_PULSE; + _mysettings->relaytype[i] = RelayType::RELAY_PULSE; } } } @@ -265,10 +281,10 @@ void DIYBMSServer::saveRuleConfiguration(AsyncWebServerRequest *request) { AsyncWebParameter *p1 = request->getParam(name.c_str(), true, false); //Default - mysettings.rulerelaydefault[i] = RelayState::RELAY_OFF; + _mysettings->rulerelaydefault[i] = RelayState::RELAY_OFF; if (p1->value().equals("On")) { - mysettings.rulerelaydefault[i] = RelayState::RELAY_ON; + _mysettings->rulerelaydefault[i] = RelayState::RELAY_ON; } } } @@ -284,7 +300,7 @@ void DIYBMSServer::saveRuleConfiguration(AsyncWebServerRequest *request) if (request->hasParam(name, true)) { AsyncWebParameter *p1 = request->getParam(name, true); - mysettings.rulevalue[rule] = p1->value().toInt(); + _mysettings->rulevalue[rule] = p1->value().toInt(); } //TODO: This STRING doesnt work properly if its on a single line! @@ -294,7 +310,7 @@ void DIYBMSServer::saveRuleConfiguration(AsyncWebServerRequest *request) if (request->hasParam(hname, true)) { AsyncWebParameter *p1 = request->getParam(hname, true); - mysettings.rulehysteresis[rule] = p1->value().toInt(); + _mysettings->rulehysteresis[rule] = p1->value().toInt(); } //Rule/relay processing @@ -308,21 +324,45 @@ void DIYBMSServer::saveRuleConfiguration(AsyncWebServerRequest *request) if (request->hasParam(name, true)) { AsyncWebParameter *p1 = request->getParam(name, true); - mysettings.rulerelaystate[rule][i] = p1->value().equals("X") ? RELAY_X : p1->value().equals("On") ? RelayState::RELAY_ON - : RelayState::RELAY_OFF; + _mysettings->rulerelaystate[rule][i] = p1->value().equals("X") ? RELAY_X : p1->value().equals("On") ? RelayState::RELAY_ON : RelayState::RELAY_OFF; } } //Reset state of rules after updating the new values for (int8_t r = 0; r < RELAY_RULES; r++) { - rules.rule_outcome[r] = false; + _rules->rule_outcome[r] = false; } } - //RELAY_TOTAL + saveConfiguration(); + + SendSuccess(request); +} + +void DIYBMSServer::saveStorage(AsyncWebServerRequest *request) +{ + if (!validateXSS(request)) + return; - Settings::WriteConfigToEEPROM((char *)&mysettings, sizeof(mysettings), EEPROM_SETTINGS_START_ADDRESS); + if (request->hasParam("loggingEnabled", true)) + { + AsyncWebParameter *p1 = request->getParam("loggingEnabled", true); + _mysettings->loggingEnabled = p1->value().equals("on") ? true : false; + } + + if (request->hasParam("loggingFreq", true)) + { + AsyncWebParameter *p1 = request->getParam("loggingFreq", true); + _mysettings->loggingFrequencySeconds = p1->value().toInt(); + //Validate + if (_mysettings->loggingFrequencySeconds < 15 || _mysettings->loggingFrequencySeconds > 600) + { + _mysettings->loggingFrequencySeconds = 15; + } + } + + saveConfiguration(); SendSuccess(request); } @@ -335,31 +375,31 @@ void DIYBMSServer::saveNTP(AsyncWebServerRequest *request) if (request->hasParam("NTPServer", true)) { AsyncWebParameter *p1 = request->getParam("NTPServer", true); - p1->value().toCharArray(mysettings.ntpServer, sizeof(mysettings.ntpServer)); + p1->value().toCharArray(_mysettings->ntpServer, sizeof(_mysettings->ntpServer)); } if (request->hasParam("NTPZoneHour", true)) { AsyncWebParameter *p1 = request->getParam("NTPZoneHour", true); - mysettings.timeZone = p1->value().toInt(); + _mysettings->timeZone = p1->value().toInt(); } if (request->hasParam("NTPZoneMin", true)) { AsyncWebParameter *p1 = request->getParam("NTPZoneMin", true); - mysettings.minutesTimeZone = p1->value().toInt(); + _mysettings->minutesTimeZone = p1->value().toInt(); } - mysettings.daylight = false; + _mysettings->daylight = false; if (request->hasParam("NTPDST", true)) { AsyncWebParameter *p1 = request->getParam("NTPDST", true); - mysettings.daylight = p1->value().equals("on") ? true : false; + _mysettings->daylight = p1->value().equals("on") ? true : false; } - Settings::WriteConfigToEEPROM((char *)&mysettings, sizeof(mysettings), EEPROM_SETTINGS_START_ADDRESS); + saveConfiguration(); - ConfigHasChanged = REBOOT_COUNT_DOWN; + //ConfigHasChanged = REBOOT_COUNT_DOWN; SendSuccess(request); } @@ -385,9 +425,9 @@ void DIYBMSServer::saveBankConfiguration(AsyncWebServerRequest *request) if (totalSeriesModules * totalBanks <= maximum_controller_cell_modules) { - mysettings.totalNumberOfSeriesModules = totalSeriesModules; - mysettings.totalNumberOfBanks = totalBanks; - Settings::WriteConfigToEEPROM((char *)&mysettings, sizeof(mysettings), EEPROM_SETTINGS_START_ADDRESS); + _mysettings->totalNumberOfSeriesModules = totalSeriesModules; + _mysettings->totalNumberOfBanks = totalBanks; + saveConfiguration(); SendSuccess(request); } @@ -405,50 +445,49 @@ void DIYBMSServer::saveMQTTSetting(AsyncWebServerRequest *request) if (request->hasParam("mqttEnabled", true)) { AsyncWebParameter *p1 = request->getParam("mqttEnabled", true); - mysettings.mqtt_enabled = p1->value().equals("on") ? true : false; + _mysettings->mqtt_enabled = p1->value().equals("on") ? true : false; } else { - mysettings.mqtt_enabled = false; + _mysettings->mqtt_enabled = false; } if (request->hasParam("mqttTopic", true)) { AsyncWebParameter *p1 = request->getParam("mqttTopic", true); - p1->value().toCharArray(mysettings.mqtt_topic, sizeof(mysettings.mqtt_topic)); + p1->value().toCharArray(_mysettings->mqtt_topic, sizeof(_mysettings->mqtt_topic)); } else { - sprintf(mysettings.mqtt_topic, "diybms"); + sprintf(_mysettings->mqtt_topic, "diybms"); } if (request->hasParam("mqttPort", true)) { AsyncWebParameter *p1 = request->getParam("mqttPort", true); - mysettings.mqtt_port = p1->value().toInt(); + _mysettings->mqtt_port = p1->value().toInt(); } if (request->hasParam("mqttServer", true)) { AsyncWebParameter *p1 = request->getParam("mqttServer", true); - p1->value().toCharArray(mysettings.mqtt_server, sizeof(mysettings.mqtt_server)); + p1->value().toCharArray(_mysettings->mqtt_server, sizeof(_mysettings->mqtt_server)); } if (request->hasParam("mqttUsername", true)) { AsyncWebParameter *p1 = request->getParam("mqttUsername", true); - p1->value().toCharArray(mysettings.mqtt_username, sizeof(mysettings.mqtt_username)); + p1->value().toCharArray(_mysettings->mqtt_username, sizeof(_mysettings->mqtt_username)); } if (request->hasParam("mqttPassword", true)) { AsyncWebParameter *p1 = request->getParam("mqttPassword", true); - p1->value().toCharArray(mysettings.mqtt_password, sizeof(mysettings.mqtt_password)); + p1->value().toCharArray(_mysettings->mqtt_password, sizeof(_mysettings->mqtt_password)); } - Settings::WriteConfigToEEPROM((char *)&mysettings, sizeof(mysettings), EEPROM_SETTINGS_START_ADDRESS); + saveConfiguration(); - ConfigHasChanged = REBOOT_COUNT_DOWN; SendSuccess(request); } @@ -461,23 +500,23 @@ void DIYBMSServer::saveGlobalSetting(AsyncWebServerRequest *request) { AsyncWebParameter *p1 = request->getParam("BypassOverTempShutdown", true); - mysettings.BypassOverTempShutdown = p1->value().toInt(); + _mysettings->BypassOverTempShutdown = p1->value().toInt(); AsyncWebParameter *p2 = request->getParam("BypassThresholdmV", true); - mysettings.BypassThresholdmV = p2->value().toInt(); + _mysettings->BypassThresholdmV = p2->value().toInt(); - Settings::WriteConfigToEEPROM((char *)&mysettings, sizeof(mysettings), EEPROM_SETTINGS_START_ADDRESS); + saveConfiguration(); - prg.sendSaveGlobalSetting(mysettings.BypassThresholdmV, mysettings.BypassOverTempShutdown); + _prg->sendSaveGlobalSetting(_mysettings->BypassThresholdmV, _mysettings->BypassOverTempShutdown); - uint8_t totalModules = mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules; + uint8_t totalModules = _mysettings->totalNumberOfBanks * _mysettings->totalNumberOfSeriesModules; for (uint8_t i = 0; i < totalModules; i++) { if (cmi[i].valid) { - cmi[i].BypassThresholdmV = mysettings.BypassThresholdmV; - cmi[i].BypassOverTempShutdown = mysettings.BypassOverTempShutdown; + cmi[i].BypassThresholdmV = _mysettings->BypassThresholdmV; + cmi[i].BypassOverTempShutdown = _mysettings->BypassOverTempShutdown; } } @@ -544,7 +583,7 @@ void DIYBMSServer::saveSetting(AsyncWebServerRequest *request) Calibration = p1->value().toFloat(); } - prg.sendSaveSetting(m, BypassThresholdmV, BypassOverTempShutdown, Calibration); + _prg->sendSaveSetting(m, BypassThresholdmV, BypassOverTempShutdown, Calibration); clearModuleValues(m); @@ -578,30 +617,16 @@ void DIYBMSServer::GetRules(AsyncWebServerRequest *request) DynamicJsonDocument doc(2048); JsonObject root = doc.to(); -#if defined(ESP8266) root["timenow"] = (hour() * 60) + minute(); -#endif - -#if defined(ESP32) - struct tm timeinfo; - if (!getLocalTime(&timeinfo)) - { - root["timenow"] = 0; - } - else - { - root["timenow"] = (timeinfo.tm_hour * 60) + timeinfo.tm_min; - } -#endif root["OutputsEnabled"] = OutputsEnabled; root["InputsEnabled"] = InputsEnabled; - root["ControlState"] = ControlState; + root["ControlState"] = (*_controlState); JsonArray defaultArray = root.createNestedArray("relaydefault"); for (uint8_t relay = 0; relay < RELAY_TOTAL; relay++) { - switch (mysettings.rulerelaydefault[relay]) + switch (_mysettings->rulerelaydefault[relay]) { case RELAY_OFF: defaultArray.add(false); @@ -618,7 +643,7 @@ void DIYBMSServer::GetRules(AsyncWebServerRequest *request) JsonArray typeArray = root.createNestedArray("relaytype"); for (uint8_t relay = 0; relay < RELAY_TOTAL; relay++) { - switch (mysettings.relaytype[relay]) + switch (_mysettings->relaytype[relay]) { case RELAY_STANDARD: typeArray.add("Std"); @@ -637,14 +662,14 @@ void DIYBMSServer::GetRules(AsyncWebServerRequest *request) for (uint8_t r = 0; r < RELAY_RULES; r++) { JsonObject rule = bankArray.createNestedObject(); - rule["value"] = mysettings.rulevalue[r]; - rule["hysteresis"] = mysettings.rulehysteresis[r]; - rule["triggered"] = rules.rule_outcome[r]; + rule["value"] = _mysettings->rulevalue[r]; + rule["hysteresis"] = _mysettings->rulehysteresis[r]; + rule["triggered"] = _rules->rule_outcome[r]; JsonArray data = rule.createNestedArray("relays"); for (uint8_t relay = 0; relay < RELAY_TOTAL; relay++) { - switch (mysettings.rulerelaystate[r][relay]) + switch (_mysettings->rulerelaystate[r][relay]) { case RELAY_OFF: data.add(false); @@ -676,32 +701,53 @@ void DIYBMSServer::settings(AsyncWebServerRequest *request) //settings["Version"] = String(GIT_VERSION); //settings["CompileDate"] = String(COMPILE_DATE_TIME); - settings["totalnumberofbanks"] = mysettings.totalNumberOfBanks; - settings["totalseriesmodules"] = mysettings.totalNumberOfSeriesModules; + settings["totalnumberofbanks"] = _mysettings->totalNumberOfBanks; + settings["totalseriesmodules"] = _mysettings->totalNumberOfSeriesModules; - settings["bypassthreshold"] = mysettings.BypassThresholdmV; - settings["bypassovertemp"] = mysettings.BypassOverTempShutdown; + settings["bypassthreshold"] = _mysettings->BypassThresholdmV; + settings["bypassovertemp"] = _mysettings->BypassOverTempShutdown; - settings["NTPServerName"] = mysettings.ntpServer; - settings["TimeZone"] = mysettings.timeZone; - settings["MinutesTimeZone"] = mysettings.minutesTimeZone; - settings["DST"] = mysettings.daylight; + settings["NTPServerName"] = _mysettings->ntpServer; + settings["TimeZone"] = _mysettings->timeZone; + settings["MinutesTimeZone"] = _mysettings->minutesTimeZone; + settings["DST"] = _mysettings->daylight; settings["FreeHeap"] = ESP.getFreeHeap(); settings["FreeBlockSize"] = ESP.getMaxFreeBlockSize(); -#if defined(ESP8266) settings["now"] = now(); -#endif -#if defined(ESP32) - time_t now; - if (time(&now)) + response->addHeader("Cache-Control", "no-store"); + + serializeJson(doc, *response); + request->send(response); +} + +void DIYBMSServer::storage(AsyncWebServerRequest *request) +{ + AsyncResponseStream *response = + request->beginResponseStream("application/json"); + + DynamicJsonDocument doc(2048); + JsonObject root = doc.to(); + + JsonObject settings = root.createNestedObject("storage"); + + settings["enabled"] = _mysettings->loggingEnabled; + settings["frequency"] = _mysettings->loggingFrequencySeconds; + + if (DIYBMSServer::_sdcardcallback != 0) { - settings["now"] = now; - } -#endif + //Get data from main.cpp + sdcard_info info = (*DIYBMSServer::_sdcardcallback)(); + + settings["sdcard"] = info.available; + settings["sdcard_total"] = info.totalkilobytes; + settings["sdcard_used"] = info.usedkilobytes; + settings["flash_total"] = info.flash_totalkilobytes; + settings["flash_used"] = info.flash_usedkilobytes; + } response->addHeader("Cache-Control", "no-store"); serializeJson(doc, *response); @@ -717,22 +763,22 @@ void DIYBMSServer::integration(AsyncWebServerRequest *request) JsonObject root = doc.to(); JsonObject mqtt = root.createNestedObject("mqtt"); - mqtt["enabled"] = mysettings.mqtt_enabled; - mqtt["topic"] = mysettings.mqtt_topic; - mqtt["port"] = mysettings.mqtt_port; - mqtt["server"] = mysettings.mqtt_server; - mqtt["username"] = mysettings.mqtt_username; + mqtt["enabled"] = _mysettings->mqtt_enabled; + mqtt["topic"] = _mysettings->mqtt_topic; + mqtt["port"] = _mysettings->mqtt_port; + mqtt["server"] = _mysettings->mqtt_server; + mqtt["username"] = _mysettings->mqtt_username; //We don't output the password in the json file as this could breach security - //mqtt["password"] =mysettings.mqtt_password; + //mqtt["password"] =_mysettings->mqtt_password; JsonObject influxdb = root.createNestedObject("influxdb"); - influxdb["enabled"] = mysettings.influxdb_enabled; - influxdb["port"] = mysettings.influxdb_httpPort; - influxdb["server"] = mysettings.influxdb_host; - influxdb["database"] = mysettings.influxdb_database; - influxdb["username"] = mysettings.influxdb_user; + influxdb["enabled"] = _mysettings->influxdb_enabled; + influxdb["port"] = _mysettings->influxdb_httpPort; + influxdb["server"] = _mysettings->influxdb_host; + influxdb["database"] = _mysettings->influxdb_database; + influxdb["username"] = _mysettings->influxdb_user; //We don't output the password in the json file as this could breach security - //influxdb["password"] = mysettings.influxdb_password; + //influxdb["password"] = _mysettings->influxdb_password; serializeJson(doc, *response); request->send(response); @@ -745,14 +791,14 @@ void DIYBMSServer::identifyModule(AsyncWebServerRequest *request) AsyncWebParameter *cellid = request->getParam("c", false); uint8_t c = cellid->value().toInt(); - if (c > mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules) + if (c > _mysettings->totalNumberOfBanks * _mysettings->totalNumberOfSeriesModules) { request->send(500, "text/plain", "Wrong parameter bank"); return; } else { - prg.sendIdentifyModuleRequest(c); + _prg->sendIdentifyModuleRequest(c); SendSuccess(request); } } @@ -769,7 +815,7 @@ void DIYBMSServer::modules(AsyncWebServerRequest *request) AsyncWebParameter *cellid = request->getParam("c", false); uint8_t c = cellid->value().toInt(); - if (c > mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules) + if (c > _mysettings->totalNumberOfBanks * _mysettings->totalNumberOfSeriesModules) { request->send(500, "text/plain", "Wrong parameter bank"); return; @@ -777,7 +823,7 @@ void DIYBMSServer::modules(AsyncWebServerRequest *request) if (cmi[c].settingsCached == false) { - prg.sendGetSettingsRequest(c); + _prg->sendGetSettingsRequest(c); } AsyncResponseStream *response = request->beginResponseStream("application/json"); @@ -786,8 +832,8 @@ void DIYBMSServer::modules(AsyncWebServerRequest *request) JsonObject root = doc.to(); JsonObject settings = root.createNestedObject("settings"); - uint8_t b = c / mysettings.totalNumberOfSeriesModules; - uint8_t m = c - (b * mysettings.totalNumberOfSeriesModules); + uint8_t b = c / _mysettings->totalNumberOfSeriesModules; + uint8_t m = c - (b * _mysettings->totalNumberOfSeriesModules); settings["bank"] = b; settings["module"] = m; settings["id"] = c; @@ -824,7 +870,7 @@ void DIYBMSServer::monitor3(AsyncWebServerRequest *request) //DynamicJsonDocument doc(maximum_controller_cell_modules * 50); AsyncResponseStream *response = request->beginResponseStream("application/json"); - uint8_t totalModules = mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules; + uint8_t totalModules = _mysettings->totalNumberOfBanks * _mysettings->totalNumberOfSeriesModules; uint8_t comma = totalModules - 1; response->print("{\"badpacket\":["); @@ -897,47 +943,47 @@ void DIYBMSServer::PrintStreamComma(AsyncResponseStream *response, const __Flash void DIYBMSServer::monitor2(AsyncWebServerRequest *request) { - uint8_t totalModules = mysettings.totalNumberOfBanks * mysettings.totalNumberOfSeriesModules; + uint8_t totalModules = _mysettings->totalNumberOfBanks * _mysettings->totalNumberOfSeriesModules; const char comma = ','; const char *null = "null"; AsyncResponseStream *response = request->beginResponseStream("application/json"); - PrintStreamComma(response, F("{\"banks\":"), mysettings.totalNumberOfBanks); - PrintStreamComma(response, F("\"seriesmodules\":"), mysettings.totalNumberOfSeriesModules); - PrintStreamComma(response, F("\"sent\":"), prg.packetsGenerated); - PrintStreamComma(response, F("\"received\":"), receiveProc.packetsReceived); - PrintStreamComma(response, F("\"modulesfnd\":"), receiveProc.totalModulesFound); - PrintStreamComma(response, F("\"badcrc\":"), receiveProc.totalCRCErrors); - PrintStreamComma(response, F("\"ignored\":"), receiveProc.totalNotProcessedErrors); - PrintStreamComma(response, F("\"roundtrip\":"), receiveProc.packetTimerMillisecond); - PrintStreamComma(response, F("\"oos\":"), receiveProc.totalOutofSequenceErrors); + PrintStreamComma(response, F("{\"banks\":"), _mysettings->totalNumberOfBanks); + PrintStreamComma(response, F("\"seriesmodules\":"), _mysettings->totalNumberOfSeriesModules); + PrintStreamComma(response, F("\"sent\":"), _prg->packetsGenerated); + PrintStreamComma(response, F("\"received\":"), _receiveProc->packetsReceived); + PrintStreamComma(response, F("\"modulesfnd\":"), _receiveProc->totalModulesFound); + PrintStreamComma(response, F("\"badcrc\":"), _receiveProc->totalCRCErrors); + PrintStreamComma(response, F("\"ignored\":"), _receiveProc->totalNotProcessedErrors); + PrintStreamComma(response, F("\"roundtrip\":"), _receiveProc->packetTimerMillisecond); + PrintStreamComma(response, F("\"oos\":"), _receiveProc->totalOutofSequenceErrors); response->print(F("\"errors\":[")); - for (size_t i = 0; i < sizeof(rules.ErrorCodes); i++) + for (size_t i = 0; i < sizeof(_rules->ErrorCodes); i++) { - if (rules.ErrorCodes[i] != InternalErrorCode::NoError) + if (_rules->ErrorCodes[i] != InternalErrorCode::NoError) { //Comma if not zero if (i) response->print(comma); - response->print(rules.ErrorCodes[i]); + response->print(_rules->ErrorCodes[i]); } } response->print("],"); response->print(F("\"warnings\":[")); - for (size_t i = 0; i < sizeof(rules.WarningCodes); i++) + for (size_t i = 0; i < sizeof(_rules->WarningCodes); i++) { - if (rules.WarningCodes[i] != InternalWarningCode::NoWarning) + if (_rules->WarningCodes[i] != InternalWarningCode::NoWarning) { //Comma if not zero if (i) response->print(comma); - response->print(rules.WarningCodes[i]); + response->print(_rules->WarningCodes[i]); } } response->print("],"); @@ -1122,13 +1168,13 @@ void DIYBMSServer::monitor2(AsyncWebServerRequest *request) //bypasspwm response->print(F("\"bankv\":[")); - for (uint8_t i = 0; i < mysettings.totalNumberOfBanks; i++) + for (uint8_t i = 0; i < _mysettings->totalNumberOfBanks; i++) { //Comma if not zero if (i) response->print(comma); - response->print(rules.packvoltage[i]); + response->print(_rules->packvoltage[i]); } response->print("]"); @@ -1137,13 +1183,13 @@ void DIYBMSServer::monitor2(AsyncWebServerRequest *request) //bypasspwm response->print(F("\"voltrange\":[")); - for (uint8_t i = 0; i < mysettings.totalNumberOfBanks; i++) + for (uint8_t i = 0; i < _mysettings->totalNumberOfBanks; i++) { //Comma if not zero if (i) response->print(comma); - response->print(rules.VoltageRangeInBank(i)); + response->print(_rules->VoltageRangeInBank(i)); } response->print("]"); @@ -1182,10 +1228,10 @@ String DIYBMSServer::TemplateProcessor(const String &var) // const DEFAULT_GRAPH_MIN_VOLTAGE = %graph_voltagelow%; if (var == "graph_voltagehigh") - return String(mysettings.graph_voltagehigh); + return String(_mysettings->graph_voltagehigh); if (var == "graph_voltagelow") - return String(mysettings.graph_voltagelow); + return String(_mysettings->graph_voltagelow); if (var == "integrity_file_jquery_js") return String(integrity_file_jquery_js); @@ -1204,9 +1250,24 @@ void DIYBMSServer::SetCacheAndETag(AsyncWebServerResponse *response, String ETag response->addHeader("Cache-Control", "no-cache, max-age=86400"); } -void DIYBMSServer::StartServer(AsyncWebServer *webserver) +// Start Web Server (crazy amount of pointer params!) +void DIYBMSServer::StartServer(AsyncWebServer *webserver, + diybms_eeprom_settings *mysettings, + sdcard_info (*sdcardcallback)(), + PacketRequestGenerator *prg, + PacketReceiveProcessor *pktreceiveproc, + ControllerState *controlState, + Rules *rules, + void (*sdcardaction_callback)(uint8_t action)) { _myserver = webserver; + _prg = prg; + _controlState = controlState; + _rules = rules; + _sdcardcallback = sdcardcallback; + _mysettings = mysettings; + _receiveProc = pktreceiveproc; + _sdcardaction_callback = sdcardaction_callback; String cookieValue = "DIYBMS_XSS="; cookieValue += DIYBMSServer::UUIDString; @@ -1352,12 +1413,6 @@ void DIYBMSServer::StartServer(AsyncWebServer *webserver) } }); -//Put this last... -#if defined(ESP8266) - //_myserver->serveStatic("/", LittleFS, "/").setCacheControl("max-age=600"); -#else - //_myserver->serveStatic("/", SPIFFS, "/").setCacheControl("max-age=600"); -#endif //Read endpoints _myserver->on("/monitor2.json", HTTP_GET, DIYBMSServer::monitor2); @@ -1367,6 +1422,8 @@ void DIYBMSServer::StartServer(AsyncWebServer *webserver) _myserver->on("/identifyModule.json", HTTP_GET, DIYBMSServer::identifyModule); _myserver->on("/settings.json", HTTP_GET, DIYBMSServer::settings); _myserver->on("/rules.json", HTTP_GET, DIYBMSServer::GetRules); + _myserver->on("/storage.json", HTTP_GET, DIYBMSServer::storage); + //POST method endpoints _myserver->on("/savesetting.json", HTTP_POST, DIYBMSServer::saveSetting); @@ -1377,10 +1434,14 @@ void DIYBMSServer::StartServer(AsyncWebServer *webserver) _myserver->on("/saverules.json", HTTP_POST, DIYBMSServer::saveRuleConfiguration); _myserver->on("/saventp.json", HTTP_POST, DIYBMSServer::saveNTP); _myserver->on("/savedisplaysetting.json", HTTP_POST, DIYBMSServer::saveDisplaySetting); + _myserver->on("/savestorage.json", HTTP_POST, DIYBMSServer::saveStorage); _myserver->on("/resetcounters.json", HTTP_POST, DIYBMSServer::resetCounters); _myserver->on("/restartcontroller.json", HTTP_POST, DIYBMSServer::handleRestartController); + _myserver->on("/sdmount.json", HTTP_POST, DIYBMSServer::sdMount); + _myserver->on("/sdunmount.json", HTTP_POST, DIYBMSServer::sdUnmount); + _myserver->onNotFound(DIYBMSServer::handleNotFound); _myserver->begin(); } diff --git a/ESPController/src/HAL_ESP32.cpp b/ESPController/src/HAL_ESP32.cpp deleted file mode 100644 index fb9d5f9..0000000 --- a/ESPController/src/HAL_ESP32.cpp +++ /dev/null @@ -1,186 +0,0 @@ -#if defined(ESP32) -#include "defines.h" -#include "HAL_ESP32.h" - -uint8_t HAL_ESP32::readByte(i2c_port_t i2c_num, uint8_t dev, uint8_t reg) -{ - //We use the native i2c commands for ESP32 as the Arduino library - //seems to have issues with corrupting i2c data if used from multiple threads - uint8_t data; - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - //Select the correct register on the i2c device - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (dev << 1) | I2C_MASTER_WRITE, true); - i2c_master_write_byte(cmd, reg, true); - // Send repeated start, and read the register - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (dev << 1) | I2C_MASTER_READ, true); - //Read single byte and expect NACK in reply - i2c_master_read_byte(cmd, &data, i2c_ack_type_t::I2C_MASTER_NACK); - i2c_master_stop(cmd); - esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(100))); - i2c_cmd_link_delete(cmd); - return data; -} - -//i2c: Writes a single byte to a slave devices register -esp_err_t HAL_ESP32::writeByte(i2c_port_t i2c_num, uint8_t deviceAddress, uint8_t i2cregister, uint8_t data) -{ - //We use the native i2c commands for ESP32 as the Arduino library - //seems to have issues with corrupting i2c data if used from multiple threads - i2c_cmd_handle_t cmd = i2c_cmd_link_create(); - i2c_master_start(cmd); - i2c_master_write_byte(cmd, (deviceAddress << 1) | I2C_MASTER_WRITE, true); - i2c_master_write_byte(cmd, i2cregister, true); - i2c_master_write_byte(cmd, data, true); - i2c_master_stop(cmd); - esp_err_t ret = ESP_ERROR_CHECK_WITHOUT_ABORT(i2c_master_cmd_begin(i2c_num, cmd, pdMS_TO_TICKS(100))); - i2c_cmd_link_delete(cmd); - return ret; -} - -uint8_t HAL_ESP32::ReadTCA6408InputRegisters() -{ - TCA6408_Value = readByte(i2c_port_t::I2C_NUM_0, TCA6408_ADDRESS, TCA6408_INPUT); - return TCA6408_Value & TCA6408_INPUTMASK; -} - -uint8_t HAL_ESP32::ReadTCA9534InputRegisters() -{ - TCA9534APWR_Value = readByte(i2c_port_t::I2C_NUM_0, TCA9534APWR_ADDRESS, TCA9534APWR_INPUT); - return TCA9534APWR_Value & TCA9534APWR_INPUTMASK; -} - -void HAL_ESP32::SetOutputState(uint8_t outputId, RelayState state) -{ - if (OutputsEnabled) - { - SERIAL_DEBUG.print("SetOutputState "); - SERIAL_DEBUG.print(outputId); - SERIAL_DEBUG.print("="); - SERIAL_DEBUG.println(state); - - //Relays connected to TCA6408A - //P4 = RELAY1 (outputId=0) - //P5 = RELAY2 (outputId=1) - //P6 = RELAY3_SSR (outputId=2) - //P7 = EXT_IO_E (outputId=3) - - if (outputId <= 3) - { - TCA6408_Value = readByte(i2c_port_t::I2C_NUM_0, TCA6408_ADDRESS, TCA6408_INPUT); - uint8_t bit = outputId + 4; - TCA6408_Value = (state == RelayState::RELAY_ON) ? (TCA6408_Value | (1 << bit)) : (TCA6408_Value & ~(1 << bit)); - esp_err_t ret = writeByte(i2c_port_t::I2C_NUM_0, TCA6408_ADDRESS, TCA6408_OUTPUT, TCA6408_Value); - //TODO: Check return value - TCA6408_Value = readByte(i2c_port_t::I2C_NUM_0, TCA6408_ADDRESS, TCA6408_INPUT); - } - } -} - -void HAL_ESP32::Led(uint8_t bits) -{ - //Clear LED pins - TCA9534APWR_Value = TCA9534APWR_Value & B11111000; - //Set on - TCA9534APWR_Value = TCA9534APWR_Value | (bits & B00000111); - esp_err_t ret = writeByte(i2c_port_t::I2C_NUM_0, TCA9534APWR_ADDRESS, TCA9534APWR_OUTPUT, TCA9534APWR_Value); - //TODO: Check return value -} - -void HAL_ESP32::ConfigurePins() -{ - //GPIO39 is interrupt pin from TCA6408 (doesnt have pull up/down resistors) - pinMode(TCA6408_INTERRUPT_PIN, INPUT); - - //GPIO34 is interrupt pin from TCA9534A (doesnt have pull up/down resistors) - pinMode(TCA9534A_INTERRUPT_PIN, INPUT); - - //TOUCH_CHIP_SELECT - pinMode(4, OUTPUT); - digitalWrite(4,LOW); - - //SDCARD_CHIP_SELECT - pinMode(5, OUTPUT); - digitalWrite(5,LOW); -} - -void HAL_ESP32::ConfigureI2C(void (*TCA6408Interrupt)(void), void (*TCA9534AInterrupt)(void)) -{ - SERIAL_DEBUG.println("ConfigureI2C"); - - //SDA / SCL - //ESP32 = I2C0-SDA / I2C0-SCL - //I2C Bus 1: uses GPIO 27 (SDA) and GPIO 26 (SCL); - //I2C Bus 2: uses GPIO 33 (SDA) and GPIO 32 (SCL); - - // Initialize - i2c_config_t conf; - conf.mode = I2C_MODE_MASTER; - conf.sda_io_num = gpio_num_t::GPIO_NUM_27; - conf.scl_io_num = gpio_num_t::GPIO_NUM_26; - conf.sda_pullup_en = GPIO_PULLUP_DISABLE; - conf.scl_pullup_en = GPIO_PULLUP_DISABLE; - conf.master.clk_speed = 400000; - i2c_param_config(I2C_NUM_0, &conf); - i2c_driver_install(I2C_NUM_0, conf.mode, 0, 0, 0); - - // https://datasheet.lcsc.com/szlcsc/1809041633_Texas-Instruments-TCA9534APWR_C206010.pdf - // TCA9534APWR Remote 8-Bit I2C and Low-Power I/O Expander With Interrupt Output and Configuration Registers - // https://lcsc.com/product-detail/Interface-I-O-Expanders_Texas-Instruments-TCA9534APWR_C206010.html - // A0/A1/A2 are LOW, so i2c address is 0x38 - - //PINS - //P0= BLUE - //P1= RED - //P2= GREEN - //P3= DISPLAY BACKLIGHT LED - //P4= AVRISP RESET - //P5/P6/P7 = EXTRA I/O (on internal header breakout pins) - //INTERRUPT PIN = ESP32 IO34 - - //Config all OUTPUT - //BIT 76543210 - //PORT 76543210 - - //All off - esp_err_t ret = writeByte(i2c_port_t::I2C_NUM_0, TCA9534APWR_ADDRESS, TCA9534APWR_OUTPUT, 0); - - //0×03 Configuration, P5/6/7=inputs, others outputs (0=OUTPUT) - ret = writeByte(i2c_port_t::I2C_NUM_0, TCA9534APWR_ADDRESS, TCA9534APWR_CONFIGURATION, TCA9534APWR_INPUTMASK); - - //0×02 Polarity Inversion, zero = off - //writeByte(TCA9534APWR_ADDRESS, TCA9534APWR_POLARITY_INVERSION, 0); - TCA9534APWR_Value = readByte(i2c_port_t::I2C_NUM_0, TCA9534APWR_ADDRESS, TCA9534APWR_INPUT); - SERIAL_DEBUG.println("Found TCA9534APWR"); - - attachInterrupt(TCA9534A_INTERRUPT_PIN, TCA9534AInterrupt, FALLING); - - /* -Now for the TCA6408 -*/ - - //P0=EXT_IO_A - //P1=EXT_IO_B - //P2=EXT_IO_C - //P3=EXT_IO_D - //P4=RELAY 1 - //P5=RELAY 2 - //P6=RELAY 3 (SSR) - //P7=EXT_IO_E - - //Set ports to off before we set configuration - ret = writeByte(i2c_port_t::I2C_NUM_0, TCA6408_ADDRESS, TCA6408_OUTPUT, 0); - //Ports A/B inputs, C/D outputs, RELAY1/2/3/SPARE outputs - ret = writeByte(i2c_port_t::I2C_NUM_0, TCA6408_ADDRESS, TCA6408_CONFIGURATION, B00000011); - //ret =writeByte(i2c_port_t::I2C_NUM_0,TCA6408_ADDRESS, TCA6408_POLARITY_INVERSION, B00000000); - TCA6408_Value = readByte(i2c_port_t::I2C_NUM_0, TCA6408_ADDRESS, TCA6408_INPUT); - //TODO: Validate if there was a read error or not. - - OutputsEnabled = true; - InputsEnabled = true; - - attachInterrupt(TCA6408_INTERRUPT_PIN, TCA6408Interrupt, FALLING); -} - -#endif \ No newline at end of file diff --git a/ESPController/src/HAL_ESP8266.cpp b/ESPController/src/HAL_ESP8266.cpp index 7038834..76e13f9 100644 --- a/ESPController/src/HAL_ESP8266.cpp +++ b/ESPController/src/HAL_ESP8266.cpp @@ -8,8 +8,6 @@ //PCF8574P has an i2c address of 0x38 instead of the normal 0x20 -//IF YOUR CONTROLLER DOESN'T RECOGNISE THE PCF CHIP, CHANGE 0x38 to 0x20 BELOW... - //PCF857x pcf8574(0x38, &Wire); PCF857x pcf8574_0x20(0x20, &Wire); PCF857x pcf8574_0x38(0x38, &Wire); @@ -95,7 +93,6 @@ void HAL_ESP8266::ConfigureI2C(void (*ExternalInputInterrupt)(void)) //internal pullup-resistor on the interrupt line via ESP8266 pcf8574->resetInterruptPin(); - //TODO: Fix this for ESP32 different PIN attachInterrupt(digitalPinToInterrupt(PFC_INTERRUPT_PIN), ExternalInputInterrupt, FALLING); } diff --git a/ESPController/src/Rules.cpp b/ESPController/src/Rules.cpp index 9a33ef5..b0a5725 100644 --- a/ESPController/src/Rules.cpp +++ b/ESPController/src/Rules.cpp @@ -165,8 +165,8 @@ void Rules::RunRules( rule_outcome[Rule::Individualcellundervoltage] = false; rule_outcome[Rule::IndividualcellovertemperatureExternal] = false; rule_outcome[Rule::IndividualcellundertemperatureExternal] = false; - rule_outcome[Rule::IndividualcellovertemperatureInternal] = false; - rule_outcome[Rule::IndividualcellundertemperatureInternal] = false; + rule_outcome[Rule::ModuleOverTemperatureInternal] = false; + rule_outcome[Rule::ModuleUnderTemperatureInternal] = false; //Abort processing any more rules until controller is stable/running state return; @@ -249,27 +249,27 @@ void Rules::RunRules( //Internal temperatyre monitoring and rules //Doesn't cater for negative temperatures on rule (int8 vs uint32) - if (((uint8_t)highestInternalTemp > value[Rule::IndividualcellovertemperatureInternal]) && rule_outcome[Rule::IndividualcellovertemperatureInternal] == false) + if (((uint8_t)highestInternalTemp > value[Rule::ModuleOverTemperatureInternal]) && rule_outcome[Rule::ModuleOverTemperatureInternal] == false) { //Rule Individual cell over temperature (Internal probe) - rule_outcome[Rule::IndividualcellovertemperatureInternal] = true; + rule_outcome[Rule::ModuleOverTemperatureInternal] = true; } - else if (((uint8_t)highestInternalTemp < hysteresisvalue[Rule::IndividualcellovertemperatureInternal]) && rule_outcome[Rule::IndividualcellovertemperatureInternal] == true) + else if (((uint8_t)highestInternalTemp < hysteresisvalue[Rule::ModuleOverTemperatureInternal]) && rule_outcome[Rule::ModuleOverTemperatureInternal] == true) { //Rule Individual cell over temperature (Internal probe) - HYSTERESIS RESET - rule_outcome[Rule::IndividualcellovertemperatureInternal] = false; + rule_outcome[Rule::ModuleOverTemperatureInternal] = false; } //Doesn't cater for negative temperatures on rule (int8 vs uint32) - if (((uint8_t)lowestInternalTemp < value[Rule::IndividualcellundertemperatureInternal]) && rule_outcome[Rule::IndividualcellundertemperatureInternal] == false) + if (((uint8_t)lowestInternalTemp < value[Rule::ModuleUnderTemperatureInternal]) && rule_outcome[Rule::ModuleUnderTemperatureInternal] == false) { //Rule Individual cell UNDER temperature (Internal probe) - rule_outcome[Rule::IndividualcellundertemperatureInternal] = true; + rule_outcome[Rule::ModuleUnderTemperatureInternal] = true; } - else if (((uint8_t)lowestInternalTemp > hysteresisvalue[Rule::IndividualcellundertemperatureInternal]) && rule_outcome[Rule::IndividualcellundertemperatureInternal] == true) + else if (((uint8_t)lowestInternalTemp > hysteresisvalue[Rule::ModuleUnderTemperatureInternal]) && rule_outcome[Rule::ModuleUnderTemperatureInternal] == true) { //Rule Individual cell UNDER temperature (Internal probe) - HYSTERESIS RESET - rule_outcome[Rule::IndividualcellundertemperatureInternal] = false; + rule_outcome[Rule::ModuleUnderTemperatureInternal] = false; } diff --git a/ESPController/src/SoftAP.cpp b/ESPController/src/SoftAP.cpp index a425b03..2e41bc0 100644 --- a/ESPController/src/SoftAP.cpp +++ b/ESPController/src/SoftAP.cpp @@ -1,6 +1,7 @@ #include "defines.h" #include "SoftAP.h" #include "EmbeddedFiles_AutoGenerated.h" +#include wifi_eeprom_settings DIYBMSSoftAP::_config; @@ -110,6 +111,22 @@ void DIYBMSSoftAP::SetupAccessPoint(AsyncWebServer *webserver) _myserver->begin(); IPAddress IP = WiFi.softAPIP(); + + // Set up mDNS responder: + // - first argument is the domain name, in this example + // the fully-qualified domain name is "esp8266.local" + // - second argument is the IP address to advertise + // we send our IP address on the WiFi network + if (!MDNS.begin("diybms")) + { + //ESP_LOGE("Error setting up MDNS responder!"); + } + else + { + //ESP_LOGI("mDNS responder started"); + // Add service to MDNS-SD + MDNS.addService("http", "tcp", 80); + } SERIAL_DEBUG.print("Access point IP address: "); SERIAL_DEBUG.println(IP); } diff --git a/ESPController/src/main.cpp b/ESPController/src/main.cpp index b600508..543bd07 100644 --- a/ESPController/src/main.cpp +++ b/ESPController/src/main.cpp @@ -9,16 +9,9 @@ This is the code for the controller - it talks to the V4.X cell modules over isolated serial bus - This code runs on ESP-8266 WEMOS D1 PRO and compiles with VS CODE and PLATFORM IO environment + This code runs on ESP-8266 WEMOS D1 (Mini or Pro) and compiles with VS CODE and PLATFORM IO environment */ -/* -*** NOTE IF YOU GET ISSUES WHEN COMPILING IN PLATFORM.IO *** -ERROR: "ESP Async WebServer\src\WebHandlers.cpp:67:64: error: 'strftime' was not declared in this scope" -Delete the file \diyBMSv4\ESPController\.pio\libdeps\esp8266_d1minipro\Time\Time.h -The Time.h file in this library conflicts with the time.h file in the ESP core platform code -See reasons why here https://github.com/me-no-dev/ESPAsyncWebServer/issues/60 -*/ /* ESP8266 PINS D0 = GREEN_LED @@ -30,12 +23,8 @@ See reasons why here https://github.com/me-no-dev/ESPAsyncWebServer/issues/60 D7 = GPIO13 = RECEIVE SERIAL D8 = GPIO15 = TRANSMIT SERIAL - DIAGRAM - https://www.hackster.io/Aritro/getting-started-with-esp-nodemcu-using-arduinoide-aa7267 */ -// PacketSerial library takes 1691ms round trip with 8 modules, 212ms per module @ 2400baud - #include //#define PACKET_LOGGING_RECEIVE @@ -46,23 +35,13 @@ See reasons why here https://github.com/me-no-dev/ESPAsyncWebServer/issues/60 #include "FS.h" //Libraries just for ESP8266 -#if defined(ESP8266) + #include #include #include #include -#endif +#include -//Libraries just for ESP32 -#if defined(ESP32) -#include -#include -#include -#include "time.h" -#include -#endif - -//Shared libraries across processors #include #include #include @@ -73,23 +52,16 @@ See reasons why here https://github.com/me-no-dev/ESPAsyncWebServer/issues/60 #include -#if defined(ESP8266) #include "HAL_ESP8266.h" HAL_ESP8266 hal; -#endif - -#if defined(ESP32) -#include "HAL_ESP32.h" -HAL_ESP32 hal; -#endif #include "Rules.h" volatile bool emergencyStop = false; -volatile bool WifiDisconnected = true; Rules rules; +bool _sd_card_installed = false; diybms_eeprom_settings mysettings; uint16_t ConfigHasChanged = 0; @@ -101,109 +73,11 @@ bool previousRelayPulse[RELAY_TOTAL]; volatile enumInputState InputState[INPUTS_TOTAL]; -#if defined(ESP8266) bool NTPsyncEventTriggered = false; // True if a time even has been triggered NTPSyncEvent_t ntpEvent; // Last triggered event -#endif AsyncWebServer server(80); -#if defined(ESP32) -TaskHandle_t i2c_task_handle; -TaskHandle_t ledoff_task_handle; -QueueHandle_t queue_i2c; - -void QueueLED(uint8_t bits) -{ - i2cQueueMessage m; - //3 = LED - m.command = 0x03; - //Lowest 3 bits are RGB led GREEN/RED/BLUE - m.data = bits & B00000111; - xQueueSendToBack(queue_i2c, &m, 10 / portTICK_PERIOD_MS); -} - -void ledoff_task(void *param) -{ - while (true) - { - //Wait until this task is triggered https://www.freertos.org/ulTaskNotifyTake.html - ulTaskNotifyTake(pdTRUE, portMAX_DELAY); - //Wait 60ms - vTaskDelay(60 / portTICK_PERIOD_MS); - //LED OFF - QueueLED(RGBLED::OFF); - } -} - -// Handle all calls to i2c devices in this single task -// Provides thread safe mechanism to talk to i2c -void i2c_task(void *param) -{ - while (true) - { - i2cQueueMessage m; - - if (xQueueReceive(queue_i2c, &m, portMAX_DELAY) == pdPASS) - { - // do some i2c task - if (m.command == 0x01) - { - // Read ports A/B inputs (on TCA6408) - uint8_t v = hal.ReadTCA6408InputRegisters(); - InputState[0] = (v & B00000001) == 0 ? enumInputState::INPUT_LOW : enumInputState::INPUT_HIGH; - InputState[1] = (v & B00000010) == 0 ? enumInputState::INPUT_LOW : enumInputState::INPUT_HIGH; - - //Emergency Stop (J1) has triggered - if (InputState[0] == enumInputState::INPUT_HIGH) - { - emergencyStop = true; - } - } - - if (m.command == 0x02) - { - //Read ports - //The 9534 deals with internal LED outputs and spare IO on J10 - //P5/P6/P7 = EXTRA I/O (on internal header breakout pins J10) - uint8_t v = hal.ReadTCA9534InputRegisters(); - InputState[2] = (v & B00100000) == 0 ? enumInputState::INPUT_LOW : enumInputState::INPUT_HIGH; - InputState[3] = (v & B01000000) == 0 ? enumInputState::INPUT_LOW : enumInputState::INPUT_HIGH; - InputState[4] = (v & B10000000) == 0 ? enumInputState::INPUT_LOW : enumInputState::INPUT_HIGH; - } - - if (m.command == 0x03) - { - hal.Led(m.data); - } - - if (m.command >= 0xE0 && m.command <= 0xE0 + RELAY_TOTAL) - { - //Set state of relays - hal.SetOutputState(m.command - 0xe0, (RelayState)m.data); - } - } - } -} - -void IRAM_ATTR TCA6408Interrupt() -{ - i2cQueueMessage m; - m.command = 0x01; - m.data = 0; - xQueueSendToBackFromISR(queue_i2c, &m, NULL); -} - -void IRAM_ATTR TCA9534AInterrupt() -{ - i2cQueueMessage m; - m.command = 0x02; - m.data = 0; - xQueueSendToBackFromISR(queue_i2c, &m, NULL); -} -#endif - -#if defined(ESP8266) void IRAM_ATTR ExternalInputInterrupt() { if ((hal.ReadInputRegisters() & B00010000) == 0) @@ -212,7 +86,6 @@ void IRAM_ATTR ExternalInputInterrupt() emergencyStop = true; } } -#endif //This large array holds all the information about the modules //up to 4x16 @@ -240,10 +113,8 @@ uint8_t SerialPacketReceiveBuffer[2 * sizeof(PacketStruct)]; SerialEncoder myPacketSerial; -#if defined(ESP8266) WiFiEventHandler wifiConnectHandler; WiFiEventHandler wifiDisconnectHandler; -#endif Ticker myTimerRelay; Ticker myTimer; @@ -259,7 +130,7 @@ Ticker myTimerSwitchPulsedRelay; uint16_t sequence = 0; -ControllerState ControlState; +ControllerState ControlState = ControllerState::Unknown; bool OutputsEnabled; bool InputsEnabled; @@ -349,6 +220,25 @@ void dumpPacketToDebug(char indicator, PacketStruct *buffer) SERIAL_DEBUG.println(); } +const char *ControllerStateString(ControllerState value) +{ + switch (value) + { + case ControllerState::PowerUp: + return "PowerUp"; + case ControllerState::ConfigurationSoftAP: + return "ConfigurationSoftAP"; + case ControllerState::Stabilizing: + return "Stabilizing"; + case ControllerState::Running: + return "Running"; + case ControllerState::Unknown: + return "Unknown"; + } + + return "?"; +} + void SetControllerState(ControllerState newState) { if (ControlState != newState) @@ -357,57 +247,43 @@ void SetControllerState(ControllerState newState) SERIAL_DEBUG.println(""); SERIAL_DEBUG.print(F("** Controller changed to state = ")); - SERIAL_DEBUG.println(newState, HEX); + SERIAL_DEBUG.println(ControllerStateString(newState)); } } uint16_t minutesSinceMidnight() { -#if defined(ESP8266) return (hour() * 60) + minute(); -#endif - -#if defined(ESP32) - struct tm timeinfo; - if (!getLocalTime(&timeinfo)) - { - return 0; - } - else - { - return (timeinfo.tm_hour * 60) + timeinfo.tm_min; - } -#endif } -#if defined(ESP8266) void processSyncEvent(NTPSyncEvent_t ntpEvent) { if (ntpEvent < 0) { SERIAL_DEBUG.printf("Time Sync error: %d\n", ntpEvent); + /* if (ntpEvent == noResponse) - SERIAL_DEBUG.println(F("NTP server not reachable")); + SERIAL_DEBUG.println(F("NTP svr not reachable")); else if (ntpEvent == invalidAddress) - SERIAL_DEBUG.println(F("Invalid NTP server address")); + SERIAL_DEBUG.println(F("Invalid NTP svr address")); else if (ntpEvent == errorSending) SERIAL_DEBUG.println(F("Error sending request")); else if (ntpEvent == responseError) SERIAL_DEBUG.println(F("NTP response error")); + */ } else { if (ntpEvent == timeSyncd) { - SERIAL_DEBUG.print(F("Got NTP time")); + SERIAL_DEBUG.print(F("NTP time ")); time_t lastTime = NTP.getLastNTPSync(); SERIAL_DEBUG.println(NTP.getTimeDateString(lastTime)); setTime(lastTime); } } } -#endif void serviceReplyQueue() { @@ -429,10 +305,6 @@ void serviceReplyQueue() } else { -#if defined(ESP32) - //Error blue - QueueLED(RGBLED::Blue); -#endif SERIAL_DEBUG.print(F("*FAIL*")); dumpPacketToDebug('F', &ps); } @@ -441,12 +313,8 @@ void serviceReplyQueue() void onPacketReceived() { -#if defined(ESP8266) + hal.GreenLedOn(); -#endif -#if defined(ESP32) - QueueLED(RGBLED::Green); -#endif PacketStruct ps; memcpy(&ps, SerialPacketReceiveBuffer, sizeof(PacketStruct)); @@ -471,13 +339,7 @@ void onPacketReceived() //dumpPacketToDebug('Q', &ps); //#endif -#if defined(ESP8266) hal.GreenLedOff(); -#endif -#if defined(ESP32) - //Fire task to switch off LED in a few ms - xTaskNotify(ledoff_task_handle, 0x00, eNotifyAction::eNoAction); -#endif } void timerTransmitCallback() @@ -485,8 +347,8 @@ void timerTransmitCallback() if (requestQueue.isEmpty()) return; - // Called to transmit the next packet in the queue need to ensure this procedure is called more frequently than - // items are added into the queue + // Called to transmit the next packet in the queue need to ensure this procedure + // is called more frequently than items are added into the queue PacketStruct transmitBuffer; @@ -578,6 +440,11 @@ void ProcessRules() rules.ProcessBank(bank); } + if (mysettings.loggingEnabled && !_sd_card_installed) + { + rules.SetWarning(InternalWarningCode::LoggingEnabledNoSDCard); + } + if (rules.invalidModuleCount > 0) { //Some modules are not yet valid @@ -604,14 +471,6 @@ void ProcessRules() SetControllerState(ControllerState::Running); } } - -#if defined(ESP32) - if (emergencyStop) - { - //Lowest 3 bits are RGB led GREEN/RED/BLUE - QueueLED(RGBLED::Red); - } -#endif } void timerSwitchPulsedRelay() @@ -621,20 +480,11 @@ void timerSwitchPulsedRelay() { if (previousRelayPulse[y]) { -//We now need to rapidly turn off the relay after a fixed period of time (pulse mode) -//However we leave the relay and previousRelayState looking like the relay has triggered (it has!) -//to prevent multiple pulses being sent on each rule refresh -#if defined(ESP8266) - hal.SetOutputState(y, previousRelayState[y] == RelayState::RELAY_ON ? RelayState::RELAY_OFF : RelayState::RELAY_ON); -#endif + //We now need to rapidly turn off the relay after a fixed period of time (pulse mode) + //However we leave the relay and previousRelayState looking like the relay has triggered (it has!) + //to prevent multiple pulses being sent on each rule refresh -#if defined(ESP32) - i2cQueueMessage m; - //Different command for each relay - m.command = 0xE0 + y; - m.data = previousRelayState[y] == RelayState::RELAY_ON ? RelayState::RELAY_OFF : RelayState::RELAY_ON; - xQueueSendToBack(queue_i2c, &m, 10 / portTICK_PERIOD_MS); -#endif + hal.SetOutputState(y, previousRelayState[y] == RelayState::RELAY_ON ? RelayState::RELAY_OFF : RelayState::RELAY_ON); previousRelayPulse[y] = false; } @@ -707,17 +557,7 @@ void timerProcessRules() //This would be better if we worked out the bit pattern first and then just //submitted that as a single i2c read/write transaction -#if defined(ESP8266) hal.SetOutputState(n, relay[n]); -#endif - -#if defined(ESP32) - i2cQueueMessage m; - //Different command for each relay - m.command = 0xE0 + n; - m.data = relay[n]; - xQueueSendToBack(queue_i2c, &m, 10 / portTICK_PERIOD_MS); -#endif previousRelayState[n] = relay[n]; @@ -778,34 +618,45 @@ void timerEnqueueCallback() void connectToWifi() { - if (WiFi.status() != WL_CONNECTED) + wl_status_t status = WiFi.status(); + if (status == WL_CONNECTED) { - //SERIAL_DEBUG.println(F("Configuring Wi-Fi STA...")); - WiFi.mode(WIFI_STA); - - char hostname[40]; + return; + } -#if defined(ESP8266) - sprintf(hostname, "DIYBMS-%08X", ESP.getChipId()); - wifi_station_set_hostname(hostname); - WiFi.hostname(hostname); -#endif -#if defined(ESP32) - uint32_t chipId = 0; - for (int i = 0; i < 17; i = i + 8) - { - chipId |= ((ESP.getEfuseMac() >> (40 - i)) & 0xff) << i; + /* +WiFi.status() only returns: + + switch(status) { + case STATION_GOT_IP: + return WL_CONNECTED; + case STATION_NO_AP_FOUND: + return WL_NO_SSID_AVAIL; + case STATION_CONNECT_FAIL: + case STATION_WRONG_PASSWORD: + return WL_CONNECT_FAILED; + case STATION_IDLE: + return WL_IDLE_STATUS; + default: + return WL_DISCONNECTED; } - sprintf(hostname, "DIYBMS-%08X", chipId); - WiFi.setHostname(hostname); -#endif - SERIAL_DEBUG.print(F("Hostname: ")); - SERIAL_DEBUG.print(hostname); - SERIAL_DEBUG.println(F(" Connecting to Wi-Fi...")); - WiFi.begin(DIYBMSSoftAP::WifiSSID(), DIYBMSSoftAP::WifiPassword()); - } +*/ + + WiFi.mode(WIFI_STA); + + char hostname[40]; - WifiDisconnected = false; + sprintf(hostname, "DIYBMS-%08X", ESP.getChipId()); + wifi_station_set_hostname(hostname); + WiFi.hostname(hostname); + + SERIAL_DEBUG.print(F("Hostname: ")); + SERIAL_DEBUG.print(hostname); + SERIAL_DEBUG.print(F(" Current state: ")); + SERIAL_DEBUG.print((uint8_t)status); + + SERIAL_DEBUG.println(F(",Connect to Wi-Fi...")); + WiFi.begin(DIYBMSSoftAP::WifiSSID(), DIYBMSSoftAP::WifiPassword()); } void connectToMqtt() @@ -913,7 +764,6 @@ void SetupOTA() { ArduinoOTA.setPort(3232); - //ArduinoOTA.setHostname("diybmsesp32"); ArduinoOTA.setPassword("1jiOOx12AQgEco4e"); ArduinoOTA @@ -949,34 +799,42 @@ void SetupOTA() ArduinoOTA.begin(); } -#if defined(ESP8266) -void onWifiConnect(const WiFiEventStationModeGotIP &event) + +sdcard_info sdcard_callback() { -#else -void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) + //Fake + sdcard_info ret; + ret.available = _sd_card_installed; + ret.totalkilobytes = 0; + ret.usedkilobytes = 0; + ret.flash_totalkilobytes = 0; + ret.flash_usedkilobytes = 0; + return ret; +} +void sdcardaction_callback(uint8_t action) +{ + //Fake + _sd_card_installed = false; +} + +void onWifiConnect(const WiFiEventStationModeGotIP &event) { -#endif - SERIAL_DEBUG.print(F("Wi-Fi status=")); - SERIAL_DEBUG.print(WiFi.status()); - SERIAL_DEBUG.print(F(". Connected IP:")); + SERIAL_DEBUG.print(F("onWifiConnect status=")); + SERIAL_DEBUG.println(WiFi.status()); + SERIAL_DEBUG.print(F("Connected IP:")); SERIAL_DEBUG.println(WiFi.localIP()); + SERIAL_DEBUG.print(F("Hostname:")); + SERIAL_DEBUG.println(WiFi.hostname().c_str()); - SERIAL_DEBUG.print(F("Request NTP from ")); - SERIAL_DEBUG.println(mysettings.ntpServer); + //SERIAL_DEBUG.print(F("Request NTP from ")); + //SERIAL_DEBUG.println(mysettings.ntpServer); -#if defined(ESP8266) - //Update time every 10 minutes - NTP.setInterval(600); + //Update time every 20 minutes + NTP.setInterval(1200); NTP.setNTPTimeout(NTP_TIMEOUT); // String ntpServerName, int8_t timeZone, bool daylight, int8_t minutes, AsyncUDP* udp_conn NTP.begin(mysettings.ntpServer, mysettings.timeZone, mysettings.daylight, mysettings.minutesTimeZone); -#endif - -#if defined(ESP32) - //Use native ESP32 code - configTime(mysettings.minutesTimeZone * 60, mysettings.daylight * 60, mysettings.ntpServer); -#endif /* TODO: CHECK ERROR CODES BETTER! @@ -988,7 +846,7 @@ void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) */ if (!server_running) { - DIYBMSServer::StartServer(&server); + DIYBMSServer::StartServer(&server, &mysettings, &sdcard_callback, &prg, &receiveProc, &ControlState, &rules, &sdcardaction_callback); server_running = true; } @@ -1003,28 +861,36 @@ void onWifiConnect(WiFiEvent_t event, WiFiEventInfo_t info) } SetupOTA(); + + // Set up mDNS responder: + // - first argument is the domain name, in this example + // the fully-qualified domain name is "esp8266.local" + // - second argument is the IP address to advertise + // we send our IP address on the WiFi network + + if (MDNS.begin(WiFi.hostname().c_str(), WiFi.localIP())) + { + // Add service to MDNS-SD + MDNS.addService("http", "tcp", 80); + } + else + { + SERIAL_DEBUG.println("Error setting up MDNS responder!"); + } } -#if defined(ESP8266) void onWifiDisconnect(const WiFiEventStationModeDisconnected &event) { -#else -void onWifiDisconnect(WiFiEvent_t event, WiFiEventInfo_t info) -{ -#endif SERIAL_DEBUG.println(F("Disconnected from Wi-Fi.")); - //Indicate to loop() to reconnect, seems to be - //ESP issues using Wifi from timers - https://github.com/espressif/arduino-esp32/issues/2686 - WifiDisconnected = true; - // ensure we don't reconnect to MQTT while reconnecting to Wi-Fi mqttReconnectTimer.detach(); myTimerSendMqttPacket.detach(); myTimerSendMqttStatus.detach(); myTimerSendInfluxdbPacket.detach(); - //wifiReconnectTimer.once(2, connectToWifi); + NTP.stop(); + MDNS.end(); } void onMqttDisconnect(AsyncMqttClientDisconnectReason reason) @@ -1054,22 +920,14 @@ void sendMqttStatus() root["cells"] = mysettings.totalNumberOfSeriesModules; root["uptime"] = millis() / 1000; // I want to know the uptime of the device. - JsonArray bankVoltage = root.createNestedArray("bankVoltage"); - for (int8_t bank = 0; bank < mysettings.totalNumberOfBanks; bank++) - { - bankVoltage.add((float)rules.packvoltage[bank] / (float)1000.0); - } - - JsonObject monitor = root.createNestedObject("monitor"); - // Set error flag if we have attempted to send 2*number of banks without a reply - monitor["commserr"] = receiveProc.HasCommsTimedOut() ? 1 : 0; - monitor["sent"] = prg.packetsGenerated; - monitor["received"] = receiveProc.packetsReceived; - monitor["badcrc"] = receiveProc.totalCRCErrors; - monitor["ignored"] = receiveProc.totalNotProcessedErrors; - monitor["oos"] = receiveProc.totalOutofSequenceErrors; - monitor["roundtrip"] = receiveProc.packetTimerMillisecond; + root["commserr"] = receiveProc.HasCommsTimedOut() ? 1 : 0; + root["sent"] = prg.packetsGenerated; + root["received"] = receiveProc.packetsReceived; + root["badcrc"] = receiveProc.totalCRCErrors; + root["ignored"] = receiveProc.totalNotProcessedErrors; + root["oos"] = receiveProc.totalOutofSequenceErrors; + root["roundtrip"] = receiveProc.packetTimerMillisecond; serializeJson(doc, jsonbuffer, sizeof(jsonbuffer)); sprintf(topic, "%s/status", mysettings.mqtt_topic); @@ -1080,6 +938,22 @@ void sendMqttStatus() SERIAL_DEBUG.print('='); SERIAL_DEBUG.println(jsonbuffer); #endif + //Output bank level information (just voltage for now) + for (int8_t bank = 0; bank < mysettings.totalNumberOfBanks; bank++) + { + doc.clear(); + doc["voltage"] = (float)rules.packvoltage[bank] / (float)1000.0; + + serializeJson(doc, jsonbuffer, sizeof(jsonbuffer)); + sprintf(topic, "%s/bank/%d", mysettings.mqtt_topic, bank); + mqttClient.publish(topic, 0, false, jsonbuffer); +#if defined(MQTT_LOGGING) + SERIAL_DEBUG.print("MQTT - "); + SERIAL_DEBUG.print(topic); + SERIAL_DEBUG.print('='); + SERIAL_DEBUG.println(jsonbuffer); +#endif + } //Using Json for below reduced MQTT messages from 14 to 2. Could be combined into same json object too. But even better is status + event driven. doc.clear(); // Need to clear the json object for next message @@ -1219,6 +1093,9 @@ void LoadConfiguration() mysettings.mqtt_enabled = false; mysettings.mqtt_port = 1883; + mysettings.loggingEnabled = false; + mysettings.loggingFrequencySeconds = 15; + //Default to EMONPI default MQTT settings strcpy(mysettings.mqtt_topic, "diybms"); strcpy(mysettings.mqtt_server, "192.168.0.26"); @@ -1241,25 +1118,28 @@ void LoadConfiguration() mysettings.rulerelaydefault[x] = RELAY_OFF; } - //1. Emergency stop + //Emergency stop mysettings.rulevalue[Rule::EmergencyStop] = 0; - //2. Internal BMS error (communication issues, fault readings from modules etc) + //Internal BMS error (communication issues, fault readings from modules etc) mysettings.rulevalue[Rule::BMSError] = 0; - //3. Individual cell over voltage + //Individual cell over voltage mysettings.rulevalue[Rule::Individualcellovervoltage] = 4150; - //4. Individual cell under voltage + //Individual cell under voltage mysettings.rulevalue[Rule::Individualcellundervoltage] = 3000; - //5. Individual cell over temperature (external probe) + //Individual cell over temperature (external probe) mysettings.rulevalue[Rule::IndividualcellovertemperatureExternal] = 55; - //6. Pack over voltage (mV) + //Pack over voltage (mV) mysettings.rulevalue[Rule::IndividualcellundertemperatureExternal] = 5; - //7. Pack under voltage (mV) + //Pack under voltage (mV) mysettings.rulevalue[Rule::PackOverVoltage] = 4200 * 8; - //8. RULE_PackUnderVoltage + //RULE_PackUnderVoltage mysettings.rulevalue[Rule::PackUnderVoltage] = 3000 * 8; mysettings.rulevalue[Rule::Timer1] = 60 * 8; //8am mysettings.rulevalue[Rule::Timer2] = 60 * 17; //5pm + mysettings.rulevalue[Rule::ModuleOverTemperatureInternal] = 60; + mysettings.rulevalue[Rule::ModuleUnderTemperatureInternal] = 50; + for (size_t i = 0; i < RELAY_RULES; i++) { mysettings.rulehysteresis[i] = mysettings.rulevalue[i]; @@ -1372,40 +1252,158 @@ void resetAllRules() } } +bool CaptureSerialInput(HardwareSerial stream, char *buffer, int buffersize, bool OnlyDigits, bool ShowPasswordChar) +{ + int length = 0; + unsigned long timer = millis() + 30000; + + while (true) + { + + //Abort after 30 seconds of inactivity + if (millis() > timer) + return false; + + //We should add a timeout in here, and return FALSE when we abort.... + while (stream.available()) + { + //Reset timer on serial input + timer = millis() + 30000; + + int data = stream.read(); + if (data == '\b' || data == '\177') + { // BS and DEL + if (length) + { + length--; + stream.write("\b \b"); + } + } + else if (data == '\n') + { + //Ignore + } + else if (data == '\r') + { + if (length > 0) + { + stream.write("\r\n"); // output CRLF + buffer[length] = '\0'; + + //Soak up any other characters on the buffer and throw away + while (stream.available()) + { + stream.read(); + } + + //Return to caller + return true; + } + + length = 0; + } + else if (length < buffersize - 1) + { + if (OnlyDigits && (data < '0' || data > '9')) + { + //We need to filter out non-digit characters + } + else + { + buffer[length++] = data; + if (ShowPasswordChar) + { + //Hide real character + stream.write('*'); + } + else + { + stream.write(data); + } + } + } + } + } +} + +void TerminalBasedWifiSetup(HardwareSerial stream) +{ + stream.println(F("\r\n\r\nDIYBMS CONTROLLER - Scanning Wifi")); + + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + + int n = WiFi.scanNetworks(); + + if (n == 0) + stream.println(F("no networks found")); + else + { + for (int i = 0; i < n; ++i) + { + if (i < 10) + { + stream.print(' '); + } + stream.print(i); + stream.print(':'); + stream.print(WiFi.SSID(i)); + + //Pad out the wifi names into 2 columns + for (size_t spaces = WiFi.SSID(i).length(); spaces < 36; spaces++) + { + stream.print(' '); + } + + if ((i + 1) % 2 == 0) + { + stream.println(); + } + delay(5); + } + stream.println(); + } + + WiFi.mode(WIFI_OFF); + + stream.print(F("Enter the NUMBER of the Wifi network to connect to:")); + + bool result; + char buffer[10]; + result = CaptureSerialInput(stream, buffer, 10, true, false); + if (result) + { + int index = String(buffer).toInt(); + stream.print(F("Enter the password to use when connecting to '")); + stream.print(WiFi.SSID(index)); + stream.print("':"); + + char passwordbuffer[80]; + result = CaptureSerialInput(stream, passwordbuffer, 80, false, true); + + if (result) + { + wifi_eeprom_settings config; + memset(&config, 0, sizeof(config)); + WiFi.SSID(index).toCharArray(config.wifi_ssid, sizeof(config.wifi_ssid)); + strcpy(config.wifi_passphrase, passwordbuffer); + Settings::WriteConfigToEEPROM((char *)&config, sizeof(config), EEPROM_WIFI_START_ADDRESS); + } + } + + stream.println(F("REBOOTING IN 5...")); + delay(5000); + ESP.restart(); +} void setup() { WiFi.mode(WIFI_OFF); -#if defined(ESP32) - btStop(); - esp_log_level_set("*", ESP_LOG_WARN); // set all components to WARN level -//esp_log_level_set("wifi", ESP_LOG_WARN); // enable WARN logs from WiFi stack -//esp_log_level_set("dhcpc", ESP_LOG_WARN); // enable INFO logs from DHCP client -#endif -//Debug serial output -#if defined(ESP8266) + //Debug serial output + //ESP8266 uses dedicated 2nd serial port, but transmit only SERIAL_DEBUG.begin(115200, SERIAL_8N1, SERIAL_TX_ONLY); SERIAL_DEBUG.setDebugOutput(true); -#endif -#if defined(ESP32) - //ESP32 we use the USB serial interface for console/debug messages - SERIAL_DEBUG.begin(115200, SERIAL_8N1); - SERIAL_DEBUG.setDebugOutput(true); - - esp_chip_info_t chip_info; - esp_chip_info(&chip_info); - - SERIAL_DEBUG.print(F("ESP32 Chip model = ")); - SERIAL_DEBUG.print(chip_info.model); - SERIAL_DEBUG.print(", Rev "); - SERIAL_DEBUG.print(chip_info.revision); - SERIAL_DEBUG.print(", Cores "); - SERIAL_DEBUG.print(chip_info.cores); - SERIAL_DEBUG.print(", Features=0x"); - SERIAL_DEBUG.println(chip_info.features, HEX); - -#endif //We generate a unique number which is used in all following JSON requests //we use this as a simple method to avoid cross site scripting attacks @@ -1414,37 +1412,8 @@ void setup() SetControllerState(ControllerState::PowerUp); hal.ConfigurePins(); -#if defined(ESP32) - hal.ConfigureI2C(TCA6408Interrupt, TCA9534AInterrupt); - //Purple during start up - hal.Led(RGBLED::Purple); -#endif -#if defined(ESP8266) hal.ConfigureI2C(ExternalInputInterrupt); -#endif - -#if defined(ESP32) - //All comms to i2c needs to go through this single task - //to prevent issues with thread safety on the i2c hardware/libraries - queue_i2c = xQueueCreate(10, sizeof(i2cQueueMessage)); - - //Create i2c task on CPU 0 (normal code runs on CPU 1) - xTaskCreatePinnedToCore(i2c_task, "i2c", 2048, nullptr, 2, &i2c_task_handle, 0); - xTaskCreatePinnedToCore(ledoff_task, "ledoff", 1048, nullptr, 1, &ledoff_task_handle, 0); -#endif - - //Pretend the button is not pressed - uint8_t clearAPSettings = 0xFF; -#if defined(ESP8266) - //Fix for issue 5, delay for 3 seconds on power up with green LED lit so - //people get chance to jump WIFI reset pin (d3) - hal.GreenLedOn(); - delay(3000); - //This is normally pulled high, D3 is used to reset WIFI details - clearAPSettings = digitalRead(RESET_WIFI_PIN); - hal.GreenLedOff(); -#endif //Pre configure the array memset(&cmi, 0, sizeof(cmi)); @@ -1455,35 +1424,17 @@ void setup() resetAllRules(); -#if defined(ESP32) - //Receive is IO2 which means the RX1 plug must be disconnected for programming to work! - SERIAL_DATA.begin(COMMS_BAUD_RATE, SERIAL_8N1, 2, 32); // Serial for comms to modules -#endif - -#if defined(ESP8266) - SERIAL_DATA.begin(COMMS_BAUD_RATE, SERIAL_8N1); // Serial for comms to modules - //Use alternative GPIO pins of D7/D8 - //D7 = GPIO13 = RECEIVE SERIAL - //D8 = GPIO15 = TRANSMIT SERIAL - SERIAL_DATA.swap(); -#endif - - myPacketSerial.begin(&SERIAL_DATA, &onPacketReceived, sizeof(PacketStruct), SerialPacketReceiveBuffer, sizeof(SerialPacketReceiveBuffer)); - -#if defined(ESP8266) // initialize LittleFS if (!LittleFS.begin()) -#endif -#if defined(ESP32) - // initialize LittleFS - if (!SPIFFS.begin()) -#endif - { - SERIAL_DEBUG.println(F("An Error has occurred while mounting LittleFS")); - } + { + SERIAL_DEBUG.println(F("An Error has occurred while mounting LittleFS")); + } LoadConfiguration(); + //Force logging off for ESP8266 + mysettings.loggingEnabled = false; + InputsEnabled = hal.InputsEnabled; OutputsEnabled = hal.OutputsEnabled; @@ -1495,6 +1446,47 @@ void setup() hal.SetOutputState(y, mysettings.rulerelaydefault[y]); } + //Pretend the button is not pressed + uint8_t clearAPSettings = 0xFF; + //Fix for issue 5, delay for 3 seconds on power up with green LED lit so + //people get chance to jump WIFI reset pin (d3) + hal.GreenLedOn(); + + SERIAL_DATA.begin(115200, SERIAL_8N1); // Serial for comms to modules + + //Allow user to press SPACE BAR key on serial terminal + //to enter text based WIFI setup + SERIAL_DATA.print(F("\r\n\r\n\r\nPress SPACE BAR to enter terminal based configuration....")); + for (size_t i = 0; i < (3000 / 250); i++) + { + SERIAL_DATA.print('.'); + while (SERIAL_DATA.available()) + { + int x = SERIAL_DATA.read(); + //SPACE BAR + if (x == 32) + { + TerminalBasedWifiSetup(SERIAL_DATA); + } + } + delay(250); + } + SERIAL_DATA.println(F("skipped")); + SERIAL_DATA.flush(); + SERIAL_DATA.end(); + + //This is normally pulled high, D3 is used to reset WIFI details + clearAPSettings = digitalRead(RESET_WIFI_PIN); + hal.GreenLedOff(); + + SERIAL_DATA.begin(COMMS_BAUD_RATE, SERIAL_8N1); // Serial for comms to modules + //Use alternative GPIO pins of D7/D8 + //D7 = GPIO13 = RECEIVE SERIAL + //D8 = GPIO15 = TRANSMIT SERIAL + SERIAL_DATA.swap(); + + myPacketSerial.begin(&SERIAL_DATA, &onPacketReceived, sizeof(PacketStruct), SerialPacketReceiveBuffer, sizeof(SerialPacketReceiveBuffer)); + //Temporarly force WIFI settings //wifi_eeprom_settings xxxx; //strcpy(xxxx.wifi_ssid,"XXXXXX"); @@ -1516,28 +1508,19 @@ void setup() else { -#if defined(ESP8266) //Config NTP NTP.onNTPSyncEvent([](NTPSyncEvent_t event) { ntpEvent = event; NTPsyncEventTriggered = true; }); -#endif SERIAL_DEBUG.println(F("Connecting to WIFI")); /* Explicitly set the ESP8266 to be a WiFi-client, otherwise by default, would try to act as both a client and an access-point */ -#if defined(ESP8266) wifiConnectHandler = WiFi.onStationModeGotIP(onWifiConnect); wifiDisconnectHandler = WiFi.onStationModeDisconnected(onWifiDisconnect); -#endif - -#if defined(ESP32) - WiFi.onEvent(onWifiConnect, WiFiEvent_t::SYSTEM_EVENT_STA_GOT_IP); - WiFi.onEvent(onWifiDisconnect, WiFiEvent_t::SYSTEM_EVENT_STA_DISCONNECTED); -#endif mqttClient.onConnect(onMqttConnect); mqttClient.onDisconnect(onMqttDisconnect); @@ -1568,16 +1551,29 @@ void setup() //We have just started... SetControllerState(ControllerState::Stabilizing); + + //Attempt connection in setup(), loop() will also try every 30 seconds + connectToWifi(); } } +unsigned long wifitimer = 0; + void loop() { //ESP_LOGW("LOOP","LOOP"); + unsigned long currentMillis = millis(); - if (WifiDisconnected && ControlState != ControllerState::ConfigurationSoftAP) + if (ControlState != ControllerState::ConfigurationSoftAP) { - connectToWifi(); + //on first pass wifitimer is zero + if (currentMillis - wifitimer > 30000) + { + //Attempt to connect to WiFi every 30 seconds, this caters for when WiFi drops + //such as AP reboot, its written to return without action if we are already connected + connectToWifi(); + wifitimer = currentMillis; + } } ArduinoOTA.handle(); @@ -1585,29 +1581,11 @@ void loop() // Call update to receive, decode and process incoming packets. myPacketSerial.checkInputStream(); - if (ConfigHasChanged > 0) - { - //Auto reboot if needed (after changing MQTT or INFLUX settings) - //Ideally we wouldn't need to reboot if the code could sort itself out! - ConfigHasChanged--; - if (ConfigHasChanged == 0) - { - SERIAL_DEBUG.println(F("RESTART AFTER CONFIG CHANGE")); - //Stop networking - if (mqttClient.connected()) - { - mqttClient.disconnect(true); - } - WiFi.disconnect(); - ESP.restart(); - } - } - -#if defined(ESP8266) if (NTPsyncEventTriggered) { processSyncEvent(ntpEvent); NTPsyncEventTriggered = false; } -#endif + + MDNS.update(); } diff --git a/ESPController/web_src/default.htm b/ESPController/web_src/default.htm index 142f0b7..4ebb9f7 100644 --- a/ESPController/web_src/default.htm +++ b/ESPController/web_src/default.htm @@ -14,7 +14,8 @@ var DEFAULT_GRAPH_MIN_VOLTAGE = parseFloat('%graph_voltagelow%'); var XSS_KEY = '%XSS_KEY%'; - + DIY BMS CONTROLLER v4 @@ -28,6 +29,7 @@ Settings Rules Integration + About @@ -52,7 +54,7 @@
Warning: Module bypass temperature is different to global setting
Warning: Modules have mixed versions of code, may cause instability
Warning: Modules have mixed versions of hardware/boards
- +
Warning: Logging enabled but SD card not installed/found
Current:
@@ -79,12 +81,16 @@

About

WARNING

@@ -100,19 +106,22 @@

Patreon

Remember, this product is free for personal use, if you would like to make a regular donation to keep the features and improvements flowing, use the Patreon link below. Even just a coffee/beer a month makes a difference. Thank you! - Patron + Patron

License

This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 2.0 UK: England & Wales License.

- https://creativecommons.org/licenses/by-nc-sa/2.0/uk/ + https://creativecommons.org/licenses/by-nc-sa/2.0/uk/

Platform & Version

Processor: %PLATFORM%

-

Version: %GIT_VERSION%

+

Version: %GIT_VERSION%

Compiled: %COMPILE_DATE_TIME%

Free block size:

Free heap:

@@ -155,7 +164,7 @@

Settings for module

- +
@@ -225,8 +234,7 @@

Global Settings

Integration

For security, you will need to re-enter the password for the service(s) you want to enable or modify, before you save.

-

After changes are made, the controller will automatically reboot. You will need to refresh the web page to - continue.

+

After changes are made, the controller will need to be rebooted, do this manually.

MQTT

@@ -359,7 +367,7 @@

Rules

- + Rules - + Rules - + Rules - + Rules - + Restart Controller
+
+

Storage

+
+

Logging

+

Cell data and output states can be stored into log files using an SD card.

+ +
+
+ + +
+
+ + +
+ +
+ +
+ +
+

SD Card

+

SD card not installed

+ Used KiB (%%) of KiB +

+ + +

+

Flash Memory

+ Used KiB (%%) of KiB + +
+ +
+