diff --git a/.github/workflows/platformio.yml b/.github/workflows/platformio.yml index 004e8c62..30b43fe0 100644 --- a/.github/workflows/platformio.yml +++ b/.github/workflows/platformio.yml @@ -4,6 +4,8 @@ on: push: branches: - master + paths-ignore: + - '**/README.md' jobs: build: @@ -25,9 +27,5 @@ jobs: platformio update - name: Build test run: | - ./build all - ./build installer - - uses: actions/upload-artifact@v2 - with: - name: firmware - path: releases/installer/canairio_installer/ \ No newline at end of file + pio run -e TTGO_T7 -e ESP32DEVKIT -e TTGO_TDISPLAY -e M5STICKCPLUS -e ESP32C3LOLIN + diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 29a3be60..02e3a20c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -4,6 +4,8 @@ on: push: branches: - devel + paths-ignore: + - '**/README.md' jobs: build: @@ -25,5 +27,5 @@ jobs: platformio update - name: Build test run: | - platformio run + pio run -e TTGO_T7 -e TTGO_TDISPLAY -e M5STICKCPLUS -e ESP32C3LOLIN \ No newline at end of file diff --git a/README.md b/README.md index 8cc0e4f4..b48e7c5e 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@ ![CanAirIO Community](/images/canairio_collage_community.jpg) - + -Citizen science project with mobile and fixed sensors for measuring air quality (PM 2.5 or CO2) using low-cost sensors and smartphones. Built with a `ESP32` module board interfaced with an [CanAirIO Android client app](https://github.com/kike-canaries/canairio_android). +A citizen science project that employs both mobile and fixed sensors to measure air quality (PM 2.5 or CO2) and environmental variables. This is achieved using low-cost sensors and smartphones. The project is built around an ESP32 module board integrated with the [CanAirIO Android client app](https://github.com/kike-canaries/canairio_android). @@ -22,15 +22,15 @@ Citizen science project with mobile and fixed sensors for measuring air quality - Super easy [web installer](https://canair.io/installer), via Chrome or Edge - Mobile station (via Bluetooth LE for tag GPS variables) - Fixed station, (using only your WiFi) -- Fast WiFi credentials provisioning via Bluetooth +- Fast WiFi credentials provisioning via Bluetooth or via - [CLI tool](https://canair.io/docs/cli.html) alternative for configuration and provisioning -- Automatic firmware OTA updates (stable/testing channels) +- Based on [CanAirIO Sensors Library](https://github.com/kike-canaries/canairio_sensorlib#readme) to support more sensors in the future +- Automatic firmware OTA updates (with stable/testing channels) - Share mobile tracks via [mobile.canair.io](https://mobile.canair.io) or [CanAirIO app](https://github.com/kike-canaries/canairio_android) - [Home Assistant](https://www.home-assistant.io/) integration, discovery and multisensor support - Share your fixed station quickly via [Anaire Cloud](https://portal.anaire.org/d/detail/detalle?orgId=1&var-uid=U33TTGOTDA3D46&var-name=&refresh=1m) -- Separated [sensors layer](https://github.com/kike-canaries/canairio_sensorlib#readme) for improve support of new sensors - PAX Counter feature (default wifi sniffer sensor to count people) -- Multiple boards and sensors support with only one firmware +- Multiple boards and sensors supported with only one firmware ## Boards supported @@ -50,12 +50,13 @@ The [last release](https://github.com/kike-canaries/canairio_firmware/releases) | **ESP32C3OIPLUS** | TTGO-T-OI-Plus | OLED | BLE not supported | | **ESP32C3LOLIN** | LOLIN Mini C3 | OLED | BLE not supported | | **ESP32C3SEEDX** | Seeed_xiao_esp32c3 | OLED | BLE not supported | +| **ESP32S3** | T7S3 and Makerfabs | - | In development | -** is possible that the **current firmware supports more boards** and sensors. Also you can choose the sensor brand or type on the CanAirIO Android app. +** is possible that the **current firmware supports more boards** and sensors. Also you can choose the sensor brand or type on the CanAirIO Android app or on the firmware CLI. # Installation alternatives -We have different alternatives for load the current firmware. In order of complexity they are: +We have different alternatives to load the current firmware. By complexity order, they are: ## Via CanAirIO Web Installer (RECOMMENDED) @@ -66,7 +67,7 @@ If you already have a ESP32 board, you can test our CanAirIO firmware on one cli ## Via CanAirIO loader -You will able to install the last version of CanAirIO firmware with internet updates via a simple Arduino sketch that it will doing all for you, you only need to use the official [Arduino IDE](https://www.arduino.cc/en/software) or [Arduino Droid app for Android](https://play.google.com/store/apps/details?id=name.antonsmirnov.android.arduinodroid2&hl=en&gl=US) for load this [simple sketch](https://github.com/hpsaturn/esp32-canairio-loader/blob/master/canairio_loader/canairio_loader.ino). Please follow the instructions [here](https://github.com/hpsaturn/esp32-canairio-loader) or follow the next [YouTube video guide](https://youtu.be/FjfGdnTk-rc) for Android OTG installation alternative. +You will able to install the last version of CanAirIO firmware using a simple Arduino sketch that it will doing all for you, you only need to use the official [Arduino IDE](https://www.arduino.cc/en/software) or [Arduino Droid app for Android](https://play.google.com/store/apps/details?id=name.antonsmirnov.android.arduinodroid2&hl=en&gl=US) for load this [simple sketch](https://github.com/hpsaturn/esp32-canairio-loader/blob/master/canairio_loader/canairio_loader.ino). Please follow the instructions [here](https://github.com/hpsaturn/esp32-canairio-loader) or follow the next [YouTube video guide](https://youtu.be/FjfGdnTk-rc) for Android OTG installation alternative. ## Via binaries @@ -74,7 +75,7 @@ You can download the last firmware version in [releases](https://github.com/kike ![releases assets](images/assets.jpg) -please uncompress the zip file and connect your CanAirIO device to your USB and execute the next command for upload the firmware to your model board, for example for an ESP32DevKit board you should run the next commands: +please uncompress the zip file and connect your CanAirIO device to your USB and execute the next command to upload the firmware to your board, for example for an ESP32DevKit board you should run the next commands: ### Linux and MacOSx @@ -88,7 +89,7 @@ esptool --port /dev/ttyUSB0 -b 1500000 write_flash 0x0 canairio_ESP32DEVKIT_rev9 ### Windows -Please read the [Espressif Uploader](https://canair.io/docs/firmware_upload.html#espressif-uploader) section in the main documentation for details to load the firmware via the oficial **Espressif Download Tool** in Windows. +Please read the [Espressif Uploader](https://canair.io/docs/firmware_upload.html#espressif-uploader) section in the main documentation to have details of how load the firmware via the official **Espressif Download Tool** in Windows. ## Via PlatformIO (Compiling on Linux, Mac or Windows) @@ -124,9 +125,9 @@ This build a basic compiler image with all PlatformIO stuff. For build the proje ## OTA WAN updates -CanAirIO has two channels to have remote OTA updates of your device, the production channel and development channel. With that you don't need again install the firmware again for any update, all here is automatic and you only need have WiFi enable on your device for receive this firmware updates. +CanAirIO offers two channels for remote OTA (Over-The-Air) updates for your device: the production channel and the development channel. This means you won't need to reinstall the firmware manually for any updates; it's all automatic. You only need to have Wi-Fi enabled on your device to receive these firmware updates. -If you want the last testing updates, please choose in releases the development firmware (zip file with **dev** name), and upload it to your board or via the web installer choose testing option of each firmare. +If you're interested in the latest testing updates, please go to the releases section and choose and download the development firmware (a zip file with `dev` in its name), then upload it to your board to receive these kind of updates. # Supporting the project @@ -142,15 +143,18 @@ When creating a pull request, we recommend that you do the following: Also you can consider make a donation, be a patron or buy a device: + - Via **Ethereum**: - 0x1779cD3b85b6D8Cf1A5886B2CF5C53a0E072C108 - Be a patron: [Github Sponsors](https://github.com/sponsors/hpsaturn), [LiberaPay](https://liberapay.com/CanAirIO) - **Buy a device**: [CanAirIO Bike in Tindie](https://www.tindie.com/products/hpsaturn/canairio-bike/) - Inviting us **a coffee**: [buymeacoffee](https://www.buymeacoffee.com/hpsaturn), [Sponsors](https://github.com/sponsors/hpsaturn?frequency=one-time) - + + +**NOTE:** +Supporting our Citizen Science Initiative many people be able to fight for air quality rights in many countries with this kind of problems. More info in [CanAir.IO](https://canair.io) -**NOTE:** Supporting our Citizen Science Initiative many people be able to fight for air quality rights in many countries with this kind of problems. More info in [CanAir.IO](https://canair.io) # CanAirIO device HOWTO guide @@ -172,7 +176,7 @@ We have some build guides with different alternatives, please visit our [CanAirI ** W A R N N I N G ** -The full updated and last versions for all box versions, are in the [official repository](https://github.com/kike-canaries/canairio_firmware/tree/master/box) because it is more easy for handling the versions than Thingiverse. This page it is only a guide. +The last versions for all box versions, are in the [official repository](https://github.com/kike-canaries/canairio_firmware/tree/master/box) because it is more easy for handling the versions than Thingiverse. # TODO @@ -190,5 +194,7 @@ The full updated and last versions for all box versions, are in the [official re - [x] Pax counter disable/enable - [x] Home Assistant integration (with zero-config) - [x] Anaire cloud integration (Automatic time series of your station) +- [x] Fahrenheit and Kelvin units supported +- [x] Geiger sensor supported - [ ] Sensor community alternativa for fixed stations - [ ] Anonymous authentication diff --git a/box/canairio_tdisplay_mini_box/canairio_mini_base.stl b/box/canairio_tdisplay_mini_box/canairio_mini_base.stl new file mode 100644 index 00000000..3f12d2ec Binary files /dev/null and b/box/canairio_tdisplay_mini_box/canairio_mini_base.stl differ diff --git a/box/canairio_tdisplay_mini_box/canairio_mini_board_base.stl b/box/canairio_tdisplay_mini_box/canairio_mini_board_base.stl new file mode 100644 index 00000000..5719384b Binary files /dev/null and b/box/canairio_tdisplay_mini_box/canairio_mini_board_base.stl differ diff --git a/box/canairio_tdisplay_mini_box/canairio_mini_buttons.stl b/box/canairio_tdisplay_mini_box/canairio_mini_buttons.stl new file mode 100644 index 00000000..6627de3e Binary files /dev/null and b/box/canairio_tdisplay_mini_box/canairio_mini_buttons.stl differ diff --git a/box/canairio_tdisplay_mini_box/canairio_mini_buttons_lid.stl b/box/canairio_tdisplay_mini_box/canairio_mini_buttons_lid.stl new file mode 100644 index 00000000..5d099334 Binary files /dev/null and b/box/canairio_tdisplay_mini_box/canairio_mini_buttons_lid.stl differ diff --git a/box/canairio_tdisplay_mini_box/canairio_mini_tdisplay_front.stl b/box/canairio_tdisplay_mini_box/canairio_mini_tdisplay_front.stl new file mode 100644 index 00000000..e0d33634 Binary files /dev/null and b/box/canairio_tdisplay_mini_box/canairio_mini_tdisplay_front.stl differ diff --git a/box/canairio_tdisplay_mini_box/canairio_mini_two_button_reset.stl b/box/canairio_tdisplay_mini_box/canairio_mini_two_button_reset.stl new file mode 100644 index 00000000..2043db3c Binary files /dev/null and b/box/canairio_tdisplay_mini_box/canairio_mini_two_button_reset.stl differ diff --git a/build b/build index 533ee738..dbf88c82 100755 --- a/build +++ b/build @@ -26,7 +26,7 @@ OWNER="kike-canaries" REPO="canairio_firmware" INSDIR=$RELDIR/installer/${NAME}_installer -flavors="TTGO_T7 WEMOSOLED HELTEC TTGO_TQ ESP32DEVKIT TTGO_TDISPLAY ESP32PICOD4 M5STICKCPLUS M5ATOM M5PICOD4 ESP32C3 ESP32C3OIPLUS ESP32C3LOLIN ESP32C3SEEDX" +flavors="TTGO_T7 WEMOSOLED HELTEC TTGO_TQ ESP32DEVKIT TTGO_TDISPLAY ESP32PICOD4 M5STICKCPLUS M5ATOM M5PICOD4 ESP32C3 ESP32C3OIPLUS ESP32C3LOLIN ESP32C3SEEDX ESP32S3 TTGO_T7S3 LORADEVKIT" #flavors="TTGO_T7 WEMOSOLED ESP32DEVKIT TTGO_TDISPLAY M5STICKCPLUS M5ATOM ESP32C3 ESP32C3OIPLUS ESP32C3LOLIN ESP32C3SEEDX" #flavors="TTGO_T7 ESP32C3 ESP32C3OIPLUS ESP32C3LOLIN ESP32C3SEEDX" @@ -81,6 +81,8 @@ build () { cp $OUTDIR/$1/firmware.bin $FIRMDIR/${NAME}_${1}_rev${SRC_REV}.bin if [ $1 == ESP32C3 ] || [ $1 == ESP32C3LOLIN ] || [ $1 == ESP32C3SEEDX ] || [ $1 == ESP32C3OIPLUS ]; then /usr/bin/python3 ${PIO_HOME}/packages/tool-esptoolpy/esptool.py --chip esp32c3 merge_bin -o $FIRMDIR/${NAME}_${1}_rev${SRC_REV}_merged.bin --flash_mode dio --flash_freq 80m --flash_size 4MB 0x0000 $OUTDIR/$1/bootloader.bin 0x8000 $OUTDIR/$1/partitions.bin 0xe000 ${PIO_HOME}/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 $OUTDIR/$1/firmware.bin + elif [ $1 == ESP32S3 ] || [ $1 == TTGO_T7S3 ]; then + /usr/bin/python3 ${PIO_HOME}/packages/tool-esptoolpy/esptool.py --chip esp32s3 merge_bin -o $FIRMDIR/${NAME}_${1}_rev${SRC_REV}_merged.bin --flash_mode dio --flash_freq 80m --flash_size 8MB 0x0000 $OUTDIR/$1/bootloader.bin 0x8000 $OUTDIR/$1/partitions.bin 0xe000 ${PIO_HOME}/packages/framework-arduinoespressif32/tools/partitions/boot_app0.bin 0x10000 $OUTDIR/$1/firmware.bin else esptool --chip esp32 merge_bin -o $FIRMDIR/${NAME}_${1}_rev${SRC_REV}_merged.bin --flash_mode dio --flash_freq 40m --flash_size 4MB 0x1000 ${PIO_HOME}/packages/framework-arduinoespressif32@3.20003.220626/tools/sdk/esp32/bin/bootloader_dio_40m.bin 0x8000 $OUTDIR/$1/partitions.bin 0xe000 ${PIO_HOME}/packages/framework-arduinoespressif32@3.20003.220626/tools/partitions/boot_app0.bin 0x10000 $OUTDIR/$1/firmware.bin fi diff --git a/docs/CO, NH3, Gas Sensor Wiki - DFRobot.pdf b/docs/CO, NH3, Gas Sensor Wiki - DFRobot.pdf new file mode 100644 index 00000000..35d9c71d Binary files /dev/null and b/docs/CO, NH3, Gas Sensor Wiki - DFRobot.pdf differ diff --git a/docs/dfrobot board size.pdf b/docs/dfrobot board size.pdf new file mode 100644 index 00000000..0c8273b9 Binary files /dev/null and b/docs/dfrobot board size.pdf differ diff --git a/docs/dfrobot datasheet.pdf b/docs/dfrobot datasheet.pdf new file mode 100644 index 00000000..40367ca8 Binary files /dev/null and b/docs/dfrobot datasheet.pdf differ diff --git a/docs/t7_s3_v1.2.pdf b/docs/t7_s3_v1.2.pdf new file mode 100644 index 00000000..3f41e0b4 Binary files /dev/null and b/docs/t7_s3_v1.2.pdf differ diff --git a/docs/t7_v1.3.pdf b/docs/t7_v1.3.pdf new file mode 100644 index 00000000..5f47c94e Binary files /dev/null and b/docs/t7_v1.3.pdf differ diff --git a/include/logmem.hpp b/include/logmem.hpp new file mode 100644 index 00000000..b627b51a --- /dev/null +++ b/include/logmem.hpp @@ -0,0 +1,6 @@ +#include +#include +#include + +void logMemory(const char* msg); +void logMemoryObjects(); \ No newline at end of file diff --git a/include/power.hpp b/include/power.hpp index 7fd06f4b..1416cc34 100644 --- a/include/power.hpp +++ b/include/power.hpp @@ -1,3 +1,6 @@ +#ifdef CONFIG_IDF_TARGET_ESP32S3 +#include "driver/temp_sensor.h" +#endif #include #include #include @@ -5,6 +8,7 @@ #include +float powerESP32TempRead(); void powerCompleteShutdown(); void powerDeepSleepButton(); void powerDeepSleepTimer(int); diff --git a/include/wifi.hpp b/include/wifi.hpp index 27ccd8e2..b94349e9 100644 --- a/include/wifi.hpp +++ b/include/wifi.hpp @@ -11,7 +11,10 @@ #include #include #include -#include + +#ifndef DISABLE_CLI +#include +#endif //#define IFX_RETRY_CONNECTION 5 // influxdb publish retry @@ -26,11 +29,8 @@ void wifiInit(); void wifiStop(); void wifiRestart(); void wifiLoop(); -void cliInit(); -void cliTaskInit(); int getWifiRSSI(); void printWifiRSSI(); String getDeviceInfo(); String getHostId(); -void logMemory(const char *msg); diff --git a/lib/batterylib/battery.hpp b/lib/batterylib/battery.hpp index 465b6c4a..9d178383 100644 --- a/lib/batterylib/battery.hpp +++ b/lib/batterylib/battery.hpp @@ -20,6 +20,11 @@ class Battery { CircularBuffer buffer; int isDischarging = -1; + float btDiscVMin = 0.0; + float btDiscVMax = 0.0; + float btCharVMin = 0.0; + float btCharVMax = 0.0; + virtual void init(bool debug = false) = 0; virtual void update() = 0; virtual float getVoltage() = 0; @@ -54,6 +59,30 @@ class Battery { this->callback = callbacks; } + void setLimits(float battDiscVMin, float battDiscVMax, float battChargVMin, float battChargVMax) { + this->btDiscVMin = battDiscVMin; + this->btDiscVMax = battDiscVMax; + this->btCharVMin = battChargVMin; + this->btCharVMax = battChargVMax; + } + + void setBattLimits(float battMinV, float battMaxV) { + this->btDiscVMin = battMinV; + this->btDiscVMax = battMaxV; + } + + void setChargLimits(float chargMinV, float chargMaxV) { + this->btCharVMin = chargMinV; + this->btCharVMax = chargMaxV; + } + + void printLimits(){ + Serial.printf("-->[BATT] BVmin:%1.2f BVmax:%1.2f\t: CVmax:%1.2f CVmin:%1.2f\r\n", + btDiscVMin, btDiscVMax, + btCharVMin, btCharVMax + ); + } + void loop() { static uint32_t pmLoopTimeStamp = 0; // timestamp for sensor loop check data if ((millis() - pmLoopTimeStamp > interval)) { // sample time for each capture diff --git a/lib/batterylib/battery_m5stack.cpp b/lib/batterylib/battery_m5stack.cpp index ca797385..76fdb9e7 100644 --- a/lib/batterylib/battery_m5stack.cpp +++ b/lib/batterylib/battery_m5stack.cpp @@ -5,6 +5,7 @@ void Battery_M5STACK::init(bool debug) { this->debug = debug; M5.Axp.EnableCoulombcounter(); // Enable Coulomb counter. + setLimits(BATTERY_MIN_V, BATTERY_MAX_V, BATTCHARG_MIN_V, BATTCHARG_MAX_V); } float Battery_M5STACK::getVoltage() { @@ -17,14 +18,14 @@ void Battery_M5STACK::update() { } bool Battery_M5STACK::isCharging() { - return M5.axp.GetVBusVoltage() > BATTCHARG_MAX_V; + return M5.axp.GetVBusVoltage() > btCharVMax; } int Battery_M5STACK::getCharge() { if (isCharging()) { - return calcPercentage(curv, BATTCHARG_MAX_V, BATTCHARG_MIN_V); + return calcPercentage(curv, btCharVMax, btCharVMin); } else { - return calcPercentage(curv, BATTERY_MAX_V, BATTERY_MIN_V); + return calcPercentage(curv, btDiscVMax, btDiscVMin); } } diff --git a/lib/batterylib/battery_oled.cpp b/lib/batterylib/battery_oled.cpp index 51af7f25..747d2237 100644 --- a/lib/batterylib/battery_oled.cpp +++ b/lib/batterylib/battery_oled.cpp @@ -6,6 +6,9 @@ #elif TTGO_T7 #define ADC_PIN 35 int channel_atten = ADC1_CHANNEL_7; +#elif TTGO_T7S3 + #define ADC_PIN 2 + int channel_atten = ADC1_CHANNEL_1; #else #define ADC_PIN 34 int channel_atten = 0; @@ -17,14 +20,15 @@ void Battery_OLED::setupBattADC() { // TODO: all here is deprecated we need review the documentation esp_adc_cal_characteristics_t adc_chars; esp_adc_cal_value_t val_type = esp_adc_cal_characterize((adc_unit_t)ADC_UNIT_1, (adc_atten_t)channel_atten, (adc_bits_width_t)ADC_WIDTH_BIT_12, 1100, &adc_chars); + analogReadResolution(12); // Check type of calibration value used to characterize ADC if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { - Serial.printf("-->[BATT] ADC eFuse Vref:%u mV\r\n", adc_chars.vref); + log_i("[BATT] ADC eFuse Vref \t: %u mV\r\n", adc_chars.vref); vref = adc_chars.vref; } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { - Serial.printf("-->[BATT] ADC Two Point --> coeff_a:%umV coeff_b:%umV\r\n", adc_chars.coeff_a, adc_chars.coeff_b); + log_i("[BATT] ADC Two Point coeff_a\t: %umV coeff_b:%umV\r\n", adc_chars.coeff_a, adc_chars.coeff_b); } else { - Serial.printf("-->[BATT] ADC Default Vref: %u mV\r\n", vref); + log_i("[BATT] ADC Default Vref \t: %u mV\r\n", vref); } } @@ -40,6 +44,7 @@ void Battery_OLED::init(bool debug) { delay(10); // suggested by @ygator user in issue #2 setupBattADC(); delay(10); // suggested by @ygator user in issue #2 + setLimits(BATTERY_MIN_V, BATTERY_MAX_V, BATTCHARG_MIN_V, BATTCHARG_MAX_V); } float Battery_OLED::getVoltage() { @@ -51,7 +56,7 @@ bool Battery_OLED::isCharging() { if (isDischarging >= 0) charging = !isDischarging; else - charging = curv > BATTERY_MAX_V + (BATTCHARG_MIN_V - BATTERY_MAX_V) / 2; + charging = curv > btDiscVMax + (btCharVMin - btDiscVMax) / 2; // if (debug) Serial.printf("-->[BATT] Batt is charging\t: %s\r\n", charging ? "True" : "False"); return charging; } @@ -65,7 +70,7 @@ void Battery_OLED::update() { digitalWrite(ADC_EN, HIGH); delay(10); // suggested by @ygator user in issue #2 uint16_t v = analogRead(ADC_PIN); -#ifdef TTGO_T7 +#if defined(TTGO_T7) || defined(TTGO_T7S3) curv = ((float)v / 4095.0) * 7.58; #else curv = ((float)v / 4095.0) * 15.83; @@ -74,11 +79,11 @@ void Battery_OLED::update() { } int Battery_OLED::getCharge() { - if (isCharging()) { - return calcPercentage(curv, BATTCHARG_MAX_V, BATTCHARG_MIN_V); - } else { - return calcPercentage(curv, BATTERY_MAX_V, BATTERY_MIN_V); - } + if (isCharging()) { + return calcPercentage(curv, btCharVMax, btCharVMin); + } else { + return calcPercentage(curv, btDiscVMax, btDiscVMin); + } } #if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_OLEDBATTERY) diff --git a/lib/batterylib/battery_oled.hpp b/lib/batterylib/battery_oled.hpp index f0d83b2b..1cdfbb01 100644 --- a/lib/batterylib/battery_oled.hpp +++ b/lib/batterylib/battery_oled.hpp @@ -4,13 +4,14 @@ #include #include -#ifdef TTGO_T7 +#if defined(TTGO_T7) || defined(TTGO_T7S3) #define BATTERY_MIN_V 3.4 #define BATTERY_MAX_V 4.28 #define BATTCHARG_MIN_V 3.8 #define BATTCHARG_MAX_V 4.34 #else -#define BATTERY_MIN_V 3.4 + +#define BATTERY_MIN_V 3.1 #define BATTERY_MAX_V 4.04 #define BATTCHARG_MIN_V 4.06 #define BATTCHARG_MAX_V 4.198 diff --git a/lib/batterylib/battery_tft.cpp b/lib/batterylib/battery_tft.cpp index 8d87bf19..94ed5b3a 100644 --- a/lib/batterylib/battery_tft.cpp +++ b/lib/batterylib/battery_tft.cpp @@ -11,12 +11,12 @@ void Battery_TFT::setupBattADC() { esp_adc_cal_value_t val_type = esp_adc_cal_characterize((adc_unit_t)ADC_UNIT_1, (adc_atten_t)channel_atten, (adc_bits_width_t)ADC_WIDTH_BIT_12, 1100, &adc_chars); //Check type of calibration value used to characterize ADC if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) { - Serial.printf("-->[BATT] ADC eFuse Vref:%u mV\r\n", adc_chars.vref); + log_i("[BATT] ADC eFuse Vref \t: %u mV\r\n", adc_chars.vref); vref = adc_chars.vref; } else if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) { - Serial.printf("-->[BATT] ADC Two Point --> coeff_a:%umV coeff_b:%umV\r\n", adc_chars.coeff_a, adc_chars.coeff_b); + log_i("[BATT] ADC Two Point coeff_a \t: %umV coeff_b:%umV\r\n", adc_chars.coeff_a, adc_chars.coeff_b); } else { - Serial.printf("-->[BATT] ADC Default Vref: %u mV\r\n", vref); + log_i("[BATT] ADC Default Vref \t: %u mV\r\n", vref); } } @@ -32,6 +32,7 @@ void Battery_TFT::init(bool debug) { delay(10); // suggested by @ygator user in issue #2 setupBattADC(); delay(10); // suggested by @ygator user in issue #2 + setLimits(BATTERY_MIN_V, BATTERY_MAX_V, BATTCHARG_MIN_V, BATTCHARG_MAX_V); } float Battery_TFT::getVoltage () { @@ -48,19 +49,19 @@ void Battery_TFT::update() { bool Battery_TFT::isCharging() { bool charging = false; - if (BATTERY_MAX_V <= BATTCHARG_MIN_V && isDischarging >= 0) + if (btDiscVMax <= btCharVMin && isDischarging >= 0) charging = !isDischarging; else - charging = curv > BATTERY_MAX_V + (BATTCHARG_MIN_V - BATTERY_MAX_V) / 2; + charging = curv > btDiscVMax + (btCharVMin - btDiscVMax) / 2; // if (debug) Serial.printf("-->[BATT] Batt is charging\t: %s\r\n", charging ? "True" : "False"); return charging; } int Battery_TFT::getCharge() { if (isCharging()) { - return calcPercentage(curv, BATTCHARG_MAX_V, BATTCHARG_MIN_V); + return calcPercentage(curv, this->btCharVMax, this->btCharVMin); } else { - return calcPercentage(curv, BATTERY_MAX_V, BATTERY_MIN_V); + return calcPercentage(curv, this->btDiscVMax, this->btDiscVMin); } } diff --git a/lib/configlib/ConfigApp.cpp b/lib/configlib/ConfigApp.cpp index 440d55e5..ca77b928 100644 --- a/lib/configlib/ConfigApp.cpp +++ b/lib/configlib/ConfigApp.cpp @@ -22,6 +22,7 @@ void ConfigApp::init(const char app_name[]) { } void ConfigApp::reload() { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RO_MODE); // device name or station name dname = preferences.getString("dname", ""); @@ -60,6 +61,7 @@ void ConfigApp::reload() { String ConfigApp::getCurrentConfig() { StaticJsonDocument<1000> doc; + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RO_MODE); doc["dname"] = preferences.getString("dname", ""); // device or station name doc["stime"] = preferences.getInt("stime", 5); // sensor measure time @@ -110,6 +112,7 @@ void ConfigApp::setLastKeySaved(String key){ } void ConfigApp::saveString(String key, String value){ + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putString(key.c_str(), value.c_str()); preferences.end(); @@ -121,6 +124,7 @@ void ConfigApp::saveString(CONFKEYS key, String value){ } String ConfigApp::getString(String key, String defaultValue){ + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RO_MODE); String out = preferences.getString(key.c_str(), defaultValue); preferences.end(); @@ -132,6 +136,7 @@ String ConfigApp::getString(CONFKEYS key, String defaultValue){ } void ConfigApp::saveInt(String key, int value){ + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putInt(key.c_str(), value); preferences.end(); @@ -143,6 +148,7 @@ void ConfigApp::saveInt(CONFKEYS key, int value){ } int32_t ConfigApp::getInt(String key, int defaultValue){ + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RO_MODE); int32_t out = preferences.getInt(key.c_str(), defaultValue); preferences.end(); @@ -154,6 +160,7 @@ int32_t ConfigApp::getInt(CONFKEYS key, int defaultValue){ } void ConfigApp::saveBool(String key, bool value){ + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putBool(key.c_str(), value); preferences.end(); @@ -165,6 +172,7 @@ void ConfigApp::saveBool(CONFKEYS key, bool value){ } bool ConfigApp::getBool(String key, bool defaultValue){ + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RO_MODE); bool out = preferences.getBool(key.c_str(), defaultValue); preferences.end(); @@ -176,6 +184,7 @@ bool ConfigApp::getBool(CONFKEYS key, bool defaultValue){ } void ConfigApp::saveFloat(String key, float value){ + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putFloat(key.c_str(), value); preferences.end(); @@ -187,6 +196,7 @@ void ConfigApp::saveFloat(CONFKEYS key, float value){ } float ConfigApp::getFloat(String key, float defaultValue){ + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RO_MODE); float out = preferences.getFloat(key.c_str(), defaultValue); preferences.end(); @@ -198,19 +208,27 @@ float ConfigApp::getFloat(CONFKEYS key, float defaultValue){ } PreferenceType ConfigApp::keyType(String key) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RO_MODE); PreferenceType type = preferences.getType(key.c_str()); preferences.end(); return type; } + + bool ConfigApp::isKey(String key) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RO_MODE); bool iskey = preferences.isKey(key.c_str()); preferences.end(); return iskey; } +bool ConfigApp::isKey(CONFKEYS key) { + return isKey(getKey(key)); +} + String ConfigApp::getKey(CONFKEYS key) { if (key < 0 || key > CONFKEYS::KCOUNT) return ""; return String(keys[key]); @@ -244,6 +262,7 @@ bool ConfigApp::saveDeviceName(String name) { bool ConfigApp::saveSampleTime(int time) { if (time >= 5) { saveInt("stime", time); + stime=time; Serial.printf("-->[CONF] set sample time to\t: %d\r\n", time); return true; } @@ -320,6 +339,7 @@ bool ConfigApp::saveSeaLevel(float hpa) { bool ConfigApp::saveSSID(String ssid){ if (ssid.length() > 0) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putString("ssid", ssid); preferences.end(); @@ -333,6 +353,7 @@ bool ConfigApp::saveSSID(String ssid){ bool ConfigApp::saveWifi(String ssid, String pass){ if (ssid.length() > 0) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putString("ssid", ssid); preferences.putString("pass", pass); @@ -351,6 +372,7 @@ bool ConfigApp::saveWifi(String ssid, String pass){ bool ConfigApp::saveInfluxDb(String db, String ip, int pt) { if (db.length() > 0 && ip.length() > 0) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putString("ifxdb", db); preferences.putString("ifxip", ip); @@ -369,6 +391,7 @@ bool ConfigApp::saveInfluxDb(String db, String ip, int pt) { bool ConfigApp::saveGeo(double lat, double lon, String geo){ if (lat != 0 && lon != 0) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putDouble("lat", lat); preferences.putDouble("lon", lon); @@ -449,6 +472,7 @@ bool ConfigApp::saveI2COnly(bool enable) { } bool ConfigApp::saveHassIP(String ip) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putString("hassip", ip); preferences.end(); @@ -458,6 +482,7 @@ bool ConfigApp::saveHassIP(String ip) { } bool ConfigApp::saveHassPort(int port) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putInt("hasspt", port); preferences.end(); @@ -467,6 +492,7 @@ bool ConfigApp::saveHassPort(int port) { } bool ConfigApp::saveHassUser(String user) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putString("hassusr", user); preferences.end(); @@ -476,6 +502,7 @@ bool ConfigApp::saveHassUser(String user) { } bool ConfigApp::saveHassPassword(String passw) { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.putString("hasspsw", passw); preferences.end(); @@ -616,6 +643,7 @@ bool ConfigApp::isWifiConnected() { } void ConfigApp::clear() { + std::lock_guard lck(config_mtx); preferences.begin(_app_name, RW_MODE); preferences.clear(); preferences.end(); diff --git a/lib/configlib/ConfigApp.hpp b/lib/configlib/ConfigApp.hpp index a193610b..8b8e9047 100644 --- a/lib/configlib/ConfigApp.hpp +++ b/lib/configlib/ConfigApp.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #define RW_MODE false #define RO_MODE true @@ -20,6 +21,7 @@ typedef enum { X(KBI2COLY, "i2conly", BOOL) \ X(KFALTFST, "altoffset", FLOAT) \ X(KFTOFFST, "toffset", FLOAT) \ + X(KTEMPUNT, "tunit", INT) \ X(KBASIC, "-----", UNKNOWN) \ X(KDEBUG, "debugEnable", BOOL) \ X(KFLIPV, "flipVEnable", BOOL) \ @@ -34,10 +36,16 @@ typedef enum { X(KSHASSPW, "hasspsw", STRING) \ X(KIHASSPT, "hasspt", INT) \ X(KFSEALV, "sealevel", FLOAT) \ + X(KSTIME, "stime", INT) \ + X(KBATVMX, "battVmax", FLOAT) \ + X(KBATVMI, "battVmin", FLOAT) \ + X(KCHRVMX, "chrgVmin", FLOAT) \ + X(KCHRVMI, "chrgVmax", FLOAT) \ X(KFAILSAFE, "fsafeEnable", BOOL) \ X(KWKUPRST, "wkrstEnable", BOOL) \ X(KBSOLARE, "solarEnable", BOOL) \ X(KIDEEPSL, "deepSleep", INT) \ + X(KGEIGERP, "geigerPin", INT) \ X(KCOUNT, "KCOUNT", UNKNOWN) #define X(kname, kreal, ktype) kname, @@ -159,8 +167,8 @@ class ConfigApp { void saveBool(String key, bool value); void saveBool(CONFKEYS key, bool value); - float getFloat(String key, float defaultValue); - float getFloat(CONFKEYS key, float defaultValue); + float getFloat(String key, float defaultValue = 0.0); + float getFloat(CONFKEYS key, float defaultValue = 0.0); void saveFloat(String key, float value); void saveFloat(CONFKEYS key, float value); @@ -218,6 +226,8 @@ class ConfigApp { PreferenceType keyType(String key); bool isKey(String key); + + bool isKey(CONFKEYS key); String getKey(CONFKEYS key); @@ -225,7 +235,9 @@ class ConfigApp { ConfKeyType getKeyType(CONFKEYS key); - private: + private: + /// mutex for R/W actions + std::mutex config_mtx; ///preferences main key char* _app_name; ///ESP32 preferences abstraction diff --git a/lib/gui-utils-oled/src/GUIUtils.cpp b/lib/gui-utils-oled/src/GUIUtils.cpp index 8dc5a96c..038343c8 100644 --- a/lib/gui-utils-oled/src/GUIUtils.cpp +++ b/lib/gui-utils-oled/src/GUIUtils.cpp @@ -18,6 +18,8 @@ void GUIUtils::displayInit() { U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); #elif M5PICOD4 U8G2_SSD1306_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); +#elif LORADEVKIT + U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, 15, 4, 16); #else // display via i2c for TTGO_T7 (old D1MINI) board U8G2_SSD1306_64X48_ER_F_HW_I2C u8g2(U8G2_R0, U8X8_PIN_NONE, U8X8_PIN_NONE, U8X8_PIN_NONE); #endif @@ -511,38 +513,35 @@ void GUIUtils::pageEnd() { u8g2.nextPage(); } -void GUIUtils::setBrightness(uint32_t value){ - +void GUIUtils::setBrightness(uint32_t value) { } -void GUIUtils::setWifiMode(bool enable){ - +void GUIUtils::setWifiMode(bool enable) { } -void GUIUtils::setPaxMode(bool enable){ - +void GUIUtils::setPaxMode(bool enable) { } -void GUIUtils::setSampleTime(int time){ - +void GUIUtils::setSampleTime(int time) { } -void GUIUtils::setTrackValues(float speed, float distance){ - +void GUIUtils::setTrackValues(float speed, float distance) { } -void GUIUtils::setTrackTime(int h, int m, int s){ - +void GUIUtils::setTrackTime(int h, int m, int s) { } -void GUIUtils::suspendTaskGUI(){ +void GUIUtils::suspendTaskGUI() { } -void GUIUtils::resumeTaskGUI(){ +void GUIUtils::resumeTaskGUI() { } -void GUIUtils::setCallbacks(GUIUserPreferencesCallbacks* pCallBacks){ +int32_t GUIUtils::getStackFree() { + return 0; +} +void GUIUtils::setCallbacks(GUIUserPreferencesCallbacks* pCallBacks) { } void GUIUtils::loop(){ diff --git a/lib/gui-utils-oled/src/GUIUtils.hpp b/lib/gui-utils-oled/src/GUIUtils.hpp index f502d65d..6ff18973 100644 --- a/lib/gui-utils-oled/src/GUIUtils.hpp +++ b/lib/gui-utils-oled/src/GUIUtils.hpp @@ -84,6 +84,8 @@ class GUIUtils { void resumeTaskGUI(); + int32_t getStackFree(); + void setPowerSave(); String getFirmwareVersionCode (); diff --git a/lib/gui-utils-tft/src/TFTUtils.cpp b/lib/gui-utils-tft/src/TFTUtils.cpp index 75e899e8..d4a576af 100644 --- a/lib/gui-utils-tft/src/TFTUtils.cpp +++ b/lib/gui-utils-tft/src/TFTUtils.cpp @@ -5,7 +5,7 @@ ******************************************************************************/ void guiTask(void* pvParameters) { - Serial.println("-->[TGUI] starting task loop"); + log_i("[TGUI] starting task loop"); while (1) { gui.pageStart(); gui.checkButtons(); @@ -18,13 +18,13 @@ void guiTask(void* pvParameters) { void TFTUtils::setupGUITask() { taskGUIrunning = true; xTaskCreatePinnedToCore( - guiTask, /* Function to implement the task */ - "tempTask ", /* Name of the task */ - 10000, /* Stack size in words */ - NULL, /* Task input parameter */ - 5, /* Priority of the task */ - &xHandle, /* Task handle. */ - 1); /* Core where the task should run */ + guiTask, // Function to implement the task + "tempTask ", // Name of the task + GUI_TASK_STACK, // Stack size in words + NULL, // Task input parameter + 5, // Priority of the task + &xHandle, // Task handle + 1); // Core where the task should run } void TFTUtils::displayInit() { @@ -47,7 +47,7 @@ void TFTUtils::displayInit() { ledcAttachPin(TFT_BL, pwmLedChannelTFT); #endif notifyBrightness(); - Serial.println("-->[TGUI] display config ready."); + log_i("[TGUI] display config ready."); } void TFTUtils::showWelcome() { @@ -64,11 +64,11 @@ void TFTUtils::showWelcome() { lastDrawedLine = 32; tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setTextFont(1); - Serial.println("-->[TGUI] displayed welcome screen"); + log_i("[TGUI] displayed welcome screen"); } void TFTUtils::welcomeAddMessage(String msg) { - // Serial.println("-->[TGUI] add message: "+msg); + log_d("[TGUI] add message: %s",msg); tft.setTextFont(1); tft.setCursor(5, lastDrawedLine); tft.println(msg.substring(0,21).c_str()); @@ -124,7 +124,7 @@ void TFTUtils::showMain() { if(!taskGUIrunning) setupGUITask(); // init GUI thread - Serial.println("-->[TGUI] displayed main screen"); + log_i("[TGUI] displayed main screen"); } void TFTUtils::showWindowBike(){ @@ -151,7 +151,7 @@ void TFTUtils::showWindowBike(){ loadLastData(); - Serial.println("-->[TGUI] displayed bike screen"); + log_i("[TGUI] displayed bike screen"); } void TFTUtils::showInfoWindow() { @@ -164,7 +164,7 @@ void TFTUtils::showInfoWindow() { tft.drawLine(18,44,117,44,TFT_GREY); state = 7; refreshInfoWindow(); - Serial.println("-->[TGUI] displayed info screen"); + log_i("[TGUI] displayed info screen"); } void TFTUtils::refreshInfoWindow() { @@ -219,7 +219,7 @@ void TFTUtils::showSetup() { tft.fillRect(68, SSTART, 1, 150, TFT_GREY); - Serial.println("-->[TGUI] displayed setup screen"); + log_i("[TGUI] displayed setup screen"); } void TFTUtils::refreshSetup() { @@ -336,7 +336,7 @@ void TFTUtils::updateCalibrationField(){ tft.setTextColor(TFT_WHITE, TFT_BLACK); tft.setCursor(MARVALL, SSTART + PRESETH * 4, 2); if (_calibration_counter > 0){ - Serial.println("-->[TGUI] coundown to calibration: "+String(_calibration_counter)); + log_i("[TGUI] coundown to calibration: %i",_calibration_counter); tft.println("" + String(_calibration_counter--) + "s"); } else if (_calibration_counter == 0) { @@ -352,7 +352,7 @@ void TFTUtils::updateCalibrationField(){ } void TFTUtils::startCalibration(){ - Serial.println("-->[TGUI] starting sensor calibration"); + log_i("[TGUI] starting sensor calibration"); _calibration_counter = 10; } @@ -517,9 +517,18 @@ void TFTUtils::displayMainHeader() { if (state != 0) return; tft.fillRect(2, 25, 130, 40, TFT_BLACK); if (wstate == 3 && toggle1s) return; - char output[6]; - if (_unit == 0) sprintf(output, "%04d", _mainValue); - else sprintf(output, "%04d", _minorValue); + char output[10]; + float value = 0.0; + if (_unit == 0) value = _mainValue; + else value = _minorValue; + + uint16_t m_int_val = value; + double m_tmp_val = value - m_int_val; + bool isInt = !(m_tmp_val > 0); + + if (isInt) sprintf(output, "%04d", m_int_val); + else sprintf(output, "%05.1f", value); + displayCenterBig(output); displayMainUnit(_unit_name, _unit_symbol); } @@ -632,7 +641,7 @@ void TFTUtils::displayGUIStatusFlags() { } } -uint32_t TFTUtils::getAQIColor(uint32_t value) { +uint32_t TFTUtils::getAQIColor(uint8_t value) { if (_colorType == AQI_COLOR::AQI_PM) { if (value <= 13) return 0; @@ -650,6 +659,31 @@ uint32_t TFTUtils::getAQIColor(uint32_t value) { else if (value <= 1500) return 3; else if (value <= 2000) return 4; else return 5; + + } + if (_colorType == AQI_COLOR::AQI_CO) { +//Conversion factor at 20ºC and 1013 hPa, 1ppm=1165 mg/m3 and 1 mg/m3=0,858 ppm +// 1 ppm is equivalent to 1.16mg/m3 for a gas with molecular weight=28.01, Pressure=1013.25 mbar, Temperature=20C ****** TO CHECK ******* +// 1 ppm is equivalent to 1.25mg/Nm3 for a gas with molecular weight=28.01, Pressure=1013.25 mbar, Temperature=0C + + if (value <= 6) return 0; + else if (value <= 8) return 1; + else if (value <= 11) return 2; + else if (value <= 17) return 3; + else if (value > 17) return 4; + else return 5; + + } + else if (_colorType == AQI_COLOR::AQI_NH3) { +//1 ppm is equivalent to 0.71mg/m3 for a gas with molecular weight=17.031, Pressure=1013.25 mbar, Temperature=20C +// 1 ppm is equivalent to 0.76mg/Nm3 for a gas with molecular weight=17.031, Pressure=1013.25 mbar, Temperature=0C + + if (value <= 7) return 0; + else if (value <= 10) return 1; + else if (value <= 14) return 2; + else if (value <= 21) return 3; + else if (value > 21) return 4; + else return 5; } else return 0; } @@ -731,11 +765,11 @@ void TFTUtils::drawDualLineGraph() { _average = _average / MAX_X; } -void TFTUtils::resetBuffer(uint32_t *buf) { +void TFTUtils::resetBuffer(uint8_t *buf) { for (int i = 0; i < MAX_X; i++) buf[i] = 0; } -double TFTUtils::getMultiplicator(uint32_t * buf) { +double TFTUtils::getMultiplicator(uint8_t * buf) { uint32_t maxVal = 1; for (int i = 0; i < MAX_X; i++) { if (buf[i] > maxVal) maxVal = buf[i]; @@ -869,6 +903,10 @@ void TFTUtils::resumeTaskGUI(){ if(taskGUIrunning) vTaskResume(xHandle); } +int32_t TFTUtils::getStackFree(){ + return uxTaskGetStackHighWaterMark(xHandle); +} + TFTUtils* TFTUtils::getInstance() { return this; } diff --git a/lib/gui-utils-tft/src/TFTUtils.hpp b/lib/gui-utils-tft/src/TFTUtils.hpp index 47e99fd6..e37e668f 100644 --- a/lib/gui-utils-tft/src/TFTUtils.hpp +++ b/lib/gui-utils-tft/src/TFTUtils.hpp @@ -29,13 +29,15 @@ #define blue 0x5D9B #define ligthgreen 0xF59F -enum AQI_COLOR { AQI_NONE, AQI_PM, AQI_CO2 }; +#define GUI_TASK_STACK 10000 + +enum AQI_COLOR { AQI_NONE, AQI_PM, AQI_CO2, AQI_CO, AQI_NH3 }; typedef struct GUIData { uint8_t mainUnitId; uint8_t onSelectionUnit; - uint16_t mainValue; - uint16_t minorValue; + float mainValue; + float minorValue; String unitName; String unitSymbol; float humi; @@ -115,6 +117,8 @@ class TFTUtils { void resumeTaskGUI(); + int32_t getStackFree(); + String getFirmwareVersionCode (); void loop(); @@ -160,9 +164,9 @@ class TFTUtils { int dh = 0; // display height - uint32_t bufGraphMain[MAX_X]; // Main graph buffer + uint8_t bufGraphMain[MAX_X]; // Main graph buffer - uint32_t bufGraphMinor[MAX_X]; // Secondary graph buffer + uint8_t bufGraphMinor[MAX_X]; // Secondary graph buffer int state = 0; @@ -186,11 +190,11 @@ class TFTUtils { float _temp = 0.0; - int _mainValue = 0; + float _mainValue = 0.0; int _mainUnitId = 0; - int _minorValue = 0; + float _minorValue = 0.0; String _unit_symbol = ""; @@ -274,11 +278,11 @@ class TFTUtils { void displayBigLabel(int cursor, String msg); - void resetBuffer(uint32_t * buf); + void resetBuffer(uint8_t * buf); - double getMultiplicator(uint32_t * buf); + double getMultiplicator(uint8_t * buf); - uint32_t getAQIColor(uint32_t value); + uint32_t getAQIColor(uint8_t value); void drawBarGraph(); diff --git a/lib/lorawan/functions.cpp b/lib/lorawan/functions.cpp new file mode 100644 index 00000000..a453d6f9 --- /dev/null +++ b/lib/lorawan/functions.cpp @@ -0,0 +1,60 @@ +#include "functions.h" +#include +#include + + +void PrintResetReason() +{ + Serial.print("MCUSR: "); + switch (rtc_get_reset_reason(0)) + { + case 1: + Serial.println("POWERON_RESET"); + break; /**<1, Vbat power on reset*/ + case 3: + Serial.println("SW_RESET"); + break; /**<3, Software reset digital core*/ + case 4: + Serial.println("OWDT_RESET"); + break; /**<4, Legacy watch dog reset digital core*/ + case 5: + Serial.println("DEEPSLEEP_RESET"); + break; /**<5, Deep Sleep reset digital core*/ + case 6: + Serial.println("SDIO_RESET"); + break; /**<6, Reset by SLC module, reset digital core*/ + case 7: + Serial.println("TG0WDT_SYS_RESET"); + break; /**<7, Timer Group0 Watch dog reset digital core*/ + case 8: + Serial.println("TG1WDT_SYS_RESET"); + break; /**<8, Timer Group1 Watch dog reset digital core*/ + case 9: + Serial.println("RTCWDT_SYS_RESET"); + break; /**<9, RTC Watch dog Reset digital core*/ + case 10: + Serial.println("INTRUSION_RESET"); + break; /**<10, Instrusion tested to reset CPU*/ + case 11: + Serial.println("TGWDT_CPU_RESET"); + break; /**<11, Time Group reset CPU*/ + case 12: + Serial.println("SW_CPU_RESET"); + break; /**<12, Software reset CPU*/ + case 13: + Serial.println("RTCWDT_CPU_RESET"); + break; /**<13, RTC Watch dog Reset CPU*/ + case 14: + Serial.println("EXT_CPU_RESET"); + break; /**<14, for APP CPU, reseted by PRO CPU*/ + case 15: + Serial.println("RTCWDT_BROWN_OUT_RESET"); + break; /**<15, Reset when the vdd voltage is not stable*/ + case 16: + Serial.println("RTCWDT_RTC_RESET"); + break; /**<16, RTC Watch dog reset digital core and rtc module*/ + default: + Serial.println("NO_MEAN"); + } + Serial.println(); +} diff --git a/lib/lorawan/functions.h b/lib/lorawan/functions.h new file mode 100644 index 00000000..11d01d59 --- /dev/null +++ b/lib/lorawan/functions.h @@ -0,0 +1,9 @@ +#pragma once +#ifndef _FUNCTIONS_H +#define _FUNCTIONS_H + +#include + +void PrintResetReason(void); + +#endif \ No newline at end of file diff --git a/lib/lorawan/io_pins.h b/lib/lorawan/io_pins.h new file mode 100644 index 00000000..0cc149a2 --- /dev/null +++ b/lib/lorawan/io_pins.h @@ -0,0 +1,9 @@ +// PIN definition for module +#define PIN_LMIC_NSS 18 +#define PIN_LMIC_RST 14 +#define PIN_LMIC_DIO0 26 +#define PIN_LMIC_DIO1 33 +#define PIN_LMIC_DIO2 32 + +#define LORA_TX_INTERVAL 300 +#define LMIC_LORA_SF DR_SF12 // LORA Data rate \ No newline at end of file diff --git a/lib/lorawan/lora_credentials.h b/lib/lorawan/lora_credentials.h new file mode 100644 index 00000000..0bf832bd --- /dev/null +++ b/lib/lorawan/lora_credentials.h @@ -0,0 +1,3 @@ +#define TTN_APPEUI { } // TTN Application EUI with "lsb" , also for Helium network hex format +#define TTN_DEVEUI { } // TTN Device EUI with "lsb" , also for Helium network hex format +#define TTN_APPKEY { } // TTN App Key with "msb" , also for Helium network hex format \ No newline at end of file diff --git a/lib/lorawan/lorawan.cpp b/lib/lorawan/lorawan.cpp new file mode 100644 index 00000000..16f2ad06 --- /dev/null +++ b/lib/lorawan/lorawan.cpp @@ -0,0 +1,410 @@ +#include "lorawan.h" +#include +#include +#include "io_pins.h" +#include "functions.h" + +#include "Batterylib.hpp" +#include +#include + +// Pin mapping +const lmic_pinmap lmic_pins = { + .nss = PIN_LMIC_NSS, + .rxtx = LMIC_UNUSED_PIN, + .rst = PIN_LMIC_RST, + .dio = {PIN_LMIC_DIO0, PIN_LMIC_DIO1, PIN_LMIC_DIO2}, +}; + + +// Schedule TX every this many seconds (might become longer due to duty cycle limitations). +const unsigned TX_INTERVAL = LORA_TX_INTERVAL; + +void os_getArtEui(u1_t *buf) { memcpy_P(buf, APPEUI, 8); } +void os_getDevEui(u1_t *buf) { memcpy_P(buf, DEVEUI, 8); } +void os_getDevKey(u1_t *buf) { memcpy_P(buf, APPKEY, 16); } + +bool GO_DEEP_SLEEP = false; + +RTC_DATA_ATTR lmic_t RTC_LMIC; + +DynamicJsonDocument jsonBuffer(4096); +CayenneLPP lpp(160); +//JsonObject root = jsonBuffer.to(); +JsonObject LORA_DATA = jsonBuffer.to(); + +void LoRaWANSetup() +{ + + + Serial.println(F("LoRaWAN_Setup ...")); + + Serial.print(F("Saved seqnoUp: ")); + Serial.println(LMIC.seqnoUp); + + // LMIC init + os_init(); + + // Let LMIC compensate for +/- 1% clock error + LMIC_setClockError(MAX_CLOCK_ERROR * 1 / 100); + + // Reset the MAC state. Session and pending data transfers will be discarded. + LMIC_reset(); + + if (RTC_LMIC.seqnoUp != 0) + { + LoraWANLoadLMICFromRTC(); + } + + // Start job + LoraWANDo_send(&sendjob); +} + +void LoraWANDo_send(osjob_t *j) +{ + // Check if there is not a current TX/RX job running + if (LMIC.opmode & OP_TXRXPEND) { + Serial.println(F("OP_TXRXPEND, not sending")); + } else if (LMIC.opmode & OP_TXDATA) { + Serial.println(F("OP_TXDATA, not sending")); + } else { + LoraWANGetData(); + + // Prepare upstream data transmission at the next possible time. + //LMIC_setTxData2(1, LORA_DATA, sizeof(LORA_DATA)-1, 0); + LMIC_setTxData2(1, (lpp.getBuffer()), sizeof(lpp.getSize()), 0); + Serial.println(F("Packet queued")); + } + // Next TX is scheduled after TX_COMPLETE event. +} + +void onEvent(ev_t ev) +{ + Serial.print(os_getTime()); + Serial.print(": "); + switch (ev) + { + case EV_SCAN_TIMEOUT: + Serial.println(F("EV_SCAN_TIMEOUT")); + break; + case EV_BEACON_FOUND: + Serial.println(F("EV_BEACON_FOUND")); + break; + case EV_BEACON_MISSED: + Serial.println(F("EV_BEACON_MISSED")); + break; + case EV_BEACON_TRACKED: + Serial.println(F("EV_BEACON_TRACKED")); + break; + case EV_JOINING: + Serial.println(F("EV_JOINING")); + break; + case EV_JOINED: + Serial.println(F("EV_JOINED")); +#ifndef DISABLE_JOIN + { + u4_t netid = 0; + devaddr_t devaddr = 0; + u1_t nwkKey[16]; + u1_t artKey[16]; + LMIC_getSessionKeys(&netid, &devaddr, nwkKey, artKey); + Serial.print("netid: "); + Serial.println(netid, DEC); + Serial.print("devaddr: "); + Serial.println(devaddr, HEX); + Serial.print("artKey: "); + for (size_t i = 0; i < sizeof(artKey); ++i) + { + Serial.print(artKey[i], HEX); + } + Serial.println(""); + Serial.print("nwkKey: "); + for (size_t i = 0; i < sizeof(nwkKey); ++i) + { + Serial.print(nwkKey[i], HEX); + } + Serial.println(""); + } + // Disable link check validation (automatically enabled + // during join, but because slow data rates change max TX + // size, we don't use it in this example. + LMIC_setLinkCheckMode(0); +#endif + break; + /* + || This event is defined but not used in the code. No + || point in wasting codespace on it. + || + || case EV_SCAN_FOUND: + || Serial.println(F("EV_SCAN_FOUND")); + || break; + */ + case EV_JOIN_FAILED: + Serial.println(F("EV_JOIN_FAILED")); + break; + case EV_REJOIN_FAILED: + Serial.println(F("EV_REJOIN_FAILED")); + break; + case EV_TXCOMPLETE: + Serial.println(F("EV_TXCOMPLETE")); + + if (LMIC.txrxFlags & TXRX_ACK) + { + Serial.println(F("Received ack")); + } + + if (LMIC.dataLen) + { + Serial.print(LMIC.dataLen); + Serial.println(F(" bytes of payload")); + } + + GO_DEEP_SLEEP = false; //true + + break; + case EV_LOST_TSYNC: + Serial.println(F("EV_LOST_TSYNC")); + break; + case EV_RESET: + Serial.println(F("EV_RESET")); + break; + case EV_RXCOMPLETE: + // data received in ping slot + Serial.println(F("EV_RXCOMPLETE")); + break; + case EV_LINK_DEAD: + Serial.println(F("EV_LINK_DEAD")); + break; + case EV_LINK_ALIVE: + Serial.println(F("EV_LINK_ALIVE")); + break; + /* This event is defined but not used in the code. + case EV_SCAN_FOUND: + DisplayPrintln(F("EV_SCAN_FOUND"), LORAWAN_STATE_DISPLAY_LINE); + break; + */ + case EV_TXSTART: + Serial.println(F("EV_TXSTART")); + break; + case EV_TXCANCELED: + Serial.println(F("EV_TXCANCELED")); + break; + case EV_RXSTART: + /* do not print anything -- it wrecks timing */ + break; + case EV_JOIN_TXCOMPLETE: + Serial.println(F("EV_JOIN_TXCOMPLETE: no JoinAccept")); + break; + default: + Serial.print(F("Unknown event: ")); + Serial.println((unsigned)ev); + break; + } +} + + +void LoraWANDo(void) +{ + long seconds = millis() / 1000; + static unsigned long last_runntime_info = 0; + static unsigned long runntime_info_ever_ms = 5000; + + if (GO_DEEP_SLEEP == true && !os_queryTimeCriticalJobs(ms2osticksRound((LORA_TX_INTERVAL * 1000)))) + { + Serial.println(F("Go to DeepSleep ...")); + Serial.print(F("Runtime was: ")); + Serial.print(seconds); + Serial.println(F(" seconds")); + + LoraWANSaveLMICToRTC(LORA_TX_INTERVAL); + Serial.flush(); + + //PowerDeepSleepTimer(LORA_TX_INTERVAL - 30 - 8); // 30sec for SDS011, 8 sec for remaining code + } + else + { + if(last_runntime_info + runntime_info_ever_ms < millis()) + { + Serial.print("Runtime: "); + Serial.print(seconds); + Serial.println(" seconds"); + #ifndef PRINTDEBUGS + LoraWANDebug(LMIC); + #endif + last_runntime_info = millis(); + } + + os_runloop_once(); + } +} + +void LoraWANGetData() +{ + float humi = sensors.getHumidity(); + if (humi == 0.0) humi = sensors.getCO2humi(); + float temp = sensors.getTemperature(); + if (temp == 0.0) temp = sensors.getCO2temp(); + lpp.reset(); + lpp.addGenericSensor(1,(sensors.getPM1())); + lpp.addGenericSensor(2,(sensors.getPM25())); + lpp.addGenericSensor(3,sensors.getPM10()); + lpp.addGenericSensor(4,sensors.getCO2()); + lpp.addGenericSensor(5,sensors.getCO2humi()); + lpp.addGenericSensor(6,sensors.getCO2temp()); + lpp.addTemperature(7,temp); + lpp.addRelativeHumidity(8,humi); + //lpp.addGPS(9,geo); + lpp.addBarometricPressure(10,(sensors.getPressure())); + lpp.addGenericSensor(11,(sensors.getGas())); + lpp.addGenericSensor(12,(sensors.getNH3())); + lpp.addGenericSensor(13,(sensors.getCO())); + lpp.addAltitude(14,(sensors.getAltitude())); + lpp.addGenericSensor(15,(battery.getCharge())); + lpp.addVoltage(16,(battery.getVoltage())); + //lpp.addGenericSensor(17,(WiFi.RSSI())); + lpp.addGenericSensor(18,(ESP.getFreeHeap())); + //lpp.addGenericSensor(19,(cfg.getStationName().c_str())); + //lpp.addGenericSensor(20,(cfg.getVersion())); + //lpp.addGenericSensor(21,(cfg.deviceId.c_str())); + + lpp.decodeTTN(lpp.getBuffer(), lpp.getSize(), LORA_DATA); + serializeJsonPretty(LORA_DATA, Serial); + + Serial.println(); +} + +void LoraWANSaveLMICToRTC(int deepsleep_sec) +{ + Serial.println(F("Save LMIC to RTC ...")); + RTC_LMIC = LMIC; + + //System time is resetted after sleep. So we need to calculate the dutycycle with a resetted system time + unsigned long now = millis(); + + // EU Like Bands +#if defined(CFG_LMIC_EU_like) + for(int i = 0; i < MAX_BANDS; i++) { + ostime_t correctedAvail = RTC_LMIC.bands[i].avail - ((now/1000.0 + deepsleep_sec ) * OSTICKS_PER_SEC); + if(correctedAvail < 0) { + correctedAvail = 0; + } + RTC_LMIC.bands[i].avail = correctedAvail; + } + + RTC_LMIC.globalDutyAvail = RTC_LMIC.globalDutyAvail - ((now/1000.0 + deepsleep_sec ) * OSTICKS_PER_SEC); + if(RTC_LMIC.globalDutyAvail < 0) + { + RTC_LMIC.globalDutyAvail = 0; + } +#else + Serial.println("No DutyCycle recalculation function!") +#endif + + #ifndef PRINTDEBUGS + LoraWANDebug(RTC_LMIC); + #endif +} + +void LoraWANLoadLMICFromRTC() +{ + Serial.println(F("Load LMIC vars from RTC ...")); + LMIC = RTC_LMIC; + + #ifndef PRINTDEBUGS + LoraWANDebug(RTC_LMIC); + #endif +} + +void LoraWANPrintVersion(void) +{ + Serial.print(F("LMIC Version: ")); + Serial.print(ARDUINO_LMIC_VERSION_GET_MAJOR (ARDUINO_LMIC_VERSION) ); + Serial.print(F(".")); + Serial.print(ARDUINO_LMIC_VERSION_GET_MINOR (ARDUINO_LMIC_VERSION) ); + Serial.print(F(".")); + Serial.print(ARDUINO_LMIC_VERSION_GET_PATCH (ARDUINO_LMIC_VERSION) ); + Serial.print(F(".")); + Serial.println(ARDUINO_LMIC_VERSION_GET_LOCAL (ARDUINO_LMIC_VERSION) ); +} + +// opmode def +// https://github.com/mcci-catena/arduino-lmic/blob/89c28c5888338f8fc851851bb64968f2a493462f/src/lmic/lmic.h#L233 +void LoraWANPrintLMICOpmode(void) +{ + Serial.print(F("LMIC.opmode: ")); + if (LMIC.opmode & OP_NONE) { Serial.print(F("OP_NONE ")); } + if (LMIC.opmode & OP_SCAN) { Serial.print(F("OP_SCAN ")); } + if (LMIC.opmode & OP_TRACK) { Serial.print(F("OP_TRACK ")); } + if (LMIC.opmode & OP_JOINING) { Serial.print(F("OP_JOINING ")); } + if (LMIC.opmode & OP_TXDATA) { Serial.print(F("OP_TXDATA ")); } + if (LMIC.opmode & OP_POLL) { Serial.print(F("OP_POLL ")); } + if (LMIC.opmode & OP_REJOIN) { Serial.print(F("OP_REJOIN ")); } + if (LMIC.opmode & OP_SHUTDOWN) { Serial.print(F("OP_SHUTDOWN ")); } + if (LMIC.opmode & OP_TXRXPEND) { Serial.print(F("OP_TXRXPEND ")); } + if (LMIC.opmode & OP_RNDTX) { Serial.print(F("OP_RNDTX ")); } + if (LMIC.opmode & OP_PINGINI) { Serial.print(F("OP_PINGINI ")); } + if (LMIC.opmode & OP_PINGABLE) { Serial.print(F("OP_PINGABLE ")); } + if (LMIC.opmode & OP_NEXTCHNL) { Serial.print(F("OP_NEXTCHNL ")); } + if (LMIC.opmode & OP_LINKDEAD) { Serial.print(F("OP_LINKDEAD ")); } + if (LMIC.opmode & OP_LINKDEAD) { Serial.print(F("OP_LINKDEAD ")); } + if (LMIC.opmode & OP_TESTMODE) { Serial.print(F("OP_TESTMODE ")); } + if (LMIC.opmode & OP_UNJOIN) { Serial.print(F("OP_UNJOIN ")); } + Serial.println(""); +} + +void LoraWANDebug(lmic_t lmic_to_check) +{ + Serial.println(""); + Serial.println(""); + + LoraWANPrintLMICOpmode(); + + Serial.print("LMIC.seqnoUp = "); + Serial.println(lmic_to_check.seqnoUp); + + Serial.print("LMIC.globalDutyRate = "); + Serial.print(lmic_to_check.globalDutyRate); + Serial.print(" osTicks, "); + Serial.print(osticks2ms(lmic_to_check.globalDutyRate)/1000); + Serial.println(" sec"); + + Serial.print("LMIC.globalDutyAvail = "); + Serial.print(lmic_to_check.globalDutyAvail); + Serial.print(" osTicks, "); + Serial.print(osticks2ms(lmic_to_check.globalDutyAvail)/1000); + Serial.println(" sec"); + + Serial.print("LMICbandplan_nextTx = "); + Serial.print(LMICbandplan_nextTx(os_getTime())); + Serial.print(" osTicks, "); + Serial.print(osticks2ms(LMICbandplan_nextTx(os_getTime()))/1000); + Serial.println(" sec"); + + Serial.print("os_getTime = "); + Serial.print(os_getTime()); + Serial.print(" osTicks, "); + Serial.print(osticks2ms(os_getTime()) / 1000); + Serial.println(" sec"); + + Serial.print("LMIC.txend = "); + Serial.println(lmic_to_check.txend); + Serial.print("LMIC.txChnl = "); + Serial.println(lmic_to_check.txChnl); + + Serial.println("Band \tavail \t\tavail_sec\tlastchnl \ttxcap"); + for (u1_t bi = 0; bi < MAX_BANDS; bi++) + { + Serial.print(bi); + Serial.print("\t"); + Serial.print(lmic_to_check.bands[bi].avail); + Serial.print("\t\t"); + Serial.print(osticks2ms(lmic_to_check.bands[bi].avail)/1000); + Serial.print("\t\t"); + Serial.print(lmic_to_check.bands[bi].lastchnl); + Serial.print("\t\t"); + Serial.println(lmic_to_check.bands[bi].txcap); + + } + Serial.println(""); + Serial.println(""); +} \ No newline at end of file diff --git a/lib/lorawan/lorawan.h b/lib/lorawan/lorawan.h new file mode 100644 index 00000000..0c0501cd --- /dev/null +++ b/lib/lorawan/lorawan.h @@ -0,0 +1,45 @@ +#pragma once +#ifndef _LORAWAN_H +#define _LORAWAN_H + +#include +#include +#include "lora_credentials.h" + + +// This EUI must be in little-endian format, so least-significant-byte +// first. When copying an EUI from ttnctl output, this means to reverse +// the bytes. For TTN issued EUIs the last bytes should be 0xD5, 0xB3, +// 0x70. +static const u1_t PROGMEM APPEUI[8] = TTN_APPEUI; + +// This should also be in little endian format, see above. +static const u1_t PROGMEM DEVEUI[8] = TTN_DEVEUI; + +// This key should be in big endian format (or, since it is not really a +// number but a block of memory, endianness does not really apply). In +// practice, a key taken from ttnctl can be copied as-is. +static const u1_t PROGMEM APPKEY[16] = TTN_APPKEY; + +//static uint8_t LORA_DATA[] = "Only test"; +//static uint8_t LORA_DATA[21]; +static osjob_t sendjob; + +// LMIC State save for reboot +extern RTC_DATA_ATTR lmic_t RTC_LMIC; + +void os_getArtEui(u1_t *buf); +void os_getDevEui(u1_t *buf); +void os_getDevKey(u1_t *buf); + +void LoRaWANSetup(void); +void LoraWANDo_send(osjob_t *j); +void LoraWANDo(void); +void LoraWANGetData(void); +void LoraWANSaveLMICToRTC(int deepsleep_sec); +void LoraWANLoadLMICFromRTC(void); +void LoraWANPrintVersion(void); +void LoraWANPrintLMICOpmode(void); +void LoraWANDebug(lmic_t lmic_to_check); + +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index d867dda5..91f62ba4 100644 --- a/platformio.ini +++ b/platformio.ini @@ -10,7 +10,7 @@ [platformio] default_envs = TTGO_TDISPLAY -; extra_configs = ../canairio_sensorlib/unified-lib-deps.ini ; only for local tests of sensorslib +;extra_configs = ../canairio_sensorlib/unified-lib-deps.ini ; only for local tests of sensorslib [common] build_type = release @@ -18,31 +18,33 @@ platform = espressif32 @ 4.4.0 framework = arduino upload_speed = 1500000 monitor_speed = 115200 -version = 0.5.8 -revision = 951 +version = 0.6.1 +revision = 978 # OTA remote update target (change it to none, if you want prevent CanAirIO updates) -target = prod +target = dev monitor_filters = esp32_exception_decoder - time + ; time extra_scripts = pre:prebuild.py build_flags = -D CORE_DEBUG_LEVEL=0 # For debugging set to 3 and enable debug mode in the app -D MIN_PUBLISH_INTERVAL=30 # Minimum interval between clouds updates - -D MAIN_HW_EN_PIN=27 # enable the main hardware pin (main sensor) -D WAIT_FOR_PM_SENSOR=25 # time of stabilization of PM sensors in seconds + -D ARDUINO_ESP32_DEV=1 # compatibilty for DFRobot NH3/CO library -D DISABLE_ALL_LIBRARY_WARNINGS=1 -; -D ENABLE_OTA # disable for memory saving, we have FOTA enable + ; -D DISABLE_CLI # uncomment to only have Bluetooth config via + ; -D ENABLE_OTA # disable for memory saving, we have FOTA enable lib_deps = - bblanchon/ArduinoJson @ 6.20.0 - ; chrisjoyce911/esp32FOTA @ 0.2.7 - https://github.com/hpsaturn/esp32FOTA.git#fix_log_newline - https://github.com/rlogiacco/CircularBuffer.git + bblanchon/ArduinoJson @ 6.21.2 hpsaturn/ESP32 Wifi CLI @ 0.2.1 + https://github.com/chrisjoyce911/esp32FOTA.git#2bbc9cb + https://github.com/rlogiacco/CircularBuffer.git#f29cf01 https://github.com/256dpi/arduino-mqtt.git#7afcfb1 https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino.git#8e5f051 - hpsaturn/CanAirIO Air Quality Sensors Library @ 0.6.7 - ; ${commonlibs.lib_deps} + + ; hpsaturn/CanAirIO Air Quality Sensors Library @ 0.7.2 + https://github.com/kike-canaries/canairio_sensorlib.git#devel + ; ${commonlibs.lib_deps} ; only for local tests of sensorslib [esp32_common] build_type = ${common.build_type} @@ -51,15 +53,19 @@ board = lolin32 framework = ${common.framework} upload_speed = ${common.upload_speed} monitor_speed = ${common.monitor_speed} -build_flags = ${common.build_flags} lib_deps = ${common.lib_deps} monitor_filters = ${common.monitor_filters} extra_scripts = ${common.extra_scripts} board_build.partitions = min_spiffs.csv +build_flags = + ${common.build_flags} + -D MAIN_HW_EN_PIN=27 # enable the main hardware pin (main sensor) [oled_common] extends = esp32_common -lib_ignore = gui-utils-tft +lib_ignore = + gui-utils-tft + lorawan lib_deps = ${common.lib_deps} U8g2 @ ^2.28.8 @@ -86,58 +92,110 @@ upload_speed = 921600 [env:ESP32PICOD4] extends = oled_common -[env:TTGO_TDISPLAY] -extends = esp32_common +[env:M5ATOM] +extends = oled_common board = esp32dev lib_deps = - ${common.lib_deps} - bodmer/TFT_eSPI @ 2.5.23 + ${oled_common.lib_deps} + fastled/FastLED@^3.5.0 + m5stack/M5Atom@^0.1.0 + +[env:M5PICOD4] +extends = oled_common +build_flags = + ${esp32_common.build_flags} + -D MAIN_HW_PIN=19 + +[tft_common] +extends = esp32_common +board = esp32dev lib_ignore = gui-utils-oled + lorawan + +[env:TTGO_TDISPLAY] +extends = tft_common +lib_deps = + ${common.lib_deps} + bodmer/TFT_eSPI @ 2.5.31 build_flags = - ${common.build_flags} + ${esp32_common.build_flags} -D USER_SETUP_LOADED=1 - -include /$PROJECT_LIBDEPS_DIR/$PIOENV/TFT_eSPI/User_Setups/Setup25_TTGO_T_Display.h + -include .pio/libdeps/${PIOENV}/TFT_eSPI/User_Setups/Setup25_TTGO_T_Display.h [env:M5STICKCPLUS] -extends = esp32_common -board = esp32dev +extends = tft_common lib_deps = ${common.lib_deps} https://github.com/hpsaturn/M5StickC-Plus.git -lib_ignore = - gui-utils-oled -[env:ESP32C3] +[esp32c3_common] platform = espressif32 @ 5.3.0 extends = oled_common + +[env:ESP32C3] +extends = esp32c3_common board = esp32-c3-devkitm-1 [env:ESP32C3OIPLUS] -platform = espressif32 @ 5.3.0 -extends = oled_common +extends = esp32c3_common board = ttgo-t-oi-plus [env:ESP32C3LOLIN] -platform = espressif32 @ 5.3.0 -extends = oled_common +extends = esp32c3_common board = lolin_c3_mini [env:ESP32C3SEEDX] -platform = espressif32 @ 5.3.0 -extends = oled_common +extends = esp32c3_common board = seeed_xiao_esp32c3 -[env:M5ATOM] -extends = oled_common -board = esp32dev -lib_deps = - ${oled_common.lib_deps} - fastled/FastLED@^3.5.0 - m5stack/M5Atom@^0.1.0 - -[env:M5PICOD4] +[esp32s3_common] extends = oled_common +platform = espressif32 @ 6.5.0 +board = esp32-s3-devkitc-1 +board_build.mcu = esp32s3 +board_build.f_cpu = 240000000L build_flags = ${common.build_flags} - -D MAIN_HW_PIN=19 + -D BOARD_HAS_PSRAM=1 + -mfix-esp32-psram-cache-issue + -D MAIN_HW_EN_PIN=3 # enable the main hardware pin (main sensor) + +; ESP32S3 DEVKIT (Also ESP32S3 CAM) +[env:ESP32S3] +extends = esp32s3_common + +[env:TTGO_T7S3] +extends = esp32s3_common +build_flags = + ${esp32s3_common.build_flags} + -D ARDUINO_USB_CDC_ON_BOOT=1 + +[env:LORADEVKIT] +extends = esp32_common +upload_speed = 921600 +board = ttgo-lora32-v1 +lib_ignore = + gui-utils-tft +lib_deps = + ${oled_common.lib_deps} + mcci-catena/MCCI LoRaWAN LMIC library @ ^4.1.1 + lorawan + ;nanopb/Nanopb@^0.4.7 + ;sabas1080/CayenneLPP @ ^1.1.0 + https://github.com/ElectronicCats/CayenneLPP +build_flags = + ${esp32_common.build_flags} + -D RXD2=23 + -D TXD2=17 + -D hal_init=LMICHAL_init + -D ARDUINO_LMIC_PROJECT_CONFIG_H_SUPPRESS + -D CFG_eu868=1 + -D CFG_sx1276_radio=1 + -D LMIC_PRINTF_TO=Serial + -D LMIC_DEBUG_LEVEL=2 + -D DISABLE_PING=1 + -D DISABLE_BEACONS=1 + -D ARDUINO_SAMD_VARIANT_COMPLIANCE=1 + -D TTGOLORA32V1 + ;lib_ignore = gui-utils-tft diff --git a/prebuild.py b/prebuild.py index 4d727b40..db0e38ff 100755 --- a/prebuild.py +++ b/prebuild.py @@ -35,6 +35,9 @@ if flavor == "ESP32C3" or flavor == "ESP32C3OIPLUS" or flavor == "ESP32C3LOLIN" or flavor == "ESP32C3SEEDX": chipFamily = "ESP32-C3" +if flavor == "ESP32S3" or flavor == "TTGO_T7S3": + chipFamily = "ESP32-S3" + # get runtime credentials and put them to compiler directive env.Append(BUILD_FLAGS=[ u'-DREVISION=' + revision + '', diff --git a/src/bluetooth.cpp b/src/bluetooth.cpp index 1dea4e4a..7123fc84 100644 --- a/src/bluetooth.cpp +++ b/src/bluetooth.cpp @@ -38,6 +38,8 @@ String getSensorData() { doc["hum"] = sensors.getHumidity(); doc["alt"] = sensors.getAltitude(); doc["pre"] = sensors.getPressure(); + doc["nh3"] = sensors.getNH3(); + doc["co"] = sensors.getCO(); doc["bat"] = battery.getCharge(); doc["vol"] = battery.getVoltage(); doc["PAX"] = getPaxCount(); diff --git a/src/cli.cpp b/src/cli.cpp new file mode 100644 index 00000000..af82f542 --- /dev/null +++ b/src/cli.cpp @@ -0,0 +1,365 @@ +#ifndef DISABLE_CLI +#include +#include + +bool setup_mode = false; +int setup_time = 10000; +bool first_run = true; + +TaskHandle_t xCliHandle; + +void wcli_debug(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + String param = operands.first(); + param.toUpperCase(); + bool dbgmode = param.equals("ON") || param.equals("1"); + cfg.debugEnable(dbgmode); + cfg.devmode = dbgmode; + sensors.setDebugMode(dbgmode); + battery.debug = dbgmode; +} + +bool isValidKey(String key) { + for (int i = 0; i < KCOUNT; i++) { + if (key.equals(cfg.getKey((CONFKEYS)i))) return true; + } + return false; +} + +String getValue(String key) { + ConfKeyType type = cfg.getKeyType(key); + if (type == ConfKeyType::BOOL) return cfg.getBool(key, false) ? "true" : "false"; + if (type == ConfKeyType::FLOAT) return String(cfg.getFloat(key, false)); + if (type == ConfKeyType::INT) return String(cfg.getInt(key, false)); + if (type == ConfKeyType::STRING) return cfg.getString(key, ""); + return ""; +} + +void wcli_klist(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + String opt = operands.first(); + int key_count = KCOUNT; // Show all keys to configure + if (opt.equals("basic")) key_count = KBASIC; // Only show the basic keys to configure + Serial.printf("\n%11s \t%s \t%s \r\n", "KEYNAME", "DEFINED", "VALUE"); + Serial.printf("\n%11s \t%s \t%s \r\n", "=======", "=======", "====="); + + for (int i = 0; i < key_count; i++) { + if(i==KBASIC) continue; + String key = cfg.getKey((CONFKEYS)i); + bool isDefined = cfg.isKey(key); + String defined = isDefined ? "custom " : "default"; + String value = ""; + if (isDefined) value = getValue(key); + Serial.printf("%11s \t%s \t%s \r\n", key, defined.c_str(), value.c_str()); + } + + Serial.printf("\r\nMore info: https://canair.io/docs/cli\r\n"); +} + +void saveInteger(String key, String v) { + int32_t value = v.toInt(); + cfg.saveInt(key, value); + Serial.printf("saved: %s:%i\r\n",key.c_str(),value); +} + +void saveFloat(String key, String v) { + float value = v.toFloat(); + cfg.saveFloat(key, value); + Serial.printf("saved: %s:%.5f\r\n",key.c_str(),value); +} + +void saveBoolean(String key, String v) { + v.toLowerCase(); + cfg.saveBool(key, v.equals("on") || v.equals("1") || v.equals("enable") || v.equals("true")); + Serial.printf("saved: %s:%s\r\n", key.c_str(), cfg.getBool(key, false) ? "true" : "false"); +} + +void saveString(String key, String v) { + cfg.saveString(key, v); + Serial.printf("saved: %s:%s\r\n",key.c_str(),v.c_str()); +} + +void wcli_kset(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + String key = operands.first(); + String v = operands.second(); + if(isValidKey(key)){ + if(cfg.getKeyType(key) == ConfKeyType::BOOL) saveBoolean(key,v); + else if(cfg.getKeyType(key) == ConfKeyType::FLOAT) saveFloat(key,v); + else if(cfg.getKeyType(key) == ConfKeyType::INT) saveInteger(key,v); + else if(cfg.getKeyType(key) == ConfKeyType::STRING) saveString(key,v); + else Serial.println("Invalid key action for: " + key); + } + else { + Serial.printf("invalid key: %s\r\nPlease see the valid keys with klist command.\r\n",key.c_str()); + } +} + +void wcli_uartpins(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + int sTX = operands.first().toInt(); + int sRX = operands.second().toInt(); + if (sTX >= 0 && sRX >= 0) { + cfg.saveSensorPins(sTX, sRX); + } + else + Serial.println("invalid pins values"); +} + +bool validBattLimits(float min, float max){ + return (min >= 3.0 && min <= 5.0 && max <=5.0 && max >= 3.0); +} + +void wcli_battvLimits(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + float battMin = operands.first().toFloat(); + float battMax = operands.second().toFloat(); + if (validBattLimits(battMin, battMax)) { + Serial.printf("Battery limits: Vmin: %2.2f Vmax: %2.2f\r\n", battMin, battMax); + battery.setBattLimits(battMin, battMax); + cfg.saveFloat(CONFKEYS::KBATVMI,battMin); + cfg.saveFloat(CONFKEYS::KBATVMX,battMax); + } + else { + Serial.println("-->[BATT] !invalid battery value! Current values:"); + battery.printLimits(); + } +} + +void wcli_chargLimits(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + float battMin = operands.first().toFloat(); + float battMax = operands.second().toFloat(); + if (validBattLimits(battMin, battMax)) { + Serial.printf("Battery charging limits: Vmin: %2.2f Vmax: %2.2f\r\n", battMin, battMax); + battery.setChargLimits(battMin, battMax); + cfg.saveFloat(CONFKEYS::KCHRVMI,battMin); + cfg.saveFloat(CONFKEYS::KCHRVMX,battMax); + } + else { + Serial.println("-->[BATT] !invalid battery value! Current values:"); + battery.printLimits(); + } +} + +void wcli_stime(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + int stime = operands.first().toInt(); + if (stime >= 5) { + cfg.saveSampleTime(stime); + sensors.setSampleTime(stime); + } + else + Serial.println("invalid sample time"); +} + +void wcli_stype_error(){ + Serial.println("invalid UART sensor type! Choose one into 0-7:"); + for (int i=0; i<=7 ;i++)Serial.printf("%i\t%s\r\n",i,sensors.getSensorName((SENSORS)i)); +} + +void wcli_stype(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + String stype = operands.first(); + if(stype.length()==0){ + wcli_stype_error(); + return; + } + int type = stype.toInt(); + if (type > 7 || type < 0) wcli_stype_error(); + else { + cfg.saveSensorType(type); + Serial.printf("\nselected UART sensor model\t: %s\r\n", sensors.getSensorName((SENSORS)type)); + Serial.println("Please reboot to changes apply"); + } +} + +void wcli_sgeoh (String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + String geoh = operands.first(); + if (geoh.length() > 5) { + geoh.toLowerCase(); + cfg.saveGeo(geoh); + cfg.ifxdbEnable(true); + } else { + Serial.println("\nInvalid Geohash. (Precision should be > to 5).\r\n"); + Serial.println("Please visit: http://bit.ly/geohashe"); + Serial.println("\nand select one of your fixed station."); + } +} + +void wcli_sensors() { + Serial.printf("\r\nCanAirIO Sensorslib\t: %s\r\n",sensors.getLibraryVersion().c_str()); + int i = 0; + int count = sensors.getSensorsRegisteredCount(); + Serial.printf("Sensors count \t\t: %i (", count); + if (count > 0 && sensors.getSensorsRegistered()[0] == SENSORS::Auto) { + Serial.printf("%s,", sensors.getSensorName((SENSORS)sensors.getSensorsRegistered()[0])); + i = 1; + } + while (sensors.getSensorsRegistered()[i++] != 0) { + Serial.printf("%s,", sensors.getSensorName((SENSORS)sensors.getSensorsRegistered()[i-1])); + } + Serial.println(")"); +} + +void wcli_sensors_values() { + Serial.println("\r\nCurrent sensors values:"); + UNIT unit = sensors.getNextUnit(); + while(unit != UNIT::NUNIT) { + String uName = sensors.getUnitName(unit); + float uValue = sensors.getUnitValue(unit); + String uSymb = sensors.getUnitSymbol(unit); + Serial.printf(" %s:\t%02.1f\t%s\r\n", uName.c_str(), uValue, uSymb.c_str()); + unit = sensors.getNextUnit(); + } +} + +void wcli_info(String opts) { + Serial.println(); + Serial.print(getDeviceInfo()); + wcli_sensors(); + wcli_sensors_values(); +} + +void wcli_exit(String opts) { + setup_time = 0; + setup_mode = false; +} + +void wcli_setup(String opts) { + setup_mode = true; + Serial.println("\r\nSetup Mode. Main presets:\r\n"); + String canAirIOname = "Please set your geohash with \"sgeoh\" cmd"; + if(cfg.geo.length()>5)canAirIOname = cfg.getStationName(); + Serial.printf("CanAirIO device id\t: %s\r\n", canAirIOname.c_str()); + Serial.printf("Device factory id\t: %s\r\n", cfg.getAnaireDeviceId().c_str()); + Serial.printf("Sensor geohash id\t: %s\r\n", cfg.geo.length() == 0 ? "undefined" : cfg.geo.c_str()); + Serial.printf("WiFi current status\t: %s\r\n", WiFi.status() == WL_CONNECTED ? "connected" : "disconnected"); + Serial.printf("Sensor sample time \t: %d\r\n", cfg.stime); + Serial.printf("UART sensor model \t: %s\r\n", sensors.getSensorName((SENSORS)cfg.stype)); + Serial.printf("UART sensor TX pin\t: %d\r\n", cfg.sTX == -1 ? PMS_TX : cfg.sTX); + Serial.printf("UART sensor RX pin\t: %d\r\n", cfg.sRX == -1 ? PMS_RX : cfg.sRX); + Serial.printf("Current debug mode\t: %s\r\n", cfg.devmode == true ? "enabled" : "disabled"); + + wcli_klist("basic"); + + Serial.printf("\r\nType \"klist\" for advanced settings\r\n"); + Serial.printf("Type \"help\" for available commands details\r\n"); + Serial.printf("Type \"exit\" for leave the safe mode\r\n"); +} + +void wcli_reboot(String opts) { + wd.execute(); +} + +void wcli_clear(String opts) { + maschinendeck::Pair operands = maschinendeck::SerialTerminal::ParseCommand(opts); + String deviceId = operands.first(); + if (deviceId.equals(cfg.getAnaireDeviceId())) { + Serial.println("Clearing device to defaults.."); + wcli.clearSettings(); + cfg.clear(); + } + else { + Serial.println("\nPlease type clear and the factory device id to confirm."); + } +} + +class mESP32WifiCLICallbacks : public ESP32WifiCLICallbacks { + void onWifiStatus(bool isConnected) { + + } + + void onHelpShow() { + // Enter your custom help here: + Serial.println("\r\nCanAirIO Commands:\r\n"); + Serial.println("reboot\t\t\tperform a soft ESP32 reboot"); + Serial.println("clear\t\t\tfactory settings reset. (needs confirmation)"); + Serial.println("debug\t\tto enable debug mode"); + Serial.println("stime\t