diff --git a/platformio/libraries/SensorDriver/SensorDriver.cpp b/platformio/libraries/SensorDriver/SensorDriver.cpp index 0a5953635..3370ccecb 100644 --- a/platformio/libraries/SensorDriver/SensorDriver.cpp +++ b/platformio/libraries/SensorDriver/SensorDriver.cpp @@ -4607,7 +4607,7 @@ void SensorDriverSps::prepare(bool is_test) { SensorDriver::printInfo(); *_is_prepared =_sps30.start(); - if (*_is_prepared){ + if (*_is_setted){ _error_count = 0; LOGT(F("SPS prepare... [ %s ]"), OK_STRING); }else{ @@ -4940,46 +4940,58 @@ void SensorDriverScd::resetPrepared(bool is_test) { void SensorDriverScd::setup() { SensorDriver::printInfo(); - _delay_ms = 0; - if (!*_is_setted) { - _scd30.begin(_address); //This will cause readings to occur every two seconds - - /* - Maximal I2C speed is 100 kHz and the master has to support clock - stretching. Clock stretching period in write- and read- frames is 12 - ms, however, due to internal calibration processes a maximal clock - stretching of 150 ms may occur once per day. For detailed information - to the I2C protocol, refer to NXP I2C-bus specification 1 . SCD30 does - not support repeated start condition. Clock stretching is necessary to - start the microcontroller and might occur before every ACK. I2C master - clock stretching needs to be implemented according to the NXP - specification. The boot-up time is < 2 s. + /* + Maximal I2C speed is 100 kHz and the master has to support clock + stretching. Clock stretching period in write- and read- frames is 12 + ms, however, due to internal calibration processes a maximal clock + stretching of 150 ms may occur once per day. For detailed information + to the I2C protocol, refer to NXP I2C-bus specification 1 . SCD30 does + not support repeated start condition. Clock stretching is necessary to + start the microcontroller and might occur before every ACK. I2C master + clock stretching needs to be implemented according to the NXP + specification. The boot-up time is < 2 s. */ - _scd30.sendCommand(COMMAND_SOFT_RESET); - delay(50); // ??? not explained in documentation - if(!_scd30.beginMeasuring()) { //Start continuous measurements - _error_count++; - LOGE(F("SCD beginMeasuring error")); - //LOGE(F("SCD setup... [ %s ]"), ERROR_STRING); - return; - } - if(!_scd30.setMeasurementInterval(2)) { //2 seconds between measurements - _error_count++; - LOGE(F("SCD setMeasurementInterval error")); - //LOGE(F("SCD setup... [ %s ]"), ERROR_STRING); - return; - } - if (!_scd30.setAutoSelfCalibration(false)) { //Enable auto-self-calibration - _error_count++; - LOGE(F("SCD setAutoSelfCalibration error")); - //LOGE(F("SCD setup... [ %s ]"), ERROR_STRING); - return; - } - *_is_setted = true; - _error_count = 0; - LOGT(F("SCD setup... [ %s ]"), YES_STRING); - } + if (_address != SCD30_ADDRESS){ + LOGE(F("SCD address error")); + return; + } + + _scd30.begin(Wire,false,false); // begin(TwoWire &wirePort, bool autoCalibrate, bool measBegin) + + _scd30.reset(); // this make the first measure not releable + delay(50); // ??? not explained in documentation + + if (!_scd30.isConnected()){ + _error_count++; + LOGE(F("SCD setup error")); + return; + } + + if (!_scd30.setAutoSelfCalibration(false)) { //Force disable auto-self-calibration + _error_count++; + LOGE(F("SCD setAutoSelfCalibration error")); + //LOGE(F("SCD setup... [ %s ]"), ERROR_STRING); + return; + } + + if(!_scd30.setMeasurementInterval(2)) { //2 seconds between measurements + _error_count++; + LOGE(F("SCD setMeasurementInterval error")); + //LOGE(F("SCD setup... [ %s ]"), ERROR_STRING); + return; + } + + if(!_scd30.beginMeasuring()) { //Start continuous measurements + _error_count++; + LOGE(F("SCD beginMeasuring error")); + //LOGE(F("SCD setup... [ %s ]"), ERROR_STRING); + return; + } + + *_is_setted = true; + _error_count = 0; + LOGT(F("SCD setup... [ %s ]"), YES_STRING); } bool SensorDriverScd::setForcedRecalibrationFactor(uint16_t value) { @@ -4988,26 +5000,16 @@ bool SensorDriverScd::setForcedRecalibrationFactor(uint16_t value) { void SensorDriverScd::prepare(bool is_test) { SensorDriver::printInfo(); - + // clear previous measurements _scd30.getCO2(); - _scd30.getTemperature(); - _scd30.getHumidity(); + //_scd30.getTemperature(); + //_scd30.getHumidity(); + - //_scd30->beginMeasuring(); *_is_prepared =true; _error_count = 0; LOGT(F("SCD prepare... [ %s ]"), OK_STRING); - - /* - if (*_is_prepared){ - _error_count = 0; - LOGT(F("SCD prepare... [ %s ]"), OK_STRING); - }else{ - _error_count++; - LOGE(F("SCD prepare... [ %s ]"), FAIL_STRING); - } - */ _delay_ms = 2500ul; _start_time_ms = millis(); @@ -5051,20 +5053,30 @@ void SensorDriverScd::get(int32_t *values, uint8_t length, bool is_test) { // 1,800009 coefficiente di conversione ppm-> mg/m3 (riferito a 25°C e 760 mm Hg) // quindi assumioamo 0-0.020 Kg/m3 con risoluzione 0.000002 circa // in interi fattore di scala in tabella B 10**6 quindi mg/m3 - - values[0] = _scd30.getCO2()* 1.8; + + uint16_t co2=_scd30.getCO2(); + if (co2 != 0){ // 0 is missing value in library + values[0] = co2 * 1.8; + } } /* // do not use temperature and humidity from this sensor // get temperature + uint16_t temp=_scd30.getTemperature(); + if (length >= 2) { - values[1] = _scd30.getTemperature()*100+27315 ; + if (temp != 0){ + values[1] = temp * 100 + 27315 ; + } } // get humidity if (length >= 3) { - values[2] = _scd30.getHumidity() ; + uint16_t humid = _scd30.getHumidity(); + if (humid != 0){ + values[2] = humid ; + } } */ @@ -5079,6 +5091,16 @@ void SensorDriverScd::get(int32_t *values, uint8_t length, bool is_test) { _is_success = false; _get_state = END; } + + /* + if(!_scd30.StopMeasurement()) { //Stop continuous measurements + _error_count++; + LOGE(F("SCD StopMeasurement error")); + //LOGE(F("SCD get... [ %s ]"), ERROR_STRING); + return; + } + */ + _delay_ms = 0; _start_time_ms = millis(); break; diff --git a/platformio/libraries/SensorDriverb/SensorDriverb.cpp b/platformio/libraries/SensorDriverb/SensorDriverb.cpp index 75fc14772..b0ac75684 100644 --- a/platformio/libraries/SensorDriverb/SensorDriverb.cpp +++ b/platformio/libraries/SensorDriverb/SensorDriverb.cpp @@ -5479,7 +5479,7 @@ int SensorDriverSCDoneshot::setup(const char* driver, const int address, const i clock stretching needs to be implemented according to the NXP specification. The boot-up time is < 2 s. */ - _scd->sendCommand(COMMAND_SOFT_RESET); + _scd->reset(); delay(50); // ??? not explained in documentation if(_scd->beginMeasuring()) { //Start continuous measurements IF_SDSDEBUG(SDDBGSERIAL.println(F("# scd beginMeasuring ok"))); diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/LICENSE.md b/platformio/libraries/SparkFun_SCD30_Arduino_Library/LICENSE.md index e64bd4ef3..37238686d 100644 --- a/platformio/libraries/SparkFun_SCD30_Arduino_Library/LICENSE.md +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/LICENSE.md @@ -34,7 +34,7 @@ Code The MIT License (MIT) -Copyright (c) 2016 SparkFun Electronics +Copyright (c) 2020 SparkFun Electronics Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/README.md b/platformio/libraries/SparkFun_SCD30_Arduino_Library/README.md index 6de59dd27..9be1a0eae 100644 --- a/platformio/libraries/SparkFun_SCD30_Arduino_Library/README.md +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/README.md @@ -1,11 +1,11 @@ -SparkFun SCD30 CO2 Sensor Library +SparkFun SCD30 CO₂ Sensor Library =========================================================== -![SparkFun SCD30 CO2 Sensor](https://cdn.sparkfun.com//assets/parts/1/2/9/8/4/SparkFun_Sensirion_SCD30.jpg) +![SparkFun SCD30 CO₂ Sensor](https://cdn.sparkfun.com//assets/parts/1/2/9/8/4/SparkFun_Sensirion_SCD30.jpg) [*SparkX CO₂ Humidity and Temperature Sensor - SCD30 (SPX-14751)*](https://www.sparkfun.com/products/14751) -The SCD30 from Sensirion is a high quality [NDIR](https://en.wikipedia.org/wiki/Nondispersive_infrared_sensor) based CO₂ sensor capable of detecting 400 to 10000ppm with an accuracy of ±(30ppm+3%). In order to improve accuracy the SCD30 has temperature and humidity sensing built-in, as well as commands to set the current altitude. +The SCD30 from Sensirion is a high quality [NDIR](https://en.wikipedia.org/wiki/Nondispersive_infrared_sensor) based CO₂ sensor capable of detecting 400 to 10000ppm with an accuracy of ±(30ppm+3%). In order to improve accuracy the SCD30 has temperature and humidity sensing built-in, as well as commands to compensate for altitude. We've written an Arduino library to make reading the CO₂, humidity, and temperature very easy. It can be downloaded through the Arduino Library manager: search for 'SparkFun SCD30'. We recommend using a [Qwiic Breadboard Cable](https://www.sparkfun.com/products/14425) to connect the SCD30 to a Qwiic compatible board. The Ye*LL*ow wire goes in the SC*L* pin. The SCD30 also supports a serial interface but we haven't worked with it. @@ -19,14 +19,20 @@ Thanks to! * [jobr97](https://github.com/jobr97) for adding the getTemperatureOffset() method * [bobobo1618](https://github.com/bobobo1618) for writing a CRC check and improving the return values of the library +* [labeneator](https://github.com/labeneator) for adding method to disable calibrate at begin +* [AndreasExner](https://github.com/AndreasExner) for adding [reset and getAutoSelfCalibration methods](https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library/pull/17) +* [awatterott](https://github.com/awatterott) for adding [getAltitudeCompensation()](https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library/pull/18) +* [jogi-k](https://github.com/jogi-k) for adding [teensy i2clib](https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library/pull/19) support +* [paulvha](https://github.com/paulvha) for the suggestions and corrections in [his version of the library](https://github.com/paulvha/scd30) +* [yamamaya](https://github.com/yamamaya) for the [3ms delay](https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library/pull/24) Repository Contents ------------------- -* **/examples** - Example sketches for the library (.ino). Run these from the Arduino IDE. +* **/examples** - Example sketches for the library (.ino). Run these from the Arduino IDE. * **/src** - Source files for the library (.cpp, .h). -* **keywords.txt** - Keywords from this library that will be highlighted in the Arduino IDE. -* **library.properties** - General library properties for the Arduino package manager. +* **keywords.txt** - Keywords from this library that will be highlighted in the Arduino IDE. +* **library.properties** - General library properties for the Arduino package manager. Documentation -------------- @@ -36,9 +42,7 @@ Documentation License Information ------------------- -This product is _**open source**_! - -Various bits of the code have different licenses applied. Anything SparkFun wrote is beerware; if you see me (or any other SparkFun employee) at the local, and you've found our code helpful, please buy us a round! +This product is _**open source**_! Please use, reuse, and modify these files as you see fit. Please maintain attribution to SparkFun Electronics and release anything derivative under the same license. diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example2_SetOptions/Example2_SetOptions.ino b/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example2_SetOptions/Example2_SetOptions.ino index dfbb76daa..7e7f9036c 100644 --- a/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example2_SetOptions/Example2_SetOptions.ino +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example2_SetOptions/Example2_SetOptions.ino @@ -40,20 +40,34 @@ void setup() ; } - airSensor.setMeasurementInterval(4); //Change number of seconds between measurements: 2 to 1800 (30 minutes) + airSensor.setMeasurementInterval(16); //Change number of seconds between measurements: 2 to 1800 (30 minutes), stored in non-volatile memory of SCD30 + + //While the setting is recorded, it is not immediately available to be read. + delay(200); + + int interval = airSensor.getMeasurementInterval(); + Serial.print("Measurement Interval: "); + Serial.println(interval); //My desk is ~1600m above sealevel - airSensor.setAltitudeCompensation(1600); //Set altitude of the sensor in m + airSensor.setAltitudeCompensation(1600); //Set altitude of the sensor in m, stored in non-volatile memory of SCD30 + + //Read altitude compensation value + unsigned int altitude = airSensor.getAltitudeCompensation(); + Serial.print("Current altitude: "); + Serial.print(altitude); + Serial.println("m"); //Pressure in Boulder, CO is 24.65inHg or 834.74mBar - airSensor.setAmbientPressure(835); //Current ambient pressure in mBar: 700 to 1200 + airSensor.setAmbientPressure(835); //Current ambient pressure in mBar: 700 to 1200, will overwrite altitude compensation + airSensor.setTemperatureOffset(5); //Optionally we can set temperature offset to 5°C, stored in non-volatile memory of SCD30 + + //Read temperature offset float offset = airSensor.getTemperatureOffset(); Serial.print("Current temp offset: "); Serial.print(offset, 2); Serial.println("C"); - - //airSensor.setTemperatureOffset(5); //Optionally we can set temperature offset to 5°C } void loop() diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example4_EnableCalibrate/Example4_EnableCalibrate.ino b/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example4_EnableCalibrate/Example4_EnableCalibrate.ino new file mode 100644 index 000000000..50ca661d9 --- /dev/null +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example4_EnableCalibrate/Example4_EnableCalibrate.ino @@ -0,0 +1,75 @@ +/* + Reading CO2, humidity and temperature from the SCD30 + By: Nathan Seidle + SparkFun Electronics + Date: May 22nd, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! https://www.sparkfun.com/products/15112 + + This example turns on the auto-calibrate function during .begin() + Please see section 1.3.6 of the SCD30 datasheet: + + "When activated for the first time a + period of minimum 7 days is needed so that the algorithm can find its initial parameter set for ASC. The sensor has to be exposed + to fresh air for at least 1 hour every day. Also during that period, the sensor may not be disconnected from the power supply, + otherwise the procedure to find calibration parameters is aborted and has to be restarted from the beginning. The successfully + calculated parameters are stored in non-volatile memory of the SCD30 having the effect that after a restart the previously found + parameters for ASC are still present. " + + Hardware Connections: + Attach RedBoard to computer using a USB cable. + Connect SCD30 to RedBoard using Qwiic cable. + Open Serial Monitor at 115200 baud. +*/ + +#include + +#include "SparkFun_SCD30_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_SCD30 +SCD30 airSensor; + +void setup() +{ + Serial.begin(115200); + Serial.println("SCD30 Example"); + Wire.begin(); + + //Start sensor using the Wire port and enable the auto-calibration (ASC) + if (airSensor.begin(Wire, true) == false) + { + Serial.println("Air sensor not detected. Please check wiring. Freezing..."); + while (1) + ; + } + + Serial.print("Auto calibration set to "); + if (airSensor.getAutoSelfCalibration() == true) + Serial.println("true"); + else + Serial.println("false"); + + //The SCD30 has data ready every two seconds +} + +void loop() +{ + if (airSensor.dataAvailable()) + { + Serial.print("co2(ppm):"); + Serial.print(airSensor.getCO2()); + + Serial.print(" temp(C):"); + Serial.print(airSensor.getTemperature(), 1); + + Serial.print(" humidity(%):"); + Serial.print(airSensor.getHumidity(), 1); + + Serial.println(); + } + else + Serial.println("Waiting for new data"); + + delay(500); +} diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example5_GetSettings/Example5_GetSettings.ino b/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example5_GetSettings/Example5_GetSettings.ino new file mode 100644 index 000000000..bdfcf7705 --- /dev/null +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/examples/Example5_GetSettings/Example5_GetSettings.ino @@ -0,0 +1,129 @@ +/* + Reading CO2, humidity and temperature from the SCD30 + By: Nathan Seidle + SparkFun Electronics + Date: May 22nd, 2018 + License: MIT. See license file for more information but you can + basically do whatever you want with this code. + + Feel like supporting open source hardware? + Buy a board from SparkFun! https://www.sparkfun.com/products/15112 + + This example gets the SCD30's settings using the new getSettingValue function (thank you paulvha) + + Hardware Connections: + Attach RedBoard to computer using a USB cable. + Connect SCD30 to RedBoard using Qwiic cable. + Open Serial Monitor at 115200 baud. +*/ + +#include + +#include "SparkFun_SCD30_Arduino_Library.h" //Click here to get the library: http://librarymanager/All#SparkFun_SCD30 +SCD30 airSensor; + +void setup() +{ + Serial.begin(115200); + Serial.println("SCD30 Example"); + Wire.begin(); + + //Start sensor using the Wire port, but disable the auto-calibration + if (airSensor.begin(Wire, false) == false) + { + Serial.println("Air sensor not detected. Please check wiring. Freezing..."); + while (1) + ; + } + + uint16_t settingVal; // The settings will be returned in settingVal + + if (airSensor.getForcedRecalibration(&settingVal) == true) // Get the setting + { + Serial.print("Forced recalibration factor (ppm) is "); + Serial.println(settingVal); + } + else + { + Serial.print("getForcedRecalibration failed! Freezing..."); + while (1) + ; // Do nothing more + } + + if (airSensor.getMeasurementInterval(&settingVal) == true) // Get the setting + { + Serial.print("Measurement interval (s) is "); + Serial.println(settingVal); + } + else + { + Serial.print("getMeasurementInterval failed! Freezing..."); + while (1) + ; // Do nothing more + } + + if (airSensor.getTemperatureOffset(&settingVal) == true) // Get the setting + { + Serial.print("Temperature offset (C) is "); + Serial.println(((float)settingVal) / 100.0, 2); + } + else + { + Serial.print("getTemperatureOffset failed! Freezing..."); + while (1) + ; // Do nothing more + } + + if (airSensor.getAltitudeCompensation(&settingVal) == true) // Get the setting + { + Serial.print("Altitude offset (m) is "); + Serial.println(settingVal); + } + else + { + Serial.print("getAltitudeCompensation failed! Freezing..."); + while (1) + ; // Do nothing more + } + + if (airSensor.getFirmwareVersion(&settingVal) == true) // Get the setting + { + Serial.print("Firmware version is 0x"); + Serial.println(settingVal, HEX); + } + else + { + Serial.print("getFirmwareVersion! Freezing..."); + while (1) + ; // Do nothing more + } + + Serial.print("Auto calibration set to "); + if (airSensor.getAutoSelfCalibration() == true) + Serial.println("true"); + else + Serial.println("false"); + + //The SCD30 has data ready every two seconds +} + +void loop() +{ + if (airSensor.dataAvailable()) + { + Serial.print("co2(ppm):"); + Serial.print(airSensor.getCO2()); + + Serial.print(" temp(C):"); + Serial.print(airSensor.getTemperature(), 1); + + Serial.print(" humidity(%):"); + Serial.print(airSensor.getHumidity(), 1); + + Serial.println(); + } + else + Serial.println("Waiting for new data"); + + delay(500); +} diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/keywords.txt b/platformio/libraries/SparkFun_SCD30_Arduino_Library/keywords.txt index 82d47f89c..758eeb23e 100644 --- a/platformio/libraries/SparkFun_SCD30_Arduino_Library/keywords.txt +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/keywords.txt @@ -14,24 +14,43 @@ SCD30 KEYWORD1 SCD30 KEYWORD2 begin KEYWORD2 +isConnected KEYWORD2 +enableDebugging KEYWORD2 beginMeasuring KEYWORD2 -beginMeasuring KEYWORD2 +StopMeasurement KEYWORD2 + +setAmbientPressure KEYWORD2 + +getSettingValue KEYWORD2 +getFirmwareVersion KEYWORD2 getCO2 KEYWORD2 getHumidity KEYWORD2 getTemperature KEYWORD2 -getTemperatureOffset KEYWORD2 + +getMeasurementInterval KEYWORD2 setMeasurementInterval KEYWORD2 -setAmbientPressure KEYWORD2 + +getAltitudeCompensation KEYWORD2 setAltitudeCompensation KEYWORD2 + +getAutoSelfCalibration KEYWORD2 setAutoSelfCalibration KEYWORD2 + +getForcedRecalibration KEYWORD2 +setForcedRecalibrationFactor KEYWORD2 + +getTemperatureOffset KEYWORD2 setTemperatureOffset KEYWORD2 + dataAvailable KEYWORD2 readMeasurement KEYWORD2 -sendCommand KEYWORD2 +reset KEYWORD2 sendCommand KEYWORD2 readRegister KEYWORD2 computeCRC8 KEYWORD2 +useStaleData KEYWORD2 + ####################################### # Constants (LITERAL1) ####################################### @@ -45,3 +64,6 @@ COMMAND_AUTOMATIC_SELF_CALIBRATION LITERAL1 COMMAND_SET_FORCED_RECALIBRATION_FACTOR LITERAL1 COMMAND_SET_TEMPERATURE_OFFSET LITERAL1 COMMAND_SET_ALTITUDE_COMPENSATION LITERAL1 +COMMAND_RESET LITERAL1 +COMMAND_STOP_MEAS LITERAL1 +COMMAND_READ_FW_VER LITERAL1 diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/library.properties b/platformio/libraries/SparkFun_SCD30_Arduino_Library/library.properties index 1867b21c0..fd0290946 100644 --- a/platformio/libraries/SparkFun_SCD30_Arduino_Library/library.properties +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/library.properties @@ -1,9 +1,9 @@ name=SparkFun SCD30 Arduino Library -version=1.0.6 +version=1.0.20 author=SparkFun Electronics maintainer=SparkFun Electronics sentence=Library for the Sensirion SCD30 CO2 Sensor -paragraph=An Arduinolibrary for the SCD30 CO2 sensor from Sensirion. The SCD30 is a high quality NDIR based CO₂ sensor capable of detecting 400 to 10000ppm with an accuracy of ±(30ppm+3%). In order to improve accuracy the SCD30 has temperature and humidity sensing built-in, as well as commands to set the current altitude.

Get the SCD30 here. +paragraph=An Arduino library for the SCD30 CO2 sensor from Sensirion. The SCD30 is a high quality NDIR based CO₂ sensor capable of detecting 400 to 10000ppm with an accuracy of ±(30ppm+3%). In order to improve accuracy the SCD30 has temperature and humidity sensing built-in, as well as commands to set the current altitude.

Get the SCD30 here. category=Sensors url=https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library architectures=* diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.cpp b/platformio/libraries/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.cpp index d22b373a3..b417fa883 100644 --- a/platformio/libraries/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.cpp +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.cpp @@ -6,6 +6,11 @@ Written by Nathan Seidle @ SparkFun Electronics, May 22nd, 2018 + Updated February 1st 2021 to include some of the features of paulvha's version of the library + (while maintaining backward-compatibility): + https://github.com/paulvha/scd30 + Thank you Paul! + The SCD30 measures CO2 with accuracy of +/- 30ppm. This library handles the initialization of the SCD30 and outputs @@ -14,15 +19,10 @@ https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library Development environment specifics: - Arduino IDE 1.8.5 - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + Arduino IDE 1.8.13 - You should have received a copy of the GNU General Public License - along with this program. If not, see . + SparkFun code, firmware, and software is released under the MIT License. + Please see LICENSE.md for more details. */ #include "SparkFun_SCD30_Arduino_Library.h" @@ -32,11 +32,15 @@ SCD30::SCD30(void) // Constructor } -//Initialize the Serial port -bool SCD30::begin( uint8_t address, TwoWire &wirePort) +// Initialize the Serial port +#ifdef USE_TEENSY3_I2C_LIB +bool SCD30::begin(i2c_t3 &wirePort, bool autoCalibrate, bool measBegin) +#else +bool SCD30::begin(TwoWire &wirePort, bool autoCalibrate, bool measBegin) +#endif { - _i2cPort = &wirePort; //Grab which port the user wants us to use - _address=address; + _i2cPort = &wirePort; // Grab which port the user wants us to use + /* Especially during obtaining the ACK BIT after a byte sent the SCD30 is using clock stretching (but NOT only there)! * The need for clock stretching is described in the Sensirion_CO2_Sensors_SCD30_Interface_Description.pdf * @@ -46,7 +50,7 @@ bool SCD30::begin( uint8_t address, TwoWire &wirePort) * * To set ClockStretchlimit() a check for ESP8266 boards has been added in the driver. * - * With setting to 20000, we set a max timeout of 20mS (> 20x the maximum measured) basically disabling the time-out + * With setting to 20000, we set a max timeout of 20mS (> 20x the maximum measured) basically disabling the time-out * and now wait for clock stretch to be controlled by the client. */ @@ -54,100 +58,156 @@ bool SCD30::begin( uint8_t address, TwoWire &wirePort) _i2cPort->setClockStretchLimit(200000); #endif - /* - //Check for device to respond correctly - if (beginMeasuring() == true) //Start continuous measurements + if (isConnected() == false) + return (false); + + if (measBegin == false) // Exit now if measBegin is false + return (true); + + // Check for device to respond correctly + if (beginMeasuring() == true) // Start continuous measurements { - setMeasurementInterval(2); //2 seconds between measurements - setAutoSelfCalibration(true); //Enable auto-self-calibration + setMeasurementInterval(2); // 2 seconds between measurements + setAutoSelfCalibration(autoCalibrate); // Enable auto-self-calibration return (true); } - return (false); //Something went wrong - */ - //return (false); //Something went wrong -return (true); + return (false); // Something went wrong +} + +// Returns true if device responds to a firmware request +bool SCD30::isConnected() +{ + uint16_t fwVer; + if (getFirmwareVersion(&fwVer) == false) // Read the firmware version. Return false if the CRC check fails. + return (false); + + if (_printDebug == true) + { + _debugPort->print(F("Firmware version 0x")); + _debugPort->println(fwVer, HEX); + } + + return (true); +} + +// Calling this function with nothing sets the debug port to Serial +// You can also call it with other streams like Serial1, SerialUSB, etc. +void SCD30::enableDebugging(Stream &debugPort) +{ + _debugPort = &debugPort; + _printDebug = true; } -//Returns the latest available CO2 level -//If the current level has already been reported, trigger a new read +// Returns the latest available CO2 level +// If the current level has already been reported, trigger a new read uint16_t SCD30::getCO2(void) { - if (co2HasBeenReported == true) //Trigger a new read - readMeasurement(); //Pull in new co2, humidity, and temp into global vars + if (co2HasBeenReported == true) // Trigger a new read + { + if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars + if (!_useStaleData) + co2 = 0; // Failed to read sensor + } co2HasBeenReported = true; - return (uint16_t)co2; //Cut off decimal as co2 is 0 to 10,000 + return (uint16_t)co2; // Cut off decimal as co2 is 0 to 10,000 } -//Returns the latest available humidity -//If the current level has already been reported, trigger a new read +// Returns the latest available humidity +// If the current level has already been reported, trigger a new read float SCD30::getHumidity(void) { - if (humidityHasBeenReported == true) //Trigger a new read - readMeasurement(); //Pull in new co2, humidity, and temp into global vars + if (humidityHasBeenReported == true) // Trigger a new read + if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars + if (!_useStaleData) + humidity = 0; // Failed to read sensor humidityHasBeenReported = true; return humidity; } -//Returns the latest available temperature -//If the current level has already been reported, trigger a new read +// Returns the latest available temperature +// If the current level has already been reported, trigger a new read float SCD30::getTemperature(void) { - if (temperatureHasBeenReported == true) //Trigger a new read - readMeasurement(); //Pull in new co2, humidity, and temp into global vars + if (temperatureHasBeenReported == true) // Trigger a new read + if (readMeasurement() == false) // Pull in new co2, humidity, and temp into global vars + if (!_useStaleData) + temperature = 0; // Failed to read sensor temperatureHasBeenReported = true; return temperature; } -//Enables or disables the ASC +// Enables or disables the ASC bool SCD30::setAutoSelfCalibration(bool enable) { if (enable) - return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 1); //Activate continuous ASC + return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 1); // Activate continuous ASC else - return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 0); //Deactivate continuous ASC + return sendCommand(COMMAND_AUTOMATIC_SELF_CALIBRATION, 0); // Deactivate continuous ASC } -//Set the forced recalibration factor. See 1.3.7. -//The reference CO2 concentration has to be within the range 400 ppm ≤ cref(CO2) ≤ 2000 ppm. +// Set the forced recalibration factor. See 1.3.7. +// The reference CO2 concentration has to be within the range 400 ppm ≤ cref(CO2) ≤ 2000 ppm. bool SCD30::setForcedRecalibrationFactor(uint16_t concentration) { if (concentration < 400 || concentration > 2000) { - return false; //Error check. + return false; // Error check. } return sendCommand(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, concentration); } -//Get the temperature offset. See 1.3.8. -float SCD30::getTemperatureOffset() +// Get the temperature offset. See 1.3.8. +float SCD30::getTemperatureOffset(void) { uint16_t response = readRegister(COMMAND_SET_TEMPERATURE_OFFSET); - return (float)response / 100; + + union + { + int16_t signed16; + uint16_t unsigned16; + } signedUnsigned; // Avoid any ambiguity casting int16_t to uint16_t + signedUnsigned.unsigned16 = response; + + return (((float)signedUnsigned.signed16) / 100.0); } -//Set the temperature offset. See 1.3.8. +// Set the temperature offset to remove module heating from temp reading bool SCD30::setTemperatureOffset(float tempOffset) { - int16_t tickOffset = tempOffset * 100; - return sendCommand(COMMAND_SET_TEMPERATURE_OFFSET, tickOffset); + // Temp offset is only positive. See: https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library/issues/27#issuecomment-971986826 + //"The SCD30 offset temperature is obtained by subtracting the reference temperature from the SCD30 output temperature" + // https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/9.5_CO2/Sensirion_CO2_Sensors_SCD30_Low_Power_Mode.pdf + + if (tempOffset < 0.0) + return (false); + + uint16_t value = tempOffset * 100; + + return sendCommand(COMMAND_SET_TEMPERATURE_OFFSET, value); +} + +// Get the altitude compenstation. See 1.3.9. +uint16_t SCD30::getAltitudeCompensation(void) +{ + return readRegister(COMMAND_SET_ALTITUDE_COMPENSATION); } -//Set the altitude compenstation. See 1.3.9. +// Set the altitude compenstation. See 1.3.9. bool SCD30::setAltitudeCompensation(uint16_t altitude) { return sendCommand(COMMAND_SET_ALTITUDE_COMPENSATION, altitude); } -//Set the pressure compenstation. This is passed during measurement startup. -//mbar can be 700 to 1200 +// Set the pressure compenstation. This is passed during measurement startup. +// mbar can be 700 to 1200 bool SCD30::setAmbientPressure(uint16_t pressure_mbar) { if (pressure_mbar < 700 || pressure_mbar > 1200) @@ -157,30 +217,65 @@ bool SCD30::setAmbientPressure(uint16_t pressure_mbar) return sendCommand(COMMAND_CONTINUOUS_MEASUREMENT, pressure_mbar); } -//Begins continuous measurements -//Continuous measurement status is saved in non-volatile memory. When the sensor -//is powered down while continuous measurement mode is active SCD30 will measure -//continuously after repowering without sending the measurement command. -//Returns true if successful +// SCD30 soft reset +void SCD30::reset() +{ + sendCommand(COMMAND_RESET); +} + +// Get the current ASC setting +bool SCD30::getAutoSelfCalibration() +{ + uint16_t response = readRegister(COMMAND_AUTOMATIC_SELF_CALIBRATION); + if (response == 1) + { + return true; + } + else + { + return false; + } +} + +// Begins continuous measurements +// Continuous measurement status is saved in non-volatile memory. When the sensor +// is powered down while continuous measurement mode is active SCD30 will measure +// continuously after repowering without sending the measurement command. +// Returns true if successful bool SCD30::beginMeasuring(uint16_t pressureOffset) { return (sendCommand(COMMAND_CONTINUOUS_MEASUREMENT, pressureOffset)); } -//Overload - no pressureOffset +// Overload - no pressureOffset bool SCD30::beginMeasuring(void) { return (beginMeasuring(0)); } -//Sets interval between measurements -//2 seconds to 1800 seconds (30 minutes) +// Stop continuous measurement +bool SCD30::StopMeasurement(void) +{ + return (sendCommand(COMMAND_STOP_MEAS)); +} + +// Sets interval between measurements +// 2 seconds to 1800 seconds (30 minutes) bool SCD30::setMeasurementInterval(uint16_t interval) { return sendCommand(COMMAND_SET_MEASUREMENT_INTERVAL, interval); } -//Returns true when data is available +// Gets interval between measurements +// 2 seconds to 1800 seconds (30 minutes) +uint16_t SCD30::getMeasurementInterval(void) +{ + uint16_t interval = 0; + getSettingValue(COMMAND_SET_MEASUREMENT_INTERVAL, &interval); + return (interval); +} + +// Returns true when data is available bool SCD30::dataAvailable() { uint16_t response = readRegister(COMMAND_GET_DATA_READY); @@ -190,33 +285,38 @@ bool SCD30::dataAvailable() return (false); } -//Get 18 bytes from SCD30 -//Updates global variables with floats -//Returns true if success +// Get 18 bytes from SCD30 +// Updates global variables with floats +// Returns true if success bool SCD30::readMeasurement() { - //Verify we have data from the sensor + // Verify we have data from the sensor if (dataAvailable() == false) return (false); - uint32_t tempCO2 = 0; - uint32_t tempHumidity = 0; - uint32_t tempTemperature = 0; + ByteToFl tempCO2; + tempCO2.value = 0; + ByteToFl tempHumidity; + tempHumidity.value = 0; + ByteToFl tempTemperature; + tempTemperature.value = 0; - _i2cPort->beginTransmission(_address); - _i2cPort->write(COMMAND_READ_MEASUREMENT >> 8); //MSB - _i2cPort->write(COMMAND_READ_MEASUREMENT & 0xFF); //LSB + _i2cPort->beginTransmission(SCD30_ADDRESS); + _i2cPort->write(COMMAND_READ_MEASUREMENT >> 8); // MSB + _i2cPort->write(COMMAND_READ_MEASUREMENT & 0xFF); // LSB if (_i2cPort->endTransmission() != 0) - return (0); //Sensor did not ACK + return (0); // Sensor did not ACK + + delay(3); const uint8_t receivedBytes = _i2cPort->requestFrom((uint8_t)SCD30_ADDRESS, (uint8_t)18); bool error = false; if (_i2cPort->available()) { - byte bytesToCrc[2]; - for (byte x = 0; x < 18; x++) + uint8_t bytesToCrc[2]; + for (uint8_t x = 0; x < 18; x++) { - byte incoming = _i2cPort->read(); + uint8_t incoming = _i2cPort->read(); switch (x) { @@ -224,32 +324,37 @@ bool SCD30::readMeasurement() case 1: case 3: case 4: - tempCO2 <<= 8; - tempCO2 |= incoming; + tempCO2.array[x < 3 ? 3 - x : 4 - x] = incoming; bytesToCrc[x % 3] = incoming; break; case 6: case 7: case 9: case 10: - tempTemperature <<= 8; - tempTemperature |= incoming; + tempTemperature.array[x < 9 ? 9 - x : 10 - x] = incoming; bytesToCrc[x % 3] = incoming; break; case 12: case 13: case 15: case 16: - tempHumidity <<= 8; - tempHumidity |= incoming; + tempHumidity.array[x < 15 ? 15 - x : 16 - x] = incoming; bytesToCrc[x % 3] = incoming; break; default: - //Validate CRC - const uint8_t foundCrc = computeCRC8(bytesToCrc, 2); + // Validate CRC + uint8_t foundCrc = computeCRC8(bytesToCrc, 2); if (foundCrc != incoming) { - //Serial.printf("Found CRC in byte %u, expected %u, got %u\n", x, incoming, foundCrc); + if (_printDebug == true) + { + _debugPort->print(F("readMeasurement: found CRC in byte ")); + _debugPort->print(x); + _debugPort->print(F(", expected 0x")); + _debugPort->print(foundCrc, HEX); + _debugPort->print(F(", got 0x")); + _debugPort->println(incoming, HEX); + } error = true; } break; @@ -258,36 +363,78 @@ bool SCD30::readMeasurement() } else { - //Serial.printf("No SCD30 data found from I2C, i2c claims we should receive %u bytes\n", receivedBytes); + if (_printDebug == true) + { + _debugPort->print(F("readMeasurement: no SCD30 data found from I2C, i2c claims we should receive ")); + _debugPort->print(receivedBytes); + _debugPort->println(F(" bytes")); + } return false; } if (error) { - //Serial.println("Encountered error reading SCD30 data."); + if (_printDebug == true) + _debugPort->println(F("readMeasurement: encountered error reading SCD30 data.")); return false; } - //Now copy the uint32s into their associated floats - memcpy(&co2, &tempCO2, sizeof(co2)); - memcpy(&temperature, &tempTemperature, sizeof(temperature)); - memcpy(&humidity, &tempHumidity, sizeof(humidity)); + // Now copy the uint32s into their associated floats + co2 = tempCO2.value; + temperature = tempTemperature.value; + humidity = tempHumidity.value; - //Mark our global variables as fresh + // Mark our global variables as fresh co2HasBeenReported = false; humidityHasBeenReported = false; temperatureHasBeenReported = false; - return (true); //Success! New data available in globals. + return (true); // Success! New data available in globals. +} + +// Gets a setting by reading the appropriate register. +// Returns true if the CRC is valid. +bool SCD30::getSettingValue(uint16_t registerAddress, uint16_t *val) +{ + _i2cPort->beginTransmission(SCD30_ADDRESS); + _i2cPort->write(registerAddress >> 8); // MSB + _i2cPort->write(registerAddress & 0xFF); // LSB + if (_i2cPort->endTransmission() != 0) + return (false); // Sensor did not ACK + + delay(3); + + _i2cPort->requestFrom((uint8_t)SCD30_ADDRESS, (uint8_t)3); // Request data and CRC + if (_i2cPort->available()) + { + uint8_t data[2]; + data[0] = _i2cPort->read(); + data[1] = _i2cPort->read(); + uint8_t crc = _i2cPort->read(); + *val = (uint16_t)data[0] << 8 | data[1]; + uint8_t expectedCRC = computeCRC8(data, 2); + if (crc == expectedCRC) // Return true if CRC check is OK + return (true); + if (_printDebug == true) + { + _debugPort->print(F("getSettingValue: CRC fail: expected 0x")); + _debugPort->print(expectedCRC, HEX); + _debugPort->print(F(", got 0x")); + _debugPort->println(crc, HEX); + } + } + return (false); } -//Gets two bytes from SCD30 +// Gets two bytes from SCD30 uint16_t SCD30::readRegister(uint16_t registerAddress) { - _i2cPort->beginTransmission(_address); - _i2cPort->write(registerAddress >> 8); //MSB - _i2cPort->write(registerAddress & 0xFF); //LSB + _i2cPort->beginTransmission(SCD30_ADDRESS); + _i2cPort->write(registerAddress >> 8); // MSB + _i2cPort->write(registerAddress & 0xFF); // LSB if (_i2cPort->endTransmission() != 0) - return (0); //Sensor did not ACK + return (0); // Sensor did not ACK + + delay(3); _i2cPort->requestFrom((uint8_t)SCD30_ADDRESS, (uint8_t)2); if (_i2cPort->available()) @@ -296,49 +443,49 @@ uint16_t SCD30::readRegister(uint16_t registerAddress) uint8_t lsb = _i2cPort->read(); return ((uint16_t)msb << 8 | lsb); } - return (0); //Sensor did not respond + return (0); // Sensor did not respond } -//Sends a command along with arguments and CRC +// Sends a command along with arguments and CRC bool SCD30::sendCommand(uint16_t command, uint16_t arguments) { uint8_t data[2]; data[0] = arguments >> 8; data[1] = arguments & 0xFF; - uint8_t crc = computeCRC8(data, 2); //Calc CRC on the arguments only, not the command + uint8_t crc = computeCRC8(data, 2); // Calc CRC on the arguments only, not the command - _i2cPort->beginTransmission(_address); - _i2cPort->write(command >> 8); //MSB - _i2cPort->write(command & 0xFF); //LSB - _i2cPort->write(arguments >> 8); //MSB - _i2cPort->write(arguments & 0xFF); //LSB + _i2cPort->beginTransmission(SCD30_ADDRESS); + _i2cPort->write(command >> 8); // MSB + _i2cPort->write(command & 0xFF); // LSB + _i2cPort->write(arguments >> 8); // MSB + _i2cPort->write(arguments & 0xFF); // LSB _i2cPort->write(crc); if (_i2cPort->endTransmission() != 0) - return (false); //Sensor did not ACK + return (false); // Sensor did not ACK return (true); } -//Sends just a command, no arguments, no CRC +// Sends just a command, no arguments, no CRC bool SCD30::sendCommand(uint16_t command) { - _i2cPort->beginTransmission(_address); - _i2cPort->write(command >> 8); //MSB - _i2cPort->write(command & 0xFF); //LSB + _i2cPort->beginTransmission(SCD30_ADDRESS); + _i2cPort->write(command >> 8); // MSB + _i2cPort->write(command & 0xFF); // LSB if (_i2cPort->endTransmission() != 0) - return (false); //Sensor did not ACK + return (false); // Sensor did not ACK return (true); } -//Given an array and a number of bytes, this calculate CRC8 for those bytes -//CRC is only calc'd on the data portion (two bytes) of the four bytes being sent -//From: http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html -//Tested with: http://www.sunshine2k.de/coding/javascript/crc/crc_js.html -//x^8+x^5+x^4+1 = 0x31 +// Given an array and a number of bytes, this calculate CRC8 for those bytes +// CRC is only calc'd on the data portion (two bytes) of the four bytes being sent +// From: http://www.sunshine2k.de/articles/coding/crc/understanding_crc.html +// Tested with: http://www.sunshine2k.de/coding/javascript/crc/crc_js.html +// x^8+x^5+x^4+1 = 0x31 uint8_t SCD30::computeCRC8(uint8_t data[], uint8_t len) { - uint8_t crc = 0xFF; //Init with 0xFF + uint8_t crc = 0xFF; // Init with 0xFF for (uint8_t x = 0; x < len; x++) { @@ -353,5 +500,5 @@ uint8_t SCD30::computeCRC8(uint8_t data[], uint8_t len) } } - return crc; //No output reflection + return crc; // No output reflection } diff --git a/platformio/libraries/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h b/platformio/libraries/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h index e2ff1b2f6..49d6e052a 100644 --- a/platformio/libraries/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h +++ b/platformio/libraries/SparkFun_SCD30_Arduino_Library/src/SparkFun_SCD30_Arduino_Library.h @@ -6,6 +6,11 @@ Written by Nathan Seidle @ SparkFun Electronics, May 22nd, 2018 + Updated February 1st 2021 to include some of the features of paulvha's version of the library + (while maintaining backward-compatibility): + https://github.com/paulvha/scd30 + Thank you Paul! + The SCD30 measures CO2 with accuracy of +/- 30ppm. This library handles the initialization of the SCD30 and outputs @@ -14,30 +19,33 @@ https://github.com/sparkfun/SparkFun_SCD30_Arduino_Library Development environment specifics: - Arduino IDE 1.8.5 - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. + Arduino IDE 1.8.13 - You should have received a copy of the GNU General Public License - along with this program. If not, see . + SparkFun code, firmware, and software is released under the MIT License. + Please see LICENSE.md for more details. */ #ifndef __SparkFun_SCD30_ARDUINO_LIBARARY_H__ #define __SparkFun_SCD30_ARDUINO_LIBARARY_H__ +// Uncomment the next #define if using an Teensy >= 3 or Teensy LC and want to use the dedicated I2C-Library for it +// Then you also have to include on your application instead of + +// #define USE_TEENSY3_I2C_LIB + #include "Arduino.h" +#ifdef USE_TEENSY3_I2C_LIB +#include +#else #include +#endif -//The default I2C address for the SCD30 is 0x61. +// The default I2C address for the SCD30 is 0x61. #define SCD30_ADDRESS 0x61 -//Available commands +// Available commands #define COMMAND_CONTINUOUS_MEASUREMENT 0x0010 -#define COMMAND_STOP_CONTINUOS_MEASUREMENT 0x0104 #define COMMAND_SET_MEASUREMENT_INTERVAL 0x4600 #define COMMAND_GET_DATA_READY 0x0202 #define COMMAND_READ_MEASUREMENT 0x0300 @@ -45,34 +53,66 @@ #define COMMAND_SET_FORCED_RECALIBRATION_FACTOR 0x5204 #define COMMAND_SET_TEMPERATURE_OFFSET 0x5403 #define COMMAND_SET_ALTITUDE_COMPENSATION 0x5102 -#define COMMAND_SOFT_RESET 0xD304 -//#define COMMAND_READ_FIRMWARE_VERSION 0xD100 +#define COMMAND_RESET 0xD304 // Soft reset +#define COMMAND_STOP_MEAS 0x0104 +#define COMMAND_READ_FW_VER 0xD100 + +typedef union +{ + uint8_t array[4]; + float value; +} ByteToFl; // paulvha class SCD30 { public: SCD30(void); - boolean begin(uint8_t address=SCD30_ADDRESS, TwoWire &wirePort = Wire); //By default use Wire port + bool begin(bool autoCalibrate) { return begin(Wire, autoCalibrate); } +#ifdef USE_TEENSY3_I2C_LIB + bool begin(i2c_t3 &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); // By default use Wire port +#else + bool begin(TwoWire &wirePort = Wire, bool autoCalibrate = false, bool measBegin = true); // By default use Wire port +#endif + + bool isConnected(); + void enableDebugging(Stream &debugPort = Serial); // Turn on debug printing. If user doesn't specify then Serial will be used. bool beginMeasuring(uint16_t pressureOffset); bool beginMeasuring(void); + bool StopMeasurement(void); // paulvha + + bool setAmbientPressure(uint16_t pressure_mbar); + bool getSettingValue(uint16_t registerAddress, uint16_t *val); + bool getFirmwareVersion(uint16_t *val) { return (getSettingValue(COMMAND_READ_FW_VER, val)); } uint16_t getCO2(void); float getHumidity(void); float getTemperature(void); - float getTemperatureOffset(void); + uint16_t getMeasurementInterval(void); + bool getMeasurementInterval(uint16_t *val) { return (getSettingValue(COMMAND_SET_MEASUREMENT_INTERVAL, val)); } bool setMeasurementInterval(uint16_t interval); - bool setAmbientPressure(uint16_t pressure_mbar); + + uint16_t getAltitudeCompensation(void); + bool getAltitudeCompensation(uint16_t *val) { return (getSettingValue(COMMAND_SET_ALTITUDE_COMPENSATION, val)); } bool setAltitudeCompensation(uint16_t altitude); + + bool getAutoSelfCalibration(void); bool setAutoSelfCalibration(bool enable); + + bool getForcedRecalibration(uint16_t *val) { return (getSettingValue(COMMAND_SET_FORCED_RECALIBRATION_FACTOR, val)); } bool setForcedRecalibrationFactor(uint16_t concentration); + + float getTemperatureOffset(void); + bool getTemperatureOffset(uint16_t *val) { return (getSettingValue(COMMAND_SET_TEMPERATURE_OFFSET, val)); } bool setTemperatureOffset(float tempOffset); bool dataAvailable(); bool readMeasurement(); + void reset(); + bool sendCommand(uint16_t command, uint16_t arguments); bool sendCommand(uint16_t command); @@ -80,19 +120,29 @@ class SCD30 uint8_t computeCRC8(uint8_t data[], uint8_t len); + void useStaleData(bool enable) { _useStaleData = enable; } + private: - //Variables - TwoWire *_i2cPort; //The generic connection to user's chosen I2C hardware - uint8_t _address; // i2c address - //Global main datums + // Variables +#ifdef USE_TEENSY3_I2C_LIB + i2c_t3 *_i2cPort; // The generic connection to user's chosen I2C hardware +#else + TwoWire *_i2cPort; // The generic connection to user's chosen I2C hardware +#endif + // Global main datums float co2 = 0; float temperature = 0; float humidity = 0; + bool _useStaleData = false; // If true, stale data is returned instead of zeros - //These track the staleness of the current data - //This allows us to avoid calling readMeasurement() every time individual datums are requested + // These track the staleness of the current data + // This allows us to avoid calling readMeasurement() every time individual datums are requested bool co2HasBeenReported = true; bool humidityHasBeenReported = true; bool temperatureHasBeenReported = true; + + // Debug + Stream *_debugPort; // The stream to send debug messages to if enabled. Usually Serial. + boolean _printDebug = false; // Flag to print debugging variables }; #endif diff --git a/platformio/stima_v3/stimawifi/bin/lolin_c3_mini/firmware.bin b/platformio/stima_v3/stimawifi/bin/lolin_c3_mini/firmware.bin index e0316e496..2ae4ae298 100644 Binary files a/platformio/stima_v3/stimawifi/bin/lolin_c3_mini/firmware.bin and b/platformio/stima_v3/stimawifi/bin/lolin_c3_mini/firmware.bin differ diff --git a/platformio/stima_v3/stimawifi/bin/lolin_s3_mini/firmware.bin b/platformio/stima_v3/stimawifi/bin/lolin_s3_mini/firmware.bin index a52b4ef3e..2946881ea 100644 Binary files a/platformio/stima_v3/stimawifi/bin/lolin_s3_mini/firmware.bin and b/platformio/stima_v3/stimawifi/bin/lolin_s3_mini/firmware.bin differ diff --git a/platformio/stima_v3/stimawifi/include/stimawifi_config.h b/platformio/stima_v3/stimawifi/include/stimawifi_config.h index 215430bee..b399fc72f 100644 --- a/platformio/stima_v3/stimawifi/include/stimawifi_config.h +++ b/platformio/stima_v3/stimawifi/include/stimawifi_config.h @@ -2,9 +2,9 @@ #define STIMAWIFI_CONFIG_H_ // increment on change -#define SOFTWARE_VERSION "2026-06-16T12:00" // date and time iso format -#define MAJOR_VERSION "20260616" // date YYYYMMDD -#define MINOR_VERSION "1200" // time HHMM without leading 0 +#define SOFTWARE_VERSION "2026-10-01T00:00" // date and time iso format +#define MAJOR_VERSION "20261001" // date YYYYMMDD +#define MINOR_VERSION "0000" // time HHMM without leading 0 // SSID and password of WiFi for setup #define WIFI_SSED "STIMA-config" @@ -212,6 +212,12 @@ */ #define IP_STACK_TIMEOUT_MS (MQTT_TIMEOUT_MS) +/*! +\def TRANSACTION_TIMEOUT +\brief esp-now sequence of send/ack timeout. +*/ +#define TRANSACTION_TIMEOUT 3000 + /*! \def STATUS_SEND_S \brief send MQTT board status every STATUS_SEND_S. @@ -267,6 +273,12 @@ #define TASK_PUBLISH_PRIORITY TASK_BASE_PRIORITY #define TASK_PUBLISH_STACK_SIZE 3500 +#define TASK_NOW_PRIORITY TASK_BASE_PRIORITY+2 +#define TASK_NOW_STACK_SIZE 3000 + +#define TASK_NOW_SAT_PRIORITY TASK_BASE_PRIORITY+2 +#define TASK_NOW_SAT_STACK_SIZE 3000 + #define TASK_LOOP_PRIORITY TASK_BASE_PRIORITY //#define ARDUINO_LOOP_STACK_SIZE 5000 // defined in platformio.ini diff --git a/platformio/stima_v3/stimawifi/platformio.ini b/platformio/stima_v3/stimawifi/platformio.ini index 10ae84380..b22029886 100644 --- a/platformio/stima_v3/stimawifi/platformio.ini +++ b/platformio/stima_v3/stimawifi/platformio.ini @@ -73,6 +73,7 @@ lib_deps = Adafruit NeoPixel SdFat SD + fbiego/ESP32Time ;@ ^2.0.6 ;libretiny-eu/FlashDB ;siara-cc/Sqlite3Esp32 diff --git a/platformio/stima_v3/stimawifi/src/common.h b/platformio/stima_v3/stimawifi/src/common.h index 091fe89bc..81a181240 100644 --- a/platformio/stima_v3/stimawifi/src/common.h +++ b/platformio/stima_v3/stimawifi/src/common.h @@ -15,7 +15,7 @@ #include //https://github.com/bblanchon/ArduinoJson #include #include -#include "time.h" +#include #include #include #include @@ -38,6 +38,8 @@ #include "measure_thread.h" #include "publish_thread.h" #include "db_thread.h" +#include "now_thread.h" +#include "now_sat_thread.h" #include "critical.hpp" #include "semaphore.hpp" #include "SD.h" diff --git a/platformio/stima_v3/stimawifi/src/db_thread.cpp b/platformio/stima_v3/stimawifi/src/db_thread.cpp index 9a90d84c7..02e1208b6 100644 --- a/platformio/stima_v3/stimawifi/src/db_thread.cpp +++ b/platformio/stima_v3/stimawifi/src/db_thread.cpp @@ -655,8 +655,10 @@ bool dbThread::data_recovery(void){ bool somedata = false; // is time to run ? - if (!run_data_recovery) return true; - + if (!run_data_recovery){ + data->state = STATE_DB_RECOVERY_DONE; + return true; + } data->logger->notice(F("db recovery from DB started")); // Retry to run the next time waiting to run for the queue go empty. @@ -667,6 +669,7 @@ bool dbThread::data_recovery(void){ // We use this check in any case if the transaction management fail if (!data->recoveryqueue->IsEmpty()){ data->logger->warning(F("db recovery skip: queue is not empty")); + data->state = STATE_DB_RECOVERY_DONE; return true; } @@ -748,7 +751,8 @@ bool dbThread::data_recovery(void){ } } - data->logger->notice(F("db End recovery from DB")); + data->logger->notice(F("db End recovery from DB")); + data->state = STATE_DB_RECOVERY_DONE; return rc == SQLITE_OK; } @@ -952,6 +956,8 @@ dbThread::dbThread(db_data_t* db_data) db_recovery_state = DB_RECOVERY_NONE; run_data_recovery=false; status_data_recovery=false; + data->state = STATE_DB_NONE; + //Start(); }; @@ -976,6 +982,8 @@ void dbThread::Cleanup() void dbThread::Run() { data->logger->notice("Starting Thread %s %d", GetName().c_str(), data->id); + data->state = STATE_DB_SDCARD; + # if portNUM_PROCESSORS > 1 //data->logger->notice(F("db largest free block %l"),heap_caps_get_largest_free_block(MALLOC_CAP_DEFAULT)); sqlite_memory= ps_malloc(SQLITE_MEMORY); @@ -1030,6 +1038,8 @@ void dbThread::Run() { data->status->archive=ok; } + data->state = STATE_DB_SQLITE; + // sqlite use sqlite_memory pre allocated memory if (sqlite3_config(SQLITE_CONFIG_HEAP, sqlite_memory, SQLITE_MEMORY, 32)!=SQLITE_OK){ data->logger->error(F("db sqlite3_config: %s"),sqlite3_errmsg(db)); @@ -1200,7 +1210,10 @@ void dbThread::Run() { // uint8_t basePriority = GetPriority(); // SetPriority(basePriority-1); // SetPriority(basePriority); - + + + data->state = STATE_DB_QUEUE_START; + for(;;){ while (data->dbqueue->Peek(&message, pdMS_TO_TICKS( 1000 ))){ // peek one message diff --git a/platformio/stima_v3/stimawifi/src/db_thread.h b/platformio/stima_v3/stimawifi/src/db_thread.h index 574c722d4..3b57d8709 100644 --- a/platformio/stima_v3/stimawifi/src/db_thread.h +++ b/platformio/stima_v3/stimawifi/src/db_thread.h @@ -2,6 +2,14 @@ #ifndef DB_THREAD_H_ #define DB_THREAD_H_ +typedef enum { + STATE_DB_NONE, + STATE_DB_SDCARD, + STATE_DB_SQLITE, + STATE_DB_QUEUE_START, + STATE_DB_RECOVERY_DONE +} state_db_t; + struct db_data_t { int id; frtosLogging* logger; @@ -12,6 +20,7 @@ struct db_data_t { dbStatus_t* status; station_t* station; File* logFile; + state_db_t state; }; typedef enum { diff --git a/platformio/stima_v3/stimawifi/src/measure_thread.cpp b/platformio/stima_v3/stimawifi/src/measure_thread.cpp index 65008707f..126334968 100644 --- a/platformio/stima_v3/stimawifi/src/measure_thread.cpp +++ b/platformio/stima_v3/stimawifi/src/measure_thread.cpp @@ -147,7 +147,14 @@ void measureThread::enqueueMqttMessage(uint8_t i ) { strcat(mqtt_message.payload,value); if (timeStatus() == timeSet){ char jsontime[30]; - time_t messagetime=now(); + time_t messagetime; + if(data->station->nowsat){ + // if station type is espnow satellite compute report time as approximation + time_t counter = ((now() + (data->station->sampletime/2)) / data->station->sampletime); + messagetime = counter * data->station->sampletime; + }else{ + messagetime=now(); + } snprintf(jsontime,30,",\"t\":\"%04u-%02u-%02uT%02u:%02u:%02u\"}", year(messagetime), month(messagetime), day(messagetime), hour(messagetime), minute(messagetime), second(messagetime)); @@ -190,7 +197,7 @@ void measureThread::doMeasure() { data->status->sensor=unknown; data->status->novalue=unknown; data->status->geodef=unknown; - + // sensorm (sensor Manager) is a finite state machine // here we can execute measure in parallel starting one state machine (sensorm) for each sensor // each sensor can do one or more measure @@ -273,7 +280,6 @@ void measureThread::doMeasure() { } - measureThread::measureThread(measure_data_t* measure_data) : Thread{"measure", TASK_MEASURE_STACK_SIZE, TASK_MEASURE_PRIORITY # if portNUM_PROCESSORS > 1 @@ -300,6 +306,7 @@ measureThread::~measureThread() void measureThread::Begin() { + data->state = STATE_MEASURE_NONE; // create one driver for each sensor uint8_t tmp_count=0; for (uint8_t i = 0; i < data->sensors_count; i++) { @@ -336,7 +343,10 @@ void measureThread::Run() { for(;;){ // wait for notification from the main task; start when we have to do measurements WaitForNotification(); + + data->state = STATE_MEASURE_STARTED; if (timeStatus() == timeSet) doMeasure(); // measure il we can use a timestamp + data->state = STATE_MEASURE_DONE; // check heap and stack //data->logger->notice(F("HEAP: %l"),esp_get_minimum_free_heap_size()); diff --git a/platformio/stima_v3/stimawifi/src/measure_thread.h b/platformio/stima_v3/stimawifi/src/measure_thread.h index 7875a5c5a..3d171a80e 100644 --- a/platformio/stima_v3/stimawifi/src/measure_thread.h +++ b/platformio/stima_v3/stimawifi/src/measure_thread.h @@ -3,6 +3,13 @@ #ifndef MEASURE_THREAD_H_ #define MEASURE_THREAD_H_ +typedef enum { + STATE_MEASURE_NONE, + STATE_MEASURE_STARTED, + STATE_MEASURE_DONE +} state_measure_t; + + struct measure_data_t { // thread communication data int id; frtosLogging* logger; @@ -16,12 +23,9 @@ struct measure_data_t { // thread communication data BinaryQueue* rpccalibratequeue; sensor_t sensors[SENSORS_MAX]; uint8_t sensors_count; + state_measure_t state; }; -void enqueueMqttMessage(const char* values, const char* timerange, const char* level, measure_data_t& data ); -void doMeasure( measure_data_t& data ); -void web_values(const char* values); - using namespace cpp_freertos; class measureThread : public Thread { diff --git a/platformio/stima_v3/stimawifi/src/now_sat_thread.cpp b/platformio/stima_v3/stimawifi/src/now_sat_thread.cpp new file mode 100644 index 000000000..1060ca827 --- /dev/null +++ b/platformio/stima_v3/stimawifi/src/now_sat_thread.cpp @@ -0,0 +1,783 @@ +#include "common.h" + +//*********************************************************************************************** +// global definition to use in NOW callback +// pointers setted by class istance +now_sat_data_t* nowSatThread::global_data=NULL; +static bool have_to_write_config=false; +//*********************************************************************************************** + +/* +// Define global key PMK ( 16 byte long) +// Have to be the same on all devices wants inter communicate. +const uint8_t mia_pmk[16] = { + 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11 +}; + +// Define local key LMK ( 16 byte long) +// you can choose any value.Two communicate device have to have the same key. +const uint8_t mio_lmk[16] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 +}; +*/ + +static const uint8_t broadcastAddress[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; +static RTC_DATA_ATTR int bootCount; +static RTC_DATA_ATTR volatile uint8_t error_count; +static RTC_DATA_ATTR volatile unsigned int last_state_update; +static RTC_DATA_ATTR volatile state_sat_t state; +static RTC_DATA_ATTR volatile uint16_t seq; +// Flag per verificare se l'invio è completato +static RTC_DATA_ATTR volatile bool transmissionCompleted ; +// Flag per sapere se il canale è cambiato rispetto alla conf salvata +static volatile bool channelchanged; +static now_config_t config; +static RTC_DATA_ATTR volatile uint8_t channel; + +// read configuration from EEPROM +static bool read_local_config() { + + if (LittleFS.exists("/satellite.json")) { + //file exists, read and load + nowSatThread::global_data->logger->notice(F("nowsat reading satellite config file")); + File configFile = LittleFS.open("/satellite.json", "r"); + if (configFile) { + nowSatThread::global_data->logger->notice(F("nowsat opened satellite config file")); + + //size_t size = configFile.size(); + // Allocate a buffer to store contents of the file. + //std::unique_ptr buf(new char[size]); + //configfile.readBytes(buf.get(), size); + String content = configFile.readString(); + configFile.close(); + + DynamicJsonDocument doc(256); + DeserializationError error = deserializeJson(doc,content); + if (!error){ + const char* ver = doc["ver"]; // "1.0" + config.paired = doc["paired"]; + JsonArray mac = doc["mastermac"]; + config.peerMac[0]= mac[0]; // 1 + config.peerMac[1]= mac[1]; // 2 + config.peerMac[2]= mac[2]; // 3 + config.peerMac[3]= mac[3]; // 4 + config.peerMac[4]= mac[4]; // 5 + config.peerMac[5]= mac[5]; // 6 + config.channel = doc["channel"]; + + nowSatThread::global_data->logger->notice(F("nowsat Config read:")); + nowSatThread::global_data->logger->notice(F("nowsat paired: %T"),config.paired); + nowSatThread::global_data->logger->notice(F("nowsat MAC 0: %X"),config.peerMac[0]); + nowSatThread::global_data->logger->notice(F("nowsat MAC 1: %X"),config.peerMac[1]); + nowSatThread::global_data->logger->notice(F("nowsat MAC 2: %X"),config.peerMac[2]); + nowSatThread::global_data->logger->notice(F("nowsat MAC 3: %X"),config.peerMac[3]); + nowSatThread::global_data->logger->notice(F("nowsat MAC 4: %X"),config.peerMac[4]); + nowSatThread::global_data->logger->notice(F("nowsat MAC 5: %X"),config.peerMac[5]); + nowSatThread::global_data->logger->notice(F("nowsat channel: %d"),config.channel); + nowSatThread::global_data->logger->notice(F("nowsat END config")); + + return true; + } else { + nowSatThread::global_data->logger->error(F("nowsat reading satellite file: %s"),error.c_str()); + } + } else { + nowSatThread::global_data->logger->warning(F("nowsat satellite file do not exist")); + } + } + return false; +} + +// write configuration to EEPROM + static bool write_local_config() { + + //save the custom parameters to FS + nowSatThread::global_data->logger->notice(F("nowsat saving satellite config")); + + File configFile = LittleFS.open("/satellite.json", "w"); + if (!configFile) { + nowSatThread::global_data->logger->error(F("nowsat failed to open rmap config file for writing")); + return false; + } + + DynamicJsonDocument doc(256); + doc[F("ver")] = F(SOFTWARE_VERSION); + doc[F("paired")] = config.paired; + + //doc[F("mastermac")][0] = config.peerMac[0]; + //doc[F("mastermac")][1] = config.peerMac[1]; + //doc[F("mastermac")][2] = config.peerMac[2]; + //doc[F("mastermac")][3] = config.peerMac[3]; + //doc[F("mastermac")][4] = config.peerMac[4]; + //doc[F("mastermac")][5] = config.peerMac[5]; + + JsonArray mastermac = doc[F("mastermac")].to(); + mastermac.add(config.peerMac[0]); + mastermac.add(config.peerMac[1]); + mastermac.add(config.peerMac[2]); + mastermac.add(config.peerMac[3]); + mastermac.add(config.peerMac[4]); + mastermac.add(config.peerMac[5]); + + doc[F("channel")] = config.channel; + char buffer[100]; + serializeJson(doc, buffer, sizeof(buffer)); + configFile.print(buffer); + configFile.close(); + nowSatThread::global_data->logger->notice(F("nowsat saved satellite config parameter")); + //end save + return true; +} + +// Callback when data is sent +// this is called by an other thread with other priority level and on CPU 0 +// logging do not work: mutex do not work on bi-processor MCU +static void OnDataSent(const uint8_t *des_addr, esp_now_send_status_t status) { + //nowSatThread::global_data->logger->notice(F("nowsat OnDataSent")); + //nowSatThread::global_data->logger->notice(F("nowsat State: %d"),state); + //nowSatThread::global_data->logger->notice(F("nowsat destination MAC: %X:%X:%X:%X:%X:%X"), + // des_addr[0], des_addr[1], des_addr[2], + // des_addr[3], des_addr[4], des_addr[5]); + //nowSatThread::global_data->logger->notice("nowsat Last Packet Send Status: %s", status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail" ); + last_state_update=millis(); + if (status != ESP_NOW_SEND_SUCCESS){ + error_count++; + state=STATE_SAT_NONE; + //nowSatThread::global_data->logger->error("nowsat Error sending"); + }else{ + error_count=0; + } + //nowSatThread::global_data->logger->notice(F("nowsat State: %d"),state); +} + +/* + Callback when data is received + this is called by an other thread with other priority level and on CPU 0 + logging do not work: mutex do not work on bi-processor MCU + a packet is defined by: + 1 byte type of packet + + Sequence for pairing: + master : type 0 : pair request : struct message_pair_crc + satellite : type 1 : pair response : struct message_pair_crc + master : type 2 : pair ACK : struct message_pair_crc + + Sequence for data: + satellite : type 99 : data send : struct message_data_crc + master : type 3 : data ACK : struct message_pair_crc + master : type 10 : data NACK : struct message_pair_crc not managed by satellite + + any packey have a squence number incremented by one any transmission + and a CRC computed for error checking + + The send receive machine have a "state" defined by state_t + Any change of state have rules for permitted or deny change of state + + Paired state is an addition acondition to take in account for discrimine action to take + + Any message have a datetime time stamp used by satellite to sync system time + A special packet with sent=1 topic and payload NULL is a packet used to make a sync request +*/ +static void OnDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *incomingData, int len) { + // Create a struct_message to hold incoming sensor readings + //nowSatThread::global_data->logger->notice(F("nowsat Pacchetto ricevuto da MAC: : %X:%X:%X:%X:%X:%X"), + // esp_now_info->src_addr[0], esp_now_info->src_addr[1], esp_now_info->src_addr[2], + // esp_now_info->src_addr[3], esp_now_info->src_addr[4], esp_now_info->src_addr[5]); + //nowSatThread::global_data->logger->notice(F("nowsat Bytes received: %d"),len); + + // Controlla se è una richiesta di Pairing + uint16_t type; + memcpy(&type, incomingData, sizeof(type)); + if (type == 0) { + //nowSatThread::global_data->logger->notice("nowsat Richiesta di pairing ricevuta"); + message_pair_crc incomingMessage; + memcpy(&incomingMessage, incomingData, len); + + uint8_t crc = esp_rom_crc8_le(0, (const uint8_t*)&incomingMessage.message, sizeof(incomingMessage.message)); + //nowSatThread::global_data->logger->notice(F("nowsat computed CRC: %d"),crc); + + if (crc != incomingMessage.crc){ + //nowSatThread::global_data->logger->error("nowsat CRC mismatch"); + //nowSatThread::global_data->logger->error("nowsat crcc=%d crcr=%d",crc,incomingMessage.crc); + return; + } + + if (state != STATE_SAT_NONE){ + //nowSatThread::global_data->logger->error(F("nowsat STATE mismatch: %d, %d"),state, STATE_SAT_NONE); + state = STATE_SAT_NONE; + return; + } + + if (config.paired){ + //nowSatThread::global_data->logger->error("nowsat PAIR mismatch"); + return; + } + + //nowSatThread::global_data->logger->notice(F("nowsat SEQ: %d"),incomingMessage.message.seq); + seq=incomingMessage.message.seq; + last_state_update=millis(); + state = STATE_SAT_PAIR_RECEIVED; + // Answer to tx for pairing confirmation + // Create a struct message + message_pair_crc outgoingMessage; + outgoingMessage.message.type=1; + outgoingMessage.message.seq=++seq; + outgoingMessage.message.datetime=now(); + outgoingMessage.crc = esp_rom_crc8_le(0, (const uint8_t*)&outgoingMessage.message, sizeof(outgoingMessage.message)); + + add_broadcast_peer(); + esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &outgoingMessage, sizeof(outgoingMessage)); + if (result == ESP_OK) { + state=STATE_SAT_PAIR_ACK_SENDED; + //nowSatThread::global_data->logger->notice("nowsat Sent with success"); + } else { + state=STATE_SAT_NONE; + //nowSatThread::global_data->logger->error("nowsat Sent with error"); + return; + } + + } else if (type == 2 ) { + //nowSatThread::global_data->logger->notice("nowsat ACK al broadcast ricevuta"); + message_pair_crc incomingMessage; + memcpy(&incomingMessage, incomingData, len); + uint8_t crc = esp_rom_crc8_le(0, (const uint8_t*)&incomingMessage.message, sizeof(incomingMessage.message)); + if (crc != incomingMessage.crc){ + //nowSatThread::global_data->logger->error("nowsat CRC mismatch"); + return; + } + + if ((millis() - last_state_update) > TRANSACTION_TIMEOUT){ + //nowSatThread::global_data->logger->error(F("nowsat Transaction timeout %d"),millis() - last_state_update); + state=STATE_SAT_NONE; + return; + } + + if (state != STATE_SAT_PAIR_ACK_SENDED){ + //nowSatThread::global_data->logger->error(F("nowsat STATE mismatch: %d, %d"),state, STATE_SAT_PAIR_ACK_SENDED); + state = STATE_SAT_NONE; + return; + } + + if (seq+1 == incomingMessage.message.seq){ + //nowSatThread::global_data->logger->notice(F("nowsat SEQ: %d"),incomingMessage.message.seq); + }else{ + //nowSatThread::global_data->logger->error(F("nowsat SEQ mismatch: %d, %d"),incomingMessage.message.seq,seq+1); + return; + } + + if (esp_now_is_peer_exist(esp_now_info->src_addr)){ + //nowSatThread::global_data->logger->notice("nowsat peer già registrato"); + }else{ + // Aggiunge il master come peer specifico + esp_now_peer_info_t peerInfo = {}; + memcpy(peerInfo.peer_addr, esp_now_info->src_addr, 6); + peerInfo.channel = 0; + //peerInfo.ifidx = WIFI_IF_STA; // Interface used (Station o AP) + peerInfo.encrypt = false; // enable with pioarduino only! tasmota configurated with no encryption + //memcpy(peerInfo.lmk, mio_lmk, 16); + + if (esp_now_add_peer(&peerInfo) == ESP_OK) { + //nowSatThread::global_data->logger->notice("nowsat Master registrato come peer fisso."); + //nowSatThread::global_data->logger->notice("nowsat Accoppiamento riuscito!"); + memcpy(config.peerMac, esp_now_info->src_addr, 6); // Salva il MAC reale del master + config.paired=true; + error_count=0; + //Rimuove il peer broadcast generico + esp_now_del_peer(broadcastAddress); + have_to_write_config=true; + setTime(incomingMessage.message.datetime); + nowSatThread::global_data->esprtc->setTime(now()); + + }else{ + //nowSatThread::global_data->logger->notice("nowsat Error adding peer"); + } + } + + state = STATE_SAT_PAIR_DONE; + + } else if (type == 3 ) { + //nowSatThread::global_data->logger->notice("nowsat ACK ai dati ricevuta"); + message_pair_crc incomingMessage; + memcpy(&incomingMessage, incomingData, len); + uint8_t crc = esp_rom_crc8_le(0, (const uint8_t*)&incomingMessage.message, sizeof(incomingMessage.message)); + if (crc != incomingMessage.crc){ + //nowSatThread::global_data->logger->error("nowsat CRC mismatch"); + return; + } + + if ((millis() - last_state_update) > TRANSACTION_TIMEOUT){ + //nowSatThread::global_data->logger->error(F("nowsat Transaction timeout %d"),millis() - last_state_update); + state=STATE_SAT_NONE; + return; + } + + if (state != STATE_SAT_DATA_SENDED){ + //nowSatThread::global_data->logger->error(F("nowsat STATE mismatch: %d, %d"),state, STATE_SAT_DATA_SENDED); + state = STATE_SAT_NONE; + return; + } + + if (seq+1 == incomingMessage.message.seq){ + //nowSatThread::global_data->logger->notice(F("nowsat SEQ: %d"),incomingMessage.message.seq); + }else{ + //nowSatThread::global_data->logger->error(F("nowsat SEQ mismatch: %d, %d"),incomingMessage.message.seq,seq+1); + return; + } + + // Sblocca il ciclo principale consentendo il deep sleep + transmissionCompleted = true; + if (channelchanged){ + have_to_write_config=true; + } + channelchanged=false; + setTime(incomingMessage.message.datetime); + nowSatThread::global_data->esprtc->setTime(now()); + state = STATE_SAT_DATA_DONE; + } else { + //nowSatThread::global_data->logger->error("nowsat message type unknown"); + return; + } +} + +// add broadcast address to the list of peers +static void add_broadcast_peer(){ + // Register peer + esp_now_peer_info_t peerInfo = {}; + memcpy(peerInfo.peer_addr, broadcastAddress, 6); + //peerInfo.ifidx = WIFI_IF_STA; // Interfaccia usata (Station o AP) + peerInfo.channel = 0; + peerInfo.encrypt = false; + + if (esp_now_is_peer_exist(peerInfo.peer_addr)){ + nowSatThread::global_data->logger->notice(F("nowsat now peer broadcast already registered")); + }else{ + // Add peer + if (esp_now_add_peer(&peerInfo) != ESP_OK){ + nowSatThread::global_data->logger->error(F("nowsat now Failed to add broadcast peer")); + } + } +} + +// The thread +nowSatThread::nowSatThread(now_sat_data_t* now_sat_data) + : Thread{"now sat", TASK_NOW_SAT_STACK_SIZE, TASK_NOW_SAT_PRIORITY + # if portNUM_PROCESSORS > 1 + ,1 // if multicore 1 indicate the index number of the CPU which the task should be pinned to + #endif + }, + data{now_sat_data} +{ + //data->logger->notice("nowsat Create Thread %s %d", GetName().c_str(), data->id); + data->status->memory_collision=ok; + data->status->no_heap_memory=ok; + + global_data=data; + + //Start(); + +}; + +nowSatThread::~nowSatThread() +{ +} + +void nowSatThread::Begin() +{ + if (bootCount == 0 ){ + + config.channel=0; + config.paired=false; + error_count=0; + last_state_update = 0; + state = STATE_SAT_NONE; + seq=0; + transmissionCompleted = false ; + channelchanged = false; + + uintptr_t start = (uintptr_t)&_rtc_data_start; + uintptr_t end = (uintptr_t)&_rtc_data_end; + data->logger->notice(F("nowsat RTC used data: %d bytes on 8192 total"), (unsigned)(end - start)); + } + + if(read_local_config()){ + if (bootCount == 0 ){ + channel=config.channel; + } + }else{ + data->logger->error(F("nowsat failed reading config file")); + } + + // Set device as a Wi-Fi Station + WiFi.mode(WIFI_STA); + WiFi.disconnect(); + + // ESP-Now Range Test: Real-World Results for ESP32 Devices + // https://youtu.be/oz0a7Ur7nko?si=aUMBJ4SpeXTSMMPg + /* + esp_err_t err = esp_wifi_set_protocol(WIFI_IF_STA,WIFI_PROTOCOL_LR); + if (err == ESP_OK) { + data->logger->notice("nowsat Protocol successfully restricted to Long Range (LR) Mode successfully enabled!"); + } else { + data->logger->notice("nowsat Failed to set protocol. Error code: %d", err); + } + */ + + // Init ESP-NOW + if (esp_now_init() != ESP_OK) { + data->logger->notice("nowsat Error initializing ESP-NOW"); + return; + } + + /* + // Imposta la PMK globale + // Se questa funzione fallisce o non viene chiamata, ESP-NOW userà una PMK di default della mesh, + // compromettendo la sicurezza dell'intero ecosistema. + if (esp_now_set_pmk(mia_pmk) == ESP_OK) { + data->logger->notice("nowsat PMK impostata con successo!"); + } else { + data->logger->notice("nowsat Errore nell'impostazione della PMK"); + return; + } + */ + + // Once ESPNow is successfully Init, we will register for Send CB to + // get the status of Trasnmitted packet + esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent)); + + // Register for a callback function that will be called when data is received + esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv)); + + data->logger->notice("nowsat Boot number: %d paired: %T", bootCount,config.paired); + + if (config.paired){ + esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + // Aggiunge il master come peer specifico + + esp_now_peer_info_t peerInfo = {}; + memcpy(peerInfo.peer_addr, config.peerMac, 6); + peerInfo.channel = 0; + //peerInfo.ifidx = WIFI_IF_STA; // Interfaccia usata (Station o AP) + peerInfo.encrypt = false; // enable with pioarduino only! tasmota configurated with no encryption + //memcpy(peerInfo.lmk, mio_lmk, 16); + + if (esp_now_is_peer_exist(peerInfo.peer_addr)){ + data->logger->notice("nowsat boot peer broadcast già registrato"); + }else{ + if (esp_now_add_peer(&peerInfo) == ESP_OK) { + data->logger->notice("nowsat boot Master registered as fixed peer"); + data->logger->notice("nowsat boot Pairing done!"); + config.paired=true; + }else{ + data->logger->error("nowsat boot Error adding peer"); + config.paired=false; + } + } + } + + //Increment boot number + ++bootCount; +} + +void nowSatThread::Cleanup() +{ + data->logger->notice(F("nowsat Delete Thread %s %d"), GetName().c_str(), data->id); + // todo disconnect and others + data->status->memory_collision=unknown; + data->status->no_heap_memory=unknown; + delete this; +} + +void nowSatThread::Run() { + data->logger->notice(F("nowsat Starting Thread %s %d"), GetName().c_str(), data->id); + + mqttMessage_t message; + bool skip=false; + + // wait for pairing + while(!config.paired){ + add_broadcast_peer(); + channel++; + if (channel >13) channel=1; + data->logger->notice("nowsat channel: %d",channel); + esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + + data->logger->notice(F("nowsat wait for pairing")); + delay(1000); + if(have_to_write_config){ + config.channel=channel; + if (write_local_config()){ + data->logger->notice(F("nowsat config file written")); + }else{ + data->logger->error(F("nowsat Error writing config file")); + } + have_to_write_config=false; + } + } + + // we need time in sync; without it no measure are done + if (timeStatus() != timeSet){ + // send null message to sync time + message.sent=1; + message.topic[0] = NULL; + message.payload[0] = NULL; + if(nowPublish(message)){ + config.channel=channel; + if (write_local_config()){ + data->logger->notice(F("nowsat config file written")); + }else{ + data->logger->error(F("nowsat Error writing config file")); + } + }else{ + skip=true; + data->logger->error(F("nowsat sendig message for time sync")); + channel++; + if (channel >13) channel=1; + data->logger->notice("nowsat channel: %d",channel); + } + } + + // at least one time check the queue until measure are done and the queue is empty + bool atleastone=true; + while((*data->state_measure != STATE_MEASURE_DONE) or (*data->state_db != STATE_DB_RECOVERY_DONE) or atleastone){ + atleastone=false; + // if there are no enough space left on the mqtt queue send it to the DB + while (data->mqttqueue->NumSpacesLeft() <= QUEUE_SPACELEFT_PUBLISH){ + store(); + } + + // during the measure check the queue every 1 sec + data->mqttqueue->Peek(&message, pdMS_TO_TICKS( 1000 )); // wait for a new incoming message + while(data->mqttqueue->Peek(&message, pdMS_TO_TICKS( 0 ))){ + if (!doRelay(message)) break; // publish message + data->mqttqueue->Dequeue(&message, pdMS_TO_TICKS( 0 )); + } + while (data->recoveryqueue->Peek(&message, pdMS_TO_TICKS( 0 ))){ + if (message.sent==0 and message.topic[0] == NULL and message.payload[0] == NULL){ + data->recoveryqueue->Dequeue(&message, pdMS_TO_TICKS( 0 )); + if(!data->dbqueue->Enqueue(&message,pdMS_TO_TICKS(0))){ + data->logger->error(F("nowsat lost SYNC message for db: %s ; %s"), message.topic, message.payload); + }else{ + data->logger->notice(F("nowsat SYNC message queued for db")); + } + }else{ + if (!doRelay(message,true)) break; // publish message + data->recoveryqueue->Dequeue(&message, pdMS_TO_TICKS( 0 )); + while(data->mqttqueue->Peek(&message, pdMS_TO_TICKS( 0 ))){ + if (!doRelay(message)) break; // publish message + data->mqttqueue->Dequeue(&message, pdMS_TO_TICKS( 0 )); + } + } + } + } + + // check if we have to write config + if(have_to_write_config){ + config.channel=channel; + if (write_local_config()){ + data->logger->notice(F("nowsat config file written for channel change")); + }else{ + data->logger->error(F("nowsat Error writing config file for channel change")); + } + have_to_write_config=false; + } + + // try to flush messages in queue + data->logger->notice(F("nowsat mqtt queue space left %d"),data->mqttqueue->NumSpacesLeft()); + data->logger->notice(F("nowsat recovery queue space left %d"),data->recoveryqueue->NumSpacesLeft()); + data->logger->notice(F("nowsat db queue space left %d"),data->dbqueue->NumSpacesLeft()); + + // if there messages on the mqtt queue send it to the DB + bool timeout=false; + time_t start=millis(); + while (!data->mqttqueue->IsEmpty() and !timeout){ + store(); + timeout=(millis()-start) > 10000; + } + + // wait db queue to go empty + timeout=false; + start=millis(); + while (!data->dbqueue->IsEmpty() and !timeout){ + timeout=(millis()-start) > 10000; + delay(100); + } + + // here we want 0 meaaseges in queues + data->logger->notice(F("nowsat mqtt queue space left before sleep %d"),data->mqttqueue->NumSpacesLeft()); + data->logger->notice(F("nowsat db queue space left before sleep %d"),data->dbqueue->NumSpacesLeft()); + + // set RTC time + if (timeStatus() == timeSet){ + if (!data->frtosRTC->set(now())){ + data->logger->error("nowsat Setting RTC time from esp-now!"); + } + } + + // checks for heap and stack + //data->logger->notice(F("HEAP: %l"),esp_get_minimum_free_heap_size()); + if( esp_get_minimum_free_heap_size() < HEAP_MIN_WARNING){ + data->logger->error(F("free HEAP: %l"),esp_get_minimum_free_heap_size()); + data->status->no_heap_memory=error; + } + + //data->logger->notice(F("stack nowsat: %d"),uxTaskGetStackHighWaterMark(NULL)); + if(uxTaskGetStackHighWaterMark(NULL) < STACK_MIN_WARNING){ + data->logger->error(F("nowsat stack")); + data->status->memory_collision=error; + } + + unsigned int sleeptime; + if (skip){ + sleeptime=1; + }else{ + time_t counter = ((now() + (data->station->sampletime/2)) / data->station->sampletime); + time_t nextreporttime = (counter+1) * data->station->sampletime -10; + sleeptime = nextreporttime - now(); + } + + /* + First we configure the wake up source + We set our ESP32 to wake up every sampletime seconds + */ + data->logger->notice("nowsat Setup ESP32 to sleep for %d seconds",sleeptime); + esp_sleep_enable_timer_wakeup(sleeptime * S_TO_uS_FACTOR); + + /* + Next we decide what all peripherals to shut down/keep on + By default, ESP32 will automatically power down the peripherals + not needed by the wakeup source, but if you want to be a poweruser + this is for you. Read in detail at the API docs + http://esp-idf.readthedocs.io/en/latest/api-reference/system/deep_sleep.html + Left the line commented as an example of how to configure peripherals. + The line below turns off all RTC peripherals in deep sleep. + */ + //esp_deep_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_OFF); + //data->logger->notice("nowsat Configured all RTC Peripherals to be powered down in sleep"); + + /* + Now that we have setup a wake cause and if needed setup the + peripherals state in deep sleep, we can now start going to + deep sleep. + */ + + data->logger->notice("nowsat I am going to sleep now"); + Serial.flush(); + // + esp_deep_sleep_start(); + data->logger->notice(F("nowsat This will never be printed")); +} + +// get one message from publish queue and send it to the queue for DB +// but if it is a resend message (sent == true) skip it; +// now we have a separate queue for recovery so the check is no more required +void nowSatThread::store() { + + mqttMessage_t mqtt_message; + + if (data->mqttqueue->Dequeue(&mqtt_message, pdMS_TO_TICKS( 0 ))){; // dequeue + //if (mqtt_message.sent){ + // data->logger->error(F("nowsat skip and do not store message sended before: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + //}else{ + if(data->dbqueue->Enqueue(&mqtt_message,pdMS_TO_TICKS(0))){ + data->logger->notice(F("nowsat skip and enqueue message for db: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + }else{ + data->logger->error(F("nowsat lost message for db: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + } + //} + }else{ + data->logger->error(F("nowsat getting message from mqtt queue")); + } +} + +// try to send message to the master and send the message to the DB queue +// only if it is required +// set recovery to true if the message come from recovery queue +bool nowSatThread::doRelay(mqttMessage_t mqtt_message, const bool recovery) { + + bool rc=false; + bool resend = mqtt_message.sent; + + if(nowPublish(mqtt_message)){ + mqtt_message.sent=1; // all done: flag as sent + rc=true; + } + + // if it was already sendend skip it and do do not store + // if publish fail and come from recovery skip it and do do not store + if (not resend and not (rc == false and recovery)) { + if(!data->dbqueue->Enqueue(&mqtt_message,pdMS_TO_TICKS(0))){ + data->logger->error(F("nowsat lost message for db: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + } + } + + return rc; +} + +// publish the message by radio with esp-now protocol +// is a blocking function waiting for the end of trasaction with a timeout control +// return the status of transmission +bool nowSatThread::nowPublish(mqttMessage_t mqtt_message) { + + bool rc=false; + + if (!config.paired) return rc; + + if (state != STATE_SAT_NONE and state != STATE_SAT_DATA_DONE){ + data->logger->notice(F("nowsat STATE not ready: %d, %d"),state, STATE_SAT_NONE); + state = STATE_SAT_NONE; + return rc; + } + + if(state == STATE_SAT_PAIR_DONE) state = STATE_SAT_NONE; + + if (!config.paired or error_count > 10){ + data->logger->notice(F("nowsat paired %T error count %d"),config.paired, error_count); + + channel++; + channelchanged=true; + if (channel >13) channel=1; + data->logger->notice("nowsat channel: %d",channel); + esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE); + if (!config.paired) delay(3000); + } + + transmissionCompleted = false; + // Create a struct_message called Readings to hold sensor readings + message_data_crc outgoingMessage; + outgoingMessage.message.type=99; + outgoingMessage.message.seq = ++seq; + + // Set values to send + outgoingMessage.message.mqttmessage=mqtt_message; + + outgoingMessage.crc = esp_rom_crc8_le(0, (const uint8_t*)&outgoingMessage.message, sizeof(outgoingMessage.message)); + + // Send message via ESP-NOW + esp_err_t result = esp_now_send(config.peerMac, (uint8_t *) &outgoingMessage, sizeof(outgoingMessage)); + if (result == ESP_OK) { + data->logger->notice("nowsat outgoing message Queued for send"); + //error_count=0; + state = STATE_SAT_DATA_SENDED; + rc=true; + + //ATTESA CRITICA: Aspetta che la callback OnDataSent venga eseguita + unsigned long startTimeout = millis(); + while (!transmissionCompleted) { + delay(10); + // Timeout di sicurezza (es. 1000ms) per evitare che l'ESP resti acceso all'infinito se il destinatario è spento + if (millis() - startTimeout > TRANSACTION_TIMEOUT) { + data->logger->error("nowsat Transaction timeout exceded!"); + rc=false; + state = STATE_SAT_NONE; + break; + } + } + } else { + data->logger->error("nowsat Error queueing the data"); + error_count++; + state = STATE_SAT_NONE; + } + + return rc; +} diff --git a/platformio/stima_v3/stimawifi/src/now_sat_thread.h b/platformio/stima_v3/stimawifi/src/now_sat_thread.h new file mode 100644 index 000000000..0af726518 --- /dev/null +++ b/platformio/stima_v3/stimawifi/src/now_sat_thread.h @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include +#include +#include +#include "TimeLib.h" + +#ifndef NOW_SAT_THREAD_H_ +#define NOW_SAT_THREAD_H_ + +#define S_TO_uS_FACTOR 1000000ULL /* Conversion factor for micro seconds to seconds */ + +struct now_sat_data_t { // thread communication data + int id; + frtosLogging* logger; + Queue* mqttqueue; + Queue* dbqueue; + Queue* recoveryqueue; + nowsatStatus_t* status; + station_t* station; + state_measure_t* state_measure; + state_db_t* state_db; + frtosRtc* frtosRTC; + ESP32Time* esprtc; +}; + +typedef enum { + STATE_SAT_NONE, + STATE_SAT_PAIR_RECEIVED, + STATE_SAT_PAIR_ACK_SENDED, + STATE_SAT_PAIR_DONE, + STATE_SAT_DATA_SENDED, + STATE_SAT_DATA_ACK_RECEIVED, + STATE_SAT_DATA_DONE +} state_sat_t; + +extern "C" { + extern uint8_t _rtc_data_start; + extern uint8_t _rtc_data_end; +} + +static bool read_local_config(); +static bool write_local_config(); +static void OnDataSent(const uint8_t *des_addr, esp_now_send_status_t status); +static void OnDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *incomingData, int len); +static void add_broadcast_peer(); + +using namespace cpp_freertos; + +class nowSatThread : public Thread { + +public: + nowSatThread(now_sat_data_t* now_sat_data); + ~nowSatThread(); + virtual void Cleanup(); + void Begin(); + + static now_sat_data_t* global_data; + + +protected: + virtual void Run(); + +private: + now_sat_data_t* data; + void store(); + bool doRelay(mqttMessage_t mqtt_message, const bool recovery=false); + bool nowPublish(mqttMessage_t mqtt_message); + +}; + +#endif diff --git a/platformio/stima_v3/stimawifi/src/now_thread.cpp b/platformio/stima_v3/stimawifi/src/now_thread.cpp new file mode 100644 index 000000000..d800b7b4b --- /dev/null +++ b/platformio/stima_v3/stimawifi/src/now_thread.cpp @@ -0,0 +1,531 @@ +#include "common.h" + + +//*********************************************************************************************** +// global definition to use in NOW callback +// pointers setted by class istance +now_data_t* nowThread::global_data=NULL; +static bool have_to_write_config=false; +now_config_t config; +const uint8_t broadcastAddress[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; +volatile unsigned int last_state_update =0; +volatile state_t state = STATE_NONE; +volatile uint16_t seq=0; + +//*********************************************************************************************** + + +/* +// Define global key PMK ( 16 byte long) +// Have to be the same on all devices wants inter communicate. +const uint8_t mia_pmk[16] = { + 0xA1, 0xB2, 0xC3, 0xD4, 0xE5, 0xF6, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x11 +}; + +// Define local key LMK ( 16 byte long) +// you can choose any value.Two communicate device have to have the same key. +const uint8_t mio_lmk[16] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 +}; +*/ + +// read esp-now master configuration from EEPROM +static bool read_local_config() { + + if (LittleFS.exists("/master.json")) { + //file exists, read and load + nowThread::global_data->logger->notice(F("now reading rmap config file")); + File configFile = LittleFS.open("/master.json", "r"); + if (configFile) { + nowThread::global_data->logger->notice(F("now opened master config file")); + + //size_t size = configFile.size(); + // Allocate a buffer to store contents of the file. + //std::unique_ptr buf(new char[size]); + //configfile.readBytes(buf.get(), size); + String content = configFile.readString(); + configFile.close(); + + DynamicJsonDocument doc(256); // size computed by on line utils + DeserializationError error = deserializeJson(doc,content); + if (!error){ + const char* ver = doc["ver"]; // "1.0" + config.paired = doc["paired"]; + JsonArray mac = doc["satellitemac"]; + config.peerMac[0]= mac[0]; // 1 + config.peerMac[1]= mac[1]; // 2 + config.peerMac[2]= mac[2]; // 3 + config.peerMac[3]= mac[3]; // 4 + config.peerMac[4]= mac[4]; // 5 + config.peerMac[5]= mac[5]; // 6 + config.channel = doc["channel"]; + + nowThread::global_data->logger->notice(F("now Config read:")); + nowThread::global_data->logger->notice(F("now paired: %T"),config.paired); + nowThread::global_data->logger->notice(F("now MAC 0: %X"),config.peerMac[0]); + nowThread::global_data->logger->notice(F("now MAC 1: %X"),config.peerMac[1]); + nowThread::global_data->logger->notice(F("now MAC 2: %X"),config.peerMac[2]); + nowThread::global_data->logger->notice(F("now MAC 3: %X"),config.peerMac[3]); + nowThread::global_data->logger->notice(F("now MAC 4: %X"),config.peerMac[4]); + nowThread::global_data->logger->notice(F("now MAC 5: %X"),config.peerMac[5]); + nowThread::global_data->logger->notice(F("now channel: %d"),config.channel); + nowThread::global_data->logger->notice(F("now END config")); + + return true; + } else { + nowThread::global_data->logger->error(F("now reading master file: %s"),error.c_str()); + } + } else { + nowThread::global_data->logger->warning(F("now master file do not exist")); + } + } + return false; +} + +// write esp-now master configuration to EEPROM +static bool write_local_config() { + + //save the custom parameters to FS + //nowThread::global_data->logger->notice(F("now saving master config")); + + File configFile = LittleFS.open("/master.json", "w"); + if (!configFile) { + //nowThread::global_data->logger->error(F("now failed to open rmap config file for writing")); + return false; + } + + DynamicJsonDocument doc(256); // size computed by on line utils + doc[F("ver")] = F(SOFTWARE_VERSION); + doc[F("paired")] = config.paired; + + //doc[F("satellitemac")][0] = config.peerMac[0]; + //doc[F("satellitemac")][1] = config.peerMac[1]; + //doc[F("satellitemac")][2] = config.peerMac[2]; + //doc[F("satellitemac")][3] = config.peerMac[3]; + //doc[F("satellitemac")][4] = config.peerMac[4]; + //doc[F("satellitemac")][5] = config.peerMac[5]; + + JsonArray satellitemac = doc[F("satellitemac")].to(); + satellitemac.add(config.peerMac[0]); + satellitemac.add(config.peerMac[1]); + satellitemac.add(config.peerMac[2]); + satellitemac.add(config.peerMac[3]); + satellitemac.add(config.peerMac[4]); + satellitemac.add(config.peerMac[5]); + + doc["channel"] = config.channel; + //{"ver":"12.123","paired":true,"satellitemac":[1111,2222,3333,4444,5555,6666],"channel":13} + //char buffer[100]; + //serializeJson(doc, buffer, sizeof(buffer)); + //configFile.print(buffer); + serializeJson(doc, configFile); + configFile.close(); + //nowThread::global_data->logger->notice(F("now saved master config parameter")); + //end save + return true; +} + +// add broadcast address to the list of peers +void nowThread::add_broadcast_peer(){ + // Register peer + esp_now_peer_info_t peerInfo = {}; + memcpy(peerInfo.peer_addr, broadcastAddress, 6); + //peerInfo.ifidx = WIFI_IF_STA; // Interfaccia usata (Station o AP) + peerInfo.channel = 0; + peerInfo.encrypt = false; + + if (esp_now_is_peer_exist(peerInfo.peer_addr)){ + //data->logger->notice(F("now peer broadcast already registered")); + }else{ + // Add peer + if (esp_now_add_peer(&peerInfo) != ESP_OK){ + //data->logger->error(F("now Failed to add broadcast peer")); + } + } +} + + +// Callback when data is sent +// this is called by an other thread with other priority level and on CPU 0 +// logging do not work: mutex do not work on bi-processor MCU +static void OnDataSent(const uint8_t *des_addr, esp_now_send_status_t status) { + //nowThread::global_data->logger->notice(F("now OnDataSent")); + //nowThread::global_data->logger->notice(F("now State: %d"),state); + //nowThread::global_data->logger->notice(F("now destination MAC: %X:%X:%X:%X:%X:%X"), + // des_addr[0], des_addr[1], des_addr[2], + // des_addr[3], des_addr[4], des_addr[5]); + //nowThread::global_data->logger->notice(F("now Last Packet Send Status: %s"), status == ESP_NOW_SEND_SUCCESS ? "Delivery Success" : "Delivery Fail" ); + last_state_update=millis(); + if (status != ESP_NOW_SEND_SUCCESS){ + state=STATE_NONE; + //nowThread::global_data->logger->error(F("now Error sending")); + } + //nowThread::global_data->logger->notice(F("State: %d"),state); +} + +/* + Callback when data is received + this is called by an other thread with other priority level and on CPU 0 + logging do not work: mutex do not work on bi-processor MCU + a packet is defined by: + 1 byte type of packet + + Sequence for pairing: + master : type 0 : pair request : struct message_pair_crc + satellite : type 1 : pair response : struct message_pair_crc + master : type 2 : pair ACK : struct message_pair_crc + + Sequence for data: + satellite : type 99 : data send : struct message_data_crc + master : type 3 : data ACK : struct message_pair_crc + master : type 10 : data NACK : struct message_pair_crc not managed by satellite + + any packey have a squence number incremented by one any transmission + and a CRC computed for error checking + + The send receive machine have a "state" defined by state_t + Any change of state have rules for permitted or deny change of state + + Paired state is an addition acondition to take in account for discrimine action to take + + Any message have a datetime time stamp used by satellite to sync system time + A special packet with sent=1 topic and payload NULL is a packet used to make a sync request +*/ + +static void OnDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *incomingData, int len) { + //nowThread::global_data->logger->notice(F("now Packed received from MAC: : %X:%X:%X:%X:%X:%X"), + // esp_now_info->src_addr[0], esp_now_info->src_addr[1], esp_now_info->src_addr[2], + // esp_now_info->src_addr[3], esp_now_info->src_addr[4], esp_now_info->src_addr[5]); + //nowThread::global_data->logger->notice(F("now Bytes received: %d"),len); + + // Controlla se è una risposta al Pairing + uint16_t type; + memcpy(&type, incomingData, sizeof(type)); + if (type == 1 ) { + //nowThread::global_data->logger->notice(F("now broadcast ack received")); + message_pair_crc incomingMessage; + memcpy(&incomingMessage, incomingData, len); + uint8_t crc = esp_rom_crc8_le(0, (const uint8_t*)&incomingMessage.message,sizeof(incomingMessage.message)); + //nowThread::global_data->logger->notice(F("now computed CRC: %d"),crc); + if (crc != incomingMessage.crc){ + //nowThread::global_data->logger->error(F("now CRC mismatch")); + return; + } + + if (seq+1 == incomingMessage.message.seq){ + //nowThread::global_data->logger->notice(F("now SEQ: %d"),incomingMessage.message.seq); + }else{ + //nowThread::global_data->logger->error(F("now SEQ mismatch: %d, %d"),incomingMessage.message.seq,seq+1); + return; + } + + if (state != STATE_PAIR_SENDED){ + //nowThread::global_data->logger->error(F("now STATE mismatch: %d, %d"),state, STATE_PAIR_SENDED); + state = STATE_NONE; + return; + } + + if (config.paired){ + //nowThread::global_data->logger->error(F("now PAIR mismatch")); + return; + } + + if ((millis() - last_state_update) > TRANSACTION_TIMEOUT){ + //nowThread::global_data->logger->error(F("now Transaction timeout %d"),millis() - last_state_update); + state=STATE_NONE; + return; + } + + state=STATE_PAIR_ACK_RECEIVED; + + if (esp_now_is_peer_exist(esp_now_info->src_addr)){ + //nowThread::global_data->logger->notice(F("now peer already registered")); + }else{ + // Aggiunge il satellite come peer specifico + esp_now_peer_info_t peerInfo = {}; + memcpy(peerInfo.peer_addr, esp_now_info->src_addr, 6); + peerInfo.channel = 0; + //peerInfo.ifidx = WIFI_IF_STA; // Interfaccia usata (Station o AP) + peerInfo.encrypt = false; // enable with pioarduino only! tasmota configurated with no encryption + //memcpy(peerInfo.lmk, mio_lmk, 16); + + if (esp_now_add_peer(&peerInfo) == ESP_OK) { + //nowThread::global_data->logger->notice(F("now Satellite registered")); + //nowThread::global_data->logger->notice(F("now Paired!")); + memcpy(config.peerMac, esp_now_info->src_addr, 6); // Salva il MAC reale del satellite + + // Risponde al trasmettitore per confermare il pairing + // Create a message_pair called Readings to hold sensor readings + message_pair_crc outgoingMessage; + outgoingMessage.message.type=2; + outgoingMessage.message.seq=incomingMessage.message.seq+1; + outgoingMessage.message.datetime=now(); + outgoingMessage.crc = esp_rom_crc8_le(0, (const uint8_t*)&outgoingMessage.message, sizeof(outgoingMessage.message)); + esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &outgoingMessage, sizeof(outgoingMessage)); + if (result == ESP_OK) { + state=STATE_PAIR_DONE; + //nowThread::global_data->logger->notice(F("now Sent with success")); + } else { + state=STATE_NONE; + //nowThread::global_data->logger->error(F("now Sent with error")); + return; + } + + config.paired=true; + esp_now_del_peer(broadcastAddress); + have_to_write_config=true; + //}else{ + //nowThread::global_data->logger->error(F("now Error adding peer")); + } + } + } else if (type == 99) { + + message_data_crc incomingMessage; + memcpy(&incomingMessage, incomingData, len); + uint8_t crc = esp_rom_crc8_le(0, (const uint8_t*)&incomingMessage.message,sizeof(incomingMessage.message)); + if (crc != incomingMessage.crc){ + //nowThread::global_data->logger->error(F("now CRC mismatch")); + return; + } + + if (!config.paired){ + //nowThread::global_data->logger->error(F("now PAIR mismatch")); + return; + } + + if (state != STATE_NONE and state != STATE_PAIR_DONE and state != STATE_DATA_DONE){ + //nowThread::global_data->logger->error(F("now STATE mismatch: %d, %d/%d/%d"),state, STATE_NONE,STATE_PAIR_DONE,STATE_DATA_DONE); + return; + } + + state=STATE_DATA_RECEIVED; + + char dt[DATE_TIME_STRING_LENGTH]; + snprintf(dt, DATE_TIME_STRING_LENGTH, "%04u-%02u-%02uT%02u:%02u:%02u", year(), month(), day(), hour(), minute(), second()); + + //nowThread::global_data->logger->notice(F("now topic received: %s"),incomingMessage.message.mqttmessage.topic); + //nowThread::global_data->logger->notice(F("now payload received: %s"),incomingMessage.message.mqttmessage.payload); + + message_pair_crc outgoingMessage; + if (incomingMessage.message.mqttmessage.sent==1 + and incomingMessage.message.mqttmessage.topic[0] == NULL + and incomingMessage.message.mqttmessage.payload[0] == NULL){ + //nowThread::global_data->logger->notice(F("now time sync message received")); + outgoingMessage.message.type=3; // data ACK + }else{ + if(enqueueMqttMessage(incomingMessage.message.mqttmessage)){ + outgoingMessage.message.type=3; // data ACK + }else{ + // TODO NACK + outgoingMessage.message.type=10; // not managed ! + } + } + outgoingMessage.message.seq=incomingMessage.message.seq+1; + outgoingMessage.message.datetime=now(); + outgoingMessage.crc = esp_rom_crc8_le(0, (const uint8_t*)&outgoingMessage.message, sizeof(outgoingMessage.message)); + //nowThread::global_data->logger->notice(F("now computed CRC: %d"),outgoingMessage.crc); + esp_err_t result = esp_now_send(config.peerMac, (uint8_t *) &outgoingMessage, sizeof(outgoingMessage)); + if (result == ESP_OK) { + state=STATE_DATA_DONE; + //nowThread::global_data->logger->notice(F("now Sent with success")); + } else { + state=STATE_NONE; + //nowThread::global_data->logger->error(F("now Sent with error")); + } + } +} + +// encode and enqueue in a proper queue one message +// sendig to mqttqueue and when require to dbqueue +static bool enqueueMqttMessage(const mqttMessage_t mqtt_message) { + + bool rc=true; + //mqtt_message.sent=0; + + //nowThread::global_data->logger->notice(F("now have to publish:")); + //nowThread::global_data->logger->notice(F("now Topic: %s"),mqtt_message.topic); + //nowThread::global_data->logger->notice(F("now Payload: %s"),mqtt_message.payload); + + // if there are enough space left on the publish queue send it + if (nowThread::global_data->mqttqueue->NumSpacesLeft() > QUEUE_SPACELEFT_MEASURE){ + //nowThread::global_data->logger->notice(F("now enqueue for mqtt: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + if(!nowThread::global_data->mqttqueue->Enqueue(&mqtt_message,pdMS_TO_TICKS(0))){ + //nowThread::global_data->logger->error(F("now enqueue for mqtt: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + if (nowThread::global_data->dbqueue->Enqueue(&mqtt_message,pdMS_TO_TICKS(0))){ // on error send il to DB + //nowThread::global_data->logger->notice(F("now enqueue for db")); + }else{ + //nowThread::global_data->logger->error(F("now lost message for db: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + rc=false; + } + } + } else { // if there are no enough space left on the publish queue send it to the archive + if(nowThread::global_data->dbqueue->Enqueue(&mqtt_message,pdMS_TO_TICKS(0))){ + //nowThread::global_data->logger->notice(F("now enqueue for db")); + }else{ + //nowThread::global_data->logger->error(F("now lost message for db: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + rc=false; + } + } + return rc; +} + +// The thread +nowThread::nowThread(now_data_t* now_data) + : Thread{"now", TASK_NOW_STACK_SIZE, TASK_NOW_PRIORITY + # if portNUM_PROCESSORS > 1 + ,1 // if multicore 1 indicate the index number of the CPU which the task should be pinned to + #endif + }, + data{now_data} +{ + //data->logger->notice("Create Thread %s %d", GetName().c_str(), data->id); + data->status->memory_collision=ok; + data->status->no_heap_memory=ok; + + global_data=data; + + //Start(); + +}; + +nowThread::~nowThread() +{ +} + + +void nowThread::Begin() +{ + // slow down + //setCpuFrequencyMhz(80); + + data->logger->notice(F("now Started")); + + if(!read_local_config()) data->logger->error(F("now failed reading config file")); + + data->logger->notice(F("now channel: %d"),config.channel); + esp_wifi_set_channel(config.channel, WIFI_SECOND_CHAN_NONE); + + // ESP-Now Range Test: Real-World Results for ESP32 Devices + // https://youtu.be/oz0a7Ur7nko?si=aUMBJ4SpeXTSMMPg + /* + esp_err_t err = esp_wifi_set_protocol(WIFI_IF_STA,WIFI_PROTOCOL_LR); + if (err == ESP_OK) { + data->logger->notice(F("now Protocol successfully restricted to Long Range (LR) Mode successfully enabled!")); + } else { + data->logger->error(F("now Failed to set protocol. Error code: %d"), err); + } + */ + + // Init ESP-NOW + if (esp_now_init() != ESP_OK) { + data->logger->notice(F("now Error initializing ESP-NOW")); + return; + } + + // Imposta la PMK globale + // Se questa funzione fallisce o non viene chiamata, ESP-NOW userà una PMK di default della mesh, + // compromettendo la sicurezza dell'intero ecosistema. + /* + if (esp_now_set_pmk(mia_pmk) == ESP_OK) { + data->logger->notice(F("now PMK impostata con successo!")); + } else { + data->logger->error(F("now Errore nell'impostazione della PMK")); + return; + } + */ + + // Once ESPNow is successfully Init, we will register for Send CB to + // get the status of Trasnmitted packet + esp_now_register_send_cb(esp_now_send_cb_t(OnDataSent)); + + // Register for a callback function that will be called when data is received + esp_now_register_recv_cb(esp_now_recv_cb_t(OnDataRecv)); + + if (config.paired){ + + // Aggiunge il master come peer specifico + esp_now_peer_info_t peerInfo = {}; + memcpy(peerInfo.peer_addr, config.peerMac, 6); + peerInfo.channel = 0; + //peerInfo.ifidx = WIFI_IF_STA; // Interfaccia usata (Station o AP) + peerInfo.encrypt = false; // enable with pioarduino only! tasmota configurated with no encryption + //memcpy(peerInfo.lmk, mio_lmk, 16); + + if (esp_now_is_peer_exist(peerInfo.peer_addr)){ + data->logger->notice(F("now boot peer broadcast già registrato")); + }else{ + if (esp_now_add_peer(&peerInfo) == ESP_OK) { + data->logger->notice(F("now boot Satellite registrato come peer fisso.")); + data->logger->notice(F("now boot Accoppiamento riuscito!")); + }else{ + data->logger->error(F("now boot Error adding peer")); + config.paired=false; + } + } + }else{ + add_broadcast_peer(); + } +} + +void nowThread::Cleanup() +{ + data->logger->notice(F("now Delete Thread %s %d"), GetName().c_str(), data->id); + // todo disconnect and others + data->status->memory_collision=unknown; + data->status->no_heap_memory=unknown; + delete this; +} + +void nowThread::Run() { + data->logger->notice(F("now Starting Thread %s %d"), GetName().c_str(), data->id); + for(;;){ + + delay(1000); + + if(have_to_write_config){ + if (write_local_config()){ + data->logger->notice(F("now config file written")); + }else{ + data->logger->error(F("now Error writing config file")); + } + have_to_write_config=false; + } + + if (!config.paired){ + + // send pairing request + message_pair_crc outgoingMessage; + outgoingMessage.message.type=0; + outgoingMessage.message.seq=++seq; + outgoingMessage.message.datetime=now(); + outgoingMessage.crc = esp_rom_crc8_le(0, (const uint8_t*)&outgoingMessage.message, sizeof(outgoingMessage.message)); + data->logger->notice(F("now State: %d"),state); + data->logger->notice(F("now Send broadcast message")); + data->logger->notice(F("now computed CRC: %d"),outgoingMessage.crc); + esp_err_t result = esp_now_send(broadcastAddress, (uint8_t *) &outgoingMessage, sizeof(outgoingMessage)); + if (result == ESP_OK) { + state=STATE_PAIR_SENDED; + data->logger->notice(F("now Pairing request sent with success")); + } else { + state=STATE_NONE; + data->logger->error(F("now Error sending pairing request")); + } + } + + // checks for heap and stack + //data->logger->notice(F("HEAP: %l"),esp_get_minimum_free_heap_size()); + if( esp_get_minimum_free_heap_size() < HEAP_MIN_WARNING){ + data->logger->error(F("free HEAP: %l"),esp_get_minimum_free_heap_size()); + data->status->no_heap_memory=error; + } + + //data->logger->notice(F("stack now: %d"),uxTaskGetStackHighWaterMark(NULL)); + if(uxTaskGetStackHighWaterMark(NULL) < STACK_MIN_WARNING){ + data->logger->error(F("now stack")); + data->status->memory_collision=error; + } + } +}; + diff --git a/platformio/stima_v3/stimawifi/src/now_thread.h b/platformio/stima_v3/stimawifi/src/now_thread.h new file mode 100644 index 000000000..8c2e8cc70 --- /dev/null +++ b/platformio/stima_v3/stimawifi/src/now_thread.h @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include +#include "TimeLib.h" + + +#ifndef NOW_THREAD_H_ +#define NOW_THREAD_H_ + +#define RESET_PAIR false + +struct now_data_t { // thread communication data + int id; + frtosLogging* logger; + Queue* mqttqueue; + Queue* dbqueue; + nowStatus_t* status; +}; + +typedef enum { + STATE_NONE, + STATE_PAIR_SENDED, + STATE_PAIR_ACK_RECEIVED, + STATE_PAIR_DONE, + STATE_DATA_RECEIVED, + STATE_DATA_ACK_SENDED, + STATE_DATA_DONE +} state_t; + + +static bool enqueueMqttMessage(const mqttMessage_t mqtt_message); +static bool read_local_config(); +static bool write_local_config(); +static void OnDataSent(const uint8_t *des_addr, esp_now_send_status_t status); +static void OnDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *incomingData, int len); + +using namespace cpp_freertos; + +class nowThread : public Thread { + +public: + nowThread(now_data_t* now_data); + ~nowThread(); + virtual void Cleanup(); + void Begin(); + + static now_data_t* global_data; + + +protected: + virtual void Run(); + +private: + void add_broadcast_peer(); + now_data_t* data; +}; + +#endif diff --git a/platformio/stima_v3/stimawifi/src/publish_thread.cpp b/platformio/stima_v3/stimawifi/src/publish_thread.cpp index 2cd5cedb9..7a5ac6f43 100644 --- a/platformio/stima_v3/stimawifi/src/publish_thread.cpp +++ b/platformio/stima_v3/stimawifi/src/publish_thread.cpp @@ -611,15 +611,15 @@ void publishThread::store() { mqttMessage_t mqtt_message; if (data->mqttqueue->Dequeue(&mqtt_message, pdMS_TO_TICKS( 0 ))){; // dequeue - if (mqtt_message.sent){ - data->logger->error(F("publish skip and do not store message sended before: %s ; %s"), mqtt_message.topic, mqtt_message.payload); - }else{ + //if (mqtt_message.sent){ + // data->logger->error(F("publish skip and do not store message sended before: %s ; %s"), mqtt_message.topic, mqtt_message.payload); + //}else{ if(data->dbqueue->Enqueue(&mqtt_message,pdMS_TO_TICKS(0))){ data->logger->notice(F("publish skip and enqueue message for db: %s ; %s"), mqtt_message.topic, mqtt_message.payload); }else{ data->logger->error(F("publish lost message for db: %s ; %s"), mqtt_message.topic, mqtt_message.payload); } - } + //} }else{ data->logger->error(F("publish getting message from mqtt queue")); } diff --git a/platformio/stima_v3/stimawifi/src/stimawifi.h b/platformio/stima_v3/stimawifi/src/stimawifi.h index 7ae5e6637..51715181d 100644 --- a/platformio/stima_v3/stimawifi/src/stimawifi.h +++ b/platformio/stima_v3/stimawifi/src/stimawifi.h @@ -57,6 +57,9 @@ File logFile; WriteLoggingStream loggingStream(logFile,Serial); #endif +// if reset is requested by user +bool reset; + //flag for saving data bool shouldSaveConfig = false; bool pmspresent = false; @@ -101,6 +104,7 @@ gps_i2c_data_t gps_i2c_data={1,&frtosLog,&stimawifiStatus.gps,&georef,&frtosRTC, gpsI2cThread threadGpsI2c(&gps_i2c_data); #endif +ESP32Time esprtc; Queue dbQueue(DB_QUEUE_LEN,sizeof(mqttMessage_t)); Queue recoveryQueue(RECOVERY_QUEUE_LEN,sizeof(mqttMessage_t)); Queue mqttQueue(MQTT_QUEUE_LEN,sizeof(mqttMessage_t)); @@ -121,6 +125,12 @@ measureThread threadMeasure(&measure_data); publish_data_t publish_data={1,&frtosLog,&mqttQueue,&dbQueue,&recoveryQueue,&rpcRecoveryQueue,&rpcCalibrateQueue,&stimawifiStatus,&station}; publishThread threadPublish(&publish_data); +now_data_t now_data={1,&frtosLog,&mqttQueue,&dbQueue,&stimawifiStatus.now}; +nowThread threadNow(&now_data); + +now_sat_data_t now_sat_data={1,&frtosLog,&mqttQueue,&dbQueue,&recoveryQueue,&stimawifiStatus.nowsat,&station,&measure_data.state,&db_data.state,&frtosRTC,&esprtc}; +nowSatThread threadNowSat(&now_sat_data); + #if defined(ARDUINO_LOLIN_C3_MINI) Adafruit_NeoPixel pixels = Adafruit_NeoPixel(1, LED_PIN, NEO_GRB + NEO_KHZ800); #else @@ -139,6 +149,15 @@ time_t rtc_set_time(); String Json(); String Data(); String FullPage(); + +// setup routine for master snd satellite stations +void setup(); +void setup_common(); +void setup_satellite_1(); +void setup_satellite_2(); +void setup_master(); +void setup_threads(); + void writeconfig(); // web server response function diff --git a/platformio/stima_v3/stimawifi/src/stimawifi.ino b/platformio/stima_v3/stimawifi/src/stimawifi.ino index 6f86c763f..c306e312b 100644 --- a/platformio/stima_v3/stimawifi/src/stimawifi.ino +++ b/platformio/stima_v3/stimawifi/src/stimawifi.ino @@ -1,5 +1,5 @@ /* -Copyright (C) 2025 Paolo Patruno +Copyright (C) 2026 Paolo Patruno authors: Paolo Patruno @@ -57,6 +57,22 @@ void print_reset_reason() { } } +// Method to print the reason by which ESP32 has been awaken from sleep +void print_wakeup_reason() { + esp_sleep_wakeup_cause_t wakeup_reason; + + wakeup_reason = esp_sleep_get_wakeup_cause(); + + switch (wakeup_reason) { + case ESP_SLEEP_WAKEUP_EXT0: Serial.println(F("Wakeup caused by external signal using RTC_IO")); break; + case ESP_SLEEP_WAKEUP_EXT1: Serial.println(F("Wakeup caused by external signal using RTC_CNTL")); break; + case ESP_SLEEP_WAKEUP_TIMER: Serial.println(F("Wakeup caused by timer")); break; + case ESP_SLEEP_WAKEUP_TOUCHPAD: Serial.println(F("Wakeup caused by touchpad")); break; + case ESP_SLEEP_WAKEUP_ULP: Serial.println(F("Wakeup caused by ULP program")); break; + default: Serial.println(F("Wakeup was not caused by deep sleep")); break; + } +} + void set_status_summary(void) { stimawifiStatus.summary.err_power_on=false; @@ -709,12 +725,15 @@ bool write_local_rmap_config(const String payload) {; int rmap_config(const String payload){ bool status_station = false; + bool status_board = false; bool status_board_mqtt = false; + bool status_board_espnow = false; bool status_board_tcpip = false; bool status_sensors = false; int status = 0; measure_data.sensors_count=0; - + station.constantdata_count=0; + if (! (payload == String())) { DynamicJsonDocument doc(4000); status = 3; @@ -764,6 +783,15 @@ int rmap_config(const String payload){ } } + if (element["model"] == "stations.board"){ + if (element["fields"]["slug"] == station.boardslug){ + if (element["fields"]["active"]){ + frtosLog.notice(F("board found!")); + status_board = true; + } + } + } + if (element["model"] == "stations.transportmqtt"){ if (element["fields"]["board"][0] == station.boardslug){ if (element["fields"]["active"]){ @@ -788,6 +816,24 @@ int rmap_config(const String payload){ } } + if (element["model"] == "stations.transportespnow"){ + if (element["fields"]["board"][0] == station.boardslug){ + if (element["fields"]["active"]){ + frtosLog.notice(F("board transportespnow found!")); + if(element["fields"]["espnowtype"] == 2){ + frtosLog.notice(F("station.espnowtype: satellite")); + station.sampletime=element["fields"]["espnowsampletime"]; + frtosLog.notice(F("station.sampletime: %d"),station.sampletime); + station.nowsat=true; + status_board_espnow = true; + }else if (element["fields"]["espnowtype"] == 1){ + frtosLog.notice(F("station.espnowtype: master")); + station.nowmas=true; + } + } + } + } + if (element["model"] == "stations.transporttcpip"){ if (element["fields"]["board"][0] == station.boardslug){ if (element["fields"]["active"]){ @@ -806,24 +852,26 @@ int rmap_config(const String payload){ if (element["model"] == "stations.sensor"){ if (element["fields"]["active"]){ - if (measure_data.sensors_count < SENSORS_MAX) { - frtosLog.notice(F("station sensor found!")); - strncpy (measure_data.sensors[measure_data.sensors_count].driver , element["fields"]["driver"].as< const char*>(),SENSORDRIVER_DRIVER_LEN); - frtosLog.notice(F("driver: %s"),measure_data.sensors[measure_data.sensors_count].driver); - strncpy (measure_data.sensors[measure_data.sensors_count].type , element["fields"]["type"][0].as< const char*>(),SENSORDRIVER_TYPE_LEN); - frtosLog.notice(F("type: %s"),measure_data.sensors[measure_data.sensors_count].type); - strncpy (measure_data.sensors[measure_data.sensors_count].timerange, element["fields"]["timerange"].as< const char*>(),SENSORDRIVER_META_LEN); - frtosLog.notice(F("timerange: %s"),measure_data.sensors[measure_data.sensors_count].timerange); - strncpy (measure_data.sensors[measure_data.sensors_count].level, element["fields"]["level"].as< const char*>(),SENSORDRIVER_META_LEN); - frtosLog.notice(F("level: %s"),measure_data.sensors[measure_data.sensors_count].level); - measure_data.sensors[measure_data.sensors_count].address = element["fields"]["address"]; - frtosLog.notice(F("address: %d"),measure_data.sensors[measure_data.sensors_count].address); - - if (strcmp(measure_data.sensors[measure_data.sensors_count].type,"PMS")==0) pmspresent=true; - - measure_data.sensors_count++; + if (element["fields"]["board"][0] == station.boardslug){ + if (measure_data.sensors_count < SENSORS_MAX) { + frtosLog.notice(F("station sensor found!")); + strncpy (measure_data.sensors[measure_data.sensors_count].driver , element["fields"]["driver"].as< const char*>(),SENSORDRIVER_DRIVER_LEN); + frtosLog.notice(F("driver: %s"),measure_data.sensors[measure_data.sensors_count].driver); + strncpy (measure_data.sensors[measure_data.sensors_count].type , element["fields"]["type"][0].as< const char*>(),SENSORDRIVER_TYPE_LEN); + frtosLog.notice(F("type: %s"),measure_data.sensors[measure_data.sensors_count].type); + strncpy (measure_data.sensors[measure_data.sensors_count].timerange, element["fields"]["timerange"].as< const char*>(),SENSORDRIVER_META_LEN); + frtosLog.notice(F("timerange: %s"),measure_data.sensors[measure_data.sensors_count].timerange); + strncpy (measure_data.sensors[measure_data.sensors_count].level, element["fields"]["level"].as< const char*>(),SENSORDRIVER_META_LEN); + frtosLog.notice(F("level: %s"),measure_data.sensors[measure_data.sensors_count].level); + measure_data.sensors[measure_data.sensors_count].address = element["fields"]["address"]; + frtosLog.notice(F("address: %d"),measure_data.sensors[measure_data.sensors_count].address); + + if (strcmp(measure_data.sensors[measure_data.sensors_count].type,"PMS")==0) pmspresent=true; + + measure_data.sensors_count++; + } + status_sensors = true; } - status_sensors = true; } } @@ -845,7 +893,21 @@ int rmap_config(const String payload){ } } } - status = (int)!(status_station && status_board_mqtt && status_board_tcpip && status_sensors); //Variable 'status' is reassigned a value before the old one has been used. + + if (status_board_mqtt && status_board_espnow){ + frtosLog.error(F("error in station configuration: MQTT and ESPNOW transport enabled")); + status = 3; + pixels.setPixelColor(0, pixels.Color(255, 0, 0)); + pixels.show(); + delay(5000); + } else { + + status = (int)!(status_station && status_board + && (status_board_mqtt || status_board_espnow) + && (status_board_tcpip|| status_board_espnow) + && status_board_espnow ? status_sensors: true); // master board can have no sensors + //Variable 'status' is reassigned a value before the old one has been used. + } } else { frtosLog.error(F("error parsing array: %s"),error.c_str()); //analogWrite(LED_PIN,973); @@ -879,11 +941,13 @@ void readconfig() { if (doc.containsKey("rmap_user")) strcpy(station.user, doc["rmap_user"]); if (doc.containsKey("rmap_password")) strcpy(station.password, doc["rmap_password"]); if (doc.containsKey("rmap_stationslug")) strcpy(station.stationslug, doc["rmap_stationslug"]); + if (doc.containsKey("rmap_boardslug")) strcpy(station.boardslug, doc["rmap_boardslug"]); frtosLog.notice(F("loaded config parameter:")); frtosLog.notice(F("server: %s"),station.server); frtosLog.notice(F("user: %s"),station.user); frtosLog.notice(F("stationslug: %s"),station.stationslug); + frtosLog.notice(F("boardslug: %s"),station.boardslug); } else { frtosLog.error(F("failed to deserialize json config %s"),error.c_str()); @@ -910,6 +974,7 @@ void writeconfig() {; json["rmap_user"] = station.user; json["rmap_password"] = station.password; json["rmap_stationslug"] = station.stationslug; + json["rmap_boardslug"] = station.boardslug; File configFile = LittleFS.open("/config.json", "w"); if (!configFile) { @@ -947,7 +1012,7 @@ void displayStatus() frtosLog.notice(F("status gps : receive %d" ),stimawifiStatus.gps.receive); frtosLog.notice(F("status udp : receive %d" ),stimawifiStatus.udp.receive); } - + frtosLog.notice(F("status measure : noheap %d, stack %d"),stimawifiStatus.measure.no_heap_memory,stimawifiStatus.measure.memory_collision); frtosLog.notice(F("status publish : noheap %d, stack %d"),stimawifiStatus.publish.no_heap_memory,stimawifiStatus.publish.memory_collision); frtosLog.notice(F("status db : noheap %d, stack %d"),stimawifiStatus.db.no_heap_memory,stimawifiStatus.db.memory_collision); @@ -956,6 +1021,12 @@ void displayStatus() frtosLog.notice(F("status gps : noheap %d, stack %d"),stimawifiStatus.gps.no_heap_memory,stimawifiStatus.gps.memory_collision); frtosLog.notice(F("status udp : noheap %d, stack %d"),stimawifiStatus.udp.no_heap_memory,stimawifiStatus.udp.memory_collision); } + if (station.nowsat){ + frtosLog.notice(F("status nowsat : noheap %d, stack %d"),stimawifiStatus.nowsat.no_heap_memory,stimawifiStatus.nowsat.memory_collision); + } + if (station.nowmas){ + frtosLog.notice(F("status nowmas : noheap %d, stack %d"),stimawifiStatus.now.no_heap_memory,stimawifiStatus.now.memory_collision); + } // collect error in summary // data.status.summary.err_power_on= false; @@ -976,6 +1047,8 @@ void displayStatus() stimawifiStatus.measure.memory_collision == error || stimawifiStatus.measure.no_heap_memory == error || stimawifiStatus.udp.memory_collision == error || stimawifiStatus.udp.no_heap_memory == error || stimawifiStatus.gps.memory_collision == error || stimawifiStatus.gps.no_heap_memory == error || + stimawifiStatus.now.memory_collision == error || stimawifiStatus.now.no_heap_memory == error || + stimawifiStatus.nowsat.memory_collision == error || stimawifiStatus.nowsat.no_heap_memory == error || stimawifiStatus.memory_collision == error || stimawifiStatus.no_heap_memory == error ; stimawifiStatus.summary.err_rssi |= stimawifiStatus.rssi == error; @@ -1132,8 +1205,30 @@ void logSuffix(Print* _logOutput) { _logOutput->flush(); // we use this to flush every log message } + // arduino setup routine void setup() { + setup_common(); + if (station.nowsat){ + setup_satellite_1(); + setup_threads(); + setup_satellite_2(); + } else { + setup_master(); + setup_threads(); + } + + //esp_task_wdt_init(60, true); + //enableLoopWDT(); + //disableLoopWDT(); + //rtc_wdt_protect_off(); + //rtc_wdt_disable(); + //wdt_hal_disable(); +} + + +// arduino setup routine part 1 +void setup_common() { // put your setup code here, to run once in Arduin task: /* @@ -1163,7 +1258,6 @@ void setup() { //Serial.setDebugOutput(true); // set summary status from one CPU only - //set_status_summary(rtc_get_reset_reason(0)); set_status_summary(); stimawifiStatus.rtc=unknown; @@ -1171,9 +1265,10 @@ void setup() { stimawifiStatus.no_heap_memory=ok; stimawifiStatus.memory_collision=ok; - // print esp reset reason + // print esp reset and wakeup reason print_reset_reason(); - + print_wakeup_reason(); + /* Serial.println("CPU0 reset reason:"); print_reset_reason(rtc_get_reset_reason(0)); @@ -1185,9 +1280,14 @@ void setup() { verbose_print_reset_reason(rtc_get_reset_reason(1)); #endif */ + + if (esprtc.getYear() > 2020){ + // sync ESP RTC with time of Timelib + setTime(esprtc.getEpoch()); + } // manage reset button in hardware (RESET_PIN) or in software (I2C) - bool reset=digitalRead(RESET_PIN) == LOW; + reset=digitalRead(RESET_PIN) == LOW; if (button.get() == 0) { if (button.BUTTON_A) @@ -1302,6 +1402,9 @@ void setup() { frtosLog.notice(F("Started")); frtosLog.notice(F("Version: " SOFTWARE_VERSION)); frtosLog.notice(F("Total PSRAM: %d"), ESP.getPsramSize()); + uintptr_t start = (uintptr_t)&_rtc_data_start; + uintptr_t end = (uintptr_t)&_rtc_data_end; + frtosLog.notice(F("RTC used data: %d bytes on 8192 total"), (unsigned)(end - start)); // two different display with different dimension are managed with two different I2C address // check return value of @@ -1414,6 +1517,41 @@ void setup() { delay(3000); } } + + String local_config = read_local_rmap_config(); + rmap_config(local_config); + + Alarm.timerRepeat(3,displayStatus); // display status every 3 seconds + +} + + +void setup_satellite_1() { + + pixels.clear(); // Turn OFF all pixels ASAP + pixels.show(); + + // initialize RTC with mutex + frtosRTC.begin(RTC,i2cmutex); + + if (timeStatus() != timeSet) { + if (frtosRTC.isRunning() && (year(frtosRTC.get()) > 2020)){ + frtosLog.notice(F("Getted time from RTC")); + stimawifiStatus.rtc=ok; + setSyncProvider(rtc_set_time); // the function to get the time from the RTC + }else{ + stimawifiStatus.rtc=error; + } + } +} + +void setup_satellite_2() { + dataRecovery(); + measureAndPublish(); +} + +// arduino setup routine for master station +void setup_master() { String local_config = read_local_rmap_config(); @@ -1424,7 +1562,7 @@ void setup() { frtosLog.notice(F("no station conf; Reset wifi configuration")); wifiManager.resetSettings(); } - + // initialize RTC with mutex frtosRTC.begin(RTC,i2cmutex); @@ -1451,13 +1589,15 @@ void setup() { WiFiManagerParameter custom_rmap_server("server", "rmap server", station.server, 41); WiFiManagerParameter custom_rmap_user("user", "rmap user", station.user, 10); WiFiManagerParameter custom_rmap_password("password", "station password", station.password, 31, "type = \"password\""); - WiFiManagerParameter custom_rmap_stationslug("slug", "station slug", station.stationslug, 31); + WiFiManagerParameter custom_rmap_stationslug("stationslug", "station slug", station.stationslug, 31); + WiFiManagerParameter custom_rmap_boardslug("boardslug", "board slug", station.boardslug, 31); //add all your parameters here wifiManager.addParameter(&custom_rmap_server); wifiManager.addParameter(&custom_rmap_user); wifiManager.addParameter(&custom_rmap_password); wifiManager.addParameter(&custom_rmap_stationslug); + wifiManager.addParameter(&custom_rmap_boardslug); //set config save notify callback wifiManager.setSaveConfigCallback(saveConfigCallback); @@ -1523,25 +1663,25 @@ void setup() { //if you get here you have connected to the WiFi //WiFi.setAutoReconnect(true); wifiManager.setDisableConfigPortal(true); - frtosLog.notice(F("connected... good!")); - frtosLog.notice(F("local ip: %s"),WiFi.localIP().toString().c_str()); - frtosLog.notice(F("WiFi tx power: %d"),WiFi.getTxPower()); - pixels.setPixelColor(0, pixels.Color(0, 255, 0)); - pixels.show(); - delay(3000); - - if (oledpresent) { - LockGuard guard(i2cmutex); - u8g2->clearBuffer(); - u8g2->setCursor(0, 1*CH); - u8g2->print(F("WIFI OK")); - u8g2->sendBuffer(); - u8g2->setCursor(0, 4*CH); - u8g2->print(F("IP:")); - //u8g2->setFont(u8g2_font_u8glib_4_tf); - u8g2->print(WiFi.localIP().toString().c_str()); - //u8g2->setFont(u8g2_font_5x7_tf); - u8g2->sendBuffer(); + frtosLog.notice(F("connected... good!")); + frtosLog.notice(F("local ip: %s"),WiFi.localIP().toString().c_str()); + frtosLog.notice(F("WiFi tx power: %d"),WiFi.getTxPower()); + pixels.setPixelColor(0, pixels.Color(0, 255, 0)); + pixels.show(); + delay(3000); + + if (oledpresent) { + LockGuard guard(i2cmutex); + u8g2->clearBuffer(); + u8g2->setCursor(0, 1*CH); + u8g2->print(F("WIFI OK")); + u8g2->sendBuffer(); + u8g2->setCursor(0, 4*CH); + u8g2->print(F("IP:")); + //u8g2->setFont(u8g2_font_u8glib_4_tf); + u8g2->print(WiFi.localIP().toString().c_str()); + //u8g2->setFont(u8g2_font_5x7_tf); + u8g2->sendBuffer(); } } @@ -1553,6 +1693,7 @@ void setup() { strcpy(station.user, custom_rmap_user.getValue()); strcpy(station.password, custom_rmap_password.getValue()); strcpy(station.stationslug, custom_rmap_stationslug.getValue()); + strcpy(station.boardslug, custom_rmap_boardslug.getValue()); writeconfig(); if (oledpresent) { @@ -1782,37 +1923,45 @@ void setup() { } frtosLog.notice(F("mqtt server: %s"),station.mqtt_server); - - Alarm.timerRepeat(10, dataRecovery); // timer for data recovery from DB - Alarm.timerRepeat(station.sampletime, measureAndPublish); // timer for measure every SAMPLETIME seconds - Alarm.timerRepeat(3,displayStatus); // display status every 3 seconds // Add http service to MDNS-SD - MDNS.addService("http", "tcp", STIMAHTTP_PORT); - - // if mobile station start geolocation thread or if we need to acquire time - if (strcmp(station.ident,"") != 0 || (timeStatus() != timeSet)){ - threadUdp.Start(); - #ifdef GPS_SERIAL - threadGps.Start(); - #endif - #ifdef GPS_I2C - threadGpsI2c.Start(); - #endif - } + MDNS.addService("http", "tcp", STIMAHTTP_PORT); + + Alarm.timerRepeat(10, dataRecovery); // timer for data recovery from DB + Alarm.timerRepeat(station.sampletime, measureAndPublish); // timer for measure every SAMPLETIME seconds + +} +// arduino setup routine part 2 +void setup_threads() { + // start other thread - threadDb.Start(); - threadPublish.Start(); threadMeasure.Begin(); threadMeasure.Start(); - - //esp_task_wdt_init(60, true); - //enableLoopWDT(); - //disableLoopWDT(); - //rtc_wdt_protect_off(); - //rtc_wdt_disable(); - //wdt_hal_disable(); + threadDb.Start(); + if (station.nowsat){ + threadNowSat.Begin(); + threadNowSat.Start(); + }else{ + threadPublish.Start(); + if (station.nowmas){ + threadNow.Begin(); + threadNow.Start(); + } + + // start geolocation thread if mobile station or if we need to acquire time + // (but not for satellite station) + if ((strcmp(station.ident,"") != 0 || (timeStatus() != timeSet)) + && !station.nowsat){ + threadUdp.Start(); + #ifdef GPS_SERIAL + threadGps.Start(); + #endif + #ifdef GPS_I2C + threadGpsI2c.Start(); + #endif + } + } } // arduino loop routine @@ -1824,30 +1973,33 @@ void loop() { loopinit=false; } - // set alarm for fixed station and when time is setted - if (!periodic_work_setted and strcmp(station.ident,"") == 0 and timeStatus() == timeSet){ - time_t reboottime; - periodic_work_setted=true; - - if (pmspresent){ - reboottime=3600*24; // pms stall sometime, we reboot more - }else{ - reboottime=3600*24*7; // we reset everythings one time a week + if (!station.nowsat){ + + // set alarm for fixed station and when time is setted + if (!periodic_work_setted and strcmp(station.ident,"") == 0 and timeStatus() == timeSet){ + time_t reboottime; + periodic_work_setted=true; + + if (pmspresent){ + reboottime=3600*24; // pms stall sometime, we reboot more + }else{ + reboottime=3600*24*7; // we reset everythings one time a week + } + frtosLog.notice(F("reboot every: %l"),reboottime); + Alarm.timerRepeat(reboottime,protectedReboot); // timer for reboot + + // update firmware + //Alarm.alarmRepeat(4,0,0,protectedFirmwareUpdate); // 4:00:00 every day + Alarm.timerRepeat(3600*24,protectedFirmwareUpdate); // check for firmware update every day } - frtosLog.notice(F("reboot every: %l"),reboottime); - Alarm.timerRepeat(reboottime,protectedReboot); // timer for reboot - // update firmware - //Alarm.alarmRepeat(4,0,0,protectedFirmwareUpdate); // 4:00:00 every day - Alarm.timerRepeat(3600*24,protectedFirmwareUpdate); // check for firmware update every day + webserver.handleClient(); + //MDNS.update(); } - webserver.handleClient(); - //MDNS.update(); Alarm.delay(0); // check for alarms delay(100); - // check heap and stack //data->logger->notice(F("HEAP: %l"),esp_get_minimum_free_heap_size()); if( esp_get_minimum_free_heap_size() < HEAP_MIN_WARNING){ diff --git a/platformio/stima_v3/stimawifi/src/typedef.h b/platformio/stima_v3/stimawifi/src/typedef.h index 2c0163275..10530d123 100644 --- a/platformio/stima_v3/stimawifi/src/typedef.h +++ b/platformio/stima_v3/stimawifi/src/typedef.h @@ -76,7 +76,7 @@ struct station_t char server[41]; //!< server RMAP char ntp_server[41]; //!< server NTP char mqtt_server[41]; //!< broker MQTT - int sampletime; //!< intervallo tra le misurazioni in secondi + int sampletime; //!< intervallo tra le misurazioni in secondi char user[10]; //!< utente char password[31]; //!< password char stationslug[31]; //!< nome sintetico della stazione @@ -86,6 +86,8 @@ struct station_t char mqttrpcpath[10]; //!< radice del topic MQTT per le RPC constantdata_t constantdata[MAX_CONSTANTDATA_COUNT]; //!< Constantdata buffer for storing constant station data parameter (metadati) uint8_t constantdata_count; //!< configured constantdata number + bool nowsat; //!< enable espnow satellite transport + bool nowmas; //!< enable espnow master transport //define your default values here, if there are different values in config.json, they are overwritten. station_t() { @@ -104,6 +106,8 @@ struct station_t strcpy(mqttmaintpath,"maint"); strcpy(mqttrpcpath,"rpc"); constantdata_count=0; + nowsat=false; + nowmas=false; } }; @@ -207,6 +211,26 @@ struct dbStatus_t status_e no_heap_memory; //!< no memory for allocation in heap }; +/*! +\def struct nowStatus_t +\brief Stati relativi al thread di ricezione dei dati via radio (esp now). +*/ +struct nowStatus_t +{ + status_e memory_collision; //!< check collisione stack e heap + status_e no_heap_memory; //!< no memory for allocation in heap +}; + +/*! +\def struct nowsatStatus_t +\brief Stati relativi al thread di invio dei dati via radio (esp now). +*/ +struct nowsatStatus_t +{ + status_e memory_collision; //!< check collisione stack e heap + status_e no_heap_memory; //!< no memory for allocation in heap +}; + /*! \def struct summaryStatus_t \brief Sommario degli errori di tutta la stazione. @@ -240,6 +264,8 @@ struct stimawifiStatus_t udpStatus_t udp; //!< Stati relativi al thread di ricezione UDP dei dati di georeferenziazione gpsStatus_t gps; //!< Stati relativi al thread di ricezione GPS (porta seriale) dei dati di georeferenziazione dbStatus_t db; //!< Stati relativi al thread di gestione del DataBase + nowStatus_t now; //!< Stati relativi al thread di ricezione dati via radio (esp now) + nowsatStatus_t nowsat; //!< Stati relativi al thread di invio dati via radio (esp now) status_e rtc; //!< Stato dell'RTC status_e rssi; //!< Stato rssi dell segnale radio WiFi status_e memory_collision; //!< check collisione stack e heap loop task @@ -269,4 +295,37 @@ struct rpcCalibrate_t uint16_t value; }; + +//Structure to send esp-now pair messages +struct message_pair { + uint16_t type; + uint16_t seq; + time_t datetime; +}; + +struct message_pair_crc { + message_pair message; + uint8_t crc; +}; + +//Structure to send esp-now data messages +// 5 + MQTT_ROOT_TOPIC_LENGTH + MQTT_SENSOR_TOPIC_LENGTH + MQTT_MESSAGE_LENGTH] < 250 (max size of esp-now packet) + +struct message_data { + uint16_t type; + uint16_t seq; + mqttMessage_t mqttmessage; +}; + +struct message_data_crc { + message_data message; + uint8_t crc; +}; + +struct now_config_t { + bool paired = false; + uint8_t peerMac[6]; + uint8_t channel = 3; +}; + #endif diff --git a/platformio/stima_v3/test_sim800/src/basic.ino b/platformio/stima_v3/test_sim800/src/basic.ino index b136bf1d5..eb3bd02f2 100644 --- a/platformio/stima_v3/test_sim800/src/basic.ino +++ b/platformio/stima_v3/test_sim800/src/basic.ino @@ -35,7 +35,7 @@ uint8_t ready_tasks_count; gsm_state_t gsm_state; #ifndef ARDUINO_ARCH_AVR -HardwareSerial Serial1(PB11, PB10); +Uart Serial1(PB11, PB10); #endif diff --git a/platformio/stima_v3_devel/stima/platformio.ini b/platformio/stima_v3_devel/stima/platformio.ini index 417333462..0befe4392 100644 --- a/platformio/stima_v3_devel/stima/platformio.ini +++ b/platformio/stima_v3_devel/stima/platformio.ini @@ -82,7 +82,7 @@ lib_ignore = RmapConfig lib_deps = STM32duino RTC@<2 - STM32duino Low Power + STM32duino Low Power@1.5.0 ; required by freertos ; https://community.platformio.org/t/stm32freertos-vtaskdelay-not-returning/12297/4 @@ -90,7 +90,7 @@ lib_archive = no [env:microduino_stm32l433] -platform = ststm32 +platform = ststm32@19.7.1 board = nucleo_l433rc_p board_build.variants_dir = ../../custom_variants board_build.variant = L433CC @@ -113,7 +113,7 @@ debug_build_flags = -ggdb2 lib_deps = STM32duino RTC@<2 - STM32duino Low Power + STM32duino Low Power@1.5.0 upload_protocol = stlink debug_tool = stlink ;lib_deps=STM32Duino FreeRTOS @@ -156,7 +156,7 @@ lib_ignore = RmapConfig lib_deps = STM32duino RTC@<2 - STM32duino Low Power + STM32duino Low Power@1.5.0 ; ;FreeRTOS ; required by freertos ; https://community.platformio.org/t/stm32freertos-vtaskdelay-not-returning/12297/4 @@ -203,4 +203,4 @@ lib_ignore = RmapConfig lib_deps = STM32duino RTC@<2 - STM32duino Low Power + STM32duino Low Power@1.5.0 diff --git a/platformio/test/esp32_now_satellite/src/esp-now.ino b/platformio/test/esp32_now_satellite/src/esp-now.ino index 93332eace..01a7ed253 100644 --- a/platformio/test/esp32_now_satellite/src/esp-now.ino +++ b/platformio/test/esp32_now_satellite/src/esp-now.ino @@ -182,6 +182,10 @@ RTC_DATA_ATTR volatile uint8_t error_count; RTC_DATA_ATTR volatile unsigned int last_state_update; RTC_DATA_ATTR volatile state_t state; RTC_DATA_ATTR volatile uint16_t seq; +// Flag per verificare se l'invio è completato +RTC_DATA_ATTR volatile bool transmissionCompleted ; +// Flag per sapere se il canale è cambiato rispetto alla conf salvata +RTC_DATA_ATTR volatile bool channelchanged; /* // Definisci la PMK globale (esattamente 16 byte) @@ -202,9 +206,6 @@ const uint8_t mio_lmk[16] = { const uint8_t broadcastAddress[] = {0xFF,0xFF,0xFF,0xFF,0xFF,0xFF}; MutexStandard loggingmutex; -// Flag per verificare se l'invio è completato -volatile bool transmissionCompleted = false; - // prefix for logging system void logPrefix(Print* _logOutput) { #define DATE_TIME_STRING_LENGTH (25) @@ -517,8 +518,15 @@ void OnDataRecv(const esp_now_recv_info_t *esp_now_info, const uint8_t *incoming // Sblocca il ciclo principale consentendo il deep sleep transmissionCompleted = true; + if (channelchanged){ + if (!write_local_config())frtosLog.error("Error writing config file"); + } + channelchanged=false; setTime(incomingMessage.message.datetime); state = STATE_DATA_DONE; + } else { + frtosLog.error("message type unknown"); + return; } } @@ -545,7 +553,9 @@ void setup() { last_state_update = 0; state = STATE_NONE; seq=0; - + transmissionCompleted = false ; + channelchanged = false; + delay(5000); frtosLog.notice(F("Started")); frtosLog.notice(F("Version: " SOFTWARE_VERSION)); @@ -732,16 +742,18 @@ void loop() { frtosLog.notice(F("accoppiato %T error count %d"),config.accoppiato, error_count); config.channel++; + channelchanged=true; if (config.channel >13) config.channel=1; frtosLog.notice("channel: %d",config.channel); esp_wifi_set_channel(config.channel, WIFI_SECOND_CHAN_NONE); - delay(3000); + if (!config.accoppiato) delay(3000); } if (!config.accoppiato) return; if (state != STATE_NONE and state != STATE_DATA_DONE){ frtosLog.notice(F("STATE not ready: %d, %d"),state, STATE_NONE); + state != STATE_NONE; return; } @@ -792,14 +804,14 @@ void loop() { unsigned long startTimeout = millis(); while (!transmissionCompleted) { delay(10); - // Timeout di sicurezza (es. 500ms) per evitare che l'ESP resti acceso all'infinito se il destinatario è spento - if (millis() - startTimeout > 500) { - frtosLog.notice("Send timeout exceded!"); + // Timeout di sicurezza (es. 1000ms) per evitare che l'ESP resti acceso all'infinito se il destinatario è spento + if (millis() - startTimeout > 1000) { + frtosLog.notice("Transaction timeout exceded!"); break; } } - esp_deep_sleep_start(); - //delay(TIME_TO_SLEEP*1000); // as alternative to sleep - frtosLog.notice("This will never be printed"); + // esp_deep_sleep_start(); + delay(TIME_TO_SLEEP*1000); // as alternative to sleep + frtosLog.notice(F("This will never be printed in DEEP sleep mode")); } diff --git a/platformio/test/faketh/src/faketh.ino b/platformio/test/faketh/src/faketh.ino index a2086fbb3..2b85717ee 100644 --- a/platformio/test/faketh/src/faketh.ino +++ b/platformio/test/faketh/src/faketh.ino @@ -327,7 +327,7 @@ void setup() { IF_SDEBUG(Serial.println(i2c_writabledataset1->i2c_address)); //Start I2C communication routines - Wire.begin(i2c_writabledataset1->i2c_address); + Wire.begin((int)i2c_writabledataset1->i2c_address); //The Wire library enables the internal pullup resistors for SDA and SCL. //You can turn them off after Wire.begin() diff --git a/platformio/test/fakewind/src/fakewind.ino b/platformio/test/fakewind/src/fakewind.ino index 76c224732..068842cd9 100644 --- a/platformio/test/fakewind/src/fakewind.ino +++ b/platformio/test/fakewind/src/fakewind.ino @@ -325,7 +325,7 @@ void setup() { IF_SDEBUG(Serial.println(i2c_writabledataset1->i2c_address)); //Start I2C communication routines - Wire.begin(i2c_writabledataset1->i2c_address); + Wire.begin((int)i2c_writabledataset1->i2c_address); //The Wire library enables the internal pullup resistors for SDA and SCL. //You can turn them off after Wire.begin() diff --git a/platformio/test/fakewindsonic/src/fakewindsonic.ino b/platformio/test/fakewindsonic/src/fakewindsonic.ino index 1f04c296c..1952dd328 100644 --- a/platformio/test/fakewindsonic/src/fakewindsonic.ino +++ b/platformio/test/fakewindsonic/src/fakewindsonic.ino @@ -131,11 +131,11 @@ V - NMEA data Void HardwareTimer Tim2 = HardwareTimer(TIM2); #if defined (STM32L452xx) - HardwareSerial MYSERIAL0(PC11, PC10); + Uart MYSERIAL0(PC11, PC10); #elif defined (STM32L476xx) - HardwareSerial MYSERIAL0(PC11, PC10); + Uart MYSERIAL0(PC11, PC10); #else - HardwareSerial MYSERIAL0(D0, D1); + Uart MYSERIAL0(D0, D1); #endif #endif diff --git a/python/borinud/v1/utils.py b/python/borinud/v1/utils.py index f7f2adb98..bba808ab5 100644 --- a/python/borinud/v1/utils.py +++ b/python/borinud/v1/utils.py @@ -33,12 +33,14 @@ def params2record(p): #leveltype2=MISSING_INT-1 o leveltype2="-", per significare "il #valore nel database deve essere "mancante" invece di "qualunque" - # solo per sistemi a 64 bit - REQUIRED_MISSING_INT = 2147483646 - - for tl2 in ("leveltype2","l2"): - if (tl2 in q): - if (q[tl2] is None): - q[tl2]= REQUIRED_MISSING_INT + ## ATTENTION uncomment when issue will be resolved + # + ## solo per sistemi a 64 bit + #REQUIRED_MISSING_INT = 2147483646 + # + #for tl2 in ("leveltype2","l2"): + # if (tl2 in q): + # if (q[tl2] is None): + # q[tl2]= REQUIRED_MISSING_INT return q diff --git a/python/insertdata/templates/insertdata/boardmodifyform.html b/python/insertdata/templates/insertdata/boardmodifyform.html index d0d0b8aa6..8264977f0 100644 --- a/python/insertdata/templates/insertdata/boardmodifyform.html +++ b/python/insertdata/templates/insertdata/boardmodifyform.html @@ -32,6 +32,7 @@

{% trans 'Cancel' %}

  • Transport MQTT
  • Transport TCP/IP
  • Transport CAN
  • +
  • Transport ESP-NOW
  • Transport AMQP
  • @@ -89,7 +90,16 @@

    {% trans 'Modify transport CAN' %}

    - + +
    +
    +

    {% trans 'Modify transport ESP-NOW' %}

    + + {{ transportespnowformset.as_table }} +
    +
    +
    +

    {% trans 'Modify transport AMQP' %}

    diff --git a/python/insertdata/views.py b/python/insertdata/views.py index 8d92c500a..8b1b4179b 100644 --- a/python/insertdata/views.py +++ b/python/insertdata/views.py @@ -31,7 +31,7 @@ from django.db.utils import IntegrityError from django.core import serializers from django.forms import BoundField, Field -from rmap.stations.models import TransportMqtt,TransportTcpip,TransportCan,TransportAmqp,Sensor +from rmap.stations.models import TransportEspnow,TransportMqtt,TransportTcpip,TransportCan,TransportAmqp,Sensor import traceback @@ -748,6 +748,10 @@ def insertNewStation(request): transports.append(board.transportmqtt) except ObjectDoesNotExist: pass + try: + transports.append(board.transportespnow) + except ObjectDoesNotExist: + pass try: transports.append(board.transportbluetooth) except ObjectDoesNotExist: @@ -992,18 +996,20 @@ def boardModify(request,slug,bslug): TransportTcpipFormSet = inlineformset_factory(Board,TransportTcpip, fields=["active","name","ntpserver","gsmapn","pppnumber"]) TransportCanFormSet = inlineformset_factory(Board,TransportCan, fields=["active","cansampletime","node_id","subject","subject_id"]) TransportAmqpFormSet = inlineformset_factory(Board,TransportAmqp, fields=["active","amqpserver","exchange","queue","amqpuser","amqppassword"]) + TransportEspnowFormSet = inlineformset_factory(Board,TransportEspnow, fields=["active","espnowtype","espnowsampletime"]) try: if request.method == 'POST': # If the form has been submitted... mystation=StationMetadata.objects.get(slug__exact=slug,user__username=request.user.username) myboard=Board.objects.get(slug__exact=bslug,stationmetadata=mystation) - boardform = BoardForm (request.POST, instance=myboard) - sensorformset = SensorFormSet (request.POST, instance=myboard) - transportmqttformset = TransportMqttFormSet (request.POST, instance=myboard) - transporttcpipformset = TransportTcpipFormSet(request.POST, instance=myboard) - transportcanformset = TransportCanFormSet (request.POST, instance=myboard) - transportamqpformset = TransportAmqpFormSet (request.POST, instance=myboard) + boardform = BoardForm (request.POST, instance=myboard) + sensorformset = SensorFormSet (request.POST, instance=myboard) + transportmqttformset = TransportMqttFormSet (request.POST, instance=myboard) + transporttcpipformset = TransportTcpipFormSet (request.POST, instance=myboard) + transportcanformset = TransportCanFormSet (request.POST, instance=myboard) + transportamqpformset = TransportAmqpFormSet (request.POST, instance=myboard) + transportespnowformset = TransportEspnowFormSet(request.POST, instance=myboard) valid=False if (boardform.is_valid() @@ -1011,7 +1017,8 @@ def boardModify(request,slug,bslug): and transportmqttformset.is_valid() and transporttcpipformset.is_valid() and transportcanformset.is_valid() - and transportamqpformset.is_valid()): + and transportamqpformset.is_valid() + and transportespnowformset.is_valid()): boardform.save() sensorformset.save() @@ -1019,18 +1026,20 @@ def boardModify(request,slug,bslug): transporttcpipformset.save() transportcanformset.save() transportamqpformset.save() + transportespnowformset.save() valid=True mystation=StationMetadata.objects.get(slug__exact=slug,user__username=request.user.username) myboard=Board.objects.get(slug__exact=bslug,stationmetadata=mystation) boardform = BoardForm(instance=myboard) - boardformset = BoardForm(instance=mystation) - sensorformset = SensorFormSet (instance=myboard) - transportmqttformset = TransportMqttFormSet (instance=myboard) - transporttcpipformset = TransportTcpipFormSet(instance=myboard) - transportcanformset = TransportCanFormSet (instance=myboard) - transportamqpformset = TransportAmqpFormSet (instance=myboard) + boardformset = BoardForm (instance=mystation) + sensorformset = SensorFormSet (instance=myboard) + transportmqttformset = TransportMqttFormSet (instance=myboard) + transporttcpipformset = TransportTcpipFormSet (instance=myboard) + transportcanformset = TransportCanFormSet (instance=myboard) + transportamqpformset = TransportAmqpFormSet (instance=myboard) + transportespnowformset = TransportEspnowFormSet (instance=myboard) if (valid): return render(request, 'insertdata/boardmodifyform.html',{'boardform':boardform, @@ -1039,6 +1048,7 @@ def boardModify(request,slug,bslug): "transporttcpipformset":transporttcpipformset, "transportcanformset":transportcanformset, "transportamqpformset":transportamqpformset, + "transportespnowformset":transportespnowformset, "station":mystation, "board":myboard, "saved":True}) @@ -1050,6 +1060,7 @@ def boardModify(request,slug,bslug): "transporttcpipformset":transporttcpipformset, "transportcanformset":transportcanformset, "transportamqpformset":transportamqpformset, + "transportespnowformset":transportespnowformset, "station":mystation, "board":myboard, "invalid":True}) @@ -1059,18 +1070,20 @@ def boardModify(request,slug,bslug): myboard=Board.objects.get(slug__exact=bslug,stationmetadata=mystation) boardform = BoardForm(instance=myboard) - boardformset = BoardForm(instance=mystation) - sensorformset = SensorFormSet (instance=myboard) - transportmqttformset = TransportMqttFormSet (instance=myboard) - transporttcpipformset = TransportTcpipFormSet(instance=myboard) - transportcanformset = TransportCanFormSet (instance=myboard) - transportamqpformset = TransportAmqpFormSet (instance=myboard) + boardformset = BoardForm (instance=mystation) + sensorformset = SensorFormSet (instance=myboard) + transportmqttformset = TransportMqttFormSet (instance=myboard) + transporttcpipformset = TransportTcpipFormSet (instance=myboard) + transportcanformset = TransportCanFormSet (instance=myboard) + transportamqpformset = TransportAmqpFormSet (instance=myboard) + transportespnowformset = TransportEspnowFormSet(instance=myboard) return render(request, 'insertdata/boardmodifyform.html',{'boardform':boardform, "sensorformset":sensorformset, "transportmqttformset":transportmqttformset, "transporttcpipformset":transporttcpipformset, "transportcanformset":transportcanformset, "transportamqpformset":transportamqpformset, + "transportespnowformset":transportespnowformset, "station":mystation, "board":myboard, }) @@ -1082,16 +1095,18 @@ def boardModify(request,slug,bslug): mystation=StationMetadata.objects.get(slug__exact=slug,user__username=request.user.username) myboard=Board.objects.get(slug__exact=bslug,stationmetadata=mystation) - boardform = BoardForm (instance=myboard) - sensorformset = SensorFormSet (instance=myboard) - transportmqttformset = TransportMqttFormSet (instance=myboard) - transporttcpipformset = TransportTcpipFormSet(instance=myboard) - transportcanformset = TransportCanFormSet (instance=myboard) - transportamqpformset = TransportAmqpFormSet (instance=myboard) + boardform = BoardForm (instance=myboard) + sensorformset = SensorFormSet (instance=myboard) + transportmqttformset = TransportMqttFormSet (instance=myboard) + transporttcpipformset = TransportTcpipFormSet (instance=myboard) + transportcanformset = TransportCanFormSet (instance=myboard) + transportamqpformset = TransportAmqpFormSet (instance=myboard) + transportespnowformset = TransportEspnowFormSet(instance=myboard) return render(request, 'insertdata/boardmodifyform.html',{'boardform':boardform, "sensorformset":sensorformset, "transportmqttformset":transportmqttformset, "transporttcpipformset":transporttcpipformset, "transportcanformset":transportcanformset, "transportamqpformset":transportamqpformset,"station":mystation, + "transportespnowformset":transportespnowformset,"station":mystation, "board":myboard,"error":True}) diff --git a/python/rmap/__init__.py b/python/rmap/__init__.py index 6469fe3ae..9227d0ead 100644 --- a/python/rmap/__init__.py +++ b/python/rmap/__init__.py @@ -1 +1 @@ -__version__ = "17.11" +__version__ = "18.0" diff --git a/python/rmap/rmap_core.py b/python/rmap/rmap_core.py index eebb1fa16..2bd0456c1 100644 --- a/python/rmap/rmap_core.py +++ b/python/rmap/rmap_core.py @@ -1689,6 +1689,11 @@ def add_board(objects,board): if (transport.active): objects.append(transport) except ObjectDoesNotExist: pass + try: + transport=board.transportespnow + if (transport.active): objects.append(transport) + except ObjectDoesNotExist: + pass try: transport=board.transportamqp if without_password: diff --git a/python/rmap/stations/admin.py b/python/rmap/stations/admin.py index 00d6dc082..f2eea25f1 100644 --- a/python/rmap/stations/admin.py +++ b/python/rmap/stations/admin.py @@ -1,6 +1,6 @@ from django.contrib import admin from .models import (StationMetadata,Board,Sensor,SensorType,Bcode, -TransportSerial,TransportTcpip,TransportRF24Network,TransportMqtt,TransportCan,TransportAmqp,TransportBluetooth, + TransportSerial,TransportTcpip,TransportRF24Network,TransportMqtt,TransportCan,TransportAmqp,TransportBluetooth,TransportEspnow, StationConstantData,BoardFirmwareMetadata,StationMaintStatus,StationImage,BoardMaintStatus) from django import forms from django.utils.translation import ugettext_lazy @@ -16,6 +16,9 @@ class TransportBluetoothInline(admin.TabularInline): class TransportTcpipInline(admin.TabularInline): model = TransportTcpip +class TransportEspnowInline(admin.TabularInline): + model = TransportEspnow + class TransportRF24NetworkInline(admin.TabularInline): model = TransportRF24Network @@ -102,6 +105,7 @@ class BoardAdmin(admin.ModelAdmin): TransportSerialInline, TransportBluetoothInline, TransportTcpipInline, + TransportEspnowInline, TransportRF24NetworkInline, TransportAmqpInline, TransportMqttInline, diff --git a/python/rmap/stations/fixtures/template_station_airquality_satellite.json b/python/rmap/stations/fixtures/template_station_airquality_satellite.json new file mode 100644 index 000000000..412efc869 --- /dev/null +++ b/python/rmap/stations/fixtures/template_station_airquality_satellite.json @@ -0,0 +1,223 @@ +[ +{ + "model": "stations.stationmetadata", + "fields": { + "name": "airquality now", + "active": true, + "slug": "airquality-now", + "user": [ + "rmap" + ], + "ident": "", + "lat": 19e-05, + "lon": 19e-05, + "network": "fixed", + "mqttrootpath": "sample", + "mqttmaintpath": "maint", + "category": "template", + "type": "stimav3" + } +}, +{ + "model": "stations.board", + "fields": { + "name": "satellite", + "active": true, + "slug": "satellite", + "category": "stimawifiv3espnow", + "type": 102, + "sn": null, + "stationmetadata": [ + "airquality-now", + [ + "rmap" + ] + ] + } +}, +{ + "model": "stations.board", + "fields": { + "name": "master", + "active": true, + "slug": "master", + "category": "stimawifiv3", + "type": 101, + "sn": null, + "stationmetadata": [ + "airquality-now", + [ + "rmap" + ] + ] + } +}, +{ + "model": "stations.sensor", + "fields": { + "active": true, + "name": "Dust", + "driver": "I2C", + "type": [ + "SPS" + ], + "i2cbus": null, + "address": 105, + "node": null, + "timerange": "254,0,0", + "level": "103,2000,-,-", + "board": [ + "master", + [ + "airquality-now", + [ + "rmap" + ] + ] + ] + } +}, +{ + "model": "stations.sensor", + "fields": { + "active": true, + "name": "Temperature_Humidity", + "driver": "I2C", + "type": [ + "SHT" + ], + "i2cbus": null, + "address": 68, + "node": null, + "timerange": "254,0,0", + "level": "103,2000,-,-", + "board": [ + "satellite", + [ + "airquality-now", + [ + "rmap" + ] + ] + ] + } +}, +{ + "model": "stations.sensor", + "fields": { + "active": true, + "name": "CO2", + "driver": "I2C", + "type": [ + "SCD" + ], + "i2cbus": null, + "address": 97, + "node": null, + "timerange": "254,0,0", + "level": "103,2000,-,-", + "board": [ + "master", + [ + "airquality-now", + [ + "rmap" + ] + ] + ] + } +}, +{ + "model": "stations.transportespnow", + "fields": { + "active": true, + "espnowtype": 2, + "espnowsampletime": 30, + "board": [ + "satellite", + [ + "airquality-now", + [ + "rmap" + ] + ] + ] + } +}, +{ + "model": "stations.transportmqtt", + "fields": { + "active": true, + "mqttsampletime": 30, + "mqttserver": "rmap.cc", + "mqttuser": "rmap", + "mqttpassword": "none", + "mqttpskkey": null, + "board": [ + "master", + [ + "airquality-now", + [ + "rmap" + ] + ] + ] + } +}, +{ + "model": "stations.transportespnow", + "fields": { + "active": true, + "espnowtype": 1, + "espnowsampletime": 0, + "board": [ + "master", + [ + "airquality-now", + [ + "rmap" + ] + ] + ] + } +}, +{ + "model": "stations.transporttcpip", + "fields": { + "active": true, + "name": "stima", + "ntpserver": "it.pool.ntp.org", + "gsmapn": "ibox.tim.it", + "pppnumber": "*99#", + "board": [ + "master", + [ + "airquality-now", + [ + "rmap" + ] + ] + ] + } +}, +{ + "model": "stations.stationmetadata", + "fields": { + "name": "Observer", + "active": false, + "slug": "observer", + "user": [ + "rmap" + ], + "ident": "", + "lat": 10e-05, + "lon": 10e-05, + "network": "fixed", + "mqttrootpath": "sample", + "mqttmaintpath": "maint", + "type": "manual", + "category": "template" + } +} +] + diff --git a/python/rmap/stations/migrations/0037_stationmetadata_type.py b/python/rmap/stations/migrations/0037_stationmetadata_type.py index 0ffb92662..07253167f 100644 --- a/python/rmap/stations/migrations/0037_stationmetadata_type.py +++ b/python/rmap/stations/migrations/0037_stationmetadata_type.py @@ -24,13 +24,6 @@ def load_fixture_from_file(fixture_file): print("load fixture from file: ",fixture_file) load_fixture_from_file(fixture_file) - for fixture_filename in os.listdir(fixture_dir): - if fixture_filename[:3] == "sta" and fixture_filename[-5:] == ".json": - - fixture_file = os.path.join(fixture_dir, fixture_filename) - print("load fixture from file: ",fixture_file) - load_fixture_from_file(fixture_file) - fixture_filename = "sensor_type_02.json" fixture_file = os.path.join(fixture_dir, fixture_filename) print("load fixture from file: ",fixture_file) @@ -68,21 +61,25 @@ def load_fixture_from_file(fixture_file): fixture_filename = "sensor_type_09.json" - fixture_file = os.path.join(fixture_dir, fixture_filename) print("load fixture from file: ",fixture_file) - fixture = open(fixture_file, 'rb') objects = serializers.deserialize('json', fixture, ignorenonexistent=True) for obj in objects: obj.save() fixture.close() - fixture_filename = "template_stations.json" + for fixture_filename in os.listdir(fixture_dir): + if fixture_filename[:3] == "sta" and fixture_filename[-5:] == ".json": + + fixture_file = os.path.join(fixture_dir, fixture_filename) + print("load fixture from file: ",fixture_file) + load_fixture_from_file(fixture_file) + + fixture_filename = "template_stations.json" fixture_file = os.path.join(fixture_dir, fixture_filename) print("load fixture from file: ",fixture_file) - fixture = open(fixture_file, 'rb') objects = serializers.deserialize('json', fixture, ignorenonexistent=True) for obj in objects: @@ -90,10 +87,6 @@ def load_fixture_from_file(fixture_file): fixture.close() - - - - def unload_fixture(apps, schema_editor): "Brutally deleting all entries for this model..." diff --git a/python/rmap/stations/migrations/0044_auto_20260917_1836.py b/python/rmap/stations/migrations/0044_auto_20260917_1836.py new file mode 100644 index 000000000..03d6c6d2e --- /dev/null +++ b/python/rmap/stations/migrations/0044_auto_20260917_1836.py @@ -0,0 +1,38 @@ +# Generated by Django 2.2.17 on 2026-09-17 18:36 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('stations', '0043_airquality_mobile_template'), + ] + + operations = [ + migrations.AlterField( + model_name='board', + name='category', + field=models.CharField(blank=True, choices=[('base', 'Raspberry base'), ('master', 'Stima V3 master eth transport'), ('slave', 'Stima V3 slave'), ('satellite', 'Stima V3 slave radio transport'), ('gsm', 'Stima V3 master GSM/GPRS transport'), ('bluetooth', 'Stima V3 slave Bluetooth transport'), ('masterv4', 'Stima V4 master eth transport'), ('gsmv4', 'Stima V4 master LTS transport'), ('slavev4', 'Stima V4 slave CAN transport'), ('stimawifiv3', 'Stima V3 Stima WiFI'), ('stimawifiv3espnow', 'Stima V3 Stima WiFI ESP NOW')], help_text='General standard category', max_length=50, null=True), + ), + migrations.AlterField( + model_name='board', + name='type', + field=models.PositiveIntegerField(choices=[(0, 'Module type stima not defined'), (10, 'Module send report over ethernet'), (11, 'Module send sample over gsm/gprs'), (20, 'Module acquire rain tips'), (21, 'Module acquire temperature and humidity'), (22, 'Module acquire temperature, humidity and rain'), (23, 'Module acquire air particle'), (24, 'Module acquire leaf wetness'), (25, 'Module acquire wind sensor'), (26, 'Module acquire radiation sensor'), (27, 'Module acquire gas (NO2, CO2)'), (28, 'Module acquire power regulator mppt'), (29, 'Module acquire soil humidity'), (30, 'Module acquire level river'), (101, 'Module StimaV3 WiFi'), (102, 'Module StimaV3 WiFi ESP NOW')], default=0, help_text='Stima V4 standard type'), + ), + migrations.CreateModel( + name='TransportEspnow', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('active', models.BooleanField(default=False, help_text='Activate this transport for measurements', verbose_name='Active')), + ('espnowsampletime', models.PositiveIntegerField(default=300, help_text='interval in seconds for publish')), + ('board', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='stations.Board')), + ], + options={ + 'verbose_name': 'ESP NOW node', + 'verbose_name_plural': 'ESP NOW nodes', + 'ordering': ['board'], + }, + ), + ] diff --git a/python/rmap/stations/migrations/0045_transportespnow_espnowtype.py b/python/rmap/stations/migrations/0045_transportespnow_espnowtype.py new file mode 100644 index 000000000..4b0397f98 --- /dev/null +++ b/python/rmap/stations/migrations/0045_transportespnow_espnowtype.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2.17 on 2026-09-30 10:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('stations', '0044_auto_20260917_1836'), + ] + + operations = [ + migrations.AddField( + model_name='transportespnow', + name='espnowtype', + field=models.PositiveIntegerField(choices=[(1, 'Module type master'), (2, 'Module type satellite')], default=1, help_text='Stimawifi espnow type'), + ), + ] diff --git a/python/rmap/stations/migrations/0046_template_airquality_satellite.py b/python/rmap/stations/migrations/0046_template_airquality_satellite.py new file mode 100644 index 000000000..4430ec229 --- /dev/null +++ b/python/rmap/stations/migrations/0046_template_airquality_satellite.py @@ -0,0 +1,46 @@ +from django.db import migrations, models +from django.core import serializers +import os + +fixture_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../fixtures')) + +def load_fixture(apps, schema_editor): + + def load_fixture_from_file(fixture_file): + fixture = open(fixture_file, 'rb') + objects = serializers.deserialize('json', fixture, ignorenonexistent=True) + for obj in objects: + obj.save() + fixture.close() + + + fixture_filename = "template_station_airquality_satellite.json" + fixture_file = os.path.join(fixture_dir, fixture_filename) + print("load fixture from file: ",fixture_file) + load_fixture_from_file(fixture_file) + + + +def unload_fixture(apps, schema_editor): + "Brutally deleting all entries for this model..." + + #MyModel = apps.get_model("stations", "StationMetadata") + #MyModel.objects.all().delete() + #MyModel = apps.get_model("stations", "Board") + #MyModel.objects.all().delete() + #MyModel = apps.get_model("stations", "Sensor") + #MyModel.objects.all().delete() + pass + +class Migration(migrations.Migration): + + dependencies = [ + ('stations', '0045_transportespnow_espnowtype'), + ] + + operations = [ + migrations.RunPython( + code=load_fixture, + reverse_code=unload_fixture, + ), + ] diff --git a/python/rmap/stations/models.py b/python/rmap/stations/models.py index bb8bbd071..cc2badc2d 100644 --- a/python/rmap/stations/models.py +++ b/python/rmap/stations/models.py @@ -164,7 +164,14 @@ def dynamic_timerange(self): except: pass - #board do not have mqtttransport active; search for other board in station with mqtttransport + try: + if self.board.transportespnow.active: + #board have espnow transport active; use it + return self.timerange.format(P2=self.board.transportespnow.espnowsampletime) + except: + pass + + #board do not have mqtttransport active; search for other board in station with mqtttransport or espnowtransport mystation=self.board.stationmetadata for board in mystation.board_set.all(): # try to get sampletime: from mqtt transport of one board of the station @@ -173,6 +180,10 @@ def dynamic_timerange(self): if (board.transportmqtt.active) : return self.timerange.format(P2=board.transportmqtt.mqttsampletime) + if (hasattr(board, 'transportespnow')): + if (board.transportespnow.active) : + return self.timerange.format(P2=board.transportespnow.sampletime) + print ("dynamic_timerange warning: return timerange with default 900 sec. if not static defined") return self.timerange.format(P2=900) @@ -399,6 +410,45 @@ def __str__(self): return '%s' % (self.node) +class TransportEspnowManager(models.Manager): + def get_by_natural_key(self, board): + #print "TransportEspnowManager:",board + return self.get(board=Board.objects.get_by_natural_key(board[0],board[1])) + +class TransportEspnow(models.Model): + """ESP now transport.""" + + + + ESPNOW_TYPE_CHOICES = ( + ( 1,"Module type master"), + ( 2,"Module type satellite"), + ) + + objects = TransportEspnowManager() + active = models.BooleanField(_("Active"),default=False,null=False,blank=False, + help_text=_("Activate this transport for measurements")) + espnowtype = models.PositiveIntegerField(default=1,null=False, blank=False, + choices=ESPNOW_TYPE_CHOICES,help_text=_("Stimawifi espnow type")) + espnowsampletime = models.PositiveIntegerField(default=300,null=False,blank=False, + help_text=_("interval in seconds for publish")) + board = models.OneToOneField("Board",on_delete=models.CASCADE) + + def natural_key(self): + #print "natural key TransportEspnow" + #print self,self.board.natural_key() + return (self.board.natural_key(),) + + natural_key.dependencies = ['stations.board'] + + class Meta: + ordering = ['board'] + verbose_name = 'ESP NOW node' + verbose_name_plural = 'ESP NOW nodes' + + def __str__(self): + return '%s' % (self.board) + class TransportMqttManager(models.Manager): def get_by_natural_key(self, board): #print "TransportMqttManager: ",board @@ -701,6 +751,7 @@ class Board(models.Model): ('gsmv4', 'Stima V4 master LTS transport'), ('slavev4', 'Stima V4 slave CAN transport'), ('stimawifiv3', 'Stima V3 Stima WiFI'), + ('stimawifiv3espnow', 'Stima V3 Stima WiFI ESP NOW'), ) STIMAV4_MODULE_TYPE_CHOICES = ( @@ -719,6 +770,7 @@ class Board(models.Model): (29,"Module acquire soil humidity"), (30,"Module acquire level river"), (101,"Module StimaV3 WiFi"), + (102,"Module StimaV3 WiFi ESP NOW"), ) name = models.CharField(max_length=255,help_text=_("board name")) diff --git a/python/rmap/templates/stations/stationlocaldata.html b/python/rmap/templates/stations/stationlocaldata.html index 252318f5b..f5384a3cd 100644 --- a/python/rmap/templates/stations/stationlocaldata.html +++ b/python/rmap/templates/stations/stationlocaldata.html @@ -46,7 +46,7 @@ $("#data").hide(); //nascondo i dati }); - getLocation(); + //getLocation(); }; window.onload = hideNodata; setInterval(loadData,10000);