From 88ed293c6b7ab0448d95c502a6a2ee834bc8b9d6 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:51:36 -0400 Subject: [PATCH 001/115] start scaffolding for sensor --- ads1115.hpp | 19 +++++++++++++++++++ analog.h | 15 +++++++++++++++ external/influxdb-cpp | 1 + 3 files changed, 35 insertions(+) create mode 100644 ads1115.hpp create mode 100644 analog.h create mode 160000 external/influxdb-cpp diff --git a/ads1115.hpp b/ads1115.hpp new file mode 100644 index 000000000..87a062423 --- /dev/null +++ b/ads1115.hpp @@ -0,0 +1,19 @@ +#pragma once + +#include "analog.h" +#include + +class ADS1115 : AnalogSensor { +public: + void poll(); + uint8_t pin_count() { + return 4; + } + uint16_t get_pin_value(uint8_t pin); + float get_scale_constant(uint8_t pin) { + return -32768.0; + } + float get_scale_factor(uint8_t pin) { + return 6144.0 / 32768.0; + } +}; \ No newline at end of file diff --git a/analog.h b/analog.h new file mode 100644 index 000000000..3de7e7395 --- /dev/null +++ b/analog.h @@ -0,0 +1,15 @@ +#ifndef ANALOG_H +#define ANALOG_H + +#include + +class AnalogSensor { +public: + virtual void poll(); + virtual uint8_t pin_count(); + virtual uint16_t get_pin_value(uint8_t pin); + virtual float get_scale_constant(uint8_t pin); + virtual float get_scale_factor(uint8_t pin); +}; + +#endif // ANALOG_H \ No newline at end of file diff --git a/external/influxdb-cpp b/external/influxdb-cpp new file mode 160000 index 000000000..fa4cb4927 --- /dev/null +++ b/external/influxdb-cpp @@ -0,0 +1 @@ +Subproject commit fa4cb4927877ef64a4e2aae4ef29daf0ac309614 From fdb625a3ff1cf6b4aac7552781180b68a7479124 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 12:18:57 -0400 Subject: [PATCH 002/115] ads1115 support for esp and ospi --- Makefile | 2 +- ads1115.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++ ads1115.hpp | 32 +++++++++++++++++++++--- analog.h | 2 +- build.sh | 2 +- i2cd.h | 24 ++++++++++++++++++ main.cpp | 7 ++++++ platformio.ini | 26 +++++++++++--------- 8 files changed, 143 insertions(+), 19 deletions(-) create mode 100644 ads1115.cpp diff --git a/Makefile b/Makefile index c11e2ae91..0cd93b448 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LD=$(CXX) LIBS=pthread mosquitto ssl crypto i2c gpiod LDFLAGS=$(addprefix -l,$(LIBS)) BINARY=OpenSprinkler -SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) +SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp ads1115.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) HEADERS=$(wildcard *.h) $(wildcard *.hpp) OBJECTS=$(addsuffix .o,$(basename $(SOURCES))) diff --git a/ads1115.cpp b/ads1115.cpp new file mode 100644 index 000000000..a0663727f --- /dev/null +++ b/ads1115.cpp @@ -0,0 +1,67 @@ +#include + +#if defined(ARDUINO) +ADS1115::ADS1115(uint8_t address) { + this->adc = ADS1X15::ADS1115(address); +} + +bool ADS1115::begin() { + if (!this->adc.begin()) { + return false; + } + + this->adc.setGain(0); // 6.144 volt + this->adc.setDataRate(7); // 0 = slow 4 = medium 7 = fast + this->adc.setMode(1); // Once + return true; +} + +int16_t ADS1115::get_pin_value(uint8_t pin) { + return this->adc.readADC(pin); +} +#else // OSPI +ADS1115::ADS1115(uint8_t address) { + this->_addr = address; + this->i2c = I2CDevice(); +} + +bool ADS1115::begin() { + if (this->i2c.begin(_addr) < 0) { + return false; + } + return true; +} + +uint16_t ADS1115::get_pin_value(uint8_t pin) { + { + uint32_t start = millis(); + // timeout == { 138, 74, 42, 26, 18, 14, 12, 11 } + // added 10 ms more than maximum conversion time from datasheet. + // to prevent premature timeout in RTOS context. + // See #82 + uint8_t timeOut = (128 >> (_datarate >> 5)) + 10; + while (isBusy()) + { + if ( (millis() - start) > timeOut) + { + _error = ADS1X15_ERROR_TIMEOUT; + return ADS1X15_ERROR_TIMEOUT; + } + yield(); // wait for conversion; yield for ESP. + } + } +} + +void ADS1115::_write_register(uint8_t reg, uint16_t value) { + this->i2c.begin_transaction(reg); + this->i2c.send(reg, value >> 8); + this->i2c.send(reg, value & 0xFF); + this->i2c.end_transaction(); +} + +uint16_t _read_register(uint8_t reg) { + uint8_t values[2]; + this->i2c.read(reg, 2, values); + return values[0] << 8 | values[1] +} +#endif \ No newline at end of file diff --git a/ads1115.hpp b/ads1115.hpp index 87a062423..49c11ca7b 100644 --- a/ads1115.hpp +++ b/ads1115.hpp @@ -3,17 +3,41 @@ #include "analog.h" #include +#define ADS1115_SCALE_FACTOR (6144.0 / 32768.0) + +#if defined(ARDUINO) +namespace ADS1X15 { +#include +} +#else +#include "i2cd.h" +#endif + class ADS1115 : AnalogSensor { public: - void poll(); + ADS1115(uint8_t address); + int16_t get_pin_value(uint8_t pin); + bool begin(); + uint8_t pin_count() { return 4; } - uint16_t get_pin_value(uint8_t pin); + float get_scale_constant(uint8_t pin) { - return -32768.0; + return 0; } + float get_scale_factor(uint8_t pin) { - return 6144.0 / 32768.0; + return ADS1115_SCALE_FACTOR; } + + private: +#if defined(ARDUINO) + ADS1X15::ADS1115 adc; +#else + uint8_t _addr; + I2CDevice i2c; + void _write_register(uint8_t reg, uint16_t value); + uint16_t _read_register(uint8_t reg); +#endif }; \ No newline at end of file diff --git a/analog.h b/analog.h index 3de7e7395..f31d857ed 100644 --- a/analog.h +++ b/analog.h @@ -7,7 +7,7 @@ class AnalogSensor { public: virtual void poll(); virtual uint8_t pin_count(); - virtual uint16_t get_pin_value(uint8_t pin); + virtual int16_t get_pin_value(uint8_t pin); virtual float get_scale_constant(uint8_t pin); virtual float get_scale_factor(uint8_t pin); }; diff --git a/build.sh b/build.sh index 740b6188b..d87a07ae2 100755 --- a/build.sh +++ b/build.sh @@ -67,7 +67,7 @@ else ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB + g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp ads1115.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB fi diff --git a/i2cd.h b/i2cd.h index 53d46f9a0..55e3ef638 100644 --- a/i2cd.h +++ b/i2cd.h @@ -67,6 +67,30 @@ class I2CDevice { } } + int send(unsigned char reg, unsigned char data) { + if (transaction) { + if (reg != transaction_id) { + return -1; + } + + int res = 0; + if (transaction_buffer_length >= sizeof(transaction_buffer)) { + res = send_transaction(); + transaction_buffer_length = 0; + } + + transaction_buffer[transaction_buffer_length] = data; + transaction_buffer_length++; + return res; + } else { + return i2c_smbus_write_byte_data(_file, reg, data); + } + } + + int read(unsigned char reg, unsigned char length, unsigned char *values) { + return i2c_smbus_read_i2c_block_data_or_emulated(_file, reg, length, values); + } + private: int _file = -1; bool transaction = false; diff --git a/main.cpp b/main.cpp index 45f5922db..01652df0c 100644 --- a/main.cpp +++ b/main.cpp @@ -407,6 +407,8 @@ void ui_state_machine() { // ====================== // Setup Function // ====================== +#include "ads1115.hpp" +ADS1115 adc(0x48); #if defined(ARDUINO) void do_setup() { /* Clear WDT reset flag. */ @@ -461,6 +463,7 @@ void do_setup() { os.switch_special_station(sid, 0); } + adc.begin(); os.button_timeout = LCD_BACKLIGHT_TIMEOUT; } @@ -551,6 +554,10 @@ void do_loop() } } + Serial.print("Pin 0: "); + Serial.print(adc.get_pin_value(0)); + Serial.println("."); + static time_os_t last_time = 0; static ulong last_minute = 0; diff --git a/platformio.ini b/platformio.ini index 29d823e24..e5d2cabb8 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,10 +17,11 @@ platform = espressif8266@4.2.1 board = d1_mini framework = arduino lib_ldf_mode = deep -lib_deps = - https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip - knolleary/PubSubClient @ ^2.8 - https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.2.0 +lib_deps = + https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip + knolleary/PubSubClient @ ^2.8 + https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.2.0 + robtillaart/ADS1X15@^0.5.3 ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - -- upload_speed = 460800 @@ -29,8 +30,9 @@ board_build.flash_mode = dio board_build.ldscript = eagle.flash.4m2m.ld board_build.f_cpu = 160000000L board_build.f_flash = 80000000L -build_flags = -DARP_TABLE_SIZE=40 -;build_flags = -DENABLE_DEBUG +build_flags = + -DARP_TABLE_SIZE=40 + -DENABLE_DEBUG [env:sanguino_atmega1284p] platform = atmelavr @@ -39,13 +41,13 @@ board_build.f_cpu = 16000000L board_build.variant = sanguino framework = arduino lib_ldf_mode = deep -lib_deps = - https://github.com/UIPEthernet/UIPEthernet/archive/refs/tags/v2.0.12.zip - knolleary/PubSubClient @ ^2.8 - https://github.com/greiman/SdFat/archive/refs/tags/1.0.7.zip - Wire +lib_deps = + https://github.com/UIPEthernet/UIPEthernet/archive/refs/tags/v2.0.12.zip + knolleary/PubSubClient @ ^2.8 + https://github.com/greiman/SdFat/archive/refs/tags/1.0.7.zip + Wire build_src_filter = +<*> - -- -monitor_speed=115200 +monitor_speed = 115200 ; The following env is for syntax highlighting only, ; it is NOT for building the firmware for Linux. From 8c58b8b46b1557513ef435d78f472df22dd44f2f Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:39:05 -0400 Subject: [PATCH 003/115] Wrote code for esp from scratch --- ads1115.cpp | 87 ++++++++++++++++++++++++++++++-------------------- ads1115.hpp | 25 +++++++++++---- analog.h | 1 - main.cpp | 13 ++++++-- platformio.ini | 1 - 5 files changed, 83 insertions(+), 44 deletions(-) diff --git a/ads1115.cpp b/ads1115.cpp index a0663727f..8621ceb32 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -1,57 +1,53 @@ #include #if defined(ARDUINO) -ADS1115::ADS1115(uint8_t address) { - this->adc = ADS1X15::ADS1115(address); -} +ADS1115::ADS1115(uint8_t address, TwoWire& wire) : _address(address), _wire(&wire) {} +ADS1115::ADS1115(uint8_t address) : ADS1115(address, Wire) {} bool ADS1115::begin() { - if (!this->adc.begin()) { + if ((this->_address < 0x48) || (this->_address > 0x4B)) { return false; } + this-> _wire->beginTransmission(_address); + return (this->_wire->endTransmission() == 0); +} - this->adc.setGain(0); // 6.144 volt - this->adc.setDataRate(7); // 0 = slow 4 = medium 7 = fast - this->adc.setMode(1); // Once - return true; + +void ADS1115::_write_register(uint8_t reg, uint16_t value) { + this->_wire->beginTransmission(this->_address); + this->_wire->write(reg); + this->_wire->write((uint8_t) (value >> 8)); + this->_wire->write((uint8_t) (value & 0xFF)); + this->_wire->endTransmission(); } -int16_t ADS1115::get_pin_value(uint8_t pin) { - return this->adc.readADC(pin); +uint16_t ADS1115::_read_register(uint8_t reg) { + this->_wire->beginTransmission(this->_address); + this->_wire->write(reg); + if (!this->_wire->endTransmission()) { + if (this->_wire->requestFrom((int) _address, (int) 2) == 2) { + uint16_t val = ((uint16_t) this->_wire->read()) << 8; + val += (uint16_t) this->_wire->read(); + return val; + } + } + + return 0; } + #else // OSPI ADS1115::ADS1115(uint8_t address) { - this->_addr = address; + this->_address = address; this->i2c = I2CDevice(); } bool ADS1115::begin() { - if (this->i2c.begin(_addr) < 0) { + if (this->i2c.begin(_address) < 0) { return false; } return true; } -uint16_t ADS1115::get_pin_value(uint8_t pin) { - { - uint32_t start = millis(); - // timeout == { 138, 74, 42, 26, 18, 14, 12, 11 } - // added 10 ms more than maximum conversion time from datasheet. - // to prevent premature timeout in RTOS context. - // See #82 - uint8_t timeOut = (128 >> (_datarate >> 5)) + 10; - while (isBusy()) - { - if ( (millis() - start) > timeOut) - { - _error = ADS1X15_ERROR_TIMEOUT; - return ADS1X15_ERROR_TIMEOUT; - } - yield(); // wait for conversion; yield for ESP. - } - } -} - void ADS1115::_write_register(uint8_t reg, uint16_t value) { this->i2c.begin_transaction(reg); this->i2c.send(reg, value >> 8); @@ -59,9 +55,32 @@ void ADS1115::_write_register(uint8_t reg, uint16_t value) { this->i2c.end_transaction(); } -uint16_t _read_register(uint8_t reg) { +uint16_t ADS1115::_read_register(uint8_t reg) { uint8_t values[2]; this->i2c.read(reg, 2, values); return values[0] << 8 | values[1] } -#endif \ No newline at end of file +#endif + +int16_t ADS1115::get_pin_value(uint8_t pin) { + this->request_pin(pin); + + uint32_t start = millis(); + while (this->is_busy()) { + // if ((millis() - start) > 11) { + if ((millis() - start) > 18) { + return 0; + } + + #if defined(ARDUINO) + yield(); + #endif + } + + return this->get_value(); +} + +void ADS1115::request_pin(uint8_t pin) { + uint16_t config = 0x8000 | ((4 + ((uint16_t) pin)) << 12) | 0x0100 | (4 << 5); + this->_write_register(0x01, config); +} \ No newline at end of file diff --git a/ads1115.hpp b/ads1115.hpp index 49c11ca7b..8b8dd18da 100644 --- a/ads1115.hpp +++ b/ads1115.hpp @@ -6,15 +6,17 @@ #define ADS1115_SCALE_FACTOR (6144.0 / 32768.0) #if defined(ARDUINO) -namespace ADS1X15 { -#include -} +#include +#include #else #include "i2cd.h" #endif class ADS1115 : AnalogSensor { public: +#if defined(ARDUINO) + ADS1115(uint8_t address, TwoWire& wire); +#endif ADS1115(uint8_t address); int16_t get_pin_value(uint8_t pin); bool begin(); @@ -31,13 +33,24 @@ class ADS1115 : AnalogSensor { return ADS1115_SCALE_FACTOR; } + int16_t get_value() { + return (int16_t) this->_read_register(0x00); + } + + void request_pin(uint8_t pin); + + bool is_busy() { + return (this->_read_register(0x01) & 0x8000) == 0; + } + private: + uint8_t _address; #if defined(ARDUINO) - ADS1X15::ADS1115 adc; + TwoWire *_wire; #else - uint8_t _addr; I2CDevice i2c; +#endif + void _write_register(uint8_t reg, uint16_t value); uint16_t _read_register(uint8_t reg); -#endif }; \ No newline at end of file diff --git a/analog.h b/analog.h index f31d857ed..bdd4bd30f 100644 --- a/analog.h +++ b/analog.h @@ -5,7 +5,6 @@ class AnalogSensor { public: - virtual void poll(); virtual uint8_t pin_count(); virtual int16_t get_pin_value(uint8_t pin); virtual float get_scale_constant(uint8_t pin); diff --git a/main.cpp b/main.cpp index 01652df0c..627aaceb3 100644 --- a/main.cpp +++ b/main.cpp @@ -554,9 +554,18 @@ void do_loop() } } + int16_t val = adc.get_pin_value(0); Serial.print("Pin 0: "); - Serial.print(adc.get_pin_value(0)); - Serial.println("."); + Serial.print(val); + Serial.print(" - "); + Serial.print(((float) val) * adc.get_scale_factor(0)); + Serial.println("mV."); + val = adc.get_pin_value(1); + Serial.print("Pin 1: "); + Serial.print(val); + Serial.print(" - "); + Serial.print(((float) val) * adc.get_scale_factor(0)); + Serial.println("mV."); static time_os_t last_time = 0; diff --git a/platformio.ini b/platformio.ini index e5d2cabb8..c11decb48 100644 --- a/platformio.ini +++ b/platformio.ini @@ -21,7 +21,6 @@ lib_deps = https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.2.0 - robtillaart/ADS1X15@^0.5.3 ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - -- upload_speed = 460800 From ee0a5312fa840227cdc79a3805668c16635bae15 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:49:32 -0400 Subject: [PATCH 004/115] fix submodules --- .gitmodules | 3 +++ external/influxdb-cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index 0c7b52e67..a2bf9ffbd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "external/TinyWebsockets"] path = external/TinyWebsockets url = https://github.com/gilmaimon/TinyWebsockets.git +[submodule "external/influxdb-cpp"] + path = external/influxdb-cpp + url = https://github.com/orca-zhang/influxdb-cpp.git diff --git a/external/influxdb-cpp b/external/influxdb-cpp index fa4cb4927..75a4ec9bb 160000 --- a/external/influxdb-cpp +++ b/external/influxdb-cpp @@ -1 +1 @@ -Subproject commit fa4cb4927877ef64a4e2aae4ef29daf0ac309614 +Subproject commit 75a4ec9bb216e4d4a073c363e5ba10f95900e3ec From e6190ca2a2280b50fb1ebd26208e0f0b594b12e0 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:00:01 -0400 Subject: [PATCH 005/115] fix i2cd.h --- ads1115.hpp => ads1115.h | 0 i2cd.h | 22 +--------------------- 2 files changed, 1 insertion(+), 21 deletions(-) rename ads1115.hpp => ads1115.h (100%) diff --git a/ads1115.hpp b/ads1115.h similarity index 100% rename from ads1115.hpp rename to ads1115.h diff --git a/i2cd.h b/i2cd.h index 55e3ef638..ee5049eb6 100644 --- a/i2cd.h +++ b/i2cd.h @@ -67,28 +67,8 @@ class I2CDevice { } } - int send(unsigned char reg, unsigned char data) { - if (transaction) { - if (reg != transaction_id) { - return -1; - } - - int res = 0; - if (transaction_buffer_length >= sizeof(transaction_buffer)) { - res = send_transaction(); - transaction_buffer_length = 0; - } - - transaction_buffer[transaction_buffer_length] = data; - transaction_buffer_length++; - return res; - } else { - return i2c_smbus_write_byte_data(_file, reg, data); - } - } - int read(unsigned char reg, unsigned char length, unsigned char *values) { - return i2c_smbus_read_i2c_block_data_or_emulated(_file, reg, length, values); + return i2c_smbus_read_i2c_block_data(_file, reg, length, values); } private: From 14e3ac198c22349c7e8903d2076cd9f52b91d770 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:02:55 -0400 Subject: [PATCH 006/115] fix print --- main.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/main.cpp b/main.cpp index 627aaceb3..a27e07bad 100644 --- a/main.cpp +++ b/main.cpp @@ -407,7 +407,7 @@ void ui_state_machine() { // ====================== // Setup Function // ====================== -#include "ads1115.hpp" +#include "ads1115.h" ADS1115 adc(0x48); #if defined(ARDUINO) void do_setup() { @@ -555,17 +555,9 @@ void do_loop() } int16_t val = adc.get_pin_value(0); - Serial.print("Pin 0: "); - Serial.print(val); - Serial.print(" - "); - Serial.print(((float) val) * adc.get_scale_factor(0)); - Serial.println("mV."); + printf("Pin 0: " PRId16 " - %fmV.", val, ((float) val) * adc.get_scale_factor(0)); val = adc.get_pin_value(1); - Serial.print("Pin 1: "); - Serial.print(val); - Serial.print(" - "); - Serial.print(((float) val) * adc.get_scale_factor(0)); - Serial.println("mV."); + printf("Pin 0: " PRId16 " - %fmV.", val, ((float) val) * adc.get_scale_factor(0)); static time_os_t last_time = 0; From b3e8d9b5e437e181b8cf8206faeac018f1cf327f Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:04:44 -0400 Subject: [PATCH 007/115] fix import --- ads1115.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ads1115.cpp b/ads1115.cpp index 8621ceb32..db7169702 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -1,4 +1,4 @@ -#include +#include "ads1115.h" #if defined(ARDUINO) ADS1115::ADS1115(uint8_t address, TwoWire& wire) : _address(address), _wire(&wire) {} From f5114e1f3979022236320bd3b30a45223d06fe2b Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:06:41 -0400 Subject: [PATCH 008/115] added missing semicolon --- ads1115.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ads1115.cpp b/ads1115.cpp index db7169702..e515a7fec 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -58,7 +58,7 @@ void ADS1115::_write_register(uint8_t reg, uint16_t value) { uint16_t ADS1115::_read_register(uint8_t reg) { uint8_t values[2]; this->i2c.read(reg, 2, values); - return values[0] << 8 | values[1] + return values[0] << 8 | values[1]; } #endif From 67d3381def07c0d14c40e4577b74e3205489921c Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:13:34 -0400 Subject: [PATCH 009/115] add virtual deconsturctor for analogsensor interface --- analog.h | 1 + 1 file changed, 1 insertion(+) diff --git a/analog.h b/analog.h index bdd4bd30f..5f8f54d7d 100644 --- a/analog.h +++ b/analog.h @@ -5,6 +5,7 @@ class AnalogSensor { public: + virtual ~AnalogSensor() = default; virtual uint8_t pin_count(); virtual int16_t get_pin_value(uint8_t pin); virtual float get_scale_constant(uint8_t pin); From dbaf646e403be5c7b1f81200ea6f6a0d47fde98a Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:13:59 -0400 Subject: [PATCH 010/115] only include i2c in the c block for i2cd.h --- i2cd.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/i2cd.h b/i2cd.h index ee5049eb6..291da1fa4 100644 --- a/i2cd.h +++ b/i2cd.h @@ -7,9 +7,10 @@ extern "C" { #include #include +} #include #include "utils.h" -} + class I2CDevice { public: From 591ff820605874a703b561cbe30d2befa9fa79f1 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:17:17 -0400 Subject: [PATCH 011/115] fix interface again --- analog.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/analog.h b/analog.h index 5f8f54d7d..8c16d2a62 100644 --- a/analog.h +++ b/analog.h @@ -6,10 +6,10 @@ class AnalogSensor { public: virtual ~AnalogSensor() = default; - virtual uint8_t pin_count(); - virtual int16_t get_pin_value(uint8_t pin); - virtual float get_scale_constant(uint8_t pin); - virtual float get_scale_factor(uint8_t pin); + virtual uint8_t pin_count() = 0; + virtual int16_t get_pin_value(uint8_t pin) = 0; + virtual float get_scale_constant(uint8_t pin) = 0; + virtual float get_scale_factor(uint8_t pin) = 0; }; #endif // ANALOG_H \ No newline at end of file From 9f032f3ea70f28fa99c6d07c37d72a2320df7ae0 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:37:51 -0400 Subject: [PATCH 012/115] add mutex for i2cd.h --- i2cd.h | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 5 deletions(-) diff --git a/i2cd.h b/i2cd.h index 291da1fa4..f57ce2097 100644 --- a/i2cd.h +++ b/i2cd.h @@ -8,20 +8,37 @@ extern "C" { #include #include } -#include +#include +#include +#include #include "utils.h" class I2CDevice { public: - I2CDevice() {} + I2CDevice() = default; + ~I2CDevice() { + closeBusIfUnused(); + } int begin(const char *bus, unsigned char addr) { - _file = open(bus, O_RDWR); - if (_file < 0) { - return _file; + std::lock_guard lock(_bus_mutex); + + _bus = bus; + _addr = addr; + + // Open bus if not already opened + if (_bus_fds.count(bus) == 0) { + int fd = open(bus, O_RDWR); + if (fd < 0) return fd; + _bus_fds[bus] = fd; + _bus_refcount[bus] = 1; + } else { + _bus_refcount[bus]++; } + _file = _bus_fds[bus]; + return ioctl(_file, I2C_SLAVE, addr); } @@ -49,6 +66,9 @@ class I2CDevice { } int send(unsigned char reg, unsigned char data) { + std::lock_guard lock(_bus_mutex); + int res = ioctl(_file, I2C_SLAVE, _addr); + if (res < 0) return res; if (transaction) { if (reg != transaction_id) { return -1; @@ -69,11 +89,22 @@ class I2CDevice { } int read(unsigned char reg, unsigned char length, unsigned char *values) { + std::lock_guard lock(_bus_mutex); + int res = ioctl(_file, I2C_SLAVE, _addr); + if (res < 0) return res; return i2c_smbus_read_i2c_block_data(_file, reg, length, values); } private: int _file = -1; + const char *_bus = nullptr; + unsigned char _addr = 0; + + // Static map of open bus file descriptors + static std::map _bus_fds; + static std::map _bus_refcount; + static std::mutex _bus_mutex; + bool transaction = false; unsigned char transaction_id = 0; unsigned char transaction_buffer[32]; @@ -98,6 +129,25 @@ class I2CDevice { return i2c_smbus_write_i2c_block_data( _file, transaction_id, transaction_buffer_length, transaction_buffer); } + + void closeBusIfUnused() { + std::lock_guard lock(_bus_mutex); + if (!_bus) return; + auto it = _bus_refcount.find(_bus); + if (it != _bus_refcount.end()) { + it->second--; + if (it->second <= 0) { + close(_bus_fds[_bus]); + _bus_fds.erase(_bus); + _bus_refcount.erase(it); + } + } + } }; +// Static members initialization +std::map I2CDevice::_bus_fds; +std::map I2CDevice::_bus_refcount; +std::mutex I2CDevice::_bus_mutex; + #endif // I2CD_H \ No newline at end of file From 840d932d8dc034a0e3f8e50b2834eb5ac8a532ee Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:47:18 -0400 Subject: [PATCH 013/115] fix init --- Makefile | 2 +- build.sh | 2 +- i2cd.cpp | 5 +++++ i2cd.h | 7 +------ 4 files changed, 8 insertions(+), 8 deletions(-) create mode 100644 i2cd.cpp diff --git a/Makefile b/Makefile index 0cd93b448..9e739cb9f 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LD=$(CXX) LIBS=pthread mosquitto ssl crypto i2c gpiod LDFLAGS=$(addprefix -l,$(LIBS)) BINARY=OpenSprinkler -SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp ads1115.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) +SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp i2cd.cpp ads1115.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) HEADERS=$(wildcard *.h) $(wildcard *.hpp) OBJECTS=$(addsuffix .o,$(basename $(SOURCES))) diff --git a/build.sh b/build.sh index d87a07ae2..b89200eb9 100755 --- a/build.sh +++ b/build.sh @@ -67,7 +67,7 @@ else ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp ads1115.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB + g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp i2cd.cpp ads1115.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB fi diff --git a/i2cd.cpp b/i2cd.cpp new file mode 100644 index 000000000..7426ed069 --- /dev/null +++ b/i2cd.cpp @@ -0,0 +1,5 @@ +#include "i2cd.h" + +std::map I2CDevice::_bus_fds; +std::map I2CDevice::_bus_refcount; +std::mutex I2CDevice::_bus_mutex; \ No newline at end of file diff --git a/i2cd.h b/i2cd.h index f57ce2097..a70f3051c 100644 --- a/i2cd.h +++ b/i2cd.h @@ -2,6 +2,7 @@ #define I2CD_H #include +#include #include extern "C" { @@ -144,10 +145,4 @@ class I2CDevice { } } }; - -// Static members initialization -std::map I2CDevice::_bus_fds; -std::map I2CDevice::_bus_refcount; -std::mutex I2CDevice::_bus_mutex; - #endif // I2CD_H \ No newline at end of file From 73bdfc8962974b9944debccdcec0cb59608f195f Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 9 Jul 2025 16:39:11 -0400 Subject: [PATCH 014/115] fix i2c --- SSD1306Display.h | 6 +-- ads1115.cpp | 22 ++++----- ads1115.h | 6 ++- i2cd.cpp | 4 +- i2cd.h | 116 +++++++++++++++++++++++------------------------ main.cpp | 5 +- 6 files changed, 77 insertions(+), 82 deletions(-) diff --git a/SSD1306Display.h b/SSD1306Display.h index f97683a5f..ca7339e8c 100644 --- a/SSD1306Display.h +++ b/SSD1306Display.h @@ -174,7 +174,6 @@ class SSD1306Display { SSD1306Display(uint8_t addr, uint8_t _sda, uint8_t _scl) { cx = 0; cy = 0; - _addr = addr; for (uint8_t i = 0; i < NUM_CUSTOM_ICONS; i++) custom_chars[i] = 0; @@ -183,7 +182,7 @@ class SSD1306Display { height = 64; width = 128; - i2c = I2CDevice(); + i2c = I2CDevice(Bus, addr); } ~SSD1306Display() { @@ -194,8 +193,6 @@ class SSD1306Display { void init() {} // Dummy function to match ESP8266 int begin() { - i2c.begin(_addr); - setFont(Monospaced_plain_13); fontWidth = 8; fontHeight = 16; @@ -541,7 +538,6 @@ class SSD1306Display { uint8_t *font; I2CDevice i2c; - unsigned char _addr; unsigned char height; unsigned char width; diff --git a/ads1115.cpp b/ads1115.cpp index e515a7fec..e9d422959 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -36,28 +36,26 @@ uint16_t ADS1115::_read_register(uint8_t reg) { } #else // OSPI -ADS1115::ADS1115(uint8_t address) { - this->_address = address; - this->i2c = I2CDevice(); -} +ADS1115::ADS1115(uint8_t address, I2CBus& bus) : _address(address), i2c(&bus, address) {} +ADS1115::ADS1115(uint8_t address) : ADS1115(address, Bus) {} bool ADS1115::begin() { - if (this->i2c.begin(_address) < 0) { - return false; - } + // if (this->_i2c.begin(_address) < 0) { + // return false; + // } return true; } void ADS1115::_write_register(uint8_t reg, uint16_t value) { - this->i2c.begin_transaction(reg); - this->i2c.send(reg, value >> 8); - this->i2c.send(reg, value & 0xFF); - this->i2c.end_transaction(); + this->_i2c.begin_transaction(reg); + this->_i2c.send(reg, value >> 8); + this->_i2c.send(reg, value & 0xFF); + this->_i2c.end_transaction(); } uint16_t ADS1115::_read_register(uint8_t reg) { uint8_t values[2]; - this->i2c.read(reg, 2, values); + printf("res: %d \n", this->_i2c.read(reg, 2, values)); return values[0] << 8 | values[1]; } #endif diff --git a/ads1115.h b/ads1115.h index 8b8dd18da..e30dc0fee 100644 --- a/ads1115.h +++ b/ads1115.h @@ -16,8 +16,10 @@ class ADS1115 : AnalogSensor { public: #if defined(ARDUINO) ADS1115(uint8_t address, TwoWire& wire); +#else + ADS1115(uint8_t address, I2CBus& bus); #endif - ADS1115(uint8_t address); +ADS1115(uint8_t address); int16_t get_pin_value(uint8_t pin); bool begin(); @@ -48,7 +50,7 @@ class ADS1115 : AnalogSensor { #if defined(ARDUINO) TwoWire *_wire; #else - I2CDevice i2c; + I2CDevice _i2c; #endif void _write_register(uint8_t reg, uint16_t value); diff --git a/i2cd.cpp b/i2cd.cpp index 7426ed069..c38918b8a 100644 --- a/i2cd.cpp +++ b/i2cd.cpp @@ -1,5 +1,3 @@ #include "i2cd.h" -std::map I2CDevice::_bus_fds; -std::map I2CDevice::_bus_refcount; -std::mutex I2CDevice::_bus_mutex; \ No newline at end of file +I2CBus Bus; \ No newline at end of file diff --git a/i2cd.h b/i2cd.h index a70f3051c..77e0447b2 100644 --- a/i2cd.h +++ b/i2cd.h @@ -2,48 +2,71 @@ #define I2CD_H #include -#include #include extern "C" { #include #include } -#include -#include -#include +#include #include "utils.h" - -class I2CDevice { +class I2CBus { public: - I2CDevice() = default; - ~I2CDevice() { - closeBusIfUnused(); - } + I2CBus() {} - int begin(const char *bus, unsigned char addr) { - std::lock_guard lock(_bus_mutex); + int begin(const char *bus) { + _file = open(bus, O_RDWR); + if (_file < 0) { + return _file; + } - _bus = bus; - _addr = addr; + return 0; + } - // Open bus if not already opened - if (_bus_fds.count(bus) == 0) { - int fd = open(bus, O_RDWR); - if (fd < 0) return fd; - _bus_fds[bus] = fd; - _bus_refcount[bus] = 1; - } else { - _bus_refcount[bus]++; - } + int begin() { return begin(getDefaultBus()); } - _file = _bus_fds[bus]; + int send(unsigned char addr, unsigned char reg, unsigned char data) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_write_byte_data(_file, reg, data); + } - return ioctl(_file, I2C_SLAVE, addr); + int send_transaction(unsigned char addr, unsigned char transaction_id, unsigned char *transaction_buffer, unsigned char transaction_buffer_length) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_write_i2c_block_data( + _file, transaction_id, transaction_buffer_length, transaction_buffer); } - int begin(unsigned char addr) { return begin(getDefaultBus(), addr); } + int read(unsigned char addr, unsigned char reg, unsigned char length, unsigned char *values) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_read_i2c_block_data(_file, reg, length, values); + } + +private: + int _file = -1; + + const char *getDefaultBus() { + switch (get_board_type()) { + case BoardType::RaspberryPi_bcm2712: + case BoardType::RaspberryPi_bcm2711: + case BoardType::RaspberryPi_bcm2837: + case BoardType::RaspberryPi_bcm2836: + case BoardType::RaspberryPi_bcm2835: + return "/dev/i2c-1"; + case BoardType::Unknown: + case BoardType::RaspberryPi_Unknown: + default: + return "/dev/i2c-0"; + } + } +}; + +class I2CDevice { +public: + I2CDevice(I2CBus &bus, unsigned char addr) : _addr(addr), _bus(&bus) {} int begin_transaction(unsigned char id) { if (transaction) { @@ -67,9 +90,6 @@ class I2CDevice { } int send(unsigned char reg, unsigned char data) { - std::lock_guard lock(_bus_mutex); - int res = ioctl(_file, I2C_SLAVE, _addr); - if (res < 0) return res; if (transaction) { if (reg != transaction_id) { return -1; @@ -85,26 +105,17 @@ class I2CDevice { transaction_buffer_length++; return res; } else { - return i2c_smbus_write_byte_data(_file, reg, data); + return _bus->send(_addr, reg, data); } } int read(unsigned char reg, unsigned char length, unsigned char *values) { - std::lock_guard lock(_bus_mutex); - int res = ioctl(_file, I2C_SLAVE, _addr); - if (res < 0) return res; - return i2c_smbus_read_i2c_block_data(_file, reg, length, values); + return _bus->read(_addr, reg, length, values); } private: - int _file = -1; - const char *_bus = nullptr; - unsigned char _addr = 0; - - // Static map of open bus file descriptors - static std::map _bus_fds; - static std::map _bus_refcount; - static std::mutex _bus_mutex; + I2CBus *_bus; + unsigned char _addr; bool transaction = false; unsigned char transaction_id = 0; @@ -127,22 +138,11 @@ class I2CDevice { } int send_transaction() { - return i2c_smbus_write_i2c_block_data( - _file, transaction_id, transaction_buffer_length, transaction_buffer); - } - - void closeBusIfUnused() { - std::lock_guard lock(_bus_mutex); - if (!_bus) return; - auto it = _bus_refcount.find(_bus); - if (it != _bus_refcount.end()) { - it->second--; - if (it->second <= 0) { - close(_bus_fds[_bus]); - _bus_fds.erase(_bus); - _bus_refcount.erase(it); - } - } + return _bus->send_transaction( + _addr, transaction_id, transaction_buffer_length, transaction_buffer); } }; + +static I2CBus Bus; + #endif // I2CD_H \ No newline at end of file diff --git a/main.cpp b/main.cpp index a27e07bad..246b843e4 100644 --- a/main.cpp +++ b/main.cpp @@ -408,7 +408,8 @@ void ui_state_machine() { // Setup Function // ====================== #include "ads1115.h" -ADS1115 adc(0x48); +I2CBus bus(); +ADS1115 adc(bus, 0x48); #if defined(ARDUINO) void do_setup() { /* Clear WDT reset flag. */ @@ -463,7 +464,6 @@ void do_setup() { os.switch_special_station(sid, 0); } - adc.begin(); os.button_timeout = LCD_BACKLIGHT_TIMEOUT; } @@ -488,6 +488,7 @@ ISR(WDT_vect) void initialize_otf(); void do_setup() { + bus.begin() initialiseEpoch(); // initialize time reference for millis() and micros() os.begin(); // OpenSprinkler init os.options_setup(); // Setup options From dd4e7339affb5b1a87f831371ab8e7c5493674f7 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 10 Jul 2025 11:26:57 -0400 Subject: [PATCH 015/115] Working ads 1115 impl --- SSD1306Display.h | 4 +--- ads1115.cpp | 28 +++++++++++++++------------- ads1115.h | 3 +++ i2cd.cpp | 4 +++- i2cd.h | 40 ++++++++++++++++++++++++++++++++++++++-- main.cpp | 13 ++++++++----- 6 files changed, 68 insertions(+), 24 deletions(-) diff --git a/SSD1306Display.h b/SSD1306Display.h index ca7339e8c..4c158cf9e 100644 --- a/SSD1306Display.h +++ b/SSD1306Display.h @@ -171,7 +171,7 @@ class SSD1306Display : public SSD1306 { class SSD1306Display { public: - SSD1306Display(uint8_t addr, uint8_t _sda, uint8_t _scl) { + SSD1306Display(uint8_t addr, uint8_t _sda, uint8_t _scl) : i2c(Bus, addr) { cx = 0; cy = 0; for (uint8_t i = 0; i < NUM_CUSTOM_ICONS; i++) @@ -181,8 +181,6 @@ class SSD1306Display { height = 64; width = 128; - - i2c = I2CDevice(Bus, addr); } ~SSD1306Display() { diff --git a/ads1115.cpp b/ads1115.cpp index e9d422959..1b09c19cf 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -36,34 +36,36 @@ uint16_t ADS1115::_read_register(uint8_t reg) { } #else // OSPI -ADS1115::ADS1115(uint8_t address, I2CBus& bus) : _address(address), i2c(&bus, address) {} +ADS1115::ADS1115(uint8_t address, I2CBus& bus) : _address(address), _i2c(bus, address) {} ADS1115::ADS1115(uint8_t address) : ADS1115(address, Bus) {} bool ADS1115::begin() { - // if (this->_i2c.begin(_address) < 0) { - // return false; - // } + if ((this->_address < 0x48) || (this->_address > 0x4B)) { + return false; + } + if (this->_i2c.detect() < 0) { + return false; + } return true; } void ADS1115::_write_register(uint8_t reg, uint16_t value) { - this->_i2c.begin_transaction(reg); - this->_i2c.send(reg, value >> 8); - this->_i2c.send(reg, value & 0xFF); - this->_i2c.end_transaction(); + this->_i2c.send_word(reg, this->swap_reg(value)); } uint16_t ADS1115::_read_register(uint8_t reg) { - uint8_t values[2]; - printf("res: %d \n", this->_i2c.read(reg, 2, values)); - return values[0] << 8 | values[1]; + return this->swap_reg((uint16_t) (this->_i2c.read_word(reg) & 0xFFFF)); } #endif int16_t ADS1115::get_pin_value(uint8_t pin) { + #if defined(ARDUINO) + uint32_t start; + #else + ulong start; + #endif this->request_pin(pin); - - uint32_t start = millis(); + start = millis(); while (this->is_busy()) { // if ((millis() - start) > 11) { if ((millis() - start) > 18) { diff --git a/ads1115.h b/ads1115.h index e30dc0fee..69fa926ff 100644 --- a/ads1115.h +++ b/ads1115.h @@ -51,6 +51,9 @@ ADS1115(uint8_t address); TwoWire *_wire; #else I2CDevice _i2c; + uint16_t swap_reg(uint16_t val) { + return (val << 8) | (val >> 8); + } #endif void _write_register(uint8_t reg, uint16_t value); diff --git a/i2cd.cpp b/i2cd.cpp index c38918b8a..3bc67916f 100644 --- a/i2cd.cpp +++ b/i2cd.cpp @@ -1,3 +1,5 @@ +#if defined(OSPI) #include "i2cd.h" -I2CBus Bus; \ No newline at end of file +I2CBus Bus; +#endif \ No newline at end of file diff --git a/i2cd.h b/i2cd.h index 77e0447b2..0eefe37e1 100644 --- a/i2cd.h +++ b/i2cd.h @@ -32,7 +32,7 @@ class I2CBus { return i2c_smbus_write_byte_data(_file, reg, data); } - int send_transaction(unsigned char addr, unsigned char transaction_id, unsigned char *transaction_buffer, unsigned char transaction_buffer_length) { + int send_transaction(unsigned char addr, unsigned char transaction_id, unsigned char transaction_buffer_length, unsigned char *transaction_buffer) { int res = ioctl(_file, I2C_SLAVE, addr); if (res < 0) return -1; return i2c_smbus_write_i2c_block_data( @@ -45,6 +45,30 @@ class I2CBus { return i2c_smbus_read_i2c_block_data(_file, reg, length, values); } + int send_word(unsigned char addr, unsigned char reg, unsigned short data) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_write_word_data(_file, reg, data); + } + + int read_word(unsigned char addr, unsigned char reg) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_read_word_data(_file, reg); + } + + int detect(unsigned char addr) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + + res = i2c_smbus_read_byte(_file); + if (res < 0) { + return res; + } else { + return 0; + } + } + private: int _file = -1; @@ -68,6 +92,10 @@ class I2CDevice { public: I2CDevice(I2CBus &bus, unsigned char addr) : _addr(addr), _bus(&bus) {} + bool detect() { + return _bus->detect(_addr); + } + int begin_transaction(unsigned char id) { if (transaction) { return -1; @@ -113,6 +141,14 @@ class I2CDevice { return _bus->read(_addr, reg, length, values); } + int send_word(unsigned char reg, unsigned short data) { + return _bus->send_word(_addr, reg, data); + } + + int read_word(unsigned char reg) { + return _bus->read_word(_addr, reg); + } + private: I2CBus *_bus; unsigned char _addr; @@ -143,6 +179,6 @@ class I2CDevice { } }; -static I2CBus Bus; +extern I2CBus Bus; #endif // I2CD_H \ No newline at end of file diff --git a/main.cpp b/main.cpp index 246b843e4..3156a2c5f 100644 --- a/main.cpp +++ b/main.cpp @@ -408,10 +408,10 @@ void ui_state_machine() { // Setup Function // ====================== #include "ads1115.h" -I2CBus bus(); -ADS1115 adc(bus, 0x48); +ADS1115 adc(0x48); #if defined(ARDUINO) void do_setup() { + adc.begin(); /* Clear WDT reset flag. */ #if defined(ESP8266) WiFi.persistent(false); @@ -488,7 +488,8 @@ ISR(WDT_vect) void initialize_otf(); void do_setup() { - bus.begin() + Bus.begin(); + adc.begin(); initialiseEpoch(); // initialize time reference for millis() and micros() os.begin(); // OpenSprinkler init os.options_setup(); // Setup options @@ -556,9 +557,11 @@ void do_loop() } int16_t val = adc.get_pin_value(0); - printf("Pin 0: " PRId16 " - %fmV.", val, ((float) val) * adc.get_scale_factor(0)); + Serial.printf("Pin 0: %" PRId16 " - %fmV.\n", val, ((float) val) * adc.get_scale_factor(0)); + // printf("Pin 0: %" PRId16 " - %fmV.\n", val, ((float) val) * adc.get_scale_factor(0)); val = adc.get_pin_value(1); - printf("Pin 0: " PRId16 " - %fmV.", val, ((float) val) * adc.get_scale_factor(0)); + Serial.printf("Pin 1: %" PRId16 " - %fmV.\n", val, ((float) val) * adc.get_scale_factor(1)); + // printf("Pin 1: %" PRId16 " - %fmV.\n", val, ((float) val) * adc.get_scale_factor(1)); static time_os_t last_time = 0; From b6de2dde0bc30c93fed78f91516d5ba2d0f6f2f3 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 10 Jul 2025 12:26:14 -0400 Subject: [PATCH 016/115] test smt50 --- main.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/main.cpp b/main.cpp index 3156a2c5f..1361c9139 100644 --- a/main.cpp +++ b/main.cpp @@ -556,12 +556,9 @@ void do_loop() } } - int16_t val = adc.get_pin_value(0); - Serial.printf("Pin 0: %" PRId16 " - %fmV.\n", val, ((float) val) * adc.get_scale_factor(0)); - // printf("Pin 0: %" PRId16 " - %fmV.\n", val, ((float) val) * adc.get_scale_factor(0)); - val = adc.get_pin_value(1); - Serial.printf("Pin 1: %" PRId16 " - %fmV.\n", val, ((float) val) * adc.get_scale_factor(1)); - // printf("Pin 1: %" PRId16 " - %fmV.\n", val, ((float) val) * adc.get_scale_factor(1)); + float temp = (((float)adc.get_pin_value(0)) * adc.get_scale_factor(0) * 0.1) - 50.0; + float moisture = ((float)adc.get_pin_value(1)) * adc.get_scale_factor(1) * (50.0/3000.0); + Serial.printf("Temp: %f - moisture: %f\n", temp, moisture); static time_os_t last_time = 0; From dfb26233b4e5c08b3dc22724e8a43e7c00df8d37 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 10 Jul 2025 17:20:50 -0400 Subject: [PATCH 017/115] add sensor class --- Makefile | 2 +- ads1115.cpp | 45 ++++++++++++++--------- ads1115.h | 33 +++++++++-------- analog.h | 15 -------- build.sh | 2 +- sensor.cpp | 79 +++++++++++++++++++++++++++++++++++++++++ sensor.h | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 228 insertions(+), 48 deletions(-) delete mode 100644 analog.h create mode 100644 sensor.cpp create mode 100644 sensor.h diff --git a/Makefile b/Makefile index 9e739cb9f..97b84e6ab 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LD=$(CXX) LIBS=pthread mosquitto ssl crypto i2c gpiod LDFLAGS=$(addprefix -l,$(LIBS)) BINARY=OpenSprinkler -SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp i2cd.cpp ads1115.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) +SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp i2cd.cpp sensor.cpp ads1115.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) HEADERS=$(wildcard *.h) $(wildcard *.hpp) OBJECTS=$(addsuffix .o,$(basename $(SOURCES))) diff --git a/ads1115.cpp b/ads1115.cpp index 1b09c19cf..0c2c78b7a 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -8,7 +8,7 @@ bool ADS1115::begin() { if ((this->_address < 0x48) || (this->_address > 0x4B)) { return false; } - this-> _wire->beginTransmission(_address); + this->_wire->beginTransmission(_address); return (this->_wire->endTransmission() == 0); } @@ -16,8 +16,8 @@ bool ADS1115::begin() { void ADS1115::_write_register(uint8_t reg, uint16_t value) { this->_wire->beginTransmission(this->_address); this->_wire->write(reg); - this->_wire->write((uint8_t) (value >> 8)); - this->_wire->write((uint8_t) (value & 0xFF)); + this->_wire->write((uint8_t)(value >> 8)); + this->_wire->write((uint8_t)(value & 0xFF)); this->_wire->endTransmission(); } @@ -25,9 +25,9 @@ uint16_t ADS1115::_read_register(uint8_t reg) { this->_wire->beginTransmission(this->_address); this->_wire->write(reg); if (!this->_wire->endTransmission()) { - if (this->_wire->requestFrom((int) _address, (int) 2) == 2) { - uint16_t val = ((uint16_t) this->_wire->read()) << 8; - val += (uint16_t) this->_wire->read(); + if (this->_wire->requestFrom((int)_address, (int)2) == 2) { + uint16_t val = ((uint16_t)this->_wire->read()) << 8; + val += (uint16_t)this->_wire->read(); return val; } } @@ -54,33 +54,44 @@ void ADS1115::_write_register(uint8_t reg, uint16_t value) { } uint16_t ADS1115::_read_register(uint8_t reg) { - return this->swap_reg((uint16_t) (this->_i2c.read_word(reg) & 0xFFFF)); + return this->swap_reg((uint16_t)(this->_i2c.read_word(reg) & 0xFFFF)); } #endif int16_t ADS1115::get_pin_value(uint8_t pin) { - #if defined(ARDUINO) - uint32_t start; - #else - ulong start; - #endif this->request_pin(pin); - start = millis(); + ulong start = millis(); while (this->is_busy()) { // if ((millis() - start) > 11) { if ((millis() - start) > 18) { return 0; } - #if defined(ARDUINO) +#if defined(ARDUINO) yield(); - #endif +#endif } return this->get_value(); } void ADS1115::request_pin(uint8_t pin) { - uint16_t config = 0x8000 | ((4 + ((uint16_t) pin)) << 12) | 0x0100 | (4 << 5); + uint16_t config = 0x8000 | ((4 + ((uint16_t)pin)) << 12) | 0x0100 | (4 << 5); this->_write_register(0x01, config); -} \ No newline at end of file +} + +ADS1115Sensor::ADS1115Sensor(unsigned long interval, float min, float max, float scale, float offset, char* name, SensorUnit unit, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : +Sensor(interval, min, max, scale, offset, name, unit), +sensors(sensors), +sensor_index(sensor_index), +pin(pin) {} + +void ADS1115Sensor::_update_raw_value() { + if (this->sensors[sensor_index] == nullptr) { + this->value = 0.0; + } + else { + this->value = ((float)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; + } +} + diff --git a/ads1115.h b/ads1115.h index 69fa926ff..4e399c74f 100644 --- a/ads1115.h +++ b/ads1115.h @@ -1,7 +1,7 @@ #pragma once -#include "analog.h" #include +#include #define ADS1115_SCALE_FACTOR (6144.0 / 32768.0) @@ -12,7 +12,7 @@ #include "i2cd.h" #endif -class ADS1115 : AnalogSensor { +class ADS1115 { public: #if defined(ARDUINO) ADS1115(uint8_t address, TwoWire& wire); @@ -23,18 +23,6 @@ ADS1115(uint8_t address); int16_t get_pin_value(uint8_t pin); bool begin(); - uint8_t pin_count() { - return 4; - } - - float get_scale_constant(uint8_t pin) { - return 0; - } - - float get_scale_factor(uint8_t pin) { - return ADS1115_SCALE_FACTOR; - } - int16_t get_value() { return (int16_t) this->_read_register(0x00); } @@ -58,4 +46,21 @@ ADS1115(uint8_t address); void _write_register(uint8_t reg, uint16_t value); uint16_t _read_register(uint8_t reg); +}; + + +class ADS1115Sensor : Sensor { + public: + ADS1115Sensor(unsigned long interval, float min, float max, float scale, float offset, char *name, SensorUnit unit, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + + SensorType get_sensor_type() { + return SensorType::ADS1115; + } + + private: + void _update_raw_value(); + + ADS1115 **sensors; + uint8_t sensor_index; + uint8_t pin; }; \ No newline at end of file diff --git a/analog.h b/analog.h deleted file mode 100644 index 8c16d2a62..000000000 --- a/analog.h +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef ANALOG_H -#define ANALOG_H - -#include - -class AnalogSensor { -public: - virtual ~AnalogSensor() = default; - virtual uint8_t pin_count() = 0; - virtual int16_t get_pin_value(uint8_t pin) = 0; - virtual float get_scale_constant(uint8_t pin) = 0; - virtual float get_scale_factor(uint8_t pin) = 0; -}; - -#endif // ANALOG_H \ No newline at end of file diff --git a/build.sh b/build.sh index b89200eb9..45ed6deeb 100755 --- a/build.sh +++ b/build.sh @@ -67,7 +67,7 @@ else ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp i2cd.cpp ads1115.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB + g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp i2cd.cpp sensor.cpp ads1115.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB fi diff --git a/sensor.cpp b/sensor.cpp new file mode 100644 index 000000000..cd1d3b7d1 --- /dev/null +++ b/sensor.cpp @@ -0,0 +1,79 @@ +#include + +Sensor::Sensor(unsigned long interval, float min, float max, float scale, float offset, char *name, SensorUnit unit) : +interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit) { + strncpy(this->name, name, 32); +} + +void Sensor::poll() { + if ((this->_last_update - millis()) > this->interval) { + this->_update_raw_value(); + if (this->value < this->min) this->value = this->min; + if (this->value > this->max) this->value = this->max; + this->value = (this->value * this->scale) + this->offset; + this->_last_update = millis(); + } +} + +EnsembleSensor::EnsembleSensor(unsigned long interval, float min, float max, float scale, float offset, char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : +Sensor(interval, min, max, scale, offset, name, unit), +sensors(sensors), +sensor_mask(sensor_mask), +action(action) {} + +void EnsembleSensor::_update_raw_value() { + float inital; + uint8_t count = 0; + switch (this->action) { + case EnsembleAction::Min: + inital = this->max; + break; + case EnsembleAction::Max: + inital = this->min; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + inital = 0; + break; + case EnsembleAction::Product: + inital = 1; + break; + } + + uint64_t mask = this->sensor_mask; + uint8_t i = 0; + while (mask) { + if ((mask & 1) && (sensors[i])) { + float value = sensors[i]->value; + + switch (this->action) { + case EnsembleAction::Min: + if (value < inital) inital = value; + break; + case EnsembleAction::Max: + if (value > inital) inital = value; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + inital += value; + break; + case EnsembleAction::Product: + inital *= value; + break; + } + + count += 1; + } + + i += 1; + mask >>= 1; + } + + if (count == 0) { + this->value = 0.0; + } else if (this->action == EnsembleAction::Average) { + this->value = inital / (float) count; + } else { + this->value = inital; + } +} diff --git a/sensor.h b/sensor.h new file mode 100644 index 000000000..4e9e60447 --- /dev/null +++ b/sensor.h @@ -0,0 +1,100 @@ +#ifndef SENSOR_H +#define SENSOR_H + +#include +#if defined(ARDUINO) +#include +#else +#include "utils.h" +#endif +#include + +enum class SensorType { + Constant, + Ensemble, + ADS1115, + Weather, +}; + +enum class SensorUnit { + None, + Celsius, + Fahrenheit, + Kelvin, + Milimeter, + Centieter, + Meter, + Kilometer, + Inch, + Foot, + Mile, + Lux, + Lumen, + Milivolt, + Volt, + Miliamp, + Amp, + Percent, + MilesPerHour, + KilometersPerHour, + MetersPerSection, + DialetricConstant, + PartsPerMillion, + Ohm, + Miliohm, + Kiloohm, + Barr, // Pressure + Kilopascal, + Pascal, + LitersPerSecond, + GallonsPerSecond, +}; + +class Sensor { +public: + Sensor(unsigned long interval, float min, float max, float scale, float offset, char *name, SensorUnit unit); + void poll(); + + unsigned long interval = 1; + float value = 0.0; + float min = 0.0; + float max = 0.0; + float scale = 0.0; + float offset = 0.0; + char name[32] = {0}; + SensorUnit unit = SensorUnit::None; + + SensorType virtual get_sensor_type() = 0; +private: + unsigned long _last_update = 0; + void virtual _update_raw_value() = 0; +}; + +enum class EnsembleAction { + Min, + Max, + Average, + Sum, + Product, +}; + + +class EnsembleSensor : Sensor { + public: + EnsembleSensor(unsigned long interval, float min, float max, float scale, float offset, char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); + + SensorType get_sensor_type() { + return SensorType::Ensemble; + } + + private: + void _update_raw_value(); + + Sensor **sensors; + uint64_t sensor_mask; + EnsembleAction action; +}; + + + +#endif //SENSOR_H \ No newline at end of file From 77ad166e650e778d9df7dc6c30d10a83343e2713 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Fri, 11 Jul 2025 12:36:43 -0400 Subject: [PATCH 018/115] inital setup of sensors and endpoint to get them --- OpenSprinkler.cpp | 32 ++++++++++ OpenSprinkler.h | 15 +++++ ads1115.cpp | 2 +- ads1115.h | 4 +- defines.h | 6 ++ main.cpp | 10 +-- opensprinkler_server.cpp | 134 ++++++++++++++++++++++++++++++++------- opensprinkler_server.h | 3 + sensor.cpp | 9 +-- sensor.h | 8 +-- 10 files changed, 181 insertions(+), 42 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index e51d97d75..600f6604a 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -29,6 +29,7 @@ #include "ArduinoJson.hpp" /** Declare static data members */ +Sensor *OpenSprinkler::sensors[64] = {nullptr}; OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; ConStatus OpenSprinkler::status; @@ -85,6 +86,10 @@ extern const char* user_agent_string; LiquidCrystal OpenSprinkler::lcd; #endif +#if defined(USE_ADS1115) + ADS1115 *OpenSprinkler::ads1115_devices[4] = {nullptr}; +#endif + #if defined(ESP8266) unsigned char OpenSprinkler::state = OS_STATE_INITIAL; unsigned char OpenSprinkler::prev_station_bits[MAX_NUM_BOARDS]; @@ -838,8 +843,25 @@ void OpenSprinkler::begin() { #if defined(ARDUINO) Wire.begin(); // init I2C +#else + Bus.begin(); // init I2C for OSPI +#endif + +#if defined(USE_ADS1115) + for (size_t i = 0; i < 4; i++) { + uint8_t address = 0x48 + i; + if (detect_i2c(address)) { + ads1115_devices[i] = new ADS1115(address); + } + } #endif +// TODO: remove testing + sensors[0] = new ADS1115Sensor(5000, -10.0, 30.0, 0.1, -50.0, "Temperature", SensorUnit::Celsius, ads1115_devices, 0, 0); + sensors[1] = new ADS1115Sensor(5000, 0.0, 100.0, 50.0/3000.0, 0, "Moisture", SensorUnit::Percent, ads1115_devices, 0, 1); + sensors[2] = new EnsembleSensor(5000, 0.0, 100000000.0, 1.0, 0, "VS1", SensorUnit::None, sensors, 0b110011, EnsembleAction::Product); + sensors[3] = new EnsembleSensor(5000, 20.0, 100000000.0, 1.0, 0, "VS2", SensorUnit::None, sensors, 0b110000, EnsembleAction::Product); + hw_type = HW_TYPE_UNKNOWN; hw_rev = 0; @@ -2540,6 +2562,16 @@ void OpenSprinkler::raindelay_stop() { nvdata_save(); } +/** Sensor functions */ +void OpenSprinkler::poll_sensors() { + for (size_t i = 0; i < MAX_SENSORS; i++) { + if (sensors[i]) { + sensors[i]->poll(); + } + } + +} + /** LCD and button functions */ #if defined(USE_DISPLAY) #if defined(ARDUINO) // AVR LCD and button functions diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 8010d2326..c0ff7fc1d 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -76,6 +76,12 @@ #include "SSD1306Display.h" #endif +#include "sensor.h" + +#if defined(USE_ADS1115) + #include "ads1115.h" +#endif + #if defined(ARDUINO) #if defined(ESP8266) extern ESP8266WebServer *update_server; @@ -245,6 +251,12 @@ class OpenSprinkler { static LiquidCrystal lcd; // 16x2 character LCD #endif +#if defined(USE_ADS1115) + static ADS1115 *ads1115_devices[4]; +#endif + + static Sensor *sensors[MAX_SENSORS]; + #if defined(OSPI) static unsigned char pin_sr_data; // RPi shift register data pin to handle RPi rev. 1 #endif @@ -372,6 +384,9 @@ class OpenSprinkler { static OTCConfig otc; #endif + // -- Sensor functions + static void poll_sensors(); + // -- LCD functions #if defined(USE_DISPLAY) static void lcd_print_time(time_os_t t); // print current time diff --git a/ads1115.cpp b/ads1115.cpp index 0c2c78b7a..c094c94a0 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -80,7 +80,7 @@ void ADS1115::request_pin(uint8_t pin) { this->_write_register(0x01, config); } -ADS1115Sensor::ADS1115Sensor(unsigned long interval, float min, float max, float scale, float offset, char* name, SensorUnit unit, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : +ADS1115Sensor::ADS1115Sensor(unsigned long interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : Sensor(interval, min, max, scale, offset, name, unit), sensors(sensors), sensor_index(sensor_index), diff --git a/ads1115.h b/ads1115.h index 4e399c74f..444974815 100644 --- a/ads1115.h +++ b/ads1115.h @@ -49,9 +49,9 @@ ADS1115(uint8_t address); }; -class ADS1115Sensor : Sensor { +class ADS1115Sensor : public Sensor { public: - ADS1115Sensor(unsigned long interval, float min, float max, float scale, float offset, char *name, SensorUnit unit, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(unsigned long interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); SensorType get_sensor_type() { return SensorType::ADS1115; diff --git a/defines.h b/defines.h index 04124562c..c34bf703a 100755 --- a/defines.h +++ b/defines.h @@ -136,6 +136,8 @@ typedef unsigned long ulong; #define STATION_NAME_SIZE 32 // maximum number of characters in each station name #define MAX_SOPTS_SIZE 320 // maximum string option size +#define MAX_SENSORS 64 + #define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) /** Default string option values */ @@ -432,6 +434,8 @@ enum { #define USE_DISPLAY #define USE_SSD1306 + + #define USE_ADS1115 #elif defined(OSPI) // for OSPi @@ -457,6 +461,8 @@ enum { #define USE_DISPLAY #define USE_SSD1306 + #define USE_ADS1115 + #else // for demo / simulation // use fake hardware pins #if defined(DEMO) diff --git a/main.cpp b/main.cpp index 1361c9139..fedc144be 100644 --- a/main.cpp +++ b/main.cpp @@ -407,11 +407,8 @@ void ui_state_machine() { // ====================== // Setup Function // ====================== -#include "ads1115.h" -ADS1115 adc(0x48); #if defined(ARDUINO) void do_setup() { - adc.begin(); /* Clear WDT reset flag. */ #if defined(ESP8266) WiFi.persistent(false); @@ -488,8 +485,6 @@ ISR(WDT_vect) void initialize_otf(); void do_setup() { - Bus.begin(); - adc.begin(); initialiseEpoch(); // initialize time reference for millis() and micros() os.begin(); // OpenSprinkler init os.options_setup(); // Setup options @@ -556,10 +551,7 @@ void do_loop() } } - float temp = (((float)adc.get_pin_value(0)) * adc.get_scale_factor(0) * 0.1) - 50.0; - float moisture = ((float)adc.get_pin_value(1)) * adc.get_scale_factor(1) * (50.0/3000.0); - Serial.printf("Temp: %f - moisture: %f\n", temp, moisture); - + os.poll_sensors(); static time_os_t last_time = 0; static ulong last_minute = 0; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 75bf20aa0..7e2c7fccd 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2071,6 +2071,39 @@ void server_json_debug(OTF_PARAMS_DEF) { handle_return(HTML_OK); } +void server_json_sensor_main() { + bfill.emit_p(PSTR("\"sn\":[")); + unsigned char i; + + uint8_t sensor_count = 0; + + for (i=0;iname, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, sensor->value); + sensor_count += 1; + } + } + bfill.emit_p(PSTR("],\"count\":$D}"), sensor_count); +} + +/** Sensor status */ +void server_json_sensor(OTF_PARAMS_DEF) +{ +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{")); + server_json_sensor_main(); + handle_return(HTML_OK); +} + /* // fill ESP8266 flash with some dummy files void server_fill_files(OTF_PARAMS_DEF) { @@ -2101,6 +2134,63 @@ typedef void (*URLHandler)(OTF_PARAMS_DEF); * The order must exactly match the order of the * handler functions below */ + +#if defined(USE_OTF) +const char *uris[] PROGMEM = { + "cv", + "jc", + "dp", + "cp", + "cr", + "mp", + "up", + "jp", + "co", + "jo", + "sp", + "js", + "cm", + "cs", + "jn", + "je", + "jl", + "dl", + "su", + "cu", + "ja", + "pq", + "db", + "jsr" +}; + +// Server function handlers +URLHandler urls[] = { + server_change_values, // cv + server_json_controller, // jc + server_delete_program, // dp + server_change_program, // cp + server_change_runonce, // cr + server_manual_program, // mp + server_moveup_program, // up + server_json_programs, // jp + server_change_options, // co + server_json_options, // jo + server_change_password, // sp + server_json_status, // js + server_change_manual, // cm + server_change_stations, // cs + server_json_stations, // jn + server_json_station_special,// je + server_json_log, // jl + server_delete_log, // dl + server_view_scripturl, // su + server_change_scripturl,// cu + server_json_all, // ja + server_pause_queue, // pq + server_json_debug, // db + server_json_sensor // jsr +}; +#else const char _url_keys[] PROGMEM = "cv" "jc" @@ -2129,7 +2219,6 @@ const char _url_keys[] PROGMEM = //"ff" #endif ; - // Server function handlers URLHandler urls[] = { server_change_values, // cv @@ -2159,6 +2248,7 @@ URLHandler urls[] = { //server_fill_files, #endif }; +#endif // handle Ethernet request #if defined(ESP8266) @@ -2238,14 +2328,14 @@ void start_server_client() { otf->on("/update", on_sta_update, OTF::HTTP_GET); // handle firmware update update_server->on("/update", HTTP_POST, on_sta_upload_fin, on_sta_upload); + char uri_buf[10] = {0}; + uri_buf[0] = '/'; + // set up all other handlers - char uri[4]; - uri[0]='/'; - uri[3]=0; for(unsigned char i=0;ion(uri, urls[i]); + strncpy_P(uri_buf+1, uris[i], 9); + uri_buf[9] = 0; + otf->on(uri_buf, urls[i]); } callback_initialized = true; } @@ -2268,15 +2358,15 @@ void start_server_ap() { otf->onMissingPage(on_ap_home); update_server->begin(); - // set up all other handlers - char uri[4]; - uri[0]='/'; - uri[3]=0; - for(unsigned char i=0;ion(uri, urls[i]); - } + char uri_buf[10] = {0}; + uri_buf[0] = '/'; + + // set up all other handlers + for(unsigned char i=0;ion(uri_buf, urls[i]); + } os.lcd.setCursor(0, -1); os.lcd.print(F("OSAP:")); @@ -2296,14 +2386,14 @@ void initialize_otf() { otf->on("/", server_home); // handle home page otf->on("/index.html", server_home); + char uri_buf[10] = {0}; + uri_buf[0] = '/'; + // set up all other handlers - char uri[4]; - uri[0]='/'; - uri[3]=0; for(unsigned char i=0;ion(uri, urls[i]); + strncpy(uri_buf+1, uris[i], 9); + uri_buf[9] = 0; + otf->on(uri_buf, urls[i]); } callback_initialized = true; } diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 1635236c4..7a7493981 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -64,6 +64,9 @@ class BufferFiller { // itoa(va_arg(ap, int), (char*) ptr, 10); // ray snprintf((char*) ptr, len - position(), "%d", va_arg(ap, int)); break; + case 'E': //Double + sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); + break; case 'L': // ultoa(va_arg(ap, uint32_t), (char*) ptr, 10); snprintf((char*) ptr, len - position(), "%lu", (unsigned long) va_arg(ap, uint32_t)); diff --git a/sensor.cpp b/sensor.cpp index cd1d3b7d1..fe373f1ff 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,21 +1,22 @@ #include -Sensor::Sensor(unsigned long interval, float min, float max, float scale, float offset, char *name, SensorUnit unit) : +Sensor::Sensor(unsigned long interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit) : interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit) { strncpy(this->name, name, 32); + this->name[33] = 0; } void Sensor::poll() { - if ((this->_last_update - millis()) > this->interval) { + if ((millis() - this->_last_update) > this->interval) { this->_update_raw_value(); + this->value = (this->value * this->scale) + this->offset; if (this->value < this->min) this->value = this->min; if (this->value > this->max) this->value = this->max; - this->value = (this->value * this->scale) + this->offset; this->_last_update = millis(); } } -EnsembleSensor::EnsembleSensor(unsigned long interval, float min, float max, float scale, float offset, char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : +EnsembleSensor::EnsembleSensor(unsigned long interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : Sensor(interval, min, max, scale, offset, name, unit), sensors(sensors), sensor_mask(sensor_mask), diff --git a/sensor.h b/sensor.h index 4e9e60447..742391c98 100644 --- a/sensor.h +++ b/sensor.h @@ -52,7 +52,7 @@ enum class SensorUnit { class Sensor { public: - Sensor(unsigned long interval, float min, float max, float scale, float offset, char *name, SensorUnit unit); + Sensor(unsigned long interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit); void poll(); unsigned long interval = 1; @@ -61,7 +61,7 @@ class Sensor { float max = 0.0; float scale = 0.0; float offset = 0.0; - char name[32] = {0}; + char name[33] = {0}; SensorUnit unit = SensorUnit::None; SensorType virtual get_sensor_type() = 0; @@ -79,9 +79,9 @@ enum class EnsembleAction { }; -class EnsembleSensor : Sensor { +class EnsembleSensor : public Sensor { public: - EnsembleSensor(unsigned long interval, float min, float max, float scale, float offset, char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); + EnsembleSensor(unsigned long interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); SensorType get_sensor_type() { return SensorType::Ensemble; From 86fef74513e33e73d13158a35e5b6e8e3c25e010 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Fri, 11 Jul 2025 16:35:22 -0400 Subject: [PATCH 019/115] add delete sensor --- opensprinkler_server.cpp | 47 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7e2c7fccd..1dbc3103e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2089,7 +2089,7 @@ void server_json_sensor_main() { } /** Sensor status */ -void server_json_sensor(OTF_PARAMS_DEF) +void server_json_sensors(OTF_PARAMS_DEF) { #if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; @@ -2104,6 +2104,43 @@ void server_json_sensor(OTF_PARAMS_DEF) handle_return(HTML_OK); } +/** + * Delete a sensor + * Command: /dsn?pw=xxx&sid=xxx + * + * pw: password + * sid:staiton index (-1 will delete all programs) + */ +void server_delete_sensor(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) + handle_return(HTML_DATA_MISSING); + + int sid=atoi(tmp_buffer); + if (sid == -1) { + uint8_t i; + for (i=0;i Date: Fri, 11 Jul 2025 16:42:27 -0400 Subject: [PATCH 020/115] move sensors over to define to allow them to be disabled --- OpenSprinkler.cpp | 4 ++++ OpenSprinkler.h | 6 ++++++ defines.h | 2 ++ main.cpp | 2 ++ opensprinkler_server.cpp | 24 +++++++++++++++++------- 5 files changed, 31 insertions(+), 7 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 600f6604a..ea5684bc6 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -29,7 +29,9 @@ #include "ArduinoJson.hpp" /** Declare static data members */ +#if defined(USE_SENSORS) Sensor *OpenSprinkler::sensors[64] = {nullptr}; +#endif OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; ConStatus OpenSprinkler::status; @@ -2562,6 +2564,7 @@ void OpenSprinkler::raindelay_stop() { nvdata_save(); } +#if defined(USE_SENSORS) /** Sensor functions */ void OpenSprinkler::poll_sensors() { for (size_t i = 0; i < MAX_SENSORS; i++) { @@ -2571,6 +2574,7 @@ void OpenSprinkler::poll_sensors() { } } +#endif /** LCD and button functions */ #if defined(USE_DISPLAY) diff --git a/OpenSprinkler.h b/OpenSprinkler.h index c0ff7fc1d..d2d78ed02 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -76,7 +76,9 @@ #include "SSD1306Display.h" #endif +#if defined(USE_SENSORS) #include "sensor.h" +#endif #if defined(USE_ADS1115) #include "ads1115.h" @@ -255,7 +257,9 @@ class OpenSprinkler { static ADS1115 *ads1115_devices[4]; #endif +#if defined(USE_SENSORS) static Sensor *sensors[MAX_SENSORS]; +#endif #if defined(OSPI) static unsigned char pin_sr_data; // RPi shift register data pin to handle RPi rev. 1 @@ -385,7 +389,9 @@ class OpenSprinkler { #endif // -- Sensor functions + #if defined(USE_SENSORS) static void poll_sensors(); + #endif // -- LCD functions #if defined(USE_DISPLAY) diff --git a/defines.h b/defines.h index c34bf703a..b02e8bde5 100755 --- a/defines.h +++ b/defines.h @@ -436,6 +436,7 @@ enum { #define USE_SSD1306 #define USE_ADS1115 + #define USE_SENSORS #elif defined(OSPI) // for OSPi @@ -462,6 +463,7 @@ enum { #define USE_SSD1306 #define USE_ADS1115 + #define USE_SENSORS #else // for demo / simulation // use fake hardware pins diff --git a/main.cpp b/main.cpp index fedc144be..592eb5d8f 100644 --- a/main.cpp +++ b/main.cpp @@ -551,7 +551,9 @@ void do_loop() } } + #if defined(USE_SENSORS) os.poll_sensors(); + #endif static time_os_t last_time = 0; static ulong last_minute = 0; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 1dbc3103e..5853971d2 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2003,6 +2003,10 @@ void server_json_all(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); bfill.emit_p(PSTR(",\"stations\":{")); server_json_stations_main(OTF_PARAMS); + #if defined(USE_SENSORS) + bfill.emit_p(PSTR(",\"sensors\":{")); + server_json_sensors_main(OTF_PARAMS); + #endif bfill.emit_p(PSTR("}")); handle_return(HTML_OK); } @@ -2071,7 +2075,8 @@ void server_json_debug(OTF_PARAMS_DEF) { handle_return(HTML_OK); } -void server_json_sensor_main() { +#if defined(USE_SENSORS) +void server_json_sensors_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"sn\":[")); unsigned char i; @@ -2100,7 +2105,7 @@ void server_json_sensors(OTF_PARAMS_DEF) #endif bfill.emit_p(PSTR("{")); - server_json_sensor_main(); + server_json_sensors_main(OTF_PARAMS); handle_return(HTML_OK); } @@ -2140,6 +2145,7 @@ void server_delete_sensor(OTF_PARAMS_DEF) { handle_return(HTML_SUCCESS); } +#endif /* // fill ESP8266 flash with some dummy files @@ -2197,9 +2203,11 @@ const char *uris[] PROGMEM = { "ja", "pq", "db", - "jsn" - "csn" - "dsn" + #if defined(USE_SENSORS) + "jsn", + "csn", + "dsn", + #endif }; // Server function handlers @@ -2227,9 +2235,11 @@ URLHandler urls[] = { server_json_all, // ja server_pause_queue, // pq server_json_debug, // db + #if defined(USE_SENSORS) server_json_sensors, // jsn - server_change_sensor, // csn - server_delete_sensor // dsn + server_change_sensor, // csn + server_delete_sensor, // dsn + #endif }; #else const char _url_keys[] PROGMEM = From 5ddcba97722c0b5cd424675676b9b334b8fd9b90 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Tue, 15 Jul 2025 16:07:02 -0400 Subject: [PATCH 021/115] current analog setup with endpoints --- OpenSprinkler.cpp | 19 ++++ OpenSprinkler.h | 2 + ads1115.cpp | 7 +- ads1115.h | 8 +- defines.h | 1 + opensprinkler_server.cpp | 182 +++++++++++++++++++++++++++++++++++++++ sensor.cpp | 113 ++++++++++++++++++++++-- sensor.h | 46 +++++++--- 8 files changed, 355 insertions(+), 23 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index ea5684bc6..187acf7f4 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2566,6 +2566,25 @@ void OpenSprinkler::raindelay_stop() { #if defined(USE_SENSORS) /** Sensor functions */ +void OpenSprinkler::load_sensors() { + for (size_t i = 0; i < MAX_SENSORS; i++) { + if (sensors[i]) { + sensors[i]->poll(); + } + } + +} + +void OpenSprinkler::save_sensors() { + for (size_t i = 0; i < MAX_SENSORS; i++) { + if (sensors[i]) { + file_write_block(SENSORS_FILENAME, tmp_buffer, + (uint32_t)sid*sizeof(StationData)+offsetof(StationData,type), STATION_SPECIAL_DATA_SIZE+1); + } + } + +} + void OpenSprinkler::poll_sensors() { for (size_t i = 0; i < MAX_SENSORS; i++) { if (sensors[i]) { diff --git a/OpenSprinkler.h b/OpenSprinkler.h index d2d78ed02..f95be2955 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -390,6 +390,8 @@ class OpenSprinkler { // -- Sensor functions #if defined(USE_SENSORS) + static void load_sensors(); + static void save_sensors(); static void poll_sensors(); #endif diff --git a/ads1115.cpp b/ads1115.cpp index c094c94a0..48f15f0c1 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -80,7 +80,7 @@ void ADS1115::request_pin(uint8_t pin) { this->_write_register(0x01, config); } -ADS1115Sensor::ADS1115Sensor(unsigned long interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : +ADS1115Sensor::ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : Sensor(interval, min, max, scale, offset, name, unit), sensors(sensors), sensor_index(sensor_index), @@ -91,7 +91,10 @@ void ADS1115Sensor::_update_raw_value() { this->value = 0.0; } else { - this->value = ((float)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; + this->value = ((double)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; } } +int ADS1115Sensor::_serialize_internal(char *buf) { + return 0; +} \ No newline at end of file diff --git a/ads1115.h b/ads1115.h index 444974815..7a531e159 100644 --- a/ads1115.h +++ b/ads1115.h @@ -51,16 +51,18 @@ ADS1115(uint8_t address); class ADS1115Sensor : public Sensor { public: - ADS1115Sensor(unsigned long interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); SensorType get_sensor_type() { return SensorType::ADS1115; } + uint8_t sensor_index; + uint8_t pin; + private: void _update_raw_value(); + int _serialize_internal(char *buf); ADS1115 **sensors; - uint8_t sensor_index; - uint8_t pin; }; \ No newline at end of file diff --git a/defines.h b/defines.h index b02e8bde5..ce8f22c09 100755 --- a/defines.h +++ b/defines.h @@ -55,6 +55,7 @@ typedef unsigned long ulong; #define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files +#define SENSORS_FILENAME "sens.dat" // used to indicate the completion of all files /** Station macro defines */ #define STN_TYPE_STANDARD 0x00 // standard solenoid station diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 5853971d2..30fa7a0e1 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2089,6 +2089,12 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"id\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E}"), i, sensor->name, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, sensor->value); sensor_count += 1; } + + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } } bfill.emit_p(PSTR("],\"count\":$D}"), sensor_count); } @@ -2109,6 +2115,182 @@ void server_json_sensors(OTF_PARAMS_DEF) handle_return(HTML_OK); } +void server_change_sensor(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + unsigned char i; + + // ProgramStruct prog; + + // parse program index + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) handle_return(HTML_DATA_MISSING); + + char *end; + long sid = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + + if (!(sid >= -1 && sid < MAX_SENSORS)) handle_return(HTML_DATA_OUTOFBOUND); + + if (sid == -1 ) { + while (++sid < MAX_SENSORS) { + if (os.sensors[sid]) { + break; + } + } + + if (sid == MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + } + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); + + ulong type_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (type_raw >= (ulong)SensorType::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + + SensorType sensor_type = static_cast(type_raw); + + char name[SENSOR_NAME_LEN]; + + // parse sensor name + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) { + #if !defined(USE_OTF) + urlDecode(tmp_buffer); + #endif + strReplaceQuoteBackslash(tmp_buffer); + strncpy(name, tmp_buffer, SENSOR_NAME_LEN); + } else if (os.sensors[sid]) { + strncpy(name, os.sensors[sid]->name, SENSOR_NAME_LEN); + } else { + snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", sid); + } + + double min = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) { + min=strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } else if (os.sensors[sid]) { + min = os.sensors[sid]->min; + } + + double max = 100; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) { + max=strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } else if (os.sensors[sid]) { + max = os.sensors[sid]->max; + } + + double scale = 1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { + scale=strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } else if (os.sensors[sid]) { + scale = os.sensors[sid]->scale; + } + + double offset = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) { + offset=strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } else if (os.sensors[sid]) { + offset = os.sensors[sid]->offset; + } + + ulong interval = 1000; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("interval"), true)) { + interval=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } else if (os.sensors[sid]) { + interval = os.sensors[sid]->interval; + } + + if (interval < 1000) handle_return(HTML_DATA_OUTOFBOUND); + + int raw_unit = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("interval"), true)) { + interval=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } else if (os.sensors[sid]) { + interval = os.sensors[sid]->interval; + } + + SensorUnit unit = SensorUnit::None; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { + ulong unit_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (unit_raw >= (ulong)SensorUnit::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + unit = static_cast(unit_raw); + } else if (os.sensors[sid]) { + unit = os.sensors[sid]->unit; + } + + switch (sensor_type) { + case SensorType::Ensemble: + uint64_t sensor_mask = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mask"), true)) { + sensor_mask = strtoull(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { + if (EnsembleSensor* e = dynamic_cast(os.sensors[sid])) { + sensor_mask = e->sensor_mask; + } + } + + EnsembleAction action = EnsembleAction::Min; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { + ulong action_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (action_raw >= (ulong)EnsembleAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + action = static_cast(action_raw); + } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { + if (EnsembleSensor* e = dynamic_cast(os.sensors[sid])) { + action = e->action; + } + } + + os.sensors[sid] = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.sensors, sensor_mask, action); + break; + case SensorType::ADS1115: + ulong sensor_index = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("index"), true)) { + sensor_index = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (sensor_index >= 4) handle_return(HTML_DATA_OUTOFBOUND); + } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { + if (ADS1115Sensor* e = dynamic_cast(os.sensors[sid])) { + sensor_index = e->sensor_index; + } + } + + ulong sensor_pin = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pin"), true)) { + sensor_pin = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (sensor_pin >= 4) handle_return(HTML_DATA_OUTOFBOUND); + } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { + if (ADS1115Sensor* e = dynamic_cast(os.sensors[sid])) { + sensor_pin = e->pin; + } + } + + os.sensors[sid] = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, os.ads1115_devices, sensor_index, sensor_pin); + break; + // case SensorType::Weather: + // os.weather_update_flag + // break; + default: + case SensorType::MAX_VALUE: + handle_return(HTML_DATA_OUTOFBOUND) + break; + } + + handle_return(HTML_SUCCESS); +} + /** * Delete a sensor * Command: /dsn?pw=xxx&sid=xxx diff --git a/sensor.cpp b/sensor.cpp index fe373f1ff..bcb258d07 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,9 +1,9 @@ #include -Sensor::Sensor(unsigned long interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit) : +Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit) : interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit) { - strncpy(this->name, name, 32); - this->name[33] = 0; + strncpy(this->name, name, SENSOR_NAME_LEN); + this->name[SENSOR_NAME_LEN] = 0; } void Sensor::poll() { @@ -16,14 +16,109 @@ void Sensor::poll() { } } -EnsembleSensor::EnsembleSensor(unsigned long interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : +int write_double(char *buf, double val) { + std::memcpy(buf, &val, sizeof(val)); + return sizeof(val); +} + +int write_ulong(char *buf, ulong val) { + std::memcpy(buf, &val, sizeof(val)); + return sizeof(val); +} + +int Sensor::serialize(char *buf) { + int i = 0; + + buf[i++] = static_cast(this->get_sensor_type()); + memcpy(buf+i, this->name, SENSOR_NAME_LEN); + i += SENSOR_NAME_LEN; + buf[i++] = static_cast(this->unit); + i += write_ulong(buf+i, this->interval); + i += write_double(buf+i, this->scale); + i += write_double(buf+i, this->offset); + i += write_double(buf+i, this->min); + i += write_double(buf+i, this->max); + + i += this->serialize_internal(buf + i); + return i; +} + +EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : Sensor(interval, min, max, scale, offset, name, unit), sensors(sensors), sensor_mask(sensor_mask), action(action) {} void EnsembleSensor::_update_raw_value() { - float inital; + double inital; + uint8_t count = 0; + switch (this->action) { + case EnsembleAction::Min: + inital = this->max; + break; + case EnsembleAction::Max: + inital = this->min; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + inital = 0; + break; + case EnsembleAction::Product: + inital = 1; + break; + } + + uint64_t mask = this->sensor_mask; + uint8_t i = 0; + while (mask) { + if ((mask & 1) && (sensors[i])) { + double value = sensors[i]->value; + + switch (this->action) { + case EnsembleAction::Min: + if (value < inital) inital = value; + break; + case EnsembleAction::Max: + if (value > inital) inital = value; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + inital += value; + break; + case EnsembleAction::Product: + inital *= value; + break; + } + + count += 1; + } + + i += 1; + mask >>= 1; + } + + if (count == 0) { + this->value = 0.0; + } else if (this->action == EnsembleAction::Average) { + this->value = inital / (double) count; + } else { + this->value = inital; + } +} + +int EnsembleSensor::_serialize_internal(char *buf) { + return 0; +} + + +WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : +Sensor(interval, min, max, scale, offset, name, unit), +sensors(sensors), +sensor_mask(sensor_mask), +action(action) {} + +void WeatherSensor::_update_raw_value() { + double inital; uint8_t count = 0; switch (this->action) { case EnsembleAction::Min: @@ -45,7 +140,7 @@ void EnsembleSensor::_update_raw_value() { uint8_t i = 0; while (mask) { if ((mask & 1) && (sensors[i])) { - float value = sensors[i]->value; + double value = sensors[i]->value; switch (this->action) { case EnsembleAction::Min: @@ -73,8 +168,12 @@ void EnsembleSensor::_update_raw_value() { if (count == 0) { this->value = 0.0; } else if (this->action == EnsembleAction::Average) { - this->value = inital / (float) count; + this->value = inital / (double) count; } else { this->value = inital; } } + +int WeatherSensor::_serialize_internal(char *buf) { + return 0; +} \ No newline at end of file diff --git a/sensor.h b/sensor.h index 742391c98..13795dc48 100644 --- a/sensor.h +++ b/sensor.h @@ -9,11 +9,13 @@ #endif #include +#define SENSOR_NAME_LEN 33 + enum class SensorType { - Constant, Ensemble, ADS1115, Weather, + MAX_VALUE, }; enum class SensorUnit { @@ -48,26 +50,29 @@ enum class SensorUnit { Pascal, LitersPerSecond, GallonsPerSecond, + MAX_VALUE, }; class Sensor { public: - Sensor(unsigned long interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit); + Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit); void poll(); + int serialize(char *buf); unsigned long interval = 1; - float value = 0.0; - float min = 0.0; - float max = 0.0; - float scale = 0.0; - float offset = 0.0; - char name[33] = {0}; + double value = 0.0; + double min = 0.0; + double max = 0.0; + double scale = 0.0; + double offset = 0.0; + char name[SENSOR_NAME_LEN] = {0}; SensorUnit unit = SensorUnit::None; SensorType virtual get_sensor_type() = 0; private: unsigned long _last_update = 0; void virtual _update_raw_value() = 0; + int virtual _serialize_internal(char *buf) = 0; }; enum class EnsembleAction { @@ -76,25 +81,44 @@ enum class EnsembleAction { Average, Sum, Product, + MAX_VALUE, }; class EnsembleSensor : public Sensor { public: - EnsembleSensor(unsigned long interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); + EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); SensorType get_sensor_type() { return SensorType::Ensemble; } + uint64_t sensor_mask; + EnsembleAction action; + private: void _update_raw_value(); + int _serialize_internal(char *buf); Sensor **sensors; - uint64_t sensor_mask; - EnsembleAction action; }; +class WeatherSensor : public Sensor { + public: + WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); + + SensorType get_sensor_type() { + return SensorType::Weather; + } + + uint64_t sensor_mask; + EnsembleAction action; + private: + void _update_raw_value(); + int _serialize_internal(char *buf); + + Sensor **sensors; +}; #endif //SENSOR_H \ No newline at end of file From e36c29293a45c8be9d080f362177b1d920c4af8a Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 16 Jul 2025 17:04:00 -0400 Subject: [PATCH 022/115] added logs and saving / loading of sensors from flash --- OpenSprinkler.cpp | 110 ++++-- OpenSprinkler.h | 1 + ads1115.cpp | 18 +- ads1115.h | 5 +- defines.h | 3 + .../OpenThings-Framework-Firmware-Library | 2 +- gpio.h | 1 + opensprinkler_server.cpp | 342 +++++++++++------- sensor.cpp | 89 +++-- sensor.h | 23 +- 10 files changed, 408 insertions(+), 186 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 187acf7f4..a89f4f4d7 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -849,21 +849,6 @@ void OpenSprinkler::begin() { Bus.begin(); // init I2C for OSPI #endif -#if defined(USE_ADS1115) - for (size_t i = 0; i < 4; i++) { - uint8_t address = 0x48 + i; - if (detect_i2c(address)) { - ads1115_devices[i] = new ADS1115(address); - } - } -#endif - -// TODO: remove testing - sensors[0] = new ADS1115Sensor(5000, -10.0, 30.0, 0.1, -50.0, "Temperature", SensorUnit::Celsius, ads1115_devices, 0, 0); - sensors[1] = new ADS1115Sensor(5000, 0.0, 100.0, 50.0/3000.0, 0, "Moisture", SensorUnit::Percent, ads1115_devices, 0, 1); - sensors[2] = new EnsembleSensor(5000, 0.0, 100000000.0, 1.0, 0, "VS1", SensorUnit::None, sensors, 0b110011, EnsembleAction::Product); - sensors[3] = new EnsembleSensor(5000, 20.0, 100000000.0, 1.0, 0, "VS2", SensorUnit::None, sensors, 0b110000, EnsembleAction::Product); - hw_type = HW_TYPE_UNKNOWN; hw_rev = 0; @@ -1139,6 +1124,22 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); #else //DEBUG_PRINTLN(get_runtime_path()); #endif + +#if defined(USE_ADS1115) + for (size_t i = 0; i < 4; i++) { + uint8_t address = 0x48 + i; + if (detect_i2c(address)) { + ads1115_devices[i] = new ADS1115(address); + } + } +#endif + +#if defined(USE_SENSORS) + lcd.clear(); + lcd.setCursor(0,0); + lcd.print(F("Init sensors")); + os.load_sensors(); +#endif } #if defined(ESP8266) @@ -2567,28 +2568,89 @@ void OpenSprinkler::raindelay_stop() { #if defined(USE_SENSORS) /** Sensor functions */ void OpenSprinkler::load_sensors() { - for (size_t i = 0; i < MAX_SENSORS; i++) { - if (sensors[i]) { - sensors[i]->poll(); + ulong pos = 0; + uint8_t index; + + while (true) { + index = 0; + file_read_block(SENSORS_FILENAME, &index, pos++, 1); + if (index >= MAX_SENSORS) break; + uint32_t len = 0; + file_read_block(SENSORS_FILENAME, &len, pos, sizeof(len)); + pos += sizeof(len); + + if (len == 0 || len > TMP_BUFFER_SIZE) break; + + file_read_block(SENSORS_FILENAME, tmp_buffer, pos, len); + + if ((uint8_t)(*tmp_buffer) >= (uint8_t)SensorType::MAX_VALUE) { + os.sensors[index] = nullptr; + continue; } - } + SensorType sensor_type = static_cast(*tmp_buffer); + + switch (sensor_type) { + case SensorType::Ensemble: + os.sensors[index] = new EnsembleSensor(os.sensors, (char*)tmp_buffer); + break; + case SensorType::ADS1115: + os.sensors[index] = new ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + break; + case SensorType::Weather: + os.sensors[index] = new WeatherSensor(os.sensors, (char*)tmp_buffer); + break; + default: + case SensorType::MAX_VALUE: + break; + }; + + pos += len; + } } void OpenSprinkler::save_sensors() { - for (size_t i = 0; i < MAX_SENSORS; i++) { + ulong pos = 0; + for (uint8_t i = 0; i < MAX_SENSORS; i++) { if (sensors[i]) { - file_write_block(SENSORS_FILENAME, tmp_buffer, - (uint32_t)sid*sizeof(StationData)+offsetof(StationData,type), STATION_SPECIAL_DATA_SIZE+1); + file_write_byte(SENSORS_FILENAME, pos++, i); + uint32_t len = sensors[i]->serialize(tmp_buffer); + file_write_block(SENSORS_FILENAME, &len, pos, sizeof(len)); + pos += sizeof(len); + file_write_block(SENSORS_FILENAME, tmp_buffer, pos, len); + pos += len; } } - + + file_write_byte(SENSORS_FILENAME, pos++, 0xFF); + + file_read_block(SENSORS_FILENAME, tmp_buffer, 0, 79); +} + +void OpenSprinkler::log_sensor(uint8_t sid, float value) { + uint16_t next = 0; + file_read_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); + + time_os_t timestamp = now(); + tmp_buffer[0] = sid; + char *ptr = tmp_buffer + 1; + memcpy(ptr, ×tamp, sizeof(timestamp)); + ptr += sizeof(timestamp); + memcpy(ptr, &value, sizeof(value)); + + uint32_t pos = sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE); + file_write_block(SENSORS_LOG_FILENAME, tmp_buffer, pos, SENSOR_LOG_ITEM_SIZE); + + next = (next + 1) % MAX_SENSOR_LOG_COUNT; + file_write_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); } void OpenSprinkler::poll_sensors() { for (size_t i = 0; i < MAX_SENSORS; i++) { if (sensors[i]) { - sensors[i]->poll(); + if (sensors[i]->poll()) { + os.log_sensor(i, sensors[i]->value); + } } } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index f95be2955..50c72d498 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -392,6 +392,7 @@ class OpenSprinkler { #if defined(USE_SENSORS) static void load_sensors(); static void save_sensors(); + void log_sensor(uint8_t sid, float value); static void poll_sensors(); #endif diff --git a/ads1115.cpp b/ads1115.cpp index 48f15f0c1..fa34995b6 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -82,9 +82,9 @@ void ADS1115::request_pin(uint8_t pin) { ADS1115Sensor::ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : Sensor(interval, min, max, scale, offset, name, unit), -sensors(sensors), sensor_index(sensor_index), -pin(pin) {} +pin(pin), +sensors(sensors) {} void ADS1115Sensor::_update_raw_value() { if (this->sensors[sensor_index] == nullptr) { @@ -95,6 +95,16 @@ void ADS1115Sensor::_update_raw_value() { } } -int ADS1115Sensor::_serialize_internal(char *buf) { - return 0; +uint32_t ADS1115Sensor::_serialize_internal(char *buf) { + uint32_t i = 0; + buf[i++] = static_cast(this->sensor_index); + buf[i++] = static_cast(this->pin); + return i; +} + +ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf) { + uint32_t i = Sensor::_deserialize(buf); + this->sensor_index = static_cast(buf[i++]); + this->pin = static_cast(buf[i++]); + this->sensors = sensors; } \ No newline at end of file diff --git a/ads1115.h b/ads1115.h index 7a531e159..f0c3ae526 100644 --- a/ads1115.h +++ b/ads1115.h @@ -1,7 +1,7 @@ #pragma once #include -#include +#include "sensor.h" #define ADS1115_SCALE_FACTOR (6144.0 / 32768.0) @@ -52,6 +52,7 @@ ADS1115(uint8_t address); class ADS1115Sensor : public Sensor { public: ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(ADS1115 **sensors, char *buf); SensorType get_sensor_type() { return SensorType::ADS1115; @@ -62,7 +63,7 @@ class ADS1115Sensor : public Sensor { private: void _update_raw_value(); - int _serialize_internal(char *buf); + uint32_t _serialize_internal(char *buf); ADS1115 **sensors; }; \ No newline at end of file diff --git a/defines.h b/defines.h index ce8f22c09..8b74d1552 100755 --- a/defines.h +++ b/defines.h @@ -56,6 +56,7 @@ typedef unsigned long ulong; #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files #define SENSORS_FILENAME "sens.dat" // used to indicate the completion of all files +#define SENSORS_LOG_FILENAME "senslog.dat" // used to indicate the completion of all files /** Station macro defines */ #define STN_TYPE_STANDARD 0x00 // standard solenoid station @@ -138,6 +139,8 @@ typedef unsigned long ulong; #define MAX_SOPTS_SIZE 320 // maximum string option size #define MAX_SENSORS 64 +#define MAX_SENSOR_LOG_COUNT 32768 +#define SENSOR_LOG_ITEM_SIZE (sizeof(time_os_t) + sizeof(float) + 1) #define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) diff --git a/external/OpenThings-Framework-Firmware-Library b/external/OpenThings-Framework-Firmware-Library index 0f74fa747..4e9306153 160000 --- a/external/OpenThings-Framework-Firmware-Library +++ b/external/OpenThings-Framework-Firmware-Library @@ -1 +1 @@ -Subproject commit 0f74fa747ea98451ab6956be9555227d467b568d +Subproject commit 4e93061538da197e1820c7b325a3c996ff7851ea diff --git a/gpio.h b/gpio.h index d9b028b84..50c1eb3f6 100644 --- a/gpio.h +++ b/gpio.h @@ -45,6 +45,7 @@ class IOEXP { public: IOEXP(uint8_t addr=255) { address = addr; type = IOEXP_TYPE_NONEXIST; } + virtual ~IOEXP() {} virtual void pinMode(uint8_t pin, uint8_t IOMode) { } virtual uint16_t i2c_read(uint8_t reg) { return 0xFFFF; } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 30fa7a0e1..555214d09 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1980,101 +1980,6 @@ void server_pause_queue(OTF_PARAMS_DEF) { handle_return(HTML_SUCCESS); } -/** Output all JSON data, including jc, jp, jo, js, jn */ -void server_json_all(OTF_PARAMS_DEF) { -#if defined(USE_OTF) - if(!process_password(OTF_PARAMS,true)) return; - rewind_ether_buffer(); - print_header(OTF_PARAMS); -#else - print_header(); -#endif - bfill.emit_p(PSTR("{\"settings\":{")); - server_json_controller_main(OTF_PARAMS); - send_packet(OTF_PARAMS); - bfill.emit_p(PSTR(",\"programs\":{")); - server_json_programs_main(OTF_PARAMS); - send_packet(OTF_PARAMS); - bfill.emit_p(PSTR(",\"options\":{")); - server_json_options_main(); - send_packet(OTF_PARAMS); - bfill.emit_p(PSTR(",\"status\":{")); - server_json_status_main(); - send_packet(OTF_PARAMS); - bfill.emit_p(PSTR(",\"stations\":{")); - server_json_stations_main(OTF_PARAMS); - #if defined(USE_SENSORS) - bfill.emit_p(PSTR(",\"sensors\":{")); - server_json_sensors_main(OTF_PARAMS); - #endif - bfill.emit_p(PSTR("}")); - handle_return(HTML_OK); -} - -#if defined(ARDUINO) - -#if defined(OS_AVR) -static int freeHeap () { - extern int __heap_start, *__brkval; - int v; - return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); -} -#endif -#else -#include -static unsigned long freeHeap() { - //return sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE); - struct sysinfo info; - if (sysinfo(&info) == 0) { - return info.freeram; - } else { - return 0; - } -} -#endif - -void server_json_debug(OTF_PARAMS_DEF) { -#if defined(USE_OTF) - rewind_ether_buffer(); - print_header(OTF_PARAMS); -#else - print_header(); -#endif - bfill.emit_p(PSTR("{\"date\":\"$S\",\"time\":\"$S\",\"heap\":$L"), __DATE__, __TIME__, -#if defined(ESP8266) - (unsigned long)ESP.getFreeHeap()); - FSInfo fs_info; - LittleFS.info(fs_info); - bfill.emit_p(PSTR(",\"flash\":$D,\"used\":$D,\"devip\":\"$S\","), fs_info.totalBytes, fs_info.usedBytes, (useEth?eth.localIP():WiFi.localIP()).toString().c_str()); - if(useEth) { - bfill.emit_p(PSTR("\"isW5500\":$D,\"spi_clock\":$L,\"arp_size\":$D}"), eth.isW5500, ETHER_SPI_CLOCK, ARP_TABLE_SIZE); - } else { - bfill.emit_p(PSTR("\"rssi\":$D,\"bssid\":\"$S\",\"bssidchl\":\"$O\"}"), - WiFi.RSSI(), WiFi.BSSIDstr().c_str(), SOPT_STA_BSSID_CHL); - } -/* -// print out all log files and all files in the main folder with file sizes - DEBUG_PRINTLN(F("List Files:")); - Dir dir = LittleFS.openDir("/logs/"); - while (dir.next()) { - DEBUG_PRINT(dir.fileName()); - DEBUG_PRINT("/"); - DEBUG_PRINTLN(dir.fileSize()); - } - dir = LittleFS.openDir("/"); - while (dir.next()) { - DEBUG_PRINT(dir.fileName()); - DEBUG_PRINT("/"); - DEBUG_PRINTLN(dir.fileSize()); - } -*/ -#else - (unsigned long)freeHeap()); - bfill.emit_p(PSTR("}")); -#endif - handle_return(HTML_OK); -} - #if defined(USE_SENSORS) void server_json_sensors_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"sn\":[")); @@ -2121,23 +2026,17 @@ void server_change_sensor(OTF_PARAMS_DEF) { #else char *p = get_buffer; #endif - - unsigned char i; - - // ProgramStruct prog; - - // parse program index if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) handle_return(HTML_DATA_MISSING); char *end; long sid = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (!(sid >= -1 && sid < MAX_SENSORS)) handle_return(HTML_DATA_OUTOFBOUND); + if (sid < -1 || sid >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); if (sid == -1 ) { while (++sid < MAX_SENSORS) { - if (os.sensors[sid]) { + if (!os.sensors[sid]) { break; } } @@ -2165,7 +2064,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { } else if (os.sensors[sid]) { strncpy(name, os.sensors[sid]->name, SENSOR_NAME_LEN); } else { - snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", sid); + snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); } double min = 0; @@ -2210,14 +2109,6 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (interval < 1000) handle_return(HTML_DATA_OUTOFBOUND); - int raw_unit = 0; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("interval"), true)) { - interval=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } else if (os.sensors[sid]) { - interval = os.sensors[sid]->interval; - } - SensorUnit unit = SensorUnit::None; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { ulong unit_raw = strtol(tmp_buffer, &end, 10); @@ -2229,15 +2120,14 @@ void server_change_sensor(OTF_PARAMS_DEF) { } switch (sensor_type) { - case SensorType::Ensemble: + case SensorType::Ensemble: { uint64_t sensor_mask = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mask"), true)) { sensor_mask = strtoull(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { - if (EnsembleSensor* e = dynamic_cast(os.sensors[sid])) { - sensor_mask = e->sensor_mask; - } + EnsembleSensor* e = static_cast(os.sensors[sid]); + sensor_mask = e->sensor_mask; } EnsembleAction action = EnsembleAction::Min; @@ -2247,23 +2137,22 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (action_raw >= (ulong)EnsembleAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); action = static_cast(action_raw); } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { - if (EnsembleSensor* e = dynamic_cast(os.sensors[sid])) { - action = e->action; - } + EnsembleSensor* e = static_cast(os.sensors[sid]); + action = e->action; } os.sensors[sid] = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.sensors, sensor_mask, action); break; - case SensorType::ADS1115: + } + case SensorType::ADS1115: { ulong sensor_index = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("index"), true)) { sensor_index = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (sensor_index >= 4) handle_return(HTML_DATA_OUTOFBOUND); } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { - if (ADS1115Sensor* e = dynamic_cast(os.sensors[sid])) { - sensor_index = e->sensor_index; - } + ADS1115Sensor* e = static_cast(os.sensors[sid]); + sensor_index = e->sensor_index; } ulong sensor_pin = 0; @@ -2272,22 +2161,25 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (sensor_pin >= 4) handle_return(HTML_DATA_OUTOFBOUND); } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { - if (ADS1115Sensor* e = dynamic_cast(os.sensors[sid])) { - sensor_pin = e->pin; - } + ADS1115Sensor* e = static_cast(os.sensors[sid]); + sensor_pin = e->pin; } os.sensors[sid] = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, os.ads1115_devices, sensor_index, sensor_pin); break; - // case SensorType::Weather: + } + // case SensorType::Weather: { // os.weather_update_flag // break; - default: - case SensorType::MAX_VALUE: + // } + default: { handle_return(HTML_DATA_OUTOFBOUND) break; + } } + os.save_sensors(); + handle_return(HTML_SUCCESS); } @@ -2325,10 +2217,200 @@ void server_delete_sensor(OTF_PARAMS_DEF) { handle_return(HTML_DATA_OUTOFBOUND); } + os.save_sensors(); + handle_return(HTML_SUCCESS); } + +void server_log_sensor(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); #endif + ulong count = 0; + ulong i; + + char *end; + ulong max_count = 100; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { + max_count = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (max_count > 1000) handle_return(HTML_DATA_OUTOFBOUND); + } + + ulong skip = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("skip"), true)) { + skip = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (skip > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); + } + + time_os_t before = std::numeric_limits::max(); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) { + before = (time_os_t)strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (before == 0) handle_return(HTML_DATA_OUTOFBOUND); + } + + time_os_t after = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) { + after = (time_os_t)strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (after <= before) handle_return(HTML_DATA_OUTOFBOUND); + } + + long target_sid = -1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + target_sid = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (target_sid >= MAX_SENSORS || target_sid < -1) handle_return(HTML_DATA_OUTOFBOUND); + } + + bfill.emit_p(PSTR("{\"log\":[")); + + uint16_t next; + file_read_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); + + next = (next + skip) % MAX_SENSOR_LOG_COUNT; + + // Clear out buffer + memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); + + for (i=0;i MAX_SENSORS) continue; + + if (target_sid > -1 && sid != target_sid) continue; + + time_os_t timestamp; + memcpy(×tamp, tmp_buffer+1, sizeof(timestamp)); + float value; + memcpy(&value, tmp_buffer+(1 + sizeof(time_os_t)), sizeof(value)); + + if (timestamp > before || timestamp < after) continue; + + if (count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"sid\":$D,\"timestamp\":$L,\"value\":$E}"), sid, timestamp, value); + count += 1; + + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } + bfill.emit_p(PSTR("],\"count\":$D}"), count); + + os.save_sensors(); + + handle_return(HTML_OK); +} +#endif + +/** Output all JSON data, including jc, jp, jo, js, jn */ +void server_json_all(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS,true)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + bfill.emit_p(PSTR("{\"settings\":{")); + server_json_controller_main(OTF_PARAMS); + send_packet(OTF_PARAMS); + bfill.emit_p(PSTR(",\"programs\":{")); + server_json_programs_main(OTF_PARAMS); + send_packet(OTF_PARAMS); + bfill.emit_p(PSTR(",\"options\":{")); + server_json_options_main(); + send_packet(OTF_PARAMS); + bfill.emit_p(PSTR(",\"status\":{")); + server_json_status_main(); + send_packet(OTF_PARAMS); + bfill.emit_p(PSTR(",\"stations\":{")); + server_json_stations_main(OTF_PARAMS); + #if defined(USE_SENSORS) + bfill.emit_p(PSTR(",\"sensors\":{")); + server_json_sensors_main(OTF_PARAMS); + #endif + bfill.emit_p(PSTR("}")); + handle_return(HTML_OK); +} + +#if defined(ARDUINO) + +#if defined(OS_AVR) +static int freeHeap () { + extern int __heap_start, *__brkval; + int v; + return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); +} +#endif +#else +#include +static unsigned long freeHeap() { + //return sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE); + struct sysinfo info; + if (sysinfo(&info) == 0) { + return info.freeram; + } else { + return 0; + } +} +#endif + +void server_json_debug(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + bfill.emit_p(PSTR("{\"date\":\"$S\",\"time\":\"$S\",\"heap\":$L"), __DATE__, __TIME__, +#if defined(ESP8266) + (unsigned long)ESP.getFreeHeap()); + FSInfo fs_info; + LittleFS.info(fs_info); + bfill.emit_p(PSTR(",\"flash\":$D,\"used\":$D,\"devip\":\"$S\","), fs_info.totalBytes, fs_info.usedBytes, (useEth?eth.localIP():WiFi.localIP()).toString().c_str()); + if(useEth) { + bfill.emit_p(PSTR("\"isW5500\":$D,\"spi_clock\":$L,\"arp_size\":$D}"), eth.isW5500, ETHER_SPI_CLOCK, ARP_TABLE_SIZE); + } else { + bfill.emit_p(PSTR("\"rssi\":$D,\"bssid\":\"$S\",\"bssidchl\":\"$O\"}"), + WiFi.RSSI(), WiFi.BSSIDstr().c_str(), SOPT_STA_BSSID_CHL); + } +/* +// print out all log files and all files in the main folder with file sizes + DEBUG_PRINTLN(F("List Files:")); + Dir dir = LittleFS.openDir("/logs/"); + while (dir.next()) { + DEBUG_PRINT(dir.fileName()); + DEBUG_PRINT("/"); + DEBUG_PRINTLN(dir.fileSize()); + } + dir = LittleFS.openDir("/"); + while (dir.next()) { + DEBUG_PRINT(dir.fileName()); + DEBUG_PRINT("/"); + DEBUG_PRINTLN(dir.fileSize()); + } +*/ +#else + (unsigned long)freeHeap()); + bfill.emit_p(PSTR("}")); +#endif + handle_return(HTML_OK); +} + /* // fill ESP8266 flash with some dummy files void server_fill_files(OTF_PARAMS_DEF) { @@ -2389,6 +2471,7 @@ const char *uris[] PROGMEM = { "jsn", "csn", "dsn", + "lsn", #endif }; @@ -2421,6 +2504,7 @@ URLHandler urls[] = { server_json_sensors, // jsn server_change_sensor, // csn server_delete_sensor, // dsn + server_log_sensor, // lsn #endif }; #else diff --git a/sensor.cpp b/sensor.cpp index bcb258d07..1ec8a5927 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,53 +1,78 @@ #include +#include "OpenSprinkler.h" Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit) : interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit) { strncpy(this->name, name, SENSOR_NAME_LEN); - this->name[SENSOR_NAME_LEN] = 0; + this->name[SENSOR_NAME_LEN-1] = 0; } -void Sensor::poll() { +Sensor::Sensor() {} + +bool Sensor::poll() { if ((millis() - this->_last_update) > this->interval) { this->_update_raw_value(); this->value = (this->value * this->scale) + this->offset; if (this->value < this->min) this->value = this->min; if (this->value > this->max) this->value = this->max; this->_last_update = millis(); + return true; } + return false; } -int write_double(char *buf, double val) { +template +uint32_t Sensor::write_buf(char *buf, T val) { std::memcpy(buf, &val, sizeof(val)); return sizeof(val); } -int write_ulong(char *buf, ulong val) { - std::memcpy(buf, &val, sizeof(val)); - return sizeof(val); +template +T Sensor::read_buf(char *buf, uint32_t *i) { + T val; + std::memcpy(&val, buf + (*i), sizeof(T)); + *i += sizeof(T); + return val; } -int Sensor::serialize(char *buf) { - int i = 0; + +uint32_t Sensor::serialize(char *buf) { + uint32_t i = 0; buf[i++] = static_cast(this->get_sensor_type()); memcpy(buf+i, this->name, SENSOR_NAME_LEN); i += SENSOR_NAME_LEN; buf[i++] = static_cast(this->unit); - i += write_ulong(buf+i, this->interval); - i += write_double(buf+i, this->scale); - i += write_double(buf+i, this->offset); - i += write_double(buf+i, this->min); - i += write_double(buf+i, this->max); + i += write_buf(buf+i, this->interval); + i += write_buf(buf+i, this->scale); + i += write_buf(buf+i, this->offset); + i += write_buf(buf+i, this->min); + i += write_buf(buf+i, this->max); + + i += this->_serialize_internal(buf + i); + return i; +} + +uint32_t Sensor::_deserialize(char *buf) { + uint32_t i = 1; // Skip sensor type + + memcpy(this->name, buf+i, SENSOR_NAME_LEN); + i += SENSOR_NAME_LEN; + this->unit = static_cast(buf[i++]); + this->interval = read_buf(buf, &i); + this->scale = read_buf(buf, &i); + this->offset = read_buf(buf, &i); + this->min = read_buf(buf, &i); + this->max = read_buf(buf, &i); - i += this->serialize_internal(buf + i); return i; } EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : Sensor(interval, min, max, scale, offset, name, unit), -sensors(sensors), sensor_mask(sensor_mask), -action(action) {} +action(action), +sensors(sensors) {} void EnsembleSensor::_update_raw_value() { double inital; @@ -66,6 +91,8 @@ void EnsembleSensor::_update_raw_value() { case EnsembleAction::Product: inital = 1; break; + default: + return; } uint64_t mask = this->sensor_mask; @@ -88,6 +115,8 @@ void EnsembleSensor::_update_raw_value() { case EnsembleAction::Product: inital *= value; break; + default: + return; } count += 1; @@ -106,16 +135,26 @@ void EnsembleSensor::_update_raw_value() { } } -int EnsembleSensor::_serialize_internal(char *buf) { - return 0; +uint32_t EnsembleSensor::_serialize_internal(char *buf) { + uint32_t i = 0; + i += write_buf(buf+i, this->sensor_mask); + buf[i++] = static_cast(this->action); + return i; +} + +EnsembleSensor::EnsembleSensor(Sensor **sensors, char *buf) { + uint32_t i = Sensor::_deserialize(buf); + this->sensor_mask = read_buf(buf, &i); + this->action = static_cast(buf[i++]); + this->sensors = sensors; } WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : Sensor(interval, min, max, scale, offset, name, unit), -sensors(sensors), sensor_mask(sensor_mask), -action(action) {} +action(action), +sensors(sensors) {} void WeatherSensor::_update_raw_value() { double inital; @@ -134,6 +173,8 @@ void WeatherSensor::_update_raw_value() { case EnsembleAction::Product: inital = 1; break; + default: + return; } uint64_t mask = this->sensor_mask; @@ -156,6 +197,8 @@ void WeatherSensor::_update_raw_value() { case EnsembleAction::Product: inital *= value; break; + default: + return; } count += 1; @@ -174,6 +217,10 @@ void WeatherSensor::_update_raw_value() { } } -int WeatherSensor::_serialize_internal(char *buf) { +uint32_t WeatherSensor::_serialize_internal(char *buf) { return 0; +} + +WeatherSensor::WeatherSensor(Sensor **sensors, char *buf) { + uint32_t i = Sensor::_deserialize(buf); } \ No newline at end of file diff --git a/sensor.h b/sensor.h index 13795dc48..4c9818e41 100644 --- a/sensor.h +++ b/sensor.h @@ -56,8 +56,11 @@ enum class SensorUnit { class Sensor { public: Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit); - void poll(); - int serialize(char *buf); + Sensor(); + virtual ~Sensor() {} + + bool poll(); + uint32_t serialize(char *buf); unsigned long interval = 1; double value = 0.0; @@ -72,7 +75,14 @@ class Sensor { private: unsigned long _last_update = 0; void virtual _update_raw_value() = 0; - int virtual _serialize_internal(char *buf) = 0; +protected: + uint32_t _deserialize(char *buf); + template + uint32_t write_buf(char *buf, T val); + template + T read_buf(char *buf, uint32_t *i); + + uint32_t virtual _serialize_internal(char *buf) = 0; }; enum class EnsembleAction { @@ -88,6 +98,7 @@ enum class EnsembleAction { class EnsembleSensor : public Sensor { public: EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); + EnsembleSensor(Sensor **sensors, char *buf); SensorType get_sensor_type() { return SensorType::Ensemble; @@ -98,7 +109,7 @@ class EnsembleSensor : public Sensor { private: void _update_raw_value(); - int _serialize_internal(char *buf); + uint32_t _serialize_internal(char *buf); Sensor **sensors; }; @@ -106,6 +117,8 @@ class EnsembleSensor : public Sensor { class WeatherSensor : public Sensor { public: WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); + WeatherSensor(Sensor **sensors, char *buf); + SensorType get_sensor_type() { return SensorType::Weather; @@ -116,7 +129,7 @@ class WeatherSensor : public Sensor { private: void _update_raw_value(); - int _serialize_internal(char *buf); + uint32_t _serialize_internal(char *buf); Sensor **sensors; }; From fef742a28d5177ecb89ec85262321fdbe7f0c664 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 17 Jul 2025 12:55:43 -0400 Subject: [PATCH 023/115] Store sensor data in flash not memory and make weather sensor class log return newest first --- OpenSprinkler.cpp | 128 ++++++++++++++++++------------- OpenSprinkler.h | 7 +- ads1115.cpp | 6 +- ads1115.h | 2 +- opensprinkler_server.cpp | 162 ++++++++++++++++++++++++--------------- sensor.cpp | 113 +++++++-------------------- sensor.h | 40 ++++++---- 7 files changed, 236 insertions(+), 222 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index a89f4f4d7..f9f802ed5 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -30,7 +30,7 @@ /** Declare static data members */ #if defined(USE_SENSORS) -Sensor *OpenSprinkler::sensors[64] = {nullptr}; +sensor_memory_t OpenSprinkler::sensors[64] = {0}; #endif OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; @@ -2264,6 +2264,17 @@ void OpenSprinkler::factory_reset() { // 4. write program data: just need to write a program counter: 0 file_write_byte(PROG_FILENAME, 0, 0); + #if defined(USE_SENSORS) + // Initalize the senor file + memset(tmp_buffer, 0, TMP_BUFFER_SIZE); + for (size_t i = 0; i < MAX_SENSORS; i++) { + ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * i; + file_write_block(SENSORS_FILENAME, tmp_buffer, pos, sizeof(uint32_t)); + file_write_block(SENSORS_FILENAME, tmp_buffer, pos+sizeof(uint32_t), TMP_BUFFER_SIZE); + } + + #endif + // 5. write 'done' file file_write_byte(DONE_FILENAME, 0, 1); } @@ -2567,64 +2578,57 @@ void OpenSprinkler::raindelay_stop() { #if defined(USE_SENSORS) /** Sensor functions */ -void OpenSprinkler::load_sensors() { - ulong pos = 0; - uint8_t index; - - while (true) { - index = 0; - file_read_block(SENSORS_FILENAME, &index, pos++, 1); - if (index >= MAX_SENSORS) break; - uint32_t len = 0; - file_read_block(SENSORS_FILENAME, &len, pos, sizeof(len)); - pos += sizeof(len); - - if (len == 0 || len > TMP_BUFFER_SIZE) break; +Sensor *OpenSprinkler::get_sensor(uint8_t index) { + ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; - file_read_block(SENSORS_FILENAME, tmp_buffer, pos, len); + uint32_t len = 0; + file_read_block(SENSORS_FILENAME, &len, pos, sizeof(len)); - if ((uint8_t)(*tmp_buffer) >= (uint8_t)SensorType::MAX_VALUE) { - os.sensors[index] = nullptr; - continue; - } + if (len == 0 || len > TMP_BUFFER_SIZE) return nullptr; - SensorType sensor_type = static_cast(*tmp_buffer); - - switch (sensor_type) { - case SensorType::Ensemble: - os.sensors[index] = new EnsembleSensor(os.sensors, (char*)tmp_buffer); - break; - case SensorType::ADS1115: - os.sensors[index] = new ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); - break; - case SensorType::Weather: - os.sensors[index] = new WeatherSensor(os.sensors, (char*)tmp_buffer); - break; - default: - case SensorType::MAX_VALUE: - break; - }; - - pos += len; + file_read_block(SENSORS_FILENAME, tmp_buffer, pos + sizeof(len), len); + + if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) { + return nullptr; } + + SensorType sensor_type = static_cast(*tmp_buffer); + + switch (sensor_type) { + case SensorType::Ensemble: + return new EnsembleSensor(os.sensors, (char*)tmp_buffer); + case SensorType::ADS1115: + return new ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + case SensorType::Weather: + return new WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); + default: + return nullptr; + }; } -void OpenSprinkler::save_sensors() { - ulong pos = 0; - for (uint8_t i = 0; i < MAX_SENSORS; i++) { - if (sensors[i]) { - file_write_byte(SENSORS_FILENAME, pos++, i); - uint32_t len = sensors[i]->serialize(tmp_buffer); - file_write_block(SENSORS_FILENAME, &len, pos, sizeof(len)); - pos += sizeof(len); - file_write_block(SENSORS_FILENAME, tmp_buffer, pos, len); - pos += len; +void OpenSprinkler::load_sensors() { + Sensor *sensor; + for (size_t i = 0; i < MAX_SENSORS; i++) { + if ((sensor = get_sensor(i))) { + sensors[i].interval = sensor->interval; + sensors[i].next_update = 0; + sensors[i].value = 0.0; + delete sensor; } } + +} - file_write_byte(SENSORS_FILENAME, pos++, 0xFF); +void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { + ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; + uint32_t len = 0; - file_read_block(SENSORS_FILENAME, tmp_buffer, 0, 79); + if (sensor) { + len = sensor->serialize(tmp_buffer); + file_write_block(SENSORS_FILENAME, tmp_buffer, pos + sizeof(len), len); + } + + file_write_block(SENSORS_FILENAME, &len, pos, sizeof(len)); } void OpenSprinkler::log_sensor(uint8_t sid, float value) { @@ -2641,19 +2645,33 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { uint32_t pos = sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE); file_write_block(SENSORS_LOG_FILENAME, tmp_buffer, pos, SENSOR_LOG_ITEM_SIZE); - next = (next + 1) % MAX_SENSOR_LOG_COUNT; + if (next > 0) { + next -= 1; + } else { + next = MAX_SENSOR_LOG_COUNT - 1; + } + file_write_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); } void OpenSprinkler::poll_sensors() { - for (size_t i = 0; i < MAX_SENSORS; i++) { - if (sensors[i]) { - if (sensors[i]->poll()) { - os.log_sensor(i, sensors[i]->value); + for (uint8_t i = 0; i < MAX_SENSORS; i++) { + if (sensors[i].interval) { + if (millis() >= sensors[i].next_update) { + Sensor *sensor = get_sensor(i); + if (sensor) { + sensors[i].value = sensor->get_new_value(); + sensors[i].next_update = millis() + sensors[i].interval; + os.log_sensor(i, sensors[i].value); + delete sensor; + } } } } - +} + +double OpenSprinkler::get_sensor_weather_data(WeatherAction action) { + return NAN; // TODO make function for WeatherSensor } #endif diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 50c72d498..98513a0bc 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -258,7 +258,7 @@ class OpenSprinkler { #endif #if defined(USE_SENSORS) - static Sensor *sensors[MAX_SENSORS]; + static sensor_memory_t sensors[MAX_SENSORS]; #endif #if defined(OSPI) @@ -391,9 +391,12 @@ class OpenSprinkler { // -- Sensor functions #if defined(USE_SENSORS) static void load_sensors(); - static void save_sensors(); + static Sensor *get_sensor(uint8_t index); + static void write_sensor(Sensor *sensor, uint8_t index); void log_sensor(uint8_t sid, float value); static void poll_sensors(); + + static double get_sensor_weather_data(WeatherAction action); #endif // -- LCD functions diff --git a/ads1115.cpp b/ads1115.cpp index fa34995b6..2acb6b16a 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -86,12 +86,12 @@ sensor_index(sensor_index), pin(pin), sensors(sensors) {} -void ADS1115Sensor::_update_raw_value() { +double ADS1115Sensor::_get_raw_value() { if (this->sensors[sensor_index] == nullptr) { - this->value = 0.0; + return 0.0; } else { - this->value = ((double)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; + return ((double)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; } } diff --git a/ads1115.h b/ads1115.h index f0c3ae526..c7f7c9469 100644 --- a/ads1115.h +++ b/ads1115.h @@ -62,7 +62,7 @@ class ADS1115Sensor : public Sensor { uint8_t pin; private: - void _update_raw_value(); + double _get_raw_value(); uint32_t _serialize_internal(char *buf); ADS1115 **sensors; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 555214d09..c44ccb95e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1988,11 +1988,12 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { uint8_t sensor_count = 0; for (i=0;iname, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, sensor->value); + bfill.emit_p(PSTR("{\"id\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E}"), i, sensor->name, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value); sensor_count += 1; + delete sensor; } // push out a packet if available @@ -2036,7 +2037,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (sid == -1 ) { while (++sid < MAX_SENSORS) { - if (!os.sensors[sid]) { + if (!os.sensors[sid].interval) { break; } } @@ -2052,7 +2053,31 @@ void server_change_sensor(OTF_PARAMS_DEF) { SensorType sensor_type = static_cast(type_raw); + Sensor *sensor = nullptr; + double min = 0; + double max = 100; + double scale = 1; + double offset = 0; + ulong interval = 1000; + SensorUnit unit = SensorUnit::None; + char name[SENSOR_NAME_LEN]; + snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); + + SensorType original_sensor_type = SensorType::MAX_VALUE; + if (os.sensors[sid].interval) { + if ((sensor = os.get_sensor(sid))) { + original_sensor_type = sensor->get_sensor_type(); + strncpy(name, sensor->name, SENSOR_NAME_LEN); + min = sensor->min; + max = sensor->max; + scale = sensor->scale; + offset = sensor->offset; + interval = sensor->interval; + unit = sensor->unit; + delete sensor; + } + } // parse sensor name if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) { @@ -2061,124 +2086,137 @@ void server_change_sensor(OTF_PARAMS_DEF) { #endif strReplaceQuoteBackslash(tmp_buffer); strncpy(name, tmp_buffer, SENSOR_NAME_LEN); - } else if (os.sensors[sid]) { - strncpy(name, os.sensors[sid]->name, SENSOR_NAME_LEN); - } else { - snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); } - double min = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) { min=strtod(tmp_buffer, &end); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } else if (os.sensors[sid]) { - min = os.sensors[sid]->min; - } + } - double max = 100; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) { max=strtod(tmp_buffer, &end); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } else if (os.sensors[sid]) { - max = os.sensors[sid]->max; - } + } + - double scale = 1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { scale=strtod(tmp_buffer, &end); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } else if (os.sensors[sid]) { - scale = os.sensors[sid]->scale; - } + } - double offset = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) { offset=strtod(tmp_buffer, &end); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } else if (os.sensors[sid]) { - offset = os.sensors[sid]->offset; - } + } - ulong interval = 1000; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("interval"), true)) { interval=strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } else if (os.sensors[sid]) { - interval = os.sensors[sid]->interval; - } + } if (interval < 1000) handle_return(HTML_DATA_OUTOFBOUND); - SensorUnit unit = SensorUnit::None; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { ulong unit_raw = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (unit_raw >= (ulong)SensorUnit::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); unit = static_cast(unit_raw); - } else if (os.sensors[sid]) { - unit = os.sensors[sid]->unit; } + Sensor *result_sensor; switch (sensor_type) { case SensorType::Ensemble: { uint64_t sensor_mask = 0; + EnsembleAction action = EnsembleAction::Min; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + EnsembleSensor* e = static_cast(sensor); + sensor_mask = e->sensor_mask; + action = e->action; + delete sensor; + } + } + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mask"), true)) { sensor_mask = strtoull(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { - EnsembleSensor* e = static_cast(os.sensors[sid]); - sensor_mask = e->sensor_mask; } - EnsembleAction action = EnsembleAction::Min; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { ulong action_raw = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (action_raw >= (ulong)EnsembleAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); action = static_cast(action_raw); - } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { - EnsembleSensor* e = static_cast(os.sensors[sid]); - action = e->action; } - os.sensors[sid] = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.sensors, sensor_mask, action); + result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.sensors, sensor_mask, action); break; } case SensorType::ADS1115: { ulong sensor_index = 0; + ulong sensor_pin = 0; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + ADS1115Sensor* e = static_cast(sensor); + sensor_index = e->sensor_index; + sensor_pin = e->pin; + delete sensor; + } + } + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("index"), true)) { sensor_index = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (sensor_index >= 4) handle_return(HTML_DATA_OUTOFBOUND); - } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { - ADS1115Sensor* e = static_cast(os.sensors[sid]); - sensor_index = e->sensor_index; } - ulong sensor_pin = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pin"), true)) { sensor_pin = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (sensor_pin >= 4) handle_return(HTML_DATA_OUTOFBOUND); - } else if (os.sensors[sid] && os.sensors[sid]->get_sensor_type() == sensor_type) { - ADS1115Sensor* e = static_cast(os.sensors[sid]); - sensor_pin = e->pin; } - os.sensors[sid] = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, os.ads1115_devices, sensor_index, sensor_pin); + result_sensor = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, os.ads1115_devices, sensor_index, sensor_pin); + break; + } + case SensorType::Weather: { + WeatherAction action = WeatherAction::MAX_VALUE; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + WeatherSensor* e = static_cast(sensor); + action = e->action; + delete sensor; + } + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { + ulong action_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (action_raw >= (ulong)WeatherAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + action = static_cast(action_raw); + } + + result_sensor = new WeatherSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.get_sensor_weather_data, action); + break; } - // case SensorType::Weather: { - // os.weather_update_flag - // break; - // } default: { handle_return(HTML_DATA_OUTOFBOUND) break; } } - os.save_sensors(); + os.sensors[sid].interval = interval; + os.sensors[sid].next_update = 0; + os.sensors[sid].value = 0.0; + os.write_sensor(result_sensor, sid); + + delete result_sensor; handle_return(HTML_SUCCESS); } @@ -2203,22 +2241,20 @@ void server_delete_sensor(OTF_PARAMS_DEF) { if (sid == -1) { uint8_t i; for (i=0;i MAX_SENSORS) continue; if (target_sid > -1 && sid != target_sid) continue; @@ -2295,6 +2332,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { memcpy(×tamp, tmp_buffer+1, sizeof(timestamp)); float value; memcpy(&value, tmp_buffer+(1 + sizeof(time_os_t)), sizeof(value)); + Serial.printf("sid: %l\n", timestamp); if (timestamp > before || timestamp < after) continue; @@ -2310,8 +2348,6 @@ void server_log_sensor(OTF_PARAMS_DEF) { } bfill.emit_p(PSTR("],\"count\":$D}"), count); - os.save_sensors(); - handle_return(HTML_OK); } #endif diff --git a/sensor.cpp b/sensor.cpp index 1ec8a5927..d70444dbc 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -9,16 +9,13 @@ interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit) Sensor::Sensor() {} -bool Sensor::poll() { - if ((millis() - this->_last_update) > this->interval) { - this->_update_raw_value(); - this->value = (this->value * this->scale) + this->offset; - if (this->value < this->min) this->value = this->min; - if (this->value > this->max) this->value = this->max; - this->_last_update = millis(); - return true; - } - return false; +double Sensor::get_new_value() { + double value = this->_get_raw_value(); + value = (value * this->scale) + this->offset; + if (value < this->min) value = this->min; + if (value > this->max) value = this->max; + + return value; } template @@ -68,13 +65,13 @@ uint32_t Sensor::_deserialize(char *buf) { return i; } -EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : +EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, sensor_memory_t *sensors, uint64_t sensor_mask, EnsembleAction action) : Sensor(interval, min, max, scale, offset, name, unit), sensor_mask(sensor_mask), action(action), sensors(sensors) {} -void EnsembleSensor::_update_raw_value() { +double EnsembleSensor::_get_raw_value() { double inital; uint8_t count = 0; switch (this->action) { @@ -92,14 +89,16 @@ void EnsembleSensor::_update_raw_value() { inital = 1; break; default: - return; + // Unreachable + return 0.0; } uint64_t mask = this->sensor_mask; uint8_t i = 0; + Sensor *sensor; while (mask) { - if ((mask & 1) && (sensors[i])) { - double value = sensors[i]->value; + if ((mask & 1) && sensors[i].interval) { + double value = sensors[i].value; switch (this->action) { case EnsembleAction::Min: @@ -116,7 +115,8 @@ void EnsembleSensor::_update_raw_value() { inital *= value; break; default: - return; + // Unreachable + return 0.0; } count += 1; @@ -127,11 +127,11 @@ void EnsembleSensor::_update_raw_value() { } if (count == 0) { - this->value = 0.0; + return 0.0; } else if (this->action == EnsembleAction::Average) { - this->value = inital / (double) count; + return inital / (double) count; } else { - this->value = inital; + return inital; } } @@ -142,7 +142,7 @@ uint32_t EnsembleSensor::_serialize_internal(char *buf) { return i; } -EnsembleSensor::EnsembleSensor(Sensor **sensors, char *buf) { +EnsembleSensor::EnsembleSensor(sensor_memory_t *sensors, char *buf) { uint32_t i = Sensor::_deserialize(buf); this->sensor_mask = read_buf(buf, &i); this->action = static_cast(buf[i++]); @@ -150,77 +150,22 @@ EnsembleSensor::EnsembleSensor(Sensor **sensors, char *buf) { } -WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action) : +WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, WeatherGetter weather_getter, WeatherAction action) : Sensor(interval, min, max, scale, offset, name, unit), -sensor_mask(sensor_mask), action(action), -sensors(sensors) {} - -void WeatherSensor::_update_raw_value() { - double inital; - uint8_t count = 0; - switch (this->action) { - case EnsembleAction::Min: - inital = this->max; - break; - case EnsembleAction::Max: - inital = this->min; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - inital = 0; - break; - case EnsembleAction::Product: - inital = 1; - break; - default: - return; - } - - uint64_t mask = this->sensor_mask; - uint8_t i = 0; - while (mask) { - if ((mask & 1) && (sensors[i])) { - double value = sensors[i]->value; - - switch (this->action) { - case EnsembleAction::Min: - if (value < inital) inital = value; - break; - case EnsembleAction::Max: - if (value > inital) inital = value; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - inital += value; - break; - case EnsembleAction::Product: - inital *= value; - break; - default: - return; - } +weather_getter(weather_getter) {} - count += 1; - } - - i += 1; - mask >>= 1; - } - - if (count == 0) { - this->value = 0.0; - } else if (this->action == EnsembleAction::Average) { - this->value = inital / (double) count; - } else { - this->value = inital; - } +double WeatherSensor::_get_raw_value() { + return this->weather_getter(this->action); } uint32_t WeatherSensor::_serialize_internal(char *buf) { - return 0; + uint32_t i = 0; + buf[i++] = static_cast(this->action); + return i; } -WeatherSensor::WeatherSensor(Sensor **sensors, char *buf) { +WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char *buf) { uint32_t i = Sensor::_deserialize(buf); + this->action = static_cast(buf[i++]); } \ No newline at end of file diff --git a/sensor.h b/sensor.h index 4c9818e41..f4cb992dd 100644 --- a/sensor.h +++ b/sensor.h @@ -10,6 +10,13 @@ #include #define SENSOR_NAME_LEN 33 +#define SENSOR_CUSTOM_UNIT_LEN 9 + +typedef struct { + ulong interval; + ulong next_update; + double value; +} sensor_memory_t; enum class SensorType { Ensemble, @@ -20,6 +27,7 @@ enum class SensorType { enum class SensorUnit { None, + UserDefined, Celsius, Fahrenheit, Kelvin, @@ -59,11 +67,10 @@ class Sensor { Sensor(); virtual ~Sensor() {} - bool poll(); + double get_new_value(); uint32_t serialize(char *buf); unsigned long interval = 1; - double value = 0.0; double min = 0.0; double max = 0.0; double scale = 0.0; @@ -73,8 +80,7 @@ class Sensor { SensorType virtual get_sensor_type() = 0; private: - unsigned long _last_update = 0; - void virtual _update_raw_value() = 0; + double virtual _get_raw_value() = 0; protected: uint32_t _deserialize(char *buf); template @@ -94,11 +100,12 @@ enum class EnsembleAction { MAX_VALUE, }; +typedef Sensor* (*SensorGetter)(uint8_t); class EnsembleSensor : public Sensor { public: - EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); - EnsembleSensor(Sensor **sensors, char *buf); + EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, sensor_memory_t *sensors, uint64_t sensor_mask, EnsembleAction action); + EnsembleSensor(sensor_memory_t *sensors, char *buf); SensorType get_sensor_type() { return SensorType::Ensemble; @@ -108,30 +115,35 @@ class EnsembleSensor : public Sensor { EnsembleAction action; private: - void _update_raw_value(); + double _get_raw_value(); uint32_t _serialize_internal(char *buf); - Sensor **sensors; + sensor_memory_t *sensors; +}; + +enum class WeatherAction { + MAX_VALUE, }; +typedef double (*WeatherGetter)(WeatherAction); + class WeatherSensor : public Sensor { public: - WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, Sensor **sensors, uint64_t sensor_mask, EnsembleAction action); - WeatherSensor(Sensor **sensors, char *buf); + WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(WeatherGetter weather_getter, char *buf); SensorType get_sensor_type() { return SensorType::Weather; } - uint64_t sensor_mask; - EnsembleAction action; + WeatherAction action; private: - void _update_raw_value(); + double _get_raw_value(); uint32_t _serialize_internal(char *buf); - Sensor **sensors; + WeatherGetter weather_getter; }; #endif //SENSOR_H \ No newline at end of file From 5ce6e67323c32e5edc316aad31bde0a0bf0d1bc8 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 17 Jul 2025 12:56:05 -0400 Subject: [PATCH 024/115] remove serial.printf --- opensprinkler_server.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index c44ccb95e..d733540ef 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2323,7 +2323,6 @@ void server_log_sensor(OTF_PARAMS_DEF) { next = (next + 1) % MAX_SENSOR_LOG_COUNT; uint8_t sid = tmp_buffer[0]; - Serial.printf("sid: %d\n", sid); if (sid > MAX_SENSORS) continue; if (target_sid > -1 && sid != target_sid) continue; @@ -2332,7 +2331,6 @@ void server_log_sensor(OTF_PARAMS_DEF) { memcpy(×tamp, tmp_buffer+1, sizeof(timestamp)); float value; memcpy(&value, tmp_buffer+(1 + sizeof(time_os_t)), sizeof(value)); - Serial.printf("sid: %l\n", timestamp); if (timestamp > before || timestamp < after) continue; From 3d1f23206c0f68ed60ecbf945c817eaab932642a Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 17 Jul 2025 14:29:26 -0400 Subject: [PATCH 025/115] change pagination to cursor instead of skip for reading sensor log --- opensprinkler_server.cpp | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index d733540ef..fa27c609b 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2278,11 +2278,13 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (max_count > 1000) handle_return(HTML_DATA_OUTOFBOUND); } - ulong skip = 0; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("skip"), true)) { - skip = strtoul(tmp_buffer, &end, 10); + uint16_t cursorv; + file_read_block(SENSORS_LOG_FILENAME, &cursorv, 0, sizeof(cursorv)); + ulong cursor = (cursorv + 1) % MAX_SENSOR_LOG_COUNT; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("cursor"), true)) { + cursor = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (skip > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); + if (cursor > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); } time_os_t before = std::numeric_limits::max(); @@ -2308,19 +2310,14 @@ void server_log_sensor(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"log\":[")); - uint16_t next; - file_read_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); - - next = (next + skip + 1) % MAX_SENSOR_LOG_COUNT; - // Clear out buffer memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); for (i=0;i MAX_SENSORS) continue; @@ -2344,7 +2341,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); } } - bfill.emit_p(PSTR("],\"count\":$D}"), count); + bfill.emit_p(PSTR("],\"total\":$D,\"count\":$D,\"next_cursor\":$D}"), MAX_SENSOR_LOG_COUNT, count, cursor); handle_return(HTML_OK); } From 90f8268bfba61cbf6ee091a4e9f93c7e1a7ca57e Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 21 Jul 2025 10:15:45 -0400 Subject: [PATCH 026/115] Current sensor code, littlefs makes this too slow on each read/write --- OpenSprinkler.cpp | 170 ++++++++++++++++++++++++++++++--------- OpenSprinkler.h | 1 + opensprinkler_server.cpp | 109 +++++++++++++++---------- utils.cpp | 84 +++++++++++++++++++ utils.h | 32 ++++++++ 5 files changed, 313 insertions(+), 83 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index f9f802ed5..cd367187d 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -949,7 +949,6 @@ void OpenSprinkler::begin() { for(unsigned char i=0;i<(MAX_NUM_BOARDS)/2;i++) expanders[i] = NULL; detect_expanders(); - #else // shift register setup @@ -1097,6 +1096,34 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); state = OS_STATE_INITIAL; + remove_file(SENSORS_FILENAME); + remove_file(SENSORS_LOG_FILENAME); + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + for (size_t i = 0; i < MAX_SENSORS; i++) { + file_write(file, tmp_buffer, sizeof(uint32_t)); + file_write(file, tmp_buffer, TMP_BUFFER_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } + + file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + for (size_t i = 0; i < MAX_SENSOR_LOG_COUNT; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_LOG_FILENAME); + } + #else // set sd cs pin high to release SD @@ -2267,10 +2294,30 @@ void OpenSprinkler::factory_reset() { #if defined(USE_SENSORS) // Initalize the senor file memset(tmp_buffer, 0, TMP_BUFFER_SIZE); - for (size_t i = 0; i < MAX_SENSORS; i++) { - ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * i; - file_write_block(SENSORS_FILENAME, tmp_buffer, pos, sizeof(uint32_t)); - file_write_block(SENSORS_FILENAME, tmp_buffer, pos+sizeof(uint32_t), TMP_BUFFER_SIZE); + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + for (size_t i = 0; i < MAX_SENSORS; i++) { + file_write(file, tmp_buffer, sizeof(uint32_t)); + file_write(file, tmp_buffer, TMP_BUFFER_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } + + file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + for (size_t i = 0; i < MAX_SENSOR_LOG_COUNT; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_LOG_FILENAME); } #endif @@ -2578,15 +2625,13 @@ void OpenSprinkler::raindelay_stop() { #if defined(USE_SENSORS) /** Sensor functions */ -Sensor *OpenSprinkler::get_sensor(uint8_t index) { - ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; - +Sensor *OpenSprinkler::parse_sensor(os_file_type file) { uint32_t len = 0; - file_read_block(SENSORS_FILENAME, &len, pos, sizeof(len)); + file_read(file, &len, sizeof(len)); if (len == 0 || len > TMP_BUFFER_SIZE) return nullptr; - file_read_block(SENSORS_FILENAME, tmp_buffer, pos + sizeof(len), len); + file_read(file, tmp_buffer, len); if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) { return nullptr; @@ -2606,52 +2651,97 @@ Sensor *OpenSprinkler::get_sensor(uint8_t index) { }; } +Sensor *OpenSprinkler::get_sensor(uint8_t index) { + ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + Sensor *result = parse_sensor(file); + file_close(file); + return result; + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + return nullptr; + } +} + void OpenSprinkler::load_sensors() { Sensor *sensor; - for (size_t i = 0; i < MAX_SENSORS; i++) { - if ((sensor = get_sensor(i))) { - sensors[i].interval = sensor->interval; - sensors[i].next_update = 0; - sensors[i].value = 0.0; - delete sensor; + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + for (size_t i = 0; i < MAX_SENSORS; i++) { + if ((sensor = parse_sensor(file))) { + sensors[i].interval = sensor->interval; + sensors[i].next_update = 0; + sensors[i].value = 0.0; + delete sensor; + } } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); } - } void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; uint32_t len = 0; - if (sensor) { - len = sensor->serialize(tmp_buffer); - file_write_block(SENSORS_FILENAME, tmp_buffer, pos + sizeof(len), len); - } + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); + if (file) { + if (sensor) { + len = sensor->serialize(tmp_buffer); + } + + file_seek(file, pos, FileSeekMode::Current); + file_write(file, &len, sizeof(len)); + if (sensor) { + file_write(file, tmp_buffer, len); + } - file_write_block(SENSORS_FILENAME, &len, pos, sizeof(len)); + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } } void OpenSprinkler::log_sensor(uint8_t sid, float value) { - uint16_t next = 0; - file_read_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); - - time_os_t timestamp = now(); - tmp_buffer[0] = sid; - char *ptr = tmp_buffer + 1; - memcpy(ptr, ×tamp, sizeof(timestamp)); - ptr += sizeof(timestamp); - memcpy(ptr, &value, sizeof(value)); - - uint32_t pos = sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE); - file_write_block(SENSORS_LOG_FILENAME, tmp_buffer, pos, SENSOR_LOG_ITEM_SIZE); - - if (next > 0) { - next -= 1; + os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::ReadWrite); + if (file) { + uint16_t next = 0; + file_read(file, &next, sizeof(next)); + + time_os_t timestamp = now(); + tmp_buffer[0] = sid; + char *ptr = tmp_buffer + 1; + memcpy(ptr, ×tamp, sizeof(timestamp)); + ptr += sizeof(timestamp); + memcpy(ptr, &value, sizeof(value)); + + uint32_t pos = sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE); + file_seek(file, pos); + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + + if (next > 0) { + next -= 1; + } else { + next = MAX_SENSOR_LOG_COUNT - 1; + } + + file_seek(file, pos); + file_write(file, &next, sizeof(next)); + + file_close(file); } else { - next = MAX_SENSOR_LOG_COUNT - 1; + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_LOG_FILENAME); } - - file_write_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); } void OpenSprinkler::poll_sensors() { diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 98513a0bc..fa8be1753 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -391,6 +391,7 @@ class OpenSprinkler { // -- Sensor functions #if defined(USE_SENSORS) static void load_sensors(); + static Sensor *parse_sensor(os_file_type file); static Sensor *get_sensor(uint8_t index); static void write_sensor(Sensor *sensor, uint8_t index); void log_sensor(uint8_t sid, float value); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index fa27c609b..446e74bf0 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1983,25 +1983,33 @@ void server_pause_queue(OTF_PARAMS_DEF) { #if defined(USE_SENSORS) void server_json_sensors_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"sn\":[")); - unsigned char i; - uint8_t sensor_count = 0; - for (i=0;iname, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value); - sensor_count += 1; - delete sensor; + Sensor *sensor; + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + for (size_t i = 0; i < MAX_SENSORS; i++) { + if (os.sensors[i].interval && (sensor = os.parse_sensor(file))) { + if (sensor_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"id\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E}"), i, sensor->name, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value); + sensor_count += 1; + delete sensor; + + + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } } - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - } + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } + bfill.emit_p(PSTR("],\"count\":$D}"), sensor_count); } @@ -2275,12 +2283,12 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { max_count = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (max_count > 1000) handle_return(HTML_DATA_OUTOFBOUND); + if (max_count > 10000) handle_return(HTML_DATA_OUTOFBOUND); } - uint16_t cursorv; - file_read_block(SENSORS_LOG_FILENAME, &cursorv, 0, sizeof(cursorv)); - ulong cursor = (cursorv + 1) % MAX_SENSOR_LOG_COUNT; + uint16_t cursor_v; + file_read_block(SENSORS_LOG_FILENAME, &cursor_v, 0, sizeof(cursor_v)); + ulong cursor = (cursor_v + 1) % MAX_SENSOR_LOG_COUNT; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("cursor"), true)) { cursor = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); @@ -2308,39 +2316,54 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (target_sid >= MAX_SENSORS || target_sid < -1) handle_return(HTML_DATA_OUTOFBOUND); } - bfill.emit_p(PSTR("{\"log\":[")); - // Clear out buffer memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); - for (i=0;i MAX_SENSORS) continue; + uint8_t sid = tmp_buffer[0]; + if (sid > MAX_SENSORS) continue; - if (target_sid > -1 && sid != target_sid) continue; + if (target_sid > -1 && sid != target_sid) continue; - time_os_t timestamp; - memcpy(×tamp, tmp_buffer+1, sizeof(timestamp)); - float value; - memcpy(&value, tmp_buffer+(1 + sizeof(time_os_t)), sizeof(value)); + time_os_t timestamp; + memcpy(×tamp, tmp_buffer+1, sizeof(timestamp)); + float value; + memcpy(&value, tmp_buffer+(1 + sizeof(time_os_t)), sizeof(value)); - if (timestamp > before || timestamp < after) continue; + if (timestamp > before || timestamp < after) continue; - if (count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"sid\":$D,\"timestamp\":$L,\"value\":$E}"), sid, timestamp, value); - count += 1; + if (count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"sid\":$D,\"timestamp\":$L,\"value\":$E}"), sid, timestamp, value); + count += 1; + + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_LOG_FILENAME); + handle_return(HTML_RFCODE_ERROR) // TODO error code + } - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - } bfill.emit_p(PSTR("],\"total\":$D,\"count\":$D,\"next_cursor\":$D}"), MAX_SENSOR_LOG_COUNT, count, cursor); handle_return(HTML_OK); diff --git a/utils.cpp b/utils.cpp index 18de656b4..98cd497ef 100644 --- a/utils.cpp +++ b/utils.cpp @@ -329,6 +329,90 @@ bool file_exists(const char *fn) { #endif } +os_file_type file_open(const char *fn, FileOpenMode mode) { + #if defined(ARDUINO) + switch (mode) { + default: + case FileOpenMode::Read: + return LittleFS.open(fn, "r"); + break; + case FileOpenMode::ReadWrite: + if (!LittleFS.exists(fn)) { + File f = LittleFS.open(fn, "w"); + if (!f) return f; + f.close(); + } + return LittleFS.open(fn, "r+"); + break; + case FileOpenMode::WriteTruncate: + return LittleFS.open(fn, "w"); + break; + case FileOpenMode::ReadWriteTruncate: + return LittleFS.open(fn, "w+"); + break; + case FileOpenMode::Append: + return LittleFS.open(fn, "a"); + break; + case FileOpenMode::ReadAppend: + return LittleFS.open(fn, "a+"); + break; + } + #else + //TODO + // return file.open(fn, O_READ); + #endif +} + +void file_close(os_file_type f) { + #if defined(ARDUINO) + f.close(); + #else + //TODO + file.close(fn); + // return file.open(fn, O_READ); + #endif +} + +bool file_seek(os_file_type f, uint32_t position, FileSeekMode mode) { + #if defined(ARDUINO) + switch (mode) { + case FileSeekMode::Set: + return f.seek(position, fs::SeekMode::SeekSet); + case FileSeekMode::Current: + return f.seek(position, fs::SeekMode::SeekCur); + case FileSeekMode::End: + return f.seek(position, fs::SeekMode::SeekEnd); + } + #else + //TODO + // return file.open(fn, O_READ); + #endif + + return false; +} + +bool file_seek(os_file_type f, uint32_t position) { + return file_seek(f, position, FileSeekMode::Set); +} + +int file_read(os_file_type f, void *target, uint32_t len) { + #if defined(ARDUINO) + return f.read((uint8_t*)target, len); + #else + //TODO + // return file.open(fn, O_READ); + #endif +} + +int file_write(os_file_type f, const void *source, uint32_t len) { + #if defined(ARDUINO) + return f.write((const uint8_t*)source, len); + #else + //TODO + // return file.open(fn, O_READ); + #endif +} + // file functions void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { #if defined(ESP8266) diff --git a/utils.h b/utils.h index 24790bed9..f4a9c32ce 100644 --- a/utils.h +++ b/utils.h @@ -26,6 +26,8 @@ #if defined(ARDUINO) #include + #include + #include #else // headers for RPI/LINUX #include #include @@ -37,12 +39,42 @@ #endif #include "defines.h" + +#if defined(ARDUINO) +typedef File os_file_type; +#else +typedef FILE* os_file_type; +#endif + +enum class FileOpenMode { + Read, + ReadWrite, + WriteTruncate, + ReadWriteTruncate, + Append, + ReadAppend, +}; + +enum class FileSeekMode { + Set, + Current, + End +}; + + // File reading/writing functions //remove unused functions: void write_to_file(const char *fname, const char *data, ulong size, ulong pos=0, bool trunc=true); //remove unused functions: void read_from_file(const char *fname, char *data, ulong maxsize=TMP_BUFFER_SIZE, int pos=0); void remove_file(const char *fname); bool file_exists(const char *fname); +os_file_type file_open(const char *fn, FileOpenMode mode); +void file_close(os_file_type f); +bool file_seek(os_file_type f, uint32_t position, FileSeekMode mode); +bool file_seek(os_file_type f, uint32_t position); +int file_read(os_file_type f, void *target, uint32_t len); +int file_write(os_file_type f, const void *source, uint32_t len); + void file_read_block (const char *fname, void *dst, ulong pos, ulong len); void file_write_block(const char *fname, const void *src, ulong pos, ulong len); void file_copy_block (const char *fname, ulong from, ulong to, ulong len, void *tmp=0); From 5d8d9e62cb72407471dd6ef8746a37c0d1981000 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 21 Jul 2025 11:47:25 -0400 Subject: [PATCH 027/115] Temp disabled logging sensors since it would add 4s of latency --- OpenSprinkler.cpp | 95 ++++++++++++++++++++++++++++++----------------- OpenSprinkler.h | 1 + 2 files changed, 61 insertions(+), 35 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index cd367187d..35715c950 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -31,6 +31,7 @@ /** Declare static data members */ #if defined(USE_SENSORS) sensor_memory_t OpenSprinkler::sensors[64] = {0}; +os_file_type OpenSprinkler::log_sensor_file; #endif OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; @@ -1096,33 +1097,36 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); state = OS_STATE_INITIAL; - remove_file(SENSORS_FILENAME); + //TODO! + // remove_file(SENSORS_FILENAME); remove_file(SENSORS_LOG_FILENAME); - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); - if (file) { - for (size_t i = 0; i < MAX_SENSORS; i++) { - file_write(file, tmp_buffer, sizeof(uint32_t)); - file_write(file, tmp_buffer, TMP_BUFFER_SIZE); - } + // os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); + // if (file) { + // for (size_t i = 0; i < MAX_SENSORS; i++) { + // file_write(file, tmp_buffer, sizeof(uint32_t)); + // file_write(file, tmp_buffer, TMP_BUFFER_SIZE); + // } - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } - - file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::WriteTruncate); - if (file) { - for (size_t i = 0; i < MAX_SENSOR_LOG_COUNT; i++) { - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - } + // file_close(file); + // } else { + // DEBUG_PRINT("Failed to open file: "); + // DEBUG_PRINTLN(SENSORS_FILENAME); + // } + + // os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::WriteTruncate); + // if (file) { + // for (size_t i = 0; i < MAX_SENSOR_LOG_COUNT; i++) { + // file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + // } - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_LOG_FILENAME); - } + // file_close(file); + // } else { + // DEBUG_PRINT("Failed to open file: "); + // DEBUG_PRINTLN(SENSORS_LOG_FILENAME); + // } + + // OpenSprinkler::log_sensor_file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::ReadWrite); #else @@ -2712,10 +2716,13 @@ void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { } void OpenSprinkler::log_sensor(uint8_t sid, float value) { - os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::ReadWrite); - if (file) { + Serial.println("Log sensor waterfall ----"); + unsigned long start = millis(); + // os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::ReadWrite); + if (log_sensor_file) { uint16_t next = 0; - file_read(file, &next, sizeof(next)); + file_read(log_sensor_file, &next, sizeof(next)); + Serial.printf("Read: %u ms.\n", millis()-start); time_os_t timestamp = now(); tmp_buffer[0] = sid; @@ -2724,9 +2731,9 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { ptr += sizeof(timestamp); memcpy(ptr, &value, sizeof(value)); - uint32_t pos = sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE); - file_seek(file, pos); - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + uint32_t pos = (next * SENSOR_LOG_ITEM_SIZE); + Serial.printf("mem: %u ms.\n", millis()-start); + if (next > 0) { next -= 1; @@ -2734,14 +2741,30 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { next = MAX_SENSOR_LOG_COUNT - 1; } - file_seek(file, pos); - file_write(file, &next, sizeof(next)); - - file_close(file); + file_seek(log_sensor_file, 0); + Serial.printf("Seek2: %u ms.\n", millis()-start); + file_write(log_sensor_file, &next, sizeof(next)); + Serial.printf("Write2: %u ms.\n", millis()-start); + + // log_sensor_file.flush(); + // Serial.printf("Flush2: %u ms.\n", millis()-start); + + file_seek(log_sensor_file, pos, FileSeekMode::Current); + Serial.printf("Seek: %u ms.\n", millis()-start); + file_write(log_sensor_file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + Serial.printf("Write: %u ms.\n", millis()-start); + + log_sensor_file.flush(); + Serial.printf("Flush: %u ms.\n", millis()-start); + + // file_close(log_sensor_file); + // Serial.printf("Close: %u ms.\n", millis()-start); } else { DEBUG_PRINT("Failed to open file: "); DEBUG_PRINTLN(SENSORS_LOG_FILENAME); } + + Serial.println("Log sensor waterfall ----"); } void OpenSprinkler::poll_sensors() { @@ -2751,9 +2774,11 @@ void OpenSprinkler::poll_sensors() { Sensor *sensor = get_sensor(i); if (sensor) { sensors[i].value = sensor->get_new_value(); - sensors[i].next_update = millis() + sensors[i].interval; - os.log_sensor(i, sensors[i].value); delete sensor; + sensors[i].next_update = millis() + sensors[i].interval; + // unsigned long start = millis(); + // os.log_sensor(i, sensors[i].value); + // Serial.printf("logging sensor took: %u ms.\n", millis() - start); } } } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index fa8be1753..d38e40f4c 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -259,6 +259,7 @@ class OpenSprinkler { #if defined(USE_SENSORS) static sensor_memory_t sensors[MAX_SENSORS]; + static os_file_type log_sensor_file; #endif #if defined(OSPI) From 6affe8652281ecda1daedf8167264d0010d3fb5b Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:22:55 -0400 Subject: [PATCH 028/115] split log file into multiple files to make it a reasonable speed on littlefs --- OpenSprinkler.cpp | 187 +++++++++++++++++++++++---------------- OpenSprinkler.h | 3 +- defines.h | 7 +- opensprinkler_server.cpp | 96 +++++++++++++++----- sensor.cpp | 1 - 5 files changed, 194 insertions(+), 100 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 35715c950..59aa3b257 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -31,7 +31,7 @@ /** Declare static data members */ #if defined(USE_SENSORS) sensor_memory_t OpenSprinkler::sensors[64] = {0}; -os_file_type OpenSprinkler::log_sensor_file; +uint16_t OpenSprinkler::sensor_file_no = 0; #endif OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; @@ -1096,38 +1096,6 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); } state = OS_STATE_INITIAL; - - //TODO! - // remove_file(SENSORS_FILENAME); - remove_file(SENSORS_LOG_FILENAME); - - // os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); - // if (file) { - // for (size_t i = 0; i < MAX_SENSORS; i++) { - // file_write(file, tmp_buffer, sizeof(uint32_t)); - // file_write(file, tmp_buffer, TMP_BUFFER_SIZE); - // } - - // file_close(file); - // } else { - // DEBUG_PRINT("Failed to open file: "); - // DEBUG_PRINTLN(SENSORS_FILENAME); - // } - - // os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::WriteTruncate); - // if (file) { - // for (size_t i = 0; i < MAX_SENSOR_LOG_COUNT; i++) { - // file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - // } - - // file_close(file); - // } else { - // DEBUG_PRINT("Failed to open file: "); - // DEBUG_PRINTLN(SENSORS_LOG_FILENAME); - // } - - // OpenSprinkler::log_sensor_file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::ReadWrite); - #else // set sd cs pin high to release SD @@ -1169,6 +1137,68 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); lcd.clear(); lcd.setCursor(0,0); lcd.print(F("Init sensors")); + + { + // //TODO! + // // remove_file(SENSORS_FILENAME); + // remove_file(SENSORS_LOG_FILENAME); + + // // os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); + // // if (file) { + // // for (size_t i = 0; i < MAX_SENSORS; i++) { + // // file_write(file, tmp_buffer, sizeof(uint32_t)); + // // file_write(file, tmp_buffer, TMP_BUFFER_SIZE); + // // } + + // // file_close(file); + // // } else { + // // DEBUG_PRINT("Failed to open file: "); + // // DEBUG_PRINTLN(SENSORS_FILENAME); + // // } + + os_file_type file; + uint16_t next = SENSOR_LOG_PER_FILE; + for (size_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + { + char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; + sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; + memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03d", f); + remove_file(sensor_log_name_buf); + } + file = open_sensor_log(f, FileOpenMode::WriteTruncate); + if (file) { + file_write(file, &next, sizeof(next)); + for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + } + } + } + + os_file_type file; + uint16_t next = 0; + size_t f; + for (f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + file = open_sensor_log(f, FileOpenMode::Read); + if (file) { + file_read(file, &next, sizeof(next)); + file_close(file); + + if (next < SENSOR_LOG_PER_FILE) break; + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + } + } + if (f == SENSOR_LOG_FILE_COUNT) f -= 1; + sensor_file_no = f; + os.load_sensors(); #endif } @@ -2299,6 +2329,7 @@ void OpenSprinkler::factory_reset() { // Initalize the senor file memset(tmp_buffer, 0, TMP_BUFFER_SIZE); + remove_file(SENSORS_FILENAME); os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); if (file) { for (size_t i = 0; i < MAX_SENSORS; i++) { @@ -2312,18 +2343,29 @@ void OpenSprinkler::factory_reset() { DEBUG_PRINTLN(SENSORS_FILENAME); } - file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::WriteTruncate); - if (file) { - for (size_t i = 0; i < MAX_SENSOR_LOG_COUNT; i++) { - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + + uint16_t next = SENSOR_LOG_PER_FILE; + for (size_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + { + char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; + sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; + memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03d", f); + remove_file(sensor_log_name_buf); + } + file = open_sensor_log(f, FileOpenMode::WriteTruncate); + if (file) { + file_write(file, &next, sizeof(next)); + for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_LOG_FILENAME); } - #endif // 5. write 'done' file @@ -2672,6 +2714,15 @@ Sensor *OpenSprinkler::get_sensor(uint8_t index) { } } +os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) { + char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; + sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; + memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03d", file_no); + + return file_open(sensor_log_name_buf, mode); +} + void OpenSprinkler::load_sensors() { Sensor *sensor; os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); @@ -2716,55 +2767,45 @@ void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { } void OpenSprinkler::log_sensor(uint8_t sid, float value) { - Serial.println("Log sensor waterfall ----"); - unsigned long start = millis(); - // os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::ReadWrite); - if (log_sensor_file) { + os_file_type file = open_sensor_log(sensor_file_no, FileOpenMode::ReadWrite); + if (file) { uint16_t next = 0; - file_read(log_sensor_file, &next, sizeof(next)); - Serial.printf("Read: %u ms.\n", millis()-start); + file_read(file, &next, sizeof(next)); + if (next == SENSOR_LOG_PER_FILE) next -= 1; time_os_t timestamp = now(); - tmp_buffer[0] = sid; - char *ptr = tmp_buffer + 1; + tmp_buffer[0] = 1; + tmp_buffer[1] = sid; + char *ptr = tmp_buffer + 2; memcpy(ptr, ×tamp, sizeof(timestamp)); ptr += sizeof(timestamp); memcpy(ptr, &value, sizeof(value)); uint32_t pos = (next * SENSOR_LOG_ITEM_SIZE); - Serial.printf("mem: %u ms.\n", millis()-start); if (next > 0) { next -= 1; } else { - next = MAX_SENSOR_LOG_COUNT - 1; + next = SENSOR_LOG_PER_FILE; + if (sensor_file_no > 0) { + sensor_file_no -= 1; + } else { + sensor_file_no = SENSOR_LOG_FILE_COUNT - 1; + } } - - file_seek(log_sensor_file, 0); - Serial.printf("Seek2: %u ms.\n", millis()-start); - file_write(log_sensor_file, &next, sizeof(next)); - Serial.printf("Write2: %u ms.\n", millis()-start); - // log_sensor_file.flush(); - // Serial.printf("Flush2: %u ms.\n", millis()-start); + file_seek(file, 0); + file_write(file, &next, sizeof(next)); - file_seek(log_sensor_file, pos, FileSeekMode::Current); - Serial.printf("Seek: %u ms.\n", millis()-start); - file_write(log_sensor_file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - Serial.printf("Write: %u ms.\n", millis()-start); - - log_sensor_file.flush(); - Serial.printf("Flush: %u ms.\n", millis()-start); + file_seek(file, pos, FileSeekMode::Current); + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - // file_close(log_sensor_file); - // Serial.printf("Close: %u ms.\n", millis()-start); + file_close(file); } else { DEBUG_PRINT("Failed to open file: "); DEBUG_PRINTLN(SENSORS_LOG_FILENAME); } - - Serial.println("Log sensor waterfall ----"); } void OpenSprinkler::poll_sensors() { @@ -2776,9 +2817,7 @@ void OpenSprinkler::poll_sensors() { sensors[i].value = sensor->get_new_value(); delete sensor; sensors[i].next_update = millis() + sensors[i].interval; - // unsigned long start = millis(); - // os.log_sensor(i, sensors[i].value); - // Serial.printf("logging sensor took: %u ms.\n", millis() - start); + os.log_sensor(i, sensors[i].value); } } } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index d38e40f4c..c2124b871 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -259,7 +259,7 @@ class OpenSprinkler { #if defined(USE_SENSORS) static sensor_memory_t sensors[MAX_SENSORS]; - static os_file_type log_sensor_file; + static uint16_t sensor_file_no; #endif #if defined(OSPI) @@ -391,6 +391,7 @@ class OpenSprinkler { // -- Sensor functions #if defined(USE_SENSORS) + static os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); static void load_sensors(); static Sensor *parse_sensor(os_file_type file); static Sensor *get_sensor(uint8_t index); diff --git a/defines.h b/defines.h index 8b74d1552..54fb429d6 100755 --- a/defines.h +++ b/defines.h @@ -139,8 +139,11 @@ typedef unsigned long ulong; #define MAX_SOPTS_SIZE 320 // maximum string option size #define MAX_SENSORS 64 -#define MAX_SENSOR_LOG_COUNT 32768 -#define SENSOR_LOG_ITEM_SIZE (sizeof(time_os_t) + sizeof(float) + 1) +// #define SENSOR_LOG_PER_FILE 1024 +#define SENSOR_LOG_PER_FILE 1024 +#define SENSOR_LOG_FILE_COUNT 32 +#define MAX_SENSOR_LOG_COUNT (SENSOR_LOG_PER_FILE * SENSOR_LOG_FILE_COUNT) +#define SENSOR_LOG_ITEM_SIZE (sizeof(time_os_t) + sizeof(float) + 1 + 1) #define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 446e74bf0..6a7a51dc6 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2283,16 +2283,37 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { max_count = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (max_count > 10000) handle_return(HTML_DATA_OUTOFBOUND); + if (max_count > 10000 || max_count > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); } - uint16_t cursor_v; - file_read_block(SENSORS_LOG_FILENAME, &cursor_v, 0, sizeof(cursor_v)); - ulong cursor = (cursor_v + 1) % MAX_SENSOR_LOG_COUNT; + uint16_t file_no = os.sensor_file_no; + uint16_t next; + // file_read_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); + + os_file_type file = os.open_sensor_log(file_no, FileOpenMode::Read); + if (file) { + file_read(file, &next, sizeof(next)); + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(file_no); + handle_return(HTML_DATA_OUTOFBOUND); // TODO: INTERNAL ERROR + } + + if (next == SENSOR_LOG_PER_FILE) { + next = 0; + file_no = (file_no + 1) % SENSOR_LOG_FILE_COUNT; + } else { + next += 1; + } + + ulong cursor = (file_no * SENSOR_LOG_PER_FILE) + next; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("cursor"), true)) { cursor = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (cursor > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); + next = cursor % SENSOR_LOG_PER_FILE; + file_no = (cursor - next) / SENSOR_LOG_PER_FILE; } time_os_t before = std::numeric_limits::max(); @@ -2319,30 +2340,61 @@ void server_log_sensor(OTF_PARAMS_DEF) { // Clear out buffer memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); - os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::Read); + // os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::Read); + file = os.open_sensor_log(file_no, FileOpenMode::Read); if (file) { - bfill.emit_p(PSTR("{\"log\":[")); - - file_seek(file, 2 + (cursor * SENSOR_LOG_ITEM_SIZE)); + file_seek(file, sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE), FileSeekMode::Current); + } else { + DEBUG_PRINT("Failed to sensor log file: "); + DEBUG_PRINTLN(file_no); + handle_return(HTML_RFCODE_ERROR); // TODO: INTERNAL SERVER ERROR + } - for (i=0;i MAX_SENSORS) continue; + buf_ptr += 1; if (target_sid > -1 && sid != target_sid) continue; time_os_t timestamp; - memcpy(×tamp, tmp_buffer+1, sizeof(timestamp)); + memcpy(×tamp, buf_ptr, sizeof(timestamp)); + buf_ptr += sizeof(timestamp); float value; - memcpy(&value, tmp_buffer+(1 + sizeof(time_os_t)), sizeof(value)); + memcpy(&value, buf_ptr, sizeof(value)); + buf_ptr += sizeof(value); if (timestamp > before || timestamp < after) continue; @@ -2355,15 +2407,15 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (available_ether_buffer() <= 0) { send_packet(OTF_PARAMS); } + } else { + DEBUG_PRINT("Failed to sensor log file: "); + DEBUG_PRINTLN(file_no); + break; } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_LOG_FILENAME); - handle_return(HTML_RFCODE_ERROR) // TODO error code } + if (file) file_close(file); + bfill.emit_p(PSTR("],\"total\":$D,\"count\":$D,\"next_cursor\":$D}"), MAX_SENSOR_LOG_COUNT, count, cursor); handle_return(HTML_OK); diff --git a/sensor.cpp b/sensor.cpp index d70444dbc..ccc6352b2 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -95,7 +95,6 @@ double EnsembleSensor::_get_raw_value() { uint64_t mask = this->sensor_mask; uint8_t i = 0; - Sensor *sensor; while (mask) { if ((mask & 1) && sensors[i].interval) { double value = sensors[i].value; From cb00122d2c5b1d36ba4441b3f1b7558fbaf1482d Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:27:05 -0400 Subject: [PATCH 029/115] added HTML_INTERNAL_ERROR (0x50) for error codes --- OpenSprinkler.cpp | 43 ---------------------------------------- opensprinkler_server.cpp | 13 ++++++------ 2 files changed, 7 insertions(+), 49 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 59aa3b257..0ff8895ab 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1138,49 +1138,6 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); lcd.setCursor(0,0); lcd.print(F("Init sensors")); - { - // //TODO! - // // remove_file(SENSORS_FILENAME); - // remove_file(SENSORS_LOG_FILENAME); - - // // os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); - // // if (file) { - // // for (size_t i = 0; i < MAX_SENSORS; i++) { - // // file_write(file, tmp_buffer, sizeof(uint32_t)); - // // file_write(file, tmp_buffer, TMP_BUFFER_SIZE); - // // } - - // // file_close(file); - // // } else { - // // DEBUG_PRINT("Failed to open file: "); - // // DEBUG_PRINTLN(SENSORS_FILENAME); - // // } - - os_file_type file; - uint16_t next = SENSOR_LOG_PER_FILE; - for (size_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - { - char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; - sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; - memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); - snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03d", f); - remove_file(sensor_log_name_buf); - } - file = open_sensor_log(f, FileOpenMode::WriteTruncate); - if (file) { - file_write(file, &next, sizeof(next)); - for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(f); - } - } - } - os_file_type file; uint16_t next = 0; size_t f; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 6a7a51dc6..b0f7991ee 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -93,6 +93,7 @@ int available_ether_buffer() { #define HTML_PAGE_NOT_FOUND 0x20 #define HTML_NOT_PERMITTED 0x30 #define HTML_UPLOAD_FAILED 0x40 +#define HTML_INTERNAL_ERROR 0x50 #define HTML_REDIRECT_HOME 0xFF #if !defined(USE_OTF) @@ -2297,7 +2298,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { } else { DEBUG_PRINT("Failed to open sensor log file: "); DEBUG_PRINTLN(file_no); - handle_return(HTML_DATA_OUTOFBOUND); // TODO: INTERNAL ERROR + handle_return(HTML_INTERNAL_ERROR); } if (next == SENSOR_LOG_PER_FILE) { @@ -2345,9 +2346,9 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (file) { file_seek(file, sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE), FileSeekMode::Current); } else { - DEBUG_PRINT("Failed to sensor log file: "); + DEBUG_PRINT("Failed to open sensor log file: "); DEBUG_PRINTLN(file_no); - handle_return(HTML_RFCODE_ERROR); // TODO: INTERNAL SERVER ERROR + handle_return(HTML_INTERNAL_ERROR); } bfill.emit_p(PSTR("{\"log\":[")); @@ -2362,12 +2363,12 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (file) { file_seek(file, sizeof(next), FileSeekMode::Current); } else { - DEBUG_PRINT("Failed to sensor log file: "); + DEBUG_PRINT("Failed to open sensor log file: "); DEBUG_PRINTLN(file_no); break; } } else { - DEBUG_PRINT("Failed to sensor log file: "); + DEBUG_PRINT("Failed to open sensor log file: "); DEBUG_PRINTLN(file_no); break; } @@ -2408,7 +2409,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); } } else { - DEBUG_PRINT("Failed to sensor log file: "); + DEBUG_PRINT("Failed to open sensor log file: "); DEBUG_PRINTLN(file_no); break; } From 0a57e3e32d964c898ea1db94e0349dcbb9d74eff Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:34:05 -0400 Subject: [PATCH 030/115] switch include bracket to quote --- sensor.cpp | 2 +- sensor.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sensor.cpp b/sensor.cpp index ccc6352b2..96c687308 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,4 +1,4 @@ -#include +#include "sensor.h" #include "OpenSprinkler.h" Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit) : diff --git a/sensor.h b/sensor.h index f4cb992dd..3a01f4031 100644 --- a/sensor.h +++ b/sensor.h @@ -7,7 +7,7 @@ #else #include "utils.h" #endif -#include +#include "defines.h" #define SENSOR_NAME_LEN 33 #define SENSOR_CUSTOM_UNIT_LEN 9 From 4d1a86f3d489297e085f51c2aa42a4991a1ae166 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:39:01 -0400 Subject: [PATCH 031/115] add cmath to fix nan issue on docker --- OpenSprinkler.cpp | 4 ++-- OpenSprinkler.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 0ff8895ab..99b31d576 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2307,7 +2307,7 @@ void OpenSprinkler::factory_reset() { char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); - snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03d", f); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", f); remove_file(sensor_log_name_buf); } file = open_sensor_log(f, FileOpenMode::WriteTruncate); @@ -2675,7 +2675,7 @@ os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); - snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03d", file_no); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", file_no); return file_open(sensor_log_name_buf, mode); } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index c2124b871..cc658aacb 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -32,6 +32,7 @@ #include "images.h" #include "mqtt.h" #include "RCSwitch.h" +#include #if defined(ARDUINO) // headers for Arduino #include From cc50109bcee4f010cb12d23388817bca6d494f47 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:50:57 -0400 Subject: [PATCH 032/115] fix printf type from sizet to uint16t --- OpenSprinkler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 99b31d576..9da54fc79 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2302,7 +2302,7 @@ void OpenSprinkler::factory_reset() { uint16_t next = SENSOR_LOG_PER_FILE; - for (size_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { { char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; From fc414e784517e0bdb584b802a92f3f89494e9f04 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 11:51:25 -0400 Subject: [PATCH 033/115] add file commands for linux --- utils.cpp | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/utils.cpp b/utils.cpp index 98cd497ef..30c3a75df 100644 --- a/utils.cpp +++ b/utils.cpp @@ -335,7 +335,6 @@ os_file_type file_open(const char *fn, FileOpenMode mode) { default: case FileOpenMode::Read: return LittleFS.open(fn, "r"); - break; case FileOpenMode::ReadWrite: if (!LittleFS.exists(fn)) { File f = LittleFS.open(fn, "w"); @@ -343,23 +342,37 @@ os_file_type file_open(const char *fn, FileOpenMode mode) { f.close(); } return LittleFS.open(fn, "r+"); - break; case FileOpenMode::WriteTruncate: return LittleFS.open(fn, "w"); - break; case FileOpenMode::ReadWriteTruncate: return LittleFS.open(fn, "w+"); - break; case FileOpenMode::Append: return LittleFS.open(fn, "a"); - break; case FileOpenMode::ReadAppend: return LittleFS.open(fn, "a+"); - break; } #else - //TODO - // return file.open(fn, O_READ); + switch (mode) { + default: + case FileOpenMode::Read: + return fopen(get_filename_fullpath(fn), "rb"); + case FileOpenMode::ReadWrite: + if (!LittleFS.exists(fn)) { + FILE *fp = fopen(get_filename_fullpath(fn), "wb"); + if (!fp) return fp; + fclose(fp); + } + return fopen(get_filename_fullpath(fn), "rb+"); + case FileOpenMode::WriteTruncate: + return fopen(get_filename_fullpath(fn), "wb"); + case FileOpenMode::ReadWriteTruncate: + return fopen(get_filename_fullpath(fn), "wb+"); + case FileOpenMode::Append: + return fopen(get_filename_fullpath(fn), "ab"); + case FileOpenMode::ReadAppend: + return fopen(get_filename_fullpath(fn), "ab+"); + } + #endif } @@ -367,9 +380,7 @@ void file_close(os_file_type f) { #if defined(ARDUINO) f.close(); #else - //TODO - file.close(fn); - // return file.open(fn, O_READ); + fclose(f); #endif } @@ -384,8 +395,14 @@ bool file_seek(os_file_type f, uint32_t position, FileSeekMode mode) { return f.seek(position, fs::SeekMode::SeekEnd); } #else - //TODO - // return file.open(fn, O_READ); + switch (mode) { + case FileSeekMode::Set: + return fseek(f, position, SEEK_SET); + case FileSeekMode::Current: + return fseek(f, position, SEEK_CUR); + case FileSeekMode::End: + return fseek(f, position, SEEK_END); + } #endif return false; @@ -399,8 +416,7 @@ int file_read(os_file_type f, void *target, uint32_t len) { #if defined(ARDUINO) return f.read((uint8_t*)target, len); #else - //TODO - // return file.open(fn, O_READ); + return fread(target, 1, len, f); #endif } @@ -408,8 +424,7 @@ int file_write(os_file_type f, const void *source, uint32_t len) { #if defined(ARDUINO) return f.write((const uint8_t*)source, len); #else - //TODO - // return file.open(fn, O_READ); + return fwrite(target, 1, len, f); #endif } From 60a94392c273d0a791080462c2c0b417784964b0 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:01:46 -0400 Subject: [PATCH 034/115] fix read write to not cause race condition --- utils.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/utils.cpp b/utils.cpp index 30c3a75df..633d58418 100644 --- a/utils.cpp +++ b/utils.cpp @@ -352,25 +352,24 @@ os_file_type file_open(const char *fn, FileOpenMode mode) { return LittleFS.open(fn, "a+"); } #else + char *full_file = get_filename_fullpath(fn); switch (mode) { default: case FileOpenMode::Read: - return fopen(get_filename_fullpath(fn), "rb"); - case FileOpenMode::ReadWrite: - if (!LittleFS.exists(fn)) { - FILE *fp = fopen(get_filename_fullpath(fn), "wb"); - if (!fp) return fp; - fclose(fp); - } - return fopen(get_filename_fullpath(fn), "rb+"); + return fopen(full_file, "rb"); + case FileOpenMode::ReadWrite: { + int fd = open(full_file, O_RDWR | O_CREAT, 0644); + if (fd == -1) return nullptr; + return fdopen(fd, "rb+"); + } case FileOpenMode::WriteTruncate: - return fopen(get_filename_fullpath(fn), "wb"); + return fopen(full_file, "wb"); case FileOpenMode::ReadWriteTruncate: - return fopen(get_filename_fullpath(fn), "wb+"); + return fopen(full_file, "wb+"); case FileOpenMode::Append: - return fopen(get_filename_fullpath(fn), "ab"); + return fopen(full_file, "ab"); case FileOpenMode::ReadAppend: - return fopen(get_filename_fullpath(fn), "ab+"); + return fopen(full_file, "ab+"); } #endif From d94e0242059b18a17eb04e268010e75688ee4336 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:02:51 -0400 Subject: [PATCH 035/115] fix fwrite --- utils.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.cpp b/utils.cpp index 633d58418..4f272520b 100644 --- a/utils.cpp +++ b/utils.cpp @@ -423,7 +423,7 @@ int file_write(os_file_type f, const void *source, uint32_t len) { #if defined(ARDUINO) return f.write((const uint8_t*)source, len); #else - return fwrite(target, 1, len, f); + return fwrite(source, 1, len, f); #endif } From db5285d247746e528d1468b691f8db1ef2c10d6d Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:08:26 -0400 Subject: [PATCH 036/115] add i2c detect --- OpenSprinkler.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 9da54fc79..72a9bfdaf 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -761,6 +761,10 @@ bool OpenSprinkler::network_connected(void) { return true; } +bool detect_i2c(int addr) { + Bus.detect(addr); +} + // Return mac of first recognised interface and fallback to software mac // Note: on OSPi, operating system handles interface allocation so 'wired' ignored bool OpenSprinkler::load_hardware_mac(unsigned char* mac, bool wired) { From 07ec96e7518c7e59b11a68d28dbaf6de0159517f Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:11:13 -0400 Subject: [PATCH 037/115] add limits include for the server --- opensprinkler_server.h | 1 + 1 file changed, 1 insertion(+) diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 7a7493981..1eed0af26 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -27,6 +27,7 @@ #if !defined(ARDUINO) #include #include +#include #endif char dec2hexchar(unsigned char dec); From 1e8bcdc2e2daa46b87bde6d9015dc97d4d546043 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 23 Jul 2025 12:13:58 -0400 Subject: [PATCH 038/115] fix demo using bus --- OpenSprinkler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 72a9bfdaf..7c2446738 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -761,9 +761,11 @@ bool OpenSprinkler::network_connected(void) { return true; } +#if defined(OSPI) bool detect_i2c(int addr) { Bus.detect(addr); } +#endif // Return mac of first recognised interface and fallback to software mac // Note: on OSPi, operating system handles interface allocation so 'wired' ignored @@ -850,7 +852,7 @@ void OpenSprinkler::begin() { #if defined(ARDUINO) Wire.begin(); // init I2C -#else +#elif defined(OSPI) Bus.begin(); // init I2C for OSPI #endif From 4b7f71957011e8206d91452889edc3fc2b2c223f Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Fri, 1 Aug 2025 16:22:06 -0400 Subject: [PATCH 039/115] Fix sensor reading for more than 1 sensor, made it so sensor's inital values dont have to be 0 (useful for min on an ensemble sensor) made ensemble sensors have 4 inputs that can be scaled --- OpenSprinkler.cpp | 5 +-- ads1115.cpp | 4 +++ ads1115.h | 2 ++ opensprinkler_server.cpp | 40 +++++++++++++++++++----- sensor.cpp | 66 ++++++++++++++++++++++++++++------------ sensor.h | 20 ++++++++++-- 6 files changed, 106 insertions(+), 31 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 7c2446738..252024df8 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2641,6 +2641,7 @@ Sensor *OpenSprinkler::parse_sensor(os_file_type file) { if (len == 0 || len > TMP_BUFFER_SIZE) return nullptr; file_read(file, tmp_buffer, len); + file_seek(file, TMP_BUFFER_SIZE - len, FileSeekMode::Current); if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) { return nullptr; @@ -2694,7 +2695,7 @@ void OpenSprinkler::load_sensors() { if ((sensor = parse_sensor(file))) { sensors[i].interval = sensor->interval; sensors[i].next_update = 0; - sensors[i].value = 0.0; + sensors[i].value = sensor->get_inital_value(); delete sensor; } } @@ -2774,7 +2775,7 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { void OpenSprinkler::poll_sensors() { for (uint8_t i = 0; i < MAX_SENSORS; i++) { if (sensors[i].interval) { - if (millis() >= sensors[i].next_update) { + if ((long)(millis() - sensors[i].next_update) > 0) { Sensor *sensor = get_sensor(i); if (sensor) { sensors[i].value = sensor->get_new_value(); diff --git a/ads1115.cpp b/ads1115.cpp index 2acb6b16a..c7aa20076 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -86,6 +86,10 @@ sensor_index(sensor_index), pin(pin), sensors(sensors) {} +double ADS1115Sensor::get_inital_value() { + return 0.0; +} + double ADS1115Sensor::_get_raw_value() { if (this->sensors[sensor_index] == nullptr) { return 0.0; diff --git a/ads1115.h b/ads1115.h index c7f7c9469..2e73be6c1 100644 --- a/ads1115.h +++ b/ads1115.h @@ -61,6 +61,8 @@ class ADS1115Sensor : public Sensor { uint8_t sensor_index; uint8_t pin; + double get_inital_value(); + private: double _get_raw_value(); uint32_t _serialize_internal(char *buf); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index b0f7991ee..7b5c50ad9 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2137,21 +2137,47 @@ void server_change_sensor(OTF_PARAMS_DEF) { Sensor *result_sensor; switch (sensor_type) { case SensorType::Ensemble: { - uint64_t sensor_mask = 0; + ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + children[i].sensor_id = 255; + } + EnsembleAction action = EnsembleAction::Min; if (sensor_type == original_sensor_type) { if ((sensor = os.get_sensor(sid))) { EnsembleSensor* e = static_cast(sensor); - sensor_mask = e->sensor_mask; + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + children[i] = e->children[i]; + } + action = e->action; delete sensor; } } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mask"), true)) { - sensor_mask = strtoull(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("children"), true)) { + unsigned int i = 0; + unsigned int u; + double d1, d2, d3, d4; + const char *ptr = tmp_buffer; + int result; + + while (*ptr != '\0') { + if (i >= ENSEMBLE_SENSOR_CHILDREN_COUNT) handle_return(HTML_DATA_FORMATERROR); + + result = sscanf(ptr, "%u,%lf,%lf,%lf,%lf;", &u, &d1, &d2, &d3, &d4); + + if (result != 5) { + handle_return(HTML_DATA_FORMATERROR); + } + + if (u >= MAX_SENSORS) handle_return(HTML_DATA_FORMATERROR); + + children[i++] = ensemble_children_t {(uint8_t)u, d1, d2, d3, d4}; + + while (*(ptr++) != ';') {} + } } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { @@ -2161,7 +2187,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { action = static_cast(action_raw); } - result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.sensors, sensor_mask, action); + result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.sensors, children, ENSEMBLE_SENSOR_CHILDREN_COUNT, action); break; } case SensorType::ADS1115: { @@ -2222,7 +2248,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { os.sensors[sid].interval = interval; os.sensors[sid].next_update = 0; - os.sensors[sid].value = 0.0; + os.sensors[sid].value = result_sensor->get_inital_value(); os.write_sensor(result_sensor, sid); delete result_sensor; diff --git a/sensor.cpp b/sensor.cpp index 96c687308..b1539eaa6 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -65,39 +65,51 @@ uint32_t Sensor::_deserialize(char *buf) { return i; } -EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, sensor_memory_t *sensors, uint64_t sensor_mask, EnsembleAction action) : +EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action) : Sensor(interval, min, max, scale, offset, name, unit), -sensor_mask(sensor_mask), action(action), -sensors(sensors) {} +sensors(sensors) { + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + if (i < children_count) { + this->children[i] = children[i]; + } else { + this->children[i].sensor_id = 255; + } + } +} -double EnsembleSensor::_get_raw_value() { - double inital; - uint8_t count = 0; +double EnsembleSensor::get_inital_value() { switch (this->action) { case EnsembleAction::Min: - inital = this->max; + return this->max; break; case EnsembleAction::Max: - inital = this->min; + return this->min; break; case EnsembleAction::Average: case EnsembleAction::Sum: - inital = 0; + return 0; break; case EnsembleAction::Product: - inital = 1; + return 1; break; default: // Unreachable return 0.0; } +} + +double EnsembleSensor::_get_raw_value() { + double inital = this->get_inital_value(); + uint8_t count = 0; - uint64_t mask = this->sensor_mask; - uint8_t i = 0; - while (mask) { - if ((mask & 1) && sensors[i].interval) { - double value = sensors[i].value; + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + uint8_t sensor = this->children[i].sensor_id; + if (sensor < MAX_SENSORS && sensors[sensor].interval) { + double value = sensors[sensor].value; + value = (value * this->children[i].scale) + this->children[i].offset; + if (value < this->children[i].min) value = this->children[i].min; + if (value > this->children[i].max) value = this->children[i].max; switch (this->action) { case EnsembleAction::Min: @@ -120,9 +132,6 @@ double EnsembleSensor::_get_raw_value() { count += 1; } - - i += 1; - mask >>= 1; } if (count == 0) { @@ -136,14 +145,27 @@ double EnsembleSensor::_get_raw_value() { uint32_t EnsembleSensor::_serialize_internal(char *buf) { uint32_t i = 0; - i += write_buf(buf+i, this->sensor_mask); + for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { + i += write_buf(buf+i, this->children[j].sensor_id); + i += write_buf(buf+i, this->children[j].min); + i += write_buf(buf+i, this->children[j].max); + i += write_buf(buf+i, this->children[j].scale); + i += write_buf(buf+i, this->children[j].offset); + } + buf[i++] = static_cast(this->action); return i; } EnsembleSensor::EnsembleSensor(sensor_memory_t *sensors, char *buf) { uint32_t i = Sensor::_deserialize(buf); - this->sensor_mask = read_buf(buf, &i); + for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { + this->children[j].sensor_id = read_buf(buf, &i); + this->children[j].min = read_buf(buf, &i); + this->children[j].max = read_buf(buf, &i); + this->children[j].scale = read_buf(buf, &i); + this->children[j].offset = read_buf(buf, &i); + } this->action = static_cast(buf[i++]); this->sensors = sensors; } @@ -154,6 +176,10 @@ Sensor(interval, min, max, scale, offset, name, unit), action(action), weather_getter(weather_getter) {} +double WeatherSensor::get_inital_value() { + return 0.0; +} + double WeatherSensor::_get_raw_value() { return this->weather_getter(this->action); } diff --git a/sensor.h b/sensor.h index 3a01f4031..bf4d1b660 100644 --- a/sensor.h +++ b/sensor.h @@ -79,6 +79,8 @@ class Sensor { SensorUnit unit = SensorUnit::None; SensorType virtual get_sensor_type() = 0; + double virtual get_inital_value() = 0; + private: double virtual _get_raw_value() = 0; protected: @@ -102,18 +104,30 @@ enum class EnsembleAction { typedef Sensor* (*SensorGetter)(uint8_t); +typedef struct { + uint8_t sensor_id; + double min; + double max; + double scale; + double offset; +} ensemble_children_t; + +#define ENSEMBLE_SENSOR_CHILDREN_COUNT 4 + class EnsembleSensor : public Sensor { public: - EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, sensor_memory_t *sensors, uint64_t sensor_mask, EnsembleAction action); + EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); EnsembleSensor(sensor_memory_t *sensors, char *buf); SensorType get_sensor_type() { return SensorType::Ensemble; } - uint64_t sensor_mask; + ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; EnsembleAction action; + double get_inital_value(); + private: double _get_raw_value(); uint32_t _serialize_internal(char *buf); @@ -139,6 +153,8 @@ class WeatherSensor : public Sensor { WeatherAction action; + double get_inital_value(); + private: double _get_raw_value(); uint32_t _serialize_internal(char *buf); From ab5a6a2fa460e444ebd86091110a13bee9586d61 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:56:12 -0400 Subject: [PATCH 040/115] Added sensor adjustments for programs --- OpenSprinkler.cpp | 68 +++++++++++++- OpenSprinkler.h | 2 + defines.h | 6 +- main.cpp | 20 ++++- opensprinkler_server.cpp | 186 ++++++++++++++++++++++++++++++++++++++- sensor.cpp | 77 +++++++++++++++- sensor.h | 29 ++++-- 7 files changed, 371 insertions(+), 17 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 252024df8..62c6e2c85 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2305,8 +2305,7 @@ void OpenSprinkler::factory_reset() { DEBUG_PRINT("Failed to open file: "); DEBUG_PRINTLN(SENSORS_FILENAME); } - - + uint16_t next = SENSOR_LOG_PER_FILE; for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { { @@ -2329,6 +2328,23 @@ void OpenSprinkler::factory_reset() { DEBUG_PRINTLN(f); } } + + remove_file(SENADJ_FILENAME); + file = file_open(SENADJ_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + sensor_adjustment_piecewise_t parts = sensor_adjustment_piecewise_t {0.0, 0.0}; + SensorAdjustment adj = SensorAdjustment(0, 0, 0, nullptr, &parts); + + uint32_t size = adj.serialize(tmp_buffer); + for (size_t i = 0; i < MAX_NUM_PROGRAMS; i++) { + file_write(file, tmp_buffer, size); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } #endif // 5. write 'done' file @@ -2788,6 +2804,54 @@ void OpenSprinkler::poll_sensors() { } } +SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { + if (index > MAX_NUM_PROGRAMS) return nullptr; + ulong pos = SENSOR_ADJUSTMENT_SIZE * index; + + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + file_read(file, tmp_buffer, SENSOR_ADJUSTMENT_SIZE); + + SensorAdjustment *result = new SensorAdjustment(tmp_buffer); + file_close(file); + + if (result->sid == 255) { + delete result; + return nullptr; + } else { + return result; + } + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + return nullptr; + } +} + +void OpenSprinkler::write_sensor_adjust(SensorAdjustment *adj, uint8_t index) { + ulong pos = SENSOR_ADJUSTMENT_SIZE * index; + + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + if (adj) { + ulong len = adj->serialize(tmp_buffer); + file_write(file, tmp_buffer, len); + } else { + tmp_buffer[0] = 0; + file_write(file, tmp_buffer, 1); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } +} + double OpenSprinkler::get_sensor_weather_data(WeatherAction action) { return NAN; // TODO make function for WeatherSensor } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index cc658aacb..19ef61cb7 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -399,6 +399,8 @@ class OpenSprinkler { static void write_sensor(Sensor *sensor, uint8_t index); void log_sensor(uint8_t sid, float value); static void poll_sensors(); + static SensorAdjustment *get_sensor_adjust(uint8_t index); + static void write_sensor_adjust(SensorAdjustment *adj, uint8_t index); static double get_sensor_weather_data(WeatherAction action); #endif diff --git a/defines.h b/defines.h index 54fb429d6..a029e13de 100755 --- a/defines.h +++ b/defines.h @@ -55,8 +55,9 @@ typedef unsigned long ulong; #define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files -#define SENSORS_FILENAME "sens.dat" // used to indicate the completion of all files -#define SENSORS_LOG_FILENAME "senslog.dat" // used to indicate the completion of all files +#define SENSORS_FILENAME "sens.dat" // sensor data file +#define SENSORS_LOG_FILENAME "senslog.dat" // name base for all sensor files +#define SENADJ_FILENAME "senadj.dat" // sensor adjustment data for programs file /** Station macro defines */ #define STN_TYPE_STANDARD 0x00 // standard solenoid station @@ -139,7 +140,6 @@ typedef unsigned long ulong; #define MAX_SOPTS_SIZE 320 // maximum string option size #define MAX_SENSORS 64 -// #define SENSOR_LOG_PER_FILE 1024 #define SENSOR_LOG_PER_FILE 1024 #define SENSOR_LOG_FILE_COUNT 32 #define MAX_SENSOR_LOG_COUNT (SENSOR_LOG_PER_FILE * SENSOR_LOG_FILE_COUNT) diff --git a/main.cpp b/main.cpp index 592eb5d8f..283a744e4 100644 --- a/main.cpp +++ b/main.cpp @@ -821,6 +821,9 @@ void do_loop() // check through all programs for(pid=0; pidget_adjustment_factor(os.sensors); + delete adj; bool will_delete = false; unsigned char runcount = prog.check_match(curr_time, &will_delete); if(runcount>0) { @@ -840,10 +843,11 @@ void do_loop() if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) continue; + ulong dur = (ulong)((double)prog.durations[sid] * adjustment); // if station has non-zero water time and the station is not disabled - if (prog.durations[sid] && !(os.attrib_dis[bid]&(1<0)&&(pid<255)) { pd.read(pid-1, &prog); + SensorAdjustment *adj = os.get_sensor_adjust(pid-1); + adjustment = adj->get_adjustment_factor(os.sensors); + delete adj; notif.add(NOTIFY_PROGRAM_SCHED, pid-1, uwt?os.iopts[IOPT_WATER_PERCENTAGE]:100, 1); } for(sid=0;sid0) + if(pid==255) { + dur=2; + } else if (pid>0) { dur = water_time_resolve(prog.durations[sid]); + dur = (ulong)((double)dur * adjustment); + } + if(uwt) { dur = dur * os.iopts[IOPT_WATER_PERCENTAGE] / 100; } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7b5c50ad9..00484232d 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -829,7 +829,13 @@ void server_delete_program(OTF_PARAMS_DEF) { if (pid == -1) { pd.eraseall(); } else if (pid < pd.nprograms) { - pd.del(pid); + if (pd.del(pid)) { + #if defined(USE_SENSORS) + for (size_t i = pid; i < pd.nprograms; i++) { + file_copy_block(SENADJ_FILENAME, SENSOR_ADJUSTMENT_SIZE * (i+1), SENSOR_ADJUSTMENT_SIZE * i, SENSOR_ADJUSTMENT_SIZE, tmp_buffer); + } + #endif + } } else { handle_return(HTML_DATA_OUTOFBOUND); } @@ -2447,6 +2453,177 @@ void server_log_sensor(OTF_PARAMS_DEF) { handle_return(HTML_OK); } + +void server_json_sen_adj_main(OTF_PARAMS_DEF) { + bfill.emit_p(PSTR("\"adj\":[")); + uint8_t adj_count = 0; + + SensorAdjustment *adj; + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + for (size_t i = 0; i < pd.nprograms; i++) { + if ((adj = os.get_sensor_adjust(i))) { + if (adj_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"splits\":$D,\"points\":["), i, adj->flags, adj->sid, adj->splits); + for (size_t j = 0; j < adj->splits; j++) { + if (j) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("$E"), adj->split_points[j]); + } + bfill.emit_p(PSTR("],\"parts\":["), i, adj->flags, adj->sid, adj->splits); + for (int j = 0; j < adj->splits+1; j++) { + if (j) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"scale\":$E,\"offset\":$E}"), adj->piecewise_parts[j].scale, adj->piecewise_parts[j].offset); + } + bfill.emit_p(PSTR("]}"), i, adj->flags, adj->sid, adj->splits); + adj_count += 1; + delete adj; + + + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } + + bfill.emit_p(PSTR("],\"count\":$D}"), adj_count); +} + +/** Sensor status */ +void server_json_sen_adj(OTF_PARAMS_DEF) +{ +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{")); + server_json_sen_adj_main(OTF_PARAMS); + handle_return(HTML_OK); +} + +void server_change_sen_adj(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); + + char *end; + long pid = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + + if (pid < 0 || pid >= pd.nprograms) handle_return(HTML_DATA_OUTOFBOUND); + + SensorAdjustment *adj = nullptr; + unsigned long flags = 0; + unsigned long sid = 255; + unsigned long splits = 255; + double split_points[SENSOR_ADJUSTMENT_PARTS-1] = {0}; + sensor_adjustment_piecewise_t piecewise_parts[SENSOR_ADJUSTMENT_PARTS] = {0.0, 0.0}; + + if ((adj = os.get_sensor_adjust(pid))) { + flags = adj->flags; + sid = adj->sid; + splits = adj->splits; + + for (size_t i = 0; i <= splits; i++) { + if (i < splits) { + split_points[i] = adj->split_points[i]; + } + + piecewise_parts[i] = adj->piecewise_parts[i]; + } + delete adj; + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flags"), true)) { + flags=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + sid=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (sid >= MAX_SENSORS) sid = 255; + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("splits"), true)) { + splits=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (splits > SENSOR_ADJUSTMENT_PARTS - 1) handle_return(HTML_DATA_FORMATERROR); + + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("points"), true)) { + int i = 0; + double d; + const char *ptr = tmp_buffer; + int result; + + while (*ptr != '\0') { + if (i >= splits) handle_return(HTML_DATA_FORMATERROR); + + result = sscanf(ptr, "%lf;", &d); + + if (result != 1) { + handle_return(HTML_DATA_FORMATERROR); + } + + split_points[i++] = d; + + while (*(ptr++) != ';') {} + } + + if (i != splits) handle_return(HTML_DATA_MISSING); + } else if (splits > 0) { + handle_return(HTML_DATA_MISSING); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("parts"), true)) { + int i = 0; + double d1, d2; + const char *ptr = tmp_buffer; + int result; + + while (*ptr != '\0') { + if (i >= splits+1) handle_return(HTML_DATA_FORMATERROR); + + result = sscanf(ptr, "%lf,%lf;", &d1, &d2); + + if (result != 2) { + handle_return(HTML_DATA_FORMATERROR); + } + + piecewise_parts[i++] = sensor_adjustment_piecewise_t {d1, d2}; + + while (*(ptr++) != ';') {} + } + + if (i != splits+1) handle_return(HTML_DATA_MISSING); + } else { + handle_return(HTML_DATA_MISSING); + } + } + + if (splits == 255) handle_return(HTML_DATA_MISSING); + + adj = new SensorAdjustment(flags, sid, splits, split_points, piecewise_parts); + os.write_sensor_adjust(adj, pid); + delete adj; + + handle_return(HTML_SUCCESS); +} #endif /** Output all JSON data, including jc, jp, jo, js, jn */ @@ -2475,6 +2652,8 @@ void server_json_all(OTF_PARAMS_DEF) { #if defined(USE_SENSORS) bfill.emit_p(PSTR(",\"sensors\":{")); server_json_sensors_main(OTF_PARAMS); + bfill.emit_p(PSTR(",\"senadj\":{")); + server_json_sen_adj_main(OTF_PARAMS); #endif bfill.emit_p(PSTR("}")); handle_return(HTML_OK); @@ -2605,6 +2784,9 @@ const char *uris[] PROGMEM = { "csn", "dsn", "lsn", + "jsa", + "csa", + "dsa", #endif }; @@ -2638,6 +2820,8 @@ URLHandler urls[] = { server_change_sensor, // csn server_delete_sensor, // dsn server_log_sensor, // lsn + server_json_sen_adj, // jsa + server_change_sen_adj, // csa #endif }; #else diff --git a/sensor.cpp b/sensor.cpp index b1539eaa6..b95a28884 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -19,13 +19,13 @@ double Sensor::get_new_value() { } template -uint32_t Sensor::write_buf(char *buf, T val) { +uint32_t write_buf(char *buf, T val) { std::memcpy(buf, &val, sizeof(val)); return sizeof(val); } template -T Sensor::read_buf(char *buf, uint32_t *i) { +T read_buf(char *buf, uint32_t *i) { T val; std::memcpy(&val, buf + (*i), sizeof(T)); *i += sizeof(T); @@ -193,4 +193,77 @@ uint32_t WeatherSensor::_serialize_internal(char *buf) { WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char *buf) { uint32_t i = Sensor::_deserialize(buf); this->action = static_cast(buf[i++]); +} + +SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t splits, double *split_points, sensor_adjustment_piecewise_t *piecewise_parts) { + this->flags = flags; + this->sid = sid; + if (splits > SENSOR_ADJUSTMENT_PARTS - 1) splits = SENSOR_ADJUSTMENT_PARTS - 1; + this->splits = splits; + for (size_t i = 0; i <= splits; i++) { + if (i < splits) { + this->split_points[i] = split_points[i]; + } + + this->piecewise_parts[i] = piecewise_parts[i]; + } + +} + +SensorAdjustment::SensorAdjustment(char *buf) { + uint32_t i = 0; + this->flags = buf[i++]; + this->sid = buf[i++]; + this->splits = buf[i++]; + + for (size_t j = 0; j < SENSOR_ADJUSTMENT_PARTS; j++) { + this->piecewise_parts[j].scale = read_buf(buf, &i); + this->piecewise_parts[j].offset = read_buf(buf, &i); + } + + for (size_t j = 0; j < SENSOR_ADJUSTMENT_PARTS-1; j++) { + this->split_points[j] = read_buf(buf, &i); + } +} + +double SensorAdjustment::get_adjustment_factor(sensor_memory_t *sensors) { + if (this->flags & 1 && sensors[this->sid].interval) { + double value = sensors[1].value; + uint8_t i; + double split_point = 0; + for (i = 0; i < this->splits; i++) { + if (this->split_points[i] > value) { + break; + } + + split_point = this->split_points[i]; + } + + sensor_adjustment_piecewise_t part = this->piecewise_parts[i]; + value -= split_point; + value = (value * part.scale) + part.offset; + if (value < 0.0) value = 0.0; + + return value; + } else { + return 1.0; + } +} + +uint32_t SensorAdjustment::serialize(char *buf) { + uint32_t i = 0; + buf[i++] = this->flags; + buf[i++] = this->sid; + buf[i++] = this->splits; + + for (size_t j = 0; j < SENSOR_ADJUSTMENT_PARTS; j++) { + i += write_buf(buf+i, this->piecewise_parts[j].scale); + i += write_buf(buf+i, this->piecewise_parts[j].offset); + } + + for (size_t j = 0; j < SENSOR_ADJUSTMENT_PARTS-1; j++) { + i += write_buf(buf+i, this->split_points[j]); + } + + return i; } \ No newline at end of file diff --git a/sensor.h b/sensor.h index bf4d1b660..3805d1af5 100644 --- a/sensor.h +++ b/sensor.h @@ -85,11 +85,6 @@ class Sensor { double virtual _get_raw_value() = 0; protected: uint32_t _deserialize(char *buf); - template - uint32_t write_buf(char *buf, T val); - template - T read_buf(char *buf, uint32_t *i); - uint32_t virtual _serialize_internal(char *buf) = 0; }; @@ -162,4 +157,28 @@ class WeatherSensor : public Sensor { WeatherGetter weather_getter; }; +typedef struct { + double scale; + double offset; +} sensor_adjustment_piecewise_t; + +#define SENSOR_ADJUSTMENT_PARTS 4 + +class SensorAdjustment { +public: + SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t splits, double *split_points, sensor_adjustment_piecewise_t *piecewise_parts); + SensorAdjustment(char *buf); + + double get_adjustment_factor(sensor_memory_t *sensors); + uint32_t serialize(char *buf); + + uint8_t flags; + uint8_t sid; + uint8_t splits; + double split_points[SENSOR_ADJUSTMENT_PARTS-1]; + sensor_adjustment_piecewise_t piecewise_parts[SENSOR_ADJUSTMENT_PARTS]; +}; + +#define SENSOR_ADJUSTMENT_SIZE (3 + (SENSOR_ADJUSTMENT_PARTS * sizeof(sensor_adjustment_piecewise_t)) + ((SENSOR_ADJUSTMENT_PARTS - 1) * sizeof(double))) + #endif //SENSOR_H \ No newline at end of file From cea79c408c69c248af4b7a4701f6015e123e88a1 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 14:00:52 -0400 Subject: [PATCH 041/115] gate the adjustments inside of a defined block for demo --- main.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/main.cpp b/main.cpp index 283a744e4..ea48d0b9a 100644 --- a/main.cpp +++ b/main.cpp @@ -821,9 +821,13 @@ void do_loop() // check through all programs for(pid=0; pidget_adjustment_factor(os.sensors); delete adj; + #else + double adjustment = 1.0; + #endif bool will_delete = false; unsigned char runcount = prog.check_match(curr_time, &will_delete); if(runcount>0) { @@ -1472,9 +1476,11 @@ void manual_start_program(unsigned char pid, unsigned char uwt) { unsigned char sid, bid, s; if ((pid>0)&&(pid<255)) { pd.read(pid-1, &prog); + #if defined(USE_SENSORS) SensorAdjustment *adj = os.get_sensor_adjust(pid-1); adjustment = adj->get_adjustment_factor(os.sensors); delete adj; + #endif notif.add(NOTIFY_PROGRAM_SCHED, pid-1, uwt?os.iopts[IOPT_WATER_PERCENTAGE]:100, 1); } for(sid=0;sid Date: Mon, 4 Aug 2025 14:39:26 -0400 Subject: [PATCH 042/115] removed dsa which doesn't exist --- opensprinkler_server.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 00484232d..702faf9bf 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1998,7 +1998,7 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { for (size_t i = 0; i < MAX_SENSORS; i++) { if (os.sensors[i].interval && (sensor = os.parse_sensor(file))) { if (sensor_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"id\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E}"), i, sensor->name, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value); + bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E}"), i, sensor->name, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value); sensor_count += 1; delete sensor; @@ -2786,7 +2786,6 @@ const char *uris[] PROGMEM = { "lsn", "jsa", "csa", - "dsa", #endif }; From d039d2dd834a1cfcd125d51dd46de3c7ecb5c96c Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:24:43 -0400 Subject: [PATCH 043/115] allowed jsn to give information specific to sensor type --- ads1115.cpp | 4 ++ ads1115.h | 2 + bfiller.h | 85 ++++++++++++++++++++++++++++++++++++++++ mainArduino.ino.cpp | 17 ++++++++ opensprinkler_server.cpp | 15 +++---- opensprinkler_server.h | 77 +----------------------------------- sensor.cpp | 13 ++++++ sensor.h | 6 +++ utils.cpp | 7 +++- utils.h | 2 + 10 files changed, 143 insertions(+), 85 deletions(-) create mode 100644 bfiller.h create mode 100644 mainArduino.ino.cpp diff --git a/ads1115.cpp b/ads1115.cpp index c7aa20076..6cc983938 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -86,6 +86,10 @@ sensor_index(sensor_index), pin(pin), sensors(sensors) {} +void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { + bfill->emit_p(PSTR("{\"index\":$D,\"pin\":$D}"), this->sensor_index, this->pin); +} + double ADS1115Sensor::get_inital_value() { return 0.0; } diff --git a/ads1115.h b/ads1115.h index 2e73be6c1..b36a8a288 100644 --- a/ads1115.h +++ b/ads1115.h @@ -54,6 +54,8 @@ class ADS1115Sensor : public Sensor { ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); ADS1115Sensor(ADS1115 **sensors, char *buf); + void emit_extra_json(BufferFiller *bfill); + SensorType get_sensor_type() { return SensorType::ADS1115; } diff --git a/bfiller.h b/bfiller.h new file mode 100644 index 000000000..87b485994 --- /dev/null +++ b/bfiller.h @@ -0,0 +1,85 @@ +#ifndef _BFILLER_H +#define _BFILLER_H + +#include "utils.h" + +#if defined(ARDUINO) +#include +#else +#include +#include +#endif + +class BufferFiller { + char *start; //!< Pointer to start of buffer + char *ptr; //!< Pointer to cursor position + size_t len; +public: + BufferFiller () {} + BufferFiller (char *buf, size_t buffer_len) { + start = buf; + ptr = buf; + len = buffer_len; + } + + char* buffer () const { return start; } + size_t length () const { return len; } + unsigned int position () const { return ptr - start; } + + void emit_p(PGM_P fmt, ...) { + va_list ap; + va_start(ap, fmt); + for (;;) { + char c = pgm_read_byte(fmt++); + if (c == 0) + break; + if (c != '$') { + *ptr++ = c; + continue; + } + c = pgm_read_byte(fmt++); + switch (c) { + case 'D': + // itoa(va_arg(ap, int), (char*) ptr, 10); // ray + snprintf((char*) ptr, len - position(), "%d", va_arg(ap, int)); + break; + case 'E': //Double + sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); + break; + case 'L': + // ultoa(va_arg(ap, uint32_t), (char*) ptr, 10); + snprintf((char*) ptr, len - position(), "%lu", (unsigned long) va_arg(ap, uint32_t)); + break; + case 'S': + strcpy((char*) ptr, va_arg(ap, const char*)); + break; + case 'X': { + char d = va_arg(ap, int); + *ptr++ = dec2hexchar((d >> 4) & 0x0F); + *ptr++ = dec2hexchar(d & 0x0F); + } + continue; + case 'F': { + PGM_P s = va_arg(ap, PGM_P); + char d; + while ((d = pgm_read_byte(s++)) != 0) + *ptr++ = d; + continue; + } + case 'O': { + uint16_t oid = va_arg(ap, int); + file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); + } + break; + default: + *ptr++ = c; + continue; + } + ptr += strlen((char*) ptr); + } + *(ptr)=0; + va_end(ap); + } +}; + +#endif //_BFILLER_H \ No newline at end of file diff --git a/mainArduino.ino.cpp b/mainArduino.ino.cpp new file mode 100644 index 000000000..97b75de59 --- /dev/null +++ b/mainArduino.ino.cpp @@ -0,0 +1,17 @@ +# 1 "/var/folders/r5/7m6_x43n3vs1k7xvkh_3vh2c0000gn/T/tmpxqq5jl8f" +#include +# 1 "/Users/gayinc/Projects/OpenSprinkler-Firmware/mainArduino.ino" +#include "OpenSprinkler.h" + +void do_setup(); +void do_loop(); +void setup(); +void loop(); +#line 6 "/Users/gayinc/Projects/OpenSprinkler-Firmware/mainArduino.ino" +void setup() { + do_setup(); +} + +void loop() { + do_loop(); +} \ No newline at end of file diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 702faf9bf..04a4860b7 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -24,7 +24,7 @@ #include "types.h" #include "OpenSprinkler.h" #include "program.h" -#include "opensprinkler_server.h" +#include "bfiller.h" #include "weather.h" #include "mqtt.h" #include "main.h" @@ -220,11 +220,6 @@ void send_packet(OTF_PARAMS_DEF) { rewind_ether_buffer(); } -char dec2hexchar(unsigned char dec) { - if(dec<10) return '0'+dec; - else return 'A'+(dec-10); -} - #if defined(USE_OTF) void print_header(OTF_PARAMS_DEF, bool isJson=true, int len=0) { res.writeStatus(200, F("OK")); @@ -1998,7 +1993,9 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { for (size_t i = 0; i < MAX_SENSORS; i++) { if (os.sensors[i].interval && (sensor = os.parse_sensor(file))) { if (sensor_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E}"), i, sensor->name, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value); + bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"extra\":"), i, sensor->name, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value); + sensor->emit_extra_json(&bfill); + bfill.emit_p(PSTR("}")); sensor_count += 1; delete sensor; @@ -2566,7 +2563,7 @@ void server_change_sen_adj(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("points"), true)) { - int i = 0; + unsigned long i = 0; double d; const char *ptr = tmp_buffer; int result; @@ -2591,7 +2588,7 @@ void server_change_sen_adj(OTF_PARAMS_DEF) { } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("parts"), true)) { - int i = 0; + unsigned long i = 0; double d1, d2; const char *ptr = tmp_buffer; int result; diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 1eed0af26..409385cc5 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -24,85 +24,12 @@ #ifndef _OPENSPRINKLER_SERVER_H #define _OPENSPRINKLER_SERVER_H +#include "bfiller.h" + #if !defined(ARDUINO) #include #include #include #endif -char dec2hexchar(unsigned char dec); - -class BufferFiller { - char *start; //!< Pointer to start of buffer - char *ptr; //!< Pointer to cursor position - size_t len; -public: - BufferFiller () {} - BufferFiller (char *buf, size_t buffer_len) { - start = buf; - ptr = buf; - len = buffer_len; - } - - char* buffer () const { return start; } - size_t length () const { return len; } - unsigned int position () const { return ptr - start; } - - void emit_p(PGM_P fmt, ...) { - va_list ap; - va_start(ap, fmt); - for (;;) { - char c = pgm_read_byte(fmt++); - if (c == 0) - break; - if (c != '$') { - *ptr++ = c; - continue; - } - c = pgm_read_byte(fmt++); - switch (c) { - case 'D': - // itoa(va_arg(ap, int), (char*) ptr, 10); // ray - snprintf((char*) ptr, len - position(), "%d", va_arg(ap, int)); - break; - case 'E': //Double - sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); - break; - case 'L': - // ultoa(va_arg(ap, uint32_t), (char*) ptr, 10); - snprintf((char*) ptr, len - position(), "%lu", (unsigned long) va_arg(ap, uint32_t)); - break; - case 'S': - strcpy((char*) ptr, va_arg(ap, const char*)); - break; - case 'X': { - char d = va_arg(ap, int); - *ptr++ = dec2hexchar((d >> 4) & 0x0F); - *ptr++ = dec2hexchar(d & 0x0F); - } - continue; - case 'F': { - PGM_P s = va_arg(ap, PGM_P); - char d; - while ((d = pgm_read_byte(s++)) != 0) - *ptr++ = d; - continue; - } - case 'O': { - uint16_t oid = va_arg(ap, int); - file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); - } - break; - default: - *ptr++ = c; - continue; - } - ptr += strlen((char*) ptr); - } - *(ptr)=0; - va_end(ap); - } -}; - - #endif // _OPENSPRINKLER_SERVER_H diff --git a/sensor.cpp b/sensor.cpp index b95a28884..ce502586e 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -78,6 +78,15 @@ sensors(sensors) { } } +void EnsembleSensor::emit_extra_json(BufferFiller *bfill) { + bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + ensemble_children_t *child = &this->children[i]; + bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->max, child->min, child->scale, child->offset); + } + bfill->emit_p(PSTR("]}")); +} + double EnsembleSensor::get_inital_value() { switch (this->action) { case EnsembleAction::Min: @@ -176,6 +185,10 @@ Sensor(interval, min, max, scale, offset, name, unit), action(action), weather_getter(weather_getter) {} +void WeatherSensor::emit_extra_json(BufferFiller *bfill) { + bfill->emit_p(PSTR("{\"action\":$D}"), this->action); +} + double WeatherSensor::get_inital_value() { return 0.0; } diff --git a/sensor.h b/sensor.h index 3805d1af5..6b441cfed 100644 --- a/sensor.h +++ b/sensor.h @@ -8,6 +8,7 @@ #include "utils.h" #endif #include "defines.h" +#include "bfiller.h" #define SENSOR_NAME_LEN 33 #define SENSOR_CUSTOM_UNIT_LEN 9 @@ -70,6 +71,8 @@ class Sensor { double get_new_value(); uint32_t serialize(char *buf); + void virtual emit_extra_json(BufferFiller *bfill) = 0; + unsigned long interval = 1; double min = 0.0; double max = 0.0; @@ -114,6 +117,8 @@ class EnsembleSensor : public Sensor { EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); EnsembleSensor(sensor_memory_t *sensors, char *buf); + void emit_extra_json(BufferFiller *bfill); + SensorType get_sensor_type() { return SensorType::Ensemble; } @@ -141,6 +146,7 @@ class WeatherSensor : public Sensor { WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, WeatherGetter weather_getter, WeatherAction action); WeatherSensor(WeatherGetter weather_getter, char *buf); + void emit_extra_json(BufferFiller *bfill); SensorType get_sensor_type() { return SensorType::Weather; diff --git a/utils.cpp b/utils.cpp index 4f272520b..9f0c8dc58 100644 --- a/utils.cpp +++ b/utils.cpp @@ -816,4 +816,9 @@ void str2mac(const char *_str, unsigned char mac[]) { yield(); } } -#endif \ No newline at end of file +#endif + +char dec2hexchar(unsigned char dec) { + if(dec<10) return '0'+dec; + else return 'A'+(dec-10); +} \ No newline at end of file diff --git a/utils.h b/utils.h index f4a9c32ce..d6270eeba 100644 --- a/utils.h +++ b/utils.h @@ -145,4 +145,6 @@ void str2mac(const char *_str, unsigned char mac[]); BoardType get_board_type(); #endif +char dec2hexchar(unsigned char dec); + #endif // _UTILS_H From 4ec2b8d2958c3aa969d189752615ad6080915a6e Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:30:02 -0400 Subject: [PATCH 044/115] fix json --- mainArduino.ino.cpp | 17 ----------------- sensor.cpp | 1 + 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 mainArduino.ino.cpp diff --git a/mainArduino.ino.cpp b/mainArduino.ino.cpp deleted file mode 100644 index 97b75de59..000000000 --- a/mainArduino.ino.cpp +++ /dev/null @@ -1,17 +0,0 @@ -# 1 "/var/folders/r5/7m6_x43n3vs1k7xvkh_3vh2c0000gn/T/tmpxqq5jl8f" -#include -# 1 "/Users/gayinc/Projects/OpenSprinkler-Firmware/mainArduino.ino" -#include "OpenSprinkler.h" - -void do_setup(); -void do_loop(); -void setup(); -void loop(); -#line 6 "/Users/gayinc/Projects/OpenSprinkler-Firmware/mainArduino.ino" -void setup() { - do_setup(); -} - -void loop() { - do_loop(); -} \ No newline at end of file diff --git a/sensor.cpp b/sensor.cpp index ce502586e..dac4f385a 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -81,6 +81,7 @@ sensors(sensors) { void EnsembleSensor::emit_extra_json(BufferFiller *bfill) { bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + if (i) bfill->emit_p(PSTR(",")); ensemble_children_t *child = &this->children[i]; bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->max, child->min, child->scale, child->offset); } From 540ad16a9567171b9a073f4dd3f14bde42411ba2 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:33:48 -0400 Subject: [PATCH 045/115] Try to fix compilation on docker --- opensprinkler_server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 04a4860b7..45a62bd55 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2346,6 +2346,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { file_no = (cursor - next) / SENSOR_LOG_PER_FILE; } + using std::numeric_limits; time_os_t before = std::numeric_limits::max(); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) { before = (time_os_t)strtoul(tmp_buffer, &end, 10); From a171650635d95060a3005b6bc316e5bdfe85afef Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:44:15 -0400 Subject: [PATCH 046/115] update otf --- external/OpenThings-Framework-Firmware-Library | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/OpenThings-Framework-Firmware-Library b/external/OpenThings-Framework-Firmware-Library index 4e9306153..a818be8e5 160000 --- a/external/OpenThings-Framework-Firmware-Library +++ b/external/OpenThings-Framework-Firmware-Library @@ -1 +1 @@ -Subproject commit 4e93061538da197e1820c7b325a3c996ff7851ea +Subproject commit a818be8e57eb00d27ffb27c3600b58ea0ab417f7 From e3ac642325a67389080b2a3b3fe261c447a8603b Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:51:28 -0400 Subject: [PATCH 047/115] update otf --- external/OpenThings-Framework-Firmware-Library | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/external/OpenThings-Framework-Firmware-Library b/external/OpenThings-Framework-Firmware-Library index a818be8e5..86d621734 160000 --- a/external/OpenThings-Framework-Firmware-Library +++ b/external/OpenThings-Framework-Firmware-Library @@ -1 +1 @@ -Subproject commit a818be8e57eb00d27ffb27c3600b58ea0ab417f7 +Subproject commit 86d621734cf8d457575f38f7ab7fa83dcb074b79 From c88b17e9f841b007d0f015388f9eeea36ee2dce4 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:54:10 -0400 Subject: [PATCH 048/115] move numeric limits to above bfiller.h --- opensprinkler_server.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 409385cc5..822407edd 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -24,12 +24,12 @@ #ifndef _OPENSPRINKLER_SERVER_H #define _OPENSPRINKLER_SERVER_H +#include #include "bfiller.h" #if !defined(ARDUINO) #include #include -#include #endif #endif // _OPENSPRINKLER_SERVER_H From f103fd5380c9f954c3b52f6bffcd7fc45c6a1fb1 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 15:57:06 -0400 Subject: [PATCH 049/115] include types.h --- opensprinkler_server.h | 1 + 1 file changed, 1 insertion(+) diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 822407edd..f4f443dff 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -24,6 +24,7 @@ #ifndef _OPENSPRINKLER_SERVER_H #define _OPENSPRINKLER_SERVER_H +#include "types.h" #include #include "bfiller.h" From 5dfe115fbb4c949f6ab755cbc13a974d862f154d Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:01:50 -0400 Subject: [PATCH 050/115] added missing include for opensprinkler server --- opensprinkler_server.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 45a62bd55..422f3eddd 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -21,6 +21,7 @@ * . */ +#include "opensprinkler_server.h" #include "types.h" #include "OpenSprinkler.h" #include "program.h" From d824039c21ef0acf51f17e74098966fe7e4d4fa8 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 4 Aug 2025 16:04:23 -0400 Subject: [PATCH 051/115] added missing stdarg --- bfiller.h | 1 + 1 file changed, 1 insertion(+) diff --git a/bfiller.h b/bfiller.h index 87b485994..f60cd3940 100644 --- a/bfiller.h +++ b/bfiller.h @@ -8,6 +8,7 @@ #else #include #include +#include #endif class BufferFiller { From 018968aff95851787b0a39087b9894815b68282c Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 6 Aug 2025 15:56:53 -0400 Subject: [PATCH 052/115] switch to using points to describe function instead of piecewise --- OpenSprinkler.cpp | 4 +- opensprinkler_server.cpp | 96 +++++++++++----------------------------- sensor.cpp | 63 +++++++++++--------------- sensor.h | 17 ++++--- 4 files changed, 63 insertions(+), 117 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 62c6e2c85..13ccd5e6e 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2332,8 +2332,8 @@ void OpenSprinkler::factory_reset() { remove_file(SENADJ_FILENAME); file = file_open(SENADJ_FILENAME, FileOpenMode::WriteTruncate); if (file) { - sensor_adjustment_piecewise_t parts = sensor_adjustment_piecewise_t {0.0, 0.0}; - SensorAdjustment adj = SensorAdjustment(0, 0, 0, nullptr, &parts); + sensor_adjustment_point_t point = sensor_adjustment_point_t {0.0, 0.0}; + SensorAdjustment adj = SensorAdjustment(0, 0, 0, &point); uint32_t size = adj.serialize(tmp_buffer); for (size_t i = 0; i < MAX_NUM_PROGRAMS; i++) { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 422f3eddd..b18c12493 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2463,17 +2463,12 @@ void server_json_sen_adj_main(OTF_PARAMS_DEF) { for (size_t i = 0; i < pd.nprograms; i++) { if ((adj = os.get_sensor_adjust(i))) { if (adj_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"splits\":$D,\"points\":["), i, adj->flags, adj->sid, adj->splits); - for (size_t j = 0; j < adj->splits; j++) { + bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); + for (int j = 0; j < adj->point_count; j++) { if (j) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("$E"), adj->split_points[j]); + bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); } - bfill.emit_p(PSTR("],\"parts\":["), i, adj->flags, adj->sid, adj->splits); - for (int j = 0; j < adj->splits+1; j++) { - if (j) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"scale\":$E,\"offset\":$E}"), adj->piecewise_parts[j].scale, adj->piecewise_parts[j].offset); - } - bfill.emit_p(PSTR("]}"), i, adj->flags, adj->sid, adj->splits); + bfill.emit_p(PSTR("]}")); adj_count += 1; delete adj; @@ -2528,21 +2523,16 @@ void server_change_sen_adj(OTF_PARAMS_DEF) { SensorAdjustment *adj = nullptr; unsigned long flags = 0; unsigned long sid = 255; - unsigned long splits = 255; - double split_points[SENSOR_ADJUSTMENT_PARTS-1] = {0}; - sensor_adjustment_piecewise_t piecewise_parts[SENSOR_ADJUSTMENT_PARTS] = {0.0, 0.0}; + unsigned long point_count = 0; + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; if ((adj = os.get_sensor_adjust(pid))) { flags = adj->flags; sid = adj->sid; - splits = adj->splits; - - for (size_t i = 0; i <= splits; i++) { - if (i < splits) { - split_points[i] = adj->split_points[i]; - } + point_count = adj->point_count; - piecewise_parts[i] = adj->piecewise_parts[i]; + for (size_t i = 0; i <= point_count; i++) { + points[i] = adj->points[i]; } delete adj; } @@ -2558,66 +2548,32 @@ void server_change_sen_adj(OTF_PARAMS_DEF) { if (sid >= MAX_SENSORS) sid = 255; } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("splits"), true)) { - splits=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (splits > SENSOR_ADJUSTMENT_PARTS - 1) handle_return(HTML_DATA_FORMATERROR); - + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("points"), true)) { + unsigned long i = 0; + double x, y; + const char *ptr = tmp_buffer; + int result; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("points"), true)) { - unsigned long i = 0; - double d; - const char *ptr = tmp_buffer; - int result; + while (*ptr != '\0') { + if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); - while (*ptr != '\0') { - if (i >= splits) handle_return(HTML_DATA_FORMATERROR); + result = sscanf(ptr, "%lf,%lf;", &x, &y); - result = sscanf(ptr, "%lf;", &d); - - if (result != 1) { - handle_return(HTML_DATA_FORMATERROR); - } - - split_points[i++] = d; - - while (*(ptr++) != ';') {} + if (result != 2) { + handle_return(HTML_DATA_FORMATERROR); } - if (i != splits) handle_return(HTML_DATA_MISSING); - } else if (splits > 0) { - handle_return(HTML_DATA_MISSING); - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("parts"), true)) { - unsigned long i = 0; - double d1, d2; - const char *ptr = tmp_buffer; - int result; + points[i++] = sensor_adjustment_point_t {x, y}; - while (*ptr != '\0') { - if (i >= splits+1) handle_return(HTML_DATA_FORMATERROR); - - result = sscanf(ptr, "%lf,%lf;", &d1, &d2); - - if (result != 2) { - handle_return(HTML_DATA_FORMATERROR); - } - - piecewise_parts[i++] = sensor_adjustment_piecewise_t {d1, d2}; - - while (*(ptr++) != ';') {} - } - - if (i != splits+1) handle_return(HTML_DATA_MISSING); - } else { - handle_return(HTML_DATA_MISSING); + while (*(ptr++) != ';') {} } - } - if (splits == 255) handle_return(HTML_DATA_MISSING); + point_count = i; + } + + if (point_count == 0) handle_return(HTML_DATA_MISSING); - adj = new SensorAdjustment(flags, sid, splits, split_points, piecewise_parts); + adj = new SensorAdjustment(flags, sid, point_count, points); os.write_sensor_adjust(adj, pid); delete adj; diff --git a/sensor.cpp b/sensor.cpp index dac4f385a..dd2b12d75 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -209,17 +209,13 @@ WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char *buf) { this->action = static_cast(buf[i++]); } -SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t splits, double *split_points, sensor_adjustment_piecewise_t *piecewise_parts) { +SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points) { this->flags = flags; this->sid = sid; - if (splits > SENSOR_ADJUSTMENT_PARTS - 1) splits = SENSOR_ADJUSTMENT_PARTS - 1; - this->splits = splits; - for (size_t i = 0; i <= splits; i++) { - if (i < splits) { - this->split_points[i] = split_points[i]; - } - - this->piecewise_parts[i] = piecewise_parts[i]; + if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; + this->point_count = point_count; + for (size_t i = 0; i < point_count; i++) { + this->points[i] = points[i]; } } @@ -228,36 +224,35 @@ SensorAdjustment::SensorAdjustment(char *buf) { uint32_t i = 0; this->flags = buf[i++]; this->sid = buf[i++]; - this->splits = buf[i++]; + this->point_count = buf[i++]; - for (size_t j = 0; j < SENSOR_ADJUSTMENT_PARTS; j++) { - this->piecewise_parts[j].scale = read_buf(buf, &i); - this->piecewise_parts[j].offset = read_buf(buf, &i); - } - - for (size_t j = 0; j < SENSOR_ADJUSTMENT_PARTS-1; j++) { - this->split_points[j] = read_buf(buf, &i); + for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { + this->points[j].x = read_buf(buf, &i); + this->points[j].y = read_buf(buf, &i); } } double SensorAdjustment::get_adjustment_factor(sensor_memory_t *sensors) { - if (this->flags & 1 && sensors[this->sid].interval) { - double value = sensors[1].value; + if (this->flags & 1 && this->sid < MAX_SENSORS && sensors[this->sid].interval) { + double value = sensors[this->sid].value; + if (value <= this->points[0].x) return this->points[0].y; + if (value >= this->points[this->point_count-1].x) return this->points[this->point_count-1].y; + uint8_t i; - double split_point = 0; - for (i = 0; i < this->splits; i++) { - if (this->split_points[i] > value) { + + for (i = 0; i < this->point_count-1; i++) { + if (value >= this->points[i].x) { break; } - - split_point = this->split_points[i]; } - sensor_adjustment_piecewise_t part = this->piecewise_parts[i]; - value -= split_point; - value = (value * part.scale) + part.offset; - if (value < 0.0) value = 0.0; + sensor_adjustment_point_t left = this->points[i]; + sensor_adjustment_point_t right = this->points[i+1]; + if (right.x == left.x) return left.y; + + value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; + if (value < 0) return 0; return value; } else { return 1.0; @@ -268,15 +263,11 @@ uint32_t SensorAdjustment::serialize(char *buf) { uint32_t i = 0; buf[i++] = this->flags; buf[i++] = this->sid; - buf[i++] = this->splits; - - for (size_t j = 0; j < SENSOR_ADJUSTMENT_PARTS; j++) { - i += write_buf(buf+i, this->piecewise_parts[j].scale); - i += write_buf(buf+i, this->piecewise_parts[j].offset); - } + buf[i++] = this->point_count; - for (size_t j = 0; j < SENSOR_ADJUSTMENT_PARTS-1; j++) { - i += write_buf(buf+i, this->split_points[j]); + for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { + i += write_buf(buf+i, this->points[j].x); + i += write_buf(buf+i, this->points[j].y); } return i; diff --git a/sensor.h b/sensor.h index 6b441cfed..fa9bef1a6 100644 --- a/sensor.h +++ b/sensor.h @@ -164,15 +164,15 @@ class WeatherSensor : public Sensor { }; typedef struct { - double scale; - double offset; -} sensor_adjustment_piecewise_t; + double x; + double y; +} sensor_adjustment_point_t; -#define SENSOR_ADJUSTMENT_PARTS 4 +#define SENSOR_ADJUSTMENT_POINTS 8 class SensorAdjustment { public: - SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t splits, double *split_points, sensor_adjustment_piecewise_t *piecewise_parts); + SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points); SensorAdjustment(char *buf); double get_adjustment_factor(sensor_memory_t *sensors); @@ -180,11 +180,10 @@ class SensorAdjustment { uint8_t flags; uint8_t sid; - uint8_t splits; - double split_points[SENSOR_ADJUSTMENT_PARTS-1]; - sensor_adjustment_piecewise_t piecewise_parts[SENSOR_ADJUSTMENT_PARTS]; + uint8_t point_count; + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; }; -#define SENSOR_ADJUSTMENT_SIZE (3 + (SENSOR_ADJUSTMENT_PARTS * sizeof(sensor_adjustment_piecewise_t)) + ((SENSOR_ADJUSTMENT_PARTS - 1) * sizeof(double))) +#define SENSOR_ADJUSTMENT_SIZE (3 + (SENSOR_ADJUSTMENT_POINTS * sizeof(sensor_adjustment_point_t))) #endif //SENSOR_H \ No newline at end of file From 76f80ec0707ced4ce833f4d00f6b3eb69ddd14a0 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:02:06 -0400 Subject: [PATCH 053/115] ensure that points are entered in order --- opensprinkler_server.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index b18c12493..b84bf92da 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2553,18 +2553,21 @@ void server_change_sen_adj(OTF_PARAMS_DEF) { double x, y; const char *ptr = tmp_buffer; int result; + double last_x = -infinity(); while (*ptr != '\0') { if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); result = sscanf(ptr, "%lf,%lf;", &x, &y); - if (result != 2) { + if (result != 2 || x < last_x) { handle_return(HTML_DATA_FORMATERROR); } points[i++] = sensor_adjustment_point_t {x, y}; + last_x = x; + while (*(ptr++) != ';') {} } From 9d05165b2c17edd4bc351198468fe6751f800a19 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 6 Aug 2025 16:11:32 -0400 Subject: [PATCH 054/115] use std limits infinity --- opensprinkler_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index b84bf92da..59796f221 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2553,7 +2553,7 @@ void server_change_sen_adj(OTF_PARAMS_DEF) { double x, y; const char *ptr = tmp_buffer; int result; - double last_x = -infinity(); + double last_x = -std::numeric_limits::infinity();; while (*ptr != '\0') { if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); From e7b8791541d8596d8ed5e76ac22649f9ad3602fc Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 6 Aug 2025 17:18:22 -0400 Subject: [PATCH 055/115] added sensor type to json --- opensprinkler_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 59796f221..53c7b25b2 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1994,7 +1994,7 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { for (size_t i = 0; i < MAX_SENSORS; i++) { if (os.sensors[i].interval && (sensor = os.parse_sensor(file))) { if (sensor_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"extra\":"), i, sensor->name, sensor->unit, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value); + bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); sensor->emit_extra_json(&bfill); bfill.emit_p(PSTR("}")); sensor_count += 1; From 2bf808b9f1a63795ef72e02eee263907984b8cbb Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 7 Aug 2025 12:50:41 -0400 Subject: [PATCH 056/115] add sensors flags to disable sensor/logging, and fixed the json output from ensemble sensor children --- OpenSprinkler.cpp | 7 +++++-- ads1115.cpp | 4 ++-- ads1115.h | 2 +- opensprinkler_server.cpp | 24 +++++++++++++++++++----- sensor.cpp | 21 ++++++++++++--------- sensor.h | 24 +++++++++++++++++++++--- 6 files changed, 60 insertions(+), 22 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 13ccd5e6e..27c154af5 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2710,6 +2710,7 @@ void OpenSprinkler::load_sensors() { for (size_t i = 0; i < MAX_SENSORS; i++) { if ((sensor = parse_sensor(file))) { sensors[i].interval = sensor->interval; + sensors[i].flags = sensor->flags; sensors[i].next_update = 0; sensors[i].value = sensor->get_inital_value(); delete sensor; @@ -2790,14 +2791,16 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { void OpenSprinkler::poll_sensors() { for (uint8_t i = 0; i < MAX_SENSORS; i++) { - if (sensors[i].interval) { + if (sensors[i].interval && sensors[i].flags & (1 << SENSOR_FLAG_ENABLE)) { if ((long)(millis() - sensors[i].next_update) > 0) { Sensor *sensor = get_sensor(i); if (sensor) { sensors[i].value = sensor->get_new_value(); delete sensor; sensors[i].next_update = millis() + sensors[i].interval; - os.log_sensor(i, sensors[i].value); + if (sensors[i].flags & (1 << SENSOR_FLAG_LOG)) { + os.log_sensor(i, sensors[i].value); + } } } } diff --git a/ads1115.cpp b/ads1115.cpp index 6cc983938..2145a73ad 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -80,8 +80,8 @@ void ADS1115::request_pin(uint8_t pin) { this->_write_register(0x01, config); } -ADS1115Sensor::ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : -Sensor(interval, min, max, scale, offset, name, unit), +ADS1115Sensor::ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : +Sensor(interval, min, max, scale, offset, name, unit, flags), sensor_index(sensor_index), pin(pin), sensors(sensors) {} diff --git a/ads1115.h b/ads1115.h index b36a8a288..b2a1085a8 100644 --- a/ads1115.h +++ b/ads1115.h @@ -51,7 +51,7 @@ ADS1115(uint8_t address); class ADS1115Sensor : public Sensor { public: - ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); ADS1115Sensor(ADS1115 **sensors, char *buf); void emit_extra_json(BufferFiller *bfill); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 53c7b25b2..d0e9ef7d8 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1994,7 +1994,7 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { for (size_t i = 0; i < MAX_SENSORS; i++) { if (os.sensors[i].interval && (sensor = os.parse_sensor(file))) { if (sensor_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); + bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); sensor->emit_extra_json(&bfill); bfill.emit_p(PSTR("}")); sensor_count += 1; @@ -2073,6 +2073,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { double offset = 0; ulong interval = 1000; SensorUnit unit = SensorUnit::None; + uint32_t flags = 0; char name[SENSOR_NAME_LEN]; snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); @@ -2088,6 +2089,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { offset = sensor->offset; interval = sensor->interval; unit = sensor->unit; + flags = sensor->flags; delete sensor; } } @@ -2137,10 +2139,16 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (unit_raw >= (ulong)SensorUnit::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); unit = static_cast(unit_raw); } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flags"), true)) { + flags = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } Sensor *result_sensor; switch (sensor_type) { case SensorType::Ensemble: { + uint8_t children_count = 0; ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { children[i].sensor_id = 255; @@ -2181,7 +2189,9 @@ void server_change_sensor(OTF_PARAMS_DEF) { children[i++] = ensemble_children_t {(uint8_t)u, d1, d2, d3, d4}; while (*(ptr++) != ';') {} - } + } + + children_count = i; } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { @@ -2191,7 +2201,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { action = static_cast(action_raw); } - result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.sensors, children, ENSEMBLE_SENSOR_CHILDREN_COUNT, action); + result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.sensors, children, children_count, action); break; } case SensorType::ADS1115: { @@ -2219,7 +2229,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (sensor_pin >= 4) handle_return(HTML_DATA_OUTOFBOUND); } - result_sensor = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, os.ads1115_devices, sensor_index, sensor_pin); + result_sensor = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.ads1115_devices, sensor_index, sensor_pin); break; } case SensorType::Weather: { @@ -2240,7 +2250,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { action = static_cast(action_raw); } - result_sensor = new WeatherSensor(interval, min, max, scale, offset, (const char*)&name, unit, os.get_sensor_weather_data, action); + result_sensor = new WeatherSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.get_sensor_weather_data, action); break; } @@ -2251,6 +2261,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { } os.sensors[sid].interval = interval; + os.sensors[sid].flags = flags; os.sensors[sid].next_update = 0; os.sensors[sid].value = result_sensor->get_inital_value(); os.write_sensor(result_sensor, sid); @@ -2453,6 +2464,9 @@ void server_log_sensor(OTF_PARAMS_DEF) { handle_return(HTML_OK); } + +// TODO: delete sensor log delete + void server_json_sen_adj_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"adj\":[")); uint8_t adj_count = 0; diff --git a/sensor.cpp b/sensor.cpp index dd2b12d75..dde2685de 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,8 +1,8 @@ #include "sensor.h" #include "OpenSprinkler.h" -Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit) : -interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit) { +Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags) : +interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit), flags(flags) { strncpy(this->name, name, SENSOR_NAME_LEN); this->name[SENSOR_NAME_LEN-1] = 0; } @@ -41,6 +41,7 @@ uint32_t Sensor::serialize(char *buf) { i += SENSOR_NAME_LEN; buf[i++] = static_cast(this->unit); i += write_buf(buf+i, this->interval); + i += write_buf(buf+i, this->flags); i += write_buf(buf+i, this->scale); i += write_buf(buf+i, this->offset); i += write_buf(buf+i, this->min); @@ -57,6 +58,7 @@ uint32_t Sensor::_deserialize(char *buf) { i += SENSOR_NAME_LEN; this->unit = static_cast(buf[i++]); this->interval = read_buf(buf, &i); + this->flags = read_buf(buf, &i); this->scale = read_buf(buf, &i); this->offset = read_buf(buf, &i); this->min = read_buf(buf, &i); @@ -65,15 +67,15 @@ uint32_t Sensor::_deserialize(char *buf) { return i; } -EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action) : -Sensor(interval, min, max, scale, offset, name, unit), +EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action) : +Sensor(interval, min, max, scale, offset, name, unit, flags), action(action), sensors(sensors) { for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { if (i < children_count) { this->children[i] = children[i]; } else { - this->children[i].sensor_id = 255; + this->children[i] = ensemble_children_t { sensor_id: 255, min: 0.0, max: 0.0, scale: 0.0, offset: 0.0 }; } } } @@ -83,7 +85,7 @@ void EnsembleSensor::emit_extra_json(BufferFiller *bfill) { for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { if (i) bfill->emit_p(PSTR(",")); ensemble_children_t *child = &this->children[i]; - bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->max, child->min, child->scale, child->offset); + bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->sensor_id, child->max, child->min, child->scale, child->offset); } bfill->emit_p(PSTR("]}")); } @@ -176,13 +178,14 @@ EnsembleSensor::EnsembleSensor(sensor_memory_t *sensors, char *buf) { this->children[j].scale = read_buf(buf, &i); this->children[j].offset = read_buf(buf, &i); } + this->action = static_cast(buf[i++]); this->sensors = sensors; } -WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, WeatherGetter weather_getter, WeatherAction action) : -Sensor(interval, min, max, scale, offset, name, unit), +WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : +Sensor(interval, min, max, scale, offset, name, unit, flags), action(action), weather_getter(weather_getter) {} @@ -233,7 +236,7 @@ SensorAdjustment::SensorAdjustment(char *buf) { } double SensorAdjustment::get_adjustment_factor(sensor_memory_t *sensors) { - if (this->flags & 1 && this->sid < MAX_SENSORS && sensors[this->sid].interval) { + if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->sid < MAX_SENSORS && sensors[this->sid].interval) { double value = sensors[this->sid].value; if (value <= this->points[0].x) return this->points[0].y; if (value >= this->points[this->point_count-1].x) return this->points[this->point_count-1].y; diff --git a/sensor.h b/sensor.h index fa9bef1a6..0cf2bef59 100644 --- a/sensor.h +++ b/sensor.h @@ -15,6 +15,7 @@ typedef struct { ulong interval; + uint32_t flags; ulong next_update; double value; } sensor_memory_t; @@ -62,9 +63,20 @@ enum class SensorUnit { MAX_VALUE, }; +// typedef struct { +// unsigned int enabled : 1; +// unsigned int log : 1; +// unsigned int reserved : 30; +// } sensor_flags_t; + +typedef enum { + SENSOR_FLAG_ENABLE = 0, + SENSOR_FLAG_LOG, +} sensor_flags; + class Sensor { public: - Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit); + Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags); Sensor(); virtual ~Sensor() {} @@ -80,6 +92,8 @@ class Sensor { double offset = 0.0; char name[SENSOR_NAME_LEN] = {0}; SensorUnit unit = SensorUnit::None; + + uint32_t flags = 0; SensorType virtual get_sensor_type() = 0; double virtual get_inital_value() = 0; @@ -114,7 +128,7 @@ typedef struct { class EnsembleSensor : public Sensor { public: - EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); + EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); EnsembleSensor(sensor_memory_t *sensors, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -143,7 +157,7 @@ typedef double (*WeatherGetter)(WeatherAction); class WeatherSensor : public Sensor { public: - WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); WeatherSensor(WeatherGetter weather_getter, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -170,6 +184,10 @@ typedef struct { #define SENSOR_ADJUSTMENT_POINTS 8 +typedef enum { + SENADJ_FLAG_ENABLE = 0, +} senadj_flags; + class SensorAdjustment { public: SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points); From 1e18d324f50048dc16feda92a3e1c5f212fdf8aa Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 7 Aug 2025 14:20:57 -0400 Subject: [PATCH 057/115] add clearing sensor log --- opensprinkler_server.cpp | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index d0e9ef7d8..5b374c3a8 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2466,6 +2466,38 @@ void server_log_sensor(OTF_PARAMS_DEF) { // TODO: delete sensor log delete +void server_clear_sensor_log(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + for (size_t i = 0; i < SENSOR_LOG_FILE_COUNT; i++) { + /* code */ + } + + os_file_type file; + + uint16_t next = SENSOR_LOG_PER_FILE; + for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + file = os.open_sensor_log(f, FileOpenMode::ReadWrite); + if (file) { + file_write(file, &next, sizeof(next)); + for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + handle_return(HTML_INTERNAL_ERROR); + } + } + + + handle_return(HTML_SUCCESS); +} void server_json_sen_adj_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"adj\":[")); @@ -2756,6 +2788,7 @@ const char *uris[] PROGMEM = { "csn", "dsn", "lsn", + "csl", "jsa", "csa", #endif @@ -2791,6 +2824,7 @@ URLHandler urls[] = { server_change_sensor, // csn server_delete_sensor, // dsn server_log_sensor, // lsn + server_clear_sensor_log, // csl server_json_sen_adj, // jsa server_change_sen_adj, // csa #endif From 300d710006a02d86b5eef523ee8c0ee526731b8f Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Fri, 8 Aug 2025 12:13:59 -0400 Subject: [PATCH 058/115] make it so sensor adjustments can't have two points with the same x value --- opensprinkler_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 5b374c3a8..ebbc95cec 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2606,7 +2606,7 @@ void server_change_sen_adj(OTF_PARAMS_DEF) { result = sscanf(ptr, "%lf,%lf;", &x, &y); - if (result != 2 || x < last_x) { + if (result != 2 || x <= last_x) { handle_return(HTML_DATA_FORMATERROR); } From 343f5e179ca683e2bb27a9031c8a80d1602c2940 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:49:10 -0400 Subject: [PATCH 059/115] start of dynamically generating sensor page --- ads1115.cpp | 4 + ads1115.h | 4 +- opensprinkler_server.cpp | 51 +++- sensor.cpp | 490 ++++++++++++++++++++++++++++++++------- sensor.h | 35 ++- 5 files changed, 481 insertions(+), 103 deletions(-) diff --git a/ads1115.cpp b/ads1115.cpp index 2145a73ad..389ec856f 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -90,6 +90,10 @@ void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { bfill->emit_p(PSTR("{\"index\":$D,\"pin\":$D}"), this->sensor_index, this->pin); } +void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"extra\":[]}]}")); +} + double ADS1115Sensor::get_inital_value() { return 0.0; } diff --git a/ads1115.h b/ads1115.h index b2a1085a8..629f0bdb5 100644 --- a/ads1115.h +++ b/ads1115.h @@ -55,7 +55,9 @@ class ADS1115Sensor : public Sensor { ADS1115Sensor(ADS1115 **sensors, char *buf); void emit_extra_json(BufferFiller *bfill); - + static void emit_description_json(BufferFiller *bfill); + + SensorType get_sensor_type() { return SensorType::ADS1115; } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index ebbc95cec..2313544b7 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2217,16 +2217,13 @@ void server_change_sensor(OTF_PARAMS_DEF) { } } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("index"), true)) { - sensor_index = strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (sensor_index >= 4) handle_return(HTML_DATA_OUTOFBOUND); - } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pin"), true)) { - sensor_pin = strtoul(tmp_buffer, &end, 10); + ulong raw_sensor_pin = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (sensor_pin >= 4) handle_return(HTML_DATA_OUTOFBOUND); + if (raw_sensor_pin == 0 || raw_sensor_pin > 16) handle_return(HTML_DATA_OUTOFBOUND); + raw_sensor_pin -= 1; + sensor_index = raw_sensor_pin >> 2; + sensor_pin = raw_sensor_pin & 0b11; } result_sensor = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.ads1115_devices, sensor_index, sensor_pin); @@ -2536,6 +2533,42 @@ void server_json_sen_adj_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("],\"count\":$D}"), adj_count); } +void server_json_sensor_description_main(OTF_PARAMS_DEF) { + bfill.emit_p(PSTR("\"sensor\":[")); + for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE)-1; i++) { + if (i) bfill.emit_p(PSTR(",")); + switch (static_cast(i)) { + case SensorType::Ensemble: + EnsembleSensor::emit_description_json(&bfill); + break; + case SensorType::ADS1115: + ADS1115Sensor::emit_description_json(&bfill); + break; + case SensorType::Weather: + WeatherSensor::emit_description_json(&bfill); + break; + case SensorType::MAX_VALUE: + break; + } + } + + bfill.emit_p(PSTR("],\"units\":[")); + for (uint8_t i = 0; i < static_cast(SensorUnit::MAX_VALUE)-1; i++) { + if (i) bfill.emit_p(PSTR(",")); + SensorUnit unit = static_cast(i); + bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":\"$D\",\"index\":\"$D\"}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit)); + } + + bfill.emit_p(PSTR("],\"enums\":[")); + bfill.emit_p(PSTR("{\"name\":\"SensorUnitGroup\",\"value\":[")); + for (uint8_t i = 0; i < static_cast(SensorUnitGroup::MAX_VALUE)-1; i++) { + if (i) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("\"$S\""), get_sensor_unit_group_name(static_cast(i))); + } + bfill.emit_p(PSTR("]}")); + bfill.emit_p(PSTR("}")); +} + /** Sensor status */ void server_json_sen_adj(OTF_PARAMS_DEF) { @@ -2658,6 +2691,8 @@ void server_json_all(OTF_PARAMS_DEF) { server_json_sensors_main(OTF_PARAMS); bfill.emit_p(PSTR(",\"senadj\":{")); server_json_sen_adj_main(OTF_PARAMS); + bfill.emit_p(PSTR(",\"sensor_desc\":{")); + server_json_sensor_description_main(OTF_PARAMS); #endif bfill.emit_p(PSTR("}")); handle_return(HTML_OK); diff --git a/sensor.cpp b/sensor.cpp index dde2685de..b0ff07016 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,10 +1,319 @@ #include "sensor.h" #include "OpenSprinkler.h" -Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags) : -interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit), flags(flags) { +const char *get_sensor_unit_group_name(SensorUnitGroup group) { + switch (group) { + case SensorUnitGroup::None: + return PSTR("No Group"); + case SensorUnitGroup::Temperature: + return PSTR("Temperature"); + case SensorUnitGroup::Length: + return PSTR("Length"); + case SensorUnitGroup::Volume: + return PSTR("Volume"); + case SensorUnitGroup::Light: + return PSTR("Light"); + case SensorUnitGroup::Energy: + return PSTR("Energy"); + case SensorUnitGroup::Velocity: + return PSTR("Velocity"); + case SensorUnitGroup::Pressure: + return PSTR("Pressure"); + case SensorUnitGroup::Flow: + return PSTR("Flow"); + case SensorUnitGroup::MAX_VALUE: + return nullptr; + } +} + +const char* get_sensor_unit_name(SensorUnit unit) { + switch (unit) { + case SensorUnit::None: + return PSTR("None"); + case SensorUnit::Celsius: + return PSTR("Celsius"); + case SensorUnit::Fahrenheit: + return PSTR("Fahrenheit"); + case SensorUnit::Kelvin: + return PSTR("Kelvin"); + case SensorUnit::Milimeter: + return PSTR("Milimeter"); + case SensorUnit::Centieter: + return PSTR("Centieter"); + case SensorUnit::Meter: + return PSTR("Meter"); + case SensorUnit::Kilometer: + return PSTR("Kilometer"); + case SensorUnit::Inch: + return PSTR("Inch"); + case SensorUnit::Foot: + return PSTR("Foot"); + case SensorUnit::Mile: + return PSTR("Mile"); + case SensorUnit::Lux: + return PSTR("Lux"); + case SensorUnit::Lumen: + return PSTR("Lumen"); + case SensorUnit::Milivolt: + return PSTR("Milivolt"); + case SensorUnit::Volt: + return PSTR("Volt"); + case SensorUnit::Miliampere: + return PSTR("Miliampere"); + case SensorUnit::Ampere: + return PSTR("Ampere"); + case SensorUnit::Percent: + return PSTR("Percent"); + case SensorUnit::MilesPerHour: + return PSTR("Miles Per Hour"); + case SensorUnit::KilometersPerHour: + return PSTR("Kilometers Per Hour"); + case SensorUnit::MetersPerSecond: + return PSTR("Meters Per Second"); + case SensorUnit::DialetricConstant: + return PSTR("Dialetric Constant"); + case SensorUnit::PartsPerMillion: + return PSTR("Parts Per Million"); + case SensorUnit::Ohm: + return PSTR("Ohm"); + case SensorUnit::Miliohm: + return PSTR("Miliohm"); + case SensorUnit::Kiloohm: + return PSTR("Kiloohm"); + case SensorUnit::Bar: + return PSTR("Bar"); + case SensorUnit::Kilopascal: + return PSTR("Kilopascal"); + case SensorUnit::Pascal: + return PSTR("Pascal"); + case SensorUnit::Torr: + return PSTR("Torr"); + case SensorUnit::LitersPerSecond: + return PSTR("Liters Per Second"); + case SensorUnit::GallonsPerSecond: + return PSTR("Gallons"); + case SensorUnit::MAX_VALUE: + return nullptr; + } +} + +const char* get_sensor_unit_short(SensorUnit unit) { + switch (unit) { + case SensorUnit::None: + return PSTR(""); + case SensorUnit::Celsius: + return PSTR("°C"); + case SensorUnit::Fahrenheit: + return PSTR("°F"); + case SensorUnit::Kelvin: + return PSTR("K"); + case SensorUnit::Milimeter: + return PSTR("mm"); + case SensorUnit::Centieter: + return PSTR("cm"); + case SensorUnit::Meter: + return PSTR("m"); + case SensorUnit::Kilometer: + return PSTR("km"); + case SensorUnit::Inch: + return PSTR("in"); + case SensorUnit::Foot: + return PSTR("ft"); + case SensorUnit::Mile: + return PSTR("mi"); + case SensorUnit::Lux: + return PSTR("lx"); + case SensorUnit::Lumen: + return PSTR("lm"); + case SensorUnit::Milivolt: + return PSTR("mV"); + case SensorUnit::Volt: + return PSTR("V"); + case SensorUnit::Miliampere: + return PSTR("mA"); + case SensorUnit::Ampere: + return PSTR("A"); + case SensorUnit::Percent: + return PSTR("%"); + case SensorUnit::MilesPerHour: + return PSTR("mph"); + case SensorUnit::KilometersPerHour: + return PSTR("km/h"); + case SensorUnit::MetersPerSecond: + return PSTR("xxx"); + case SensorUnit::DialetricConstant: + return PSTR("xxx"); + case SensorUnit::PartsPerMillion: + return PSTR("ppm"); + case SensorUnit::Ohm: + return PSTR("Ω"); + case SensorUnit::Miliohm: + return PSTR("mΩ"); + case SensorUnit::Kiloohm: + return PSTR("kΩ"); + case SensorUnit::Bar: + return PSTR("bar"); + case SensorUnit::Kilopascal: + return PSTR("kPa"); + case SensorUnit::Pascal: + return PSTR("Pa"); + case SensorUnit::Torr: + return PSTR("torr"); + case SensorUnit::LitersPerSecond: + return PSTR("L/s"); + case SensorUnit::GallonsPerSecond: + return PSTR("gal/s"); + case SensorUnit::MAX_VALUE: + return nullptr; + } +} + +const SensorUnitGroup get_sensor_unit_group(SensorUnit unit) { + switch (unit) { + case SensorUnit::None: + return SensorUnitGroup::None; + case SensorUnit::Celsius: + return SensorUnitGroup::Temperature; + case SensorUnit::Fahrenheit: + return SensorUnitGroup::Temperature; + case SensorUnit::Kelvin: + return SensorUnitGroup::Temperature; + case SensorUnit::Milimeter: + return SensorUnitGroup::Length; + case SensorUnit::Centieter: + return SensorUnitGroup::Length; + case SensorUnit::Meter: + return SensorUnitGroup::Length; + case SensorUnit::Kilometer: + return SensorUnitGroup::Length; + case SensorUnit::Inch: + return SensorUnitGroup::Length; + case SensorUnit::Foot: + return SensorUnitGroup::Length; + case SensorUnit::Mile: + return SensorUnitGroup::Length; + case SensorUnit::Lux: + return SensorUnitGroup::Light; + case SensorUnit::Lumen: + return SensorUnitGroup::Light; + case SensorUnit::Milivolt: + return SensorUnitGroup::Energy; + case SensorUnit::Volt: + return SensorUnitGroup::Energy; + case SensorUnit::Miliampere: + return SensorUnitGroup::Energy; + case SensorUnit::Ampere: + return SensorUnitGroup::Energy; + case SensorUnit::Percent: + return SensorUnitGroup::None; + case SensorUnit::MilesPerHour: + return SensorUnitGroup::Velocity; + case SensorUnit::KilometersPerHour: + return SensorUnitGroup::Velocity; + case SensorUnit::MetersPerSecond: + return SensorUnitGroup::Velocity; + case SensorUnit::DialetricConstant: + return SensorUnitGroup::Energy; + case SensorUnit::PartsPerMillion: + return SensorUnitGroup::None; + case SensorUnit::Ohm: + return SensorUnitGroup::Energy; + case SensorUnit::Miliohm: + return SensorUnitGroup::Energy; + case SensorUnit::Kiloohm: + return SensorUnitGroup::Energy; + case SensorUnit::Bar: + return SensorUnitGroup::Pressure; + case SensorUnit::Kilopascal: + return SensorUnitGroup::Pressure; + case SensorUnit::Pascal: + return SensorUnitGroup::Pressure; + case SensorUnit::Torr: + return SensorUnitGroup::Pressure; + case SensorUnit::LitersPerSecond: + return SensorUnitGroup::Flow; + case SensorUnit::GallonsPerSecond: + return SensorUnitGroup::Flow; + case SensorUnit::MAX_VALUE: + return SensorUnitGroup::MAX_VALUE; + } +} + +const ulong get_sensor_unit_index(SensorUnit unit) { + switch (unit) { + case SensorUnit::None: + return 0; + case SensorUnit::Celsius: + return 0; + case SensorUnit::Fahrenheit: + return 0; + case SensorUnit::Kelvin: + return 0; + case SensorUnit::Milimeter: + return 0; + case SensorUnit::Centieter: + return 0; + case SensorUnit::Meter: + return 0; + case SensorUnit::Kilometer: + return 0; + case SensorUnit::Inch: + return 0; + case SensorUnit::Foot: + return 0; + case SensorUnit::Mile: + return 0; + case SensorUnit::Lux: + return 0; + case SensorUnit::Lumen: + return 0; + case SensorUnit::Milivolt: + return 0; + case SensorUnit::Volt: + return 0; + case SensorUnit::Miliampere: + return 0; + case SensorUnit::Ampere: + return 0; + case SensorUnit::Percent: + return 0; + case SensorUnit::MilesPerHour: + return 0; + case SensorUnit::KilometersPerHour: + return 0; + case SensorUnit::MetersPerSecond: + return 0; + case SensorUnit::DialetricConstant: + return 0; + case SensorUnit::PartsPerMillion: + return 0; + case SensorUnit::Ohm: + return 0; + case SensorUnit::Miliohm: + return 0; + case SensorUnit::Kiloohm: + return 0; + case SensorUnit::Bar: + return 0; + case SensorUnit::Kilopascal: + return 0; + case SensorUnit::Pascal: + return 0; + case SensorUnit::Torr: + return 0; + case SensorUnit::LitersPerSecond: + return 0; + case SensorUnit::GallonsPerSecond: + return 0; + case SensorUnit::MAX_VALUE: + return 0; + } +} + +Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags) : + interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit), flags(flags) { strncpy(this->name, name, SENSOR_NAME_LEN); - this->name[SENSOR_NAME_LEN-1] = 0; + this->name[SENSOR_NAME_LEN - 1] = 0; } Sensor::Sensor() {} @@ -19,13 +328,13 @@ double Sensor::get_new_value() { } template -uint32_t write_buf(char *buf, T val) { +uint32_t write_buf(char* buf, T val) { std::memcpy(buf, &val, sizeof(val)); return sizeof(val); } template -T read_buf(char *buf, uint32_t *i) { +T read_buf(char* buf, uint32_t* i) { T val; std::memcpy(&val, buf + (*i), sizeof(T)); *i += sizeof(T); @@ -33,28 +342,28 @@ T read_buf(char *buf, uint32_t *i) { } -uint32_t Sensor::serialize(char *buf) { +uint32_t Sensor::serialize(char* buf) { uint32_t i = 0; buf[i++] = static_cast(this->get_sensor_type()); - memcpy(buf+i, this->name, SENSOR_NAME_LEN); + memcpy(buf + i, this->name, SENSOR_NAME_LEN); i += SENSOR_NAME_LEN; buf[i++] = static_cast(this->unit); - i += write_buf(buf+i, this->interval); - i += write_buf(buf+i, this->flags); - i += write_buf(buf+i, this->scale); - i += write_buf(buf+i, this->offset); - i += write_buf(buf+i, this->min); - i += write_buf(buf+i, this->max); + i += write_buf(buf + i, this->interval); + i += write_buf(buf + i, this->flags); + i += write_buf(buf + i, this->scale); + i += write_buf(buf + i, this->offset); + i += write_buf(buf + i, this->min); + i += write_buf(buf + i, this->max); i += this->_serialize_internal(buf + i); return i; } -uint32_t Sensor::_deserialize(char *buf) { +uint32_t Sensor::_deserialize(char* buf) { uint32_t i = 1; // Skip sensor type - memcpy(this->name, buf+i, SENSOR_NAME_LEN); + memcpy(this->name, buf + i, SENSOR_NAME_LEN); i += SENSOR_NAME_LEN; this->unit = static_cast(buf[i++]); this->interval = read_buf(buf, &i); @@ -67,47 +376,52 @@ uint32_t Sensor::_deserialize(char *buf) { return i; } -EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action) : -Sensor(interval, min, max, scale, offset, name, unit, flags), -action(action), -sensors(sensors) { +EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + action(action), + sensors(sensors) { for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { if (i < children_count) { this->children[i] = children[i]; - } else { - this->children[i] = ensemble_children_t { sensor_id: 255, min: 0.0, max: 0.0, scale: 0.0, offset: 0.0 }; + } + else { + this->children[i] = ensemble_children_t{ sensor_id: 255, min : 0.0, max : 0.0, scale : 0.0, offset : 0.0 }; } } } -void EnsembleSensor::emit_extra_json(BufferFiller *bfill) { +void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { if (i) bfill->emit_p(PSTR(",")); - ensemble_children_t *child = &this->children[i]; + ensemble_children_t* child = &this->children[i]; bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->sensor_id, child->max, child->min, child->scale, child->offset); } bfill->emit_p(PSTR("]}")); } +void EnsembleSensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array[4]\",\"extra\":[{\"name\":\"Sensor ID\",\"type\":\"int:[0,63]\"},{\"name\":\"Minimum Value\",\"type\":\"double\"},{\"name\":\"Maximum Value\",\"type\":\"double\"},{\"name\":\"Scale\",\"type\":\"double\"},{\"name\":\"Offset\",\"type\":\"double\"}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"extra\":[]}]}")); +} + double EnsembleSensor::get_inital_value() { switch (this->action) { - case EnsembleAction::Min: - return this->max; - break; - case EnsembleAction::Max: - return this->min; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - return 0; - break; - case EnsembleAction::Product: - return 1; - break; - default: - // Unreachable - return 0.0; + case EnsembleAction::Min: + return this->max; + break; + case EnsembleAction::Max: + return this->min; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + return 0; + break; + case EnsembleAction::Product: + return 1; + break; + default: + // Unreachable + return 0.0; } } @@ -124,22 +438,22 @@ double EnsembleSensor::_get_raw_value() { if (value > this->children[i].max) value = this->children[i].max; switch (this->action) { - case EnsembleAction::Min: - if (value < inital) inital = value; - break; - case EnsembleAction::Max: - if (value > inital) inital = value; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - inital += value; - break; - case EnsembleAction::Product: - inital *= value; - break; - default: - // Unreachable - return 0.0; + case EnsembleAction::Min: + if (value < inital) inital = value; + break; + case EnsembleAction::Max: + if (value > inital) inital = value; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + inital += value; + break; + case EnsembleAction::Product: + inital *= value; + break; + default: + // Unreachable + return 0.0; } count += 1; @@ -148,28 +462,30 @@ double EnsembleSensor::_get_raw_value() { if (count == 0) { return 0.0; - } else if (this->action == EnsembleAction::Average) { - return inital / (double) count; - } else { + } + else if (this->action == EnsembleAction::Average) { + return inital / (double)count; + } + else { return inital; } } -uint32_t EnsembleSensor::_serialize_internal(char *buf) { +uint32_t EnsembleSensor::_serialize_internal(char* buf) { uint32_t i = 0; for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - i += write_buf(buf+i, this->children[j].sensor_id); - i += write_buf(buf+i, this->children[j].min); - i += write_buf(buf+i, this->children[j].max); - i += write_buf(buf+i, this->children[j].scale); - i += write_buf(buf+i, this->children[j].offset); + i += write_buf(buf + i, this->children[j].sensor_id); + i += write_buf(buf + i, this->children[j].min); + i += write_buf(buf + i, this->children[j].max); + i += write_buf(buf + i, this->children[j].scale); + i += write_buf(buf + i, this->children[j].offset); } - + buf[i++] = static_cast(this->action); return i; } -EnsembleSensor::EnsembleSensor(sensor_memory_t *sensors, char *buf) { +EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { uint32_t i = Sensor::_deserialize(buf); for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { this->children[j].sensor_id = read_buf(buf, &i); @@ -184,15 +500,20 @@ EnsembleSensor::EnsembleSensor(sensor_memory_t *sensors, char *buf) { } -WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : -Sensor(interval, min, max, scale, offset, name, unit, flags), -action(action), -weather_getter(weather_getter) {} +WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + action(action), + weather_getter(weather_getter) { +} -void WeatherSensor::emit_extra_json(BufferFiller *bfill) { +void WeatherSensor::emit_extra_json(BufferFiller* bfill) { bfill->emit_p(PSTR("{\"action\":$D}"), this->action); } +void WeatherSensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"extra\":[]}]}")); +} + double WeatherSensor::get_inital_value() { return 0.0; } @@ -201,18 +522,18 @@ double WeatherSensor::_get_raw_value() { return this->weather_getter(this->action); } -uint32_t WeatherSensor::_serialize_internal(char *buf) { +uint32_t WeatherSensor::_serialize_internal(char* buf) { uint32_t i = 0; buf[i++] = static_cast(this->action); return i; } -WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char *buf) { +WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) { uint32_t i = Sensor::_deserialize(buf); this->action = static_cast(buf[i++]); } -SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points) { +SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t* points) { this->flags = flags; this->sid = sid; if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; @@ -220,10 +541,10 @@ SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_cou for (size_t i = 0; i < point_count; i++) { this->points[i] = points[i]; } - + } -SensorAdjustment::SensorAdjustment(char *buf) { +SensorAdjustment::SensorAdjustment(char* buf) { uint32_t i = 0; this->flags = buf[i++]; this->sid = buf[i++]; @@ -235,42 +556,43 @@ SensorAdjustment::SensorAdjustment(char *buf) { } } -double SensorAdjustment::get_adjustment_factor(sensor_memory_t *sensors) { +double SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->sid < MAX_SENSORS && sensors[this->sid].interval) { double value = sensors[this->sid].value; if (value <= this->points[0].x) return this->points[0].y; - if (value >= this->points[this->point_count-1].x) return this->points[this->point_count-1].y; + if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; uint8_t i; - for (i = 0; i < this->point_count-1; i++) { + for (i = 0; i < this->point_count - 1; i++) { if (value >= this->points[i].x) { break; } } sensor_adjustment_point_t left = this->points[i]; - sensor_adjustment_point_t right = this->points[i+1]; + sensor_adjustment_point_t right = this->points[i + 1]; if (right.x == left.x) return left.y; value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; if (value < 0) return 0; return value; - } else { + } + else { return 1.0; } } -uint32_t SensorAdjustment::serialize(char *buf) { +uint32_t SensorAdjustment::serialize(char* buf) { uint32_t i = 0; buf[i++] = this->flags; buf[i++] = this->sid; buf[i++] = this->point_count; for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { - i += write_buf(buf+i, this->points[j].x); - i += write_buf(buf+i, this->points[j].y); + i += write_buf(buf + i, this->points[j].x); + i += write_buf(buf + i, this->points[j].y); } return i; diff --git a/sensor.h b/sensor.h index 0cf2bef59..431787c8e 100644 --- a/sensor.h +++ b/sensor.h @@ -27,9 +27,21 @@ enum class SensorType { MAX_VALUE, }; +enum class SensorUnitGroup { + None, + Temperature, + Length, + Volume, + Light, + Energy, + Velocity, + Pressure, + Flow, + MAX_VALUE, +}; + enum class SensorUnit { None, - UserDefined, Celsius, Fahrenheit, Kelvin, @@ -44,30 +56,31 @@ enum class SensorUnit { Lumen, Milivolt, Volt, - Miliamp, - Amp, + Miliampere, + Ampere, Percent, MilesPerHour, KilometersPerHour, - MetersPerSection, + MetersPerSecond, DialetricConstant, PartsPerMillion, Ohm, Miliohm, Kiloohm, - Barr, // Pressure + Bar, Kilopascal, Pascal, + Torr, LitersPerSecond, GallonsPerSecond, MAX_VALUE, }; -// typedef struct { -// unsigned int enabled : 1; -// unsigned int log : 1; -// unsigned int reserved : 30; -// } sensor_flags_t; +const char *get_sensor_unit_group_name(SensorUnitGroup group); +const char* get_sensor_unit_name(SensorUnit unit); +const char* get_sensor_unit_short(SensorUnit unit); +const SensorUnitGroup get_sensor_unit_group(SensorUnit unit); +const ulong get_sensor_unit_index(SensorUnit unit); typedef enum { SENSOR_FLAG_ENABLE = 0, @@ -132,6 +145,7 @@ class EnsembleSensor : public Sensor { EnsembleSensor(sensor_memory_t *sensors, char *buf); void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); SensorType get_sensor_type() { return SensorType::Ensemble; @@ -161,6 +175,7 @@ class WeatherSensor : public Sensor { WeatherSensor(WeatherGetter weather_getter, char *buf); void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); SensorType get_sensor_type() { return SensorType::Weather; From e2149cc01b740c69891942da6a872c8ef729f6c3 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 18 Aug 2025 17:19:52 -0400 Subject: [PATCH 060/115] faught with C++ to make it easier to print out enum names --- opensprinkler_server.cpp | 35 +++++++++++++++++++++++++++++------ sensor.cpp | 33 ++++++++++++++++++++++++++++++++- sensor.h | 15 +++++++++------ 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 2313544b7..2b92f9265 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2533,6 +2533,30 @@ void server_json_sen_adj_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("],\"count\":$D}"), adj_count); } +template +void bfill_enum_values(const char *name) { + static_assert(std::is_enum_v, "T must be an enum type"); + + bool needs_comma = false; + + bfill.emit_p(PSTR("{\"name\":\"$S\",\"value\":["), name); + + for (size_t i = 0; i < static_cast(T::MAX_VALUE); ++i) { + if (needs_comma) { + bfill.emit_p(PSTR(",")); + needs_comma = false; + } + + const char* str = enum_string(static_cast(i)); + if (str) { + bfill.emit_p(PSTR("\"$S\""), str); + needs_comma = true; + } + } + + bfill.emit_p(PSTR("]}")); +} + void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"sensor\":[")); for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE)-1; i++) { @@ -2560,13 +2584,12 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { } bfill.emit_p(PSTR("],\"enums\":[")); - bfill.emit_p(PSTR("{\"name\":\"SensorUnitGroup\",\"value\":[")); - for (uint8_t i = 0; i < static_cast(SensorUnitGroup::MAX_VALUE)-1; i++) { - if (i) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("\"$S\""), get_sensor_unit_group_name(static_cast(i))); - } + bfill_enum_values(PSTR("SensorUnitGroup")); + bfill.emit_p(PSTR(",")); + bfill_enum_values(PSTR("EnsembleAction")); + bfill.emit_p(PSTR(",")); + bfill_enum_values(PSTR("WeatherAction")); bfill.emit_p(PSTR("]}")); - bfill.emit_p(PSTR("}")); } /** Sensor status */ diff --git a/sensor.cpp b/sensor.cpp index b0ff07016..e56c19984 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,7 +1,7 @@ #include "sensor.h" #include "OpenSprinkler.h" -const char *get_sensor_unit_group_name(SensorUnitGroup group) { +const char *enum_string(SensorUnitGroup group) { switch (group) { case SensorUnitGroup::None: return PSTR("No Group"); @@ -24,6 +24,29 @@ const char *get_sensor_unit_group_name(SensorUnitGroup group) { case SensorUnitGroup::MAX_VALUE: return nullptr; } + + return nullptr; +} + +const char *enum_string(EnsembleAction action) { + switch (action) { + case EnsembleAction::Min: return PSTR("Min"); + case EnsembleAction::Max: return PSTR("Max"); + case EnsembleAction::Average: return PSTR("Average"); + case EnsembleAction::Sum: return PSTR("Sum"); + case EnsembleAction::Product: return PSTR("Product"); + case EnsembleAction::MAX_VALUE: return nullptr; + } + + return nullptr; +} + +const char *enum_string(WeatherAction action) { + switch (action) { + case WeatherAction::MAX_VALUE: return nullptr; + } + + return nullptr; } const char* get_sensor_unit_name(SensorUnit unit) { @@ -95,6 +118,8 @@ const char* get_sensor_unit_name(SensorUnit unit) { case SensorUnit::MAX_VALUE: return nullptr; } + + return nullptr; } const char* get_sensor_unit_short(SensorUnit unit) { @@ -166,6 +191,8 @@ const char* get_sensor_unit_short(SensorUnit unit) { case SensorUnit::MAX_VALUE: return nullptr; } + + return nullptr; } const SensorUnitGroup get_sensor_unit_group(SensorUnit unit) { @@ -237,6 +264,8 @@ const SensorUnitGroup get_sensor_unit_group(SensorUnit unit) { case SensorUnit::MAX_VALUE: return SensorUnitGroup::MAX_VALUE; } + + return SensorUnitGroup::MAX_VALUE; } const ulong get_sensor_unit_index(SensorUnit unit) { @@ -308,6 +337,8 @@ const ulong get_sensor_unit_index(SensorUnit unit) { case SensorUnit::MAX_VALUE: return 0; } + + return 0; } Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags) : diff --git a/sensor.h b/sensor.h index 431787c8e..f3453d173 100644 --- a/sensor.h +++ b/sensor.h @@ -76,12 +76,6 @@ enum class SensorUnit { MAX_VALUE, }; -const char *get_sensor_unit_group_name(SensorUnitGroup group); -const char* get_sensor_unit_name(SensorUnit unit); -const char* get_sensor_unit_short(SensorUnit unit); -const SensorUnitGroup get_sensor_unit_group(SensorUnit unit); -const ulong get_sensor_unit_index(SensorUnit unit); - typedef enum { SENSOR_FLAG_ENABLE = 0, SENSOR_FLAG_LOG, @@ -219,4 +213,13 @@ class SensorAdjustment { #define SENSOR_ADJUSTMENT_SIZE (3 + (SENSOR_ADJUSTMENT_POINTS * sizeof(sensor_adjustment_point_t))) +const char *enum_string(SensorUnitGroup group); +const char *enum_string(EnsembleAction action); +const char *enum_string(WeatherAction action); + +const char* get_sensor_unit_name(SensorUnit unit); +const char* get_sensor_unit_short(SensorUnit unit); +const SensorUnitGroup get_sensor_unit_group(SensorUnit unit); +const ulong get_sensor_unit_index(SensorUnit unit); + #endif //SENSOR_H \ No newline at end of file From 303cae7bd9f46736c159a4246526eb1a4b03f334 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Tue, 19 Aug 2025 11:41:17 -0400 Subject: [PATCH 061/115] update enums --- opensprinkler_server.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 2b92f9265..4a5a9e112 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2539,7 +2539,7 @@ void bfill_enum_values(const char *name) { bool needs_comma = false; - bfill.emit_p(PSTR("{\"name\":\"$S\",\"value\":["), name); + bfill.emit_p(PSTR("\"$S\":["), name); for (size_t i = 0; i < static_cast(T::MAX_VALUE); ++i) { if (needs_comma) { @@ -2554,7 +2554,7 @@ void bfill_enum_values(const char *name) { } } - bfill.emit_p(PSTR("]}")); + bfill.emit_p(PSTR("]")); } void server_json_sensor_description_main(OTF_PARAMS_DEF) { @@ -2580,16 +2580,16 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { for (uint8_t i = 0; i < static_cast(SensorUnit::MAX_VALUE)-1; i++) { if (i) bfill.emit_p(PSTR(",")); SensorUnit unit = static_cast(i); - bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":\"$D\",\"index\":\"$D\"}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit)); + bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":$D,\"index\":$D,\"value\":$D}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit), i); } - bfill.emit_p(PSTR("],\"enums\":[")); + bfill.emit_p(PSTR("],\"enums\":{")); bfill_enum_values(PSTR("SensorUnitGroup")); bfill.emit_p(PSTR(",")); bfill_enum_values(PSTR("EnsembleAction")); bfill.emit_p(PSTR(",")); bfill_enum_values(PSTR("WeatherAction")); - bfill.emit_p(PSTR("]}")); + bfill.emit_p(PSTR("}}")); } /** Sensor status */ From 40ca5ac7a36bb63345d1df5bbc45b2d32bb5d557 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:41:16 -0400 Subject: [PATCH 062/115] Update sensor description json --- opensprinkler_server.cpp | 10 ++++++++-- sensor.cpp | 2 +- sensor.h | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 4a5a9e112..ba51bda0e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2559,7 +2559,7 @@ void bfill_enum_values(const char *name) { void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"sensor\":[")); - for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE)-1; i++) { + for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE); i++) { if (i) bfill.emit_p(PSTR(",")); switch (static_cast(i)) { case SensorType::Ensemble: @@ -2589,7 +2589,13 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill_enum_values(PSTR("EnsembleAction")); bfill.emit_p(PSTR(",")); bfill_enum_values(PSTR("WeatherAction")); - bfill.emit_p(PSTR("}}")); + bfill.emit_p(PSTR("}")); + + bfill.emit_p(PSTR(",base:[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"extra\":[]}]}]")); + static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here + bfill.emit_p(PSTR(",flags:[\"Enable Sensor\",\"Enable Logging\"]")); + + bfill.emit_p(PSTR("}")); } /** Sensor status */ diff --git a/sensor.cpp b/sensor.cpp index e56c19984..b818f6df7 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -432,7 +432,7 @@ void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { } void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array[4]\",\"extra\":[{\"name\":\"Sensor ID\",\"type\":\"int:[0,63]\"},{\"name\":\"Minimum Value\",\"type\":\"double\"},{\"name\":\"Maximum Value\",\"type\":\"double\"},{\"name\":\"Scale\",\"type\":\"double\"},{\"name\":\"Offset\",\"type\":\"double\"}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"type\":\"int::[0,63]\"},{\"name\":\"Minimum Value\",\"type\":\"double\"},{\"name\":\"Maximum Value\",\"type\":\"double\"},{\"name\":\"Scale\",\"type\":\"double\"},{\"name\":\"Offset\",\"type\":\"double\"}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"extra\":[]}]}")); } double EnsembleSensor::get_inital_value() { diff --git a/sensor.h b/sensor.h index f3453d173..a1e79b8dd 100644 --- a/sensor.h +++ b/sensor.h @@ -79,6 +79,7 @@ enum class SensorUnit { typedef enum { SENSOR_FLAG_ENABLE = 0, SENSOR_FLAG_LOG, + SENSOR_FLAG_COUNT } sensor_flags; class Sensor { From 0dd5ad0fa7a89fa442caaf69b43596a416dd0753 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 21 Aug 2025 14:00:46 -0400 Subject: [PATCH 063/115] fix json --- opensprinkler_server.cpp | 4 ++-- sensor.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index ba51bda0e..7d72f5bc2 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2591,9 +2591,9 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill_enum_values(PSTR("WeatherAction")); bfill.emit_p(PSTR("}")); - bfill.emit_p(PSTR(",base:[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"extra\":[]}]}]")); + bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"extra\":[]}]}]")); static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here - bfill.emit_p(PSTR(",flags:[\"Enable Sensor\",\"Enable Logging\"]")); + bfill.emit_p(PSTR(",\"flags\":[\"Enable Sensor\",\"Enable Logging\"]")); bfill.emit_p(PSTR("}")); } diff --git a/sensor.cpp b/sensor.cpp index b818f6df7..89c17e02f 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -432,7 +432,7 @@ void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { } void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"type\":\"int::[0,63]\"},{\"name\":\"Minimum Value\",\"type\":\"double\"},{\"name\":\"Maximum Value\",\"type\":\"double\"},{\"name\":\"Scale\",\"type\":\"double\"},{\"name\":\"Offset\",\"type\":\"double\"}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"int::[0,63]\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"extra\":[]}]}")); } double EnsembleSensor::get_inital_value() { From 52c438760c10442f24c99fed7ca864c1ce77085b Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:40:15 -0400 Subject: [PATCH 064/115] add default option to json --- ads1115.cpp | 2 +- opensprinkler_server.cpp | 2 +- sensor.cpp | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ads1115.cpp b/ads1115.cpp index 389ec856f..6507762e4 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -91,7 +91,7 @@ void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { } void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"default\":\"1\",\"extra\":[]}]}")); } double ADS1115Sensor::get_inital_value() { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7d72f5bc2..7cedaa9cb 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2591,7 +2591,7 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill_enum_values(PSTR("WeatherAction")); bfill.emit_p(PSTR("}")); - bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"extra\":[]}]}]")); + bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"100\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here bfill.emit_p(PSTR(",\"flags\":[\"Enable Sensor\",\"Enable Logging\"]")); diff --git a/sensor.cpp b/sensor.cpp index 89c17e02f..5eb497839 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -432,7 +432,7 @@ void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { } void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"int::[0,63]\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"100\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); } double EnsembleSensor::get_inital_value() { @@ -542,7 +542,7 @@ void WeatherSensor::emit_extra_json(BufferFiller* bfill) { } void WeatherSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"default\":\"0\",\"extra\":[]}]}")); } double WeatherSensor::get_inital_value() { From 4648626e817fb60313558c415336f2e2a245c321 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 21 Aug 2025 15:58:39 -0400 Subject: [PATCH 065/115] flush buffer to prevent stack smashing --- opensprinkler_server.cpp | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7cedaa9cb..3ccdbf23f 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2576,12 +2576,20 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { } } + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("],\"units\":[")); for (uint8_t i = 0; i < static_cast(SensorUnit::MAX_VALUE)-1; i++) { if (i) bfill.emit_p(PSTR(",")); SensorUnit unit = static_cast(i); bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":$D,\"index\":$D,\"value\":$D}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit), i); } + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } bfill.emit_p(PSTR("],\"enums\":{")); bfill_enum_values(PSTR("SensorUnitGroup")); @@ -2591,10 +2599,23 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill_enum_values(PSTR("WeatherAction")); bfill.emit_p(PSTR("}")); + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"100\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here bfill.emit_p(PSTR(",\"flags\":[\"Enable Sensor\",\"Enable Logging\"]")); + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("}")); } From e5bcdb806730b7bc741094c175d440fac9c9a6f9 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:00:32 -0400 Subject: [PATCH 066/115] fix ensemble sensor --- sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensor.cpp b/sensor.cpp index 5eb497839..245691757 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -432,7 +432,7 @@ void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { } void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"100\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); + bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"int::[0,63]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); } double EnsembleSensor::get_inital_value() { From 854dd57ab30f69d3dc08523f2fb37febdabc5fd9 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Thu, 21 Aug 2025 16:08:47 -0400 Subject: [PATCH 067/115] add default option to flags --- opensprinkler_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 3ccdbf23f..75e348b89 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2610,7 +2610,7 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { } static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here - bfill.emit_p(PSTR(",\"flags\":[\"Enable Sensor\",\"Enable Logging\"]")); + bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]")); if (available_ether_buffer() <= 0) { send_packet(OTF_PARAMS); From ccba75f0c5e9207c89686bf90ac7e02a9e7c93c4 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:30:39 -0400 Subject: [PATCH 068/115] fixed ads1115 pin json --- ads1115.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ads1115.cpp b/ads1115.cpp index 6507762e4..d7c214ca0 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -87,7 +87,7 @@ pin(pin), sensors(sensors) {} void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { - bfill->emit_p(PSTR("{\"index\":$D,\"pin\":$D}"), this->sensor_index, this->pin); + bfill->emit_p(PSTR("{\"pin\":$D}"), ((this->sensor_index << 2) + this->pin + 1)); } void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { From 65e28d49822ddbd347f46d2fa3cf1b0f8eeda3a6 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:31:04 -0400 Subject: [PATCH 069/115] made ensemble sensor use a drop down to select sensors --- sensor.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensor.cpp b/sensor.cpp index 245691757..8deff966a 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -432,7 +432,7 @@ void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { } void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"int::[0,63]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); } double EnsembleSensor::get_inital_value() { From 8f1a38fbf413c2abb9a8a8c3a067052f536a7450 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Mon, 25 Aug 2025 12:32:06 -0400 Subject: [PATCH 070/115] made sensor interval in minutes, fixed reading sensors, made ensemble sensor support -1 for a child to refer to itself, fixed flags json added jsd endpoint --- OpenSprinkler.cpp | 2 +- opensprinkler_server.cpp | 63 +++++++++++++++++++++++----------------- 2 files changed, 38 insertions(+), 27 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 27c154af5..014e29445 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2797,7 +2797,7 @@ void OpenSprinkler::poll_sensors() { if (sensor) { sensors[i].value = sensor->get_new_value(); delete sensor; - sensors[i].next_update = millis() + sensors[i].interval; + sensors[i].next_update = millis() + (sensors[i].interval * 1000 * 60); if (sensors[i].flags & (1 << SENSOR_FLAG_LOG)) { os.log_sensor(i, sensors[i].value); } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 75e348b89..0721b58b6 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1989,32 +1989,25 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { uint8_t sensor_count = 0; Sensor *sensor; - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); - if (file) { - for (size_t i = 0; i < MAX_SENSORS; i++) { - if (os.sensors[i].interval && (sensor = os.parse_sensor(file))) { - if (sensor_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); - sensor->emit_extra_json(&bfill); - bfill.emit_p(PSTR("}")); - sensor_count += 1; - delete sensor; + for (size_t i = 0; i < MAX_SENSORS; i++) { + if (os.sensors[i].interval && (sensor = os.get_sensor(i))) { + if (sensor_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); + sensor->emit_extra_json(&bfill); + bfill.emit_p(PSTR("}")); + sensor_count += 1; + delete sensor; - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); } } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); } + bfill.emit_p(PSTR("],\"count\":$D}"), sensor_count); } @@ -2131,7 +2124,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } - if (interval < 1000) handle_return(HTML_DATA_OUTOFBOUND); + if (interval < 1) handle_return(HTML_DATA_OUTOFBOUND); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { ulong unit_raw = strtol(tmp_buffer, &end, 10); @@ -2170,7 +2163,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("children"), true)) { unsigned int i = 0; - unsigned int u; + int d; double d1, d2, d3, d4; const char *ptr = tmp_buffer; int result; @@ -2178,15 +2171,16 @@ void server_change_sensor(OTF_PARAMS_DEF) { while (*ptr != '\0') { if (i >= ENSEMBLE_SENSOR_CHILDREN_COUNT) handle_return(HTML_DATA_FORMATERROR); - result = sscanf(ptr, "%u,%lf,%lf,%lf,%lf;", &u, &d1, &d2, &d3, &d4); + result = sscanf(ptr, "%d,%lf,%lf,%lf,%lf;", &d, &d1, &d2, &d3, &d4); if (result != 5) { handle_return(HTML_DATA_FORMATERROR); } - if (u >= MAX_SENSORS) handle_return(HTML_DATA_FORMATERROR); + if (d >= MAX_SENSORS || d < -1) handle_return(HTML_DATA_FORMATERROR); + if (d == -1) d = sid; - children[i++] = ensemble_children_t {(uint8_t)u, d1, d2, d3, d4}; + children[i++] = ensemble_children_t {(uint8_t)d, d1, d2, d3, d4}; while (*(ptr++) != ';') {} } @@ -2610,7 +2604,7 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { } static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here - bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]")); + bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]]")); if (available_ether_buffer() <= 0) { send_packet(OTF_PARAMS); @@ -2711,6 +2705,21 @@ void server_change_sen_adj(OTF_PARAMS_DEF) { handle_return(HTML_SUCCESS); } + +void server_json_sen_desc(OTF_PARAMS_DEF) +{ +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{")); + server_json_sensor_description_main(OTF_PARAMS); + handle_return(HTML_OK); +} #endif /** Output all JSON data, including jc, jp, jo, js, jn */ @@ -2876,6 +2885,7 @@ const char *uris[] PROGMEM = { "csl", "jsa", "csa", + "jsd", #endif }; @@ -2912,6 +2922,7 @@ URLHandler urls[] = { server_clear_sensor_log, // csl server_json_sen_adj, // jsa server_change_sen_adj, // csa + server_json_sen_desc, // jsd #endif }; #else From 6342b50d592233086bafac1c39402f80583db309 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Fri, 29 Aug 2025 15:19:30 -0400 Subject: [PATCH 071/115] fix null ptr and move sen adj to be in the program endpoint --- main.cpp | 14 +- opensprinkler_server.cpp | 303 +++++++++++++++++++-------------------- 2 files changed, 158 insertions(+), 159 deletions(-) diff --git a/main.cpp b/main.cpp index ea48d0b9a..423dd8959 100644 --- a/main.cpp +++ b/main.cpp @@ -822,9 +822,13 @@ void do_loop() for(pid=0; pidget_adjustment_factor(os.sensors); - delete adj; + if (adj) { + adj->get_adjustment_factor(os.sensors); + delete adj; + } #else double adjustment = 1.0; #endif @@ -1478,8 +1482,10 @@ void manual_start_program(unsigned char pid, unsigned char uwt) { pd.read(pid-1, &prog); #if defined(USE_SENSORS) SensorAdjustment *adj = os.get_sensor_adjust(pid-1); - adjustment = adj->get_adjustment_factor(os.sensors); - delete adj; + if (adj) { + adjustment = adj->get_adjustment_factor(os.sensors); + delete adj; + } #endif notif.add(NOTIFY_PROGRAM_SCHED, pid-1, uwt?os.iopts[IOPT_WATER_PERCENTAGE]:100, 1); } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 0721b58b6..f90a91337 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -937,6 +937,66 @@ void server_change_program(OTF_PARAMS_DEF) { } } + char *end; + + SensorAdjustment *adj = nullptr; + unsigned long flags = 0; + unsigned long sid = 255; + unsigned long point_count = 0; + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; + + if ((adj = os.get_sensor_adjust(pid))) { + flags = adj->flags; + sid = adj->sid; + point_count = adj->point_count; + + for (size_t i = 0; i <= point_count; i++) { + points[i] = adj->points[i]; + } + delete adj; + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flags"), true)) { + flags=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_sid"), true)) { + sid=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (sid >= MAX_SENSORS) sid = 255; + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_points"), true)) { + unsigned long i = 0; + double x, y; + const char *ptr = tmp_buffer; + int result; + double last_x = -std::numeric_limits::infinity();; + + while (*ptr != '\0') { + if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); + + result = sscanf(ptr, "%lf,%lf;", &x, &y); + + if (result != 2 || x <= last_x) { + handle_return(HTML_DATA_FORMATERROR); + } + + points[i++] = sensor_adjustment_point_t {x, y}; + + last_x = x; + + while (*(ptr++) != ';') {} + } + + point_count = i; + } + + adj = new SensorAdjustment(flags, sid, point_count, points); + os.write_sensor_adjust(adj, pid); + delete adj; + #if !defined(USE_OTF) if(p) urlDecode(p); @@ -1135,6 +1195,40 @@ void server_json_programs_main(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); } } + + bfill.emit_p(PSTR("],\"adj\":[")); + uint8_t adj_count = 0; + + SensorAdjustment *adj; + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + for (size_t i = 0; i < pd.nprograms; i++) { + if ((adj = os.get_sensor_adjust(i))) { + if (adj_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); + for (int j = 0; j < adj->point_count; j++) { + if (j) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); + } + bfill.emit_p(PSTR("]}")); + adj_count += 1; + delete adj; + + + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } + bfill.emit_p(PSTR("]}")); } @@ -2299,6 +2393,21 @@ void server_delete_sensor(OTF_PARAMS_DEF) { handle_return(HTML_SUCCESS); } +uint8_t write_buf_log(uint32_t num, char *buf) { + if (num) { + uint8_t index = 0; + while (num > 0) { + buf[index++] = (num%10) + '0'; + num /= 10; + } + + return index; + } else { + buf[0] = '0'; + return 1; + } +} + void server_log_sensor(OTF_PARAMS_DEF) { #if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; @@ -2308,6 +2417,8 @@ void server_log_sensor(OTF_PARAMS_DEF) { print_header(); #endif + ulong start_time = millis(); + ulong count = 0; ulong i; @@ -2316,12 +2427,11 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { max_count = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (max_count > 10000 || max_count > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); + if (max_count > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); } uint16_t file_no = os.sensor_file_no; uint16_t next; - // file_read_block(SENSORS_LOG_FILENAME, &next, 0, sizeof(next)); os_file_type file = os.open_sensor_log(file_no, FileOpenMode::Read); if (file) { @@ -2374,7 +2484,6 @@ void server_log_sensor(OTF_PARAMS_DEF) { // Clear out buffer memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); - // os_file_type file = file_open(SENSORS_LOG_FILENAME, FileOpenMode::Read); file = os.open_sensor_log(file_no, FileOpenMode::Read); if (file) { file_seek(file, sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE), FileSeekMode::Current); @@ -2384,7 +2493,9 @@ void server_log_sensor(OTF_PARAMS_DEF) { handle_return(HTML_INTERNAL_ERROR); } - bfill.emit_p(PSTR("{\"log\":[")); + send_packet(OTF_PARAMS); + + char print_buf[22] = "00,00000000,00000000\n"; for (i=0;i before || timestamp < after) continue; - if (count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"sid\":$D,\"timestamp\":$L,\"value\":$E}"), sid, timestamp, value); + print_buf[0] = dec2hexchar((sid >> 4) & 0xF); + print_buf[1] = dec2hexchar(sid & 0xF); + + print_buf[3] = dec2hexchar((timestamp >> 28) & 0xF); + print_buf[4] = dec2hexchar((timestamp >> 24) & 0xF); + print_buf[5] = dec2hexchar((timestamp >> 20) & 0xF); + print_buf[6] = dec2hexchar((timestamp >> 16) & 0xF); + print_buf[7] = dec2hexchar((timestamp >> 12) & 0xF); + print_buf[8] = dec2hexchar((timestamp >> 8) & 0xF); + print_buf[9] = dec2hexchar((timestamp >> 4) & 0xF); + print_buf[10] = dec2hexchar(timestamp & 0xF); + + print_buf[12] = dec2hexchar((value >> 28) & 0xF); + print_buf[13] = dec2hexchar((value >> 24) & 0xF); + print_buf[14] = dec2hexchar((value >> 20) & 0xF); + print_buf[15] = dec2hexchar((value >> 16) & 0xF); + print_buf[16] = dec2hexchar((value >> 12) & 0xF); + print_buf[17] = dec2hexchar((value >> 8) & 0xF); + print_buf[18] = dec2hexchar((value >> 4) & 0xF); + print_buf[19] = dec2hexchar(value & 0xF); + res.write(print_buf, 21); count += 1; - - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } } else { DEBUG_PRINT("Failed to open sensor log file: "); DEBUG_PRINTLN(file_no); @@ -2450,8 +2574,6 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (file) file_close(file); - bfill.emit_p(PSTR("],\"total\":$D,\"count\":$D,\"next_cursor\":$D}"), MAX_SENSOR_LOG_COUNT, count, cursor); - handle_return(HTML_OK); } @@ -2463,12 +2585,19 @@ void server_clear_sensor_log(OTF_PARAMS_DEF) { #else char *p = get_buffer; #endif - for (size_t i = 0; i < SENSOR_LOG_FILE_COUNT; i++) { - /* code */ - } - os_file_type file; + int sid = -1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + sid = atoi(tmp_buffer); + if (sid<-1 || sid>=MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + } else { + handle_return(HTML_DATA_MISSING); + } + + tmp_buffer[0] = 0; + tmp_buffer[1] = 255; + uint16_t next = SENSOR_LOG_PER_FILE; for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { file = os.open_sensor_log(f, FileOpenMode::ReadWrite); @@ -2490,43 +2619,6 @@ void server_clear_sensor_log(OTF_PARAMS_DEF) { handle_return(HTML_SUCCESS); } -void server_json_sen_adj_main(OTF_PARAMS_DEF) { - bfill.emit_p(PSTR("\"adj\":[")); - uint8_t adj_count = 0; - - SensorAdjustment *adj; - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); - if (file) { - for (size_t i = 0; i < pd.nprograms; i++) { - if ((adj = os.get_sensor_adjust(i))) { - if (adj_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); - for (int j = 0; j < adj->point_count; j++) { - if (j) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); - } - bfill.emit_p(PSTR("]}")); - adj_count += 1; - delete adj; - - - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - } - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); - } - - bfill.emit_p(PSTR("],\"count\":$D}"), adj_count); -} - template void bfill_enum_values(const char *name) { static_assert(std::is_enum_v, "T must be an enum type"); @@ -2613,99 +2705,6 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("}")); } -/** Sensor status */ -void server_json_sen_adj(OTF_PARAMS_DEF) -{ -#if defined(USE_OTF) - if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); - print_header(OTF_PARAMS); -#else - print_header(); -#endif - - bfill.emit_p(PSTR("{")); - server_json_sen_adj_main(OTF_PARAMS); - handle_return(HTML_OK); -} - -void server_change_sen_adj(OTF_PARAMS_DEF) { -#if defined(USE_OTF) - if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); - - char *end; - long pid = strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - - if (pid < 0 || pid >= pd.nprograms) handle_return(HTML_DATA_OUTOFBOUND); - - SensorAdjustment *adj = nullptr; - unsigned long flags = 0; - unsigned long sid = 255; - unsigned long point_count = 0; - sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; - - if ((adj = os.get_sensor_adjust(pid))) { - flags = adj->flags; - sid = adj->sid; - point_count = adj->point_count; - - for (size_t i = 0; i <= point_count; i++) { - points[i] = adj->points[i]; - } - delete adj; - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flags"), true)) { - flags=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { - sid=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (sid >= MAX_SENSORS) sid = 255; - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("points"), true)) { - unsigned long i = 0; - double x, y; - const char *ptr = tmp_buffer; - int result; - double last_x = -std::numeric_limits::infinity();; - - while (*ptr != '\0') { - if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); - - result = sscanf(ptr, "%lf,%lf;", &x, &y); - - if (result != 2 || x <= last_x) { - handle_return(HTML_DATA_FORMATERROR); - } - - points[i++] = sensor_adjustment_point_t {x, y}; - - last_x = x; - - while (*(ptr++) != ';') {} - } - - point_count = i; - } - - if (point_count == 0) handle_return(HTML_DATA_MISSING); - - adj = new SensorAdjustment(flags, sid, point_count, points); - os.write_sensor_adjust(adj, pid); - delete adj; - - handle_return(HTML_SUCCESS); -} - void server_json_sen_desc(OTF_PARAMS_DEF) { #if defined(USE_OTF) @@ -2748,8 +2747,6 @@ void server_json_all(OTF_PARAMS_DEF) { #if defined(USE_SENSORS) bfill.emit_p(PSTR(",\"sensors\":{")); server_json_sensors_main(OTF_PARAMS); - bfill.emit_p(PSTR(",\"senadj\":{")); - server_json_sen_adj_main(OTF_PARAMS); bfill.emit_p(PSTR(",\"sensor_desc\":{")); server_json_sensor_description_main(OTF_PARAMS); #endif @@ -2883,8 +2880,6 @@ const char *uris[] PROGMEM = { "dsn", "lsn", "csl", - "jsa", - "csa", "jsd", #endif }; @@ -2920,8 +2915,6 @@ URLHandler urls[] = { server_delete_sensor, // dsn server_log_sensor, // lsn server_clear_sensor_log, // csl - server_json_sen_adj, // jsa - server_change_sen_adj, // csa server_json_sen_desc, // jsd #endif }; From 39db235284da026def6300f7f4a7a454af116a8f Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 17 Nov 2025 21:17:21 -0500 Subject: [PATCH 072/115] remove AVR support --- I2CRTC.cpp | 5 - LiquidCrystal.cpp | 383 ------------------ LiquidCrystal.h | 127 ------ OpenSprinkler.cpp | 433 +++----------------- OpenSprinkler.h | 76 +--- RCSwitch.cpp | 2 +- SSD1306Display.h | 849 +++++++++++++++++++-------------------- ch224.h | 4 - defines.h | 83 +--- docs/docs/index.md | 2 +- espconnect.h | 9 +- font.h | 5 +- gpio.cpp | 3 - gpio.h | 8 +- i2cd.h | 163 ++++---- images.h | 118 +----- main.cpp | 322 +++------------ main.h | 7 +- mqtt.cpp | 21 +- mqtt.h | 4 +- notifier.cpp | 59 +-- notifier.h | 6 +- opensprinkler_server.cpp | 572 ++------------------------ opensprinkler_server.h | 7 +- platformio.ini | 17 - program.cpp | 2 +- program.h | 6 +- rpitime.h | 4 +- types.h | 8 +- utils.cpp | 71 +--- utils.h | 9 +- weather.cpp | 15 +- weather.h | 5 +- 33 files changed, 744 insertions(+), 2661 deletions(-) delete mode 100644 LiquidCrystal.cpp delete mode 100644 LiquidCrystal.h diff --git a/I2CRTC.cpp b/I2CRTC.cpp index 6832a5580..d1d76dc37 100644 --- a/I2CRTC.cpp +++ b/I2CRTC.cpp @@ -24,9 +24,6 @@ 23 Dec 2013 -- modified by Ray Wang (Rayshobby LLC) to add support for MCP7940 */ - -#if defined(ARDUINO) - #include "I2CRTC.h" #include @@ -159,5 +156,3 @@ uint8_t I2CRTC::bcd2dec(uint8_t num) } I2CRTC RTC = I2CRTC(); // create an instance for the user - -#endif diff --git a/LiquidCrystal.cpp b/LiquidCrystal.cpp deleted file mode 100644 index 88050bd35..000000000 --- a/LiquidCrystal.cpp +++ /dev/null @@ -1,383 +0,0 @@ -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - -#include "LiquidCrystal.h" -#include -#include -#include - -// When the display powers up, it is configured as follows: -// -// 1. Display clear -// 2. Function set: -// DL = 1; 8-bit interface data -// N = 0; 1-line display -// F = 0; 5x8 dot character font -// 3. Display on/off control: -// D = 0; Display off -// C = 0; Cursor off -// B = 0; Blinking off -// 4. Entry mode set: -// I/D = 1; Increment by 1 -// S = 0; No shift -// -// Note, however, that resetting the Arduino doesn't reset the LCD, so we -// can't assume that its in that state when a sketch starts (and the -// LiquidCrystal constructor is called) - -void LiquidCrystal::begin() { - if (_type == LCD_I2C) { - _displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; - - // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! - // according to datasheet, we need at least 40ms after power rises above 2.7V - // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 - delay(50); - - // Now we pull both RS and R/W low to begin commands - expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1) - delay(1000); - - //put the LCD into 4 bit mode - // this is according to the hitachi HD44780 datasheet - // figure 24, pg 46 - - // we start in 8bit mode, try to set 4 bit mode - write4bits(0x03 << 4); - delayMicroseconds(4500); // wait min 4.1ms - - // second try - write4bits(0x03 << 4); - delayMicroseconds(4500); // wait min 4.1ms - - // third go! - write4bits(0x03 << 4); - delayMicroseconds(150); - - // finally, set to 4-bit interface - write4bits(0x02 << 4); - - // set # lines, font size, etc. - command(LCD_FUNCTIONSET | _displayfunction); - - // turn the display on with no cursor or blinking default - _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; - display(); - - // clear it off - clear(); - - // Initialize to default text direction (for roman languages) - _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; - - // set the entry mode - command(LCD_ENTRYMODESET | _displaymode); - - home(); - } - - if (_type == LCD_STD) { - _displayfunction |= LCD_2LINE; - _numlines = 2; - _currline = 0; - - // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! - // according to datasheet, we need at least 40ms after power rises above 2.7V - // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 - delayMicroseconds(50000); - // Now we pull both RS and R/W low to begin commands - digitalWrite(_rs_pin, LOW); - digitalWrite(_enable_pin, LOW); - if (_rw_pin != 255) { - digitalWrite(_rw_pin, LOW); - } - - //put the LCD into 4 bit or 8 bit mode - if (! (_displayfunction & LCD_8BITMODE)) { - // this is according to the hitachi HD44780 datasheet - // figure 24, pg 46 - - // we start in 8bit mode, try to set 4 bit mode - write4bits(0x03); - delayMicroseconds(4500); // wait min 4.1ms - - // second try - write4bits(0x03); - delayMicroseconds(4500); // wait min 4.1ms - - // third go! - write4bits(0x03); - delayMicroseconds(150); - - // finally, set to 4-bit interface - write4bits(0x02); - } else { - // this is according to the hitachi HD44780 datasheet - // page 45 figure 23 - - // Send function set command sequence - command(LCD_FUNCTIONSET | _displayfunction); - delayMicroseconds(4500); // wait more than 4.1ms - - // second try - command(LCD_FUNCTIONSET | _displayfunction); - delayMicroseconds(150); - - // third go - command(LCD_FUNCTIONSET | _displayfunction); - } - - // finally, set # lines, font size, etc. - command(LCD_FUNCTIONSET | _displayfunction); - - // turn the display on with no cursor or blinking default - _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; - display(); - - // clear it off - clear(); - - // Initialize to default text direction (for romance languages) - _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; - // set the entry mode - command(LCD_ENTRYMODESET | _displaymode); - } -} - -void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable, - uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, - uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) -{ - _rs_pin = rs; - _rw_pin = rw; - _enable_pin = enable; - - _data_pins[0] = d0; - _data_pins[1] = d1; - _data_pins[2] = d2; - _data_pins[3] = d3; - _data_pins[4] = d4; - _data_pins[5] = d5; - _data_pins[6] = d6; - _data_pins[7] = d7; - - Wire.begin(); - _type = LCD_STD; - - // detect I2C and assign _type variable accordingly - Wire.beginTransmission(LCD_I2C_ADDR1); // check type 1 - //Wire.write(0x00); - uint8_t ret1 = Wire.endTransmission(); - Wire.beginTransmission(LCD_I2C_ADDR2); // check type 2 - //Wire.write(0x00); - uint8_t ret2 = Wire.endTransmission(); - - if (!ret1 || !ret2) _type = LCD_I2C; - if (_type == LCD_I2C) { - if(!ret1) _addr = LCD_I2C_ADDR1; - else _addr = LCD_I2C_ADDR2; - _cols = 16; - _rows = 2; - _charsize = LCD_5x8DOTS; - _backlightval = LCD_BACKLIGHT; - } - - if (_type == LCD_STD) { - pinMode(_rs_pin, OUTPUT); - // we can save 1 pin by not using RW. Indicate by passing 255 instead of pin# - if (_rw_pin != 255) { - pinMode(_rw_pin, OUTPUT); - } - pinMode(_enable_pin, OUTPUT); - - } - _displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; - -} - -/********** high level commands, for the user! */ -void LiquidCrystal::clear() -{ - command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero - delayMicroseconds(2000); // this command takes a long time! -} - -void LiquidCrystal::home() -{ - command(LCD_RETURNHOME); // set cursor position to zero - delayMicroseconds(2000); // this command takes a long time! -} - -void LiquidCrystal::setCursor(uint8_t col, uint8_t row) -{ - int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 }; - if (_type == LCD_I2C) { - if (row > _rows) { - row = _rows-1; // we count rows starting w/0 - } - } - if (_type == LCD_STD) { - if (row >= _numlines) { - row = _numlines-1; - } - } - command(LCD_SETDDRAMADDR | (col + row_offsets[row])); -} - -// Turn the display on/off (quickly) -void LiquidCrystal::noDisplay() { - _displaycontrol &= ~LCD_DISPLAYON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} -void LiquidCrystal::display() { - _displaycontrol |= LCD_DISPLAYON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} - -// Turns the underline cursor on/off -void LiquidCrystal::noCursor() { - _displaycontrol &= ~LCD_CURSORON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} -void LiquidCrystal::cursor() { - _displaycontrol |= LCD_CURSORON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} - -// Turn on and off the blinking cursor -void LiquidCrystal::noBlink() { - _displaycontrol &= ~LCD_BLINKON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} -void LiquidCrystal::blink() { - _displaycontrol |= LCD_BLINKON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} - -// These commands scroll the display without changing the RAM -void LiquidCrystal::scrollDisplayLeft(void) { - command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT); -} -void LiquidCrystal::scrollDisplayRight(void) { - command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT); -} - -// This is for text that flows Left to Right -void LiquidCrystal::leftToRight(void) { - _displaymode |= LCD_ENTRYLEFT; - command(LCD_ENTRYMODESET | _displaymode); -} - -// This is for text that flows Right to Left -void LiquidCrystal::rightToLeft(void) { - _displaymode &= ~LCD_ENTRYLEFT; - command(LCD_ENTRYMODESET | _displaymode); -} - -// This will 'right justify' text from the cursor -void LiquidCrystal::autoscroll(void) { - _displaymode |= LCD_ENTRYSHIFTINCREMENT; - command(LCD_ENTRYMODESET | _displaymode); -} - -// This will 'left justify' text from the cursor -void LiquidCrystal::noAutoscroll(void) { - _displaymode &= ~LCD_ENTRYSHIFTINCREMENT; - command(LCD_ENTRYMODESET | _displaymode); -} - -// Allows us to fill the first 8 CGRAM locations -// with custom characters -//void LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) { -void LiquidCrystal::createChar(uint8_t location, PGM_P ptr) { - location &= 0x7; // we only have 8 locations 0-7 - command(LCD_SETCGRAMADDR | (location << 3)); - for (int i=0; i<8; i++) { - //write(charmap[i]); - write(pgm_read_byte(ptr++)); - } -} - -// Turn the (optional) backlight off/on -void LiquidCrystal::noBacklight(void) { - _backlightval=LCD_NOBACKLIGHT; - expanderWrite(0); -} - -void LiquidCrystal::backlight(void) { - _backlightval=LCD_BACKLIGHT; - expanderWrite(0); -} - -/*********** mid level commands, for sending data/cmds */ - -inline void LiquidCrystal::command(uint8_t value) { - send(value, 0); -} - -inline size_t LiquidCrystal::write(uint8_t value) { - send(value, Rs); - return 1; // assume sucess -} - -/************ low level data pushing commands **********/ - -// write either command or data -void LiquidCrystal::send(uint8_t value, uint8_t mode) { - if (_type == LCD_I2C) { - uint8_t highnib=value&0xf0; - uint8_t lownib=(value<<4)&0xf0; - write4bits((highnib)|mode); - write4bits((lownib)|mode); - } - if (_type == LCD_STD) { - digitalWrite(_rs_pin, mode); - - // if there is a RW pin indicated, set it low to Write - if (_rw_pin != 255) { - digitalWrite(_rw_pin, LOW); - } - - write4bits(value>>4); - write4bits(value); - } -} - -void LiquidCrystal::write4bits(uint8_t value) { - if (_type == LCD_I2C) { - expanderWrite(value); - pulseEnable(value); - } - if (_type == LCD_STD) { - for (int i = 0; i < 4; i++) { - pinMode(_data_pins[i], OUTPUT); - digitalWrite(_data_pins[i], (value >> i) & 0x01); - } - - pulseEnable(); - } -} - -void LiquidCrystal::expanderWrite(uint8_t _data){ - Wire.beginTransmission(_addr); - Wire.write((int)(_data) | _backlightval); - Wire.endTransmission(); -} - -void LiquidCrystal::pulseEnable(uint8_t _data){ - expanderWrite(_data | En); // En high - delayMicroseconds(1); // enable pulse must be >450ns - - expanderWrite(_data & ~En); // En low - delayMicroseconds(50); // commands need > 37us to settle -} - -void LiquidCrystal::pulseEnable(void) { - digitalWrite(_enable_pin, LOW); - delayMicroseconds(1); - digitalWrite(_enable_pin, HIGH); - delayMicroseconds(1); // enable pulse must be >450ns - digitalWrite(_enable_pin, LOW); - delayMicroseconds(100); // commands need > 37us to settle -} - -#endif diff --git a/LiquidCrystal.h b/LiquidCrystal.h deleted file mode 100644 index ab964da77..000000000 --- a/LiquidCrystal.h +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef LIQUID_CRYSTAL_DUAL_H -#define LIQUID_CRYSTAL_DUAL_H - -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - -#include -#include - -// commands -#define LCD_CLEARDISPLAY 0x01 -#define LCD_RETURNHOME 0x02 -#define LCD_ENTRYMODESET 0x04 -#define LCD_DISPLAYCONTROL 0x08 -#define LCD_CURSORSHIFT 0x10 -#define LCD_FUNCTIONSET 0x20 -#define LCD_SETCGRAMADDR 0x40 -#define LCD_SETDDRAMADDR 0x80 - -// flags for display entry mode -#define LCD_ENTRYRIGHT 0x00 -#define LCD_ENTRYLEFT 0x02 -#define LCD_ENTRYSHIFTINCREMENT 0x01 -#define LCD_ENTRYSHIFTDECREMENT 0x00 - -// flags for display on/off control -#define LCD_DISPLAYON 0x04 -#define LCD_DISPLAYOFF 0x00 -#define LCD_CURSORON 0x02 -#define LCD_CURSOROFF 0x00 -#define LCD_BLINKON 0x01 -#define LCD_BLINKOFF 0x00 - -// flags for display/cursor shift -#define LCD_DISPLAYMOVE 0x08 -#define LCD_CURSORMOVE 0x00 -#define LCD_MOVERIGHT 0x04 -#define LCD_MOVELEFT 0x00 - -// flags for function set -#define LCD_8BITMODE 0x10 -#define LCD_4BITMODE 0x00 -#define LCD_2LINE 0x08 -#define LCD_1LINE 0x00 -#define LCD_5x10DOTS 0x04 -#define LCD_5x8DOTS 0x00 - -// flags for backlight control -#define LCD_BACKLIGHT 0x08 -#define LCD_NOBACKLIGHT 0x00 - -#define En B00000100 // Enable bit -#define Rw B00000010 // Read/Write bit -#define Rs B00000001 // Register select bit - -#define LCD_STD 0 // Standard LCD -#define LCD_I2C 1 // I2C LCD -#define LCD_I2C_ADDR1 0x27 // type using PCF8574, at address 0x27 -#define LCD_I2C_ADDR2 0x3F // type using PCF8574A, at address 0x3F - -class LiquidCrystal : public Print { -public: - LiquidCrystal() {} - void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable, - uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, - uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); - - void begin(); - - void clear(); - void clear(int start, int end) { clear(); } - void home(); - - void noDisplay(); - void display(); - void noBlink(); - void blink(); - void noCursor(); - void cursor(); - void scrollDisplayLeft(); - void scrollDisplayRight(); - void leftToRight(); - void rightToLeft(); - void autoscroll(); - void noAutoscroll(); - - //void createChar(uint8_t, uint8_t[]); - void createChar(uint8_t, PGM_P ptr); - void setCursor(uint8_t, uint8_t); - virtual size_t write(uint8_t); - void command(uint8_t); - - inline uint8_t type() { return _type; } - void noBacklight(); - void backlight(); - - using Print::write; -private: - void send(uint8_t, uint8_t); - void write4bits(uint8_t); - void pulseEnable(); - - void expanderWrite(uint8_t); - void pulseEnable(uint8_t); - uint8_t _addr; - uint8_t _cols; - uint8_t _rows; - uint8_t _charsize; - uint8_t _backlightval; - - uint8_t _type; // LCD type. 0: standard; 1: I2C - uint8_t _rs_pin; // LOW: command. HIGH: character. - uint8_t _rw_pin; // LOW: write to LCD. HIGH: read from LCD. - uint8_t _enable_pin; // activated by a HIGH pulse. - uint8_t _data_pins[8]; - - uint8_t _displayfunction; - uint8_t _displaycontrol; - uint8_t _displaymode; - - uint8_t _initialized; - - uint8_t _numlines,_currline; -}; - -#endif - -#endif // LIQUID_CRYSTAL_DUAL_H diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 206ce5a50..344935e9d 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -79,11 +79,7 @@ extern ProgramData pd; extern const char* user_agent_string; extern unsigned char curr_alert_sid; -#if defined(USE_SSD1306) - SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); -#elif defined(USE_LCD) - LiquidCrystal OpenSprinkler::lcd; -#endif +SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); #if defined(ESP8266) unsigned char OpenSprinkler::state = OS_STATE_INITIAL; @@ -98,17 +94,13 @@ extern unsigned char curr_alert_sid; unsigned char OpenSprinkler::wifi_testmode = 0; CH224 OpenSprinkler::usbpd; uint8_t OpenSprinkler::actual_pd_voltage = 0; -#elif defined(ARDUINO) - extern SdFat sd; #else #if defined(OSPI) unsigned char OpenSprinkler::pin_sr_data = PIN_SR_DATA; #endif #endif -#if defined(USE_OTF) - OTCConfig OpenSprinkler::otc; -#endif +OTCConfig OpenSprinkler::otc; /** Option json names (stored in PROGMEM to reduce RAM usage) */ // IMPORTANT: each json name is strictly 5 characters @@ -364,7 +356,7 @@ unsigned char OpenSprinkler::iopts[] = { 0, 0, 0, -#if defined(ARDUINO) // on AVR, the default HTTP port is 80 +#if defined(ESP8266) // on Arduino, the default HTTP port is 80 80, // this and next byte define http port number 0, #else // on RPI/LINUX, the default HTTP port is 8080 @@ -474,7 +466,7 @@ static const char months_str[] PROGMEM = "Nov\0" "Dec\0"; -#if !defined(ARDUINO) +#if !defined(ESP8266) static inline uint32_t now() { time_t rawtime; time(&rawtime); @@ -486,7 +478,7 @@ time_os_t OpenSprinkler::now_tz() { return now()+(int32_t)3600/4*(int32_t)(iopts[IOPT_TIMEZONE]-48); } -#if defined(ARDUINO) +#if defined(ESP8266) bool detect_i2c(int addr) { Wire.beginTransmission(addr); @@ -494,44 +486,19 @@ bool detect_i2c(int addr) { } /** read hardware MAC into tmp_buffer */ -#define MAC_CTRL_ID 0x50 bool OpenSprinkler::load_hardware_mac(unsigned char* buffer, bool wired) { -#if defined(ESP8266) WiFi.macAddress((unsigned char*)buffer); // if requesting wired Ethernet MAC, flip the last byte to create a modified MAC if(wired) buffer[5] = ~buffer[5]; return true; -#else - // initialize the buffer by assigning software mac - buffer[0] = 0x00; - buffer[1] = 0x69; - buffer[2] = 0x69; - buffer[3] = 0x2D; - buffer[4] = 0x31; - buffer[5] = iopts[IOPT_DEVICE_ID]; - if (detect_i2c(MAC_CTRL_ID)==false) return false; - - Wire.beginTransmission(MAC_CTRL_ID); - Wire.write(0xFA); // The address of the register we want - Wire.endTransmission(); // Send the data - if(Wire.requestFrom(MAC_CTRL_ID, 6) != 6) return false; // if not enough data, return false - for(unsigned char ret=0;ret<6;ret++) { - buffer[ret] = Wire.read(); - } - return true; -#endif } -void(* resetFunc) (void) = 0; // AVR software reset function - /** Initialize network with the given mac address and http port */ unsigned char OpenSprinkler::start_network() { lcd_print_line_clear_pgm(PSTR("Starting..."), 1); uint16_t httpport = (uint16_t)(iopts[IOPT_HTTPPORT_1]<<8) + (uint16_t)iopts[IOPT_HTTPPORT_0]; -#if defined(ESP8266) - if (start_ether()) { useEth = true; WiFi.mode(WIFI_OFF); @@ -553,24 +520,9 @@ unsigned char OpenSprinkler::start_network() { DEBUG_PRINT(F("Started update server")); return 1; -#else - - if (start_ether()) { - if(m_server) { delete m_server; m_server = NULL; } - m_server = new EthernetServer(httpport); - m_server->begin(); - useEth = true; - return 1; - } else { - useEth = false; - return 0; - } - -#endif } unsigned char OpenSprinkler::start_ether() { -#if defined(ESP8266) if(hw_rev<2) return 0; // ethernet capability is only available when hw_rev>=2 eth.isW5500 = (hw_rev==2)?false:true; // os 3.2 uses enc28j60 and 3.3 uses w5500 @@ -675,42 +627,13 @@ unsigned char OpenSprinkler::start_ether() { // if wired connection has failed at this point, return depending on whether the user wants to force wired return (iopts[IOPT_FORCE_WIRED] ? 1 : 0); } - -#else - Ethernet.init(PIN_ETHER_CS); // make sure to call this before any Ethernet calls - if(Ethernet.hardwareStatus()==EthernetNoHardware) return 0; - load_hardware_mac((uint8_t*)tmp_buffer, true); - - lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); - - if (iopts[IOPT_USE_DHCP]) { - if(!Ethernet.begin((uint8_t*)tmp_buffer)) return 0; - memcpy(iopts+IOPT_STATIC_IP1, &(Ethernet.localIP()[0]), 4); - memcpy(iopts+IOPT_GATEWAY_IP1, &(Ethernet.gatewayIP()[0]),4); - memcpy(iopts+IOPT_DNS_IP1, &(Ethernet.dnsServerIP()[0]), 4); - memcpy(iopts+IOPT_SUBNET_MASK1, &(Ethernet.subnetMask()[0]), 4); - iopts_save(); - } else { - IPAddress staticip(iopts+IOPT_STATIC_IP1); - IPAddress gateway(iopts+IOPT_GATEWAY_IP1); - IPAddress dns(iopts+IOPT_DNS_IP1); - IPAddress subn(iopts+IOPT_SUBNET_MASK1); - Ethernet.begin((uint8_t*)tmp_buffer, staticip, dns, gateway, subn); - } - - return 1; -#endif } bool OpenSprinkler::network_connected(void) { -#if defined (ESP8266) if(useEth) return eth.connected(); else return (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && state==OS_STATE_CONNECTED); -#else - return (Ethernet.linkStatus()==LinkON); -#endif } /** Reboot controller */ @@ -720,11 +643,7 @@ void OpenSprinkler::reboot_dev(uint8_t cause) { nvdata.reboot_cause = cause; nvdata_save(); } -#if defined(ESP8266) ESP.restart(); -#else - resetFunc(); -#endif } #else // RPI/LINUX network init functions @@ -812,49 +731,24 @@ void OpenSprinkler::update_dev() { } #endif // end network init functions -#if defined(USE_DISPLAY) /** Initialize LCD */ void OpenSprinkler::lcd_start() { - -#if defined(USE_SSD1306) // initialize SSD1306 lcd.init(); lcd.begin(); flash_screen(); -#elif defined(USE_LCD) - // initialize 16x2 character LCD - // turn on lcd - lcd.init(1, PIN_LCD_RS, 255, PIN_LCD_EN, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7, 0,0,0,0); - lcd.begin(); - - if (lcd.type() == LCD_STD) { - // this is standard 16x2 LCD - // set PWM frequency for adjustable LCD backlight and contrast - TCCR1B = 0x02; // increase division factor for faster clock - // turn on LCD backlight and contrast - lcd_set_brightness(); - lcd_set_contrast(); - } else { - // for I2C LCD, we don't need to do anything - } -#endif } -#endif //extern void flow_isr(); /** Initialize pins, controller variables, LCD */ void OpenSprinkler::begin() { -#if defined(ARDUINO) - Wire.begin(); // init I2C -#endif - hw_type = HW_TYPE_UNKNOWN; hw_rev = 0; -#if defined(ESP8266) // ESP8266 specific initializations - +#if defined(ESP8266) + Wire.begin(); // init I2C /* detect hardware revision type */ if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists /* assign revision 0 pins */ @@ -988,35 +882,12 @@ void OpenSprinkler::begin() { expanders[i] = NULL; detect_expanders(); -#else - - // shift register setup - pinMode(PIN_SR_OE, OUTPUT); - // pull shift register OE high to disable output - digitalWrite(PIN_SR_OE, HIGH); - pinMode(PIN_SR_LATCH, OUTPUT); - digitalWrite(PIN_SR_LATCH, HIGH); - - pinMode(PIN_SR_CLOCK, OUTPUT); - - #if defined(OSPI) - pin_sr_data = PIN_SR_DATA; - // detect RPi revision - unsigned int rev = detect_rpi_rev(); - if (rev==0x0002 || rev==0x0003) - pin_sr_data = PIN_SR_DATA_ALT; - // if this is revision 1, use PIN_SR_DATA_ALT - pinMode(pin_sr_data, OUTPUT); - #else - pinMode(PIN_SR_DATA, OUTPUT); - #endif - #endif #if defined(OSPI) -pinModeExt(PIN_BUTTON_1, INPUT_PULLUP); -pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); -pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); + pinModeExt(PIN_BUTTON_1, INPUT_PULLUP); + pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); + pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); #endif // init masters_last_on array @@ -1035,9 +906,7 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); digitalWrite(PIN_SR_OE, LOW); // Rain sensor port set up pinMode(PIN_SENSOR1, INPUT_PULLUP); - #if defined(PIN_SENSOR2) pinMode(PIN_SENSOR2, INPUT_PULLUP); - #endif #endif // Default controller status variables @@ -1061,94 +930,36 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); digitalWriteExt(PIN_RFTX, LOW); } -#if defined(ARDUINO) // AVR SD and LCD functions - - #if defined(ESP8266) // OS3.0 specific detections - +#if defined(ESP8266) status.has_curr_sense = 1; // OS3.0 has current sensing capacility // measure baseline current baseline_current = 80; - - #else // OS 2.3 specific detections - - // detect hardware type - if (detect_i2c(MAC_CTRL_ID)) { - Wire.beginTransmission(MAC_CTRL_ID); - Wire.write(0x00); - Wire.endTransmission(); - Wire.requestFrom(MAC_CTRL_ID, 1); - unsigned char ret = Wire.read(); - if (ret == HW_TYPE_AC || ret == HW_TYPE_DC || ret == HW_TYPE_LATCH) { - hw_type = ret; - } else { - hw_type = HW_TYPE_AC; // if type not supported, make it AC - } - } - - if (hw_type == HW_TYPE_DC) { - pinMode(PIN_BOOST, OUTPUT); - digitalWrite(PIN_BOOST, LOW); - - pinMode(PIN_BOOST_EN, OUTPUT); - digitalWrite(PIN_BOOST_EN, LOW); - } - - // detect if current sensing pin is present - pinMode(PIN_CURR_DIGITAL, INPUT); - digitalWrite(PIN_CURR_DIGITAL, HIGH); // enable internal pullup - status.has_curr_sense = digitalRead(PIN_CURR_DIGITAL) ? 0 : 1; - digitalWrite(PIN_CURR_DIGITAL, LOW); - baseline_current = 0; - - #endif #endif + #if defined(USE_DISPLAY) lcd_start(); - - #if defined(USE_SSD1306) - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); - lcd.createChar(ICON_WIFI_CONNECTED, _iconimage_wifi_connected); - lcd.createChar(ICON_WIFI_DISCONNECTED, _iconimage_wifi_disconnected); - #elif defined(USE_LCD) - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_disconnected); - #endif - + lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); + lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); + lcd.createChar(ICON_WIFI_CONNECTED, _iconimage_wifi_connected); + lcd.createChar(ICON_WIFI_DISCONNECTED, _iconimage_wifi_disconnected); lcd.createChar(ICON_REMOTEXT, _iconimage_remotext); lcd.createChar(ICON_RAINDELAY, _iconimage_raindelay); lcd.createChar(ICON_RAIN, _iconimage_rain); lcd.createChar(ICON_SOIL, _iconimage_soil); #endif -#if defined(ARDUINO) - #if defined(ESP8266) - lcd.setCursor(0,0); - lcd.print(F("Init file system")); - lcd.setCursor(0,1); - if(!LittleFS.begin()) { - // !!! flash init failed, stall as we cannot proceed - lcd.setCursor(0, 0); - lcd_print_pgm(PSTR("Error Code: 0x2D")); - delay(5000); - } - - state = OS_STATE_INITIAL; - - #else - - // set sd cs pin high to release SD - pinMode(PIN_SD_CS, OUTPUT); - digitalWrite(PIN_SD_CS, HIGH); - - if(!sd.begin(PIN_SD_CS, SPI_HALF_SPEED)) { - // !!! sd card not detected, stall as we cannot proceed - lcd.setCursor(0, 0); - lcd_print_pgm(PSTR("Error Code: 0x2D")); - while(1){} - } +#if defined(ESP8266) + lcd.setCursor(0,0); + lcd.print(F("Init file system")); + lcd.setCursor(0,1); + if(!LittleFS.begin()) { + // !!! flash init failed, stall as we cannot proceed + lcd.setCursor(0, 0); + lcd_print_pgm(PSTR("Error Code: 0x2D")); + delay(5000); + } - #endif + state = OS_STATE_INITIAL; // set button pins // enable internal pullup @@ -1424,23 +1235,6 @@ void OpenSprinkler::apply_all_station_bits(void (*post_activation_callback)()) { } } - #if defined(ARDUINO) - if((hw_type==HW_TYPE_DC) && engage_booster) { - // for DC controller: boost voltage - digitalWrite(PIN_BOOST_EN, LOW); // disable output path - digitalWrite(PIN_BOOST, HIGH); // enable boost converter - delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge - digitalWrite(PIN_BOOST, LOW); // disable boost converter - - digitalWrite(PIN_BOOST_EN, HIGH); // enable output path - digitalWrite(PIN_SR_LATCH, HIGH); - engage_booster = 0; - } else { - digitalWrite(PIN_SR_LATCH, HIGH); - } - #else - digitalWrite(PIN_SR_LATCH, HIGH); - #endif #endif // If a post activation callback function is defined, call it here @@ -1506,8 +1300,6 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { } } -// ESP8266 is guaranteed to have sensor 2 -#if defined(ESP8266) || defined(PIN_SENSOR2) if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) { if(hw_rev>=2) pinMode(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 unsigned char val = digitalReadExt(PIN_SENSOR2); @@ -1535,8 +1327,6 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { } } } - -#endif } /** Return program switch status */ @@ -1553,7 +1343,7 @@ unsigned char OpenSprinkler::detect_programswitch_status(time_os_t curr_time) { ret |= 0x01; } } -#if defined(ESP8266) || defined(PIN_SENSOR2) + if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { static unsigned char sensor2_hist = 0; if(hw_rev>=2) pinMode(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 @@ -1563,7 +1353,7 @@ unsigned char OpenSprinkler::detect_programswitch_status(time_os_t curr_time) { ret |= 0x02; } } -#endif + return ret; } @@ -1587,7 +1377,7 @@ void OpenSprinkler::sensor_resetall() { * ESP8266's analog reference voltage is 1.0 instead of 3.3, therefore * it's further discounted by 1/3.3 */ -#if defined(ARDUINO) +#if defined(ESP8266) uint16_t OpenSprinkler::read_current(bool use_ema) { static uint16_t ema = 0; // exponential moving average static float scale = -1; @@ -1615,27 +1405,15 @@ uint16_t OpenSprinkler::read_current(bool use_ema) { #endif /** Read the number of 8-station expansion boards */ -// AVR has capability to detect number of expansion boards +// Arduino has capability to detect number of expansion boards int OpenSprinkler::detect_exp() { -#if defined(ARDUINO) - #if defined(ESP8266) +#if defined(ESP8266) // detect the highest expansion board index int n; for(n=4;n>=0;n--) { if(detect_i2c(EXP_I2CADDR_BASE+n)) break; } return (n+1)*2; - #else - // OpenSprinkler uses voltage divider to detect expansion boards - // Master controller has a 1.6K pull-up; - // each expansion board (8 stations) has 2x 4.7K pull-down connected in parallel; - // so the exact ADC value for n expansion boards is: - // ADC = 1024 * 9.4 / (10 + 9.4 * n) - // Reverse this fomular we have: - // n = (1024 * 9.4 / ADC - 9.4) / 1.6 - int n = (int)((1024 * 9.4 / analogRead(PIN_EXP_SENSE) - 9.4) / 1.6 + 0.33); - return n; - #endif #else return -1; #endif @@ -1869,7 +1647,7 @@ unsigned char OpenSprinkler::password_verify(const char *pw) { /** Index of today's weekday (Monday is 0) */ unsigned char OpenSprinkler::weekday_today() { //return ((unsigned char)weekday()+5)%7; // Time::weekday() assumes Sunday is 1 -#if defined(ARDUINO) +#if defined(ESP8266) ulong wd = now_tz() / 86400L; return (wd+3) % 7; // Jan 1, 1970 is a Thursday #else @@ -2011,27 +1789,23 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* DEBUG_PRINTLN("server:port is invalid!"); return HTTP_RQT_CONNECT_ERR; } -#if defined(ARDUINO) +#if defined(ESP8266) Client *client = NULL; - #if defined(ESP8266) - if(usessl) { - WiFiClientSecure *_c = new WiFiClientSecure(); - _c->setInsecure(); - bool mfln = _c->probeMaxFragmentLength(server, port, 512); - DEBUG_PRINTF("MFLN supported: %s\n", mfln ? "yes" : "no"); - if (mfln) { - _c->setBufferSizes(512, 512); - } else { - _c->setBufferSizes(2048, 2048); - } - client = _c; + if(usessl) { + WiFiClientSecure *_c = new WiFiClientSecure(); + _c->setInsecure(); + bool mfln = _c->probeMaxFragmentLength(server, port, 512); + DEBUG_PRINTF("MFLN supported: %s\n", mfln ? "yes" : "no"); + if (mfln) { + _c->setBufferSizes(512, 512); } else { - client = new WiFiClient(); + _c->setBufferSizes(2048, 2048); } - #else - client = new EthernetClient(); - #endif + client = _c; + } else { + client = new WiFiClient(); + } #define HTTP_CONNECT_NTRIES 3 unsigned char tries = 0; @@ -2084,7 +1858,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* uint32_t stoptime = millis()+timeout; int pos = 0; -#if defined(ARDUINO) +#if defined(ESP8266) // with ESP8266 core 3.0.2, client->connected() is not always true even if there is more data // so this loop is going to take longer than it should be // todo: can consider using HTTPClient for ESP8266 @@ -2262,7 +2036,7 @@ void OpenSprinkler::pre_factory_reset() { /** Factory reset */ void OpenSprinkler::factory_reset() { -#if defined(ARDUINO) +#if defined(ESP8266) lcd_print_line_clear_pgm(PSTR("Factory reset"), 0); lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); #else @@ -2320,7 +2094,6 @@ void OpenSprinkler::factory_reset() { } /** Parse OTC configuration */ -#if defined(USE_OTF) void OpenSprinkler::parse_otc_config() { ArduinoJson::JsonDocument doc; // make sure this has the same scope as server and token const char *server = NULL; @@ -2357,7 +2130,6 @@ void OpenSprinkler::parse_otc_config() { otc.server = server ? String(server) : ""; otc.port = port; } -#endif /** Setup function for options */ void OpenSprinkler::options_setup() { @@ -2391,14 +2163,12 @@ void OpenSprinkler::options_setup() { } } #endif - #if defined(USE_OTF) parse_otc_config(); - #endif attribs_load(); } -#if defined(ARDUINO) // handle AVR buttons +#if defined(ESP8266) // handle buttons unsigned char button = button_read(BUTTON_WAIT_NONE); switch(button & BUTTON_MASK) { @@ -2413,7 +2183,6 @@ void OpenSprinkler::options_setup() { break; case BUTTON_2: - #if defined(ESP8266) // if BUTTON_2 is pressed during startup, go to Test OS mode // only available for OS 3.0 lcd_print_line_clear_pgm(PSTR("===Test Mode==="), 0); @@ -2433,8 +2202,6 @@ void OpenSprinkler::options_setup() { wifi_pass = "opendoor"; #endif button = 0; - #endif - break; case BUTTON_3: @@ -2483,7 +2250,7 @@ void OpenSprinkler::options_setup() { lcd_print_pgm(PSTR(" AC")); } delay(1500); - #if defined(ARDUINO) + #if defined(ESP8266) lcd.setCursor(2, 1); lcd_print_pgm(PSTR("FW ")); lcd.print((char)('0'+(OS_FW_VERSION/100))); @@ -2618,13 +2385,9 @@ void OpenSprinkler::raindelay_stop() { /** LCD and button functions */ #if defined(USE_DISPLAY) -#if defined(ARDUINO) // AVR LCD and button functions +#if defined(ESP8266) // Arduino LCD and button functions /** print a program memory string */ -#if defined(ESP8266) void OpenSprinkler::lcd_print_pgm(PGM_P str) { -#else -void OpenSprinkler::lcd_print_pgm(PGM_P PROGMEM str) { -#endif uint8_t c; while((c=pgm_read_byte(str++))!= '\0') { lcd.print((char)c); @@ -2632,11 +2395,7 @@ void OpenSprinkler::lcd_print_pgm(PGM_P PROGMEM str) { } /** print a program memory string to a given line with clearing */ -#if defined(ESP8266) void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P str, unsigned char line) { -#else -void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P PROGMEM str, unsigned char line) { -#endif lcd.setCursor(0, line); uint8_t c; int8_t cnt = 0; @@ -2678,42 +2437,26 @@ void OpenSprinkler::lcd_print_2digit(int v) /** print time to a given line */ void OpenSprinkler::lcd_print_time(time_os_t t) { -#if defined(USE_SSD1306) lcd.setAutoDisplay(false); -#endif lcd.setCursor(0, 0); lcd_print_2digit(hour(t)); - lcd_print_pgm(PSTR(":")); - lcd_print_2digit(minute(t)); - lcd_print_pgm(PSTR(" ")); - // each weekday string has 3 characters + ending 0 lcd_print_pgm(days_str+4*weekday_today()); - lcd_print_pgm(PSTR(" ")); - lcd_print_pgm(months_str+4*(month(t)-1)); - lcd_print_pgm(PSTR("-")); - lcd_print_2digit(day(t)); -#if defined(USE_SSD1306) lcd.display(); lcd.setAutoDisplay(true); -#endif } /** print ip address */ void OpenSprinkler::lcd_print_ip(const unsigned char *ip, unsigned char endian) { -#if defined(USE_SSD1306) lcd.clear(0, 1); lcd.setAutoDisplay(false); -#elif defined(USE_LCD) - lcd.clear(); -#endif lcd.setCursor(0, 0); for (unsigned char i=0; i<4; i++) { lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); @@ -2722,18 +2465,13 @@ void OpenSprinkler::lcd_print_ip(const unsigned char *ip, unsigned char endian) lcd_print_pgm(PSTR(".")); } } - - #if defined(USE_SSD1306) - lcd.display(); - lcd.setAutoDisplay(true); - #endif + lcd.display(); + lcd.setAutoDisplay(true); } /** print mac address */ void OpenSprinkler::lcd_print_mac(const unsigned char *mac) { - #if defined(USE_SSD1306) - lcd.setAutoDisplay(false); // reduce screen drawing time by turning off display() when drawing individual characters - #endif + lcd.setAutoDisplay(false); // reduce screen drawing time by turning off display() when drawing individual characters lcd.setCursor(0, 0); for(unsigned char i=0; i<6; i++) { if(i) { @@ -2744,7 +2482,7 @@ void OpenSprinkler::lcd_print_mac(const unsigned char *mac) { lcd.print((mac[i]&0x0F), HEX); if(i==4) lcd.setCursor(0, 1); } - #if defined(ARDUINO) + #if defined(ESP8266) if(useEth) { lcd_print_pgm(PSTR(" (Ether MAC)")); } else { @@ -2754,17 +2492,13 @@ void OpenSprinkler::lcd_print_mac(const unsigned char *mac) { lcd_print_pgm(PSTR(" (MAC)")); #endif - #if defined(USE_SSD1306) - lcd.display(); - lcd.setAutoDisplay(true); - #endif + lcd.display(); + lcd.setAutoDisplay(true); } /** print station bits */ void OpenSprinkler::lcd_print_screen(char c) { -#if defined(USE_SSD1306) lcd.setAutoDisplay(false); // reduce screen drawing time by turning off display() when drawing individual characters -#endif lcd.setCursor(0, 1); if (status.display_board == 0) { lcd.print(F("MC:")); // Master controller is display as 'MC' @@ -2851,7 +2585,6 @@ void OpenSprinkler::lcd_print_screen(char c) { lcd.write(status.network_fails>2?ICON_ETHER_DISCONNECTED:ICON_ETHER_CONNECTED); // if network failure detection is more than 2, display disconnect icon #endif -#if defined(USE_SSD1306) #if defined(ESP8266) if(useEth || (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP())) { #else @@ -2883,11 +2616,9 @@ void OpenSprinkler::lcd_print_screen(char c) { lcd.clear(2, 2); } } - - lcd.display(); lcd.setAutoDisplay(true); -#endif + } /** print a version number */ @@ -2962,7 +2693,7 @@ void OpenSprinkler::lcd_print_option(int i) { lcd.print((int)iopts[i]); break; case IOPT_BOOST_TIME: - #if defined(ARDUINO) + #if defined(ESP8266) if(hw_type==HW_TYPE_AC) { lcd.print('-'); } else { @@ -2975,7 +2706,7 @@ void OpenSprinkler::lcd_print_option(int i) { break; case IOPT_I_MIN_THRESHOLD: case IOPT_I_MAX_LIMIT: - #if defined(ARDUINO) + #if defined(ESP8266) lcd.print((int)iopts[i]*10); lcd_print_pgm(PSTR(" mA")); #else @@ -2984,7 +2715,7 @@ void OpenSprinkler::lcd_print_option(int i) { break; case IOPT_LATCH_ON_VOLTAGE: case IOPT_LATCH_OFF_VOLTAGE: - #if defined(ARDUINO) + #if defined(ESP8266) if(hw_type==HW_TYPE_LATCH) { lcd.print((int)iopts[i]); lcd.print('V'); @@ -3075,7 +2806,7 @@ unsigned char OpenSprinkler::button_read(unsigned char waitmode) return ret; } -#if defined(ARDUINO) +#if defined(ESP8266) /** user interface for setting options during startup */ void OpenSprinkler::ui_set_options(int oid) @@ -3127,11 +2858,7 @@ void OpenSprinkler::ui_set_options(int oid) if (hw_type==HW_TYPE_AC && i==IOPT_BOOST_TIME) i++; // skip boost time for non-DC controller if (i==IOPT_LATCH_ON_VOLTAGE && hw_type!=HW_TYPE_LATCH) i+= 2; // skip latch voltage defs for non-latch controllers if (i==IOPT_TARGET_PD_VOLTAGE && !(hw_rev==4 && hw_type==HW_TYPE_DC)) i++; // skip target pd voltage if not 3.4 or not DC type - #if defined(ESP8266) else if (lcd.type()==LCD_I2C && i==IOPT_LCD_CONTRAST) i+=3; - #else - else if (lcd.type()==LCD_I2C && i==IOPT_LCD_CONTRAST) i+=2; - #endif // string options are not editable } break; @@ -3143,56 +2870,22 @@ void OpenSprinkler::ui_set_options(int oid) } lcd.noBlink(); } - -#else #endif // end of LCD and button functions /** Set LCD contrast (using PWM) */ void OpenSprinkler::lcd_set_contrast() { -#ifdef PIN_LCD_CONTRAST - // set contrast is only valid for standard LCD - if (lcd.type()==LCD_STD) { - pinMode(PIN_LCD_CONTRAST, OUTPUT); - analogWrite(PIN_LCD_CONTRAST, iopts[IOPT_LCD_CONTRAST]); - } -#endif } /** Set LCD brightness (using PWM) */ void OpenSprinkler::lcd_set_brightness(unsigned char value) { -#if defined(PIN_LCD_BACKLIGHT) - #if defined(OS_AVR) - if (lcd.type()==LCD_I2C) { - if (value) lcd.backlight(); - else { - // turn off LCD backlight - // only if dimming value is set to 0 - if(iopts[IOPT_LCD_DIMMING]==0) lcd.noBacklight(); - else lcd.backlight(); - } - } - #endif - if (lcd.type()==LCD_STD) { - pinMode(PIN_LCD_BACKLIGHT, OUTPUT); - if (value) { - analogWrite(PIN_LCD_BACKLIGHT, 255-iopts[IOPT_LCD_BACKLIGHT]); - } else { - analogWrite(PIN_LCD_BACKLIGHT, 255-iopts[IOPT_LCD_DIMMING]); - } - } - -#elif defined(USE_SSD1306) if (value) {lcd.displayOn();lcd.setBrightness(255); } else { if(iopts[IOPT_LCD_DIMMING]==0) lcd.displayOff(); else { lcd.displayOn();lcd.setBrightness(iopts[IOPT_LCD_DIMMING]); } } -#endif } - - -#if defined(USE_SSD1306) +#if defined(USE_DISPLAY) #include "images.h" void OpenSprinkler::flash_screen() { lcd.setCursor(0, -1); diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 15bac6d3b..a85f4c900 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -21,9 +21,7 @@ * . */ - -#ifndef _OPENSPRINKLER_H -#define _OPENSPRINKLER_H +#pragma once #include "types.h" #include "defines.h" @@ -33,29 +31,23 @@ #include "mqtt.h" #include "RCSwitch.h" -#if defined(ARDUINO) // headers for Arduino +#if defined(ESP8266) // headers for Arduino #include #include #include #include #include "I2CRTC.h" - #if defined(ESP8266) // for ESP8266 - #include - #include - #include - #include - #include - #include - #include - #include "espconnect.h" - #include "EMailSender.h" - #include "ch224.h" - #else // for AVR - #include - #include - #include "LiquidCrystal.h" - #endif + #include + #include + #include + #include + #include + #include + #include + #include "espconnect.h" + #include "EMailSender.h" + #include "ch224.h" #else // headers for RPI/LINUX #include @@ -69,16 +61,11 @@ #include "smtp.h" #endif // end of headers -#if defined(USE_LCD) - #include "LiquidCrystal.h" -#endif - -#if defined(USE_SSD1306) +#if defined(USE_DISPLAY) #include "SSD1306Display.h" #endif -#if defined(ARDUINO) - #if defined(ESP8266) +#if defined(ESP8266) extern ESP8266WebServer *update_server; extern ENC28J60lwIP enc28j60; extern Wiznet5500lwIP w5500; @@ -110,20 +97,12 @@ } }; extern lwipEth eth; - #else - // AVR specific - #endif extern bool useEth; #else // OSPI/Linux specific #endif -#if defined(USE_OTF) - extern OTF::OpenThingsFramework *otf; -#else - extern EthernetServer *m_server; - extern bool useEth; -#endif +extern OTF::OpenThingsFramework *otf; /** Non-volatile data structure */ struct NVConData { @@ -241,10 +220,8 @@ class OpenSprinkler { public: // data members -#if defined(USE_SSD1306) +#if defined(USE_DISPLAY) static SSD1306Display lcd; // 128x64 OLED display -#elif defined(USE_LCD) - static LiquidCrystal lcd; // 16x2 character LCD #endif #if defined(OSPI) @@ -372,9 +349,7 @@ class OpenSprinkler { static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, bool usessl=false, uint16_t timeout=5000); static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, bool usessl=false, uint16_t timeout=5000); - #if defined(USE_OTF) static OTCConfig otc; - #endif // -- LCD functions #if defined(USE_DISPLAY) @@ -385,12 +360,9 @@ class OpenSprinkler { static void lcd_print_version(unsigned char v); // print version number static void lcd_set_brightness(unsigned char value=1); static void lcd_set_contrast(); - - #if defined(USE_SSD1306) static void flash_screen(); static void toggle_screen_led(); static void set_screen_led(unsigned char status); - #endif static String time2str(uint32_t t) { uint16_t h = hour(t); @@ -417,16 +389,10 @@ class OpenSprinkler { static void ui_set_options(int oid); // ui for setting options (oid-> starting option index) #endif -#if defined(ARDUINO) // LCD functions for Arduino - #if defined(ESP8266) +#if defined(ESP8266) // LCD functions for Arduino static void lcd_print_pgm(PGM_P str); // ESP8266 does not allow PGM_P followed by PROGMEM static void lcd_print_line_clear_pgm(PGM_P str, unsigned char line); - #else - static void lcd_print_pgm(PGM_P PROGMEM str); // print a program memory string - static void lcd_print_line_clear_pgm(PGM_P PROGMEM str, unsigned char line); - #endif - #if defined(ESP8266) static IOEXP *mainio, *drio; static IOEXP *expanders[]; static CH224 usbpd; @@ -442,11 +408,10 @@ class OpenSprinkler { static void reset_to_ap(); static unsigned char state; static void setup_pd_voltage(); - #endif #else -static void lcd_print_pgm(const char *str); -static void lcd_print_line_clear_pgm(const char *str, unsigned char line); + static void lcd_print_pgm(const char *str); + static void lcd_print_line_clear_pgm(const char *str, unsigned char line); #endif // LCD functions for Arduino private: @@ -471,9 +436,6 @@ static void lcd_print_line_clear_pgm(const char *str, unsigned char line); static unsigned char engage_booster; static RCSwitch rfswitch; - #if defined(USE_OTF) static void parse_otc_config(); - #endif }; -#endif // _OPENSPRINKLER_H diff --git a/RCSwitch.cpp b/RCSwitch.cpp index 2f4ae7f64..d821f17a2 100644 --- a/RCSwitch.cpp +++ b/RCSwitch.cpp @@ -32,7 +32,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#if defined(ARDUINO) +#if defined(ESP8266) #include #endif diff --git a/SSD1306Display.h b/SSD1306Display.h index f97683a5f..6593cfafe 100644 --- a/SSD1306Display.h +++ b/SSD1306Display.h @@ -1,5 +1,4 @@ -#ifndef SSD1306_DISPLAY_H -#define SSD1306_DISPLAY_H +#pragma once #if defined(ESP8266) @@ -13,81 +12,81 @@ class SSD1306Display : public SSD1306 { public: - SSD1306Display(uint8_t _addr, uint8_t _sda, uint8_t _scl) - : SSD1306(_addr, _sda, _scl) { - cx = 0; - cy = 0; - for (unsigned char i = 0; i < NUM_CUSTOM_ICONS; i++) - custom_chars[i] = NULL; - } - void begin() { - Wire.setClock(400000L); // lower clock to 400kHz - flipScreenVertically(); - setFont(Monospaced_plain_13); - fontWidth = 8; - fontHeight = 16; - } - void clear() { SSD1306::clear(); } - void clear(int start, int end) { - setColor(BLACK); - fillRect(0, (start + 1) * fontHeight, 128, (end - start + 1) * fontHeight); - setColor(WHITE); - } - - uint8_t type() { return LCD_I2C; } - void noBlink() { /*no support*/ } - void blink() { /*no support*/ } - void setCursor(uint8_t col, int8_t row) { - /* assume 4 lines, the middle two lines - are row 0 and 1 */ - cy = (row + 1) * fontHeight; - cx = col * fontWidth; - } - void noBacklight() { /*no support*/ } - void backlight() { /*no support*/ } - size_t write(uint8_t c) { - setColor(BLACK); - fillRect(cx, cy, fontWidth, fontHeight); - setColor(WHITE); - - if (c < NUM_CUSTOM_ICONS && custom_chars[c] != NULL) { - drawXbm(cx, cy, fontWidth, fontHeight, - (const unsigned char *)custom_chars[c]); - } else { - drawString(cx, cy, String((char)c)); - } - cx += fontWidth; - if (auto_display) - display(); // todo: not very efficient - return 1; - } - size_t write(const char *s) { - uint8_t nc = strlen(s); - setColor(BLACK); - fillRect(cx, cy, fontWidth * nc, fontHeight); - setColor(WHITE); - drawString(cx, cy, String(s)); - cx += fontWidth * nc; - if (auto_display) - display(); // todo: not very efficient - return nc; - } - void createChar(unsigned char idx, PGM_P ptr) { - if (idx >= 0 && idx < NUM_CUSTOM_ICONS) - custom_chars[idx] = ptr; - } - - void createChar(unsigned char idx, const unsigned char *ptr) { - createChar(idx, (const char *)ptr); - } - - void setAutoDisplay(bool v) { auto_display = v; } + SSD1306Display(uint8_t _addr, uint8_t _sda, uint8_t _scl) + : SSD1306(_addr, _sda, _scl) { + cx = 0; + cy = 0; + for (unsigned char i = 0; i < NUM_CUSTOM_ICONS; i++) + custom_chars[i] = NULL; + } + void begin() { + Wire.setClock(400000L); // lower clock to 400kHz + flipScreenVertically(); + setFont(Monospaced_plain_13); + fontWidth = 8; + fontHeight = 16; + } + void clear() { SSD1306::clear(); } + void clear(int start, int end) { + setColor(BLACK); + fillRect(0, (start + 1) * fontHeight, 128, (end - start + 1) * fontHeight); + setColor(WHITE); + } + + uint8_t type() { return LCD_I2C; } + void noBlink() { /*no support*/ } + void blink() { /*no support*/ } + void setCursor(uint8_t col, int8_t row) { + /* assume 4 lines, the middle two lines + are row 0 and 1 */ + cy = (row + 1) * fontHeight; + cx = col * fontWidth; + } + void noBacklight() { /*no support*/ } + void backlight() { /*no support*/ } + size_t write(uint8_t c) { + setColor(BLACK); + fillRect(cx, cy, fontWidth, fontHeight); + setColor(WHITE); + + if (c < NUM_CUSTOM_ICONS && custom_chars[c] != NULL) { + drawXbm(cx, cy, fontWidth, fontHeight, + (const unsigned char *)custom_chars[c]); + } else { + drawString(cx, cy, String((char)c)); + } + cx += fontWidth; + if (auto_display) + display(); // todo: not very efficient + return 1; + } + size_t write(const char *s) { + uint8_t nc = strlen(s); + setColor(BLACK); + fillRect(cx, cy, fontWidth * nc, fontHeight); + setColor(WHITE); + drawString(cx, cy, String(s)); + cx += fontWidth * nc; + if (auto_display) + display(); // todo: not very efficient + return nc; + } + void createChar(unsigned char idx, PGM_P ptr) { + if (idx >= 0 && idx < NUM_CUSTOM_ICONS) + custom_chars[idx] = ptr; + } + + void createChar(unsigned char idx, const unsigned char *ptr) { + createChar(idx, (const char *)ptr); + } + + void setAutoDisplay(bool v) { auto_display = v; } private: - bool auto_display = true; - uint8_t cx, cy; - uint8_t fontWidth, fontHeight; - PGM_P custom_chars[NUM_CUSTOM_ICONS]; + bool auto_display = true; + uint8_t cx, cy; + uint8_t fontWidth, fontHeight; + PGM_P custom_chars[NUM_CUSTOM_ICONS]; }; #else @@ -171,387 +170,385 @@ class SSD1306Display : public SSD1306 { class SSD1306Display { public: - SSD1306Display(uint8_t addr, uint8_t _sda, uint8_t _scl) { - cx = 0; - cy = 0; - _addr = addr; - for (uint8_t i = 0; i < NUM_CUSTOM_ICONS; i++) - custom_chars[i] = 0; - - clear_buffer(); - - height = 64; - width = 128; - - i2c = I2CDevice(); - } - - ~SSD1306Display() { - displayOff(); - close(file); - } - - void init() {} // Dummy function to match ESP8266 - - int begin() { - i2c.begin(_addr); - - setFont(Monospaced_plain_13); - fontWidth = 8; - fontHeight = 16; - - i2c.begin_transaction(SSD1306_COMMAND_ADDRESS); - ssd1306_command(SSD1306_DISPLAY_OFF); - ssd1306_command(SSD1306_SET_DISPLAY_CLOCK_DIV_RATIO); - ssd1306_command(0x80); - ssd1306_command(SSD1306_SET_MULTIPLEX_RATIO); - ssd1306_command(height - 1); - ssd1306_command(SSD1306_SET_DISPLAY_OFFSET); - ssd1306_command(0x00); - ssd1306_command(SSD1306_SET_START_LINE); - ssd1306_command(SSD1306_CHARGE_PUMP); - ssd1306_command(0x14); - ssd1306_command(SSD1306_MEMORY_ADDR_MODE); - ssd1306_command(0x00); - ssd1306_command(SSD1306_SET_SEGMENT_REMAP | 0x01); - ssd1306_command(SSD1306_COM_SCAN_DIR_DEC); - - switch (height) { - case 64: - ssd1306_command(SSD1306_SET_COM_PINS); - ssd1306_command(0x12); - ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); - ssd1306_command(0xCF); - break; - case 32: - ssd1306_command(SSD1306_SET_COM_PINS); - ssd1306_command(0x02); - ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); - ssd1306_command(0x8F); - break; - case 16: // NOTE: not tested, lacking part. - ssd1306_command(SSD1306_SET_COM_PINS); - ssd1306_command(0x2); - ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); - ssd1306_command(0xAF); - break; - } - - ssd1306_command(SSD1306_SET_PRECHARGE_PERIOD); - ssd1306_command(0xF1); - - ssd1306_command(SSD1306_SET_VCOM_DESELECT); - ssd1306_command(0x40); - - ssd1306_command(SSD1306_DISPLAY_ALL_ON_RESUME); - ssd1306_command(SSD1306_NORMAL_DISPLAY); - ssd1306_command(SSD1306_DEACTIVATE_SCROLL); - ssd1306_command(SSD1306_DISPLAY_ON); - - i2c.end_transaction(); - - return 0; - } - - void setFont(const uint8_t *f) { font = (uint8_t *)f; } - - void display() { - i2c.begin_transaction(SSD1306_COMMAND_ADDRESS); - ssd1306_command(SSD1306_SET_PAGE_ADDR); - ssd1306_command(0x00); // Page start address (0 = reset) - switch (height) { - case 64: - ssd1306_command(7); - break; - case 32: - ssd1306_command(3); - break; - case 16: - ssd1306_command(1); - break; - } - - ssd1306_command(SSD1306_SET_COLUMN_ADDR); - ssd1306_command(0x00); // Column start address (0 = reset) + SSD1306Display(uint8_t addr, uint8_t _sda, uint8_t _scl) { + cx = 0; + cy = 0; + _addr = addr; + for (uint8_t i = 0; i < NUM_CUSTOM_ICONS; i++) + custom_chars[i] = 0; + + clear_buffer(); + + height = 64; + width = 128; + + i2c = I2CDevice(); + } + + ~SSD1306Display() { + displayOff(); + close(file); + } + + void init() {} // Dummy function to match ESP8266 + + int begin() { + i2c.begin(_addr); + + setFont(Monospaced_plain_13); + fontWidth = 8; + fontHeight = 16; + + i2c.begin_transaction(SSD1306_COMMAND_ADDRESS); + ssd1306_command(SSD1306_DISPLAY_OFF); + ssd1306_command(SSD1306_SET_DISPLAY_CLOCK_DIV_RATIO); + ssd1306_command(0x80); + ssd1306_command(SSD1306_SET_MULTIPLEX_RATIO); + ssd1306_command(height - 1); + ssd1306_command(SSD1306_SET_DISPLAY_OFFSET); + ssd1306_command(0x00); + ssd1306_command(SSD1306_SET_START_LINE); + ssd1306_command(SSD1306_CHARGE_PUMP); + ssd1306_command(0x14); + ssd1306_command(SSD1306_MEMORY_ADDR_MODE); + ssd1306_command(0x00); + ssd1306_command(SSD1306_SET_SEGMENT_REMAP | 0x01); + ssd1306_command(SSD1306_COM_SCAN_DIR_DEC); + + switch (height) { + case 64: + ssd1306_command(SSD1306_SET_COM_PINS); + ssd1306_command(0x12); + ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); + ssd1306_command(0xCF); + break; + case 32: + ssd1306_command(SSD1306_SET_COM_PINS); + ssd1306_command(0x02); + ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); + ssd1306_command(0x8F); + break; + case 16: // NOTE: not tested, lacking part. + ssd1306_command(SSD1306_SET_COM_PINS); + ssd1306_command(0x2); + ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); + ssd1306_command(0xAF); + break; + } + + ssd1306_command(SSD1306_SET_PRECHARGE_PERIOD); + ssd1306_command(0xF1); + + ssd1306_command(SSD1306_SET_VCOM_DESELECT); + ssd1306_command(0x40); + + ssd1306_command(SSD1306_DISPLAY_ALL_ON_RESUME); + ssd1306_command(SSD1306_NORMAL_DISPLAY); + ssd1306_command(SSD1306_DEACTIVATE_SCROLL); + ssd1306_command(SSD1306_DISPLAY_ON); + + i2c.end_transaction(); + + return 0; + } + + void setFont(const uint8_t *f) { font = (uint8_t *)f; } + + void display() { + i2c.begin_transaction(SSD1306_COMMAND_ADDRESS); + ssd1306_command(SSD1306_SET_PAGE_ADDR); + ssd1306_command(0x00); // Page start address (0 = reset) + switch (height) { + case 64: + ssd1306_command(7); + break; + case 32: + ssd1306_command(3); + break; + case 16: + ssd1306_command(1); + break; + } + + ssd1306_command(SSD1306_SET_COLUMN_ADDR); + ssd1306_command(0x00); // Column start address (0 = reset) ssd1306_command(width - 1); // Column end address (127 = reset) - i2c.end_transaction(); + i2c.end_transaction(); - i2c.begin_transaction(SSD1306_DATA_CONTINUE_ADDRESS); + i2c.begin_transaction(SSD1306_DATA_CONTINUE_ADDRESS); - int b; - for (b = 0; b < 1024; b++) { - ssd1306_data(frame[b]); - } + int b; + for (b = 0; b < 1024; b++) { + ssd1306_data(frame[b]); + } - i2c.end_transaction(); - } + i2c.end_transaction(); + } - void clear() { - clear_buffer(); - display(); - } + void clear() { + clear_buffer(); + display(); + } - void setBrightness(uint8_t brightness) { - ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); - ssd1306_command(brightness); - } + void setBrightness(uint8_t brightness) { + ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); + ssd1306_command(brightness); + } - void displayOn() { ssd1306_command(SSD1306_DISPLAY_ON); } + void displayOn() { ssd1306_command(SSD1306_DISPLAY_ON); } - void displayOff() { ssd1306_command(SSD1306_DISPLAY_OFF); } + void displayOff() { ssd1306_command(SSD1306_DISPLAY_OFF); } - void setColor(uint8_t color) { this->color = color; } + void setColor(uint8_t color) { this->color = color; } - void drawPixel(uint8_t x, uint8_t y) { - if (x >= 128 || y >= 64) - return; + void drawPixel(uint8_t x, uint8_t y) { + if (x >= 128 || y >= 64) + return; - if (color == WHITE) { - frame[x + (y / 8) * 128] |= 1 << (y % 8); - } else { - frame[x + (y / 8) * 128] &= ~(1 << (y % 8)); - } - } + if (color == WHITE) { + frame[x + (y / 8) * 128] |= 1 << (y % 8); + } else { + frame[x + (y / 8) * 128] &= ~(1 << (y % 8)); + } + } - void fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h) { - for (int _x = x; _x < x + w; _x++) { - for (int _y = y; _y < y + h; _y++) { - drawPixel(_x, _y); - } - } - } + void fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h) { + for (int _x = x; _x < x + w; _x++) { + for (int _y = y; _y < y + h; _y++) { + drawPixel(_x, _y); + } + } + } - void clear(int start, int end) { - setColor(BLACK); - fillRect(0, (start + 1) * fontHeight, 128, (end - start + 1) * fontHeight); - setColor(WHITE); - } + void clear(int start, int end) { + setColor(BLACK); + fillRect(0, (start + 1) * fontHeight, 128, (end - start + 1) * fontHeight); + setColor(WHITE); + } - void print(const char *s) { write(s); } + void print(const char *s) { write(s); } - void print(char s) { write(s); } + void print(char s) { write(s); } - void print(int i) { - char buf[100]; - snprintf(buf, 100, "%d", i); - print((const char *)buf); - } + void print(int i) { + char buf[100]; + snprintf(buf, 100, "%d", i); + print((const char *)buf); + } - void print(unsigned int i) { - char buf[100]; - snprintf(buf, 100, "%u", i); - print((const char *)buf); - } - void print(float f) { - char buf[100]; - snprintf(buf, 100, "%f", f); - print((const char *)buf); - } + void print(unsigned int i) { + char buf[100]; + snprintf(buf, 100, "%u", i); + print((const char *)buf); + } + void print(float f) { + char buf[100]; + snprintf(buf, 100, "%f", f); + print((const char *)buf); + } #define DEC 10 #define HEX 16 #define OCT 8 #define BIN 2 - void print(int i, int base) { - char buf[100]; - switch (base) { - case DEC: - snprintf(buf, 100, "%d", i); - break; - case HEX: - snprintf(buf, 100, "%x", i); - break; - case OCT: - snprintf(buf, 100, "%o", i); - break; - case BIN: - snprintf(buf, 100, "%b", i); - break; - default: - snprintf(buf, 100, "%d", i); - break; - } - print((const char *)buf); - } - - uint8_t type() { return LCD_I2C; } - void noBlink() { /*no support*/ } - void blink() { /*no support*/ } - void setCursor(uint8_t col, int8_t row) { - /* assume 4 lines, the middle two lines - are row 0 and 1 */ - cy = (row + 1) * fontHeight; - cx = col * fontWidth; - } - void noBacklight() { /*no support*/ } - void backlight() { /*no support*/ } - void drawXbm(int x, int y, int w, int h, const char *xbm) { - int xbmWidth = (w + 7) / 8; - uint8_t data = 0; - - for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - if (j & 7) { - data >>= 1; - } else { - data = xbm[(i * xbmWidth) + (j / 8)]; - } - - if (data & 0x01) { - drawPixel(x + j, y + i); - } - } - } - } - - void drawXbm(int x, int y, int w, int h, const uint8_t *data) { - drawXbm(x, y, w, h, (const char *)data); - } - - void fillCircle(int x0, int y0, int r) { - for (int y = -r; y <= r; y++) { - for (int x = -r; x <= r; x++) { - if (x * x + y * y <= r * r) { - drawPixel(x0 + x, y0 + y); - } - } - } - } - - void drawChar(int x, int y, char c) { - uint8_t textHeight = font[HEIGHT_POS]; - uint8_t firstChar = font[FIRST_CHAR_POS]; - uint8_t numChars = font[CHAR_NUM_POS]; - uint16_t sizeOfJumpTable = numChars * JUMPTABLE_BYTES; - - if (c < firstChar || c >= firstChar + numChars) - return; - - // 4 Bytes per char code - uint8_t charCode = c - firstChar; - - uint8_t msbJumpToChar = - font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES]; // MSB \ JumpAddress - uint8_t lsbJumpToChar = font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES + - JUMPTABLE_LSB]; // LSB / - uint8_t charByteSize = font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES + - JUMPTABLE_SIZE]; // Size - uint8_t currentCharWidth = - font[JUMPTABLE_START + (c - firstChar) * JUMPTABLE_BYTES + - JUMPTABLE_WIDTH]; // Width - - // Test if the char is drawable - if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { - // Get the position of the char data - uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + - ((msbJumpToChar << 8) + lsbJumpToChar); - int _y = y; - int _x = x; - - setColor(WHITE); - - for (int b = 0; b < charByteSize; b++) { - for (int i = 0; i < 8; i++) { - if (font[charDataPosition + b] & (1 << i)) { - drawPixel(_x, _y); - } - - _y++; - if (_y >= y + textHeight) { - _y = y; - _x++; - break; - } - } - } - } - } - - void drawString(int x, int y, const char *text) { - int _x = x; - int _y = y; - - while (*text) { - if (*text == '\n') { - _x = x; - _y += fontHeight; - } else { - drawChar(_x, _y, *text); - _x += fontWidth; - } - - text++; - } - } - - size_t write(uint8_t c) { - setColor(BLACK); - fillRect(cx, cy, fontWidth, fontHeight); - setColor(WHITE); - char cc[2] = {(char)c, 0}; - - if (c < NUM_CUSTOM_ICONS && custom_chars[c] != 0) { - drawXbm(cx, cy, fontWidth, fontHeight, (const char *)custom_chars[c]); - } else { - drawString(cx, cy, cc); - } - cx += fontWidth; - if (auto_display) - display(); // todo: not very efficient - return 1; - } - - uint8_t write(const char *s) { - uint8_t nc = strlen(s); - bool temp_auto_display = auto_display; - auto_display = false; - setColor(BLACK); - fillRect(cx, cy, fontWidth * nc, fontHeight); - setColor(WHITE); - drawString(cx, cy, s); - auto_display = temp_auto_display; - cx += fontWidth * nc; - if (auto_display) - display(); // todo: not very efficient - return nc; - } - - void createChar(uint8_t idx, const char *ptr) { - if (idx >= 0 && idx < NUM_CUSTOM_ICONS) - custom_chars[idx] = ptr; - } - - void createChar(unsigned char idx, const unsigned char *ptr) { - createChar(idx, (const char *)ptr); - } - - void setAutoDisplay(bool v) { auto_display = v; } + void print(int i, int base) { + char buf[100]; + switch (base) { + case DEC: + snprintf(buf, 100, "%d", i); + break; + case HEX: + snprintf(buf, 100, "%x", i); + break; + case OCT: + snprintf(buf, 100, "%o", i); + break; + case BIN: + snprintf(buf, 100, "%b", i); + break; + default: + snprintf(buf, 100, "%d", i); + break; + } + print((const char *)buf); + } + + uint8_t type() { return LCD_I2C; } + void noBlink() { /*no support*/ } + void blink() { /*no support*/ } + void setCursor(uint8_t col, int8_t row) { + /* assume 4 lines, the middle two lines + are row 0 and 1 */ + cy = (row + 1) * fontHeight; + cx = col * fontWidth; + } + void noBacklight() { /*no support*/ } + void backlight() { /*no support*/ } + void drawXbm(int x, int y, int w, int h, const char *xbm) { + int xbmWidth = (w + 7) / 8; + uint8_t data = 0; + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + if (j & 7) { + data >>= 1; + } else { + data = xbm[(i * xbmWidth) + (j / 8)]; + } + + if (data & 0x01) { + drawPixel(x + j, y + i); + } + } + } + } + + void drawXbm(int x, int y, int w, int h, const uint8_t *data) { + drawXbm(x, y, w, h, (const char *)data); + } + + void fillCircle(int x0, int y0, int r) { + for (int y = -r; y <= r; y++) { + for (int x = -r; x <= r; x++) { + if (x * x + y * y <= r * r) { + drawPixel(x0 + x, y0 + y); + } + } + } + } + + void drawChar(int x, int y, char c) { + uint8_t textHeight = font[HEIGHT_POS]; + uint8_t firstChar = font[FIRST_CHAR_POS]; + uint8_t numChars = font[CHAR_NUM_POS]; + uint16_t sizeOfJumpTable = numChars * JUMPTABLE_BYTES; + + if (c < firstChar || c >= firstChar + numChars) + return; + + // 4 Bytes per char code + uint8_t charCode = c - firstChar; + + uint8_t msbJumpToChar = + font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES]; // MSB \ JumpAddress + uint8_t lsbJumpToChar = font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES + + JUMPTABLE_LSB]; // LSB / + uint8_t charByteSize = font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES + + JUMPTABLE_SIZE]; // Size + uint8_t currentCharWidth = + font[JUMPTABLE_START + (c - firstChar) * JUMPTABLE_BYTES + + JUMPTABLE_WIDTH]; // Width + + // Test if the char is drawable + if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { + // Get the position of the char data + uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + + ((msbJumpToChar << 8) + lsbJumpToChar); + int _y = y; + int _x = x; + + setColor(WHITE); + + for (int b = 0; b < charByteSize; b++) { + for (int i = 0; i < 8; i++) { + if (font[charDataPosition + b] & (1 << i)) { + drawPixel(_x, _y); + } + + _y++; + if (_y >= y + textHeight) { + _y = y; + _x++; + break; + } + } + } + } + } + + void drawString(int x, int y, const char *text) { + int _x = x; + int _y = y; + + while (*text) { + if (*text == '\n') { + _x = x; + _y += fontHeight; + } else { + drawChar(_x, _y, *text); + _x += fontWidth; + } + + text++; + } + } + + size_t write(uint8_t c) { + setColor(BLACK); + fillRect(cx, cy, fontWidth, fontHeight); + setColor(WHITE); + char cc[2] = {(char)c, 0}; + + if (c < NUM_CUSTOM_ICONS && custom_chars[c] != 0) { + drawXbm(cx, cy, fontWidth, fontHeight, (const char *)custom_chars[c]); + } else { + drawString(cx, cy, cc); + } + cx += fontWidth; + if (auto_display) + display(); // todo: not very efficient + return 1; + } + + uint8_t write(const char *s) { + uint8_t nc = strlen(s); + bool temp_auto_display = auto_display; + auto_display = false; + setColor(BLACK); + fillRect(cx, cy, fontWidth * nc, fontHeight); + setColor(WHITE); + drawString(cx, cy, s); + auto_display = temp_auto_display; + cx += fontWidth * nc; + if (auto_display) + display(); // todo: not very efficient + return nc; + } + + void createChar(uint8_t idx, const char *ptr) { + if (idx >= 0 && idx < NUM_CUSTOM_ICONS) + custom_chars[idx] = ptr; + } + + void createChar(unsigned char idx, const unsigned char *ptr) { + createChar(idx, (const char *)ptr); + } + + void setAutoDisplay(bool v) { auto_display = v; } private: - int file = -1; - bool auto_display = false; - uint8_t cx, cy = 0; - uint8_t fontWidth, fontHeight; - const char *custom_chars[NUM_CUSTOM_ICONS]; - uint8_t frame[1024]; - int i2cd; - bool color; - uint8_t *font; + int file = -1; + bool auto_display = false; + uint8_t cx, cy = 0; + uint8_t fontWidth, fontHeight; + const char *custom_chars[NUM_CUSTOM_ICONS]; + uint8_t frame[1024]; + int i2cd; + bool color; + uint8_t *font; - I2CDevice i2c; - unsigned char _addr; + I2CDevice i2c; + unsigned char _addr; - unsigned char height; - unsigned char width; + unsigned char height; + unsigned char width; - void clear_buffer() { memset(frame, 0x00, sizeof(frame)); } + void clear_buffer() { memset(frame, 0x00, sizeof(frame)); } - int ssd1306_command(unsigned char command) { return i2c.send(0x00, command); } + int ssd1306_command(unsigned char command) { return i2c.send(0x00, command); } - int ssd1306_data(unsigned char value) { return i2c.send(0x40, value); } + int ssd1306_data(unsigned char value) { return i2c.send(0x40, value); } }; #endif - -#endif // SSD1306_DISPLAY_H diff --git a/ch224.h b/ch224.h index 9dba85bb9..d741a2338 100644 --- a/ch224.h +++ b/ch224.h @@ -1,7 +1,5 @@ #pragma once -#if defined(ESP8266) - #include #include @@ -232,5 +230,3 @@ class CH224 { uint8_t _pps_apdo_count; uint8_t _avs_apdo_count; }; - -#endif \ No newline at end of file diff --git a/defines.h b/defines.h index e92b4eab2..9cfa68402 100755 --- a/defines.h +++ b/defines.h @@ -20,13 +20,11 @@ * along with this program. If not, see * . */ +#pragma once -#ifndef _DEFINES_H -#define _DEFINES_H +#define ENABLE_DEBUG // enable serial debug -//#define ENABLE_DEBUG // enable serial debug - -typedef unsigned long ulong; +typedef unsigned long ulong; // TODO: remove and replace by uint32_t for cross-platform consistency #define TMP_BUFFER_SIZE 320 // scratch buffer size @@ -35,7 +33,7 @@ typedef unsigned long ulong; // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 4 // Firmware minor version +#define OS_FW_MINOR 5 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -139,7 +137,7 @@ enum { #define LED_SLOW_BLINK 500 /** Storage / zone expander defines */ -#if defined(ARDUINO) +#if defined(ESP8266) #define MAX_EXT_BOARDS 8 // maximum number of 8-zone expanders (each 16-zone expander counts as 2) #else #define MAX_EXT_BOARDS 24 // allow more zones for linux-based firmwares @@ -172,14 +170,6 @@ enum { #define DEFAULT_LATCH_BOOST_VOLTAGE 9 // default latch boost voltage in volt #define DEFAULT_TARGET_PD_VOLTAGE 75 // default target voltage (unit: 100mV, so 75 means 7500mV ot 7.5V) -#if (defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__)) - #define OS_AVR -#else // all non-AVR platforms support OTF, EMAIL and HTTPS - #define USE_OTF - #define SUPPORT_EMAIL - #define SUPPORT_HTTPS -#endif - /* Weather Adjustment Methods */ enum { WEATHER_METHOD_MANUAL = 0, @@ -317,56 +307,7 @@ enum { #undef OS_HW_VERSION /** Hardware defines */ -#if defined(OS_AVR) // for OS 2.3 - - #define OS_HW_VERSION (OS_HW_VERSION_BASE+23) - #define PIN_FREE_LIST {2,10,12,13,14,15,18,19} // Free GPIO pins - - // hardware pins - #define PIN_BUTTON_1 31 // button 1 - #define PIN_BUTTON_2 30 // button 2 - #define PIN_BUTTON_3 29 // button 3 - #define PIN_RFTX 28 // RF data pin - #define PORT_RF PORTA - #define PINX_RF PINA3 - #define PIN_SR_LATCH 3 // shift register latch pin - #define PIN_SR_DATA 21 // shift register data pin - #define PIN_SR_CLOCK 22 // shift register clock pin - #define PIN_SR_OE 1 // shift register output enable pin - - // regular 16x2 LCD pin defines - #define PIN_LCD_RS 19 // LCD rs pin - #define PIN_LCD_EN 18 // LCD enable pin - #define PIN_LCD_D4 20 // LCD d4 pin - #define PIN_LCD_D5 21 // LCD d5 pin - #define PIN_LCD_D6 22 // LCD d6 pin - #define PIN_LCD_D7 23 // LCD d7 pin - #define PIN_LCD_BACKLIGHT 12 // LCD backlight pin - #define PIN_LCD_CONTRAST 13 // LCD contrast pin - - // DC controller pin defines - #define PIN_BOOST 20 // booster pin - #define PIN_BOOST_EN 23 // boost voltage enable pin - - #define PIN_ETHER_CS 4 // Ethernet controller chip select pin - #define PIN_SENSOR1 11 // - #define PIN_SD_CS 0 // SD card chip select pin - #define PIN_FLOWSENSOR_INT 1 // flow sensor interrupt pin (INT1) - #define PIN_EXP_SENSE 4 // expansion board sensing pin (A4) - #define PIN_CURR_SENSE 7 // current sensing pin (A7) - #define PIN_CURR_DIGITAL 24 // digital pin index for A7 - - #define ETHER_BUFFER_SIZE 2048 - - #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset - - #define pinModeExt pinMode - #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite - - #define USE_DISPLAY - #define USE_LCD -#elif defined(ESP8266) // for ESP8266 +#if defined(ESP8266) // for ESP8266 #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) #define IOEXP_PIN 0x80 // base for pins on main IO expander @@ -453,7 +394,6 @@ enum { #define V2_PIN_BOOST_SEL IOEXP_PIN+8 #define USE_DISPLAY - #define USE_SSD1306 #elif defined(OSPI) // for OSPi @@ -477,7 +417,6 @@ enum { #define SCL 0 #define USE_DISPLAY - #define USE_SSD1306 #else // for demo / simulation // use fake hardware pins @@ -500,7 +439,7 @@ enum { #if defined(ENABLE_DEBUG) /** Serial debug functions */ - #if defined(ARDUINO) + #if defined(ESP8266) #define DEBUG_BEGIN(x) {Serial.begin(x);} #define DEBUG_PRINT(x) {Serial.print(x);} #define DEBUG_PRINTLN(x) {Serial.println(x);} @@ -516,7 +455,7 @@ enum { #else - #if defined(ARDUINO) + #if defined(ESP8266) // work-around for PIN_SENSOR1 on OS3.2 and above #define DEBUG_BEGIN(x) {Serial.begin(115200); Serial.end();} #else @@ -528,8 +467,8 @@ enum { #endif -/** Re-define avr-specific (e.g. PGM) types to use standard types */ -#if !defined(ARDUINO) +/** Re-define arduino-specific (e.g. PGM) types to use standard types */ +#if !defined(ESP8266) #include #include #include @@ -579,5 +518,3 @@ enum { #define BUTTON_WAIT_HOLD 2 // wait until button hold time expires #define DISPLAY_MSG_MS 2000 // message display time (milliseconds) - -#endif // _DEFINES_H diff --git a/docs/docs/index.md b/docs/docs/index.md index f3fdf3674..a270b7331 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -72,6 +72,6 @@ The firmware compilation instructions below are for OpenSprinkler **v3.x and v2. 1. In VS Code, click `File -> Open Folder` and select the `OpenSprinkler-Firmware` folder. 2. PlatformIO will recognize the `platformio.ini` file in that folder, which contains all the libraries and settings needed to compile the firmware. 3. Click the **PlatformIO: Build** button (with the checkmark icon ✓) in the blue status bar at the bottom of the screen to build the firmware. -4. The default build environment is for OpenSprinkler v3.x (`env:os3x_esp8266`). To build for OpenSprinkler v2.3, switch to `env:os23_atmega1284p`. +4. The default build environment is for OpenSprinkler v3.x (`env:os3x_esp8266`).
diff --git a/espconnect.h b/espconnect.h index edf595c1d..e67613d79 100644 --- a/espconnect.h +++ b/espconnect.h @@ -17,11 +17,7 @@ * along with this program. If not, see * . */ - -#if defined(ESP8266) - -#ifndef _ESP_CONNECT_H -#define _ESP_CONNECT_H +#pragma once #include #include @@ -34,6 +30,3 @@ String scan_network(); void start_network_ap(const char *ssid, const char *pass); void start_network_sta(const char *ssid, const char *pass, int32_t channel=0, const unsigned char *mac=NULL); void start_network_sta_with_ap(const char *ssid, const char *pass, int32_t channel=0, const unsigned char *mac=NULL); -#endif - -#endif diff --git a/font.h b/font.h index dc1ad7f10..0a2b37320 100644 --- a/font.h +++ b/font.h @@ -1,7 +1,6 @@ // Created by http://oleddisplay.squix.ch/ Consider a donation // In case of problems make sure that you are using the font file with the correct version! -#ifndef FONT_H -#define FONT_H +#pragma once const unsigned char Monospaced_plain_13[] PROGMEM = { 0x08, // Width: 8 @@ -459,5 +458,3 @@ const unsigned char Monospaced_plain_13[] PROGMEM = { 0x00,0x00,0x00,0xF8,0xFF,0x00,0x80,0x18,0x00,0x40,0x10,0x00,0x40,0x10,0x00,0xC0,0x18,0x00,0x80,0x0F, // 254 0x00,0x00,0x00,0xC0,0x80,0x00,0x08,0x87,0x00,0x00,0xFC,0x00,0x00,0x38,0x00,0x08,0x07,0x00,0xC0,0x01 // 255 }; - -#endif \ No newline at end of file diff --git a/gpio.cpp b/gpio.cpp index 0b0df7514..8656d24b0 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -25,8 +25,6 @@ #if defined(ARDUINO) -#if defined(ESP8266) - #include #include "defines.h" @@ -173,7 +171,6 @@ unsigned char digitalReadExt(unsigned char pin) { return digitalRead(pin); } } -#endif #elif defined(OSPI) diff --git a/gpio.h b/gpio.h index d9b028b84..e9fe40b6a 100644 --- a/gpio.h +++ b/gpio.h @@ -21,13 +21,10 @@ * . */ -#ifndef GPIO_H -#define GPIO_H +#pragma once #if defined(ARDUINO) -#if defined(ESP8266) - #include "Arduino.h" // PCA9555 register defines @@ -113,8 +110,6 @@ void pinModeExt(unsigned char pin, unsigned char mode); void digitalWriteExt(unsigned char pin, unsigned char value); unsigned char digitalReadExt(unsigned char pin); -#endif // ESP8266 - #else #include @@ -145,4 +140,3 @@ void attachInterrupt(int pin, const char* mode, void (*isr)(void)); #endif -#endif // GPIO_H diff --git a/i2cd.h b/i2cd.h index 53d46f9a0..25ed37a85 100644 --- a/i2cd.h +++ b/i2cd.h @@ -1,5 +1,6 @@ -#ifndef I2CD_H -#define I2CD_H +#pragma once + +#if !defined(ARDUINO) #include #include @@ -13,86 +14,86 @@ extern "C" { class I2CDevice { public: - I2CDevice() {} - - int begin(const char *bus, unsigned char addr) { - _file = open(bus, O_RDWR); - if (_file < 0) { - return _file; - } - - return ioctl(_file, I2C_SLAVE, addr); - } - - int begin(unsigned char addr) { return begin(getDefaultBus(), addr); } - - int begin_transaction(unsigned char id) { - if (transaction) { - return -1; - } else { - transaction_id = id; - transaction = true; - memset(transaction_buffer, 0x00, sizeof(transaction_buffer)); - transaction_buffer_length = 0; - return 0; - } - } - - int end_transaction() { - if (transaction) { - transaction = false; - return send_transaction(); - } else { - return -1; - } - } - - int send(unsigned char reg, unsigned char data) { - if (transaction) { - if (reg != transaction_id) { - return -1; - } - - int res = 0; - if (transaction_buffer_length >= sizeof(transaction_buffer)) { - res = send_transaction(); - transaction_buffer_length = 0; - } - - transaction_buffer[transaction_buffer_length] = data; - transaction_buffer_length++; - return res; - } else { - return i2c_smbus_write_byte_data(_file, reg, data); - } - } + I2CDevice() {} + + int begin(const char *bus, unsigned char addr) { + _file = open(bus, O_RDWR); + if (_file < 0) { + return _file; + } + + return ioctl(_file, I2C_SLAVE, addr); + } + + int begin(unsigned char addr) { return begin(getDefaultBus(), addr); } + + int begin_transaction(unsigned char id) { + if (transaction) { + return -1; + } else { + transaction_id = id; + transaction = true; + memset(transaction_buffer, 0x00, sizeof(transaction_buffer)); + transaction_buffer_length = 0; + return 0; + } + } + + int end_transaction() { + if (transaction) { + transaction = false; + return send_transaction(); + } else { + return -1; + } + } + + int send(unsigned char reg, unsigned char data) { + if (transaction) { + if (reg != transaction_id) { + return -1; + } + + int res = 0; + if (transaction_buffer_length >= sizeof(transaction_buffer)) { + res = send_transaction(); + transaction_buffer_length = 0; + } + + transaction_buffer[transaction_buffer_length] = data; + transaction_buffer_length++; + return res; + } else { + return i2c_smbus_write_byte_data(_file, reg, data); + } + } private: - int _file = -1; - bool transaction = false; - unsigned char transaction_id = 0; - unsigned char transaction_buffer[32]; - unsigned char transaction_buffer_length = 0; - - const char *getDefaultBus() { - switch (get_board_type()) { - case BoardType::RaspberryPi_bcm2712: - case BoardType::RaspberryPi_bcm2711: - case BoardType::RaspberryPi_bcm2837: - case BoardType::RaspberryPi_bcm2836: - case BoardType::RaspberryPi_bcm2835: - return "/dev/i2c-1"; - case BoardType::Unknown: - case BoardType::RaspberryPi_Unknown: - default: - return "/dev/i2c-0"; - } - } - - int send_transaction() { - return i2c_smbus_write_i2c_block_data( - _file, transaction_id, transaction_buffer_length, transaction_buffer); - } + int _file = -1; + bool transaction = false; + unsigned char transaction_id = 0; + unsigned char transaction_buffer[32]; + unsigned char transaction_buffer_length = 0; + + const char *getDefaultBus() { + switch (get_board_type()) { + case BoardType::RaspberryPi_bcm2712: + case BoardType::RaspberryPi_bcm2711: + case BoardType::RaspberryPi_bcm2837: + case BoardType::RaspberryPi_bcm2836: + case BoardType::RaspberryPi_bcm2835: + return "/dev/i2c-1"; + case BoardType::Unknown: + case BoardType::RaspberryPi_Unknown: + default: + return "/dev/i2c-0"; + } + } + + int send_transaction() { + return i2c_smbus_write_i2c_block_data( + _file, transaction_id, transaction_buffer_length, transaction_buffer); + } }; -#endif // I2CD_H \ No newline at end of file +#endif \ No newline at end of file diff --git a/images.h b/images.h index adaf517e4..02c3ed259 100644 --- a/images.h +++ b/images.h @@ -1,5 +1,4 @@ -#ifndef IMAGES_H -#define IMAGES_H +#pragma once enum { ICON_ETHER_CONNECTED = 0, @@ -13,7 +12,6 @@ enum { NUM_CUSTOM_ICONS }; -#if defined(ESP8266) || defined(PIN_SENSOR2) enum { LCD_CURSOR_REMOTEXT = 11, LCD_CURSOR_RAINDELAY,// 12 @@ -21,22 +19,8 @@ enum { LCD_CURSOR_SENSOR2, // 14 LCD_CURSOR_NETWORK // 15 }; -#else -enum { - LCD_CURSOR_SENSOR2 = 11, - LCD_CURSOR_REMOTEXT, // 12 - LCD_CURSOR_RAINDELAY,// 13 - LCD_CURSOR_SENSOR1, // 14 - LCD_CURSOR_NETWORK // 15 -}; -#endif - -#if defined(USE_SSD1306) - -#if not defined(ARDUINO) -#define PROGMEM -#endif +#if defined(USE_DISPLAY) #define WiFi_Logo_width 60 #define WiFi_Logo_height 36 @@ -141,102 +125,4 @@ const unsigned char _iconimage_pswitch[] PROGMEM = { */ -#elif defined(USE_LCD) - -const char _iconimage_connected[] PROGMEM = { - B00000, - B00000, - B00000, - B00001, - B00001, - B00101, - B00101, - B10101 -}; - -const char _iconimage_disconnected[] PROGMEM = { - B00000, - B10100, - B01000, - B10101, - B00001, - B00101, - B00101, - B10101 -}; - -const char _iconimage_remotext[] PROGMEM = { - B00000, - B00000, - B00000, - B10001, - B01011, - B00101, - B01001, - B11110 -}; - -const char _iconimage_raindelay[] PROGMEM = { - B00000, - B01110, - B10101, - B10101, - B10111, - B10001, - B10001, - B01110 -}; - -const char _iconimage_rain[] PROGMEM = { - B00000, - B00000, - B00110, - B01001, - B11111, - B00000, - B10101, - B10101 -}; - -const char _iconimage_soil[] PROGMEM = { - B00100, - B00100, - B01010, - B01010, - B10001, - B10001, - B10001, - B01110 -}; - -/* -const unsigned char _iconimage_flow[] PROGMEM = { - B00000, - B00000, - B00000, - B11010, - B10010, - B11010, - B10011, - B00000 -}; - -const unsigned char _iconimage_pswitch[] PROGMEM = { - B00000, - B11000, - B10100, - B11000, - B10010, - B10110, - B00010, - B00111 -}; - -// todo - -*/ - #endif - - -#endif \ No newline at end of file diff --git a/main.cpp b/main.cpp index bcc87495a..8f66ce83f 100644 --- a/main.cpp +++ b/main.cpp @@ -32,39 +32,25 @@ #include "main.h" #include "notifier.h" -#if defined(ARDUINO) -#include -#endif - -#if defined(ARDUINO) - #if defined(ESP8266) - ESP8266WebServer *update_server = NULL; - DNSServer *dns = NULL; - ENC28J60lwIP enc28j60(PIN_ETHER_CS); // ENC28J60 lwip for wired Ether - Wiznet5500lwIP w5500(PIN_ETHER_CS); // W5500 lwip for wired Ether - lwipEth eth; - bool useEth = false; // tracks whether we are using WiFi or wired Ether connection - #else - EthernetServer *m_server = NULL; - EthernetClient *m_client = NULL; - SdFat sd; // SD card object - bool useEth = true; - #endif +#if defined(ESP8266) + #include + ESP8266WebServer *update_server = NULL; + DNSServer *dns = NULL; + ENC28J60lwIP enc28j60(PIN_ETHER_CS); // ENC28J60 lwip for wired Ether + Wiznet5500lwIP w5500(PIN_ETHER_CS); // W5500 lwip for wired Ether + lwipEth eth; + bool useEth = false; // tracks whether we are using WiFi or wired Ether connection unsigned long getNtpTime(); #else // header and defs for RPI/Linux bool useEth = false; #endif -#if defined(USE_OTF) - OTF::OpenThingsFramework *otf = NULL; -#endif +OTF::OpenThingsFramework *otf = NULL; -#if defined(USE_SSD1306) - #if defined(ESP8266) - static uint16_t led_blink_ms = LED_FAST_BLINK; - #else - static uint16_t led_blink_ms = 0; - #endif +#if defined(ESP8266) +static uint16_t led_blink_ms = LED_FAST_BLINK; +#else +static uint16_t led_blink_ms = 0; #endif #define STRINGIFY(x) #x @@ -213,7 +199,6 @@ void ui_state_machine() { if(millis() - last_usm <= UI_STATE_MACHINE_INTERVAL) { return; } last_usm = millis(); -#if defined(USE_SSD1306) // process screen led static ulong led_toggle_prev = 0; if(led_blink_ms) { @@ -223,7 +208,6 @@ void ui_state_machine() { led_toggle_prev = tm; } } -#endif if (!os.button_timeout) { os.lcd_set_brightness(0); @@ -249,86 +233,68 @@ void ui_state_machine() { if(!ui_confirm(PSTR("Start 2s test?"))) {ui_state = UI_STATE_DEFAULT; break;} manual_start_program(255, 0, QUEUE_OPTION_REPLACE); } else if (digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B1, display gateway IP - #if defined(USE_SSD1306) - os.lcd.setAutoDisplay(false); - #endif + os.lcd.setAutoDisplay(false); os.lcd.clear(0, 1); os.lcd.setCursor(0, 0); - #if defined(ARDUINO) #if defined(ESP8266) - if (useEth) { os.lcd.print(eth.gatewayIP()); } - else { os.lcd.print(WiFi.gatewayIP()); } - #else - { os.lcd.print(Ethernet.gatewayIP()); } - #endif + if (useEth) { os.lcd.print(eth.gatewayIP()); } + else { os.lcd.print(WiFi.gatewayIP()); } #else - route_t route = get_route(); - char str[INET_ADDRSTRLEN]; + route_t route = get_route(); + char str[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &(route.gateway), str, INET_ADDRSTRLEN); - os.lcd.print(str); + inet_ntop(AF_INET, &(route.gateway), str, INET_ADDRSTRLEN); + os.lcd.print(str); #endif os.lcd.setCursor(0, 1); os.lcd_print_pgm(PSTR("(gwip)")); ui_state = UI_STATE_DISP_IP; - #if defined(USE_SSD1306) - os.lcd.display(); - os.lcd.setAutoDisplay(true); - #endif + os.lcd.display(); + os.lcd.setAutoDisplay(true); } else { // if no other button is clicked, stop all zones if(!ui_confirm(PSTR("Stop all zones?"))) {ui_state = UI_STATE_DEFAULT; break;} reset_all_stations(); } } else { // clicking B1: display device IP and port - #if defined(USE_SSD1306) - os.lcd.setAutoDisplay(false); - #endif + os.lcd.setAutoDisplay(false); os.lcd.clear(0, 1); os.lcd.setCursor(0, 0); - #if defined(ARDUINO) #if defined(ESP8266) - if (useEth) { os.lcd.print(eth.localIP()); } - else { os.lcd.print(WiFi.localIP()); } - #else - { os.lcd.print(Ethernet.localIP()); } - #endif + if (useEth) { os.lcd.print(eth.localIP()); } + else { os.lcd.print(WiFi.localIP()); } #else - route_t route = get_route(); - char str[INET_ADDRSTRLEN]; - in_addr_t ip = get_ip_address(route.iface); + route_t route = get_route(); + char str[INET_ADDRSTRLEN]; + in_addr_t ip = get_ip_address(route.iface); - inet_ntop(AF_INET, &ip, str, INET_ADDRSTRLEN); - os.lcd.print(str); + inet_ntop(AF_INET, &ip, str, INET_ADDRSTRLEN); + os.lcd.print(str); #endif os.lcd.setCursor(0, 1); os.lcd_print_pgm(PSTR(":")); uint16_t httpport = (uint16_t)(os.iopts[IOPT_HTTPPORT_1]<<8) + (uint16_t)os.iopts[IOPT_HTTPPORT_0]; os.lcd.print(httpport); os.lcd_print_pgm(PSTR(" (ip:port)")); - #if defined(USE_OTF) - os.lcd.setCursor(0, 2); - os.lcd_print_pgm(PSTR("OTC:")); - switch(otf->getCloudStatus()) { - case OTF::NOT_ENABLED: - os.lcd_print_pgm(PSTR(" not enabled")); - break; - case OTF::UNABLE_TO_CONNECT: - os.lcd_print_pgm(PSTR("connecting..")); - break; - case OTF::DISCONNECTED: - os.lcd_print_pgm(PSTR("disconnected")); - break; - case OTF::CONNECTED: - os.lcd_print_pgm(PSTR(" Connected")); - break; - } - #endif + os.lcd.setCursor(0, 2); + os.lcd_print_pgm(PSTR("OTC:")); + switch(otf->getCloudStatus()) { + case OTF::NOT_ENABLED: + os.lcd_print_pgm(PSTR(" not enabled")); + break; + case OTF::UNABLE_TO_CONNECT: + os.lcd_print_pgm(PSTR("connecting..")); + break; + case OTF::DISCONNECTED: + os.lcd_print_pgm(PSTR("disconnected")); + break; + case OTF::CONNECTED: + os.lcd_print_pgm(PSTR(" Connected")); + break; + } ui_state = UI_STATE_DISP_IP; - #if defined(USE_SSD1306) - os.lcd.display(); - os.lcd.setAutoDisplay(true); - #endif + os.lcd.display(); + os.lcd.setAutoDisplay(true); } break; case BUTTON_2: @@ -415,15 +381,11 @@ void ui_state_machine() { // ====================== // Setup Function // ====================== -#if defined(ARDUINO) +#if defined(ESP8266) void do_setup() { /* Clear WDT reset flag. */ -#if defined(ESP8266) WiFi.persistent(false); led_blink_ms = LED_FAST_BLINK; -#else - MCUSR &= ~(1< 15) { - // reset after 120 seconds of timeout - sysReset(); - } -} -#endif - #else void initialize_otf(); @@ -550,7 +484,7 @@ void handle_web_request(char *p); ulong currpoll_timeout = 0; void overcurrent_monitor() { -#if defined(ARDUINO) +#if defined(ESP8266) // If a zone is turning on, do immediate overcurrent monitoring here for ~50ms if (curr_alert_sid) { int16_t imax = os.get_imax(); @@ -590,7 +524,7 @@ void do_loop() } } -#if defined(ARDUINO) +#if defined(ESP8266) { ulong tn = millis(); if((long)(tn-currpoll_timeout) > 0) { // overflow proof timeout @@ -619,8 +553,7 @@ void do_loop() time_os_t curr_time = os.now_tz(); // ====== Process Ethernet packets ====== -#if defined(ARDUINO) // Process Ethernet packets for Arduino - #if defined(ESP8266) +#if defined(ESP8266) // Process Ethernet packets for Arduino static ulong connecting_timeout; switch(os.state) { case OS_STATE_INITIAL: @@ -717,45 +650,6 @@ void do_loop() break; } - #else // AVR - - static unsigned long dhcp_timeout = 0; - if(curr_time > dhcp_timeout) { - Ethernet.maintain(); - dhcp_timeout = curr_time + DHCP_CHECKLEASE_INTERVAL; - } - EthernetClient client = m_server->available(); - if (client) { - ulong cli_timeout = now() + CLIENT_READ_TIMEOUT; - size_t size = 0; - while(client.connected() && now() < cli_timeout) { - size = client.available(); // wait till we have client data available - if(size>0) break; - } - if(size>0) { - size_t len = 0; - while (client.available() && now()0) { - m_client = &client; - ether_buffer[len] = 0; // properly end the buffer - handle_web_request(ether_buffer); - m_client = NULL; - } - } - client.stop(); - } - - wdt_reset(); // reset watchdog timer - wdt_timeout = 0; - - #endif - ui_state_machine(); #else // Process Ethernet packets for RPI/LINUX @@ -1181,7 +1075,7 @@ void do_loop() } } - #if !defined(ARDUINO) + #if !defined(ESP8266) delay(1); // For OSPI/LINUX, sleep 1 ms to minimize CPU usage #endif } @@ -1230,11 +1124,9 @@ void check_weather() { } } else if (!os.checkwt_lasttime || (ntz > os.checkwt_lasttime + CHECK_WEATHER_TIMEOUT)) { os.checkwt_lasttime = ntz; - #if defined(ARDUINO) if (!ui_state) { os.lcd_print_line_clear_pgm(PSTR("Check Weather..."),1); } - #endif GetWeather(); } } @@ -1337,7 +1229,7 @@ void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shif if (!station_bit) { return; } } //else { return; } - #if defined(ARDUINO) + #if defined(ESP8266) int16_t current = (int16_t)os.read_current(true); // use ema value int16_t imin = os.get_imin(); // if current is less than imin threshold and hardware type is AC or DC @@ -1734,7 +1626,7 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo // ================================ // ====== LOGGING FUNCTIONS ======= // ================================ -#if defined(ARDUINO) +#if defined(ESP8266) char LOG_PREFIX[] = "/logs/"; #else char LOG_PREFIX[] = "./logs/"; @@ -1744,11 +1636,6 @@ char LOG_PREFIX[] = "./logs/"; * Log files will be named /logs/xxxxx.txt */ void make_logfile_name(char *name) { -#if defined(ARDUINO) - #if !defined(ESP8266) - sd.chdir("/"); - #endif -#endif strcpy(tmp_buffer+TMP_BUFFER_SIZE-10, name); // hack: we do this because name is from tmp_buffer too strcpy(tmp_buffer, LOG_PREFIX); strcat(tmp_buffer, tmp_buffer+TMP_BUFFER_SIZE-10); @@ -1780,9 +1667,7 @@ void write_log(unsigned char type, time_os_t curr_time) { // Step 1: open file if exists, or create new otherwise, // and move file pointer to the end -#if defined(ARDUINO) // prepare log folder for Arduino - - #if defined(ESP8266) +#if defined(ESP8266) // prepare log folder for Arduino File file = LittleFS.open(tmp_buffer, "r+"); if(!file) { FSInfo fs_info; @@ -1796,22 +1681,6 @@ void write_log(unsigned char type, time_os_t curr_time) { if(!file) return; } file.seek(0, SeekEnd); - #else - sd.chdir("/"); - if (sd.chdir(LOG_PREFIX) == false) { - // create dir if it doesn't exist yet - if (sd.mkdir(LOG_PREFIX) == false) { - return; - } - } - SdFile file; - int ret = file.open(tmp_buffer, O_CREAT | O_WRITE ); - file.seekEnd(); - if(!ret) { - return; - } - #endif - #else // prepare log folder for RPI/LINUX struct stat st; if(stat(get_filename_fullpath(LOG_PREFIX), &st)) { @@ -1880,7 +1749,7 @@ void write_log(unsigned char type, time_os_t curr_time) { if((os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) && (type==LOGDATA_STATION)) { // RAH implementation of flow sensor strcat_P(tmp_buffer, PSTR(",")); - #if defined(ARDUINO) + #if defined(ESP8266) dtostrf(flow_last_gpm,5,2,tmp_buffer+strlen(tmp_buffer)); #else snprintf(tmp_buffer+strlen(tmp_buffer), TMP_BUFFER_SIZE, "%5.2f", flow_last_gpm); @@ -1888,12 +1757,8 @@ void write_log(unsigned char type, time_os_t curr_time) { } strcat_P(tmp_buffer, PSTR("]\r\n")); -#if defined(ARDUINO) - #if defined(ESP8266) +#if defined(ESP8266) file.write((const uint8_t*)tmp_buffer, strlen(tmp_buffer)); - #else - file.write(tmp_buffer); - #endif file.close(); #else fwrite(tmp_buffer, 1, strlen(tmp_buffer), file); @@ -1929,9 +1794,7 @@ bool delete_log_oldest() { */ void delete_log(char *name) { if (!os.iopts[IOPT_ENABLE_LOGGING]) return; -#if defined(ARDUINO) - - #if defined(ESP8266) +#if defined(ESP8266) if (strncmp(name, "all", 3) == 0) { // delete all log files Dir dir = LittleFS.openDir(LOG_PREFIX); @@ -1944,23 +1807,6 @@ void delete_log(char *name) { if(!LittleFS.exists(tmp_buffer)) return; LittleFS.remove(tmp_buffer); } - #else - if (strncmp(name, "all", 3) == 0) { - // delete the log folder - SdFile file; - - if (sd.chdir(LOG_PREFIX)) { - // delete the whole log folder - sd.vwd()->rmRfStar(); - } - } else { - // delete a single log file - make_logfile_name(name); - if (!sd.exists(tmp_buffer)) return; - sd.remove(tmp_buffer); - } - #endif - #else // delete_log implementation for RPI/LINUX if (strncmp(name, "all", 3) == 0) { // delete the log folder @@ -1979,57 +1825,13 @@ void delete_log(char *name) { * If not, it re-initializes Ethernet controller. */ static void check_network() { -#if defined(OS_AVR) - // do not perform network checking if the controller has just started, or if a program is running - if (os.status.program_busy) {return;} - - // check network condition periodically - if (os.status.req_network) { - os.status.req_network = 0; - // change LCD icon to indicate it's checking network - if (!ui_state) { - os.lcd.setCursor(LCD_CURSOR_NETWORK, 1); - os.lcd.write('>'); - } - - - boolean failed = false; - // todo: ping gateway ip - /*ether.clientIcmpRequest(ether.gwip); - ulong start = millis(); - // wait at most PING_TIMEOUT milliseconds for ping result - do { - ether.packetLoop(ether.packetReceive()); - if (ether.packetLoopIcmpCheckReply(ether.gwip)) { - failed = false; - break; - } - } while((long)(millis() - start) < PING_TIMEOUT);*/ - if (failed) { - if(os.status.network_fails<3) os.status.network_fails++; - // clamp it to 6 - //if (os.status.network_fails > 6) os.status.network_fails = 6; - } - else os.status.network_fails=0; - // if failed more than 3 times, restart - if (os.status.network_fails==3) { - // mark for safe restart - os.nvdata.reboot_cause = REBOOT_CAUSE_NETWORK_FAIL; - os.status.safe_reboot = 1; - } else if (os.status.network_fails>2) { - // if failed more than twice, try to reconnect - if (os.start_network()) - os.status.network_fails=0; - } - } -#else + // TODO: // nothing to do for other platforms -#endif } /** Perform NTP sync */ static void perform_ntp_sync() { -#if defined(ARDUINO) +#if defined(ESP8266) // do not perform ntp if this option is disabled, or if a program is currently running if (!os.iopts[IOPT_USE_NTP] || os.status.program_busy) return; // do not perform ntp if network is not connected @@ -2061,7 +1863,7 @@ static void perform_ntp_sync() { #endif } -#if !defined(ARDUINO) // main function for RPI/LINUX +#if !defined(ESP8266) // main function for RPI/LINUX int main(int argc, char *argv[]) { // Disable buffering to work with systemctl journal setvbuf(stdout, NULL, _IOLBF, 0); diff --git a/main.h b/main.h index a7ef7ebec..9a9bd1682 100644 --- a/main.h +++ b/main.h @@ -24,8 +24,7 @@ */ -#ifndef _MAIN_H -#define _MAIN_H 1 +#pragma once void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shift=0); void turn_off_running_station_immediate(unsigned char sid, time_os_t curr_time, unsigned char shift=0); @@ -35,6 +34,4 @@ void reset_all_stations(bool running_ones_only=false); void reset_all_stations_immediate(bool running_ones_only=false); void delete_log(char *name); void write_log(unsigned char type, time_os_t curr_time); -void make_logfile_name(char *name); - -#endif // _MAIN_H +void make_logfile_name(char *name); \ No newline at end of file diff --git a/mqtt.cpp b/mqtt.cpp index b8a649efc..d088cc37f 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -21,13 +21,9 @@ * . */ -#if defined(ARDUINO) +#if defined(ESP8266) #include - #if defined(ESP8266) - #include - #else - #include - #endif + #include #define MQTT_SOCKET_TIMEOUT 5 #include @@ -52,7 +48,7 @@ // Debug routines to help identify any blocking of the event loop for an extended period #if defined(ENABLE_DEBUG) - #if defined(ARDUINO) + #if defined(ESP8266) #include "TimeLib.h" #define DEBUG_TIMESTAMP(msg, ...) {time_os_t t = os.now_tz(); Serial.printf("%02d-%02d-%02d %02d:%02d:%02d - ", year(t), month(t), day(t), hour(t), minute(t), second(t));} #else @@ -480,14 +476,9 @@ void OSMqtt::loop(void) { #endif } -/**************************** ARDUINO ********************************************/ -#if defined(ARDUINO) - - #if defined(ESP8266) - WiFiClient wifiClient; - #else - EthernetClient ethClient; - #endif +/**************************** ESP8266 ********************************************/ +#if defined(ESP8266) +WiFiClient wifiClient; int OSMqtt::_init(void) { Client * client = NULL; diff --git a/mqtt.h b/mqtt.h index 65c2fd0a3..d39cdd383 100644 --- a/mqtt.h +++ b/mqtt.h @@ -21,8 +21,7 @@ * . */ -#ifndef _MQTT_H -#define _MQTT_H +#pragma once class OSMqtt { private: @@ -57,4 +56,3 @@ class OSMqtt { static char* get_sub_topic() { return _sub_topic; } }; -#endif // _MQTT_H diff --git a/notifier.cpp b/notifier.cpp index 7fefbb751..08b096b6e 100644 --- a/notifier.cpp +++ b/notifier.cpp @@ -130,8 +130,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { #define DEFAULT_EMAIL_PORT 465 // parse email variables - #if defined(SUPPORT_EMAIL) - // define email variables ArduinoJson::JsonDocument doc; // make sure this has the same scope of email_x variables to prevent use after free const char *email_host = NULL; const char *email_username = NULL; @@ -163,8 +161,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { email_recipient= doc["recipient"]; } } - #endif - + #if defined(ESP8266) EMailSender::EMailMessage email_message; #else @@ -175,13 +172,11 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { #endif bool email_enabled = false; -#if defined(SUPPORT_EMAIL) if(!email_en){ // todo: this should be simplified email_enabled = false; }else{ email_enabled = true; } -#endif // if none if enabled, return here if ((!ifttt_enabled) && (!email_enabled) && (!os.mqtt.enabled())) @@ -236,11 +231,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"duration\":%d"), (int)fval); if (os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"flow\":%d.%02d"), (int)gpm, (int)(gpm*100)%100); - #else snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"flow\":%.2f"), gpm); - #endif } } strcat_P(payload, PSTR("}")); @@ -256,11 +247,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" Flow rate: %d.%02d"), (int)gpm, (int)(gpm*100)%100); - #else snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" Flow rate: %.2f"), gpm); - #endif } if(email_enabled) { email_message.subject += PSTR("station event"); } } @@ -313,12 +300,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { //Format mqtt message snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d/alert/flow"), lval); float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%d.%02d,\"duration\":%d,\"alert_setpoint\":%d.%02d}"), (int)gpm, (int)(gpm*100)%100, - (int)fval, (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint*100)%100); - #else snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%.2f,\"duration\":%d,\"alert_setpoint\":%.4f}"), gpm, (int)fval, flow_gpm_alert_setpoint); - #endif } @@ -328,7 +310,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" strcat_P(postval, PSTR("at ")); time_os_t curr_time = os.now_tz(); - #if defined(ARDUINO) + #if defined(ESP8266) tmElements_t tm; breakTime(curr_time, tm); snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), @@ -351,13 +333,8 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { strcat_P(postval, PSTR(" FLOW ALERT!")); float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" | Flow rate: %d.%02d > Flow alert setpoint: %d.%02d"), - (int)gpm, (int)(gpm*100)%100, (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint*100)%100); - #else snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" | Flow rate: %.2f > Flow alert setpoint: %.4f"), gpm, flow_gpm_alert_setpoint); - #endif if(email_enabled) { email_message.subject += PSTR("- FLOW ALERT"); } @@ -451,18 +428,10 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { float vol = lval*flowrate100/100.f; if (os.mqtt.enabled()) { strcpy_P(topic, PSTR("sensor/flow")); - #if defined(OS_AVR) - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"count\":%d,\"volume\":%d.%02d}"), (int)lval, (int)vol, (int)(vol*100)%100); - #else snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"count\":%d,\"volume\":%.2f}"), (int)lval, vol); - #endif } if (ifttt_enabled || email_enabled) { - #if defined(OS_AVR) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("Flow count: %d, volume: %d.%02d"), (int)lval, (int)vol, (int)(vol*100)%100); - #else snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("Flow count: %d, volume: %.2f"), (int)lval, vol); - #endif if(email_enabled) { email_message.subject += PSTR("flow sensor event"); } } } @@ -496,7 +465,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" strcat_P(postval, PSTR("at ")); time_os_t curr_time = os.now_tz(); - #if defined(ARDUINO) + #if defined(ESP8266) tmElements_t tm; breakTime(curr_time, tm); snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), @@ -565,9 +534,8 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":\"started\",\"cause\":%d}"), (int)os.last_reboot_cause); } if (ifttt_enabled || email_enabled) { - #if defined(ARDUINO) + #if defined(ESP8266) snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("rebooted. Cause: %d. Device IP: "), os.last_reboot_cause); - #if defined(ESP8266) { IPAddress _ip; if (useEth) { @@ -579,9 +547,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; ip2string(postval, TMP_BUFFER_SIZE, ip); } - #else - ip2string(postval, TMP_BUFFER_SIZE, &(Ethernet.localIP()[0])); - #endif #else strcat_P(postval, PSTR("controller process restarted.")); #endif @@ -610,15 +575,13 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { if(email_enabled){ email_message.message = strchr(postval, 'O'); // ad-hoc: remove the value1 part from the ifttt message - #if defined(ARDUINO) - #if defined(ESP8266) - if(email_host && email_username && email_password && email_recipient) { // make sure all are valid - EMailSender emailSend(email_username, email_password); - emailSend.setSMTPServer(email_host); - emailSend.setSMTPPort(email_port); - EMailSender::Response resp = emailSend.send(email_recipient, email_message); - } - #endif + #if defined(ESP8266) + if(email_host && email_username && email_password && email_recipient) { // make sure all are valid + EMailSender emailSend(email_username, email_password); + emailSend.setSMTPServer(email_host); + emailSend.setSMTPPort(email_port); + EMailSender::Response resp = emailSend.send(email_recipient, email_message); + } #else struct smtp *smtp = NULL; String email_port_str = to_string(email_port); diff --git a/notifier.h b/notifier.h index 2572bd846..ca1f3e8c0 100644 --- a/notifier.h +++ b/notifier.h @@ -21,9 +21,7 @@ * . */ - -#ifndef _NOTIFIER_H -#define _NOTIFIER_H +#pragma once #define NOTIF_QUEUE_MAXSIZE 32 @@ -55,5 +53,3 @@ class NotifQueue { static NotifNodeStruct* tail; static unsigned char nqueue; }; - -#endif // _NOTIFIER_H \ No newline at end of file diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 0314337e6..6953d045d 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -30,33 +30,20 @@ #include "main.h" // External variables defined in main ion file -#if defined(USE_OTF) - extern OTF::OpenThingsFramework *otf; - #define OTF_PARAMS_DEF const OTF::Request &req,OTF::Response &res - #define OTF_PARAMS req,res - #define FKV_SOURCE req - #define handle_return(x) {if(x==HTML_OK) res.writeBodyData(ether_buffer, strlen(ether_buffer)); else otf_send_result(req,res,x); return;} -#else - extern EthernetClient *m_client; - #define OTF_PARAMS_DEF - #define OTF_PARAMS - #define FKV_SOURCE p - #define handle_return(x) {return_code=x; return;} -#endif +extern OTF::OpenThingsFramework *otf; +#define OTF_PARAMS_DEF const OTF::Request &req,OTF::Response &res +#define OTF_PARAMS req,res +#define FKV_SOURCE req +#define handle_return(x) {if(x==HTML_OK) res.writeBodyData(ether_buffer, strlen(ether_buffer)); else otf_send_result(req,res,x); return;} -#if defined(ARDUINO) - #if defined(ESP8266) - #include - #include - #include "espconnect.h" - extern ESP8266WebServer *update_server; - extern ENC28J60lwIP enc28j60; - extern Wiznet5500lwIP w5500; - extern lwipEth eth; - #else - #include "SdFat.h" - extern SdFat sd; - #endif +#if defined(ESP8266) + #include + #include + #include "espconnect.h" + extern ESP8266WebServer *update_server; + extern ENC28J60lwIP enc28j60; + extern Wiznet5500lwIP w5500; + extern lwipEth eth; #else #include #include @@ -69,11 +56,6 @@ extern OpenSprinkler os; extern ProgramData pd; extern ulong flow_count; -#if !defined(USE_OTF) -static unsigned char return_code; -static char* get_buffer = NULL; -#endif - BufferFiller bfill; /* Check available space (number of bytes) in the Ethernet buffer */ @@ -95,30 +77,6 @@ int available_ether_buffer() { #define HTML_UPLOAD_FAILED 0x40 #define HTML_REDIRECT_HOME 0xFF -#if !defined(USE_OTF) -static const char html200OK[] PROGMEM = - "HTTP/1.1 200 OK\r\n" -; - -static const char htmlNoCache[] PROGMEM = - "Cache-Control: max-age=0, no-cache, no-store, must-revalidate\r\n" -; - -static const char htmlContentJSON[] PROGMEM = - "Content-Type: application/json\r\n" - "Connection: close\r\n" -; - -static const char htmlContentHTML[] PROGMEM = - "Content-Type: text/html\r\n" - "Connection: close\r\n" -; - -static const char htmlAccessControl[] PROGMEM = - "Access-Control-Allow-Origin: *\r\n" -; -#endif - static const char htmlMobileHeader[] PROGMEM = "" ; @@ -127,9 +85,8 @@ static const char htmlReturnHome[] PROGMEM = "\n" ; -#if defined(USE_OTF) unsigned char findKeyVal (const OTF::Request &req,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL) { -#if defined(ARDUINO) +#if defined(ESP8266) char* result = key_in_pgm ? req.getQueryParameter((const __FlashStringHelper *)key) : req.getQueryParameter(key); #else char* result = req.getQueryParameter(key); @@ -144,7 +101,7 @@ unsigned char findKeyVal (const OTF::Request &req,char *strbuf, uint16_t maxlen, } return 0; } -#endif + unsigned char findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL) { uint8_t found=0; uint16_t i=0; @@ -211,11 +168,7 @@ void rewind_ether_buffer() { } void send_packet(OTF_PARAMS_DEF) { -#if defined(USE_OTF) res.writeBodyData(ether_buffer, strlen(ether_buffer)); -#else - m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); -#endif rewind_ether_buffer(); } @@ -224,7 +177,6 @@ char dec2hexchar(unsigned char dec) { else return 'A'+(dec-10); } -#if defined(USE_OTF) void print_header(OTF_PARAMS_DEF, bool isJson=true, int len=0) { res.writeStatus(200, F("OK")); res.writeHeader(F("Content-Type"), isJson?F("application/json"):F("text/html")); @@ -244,14 +196,8 @@ void print_header_compressed_html(OTF_PARAMS_DEF, int len) { res.writeHeader(F("Content-Encoding"), F("gzip")); res.writeHeader(F("Connection"), F("close")); } -#else -void print_header(bool isJson=true) { - bfill.emit_p(PSTR("$F$F$F$F\r\n"), html200OK, isJson?htmlContentJSON:htmlContentHTML, htmlAccessControl, htmlNoCache); -} -#endif -#if defined(USE_OTF) -#if !defined(ARDUINO) +#if !defined(ESP8266) string two_digits(uint8_t x) { return std::to_string(x); } @@ -267,7 +213,7 @@ String toHMS(ulong t) { void otf_send_result(OTF_PARAMS_DEF, unsigned char code, const char *item = NULL) { String json = F("{\"result\":"); -#if defined(ARDUINO) +#if defined(ESP8266) json += code; #else json += std::to_string(code); @@ -377,32 +323,18 @@ void on_ap_try_connect(OTF_PARAMS_DEF) { } } #endif -#endif /** Check and verify password */ -#if defined(USE_OTF) boolean check_password(char *p) { return true; } -boolean process_password(OTF_PARAMS_DEF, boolean fwv_on_fail=false) -#else -boolean check_password(char *p) -#endif -{ +boolean process_password(OTF_PARAMS_DEF, boolean fwv_on_fail=false) { #if defined(DEMO) return true; #endif if (os.iopts[IOPT_IGNORE_PASSWORD]) return true; -#if !defined(USE_OTF) - if (m_client && !p) { - p = get_buffer; - } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pw"), true)) { - if (os.password_verify(tmp_buffer)) return true; - } -#else /*if(req.isCloudRequest()){ // password is not required if this is coming from cloud connection return true; }*/ @@ -418,7 +350,6 @@ boolean check_password(char *p) } else { otf_send_result(OTF_PARAMS, HTML_UNAUTHORIZED); } -#endif return false; } @@ -473,13 +404,9 @@ void server_json_stations_main(OTF_PARAMS_DEF) { /** Output stations data */ void server_json_stations(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("{")); server_json_stations_main(OTF_PARAMS); @@ -488,13 +415,9 @@ void server_json_stations(OTF_PARAMS_DEF) { /** Output station special attribute */ void server_json_station_special(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif unsigned char sid; unsigned char comma=0; @@ -517,12 +440,7 @@ void server_json_station_special(OTF_PARAMS_DEF) { handle_return(HTML_OK); } -#if defined(USE_OTF) -void server_change_board_attrib(const OTF::Request &req, char header, unsigned char *attrib) -#else -void server_change_board_attrib(char *p, char header, unsigned char *attrib) -#endif -{ +void server_change_board_attrib(const OTF::Request &req, char header, unsigned char *attrib) { char tbuf2[6] = {0}; unsigned char bid; tbuf2[0]=header; @@ -534,12 +452,7 @@ void server_change_board_attrib(char *p, char header, unsigned char *attrib) } } -#if defined(USE_OTF) -void server_change_stations_attrib(const OTF::Request &req, char header, unsigned char *attrib) -#else -void server_change_stations_attrib(char *p, char header, unsigned char *attrib) -#endif -{ +void server_change_stations_attrib(const OTF::Request &req, char header, unsigned char *attrib) { char tbuf2[6] = {0}; unsigned char bid, s, sid; tbuf2[0]=header; @@ -568,11 +481,7 @@ void server_change_stations_attrib(char *p, char header, unsigned char *attrib) * g?: sequential group id */ void server_change_stations(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char* p = get_buffer; -#endif unsigned char sid; char tbuf2[5] = {'s', 0, 0, 0, 0}; @@ -580,9 +489,6 @@ void server_change_stations(OTF_PARAMS_DEF) { for(sid=0;sid sizeof(HTTPStationData)) { handle_return(HTML_DATA_OUTOFBOUND); } @@ -670,11 +573,7 @@ void manual_start_program(unsigned char, unsigned char, unsigned char); * qo: queue option (0: append; 1: insert at front; 2: replace (default) ) */ void server_manual_program(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); @@ -715,27 +614,9 @@ void server_manual_program(OTF_PARAMS_DEF) { * anno?: annotation for station ordering (refer to program name annotation) */ void server_change_runonce(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "t", false)) handle_return(HTML_DATA_MISSING); char *pv = tmp_buffer+1; -#else - char *p = get_buffer; - - // decode url first - if(p) urlDecode(p); - // search for the start of t=[ - char *pv; - boolean found=false; - for(pv=p;(*pv)!=0 && pv=IOPT_STATIC_IP1 && oid<=IOPT_STATIC_IP4) || (oid>=IOPT_GATEWAY_IP1 && oid<=IOPT_GATEWAY_IP4) || @@ -1054,12 +896,6 @@ void server_json_options_main() { continue; #endif - #if !(defined(ESP8266) || defined(PIN_SENSOR2)) - // only OS 3.x or controllers that have PIN_SENSOR2 defined support sensor 2 options - if (oid==IOPT_SENSOR2_TYPE || oid==IOPT_SENSOR2_OPTION || oid==IOPT_SENSOR2_ON_DELAY || oid==IOPT_SENSOR2_OFF_DELAY) - continue; - #endif - int32_t v=os.iopts[oid]; if (oid==IOPT_MASTER_OFF_ADJ || oid==IOPT_MASTER_OFF_ADJ_2 || oid==IOPT_MASTER_ON_ADJ || oid==IOPT_MASTER_ON_ADJ_2 || @@ -1067,7 +903,7 @@ void server_json_options_main() { v=water_time_decode_signed(v); } - #if defined(ARDUINO) + #if defined(ESP8266) if (oid==IOPT_BOOST_TIME) { if (os.hw_type==HW_TYPE_AC || os.hw_type==HW_TYPE_UNKNOWN) continue; else v<<=2; @@ -1095,22 +931,9 @@ void server_json_options_main() { } #endif - if (oid==IOPT_SEQUENTIAL_RETIRED || oid==IOPT_URS_RETIRED || oid==IOPT_RSO_RETIRED || oid==IOPT_RESERVE_7 || oid==IOPT_RESERVE_8) continue; - -#if defined(ARDUINO) - #if defined(ESP8266) - // for SSD1306, we can't adjust contrast or backlight - if(oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT) continue; - #else - if (os.lcd.type() == LCD_I2C) { - // for I2C type LCD, we can't adjust contrast or backlight - if(oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT) continue; - } - #endif -#else - // for Linux-based platforms, we can't adjust contrast or backlight - if(oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT) continue; -#endif + if (oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT || oid==IOPT_SEQUENTIAL_RETIRED || oid==IOPT_URS_RETIRED || + oid==IOPT_RSO_RETIRED || oid==IOPT_RESERVE_7 || oid==IOPT_RESERVE_8) + continue; // each json name takes 5 characters strncpy_P0(tmp_buffer, iopt_json_names+oid*5, 5); @@ -1134,13 +957,9 @@ void server_json_options_main() { /** Output Options */ void server_json_options(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS,true)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("{")); server_json_options_main(); handle_return(HTML_OK); @@ -1188,13 +1007,9 @@ void server_json_programs_main(OTF_PARAMS_DEF) { /** Output program data */ void server_json_programs(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("{")); server_json_programs_main(OTF_PARAMS); handle_return(HTML_OK); @@ -1203,11 +1018,7 @@ void server_json_programs(OTF_PARAMS_DEF) { /** Output script url form */ void server_view_scripturl(OTF_PARAMS_DEF) { rewind_ether_buffer(); -#if defined(USE_OTF) print_header(OTF_PARAMS,false,strlen(ether_buffer)); -#else - print_header(false); -#endif //bfill.emit_p(PSTR("
JavaScript:
Default:$S
Weather:
Default:$S
Password:
"), bfill.emit_p(PSTR(R"(
@@ -1257,12 +1068,10 @@ void server_json_controller_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"apdv\":$D,"), os.actual_pd_voltage); #endif -#if defined(USE_OTF) bfill.emit_p(PSTR("\"otc\":{$O},\"otcs\":$D,"), SOPT_OTC_OPTS, otf->getCloudStatus()); -#endif unsigned char mac[6] = {0}; -#if defined(ARDUINO) +#if defined(ESP8266) os.load_hardware_mac(mac, useEth); #else os.load_hardware_mac(mac, true); @@ -1282,9 +1091,7 @@ void server_json_controller_main(OTF_PARAMS_DEF) { wt_restricted, SOPT_DEVICE_NAME); -#if defined(SUPPORT_EMAIL) bfill.emit_p(PSTR("\"email\":{$O},"), SOPT_EMAIL_OPTS); -#endif bfill.emit_p(PSTR("\"wls\":[")); if (md_N == 0) { @@ -1295,7 +1102,7 @@ void server_json_controller_main(OTF_PARAMS_DEF) { bfill.emit_p((idx == md_N-1) ? PSTR("],") : PSTR(",")); } -#if defined(ARDUINO) +#if defined(ESP8266) uint16_t current = os.read_current(true); if((!os.status.program_busy) && (current$F"), @@ -1394,12 +1193,8 @@ void server_home(OTF_PARAMS_DEF) */ void server_change_values(OTF_PARAMS_DEF) { -#if defined(USE_OTF) extern uint32_t reboot_timer; if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rsn"), true) && atoi(tmp_buffer) > 0) { reset_all_stations(); } @@ -1412,24 +1207,16 @@ void server_change_values(OTF_PARAMS_DEF) os.status.overcurrent_sid = 0; // clear overcurrent status } - #if !defined(ARDUINO) + #if !defined(ESP8266) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("update"), true) && atoi(tmp_buffer) > 0) { os.update_dev(); } #endif if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rbt"), true) && atoi(tmp_buffer) > 0) { - #if defined(USE_OTF) - os.status.safe_reboot = 0; - reboot_timer = os.now_tz() + 1; - handle_return(HTML_SUCCESS); - #else - print_header(false); - //bfill.emit_p(PSTR("Rebooting...")); - send_packet(); - m_client->stop(); - os.reboot_dev(REBOOT_CAUSE_WEB); - #endif + os.status.safe_reboot = 0; + reboot_timer = os.now_tz() + 1; + handle_return(HTML_SUCCESS); } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { @@ -1485,40 +1272,26 @@ void string_remove_space(char *src) { * jsp: Javascript path */ void server_change_scripturl(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif #if defined(DEMO) handle_return(HTML_REDIRECT_HOME); #endif if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("jsp"), true)) { tmp_buffer[TMP_BUFFER_SIZE-1]=0; // make sure we don't exceed the maximum size - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif // trim unwanted space characters string_remove_space(tmp_buffer); os.sopt_save(SOPT_JAVASCRIPTURL, tmp_buffer); } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wsp"), true)) { tmp_buffer[TMP_BUFFER_SIZE-1]=0; - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif string_remove_space(tmp_buffer); os.sopt_save(SOPT_WEATHERURL, tmp_buffer); } -#if defined(USE_OTF) rewind_ether_buffer(); print_header(OTF_PARAMS,false,strlen(ether_buffer)); bfill.emit_p(PSTR("$F"), htmlReturnHome); handle_return(HTML_OK); -#else - handle_return(HTML_REDIRECT_HOME); -#endif } /** @@ -1532,12 +1305,7 @@ void server_change_scripturl(OTF_PARAMS_DEF) { */ void server_change_options(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif - // temporarily save some old options values bool time_change = false; bool weather_change = false; @@ -1598,9 +1366,6 @@ void server_change_options(OTF_PARAMS_DEF) } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("loc"), true)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif strReplaceQuoteBackslash(tmp_buffer); if (os.sopt_save(SOPT_LOCATION, tmp_buffer)) { // if location string has changed weather_change = true; @@ -1608,9 +1373,6 @@ void server_change_options(OTF_PARAMS_DEF) } uint8_t keyfound = 0; if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wto"), true)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif if (os.sopt_save(SOPT_WEATHER_OPTS, tmp_buffer)) { os.sopt_load(SOPT_WEATHER_OPTS, tmp_buffer+1); // make room for the leading '{' parse_wto(tmp_buffer); // parse wto @@ -1621,9 +1383,6 @@ void server_change_options(OTF_PARAMS_DEF) keyfound = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ifkey"), true, &keyfound)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif os.sopt_save(SOPT_IFTTT_KEY, tmp_buffer); } else if (keyfound) { tmp_buffer[0]=0; @@ -1632,9 +1391,6 @@ void server_change_options(OTF_PARAMS_DEF) keyfound = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("otc"), true, &keyfound)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif os.sopt_save(SOPT_OTC_OPTS, tmp_buffer); } else if (keyfound) { tmp_buffer[0]=0; @@ -1643,9 +1399,6 @@ void server_change_options(OTF_PARAMS_DEF) keyfound = 0; if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mqtt"), true, &keyfound)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); os.status.req_mqtt_restart = true; } else if (keyfound) { @@ -1656,9 +1409,6 @@ void server_change_options(OTF_PARAMS_DEF) keyfound = 0; if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("email"), true, &keyfound)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif os.sopt_save(SOPT_EMAIL_OPTS, tmp_buffer); } else if (keyfound) { tmp_buffer[0]=0; @@ -1666,22 +1416,19 @@ void server_change_options(OTF_PARAMS_DEF) } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("dname"), true)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif strReplaceQuoteBackslash(tmp_buffer); os.sopt_save(SOPT_DEVICE_NAME, tmp_buffer); } // if not using NTP and manually setting time if (!os.iopts[IOPT_USE_NTP] && findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ttt"), true)) { -#if defined(ARDUINO) +#if defined(ESP8266) unsigned long t; t = strtoul(tmp_buffer, NULL, 0); #endif // before chaging time, reset all stations to avoid messing up with timing reset_all_stations_immediate(); -#if defined(ARDUINO) +#if defined(ESP8266) setTime(t); RTC.set(t); #endif @@ -1731,11 +1478,7 @@ void server_change_password(OTF_PARAMS_DEF) { return; #endif -#if defined(USE_OTF) - if(!process_password(OTF_PARAMS)) return; -#else char* p = get_buffer; -#endif if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("npw"), true)) { const int pwBufferSize = TMP_BUFFER_SIZE/2; char *tbuf2 = tmp_buffer + pwBufferSize; // use the second half of tmp_buffer @@ -1763,13 +1506,9 @@ void server_json_status_main() { /** Output station status */ void server_json_status(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("{")); server_json_status_main(); @@ -1788,11 +1527,7 @@ void server_json_status(OTF_PARAMS_DEF) * qo: queuing option (0: append after others; 1: run now and pause others) */ void server_change_manual(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif int sid=-1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { @@ -1896,12 +1631,7 @@ int file_fgets(File file, char* buf, int maxsize) { * if unspecified, output all records */ void server_json_log(OTF_PARAMS_DEF) { - -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif unsigned int start, end; @@ -1932,14 +1662,10 @@ void server_json_log(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, type, 4, PSTR("type"), true)) type_specified = true; -#if defined(USE_OTF) // as the log data can be large, we will use ESP8266's sendContent function to // send multiple packets of data, instead of the standard way of using send(). rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("[")); @@ -1951,10 +1677,6 @@ void server_json_log(OTF_PARAMS_DEF) { #if defined(ESP8266) File file = LittleFS.open(tmp_buffer, "r"); if(!file) continue; -#elif defined(ARDUINO) - if (!sd.exists(tmp_buffer)) continue; - SdFile file; - file.open(tmp_buffer, O_READ); #else // prepare to open log file for Linux FILE *file = fopen(get_filename_fullpath(tmp_buffer), "rb"); if(!file) continue; @@ -1969,12 +1691,6 @@ void server_json_log(OTF_PARAMS_DEF) { break; } tmp_buffer[result]=0; - #elif defined(ARDUINO) - result = file.fgets(tmp_buffer, TMP_BUFFER_SIZE); - if (result <= 0) { - file.close(); - break; - } #else if(fgets(tmp_buffer, TMP_BUFFER_SIZE, file)) { result = strlen(tmp_buffer); @@ -2028,12 +1744,7 @@ void server_json_log(OTF_PARAMS_DEF) { * if day=all: delete all log files) */ void server_delete_log(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("day"), true)) handle_return(HTML_DATA_MISSING); @@ -2048,11 +1759,7 @@ void server_delete_log(OTF_PARAMS_DEF) { * repl: replace (in units of seconds) (New UI allows for replace, extend, and pause using this) */ void server_pause_queue(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif ulong duration = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("repl"), true)) { @@ -2079,13 +1786,10 @@ void server_pause_queue(OTF_PARAMS_DEF) { /** Output all JSON data, including jc, jp, jo, js, jn */ void server_json_all(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS,true)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif + bfill.emit_p(PSTR("{\"settings\":{")); server_json_controller_main(OTF_PARAMS); send_packet(OTF_PARAMS); @@ -2104,15 +1808,8 @@ void server_json_all(OTF_PARAMS_DEF) { handle_return(HTML_OK); } -#if defined(ARDUINO) +#if defined(ESP8266) -#if defined(OS_AVR) -static int freeHeap () { - extern int __heap_start, *__brkval; - int v; - return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); -} -#endif #else #include static unsigned long freeHeap() { @@ -2127,12 +1824,9 @@ static unsigned long freeHeap() { #endif void server_json_debug(OTF_PARAMS_DEF) { -#if defined(USE_OTF) rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif + bfill.emit_p(PSTR("{\"date\":\"$S\",\"time\":\"$S\",\"heap\":$L"), __DATE__, __TIME__, #if defined(ESP8266) ESP.getFreeHeap()); @@ -2222,7 +1916,7 @@ const char _url_keys[] PROGMEM = "ja" "pq" "db" -#if defined(ARDUINO) +#if defined(ESP8266) //"ff" #endif ; @@ -2252,7 +1946,7 @@ URLHandler urls[] = { server_json_all, // ja server_pause_queue, // pq server_json_debug, // db -#if defined(ARDUINO) +#if defined(ESP8266) //server_fill_files, #endif }; @@ -2387,7 +2081,7 @@ void start_server_ap() { #endif -#if defined(USE_OTF) && !defined(ARDUINO) +#if !defined(ESP8266) void initialize_otf() { if(!otf) return; static bool callback_initialized = false; @@ -2410,95 +2104,9 @@ void initialize_otf() { } #endif -#if !defined(USE_OTF) -// This funtion is only used for non-OTF platforms -void handle_web_request(char *p) { - rewind_ether_buffer(); - - // assume this is a GET request - // GET /xx?xxxx - char *com = p+5; - char *dat = com+3; - - if(com[0]==' ') { - server_home(); // home page handler - send_packet(); - m_client->stop(); - } else { - // server funtion handlers - unsigned char i; - for(i=0;istop(); - return; - } - switch(ret) { - case HTML_OK: - break; - case HTML_REDIRECT_HOME: - print_header(false); - bfill.emit_p(PSTR("$F"), htmlReturnHome); - break; - default: - print_header(); - bfill.emit_p(PSTR("{\"result\":$D}"), ret); - } - break; - } - } - - if(i==sizeof(urls)/sizeof(URLHandler)) { - // no server funtion found - print_header(); - bfill.emit_p(PSTR("{\"result\":$D}"), HTML_PAGE_NOT_FOUND); - } - send_packet(); - m_client->stop(); - } -} -#endif - -#if defined(ARDUINO) +#if defined(ESP8266) #define NTP_NTRIES 10 /** NTP sync request */ -#if defined(ESP8266) // due to lwip not supporting UDP, we have to use configTime and time() functions // othewise, using UDP is much faster for NTP sync ulong getNtpTime() { @@ -2533,104 +2141,4 @@ ulong getNtpTime() { } return gt; } -#else // AVR -ulong getNtpTime() { - - // only proceed if we are connected - if(!os.network_connected()) return 0; - - uint16_t port = (uint16_t)(os.iopts[IOPT_HTTPPORT_1]<<8) + (uint16_t)os.iopts[IOPT_HTTPPORT_0]; - port = (port==8000) ? 8888:8000; // use a different port than http port - EthernetUDP udp; - - #define NTP_PACKET_SIZE 48 - #define NTP_PORT 123 - #define N_PUBLIC_SERVERS 5 - - static const char* public_ntp_servers[] = { - "time.google.com", - "time.nist.gov", - "time.windows.com", - "time.cloudflare.com", - "pool.ntp.org" }; - static uint8_t sidx = 0; - - static unsigned char packetBuffer[NTP_PACKET_SIZE]; - unsigned char ntpip[4] = { - os.iopts[IOPT_NTP_IP1], - os.iopts[IOPT_NTP_IP2], - os.iopts[IOPT_NTP_IP3], - os.iopts[IOPT_NTP_IP4]}; - unsigned char tries=0; - ulong startt = millis(); - while(tries1577836800UL) { - udp.stop(); - DEBUG_PRINT(F("took ")); - DEBUG_PRINT(millis()-startt); - DEBUG_PRINTLN(F("ms")); - return gt; - } - } - } - tries++; - udp.stop(); - sidx=(sidx+1)%N_PUBLIC_SERVERS; - } - if(tries==NTP_NTRIES) {DEBUG_PRINTLN(F("NTP failed!!"));} - udp.stop(); - return 0; -} -#endif #endif diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 1635236c4..5e7f02225 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -21,10 +21,9 @@ * . */ -#ifndef _OPENSPRINKLER_SERVER_H -#define _OPENSPRINKLER_SERVER_H +#pragma once -#if !defined(ARDUINO) +#if !defined(ESP8266) #include #include #endif @@ -100,5 +99,3 @@ class BufferFiller { } }; - -#endif // _OPENSPRINKLER_SERVER_H diff --git a/platformio.ini b/platformio.ini index a14fcd484..ae1c631e3 100644 --- a/platformio.ini +++ b/platformio.ini @@ -33,23 +33,6 @@ board_build.f_flash = 80000000L build_flags = -DARP_TABLE_SIZE=40 ;build_flags = -DENABLE_DEBUG -[env:os23_atmega1284p] -platform = atmelavr -board = ATmega1284P -board_build.f_cpu = 16000000L -board_build.variant = sanguino -framework = arduino -lib_ldf_mode = deep -lib_deps = - https://github.com/UIPEthernet/UIPEthernet/archive/refs/tags/v2.0.12.zip - knolleary/PubSubClient @ ^2.8 - https://github.com/greiman/SdFat/archive/refs/tags/1.0.7.zip - Wire -build_src_filter = +<*> - -- -monitor_speed=115200 -upload_protocol = arduino -upload_speed = 115200 - ; The following env is for syntax highlighting only, ; it is NOT for building the firmware for Linux. ; To build the firmware for Linux, please follow: diff --git a/program.cpp b/program.cpp index fce3e1d7b..89e2584eb 100644 --- a/program.cpp +++ b/program.cpp @@ -227,7 +227,7 @@ int16_t ProgramStruct::starttime_decode(int16_t t) { /** Check if a given time matches the program's start day */ unsigned char ProgramStruct::check_day_match(time_os_t t) { -#if defined(ARDUINO) // get current time from Arduino +#if defined(ESP8266) // get current time from Arduino unsigned char weekday_t = weekday(t); // weekday ranges from [0,6] within Sunday being 1 unsigned char day_t = day(t); unsigned char month_t = month(t); diff --git a/program.h b/program.h index 1da1d6a53..ac899be60 100644 --- a/program.h +++ b/program.h @@ -21,9 +21,7 @@ * . */ - -#ifndef _PROGRAM_H -#define _PROGRAM_H +#pragma once #define MAX_NUM_PROGRAMS 40 // maximum number of programs #define MAX_NUM_STARTTIMES 4 @@ -157,5 +155,3 @@ class ProgramData { static void load_count(); static void save_count(); }; - -#endif // _PROGRAM_H diff --git a/rpitime.h b/rpitime.h index 49ff07e8f..ae6178949 100644 --- a/rpitime.h +++ b/rpitime.h @@ -1,5 +1,5 @@ -#ifndef RPI_TIME_H -#define RPI_TIME_H +#pragma once +#if !defined(ARDUINO) #include diff --git a/types.h b/types.h index d03aee036..e4d7831cc 100644 --- a/types.h +++ b/types.h @@ -1,12 +1,10 @@ -#ifndef TYPES_H -#define TYPES_H +#pragma once + #include -#if defined(ARDUINO) +#if defined(ESP8266) typedef unsigned long time_os_t; #else #include typedef time_t time_os_t; #endif - -#endif \ No newline at end of file diff --git a/utils.cpp b/utils.cpp index 18de656b4..34e095489 100644 --- a/utils.cpp +++ b/utils.cpp @@ -26,16 +26,9 @@ #include "OpenSprinkler.h" extern OpenSprinkler os; -#if defined(ARDUINO) // Arduino - - #if defined(ESP8266) - #include - #include - #else - #include - #include "SdFat.h" - extern SdFat sd; - #endif +#if defined(ESP8266) // Arduino + #include + #include #else // RPI/LINUX @@ -296,12 +289,6 @@ void remove_file(const char *fn) { if(!LittleFS.exists(fn)) return; LittleFS.remove(fn); -#elif defined(ARDUINO) - - sd.chdir("/"); - if (!sd.exists(fn)) return; - sd.remove(fn); - #else remove(get_filename_fullpath(fn)); @@ -314,11 +301,6 @@ bool file_exists(const char *fn) { return LittleFS.exists(fn); -#elif defined(ARDUINO) - - sd.chdir("/"); - return sd.exists(fn); - #else FILE *file; @@ -341,16 +323,6 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { f.close(); } -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - if(file.open(fn, O_READ)) { - file.seekSet(pos); - file.read(dst, len); - file.close(); - } - #else FILE *fp = fopen(get_filename_fullpath(fn), "rb"); @@ -374,16 +346,6 @@ void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { f.close(); } -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - int ret = file.open(fn, O_CREAT | O_RDWR); - if(!ret) return; - file.seekSet(pos); - file.write(src, len); - file.close(); - #else FILE *fp = fopen(get_filename_fullpath(fn), "rb+"); @@ -414,18 +376,6 @@ void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) f.write((unsigned char*)tmp, len); f.close(); -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - int ret = file.open(fn, O_RDWR); - if(!ret) return; - file.seekSet(from); - file.read(tmp, len); - file.seekSet(to); - file.write(tmp, len); - file.close(); - #else FILE *fp = fopen(get_filename_fullpath(fn), "rb+"); @@ -456,21 +406,6 @@ unsigned char file_cmp_block(const char *fn, const char *buf, ulong pos) { return (*buf==c)?0:1; } -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - if(file.open(fn, O_READ)) { - file.seekSet(pos); - char c = file.read(); - while(*buf && (c==*buf)) { - buf++; - c=file.read(); - } - file.close(); - return (*buf==c)?0:1; - } - #else FILE *fp = fopen(get_filename_fullpath(fn), "rb"); diff --git a/utils.h b/utils.h index 24790bed9..bc1ac1ccd 100644 --- a/utils.h +++ b/utils.h @@ -21,10 +21,9 @@ * . */ -#ifndef _UTILS_H -#define _UTILS_H + #pragma once -#if defined(ARDUINO) +#if defined(ESP8266) #include #else // headers for RPI/LINUX #include @@ -74,7 +73,7 @@ bool isValidMAC(const char *_mac); void str2mac(const char *_str, unsigned char mac[]); #endif -#if defined(ARDUINO) +#if defined(ESP8266) #else // Arduino compatible functions for RPI/LINUX const char* get_data_dir(); @@ -112,5 +111,3 @@ void str2mac(const char *_str, unsigned char mac[]); BoardType get_board_type(); #endif - -#endif // _UTILS_H diff --git a/weather.cpp b/weather.cpp index 628b40de7..fd5c58a6b 100644 --- a/weather.cpp +++ b/weather.cpp @@ -186,13 +186,6 @@ void GetWeather() { // Parse protocol and extract host/port char *host_start = host; -#if defined(OS_AVR) - if (strncmp_P(host, PSTR("http://"), 7) == 0) { - host_start = host + 7; - } else if (strncmp_P(host, PSTR("https://"), 8) == 0) { // note that avr does not support https - host_start = host + 8; - } -#else bool use_ssl = true; // default to https int port = 443; // default to https port @@ -214,8 +207,6 @@ void GetWeather() { port = atoi(colon + 1); } -#endif - strcat(ether_buffer, " HTTP/1.0\r\nHOST: "); strcat(ether_buffer, host_start); strcat(ether_buffer, "\r\nUser-Agent: "); @@ -224,11 +215,7 @@ void GetWeather() { wt_errCode = HTTP_RQT_NOT_RECEIVED; DEBUG_PRINT(ether_buffer); -#if defined(OS_AVR) - int ret = os.send_http_request(host_start, ether_buffer, getweather_callback_with_peel_header); -#else int ret = os.send_http_request(host_start, port, ether_buffer, getweather_callback_with_peel_header, use_ssl); -#endif if(ret!=HTTP_RQT_SUCCESS) { if(wt_errCode < 0) wt_errCode = ret; // if wt_errCode > 0, the call is successful but weather script may return error @@ -270,7 +257,7 @@ void parse_wto(char* wto) { void apply_monthly_adjustment(time_os_t curr_time) { // ====== Check monthly water percentage ====== if(os.iopts[IOPT_USE_WEATHER]==WEATHER_METHOD_MONTHLY) { -#if defined(ARDUINO) +#if defined(ESP8266) unsigned char m = month(curr_time)-1; #else time_os_t ct = curr_time; diff --git a/weather.h b/weather.h index 1ac7a7458..a8ad3621c 100644 --- a/weather.h +++ b/weather.h @@ -21,9 +21,7 @@ * */ - -#ifndef _WEATHER_H -#define _WEATHER_H +#pragma once #define WEATHER_UPDATE_SUNRISE 0x01 #define WEATHER_UPDATE_SUNSET 0x02 @@ -45,4 +43,3 @@ extern unsigned char wt_monthly[]; extern unsigned char wt_restricted; void parse_wto(char* wto); void apply_monthly_adjustment(time_os_t curr_time); -#endif // _WEATHER_H From c68b7e3f3481243ec2edd85d0d7033c85fb80826 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 17 Nov 2025 21:41:49 -0500 Subject: [PATCH 073/115] fix merge conflicts --- ads1115.cpp | 124 ++++---- ads1115.h | 82 +++--- bfiller.h | 2 +- defines.h | 8 +- .../OpenThings-Framework-Firmware-Library | 2 +- opensprinkler_server.h | 2 +- sensor.h | 274 +++++++++--------- utils.cpp | 10 +- utils.h | 2 +- 9 files changed, 253 insertions(+), 253 deletions(-) diff --git a/ads1115.cpp b/ads1115.cpp index d7c214ca0..d024f5355 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -1,38 +1,38 @@ #include "ads1115.h" -#if defined(ARDUINO) +#if defined(ESP8266) ADS1115::ADS1115(uint8_t address, TwoWire& wire) : _address(address), _wire(&wire) {} ADS1115::ADS1115(uint8_t address) : ADS1115(address, Wire) {} bool ADS1115::begin() { - if ((this->_address < 0x48) || (this->_address > 0x4B)) { - return false; - } - this->_wire->beginTransmission(_address); - return (this->_wire->endTransmission() == 0); + if ((this->_address < 0x48) || (this->_address > 0x4B)) { + return false; + } + this->_wire->beginTransmission(_address); + return (this->_wire->endTransmission() == 0); } void ADS1115::_write_register(uint8_t reg, uint16_t value) { - this->_wire->beginTransmission(this->_address); - this->_wire->write(reg); - this->_wire->write((uint8_t)(value >> 8)); - this->_wire->write((uint8_t)(value & 0xFF)); - this->_wire->endTransmission(); + this->_wire->beginTransmission(this->_address); + this->_wire->write(reg); + this->_wire->write((uint8_t)(value >> 8)); + this->_wire->write((uint8_t)(value & 0xFF)); + this->_wire->endTransmission(); } uint16_t ADS1115::_read_register(uint8_t reg) { - this->_wire->beginTransmission(this->_address); - this->_wire->write(reg); - if (!this->_wire->endTransmission()) { - if (this->_wire->requestFrom((int)_address, (int)2) == 2) { - uint16_t val = ((uint16_t)this->_wire->read()) << 8; - val += (uint16_t)this->_wire->read(); - return val; - } - } - - return 0; + this->_wire->beginTransmission(this->_address); + this->_wire->write(reg); + if (!this->_wire->endTransmission()) { + if (this->_wire->requestFrom((int)_address, (int)2) == 2) { + uint16_t val = ((uint16_t)this->_wire->read()) << 8; + val += (uint16_t)this->_wire->read(); + return val; + } + } + + return 0; } #else // OSPI @@ -40,44 +40,44 @@ ADS1115::ADS1115(uint8_t address, I2CBus& bus) : _address(address), _i2c(bus, ad ADS1115::ADS1115(uint8_t address) : ADS1115(address, Bus) {} bool ADS1115::begin() { - if ((this->_address < 0x48) || (this->_address > 0x4B)) { - return false; - } - if (this->_i2c.detect() < 0) { - return false; - } - return true; + if ((this->_address < 0x48) || (this->_address > 0x4B)) { + return false; + } + if (this->_i2c.detect() < 0) { + return false; + } + return true; } void ADS1115::_write_register(uint8_t reg, uint16_t value) { - this->_i2c.send_word(reg, this->swap_reg(value)); + this->_i2c.send_word(reg, this->swap_reg(value)); } uint16_t ADS1115::_read_register(uint8_t reg) { - return this->swap_reg((uint16_t)(this->_i2c.read_word(reg) & 0xFFFF)); + return this->swap_reg((uint16_t)(this->_i2c.read_word(reg) & 0xFFFF)); } #endif int16_t ADS1115::get_pin_value(uint8_t pin) { - this->request_pin(pin); - ulong start = millis(); - while (this->is_busy()) { - // if ((millis() - start) > 11) { - if ((millis() - start) > 18) { - return 0; - } - -#if defined(ARDUINO) - yield(); + this->request_pin(pin); + ulong start = millis(); + while (this->is_busy()) { + // if ((millis() - start) > 11) { + if ((millis() - start) > 18) { + return 0; + } + +#if defined(ESP8266) + yield(); #endif - } + } - return this->get_value(); + return this->get_value(); } void ADS1115::request_pin(uint8_t pin) { - uint16_t config = 0x8000 | ((4 + ((uint16_t)pin)) << 12) | 0x0100 | (4 << 5); - this->_write_register(0x01, config); + uint16_t config = 0x8000 | ((4 + ((uint16_t)pin)) << 12) | 0x0100 | (4 << 5); + this->_write_register(0x01, config); } ADS1115Sensor::ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : @@ -87,36 +87,36 @@ pin(pin), sensors(sensors) {} void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { - bfill->emit_p(PSTR("{\"pin\":$D}"), ((this->sensor_index << 2) + this->pin + 1)); + bfill->emit_p(PSTR("{\"pin\":$D}"), ((this->sensor_index << 2) + this->pin + 1)); } void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"default\":\"1\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"default\":\"1\",\"extra\":[]}]}")); } double ADS1115Sensor::get_inital_value() { - return 0.0; + return 0.0; } double ADS1115Sensor::_get_raw_value() { - if (this->sensors[sensor_index] == nullptr) { - return 0.0; - } - else { - return ((double)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; - } + if (this->sensors[sensor_index] == nullptr) { + return 0.0; + } + else { + return ((double)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; + } } uint32_t ADS1115Sensor::_serialize_internal(char *buf) { - uint32_t i = 0; - buf[i++] = static_cast(this->sensor_index); - buf[i++] = static_cast(this->pin); - return i; + uint32_t i = 0; + buf[i++] = static_cast(this->sensor_index); + buf[i++] = static_cast(this->pin); + return i; } ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf) { - uint32_t i = Sensor::_deserialize(buf); - this->sensor_index = static_cast(buf[i++]); - this->pin = static_cast(buf[i++]); - this->sensors = sensors; + uint32_t i = Sensor::_deserialize(buf); + this->sensor_index = static_cast(buf[i++]); + this->pin = static_cast(buf[i++]); + this->sensors = sensors; } \ No newline at end of file diff --git a/ads1115.h b/ads1115.h index 629f0bdb5..06f8508b0 100644 --- a/ads1115.h +++ b/ads1115.h @@ -5,7 +5,7 @@ #define ADS1115_SCALE_FACTOR (6144.0 / 32768.0) -#if defined(ARDUINO) +#if defined(ESP8266) #include #include #else @@ -14,62 +14,62 @@ class ADS1115 { public: -#if defined(ARDUINO) - ADS1115(uint8_t address, TwoWire& wire); +#if defined(ESP8266) + ADS1115(uint8_t address, TwoWire& wire); #else - ADS1115(uint8_t address, I2CBus& bus); + ADS1115(uint8_t address, I2CBus& bus); #endif ADS1115(uint8_t address); - int16_t get_pin_value(uint8_t pin); - bool begin(); + int16_t get_pin_value(uint8_t pin); + bool begin(); - int16_t get_value() { - return (int16_t) this->_read_register(0x00); - } + int16_t get_value() { + return (int16_t) this->_read_register(0x00); + } - void request_pin(uint8_t pin); + void request_pin(uint8_t pin); - bool is_busy() { - return (this->_read_register(0x01) & 0x8000) == 0; - } + bool is_busy() { + return (this->_read_register(0x01) & 0x8000) == 0; + } - private: - uint8_t _address; -#if defined(ARDUINO) - TwoWire *_wire; + private: + uint8_t _address; +#if defined(ESP8266) + TwoWire *_wire; #else - I2CDevice _i2c; - uint16_t swap_reg(uint16_t val) { - return (val << 8) | (val >> 8); - } + I2CDevice _i2c; + uint16_t swap_reg(uint16_t val) { + return (val << 8) | (val >> 8); + } #endif - void _write_register(uint8_t reg, uint16_t value); - uint16_t _read_register(uint8_t reg); + void _write_register(uint8_t reg, uint16_t value); + uint16_t _read_register(uint8_t reg); }; class ADS1115Sensor : public Sensor { - public: - ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); - ADS1115Sensor(ADS1115 **sensors, char *buf); + public: + ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(ADS1115 **sensors, char *buf); - void emit_extra_json(BufferFiller *bfill); - static void emit_description_json(BufferFiller *bfill); - - - SensorType get_sensor_type() { - return SensorType::ADS1115; - } + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); + + + SensorType get_sensor_type() { + return SensorType::ADS1115; + } - uint8_t sensor_index; - uint8_t pin; + uint8_t sensor_index; + uint8_t pin; - double get_inital_value(); + double get_inital_value(); - private: - double _get_raw_value(); - uint32_t _serialize_internal(char *buf); - - ADS1115 **sensors; + private: + double _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + ADS1115 **sensors; }; \ No newline at end of file diff --git a/bfiller.h b/bfiller.h index f60cd3940..9fa64b02b 100644 --- a/bfiller.h +++ b/bfiller.h @@ -3,7 +3,7 @@ #include "utils.h" -#if defined(ARDUINO) +#if defined(ESP8266) #include #else #include diff --git a/defines.h b/defines.h index 9beadc540..9133a20a2 100755 --- a/defines.h +++ b/defines.h @@ -403,7 +403,8 @@ enum { #define V2_PIN_BOOST_SEL IOEXP_PIN+8 #define USE_DISPLAY - + #define USE_ADS1115 + #define USE_SENSORS #elif defined(OSPI) // for OSPi #define OS_HW_VERSION OSPI_HW_VERSION_BASE @@ -426,9 +427,8 @@ enum { #define SCL 0 #define USE_DISPLAY - - #define USE_ADS1115 - #define USE_SENSORS + #define USE_ADS1115 + #define USE_SENSORS #else // for demo / simulation // use fake hardware pins diff --git a/external/OpenThings-Framework-Firmware-Library b/external/OpenThings-Framework-Firmware-Library index 86d621734..4e9306153 160000 --- a/external/OpenThings-Framework-Firmware-Library +++ b/external/OpenThings-Framework-Firmware-Library @@ -1 +1 @@ -Subproject commit 86d621734cf8d457575f38f7ab7fa83dcb074b79 +Subproject commit 4e93061538da197e1820c7b325a3c996ff7851ea diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 85a29b7e5..930abe415 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -27,7 +27,7 @@ #include #include "bfiller.h" -#if !defined(ARDUINO) +#if !defined(ESP8266) #include #include #endif diff --git a/sensor.h b/sensor.h index a1e79b8dd..d066345d5 100644 --- a/sensor.h +++ b/sensor.h @@ -14,202 +14,202 @@ #define SENSOR_CUSTOM_UNIT_LEN 9 typedef struct { - ulong interval; - uint32_t flags; - ulong next_update; - double value; + ulong interval; + uint32_t flags; + ulong next_update; + double value; } sensor_memory_t; enum class SensorType { - Ensemble, - ADS1115, - Weather, - MAX_VALUE, + Ensemble, + ADS1115, + Weather, + MAX_VALUE, }; enum class SensorUnitGroup { - None, - Temperature, - Length, - Volume, - Light, - Energy, - Velocity, - Pressure, - Flow, - MAX_VALUE, + None, + Temperature, + Length, + Volume, + Light, + Energy, + Velocity, + Pressure, + Flow, + MAX_VALUE, }; enum class SensorUnit { - None, - Celsius, - Fahrenheit, - Kelvin, - Milimeter, - Centieter, - Meter, - Kilometer, - Inch, - Foot, - Mile, - Lux, - Lumen, - Milivolt, - Volt, - Miliampere, - Ampere, - Percent, - MilesPerHour, - KilometersPerHour, - MetersPerSecond, - DialetricConstant, - PartsPerMillion, - Ohm, - Miliohm, - Kiloohm, - Bar, - Kilopascal, - Pascal, - Torr, - LitersPerSecond, - GallonsPerSecond, - MAX_VALUE, + None, + Celsius, + Fahrenheit, + Kelvin, + Milimeter, + Centieter, + Meter, + Kilometer, + Inch, + Foot, + Mile, + Lux, + Lumen, + Milivolt, + Volt, + Miliampere, + Ampere, + Percent, + MilesPerHour, + KilometersPerHour, + MetersPerSecond, + DialetricConstant, + PartsPerMillion, + Ohm, + Miliohm, + Kiloohm, + Bar, + Kilopascal, + Pascal, + Torr, + LitersPerSecond, + GallonsPerSecond, + MAX_VALUE, }; typedef enum { - SENSOR_FLAG_ENABLE = 0, - SENSOR_FLAG_LOG, - SENSOR_FLAG_COUNT + SENSOR_FLAG_ENABLE = 0, + SENSOR_FLAG_LOG, + SENSOR_FLAG_COUNT } sensor_flags; class Sensor { public: - Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags); - Sensor(); - virtual ~Sensor() {} - - double get_new_value(); - uint32_t serialize(char *buf); - - void virtual emit_extra_json(BufferFiller *bfill) = 0; - - unsigned long interval = 1; - double min = 0.0; - double max = 0.0; - double scale = 0.0; - double offset = 0.0; - char name[SENSOR_NAME_LEN] = {0}; - SensorUnit unit = SensorUnit::None; - - uint32_t flags = 0; - - SensorType virtual get_sensor_type() = 0; - double virtual get_inital_value() = 0; - -private: - double virtual _get_raw_value() = 0; -protected: - uint32_t _deserialize(char *buf); - uint32_t virtual _serialize_internal(char *buf) = 0; + Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags); + Sensor(); + virtual ~Sensor() {} + + double get_new_value(); + uint32_t serialize(char *buf); + + void virtual emit_extra_json(BufferFiller *bfill) = 0; + + unsigned long interval = 1; + double min = 0.0; + double max = 0.0; + double scale = 0.0; + double offset = 0.0; + char name[SENSOR_NAME_LEN] = {0}; + SensorUnit unit = SensorUnit::None; + + uint32_t flags = 0; + + SensorType virtual get_sensor_type() = 0; + double virtual get_inital_value() = 0; + + private: + double virtual _get_raw_value() = 0; + protected: + uint32_t _deserialize(char *buf); + uint32_t virtual _serialize_internal(char *buf) = 0; }; enum class EnsembleAction { - Min, - Max, - Average, - Sum, - Product, - MAX_VALUE, + Min, + Max, + Average, + Sum, + Product, + MAX_VALUE, }; typedef Sensor* (*SensorGetter)(uint8_t); typedef struct { - uint8_t sensor_id; - double min; - double max; - double scale; - double offset; + uint8_t sensor_id; + double min; + double max; + double scale; + double offset; } ensemble_children_t; #define ENSEMBLE_SENSOR_CHILDREN_COUNT 4 class EnsembleSensor : public Sensor { - public: - EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); - EnsembleSensor(sensor_memory_t *sensors, char *buf); + public: + EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); + EnsembleSensor(sensor_memory_t *sensors, char *buf); - void emit_extra_json(BufferFiller *bfill); - static void emit_description_json(BufferFiller *bfill); + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); - SensorType get_sensor_type() { - return SensorType::Ensemble; - } + SensorType get_sensor_type() { + return SensorType::Ensemble; + } - ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; - EnsembleAction action; + ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; + EnsembleAction action; - double get_inital_value(); + double get_inital_value(); - private: - double _get_raw_value(); - uint32_t _serialize_internal(char *buf); - - sensor_memory_t *sensors; + private: + double _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + sensor_memory_t *sensors; }; enum class WeatherAction { - MAX_VALUE, + MAX_VALUE, }; typedef double (*WeatherGetter)(WeatherAction); class WeatherSensor : public Sensor { - public: - WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); - WeatherSensor(WeatherGetter weather_getter, char *buf); + public: + WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(WeatherGetter weather_getter, char *buf); - void emit_extra_json(BufferFiller *bfill); - static void emit_description_json(BufferFiller *bfill); + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); - SensorType get_sensor_type() { - return SensorType::Weather; - } + SensorType get_sensor_type() { + return SensorType::Weather; + } - WeatherAction action; + WeatherAction action; - double get_inital_value(); + double get_inital_value(); - private: - double _get_raw_value(); - uint32_t _serialize_internal(char *buf); - - WeatherGetter weather_getter; + private: + double _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + WeatherGetter weather_getter; }; typedef struct { - double x; - double y; + double x; + double y; } sensor_adjustment_point_t; #define SENSOR_ADJUSTMENT_POINTS 8 typedef enum { - SENADJ_FLAG_ENABLE = 0, + SENADJ_FLAG_ENABLE = 0, } senadj_flags; class SensorAdjustment { public: - SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points); - SensorAdjustment(char *buf); - - double get_adjustment_factor(sensor_memory_t *sensors); - uint32_t serialize(char *buf); - - uint8_t flags; - uint8_t sid; - uint8_t point_count; - sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; + SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points); + SensorAdjustment(char *buf); + + double get_adjustment_factor(sensor_memory_t *sensors); + uint32_t serialize(char *buf); + + uint8_t flags; + uint8_t sid; + uint8_t point_count; + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; }; #define SENSOR_ADJUSTMENT_SIZE (3 + (SENSOR_ADJUSTMENT_POINTS * sizeof(sensor_adjustment_point_t))) diff --git a/utils.cpp b/utils.cpp index 49495da05..23cf94a46 100644 --- a/utils.cpp +++ b/utils.cpp @@ -312,7 +312,7 @@ bool file_exists(const char *fn) { } os_file_type file_open(const char *fn, FileOpenMode mode) { - #if defined(ARDUINO) + #if defined(ESP8266) switch (mode) { default: case FileOpenMode::Read: @@ -358,7 +358,7 @@ os_file_type file_open(const char *fn, FileOpenMode mode) { } void file_close(os_file_type f) { - #if defined(ARDUINO) + #if defined(ESP8266) f.close(); #else fclose(f); @@ -366,7 +366,7 @@ void file_close(os_file_type f) { } bool file_seek(os_file_type f, uint32_t position, FileSeekMode mode) { - #if defined(ARDUINO) + #if defined(ESP8266) switch (mode) { case FileSeekMode::Set: return f.seek(position, fs::SeekMode::SeekSet); @@ -394,7 +394,7 @@ bool file_seek(os_file_type f, uint32_t position) { } int file_read(os_file_type f, void *target, uint32_t len) { - #if defined(ARDUINO) + #if defined(ESP8266) return f.read((uint8_t*)target, len); #else return fread(target, 1, len, f); @@ -402,7 +402,7 @@ int file_read(os_file_type f, void *target, uint32_t len) { } int file_write(os_file_type f, const void *source, uint32_t len) { - #if defined(ARDUINO) + #if defined(ESP8266) return f.write((const uint8_t*)source, len); #else return fwrite(source, 1, len, f); diff --git a/utils.h b/utils.h index ec24265dc..1590927aa 100644 --- a/utils.h +++ b/utils.h @@ -39,7 +39,7 @@ #include "defines.h" -#if defined(ARDUINO) +#if defined(ESP8266) typedef File os_file_type; #else typedef FILE* os_file_type; From 3ed4c55f45aad07f4174faea5c3bcd5013d7eab9 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 17 Nov 2025 21:51:12 -0500 Subject: [PATCH 074/115] fix dec2hexchar issue --- opensprinkler_server.cpp | 5 ----- utils.h | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 732ac695c..afc1bc7a0 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -174,11 +174,6 @@ void send_packet(OTF_PARAMS_DEF) { rewind_ether_buffer(); } -char dec2hexchar(unsigned char dec) { - if(dec<10) return '0'+dec; - else return 'A'+(dec-10); -} - void print_header(OTF_PARAMS_DEF, bool isJson=true, int len=0) { res.writeStatus(200, F("OK")); res.writeHeader(F("Content-Type"), isJson?F("application/json"):F("text/html")); diff --git a/utils.h b/utils.h index 1590927aa..c3104f02e 100644 --- a/utils.h +++ b/utils.h @@ -143,3 +143,5 @@ void str2mac(const char *_str, unsigned char mac[]); BoardType get_board_type(); #endif + +char dec2hexchar(unsigned char dec); \ No newline at end of file From e3487c2dd153ba3d24fb9ac2b0d97e5031c770bc Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 17 Nov 2025 22:12:10 -0500 Subject: [PATCH 075/115] fix macro defines so the firmware compiled in demo --- OpenSprinkler.cpp | 8 ++++++-- ads1115.cpp | 6 +++++- ads1115.h | 6 +++++- i2cd.h | 2 +- main.cpp | 2 ++ opensprinkler_server.cpp | 5 ++++- 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 00e18dc02..9f006c153 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -83,10 +83,12 @@ extern ProgramData pd; extern const char* user_agent_string; extern unsigned char curr_alert_sid; +#if defined(USE_DISPLAY) SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); +#endif #if defined(USE_ADS1115) - ADS1115 *OpenSprinkler::ads1115_devices[4] = {nullptr}; + ADS1115 *OpenSprinkler::ads1115_devices[4] = {nullptr}; #endif #if defined(ESP8266) @@ -692,7 +694,7 @@ bool OpenSprinkler::network_connected(void) { #if defined(OSPI) bool detect_i2c(int addr) { - Bus.detect(addr); + Bus.detect(addr); } #endif @@ -746,12 +748,14 @@ void OpenSprinkler::update_dev() { #endif // end network init functions /** Initialize LCD */ +#if defined(USE_DISPLAY) void OpenSprinkler::lcd_start() { // initialize SSD1306 lcd.init(); lcd.begin(); flash_screen(); } +#endif //extern void flow_isr(); diff --git a/ads1115.cpp b/ads1115.cpp index d024f5355..6cde418c4 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -1,5 +1,7 @@ #include "ads1115.h" +#if defined(USE_SENSORS) + #if defined(ESP8266) ADS1115::ADS1115(uint8_t address, TwoWire& wire) : _address(address), _wire(&wire) {} ADS1115::ADS1115(uint8_t address) : ADS1115(address, Wire) {} @@ -119,4 +121,6 @@ ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf) { this->sensor_index = static_cast(buf[i++]); this->pin = static_cast(buf[i++]); this->sensors = sensors; -} \ No newline at end of file +} + +#endif \ No newline at end of file diff --git a/ads1115.h b/ads1115.h index 06f8508b0..d7ae118f6 100644 --- a/ads1115.h +++ b/ads1115.h @@ -1,5 +1,7 @@ #pragma once +#if defined(USE_SENSORS) + #include #include "sensor.h" @@ -72,4 +74,6 @@ class ADS1115Sensor : public Sensor { uint32_t _serialize_internal(char *buf); ADS1115 **sensors; -}; \ No newline at end of file +}; + +#endif \ No newline at end of file diff --git a/i2cd.h b/i2cd.h index a6d25a251..e4d9d6b0e 100644 --- a/i2cd.h +++ b/i2cd.h @@ -1,6 +1,6 @@ #pragma once -#if !defined(ARDUINO) +#if defined(OSPI) #include #include diff --git a/main.cpp b/main.cpp index f06226803..a6b6852e5 100644 --- a/main.cpp +++ b/main.cpp @@ -1140,9 +1140,11 @@ void check_weather() { } } else if (!os.checkwt_lasttime || (ntz > os.checkwt_lasttime + CHECK_WEATHER_TIMEOUT)) { os.checkwt_lasttime = ntz; + #if defined(USE_DISPLAY) if (!ui_state) { os.lcd_print_line_clear_pgm(PSTR("Check Weather..."),1); } + #endif GetWeather(); } } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index afc1bc7a0..8c458d103 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -842,6 +842,7 @@ void server_change_program(OTF_PARAMS_DEF) { } } + #if defined(USE_SENSORS) char *end; SensorAdjustment *adj = nullptr; @@ -901,6 +902,7 @@ void server_change_program(OTF_PARAMS_DEF) { adj = new SensorAdjustment(flags, sid, point_count, points); os.write_sensor_adjust(adj, pid); delete adj; + #endif if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); char *pv = tmp_buffer+1; @@ -1066,6 +1068,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { } } + #if defined(USE_SENSORS) bfill.emit_p(PSTR("],\"adj\":[")); uint8_t adj_count = 0; @@ -1098,7 +1101,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { DEBUG_PRINT("Failed to open file: "); DEBUG_PRINTLN(SENADJ_FILENAME); } - + #endif bfill.emit_p(PSTR("]}")); } From 6871c270ae4557c14f44c95dde7bfc95586e4efc Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Wed, 19 Nov 2025 17:18:26 -0500 Subject: [PATCH 076/115] fix indentation and fix typo (inital->initial) --- OpenSprinkler.cpp | 14 +++++++------- OpenSprinkler.h | 6 +++--- ads1115.cpp | 6 ++++-- ads1115.h | 11 ++++++----- bfiller.h | 5 +---- defines.h | 1 + opensprinkler_server.cpp | 2 +- sensor.cpp | 6 +++--- sensor.h | 6 +++--- 9 files changed, 29 insertions(+), 28 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 9f006c153..e498ffdef 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -993,12 +993,12 @@ void OpenSprinkler::begin() { #endif #if defined(USE_ADS1115) - for (size_t i = 0; i < 4; i++) { - uint8_t address = 0x48 + i; - if (detect_i2c(address)) { - ads1115_devices[i] = new ADS1115(address); - } - } + for (size_t i = 0; i < 4; i++) { + uint8_t address = 0x48 + i; + if (detect_i2c(address)) { + ads1115_devices[i] = new ADS1115(address); + } + } #endif #if defined(USE_SENSORS) @@ -2559,7 +2559,7 @@ void OpenSprinkler::load_sensors() { sensors[i].interval = sensor->interval; sensors[i].flags = sensor->flags; sensors[i].next_update = 0; - sensors[i].value = sensor->get_inital_value(); + sensors[i].value = sensor->get_initial_value(); delete sensor; } } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 9936b7b13..940926359 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -234,12 +234,12 @@ class OpenSprinkler { #endif #if defined(USE_ADS1115) - static ADS1115 *ads1115_devices[4]; + static ADS1115 *ads1115_devices[4]; #endif #if defined(USE_SENSORS) - static sensor_memory_t sensors[MAX_SENSORS]; - static uint16_t sensor_file_no; + static sensor_memory_t sensors[MAX_SENSORS]; + static uint16_t sensor_file_no; #endif #if defined(OSPI) diff --git a/ads1115.cpp b/ads1115.cpp index 6cde418c4..2b99d2bbd 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -26,7 +26,7 @@ void ADS1115::_write_register(uint8_t reg, uint16_t value) { uint16_t ADS1115::_read_register(uint8_t reg) { this->_wire->beginTransmission(this->_address); this->_wire->write(reg); - if (!this->_wire->endTransmission()) { + if (!this->_wire->endTransmission(false)) { if (this->_wire->requestFrom((int)_address, (int)2) == 2) { uint16_t val = ((uint16_t)this->_wire->read()) << 8; val += (uint16_t)this->_wire->read(); @@ -71,6 +71,8 @@ int16_t ADS1115::get_pin_value(uint8_t pin) { #if defined(ESP8266) yield(); +#else + delay(1); #endif } @@ -96,7 +98,7 @@ void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"default\":\"1\",\"extra\":[]}]}")); } -double ADS1115Sensor::get_inital_value() { +double ADS1115Sensor::get_initial_value() { return 0.0; } diff --git a/ads1115.h b/ads1115.h index d7ae118f6..f474d58a3 100644 --- a/ads1115.h +++ b/ads1115.h @@ -1,5 +1,7 @@ #pragma once +#include "defines.h" + #if defined(USE_SENSORS) #include @@ -21,7 +23,7 @@ class ADS1115 { #else ADS1115(uint8_t address, I2CBus& bus); #endif -ADS1115(uint8_t address); + ADS1115(uint8_t address); int16_t get_pin_value(uint8_t pin); bool begin(); @@ -32,7 +34,7 @@ ADS1115(uint8_t address); void request_pin(uint8_t pin); bool is_busy() { - return (this->_read_register(0x01) & 0x8000) == 0; + return (this->_read_register(0x01) & 0x8000) == 0; } private: @@ -42,7 +44,7 @@ ADS1115(uint8_t address); #else I2CDevice _i2c; uint16_t swap_reg(uint16_t val) { - return (val << 8) | (val >> 8); + return (val << 8) | (val >> 8); } #endif @@ -59,7 +61,6 @@ class ADS1115Sensor : public Sensor { void emit_extra_json(BufferFiller *bfill); static void emit_description_json(BufferFiller *bfill); - SensorType get_sensor_type() { return SensorType::ADS1115; } @@ -67,7 +68,7 @@ class ADS1115Sensor : public Sensor { uint8_t sensor_index; uint8_t pin; - double get_inital_value(); + double get_initial_value(); private: double _get_raw_value(); diff --git a/bfiller.h b/bfiller.h index 9fa64b02b..57774feb8 100644 --- a/bfiller.h +++ b/bfiller.h @@ -1,5 +1,4 @@ -#ifndef _BFILLER_H -#define _BFILLER_H +#pragma once #include "utils.h" @@ -82,5 +81,3 @@ class BufferFiller { va_end(ap); } }; - -#endif //_BFILLER_H \ No newline at end of file diff --git a/defines.h b/defines.h index 9133a20a2..d97d4e240 100755 --- a/defines.h +++ b/defines.h @@ -405,6 +405,7 @@ enum { #define USE_DISPLAY #define USE_ADS1115 #define USE_SENSORS + #elif defined(OSPI) // for OSPi #define OS_HW_VERSION OSPI_HW_VERSION_BASE diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8c458d103..8df20d0e4 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2143,7 +2143,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { os.sensors[sid].interval = interval; os.sensors[sid].flags = flags; os.sensors[sid].next_update = 0; - os.sensors[sid].value = result_sensor->get_inital_value(); + os.sensors[sid].value = result_sensor->get_initial_value(); os.write_sensor(result_sensor, sid); delete result_sensor; diff --git a/sensor.cpp b/sensor.cpp index 8deff966a..cdf69f431 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -435,7 +435,7 @@ void EnsembleSensor::emit_description_json(BufferFiller* bfill) { bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); } -double EnsembleSensor::get_inital_value() { +double EnsembleSensor::get_initial_value() { switch (this->action) { case EnsembleAction::Min: return this->max; @@ -457,7 +457,7 @@ double EnsembleSensor::get_inital_value() { } double EnsembleSensor::_get_raw_value() { - double inital = this->get_inital_value(); + double inital = this->get_initial_value(); uint8_t count = 0; for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { @@ -545,7 +545,7 @@ void WeatherSensor::emit_description_json(BufferFiller* bfill) { bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"default\":\"0\",\"extra\":[]}]}")); } -double WeatherSensor::get_inital_value() { +double WeatherSensor::get_initial_value() { return 0.0; } diff --git a/sensor.h b/sensor.h index d066345d5..45a7359b8 100644 --- a/sensor.h +++ b/sensor.h @@ -104,7 +104,7 @@ class Sensor { uint32_t flags = 0; SensorType virtual get_sensor_type() = 0; - double virtual get_inital_value() = 0; + double virtual get_initial_value() = 0; private: double virtual _get_raw_value() = 0; @@ -149,7 +149,7 @@ class EnsembleSensor : public Sensor { ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; EnsembleAction action; - double get_inital_value(); + double get_initial_value(); private: double _get_raw_value(); @@ -178,7 +178,7 @@ class WeatherSensor : public Sensor { WeatherAction action; - double get_inital_value(); + double get_initial_value(); private: double _get_raw_value(); From 18d46131ebee38030b29c0e9555a1d7c2993f83c Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 22 Nov 2025 12:26:52 -0500 Subject: [PATCH 077/115] fix minor issues with i2cd --- OpenSprinkler.cpp | 2 +- docs/docs/troubleshooting.md | 7 ++++--- i2cd.h | 29 ++++++++++++----------------- 3 files changed, 17 insertions(+), 21 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index e498ffdef..e826917d9 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -694,7 +694,7 @@ bool OpenSprinkler::network_connected(void) { #if defined(OSPI) bool detect_i2c(int addr) { - Bus.detect(addr); + return Bus.detect(addr); } #endif diff --git a/docs/docs/troubleshooting.md b/docs/docs/troubleshooting.md index 0c24cceb3..2464806fb 100644 --- a/docs/docs/troubleshooting.md +++ b/docs/docs/troubleshooting.md @@ -123,9 +123,10 @@ First confirm the expander is detected (see the question above). Note that detec **The expander is recognized, but zones on the expander don't work.** 1. **Confirm detection:** Follow the questions above to verify the expander is detected. -2. **Check the COM (common) wire:** All zones including expanded must have a common wire that goes to the COM terminal on the main controller. A missing/broken COM wire will cause zones to stop working. -3. **Perform a [solenoid resistance test](#wiring-and-solenoids)** to rule out solenoid and wiring issues. -4. **Check zones on the main controller:** If the first eight zones exhibit the same issue, this is likely due to a broken fuse/COM wire; otherwise, it's more likely an expander-specific issue. +2. **Reboot the controller** to allow it to re-detect expanders. Any expander change (connecting, disconnecting, changing DIP) should be done **while the main controller is powered off**. +3. **Check the COM (common) wire:** All zones including expanded must have a common wire that goes to the COM terminal on the main controller. A missing/broken COM wire will cause zones to stop working. +4. **Perform a [solenoid resistance test](#wiring-and-solenoids)** to rule out solenoid and wiring issues. +5. **Check zones on the main controller:** If the first eight zones exhibit the same issue, this is likely due to a broken fuse/COM wire; otherwise, it's more likely an expander-specific issue. --- diff --git a/i2cd.h b/i2cd.h index e4d9d6b0e..b5330fe27 100644 --- a/i2cd.h +++ b/i2cd.h @@ -15,7 +15,11 @@ extern "C" { class I2CBus { public: I2CBus() {} - + ~I2CBus() { + if (_file >= 0) { + close(_file); + } + } int begin(const char *bus) { _file = open(bus, O_RDWR); if (_file < 0) { @@ -28,12 +32,14 @@ class I2CBus { int begin() { return begin(getDefaultBus()); } int send(unsigned char addr, unsigned char reg, unsigned char data) { + if (_file < 0) return -1; int res = ioctl(_file, I2C_SLAVE, addr); if (res < 0) return -1; return i2c_smbus_write_byte_data(_file, reg, data); } int send_transaction(unsigned char addr, unsigned char transaction_id, unsigned char transaction_buffer_length, unsigned char *transaction_buffer) { + if (_file < 0) return -1; int res = ioctl(_file, I2C_SLAVE, addr); if (res < 0) return -1; return i2c_smbus_write_i2c_block_data( @@ -41,24 +47,28 @@ class I2CBus { } int read(unsigned char addr, unsigned char reg, unsigned char length, unsigned char *values) { + if (_file < 0) return -1; int res = ioctl(_file, I2C_SLAVE, addr); if (res < 0) return -1; return i2c_smbus_read_i2c_block_data(_file, reg, length, values); } int send_word(unsigned char addr, unsigned char reg, unsigned short data) { + if (_file < 0) return -1; int res = ioctl(_file, I2C_SLAVE, addr); if (res < 0) return -1; return i2c_smbus_write_word_data(_file, reg, data); } int read_word(unsigned char addr, unsigned char reg) { + if (_file < 0) return -1; int res = ioctl(_file, I2C_SLAVE, addr); if (res < 0) return -1; return i2c_smbus_read_word_data(_file, reg); } int detect(unsigned char addr) { + if (_file < 0) return -1; int res = ioctl(_file, I2C_SLAVE, addr); if (res < 0) return -1; @@ -94,7 +104,7 @@ class I2CDevice { I2CDevice(I2CBus &bus, unsigned char addr) : _addr(addr), _bus(&bus) {} bool detect() { - return _bus->detect(_addr); + return _bus->detect(_addr)==0; } int begin_transaction(unsigned char id) { @@ -159,21 +169,6 @@ class I2CDevice { unsigned char transaction_buffer[32]; unsigned char transaction_buffer_length = 0; - const char *getDefaultBus() { - switch (get_board_type()) { - case BoardType::RaspberryPi_bcm2712: - case BoardType::RaspberryPi_bcm2711: - case BoardType::RaspberryPi_bcm2837: - case BoardType::RaspberryPi_bcm2836: - case BoardType::RaspberryPi_bcm2835: - return "/dev/i2c-1"; - case BoardType::Unknown: - case BoardType::RaspberryPi_Unknown: - default: - return "/dev/i2c-0"; - } - } - int send_transaction() { return _bus->send_transaction(_addr, transaction_id, transaction_buffer_length, transaction_buffer); } From 5c66a48e703651369324a0fc2d4c1f92e23f2742 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 22 Nov 2025 12:40:44 -0500 Subject: [PATCH 078/115] fix ospi compilation error --- i2cd.h | 4 ++-- opensprinkler_server.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/i2cd.h b/i2cd.h index b5330fe27..9aba8dc8b 100644 --- a/i2cd.h +++ b/i2cd.h @@ -15,11 +15,11 @@ extern "C" { class I2CBus { public: I2CBus() {} - ~I2CBus() { + /*~I2CBus() { if (_file >= 0) { close(_file); } - } + }*/ int begin(const char *bus) { _file = open(bus, O_RDWR); if (_file < 0) { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8df20d0e4..4902214cf 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2404,7 +2404,7 @@ void server_clear_sensor_log(OTF_PARAMS_DEF) { template void bfill_enum_values(const char *name) { - static_assert(std::is_enum_v, "T must be an enum type"); + static_assert(std::is_enum::value, "T must be an enum type"); bool needs_comma = false; From baa171c5eb4ea7e8f175ea3c53b225500ac53645 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 6 Dec 2025 12:25:49 -0500 Subject: [PATCH 079/115] make time type consistent (uint32_t) --- Dockerfile | 2 +- I2CRTC.h | 5 +- OpenSprinkler.cpp | 558 +++++++++-------- OpenSprinkler.h | 34 +- RCSwitch.cpp | 2 +- RCSwitch.h | 4 +- SSD1306Display.h | 8 +- TimeLib.cpp | 2 +- ads1115.cpp | 4 +- ads1115.h | 2 +- bfiller.h | 6 +- defines.h | 2 - gpio.h | 2 +- main.cpp | 122 ++-- mqtt.cpp | 12 +- mqtt.h | 56 +- notifier.cpp | 8 +- opensprinkler_server.cpp | 1254 +++++++++++++++++++------------------- program.cpp | 28 +- program.h | 2 +- sensor.cpp | 835 +++++++++---------------- sensor.h | 45 +- types.h | 4 +- utils.cpp | 274 ++++----- utils.h | 50 +- weather.cpp | 6 +- 26 files changed, 1538 insertions(+), 1789 deletions(-) diff --git a/Dockerfile b/Dockerfile index 8b2cf2a54..f7efcbbab 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN echo "deb [signed-by=/usr/share/keyrings/raspberrypi-archive-keyring.gpg] ht ## 1st stage compiles OpenSprinkler code FROM base AS os-build -ARG BUILD_VERSION="DEMO" +ARG BUILD_VERSION="OSPI" ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update && apt-get install -y bash g++ make libmosquittopp-dev libssl-dev libi2c-dev liblgpio-dev diff --git a/I2CRTC.h b/I2CRTC.h index ca575feab..17660e6b4 100644 --- a/I2CRTC.h +++ b/I2CRTC.h @@ -4,8 +4,7 @@ */ -#ifndef I2CRTC_h -#define I2CRTC_h +#pragma once #define DS1307_ADDR 0x68 #define MCP7940_ADDR 0x6F @@ -35,5 +34,3 @@ class I2CRTC extern I2CRTC RTC; -#endif - diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index e826917d9..bf744a63b 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -53,10 +53,10 @@ time_os_t OpenSprinkler::sensor2_on_timer; time_os_t OpenSprinkler::sensor2_off_timer; time_os_t OpenSprinkler::sensor2_active_lasttime; time_os_t OpenSprinkler::raindelay_on_lasttime; -ulong OpenSprinkler::pause_timer; +uint32_t OpenSprinkler::pause_timer; -ulong OpenSprinkler::flowcount_log_start; -ulong OpenSprinkler::flowcount_rt; +uint32_t OpenSprinkler::flowcount_log_start; +uint32_t OpenSprinkler::flowcount_rt; unsigned char OpenSprinkler::button_timeout; time_os_t OpenSprinkler::checkwt_lasttime; time_os_t OpenSprinkler::checkwt_success_lasttime; @@ -613,7 +613,7 @@ unsigned char OpenSprinkler::start_ether() { lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); lcd_print_line_clear_pgm(eth.isW5500 ? PSTR(" [w5500] ") : PSTR(" [enc28j60] "), 2); - ulong timeout = millis()+60000; // 60 seconds time out + uint32_t timeout = millis()+60000; // 60 seconds time out unsigned char timecount = 1; while (!eth.connected() && (long)(millis()-timeout)<0) { // overflow proof DEBUG_PRINT("."); @@ -1002,29 +1002,29 @@ void OpenSprinkler::begin() { #endif #if defined(USE_SENSORS) - lcd.clear(); - lcd.setCursor(0,0); - lcd.print(F("Init sensors")); - - os_file_type file; - uint16_t next = 0; - size_t f; - for (f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - file = open_sensor_log(f, FileOpenMode::Read); - if (file) { - file_read(file, &next, sizeof(next)); - file_close(file); - - if (next < SENSOR_LOG_PER_FILE) break; - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(f); - } - } - if (f == SENSOR_LOG_FILE_COUNT) f -= 1; - sensor_file_no = f; - - os.load_sensors(); + lcd.clear(); + lcd.setCursor(0,0); + lcd.print(F("Init sensors")); + + os_file_type file; + uint16_t next = 0; + size_t f; + for (f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + file = open_sensor_log(f, FileOpenMode::Read); + if (file) { + file_read(file, &next, sizeof(next)); + file_close(file); + + if (next < SENSOR_LOG_PER_FILE) break; + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + } + } + if (f == SENSOR_LOG_FILE_COUNT) f -= 1; + sensor_file_no = f; + + os.load_sensors(); #endif } @@ -1332,7 +1332,7 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { if(status.sensor1) { if(!sensor1_on_timer) { // add minimum of 5 seconds on delay - ulong delay_time = (ulong)iopts[IOPT_SENSOR1_ON_DELAY]*60; + uint32_t delay_time = (uint32_t)iopts[IOPT_SENSOR1_ON_DELAY]*60; sensor1_on_timer = curr_time + (delay_time>5?delay_time:5); sensor1_off_timer = 0; } else { @@ -1342,7 +1342,7 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { } } else { if(!sensor1_off_timer) { - ulong delay_time = (ulong)iopts[IOPT_SENSOR1_OFF_DELAY]*60; + uint32_t delay_time = (uint32_t)iopts[IOPT_SENSOR1_OFF_DELAY]*60; sensor1_off_timer = curr_time + (delay_time>5?delay_time:5); sensor1_on_timer = 0; } else { @@ -1360,7 +1360,7 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { if(status.sensor2) { if(!sensor2_on_timer) { // add minimum of 5 seconds on delay - ulong delay_time = (ulong)iopts[IOPT_SENSOR2_ON_DELAY]*60; + uint32_t delay_time = (uint32_t)iopts[IOPT_SENSOR2_ON_DELAY]*60; sensor2_on_timer = curr_time + (delay_time>5?delay_time:5); sensor2_off_timer = 0; } else { @@ -1370,7 +1370,7 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { } } else { if(!sensor2_off_timer) { - ulong delay_time = (ulong)iopts[IOPT_SENSOR2_OFF_DELAY]*60; + uint32_t delay_time = (uint32_t)iopts[IOPT_SENSOR2_OFF_DELAY]*60; sensor2_off_timer = curr_time + (delay_time>5?delay_time:5); sensor2_on_timer = 0; } else { @@ -1472,10 +1472,10 @@ int OpenSprinkler::detect_exp() { #endif } -/** Convert hex code to ulong integer */ -static ulong hex2ulong(unsigned char *code, unsigned char len) { +/** Convert hex code to uint32_t integer */ +static uint32_t hex2uint32_t(unsigned char *code, unsigned char len) { char c; - ulong v = 0; + uint32_t v = 0; for(unsigned char i=0;itiming = 0; // temporarily set it to 0 if(data->version=='H') { // this is version G rf code data (25 bytes long including version signature at the beginning) - code->on = hex2ulong(data->on, sizeof(data->on)); - code->off = hex2ulong(data->off, sizeof(data->off)); - code->timing = hex2ulong(data->timing, sizeof(data->timing)); - code->protocol = hex2ulong(data->protocol, sizeof(data->protocol)); - code->bitlength = hex2ulong(data->bitlength, sizeof(data->bitlength)); + code->on = hex2uint32_t(data->on, sizeof(data->on)); + code->off = hex2uint32_t(data->off, sizeof(data->off)); + code->timing = hex2uint32_t(data->timing, sizeof(data->timing)); + code->protocol = hex2uint32_t(data->protocol, sizeof(data->protocol)); + code->bitlength = hex2uint32_t(data->bitlength, sizeof(data->bitlength)); } else { // this is classic rf code data (16 bytes long, assuming protocol=1 and bitlength=24) RFStationDataClassic *classic = (RFStationDataClassic*)data; - code->on = hex2ulong(classic->on, sizeof(classic->on)); - code->off = hex2ulong(classic->off, sizeof(classic->off)); - code->timing = hex2ulong(classic->timing, sizeof(classic->timing)); + code->on = hex2uint32_t(classic->on, sizeof(classic->on)); + code->off = hex2uint32_t(classic->off, sizeof(classic->off)); + code->timing = hex2uint32_t(classic->timing, sizeof(classic->timing)); code->protocol = 1; code->bitlength = 24; } @@ -1701,7 +1701,7 @@ unsigned char OpenSprinkler::password_verify(const char *pw) { unsigned char OpenSprinkler::weekday_today() { //return ((unsigned char)weekday()+5)%7; // Time::weekday() assumes Sunday is 1 #if defined(ESP8266) - ulong wd = now_tz() / 86400L; + uint32_t wd = now_tz() / 86400L; return (wd+3) % 7; // Jan 1, 1970 is a Thursday #else time_t t = time(NULL); @@ -1973,8 +1973,8 @@ void OpenSprinkler::switch_remotestation(RemoteIPStationData *data, bool turnon, RemoteIPStationData copy; memcpy((char*)©, (char*)data, sizeof(RemoteIPStationData)); - uint32_t ip4 = hex2ulong(copy.ip, sizeof(copy.ip)); - uint16_t port = (uint16_t)hex2ulong(copy.port, sizeof(copy.port)); + uint32_t ip4 = hex2uint32_t(copy.ip, sizeof(copy.ip)); + uint16_t port = (uint16_t)hex2uint32_t(copy.port, sizeof(copy.port)); unsigned char ip[4]; ip[0] = ip4>>24; @@ -1998,7 +1998,7 @@ void OpenSprinkler::switch_remotestation(RemoteIPStationData *data, bool turnon, } bf.emit_p(PSTR("GET /cm?pw=$O&sid=$D&en=$D&t=$D"), SOPT_PASSWORD, - (int)hex2ulong(copy.sid, sizeof(copy.sid)), + (int)hex2uint32_t(copy.sid, sizeof(copy.sid)), turnon, timer); bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n"), ip[0],ip[1],ip[2],ip[3]); @@ -2038,7 +2038,7 @@ void OpenSprinkler::switch_remotestation(RemoteOTCStationData *data, bool turnon bf.emit_p(PSTR("GET /forward/v1/$S/cm?pw=$O&sid=$D&en=$D&t=$D"), copy.token, SOPT_PASSWORD, - (int)hex2ulong(copy.sid, sizeof(copy.sid)), + (int)hex2uint32_t(copy.sid, sizeof(copy.sid)), turnon, timer); bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $S\r\nConnection:close\r\n"), DEFAULT_OTC_SERVER_APP); @@ -2101,7 +2101,7 @@ void OpenSprinkler::factory_reset() { // reset string options by first wiping the file clean then write default values memset(tmp_buffer, 0, MAX_SOPTS_SIZE); for(int i=0; i=MAX_SOPTS_SIZE) { - file_write_block(SOPTS_FILENAME, buf, (ulong)MAX_SOPTS_SIZE*oid, MAX_SOPTS_SIZE); + file_write_block(SOPTS_FILENAME, buf, (uint32_t)MAX_SOPTS_SIZE*oid, MAX_SOPTS_SIZE); } else { // copy ending 0 too - file_write_block(SOPTS_FILENAME, buf, (ulong)MAX_SOPTS_SIZE*oid, len+1); + file_write_block(SOPTS_FILENAME, buf, (uint32_t)MAX_SOPTS_SIZE*oid, len+1); } return true; } @@ -2498,212 +2497,211 @@ void OpenSprinkler::raindelay_stop() { #if defined(USE_SENSORS) /** Sensor functions */ Sensor *OpenSprinkler::parse_sensor(os_file_type file) { - uint32_t len = 0; - file_read(file, &len, sizeof(len)); + uint32_t len = 0; + file_read(file, &len, sizeof(len)); + + if (len == 0 || len > TMP_BUFFER_SIZE) return nullptr; - if (len == 0 || len > TMP_BUFFER_SIZE) return nullptr; - - file_read(file, tmp_buffer, len); - file_seek(file, TMP_BUFFER_SIZE - len, FileSeekMode::Current); + file_read(file, tmp_buffer, len); + file_seek(file, TMP_BUFFER_SIZE - len, FileSeekMode::Current); - if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) { - return nullptr; - } + if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) { + return nullptr; + } - SensorType sensor_type = static_cast(*tmp_buffer); + SensorType sensor_type = static_cast(*tmp_buffer); - switch (sensor_type) { - case SensorType::Ensemble: - return new EnsembleSensor(os.sensors, (char*)tmp_buffer); - case SensorType::ADS1115: - return new ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); - case SensorType::Weather: - return new WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); - default: - return nullptr; - }; + switch (sensor_type) { + case SensorType::Ensemble: + return new EnsembleSensor(os.sensors, (char*)tmp_buffer); + case SensorType::ADS1115: + return new ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + case SensorType::Weather: + return new WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); + default: + return nullptr; + }; } Sensor *OpenSprinkler::get_sensor(uint8_t index) { - ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; - - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); - if (file) { - file_seek(file, pos, FileSeekMode::Current); - - Sensor *result = parse_sensor(file); - file_close(file); - return result; - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - return nullptr; - } + uint32_t pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + Sensor *result = parse_sensor(file); + file_close(file); + return result; +} else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + return nullptr; + } } os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) { - char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; - sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; - memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); - snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", file_no); + char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; + sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; + memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", file_no); - return file_open(sensor_log_name_buf, mode); + return file_open(sensor_log_name_buf, mode); } void OpenSprinkler::load_sensors() { - Sensor *sensor; - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); - if (file) { - for (size_t i = 0; i < MAX_SENSORS; i++) { - if ((sensor = parse_sensor(file))) { - sensors[i].interval = sensor->interval; - sensors[i].flags = sensor->flags; - sensors[i].next_update = 0; - sensors[i].value = sensor->get_initial_value(); - delete sensor; - } - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } + Sensor *sensor; + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + for (size_t i = 0; i < MAX_SENSORS; i++) { + if ((sensor = parse_sensor(file))) { + sensors[i].interval = sensor->interval; + sensors[i].flags = sensor->flags; + sensors[i].next_update = 0; + sensors[i].value = sensor->get_initial_value(); + delete sensor; + } + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } } void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { - ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; - uint32_t len = 0; + uint32_t pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; + uint32_t len = 0; - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); - if (file) { - if (sensor) { - len = sensor->serialize(tmp_buffer); - } + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); + if (file) { + if (sensor) { + len = sensor->serialize(tmp_buffer); + } - file_seek(file, pos, FileSeekMode::Current); - file_write(file, &len, sizeof(len)); - if (sensor) { - file_write(file, tmp_buffer, len); - } + file_seek(file, pos, FileSeekMode::Current); + file_write(file, &len, sizeof(len)); + if (sensor) { + file_write(file, tmp_buffer, len); + } - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } } void OpenSprinkler::log_sensor(uint8_t sid, float value) { - os_file_type file = open_sensor_log(sensor_file_no, FileOpenMode::ReadWrite); - if (file) { - uint16_t next = 0; - file_read(file, &next, sizeof(next)); - if (next == SENSOR_LOG_PER_FILE) next -= 1; - - time_os_t timestamp = now(); - tmp_buffer[0] = 1; - tmp_buffer[1] = sid; - char *ptr = tmp_buffer + 2; - memcpy(ptr, ×tamp, sizeof(timestamp)); - ptr += sizeof(timestamp); - memcpy(ptr, &value, sizeof(value)); - - uint32_t pos = (next * SENSOR_LOG_ITEM_SIZE); - - - if (next > 0) { - next -= 1; - } else { - next = SENSOR_LOG_PER_FILE; - if (sensor_file_no > 0) { - sensor_file_no -= 1; - } else { - sensor_file_no = SENSOR_LOG_FILE_COUNT - 1; - } - } - - file_seek(file, 0); - file_write(file, &next, sizeof(next)); - - file_seek(file, pos, FileSeekMode::Current); - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_LOG_FILENAME); - } + os_file_type file = open_sensor_log(sensor_file_no, FileOpenMode::ReadWrite); + if (file) { + uint16_t next = 0; + file_read(file, &next, sizeof(next)); + if (next == SENSOR_LOG_PER_FILE) next -= 1; + + time_os_t timestamp = now(); + tmp_buffer[0] = 1; + tmp_buffer[1] = sid; + char *ptr = tmp_buffer + 2; + memcpy(ptr, ×tamp, sizeof(timestamp)); + ptr += sizeof(timestamp); + memcpy(ptr, &value, sizeof(value)); + + uint32_t pos = (next * SENSOR_LOG_ITEM_SIZE); + + if (next > 0) { + next -= 1; + } else { + next = SENSOR_LOG_PER_FILE; + if (sensor_file_no > 0) { + sensor_file_no -= 1; + } else { + sensor_file_no = SENSOR_LOG_FILE_COUNT - 1; + } + } + + file_seek(file, 0); + file_write(file, &next, sizeof(next)); + + file_seek(file, pos, FileSeekMode::Current); + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_LOG_FILENAME); + } } void OpenSprinkler::poll_sensors() { - for (uint8_t i = 0; i < MAX_SENSORS; i++) { - if (sensors[i].interval && sensors[i].flags & (1 << SENSOR_FLAG_ENABLE)) { - if ((long)(millis() - sensors[i].next_update) > 0) { - Sensor *sensor = get_sensor(i); - if (sensor) { - sensors[i].value = sensor->get_new_value(); - delete sensor; - sensors[i].next_update = millis() + (sensors[i].interval * 1000 * 60); - if (sensors[i].flags & (1 << SENSOR_FLAG_LOG)) { - os.log_sensor(i, sensors[i].value); - } - } - } - } - } + for (uint8_t i = 0; i < MAX_SENSORS; i++) { + if (sensors[i].interval && (sensors[i].flags & (1 << SENSOR_FLAG_ENABLE))) { + if ((long)(millis() - sensors[i].next_update) > 0) { + Sensor *sensor = get_sensor(i); + if (sensor) { + sensors[i].value = sensor->get_new_value(); + delete sensor; + sensors[i].next_update = millis() + (sensors[i].interval * 1000 * 60); + if (sensors[i].flags & (1 << SENSOR_FLAG_LOG)) { + os.log_sensor(i, sensors[i].value); + } + } + } + } + } } SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { - if (index > MAX_NUM_PROGRAMS) return nullptr; - ulong pos = SENSOR_ADJUSTMENT_SIZE * index; - - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); - if (file) { - file_seek(file, pos, FileSeekMode::Current); - - file_read(file, tmp_buffer, SENSOR_ADJUSTMENT_SIZE); - - SensorAdjustment *result = new SensorAdjustment(tmp_buffer); - file_close(file); - - if (result->sid == 255) { - delete result; - return nullptr; - } else { - return result; - } - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); - return nullptr; - } + if (index > MAX_NUM_PROGRAMS) return nullptr; + uint32_t pos = SENSOR_ADJUSTMENT_SIZE * index; + + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + file_read(file, tmp_buffer, SENSOR_ADJUSTMENT_SIZE); + + SensorAdjustment *result = new SensorAdjustment(tmp_buffer); + file_close(file); + + if (result->sid == 255) { + delete result; + return nullptr; + } else { + return result; + } + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + return nullptr; + } } void OpenSprinkler::write_sensor_adjust(SensorAdjustment *adj, uint8_t index) { - ulong pos = SENSOR_ADJUSTMENT_SIZE * index; - - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); - if (file) { - file_seek(file, pos, FileSeekMode::Current); - - if (adj) { - ulong len = adj->serialize(tmp_buffer); - file_write(file, tmp_buffer, len); - } else { - tmp_buffer[0] = 0; - file_write(file, tmp_buffer, 1); - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); - } + uint32_t pos = SENSOR_ADJUSTMENT_SIZE * index; + + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + if (adj) { + uint32_t len = adj->serialize(tmp_buffer); + file_write(file, tmp_buffer, len); + } else { + tmp_buffer[0] = 0; + file_write(file, tmp_buffer, 1); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } } double OpenSprinkler::get_sensor_weather_data(WeatherAction action) { - return NAN; // TODO make function for WeatherSensor + return NAN; // TODO make function for WeatherSensor } #endif diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 940926359..72f27ed22 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -279,9 +279,9 @@ class OpenSprinkler { static time_os_t sensor2_off_timer; // time when sensor2 is detected off last time static time_os_t sensor2_active_lasttime; // most recent time sensor1 is activated static time_os_t raindelay_on_lasttime; // time when the most recent rain delay started - static ulong pause_timer; // count down timer in paused state - static ulong flowcount_rt; // flow count (for computing real-time flow rate) - static ulong flowcount_log_start; // starting flow count (for logging) + static uint32_t pause_timer; // count down timer in paused state + static uint32_t flowcount_rt; // flow count (for computing real-time flow rate) + static uint32_t flowcount_log_start; // starting flow count (for logging) static unsigned char button_timeout; // button timeout static time_os_t checkwt_lasttime; // time when weather was checked @@ -369,20 +369,20 @@ class OpenSprinkler { static OTCConfig otc; - // -- Sensor functions - #if defined(USE_SENSORS) - static os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); - static void load_sensors(); - static Sensor *parse_sensor(os_file_type file); - static Sensor *get_sensor(uint8_t index); - static void write_sensor(Sensor *sensor, uint8_t index); - void log_sensor(uint8_t sid, float value); - static void poll_sensors(); - static SensorAdjustment *get_sensor_adjust(uint8_t index); - static void write_sensor_adjust(SensorAdjustment *adj, uint8_t index); - - static double get_sensor_weather_data(WeatherAction action); - #endif + // -- Sensor functions +#if defined(USE_SENSORS) + static os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); + static void load_sensors(); + static Sensor *parse_sensor(os_file_type file); + static Sensor *get_sensor(uint8_t index); + static void write_sensor(Sensor *sensor, uint8_t index); + void log_sensor(uint8_t sid, float value); + static void poll_sensors(); + static SensorAdjustment *get_sensor_adjust(uint8_t index); + static void write_sensor_adjust(SensorAdjustment *adj, uint8_t index); + + static double get_sensor_weather_data(WeatherAction action); +#endif // -- LCD functions #if defined(USE_DISPLAY) diff --git a/RCSwitch.cpp b/RCSwitch.cpp index d821f17a2..1aaf8fadd 100644 --- a/RCSwitch.cpp +++ b/RCSwitch.cpp @@ -173,7 +173,7 @@ void RCSwitch::transmit(HighLow pulses) { uint8_t firstLogicLevel = (this->protocol.invertedSignal) ? LOW : HIGH; uint8_t secondLogicLevel = (this->protocol.invertedSignal) ? HIGH : LOW; - ulong timeout = micros() + this->protocol.pulseLength * pulses.high; + uint32_t timeout = micros() + this->protocol.pulseLength * pulses.high; digitalWrite(this->nTransmitterPin, firstLogicLevel); while(micros() < timeout) { delayMicroseconds(5); diff --git a/RCSwitch.h b/RCSwitch.h index bb81aeedd..f429ca287 100644 --- a/RCSwitch.h +++ b/RCSwitch.h @@ -27,8 +27,7 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#ifndef _RCSwitch_h -#define _RCSwitch_h +#pragma once #include @@ -99,4 +98,3 @@ class RCSwitch { Protocol protocol; }; -#endif diff --git a/SSD1306Display.h b/SSD1306Display.h index 6438f8572..bb8369001 100644 --- a/SSD1306Display.h +++ b/SSD1306Display.h @@ -57,7 +57,7 @@ class SSD1306Display : public SSD1306 { } cx += fontWidth; if (auto_display) - display(); // todo: not very efficient + display(); return 1; } size_t write(const char *s) { @@ -68,7 +68,7 @@ class SSD1306Display : public SSD1306 { drawString(cx, cy, String(s)); cx += fontWidth * nc; if (auto_display) - display(); // todo: not very efficient + display(); return nc; } void createChar(unsigned char idx, PGM_P ptr) { @@ -493,7 +493,7 @@ class SSD1306Display { } cx += fontWidth; if (auto_display) - display(); // todo: not very efficient + display(); return 1; } @@ -508,7 +508,7 @@ class SSD1306Display { auto_display = temp_auto_display; cx += fontWidth * nc; if (auto_display) - display(); // todo: not very efficient + display(); return nc; } diff --git a/TimeLib.cpp b/TimeLib.cpp index b985a0242..5d727e16f 100644 --- a/TimeLib.cpp +++ b/TimeLib.cpp @@ -157,7 +157,7 @@ void breakTime(time_os_t timeInput, tmElements_t &tm){ uint8_t year; uint8_t month, monthLength; uint32_t time; - unsigned long days; + uint32_t days; time = (uint32_t)timeInput; tm.Second = time % 60; diff --git a/ads1115.cpp b/ads1115.cpp index 2b99d2bbd..cefcc2455 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -62,7 +62,7 @@ uint16_t ADS1115::_read_register(uint8_t reg) { int16_t ADS1115::get_pin_value(uint8_t pin) { this->request_pin(pin); - ulong start = millis(); + uint32_t start = millis(); while (this->is_busy()) { // if ((millis() - start) > 11) { if ((millis() - start) > 18) { @@ -84,7 +84,7 @@ void ADS1115::request_pin(uint8_t pin) { this->_write_register(0x01, config); } -ADS1115Sensor::ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : +ADS1115Sensor::ADS1115Sensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : Sensor(interval, min, max, scale, offset, name, unit, flags), sensor_index(sensor_index), pin(pin), diff --git a/ads1115.h b/ads1115.h index f474d58a3..defe8efd8 100644 --- a/ads1115.h +++ b/ads1115.h @@ -55,7 +55,7 @@ class ADS1115 { class ADS1115Sensor : public Sensor { public: - ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(uint32_t interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); ADS1115Sensor(ADS1115 **sensors, char *buf); void emit_extra_json(BufferFiller *bfill); diff --git a/bfiller.h b/bfiller.h index 57774feb8..9cb75ee35 100644 --- a/bfiller.h +++ b/bfiller.h @@ -8,6 +8,7 @@ #include #include #include +#include #endif class BufferFiller { @@ -43,12 +44,13 @@ class BufferFiller { // itoa(va_arg(ap, int), (char*) ptr, 10); // ray snprintf((char*) ptr, len - position(), "%d", va_arg(ap, int)); break; - case 'E': //Double + case 'E': //Double sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); break; case 'L': // ultoa(va_arg(ap, uint32_t), (char*) ptr, 10); - snprintf((char*) ptr, len - position(), "%lu", (unsigned long) va_arg(ap, uint32_t)); + // TODO: check if there is a way to print uint32_t + snprintf((char*) ptr, len - position(), "%" PRIu32, va_arg(ap, uint32_t)); break; case 'S': strcpy((char*) ptr, va_arg(ap, const char*)); diff --git a/defines.h b/defines.h index d97d4e240..e70dfeb52 100755 --- a/defines.h +++ b/defines.h @@ -24,8 +24,6 @@ #define ENABLE_DEBUG // enable serial debug -typedef unsigned long ulong; // TODO: remove and replace by uint32_t for cross-platform consistency - #define TMP_BUFFER_SIZE 320 // scratch buffer size /** Firmware version, hardware version, and maximal values */ diff --git a/gpio.h b/gpio.h index 1710b10b0..45548a206 100644 --- a/gpio.h +++ b/gpio.h @@ -42,7 +42,7 @@ class IOEXP { public: IOEXP(uint8_t addr=255) { address = addr; type = IOEXP_TYPE_NONEXIST; } - virtual ~IOEXP() {} + virtual ~IOEXP() {} virtual void pinMode(uint8_t pin, uint8_t IOMode) { } virtual uint16_t i2c_read(uint8_t reg) { return 0xFFFF; } diff --git a/main.cpp b/main.cpp index a6b6852e5..cfdc6e6ac 100644 --- a/main.cpp +++ b/main.cpp @@ -40,7 +40,7 @@ Wiznet5500lwIP w5500(PIN_ETHER_CS); // W5500 lwip for wired Ether lwipEth eth; bool useEth = false; // tracks whether we are using WiFi or wired Ether connection - unsigned long getNtpTime(); + uint32_t getNtpTime(); #else // header and defs for RPI/Linux bool useEth = false; #endif @@ -88,8 +88,8 @@ NotifQueue notif; // NotifQueue object * flow_stop - time when valve turns off (last rising edge pulse detected before off) * flow_gallons - total # of gallons+1 from flow_start to flow_stop * flow_last_gpm - last flow rate measured (averaged over flow_gallons) from last valve stopped (used to write to log file). */ -ulong flow_begin, flow_start, flow_stop, flow_gallons, flow_rt_reset, last_flow_rt; -ulong flow_count = 0; +uint32_t flow_begin, flow_start, flow_stop, flow_gallons, flow_rt_reset, last_flow_rt; +uint32_t flow_count = 0; unsigned char prev_flow_state = HIGH; float flow_last_gpm = 0; int32_t flow_rt_period = -1; @@ -97,7 +97,7 @@ uint32_t reboot_timer = 0; unsigned char curr_alert_sid = 0; void flow_poll() { - ulong curr = millis(); + uint32_t curr = millis(); // Resets counter if timeout occurs if (flow_rt_reset && curr > flow_rt_reset) { @@ -142,7 +142,7 @@ void flow_poll() { } // Use exponential moving average (alpha=0.2) if flow has been previosuly calculated, otherwise just set the value - ulong curr_period = curr - last_flow_rt; + uint32_t curr_period = curr - last_flow_rt; if (flow_rt_period > 0) { flow_rt_period = (curr_period / 5 + flow_rt_period * 4 / 5); } else { @@ -151,7 +151,7 @@ void flow_poll() { // calculates the flow rate scaled by the window size to simulated a fixed point number if (flow_rt_period > 0) { - os.flowcount_rt = (ulong) (FLOWCOUNT_RT_WINDOW * 1000L / flow_rt_period); + os.flowcount_rt = (uint32_t) (FLOWCOUNT_RT_WINDOW * 1000L / flow_rt_period); // Sets the timeout to be 10x the last period flow_rt_reset = curr + (curr - last_flow_rt) * 10; } else { @@ -182,7 +182,7 @@ bool ui_confirm(PGM_P str) { os.lcd_print_line_clear_pgm(str, 0); os.lcd_print_line_clear_pgm(PSTR("(B1:No, B3:Yes)"), 1); unsigned char button; - ulong start = millis(); + uint32_t start = millis(); do { button = os.button_read(BUTTON_WAIT_NONE); if((button&BUTTON_MASK)==BUTTON_3 && (button&BUTTON_FLAG_DOWN)) return true; @@ -200,9 +200,9 @@ void ui_state_machine() { last_usm = millis(); // process screen led - static ulong led_toggle_prev = 0; + static uint32_t led_toggle_prev = 0; if(led_blink_ms) { - ulong tm = millis(); + uint32_t tm = millis(); if(tm - led_toggle_prev > led_blink_ms) { // overflow proof timeout os.toggle_screen_led(); led_toggle_prev = tm; @@ -459,7 +459,7 @@ void do_setup() { #endif -void turn_on_station(unsigned char sid, ulong duration); +void turn_on_station(unsigned char sid, uint32_t duration); static void check_network(); void check_weather(); static bool process_special_program_command(const char*, uint32_t curr_time); @@ -482,7 +482,7 @@ void reboot_in(uint32_t ms) { void handle_web_request(char *p); #endif -ulong currpoll_timeout = 0; +uint32_t currpoll_timeout = 0; void overcurrent_monitor() { #if defined(ESP8266) // If a zone is turning on, do immediate overcurrent monitoring here for ~50ms @@ -513,11 +513,11 @@ void overcurrent_monitor() { /** Main Loop */ void do_loop() { - static ulong flowpoll_timeout = 0; + static uint32_t flowpoll_timeout = 0; if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { // handle flow sensor using polling. Maximum freq is 1/(2*FLOWPOLL_INTERVAL) // e.g. if FLOWPOLL_INTERVAL is 3ms, maximum freq is 166Hz - ulong tm = millis(); + uint32_t tm = millis(); if((long)(tm-flowpoll_timeout) > 0) { // overflow proof timeout flowpoll_timeout = tm+FLOWPOLL_INTERVAL; flow_poll(); @@ -530,7 +530,7 @@ void do_loop() #if defined(ESP8266) { - ulong tn = millis(); + uint32_t tn = millis(); if((long)(tn-currpoll_timeout) > 0) { // overflow proof timeout int16_t curr = (int16_t)os.read_current(); int16_t imax = os.get_imax(); @@ -547,7 +547,7 @@ void do_loop() #endif static time_os_t last_time = 0; - static ulong last_minute = 0; + static uint32_t last_minute = 0; unsigned char bid, sid, s, pid, qid, gid, bitvalue; ProgramStruct prog; @@ -558,7 +558,7 @@ void do_loop() // ====== Process Ethernet packets ====== #if defined(ESP8266) // Process Ethernet packets for Arduino - static ulong connecting_timeout; + static uint32_t connecting_timeout; switch(os.state) { case OS_STATE_INITIAL: if(useEth) { @@ -756,7 +756,7 @@ void do_loop() } // ====== Schedule program data ====== - ulong curr_minute = curr_time / 60; + uint32_t curr_minute = curr_time / 60; boolean match_found = false; RuntimeQueueStruct *q; // since the granularity of start time is minute @@ -769,17 +769,14 @@ void do_loop() // check through all programs for(pid=0; pidget_adjustment_factor(os.sensors); - delete adj; - } - #else - double adjustment = 1.0; - #endif + double sensor_adj = 1.0; + #if defined(USE_SENSORS) + SensorAdjustment *adj = os.get_sensor_adjust(pid); + if (adj) { + sensor_adj = adj->get_adjustment_factor(os.sensors); + delete adj; + } + #endif bool will_delete = false; unsigned char runcount = prog.check_match(curr_time, &will_delete); if(runcount>0) { @@ -818,13 +815,14 @@ void do_loop() if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) continue; - ulong dur = (ulong)((double)prog.durations[sid] * adjustment); + // TODO: compare with old code + uint32_t dur = (uint32_t)((double)prog.durations[sid] * sensor_adj); // if station has non-zero water time and the station is not disabled if (dur && !(os.attrib_dis[bid]&(1<st + q->dur; - ulong remainder = 0; + uint32_t remainder = 0; if (q_end_time > curr_time) { // remainder is non-zero remainder = (q->st < curr_time) ? q_end_time - curr_time : q->dur; @@ -1347,7 +1345,7 @@ void process_dynamic_events(time_os_t curr_time) { * this function determines the appropriate start and dequeue times * of stations bound to master stations with on and off adjustments */ -void handle_master_adjustments(time_os_t curr_time, RuntimeQueueStruct *q, unsigned char gid, ulong *seq_start_times) { +void handle_master_adjustments(time_os_t curr_time, RuntimeQueueStruct *q, unsigned char gid, uint32_t *seq_start_times) { int16_t start_adj = 0; int16_t dequeue_adj = 0; @@ -1383,7 +1381,7 @@ void handle_master_adjustments(time_os_t curr_time, RuntimeQueueStruct *q, unsig * preemptively, before existing queued stations */ void schedule_all_stations(time_os_t curr_time, unsigned char qo) { - ulong con_start_time = curr_time; // concurrent start time + uint32_t con_start_time = curr_time; // concurrent start time // if the queue is paused, make sure the start time is after the scheduled pause ends if (os.status.pause_state) { con_start_time += os.pause_timer; @@ -1404,8 +1402,8 @@ void schedule_all_stations(time_os_t curr_time, unsigned char qo) { stagger[i] += stagger[i-1]; // accumulate stagger time } - ulong seq_start_times[NUM_SEQ_GROUPS]; // sequential start times - ulong seq_adjustments[NUM_SEQ_GROUPS]; // adjustment amounts for insert-to-front + uint32_t seq_start_times[NUM_SEQ_GROUPS]; // sequential start times + uint32_t seq_adjustments[NUM_SEQ_GROUPS]; // adjustment amounts for insert-to-front memset(seq_adjustments, 0, sizeof(seq_adjustments)); unsigned char re = os.iopts[IOPT_REMOTE_EXT_MODE]; @@ -1433,14 +1431,14 @@ void schedule_all_stations(time_os_t curr_time, unsigned char qo) { if (!os.is_sequential_station(q->sid) || re) continue; gid = os.get_station_gid(q->sid); - ulong adjustment = seq_adjustments[gid] + stagger[gid]; + uint32_t adjustment = seq_adjustments[gid] + stagger[gid]; if (adjustment == 0) continue; // no adjustment needed for this group // Only adjust sequential stations in the same group // If station is currently running if (curr_time >= q->st && curr_time < q->st + q->dur) { turn_off_station(q->sid, curr_time); // TODO: double check the logic - ulong remaining = q->dur - (curr_time - q->st); + uint32_t remaining = q->dur - (curr_time - q->st); q->st = curr_time + adjustment; q->dur = remaining; q->deque_time += adjustment; @@ -1594,8 +1592,8 @@ void reset_all_stations(bool running_ones_only) { void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo) { boolean match_found = false; ProgramStruct prog; - ulong dur; - double adjustment = 1.0; + uint32_t dur; + double sensor_adj = 1.0; unsigned char sid, bid, s; unsigned char ns = os.nstations; unsigned char order[ns]; @@ -1607,13 +1605,13 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo unsigned char wl = 100; if ((pid>0)&&(pid<255)) { pd.read(pid-1, &prog); - #if defined(USE_SENSORS) - SensorAdjustment *adj = os.get_sensor_adjust(pid-1); - if (adj) { - adjustment = adj->get_adjustment_factor(os.sensors); - delete adj; - } - #endif + #if defined(USE_SENSORS) + SensorAdjustment *adj = os.get_sensor_adjust(pid-1); + if (adj) { + sensor_adj = adj->get_adjustment_factor(os.sensors); + delete adj; + } + #endif if(uwt) wl = os.iopts[IOPT_WATER_PERCENTAGE]; notif.add(NOTIFY_PROGRAM_SCHED, pid-1, wl, 1); // get station ordering from program name @@ -1629,11 +1627,11 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo continue; dur = 60; if(pid==255) { - dur=2; - } else if (pid>0) { + dur=2; + } else if (pid>0) { dur = water_time_resolve(prog.durations[sid]); - dur = (ulong)((double)dur * adjustment); - } + dur = (uint32_t)((double)dur * sensor_adj); + } dur = dur * wl / 100; if(dur>0 && !(os.attrib_dis[bid]&(1<os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0; } size_t size = strlen(tmp_buffer); - snprintf(tmp_buffer + size, TMP_BUFFER_SIZE - size , "%lu", lvalue); + snprintf(tmp_buffer + size, TMP_BUFFER_SIZE - size , "%" PRIu32, lvalue); strcat_P(tmp_buffer, PSTR(",\"")); strcat_P(tmp_buffer, log_type_names+type*3); strcat_P(tmp_buffer, PSTR("\",")); @@ -1771,11 +1769,11 @@ void write_log(unsigned char type, time_os_t curr_time) { break; } size = strlen(tmp_buffer); - snprintf(tmp_buffer + size, TMP_BUFFER_SIZE - size , "%lu", lvalue); + snprintf(tmp_buffer + size, TMP_BUFFER_SIZE - size , "%" PRIu32, lvalue); } strcat_P(tmp_buffer, PSTR(",")); size_t size = strlen(tmp_buffer); - snprintf(tmp_buffer + size, TMP_BUFFER_SIZE - size , "%lu", curr_time); + snprintf(tmp_buffer + size, TMP_BUFFER_SIZE - size , "%" PRIu32, (uint32_t)curr_time); if((os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) && (type==LOGDATA_STATION)) { // RAH implementation of flow sensor strcat_P(tmp_buffer, PSTR(",")); @@ -1873,8 +1871,8 @@ static void perform_ntp_sync() { os.lcd_print_line_clear_pgm(PSTR("NTP Syncing..."),1); } DEBUG_PRINTLN(F("NTP Syncing...")); - static ulong last_ntp_result = 0; - ulong t = getNtpTime(); + static uint32_t last_ntp_result = 0; + uint32_t t = getNtpTime(); if(last_ntp_result>3 && t>last_ntp_result-3 && t - #define DEBUG_TIMESTAMP() {char tstr[21]; time_os_t t = time(NULL); struct tm *tm = localtime(&t); strftime(tstr, 21, "%y-%m-%d %H:%M:%S - ", tm);printf("%s", tstr);} + #define DEBUG_TIMESTAMP() {char tstr[21]; time_t t = time(NULL); struct tm *tm = localtime(&t); strftime(tstr, 21, "%y-%m-%d %H:%M:%S - ", tm);printf("%s", tstr);} #endif #define DEBUG_LOGF(msg, ...) {DEBUG_TIMESTAMP(); DEBUG_PRINTF(msg, ##__VA_ARGS__);} - static unsigned long _lastMillis = 0; // Holds the timestamp associated with the last call to DEBUG_DURATION() - inline unsigned long DEBUG_DURATION() {unsigned long dur = millis() - _lastMillis; _lastMillis = millis(); return dur;} + static uint32_t _lastMillis = 0; // Holds the timestamp associated with the last call to DEBUG_DURATION() + inline uint32_t DEBUG_DURATION() {uint32_t dur = millis() - _lastMillis; _lastMillis = millis(); return dur;} #else #define DEBUG_LOGF(msg, ...) {} #define DEBUG_DURATION() {} @@ -146,7 +146,7 @@ void changeValues(char *message){ if(findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rd"), true)){ int rd = atoi(tmp_buffer); if(rd>0){ - os.nvdata.rd_stop_time = os.now_tz() + (unsigned long) rd * 3600; + os.nvdata.rd_stop_time = os.now_tz() + (uint32_t) rd * 3600; os.raindelay_start(); }else if (rd==0){ os.raindelay_stop(); @@ -177,7 +177,7 @@ void manualRun(char *message){ } uint16_t timer = 0; - unsigned long curr_time = os.now_tz(); + uint32_t curr_time = os.now_tz(); if(en){ if(findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, PSTR("t"), true)){ timer = (uint16_t)atol(tmp_buffer); @@ -438,7 +438,7 @@ void OSMqtt::subscribe(void){ // Regularly call the loop function to ensure "keep alive" messages are sent to the broker and to reconnect if needed. void OSMqtt::loop(void) { - static unsigned long last_reconnect_attempt = 0; + static uint32_t last_reconnect_attempt = 0; if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return; diff --git a/mqtt.h b/mqtt.h index d39cdd383..463a19d23 100644 --- a/mqtt.h +++ b/mqtt.h @@ -25,34 +25,34 @@ class OSMqtt { private: - static char _id[]; - static char _host[]; - static int _port; - static char _username[]; - static char _password[]; - static bool _enabled; - static char _pub_topic[]; - static char _sub_topic[]; - static bool _done_subscribed; + static char _id[]; + static char _host[]; + static int _port; + static char _username[]; + static char _password[]; + static bool _enabled; + static char _pub_topic[]; + static char _sub_topic[]; + static bool _done_subscribed; - // Following routines are platform specific versions of the public interface - static int _init(void); - static int _connect(void); - static int _disconnect(void); - static bool _connected(void); - static int _publish(const char *topic, const char *payload); - static int _subscribe(void); - static int _loop(void); - static const char * _state_string(int state); - public: - static void init(void); - static void init(const char * id); - static void begin(void); - static bool enabled(void) { return _enabled; }; - static void publish(const char *topic, const char *payload); - static void subscribe(); - static void loop(void); - static char* get_pub_topic() { return _pub_topic; } - static char* get_sub_topic() { return _sub_topic; } + // Following routines are platform specific versions of the public interface + static int _init(void); + static int _connect(void); + static int _disconnect(void); + static bool _connected(void); + static int _publish(const char *topic, const char *payload); + static int _subscribe(void); + static int _loop(void); + static const char * _state_string(int state); + public: + static void init(void); + static void init(const char * id); + static void begin(void); + static bool enabled(void) { return _enabled; }; + static void publish(const char *topic, const char *payload); + static void subscribe(); + static void loop(void); + static char* get_pub_topic() { return _pub_topic; } + static char* get_sub_topic() { return _sub_topic; } }; diff --git a/notifier.cpp b/notifier.cpp index 08b096b6e..4010fdd90 100644 --- a/notifier.cpp +++ b/notifier.cpp @@ -172,7 +172,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { #endif bool email_enabled = false; - if(!email_en){ // todo: this should be simplified + if(!email_en){ email_enabled = false; }else{ email_enabled = true; @@ -316,7 +316,8 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), 1970+tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); #else - struct tm *ti = gmtime(&curr_time); + time_t _ct = curr_time; + struct tm *ti = gmtime(&_ct); snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), ti->tm_year+1900, ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec); #endif @@ -471,7 +472,8 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), 1970+tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); #else - struct tm *ti = gmtime(&curr_time); + time_t _ct = curr_time; + struct tm *ti = gmtime(&_ct); snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), ti->tm_year+1900, ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec); #endif diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 4902214cf..116eaafaf 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -55,7 +55,7 @@ extern char ether_buffer[]; extern char tmp_buffer[]; extern OpenSprinkler os; extern ProgramData pd; -extern ulong flow_count; +extern uint32_t flow_count; BufferFiller bfill; @@ -204,7 +204,7 @@ String two_digits(uint8_t x) { } #endif -String toHMS(ulong t) { +String toHMS(uint32_t t) { return two_digits(t/3600)+":"+two_digits((t/60)%60)+":"+two_digits(t%60); } @@ -646,7 +646,7 @@ void server_change_runonce(OTF_PARAMS_DEF) { if(prog.starttimes[2] < 1){ handle_return(HTML_DATA_OUTOFBOUND); } - unsigned long curr_time = os.now_tz(); + uint32_t curr_time = os.now_tz(); curr_time = (curr_time / 60) + prog.starttimes[2] + 1; //time in minutes for one interval past current time uint16_t epoch_t = curr_time / 1440; @@ -743,12 +743,12 @@ void server_delete_program(OTF_PARAMS_DEF) { pd.eraseall(); } else if (pid < pd.nprograms) { if (pd.del(pid)) { - #if defined(USE_SENSORS) - for (size_t i = pid; i < pd.nprograms; i++) { - file_copy_block(SENADJ_FILENAME, SENSOR_ADJUSTMENT_SIZE * (i+1), SENSOR_ADJUSTMENT_SIZE * i, SENSOR_ADJUSTMENT_SIZE, tmp_buffer); - } - #endif - } + #if defined(USE_SENSORS) + for (int i = pid; i < pd.nprograms-1; i++) { + file_copy_block(SENADJ_FILENAME, SENSOR_ADJUSTMENT_SIZE * (i+1), SENSOR_ADJUSTMENT_SIZE * i, SENSOR_ADJUSTMENT_SIZE, tmp_buffer); + } + #endif + } } else { handle_return(HTML_DATA_OUTOFBOUND); } @@ -843,65 +843,61 @@ void server_change_program(OTF_PARAMS_DEF) { } #if defined(USE_SENSORS) - char *end; - - SensorAdjustment *adj = nullptr; - unsigned long flags = 0; - unsigned long sid = 255; - unsigned long point_count = 0; - sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; - - if ((adj = os.get_sensor_adjust(pid))) { - flags = adj->flags; - sid = adj->sid; - point_count = adj->point_count; - - for (size_t i = 0; i <= point_count; i++) { - points[i] = adj->points[i]; - } - delete adj; - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flags"), true)) { - flags=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + char *end; + + SensorAdjustment *adj = nullptr; + uint32_t flags = 0; + uint32_t sid = 255; + uint32_t point_count = 0; + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; + + if ((adj = os.get_sensor_adjust(pid))) { + flags = adj->flags; + sid = adj->sid; + point_count = adj->point_count; + + for (size_t i = 0; i <= point_count; i++) { // TODO: <=? + points[i] = adj->points[i]; + } + delete adj; } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flags"), true)) { + flags=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); +} - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_sid"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_sid"), true)) { sid=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (sid >= MAX_SENSORS) sid = 255; - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_points"), true)) { - unsigned long i = 0; - double x, y; - const char *ptr = tmp_buffer; - int result; - double last_x = -std::numeric_limits::infinity();; - - while (*ptr != '\0') { - if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); - - result = sscanf(ptr, "%lf,%lf;", &x, &y); - - if (result != 2 || x <= last_x) { - handle_return(HTML_DATA_FORMATERROR); - } - - points[i++] = sensor_adjustment_point_t {x, y}; + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (sid >= MAX_SENSORS) sid = 255; +} - last_x = x; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_points"), true)) { + uint32_t i = 0; + double x, y; + const char *ptr = tmp_buffer; + int result; + double last_x = -std::numeric_limits::infinity();; - while (*(ptr++) != ';') {} - } + while (*ptr != '\0') { + if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); + result = sscanf(ptr, "%lf,%lf;", &x, &y); + if (result != 2 || x <= last_x) { + handle_return(HTML_DATA_FORMATERROR); + } + points[i++] = sensor_adjustment_point_t {x, y}; + last_x = x; + while (*(ptr++) != ';') {} + } + point_count = i; + } - point_count = i; - } + // TODO: convert to stack space allocation + adj = new SensorAdjustment(flags, sid, point_count, points); + os.write_sensor_adjust(adj, pid); + delete adj; - adj = new SensorAdjustment(flags, sid, point_count, points); - os.write_sensor_adjust(adj, pid); - delete adj; #endif if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); @@ -1069,38 +1065,36 @@ void server_json_programs_main(OTF_PARAMS_DEF) { } #if defined(USE_SENSORS) - bfill.emit_p(PSTR("],\"adj\":[")); - uint8_t adj_count = 0; - - SensorAdjustment *adj; - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); - if (file) { - for (size_t i = 0; i < pd.nprograms; i++) { - if ((adj = os.get_sensor_adjust(i))) { - if (adj_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); - for (int j = 0; j < adj->point_count; j++) { - if (j) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); - } - bfill.emit_p(PSTR("]}")); - adj_count += 1; - delete adj; - - - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - } - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); - } + bfill.emit_p(PSTR("],\"adj\":[")); + uint8_t adj_count = 0; + + SensorAdjustment *adj; + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + for (size_t i = 0; i < pd.nprograms; i++) { + if ((adj = os.get_sensor_adjust(i))) { + if (adj_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); + for (int j = 0; j < adj->point_count; j++) { + if (j) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); + } + bfill.emit_p(PSTR("]}")); + adj_count += 1; + delete adj; + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } #endif bfill.emit_p(PSTR("]}")); } @@ -1223,7 +1217,7 @@ void server_json_controller_main(OTF_PARAMS_DEF) { if(available_ether_buffer() <= 0) { send_packet(OTF_PARAMS); } - unsigned long rem = 0; + uint32_t rem = 0; unsigned char qid = pd.station_qid[sid]; RuntimeQueueStruct *q = pd.queue + qid; if (qid<255) { @@ -1327,7 +1321,7 @@ void server_change_values(OTF_PARAMS_DEF) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rd"), true)) { int rd = atoi(tmp_buffer); if (rd>0) { - os.nvdata.rd_stop_time = os.now_tz() + (unsigned long) rd * 3600; + os.nvdata.rd_stop_time = os.now_tz() + (uint32_t) rd * 3600; os.raindelay_start(); } else if (rd==0){ os.raindelay_stop(); @@ -1523,7 +1517,7 @@ void server_change_options(OTF_PARAMS_DEF) // if not using NTP and manually setting time if (!os.iopts[IOPT_USE_NTP] && findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ttt"), true)) { #if defined(ESP8266) - unsigned long t; + uint32_t t; t = strtoul(tmp_buffer, NULL, 0); #endif // before chaging time, reset all stations to avoid messing up with timing @@ -1644,7 +1638,7 @@ void server_change_manual(OTF_PARAMS_DEF) { } uint16_t timer=0; - unsigned long curr_time = os.now_tz(); + uint32_t curr_time = os.now_tz(); if (en) { // if turning on a station, must provide timer if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("t"), true)) { timer=(uint16_t)atol(tmp_buffer); @@ -1860,7 +1854,7 @@ void server_delete_log(OTF_PARAMS_DEF) { void server_pause_queue(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - ulong duration = 0; + uint32_t duration = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("repl"), true)) { duration = strtoul(tmp_buffer, NULL, 0); pd.resume_stations(); @@ -1886,26 +1880,24 @@ void server_pause_queue(OTF_PARAMS_DEF) { #if defined(USE_SENSORS) void server_json_sensors_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"sn\":[")); - uint8_t sensor_count = 0; - - Sensor *sensor; - for (size_t i = 0; i < MAX_SENSORS; i++) { - if (os.sensors[i].interval && (sensor = os.get_sensor(i))) { - if (sensor_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); - sensor->emit_extra_json(&bfill); - bfill.emit_p(PSTR("}")); - sensor_count += 1; - delete sensor; - - - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - } - } + uint8_t sensor_count = 0; + + Sensor *sensor; + for (size_t i = 0; i < MAX_SENSORS; i++) { + if (os.sensors[i].interval && (sensor = os.get_sensor(i))) { + if (sensor_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); + sensor->emit_extra_json(&bfill); + bfill.emit_p(PSTR("}")); + sensor_count += 1; + delete sensor; + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } + } bfill.emit_p(PSTR("],\"count\":$D}"), sensor_count); @@ -1927,226 +1919,223 @@ void server_change_sensor(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) handle_return(HTML_DATA_MISSING); - char *end; + char *end; long sid = strtol(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (sid < -1 || sid >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); - if (sid == -1 ) { - while (++sid < MAX_SENSORS) { - if (!os.sensors[sid].interval) { - break; - } - } - - if (sid == MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); - } - - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); - - ulong type_raw = strtol(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (type_raw >= (ulong)SensorType::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); - - SensorType sensor_type = static_cast(type_raw); - - Sensor *sensor = nullptr; - double min = 0; - double max = 100; - double scale = 1; - double offset = 0; - ulong interval = 1000; - SensorUnit unit = SensorUnit::None; - uint32_t flags = 0; - - char name[SENSOR_NAME_LEN]; - snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); - - SensorType original_sensor_type = SensorType::MAX_VALUE; - if (os.sensors[sid].interval) { - if ((sensor = os.get_sensor(sid))) { - original_sensor_type = sensor->get_sensor_type(); - strncpy(name, sensor->name, SENSOR_NAME_LEN); - min = sensor->min; - max = sensor->max; - scale = sensor->scale; - offset = sensor->offset; - interval = sensor->interval; - unit = sensor->unit; - flags = sensor->flags; - delete sensor; - } - } - - // parse sensor name + if (sid == -1 ) { + while (++sid < MAX_SENSORS) { + if (!os.sensors[sid].interval) { + break; + } + } + + if (sid == MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + } + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); + + uint32_t type_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (type_raw >= (uint32_t)SensorType::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + + SensorType sensor_type = static_cast(type_raw); + + Sensor *sensor = nullptr; + double min = 0; + double max = 100; + double scale = 1; + double offset = 0; + uint32_t interval = 1000; + SensorUnit unit = SensorUnit::None; + uint32_t flags = 0; + + char name[SENSOR_NAME_LEN]; + snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); + + SensorType original_sensor_type = SensorType::MAX_VALUE; + if (os.sensors[sid].interval) { + if ((sensor = os.get_sensor(sid))) { + original_sensor_type = sensor->get_sensor_type(); + strncpy(name, sensor->name, SENSOR_NAME_LEN); + min = sensor->min; + max = sensor->max; + scale = sensor->scale; + offset = sensor->offset; + interval = sensor->interval; + unit = sensor->unit; + flags = sensor->flags; + delete sensor; + } + } + + // parse sensor name if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) { strReplaceQuoteBackslash(tmp_buffer); strncpy(name, tmp_buffer, SENSOR_NAME_LEN); } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) { min=strtod(tmp_buffer, &end); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); +} - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) { max=strtod(tmp_buffer, &end); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } - - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { scale=strtod(tmp_buffer, &end); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) { offset=strtod(tmp_buffer, &end); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("interval"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("interval"), true)) { interval=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } - - if (interval < 1) handle_return(HTML_DATA_OUTOFBOUND); - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { - ulong unit_raw = strtol(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (unit_raw >= (ulong)SensorUnit::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); - unit = static_cast(unit_raw); - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flags"), true)) { - flags = strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } - - Sensor *result_sensor; - switch (sensor_type) { - case SensorType::Ensemble: { - uint8_t children_count = 0; - ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - children[i].sensor_id = 255; - } - - EnsembleAction action = EnsembleAction::Min; - - if (sensor_type == original_sensor_type) { - if ((sensor = os.get_sensor(sid))) { - EnsembleSensor* e = static_cast(sensor); - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - children[i] = e->children[i]; - } - - action = e->action; - delete sensor; - } - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("children"), true)) { - unsigned int i = 0; - int d; - double d1, d2, d3, d4; - const char *ptr = tmp_buffer; - int result; - - while (*ptr != '\0') { - if (i >= ENSEMBLE_SENSOR_CHILDREN_COUNT) handle_return(HTML_DATA_FORMATERROR); - - result = sscanf(ptr, "%d,%lf,%lf,%lf,%lf;", &d, &d1, &d2, &d3, &d4); - - if (result != 5) { - handle_return(HTML_DATA_FORMATERROR); - } - - if (d >= MAX_SENSORS || d < -1) handle_return(HTML_DATA_FORMATERROR); - if (d == -1) d = sid; - - children[i++] = ensemble_children_t {(uint8_t)d, d1, d2, d3, d4}; - - while (*(ptr++) != ';') {} - } - - children_count = i; - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { - ulong action_raw = strtol(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (action_raw >= (ulong)EnsembleAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); - action = static_cast(action_raw); - } - - result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.sensors, children, children_count, action); - break; - } - case SensorType::ADS1115: { - ulong sensor_index = 0; - ulong sensor_pin = 0; - - if (sensor_type == original_sensor_type) { - if ((sensor = os.get_sensor(sid))) { - ADS1115Sensor* e = static_cast(sensor); - sensor_index = e->sensor_index; - sensor_pin = e->pin; - delete sensor; - } - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pin"), true)) { - ulong raw_sensor_pin = strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (raw_sensor_pin == 0 || raw_sensor_pin > 16) handle_return(HTML_DATA_OUTOFBOUND); - raw_sensor_pin -= 1; - sensor_index = raw_sensor_pin >> 2; - sensor_pin = raw_sensor_pin & 0b11; - } - - result_sensor = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.ads1115_devices, sensor_index, sensor_pin); - break; - } - case SensorType::Weather: { - WeatherAction action = WeatherAction::MAX_VALUE; - - if (sensor_type == original_sensor_type) { - if ((sensor = os.get_sensor(sid))) { - WeatherSensor* e = static_cast(sensor); - action = e->action; - delete sensor; - } - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { - ulong action_raw = strtol(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (action_raw >= (ulong)WeatherAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); - action = static_cast(action_raw); - } - - result_sensor = new WeatherSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.get_sensor_weather_data, action); - - break; - } - default: { - handle_return(HTML_DATA_OUTOFBOUND) - break; - } - } - - os.sensors[sid].interval = interval; - os.sensors[sid].flags = flags; - os.sensors[sid].next_update = 0; - os.sensors[sid].value = result_sensor->get_initial_value(); - os.write_sensor(result_sensor, sid); - - delete result_sensor; + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + if (interval < 1) handle_return(HTML_DATA_OUTOFBOUND); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { + uint32_t unit_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (unit_raw >= (uint32_t)SensorUnit::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + unit = static_cast(unit_raw); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flags"), true)) { + flags = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + Sensor *result_sensor; + switch (sensor_type) { + case SensorType::Ensemble: { + uint8_t children_count = 0; + ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + children[i].sensor_id = 255; + } + + EnsembleAction action = EnsembleAction::Min; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + EnsembleSensor* e = static_cast(sensor); + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + children[i] = e->children[i]; + } + + action = e->action; + delete sensor; + } + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("children"), true)) { + unsigned int i = 0; + int d; + double d1, d2, d3, d4; + const char *ptr = tmp_buffer; + int result; + + while (*ptr != '\0') { + if (i >= ENSEMBLE_SENSOR_CHILDREN_COUNT) handle_return(HTML_DATA_FORMATERROR); + + result = sscanf(ptr, "%d,%lf,%lf,%lf,%lf;", &d, &d1, &d2, &d3, &d4); + + if (result != 5) { + handle_return(HTML_DATA_FORMATERROR); + } + + if (d >= MAX_SENSORS || d < -1) handle_return(HTML_DATA_FORMATERROR); + if (d == -1) d = sid; + + children[i++] = ensemble_children_t {(uint8_t)d, d1, d2, d3, d4}; + + while (*(ptr++) != ';') {} + } + + children_count = i; + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { + uint32_t action_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (action_raw >= (uint32_t)EnsembleAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + action = static_cast(action_raw); + } + + result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.sensors, children, children_count, action); + break; + } + case SensorType::ADS1115: { + uint32_t sensor_index = 0; + uint32_t sensor_pin = 0; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + ADS1115Sensor* e = static_cast(sensor); + sensor_index = e->sensor_index; + sensor_pin = e->pin; + delete sensor; + } + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pin"), true)) { + uint32_t raw_sensor_pin = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (raw_sensor_pin == 0 || raw_sensor_pin > 16) handle_return(HTML_DATA_OUTOFBOUND); + raw_sensor_pin -= 1; + sensor_index = raw_sensor_pin >> 2; + sensor_pin = raw_sensor_pin & 0b11; + } + + result_sensor = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.ads1115_devices, sensor_index, sensor_pin); + break; + } + case SensorType::Weather: { + WeatherAction action = WeatherAction::MAX_VALUE; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + WeatherSensor* e = static_cast(sensor); + action = e->action; + delete sensor; + } + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { + uint32_t action_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (action_raw >= (uint32_t)WeatherAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + action = static_cast(action_raw); + } + + result_sensor = new WeatherSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.get_sensor_weather_data, action); + + break; + } + default: { + handle_return(HTML_DATA_OUTOFBOUND) + break; + } + } + + os.sensors[sid].interval = interval; + os.sensors[sid].flags = flags; + os.sensors[sid].next_update = 0; + os.sensors[sid].value = result_sensor->get_initial_value(); + os.write_sensor(result_sensor, sid); + + delete result_sensor; handle_return(HTML_SUCCESS); } @@ -2165,18 +2154,18 @@ void server_delete_sensor(OTF_PARAMS_DEF) { int sid=atoi(tmp_buffer); if (sid == -1) { - uint8_t i; - for (i=0;i 0) { - buf[index++] = (num%10) + '0'; - num /= 10; - } + if (num) { + uint8_t index = 0; + while (num > 0) { + buf[index++] = (num%10) + '0'; + num /= 10; + } - return index; - } else { - buf[0] = '0'; - return 1; - } + return index; + } else { + buf[0] = '0'; + return 1; + } } void server_log_sensor(OTF_PARAMS_DEF) { @@ -2204,162 +2193,162 @@ void server_log_sensor(OTF_PARAMS_DEF) { rewind_ether_buffer(); print_header(OTF_PARAMS); - ulong start_time = millis(); - - ulong count = 0; - ulong i; - - char *end; - ulong max_count = 100; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { - max_count = strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (max_count > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); - } - - uint16_t file_no = os.sensor_file_no; - uint16_t next; - - os_file_type file = os.open_sensor_log(file_no, FileOpenMode::Read); - if (file) { - file_read(file, &next, sizeof(next)); - file_close(file); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(file_no); - handle_return(HTML_INTERNAL_ERROR); - } - - if (next == SENSOR_LOG_PER_FILE) { - next = 0; - file_no = (file_no + 1) % SENSOR_LOG_FILE_COUNT; - } else { - next += 1; - } - - ulong cursor = (file_no * SENSOR_LOG_PER_FILE) + next; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("cursor"), true)) { - cursor = strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (cursor > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); - next = cursor % SENSOR_LOG_PER_FILE; - file_no = (cursor - next) / SENSOR_LOG_PER_FILE; - } - - using std::numeric_limits; - time_os_t before = std::numeric_limits::max(); - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) { - before = (time_os_t)strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (before == 0) handle_return(HTML_DATA_OUTOFBOUND); - } - - time_os_t after = 0; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) { - after = (time_os_t)strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (after <= before) handle_return(HTML_DATA_OUTOFBOUND); - } - - long target_sid = -1; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { - target_sid = strtol(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (target_sid >= MAX_SENSORS || target_sid < -1) handle_return(HTML_DATA_OUTOFBOUND); - } - - // Clear out buffer - memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); - - file = os.open_sensor_log(file_no, FileOpenMode::Read); - if (file) { - file_seek(file, sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE), FileSeekMode::Current); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(file_no); - handle_return(HTML_INTERNAL_ERROR); - } - - send_packet(OTF_PARAMS); - - char print_buf[22] = "00,00000000,00000000\n"; - - for (i=0;i MAX_SENSORS) continue; - buf_ptr += 1; - - if (target_sid > -1 && sid != target_sid) continue; - - time_os_t timestamp; - memcpy(×tamp, buf_ptr, sizeof(timestamp)); - buf_ptr += sizeof(timestamp); - uint32_t value; - memcpy(&value, buf_ptr, sizeof(value)); - buf_ptr += sizeof(value); - - if (timestamp > before || timestamp < after) continue; - - print_buf[0] = dec2hexchar((sid >> 4) & 0xF); - print_buf[1] = dec2hexchar(sid & 0xF); - - print_buf[3] = dec2hexchar((timestamp >> 28) & 0xF); - print_buf[4] = dec2hexchar((timestamp >> 24) & 0xF); - print_buf[5] = dec2hexchar((timestamp >> 20) & 0xF); - print_buf[6] = dec2hexchar((timestamp >> 16) & 0xF); - print_buf[7] = dec2hexchar((timestamp >> 12) & 0xF); - print_buf[8] = dec2hexchar((timestamp >> 8) & 0xF); - print_buf[9] = dec2hexchar((timestamp >> 4) & 0xF); - print_buf[10] = dec2hexchar(timestamp & 0xF); - - print_buf[12] = dec2hexchar((value >> 28) & 0xF); - print_buf[13] = dec2hexchar((value >> 24) & 0xF); - print_buf[14] = dec2hexchar((value >> 20) & 0xF); - print_buf[15] = dec2hexchar((value >> 16) & 0xF); - print_buf[16] = dec2hexchar((value >> 12) & 0xF); - print_buf[17] = dec2hexchar((value >> 8) & 0xF); - print_buf[18] = dec2hexchar((value >> 4) & 0xF); - print_buf[19] = dec2hexchar(value & 0xF); - res.write(print_buf, 21); - count += 1; - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(file_no); - break; - } - } - - if (file) file_close(file); + //uint32_t start_time = millis(); + + uint32_t count = 0; + uint32_t i; + + char *end; + uint32_t max_count = 100; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { + max_count = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (max_count > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); + } + + uint16_t file_no = os.sensor_file_no; + uint16_t next; + + os_file_type file = os.open_sensor_log(file_no, FileOpenMode::Read); + if (file) { + file_read(file, &next, sizeof(next)); + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(file_no); + handle_return(HTML_INTERNAL_ERROR); + } + + if (next == SENSOR_LOG_PER_FILE) { + next = 0; + file_no = (file_no + 1) % SENSOR_LOG_FILE_COUNT; + } else { + next += 1; + } + + uint32_t cursor = (file_no * SENSOR_LOG_PER_FILE) + next; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("cursor"), true)) { + cursor = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (cursor > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); + next = cursor % SENSOR_LOG_PER_FILE; + file_no = (cursor - next) / SENSOR_LOG_PER_FILE; + } + + using std::numeric_limits; + time_os_t before = std::numeric_limits::max(); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) { + before = (time_os_t)strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (before == 0) handle_return(HTML_DATA_OUTOFBOUND); + } + + time_os_t after = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) { + after = (time_os_t)strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (after <= before) handle_return(HTML_DATA_OUTOFBOUND); + } + + long target_sid = -1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + target_sid = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (target_sid >= MAX_SENSORS || target_sid < -1) handle_return(HTML_DATA_OUTOFBOUND); + } + + // Clear out buffer + memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); + + file = os.open_sensor_log(file_no, FileOpenMode::Read); + if (file) { + file_seek(file, sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE), FileSeekMode::Current); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(file_no); + handle_return(HTML_INTERNAL_ERROR); + } + + send_packet(OTF_PARAMS); + + char print_buf[22] = "00,00000000,00000000\n"; + + for (i=0;i MAX_SENSORS) continue; + buf_ptr += 1; + + if (target_sid > -1 && sid != target_sid) continue; + + time_os_t timestamp; + memcpy(×tamp, buf_ptr, sizeof(timestamp)); + buf_ptr += sizeof(timestamp); + uint32_t value; + memcpy(&value, buf_ptr, sizeof(value)); + buf_ptr += sizeof(value); + + if (timestamp > before || timestamp < after) continue; + + print_buf[0] = dec2hexchar((sid >> 4) & 0xF); + print_buf[1] = dec2hexchar(sid & 0xF); + + print_buf[3] = dec2hexchar((timestamp >> 28) & 0xF); + print_buf[4] = dec2hexchar((timestamp >> 24) & 0xF); + print_buf[5] = dec2hexchar((timestamp >> 20) & 0xF); + print_buf[6] = dec2hexchar((timestamp >> 16) & 0xF); + print_buf[7] = dec2hexchar((timestamp >> 12) & 0xF); + print_buf[8] = dec2hexchar((timestamp >> 8) & 0xF); + print_buf[9] = dec2hexchar((timestamp >> 4) & 0xF); + print_buf[10] = dec2hexchar(timestamp & 0xF); + + print_buf[12] = dec2hexchar((value >> 28) & 0xF); + print_buf[13] = dec2hexchar((value >> 24) & 0xF); + print_buf[14] = dec2hexchar((value >> 20) & 0xF); + print_buf[15] = dec2hexchar((value >> 16) & 0xF); + print_buf[16] = dec2hexchar((value >> 12) & 0xF); + print_buf[17] = dec2hexchar((value >> 8) & 0xF); + print_buf[18] = dec2hexchar((value >> 4) & 0xF); + print_buf[19] = dec2hexchar(value & 0xF); + res.write(print_buf, 21); + count += 1; + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(file_no); + break; + } + } + + if (file) file_close(file); handle_return(HTML_OK); } @@ -2368,9 +2357,9 @@ void server_log_sensor(OTF_PARAMS_DEF) { // TODO: delete sensor log delete void server_clear_sensor_log(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - os_file_type file; + os_file_type file; - int sid = -1; + int sid = -1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { sid = atoi(tmp_buffer); if (sid<-1 || sid>=MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); @@ -2378,114 +2367,113 @@ void server_clear_sensor_log(OTF_PARAMS_DEF) { handle_return(HTML_DATA_MISSING); } - tmp_buffer[0] = 0; - tmp_buffer[1] = 255; - - uint16_t next = SENSOR_LOG_PER_FILE; - for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - file = os.open_sensor_log(f, FileOpenMode::ReadWrite); - if (file) { - file_write(file, &next, sizeof(next)); - for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(f); - handle_return(HTML_INTERNAL_ERROR); - } - } - + tmp_buffer[0] = 0; + tmp_buffer[1] = 255; + + uint16_t next = SENSOR_LOG_PER_FILE; + for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + file = os.open_sensor_log(f, FileOpenMode::ReadWrite); + if (file) { + file_write(file, &next, sizeof(next)); + for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + handle_return(HTML_INTERNAL_ERROR); + } + } handle_return(HTML_SUCCESS); } template void bfill_enum_values(const char *name) { - static_assert(std::is_enum::value, "T must be an enum type"); + static_assert(std::is_enum::value, "T must be an enum type"); - bool needs_comma = false; + bool needs_comma = false; - bfill.emit_p(PSTR("\"$S\":["), name); + bfill.emit_p(PSTR("\"$S\":["), name); - for (size_t i = 0; i < static_cast(T::MAX_VALUE); ++i) { - if (needs_comma) { - bfill.emit_p(PSTR(",")); - needs_comma = false; - } + for (size_t i = 0; i < static_cast(T::MAX_VALUE); ++i) { + if (needs_comma) { + bfill.emit_p(PSTR(",")); + needs_comma = false; + } - const char* str = enum_string(static_cast(i)); - if (str) { - bfill.emit_p(PSTR("\"$S\""), str); - needs_comma = true; - } - } + const char* str = enum_string(static_cast(i)); + if (str) { + bfill.emit_p(PSTR("\"$S\""), str); + needs_comma = true; + } + } - bfill.emit_p(PSTR("]")); + bfill.emit_p(PSTR("]")); } void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"sensor\":[")); - for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE); i++) { - if (i) bfill.emit_p(PSTR(",")); - switch (static_cast(i)) { - case SensorType::Ensemble: - EnsembleSensor::emit_description_json(&bfill); - break; - case SensorType::ADS1115: - ADS1115Sensor::emit_description_json(&bfill); - break; - case SensorType::Weather: - WeatherSensor::emit_description_json(&bfill); - break; - case SensorType::MAX_VALUE: - break; - } - } - - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - - bfill.emit_p(PSTR("],\"units\":[")); - for (uint8_t i = 0; i < static_cast(SensorUnit::MAX_VALUE)-1; i++) { - if (i) bfill.emit_p(PSTR(",")); - SensorUnit unit = static_cast(i); - bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":$D,\"index\":$D,\"value\":$D}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit), i); - } - - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - - bfill.emit_p(PSTR("],\"enums\":{")); - bfill_enum_values(PSTR("SensorUnitGroup")); - bfill.emit_p(PSTR(",")); - bfill_enum_values(PSTR("EnsembleAction")); - bfill.emit_p(PSTR(",")); - bfill_enum_values(PSTR("WeatherAction")); + for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE); i++) { + if (i) bfill.emit_p(PSTR(",")); + switch (static_cast(i)) { + case SensorType::Ensemble: + EnsembleSensor::emit_description_json(&bfill); + break; + case SensorType::ADS1115: + ADS1115Sensor::emit_description_json(&bfill); + break; + case SensorType::Weather: + WeatherSensor::emit_description_json(&bfill); + break; + case SensorType::MAX_VALUE: + break; + } + } + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + + bfill.emit_p(PSTR("],\"units\":[")); + for (uint8_t i = 0; i < static_cast(SensorUnit::MAX_VALUE)-1; i++) { + if (i) bfill.emit_p(PSTR(",")); + SensorUnit unit = static_cast(i); + bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":$D,\"index\":$D,\"value\":$D}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit), i); + } + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + + bfill.emit_p(PSTR("],\"enums\":{")); + bfill_enum_values(PSTR("SensorUnitGroup")); + bfill.emit_p(PSTR(",")); + bfill_enum_values(PSTR("EnsembleAction")); + bfill.emit_p(PSTR(",")); + bfill_enum_values(PSTR("WeatherAction")); bfill.emit_p(PSTR("}")); - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } - bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"100\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); + bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"100\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } - static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here - bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]]")); + static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here + bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]]")); - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } - bfill.emit_p(PSTR("}")); + bfill.emit_p(PSTR("}")); } void server_json_sen_desc(OTF_PARAMS_DEF) @@ -2520,12 +2508,12 @@ void server_json_all(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); bfill.emit_p(PSTR(",\"stations\":{")); server_json_stations_main(OTF_PARAMS); - #if defined(USE_SENSORS) +#if defined(USE_SENSORS) bfill.emit_p(PSTR(",\"sensors\":{")); server_json_sensors_main(OTF_PARAMS); bfill.emit_p(PSTR(",\"sensor_desc\":{")); server_json_sensor_description_main(OTF_PARAMS); - #endif +#endif bfill.emit_p(PSTR("}")); handle_return(HTML_OK); } @@ -2534,7 +2522,7 @@ void server_json_all(OTF_PARAMS_DEF) { #else #include -static unsigned long freeHeap() { +static uint32_t freeHeap() { //return sysconf(_SC_AVPHYS_PAGES) * sysconf(_SC_PAGESIZE); struct sysinfo info; if (sysinfo(&info) == 0) { @@ -2616,37 +2604,37 @@ typedef void (*URLHandler)(OTF_PARAMS_DEF); */ const char *uris[] PROGMEM = { - "cv", - "jc", - "dp", - "cp", - "cr", - "mp", - "up", - "jp", - "co", - "jo", - "sp", - "js", - "cm", - "cs", - "jn", - "je", - "jl", - "dl", - "su", - "cu", - "ja", - "pq", - "db", - #if defined(USE_SENSORS) - "jsn", - "csn", - "dsn", - "lsn", - "csl", - "jsd", - #endif + "cv", + "jc", + "dp", + "cp", + "cr", + "mp", + "up", + "jp", + "co", + "jo", + "sp", + "js", + "cm", + "cs", + "jn", + "je", + "jl", + "dl", + "su", + "cu", + "ja", + "pq", + "db", +#if defined(USE_SENSORS) + "jsn", + "csn", + "dsn", + "lsn", + "csl", + "jsd", +#endif }; // Server function handlers @@ -2764,13 +2752,13 @@ void start_server_client() { update_server->on("/update", HTTP_POST, on_firmware_upload_fin, on_firmware_upload); update_server->on("/update", HTTP_OPTIONS, on_update_options); - char uri_buf[10] = {0}; - uri_buf[0] = '/'; + char uri_buf[10] = {0}; + uri_buf[0] = '/'; // set up all other handlers for(unsigned char i=0;ion(uri_buf, urls[i]); } callback_initialized = true; @@ -2796,14 +2784,14 @@ void start_server_ap() { update_server->begin(); char uri_buf[10] = {0}; - uri_buf[0] = '/'; + uri_buf[0] = '/'; - // set up all other handlers - for(unsigned char i=0;ion(uri_buf, urls[i]); - } + // set up all other handlers + for(unsigned char i=0;ion(uri_buf, urls[i]); + } os.lcd.setCursor(0, -1); os.lcd.print(F("OSAP:")); @@ -2824,12 +2812,12 @@ void initialize_otf() { otf->on("/index.html", server_home); char uri_buf[10] = {0}; - uri_buf[0] = '/'; + uri_buf[0] = '/'; // set up all other handlers for(unsigned char i=0;ion(uri_buf, urls[i]); } callback_initialized = true; @@ -2842,7 +2830,7 @@ void initialize_otf() { /** NTP sync request */ // due to lwip not supporting UDP, we have to use configTime and time() functions // othewise, using UDP is much faster for NTP sync -ulong getNtpTime() { +uint32_t getNtpTime() { static bool configured = false; static char customAddress[16]; if(!configured) { @@ -2864,7 +2852,7 @@ ulong getNtpTime() { configured = true; } unsigned char tries = 0; - ulong gt = 0; + uint32_t gt = 0; while(tries1577836800UL) break; diff --git a/program.cpp b/program.cpp index 89e2584eb..f555f19e2 100644 --- a/program.cpp +++ b/program.cpp @@ -101,13 +101,13 @@ void ProgramData::eraseall() { void ProgramData::read(unsigned char pid, ProgramStruct *buf) { if (pid >= nprograms) return; // first unsigned char is program counter, so 1+ - file_read_block(PROG_FILENAME, buf, 1+(ulong)pid*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); + file_read_block(PROG_FILENAME, buf, 1+(uint32_t)pid*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); } /** Add a program */ unsigned char ProgramData::add(ProgramStruct *buf) { if (nprograms >= MAX_NUM_PROGRAMS) return 0; - file_write_block(PROG_FILENAME, buf, 1+(ulong)nprograms*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); + file_write_block(PROG_FILENAME, buf, 1+(uint32_t)nprograms*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); nprograms ++; save_count(); return 1; @@ -117,8 +117,8 @@ unsigned char ProgramData::add(ProgramStruct *buf) { void ProgramData::moveup(unsigned char pid) { if(pid >= nprograms || pid == 0) return; // swap program pid-1 and pid - ulong pos = 1+(ulong)(pid-1)*PROGRAMSTRUCT_SIZE; - ulong next = pos+PROGRAMSTRUCT_SIZE; + uint32_t pos = 1+(uint32_t)(pid-1)*PROGRAMSTRUCT_SIZE; + uint32_t next = pos+PROGRAMSTRUCT_SIZE; char buf2[PROGRAMSTRUCT_SIZE]; file_read_block(PROG_FILENAME, tmp_buffer, pos, PROGRAMSTRUCT_SIZE); file_read_block(PROG_FILENAME, buf2, next, PROGRAMSTRUCT_SIZE); @@ -126,7 +126,7 @@ void ProgramData::moveup(unsigned char pid) { file_write_block(PROG_FILENAME, buf2, pos, PROGRAMSTRUCT_SIZE); } -void ProgramData::toggle_pause(ulong delay) { +void ProgramData::toggle_pause(uint32_t delay) { if (os.status.pause_state) { // was paused resume_stations(); } else { @@ -180,7 +180,7 @@ void ProgramData::clear_pause() { /** Modify a program */ unsigned char ProgramData::modify(unsigned char pid, ProgramStruct *buf) { if (pid >= nprograms) return 0; - ulong pos = 1+(ulong)pid*PROGRAMSTRUCT_SIZE; + uint32_t pos = 1+(uint32_t)pid*PROGRAMSTRUCT_SIZE; file_write_block(PROG_FILENAME, buf, pos, PROGRAMSTRUCT_SIZE); return 1; } @@ -189,9 +189,9 @@ unsigned char ProgramData::modify(unsigned char pid, ProgramStruct *buf) { unsigned char ProgramData::del(unsigned char pid) { if (pid >= nprograms) return 0; if (nprograms == 0) return 0; - ulong pos = 1+(ulong)(pid+1)*PROGRAMSTRUCT_SIZE; + uint32_t pos = 1+(uint32_t)(pid+1)*PROGRAMSTRUCT_SIZE; // erase by copying backward - for (; pos < 1+(ulong)nprograms*PROGRAMSTRUCT_SIZE; pos+=PROGRAMSTRUCT_SIZE) { + for (; pos < 1+(uint32_t)nprograms*PROGRAMSTRUCT_SIZE; pos+=PROGRAMSTRUCT_SIZE) { file_copy_block(PROG_FILENAME, pos, pos-PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE, tmp_buffer); } nprograms --; @@ -202,10 +202,10 @@ unsigned char ProgramData::del(unsigned char pid) { // set the enable bit unsigned char ProgramData::set_flagbit(unsigned char pid, unsigned char bid, unsigned char value) { if (pid >= nprograms) return 0; - unsigned char flag = file_read_byte(PROG_FILENAME, 1+(ulong)pid*PROGRAMSTRUCT_SIZE); + unsigned char flag = file_read_byte(PROG_FILENAME, 1+(uint32_t)pid*PROGRAMSTRUCT_SIZE); if(value) flag|=(1<tm_wday+1)%7; // tm_wday ranges from [0,6] with Sunday being 0 unsigned char day_t = ti->tm_mday; unsigned char month_t = ti->tm_mon+1; // tm_mon ranges from [0,11] @@ -490,14 +490,12 @@ void ProgramStruct::gen_station_runorder(uint16_t runcount, unsigned char *order void ProgramData::drem_to_relative(unsigned char days[2]) { unsigned char rem_abs=days[0]; unsigned char inv=days[1]; - // todo future: use now_tz()? - days[0] = (unsigned char)((rem_abs + inv - (os.now_tz()/SECS_PER_DAY) % inv) % inv); +days[0] = (unsigned char)((rem_abs + inv - (os.now_tz()/SECS_PER_DAY) % inv) % inv); } // relative remainder -> absolute remainder void ProgramData::drem_to_absolute(unsigned char days[2]) { unsigned char rem_rel=days[0]; unsigned char inv=days[1]; - // todo future: use now_tz()? days[0] = (unsigned char)(((os.now_tz()/SECS_PER_DAY) + rem_rel) % inv); } diff --git a/program.h b/program.h index ac899be60..9544dfd10 100644 --- a/program.h +++ b/program.h @@ -132,7 +132,7 @@ class ProgramData { static LogStruct lastrun; static time_os_t last_seq_stop_times[]; // the last stop time of a sequential station (for each sequential group respectively) - static void toggle_pause(ulong delay); + static void toggle_pause(uint32_t delay); static void set_pause(); static void resume_stations(); static void clear_pause(); diff --git a/sensor.cpp b/sensor.cpp index cdf69f431..4e32036a5 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,630 +1,403 @@ #include "sensor.h" #include "OpenSprinkler.h" +static const char* const GROUP_NAMES[] = { + PSTR("No Group"), // None + "Temperature", // Temperature + "Length", // Length + "Volume", // Volume + "Light", // Light + "Energy", // Energy + "Velocity", // Velocity + "Pressure", // Pressure + "Flow" // Flow + // MAX_VALUE is handled by bounds checking +}; + +static const char* const ENSEMBLE_NAMES[] = { + "Min", "Max", "Average", "Sum", "Product" +}; + +// --- Sensor Unit Definition Struct --- +struct UnitDefinition { + const char* name; + const char* short_name; + SensorUnitGroup group; +}; + +// --- Master Unit Table --- +// The order MUST match the SensorUnit enum in sensor.h exactly. +static const UnitDefinition UNIT_DATA[] = { + // Name, Short, Group + { "None", "", SensorUnitGroup::None }, // None + { "Celsius", "°C", SensorUnitGroup::Temperature }, // Celsius + { "Fahrenheit", "°F", SensorUnitGroup::Temperature }, // Fahrenheit + { "Kelvin", "K", SensorUnitGroup::Temperature }, // Kelvin + { "Millimeter", "mm", SensorUnitGroup::Length }, // Millimeter (Fixed typo) + { "Centimeter", "cm", SensorUnitGroup::Length }, // Centimeter (Fixed typo) + { "Meter", "m", SensorUnitGroup::Length }, // Meter + { "Kilometer", "km", SensorUnitGroup::Length }, // Kilometer + { "Inch", "in", SensorUnitGroup::Length }, // Inch + { "Foot", "ft", SensorUnitGroup::Length }, // Foot + { "Mile", "mi", SensorUnitGroup::Length }, // Mile + { "Lux", "lx", SensorUnitGroup::Light }, // Lux + { "Lumen", "lm", SensorUnitGroup::Light }, // Lumen + { "Millivolt", "mV", SensorUnitGroup::Energy }, // Millivolt (Fixed typo) + { "Volt", "V", SensorUnitGroup::Energy }, // Volt + { "Milliampere", "mA", SensorUnitGroup::Energy }, // Milliampere (Fixed typo) + { "Ampere", "A", SensorUnitGroup::Energy }, // Ampere + { "Percent", "%", SensorUnitGroup::None }, // Percent + { "Miles Per Hour", "mph", SensorUnitGroup::Velocity }, // MilesPerHour + { "Kilometers Per Hour", "km/h", SensorUnitGroup::Velocity }, // KilometersPerHour + { "Meters Per Second", "m/s", SensorUnitGroup::Velocity }, // MetersPerSecond (Fixed "xxx") + { "Dielectric Constant", "Dk", SensorUnitGroup::Energy }, // DielectricConstant (Fixed typo & "xxx") + { "Parts Per Million", "ppm", SensorUnitGroup::None }, // PartsPerMillion + { "Ohm", "Ω", SensorUnitGroup::Energy }, // Ohm + { "Milliohm", "mΩ", SensorUnitGroup::Energy }, // Milliohm (Fixed typo) + { "Kiloohm", "kΩ", SensorUnitGroup::Energy }, // Kiloohm + { "Bar", "bar", SensorUnitGroup::Pressure }, // Bar + { "Kilopascal", "kPa", SensorUnitGroup::Pressure }, // Kilopascal + { "Pascal", "Pa", SensorUnitGroup::Pressure }, // Pascal + { "Torr", "torr", SensorUnitGroup::Pressure }, // Torr + { "Liters Per Second", "L/s", SensorUnitGroup::Flow }, // LitersPerSecond + { "Gallons Per Second", "gal/s", SensorUnitGroup::Flow } // GallonsPerSecond +}; + +// TODO: PSTR()? const char *enum_string(SensorUnitGroup group) { - switch (group) { - case SensorUnitGroup::None: - return PSTR("No Group"); - case SensorUnitGroup::Temperature: - return PSTR("Temperature"); - case SensorUnitGroup::Length: - return PSTR("Length"); - case SensorUnitGroup::Volume: - return PSTR("Volume"); - case SensorUnitGroup::Light: - return PSTR("Light"); - case SensorUnitGroup::Energy: - return PSTR("Energy"); - case SensorUnitGroup::Velocity: - return PSTR("Velocity"); - case SensorUnitGroup::Pressure: - return PSTR("Pressure"); - case SensorUnitGroup::Flow: - return PSTR("Flow"); - case SensorUnitGroup::MAX_VALUE: - return nullptr; - } - - return nullptr; + if (static_cast(group) >= static_cast(SensorUnitGroup::MAX_VALUE)) { + return nullptr; + } + return GROUP_NAMES[static_cast(group)]; } const char *enum_string(EnsembleAction action) { - switch (action) { - case EnsembleAction::Min: return PSTR("Min"); - case EnsembleAction::Max: return PSTR("Max"); - case EnsembleAction::Average: return PSTR("Average"); - case EnsembleAction::Sum: return PSTR("Sum"); - case EnsembleAction::Product: return PSTR("Product"); - case EnsembleAction::MAX_VALUE: return nullptr; - } - - return nullptr; + if (static_cast(action) >= static_cast(EnsembleAction::MAX_VALUE)) { + return nullptr; + } + return ENSEMBLE_NAMES[static_cast(action)]; } const char *enum_string(WeatherAction action) { - switch (action) { - case WeatherAction::MAX_VALUE: return nullptr; - } - - return nullptr; + // TODO: Currently empty in your original code + return nullptr; } const char* get_sensor_unit_name(SensorUnit unit) { - switch (unit) { - case SensorUnit::None: - return PSTR("None"); - case SensorUnit::Celsius: - return PSTR("Celsius"); - case SensorUnit::Fahrenheit: - return PSTR("Fahrenheit"); - case SensorUnit::Kelvin: - return PSTR("Kelvin"); - case SensorUnit::Milimeter: - return PSTR("Milimeter"); - case SensorUnit::Centieter: - return PSTR("Centieter"); - case SensorUnit::Meter: - return PSTR("Meter"); - case SensorUnit::Kilometer: - return PSTR("Kilometer"); - case SensorUnit::Inch: - return PSTR("Inch"); - case SensorUnit::Foot: - return PSTR("Foot"); - case SensorUnit::Mile: - return PSTR("Mile"); - case SensorUnit::Lux: - return PSTR("Lux"); - case SensorUnit::Lumen: - return PSTR("Lumen"); - case SensorUnit::Milivolt: - return PSTR("Milivolt"); - case SensorUnit::Volt: - return PSTR("Volt"); - case SensorUnit::Miliampere: - return PSTR("Miliampere"); - case SensorUnit::Ampere: - return PSTR("Ampere"); - case SensorUnit::Percent: - return PSTR("Percent"); - case SensorUnit::MilesPerHour: - return PSTR("Miles Per Hour"); - case SensorUnit::KilometersPerHour: - return PSTR("Kilometers Per Hour"); - case SensorUnit::MetersPerSecond: - return PSTR("Meters Per Second"); - case SensorUnit::DialetricConstant: - return PSTR("Dialetric Constant"); - case SensorUnit::PartsPerMillion: - return PSTR("Parts Per Million"); - case SensorUnit::Ohm: - return PSTR("Ohm"); - case SensorUnit::Miliohm: - return PSTR("Miliohm"); - case SensorUnit::Kiloohm: - return PSTR("Kiloohm"); - case SensorUnit::Bar: - return PSTR("Bar"); - case SensorUnit::Kilopascal: - return PSTR("Kilopascal"); - case SensorUnit::Pascal: - return PSTR("Pascal"); - case SensorUnit::Torr: - return PSTR("Torr"); - case SensorUnit::LitersPerSecond: - return PSTR("Liters Per Second"); - case SensorUnit::GallonsPerSecond: - return PSTR("Gallons"); - case SensorUnit::MAX_VALUE: - return nullptr; - } - - return nullptr; + if (static_cast(unit) >= static_cast(SensorUnit::MAX_VALUE)) { + return nullptr; + } + return UNIT_DATA[static_cast(unit)].name; } const char* get_sensor_unit_short(SensorUnit unit) { - switch (unit) { - case SensorUnit::None: - return PSTR(""); - case SensorUnit::Celsius: - return PSTR("°C"); - case SensorUnit::Fahrenheit: - return PSTR("°F"); - case SensorUnit::Kelvin: - return PSTR("K"); - case SensorUnit::Milimeter: - return PSTR("mm"); - case SensorUnit::Centieter: - return PSTR("cm"); - case SensorUnit::Meter: - return PSTR("m"); - case SensorUnit::Kilometer: - return PSTR("km"); - case SensorUnit::Inch: - return PSTR("in"); - case SensorUnit::Foot: - return PSTR("ft"); - case SensorUnit::Mile: - return PSTR("mi"); - case SensorUnit::Lux: - return PSTR("lx"); - case SensorUnit::Lumen: - return PSTR("lm"); - case SensorUnit::Milivolt: - return PSTR("mV"); - case SensorUnit::Volt: - return PSTR("V"); - case SensorUnit::Miliampere: - return PSTR("mA"); - case SensorUnit::Ampere: - return PSTR("A"); - case SensorUnit::Percent: - return PSTR("%"); - case SensorUnit::MilesPerHour: - return PSTR("mph"); - case SensorUnit::KilometersPerHour: - return PSTR("km/h"); - case SensorUnit::MetersPerSecond: - return PSTR("xxx"); - case SensorUnit::DialetricConstant: - return PSTR("xxx"); - case SensorUnit::PartsPerMillion: - return PSTR("ppm"); - case SensorUnit::Ohm: - return PSTR("Ω"); - case SensorUnit::Miliohm: - return PSTR("mΩ"); - case SensorUnit::Kiloohm: - return PSTR("kΩ"); - case SensorUnit::Bar: - return PSTR("bar"); - case SensorUnit::Kilopascal: - return PSTR("kPa"); - case SensorUnit::Pascal: - return PSTR("Pa"); - case SensorUnit::Torr: - return PSTR("torr"); - case SensorUnit::LitersPerSecond: - return PSTR("L/s"); - case SensorUnit::GallonsPerSecond: - return PSTR("gal/s"); - case SensorUnit::MAX_VALUE: - return nullptr; - } - - return nullptr; + if (static_cast(unit) >= static_cast(SensorUnit::MAX_VALUE)) { + return nullptr; + } + return UNIT_DATA[static_cast(unit)].short_name; } const SensorUnitGroup get_sensor_unit_group(SensorUnit unit) { - switch (unit) { - case SensorUnit::None: - return SensorUnitGroup::None; - case SensorUnit::Celsius: - return SensorUnitGroup::Temperature; - case SensorUnit::Fahrenheit: - return SensorUnitGroup::Temperature; - case SensorUnit::Kelvin: - return SensorUnitGroup::Temperature; - case SensorUnit::Milimeter: - return SensorUnitGroup::Length; - case SensorUnit::Centieter: - return SensorUnitGroup::Length; - case SensorUnit::Meter: - return SensorUnitGroup::Length; - case SensorUnit::Kilometer: - return SensorUnitGroup::Length; - case SensorUnit::Inch: - return SensorUnitGroup::Length; - case SensorUnit::Foot: - return SensorUnitGroup::Length; - case SensorUnit::Mile: - return SensorUnitGroup::Length; - case SensorUnit::Lux: - return SensorUnitGroup::Light; - case SensorUnit::Lumen: - return SensorUnitGroup::Light; - case SensorUnit::Milivolt: - return SensorUnitGroup::Energy; - case SensorUnit::Volt: - return SensorUnitGroup::Energy; - case SensorUnit::Miliampere: - return SensorUnitGroup::Energy; - case SensorUnit::Ampere: - return SensorUnitGroup::Energy; - case SensorUnit::Percent: - return SensorUnitGroup::None; - case SensorUnit::MilesPerHour: - return SensorUnitGroup::Velocity; - case SensorUnit::KilometersPerHour: - return SensorUnitGroup::Velocity; - case SensorUnit::MetersPerSecond: - return SensorUnitGroup::Velocity; - case SensorUnit::DialetricConstant: - return SensorUnitGroup::Energy; - case SensorUnit::PartsPerMillion: - return SensorUnitGroup::None; - case SensorUnit::Ohm: - return SensorUnitGroup::Energy; - case SensorUnit::Miliohm: - return SensorUnitGroup::Energy; - case SensorUnit::Kiloohm: - return SensorUnitGroup::Energy; - case SensorUnit::Bar: - return SensorUnitGroup::Pressure; - case SensorUnit::Kilopascal: - return SensorUnitGroup::Pressure; - case SensorUnit::Pascal: - return SensorUnitGroup::Pressure; - case SensorUnit::Torr: - return SensorUnitGroup::Pressure; - case SensorUnit::LitersPerSecond: - return SensorUnitGroup::Flow; - case SensorUnit::GallonsPerSecond: - return SensorUnitGroup::Flow; - case SensorUnit::MAX_VALUE: - return SensorUnitGroup::MAX_VALUE; - } - - return SensorUnitGroup::MAX_VALUE; + if (static_cast(unit) >= static_cast(SensorUnit::MAX_VALUE)) { + return SensorUnitGroup::MAX_VALUE; + } + return UNIT_DATA[static_cast(unit)].group; } -const ulong get_sensor_unit_index(SensorUnit unit) { - switch (unit) { - case SensorUnit::None: - return 0; - case SensorUnit::Celsius: - return 0; - case SensorUnit::Fahrenheit: - return 0; - case SensorUnit::Kelvin: - return 0; - case SensorUnit::Milimeter: - return 0; - case SensorUnit::Centieter: - return 0; - case SensorUnit::Meter: - return 0; - case SensorUnit::Kilometer: - return 0; - case SensorUnit::Inch: - return 0; - case SensorUnit::Foot: - return 0; - case SensorUnit::Mile: - return 0; - case SensorUnit::Lux: - return 0; - case SensorUnit::Lumen: - return 0; - case SensorUnit::Milivolt: - return 0; - case SensorUnit::Volt: - return 0; - case SensorUnit::Miliampere: - return 0; - case SensorUnit::Ampere: - return 0; - case SensorUnit::Percent: - return 0; - case SensorUnit::MilesPerHour: - return 0; - case SensorUnit::KilometersPerHour: - return 0; - case SensorUnit::MetersPerSecond: - return 0; - case SensorUnit::DialetricConstant: - return 0; - case SensorUnit::PartsPerMillion: - return 0; - case SensorUnit::Ohm: - return 0; - case SensorUnit::Miliohm: - return 0; - case SensorUnit::Kiloohm: - return 0; - case SensorUnit::Bar: - return 0; - case SensorUnit::Kilopascal: - return 0; - case SensorUnit::Pascal: - return 0; - case SensorUnit::Torr: - return 0; - case SensorUnit::LitersPerSecond: - return 0; - case SensorUnit::GallonsPerSecond: - return 0; - case SensorUnit::MAX_VALUE: - return 0; - } - - return 0; +// TODO: it was returning 0 for everything, why? +const uint32_t get_sensor_unit_index(SensorUnit unit) { + auto idx = static_cast(unit); + if (idx >= static_cast(SensorUnit::MAX_VALUE)) { + return 0; // or some invalid index + } + return static_cast(idx); } -Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags) : - interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit), flags(flags) { - strncpy(this->name, name, SENSOR_NAME_LEN); - this->name[SENSOR_NAME_LEN - 1] = 0; +Sensor::Sensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags) : + interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit), flags(flags) { + strncpy(this->name, name, SENSOR_NAME_LEN); + this->name[SENSOR_NAME_LEN - 1] = 0; } Sensor::Sensor() {} double Sensor::get_new_value() { - double value = this->_get_raw_value(); - value = (value * this->scale) + this->offset; - if (value < this->min) value = this->min; - if (value > this->max) value = this->max; + double value = this->_get_raw_value(); + value = (value * this->scale) + this->offset; + if (value < this->min) value = this->min; + if (value > this->max) value = this->max; - return value; + return value; } template uint32_t write_buf(char* buf, T val) { - std::memcpy(buf, &val, sizeof(val)); - return sizeof(val); + std::memcpy(buf, &val, sizeof(val)); + return sizeof(val); } template T read_buf(char* buf, uint32_t* i) { - T val; - std::memcpy(&val, buf + (*i), sizeof(T)); - *i += sizeof(T); - return val; + T val; + std::memcpy(&val, buf + (*i), sizeof(T)); + *i += sizeof(T); + return val; } uint32_t Sensor::serialize(char* buf) { - uint32_t i = 0; - - buf[i++] = static_cast(this->get_sensor_type()); - memcpy(buf + i, this->name, SENSOR_NAME_LEN); - i += SENSOR_NAME_LEN; - buf[i++] = static_cast(this->unit); - i += write_buf(buf + i, this->interval); - i += write_buf(buf + i, this->flags); - i += write_buf(buf + i, this->scale); - i += write_buf(buf + i, this->offset); - i += write_buf(buf + i, this->min); - i += write_buf(buf + i, this->max); - - i += this->_serialize_internal(buf + i); - return i; + uint32_t i = 0; + + buf[i++] = static_cast(this->get_sensor_type()); + memcpy(buf + i, this->name, SENSOR_NAME_LEN); + i += SENSOR_NAME_LEN; + buf[i++] = static_cast(this->unit); + i += write_buf(buf + i, this->interval); + i += write_buf(buf + i, this->flags); + i += write_buf(buf + i, this->scale); + i += write_buf(buf + i, this->offset); + i += write_buf(buf + i, this->min); + i += write_buf(buf + i, this->max); + + i += this->_serialize_internal(buf + i); + return i; } uint32_t Sensor::_deserialize(char* buf) { - uint32_t i = 1; // Skip sensor type - - memcpy(this->name, buf + i, SENSOR_NAME_LEN); - i += SENSOR_NAME_LEN; - this->unit = static_cast(buf[i++]); - this->interval = read_buf(buf, &i); - this->flags = read_buf(buf, &i); - this->scale = read_buf(buf, &i); - this->offset = read_buf(buf, &i); - this->min = read_buf(buf, &i); - this->max = read_buf(buf, &i); - - return i; + uint32_t i = 1; // Skip sensor type + + memcpy(this->name, buf + i, SENSOR_NAME_LEN); + i += SENSOR_NAME_LEN; + this->unit = static_cast(buf[i++]); + this->interval = read_buf(buf, &i); + this->flags = read_buf(buf, &i); + this->scale = read_buf(buf, &i); + this->offset = read_buf(buf, &i); + this->min = read_buf(buf, &i); + this->max = read_buf(buf, &i); + + return i; } -EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : - Sensor(interval, min, max, scale, offset, name, unit, flags), - action(action), - sensors(sensors) { - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - if (i < children_count) { - this->children[i] = children[i]; - } - else { - this->children[i] = ensemble_children_t{ sensor_id: 255, min : 0.0, max : 0.0, scale : 0.0, offset : 0.0 }; - } - } +EnsembleSensor::EnsembleSensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + action(action), + sensors(sensors) { + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + if (i < children_count) { + this->children[i] = children[i]; + } + else { + this->children[i] = ensemble_children_t{ sensor_id: 255, min : 0.0, max : 0.0, scale : 0.0, offset : 0.0 }; + } + } } void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - if (i) bfill->emit_p(PSTR(",")); - ensemble_children_t* child = &this->children[i]; - bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->sensor_id, child->max, child->min, child->scale, child->offset); - } - bfill->emit_p(PSTR("]}")); + bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + if (i) bfill->emit_p(PSTR(",")); + ensemble_children_t* child = &this->children[i]; + bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->sensor_id, child->max, child->min, child->scale, child->offset); + } + bfill->emit_p(PSTR("]}")); } void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); } double EnsembleSensor::get_initial_value() { - switch (this->action) { - case EnsembleAction::Min: - return this->max; - break; - case EnsembleAction::Max: - return this->min; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - return 0; - break; - case EnsembleAction::Product: - return 1; - break; - default: - // Unreachable - return 0.0; - } + switch (this->action) { + case EnsembleAction::Min: + return this->max; + break; + case EnsembleAction::Max: + return this->min; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + return 0; + break; + case EnsembleAction::Product: + return 1; + break; + default: + // Unreachable + return 0.0; + } } double EnsembleSensor::_get_raw_value() { - double inital = this->get_initial_value(); - uint8_t count = 0; - - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - uint8_t sensor = this->children[i].sensor_id; - if (sensor < MAX_SENSORS && sensors[sensor].interval) { - double value = sensors[sensor].value; - value = (value * this->children[i].scale) + this->children[i].offset; - if (value < this->children[i].min) value = this->children[i].min; - if (value > this->children[i].max) value = this->children[i].max; - - switch (this->action) { - case EnsembleAction::Min: - if (value < inital) inital = value; - break; - case EnsembleAction::Max: - if (value > inital) inital = value; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - inital += value; - break; - case EnsembleAction::Product: - inital *= value; - break; - default: - // Unreachable - return 0.0; - } - - count += 1; - } - } - - if (count == 0) { - return 0.0; - } - else if (this->action == EnsembleAction::Average) { - return inital / (double)count; - } - else { - return inital; - } + double initial = this->get_initial_value(); + uint8_t count = 0; + + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + uint8_t sensor = this->children[i].sensor_id; + if (sensor < MAX_SENSORS && sensors[sensor].interval) { + double value = sensors[sensor].value; + value = (value * this->children[i].scale) + this->children[i].offset; + if (value < this->children[i].min) value = this->children[i].min; + if (value > this->children[i].max) value = this->children[i].max; + + switch (this->action) { + case EnsembleAction::Min: + if (value < initial) initial = value; + break; + case EnsembleAction::Max: + if (value > initial) initial = value; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + initial += value; + break; + case EnsembleAction::Product: + initial *= value; + break; + default: + // Unreachable + return 0.0; + } + + count += 1; + } + } + + if (count == 0) { + return 0.0; + } + else if (this->action == EnsembleAction::Average) { + return initial / (double)count; + } + else { + return initial; + } } uint32_t EnsembleSensor::_serialize_internal(char* buf) { - uint32_t i = 0; - for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - i += write_buf(buf + i, this->children[j].sensor_id); - i += write_buf(buf + i, this->children[j].min); - i += write_buf(buf + i, this->children[j].max); - i += write_buf(buf + i, this->children[j].scale); - i += write_buf(buf + i, this->children[j].offset); - } - - buf[i++] = static_cast(this->action); - return i; + uint32_t i = 0; + for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { + i += write_buf(buf + i, this->children[j].sensor_id); + i += write_buf(buf + i, this->children[j].min); + i += write_buf(buf + i, this->children[j].max); + i += write_buf(buf + i, this->children[j].scale); + i += write_buf(buf + i, this->children[j].offset); + } + + buf[i++] = static_cast(this->action); + return i; } EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { - uint32_t i = Sensor::_deserialize(buf); - for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - this->children[j].sensor_id = read_buf(buf, &i); - this->children[j].min = read_buf(buf, &i); - this->children[j].max = read_buf(buf, &i); - this->children[j].scale = read_buf(buf, &i); - this->children[j].offset = read_buf(buf, &i); - } - - this->action = static_cast(buf[i++]); - this->sensors = sensors; + uint32_t i = Sensor::_deserialize(buf); + for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { + this->children[j].sensor_id = read_buf(buf, &i); + this->children[j].min = read_buf(buf, &i); + this->children[j].max = read_buf(buf, &i); + this->children[j].scale = read_buf(buf, &i); + this->children[j].offset = read_buf(buf, &i); + } + + this->action = static_cast(buf[i++]); + this->sensors = sensors; } -WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : - Sensor(interval, min, max, scale, offset, name, unit, flags), - action(action), - weather_getter(weather_getter) { +WeatherSensor::WeatherSensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + action(action), + weather_getter(weather_getter) { } void WeatherSensor::emit_extra_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"action\":$D}"), this->action); + bfill->emit_p(PSTR("{\"action\":$D}"), this->action); } void WeatherSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"default\":\"0\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"default\":\"0\",\"extra\":[]}]}")); } double WeatherSensor::get_initial_value() { - return 0.0; + return 0.0; } double WeatherSensor::_get_raw_value() { - return this->weather_getter(this->action); + return this->weather_getter(this->action); } uint32_t WeatherSensor::_serialize_internal(char* buf) { - uint32_t i = 0; - buf[i++] = static_cast(this->action); - return i; + uint32_t i = 0; + buf[i++] = static_cast(this->action); + return i; } -WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) { - uint32_t i = Sensor::_deserialize(buf); - this->action = static_cast(buf[i++]); +WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) : weather_getter(weather_getter) { + uint32_t i = Sensor::_deserialize(buf); + this->action = static_cast(buf[i++]); } SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t* points) { - this->flags = flags; - this->sid = sid; - if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; - this->point_count = point_count; - for (size_t i = 0; i < point_count; i++) { - this->points[i] = points[i]; - } - + this->flags = flags; + this->sid = sid; + if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; + this->point_count = point_count; + for (size_t i = 0; i < point_count; i++) { + this->points[i] = points[i]; + } } SensorAdjustment::SensorAdjustment(char* buf) { - uint32_t i = 0; - this->flags = buf[i++]; - this->sid = buf[i++]; - this->point_count = buf[i++]; - - for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { - this->points[j].x = read_buf(buf, &i); - this->points[j].y = read_buf(buf, &i); - } + uint32_t i = 0; + this->flags = buf[i++]; + this->sid = buf[i++]; + this->point_count = buf[i++]; + + for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { + this->points[j].x = read_buf(buf, &i); + this->points[j].y = read_buf(buf, &i); + } } double SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { - if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->sid < MAX_SENSORS && sensors[this->sid].interval) { - double value = sensors[this->sid].value; - if (value <= this->points[0].x) return this->points[0].y; - if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; - - uint8_t i; - - for (i = 0; i < this->point_count - 1; i++) { - if (value >= this->points[i].x) { - break; - } - } - - sensor_adjustment_point_t left = this->points[i]; - sensor_adjustment_point_t right = this->points[i + 1]; - if (right.x == left.x) return left.y; - - value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; - - if (value < 0) return 0; - return value; - } - else { - return 1.0; - } + if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->sid < MAX_SENSORS && sensors[this->sid].interval) { + double value = sensors[this->sid].value; + if (this->point_count < 1) { return 1.0; } + if (this->point_count == 1) { return this->points[0].y; } + if (value <= this->points[0].x) return this->points[0].y; + if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; + + uint8_t i; + + for (i = 0; i < this->point_count - 1; i++) { + if (value < this->points[i + 1].x) { + break; + } + } + + sensor_adjustment_point_t left = this->points[i]; + sensor_adjustment_point_t right = this->points[i + 1]; + if (right.x == left.x) return left.y; + + value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; + + if (value < 0) return 0; + return value; + } + else { + return 1.0; + } } uint32_t SensorAdjustment::serialize(char* buf) { - uint32_t i = 0; - buf[i++] = this->flags; - buf[i++] = this->sid; - buf[i++] = this->point_count; + uint32_t i = 0; + buf[i++] = this->flags; + buf[i++] = this->sid; + buf[i++] = this->point_count; - for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { - i += write_buf(buf + i, this->points[j].x); - i += write_buf(buf + i, this->points[j].y); - } + for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { + i += write_buf(buf + i, this->points[j].x); + i += write_buf(buf + i, this->points[j].y); + } - return i; + return i; } \ No newline at end of file diff --git a/sensor.h b/sensor.h index 45a7359b8..d8f15e5a6 100644 --- a/sensor.h +++ b/sensor.h @@ -1,5 +1,4 @@ -#ifndef SENSOR_H -#define SENSOR_H +#pragma once #include #if defined(ARDUINO) @@ -14,21 +13,21 @@ #define SENSOR_CUSTOM_UNIT_LEN 9 typedef struct { - ulong interval; + uint32_t interval; uint32_t flags; - ulong next_update; + uint32_t next_update; double value; } sensor_memory_t; -enum class SensorType { - Ensemble, +enum class SensorType : uint8_t { + Ensemble = 0, ADS1115, Weather, MAX_VALUE, }; -enum class SensorUnitGroup { - None, +enum class SensorUnitGroup : uint8_t { + None = 0, Temperature, Length, Volume, @@ -40,13 +39,13 @@ enum class SensorUnitGroup { MAX_VALUE, }; -enum class SensorUnit { - None, +enum class SensorUnit : uint8_t { + None = 0, Celsius, Fahrenheit, Kelvin, - Milimeter, - Centieter, + Millimeter, + Centimeter, Meter, Kilometer, Inch, @@ -54,15 +53,15 @@ enum class SensorUnit { Mile, Lux, Lumen, - Milivolt, + Millivolt, Volt, - Miliampere, + Milliampere, Ampere, Percent, MilesPerHour, KilometersPerHour, MetersPerSecond, - DialetricConstant, + DielectricConstant, PartsPerMillion, Ohm, Miliohm, @@ -84,7 +83,7 @@ typedef enum { class Sensor { public: - Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags); + Sensor(uint32_t interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags); Sensor(); virtual ~Sensor() {} @@ -93,7 +92,7 @@ class Sensor { void virtual emit_extra_json(BufferFiller *bfill) = 0; - unsigned long interval = 1; + uint32_t interval = 1; double min = 0.0; double max = 0.0; double scale = 0.0; @@ -113,8 +112,8 @@ class Sensor { uint32_t virtual _serialize_internal(char *buf) = 0; }; -enum class EnsembleAction { - Min, +enum class EnsembleAction : uint8_t { + Min = 0, Max, Average, Sum, @@ -136,7 +135,7 @@ typedef struct { class EnsembleSensor : public Sensor { public: - EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); + EnsembleSensor(uint32_t interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); EnsembleSensor(sensor_memory_t *sensors, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -166,7 +165,7 @@ typedef double (*WeatherGetter)(WeatherAction); class WeatherSensor : public Sensor { public: - WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(uint32_t interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); WeatherSensor(WeatherGetter weather_getter, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -221,6 +220,4 @@ const char *enum_string(WeatherAction action); const char* get_sensor_unit_name(SensorUnit unit); const char* get_sensor_unit_short(SensorUnit unit); const SensorUnitGroup get_sensor_unit_group(SensorUnit unit); -const ulong get_sensor_unit_index(SensorUnit unit); - -#endif //SENSOR_H \ No newline at end of file +const uint32_t get_sensor_unit_index(SensorUnit unit); diff --git a/types.h b/types.h index e4d7831cc..f77651727 100644 --- a/types.h +++ b/types.h @@ -3,8 +3,8 @@ #include #if defined(ESP8266) -typedef unsigned long time_os_t; +typedef uint32_t time_os_t; #else #include -typedef time_t time_os_t; +typedef uint32_t time_os_t; #endif diff --git a/utils.cpp b/utils.cpp index 23cf94a46..4cd7e36d7 100644 --- a/utils.cpp +++ b/utils.cpp @@ -82,7 +82,7 @@ char* get_filename_fullpath(const char *filename) { return fullpath; } -void delay(ulong howLong) +void delay(uint32_t howLong) { struct timespec sleeper, dummy ; @@ -92,7 +92,7 @@ void delay(ulong howLong) nanosleep (&sleeper, &dummy) ; } -void delayMicroseconds (ulong howLong) +void delayMicroseconds (uint32_t howLong) { struct timespec sleeper ; unsigned int uSecs = howLong % 1000000 ; @@ -110,7 +110,7 @@ void delayMicroseconds (ulong howLong) } } -void delayMicrosecondsHard (ulong howLong) +void delayMicrosecondsHard (uint32_t howLong) { struct timeval tNow, tLong, tEnd ; @@ -134,7 +134,7 @@ void initialiseEpoch() epochMicro = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)(tv.tv_usec) ; } -// ulong millis (void) +// uint32_t millis (void) // { // struct timeval tv ; // uint64_t now ; @@ -142,10 +142,10 @@ void initialiseEpoch() // gettimeofday (&tv, NULL) ; // now = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; -// return (ulong)(now - epochMilli) ; +// return (uint32_t)(now - epochMilli) ; // } -ulong micros (void) +unsigned long micros (void) { struct timeval tv ; uint64_t now ; @@ -153,7 +153,7 @@ ulong micros (void) gettimeofday (&tv, NULL) ; now = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)tv.tv_usec ; - return (ulong)(now - epochMicro) ; + return (unsigned long)(now - epochMicro) ; } #if defined(OSPI) @@ -232,52 +232,52 @@ in_addr_t get_ip_address(char *iface) { #endif bool prefix(const char *pre, const char *str) { - return strncmp(pre, str, strlen(pre)) == 0; + return strncmp(pre, str, strlen(pre)) == 0; } BoardType get_board_type() { - FILE *file = fopen("/proc/device-tree/compatible", "rb"); - if (file == NULL) { - return BoardType::Unknown; - } - - char buffer[100]; - - BoardType res = BoardType::Unknown; - - int total = fread(buffer, 1, sizeof(buffer), file); - - if (prefix("raspberrypi", buffer)) { - res = BoardType::RaspberryPi_Unknown; - const char *cpu_buf = buffer; - size_t index = 0; - - // model and cpu is seperated by a null byte - while (index < (total - 1) && cpu_buf[index]) { - index += 1; - } - - cpu_buf += index + 1; - - if (!strcmp("brcm,bcm2712", cpu_buf)) { - // Pi 5 - res = BoardType::RaspberryPi_bcm2712; - } else if (!strcmp("brcm,bcm2711", cpu_buf)) { - // Pi 4 - res = BoardType::RaspberryPi_bcm2711; - } else if (!strcmp("brcm,bcm2837", cpu_buf)) { - // Pi 3 / Pi Zero 2 - res = BoardType::RaspberryPi_bcm2837; - } else if (!strcmp("brcm,bcm2836", cpu_buf)) { - // Pi 2 - res = BoardType::RaspberryPi_bcm2836; - } else if (!strcmp("brcm,bcm2835", cpu_buf)) { - // Pi / Pi Zero - res = BoardType::RaspberryPi_bcm2835; - } - } - - return res; + FILE *file = fopen("/proc/device-tree/compatible", "rb"); + if (file == NULL) { + return BoardType::Unknown; + } + + char buffer[100]; + + BoardType res = BoardType::Unknown; + + int total = fread(buffer, 1, sizeof(buffer), file); + + if (prefix("raspberrypi", buffer)) { + res = BoardType::RaspberryPi_Unknown; + const char *cpu_buf = buffer; + size_t index = 0; + + // model and cpu is seperated by a null byte + while (index < (total - 1) && cpu_buf[index]) { + index += 1; + } + + cpu_buf += index + 1; + + if (!strcmp("brcm,bcm2712", cpu_buf)) { + // Pi 5 + res = BoardType::RaspberryPi_bcm2712; + } else if (!strcmp("brcm,bcm2711", cpu_buf)) { + // Pi 4 + res = BoardType::RaspberryPi_bcm2711; + } else if (!strcmp("brcm,bcm2837", cpu_buf)) { + // Pi 3 / Pi Zero 2 + res = BoardType::RaspberryPi_bcm2837; + } else if (!strcmp("brcm,bcm2836", cpu_buf)) { + // Pi 2 + res = BoardType::RaspberryPi_bcm2836; + } else if (!strcmp("brcm,bcm2835", cpu_buf)) { + // Pi / Pi Zero + res = BoardType::RaspberryPi_bcm2835; + } + } + + return res; } #endif @@ -312,105 +312,105 @@ bool file_exists(const char *fn) { } os_file_type file_open(const char *fn, FileOpenMode mode) { - #if defined(ESP8266) - switch (mode) { - default: - case FileOpenMode::Read: - return LittleFS.open(fn, "r"); - case FileOpenMode::ReadWrite: - if (!LittleFS.exists(fn)) { - File f = LittleFS.open(fn, "w"); - if (!f) return f; - f.close(); - } - return LittleFS.open(fn, "r+"); - case FileOpenMode::WriteTruncate: - return LittleFS.open(fn, "w"); - case FileOpenMode::ReadWriteTruncate: - return LittleFS.open(fn, "w+"); - case FileOpenMode::Append: - return LittleFS.open(fn, "a"); - case FileOpenMode::ReadAppend: - return LittleFS.open(fn, "a+"); - } - #else - char *full_file = get_filename_fullpath(fn); - switch (mode) { - default: - case FileOpenMode::Read: - return fopen(full_file, "rb"); - case FileOpenMode::ReadWrite: { - int fd = open(full_file, O_RDWR | O_CREAT, 0644); - if (fd == -1) return nullptr; - return fdopen(fd, "rb+"); - } - case FileOpenMode::WriteTruncate: - return fopen(full_file, "wb"); - case FileOpenMode::ReadWriteTruncate: - return fopen(full_file, "wb+"); - case FileOpenMode::Append: - return fopen(full_file, "ab"); - case FileOpenMode::ReadAppend: - return fopen(full_file, "ab+"); - } - - #endif + #if defined(ESP8266) + switch (mode) { + default: + case FileOpenMode::Read: + return LittleFS.open(fn, "r"); + case FileOpenMode::ReadWrite: + if (!LittleFS.exists(fn)) { + File f = LittleFS.open(fn, "w"); + if (!f) return f; + f.close(); + } + return LittleFS.open(fn, "r+"); + case FileOpenMode::WriteTruncate: + return LittleFS.open(fn, "w"); + case FileOpenMode::ReadWriteTruncate: + return LittleFS.open(fn, "w+"); + case FileOpenMode::Append: + return LittleFS.open(fn, "a"); + case FileOpenMode::ReadAppend: + return LittleFS.open(fn, "a+"); + } + #else + char *full_file = get_filename_fullpath(fn); + switch (mode) { + default: + case FileOpenMode::Read: + return fopen(full_file, "rb"); + case FileOpenMode::ReadWrite: { + int fd = open(full_file, O_RDWR | O_CREAT, 0644); + if (fd == -1) return nullptr; + return fdopen(fd, "rb+"); + } + case FileOpenMode::WriteTruncate: + return fopen(full_file, "wb"); + case FileOpenMode::ReadWriteTruncate: + return fopen(full_file, "wb+"); + case FileOpenMode::Append: + return fopen(full_file, "ab"); + case FileOpenMode::ReadAppend: + return fopen(full_file, "ab+"); + } + + #endif } void file_close(os_file_type f) { - #if defined(ESP8266) - f.close(); - #else - fclose(f); - #endif + #if defined(ESP8266) + f.close(); + #else + fclose(f); + #endif } bool file_seek(os_file_type f, uint32_t position, FileSeekMode mode) { - #if defined(ESP8266) - switch (mode) { - case FileSeekMode::Set: - return f.seek(position, fs::SeekMode::SeekSet); - case FileSeekMode::Current: - return f.seek(position, fs::SeekMode::SeekCur); - case FileSeekMode::End: - return f.seek(position, fs::SeekMode::SeekEnd); - } - #else - switch (mode) { - case FileSeekMode::Set: - return fseek(f, position, SEEK_SET); - case FileSeekMode::Current: - return fseek(f, position, SEEK_CUR); - case FileSeekMode::End: - return fseek(f, position, SEEK_END); - } - #endif - - return false; + #if defined(ESP8266) + switch (mode) { + case FileSeekMode::Set: + return f.seek(position, fs::SeekMode::SeekSet); + case FileSeekMode::Current: + return f.seek(position, fs::SeekMode::SeekCur); + case FileSeekMode::End: + return f.seek(position, fs::SeekMode::SeekEnd); + } + #else + switch (mode) { + case FileSeekMode::Set: + return fseek(f, position, SEEK_SET); + case FileSeekMode::Current: + return fseek(f, position, SEEK_CUR); + case FileSeekMode::End: + return fseek(f, position, SEEK_END); + } + #endif + + return false; } bool file_seek(os_file_type f, uint32_t position) { - return file_seek(f, position, FileSeekMode::Set); + return file_seek(f, position, FileSeekMode::Set); } int file_read(os_file_type f, void *target, uint32_t len) { - #if defined(ESP8266) - return f.read((uint8_t*)target, len); - #else - return fread(target, 1, len, f); - #endif + #if defined(ESP8266) + return f.read((uint8_t*)target, len); + #else + return fread(target, 1, len, f); + #endif } int file_write(os_file_type f, const void *source, uint32_t len) { - #if defined(ESP8266) - return f.write((const uint8_t*)source, len); - #else - return fwrite(source, 1, len, f); - #endif + #if defined(ESP8266) + return f.write((const uint8_t*)source, len); + #else + return fwrite(source, 1, len, f); + #endif } // file functions -void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { +void file_read_block(const char *fn, void *dst, uint32_t pos, uint32_t len) { #if defined(ESP8266) // do not use File.read_byte or read_byteUntil because it's very slow @@ -433,7 +433,7 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { #endif } -void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { +void file_write_block(const char *fn, const void *src, uint32_t pos, uint32_t len) { #if defined(ESP8266) File f = LittleFS.open(fn, "r+"); @@ -460,7 +460,7 @@ void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { } -void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) { +void file_copy_block(const char *fn, uint32_t from, uint32_t to, uint32_t len, void *tmp) { // assume tmp buffer is provided and is larger than len // todo future: if tmp buffer is not provided, do unsigned char-to-unsigned char copy if(tmp==NULL) { return; } @@ -489,7 +489,7 @@ void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) } // compare a block of content -unsigned char file_cmp_block(const char *fn, const char *buf, ulong pos) { +unsigned char file_cmp_block(const char *fn, const char *buf, uint32_t pos) { #if defined(ESP8266) File f = LittleFS.open(fn, "r"); @@ -522,13 +522,13 @@ unsigned char file_cmp_block(const char *fn, const char *buf, ulong pos) { return 1; } -unsigned char file_read_byte(const char *fn, ulong pos) { +unsigned char file_read_byte(const char *fn, uint32_t pos) { unsigned char v = 0; file_read_block(fn, &v, pos, 1); return v; } -void file_write_byte(const char *fn, ulong pos, unsigned char v) { +void file_write_byte(const char *fn, uint32_t pos, unsigned char v) { file_write_block(fn, &v, pos, 1); } @@ -547,7 +547,7 @@ void strncpy_P0(char* dest, const char* src, int n) { * 65534: sunrise to sunset duration * 65535: sunset to sunrise duration */ -ulong water_time_resolve(uint16_t v) { +uint32_t water_time_resolve(uint16_t v) { if(v==65534) { return (os.nvdata.sunset_time-os.nvdata.sunrise_time) * 60L; } else if(v==65535) { diff --git a/utils.h b/utils.h index c3104f02e..5c26eea4d 100644 --- a/utils.h +++ b/utils.h @@ -25,8 +25,8 @@ #if defined(ESP8266) #include - #include - #include + #include + #include #else // headers for RPI/LINUX #include #include @@ -46,24 +46,24 @@ typedef FILE* os_file_type; #endif enum class FileOpenMode { - Read, - ReadWrite, - WriteTruncate, - ReadWriteTruncate, - Append, - ReadAppend, + Read, + ReadWrite, + WriteTruncate, + ReadWriteTruncate, + Append, + ReadAppend, }; enum class FileSeekMode { - Set, - Current, - End + Set, + Current, + End }; // File reading/writing functions -//remove unused functions: void write_to_file(const char *fname, const char *data, ulong size, ulong pos=0, bool trunc=true); -//remove unused functions: void read_from_file(const char *fname, char *data, ulong maxsize=TMP_BUFFER_SIZE, int pos=0); +//remove unused functions: void write_to_file(const char *fname, const char *data, uint32_t size, uint32_t pos=0, bool trunc=true); +//remove unused functions: void read_from_file(const char *fname, char *data, uint32_t maxsize=TMP_BUFFER_SIZE, int pos=0); void remove_file(const char *fname); bool file_exists(const char *fname); @@ -74,16 +74,16 @@ bool file_seek(os_file_type f, uint32_t position); int file_read(os_file_type f, void *target, uint32_t len); int file_write(os_file_type f, const void *source, uint32_t len); -void file_read_block (const char *fname, void *dst, ulong pos, ulong len); -void file_write_block(const char *fname, const void *src, ulong pos, ulong len); -void file_copy_block (const char *fname, ulong from, ulong to, ulong len, void *tmp=0); -unsigned char file_read_byte (const char *fname, ulong pos); -void file_write_byte(const char *fname, ulong pos, unsigned char v); -unsigned char file_cmp_block(const char *fname, const char *buf, ulong pos); +void file_read_block (const char *fname, void *dst, uint32_t pos, uint32_t len); +void file_write_block(const char *fname, const void *src, uint32_t pos, uint32_t len); +void file_copy_block (const char *fname, uint32_t from, uint32_t to, uint32_t len, void *tmp=0); +unsigned char file_read_byte (const char *fname, uint32_t pos); +void file_write_byte(const char *fname, uint32_t pos, unsigned char v); +unsigned char file_cmp_block(const char *fname, const char *buf, uint32_t pos); // misc. string and time converstion functions void strncpy_P0(char* dest, const char* src, int n); -ulong water_time_resolve(uint16_t v); +uint32_t water_time_resolve(uint16_t v); unsigned char water_time_encode_signed(int16_t i); int16_t water_time_decode_signed(unsigned char i); void urlDecode(char *); @@ -111,11 +111,11 @@ void str2mac(const char *_str, unsigned char mac[]); const char* get_data_dir(); void set_data_dir(const char *new_data_dir); char* get_filename_fullpath(const char *filename); - void delay(ulong ms); - void delayMicroseconds(ulong us); - void delayMicrosecondsHard(ulong us); - ulong millis(); - ulong micros(); + void delay(uint32_t ms); + void delayMicroseconds(uint32_t us); + void delayMicrosecondsHard(uint32_t us); + unsigned long millis(); + unsigned long micros(); void initialiseEpoch(); #if defined(OSPI) unsigned int detect_rpi_rev(); diff --git a/weather.cpp b/weather.cpp index fd5c58a6b..5166f80b3 100644 --- a/weather.cpp +++ b/weather.cpp @@ -127,7 +127,7 @@ static void getweather_callback(char* buffer) { if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rd"), true)) { v = atoi(tmp_buffer); if (v>0) { - os.nvdata.rd_stop_time = tnow + (unsigned long) v * 3600; + os.nvdata.rd_stop_time = tnow + (uint32_t) v * 3600; os.raindelay_start(); } else if (v==0) { os.raindelay_stop(); @@ -260,8 +260,8 @@ void apply_monthly_adjustment(time_os_t curr_time) { #if defined(ESP8266) unsigned char m = month(curr_time)-1; #else - time_os_t ct = curr_time; - struct tm *ti = gmtime(&ct); + time_t _ct = curr_time; + struct tm *ti = gmtime(&_ct); unsigned char m = ti->tm_mon; // tm_mon ranges from [0,11] #endif if(os.iopts[IOPT_WATER_PERCENTAGE]!=wt_monthly[m]) { From 92a3aa3f23b3e2dd1a20b3f8c2854ef6f21aee4a Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 6 Dec 2025 12:39:17 -0500 Subject: [PATCH 080/115] recover sensor.cpp back to original for saving RAM/flash memory consumption --- sensor.cpp | 344 ++++++++++++++++++++++++++++++++++++++--------------- sensor.h | 2 +- 2 files changed, 252 insertions(+), 94 deletions(-) diff --git a/sensor.cpp b/sensor.cpp index 4e32036a5..690580e91 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,116 +1,275 @@ #include "sensor.h" #include "OpenSprinkler.h" -static const char* const GROUP_NAMES[] = { - PSTR("No Group"), // None - "Temperature", // Temperature - "Length", // Length - "Volume", // Volume - "Light", // Light - "Energy", // Energy - "Velocity", // Velocity - "Pressure", // Pressure - "Flow" // Flow - // MAX_VALUE is handled by bounds checking -}; - -static const char* const ENSEMBLE_NAMES[] = { - "Min", "Max", "Average", "Sum", "Product" -}; - -// --- Sensor Unit Definition Struct --- -struct UnitDefinition { - const char* name; - const char* short_name; - SensorUnitGroup group; -}; - -// --- Master Unit Table --- -// The order MUST match the SensorUnit enum in sensor.h exactly. -static const UnitDefinition UNIT_DATA[] = { - // Name, Short, Group - { "None", "", SensorUnitGroup::None }, // None - { "Celsius", "°C", SensorUnitGroup::Temperature }, // Celsius - { "Fahrenheit", "°F", SensorUnitGroup::Temperature }, // Fahrenheit - { "Kelvin", "K", SensorUnitGroup::Temperature }, // Kelvin - { "Millimeter", "mm", SensorUnitGroup::Length }, // Millimeter (Fixed typo) - { "Centimeter", "cm", SensorUnitGroup::Length }, // Centimeter (Fixed typo) - { "Meter", "m", SensorUnitGroup::Length }, // Meter - { "Kilometer", "km", SensorUnitGroup::Length }, // Kilometer - { "Inch", "in", SensorUnitGroup::Length }, // Inch - { "Foot", "ft", SensorUnitGroup::Length }, // Foot - { "Mile", "mi", SensorUnitGroup::Length }, // Mile - { "Lux", "lx", SensorUnitGroup::Light }, // Lux - { "Lumen", "lm", SensorUnitGroup::Light }, // Lumen - { "Millivolt", "mV", SensorUnitGroup::Energy }, // Millivolt (Fixed typo) - { "Volt", "V", SensorUnitGroup::Energy }, // Volt - { "Milliampere", "mA", SensorUnitGroup::Energy }, // Milliampere (Fixed typo) - { "Ampere", "A", SensorUnitGroup::Energy }, // Ampere - { "Percent", "%", SensorUnitGroup::None }, // Percent - { "Miles Per Hour", "mph", SensorUnitGroup::Velocity }, // MilesPerHour - { "Kilometers Per Hour", "km/h", SensorUnitGroup::Velocity }, // KilometersPerHour - { "Meters Per Second", "m/s", SensorUnitGroup::Velocity }, // MetersPerSecond (Fixed "xxx") - { "Dielectric Constant", "Dk", SensorUnitGroup::Energy }, // DielectricConstant (Fixed typo & "xxx") - { "Parts Per Million", "ppm", SensorUnitGroup::None }, // PartsPerMillion - { "Ohm", "Ω", SensorUnitGroup::Energy }, // Ohm - { "Milliohm", "mΩ", SensorUnitGroup::Energy }, // Milliohm (Fixed typo) - { "Kiloohm", "kΩ", SensorUnitGroup::Energy }, // Kiloohm - { "Bar", "bar", SensorUnitGroup::Pressure }, // Bar - { "Kilopascal", "kPa", SensorUnitGroup::Pressure }, // Kilopascal - { "Pascal", "Pa", SensorUnitGroup::Pressure }, // Pascal - { "Torr", "torr", SensorUnitGroup::Pressure }, // Torr - { "Liters Per Second", "L/s", SensorUnitGroup::Flow }, // LitersPerSecond - { "Gallons Per Second", "gal/s", SensorUnitGroup::Flow } // GallonsPerSecond -}; - -// TODO: PSTR()? const char *enum_string(SensorUnitGroup group) { - if (static_cast(group) >= static_cast(SensorUnitGroup::MAX_VALUE)) { - return nullptr; + switch (group) { + case SensorUnitGroup::None: + return PSTR("No Group"); + case SensorUnitGroup::Temperature: + return PSTR("Temperature"); + case SensorUnitGroup::Length: + return PSTR("Length"); + case SensorUnitGroup::Volume: + return PSTR("Volume"); + case SensorUnitGroup::Light: + return PSTR("Light"); + case SensorUnitGroup::Energy: + return PSTR("Energy"); + case SensorUnitGroup::Velocity: + return PSTR("Velocity"); + case SensorUnitGroup::Pressure: + return PSTR("Pressure"); + case SensorUnitGroup::Flow: + return PSTR("Flow"); + case SensorUnitGroup::MAX_VALUE: + return nullptr; } - return GROUP_NAMES[static_cast(group)]; + + return nullptr; } const char *enum_string(EnsembleAction action) { - if (static_cast(action) >= static_cast(EnsembleAction::MAX_VALUE)) { - return nullptr; + switch (action) { + case EnsembleAction::Min: return PSTR("Min"); + case EnsembleAction::Max: return PSTR("Max"); + case EnsembleAction::Average: return PSTR("Average"); + case EnsembleAction::Sum: return PSTR("Sum"); + case EnsembleAction::Product: return PSTR("Product"); + case EnsembleAction::MAX_VALUE: return nullptr; } - return ENSEMBLE_NAMES[static_cast(action)]; + + return nullptr; } const char *enum_string(WeatherAction action) { - // TODO: Currently empty in your original code + switch (action) { + case WeatherAction::MAX_VALUE: return nullptr; + } + return nullptr; } const char* get_sensor_unit_name(SensorUnit unit) { - if (static_cast(unit) >= static_cast(SensorUnit::MAX_VALUE)) { + switch (unit) { + case SensorUnit::None: + return PSTR("None"); + case SensorUnit::Celsius: + return PSTR("Celsius"); + case SensorUnit::Fahrenheit: + return PSTR("Fahrenheit"); + case SensorUnit::Kelvin: + return PSTR("Kelvin"); + case SensorUnit::Millimeter: + return PSTR("Millimeter"); + case SensorUnit::Centimeter: + return PSTR("Centimeter"); + case SensorUnit::Meter: + return PSTR("Meter"); + case SensorUnit::Kilometer: + return PSTR("Kilometer"); + case SensorUnit::Inch: + return PSTR("Inch"); + case SensorUnit::Foot: + return PSTR("Foot"); + case SensorUnit::Mile: + return PSTR("Mile"); + case SensorUnit::Lux: + return PSTR("Lux"); + case SensorUnit::Lumen: + return PSTR("Lumen"); + case SensorUnit::Millivolt: + return PSTR("Millivolt"); + case SensorUnit::Volt: + return PSTR("Volt"); + case SensorUnit::Milliampere: + return PSTR("Milliampere"); + case SensorUnit::Ampere: + return PSTR("Ampere"); + case SensorUnit::Percent: + return PSTR("Percent"); + case SensorUnit::MilesPerHour: + return PSTR("Miles Per Hour"); + case SensorUnit::KilometersPerHour: + return PSTR("Kilometers Per Hour"); + case SensorUnit::MetersPerSecond: + return PSTR("Meters Per Second"); + case SensorUnit::DielectricConstant: + return PSTR("Dielectric Constant"); + case SensorUnit::PartsPerMillion: + return PSTR("Parts Per Million"); + case SensorUnit::Ohm: + return PSTR("Ohm"); + case SensorUnit::Milliohm: + return PSTR("Milliohm"); + case SensorUnit::Kiloohm: + return PSTR("Kiloohm"); + case SensorUnit::Bar: + return PSTR("Bar"); + case SensorUnit::Kilopascal: + return PSTR("Kilopascal"); + case SensorUnit::Pascal: + return PSTR("Pascal"); + case SensorUnit::Torr: + return PSTR("Torr"); + case SensorUnit::LitersPerSecond: + return PSTR("Liters Per Second"); + case SensorUnit::GallonsPerSecond: + return PSTR("Gallons"); + case SensorUnit::MAX_VALUE: return nullptr; } - return UNIT_DATA[static_cast(unit)].name; + + return nullptr; } const char* get_sensor_unit_short(SensorUnit unit) { - if (static_cast(unit) >= static_cast(SensorUnit::MAX_VALUE)) { + switch (unit) { + case SensorUnit::None: + return PSTR(""); + case SensorUnit::Celsius: + return PSTR("°C"); + case SensorUnit::Fahrenheit: + return PSTR("°F"); + case SensorUnit::Kelvin: + return PSTR("K"); + case SensorUnit::Millimeter: + return PSTR("mm"); + case SensorUnit::Centimeter: + return PSTR("cm"); + case SensorUnit::Meter: + return PSTR("m"); + case SensorUnit::Kilometer: + return PSTR("km"); + case SensorUnit::Inch: + return PSTR("in"); + case SensorUnit::Foot: + return PSTR("ft"); + case SensorUnit::Mile: + return PSTR("mi"); + case SensorUnit::Lux: + return PSTR("lx"); + case SensorUnit::Lumen: + return PSTR("lm"); + case SensorUnit::Millivolt: + return PSTR("mV"); + case SensorUnit::Volt: + return PSTR("V"); + case SensorUnit::Milliampere: + return PSTR("mA"); + case SensorUnit::Ampere: + return PSTR("A"); + case SensorUnit::Percent: + return PSTR("%"); + case SensorUnit::MilesPerHour: + return PSTR("mph"); + case SensorUnit::KilometersPerHour: + return PSTR("km/h"); + case SensorUnit::MetersPerSecond: + return PSTR("m/s"); + case SensorUnit::DielectricConstant: + return PSTR("-"); + case SensorUnit::PartsPerMillion: + return PSTR("ppm"); + case SensorUnit::Ohm: + return PSTR("Ω"); + case SensorUnit::Milliohm: + return PSTR("mΩ"); + case SensorUnit::Kiloohm: + return PSTR("kΩ"); + case SensorUnit::Bar: + return PSTR("bar"); + case SensorUnit::Kilopascal: + return PSTR("kPa"); + case SensorUnit::Pascal: + return PSTR("Pa"); + case SensorUnit::Torr: + return PSTR("torr"); + case SensorUnit::LitersPerSecond: + return PSTR("L/s"); + case SensorUnit::GallonsPerSecond: + return PSTR("gal/s"); + case SensorUnit::MAX_VALUE: return nullptr; } - return UNIT_DATA[static_cast(unit)].short_name; + + return nullptr; } const SensorUnitGroup get_sensor_unit_group(SensorUnit unit) { - if (static_cast(unit) >= static_cast(SensorUnit::MAX_VALUE)) { + switch (unit) { + case SensorUnit::None: + return SensorUnitGroup::None; + case SensorUnit::Celsius: + return SensorUnitGroup::Temperature; + case SensorUnit::Fahrenheit: + return SensorUnitGroup::Temperature; + case SensorUnit::Kelvin: + return SensorUnitGroup::Temperature; + case SensorUnit::Millimeter: + return SensorUnitGroup::Length; + case SensorUnit::Centimeter: + return SensorUnitGroup::Length; + case SensorUnit::Meter: + return SensorUnitGroup::Length; + case SensorUnit::Kilometer: + return SensorUnitGroup::Length; + case SensorUnit::Inch: + return SensorUnitGroup::Length; + case SensorUnit::Foot: + return SensorUnitGroup::Length; + case SensorUnit::Mile: + return SensorUnitGroup::Length; + case SensorUnit::Lux: + return SensorUnitGroup::Light; + case SensorUnit::Lumen: + return SensorUnitGroup::Light; + case SensorUnit::Millivolt: + return SensorUnitGroup::Energy; + case SensorUnit::Volt: + return SensorUnitGroup::Energy; + case SensorUnit::Milliampere: + return SensorUnitGroup::Energy; + case SensorUnit::Ampere: + return SensorUnitGroup::Energy; + case SensorUnit::Percent: + return SensorUnitGroup::None; + case SensorUnit::MilesPerHour: + return SensorUnitGroup::Velocity; + case SensorUnit::KilometersPerHour: + return SensorUnitGroup::Velocity; + case SensorUnit::MetersPerSecond: + return SensorUnitGroup::Velocity; + case SensorUnit::DielectricConstant: + return SensorUnitGroup::Energy; + case SensorUnit::PartsPerMillion: + return SensorUnitGroup::None; + case SensorUnit::Ohm: + return SensorUnitGroup::Energy; + case SensorUnit::Milliohm: + return SensorUnitGroup::Energy; + case SensorUnit::Kiloohm: + return SensorUnitGroup::Energy; + case SensorUnit::Bar: + return SensorUnitGroup::Pressure; + case SensorUnit::Kilopascal: + return SensorUnitGroup::Pressure; + case SensorUnit::Pascal: + return SensorUnitGroup::Pressure; + case SensorUnit::Torr: + return SensorUnitGroup::Pressure; + case SensorUnit::LitersPerSecond: + return SensorUnitGroup::Flow; + case SensorUnit::GallonsPerSecond: + return SensorUnitGroup::Flow; + case SensorUnit::MAX_VALUE: return SensorUnitGroup::MAX_VALUE; } - return UNIT_DATA[static_cast(unit)].group; + + return SensorUnitGroup::MAX_VALUE; } -// TODO: it was returning 0 for everything, why? const uint32_t get_sensor_unit_index(SensorUnit unit) { - auto idx = static_cast(unit); - if (idx >= static_cast(SensorUnit::MAX_VALUE)) { - return 0; // or some invalid index - } - return static_cast(idx); + return static_cast(unit); } Sensor::Sensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags) : @@ -229,7 +388,7 @@ double EnsembleSensor::get_initial_value() { } double EnsembleSensor::_get_raw_value() { - double initial = this->get_initial_value(); + double inital = this->get_initial_value(); uint8_t count = 0; for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { @@ -242,17 +401,17 @@ double EnsembleSensor::_get_raw_value() { switch (this->action) { case EnsembleAction::Min: - if (value < initial) initial = value; + if (value < inital) inital = value; break; case EnsembleAction::Max: - if (value > initial) initial = value; + if (value > inital) inital = value; break; case EnsembleAction::Average: case EnsembleAction::Sum: - initial += value; + inital += value; break; case EnsembleAction::Product: - initial *= value; + inital *= value; break; default: // Unreachable @@ -267,10 +426,10 @@ double EnsembleSensor::_get_raw_value() { return 0.0; } else if (this->action == EnsembleAction::Average) { - return initial / (double)count; + return inital / (double)count; } else { - return initial; + return inital; } } @@ -331,7 +490,7 @@ uint32_t WeatherSensor::_serialize_internal(char* buf) { return i; } -WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) : weather_getter(weather_getter) { +WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) { uint32_t i = Sensor::_deserialize(buf); this->action = static_cast(buf[i++]); } @@ -344,6 +503,7 @@ SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_cou for (size_t i = 0; i < point_count; i++) { this->points[i] = points[i]; } + } SensorAdjustment::SensorAdjustment(char* buf) { @@ -361,15 +521,13 @@ SensorAdjustment::SensorAdjustment(char* buf) { double SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->sid < MAX_SENSORS && sensors[this->sid].interval) { double value = sensors[this->sid].value; - if (this->point_count < 1) { return 1.0; } - if (this->point_count == 1) { return this->points[0].y; } if (value <= this->points[0].x) return this->points[0].y; if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; uint8_t i; for (i = 0; i < this->point_count - 1; i++) { - if (value < this->points[i + 1].x) { + if (value >= this->points[i].x) { break; } } diff --git a/sensor.h b/sensor.h index d8f15e5a6..c423ff245 100644 --- a/sensor.h +++ b/sensor.h @@ -64,7 +64,7 @@ enum class SensorUnit : uint8_t { DielectricConstant, PartsPerMillion, Ohm, - Miliohm, + Milliohm, Kiloohm, Bar, Kilopascal, From 08a38fdd5faa8e033f87eff280ce818d8a9521c5 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Thu, 22 Jan 2026 15:02:21 -0500 Subject: [PATCH 081/115] use stack variables --- opensprinkler_server.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 116eaafaf..9194da88e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -893,10 +893,8 @@ void server_change_program(OTF_PARAMS_DEF) { point_count = i; } - // TODO: convert to stack space allocation - adj = new SensorAdjustment(flags, sid, point_count, points); - os.write_sensor_adjust(adj, pid); - delete adj; + SensorAdjustment snadj(flags, sid, point_count, points); + os.write_sensor_adjust(&snadj, pid); #endif From c8d2c5735ceb4ae4cc4a0cddf823c082c6e5032a Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 12 Apr 2026 18:16:20 -0400 Subject: [PATCH 082/115] change sensor value type from double to float; set default sampling interval to 5 minutes; default max to 5000; refactor sensor files initialization to account for firmware update within dotted version; change sensor_adjustment allocation to static to avoid repeatedly calling new and delete --- OpenSprinkler.cpp | 190 ++++++++++++++++++++------------------- OpenSprinkler.h | 3 +- ads1115.cpp | 8 +- ads1115.h | 6 +- main.cpp | 16 ++-- opensprinkler_server.cpp | 31 +++---- sensor.cpp | 83 +++++++++-------- sensor.h | 46 +++++----- 8 files changed, 196 insertions(+), 187 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index bf744a63b..f2a062ac8 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1000,32 +1000,6 @@ void OpenSprinkler::begin() { } } #endif - -#if defined(USE_SENSORS) - lcd.clear(); - lcd.setCursor(0,0); - lcd.print(F("Init sensors")); - - os_file_type file; - uint16_t next = 0; - size_t f; - for (f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - file = open_sensor_log(f, FileOpenMode::Read); - if (file) { - file_read(file, &next, sizeof(next)); - file_close(file); - - if (next < SENSOR_LOG_PER_FILE) break; - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(f); - } - } - if (f == SENSOR_LOG_FILE_COUNT) f -= 1; - sensor_file_no = f; - - os.load_sensors(); -#endif } #if defined(ESP8266) @@ -2143,61 +2117,12 @@ void OpenSprinkler::factory_reset() { file_write_byte(PROG_FILENAME, 0, 0); #if defined(USE_SENSORS) - // Initialize the senor file - memset(tmp_buffer, 0, TMP_BUFFER_SIZE); - + // remove all sensor files, so they will be re-created in load_sensors() remove_file(SENSORS_FILENAME); - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); - if (file) { - for (size_t i = 0; i < MAX_SENSORS; i++) { - file_write(file, tmp_buffer, sizeof(uint32_t)); - file_write(file, tmp_buffer, TMP_BUFFER_SIZE); - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } - - uint16_t next = SENSOR_LOG_PER_FILE; for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - { - char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; - sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; - memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); - snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", f); - remove_file(sensor_log_name_buf); - } - file = open_sensor_log(f, FileOpenMode::WriteTruncate); - if (file) { - file_write(file, &next, sizeof(next)); - for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - } - file_close(file); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(f); - } + remove_sensor_log(f); } - remove_file(SENADJ_FILENAME); - file = file_open(SENADJ_FILENAME, FileOpenMode::WriteTruncate); - if (file) { - sensor_adjustment_point_t point = sensor_adjustment_point_t {0.0, 0.0}; - SensorAdjustment adj = SensorAdjustment(0, 0, 0, &point); - - uint32_t size = adj.serialize(tmp_buffer); - for (size_t i = 0; i < MAX_NUM_PROGRAMS; i++) { - file_write(file, tmp_buffer, size); - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); - } #endif // 5. write 'done' file @@ -2540,16 +2465,80 @@ Sensor *OpenSprinkler::get_sensor(uint8_t index) { } } +void OpenSprinkler::remove_sensor_log(uint16_t file_no) { + char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; + sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; + memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", (uint16_t)(file_no % 1000)); + remove_file(sensor_log_name_buf); +} + os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) { char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); - snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", file_no); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", (uint16_t)(file_no % 1000)); return file_open(sensor_log_name_buf, mode); } void OpenSprinkler::load_sensors() { + lcd.clear(); + lcd.setCursor(0,0); + lcd.print(F("Init sensors...")); + + // 1. Check if the configuration file exists. + // If not, we need to initialize all sensor-related files. + if (!file_exists(SENSORS_FILENAME)) { + DEBUG_PRINTLN(F("Sensor files missing. Initializing...")); + + // Initialize the sensors configuration file (SENSORS_FILENAME) + memset(tmp_buffer, 0, TMP_BUFFER_SIZE); + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + for (size_t i = 0; i < MAX_SENSORS; i++) { + file_write(file, tmp_buffer, sizeof(uint32_t)); // length 0 + file_write(file, tmp_buffer, TMP_BUFFER_SIZE); // empty block + } + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } + + // Initialize the sensor log files (SENSORS_LOG_FILENAME_xxx) + uint16_t next = SENSOR_LOG_PER_FILE; + for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + file = open_sensor_log(f, FileOpenMode::WriteTruncate); + if (file) { + file_write(file, &next, sizeof(next)); + for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + } + } + + // Initialize the program adjustment file (SENADJ_FILENAME) + file = file_open(SENADJ_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + sensor_adjustment_point_t point = sensor_adjustment_point_t {0.0, 0.0}; + SensorAdjustment adj = SensorAdjustment(0, 0, 0, &point); + uint32_t size = adj.serialize(tmp_buffer); + for (size_t i = 0; i < MAX_NUM_PROGRAMS; i++) { + file_write(file, tmp_buffer, size); + } + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } + } + + // 2. Proceed with existing load logic Sensor *sensor; os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); if (file) { @@ -2568,6 +2557,24 @@ void OpenSprinkler::load_sensors() { DEBUG_PRINT("Failed to open file: "); DEBUG_PRINTLN(SENSORS_FILENAME); } + + // 3. `next` pointer recovery + uint16_t next = 0; + size_t f; + for (f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + file = open_sensor_log(f, FileOpenMode::Read); + if (file) { + file_read(file, &next, sizeof(next)); + file_close(file); + + if (next < SENSOR_LOG_PER_FILE) break; + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + } + } + if (f == SENSOR_LOG_FILE_COUNT) f -= 1; + sensor_file_no = f; } void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { @@ -2580,6 +2587,9 @@ void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { len = sensor->serialize(tmp_buffer); } + DEBUG_PRINTLN(pos); + DEBUG_PRINTLN(len); + file_seek(file, pos, FileSeekMode::Current); file_write(file, &len, sizeof(len)); if (sensor) { @@ -2653,29 +2663,25 @@ void OpenSprinkler::poll_sensors() { } SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { + // result is statically allocated to avoid repeatedly new and delete + // Do not call delete on the return result from this function + static SensorAdjustment result(tmp_buffer); + if (index > MAX_NUM_PROGRAMS) return nullptr; - uint32_t pos = SENSOR_ADJUSTMENT_SIZE * index; - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); if (file) { - file_seek(file, pos, FileSeekMode::Current); - + file_seek(file, (uint32_t)index * SENSOR_ADJUSTMENT_SIZE, FileSeekMode::Current); file_read(file, tmp_buffer, SENSOR_ADJUSTMENT_SIZE); - - SensorAdjustment *result = new SensorAdjustment(tmp_buffer); file_close(file); - - if (result->sid == 255) { - delete result; - return nullptr; - } else { - return result; + result = SensorAdjustment(tmp_buffer); + if (result.sid < 255) { + return &result; } } else { DEBUG_PRINT("Failed to open file: "); DEBUG_PRINTLN(SENADJ_FILENAME); - return nullptr; } + return nullptr; } void OpenSprinkler::write_sensor_adjust(SensorAdjustment *adj, uint8_t index) { @@ -2700,7 +2706,7 @@ void OpenSprinkler::write_sensor_adjust(SensorAdjustment *adj, uint8_t index) { } } -double OpenSprinkler::get_sensor_weather_data(WeatherAction action) { +float OpenSprinkler::get_sensor_weather_data(WeatherAction action) { return NAN; // TODO make function for WeatherSensor } #endif diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 72f27ed22..e1d9d0d85 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -372,6 +372,7 @@ class OpenSprinkler { // -- Sensor functions #if defined(USE_SENSORS) static os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); + static void remove_sensor_log(uint16_t file_no); static void load_sensors(); static Sensor *parse_sensor(os_file_type file); static Sensor *get_sensor(uint8_t index); @@ -381,7 +382,7 @@ class OpenSprinkler { static SensorAdjustment *get_sensor_adjust(uint8_t index); static void write_sensor_adjust(SensorAdjustment *adj, uint8_t index); - static double get_sensor_weather_data(WeatherAction action); + static float get_sensor_weather_data(WeatherAction action); #endif // -- LCD functions diff --git a/ads1115.cpp b/ads1115.cpp index cefcc2455..4047f5ca2 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -84,7 +84,7 @@ void ADS1115::request_pin(uint8_t pin) { this->_write_register(0x01, config); } -ADS1115Sensor::ADS1115Sensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : +ADS1115Sensor::ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint32_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : Sensor(interval, min, max, scale, offset, name, unit, flags), sensor_index(sensor_index), pin(pin), @@ -98,16 +98,16 @@ void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"default\":\"1\",\"extra\":[]}]}")); } -double ADS1115Sensor::get_initial_value() { +float ADS1115Sensor::get_initial_value() { return 0.0; } -double ADS1115Sensor::_get_raw_value() { +float ADS1115Sensor::_get_raw_value() { if (this->sensors[sensor_index] == nullptr) { return 0.0; } else { - return ((double)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; + return ((float)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; } } diff --git a/ads1115.h b/ads1115.h index defe8efd8..a5fa50660 100644 --- a/ads1115.h +++ b/ads1115.h @@ -55,7 +55,7 @@ class ADS1115 { class ADS1115Sensor : public Sensor { public: - ADS1115Sensor(uint32_t interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); ADS1115Sensor(ADS1115 **sensors, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -68,10 +68,10 @@ class ADS1115Sensor : public Sensor { uint8_t sensor_index; uint8_t pin; - double get_initial_value(); + float get_initial_value(); private: - double _get_raw_value(); + float _get_raw_value(); uint32_t _serialize_internal(char *buf); ADS1115 **sensors; diff --git a/main.cpp b/main.cpp index cfdc6e6ac..46acbe6a3 100644 --- a/main.cpp +++ b/main.cpp @@ -396,6 +396,10 @@ void do_setup() { os.setup_pd_voltage(); #endif +#if defined(USE_SENSORS) + os.load_sensors(); +#endif + pd.init(); // ProgramData init // set time using RTC if it exists @@ -769,12 +773,11 @@ void do_loop() // check through all programs for(pid=0; pidget_adjustment_factor(os.sensors); - delete adj; } #endif bool will_delete = false; @@ -816,7 +819,7 @@ void do_loop() continue; // TODO: compare with old code - uint32_t dur = (uint32_t)((double)prog.durations[sid] * sensor_adj); + uint32_t dur = (uint32_t)((float)prog.durations[sid] * sensor_adj); // if station has non-zero water time and the station is not disabled if (dur && !(os.attrib_dis[bid]&(1<= q->st && curr_time < q->st + q->dur) { - turn_off_station(q->sid, curr_time); // TODO: double check the logic + turn_off_station(q->sid, curr_time); // TODO: re-check the logic uint32_t remaining = q->dur - (curr_time - q->st); q->st = curr_time + adjustment; q->dur = remaining; @@ -1593,7 +1596,7 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo boolean match_found = false; ProgramStruct prog; uint32_t dur; - double sensor_adj = 1.0; + float sensor_adj = 1.f; unsigned char sid, bid, s; unsigned char ns = os.nstations; unsigned char order[ns]; @@ -1609,7 +1612,6 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo SensorAdjustment *adj = os.get_sensor_adjust(pid-1); if (adj) { sensor_adj = adj->get_adjustment_factor(os.sensors); - delete adj; } #endif if(uwt) wl = os.iopts[IOPT_WATER_PERCENTAGE]; @@ -1630,7 +1632,7 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo dur=2; } else if (pid>0) { dur = water_time_resolve(prog.durations[sid]); - dur = (uint32_t)((double)dur * sensor_adj); + dur = (uint32_t)((float)dur * sensor_adj); } dur = dur * wl / 100; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 9194da88e..7dbb4a1c8 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -859,7 +859,6 @@ void server_change_program(OTF_PARAMS_DEF) { for (size_t i = 0; i <= point_count; i++) { // TODO: <=? points[i] = adj->points[i]; } - delete adj; } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flags"), true)) { @@ -875,14 +874,14 @@ void server_change_program(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_points"), true)) { uint32_t i = 0; - double x, y; + float x, y; const char *ptr = tmp_buffer; int result; - double last_x = -std::numeric_limits::infinity();; + float last_x = -std::numeric_limits::infinity();; while (*ptr != '\0') { if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); - result = sscanf(ptr, "%lf,%lf;", &x, &y); + result = sscanf(ptr, "%f,%f;", &x, &y); if (result != 2 || x <= last_x) { handle_return(HTML_DATA_FORMATERROR); } @@ -1079,7 +1078,6 @@ void server_json_programs_main(OTF_PARAMS_DEF) { } bfill.emit_p(PSTR("]}")); adj_count += 1; - delete adj; // push out a packet if available // buffer size is getting small if (available_ether_buffer() <= 0) { @@ -1942,11 +1940,11 @@ void server_change_sensor(OTF_PARAMS_DEF) { SensorType sensor_type = static_cast(type_raw); Sensor *sensor = nullptr; - double min = 0; - double max = 100; - double scale = 1; - double offset = 0; - uint32_t interval = 1000; + float min = 0; + float max = 5000; // default: 5000mV or 5V + float scale = 1; + float offset = 0; + uint32_t interval = 5; // default: 5 minutes SensorUnit unit = SensorUnit::None; uint32_t flags = 0; @@ -2040,14 +2038,14 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("children"), true)) { unsigned int i = 0; int d; - double d1, d2, d3, d4; + float d1, d2, d3, d4; const char *ptr = tmp_buffer; int result; while (*ptr != '\0') { if (i >= ENSEMBLE_SENSOR_CHILDREN_COUNT) handle_return(HTML_DATA_FORMATERROR); - result = sscanf(ptr, "%d,%lf,%lf,%lf,%lf;", &d, &d1, &d2, &d3, &d4); + result = sscanf(ptr, "%d,%f,%f,%f,%f;", &d, &d1, &d2, &d3, &d4); if (result != 5) { handle_return(HTML_DATA_FORMATERROR); @@ -2224,6 +2222,9 @@ void server_log_sensor(OTF_PARAMS_DEF) { next += 1; } + DEBUG_PRINTLN(file_no); + DEBUG_PRINTLN(next); + uint32_t cursor = (file_no * SENSOR_LOG_PER_FILE) + next; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("cursor"), true)) { cursor = strtoul(tmp_buffer, &end, 10); @@ -2458,7 +2459,7 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); } - bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"100\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); + bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"5000\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); if (available_ether_buffer() <= 0) { send_packet(OTF_PARAMS); @@ -2509,8 +2510,8 @@ void server_json_all(OTF_PARAMS_DEF) { #if defined(USE_SENSORS) bfill.emit_p(PSTR(",\"sensors\":{")); server_json_sensors_main(OTF_PARAMS); - bfill.emit_p(PSTR(",\"sensor_desc\":{")); - server_json_sensor_description_main(OTF_PARAMS); + //bfill.emit_p(PSTR(",\"sensor_desc\":{")); + //server_json_sensor_description_main(OTF_PARAMS); #endif bfill.emit_p(PSTR("}")); handle_return(HTML_OK); diff --git a/sensor.cpp b/sensor.cpp index 690580e91..68bca21e8 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -272,7 +272,7 @@ const uint32_t get_sensor_unit_index(SensorUnit unit) { return static_cast(unit); } -Sensor::Sensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags) : +Sensor::Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint32_t flags) : interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit), flags(flags) { strncpy(this->name, name, SENSOR_NAME_LEN); this->name[SENSOR_NAME_LEN - 1] = 0; @@ -280,8 +280,8 @@ Sensor::Sensor(uint32_t interval, double min, double max, double scale, double o Sensor::Sensor() {} -double Sensor::get_new_value() { - double value = this->_get_raw_value(); +float Sensor::get_new_value() { + float value = this->_get_raw_value(); value = (value * this->scale) + this->offset; if (value < this->min) value = this->min; if (value > this->max) value = this->max; @@ -313,10 +313,10 @@ uint32_t Sensor::serialize(char* buf) { buf[i++] = static_cast(this->unit); i += write_buf(buf + i, this->interval); i += write_buf(buf + i, this->flags); - i += write_buf(buf + i, this->scale); - i += write_buf(buf + i, this->offset); - i += write_buf(buf + i, this->min); - i += write_buf(buf + i, this->max); + i += write_buf(buf + i, this->scale); + i += write_buf(buf + i, this->offset); + i += write_buf(buf + i, this->min); + i += write_buf(buf + i, this->max); i += this->_serialize_internal(buf + i); return i; @@ -330,15 +330,15 @@ uint32_t Sensor::_deserialize(char* buf) { this->unit = static_cast(buf[i++]); this->interval = read_buf(buf, &i); this->flags = read_buf(buf, &i); - this->scale = read_buf(buf, &i); - this->offset = read_buf(buf, &i); - this->min = read_buf(buf, &i); - this->max = read_buf(buf, &i); + this->scale = read_buf(buf, &i); + this->offset = read_buf(buf, &i); + this->min = read_buf(buf, &i); + this->max = read_buf(buf, &i); return i; } -EnsembleSensor::EnsembleSensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : +EnsembleSensor::EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : Sensor(interval, min, max, scale, offset, name, unit, flags), action(action), sensors(sensors) { @@ -347,7 +347,7 @@ EnsembleSensor::EnsembleSensor(uint32_t interval, double min, double max, double this->children[i] = children[i]; } else { - this->children[i] = ensemble_children_t{ sensor_id: 255, min : 0.0, max : 0.0, scale : 0.0, offset : 0.0 }; + this->children[i] = ensemble_children_t{ sensor_id: 255, min : 0.f, max : 0.f, scale : 0.f, offset : 0.f }; } } } @@ -363,10 +363,10 @@ void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { } void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); + bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); } -double EnsembleSensor::get_initial_value() { +float EnsembleSensor::get_initial_value() { switch (this->action) { case EnsembleAction::Min: return this->max; @@ -383,18 +383,18 @@ double EnsembleSensor::get_initial_value() { break; default: // Unreachable - return 0.0; + return 0.f; } } -double EnsembleSensor::_get_raw_value() { - double inital = this->get_initial_value(); +float EnsembleSensor::_get_raw_value() { + float inital = this->get_initial_value(); uint8_t count = 0; for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { uint8_t sensor = this->children[i].sensor_id; if (sensor < MAX_SENSORS && sensors[sensor].interval) { - double value = sensors[sensor].value; + float value = sensors[sensor].value; value = (value * this->children[i].scale) + this->children[i].offset; if (value < this->children[i].min) value = this->children[i].min; if (value > this->children[i].max) value = this->children[i].max; @@ -415,7 +415,7 @@ double EnsembleSensor::_get_raw_value() { break; default: // Unreachable - return 0.0; + return 0.f; } count += 1; @@ -423,10 +423,10 @@ double EnsembleSensor::_get_raw_value() { } if (count == 0) { - return 0.0; + return 0.f; } else if (this->action == EnsembleAction::Average) { - return inital / (double)count; + return inital / (float)count; } else { return inital; @@ -437,10 +437,10 @@ uint32_t EnsembleSensor::_serialize_internal(char* buf) { uint32_t i = 0; for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { i += write_buf(buf + i, this->children[j].sensor_id); - i += write_buf(buf + i, this->children[j].min); - i += write_buf(buf + i, this->children[j].max); - i += write_buf(buf + i, this->children[j].scale); - i += write_buf(buf + i, this->children[j].offset); + i += write_buf(buf + i, this->children[j].min); + i += write_buf(buf + i, this->children[j].max); + i += write_buf(buf + i, this->children[j].scale); + i += write_buf(buf + i, this->children[j].offset); } buf[i++] = static_cast(this->action); @@ -451,10 +451,10 @@ EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { uint32_t i = Sensor::_deserialize(buf); for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { this->children[j].sensor_id = read_buf(buf, &i); - this->children[j].min = read_buf(buf, &i); - this->children[j].max = read_buf(buf, &i); - this->children[j].scale = read_buf(buf, &i); - this->children[j].offset = read_buf(buf, &i); + this->children[j].min = read_buf(buf, &i); + this->children[j].max = read_buf(buf, &i); + this->children[j].scale = read_buf(buf, &i); + this->children[j].offset = read_buf(buf, &i); } this->action = static_cast(buf[i++]); @@ -462,7 +462,7 @@ EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { } -WeatherSensor::WeatherSensor(uint32_t interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : +WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : Sensor(interval, min, max, scale, offset, name, unit, flags), action(action), weather_getter(weather_getter) { @@ -476,11 +476,11 @@ void WeatherSensor::emit_description_json(BufferFiller* bfill) { bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"default\":\"0\",\"extra\":[]}]}")); } -double WeatherSensor::get_initial_value() { - return 0.0; +float WeatherSensor::get_initial_value() { + return 0.f; } -double WeatherSensor::_get_raw_value() { +float WeatherSensor::_get_raw_value() { return this->weather_getter(this->action); } @@ -503,7 +503,6 @@ SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_cou for (size_t i = 0; i < point_count; i++) { this->points[i] = points[i]; } - } SensorAdjustment::SensorAdjustment(char* buf) { @@ -513,14 +512,14 @@ SensorAdjustment::SensorAdjustment(char* buf) { this->point_count = buf[i++]; for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { - this->points[j].x = read_buf(buf, &i); - this->points[j].y = read_buf(buf, &i); + this->points[j].x = read_buf(buf, &i); + this->points[j].y = read_buf(buf, &i); } } -double SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { +float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->sid < MAX_SENSORS && sensors[this->sid].interval) { - double value = sensors[this->sid].value; + float value = sensors[this->sid].value; if (value <= this->points[0].x) return this->points[0].y; if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; @@ -542,7 +541,7 @@ double SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { return value; } else { - return 1.0; + return 1.f; } } @@ -553,8 +552,8 @@ uint32_t SensorAdjustment::serialize(char* buf) { buf[i++] = this->point_count; for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { - i += write_buf(buf + i, this->points[j].x); - i += write_buf(buf + i, this->points[j].y); + i += write_buf(buf + i, this->points[j].x); + i += write_buf(buf + i, this->points[j].y); } return i; diff --git a/sensor.h b/sensor.h index c423ff245..31d26cb11 100644 --- a/sensor.h +++ b/sensor.h @@ -16,7 +16,7 @@ typedef struct { uint32_t interval; uint32_t flags; uint32_t next_update; - double value; + float value; } sensor_memory_t; enum class SensorType : uint8_t { @@ -83,30 +83,30 @@ typedef enum { class Sensor { public: - Sensor(uint32_t interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags); + Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint32_t flags); Sensor(); virtual ~Sensor() {} - double get_new_value(); + float get_new_value(); uint32_t serialize(char *buf); void virtual emit_extra_json(BufferFiller *bfill) = 0; uint32_t interval = 1; - double min = 0.0; - double max = 0.0; - double scale = 0.0; - double offset = 0.0; + float min = 0.f; + float max = 0.f; + float scale = 0.f; + float offset = 0.f; char name[SENSOR_NAME_LEN] = {0}; SensorUnit unit = SensorUnit::None; uint32_t flags = 0; SensorType virtual get_sensor_type() = 0; - double virtual get_initial_value() = 0; + float virtual get_initial_value() = 0; private: - double virtual _get_raw_value() = 0; + float virtual _get_raw_value() = 0; protected: uint32_t _deserialize(char *buf); uint32_t virtual _serialize_internal(char *buf) = 0; @@ -125,17 +125,17 @@ typedef Sensor* (*SensorGetter)(uint8_t); typedef struct { uint8_t sensor_id; - double min; - double max; - double scale; - double offset; + float min; + float max; + float scale; + float offset; } ensemble_children_t; #define ENSEMBLE_SENSOR_CHILDREN_COUNT 4 class EnsembleSensor : public Sensor { public: - EnsembleSensor(uint32_t interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); + EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); EnsembleSensor(sensor_memory_t *sensors, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -148,10 +148,10 @@ class EnsembleSensor : public Sensor { ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; EnsembleAction action; - double get_initial_value(); + float get_initial_value(); private: - double _get_raw_value(); + float _get_raw_value(); uint32_t _serialize_internal(char *buf); sensor_memory_t *sensors; @@ -161,11 +161,11 @@ enum class WeatherAction { MAX_VALUE, }; -typedef double (*WeatherGetter)(WeatherAction); +typedef float (*WeatherGetter)(WeatherAction); class WeatherSensor : public Sensor { public: - WeatherSensor(uint32_t interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); WeatherSensor(WeatherGetter weather_getter, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -177,18 +177,18 @@ class WeatherSensor : public Sensor { WeatherAction action; - double get_initial_value(); + float get_initial_value(); private: - double _get_raw_value(); + float _get_raw_value(); uint32_t _serialize_internal(char *buf); WeatherGetter weather_getter; }; typedef struct { - double x; - double y; + float x; + float y; } sensor_adjustment_point_t; #define SENSOR_ADJUSTMENT_POINTS 8 @@ -202,7 +202,7 @@ class SensorAdjustment { SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points); SensorAdjustment(char *buf); - double get_adjustment_factor(sensor_memory_t *sensors); + float get_adjustment_factor(sensor_memory_t *sensors); uint32_t serialize(char *buf); uint8_t flags; From 03fc4de3443e4f05460b62376c1932f2c2c420d9 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 12 Apr 2026 22:45:48 -0400 Subject: [PATCH 083/115] adopt placement new to allow statically allocated sensor object, eliminating dynamic new and delete every time we need to read a sensor --- OpenSprinkler.cpp | 63 ++++++++++++++++++++++++++++++++++------ OpenSprinkler.h | 13 +++++++-- mqtt.cpp | 8 ++++- opensprinkler_server.cpp | 5 ---- 4 files changed, 71 insertions(+), 18 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index f2a062ac8..a68e4ab5b 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2422,6 +2422,15 @@ void OpenSprinkler::raindelay_stop() { #if defined(USE_SENSORS) /** Sensor functions */ Sensor *OpenSprinkler::parse_sensor(os_file_type file) { + static uint8_t sensor_scratchpad[sizeof(SensorUnion)] __attribute__((aligned(4))); + static Sensor* active_sensor = nullptr; + + // The Auto-Destructor: If a sensor was previously loaded, + // call its destructor before we overwrite the memory. + if (active_sensor != nullptr) { + active_sensor->~Sensor(); + active_sensor = nullptr; + } uint32_t len = 0; file_read(file, &len, sizeof(len)); @@ -2433,19 +2442,21 @@ Sensor *OpenSprinkler::parse_sensor(os_file_type file) { if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) { return nullptr; } - SensorType sensor_type = static_cast(*tmp_buffer); - switch (sensor_type) { case SensorType::Ensemble: - return new EnsembleSensor(os.sensors, (char*)tmp_buffer); + active_sensor = new (sensor_scratchpad) EnsembleSensor(os.sensors, (char*)tmp_buffer); + break; case SensorType::ADS1115: - return new ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + active_sensor = new (sensor_scratchpad) ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + break; case SensorType::Weather: - return new WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); + active_sensor = new (sensor_scratchpad) WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); + break; default: return nullptr; }; + return active_sensor; } Sensor *OpenSprinkler::get_sensor(uint8_t index) { @@ -2458,7 +2469,7 @@ Sensor *OpenSprinkler::get_sensor(uint8_t index) { Sensor *result = parse_sensor(file); file_close(file); return result; -} else { + } else { DEBUG_PRINT("Failed to open file: "); DEBUG_PRINTLN(SENSORS_FILENAME); return nullptr; @@ -2482,6 +2493,40 @@ os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) return file_open(sensor_log_name_buf, mode); } +void list_all_files() { + Serial.println(PSTR("\n--- Flash File System Map ---")); + Serial.printf("%-25s %10s\n", "Filename", "Size (B)"); + Serial.println(PSTR("---------------------------------------")); + + // Open the root directory + Dir dir = LittleFS.openDir("/"); + uint32_t totalUsed = 0; + uint32_t fileCount = 0; + + // Iterate through all files + while (dir.next()) { + String fileName = dir.fileName(); + uint32_t fileSize = dir.fileSize(); + + Serial.printf("%-25s %10u\n", fileName.c_str(), fileSize); + + totalUsed += fileSize; + fileCount++; + } + + // Get filesystem totals + FSInfo fs_info; + LittleFS.info(fs_info); + + Serial.println(PSTR("---------------------------------------")); + Serial.printf("Total Files: %u\n", fileCount); + Serial.printf("Used Space: %u bytes\n", totalUsed); + Serial.printf("Total Flash: %u bytes\n", fs_info.totalBytes); + Serial.printf("Free Space: %u bytes\n", fs_info.totalBytes - fs_info.usedBytes); + Serial.printf("LittleFS Block Size: %u bytes\n", fs_info.blockSize); + Serial.println(PSTR("---------------------------------------\n")); +} + void OpenSprinkler::load_sensors() { lcd.clear(); lcd.setCursor(0,0); @@ -2548,7 +2593,6 @@ void OpenSprinkler::load_sensors() { sensors[i].flags = sensor->flags; sensors[i].next_update = 0; sensors[i].value = sensor->get_initial_value(); - delete sensor; } } @@ -2575,6 +2619,8 @@ void OpenSprinkler::load_sensors() { } if (f == SENSOR_LOG_FILE_COUNT) f -= 1; sensor_file_no = f; + + list_all_files(); } void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { @@ -2651,7 +2697,6 @@ void OpenSprinkler::poll_sensors() { Sensor *sensor = get_sensor(i); if (sensor) { sensors[i].value = sensor->get_new_value(); - delete sensor; sensors[i].next_update = millis() + (sensors[i].interval * 1000 * 60); if (sensors[i].flags & (1 << SENSOR_FLAG_LOG)) { os.log_sensor(i, sensors[i].value); @@ -2665,7 +2710,7 @@ void OpenSprinkler::poll_sensors() { SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { // result is statically allocated to avoid repeatedly new and delete // Do not call delete on the return result from this function - static SensorAdjustment result(tmp_buffer); + static SensorAdjustment result(0, 255, 0, nullptr); if (index > MAX_NUM_PROGRAMS) return nullptr; os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); diff --git a/OpenSprinkler.h b/OpenSprinkler.h index e1d9d0d85..1f295124a 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -31,6 +31,7 @@ #include "mqtt.h" #include "RCSwitch.h" #include +#include #if defined(ESP8266) // headers for Arduino #include @@ -222,6 +223,12 @@ struct OTCConfig { uint32_t port; }; +union SensorUnion { + ADS1115Sensor ads1115; + EnsembleSensor ensemble; + WeatherSensor weather; +}; + extern const char iopt_json_names[]; extern const uint8_t iopt_max[]; @@ -374,12 +381,12 @@ class OpenSprinkler { static os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); static void remove_sensor_log(uint16_t file_no); static void load_sensors(); - static Sensor *parse_sensor(os_file_type file); - static Sensor *get_sensor(uint8_t index); + static Sensor *parse_sensor(os_file_type file); // return is a statically allocated object, don't delete + static Sensor *get_sensor(uint8_t index); // return is a statically allocated object, don't delete static void write_sensor(Sensor *sensor, uint8_t index); void log_sensor(uint8_t sid, float value); static void poll_sensors(); - static SensorAdjustment *get_sensor_adjust(uint8_t index); + static SensorAdjustment *get_sensor_adjust(uint8_t index); // return is a statically allocated object, don't delete static void write_sensor_adjust(SensorAdjustment *adj, uint8_t index); static float get_sensor_weather_data(WeatherAction action); diff --git a/mqtt.cpp b/mqtt.cpp index 64a3cb4ff..b1a36dd08 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -483,7 +483,13 @@ WiFiClient wifiClient; int OSMqtt::_init(void) { Client * client = NULL; - if (mqtt_client) { delete mqtt_client; mqtt_client = 0; } + if (mqtt_client) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdelete-non-virtual-dtor" + delete mqtt_client; +#pragma GCC diagnostic pop + mqtt_client = 0; + } #if defined(ESP8266) client = &wifiClient; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7dbb4a1c8..9b048265a 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1886,7 +1886,6 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { sensor->emit_extra_json(&bfill); bfill.emit_p(PSTR("}")); sensor_count += 1; - delete sensor; // push out a packet if available // buffer size is getting small if (available_ether_buffer() <= 0) { @@ -1963,7 +1962,6 @@ void server_change_sensor(OTF_PARAMS_DEF) { interval = sensor->interval; unit = sensor->unit; flags = sensor->flags; - delete sensor; } } @@ -2031,7 +2029,6 @@ void server_change_sensor(OTF_PARAMS_DEF) { } action = e->action; - delete sensor; } } @@ -2081,7 +2078,6 @@ void server_change_sensor(OTF_PARAMS_DEF) { ADS1115Sensor* e = static_cast(sensor); sensor_index = e->sensor_index; sensor_pin = e->pin; - delete sensor; } } @@ -2104,7 +2100,6 @@ void server_change_sensor(OTF_PARAMS_DEF) { if ((sensor = os.get_sensor(sid))) { WeatherSensor* e = static_cast(sensor); action = e->action; - delete sensor; } } From 87800991c3ac926830539031e260fb86c37bc979 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 13 Apr 2026 08:07:23 -0400 Subject: [PATCH 084/115] change notifier to use circular array queue instead of linked list, to avoid new and delete repeatedly --- notifier.cpp | 40 +++++++++++++--------------------------- notifier.h | 10 ++++------ 2 files changed, 17 insertions(+), 33 deletions(-) diff --git a/notifier.cpp b/notifier.cpp index 4010fdd90..de69561c5 100644 --- a/notifier.cpp +++ b/notifier.cpp @@ -26,9 +26,10 @@ #include "ArduinoJson.hpp" #include "opensprinkler_server.h" -NotifNodeStruct* NotifQueue::head = NULL; -NotifNodeStruct* NotifQueue::tail = NULL; -unsigned char NotifQueue::nqueue = 0; +uint8_t NotifQueue::nqueue = 0; +uint8_t NotifQueue::head = 0; +uint8_t NotifQueue::tail = 0; +NotifNodeStruct NotifQueue::queue[NOTIF_QUEUE_MAXSIZE]; extern OpenSprinkler os; extern ProgramData pd; @@ -63,15 +64,10 @@ bool NotifQueue::add(uint16_t t, uint32_t l, float f, uint8_t b) { return false; } if(nqueuenext = node; - } - tail = node; + queue[tail] = NotifNodeStruct(t, l, f, b); + tail = (tail + 1 )% NOTIF_QUEUE_MAXSIZE; nqueue++; - DEBUG_PRINTF("NotifQueue::add (type %d) [%d]\n", t, nqueue); + DEBUG_PRINTF("NotifQueue::add (type %d) [%d|%d|%d]\n", t, nqueue, head, tail); return true; } DEBUG_PRINTLN(F("NotifQueue::add queue is full!")); @@ -79,15 +75,9 @@ bool NotifQueue::add(uint16_t t, uint32_t l, float f, uint8_t b) { } void NotifQueue::clear() { - while(nqueue!=0) { - NotifNodeStruct* node = head; - head = head->next; - if(head==NULL) { - tail = NULL; - } - delete node; - nqueue--; - } + nqueue = 0; + head = 0; + tail = 0; } void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval); @@ -96,16 +86,12 @@ bool NotifQueue::run(int n) { if(nqueue == 0) return false; // queue is empty if(n<=0 || n>nqueue) n=nqueue; while(nqueue!=0 && n!=0) { - NotifNodeStruct* node = head; - head = head->next; - if(head==NULL) { - tail = NULL; - } + NotifNodeStruct* node = &queue[head]; + head = (head + 1) % NOTIF_QUEUE_MAXSIZE; push_message(node->type, node->lval, node->fval, node->bval); - DEBUG_PRINTF("NotifQueue::run (type %d) [%d]\n", node->type, nqueue); - delete node; nqueue--; n--; + DEBUG_PRINTF("NotifQueue::run (type %d) [%d|%d|%d]\n", node->type, nqueue, head, tail); } return true; } diff --git a/notifier.h b/notifier.h index ca1f3e8c0..bdc81e4a3 100644 --- a/notifier.h +++ b/notifier.h @@ -23,7 +23,7 @@ #pragma once -#define NOTIF_QUEUE_MAXSIZE 32 +#define NOTIF_QUEUE_MAXSIZE 16 #include "OpenSprinkler.h" #include "types.h" @@ -34,8 +34,7 @@ struct NotifNodeStruct { uint32_t lval; float fval; uint8_t bval; - NotifNodeStruct *next; - NotifNodeStruct(uint16_t t, uint32_t l=0, float f=0.f, uint8_t b=0) : type(t), lval(l), fval(f), bval(b), next(NULL) + NotifNodeStruct(uint16_t t=0, uint32_t l=0, float f=0.f, uint8_t b=0) : type(t), lval(l), fval(f), bval(b) { } }; @@ -49,7 +48,6 @@ class NotifQueue { // Run/Process elements. By default process 1 at a time. If n<=0, process all. static bool run(int n=1); protected: - static NotifNodeStruct* head; - static NotifNodeStruct* tail; - static unsigned char nqueue; + static NotifNodeStruct queue[NOTIF_QUEUE_MAXSIZE]; + static uint8_t head, tail, nqueue; }; From 79b7f346c9689c4681b9ba557860166186d3203e Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 13 Apr 2026 08:25:16 -0400 Subject: [PATCH 085/115] fix demo compilation error --- OpenSprinkler.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 1f295124a..5ac9695cd 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -223,12 +223,6 @@ struct OTCConfig { uint32_t port; }; -union SensorUnion { - ADS1115Sensor ads1115; - EnsembleSensor ensemble; - WeatherSensor weather; -}; - extern const char iopt_json_names[]; extern const uint8_t iopt_max[]; @@ -245,6 +239,11 @@ class OpenSprinkler { #endif #if defined(USE_SENSORS) + union SensorUnion { + ADS1115Sensor ads1115; + EnsembleSensor ensemble; + WeatherSensor weather; + }; static sensor_memory_t sensors[MAX_SENSORS]; static uint16_t sensor_file_no; #endif From 8c701e936517247f32f8ede143287dc148ed9d0c Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 13 Apr 2026 08:32:06 -0400 Subject: [PATCH 086/115] fix docker build error --- OpenSprinkler.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index a68e4ab5b..644ceebfa 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2494,6 +2494,7 @@ os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) } void list_all_files() { +#if defined(ESP8266) Serial.println(PSTR("\n--- Flash File System Map ---")); Serial.printf("%-25s %10s\n", "Filename", "Size (B)"); Serial.println(PSTR("---------------------------------------")); @@ -2525,6 +2526,7 @@ void list_all_files() { Serial.printf("Free Space: %u bytes\n", fs_info.totalBytes - fs_info.usedBytes); Serial.printf("LittleFS Block Size: %u bytes\n", fs_info.blockSize); Serial.println(PSTR("---------------------------------------\n")); + #endif } void OpenSprinkler::load_sensors() { From 3948de7189124b44e0a2724060f4dbac27feb4da Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 13 Apr 2026 09:43:18 -0400 Subject: [PATCH 087/115] adjust the conversative size of tmp_buffer and ether_buffer --- main.cpp | 4 ++-- opensprinkler_server.cpp | 2 +- weather.cpp | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/main.cpp b/main.cpp index 46acbe6a3..9cee70983 100644 --- a/main.cpp +++ b/main.cpp @@ -74,8 +74,8 @@ void manual_start_program(unsigned char, unsigned char, unsigned char); #define FLOWPOLL_INTERVAL 5 // flow poll interval (in milli-seconds) #define CURRPOLL_INTERVAL 20 // current poll interval (in milli-seconds) // Define buffers: need them to be sufficiently large to cover string option reading -char ether_buffer[ETHER_BUFFER_SIZE*2]; // ethernet buffer, make it twice as large to allow overflow -char tmp_buffer[TMP_BUFFER_SIZE*2]; // scratch buffer, make it twice as large to allow overflow +char ether_buffer[ETHER_BUFFER_SIZE+100]; // ethernet buffer, expand by 100 bytes to allow possible overflow +char tmp_buffer[TMP_BUFFER_SIZE+100]; // scratch buffer, expand by 100 bytes to allow overflow // ====== Object defines ====== OpenSprinkler os; // OpenSprinkler object diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 9b048265a..923da8d91 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -165,7 +165,7 @@ unsigned char findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const ch } void rewind_ether_buffer() { - bfill = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE*2); + bfill = BufferFiller(ether_buffer, sizeof(ether_buffer)); ether_buffer[0] = 0; } diff --git a/weather.cpp b/weather.cpp index 5166f80b3..fe02f4f34 100644 --- a/weather.cpp +++ b/weather.cpp @@ -160,7 +160,7 @@ static void getweather_callback_with_peel_header(char* buffer) { void GetWeather() { if(!os.network_connected()) return; // use temp buffer to construct get command - BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(tmp_buffer, sizeof(tmp_buffer)); int method = os.iopts[IOPT_USE_WEATHER]; // use manual adjustment call for monthly adjustment -- a bit ugly, but does not involve weather server changes if(method==WEATHER_METHOD_MONTHLY) method=WEATHER_METHOD_MANUAL; From c3fdf2440b105971f4af6637a65968930577b51b Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Thu, 16 Apr 2026 11:35:45 -0400 Subject: [PATCH 088/115] adjust overflow buffer size; improve email feature to support local smtp server that does not require authentication --- EMailSender.h | 2 +- OpenSprinkler.cpp | 6 +++--- defines.h | 8 +++++++- main.cpp | 4 ++-- notifier.cpp | 24 ++++++++++++++++++++++-- opensprinkler_server.cpp | 6 +++--- weather.cpp | 2 +- 7 files changed, 39 insertions(+), 13 deletions(-) diff --git a/EMailSender.h b/EMailSender.h index 36c42f580..df806dd48 100644 --- a/EMailSender.h +++ b/EMailSender.h @@ -508,7 +508,7 @@ class EMailSender { bool isSASLLogin = false; bool useAuth = true; - bool isCramMD5Login = false; + bool isCramMD5Login = false; String _serverResponce; diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 644ceebfa..58a83b1f6 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1957,7 +1957,7 @@ void OpenSprinkler::switch_remotestation(RemoteIPStationData *data, bool turnon, ip[3] = ip4&0xff; char *p = tmp_buffer; - BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(p, TMP_BUFFER_ALLOC_SIZE); // if turning on the zone and duration is defined, give duration as the timer value // otherwise: // if autorefresh is defined, we give a fixed duration each time, and auto refresh will renew it periodically @@ -1996,7 +1996,7 @@ void OpenSprinkler::switch_remotestation(RemoteOTCStationData *data, bool turnon memcpy((char*)©, (char*)data, sizeof(RemoteOTCStationData)); copy.token[sizeof(copy.token)-1] = 0; // ensure the string ends properly char *p = tmp_buffer; - BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(p, TMP_BUFFER_ALLOC_SIZE); // if turning on the zone and duration is defined, give duration as the timer value // otherwise: // if autorefresh is defined, we give a fixed duration each time, and auto refresh will renew it periodically @@ -2037,7 +2037,7 @@ void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon, bool char * cmd = turnon ? on_cmd : off_cmd; char *p = tmp_buffer; - BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(p, TMP_BUFFER_ALLOC_SIZE); if(cmd==NULL || server==NULL) return; // proceed only if cmd and server are valid diff --git a/defines.h b/defines.h index e70dfeb52..648dda8c2 100755 --- a/defines.h +++ b/defines.h @@ -24,7 +24,8 @@ #define ENABLE_DEBUG // enable serial debug -#define TMP_BUFFER_SIZE 320 // scratch buffer size +#define TMP_BUFFER_SIZE 320 // scratch buffer size +#define TMP_BUFFER_ALLOC_SIZE TMP_BUFFER_SIZE+100 // allocate extra space to allow overflow when needed /** Firmware version, hardware version, and maximal values */ #define OS_FW_VERSION 221 // Firmware version: 221 means 2.2.1 @@ -331,6 +332,7 @@ enum { #define PIN_LATCH_VOLT_SENSE A0 // latch voltage sensing pin #define PIN_FREE_LIST {} // no free GPIO pin at the moment #define ETHER_BUFFER_SIZE 2048 + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+100 // allocate extra space to allow overflow when needed #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above #define ETHER_SPI_CLOCK 10000000L // SPI clock for Ethernet (e.g. 10MHz) @@ -421,6 +423,8 @@ enum { #define PIN_FREE_LIST {5,6,7,8,9,11,12,13,16,19,20,21,23,25,26} // free GPIO pins #define ETHER_BUFFER_SIZE 16384 + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+100 // allocate extra space to allow overflow when needed + #define SDA 0 #define SCL 0 @@ -445,6 +449,8 @@ enum { #define PIN_RFTX 0 #define PIN_FREE_LIST {} #define ETHER_BUFFER_SIZE 16384 + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+100 // allocate extra space to allow overflow when needed + #endif diff --git a/main.cpp b/main.cpp index 9cee70983..cb45d2c74 100644 --- a/main.cpp +++ b/main.cpp @@ -74,8 +74,8 @@ void manual_start_program(unsigned char, unsigned char, unsigned char); #define FLOWPOLL_INTERVAL 5 // flow poll interval (in milli-seconds) #define CURRPOLL_INTERVAL 20 // current poll interval (in milli-seconds) // Define buffers: need them to be sufficiently large to cover string option reading -char ether_buffer[ETHER_BUFFER_SIZE+100]; // ethernet buffer, expand by 100 bytes to allow possible overflow -char tmp_buffer[TMP_BUFFER_SIZE+100]; // scratch buffer, expand by 100 bytes to allow overflow +char ether_buffer[ETHER_BUFFER_ALLOC_SIZE]; // ethernet buffer +char tmp_buffer[TMP_BUFFER_ALLOC_SIZE]; // scratch buffer // ====== Object defines ====== OpenSprinkler os; // OpenSprinkler object diff --git a/notifier.cpp b/notifier.cpp index de69561c5..e9010312c 100644 --- a/notifier.cpp +++ b/notifier.cpp @@ -568,15 +568,35 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { EMailSender emailSend(email_username, email_password); emailSend.setSMTPServer(email_host); emailSend.setSMTPPort(email_port); + if (email_username == NULL || strlen(email_username) == 0) { + emailSend.setUseAuth(false); + } EMailSender::Response resp = emailSend.send(email_recipient, email_message); } #else struct smtp *smtp = NULL; + enum smtp_connection_security security_flag; + if (email_port == 25) { + security_flag = SMTP_SECURITY_NONE; + } else if (email_port == 587) { + security_flag = SMTP_SECURITY_STARTTLS; + } else { + // Default to Implicit SSL for 465 (or legacy configurations) + security_flag = SMTP_SECURITY_TLS; + } + enum smtp_authentication_method auth_flag; + // Check if the user left the SMTP Username field blank in the UI + if (email_username == NULL || strlen(email_username) == 0) { + auth_flag = SMTP_AUTH_NONE; + } else { + // Retain the OpenSprinkler default for populated credentials + auth_flag = SMTP_AUTH_PLAIN; + } String email_port_str = to_string(email_port); smtp_status_code rc; if(email_host && email_username && email_password && email_recipient) { // make sure all are valid - rc = smtp_open(email_host, email_port_str.c_str(), SMTP_SECURITY_TLS, SMTP_NO_CERT_VERIFY, NULL, &smtp); - rc = smtp_auth(smtp, SMTP_AUTH_PLAIN, email_username, email_password); + rc = smtp_open(email_host, email_port_str.c_str(), security_flag, SMTP_NO_CERT_VERIFY, NULL, &smtp); + rc = smtp_auth(smtp, auth_flag, email_username, email_password); rc = smtp_address_add(smtp, SMTP_ADDRESS_FROM, email_username, "OpenSprinkler"); rc = smtp_address_add(smtp, SMTP_ADDRESS_TO, email_recipient, "User"); rc = smtp_header_add(smtp, "Subject", email_message.subject.c_str()); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 923da8d91..2d6ee8242 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -165,7 +165,7 @@ unsigned char findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const ch } void rewind_ether_buffer() { - bfill = BufferFiller(ether_buffer, sizeof(ether_buffer)); + bfill = BufferFiller(ether_buffer, ETHER_BUFFER_ALLOC_SIZE); ether_buffer[0] = 0; } @@ -1760,7 +1760,7 @@ void server_json_log(OTF_PARAMS_DEF) { bool comma = 0; for(unsigned int i=start;i<=end;i++) { - snprintf(tmp_buffer, TMP_BUFFER_SIZE*2 , "%d", i); + snprintf(tmp_buffer, TMP_BUFFER_ALLOC_SIZE , "%d", i); make_logfile_name(tmp_buffer); #if defined(ESP8266) @@ -2573,7 +2573,7 @@ void server_fill_files(OTF_PARAMS_DEF) { ether_buffer[75] = 0; FSInfo fs_info; for(int index=1;index<64;index++) { - snprintf(tmp_buffer, TMP_BUFFER_SIZE*2 , "%d", index); + snprintf(tmp_buffer, TMP_BUFFER_ALLOC_SIZE , "%d", index); make_logfile_name(tmp_buffer); DEBUG_PRINT(F("creating ")); DEBUG_PRINT(tmp_buffer); diff --git a/weather.cpp b/weather.cpp index fe02f4f34..2a7569d01 100644 --- a/weather.cpp +++ b/weather.cpp @@ -160,7 +160,7 @@ static void getweather_callback_with_peel_header(char* buffer) { void GetWeather() { if(!os.network_connected()) return; // use temp buffer to construct get command - BufferFiller bf = BufferFiller(tmp_buffer, sizeof(tmp_buffer)); + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_ALLOC_SIZE); int method = os.iopts[IOPT_USE_WEATHER]; // use manual adjustment call for monthly adjustment -- a bit ugly, but does not involve weather server changes if(method==WEATHER_METHOD_MONTHLY) method=WEATHER_METHOD_MANUAL; From 6a8c5f5b1715c58c2e3c26f558631788d636ae86 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 25 Apr 2026 08:16:22 -0400 Subject: [PATCH 089/115] revise sensor log, change log data structure, optimize each log file to be no more than one littleFS block (8K) in size --- OpenSprinkler.cpp | 239 +++++++++++++++++++++++++-------------- OpenSprinkler.h | 7 +- defines.h | 11 +- opensprinkler_server.cpp | 231 +++++++++++++++---------------------- sensor.h | 19 ++++ utils.cpp | 12 ++ utils.h | 1 + 7 files changed, 288 insertions(+), 232 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 58a83b1f6..dd05464a1 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -31,7 +31,6 @@ /** Declare static data members */ #if defined(USE_SENSORS) sensor_memory_t OpenSprinkler::sensors[64] = {0}; -uint16_t OpenSprinkler::sensor_file_no = 0; #endif OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; @@ -2119,9 +2118,7 @@ void OpenSprinkler::factory_reset() { #if defined(USE_SENSORS) // remove all sensor files, so they will be re-created in load_sensors() remove_file(SENSORS_FILENAME); - for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - remove_sensor_log(f); - } + remove_sensor_log(); remove_file(SENADJ_FILENAME); #endif @@ -2476,21 +2473,32 @@ Sensor *OpenSprinkler::get_sensor(uint8_t index) { } } -void OpenSprinkler::remove_sensor_log(uint16_t file_no) { - char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; - sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; - memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); - snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", (uint16_t)(file_no % 1000)); - remove_file(sensor_log_name_buf); +void OpenSprinkler::get_sensor_log_filename(char *buf, uint16_t file_no) { + snprintf(buf, 16, "%s%03u", SENSORS_LOG_FILENAME, file_no%1000); } os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) { - char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; - sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; - memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); - snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", (uint16_t)(file_no % 1000)); + char fname[16]; + get_sensor_log_filename(fname, file_no); + return file_open(fname, mode); +} + +os_file_type OpenSprinkler::open_sensor_log_header(FileOpenMode mode) { + return file_open(SENSORS_LOG_HEADER_FILENAME, mode); +} - return file_open(sensor_log_name_buf, mode); +void OpenSprinkler::remove_sensor_log(int16_t file_no) { + char fname[16]; + if (file_no < 0) { + remove_file(SENSORS_LOG_HEADER_FILENAME); + for (uint16_t i = 0; i < SENSOR_LOG_MAX_FILES; i++) { + get_sensor_log_filename(fname, i); + remove_file(fname); + } + } else { + get_sensor_log_filename(fname, (uint16_t)file_no); + remove_file(fname); + } } void list_all_files() { @@ -2520,10 +2528,12 @@ void list_all_files() { LittleFS.info(fs_info); Serial.println(PSTR("---------------------------------------")); - Serial.printf("Total Files: %u\n", fileCount); - Serial.printf("Used Space: %u bytes\n", totalUsed); + Serial.printf("Total File: %u\n", fileCount); + Serial.printf("Total Size: %u bytes\n", totalUsed); Serial.printf("Total Flash: %u bytes\n", fs_info.totalBytes); Serial.printf("Free Space: %u bytes\n", fs_info.totalBytes - fs_info.usedBytes); + Serial.printf("Used Bytes: %u bytes\n", fs_info.usedBytes); + Serial.printf("LittleFS Block Size: %u bytes\n", fs_info.blockSize); Serial.println(PSTR("---------------------------------------\n")); #endif @@ -2553,22 +2563,6 @@ void OpenSprinkler::load_sensors() { DEBUG_PRINTLN(SENSORS_FILENAME); } - // Initialize the sensor log files (SENSORS_LOG_FILENAME_xxx) - uint16_t next = SENSOR_LOG_PER_FILE; - for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - file = open_sensor_log(f, FileOpenMode::WriteTruncate); - if (file) { - file_write(file, &next, sizeof(next)); - for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - } - file_close(file); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(f); - } - } - // Initialize the program adjustment file (SENADJ_FILENAME) file = file_open(SENADJ_FILENAME, FileOpenMode::WriteTruncate); if (file) { @@ -2604,24 +2598,13 @@ void OpenSprinkler::load_sensors() { DEBUG_PRINTLN(SENSORS_FILENAME); } - // 3. `next` pointer recovery - uint16_t next = 0; - size_t f; - for (f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - file = open_sensor_log(f, FileOpenMode::Read); - if (file) { - file_read(file, &next, sizeof(next)); - file_close(file); - - if (next < SENSOR_LOG_PER_FILE) break; - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(f); - } - } - if (f == SENSOR_LOG_FILE_COUNT) f -= 1; - sensor_file_no = f; + list_all_files(); + // Uncomment one of the following to run a sensor log performance test on boot: + //test_sensor_log(100); // quick smoke test + test_sensor_log(2000); // moderate + //test_sensor_log(32760); // full capacity + // test_sensor_log(40000); // beyond capacity (tests ring wrap) list_all_files(); } @@ -2652,44 +2635,124 @@ void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { } void OpenSprinkler::log_sensor(uint8_t sid, float value) { - os_file_type file = open_sensor_log(sensor_file_no, FileOpenMode::ReadWrite); - if (file) { - uint16_t next = 0; - file_read(file, &next, sizeof(next)); - if (next == SENSOR_LOG_PER_FILE) next -= 1; - - time_os_t timestamp = now(); - tmp_buffer[0] = 1; - tmp_buffer[1] = sid; - char *ptr = tmp_buffer + 2; - memcpy(ptr, ×tamp, sizeof(timestamp)); - ptr += sizeof(timestamp); - memcpy(ptr, &value, sizeof(value)); - - uint32_t pos = (next * SENSOR_LOG_ITEM_SIZE); - - if (next > 0) { - next -= 1; - } else { - next = SENSOR_LOG_PER_FILE; - if (sensor_file_no > 0) { - sensor_file_no -= 1; - } else { - sensor_file_no = SENSOR_LOG_FILE_COUNT - 1; - } - } - - file_seek(file, 0); - file_write(file, &next, sizeof(next)); - - file_seek(file, pos, FileSeekMode::Current); - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_LOG_FILENAME); - } + // Read central header; create/recreate if missing or version mismatch + SensorLogHeader hdr = {}; + bool hdr_valid = false; + os_file_type hfile = open_sensor_log_header(FileOpenMode::Read); + if (hfile) { + int n = file_read(hfile, &hdr, sizeof(hdr)); + file_close(hfile); + hdr_valid = (n == (int)sizeof(hdr) && + hdr.magic == SENSOR_LOG_MAGIC && + hdr.version == SENSOR_LOG_VERSION); + } + + if (!hdr_valid) { + // First use or firmware upgrade: wipe any stale data files, write fresh header + char fname[16]; + for (uint16_t i = 0; i < SENSOR_LOG_MAX_FILES; i++) { + get_sensor_log_filename(fname, i); + remove_file(fname); + } + hdr = {}; + hdr.magic = SENSOR_LOG_MAGIC; + hdr.version = SENSOR_LOG_VERSION; + hdr.max_files = SENSOR_LOG_MAX_FILES; + hdr.records_per_file = SENSOR_LOG_RECORDS_PER_FILE; + hfile = open_sensor_log_header(FileOpenMode::WriteTruncate); + if (!hfile) { + DEBUG_PRINTLN("Failed to create sensor log header"); + return; + } + file_write(hfile, &hdr, sizeof(hdr)); + file_close(hfile); + } + + // Open current data file for appending; infer record count from file size + os_file_type dfile = open_sensor_log(hdr.cur_file, FileOpenMode::Append); + if (!dfile) { + DEBUG_PRINTLN("Failed to open sensor log data file"); + return; + } + uint32_t count = file_size(dfile) / sizeof(SensorLogRecord); + + if (count >= hdr.records_per_file) { + // Current file is full — rotate to next slot + file_close(dfile); + hdr.cur_file = (uint16_t)((hdr.cur_file + 1) % hdr.max_files); + if (hdr.cur_file == 0 && !hdr.wrapped) hdr.wrapped = 1; + + // Evict the file at the new slot (oldest data) + remove_sensor_log(hdr.cur_file); + + // Persist updated header (only written on rotation, not on every record) + hfile = open_sensor_log_header(FileOpenMode::WriteTruncate); + if (hfile) { file_write(hfile, &hdr, sizeof(hdr)); file_close(hfile); } + + dfile = open_sensor_log(hdr.cur_file, FileOpenMode::Append); + if (!dfile) { + DEBUG_PRINTLN("Failed to open new sensor log data file"); + return; + } + } + + SensorLogRecord rec = {}; + rec.timestamp = now(); + rec.value = value; + rec.sid = sid; + file_write(dfile, &rec, sizeof(rec)); + file_close(dfile); +} + +void OpenSprinkler::test_sensor_log(uint32_t n_records) { + remove_sensor_log(); + + DEBUG_PRINTF("sensor log test: writing %lu records\n", (unsigned long)n_records); + uint32_t t0 = millis(); + + for (uint32_t i = 0; i < n_records; i++) { + os.log_sensor((uint8_t)((i + 1) % MAX_SENSORS), (float)i / 1000.f); + } + + uint32_t write_ms = millis() - t0; + DEBUG_PRINTF("sensor log write: %lu ms total, %.2f ms/record\n", + (unsigned long)write_ms, + n_records ? (float)write_ms / n_records : 0.f); + + // Read pass: iterate all files in order (oldest to newest) + os_file_type hfile = open_sensor_log_header(FileOpenMode::Read); + if (!hfile) { + DEBUG_PRINTLN("sensor log test: cannot open header"); + return; + } + SensorLogHeader hdr = {}; + file_read(hfile, &hdr, sizeof(hdr)); + file_close(hfile); + if (hdr.magic != SENSOR_LOG_MAGIC || hdr.version != SENSOR_LOG_VERSION) { + DEBUG_PRINTLN("sensor log test: bad header"); + return; + } + + uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; + uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); + DEBUG_PRINTF("sensor log state: max_files=%u records_per_file=%u cur_file=%u wrapped=%u total_files=%u\n", + hdr.max_files, hdr.records_per_file, hdr.cur_file, hdr.wrapped, total_files); + + uint32_t tr = millis(); + uint32_t count = 0; + for (uint16_t fi = 0; fi < total_files; fi++) { + uint16_t file_no = (first_file + fi) % hdr.max_files; + os_file_type dfile = open_sensor_log(file_no, FileOpenMode::Read); + if (!dfile) continue; + SensorLogRecord rec; + while (file_read(dfile, &rec, sizeof(rec)) == (int)sizeof(rec)) count++; + file_close(dfile); + } + + uint32_t read_ms = millis() - tr; + DEBUG_PRINTF("sensor log read: %lu records in %lu ms (%.2f ms/record)\n", + (unsigned long)count, (unsigned long)read_ms, + count ? (float)read_ms / count : 0.f); } void OpenSprinkler::poll_sensors() { diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 5ac9695cd..cd483e8a9 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -245,7 +245,7 @@ class OpenSprinkler { WeatherSensor weather; }; static sensor_memory_t sensors[MAX_SENSORS]; - static uint16_t sensor_file_no; + #endif #if defined(OSPI) @@ -377,13 +377,16 @@ class OpenSprinkler { // -- Sensor functions #if defined(USE_SENSORS) + static void get_sensor_log_filename(char *buf, uint16_t file_no); static os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); - static void remove_sensor_log(uint16_t file_no); + static os_file_type open_sensor_log_header(FileOpenMode mode); + static void remove_sensor_log(int16_t file_no = -1); // -1 removes header + all data files static void load_sensors(); static Sensor *parse_sensor(os_file_type file); // return is a statically allocated object, don't delete static Sensor *get_sensor(uint8_t index); // return is a statically allocated object, don't delete static void write_sensor(Sensor *sensor, uint8_t index); void log_sensor(uint8_t sid, float value); + static void test_sensor_log(uint32_t n_records); static void poll_sensors(); static SensorAdjustment *get_sensor_adjust(uint8_t index); // return is a statically allocated object, don't delete static void write_sensor_adjust(SensorAdjustment *adj, uint8_t index); diff --git a/defines.h b/defines.h index 648dda8c2..d0a5ee65e 100755 --- a/defines.h +++ b/defines.h @@ -53,7 +53,8 @@ #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files #define SENSORS_FILENAME "sens.dat" // sensor data file -#define SENSORS_LOG_FILENAME "senslog.dat" // name base for all sensor files +#define SENSORS_LOG_FILENAME "senslog.dat" // data file prefix (senslog.dat000 … senslog.datNNN) +#define SENSORS_LOG_HEADER_FILENAME "senslog.hdr" // central header file (separate to avoid per-write seeks) #define SENADJ_FILENAME "senadj.dat" // sensor adjustment data for programs file /** Station macro defines */ @@ -151,10 +152,10 @@ enum { #define MAX_SOPTS_SIZE 320 // maximum string option size #define MAX_SENSORS 64 -#define SENSOR_LOG_PER_FILE 1024 -#define SENSOR_LOG_FILE_COUNT 32 -#define MAX_SENSOR_LOG_COUNT (SENSOR_LOG_PER_FILE * SENSOR_LOG_FILE_COUNT) -#define SENSOR_LOG_ITEM_SIZE (sizeof(time_os_t) + sizeof(float) + 1 + 1) +#define SENSOR_LOG_MAGIC 0x55 +#define SENSOR_LOG_VERSION 0x01 +#define SENSOR_LOG_MAX_FILES 50 // number of data files in the rotation +#define SENSOR_LOG_RECORDS_PER_FILE 819 // records per file; 819×10 B = 8 190 B fits in one 8 KB LittleFS block #define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 2d6ee8242..7edb7710f 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2184,53 +2184,36 @@ void server_log_sensor(OTF_PARAMS_DEF) { rewind_ether_buffer(); print_header(OTF_PARAMS); - //uint32_t start_time = millis(); + char *end; - uint32_t count = 0; - uint32_t i; + // Read central header + os_file_type hfile = os.open_sensor_log_header(FileOpenMode::Read); + if (!hfile) handle_return(HTML_INTERNAL_ERROR); + SensorLogHeader hdr; + file_read(hfile, &hdr, sizeof(hdr)); + file_close(hfile); + if (hdr.magic != SENSOR_LOG_MAGIC || hdr.version != SENSOR_LOG_VERSION) + handle_return(HTML_INTERNAL_ERROR); + + uint32_t total_capacity = (uint32_t)hdr.max_files * hdr.records_per_file; - char *end; uint32_t max_count = 100; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { max_count = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (max_count > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); - } - - uint16_t file_no = os.sensor_file_no; - uint16_t next; - - os_file_type file = os.open_sensor_log(file_no, FileOpenMode::Read); - if (file) { - file_read(file, &next, sizeof(next)); - file_close(file); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(file_no); - handle_return(HTML_INTERNAL_ERROR); - } - - if (next == SENSOR_LOG_PER_FILE) { - next = 0; - file_no = (file_no + 1) % SENSOR_LOG_FILE_COUNT; - } else { - next += 1; + if (max_count > total_capacity) handle_return(HTML_DATA_OUTOFBOUND); } - DEBUG_PRINTLN(file_no); - DEBUG_PRINTLN(next); - - uint32_t cursor = (file_no * SENSOR_LOG_PER_FILE) + next; + // cursor = flat sequential index from oldest record to skip before emitting + uint32_t cursor = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("cursor"), true)) { cursor = strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (cursor > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); - next = cursor % SENSOR_LOG_PER_FILE; - file_no = (cursor - next) / SENSOR_LOG_PER_FILE; + if (cursor > total_capacity) handle_return(HTML_DATA_OUTOFBOUND); } using std::numeric_limits; - time_os_t before = std::numeric_limits::max(); + time_os_t before = numeric_limits::max(); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) { before = (time_os_t)strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); @@ -2241,7 +2224,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) { after = (time_os_t)strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (after <= before) handle_return(HTML_DATA_OUTOFBOUND); + if (after >= before) handle_return(HTML_DATA_OUTOFBOUND); } long target_sid = -1; @@ -2251,134 +2234,108 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (target_sid >= MAX_SENSORS || target_sid < -1) handle_return(HTML_DATA_OUTOFBOUND); } - // Clear out buffer - memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); - - file = os.open_sensor_log(file_no, FileOpenMode::Read); - if (file) { - file_seek(file, sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE), FileSeekMode::Current); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(file_no); - handle_return(HTML_INTERNAL_ERROR); - } - send_packet(OTF_PARAMS); + // Iterate files from oldest to newest + uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; + uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); + + SensorLogRecord rec; char print_buf[22] = "00,00000000,00000000\n"; - - for (i=0;i MAX_SENSORS) continue; - buf_ptr += 1; - - if (target_sid > -1 && sid != target_sid) continue; - - time_os_t timestamp; - memcpy(×tamp, buf_ptr, sizeof(timestamp)); - buf_ptr += sizeof(timestamp); - uint32_t value; - memcpy(&value, buf_ptr, sizeof(value)); - buf_ptr += sizeof(value); - - if (timestamp > before || timestamp < after) continue; - - print_buf[0] = dec2hexchar((sid >> 4) & 0xF); - print_buf[1] = dec2hexchar(sid & 0xF); - - print_buf[3] = dec2hexchar((timestamp >> 28) & 0xF); - print_buf[4] = dec2hexchar((timestamp >> 24) & 0xF); - print_buf[5] = dec2hexchar((timestamp >> 20) & 0xF); - print_buf[6] = dec2hexchar((timestamp >> 16) & 0xF); - print_buf[7] = dec2hexchar((timestamp >> 12) & 0xF); - print_buf[8] = dec2hexchar((timestamp >> 8) & 0xF); - print_buf[9] = dec2hexchar((timestamp >> 4) & 0xF); - print_buf[10] = dec2hexchar(timestamp & 0xF); - - print_buf[12] = dec2hexchar((value >> 28) & 0xF); - print_buf[13] = dec2hexchar((value >> 24) & 0xF); - print_buf[14] = dec2hexchar((value >> 20) & 0xF); - print_buf[15] = dec2hexchar((value >> 16) & 0xF); - print_buf[16] = dec2hexchar((value >> 12) & 0xF); - print_buf[17] = dec2hexchar((value >> 8) & 0xF); - print_buf[18] = dec2hexchar((value >> 4) & 0xF); - print_buf[19] = dec2hexchar(value & 0xF); + for (uint16_t fi = 0; fi < total_files && count < max_count; fi++) { + uint16_t file_no = (first_file + fi) % hdr.max_files; + os_file_type dfile = os.open_sensor_log(file_no, FileOpenMode::Read); + if (!dfile) continue; + + while (count < max_count) { + if (file_read(dfile, &rec, sizeof(rec)) != (int)sizeof(rec)) break; + + flat_idx++; + if (flat_idx <= cursor) continue; + if (rec.timestamp == 0) continue; + if (target_sid > -1 && rec.sid != (uint8_t)target_sid) continue; + if (rec.timestamp > before || rec.timestamp < after) continue; + + uint32_t raw_value; + memcpy(&raw_value, &rec.value, sizeof(raw_value)); + + print_buf[0] = dec2hexchar((rec.sid >> 4) & 0xF); + print_buf[1] = dec2hexchar(rec.sid & 0xF); + print_buf[3] = dec2hexchar((rec.timestamp >> 28) & 0xF); + print_buf[4] = dec2hexchar((rec.timestamp >> 24) & 0xF); + print_buf[5] = dec2hexchar((rec.timestamp >> 20) & 0xF); + print_buf[6] = dec2hexchar((rec.timestamp >> 16) & 0xF); + print_buf[7] = dec2hexchar((rec.timestamp >> 12) & 0xF); + print_buf[8] = dec2hexchar((rec.timestamp >> 8) & 0xF); + print_buf[9] = dec2hexchar((rec.timestamp >> 4) & 0xF); + print_buf[10] = dec2hexchar(rec.timestamp & 0xF); + print_buf[12] = dec2hexchar((raw_value >> 28) & 0xF); + print_buf[13] = dec2hexchar((raw_value >> 24) & 0xF); + print_buf[14] = dec2hexchar((raw_value >> 20) & 0xF); + print_buf[15] = dec2hexchar((raw_value >> 16) & 0xF); + print_buf[16] = dec2hexchar((raw_value >> 12) & 0xF); + print_buf[17] = dec2hexchar((raw_value >> 8) & 0xF); + print_buf[18] = dec2hexchar((raw_value >> 4) & 0xF); + print_buf[19] = dec2hexchar(raw_value & 0xF); res.write(print_buf, 21); - count += 1; - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(file_no); - break; + count++; } + file_close(dfile); } - if (file) file_close(file); - handle_return(HTML_OK); } -// TODO: delete sensor log delete void server_clear_sensor_log(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - os_file_type file; int sid = -1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { sid = atoi(tmp_buffer); - if (sid<-1 || sid>=MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + if (sid < -1 || sid >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); } else { handle_return(HTML_DATA_MISSING); } - tmp_buffer[0] = 0; - tmp_buffer[1] = 255; + if (sid == -1) { + // Remove all log files — frees flash immediately; header recreated on next log_sensor call + os.remove_sensor_log(); + handle_return(HTML_SUCCESS); + } + + // Per-sensor clear: read central header to know file layout + os_file_type hfile = os.open_sensor_log_header(FileOpenMode::Read); + if (!hfile) handle_return(HTML_INTERNAL_ERROR); + SensorLogHeader hdr; + file_read(hfile, &hdr, sizeof(hdr)); + file_close(hfile); + if (hdr.magic != SENSOR_LOG_MAGIC || hdr.version != SENSOR_LOG_VERSION) + handle_return(HTML_INTERNAL_ERROR); - uint16_t next = SENSOR_LOG_PER_FILE; - for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { - file = os.open_sensor_log(f, FileOpenMode::ReadWrite); - if (file) { - file_write(file, &next, sizeof(next)); - for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { - file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; + uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); + + SensorLogRecord rec; + for (uint16_t fi = 0; fi < total_files; fi++) { + uint16_t file_no = (first_file + fi) % hdr.max_files; + os_file_type dfile = os.open_sensor_log(file_no, FileOpenMode::ReadWrite); + if (!dfile) continue; + + uint32_t pos = 0; + while (true) { + if (file_read(dfile, &rec, sizeof(rec)) != (int)sizeof(rec)) break; + if (rec.timestamp != 0 && rec.sid == (uint8_t)sid) { + memset(&rec, 0, sizeof(rec)); + file_seek(dfile, pos); + file_write(dfile, &rec, sizeof(rec)); } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open sensor log file: "); - DEBUG_PRINTLN(f); - handle_return(HTML_INTERNAL_ERROR); + pos += sizeof(rec); } + file_close(dfile); } handle_return(HTML_SUCCESS); diff --git a/sensor.h b/sensor.h index 31d26cb11..6ab9ac929 100644 --- a/sensor.h +++ b/sensor.h @@ -19,6 +19,25 @@ typedef struct { float value; } sensor_memory_t; +// Sensor log file format — both structs are tightly packed (no padding) so +// sizeof() gives the exact on-disk byte count and offsetof() gives exact field offsets. +struct __attribute__((packed)) SensorLogHeader { + uint8_t magic; // SENSOR_LOG_MAGIC + uint8_t version; // SENSOR_LOG_VERSION + uint16_t max_files; // number of data files in rotation + uint16_t records_per_file; // max records per data file + uint16_t cur_file; // index of the data file currently being written + uint8_t wrapped; // 1 once all max_files slots have been used at least once + uint8_t reserved[7]; +}; // 16 bytes + +struct __attribute__((packed)) SensorLogRecord { + uint32_t timestamp; // unix epoch seconds + float value; // sensor reading + uint8_t sid; // sensor ID + uint8_t reserved; +}; + enum class SensorType : uint8_t { Ensemble = 0, ADS1115, diff --git a/utils.cpp b/utils.cpp index 4cd7e36d7..336c3520f 100644 --- a/utils.cpp +++ b/utils.cpp @@ -409,6 +409,18 @@ int file_write(os_file_type f, const void *source, uint32_t len) { #endif } +uint32_t file_size(os_file_type f) { + #if defined(ESP8266) + return f.size(); + #else + long cur = ftell(f); + fseek(f, 0, SEEK_END); + long sz = ftell(f); + fseek(f, cur, SEEK_SET); + return (uint32_t)(sz >= 0 ? sz : 0); + #endif +} + // file functions void file_read_block(const char *fn, void *dst, uint32_t pos, uint32_t len) { #if defined(ESP8266) diff --git a/utils.h b/utils.h index 5c26eea4d..31a7a4fb9 100644 --- a/utils.h +++ b/utils.h @@ -73,6 +73,7 @@ bool file_seek(os_file_type f, uint32_t position, FileSeekMode mode); bool file_seek(os_file_type f, uint32_t position); int file_read(os_file_type f, void *target, uint32_t len); int file_write(os_file_type f, const void *source, uint32_t len); +uint32_t file_size(os_file_type f); void file_read_block (const char *fname, void *dst, uint32_t pos, uint32_t len); void file_write_block(const char *fname, const void *src, uint32_t pos, uint32_t len); From b4ec2b17fc603ca3897abd5e465d21b4b2de14c3 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 25 Apr 2026 18:05:10 -0400 Subject: [PATCH 090/115] move sensor logs to /logs/ subfolder; set a cap to the sprinkler log size (to max 1.2MB) to make room for sensor logs; add comments to clarify the new sensor class and variables are for analog sensors (distinguish from onboard sensors) --- OpenSprinkler.cpp | 67 ++++++++++++++++++++++------------------ defines.h | 41 ++++++++++++++++++++----- main.cpp | 78 +++++++++++++++++++++++++++++------------------ sensor.h | 13 ++++++++ utils.cpp | 12 ++++++++ utils.h | 1 + 6 files changed, 146 insertions(+), 66 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index dd05464a1..d0a975cd6 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2474,11 +2474,11 @@ Sensor *OpenSprinkler::get_sensor(uint8_t index) { } void OpenSprinkler::get_sensor_log_filename(char *buf, uint16_t file_no) { - snprintf(buf, 16, "%s%03u", SENSORS_LOG_FILENAME, file_no%1000); + snprintf(buf, 24, "%s%03u", SENSORS_LOG_FILENAME, file_no%1000); } os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) { - char fname[16]; + char fname[24]; get_sensor_log_filename(fname, file_no); return file_open(fname, mode); } @@ -2488,7 +2488,7 @@ os_file_type OpenSprinkler::open_sensor_log_header(FileOpenMode mode) { } void OpenSprinkler::remove_sensor_log(int16_t file_no) { - char fname[16]; + char fname[24]; if (file_no < 0) { remove_file(SENSORS_LOG_HEADER_FILENAME); for (uint16_t i = 0; i < SENSOR_LOG_MAX_FILES; i++) { @@ -2504,39 +2504,47 @@ void OpenSprinkler::remove_sensor_log(int16_t file_no) { void list_all_files() { #if defined(ESP8266) Serial.println(PSTR("\n--- Flash File System Map ---")); - Serial.printf("%-25s %10s\n", "Filename", "Size (B)"); - Serial.println(PSTR("---------------------------------------")); + Serial.printf("%-30s %10s\n", "Filename", "Size (B)"); + Serial.println(PSTR("------------------------------------------")); - // Open the root directory - Dir dir = LittleFS.openDir("/"); uint32_t totalUsed = 0; uint32_t fileCount = 0; - // Iterate through all files + // Root directory (skip directory entries — they appear as dot-less names with size 0) + Dir dir = LittleFS.openDir("/"); while (dir.next()) { - String fileName = dir.fileName(); - uint32_t fileSize = dir.fileSize(); - - Serial.printf("%-25s %10u\n", fileName.c_str(), fileSize); - - totalUsed += fileSize; + if (dir.fileName().indexOf('.') < 0) continue; + Serial.printf("%-30s %10u\n", ("/" + dir.fileName()).c_str(), dir.fileSize()); + totalUsed += dir.fileSize(); fileCount++; } - // Get filesystem totals + // /logs/ subdirectory + uint32_t logUsed = 0; + uint32_t logCount = 0; + Dir logdir = LittleFS.openDir("/logs/"); + while (logdir.next()) { + String fullname = "/logs/" + logdir.fileName(); + Serial.printf("%-30s %10u\n", fullname.c_str(), logdir.fileSize()); + logUsed += logdir.fileSize(); + logCount++; + } + totalUsed += logUsed; + fileCount += logCount; + + // Filesystem totals FSInfo fs_info; LittleFS.info(fs_info); - Serial.println(PSTR("---------------------------------------")); - Serial.printf("Total File: %u\n", fileCount); - Serial.printf("Total Size: %u bytes\n", totalUsed); - Serial.printf("Total Flash: %u bytes\n", fs_info.totalBytes); - Serial.printf("Free Space: %u bytes\n", fs_info.totalBytes - fs_info.usedBytes); - Serial.printf("Used Bytes: %u bytes\n", fs_info.usedBytes); - - Serial.printf("LittleFS Block Size: %u bytes\n", fs_info.blockSize); - Serial.println(PSTR("---------------------------------------\n")); - #endif + Serial.println(PSTR("------------------------------------------")); + Serial.printf("Total Files: %u (logs/: %u)\n", fileCount, logCount); + Serial.printf("Total Size: %u bytes (logs/: %u bytes)\n", totalUsed, logUsed); + Serial.printf("FS Total: %u bytes\n", fs_info.totalBytes); + Serial.printf("FS Used: %u bytes\n", fs_info.usedBytes); + Serial.printf("FS Free: %u bytes\n", fs_info.totalBytes - fs_info.usedBytes); + Serial.printf("Block Size: %u bytes\n", fs_info.blockSize); + Serial.println(PSTR("------------------------------------------\n")); +#endif } void OpenSprinkler::load_sensors() { @@ -2602,10 +2610,10 @@ void OpenSprinkler::load_sensors() { // Uncomment one of the following to run a sensor log performance test on boot: //test_sensor_log(100); // quick smoke test - test_sensor_log(2000); // moderate + //test_sensor_log(2000); // moderate //test_sensor_log(32760); // full capacity // test_sensor_log(40000); // beyond capacity (tests ring wrap) - list_all_files(); + //list_all_files(); } void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { @@ -2648,8 +2656,9 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { } if (!hdr_valid) { - // First use or firmware upgrade: wipe any stale data files, write fresh header - char fname[16]; + // First use or firmware upgrade: ensure directory exists, wipe stale data files, write fresh header + ensure_log_dir(); + char fname[24]; for (uint16_t i = 0; i < SENSOR_LOG_MAX_FILES; i++) { get_sensor_log_filename(fname, i); remove_file(fname); diff --git a/defines.h b/defines.h index d0a5ee65e..c4d7fb9ac 100755 --- a/defines.h +++ b/defines.h @@ -52,10 +52,17 @@ #define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files -#define SENSORS_FILENAME "sens.dat" // sensor data file -#define SENSORS_LOG_FILENAME "senslog.dat" // data file prefix (senslog.dat000 … senslog.datNNN) -#define SENSORS_LOG_HEADER_FILENAME "senslog.hdr" // central header file (separate to avoid per-write seeks) -#define SENADJ_FILENAME "senadj.dat" // sensor adjustment data for programs file +// External sensor board (ADS1115-based analog inputs, see sensor.h). +// Unrelated to the onboard SENSOR1/SENSOR2 GPIO inputs. +#define SENSORS_FILENAME "sens.dat" // external sensor definitions +#if defined(ESP8266) +#define LOG_DIR "/logs/" // absolute path on LittleFS; parent dir created implicitly +#else +#define LOG_DIR "logs/" // relative to data dir on Linux; get_filename_fullpath prepends it +#endif +#define SENSORS_LOG_FILENAME LOG_DIR "sens.log" // external sensor log data (…sens.log000 … sens.logNNN) +#define SENSORS_LOG_HEADER_FILENAME LOG_DIR "sens.hdr" // external sensor log header +#define SENADJ_FILENAME "senadj.dat" // external sensor adjustment data for programs /** Station macro defines */ #define STN_TYPE_STANDARD 0x00 // standard solenoid station @@ -151,6 +158,10 @@ enum { #define STATION_NAME_SIZE 32 // maximum number of characters in each station name #define MAX_SOPTS_SIZE 320 // maximum string option size +#if defined(ESP8266) +#define LOG_SPRINKLER_MAX_KB 1200 // max total size of sprinkler .txt log files in KB (~1.2 MB) +#endif + #define MAX_SENSORS 64 #define SENSOR_LOG_MAGIC 0x55 #define SENSOR_LOG_VERSION 0x01 @@ -193,6 +204,8 @@ enum { enum { MASTER_1 = 0, MASTER_2, + //MASTER_3, + //MASTER_4, NUM_MASTER_ZONES, }; @@ -284,6 +297,20 @@ enum { IOPT_RESERVE_8, IOPT_WIFI_MODE, //ro IOPT_RESET, //ro + /*IOPT_SENSOR3_TYPE, + IOPT_SENSOR3_OPTION, + IOPT_SENSOR3_ON_DELAY, + IOPT_SENSOR3_OFF_DELAY, + IOPT_SENSOR4_TYPE, + IOPT_SENSOR4_OPTION, + IOPT_SENSOR4_ON_DELAY, + IOPT_SENSOR4_OFF_DELAY, + IOPT_MASTER_STATION_3, + IOPT_MASTER_ON_ADJ_3, + IOPT_MASTER_OFF_ADJ_3, + IOPT_MASTER_STATION_4, + IOPT_MASTER_ON_ADJ_4, + IOPT_MASTER_OFF_ADJ_4,*/ NUM_IOPTS // total number of integer options }; @@ -405,8 +432,8 @@ enum { #define USE_DISPLAY #define USE_ADS1115 - #define USE_SENSORS - + #define USE_SENSORS // enable external analog sensor board (ADS1115); distinct from onboard SENSOR1/SENSOR2 GPIO inputs + #elif defined(OSPI) // for OSPi #define OS_HW_VERSION OSPI_HW_VERSION_BASE @@ -432,7 +459,7 @@ enum { #define USE_DISPLAY #define USE_ADS1115 - #define USE_SENSORS + #define USE_SENSORS // enable external analog sensor board (ADS1115); distinct from onboard SENSOR1/SENSOR2 GPIO inputs #else // for demo / simulation // use fake hardware pins diff --git a/main.cpp b/main.cpp index cb45d2c74..3bc70839f 100644 --- a/main.cpp +++ b/main.cpp @@ -42,6 +42,7 @@ bool useEth = false; // tracks whether we are using WiFi or wired Ether connection uint32_t getNtpTime(); #else // header and defs for RPI/Linux + #include bool useEth = false; #endif @@ -471,6 +472,7 @@ static void perform_ntp_sync(); #if defined(ESP8266) bool delete_log_oldest(); +uint32_t get_sprinkler_log_size(); void start_server_ap(); void start_server_client(); static Ticker reboot_ticker; @@ -1656,18 +1658,13 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo // ================================ // ====== LOGGING FUNCTIONS ======= // ================================ -#if defined(ESP8266) -char LOG_PREFIX[] = "/logs/"; -#else -char LOG_PREFIX[] = "./logs/"; -#endif /** Generate log file name * Log files will be named /logs/xxxxx.txt */ void make_logfile_name(char *name) { strcpy(tmp_buffer+TMP_BUFFER_SIZE-10, name); // hack: we do this because name is from tmp_buffer too - strcpy(tmp_buffer, LOG_PREFIX); + strcpy(tmp_buffer, LOG_DIR); strcat(tmp_buffer, tmp_buffer+TMP_BUFFER_SIZE-10); strcat_P(tmp_buffer, PSTR(".txt")); } @@ -1700,12 +1697,10 @@ void write_log(unsigned char type, time_os_t curr_time) { #if defined(ESP8266) // prepare log folder for Arduino File file = LittleFS.open(tmp_buffer, "r+"); if(!file) { - FSInfo fs_info; - LittleFS.info(fs_info); - // check if we are getting close to run out of space, and delete some oldest files - if(fs_info.totalBytes < fs_info.usedBytes + fs_info.blockSize * 4) { - // delete the oldest 7 files (1 week of log) - for(unsigned char i=0;i<7;i++) delete_log_oldest(); + // New day file — trim sprinkler log budget before creating it + uint32_t limit = (uint32_t)LOG_SPRINKLER_MAX_KB * 1024; + while (get_sprinkler_log_size() >= limit) { + if (!delete_log_oldest()) break; } file = LittleFS.open(tmp_buffer, "w"); if(!file) return; @@ -1713,8 +1708,8 @@ void write_log(unsigned char type, time_os_t curr_time) { file.seek(0, SeekEnd); #else // prepare log folder for RPI/LINUX struct stat st; - if(stat(get_filename_fullpath(LOG_PREFIX), &st)) { - if(mkdir(get_filename_fullpath(LOG_PREFIX), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)) { + if(stat(get_filename_fullpath(LOG_DIR), &st)) { + if(mkdir(get_filename_fullpath(LOG_DIR), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)) { return; } } @@ -1797,25 +1792,35 @@ void write_log(unsigned char type, time_os_t curr_time) { } #if defined(ESP8266) +uint32_t get_sprinkler_log_size() { + Dir dir = LittleFS.openDir(LOG_DIR); + uint32_t total = 0; + while (dir.next()) { + if (dir.fileName().endsWith(".txt")) + total += dir.fileSize(); + } + return total; +} + bool delete_log_oldest() { - Dir dir = LittleFS.openDir(LOG_PREFIX); - time_os_t oldest_t = ULONG_MAX; + Dir dir = LittleFS.openDir(LOG_DIR); + uint32_t oldest_day = UINT32_MAX; String oldest_fn; while (dir.next()) { - time_os_t t = dir.fileCreationTime(); - if(t0) { + if (oldest_fn.length() > 0) { DEBUG_PRINT(F("deleting ")) - DEBUG_PRINTLN(LOG_PREFIX+oldest_fn); - LittleFS.remove(LOG_PREFIX+oldest_fn); + DEBUG_PRINTLN(LOG_DIR + oldest_fn); + LittleFS.remove(LOG_DIR + oldest_fn); return true; - } else { - return false; } + return false; } #endif @@ -1826,10 +1831,11 @@ void delete_log(char *name) { if (!os.iopts[IOPT_ENABLE_LOGGING]) return; #if defined(ESP8266) if (strncmp(name, "all", 3) == 0) { - // delete all log files - Dir dir = LittleFS.openDir(LOG_PREFIX); + // delete all sprinkler log files (.txt), leaving sensor logs intact + Dir dir = LittleFS.openDir(LOG_DIR); while (dir.next()) { - LittleFS.remove(LOG_PREFIX+dir.fileName()); + if (dir.fileName().endsWith(".txt")) + LittleFS.remove(LOG_DIR+dir.fileName()); } } else { // delete a single log file @@ -1839,9 +1845,21 @@ void delete_log(char *name) { } #else // delete_log implementation for RPI/LINUX if (strncmp(name, "all", 3) == 0) { - // delete the log folder - rmdir(get_filename_fullpath(LOG_PREFIX)); - return; + // delete all sprinkler log files (.txt), leaving sensor logs intact + char log_dir[PATH_MAX]; + strcpy(log_dir, get_filename_fullpath(LOG_DIR)); + DIR *d = opendir(log_dir); + if (d) { + struct dirent *ent; + while ((ent = readdir(d)) != NULL) { + size_t len = strlen(ent->d_name); + if (len > 4 && strcmp(ent->d_name + len - 4, ".txt") == 0) { + snprintf(tmp_buffer, TMP_BUFFER_SIZE, "%s%s", log_dir, ent->d_name); + remove(tmp_buffer); + } + } + closedir(d); + } } else { make_logfile_name(name); remove(get_filename_fullpath(tmp_buffer)); diff --git a/sensor.h b/sensor.h index 6ab9ac929..d54f76025 100644 --- a/sensor.h +++ b/sensor.h @@ -1,5 +1,18 @@ #pragma once +// External / analog sensor subsystem +// +// The types in this file (Sensor, EnsembleSensor, WeatherSensor, ADS1115Sensor, +// SensorAdjustment, etc.) model the *external* sensor board — an ADS1115-based +// I2C ADC add-on that reads analog probes (soil moisture, temperature, etc.). +// It is enabled by the USE_SENSORS compile-time guard. +// +// These are DISTINCT from the two onboard digital sensor inputs (SENSOR1 / +// SENSOR2) that are wired directly to GPIO pins. Those are simple binary +// (open/closed) or pulse inputs handled entirely in OpenSprinkler.h/.cpp via +// os.sensor1_status / os.sensor2_status and related IOPT_SENSOR* options. +// No types from this file are involved in the onboard sensor logic. + #include #if defined(ARDUINO) #include diff --git a/utils.cpp b/utils.cpp index 336c3520f..d398d509f 100644 --- a/utils.cpp +++ b/utils.cpp @@ -33,6 +33,8 @@ extern OpenSprinkler os; #else // RPI/LINUX #include +#include +#include char* get_runtime_path() { static char path[PATH_MAX]; @@ -296,6 +298,16 @@ void remove_file(const char *fn) { #endif } +void ensure_log_dir() { +#if !defined(ESP8266) + const char *dir = get_filename_fullpath(LOG_DIR); + struct stat st; + if (stat(dir, &st) != 0) { + mkdir(dir, S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IWGRP|S_IXGRP|S_IROTH|S_IWOTH|S_IXOTH); + } +#endif +} + bool file_exists(const char *fn) { #if defined(ESP8266) diff --git a/utils.h b/utils.h index 31a7a4fb9..cb2f3114a 100644 --- a/utils.h +++ b/utils.h @@ -66,6 +66,7 @@ enum class FileSeekMode { //remove unused functions: void read_from_file(const char *fname, char *data, uint32_t maxsize=TMP_BUFFER_SIZE, int pos=0); void remove_file(const char *fname); bool file_exists(const char *fname); +void ensure_log_dir(); os_file_type file_open(const char *fn, FileOpenMode mode); void file_close(os_file_type f); From c33bdbfed892880b8889bb961b045cca216f1fa4 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 25 Apr 2026 20:22:05 -0400 Subject: [PATCH 091/115] add support for Master3 and Master4; add support for expanding iopt options without having to trigger a factory reset --- OpenSprinkler.cpp | 82 ++++++++++++++++++++++++++++++++++++---- OpenSprinkler.h | 11 +++++- defines.h | 18 ++++----- main.cpp | 17 +++++++-- opensprinkler_server.cpp | 19 +++++++--- 5 files changed, 120 insertions(+), 27 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index d0a975cd6..7a5d65d23 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -67,6 +67,8 @@ unsigned char OpenSprinkler::weather_update_flag; unsigned char OpenSprinkler::attrib_mas[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_igs[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_mas2[MAX_NUM_BOARDS]; +unsigned char OpenSprinkler::attrib_mas3[MAX_NUM_BOARDS]; +unsigned char OpenSprinkler::attrib_mas4[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_igs2[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; @@ -190,6 +192,12 @@ const char iopt_json_names[] PROGMEM = "resv8" "wimod" "reset" + "mas3\0" + "mton3" + "mtof3" + "mas4\0" + "mton4" + "mtof4" ; /** Option prompts (stored in PROGMEM to reduce RAM usage) */ @@ -268,7 +276,13 @@ const char iopt_prompts[] PROGMEM = "Reserved 7 " "Reserved 8 " "WiFi mode? " - "Factory reset? "; + "Factory reset? " + "Master 3 (Mas3):" + "Mas3 on adjust:" + "Mas3 off adjust:" + "Master 4 (Mas4):" + "Mas4 on adjust:" + "Mas4 off adjust:"; // string options do not have prompts @@ -346,7 +360,13 @@ const unsigned char iopt_max[] PROGMEM = { 255, 255, 255, - 1 + 1, + MAX_NUM_STATIONS, + 255, + 255, + MAX_NUM_STATIONS, + 255, + 255 }; // string options do not have maximum values @@ -430,7 +450,13 @@ unsigned char OpenSprinkler::iopts[] = { 0, // reserved 7 0, // reserved 8 WIFI_MODE_AP, // wifi mode - 0 // reset + 0, // reset + 0, // index of master3. 0: no master3 station + 120,// master3 on adjusted time + 120,// master3 off adjusted time + 0, // index of master4. 0: no master4 station + 120,// master4 on adjusted time + 120,// master4 off adjusted time }; /** String option values (stored in RAM) */ @@ -1573,11 +1599,17 @@ unsigned char OpenSprinkler::bound_to_master(unsigned char sid, unsigned char ma switch (mas) { case MASTER_1: - attributes= attrib_mas[bid]; + attributes = attrib_mas[bid]; break; case MASTER_2: attributes = attrib_mas2[bid]; break; + case MASTER_3: + attributes = attrib_mas3[bid]; + break; + case MASTER_4: + attributes = attrib_mas4[bid]; + break; default: break; } @@ -1608,6 +1640,8 @@ void OpenSprinkler::attribs_save() { at.igs2= (attrib_igs2[bid]>>s) & 1; at.igrd= (attrib_igrd[bid]>>s) & 1; at.dis = (attrib_dis[bid]>>s) & 1; + at.mas3= (attrib_mas3[bid]>>s) & 1; + at.mas4= (attrib_mas4[bid]>>s) & 1; at.gid = get_station_gid(sid); set_station_gid(sid, at.gid); @@ -1637,6 +1671,8 @@ void OpenSprinkler::attribs_load() { memset(attrib_mas, 0, nboards); memset(attrib_igs, 0, nboards); memset(attrib_mas2, 0, nboards); + memset(attrib_mas3, 0, nboards); + memset(attrib_mas4, 0, nboards); memset(attrib_igs2, 0, nboards); memset(attrib_igrd, 0, nboards); memset(attrib_dis, 0, nboards); @@ -1649,6 +1685,8 @@ void OpenSprinkler::attribs_load() { attrib_mas[bid] |= (at.mas< NUM_IOPTS) load_count = NUM_IOPTS; + file_read_block(IOPTS_FILENAME, iopts, 0, load_count); nboards = iopts[IOPT_EXT_BOARDS]+1; nstations = nboards * 8; status.enabled = iopts[IOPT_DEVICE_ENABLE]; @@ -2347,6 +2392,14 @@ void OpenSprinkler::populate_master() { masters[MASTER_2][MASOPT_SID] = iopts[IOPT_MASTER_STATION_2]; masters[MASTER_2][MASOPT_ON_ADJ] = iopts[IOPT_MASTER_ON_ADJ_2]; masters[MASTER_2][MASOPT_OFF_ADJ] = iopts[IOPT_MASTER_OFF_ADJ_2]; + + masters[MASTER_3][MASOPT_SID] = iopts[IOPT_MASTER_STATION_3]; + masters[MASTER_3][MASOPT_ON_ADJ] = iopts[IOPT_MASTER_ON_ADJ_3]; + masters[MASTER_3][MASOPT_OFF_ADJ] = iopts[IOPT_MASTER_OFF_ADJ_3]; + + masters[MASTER_4][MASOPT_SID] = iopts[IOPT_MASTER_STATION_4]; + masters[MASTER_4][MASOPT_ON_ADJ] = iopts[IOPT_MASTER_ON_ADJ_4]; + masters[MASTER_4][MASOPT_OFF_ADJ] = iopts[IOPT_MASTER_OFF_ADJ_4]; } /** Save integer options to file */ @@ -2965,6 +3018,10 @@ void OpenSprinkler::lcd_print_screen(char c) { lcd.print((bitvalue&1) ? c : 'M'); // print master station } else if (sid == iopts[IOPT_MASTER_STATION_2]) { lcd.print((bitvalue&1) ? c : 'N'); // print master2 station + } else if (sid == iopts[IOPT_MASTER_STATION_3]) { + lcd.print((bitvalue&1) ? c : 'U'); // print master3 station + } else if (sid == iopts[IOPT_MASTER_STATION_4]) { + lcd.print((bitvalue&1) ? c : 'V'); // print master4 station } else { lcd.print((bitvalue&1) ? c : '_'); } @@ -3110,8 +3167,12 @@ void OpenSprinkler::lcd_print_option(int i) { break; case IOPT_MASTER_ON_ADJ: case IOPT_MASTER_ON_ADJ_2: + case IOPT_MASTER_ON_ADJ_3: + case IOPT_MASTER_ON_ADJ_4: case IOPT_MASTER_OFF_ADJ: case IOPT_MASTER_OFF_ADJ_2: + case IOPT_MASTER_OFF_ADJ_3: + case IOPT_MASTER_OFF_ADJ_4: case IOPT_STATION_DELAY_TIME: { int16_t t=water_time_decode_signed(iopts[i]); @@ -3196,7 +3257,10 @@ void OpenSprinkler::lcd_print_option(int i) { break; } if (i==IOPT_WATER_PERCENTAGE) lcd_print_pgm(PSTR("%")); - else if (i==IOPT_MASTER_ON_ADJ || i==IOPT_MASTER_OFF_ADJ || i==IOPT_MASTER_ON_ADJ_2 || i==IOPT_MASTER_OFF_ADJ_2) + else if (i==IOPT_MASTER_ON_ADJ || i==IOPT_MASTER_OFF_ADJ || + i==IOPT_MASTER_ON_ADJ_2 || i==IOPT_MASTER_OFF_ADJ_2 || + i==IOPT_MASTER_ON_ADJ_3 || i==IOPT_MASTER_OFF_ADJ_3 || + i==IOPT_MASTER_ON_ADJ_4 || i==IOPT_MASTER_OFF_ADJ_4) lcd_print_pgm(PSTR(" sec")); } @@ -3294,8 +3358,10 @@ void OpenSprinkler::ui_set_options(int oid) if (i==IOPT_USE_DHCP && iopts[i]) i += 9; // if use DHCP, skip static ip set else if (i==IOPT_HTTPPORT_0) i+=2; // skip IOPT_HTTPPORT_1 else if (i==IOPT_PULSE_RATE_0) i+=2; // skip IOPT_PULSE_RATE_1 - else if (i==IOPT_MASTER_STATION && iopts[i]==0) i+=3; // if not using master station, skip master on/off adjust including two retired options - else if (i==IOPT_MASTER_STATION_2&& iopts[i]==0) i+=3; // if not using master2, skip master2 on/off adjust + else if (i==IOPT_MASTER_STATION && iopts[i]==0) i+=3; // if not using master, skip on/off adjust + else if (i==IOPT_MASTER_STATION_2 && iopts[i]==0) i+=3; // if not using master2, skip on/off adjust + else if (i==IOPT_MASTER_STATION_3 && iopts[i]==0) i+=3; // if not using master3, skip on/off adjust + else if (i==IOPT_MASTER_STATION_4 && iopts[i]==0) i+=3; // if not using master4, skip on/off adjust else { i = (i+1) % NUM_IOPTS; } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index cd483e8a9..ee5fef1a3 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -134,8 +134,11 @@ struct StationAttrib { // station attributes unsigned char igpu:1; // todo: ignore pause unsigned char gid; // sequential group id - unsigned char reserved[2]; // reserved bytes for the future -}; // total is 4 bytes so far + unsigned char mas3:1; // master 3 binding bit (was reserved[0]) + unsigned char mas4:1; // master 4 binding bit + unsigned char :6; // remaining bits of this byte, reserved + unsigned char reserved; // reserved for future use (was reserved[1]) +}; // total is 4 bytes /** Station data structure */ struct StationData { @@ -207,6 +210,8 @@ struct ConStatus { unsigned char network_fails:3; // number of network fails unsigned char mas:8; // master station index unsigned char mas2:8; // master2 station index + unsigned char mas3:8; // master3 station index + unsigned char mas4:8; // master4 station index unsigned char sensor2:1; // sensor2 status bit (when set, sensor2 on is detected) unsigned char sensor1_active:1; // sensor1 active bit (when set, sensor1 is activated) unsigned char sensor2_active:1; // sensor2 active bit (when set, sensor2 is activated) @@ -269,6 +274,8 @@ class OpenSprinkler { static unsigned char attrib_mas[]; static unsigned char attrib_igs[]; static unsigned char attrib_mas2[]; + static unsigned char attrib_mas3[]; + static unsigned char attrib_mas4[]; static unsigned char attrib_igs2[]; static unsigned char attrib_igrd[]; static unsigned char attrib_dis[]; diff --git a/defines.h b/defines.h index c4d7fb9ac..b32e5f2a2 100755 --- a/defines.h +++ b/defines.h @@ -204,8 +204,8 @@ enum { enum { MASTER_1 = 0, MASTER_2, - //MASTER_3, - //MASTER_4, + MASTER_3, + MASTER_4, NUM_MASTER_ZONES, }; @@ -297,6 +297,12 @@ enum { IOPT_RESERVE_8, IOPT_WIFI_MODE, //ro IOPT_RESET, //ro + IOPT_MASTER_STATION_3, + IOPT_MASTER_ON_ADJ_3, + IOPT_MASTER_OFF_ADJ_3, + IOPT_MASTER_STATION_4, + IOPT_MASTER_ON_ADJ_4, + IOPT_MASTER_OFF_ADJ_4, /*IOPT_SENSOR3_TYPE, IOPT_SENSOR3_OPTION, IOPT_SENSOR3_ON_DELAY, @@ -304,13 +310,7 @@ enum { IOPT_SENSOR4_TYPE, IOPT_SENSOR4_OPTION, IOPT_SENSOR4_ON_DELAY, - IOPT_SENSOR4_OFF_DELAY, - IOPT_MASTER_STATION_3, - IOPT_MASTER_ON_ADJ_3, - IOPT_MASTER_OFF_ADJ_3, - IOPT_MASTER_STATION_4, - IOPT_MASTER_ON_ADJ_4, - IOPT_MASTER_OFF_ADJ_4,*/ + IOPT_SENSOR4_OFF_DELAY,*/ NUM_IOPTS // total number of integer options }; diff --git a/main.cpp b/main.cpp index 3bc70839f..5469f95b9 100644 --- a/main.cpp +++ b/main.cpp @@ -560,6 +560,8 @@ void do_loop() os.status.mas = os.iopts[IOPT_MASTER_STATION]; os.status.mas2= os.iopts[IOPT_MASTER_STATION_2]; + os.status.mas3= os.iopts[IOPT_MASTER_STATION_3]; + os.status.mas4= os.iopts[IOPT_MASTER_STATION_4]; time_os_t curr_time = os.now_tz(); // ====== Process Ethernet packets ====== @@ -817,7 +819,8 @@ void do_loop() bid=sid>>3; s=sid&0x07; // skip if the station is a master station (because master cannot be scheduled independently - if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) + if ((os.status.mas==sid+1) || (os.status.mas2==sid+1) || + (os.status.mas3==sid+1) || (os.status.mas4==sid+1)) continue; // TODO: compare with old code @@ -892,6 +895,8 @@ void do_loop() // skip master stations and any station that's not in the queue if (os.status.mas == sid+1) continue; if (os.status.mas2== sid+1) continue; + if (os.status.mas3== sid+1) continue; + if (os.status.mas4== sid+1) continue; if (pd.station_qid[sid]==255) continue; q = pd.queue + pd.station_qid[sid]; @@ -967,6 +972,8 @@ void do_loop() // in case some options have changed while executing the program os.status.mas = os.iopts[IOPT_MASTER_STATION]; // update master station os.status.mas2= os.iopts[IOPT_MASTER_STATION_2]; // update master2 station + os.status.mas3= os.iopts[IOPT_MASTER_STATION_3]; // update master3 station + os.status.mas4= os.iopts[IOPT_MASTER_STATION_4]; // update master4 station } }//if_some_program_is_running @@ -1273,7 +1280,8 @@ void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shif // because we may be turning off a station that hasn't started yet if (curr_time >= q->st) { // record lastrun log (only for non-master stations) - if (os.status.mas != (sid + 1) && os.status.mas2 != (sid + 1)) { + if (os.status.mas != (sid + 1) && os.status.mas2 != (sid + 1) && + os.status.mas3 != (sid + 1) && os.status.mas4 != (sid + 1)) { pd.lastrun.station = sid; pd.lastrun.program = q->pid; pd.lastrun.duration = curr_time - q->st; @@ -1329,6 +1337,8 @@ void process_dynamic_events(time_os_t curr_time) { // ignore master stations because they are handled separately if (os.status.mas == sid+1) continue; if (os.status.mas2== sid+1) continue; + if (os.status.mas3== sid+1) continue; + if (os.status.mas4== sid+1) continue; // If this is a normal program (not a run-once or test program) // and either the controller is disabled, or // if raining and ignore rain bit is cleared @@ -1627,7 +1637,8 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo bid=sid>>3; s=sid&0x07; // skip if the station is a master station (because master cannot be scheduled independently - if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) + if ((os.status.mas==sid+1) || (os.status.mas2==sid+1) || + (os.status.mas3==sid+1) || (os.status.mas4==sid+1)) continue; dur = 60; if(pid==255) { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7edb7710f..fb8f8b5b4 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -378,6 +378,8 @@ void server_json_stations_attrib(const char* name, unsigned char *attrib) void server_json_stations_main(OTF_PARAMS_DEF) { server_json_board_attrib(PSTR("masop"), os.attrib_mas); server_json_board_attrib(PSTR("masop2"), os.attrib_mas2); + server_json_board_attrib(PSTR("masop3"), os.attrib_mas3); + server_json_board_attrib(PSTR("masop4"), os.attrib_mas4); server_json_board_attrib(PSTR("ignore_rain"), os.attrib_igrd); server_json_board_attrib(PSTR("ignore_sn1"), os.attrib_igs); server_json_board_attrib(PSTR("ignore_sn2"), os.attrib_igs2); @@ -496,6 +498,8 @@ void server_change_stations(OTF_PARAMS_DEF) { server_change_board_attrib(FKV_SOURCE, 'j', os.attrib_igs); // ignore sensor1 server_change_board_attrib(FKV_SOURCE, 'k', os.attrib_igs2); // ignore sensor2 server_change_board_attrib(FKV_SOURCE, 'n', os.attrib_mas2); // master2 + server_change_board_attrib(FKV_SOURCE, 'u', os.attrib_mas3); // master3 + server_change_board_attrib(FKV_SOURCE, 'v', os.attrib_mas4); // master4 server_change_board_attrib(FKV_SOURCE, 'd', os.attrib_dis); // disable server_change_stations_attrib(FKV_SOURCE, 'g', os.attrib_grp); // sequential groups /* handle special data */ @@ -955,8 +959,10 @@ void server_json_options_main() { #endif int32_t v=os.iopts[oid]; - if (oid==IOPT_MASTER_OFF_ADJ || oid==IOPT_MASTER_OFF_ADJ_2 || - oid==IOPT_MASTER_ON_ADJ || oid==IOPT_MASTER_ON_ADJ_2 || + if (oid==IOPT_MASTER_OFF_ADJ || oid==IOPT_MASTER_OFF_ADJ_2 || + oid==IOPT_MASTER_OFF_ADJ_3 || oid==IOPT_MASTER_OFF_ADJ_4 || + oid==IOPT_MASTER_ON_ADJ || oid==IOPT_MASTER_ON_ADJ_2 || + oid==IOPT_MASTER_ON_ADJ_3 || oid==IOPT_MASTER_ON_ADJ_4 || oid==IOPT_STATION_DELAY_TIME) { v=water_time_decode_signed(v); } @@ -1424,8 +1430,10 @@ void server_change_options(OTF_PARAMS_DEF) strncpy_P0(tbuf2, iopt_json_names+oid*5, 5); if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, tbuf2)) { int32_t v = atol(tmp_buffer); - if (oid==IOPT_MASTER_OFF_ADJ || oid==IOPT_MASTER_OFF_ADJ_2 || - oid==IOPT_MASTER_ON_ADJ || oid==IOPT_MASTER_ON_ADJ_2 || + if (oid==IOPT_MASTER_OFF_ADJ || oid==IOPT_MASTER_OFF_ADJ_2 || + oid==IOPT_MASTER_OFF_ADJ_3 || oid==IOPT_MASTER_OFF_ADJ_4 || + oid==IOPT_MASTER_ON_ADJ || oid==IOPT_MASTER_ON_ADJ_2 || + oid==IOPT_MASTER_ON_ADJ_3 || oid==IOPT_MASTER_ON_ADJ_4 || oid==IOPT_STATION_DELAY_TIME) { v=water_time_encode_signed(v); } // encode station delay time @@ -1649,7 +1657,8 @@ void server_change_manual(OTF_PARAMS_DEF) { // schedule manual station // skip if the station is a master station // (because master cannot be scheduled independently) - if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) + if ((os.status.mas==sid+1) || (os.status.mas2==sid+1) || + (os.status.mas3==sid+1) || (os.status.mas4==sid+1)) handle_return(HTML_NOT_PERMITTED); RuntimeQueueStruct *q = NULL; From 93d7eedd5fbabf9337740d18b2ad15ef3f6ef0ea Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 26 Apr 2026 08:19:07 -0400 Subject: [PATCH 092/115] restructure sensor adjustment data file; change to lazy file creation; move relevant operations from opensprinkler_server.cpp to program.h and cpp as program data and sensor adjustment are supposed to be fully in sync --- OpenSprinkler.cpp | 51 ++++++++++++++------------- defines.h | 8 ++--- opensprinkler_server.cpp | 75 ++++++++++++++++------------------------ program.cpp | 25 ++++++++++++-- program.h | 6 ++-- 5 files changed, 87 insertions(+), 78 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 7a5d65d23..811230301 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2624,20 +2624,7 @@ void OpenSprinkler::load_sensors() { DEBUG_PRINTLN(SENSORS_FILENAME); } - // Initialize the program adjustment file (SENADJ_FILENAME) - file = file_open(SENADJ_FILENAME, FileOpenMode::WriteTruncate); - if (file) { - sensor_adjustment_point_t point = sensor_adjustment_point_t {0.0, 0.0}; - SensorAdjustment adj = SensorAdjustment(0, 0, 0, &point); - uint32_t size = adj.serialize(tmp_buffer); - for (size_t i = 0; i < MAX_NUM_PROGRAMS; i++) { - file_write(file, tmp_buffer, size); - } - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); - } + // SENADJ_FILENAME grows on demand; no pre-allocation needed. } // 2. Proceed with existing load logic @@ -2839,36 +2826,52 @@ SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { // Do not call delete on the return result from this function static SensorAdjustment result(0, 255, 0, nullptr); - if (index > MAX_NUM_PROGRAMS) return nullptr; + if (index >= pd.nprograms) return nullptr; os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); if (file) { - file_seek(file, (uint32_t)index * SENSOR_ADJUSTMENT_SIZE, FileSeekMode::Current); + uint32_t pos = (uint32_t)index * SENSOR_ADJUSTMENT_SIZE; + if (file_size(file) < pos + SENSOR_ADJUSTMENT_SIZE) { + file_close(file); + return nullptr; // entry not yet written + } + file_seek(file, pos, FileSeekMode::Set); file_read(file, tmp_buffer, SENSOR_ADJUSTMENT_SIZE); file_close(file); result = SensorAdjustment(tmp_buffer); if (result.sid < 255) { return &result; } - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); } return nullptr; } void OpenSprinkler::write_sensor_adjust(SensorAdjustment *adj, uint8_t index) { - uint32_t pos = SENSOR_ADJUSTMENT_SIZE * index; - + uint32_t pos = (uint32_t)SENSOR_ADJUSTMENT_SIZE * index; + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); if (file) { - file_seek(file, pos, FileSeekMode::Current); + // Build a disabled entry (sid=255 is the "no adjustment" sentinel). + // Used both for padding gaps and for writing a null adj. + SensorAdjustment disabled(0, 255, 0, nullptr); + uint32_t disabled_len = disabled.serialize(tmp_buffer); + + // Pad with disabled entries if the file doesn't yet reach pos. + // This can happen when programs before this one have no adjustment written. + uint32_t cur_size = file_size(file); + if (cur_size < pos) { + file_seek(file, 0, FileSeekMode::End); + while (cur_size < pos) { + file_write(file, tmp_buffer, disabled_len); + cur_size += disabled_len; + } + } + file_seek(file, pos, FileSeekMode::Set); if (adj) { uint32_t len = adj->serialize(tmp_buffer); file_write(file, tmp_buffer, len); } else { - tmp_buffer[0] = 0; - file_write(file, tmp_buffer, 1); + file_write(file, tmp_buffer, disabled_len); } file_close(file); diff --git a/defines.h b/defines.h index b32e5f2a2..d057a5b3d 100755 --- a/defines.h +++ b/defines.h @@ -25,7 +25,7 @@ #define ENABLE_DEBUG // enable serial debug #define TMP_BUFFER_SIZE 320 // scratch buffer size -#define TMP_BUFFER_ALLOC_SIZE TMP_BUFFER_SIZE+100 // allocate extra space to allow overflow when needed +#define TMP_BUFFER_ALLOC_SIZE TMP_BUFFER_SIZE+(TMP_BUFFER_SIZE/2) // allocate extra space to allow overflow when needed /** Firmware version, hardware version, and maximal values */ #define OS_FW_VERSION 221 // Firmware version: 221 means 2.2.1 @@ -360,7 +360,7 @@ enum { #define PIN_LATCH_VOLT_SENSE A0 // latch voltage sensing pin #define PIN_FREE_LIST {} // no free GPIO pin at the moment #define ETHER_BUFFER_SIZE 2048 - #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+100 // allocate extra space to allow overflow when needed + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+(ETHER_BUFFER_SIZE/2) // allocate extra space to allow overflow when needed #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above #define ETHER_SPI_CLOCK 10000000L // SPI clock for Ethernet (e.g. 10MHz) @@ -451,7 +451,7 @@ enum { #define PIN_FREE_LIST {5,6,7,8,9,11,12,13,16,19,20,21,23,25,26} // free GPIO pins #define ETHER_BUFFER_SIZE 16384 - #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+100 // allocate extra space to allow overflow when needed + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+(ETHER_BUFFER_SIZE/2) // allocate extra space to allow overflow when needed #define SDA 0 @@ -477,7 +477,7 @@ enum { #define PIN_RFTX 0 #define PIN_FREE_LIST {} #define ETHER_BUFFER_SIZE 16384 - #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+100 // allocate extra space to allow overflow when needed + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+(ETHER_BUFFER_SIZE/2) // allocate extra space to allow overflow when needed #endif diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index fb8f8b5b4..6795576f8 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -746,13 +746,7 @@ void server_delete_program(OTF_PARAMS_DEF) { if (pid == -1) { pd.eraseall(); } else if (pid < pd.nprograms) { - if (pd.del(pid)) { - #if defined(USE_SENSORS) - for (int i = pid; i < pd.nprograms-1; i++) { - file_copy_block(SENADJ_FILENAME, SENSOR_ADJUSTMENT_SIZE * (i+1), SENSOR_ADJUSTMENT_SIZE * i, SENSOR_ADJUSTMENT_SIZE, tmp_buffer); - } - #endif - } + pd.del(pid); } else { handle_return(HTML_DATA_OUTOFBOUND); } @@ -846,6 +840,7 @@ void server_change_program(OTF_PARAMS_DEF) { } } + SensorAdjustment *snadj_ptr = nullptr; #if defined(USE_SENSORS) char *end; @@ -854,17 +849,17 @@ void server_change_program(OTF_PARAMS_DEF) { uint32_t sid = 255; uint32_t point_count = 0; sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; - + if ((adj = os.get_sensor_adjust(pid))) { flags = adj->flags; sid = adj->sid; point_count = adj->point_count; - for (size_t i = 0; i <= point_count; i++) { // TODO: <=? + for (size_t i = 0; i < point_count; i++) { points[i] = adj->points[i]; } } - + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flags"), true)) { flags=strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); @@ -891,14 +886,13 @@ void server_change_program(OTF_PARAMS_DEF) { } points[i++] = sensor_adjustment_point_t {x, y}; last_x = x; - while (*(ptr++) != ';') {} + while (*ptr != '\0' && *(ptr++) != ';') {} } point_count = i; } SensorAdjustment snadj(flags, sid, point_count, points); - os.write_sensor_adjust(&snadj, pid); - + snadj_ptr = &snadj; #endif if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); @@ -938,9 +932,9 @@ void server_change_program(OTF_PARAMS_DEF) { } if (pid==-1) { - if(!pd.add(&prog)) handle_return(HTML_DATA_OUTOFBOUND); + if(!pd.add(&prog, snadj_ptr)) handle_return(HTML_DATA_OUTOFBOUND); } else { - if(!pd.modify(pid, &prog)) handle_return(HTML_DATA_OUTOFBOUND); + if(!pd.modify(pid, &prog, snadj_ptr)) handle_return(HTML_DATA_OUTOFBOUND); } handle_return(HTML_SUCCESS); } @@ -1072,30 +1066,22 @@ void server_json_programs_main(OTF_PARAMS_DEF) { uint8_t adj_count = 0; SensorAdjustment *adj; - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); - if (file) { - for (size_t i = 0; i < pd.nprograms; i++) { - if ((adj = os.get_sensor_adjust(i))) { - if (adj_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); - for (int j = 0; j < adj->point_count; j++) { - if (j) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); - } - bfill.emit_p(PSTR("]}")); - adj_count += 1; - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + for (size_t i = 0; i < pd.nprograms; i++) { + if ((adj = os.get_sensor_adjust(i))) { + if (adj_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); + for (int j = 0; j < adj->point_count; j++) { + if (j) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); + } + bfill.emit_p(PSTR("]}")); + adj_count += 1; + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); } } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); } #endif bfill.emit_p(PSTR("]}")); @@ -2062,7 +2048,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { children[i++] = ensemble_children_t {(uint8_t)d, d1, d2, d3, d4}; - while (*(ptr++) != ';') {} + while (*ptr != '\0' && *(ptr++) != ';') {} } children_count = i; @@ -2391,10 +2377,9 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { case SensorType::MAX_VALUE: break; } - } - - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } } bfill.emit_p(PSTR("],\"units\":[")); @@ -2402,11 +2387,11 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { if (i) bfill.emit_p(PSTR(",")); SensorUnit unit = static_cast(i); bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":$D,\"index\":$D,\"value\":$D}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit), i); + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } } - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } bfill.emit_p(PSTR("],\"enums\":{")); bfill_enum_values(PSTR("SensorUnitGroup")); diff --git a/program.cpp b/program.cpp index f555f19e2..d147f352f 100644 --- a/program.cpp +++ b/program.cpp @@ -105,10 +105,13 @@ void ProgramData::read(unsigned char pid, ProgramStruct *buf) { } /** Add a program */ -unsigned char ProgramData::add(ProgramStruct *buf) { +unsigned char ProgramData::add(ProgramStruct *buf, SensorAdjustment *adj) { if (nprograms >= MAX_NUM_PROGRAMS) return 0; file_write_block(PROG_FILENAME, buf, 1+(uint32_t)nprograms*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); - nprograms ++; +#if defined(USE_SENSORS) + if (adj) os.write_sensor_adjust(adj, nprograms); +#endif + nprograms++; save_count(); return 1; } @@ -124,6 +127,14 @@ void ProgramData::moveup(unsigned char pid) { file_read_block(PROG_FILENAME, buf2, next, PROGRAMSTRUCT_SIZE); file_write_block(PROG_FILENAME, tmp_buffer, next, PROGRAMSTRUCT_SIZE); file_write_block(PROG_FILENAME, buf2, pos, PROGRAMSTRUCT_SIZE); +#if defined(USE_SENSORS) + // swap senadj entries for pid-1 and pid to match the program swap + char buf3[SENSOR_ADJUSTMENT_SIZE]; + file_read_block(SENADJ_FILENAME, tmp_buffer, SENSOR_ADJUSTMENT_SIZE * (pid-1), SENSOR_ADJUSTMENT_SIZE); + file_read_block(SENADJ_FILENAME, buf3, SENSOR_ADJUSTMENT_SIZE * pid, SENSOR_ADJUSTMENT_SIZE); + file_write_block(SENADJ_FILENAME, buf3, SENSOR_ADJUSTMENT_SIZE * (pid-1), SENSOR_ADJUSTMENT_SIZE); + file_write_block(SENADJ_FILENAME, tmp_buffer, SENSOR_ADJUSTMENT_SIZE * pid, SENSOR_ADJUSTMENT_SIZE); +#endif } void ProgramData::toggle_pause(uint32_t delay) { @@ -178,10 +189,13 @@ void ProgramData::clear_pause() { } /** Modify a program */ -unsigned char ProgramData::modify(unsigned char pid, ProgramStruct *buf) { +unsigned char ProgramData::modify(unsigned char pid, ProgramStruct *buf, SensorAdjustment *adj) { if (pid >= nprograms) return 0; uint32_t pos = 1+(uint32_t)pid*PROGRAMSTRUCT_SIZE; file_write_block(PROG_FILENAME, buf, pos, PROGRAMSTRUCT_SIZE); +#if defined(USE_SENSORS) + if (adj) os.write_sensor_adjust(adj, pid); +#endif return 1; } @@ -194,6 +208,11 @@ unsigned char ProgramData::del(unsigned char pid) { for (; pos < 1+(uint32_t)nprograms*PROGRAMSTRUCT_SIZE; pos+=PROGRAMSTRUCT_SIZE) { file_copy_block(PROG_FILENAME, pos, pos-PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE, tmp_buffer); } +#if defined(USE_SENSORS) + for (int i = pid; i < nprograms-1; i++) { + file_copy_block(SENADJ_FILENAME, SENSOR_ADJUSTMENT_SIZE * (i+1), SENSOR_ADJUSTMENT_SIZE * i, SENSOR_ADJUSTMENT_SIZE, tmp_buffer); + } +#endif nprograms --; save_count(); return 1; diff --git a/program.h b/program.h index 9544dfd10..c985d1a4f 100644 --- a/program.h +++ b/program.h @@ -31,6 +31,8 @@ #include "OpenSprinkler.h" #include "types.h" +class SensorAdjustment; // forward declaration for platforms where USE_SENSORS is not defined + /** Log data structure */ struct LogStruct { unsigned char station; @@ -144,8 +146,8 @@ class ProgramData { static void init(); static void eraseall(); static void read(unsigned char pid, ProgramStruct *buf); - static unsigned char add(ProgramStruct *buf); - static unsigned char modify(unsigned char pid, ProgramStruct *buf); + static unsigned char add(ProgramStruct *buf, SensorAdjustment *adj = nullptr); + static unsigned char modify(unsigned char pid, ProgramStruct *buf, SensorAdjustment *adj = nullptr); static unsigned char set_flagbit(unsigned char pid, unsigned char bid, unsigned char value); static void moveup(unsigned char pid); static unsigned char del(unsigned char pid); From d7c55df6578617da3811cdb2c39acaf8abdeec71 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 26 Apr 2026 17:42:49 -0400 Subject: [PATCH 093/115] add uuid for sensors to reference them by unique ids instead of positional indices. this makes program adjustments and ensemble sensors more robust to sensor changes such as deletions --- OpenSprinkler.cpp | 19 +++-- OpenSprinkler.h | 12 ++-- ads1115.cpp | 2 +- ads1115.h | 2 +- opensprinkler_server.cpp | 149 +++++++++++++++++++++++---------------- sensor.cpp | 75 +++++++++++--------- sensor.h | 35 ++++----- 7 files changed, 170 insertions(+), 124 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 811230301..7b32fd555 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2600,6 +2600,14 @@ void list_all_files() { #endif } +uint8_t OpenSprinkler::find_sensor_index(uint16_t uuid) { + if (uuid == SENSOR_UUID_NONE) return MAX_SENSORS; + for (uint8_t i = 0; i < MAX_SENSORS; i++) { + if (sensors[i].uuid == uuid) return i; + } + return MAX_SENSORS; +} + void OpenSprinkler::load_sensors() { lcd.clear(); lcd.setCursor(0,0); @@ -2635,6 +2643,7 @@ void OpenSprinkler::load_sensors() { if ((sensor = parse_sensor(file))) { sensors[i].interval = sensor->interval; sensors[i].flags = sensor->flags; + sensors[i].uuid = sensor->uuid; sensors[i].next_update = 0; sensors[i].value = sensor->get_initial_value(); } @@ -2748,7 +2757,7 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { SensorLogRecord rec = {}; rec.timestamp = now(); rec.value = value; - rec.sid = sid; + rec.uuid = sensors[sid].uuid; file_write(dfile, &rec, sizeof(rec)); file_close(dfile); } @@ -2824,7 +2833,7 @@ void OpenSprinkler::poll_sensors() { SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { // result is statically allocated to avoid repeatedly new and delete // Do not call delete on the return result from this function - static SensorAdjustment result(0, 255, 0, nullptr); + static SensorAdjustment result(0, SENSOR_UUID_NONE, 0, nullptr); if (index >= pd.nprograms) return nullptr; os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); @@ -2838,7 +2847,7 @@ SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { file_read(file, tmp_buffer, SENSOR_ADJUSTMENT_SIZE); file_close(file); result = SensorAdjustment(tmp_buffer); - if (result.sid < 255) { + if (result.uuid != SENSOR_UUID_NONE) { return &result; } } @@ -2850,9 +2859,9 @@ void OpenSprinkler::write_sensor_adjust(SensorAdjustment *adj, uint8_t index) { os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); if (file) { - // Build a disabled entry (sid=255 is the "no adjustment" sentinel). + // Build a disabled entry (uuid=SENSOR_UUID_NONE is the "no adjustment" sentinel). // Used both for padding gaps and for writing a null adj. - SensorAdjustment disabled(0, 255, 0, nullptr); + SensorAdjustment disabled(0, SENSOR_UUID_NONE, 0, nullptr); uint32_t disabled_len = disabled.serialize(tmp_buffer); // Pad with disabled entries if the file doesn't yet reach pos. diff --git a/OpenSprinkler.h b/OpenSprinkler.h index ee5fef1a3..06fed9cfe 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -116,11 +116,12 @@ extern OTF::OpenThingsFramework *otf; /** Non-volatile data structure */ struct NVConData { - uint16_t sunrise_time; // sunrise time (in minutes) - uint16_t sunset_time; // sunset time (in minutes) - uint32_t rd_stop_time; // rain delay stop time - uint32_t external_ip; // external ip - uint8_t reboot_cause; // reboot cause + uint16_t sunrise_time; // sunrise time (in minutes) + uint16_t sunset_time; // sunset time (in minutes) + uint32_t rd_stop_time; // rain delay stop time + uint32_t external_ip; // external ip + uint8_t reboot_cause; // reboot cause + uint16_t last_sensor_uuid; // counter for sensor UUID generation; next sensor gets ++this }; struct StationAttrib { // station attributes @@ -389,6 +390,7 @@ class OpenSprinkler { static os_file_type open_sensor_log_header(FileOpenMode mode); static void remove_sensor_log(int16_t file_no = -1); // -1 removes header + all data files static void load_sensors(); + static uint8_t find_sensor_index(uint16_t uuid); // linear scan; returns MAX_SENSORS if not found static Sensor *parse_sensor(os_file_type file); // return is a statically allocated object, don't delete static Sensor *get_sensor(uint8_t index); // return is a statically allocated object, don't delete static void write_sensor(Sensor *sensor, uint8_t index); diff --git a/ads1115.cpp b/ads1115.cpp index 4047f5ca2..cbfd30c62 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -84,7 +84,7 @@ void ADS1115::request_pin(uint8_t pin) { this->_write_register(0x01, config); } -ADS1115Sensor::ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint32_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : +ADS1115Sensor::ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : Sensor(interval, min, max, scale, offset, name, unit, flags), sensor_index(sensor_index), pin(pin), diff --git a/ads1115.h b/ads1115.h index a5fa50660..d652afd3e 100644 --- a/ads1115.h +++ b/ads1115.h @@ -55,7 +55,7 @@ class ADS1115 { class ADS1115Sensor : public Sensor { public: - ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); ADS1115Sensor(ADS1115 **sensors, char *buf); void emit_extra_json(BufferFiller *bfill); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 6795576f8..781f603d1 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -846,13 +846,13 @@ void server_change_program(OTF_PARAMS_DEF) { SensorAdjustment *adj = nullptr; uint32_t flags = 0; - uint32_t sid = 255; + uint32_t adj_uuid = SENSOR_UUID_NONE; uint32_t point_count = 0; sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; if ((adj = os.get_sensor_adjust(pid))) { flags = adj->flags; - sid = adj->sid; + adj_uuid = adj->uuid; point_count = adj->point_count; for (size_t i = 0; i < point_count; i++) { @@ -865,10 +865,10 @@ void server_change_program(OTF_PARAMS_DEF) { if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_sid"), true)) { - sid=strtoul(tmp_buffer, &end, 10); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_uuid"), true)) { + adj_uuid=strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (sid >= MAX_SENSORS) sid = 255; + if (adj_uuid > 0xFFFF) handle_return(HTML_DATA_OUTOFBOUND); } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_points"), true)) { @@ -891,7 +891,7 @@ void server_change_program(OTF_PARAMS_DEF) { point_count = i; } - SensorAdjustment snadj(flags, sid, point_count, points); + SensorAdjustment snadj(flags, (uint16_t)adj_uuid, point_count, points); snadj_ptr = &snadj; #endif @@ -1069,7 +1069,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { for (size_t i = 0; i < pd.nprograms; i++) { if ((adj = os.get_sensor_adjust(i))) { if (adj_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); + bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"uuid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->uuid, adj->point_count); for (int j = 0; j < adj->point_count; j++) { if (j) bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); @@ -1877,7 +1877,7 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { for (size_t i = 0; i < MAX_SENSORS; i++) { if (os.sensors[i].interval && (sensor = os.get_sensor(i))) { if (sensor_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); + bfill.emit_p(PSTR("{\"uuid\":$D,\"idx\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), sensor->uuid, i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); sensor->emit_extra_json(&bfill); bfill.emit_p(PSTR("}")); sensor_count += 1; @@ -1910,19 +1910,26 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) handle_return(HTML_DATA_MISSING); char *end; - long sid = strtol(tmp_buffer, &end, 10); + long sid_param = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (sid < -1 || sid >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + bool is_new = false; + long sid; - if (sid == -1 ) { - while (++sid < MAX_SENSORS) { - if (!os.sensors[sid].interval) { - break; - } + if (sid_param == -1) { + // Find first empty slot for a new sensor + sid = -1; + for (long i = 0; i < MAX_SENSORS; i++) { + if (!os.sensors[i].interval) { sid = i; break; } } - - if (sid == MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + if (sid == -1) handle_return(HTML_DATA_OUTOFBOUND); + is_new = true; + } else { + // sid_param is a UUID — look up the slot + if (sid_param < 1 || sid_param > 0xFFFF) handle_return(HTML_DATA_OUTOFBOUND); + uint8_t idx = os.find_sensor_index((uint16_t)sid_param); + if (idx >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + sid = idx; } if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); @@ -1940,7 +1947,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { float offset = 0; uint32_t interval = 5; // default: 5 minutes SensorUnit unit = SensorUnit::None; - uint32_t flags = 0; + uint16_t flags = 0; char name[SENSOR_NAME_LEN]; snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); @@ -2001,7 +2008,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flags"), true)) { - flags = strtoul(tmp_buffer, &end, 10); + flags = (uint16_t)strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } @@ -2011,7 +2018,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { uint8_t children_count = 0; ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - children[i].sensor_id = 255; + children[i].uuid = SENSOR_UUID_NONE; } EnsembleAction action = EnsembleAction::Min; @@ -2043,10 +2050,10 @@ void server_change_sensor(OTF_PARAMS_DEF) { handle_return(HTML_DATA_FORMATERROR); } - if (d >= MAX_SENSORS || d < -1) handle_return(HTML_DATA_FORMATERROR); - if (d == -1) d = sid; + // d is the child sensor's UUID; out-of-range values map to disabled + uint16_t child_uuid = (d >= 1 && d <= 0xFFFF) ? (uint16_t)d : SENSOR_UUID_NONE; - children[i++] = ensemble_children_t {(uint8_t)d, d1, d2, d3, d4}; + children[i++] = ensemble_children_t {d1, d2, d3, d4, child_uuid}; while (*ptr != '\0' && *(ptr++) != ';') {} } @@ -2119,6 +2126,19 @@ void server_change_sensor(OTF_PARAMS_DEF) { os.sensors[sid].flags = flags; os.sensors[sid].next_update = 0; os.sensors[sid].value = result_sensor->get_initial_value(); + + if (is_new) { + // Assign a new UUID for this sensor + uint16_t new_uuid = os.nvdata.last_sensor_uuid + 1; + if (new_uuid == SENSOR_UUID_NONE) new_uuid = 1; + os.nvdata.last_sensor_uuid = new_uuid; + os.nvdata_save(); + result_sensor->uuid = new_uuid; + os.sensors[sid].uuid = new_uuid; + } else { + result_sensor->uuid = os.sensors[sid].uuid; + } + os.write_sensor(result_sensor, sid); delete result_sensor; @@ -2138,22 +2158,24 @@ void server_delete_sensor(OTF_PARAMS_DEF) { if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) handle_return(HTML_DATA_MISSING); - int sid=atoi(tmp_buffer); + int sid = atoi(tmp_buffer); if (sid == -1) { - uint8_t i; - for (i=0;i 0xFFFF) handle_return(HTML_DATA_OUTOFBOUND); + uint8_t idx = os.find_sensor_index((uint16_t)sid); + if (idx >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + os.write_sensor(nullptr, idx); + os.sensors[idx].interval = 0; + os.sensors[idx].uuid = 0; } handle_return(HTML_SUCCESS); @@ -2222,11 +2244,11 @@ void server_log_sensor(OTF_PARAMS_DEF) { if (after >= before) handle_return(HTML_DATA_OUTOFBOUND); } - long target_sid = -1; + long target_uuid = -1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { - target_sid = strtol(tmp_buffer, &end, 10); + target_uuid = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (target_sid >= MAX_SENSORS || target_sid < -1) handle_return(HTML_DATA_OUTOFBOUND); + if (target_uuid != -1 && (target_uuid < 1 || target_uuid > 0xFFFF)) handle_return(HTML_DATA_OUTOFBOUND); } send_packet(OTF_PARAMS); @@ -2236,7 +2258,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); SensorLogRecord rec; - char print_buf[22] = "00,00000000,00000000\n"; + char print_buf[24] = "0000,00000000,00000000\n"; uint32_t flat_idx = 0; // sequential record counter across all files uint32_t count = 0; @@ -2251,31 +2273,36 @@ void server_log_sensor(OTF_PARAMS_DEF) { flat_idx++; if (flat_idx <= cursor) continue; if (rec.timestamp == 0) continue; - if (target_sid > -1 && rec.sid != (uint8_t)target_sid) continue; + if (target_uuid > -1 && rec.uuid != (uint16_t)target_uuid) continue; if (rec.timestamp > before || rec.timestamp < after) continue; uint32_t raw_value; memcpy(&raw_value, &rec.value, sizeof(raw_value)); - print_buf[0] = dec2hexchar((rec.sid >> 4) & 0xF); - print_buf[1] = dec2hexchar(rec.sid & 0xF); - print_buf[3] = dec2hexchar((rec.timestamp >> 28) & 0xF); - print_buf[4] = dec2hexchar((rec.timestamp >> 24) & 0xF); - print_buf[5] = dec2hexchar((rec.timestamp >> 20) & 0xF); - print_buf[6] = dec2hexchar((rec.timestamp >> 16) & 0xF); - print_buf[7] = dec2hexchar((rec.timestamp >> 12) & 0xF); - print_buf[8] = dec2hexchar((rec.timestamp >> 8) & 0xF); - print_buf[9] = dec2hexchar((rec.timestamp >> 4) & 0xF); - print_buf[10] = dec2hexchar(rec.timestamp & 0xF); - print_buf[12] = dec2hexchar((raw_value >> 28) & 0xF); - print_buf[13] = dec2hexchar((raw_value >> 24) & 0xF); - print_buf[14] = dec2hexchar((raw_value >> 20) & 0xF); - print_buf[15] = dec2hexchar((raw_value >> 16) & 0xF); - print_buf[16] = dec2hexchar((raw_value >> 12) & 0xF); - print_buf[17] = dec2hexchar((raw_value >> 8) & 0xF); - print_buf[18] = dec2hexchar((raw_value >> 4) & 0xF); - print_buf[19] = dec2hexchar(raw_value & 0xF); - res.write(print_buf, 21); + print_buf[0] = dec2hexchar((rec.uuid >> 12) & 0xF); + print_buf[1] = dec2hexchar((rec.uuid >> 8) & 0xF); + print_buf[2] = dec2hexchar((rec.uuid >> 4) & 0xF); + print_buf[3] = dec2hexchar(rec.uuid & 0xF); + // print_buf[4] = ',' + print_buf[5] = dec2hexchar((rec.timestamp >> 28) & 0xF); + print_buf[6] = dec2hexchar((rec.timestamp >> 24) & 0xF); + print_buf[7] = dec2hexchar((rec.timestamp >> 20) & 0xF); + print_buf[8] = dec2hexchar((rec.timestamp >> 16) & 0xF); + print_buf[9] = dec2hexchar((rec.timestamp >> 12) & 0xF); + print_buf[10] = dec2hexchar((rec.timestamp >> 8) & 0xF); + print_buf[11] = dec2hexchar((rec.timestamp >> 4) & 0xF); + print_buf[12] = dec2hexchar(rec.timestamp & 0xF); + // print_buf[13] = ',' + print_buf[14] = dec2hexchar((raw_value >> 28) & 0xF); + print_buf[15] = dec2hexchar((raw_value >> 24) & 0xF); + print_buf[16] = dec2hexchar((raw_value >> 20) & 0xF); + print_buf[17] = dec2hexchar((raw_value >> 16) & 0xF); + print_buf[18] = dec2hexchar((raw_value >> 12) & 0xF); + print_buf[19] = dec2hexchar((raw_value >> 8) & 0xF); + print_buf[20] = dec2hexchar((raw_value >> 4) & 0xF); + print_buf[21] = dec2hexchar(raw_value & 0xF); + // print_buf[22] = '\n' + res.write(print_buf, 23); count++; } file_close(dfile); @@ -2288,15 +2315,15 @@ void server_log_sensor(OTF_PARAMS_DEF) { void server_clear_sensor_log(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - int sid = -1; + long uuid = -1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { - sid = atoi(tmp_buffer); - if (sid < -1 || sid >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + uuid = atol(tmp_buffer); + if (uuid != -1 && (uuid < 1 || uuid > 0xFFFF)) handle_return(HTML_DATA_OUTOFBOUND); } else { handle_return(HTML_DATA_MISSING); } - if (sid == -1) { + if (uuid == -1) { // Remove all log files — frees flash immediately; header recreated on next log_sensor call os.remove_sensor_log(); handle_return(HTML_SUCCESS); @@ -2323,7 +2350,7 @@ void server_clear_sensor_log(OTF_PARAMS_DEF) { uint32_t pos = 0; while (true) { if (file_read(dfile, &rec, sizeof(rec)) != (int)sizeof(rec)) break; - if (rec.timestamp != 0 && rec.sid == (uint8_t)sid) { + if (rec.timestamp != 0 && rec.uuid == (uint16_t)uuid) { memset(&rec, 0, sizeof(rec)); file_seek(dfile, pos); file_write(dfile, &rec, sizeof(rec)); diff --git a/sensor.cpp b/sensor.cpp index 68bca21e8..e9d841e3b 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -1,6 +1,8 @@ #include "sensor.h" #include "OpenSprinkler.h" +extern OpenSprinkler os; + const char *enum_string(SensorUnitGroup group) { switch (group) { case SensorUnitGroup::None: @@ -272,8 +274,8 @@ const uint32_t get_sensor_unit_index(SensorUnit unit) { return static_cast(unit); } -Sensor::Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint32_t flags) : - interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit), flags(flags) { +Sensor::Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags) : + interval(interval), min(min), max(max), scale(scale), offset(offset), flags(flags), unit(unit) { strncpy(this->name, name, SENSOR_NAME_LEN); this->name[SENSOR_NAME_LEN - 1] = 0; } @@ -312,11 +314,12 @@ uint32_t Sensor::serialize(char* buf) { i += SENSOR_NAME_LEN; buf[i++] = static_cast(this->unit); i += write_buf(buf + i, this->interval); - i += write_buf(buf + i, this->flags); + i += write_buf(buf + i, this->flags); i += write_buf(buf + i, this->scale); i += write_buf(buf + i, this->offset); i += write_buf(buf + i, this->min); i += write_buf(buf + i, this->max); + i += write_buf(buf + i, this->uuid); i += this->_serialize_internal(buf + i); return i; @@ -329,16 +332,17 @@ uint32_t Sensor::_deserialize(char* buf) { i += SENSOR_NAME_LEN; this->unit = static_cast(buf[i++]); this->interval = read_buf(buf, &i); - this->flags = read_buf(buf, &i); + this->flags = read_buf(buf, &i); this->scale = read_buf(buf, &i); this->offset = read_buf(buf, &i); this->min = read_buf(buf, &i); this->max = read_buf(buf, &i); + this->uuid = read_buf(buf, &i); return i; } -EnsembleSensor::EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : +EnsembleSensor::EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : Sensor(interval, min, max, scale, offset, name, unit, flags), action(action), sensors(sensors) { @@ -347,7 +351,7 @@ EnsembleSensor::EnsembleSensor(uint32_t interval, float min, float max, float sc this->children[i] = children[i]; } else { - this->children[i] = ensemble_children_t{ sensor_id: 255, min : 0.f, max : 0.f, scale : 0.f, offset : 0.f }; + this->children[i] = ensemble_children_t{ 0.f, 0.f, 0.f, 0.f, SENSOR_UUID_NONE }; } } } @@ -357,7 +361,7 @@ void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { if (i) bfill->emit_p(PSTR(",")); ensemble_children_t* child = &this->children[i]; - bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->sensor_id, child->max, child->min, child->scale, child->offset); + bfill->emit_p(PSTR("{\"uuid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->uuid, child->max, child->min, child->scale, child->offset); } bfill->emit_p(PSTR("]}")); } @@ -392,7 +396,7 @@ float EnsembleSensor::_get_raw_value() { uint8_t count = 0; for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - uint8_t sensor = this->children[i].sensor_id; + uint8_t sensor = os.find_sensor_index(this->children[i].uuid); if (sensor < MAX_SENSORS && sensors[sensor].interval) { float value = sensors[sensor].value; value = (value * this->children[i].scale) + this->children[i].offset; @@ -436,7 +440,7 @@ float EnsembleSensor::_get_raw_value() { uint32_t EnsembleSensor::_serialize_internal(char* buf) { uint32_t i = 0; for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - i += write_buf(buf + i, this->children[j].sensor_id); + i += write_buf(buf + i, this->children[j].uuid); i += write_buf(buf + i, this->children[j].min); i += write_buf(buf + i, this->children[j].max); i += write_buf(buf + i, this->children[j].scale); @@ -450,7 +454,7 @@ uint32_t EnsembleSensor::_serialize_internal(char* buf) { EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { uint32_t i = Sensor::_deserialize(buf); for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - this->children[j].sensor_id = read_buf(buf, &i); + this->children[j].uuid = read_buf(buf, &i); this->children[j].min = read_buf(buf, &i); this->children[j].max = read_buf(buf, &i); this->children[j].scale = read_buf(buf, &i); @@ -462,7 +466,7 @@ EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { } -WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : +WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, WeatherGetter weather_getter, WeatherAction action) : Sensor(interval, min, max, scale, offset, name, unit, flags), action(action), weather_getter(weather_getter) { @@ -495,9 +499,9 @@ WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) { this->action = static_cast(buf[i++]); } -SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t* points) { +SensorAdjustment::SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { this->flags = flags; - this->sid = sid; + this->uuid = uuid; if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; this->point_count = point_count; for (size_t i = 0; i < point_count; i++) { @@ -508,7 +512,7 @@ SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_cou SensorAdjustment::SensorAdjustment(char* buf) { uint32_t i = 0; this->flags = buf[i++]; - this->sid = buf[i++]; + this->uuid = read_buf(buf, &i); this->point_count = buf[i++]; for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { @@ -518,37 +522,38 @@ SensorAdjustment::SensorAdjustment(char* buf) { } float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { - if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->sid < MAX_SENSORS && sensors[this->sid].interval) { - float value = sensors[this->sid].value; - if (value <= this->points[0].x) return this->points[0].y; - if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; - - uint8_t i; - - for (i = 0; i < this->point_count - 1; i++) { - if (value >= this->points[i].x) { - break; + if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { + uint8_t idx = os.find_sensor_index(this->uuid); + if (idx < MAX_SENSORS && sensors[idx].interval) { + float value = sensors[idx].value; + if (value <= this->points[0].x) return this->points[0].y; + if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; + + uint8_t i; + + for (i = 0; i < this->point_count - 1; i++) { + if (value >= this->points[i].x) { + break; + } } - } - sensor_adjustment_point_t left = this->points[i]; - sensor_adjustment_point_t right = this->points[i + 1]; - if (right.x == left.x) return left.y; + sensor_adjustment_point_t left = this->points[i]; + sensor_adjustment_point_t right = this->points[i + 1]; + if (right.x == left.x) return left.y; - value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; + value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; - if (value < 0) return 0; - return value; - } - else { - return 1.f; + if (value < 0) return 0; + return value; + } } + return 1.f; } uint32_t SensorAdjustment::serialize(char* buf) { uint32_t i = 0; buf[i++] = this->flags; - buf[i++] = this->sid; + i += write_buf(buf + i, this->uuid); buf[i++] = this->point_count; for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { diff --git a/sensor.h b/sensor.h index d54f76025..b3ee428d9 100644 --- a/sensor.h +++ b/sensor.h @@ -25,11 +25,14 @@ #define SENSOR_NAME_LEN 33 #define SENSOR_CUSTOM_UNIT_LEN 9 +#define SENSOR_UUID_NONE 0 // sentinel: "no sensor assigned" (0 = uninitialized/disabled) + typedef struct { uint32_t interval; - uint32_t flags; uint32_t next_update; - float value; + float value; + uint16_t uuid; // stable sensor identifier (0 = empty slot) + uint16_t flags; // was uint32_t; only 2 bits used, uint16_t keeps struct at 16 bytes } sensor_memory_t; // Sensor log file format — both structs are tightly packed (no padding) so @@ -47,8 +50,7 @@ struct __attribute__((packed)) SensorLogHeader { struct __attribute__((packed)) SensorLogRecord { uint32_t timestamp; // unix epoch seconds float value; // sensor reading - uint8_t sid; // sensor ID - uint8_t reserved; + uint16_t uuid; // sensor UUID (replaces sid+reserved, same 10-byte size) }; enum class SensorType : uint8_t { @@ -115,7 +117,7 @@ typedef enum { class Sensor { public: - Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint32_t flags); + Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags); Sensor(); virtual ~Sensor() {} @@ -129,10 +131,10 @@ class Sensor { float max = 0.f; float scale = 0.f; float offset = 0.f; - char name[SENSOR_NAME_LEN] = {0}; + uint16_t flags = 0; + uint16_t uuid = 0; // assigned by write_sensor on creation; 0 = not yet assigned SensorUnit unit = SensorUnit::None; - - uint32_t flags = 0; + char name[SENSOR_NAME_LEN] = {0}; SensorType virtual get_sensor_type() = 0; float virtual get_initial_value() = 0; @@ -156,18 +158,18 @@ enum class EnsembleAction : uint8_t { typedef Sensor* (*SensorGetter)(uint8_t); typedef struct { - uint8_t sensor_id; float min; float max; float scale; float offset; + uint16_t uuid; // UUID of child sensor (SENSOR_UUID_NONE = unused slot) } ensemble_children_t; #define ENSEMBLE_SENSOR_CHILDREN_COUNT 4 class EnsembleSensor : public Sensor { public: - EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); + EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); EnsembleSensor(sensor_memory_t *sensors, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -197,7 +199,7 @@ typedef float (*WeatherGetter)(WeatherAction); class WeatherSensor : public Sensor { public: - WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, WeatherGetter weather_getter, WeatherAction action); WeatherSensor(WeatherGetter weather_getter, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -231,19 +233,20 @@ typedef enum { class SensorAdjustment { public: - SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points); + SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); SensorAdjustment(char *buf); float get_adjustment_factor(sensor_memory_t *sensors); uint32_t serialize(char *buf); - uint8_t flags; - uint8_t sid; - uint8_t point_count; sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; + uint16_t uuid; // sensor UUID (SENSOR_UUID_NONE = adjustment disabled) + uint8_t flags; + uint8_t point_count; }; -#define SENSOR_ADJUSTMENT_SIZE (3 + (SENSOR_ADJUSTMENT_POINTS * sizeof(sensor_adjustment_point_t))) +// points + uuid(2) + flags(1) + point_count(1) +#define SENSOR_ADJUSTMENT_SIZE (4 + (SENSOR_ADJUSTMENT_POINTS * sizeof(sensor_adjustment_point_t))) const char *enum_string(SensorUnitGroup group); const char *enum_string(EnsembleAction action); From 33f8dcf751f78aeda21a23abf337c6325c93b436 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 26 Apr 2026 18:08:02 -0400 Subject: [PATCH 094/115] reorganize SensorAdjustment class, move its read and write functions to sensor.cpp, use direct binary buffer read/write as (unlike Sensor classes) it doesn't use polymorphism --- OpenSprinkler.cpp | 60 ------------------------------------- OpenSprinkler.h | 3 -- main.cpp | 4 +-- opensprinkler_server.cpp | 4 +-- program.cpp | 4 +-- sensor.cpp | 64 +++++++++++++++++++++++++++------------- sensor.h | 8 ++--- 7 files changed, 53 insertions(+), 94 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 7b32fd555..54b372d1c 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2830,66 +2830,6 @@ void OpenSprinkler::poll_sensors() { } } -SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { - // result is statically allocated to avoid repeatedly new and delete - // Do not call delete on the return result from this function - static SensorAdjustment result(0, SENSOR_UUID_NONE, 0, nullptr); - - if (index >= pd.nprograms) return nullptr; - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); - if (file) { - uint32_t pos = (uint32_t)index * SENSOR_ADJUSTMENT_SIZE; - if (file_size(file) < pos + SENSOR_ADJUSTMENT_SIZE) { - file_close(file); - return nullptr; // entry not yet written - } - file_seek(file, pos, FileSeekMode::Set); - file_read(file, tmp_buffer, SENSOR_ADJUSTMENT_SIZE); - file_close(file); - result = SensorAdjustment(tmp_buffer); - if (result.uuid != SENSOR_UUID_NONE) { - return &result; - } - } - return nullptr; -} - -void OpenSprinkler::write_sensor_adjust(SensorAdjustment *adj, uint8_t index) { - uint32_t pos = (uint32_t)SENSOR_ADJUSTMENT_SIZE * index; - - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); - if (file) { - // Build a disabled entry (uuid=SENSOR_UUID_NONE is the "no adjustment" sentinel). - // Used both for padding gaps and for writing a null adj. - SensorAdjustment disabled(0, SENSOR_UUID_NONE, 0, nullptr); - uint32_t disabled_len = disabled.serialize(tmp_buffer); - - // Pad with disabled entries if the file doesn't yet reach pos. - // This can happen when programs before this one have no adjustment written. - uint32_t cur_size = file_size(file); - if (cur_size < pos) { - file_seek(file, 0, FileSeekMode::End); - while (cur_size < pos) { - file_write(file, tmp_buffer, disabled_len); - cur_size += disabled_len; - } - } - - file_seek(file, pos, FileSeekMode::Set); - if (adj) { - uint32_t len = adj->serialize(tmp_buffer); - file_write(file, tmp_buffer, len); - } else { - file_write(file, tmp_buffer, disabled_len); - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); - } -} - float OpenSprinkler::get_sensor_weather_data(WeatherAction action) { return NAN; // TODO make function for WeatherSensor } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 06fed9cfe..b76c1aa9f 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -397,9 +397,6 @@ class OpenSprinkler { void log_sensor(uint8_t sid, float value); static void test_sensor_log(uint32_t n_records); static void poll_sensors(); - static SensorAdjustment *get_sensor_adjust(uint8_t index); // return is a statically allocated object, don't delete - static void write_sensor_adjust(SensorAdjustment *adj, uint8_t index); - static float get_sensor_weather_data(WeatherAction action); #endif diff --git a/main.cpp b/main.cpp index 5469f95b9..ba51354ab 100644 --- a/main.cpp +++ b/main.cpp @@ -779,7 +779,7 @@ void do_loop() pd.read(pid, &prog); // todo future: reduce load time float sensor_adj = 1.f; #if defined(USE_SENSORS) - SensorAdjustment *adj = os.get_sensor_adjust(pid); + SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); if (adj) { sensor_adj = adj->get_adjustment_factor(os.sensors); } @@ -1621,7 +1621,7 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo if ((pid>0)&&(pid<255)) { pd.read(pid-1, &prog); #if defined(USE_SENSORS) - SensorAdjustment *adj = os.get_sensor_adjust(pid-1); + SensorAdjustment *adj = SensorAdjustment::read(pid-1, pd.nprograms); if (adj) { sensor_adj = adj->get_adjustment_factor(os.sensors); } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 781f603d1..d15dc6a88 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -850,7 +850,7 @@ void server_change_program(OTF_PARAMS_DEF) { uint32_t point_count = 0; sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; - if ((adj = os.get_sensor_adjust(pid))) { + if ((adj = SensorAdjustment::read(pid, pd.nprograms))) { flags = adj->flags; adj_uuid = adj->uuid; point_count = adj->point_count; @@ -1067,7 +1067,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { SensorAdjustment *adj; for (size_t i = 0; i < pd.nprograms; i++) { - if ((adj = os.get_sensor_adjust(i))) { + if ((adj = SensorAdjustment::read(i, pd.nprograms))) { if (adj_count) bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"uuid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->uuid, adj->point_count); for (int j = 0; j < adj->point_count; j++) { diff --git a/program.cpp b/program.cpp index d147f352f..237f63a27 100644 --- a/program.cpp +++ b/program.cpp @@ -109,7 +109,7 @@ unsigned char ProgramData::add(ProgramStruct *buf, SensorAdjustment *adj) { if (nprograms >= MAX_NUM_PROGRAMS) return 0; file_write_block(PROG_FILENAME, buf, 1+(uint32_t)nprograms*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); #if defined(USE_SENSORS) - if (adj) os.write_sensor_adjust(adj, nprograms); + if (adj) SensorAdjustment::write(adj, nprograms); #endif nprograms++; save_count(); @@ -194,7 +194,7 @@ unsigned char ProgramData::modify(unsigned char pid, ProgramStruct *buf, SensorA uint32_t pos = 1+(uint32_t)pid*PROGRAMSTRUCT_SIZE; file_write_block(PROG_FILENAME, buf, pos, PROGRAMSTRUCT_SIZE); #if defined(USE_SENSORS) - if (adj) os.write_sensor_adjust(adj, pid); + if (adj) SensorAdjustment::write(adj, pid); #endif return 1; } diff --git a/sensor.cpp b/sensor.cpp index e9d841e3b..8f24c4b2d 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -509,18 +509,6 @@ SensorAdjustment::SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_c } } -SensorAdjustment::SensorAdjustment(char* buf) { - uint32_t i = 0; - this->flags = buf[i++]; - this->uuid = read_buf(buf, &i); - this->point_count = buf[i++]; - - for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { - this->points[j].x = read_buf(buf, &i); - this->points[j].y = read_buf(buf, &i); - } -} - float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { uint8_t idx = os.find_sensor_index(this->uuid); @@ -550,16 +538,50 @@ float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { return 1.f; } -uint32_t SensorAdjustment::serialize(char* buf) { - uint32_t i = 0; - buf[i++] = this->flags; - i += write_buf(buf + i, this->uuid); - buf[i++] = this->point_count; +SensorAdjustment *SensorAdjustment::read(uint8_t index, uint8_t nprograms) { + static SensorAdjustment result(0, SENSOR_UUID_NONE, 0, nullptr); - for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { - i += write_buf(buf + i, this->points[j].x); - i += write_buf(buf + i, this->points[j].y); + if (index >= nprograms) return nullptr; + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + uint32_t pos = (uint32_t)index * SENSOR_ADJUSTMENT_SIZE; + if (file_size(file) < pos + SENSOR_ADJUSTMENT_SIZE) { + file_close(file); + return nullptr; + } + file_seek(file, pos, FileSeekMode::Set); + file_read(file, &result, SENSOR_ADJUSTMENT_SIZE); + file_close(file); + if (result.uuid != SENSOR_UUID_NONE) { + return &result; + } } + return nullptr; +} - return i; +void SensorAdjustment::write(SensorAdjustment *adj, uint8_t index) { + uint32_t pos = (uint32_t)SENSOR_ADJUSTMENT_SIZE * index; + + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); + if (file) { + SensorAdjustment disabled(0, SENSOR_UUID_NONE, 0, nullptr); + + uint32_t cur_size = file_size(file); + if (cur_size < pos) { + file_seek(file, 0, FileSeekMode::End); + while (cur_size < pos) { + file_write(file, &disabled, SENSOR_ADJUSTMENT_SIZE); + cur_size += SENSOR_ADJUSTMENT_SIZE; + } + } + + file_seek(file, pos, FileSeekMode::Set); + SensorAdjustment *to_write = adj ? adj : &disabled; + file_write(file, to_write, SENSOR_ADJUSTMENT_SIZE); + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } } \ No newline at end of file diff --git a/sensor.h b/sensor.h index b3ee428d9..b2b442179 100644 --- a/sensor.h +++ b/sensor.h @@ -234,10 +234,11 @@ typedef enum { class SensorAdjustment { public: SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); - SensorAdjustment(char *buf); + + static SensorAdjustment *read(uint8_t index, uint8_t nprograms); // returns statically allocated object, do not delete + static void write(SensorAdjustment *adj, uint8_t index); float get_adjustment_factor(sensor_memory_t *sensors); - uint32_t serialize(char *buf); sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; uint16_t uuid; // sensor UUID (SENSOR_UUID_NONE = adjustment disabled) @@ -245,8 +246,7 @@ class SensorAdjustment { uint8_t point_count; }; -// points + uuid(2) + flags(1) + point_count(1) -#define SENSOR_ADJUSTMENT_SIZE (4 + (SENSOR_ADJUSTMENT_POINTS * sizeof(sensor_adjustment_point_t))) +#define SENSOR_ADJUSTMENT_SIZE sizeof(SensorAdjustment) const char *enum_string(SensorUnitGroup group); const char *enum_string(EnsembleAction action); From d9cc50dde9ccf065acf873a946f51102ec40b7b5 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 26 Apr 2026 21:01:10 -0400 Subject: [PATCH 095/115] rearrange some sensor related functions to the sensor.h and .cpp files for better organization; change sens.dat to use lazy writing (grows as necessary); improve sensor APIs to support modification and deletion using either positional index or uuid --- OpenSprinkler.cpp | 228 +------------------------------- OpenSprinkler.h | 19 +-- main.cpp | 2 +- opensprinkler_server.cpp | 175 +++++++++++++++++------- sensor.cpp | 278 ++++++++++++++++++++++++++++++++++++++- sensor.h | 22 +++- 6 files changed, 427 insertions(+), 297 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 54b372d1c..82912a59e 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -41,6 +41,7 @@ unsigned char OpenSprinkler::hw_type; unsigned char OpenSprinkler::hw_rev; unsigned char OpenSprinkler::nboards; unsigned char OpenSprinkler::nstations; +unsigned char OpenSprinkler::nsensors; unsigned char OpenSprinkler::station_bits[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::engage_booster; uint16_t OpenSprinkler::baseline_current; @@ -2471,88 +2472,6 @@ void OpenSprinkler::raindelay_stop() { #if defined(USE_SENSORS) /** Sensor functions */ -Sensor *OpenSprinkler::parse_sensor(os_file_type file) { - static uint8_t sensor_scratchpad[sizeof(SensorUnion)] __attribute__((aligned(4))); - static Sensor* active_sensor = nullptr; - - // The Auto-Destructor: If a sensor was previously loaded, - // call its destructor before we overwrite the memory. - if (active_sensor != nullptr) { - active_sensor->~Sensor(); - active_sensor = nullptr; - } - uint32_t len = 0; - file_read(file, &len, sizeof(len)); - - if (len == 0 || len > TMP_BUFFER_SIZE) return nullptr; - - file_read(file, tmp_buffer, len); - file_seek(file, TMP_BUFFER_SIZE - len, FileSeekMode::Current); - - if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) { - return nullptr; - } - SensorType sensor_type = static_cast(*tmp_buffer); - switch (sensor_type) { - case SensorType::Ensemble: - active_sensor = new (sensor_scratchpad) EnsembleSensor(os.sensors, (char*)tmp_buffer); - break; - case SensorType::ADS1115: - active_sensor = new (sensor_scratchpad) ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); - break; - case SensorType::Weather: - active_sensor = new (sensor_scratchpad) WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); - break; - default: - return nullptr; - }; - return active_sensor; -} - -Sensor *OpenSprinkler::get_sensor(uint8_t index) { - uint32_t pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; - - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); - if (file) { - file_seek(file, pos, FileSeekMode::Current); - - Sensor *result = parse_sensor(file); - file_close(file); - return result; - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - return nullptr; - } -} - -void OpenSprinkler::get_sensor_log_filename(char *buf, uint16_t file_no) { - snprintf(buf, 24, "%s%03u", SENSORS_LOG_FILENAME, file_no%1000); -} - -os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) { - char fname[24]; - get_sensor_log_filename(fname, file_no); - return file_open(fname, mode); -} - -os_file_type OpenSprinkler::open_sensor_log_header(FileOpenMode mode) { - return file_open(SENSORS_LOG_HEADER_FILENAME, mode); -} - -void OpenSprinkler::remove_sensor_log(int16_t file_no) { - char fname[24]; - if (file_no < 0) { - remove_file(SENSORS_LOG_HEADER_FILENAME); - for (uint16_t i = 0; i < SENSOR_LOG_MAX_FILES; i++) { - get_sensor_log_filename(fname, i); - remove_file(fname); - } - } else { - get_sensor_log_filename(fname, (uint16_t)file_no); - remove_file(fname); - } -} void list_all_files() { #if defined(ESP8266) @@ -2600,96 +2519,6 @@ void list_all_files() { #endif } -uint8_t OpenSprinkler::find_sensor_index(uint16_t uuid) { - if (uuid == SENSOR_UUID_NONE) return MAX_SENSORS; - for (uint8_t i = 0; i < MAX_SENSORS; i++) { - if (sensors[i].uuid == uuid) return i; - } - return MAX_SENSORS; -} - -void OpenSprinkler::load_sensors() { - lcd.clear(); - lcd.setCursor(0,0); - lcd.print(F("Init sensors...")); - - // 1. Check if the configuration file exists. - // If not, we need to initialize all sensor-related files. - if (!file_exists(SENSORS_FILENAME)) { - DEBUG_PRINTLN(F("Sensor files missing. Initializing...")); - - // Initialize the sensors configuration file (SENSORS_FILENAME) - memset(tmp_buffer, 0, TMP_BUFFER_SIZE); - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); - if (file) { - for (size_t i = 0; i < MAX_SENSORS; i++) { - file_write(file, tmp_buffer, sizeof(uint32_t)); // length 0 - file_write(file, tmp_buffer, TMP_BUFFER_SIZE); // empty block - } - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } - - // SENADJ_FILENAME grows on demand; no pre-allocation needed. - } - - // 2. Proceed with existing load logic - Sensor *sensor; - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); - if (file) { - for (size_t i = 0; i < MAX_SENSORS; i++) { - if ((sensor = parse_sensor(file))) { - sensors[i].interval = sensor->interval; - sensors[i].flags = sensor->flags; - sensors[i].uuid = sensor->uuid; - sensors[i].next_update = 0; - sensors[i].value = sensor->get_initial_value(); - } - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } - - list_all_files(); - - // Uncomment one of the following to run a sensor log performance test on boot: - //test_sensor_log(100); // quick smoke test - //test_sensor_log(2000); // moderate - //test_sensor_log(32760); // full capacity - // test_sensor_log(40000); // beyond capacity (tests ring wrap) - //list_all_files(); -} - -void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { - uint32_t pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; - uint32_t len = 0; - - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); - if (file) { - if (sensor) { - len = sensor->serialize(tmp_buffer); - } - - DEBUG_PRINTLN(pos); - DEBUG_PRINTLN(len); - - file_seek(file, pos, FileSeekMode::Current); - file_write(file, &len, sizeof(len)); - if (sensor) { - file_write(file, tmp_buffer, len); - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } -} void OpenSprinkler::log_sensor(uint8_t sid, float value) { // Read central header; create/recreate if missing or version mismatch @@ -2762,62 +2591,11 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { file_close(dfile); } -void OpenSprinkler::test_sensor_log(uint32_t n_records) { - remove_sensor_log(); - - DEBUG_PRINTF("sensor log test: writing %lu records\n", (unsigned long)n_records); - uint32_t t0 = millis(); - - for (uint32_t i = 0; i < n_records; i++) { - os.log_sensor((uint8_t)((i + 1) % MAX_SENSORS), (float)i / 1000.f); - } - - uint32_t write_ms = millis() - t0; - DEBUG_PRINTF("sensor log write: %lu ms total, %.2f ms/record\n", - (unsigned long)write_ms, - n_records ? (float)write_ms / n_records : 0.f); - - // Read pass: iterate all files in order (oldest to newest) - os_file_type hfile = open_sensor_log_header(FileOpenMode::Read); - if (!hfile) { - DEBUG_PRINTLN("sensor log test: cannot open header"); - return; - } - SensorLogHeader hdr = {}; - file_read(hfile, &hdr, sizeof(hdr)); - file_close(hfile); - if (hdr.magic != SENSOR_LOG_MAGIC || hdr.version != SENSOR_LOG_VERSION) { - DEBUG_PRINTLN("sensor log test: bad header"); - return; - } - - uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; - uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); - DEBUG_PRINTF("sensor log state: max_files=%u records_per_file=%u cur_file=%u wrapped=%u total_files=%u\n", - hdr.max_files, hdr.records_per_file, hdr.cur_file, hdr.wrapped, total_files); - - uint32_t tr = millis(); - uint32_t count = 0; - for (uint16_t fi = 0; fi < total_files; fi++) { - uint16_t file_no = (first_file + fi) % hdr.max_files; - os_file_type dfile = open_sensor_log(file_no, FileOpenMode::Read); - if (!dfile) continue; - SensorLogRecord rec; - while (file_read(dfile, &rec, sizeof(rec)) == (int)sizeof(rec)) count++; - file_close(dfile); - } - - uint32_t read_ms = millis() - tr; - DEBUG_PRINTF("sensor log read: %lu records in %lu ms (%.2f ms/record)\n", - (unsigned long)count, (unsigned long)read_ms, - count ? (float)read_ms / count : 0.f); -} - void OpenSprinkler::poll_sensors() { - for (uint8_t i = 0; i < MAX_SENSORS; i++) { + for (uint8_t i = 0; i < nsensors; i++) { if (sensors[i].interval && (sensors[i].flags & (1 << SENSOR_FLAG_ENABLE))) { if ((long)(millis() - sensors[i].next_update) > 0) { - Sensor *sensor = get_sensor(i); + Sensor *sensor = Sensor::get(i); if (sensor) { sensors[i].value = sensor->get_new_value(); sensors[i].next_update = millis() + (sensors[i].interval * 1000 * 60); diff --git a/OpenSprinkler.h b/OpenSprinkler.h index b76c1aa9f..d11b708ca 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -263,7 +263,7 @@ class OpenSprinkler { static NVConData nvdata; static ConStatus status; static ConStatus old_status; - static unsigned char nboards, nstations; + static unsigned char nboards, nstations, nsensors; static unsigned char hw_type; // hardware type static unsigned char hw_rev; // hardware minor @@ -385,21 +385,10 @@ class OpenSprinkler { // -- Sensor functions #if defined(USE_SENSORS) - static void get_sensor_log_filename(char *buf, uint16_t file_no); - static os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); - static os_file_type open_sensor_log_header(FileOpenMode mode); - static void remove_sensor_log(int16_t file_no = -1); // -1 removes header + all data files - static void load_sensors(); - static uint8_t find_sensor_index(uint16_t uuid); // linear scan; returns MAX_SENSORS if not found - static Sensor *parse_sensor(os_file_type file); // return is a statically allocated object, don't delete - static Sensor *get_sensor(uint8_t index); // return is a statically allocated object, don't delete - static void write_sensor(Sensor *sensor, uint8_t index); - void log_sensor(uint8_t sid, float value); - static void test_sensor_log(uint32_t n_records); - static void poll_sensors(); - static float get_sensor_weather_data(WeatherAction action); + void log_sensor(uint8_t sid, float value); + static void poll_sensors(); + static float get_sensor_weather_data(WeatherAction action); #endif - // -- LCD functions #if defined(USE_DISPLAY) static void lcd_print_time(time_os_t t); // print current time diff --git a/main.cpp b/main.cpp index ba51354ab..6e35a4b8b 100644 --- a/main.cpp +++ b/main.cpp @@ -398,7 +398,7 @@ void do_setup() { #endif #if defined(USE_SENSORS) - os.load_sensors(); + Sensor::load_all(); #endif pd.init(); // ProgramData init diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index d15dc6a88..eefe72efc 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -478,6 +478,8 @@ void server_change_stations_attrib(const OTF::Request &req, char header, unsigne * q?: station sequential bit field * p?: station special flag bit field * g?: sequential group id + * u?: master3 operation bit field + * v?: master4 operation bit field */ void server_change_stations(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; @@ -1874,8 +1876,8 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { uint8_t sensor_count = 0; Sensor *sensor; - for (size_t i = 0; i < MAX_SENSORS; i++) { - if (os.sensors[i].interval && (sensor = os.get_sensor(i))) { + for (size_t i = 0; i < os.nsensors; i++) { + if (os.sensors[i].interval && (sensor = Sensor::get(i))) { if (sensor_count) bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"uuid\":$D,\"idx\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), sensor->uuid, i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); sensor->emit_extra_json(&bfill); @@ -1905,33 +1907,59 @@ void server_json_sensors(OTF_PARAMS_DEF) handle_return(HTML_OK); } +/** + * Add or change a sensor + * Command: /csn?pw=xxx&[uuid=xxx|sid=xxx]&type=xxx&... + * + * pw: password + * uuid: sensor stable ID (1-65535; -1 to add new) + * sid: sensor positional index (0-based; -1 to add new) + * (uuid takes precedence if both are provided) + * type: sensor type (0: Ensemble, 1: ADS1115, 2: Weather) + * name: sensor name + * min/max/scale/offset: calibration values + * interval: sampling interval in minutes + * unit: sensor unit index + * flags: bitmask (bit 0: enable, bit 1: log) + * [Ensemble] children: semicolon separated list of "uuid,min,max,scale,offset;" + * [Ensemble] action: ensemble action index (0: Min, 1: Max, 2: Average, 3: Sum, 4: Product) + * [ADS1115] pin: pin number (1-16) + * [Weather] action: weather information index + */ void server_change_sensor(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) handle_return(HTML_DATA_MISSING); char *end; - long sid_param = strtol(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - + long sid = -1; bool is_new = false; - long sid; - if (sid_param == -1) { - // Find first empty slot for a new sensor - sid = -1; - for (long i = 0; i < MAX_SENSORS; i++) { - if (!os.sensors[i].interval) { sid = i; break; } + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uuid"), true)) { + long uuid_param = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (uuid_param == -1) { + is_new = true; + sid = os.nsensors; + } else { + if (uuid_param < 1 || uuid_param > 0xFFFF) handle_return(HTML_DATA_OUTOFBOUND); + sid = Sensor::find_index((uint16_t)uuid_param); + if (sid >= os.nsensors) handle_return(HTML_DATA_OUTOFBOUND); + } + } else if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + long sid_param = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (sid_param == -1) { + is_new = true; + sid = os.nsensors; + } else { + if (sid_param < 0 || sid_param >= os.nsensors) handle_return(HTML_DATA_OUTOFBOUND); + sid = sid_param; } - if (sid == -1) handle_return(HTML_DATA_OUTOFBOUND); - is_new = true; } else { - // sid_param is a UUID — look up the slot - if (sid_param < 1 || sid_param > 0xFFFF) handle_return(HTML_DATA_OUTOFBOUND); - uint8_t idx = os.find_sensor_index((uint16_t)sid_param); - if (idx >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); - sid = idx; + handle_return(HTML_DATA_MISSING); } + if (is_new && sid >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); uint32_t type_raw = strtol(tmp_buffer, &end, 10); @@ -1954,7 +1982,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { SensorType original_sensor_type = SensorType::MAX_VALUE; if (os.sensors[sid].interval) { - if ((sensor = os.get_sensor(sid))) { + if ((sensor = Sensor::get(sid))) { original_sensor_type = sensor->get_sensor_type(); strncpy(name, sensor->name, SENSOR_NAME_LEN); min = sensor->min; @@ -2024,7 +2052,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { EnsembleAction action = EnsembleAction::Min; if (sensor_type == original_sensor_type) { - if ((sensor = os.get_sensor(sid))) { + if ((sensor = Sensor::get(sid))) { EnsembleSensor* e = static_cast(sensor); for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { children[i] = e->children[i]; @@ -2076,7 +2104,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { uint32_t sensor_pin = 0; if (sensor_type == original_sensor_type) { - if ((sensor = os.get_sensor(sid))) { + if ((sensor = Sensor::get(sid))) { ADS1115Sensor* e = static_cast(sensor); sensor_index = e->sensor_index; sensor_pin = e->pin; @@ -2099,7 +2127,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { WeatherAction action = WeatherAction::MAX_VALUE; if (sensor_type == original_sensor_type) { - if ((sensor = os.get_sensor(sid))) { + if ((sensor = Sensor::get(sid))) { WeatherSensor* e = static_cast(sensor); action = e->action; } @@ -2134,13 +2162,19 @@ void server_change_sensor(OTF_PARAMS_DEF) { os.nvdata.last_sensor_uuid = new_uuid; os.nvdata_save(); result_sensor->uuid = new_uuid; - os.sensors[sid].uuid = new_uuid; + + if (!Sensor::add(result_sensor)) { + delete result_sensor; + handle_return(HTML_DATA_OUTOFBOUND); + } } else { result_sensor->uuid = os.sensors[sid].uuid; + if (!Sensor::modify(sid, result_sensor)) { + delete result_sensor; + handle_return(HTML_DATA_OUTOFBOUND); + } } - os.write_sensor(result_sensor, sid); - delete result_sensor; handle_return(HTML_SUCCESS); @@ -2148,34 +2182,53 @@ void server_change_sensor(OTF_PARAMS_DEF) { /** * Delete a sensor - * Command: /dsn?pw=xxx&sid=xxx + * Command: /dsn?pw=xxx&[uuid=xxx|sid=xxx] * - * pw: password - * sid:staiton index (-1 will delete all programs) + * pw: password + * uuid: sensor stable ID (1-65535; -1 to delete all sensors) + * sid: sensor positional index (0-based; -1 to delete all sensors) + * (uuid takes precedence if both are provided) */ void server_delete_sensor(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) + + long idx = -1; + bool delete_all = false; + char *end; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uuid"), true)) { + long uuid_param = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (uuid_param == -1) { + delete_all = true; + } else { + if (uuid_param < 1 || uuid_param > 0xFFFF) handle_return(HTML_DATA_OUTOFBOUND); + idx = Sensor::find_index((uint16_t)uuid_param); + if (idx >= os.nsensors) handle_return(HTML_DATA_OUTOFBOUND); + } + } else if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + long sid_param = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (sid_param == -1) { + delete_all = true; + } else { + if (sid_param < 0 || sid_param >= os.nsensors) handle_return(HTML_DATA_OUTOFBOUND); + idx = sid_param; + } + } else { handle_return(HTML_DATA_MISSING); + } - int sid = atoi(tmp_buffer); - if (sid == -1) { + if (delete_all) { // Delete all sensors + os.nsensors = 0; + Sensor::save_count(); for (uint8_t i = 0; i < MAX_SENSORS; i++) { - if (os.sensors[i].interval) { - os.write_sensor(nullptr, i); - os.sensors[i].interval = 0; - os.sensors[i].uuid = 0; - } + os.sensors[i].interval = 0; + os.sensors[i].uuid = 0; } } else { - // sid is a UUID — look up the slot - if (sid < 1 || sid > 0xFFFF) handle_return(HTML_DATA_OUTOFBOUND); - uint8_t idx = os.find_sensor_index((uint16_t)sid); - if (idx >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); - os.write_sensor(nullptr, idx); - os.sensors[idx].interval = 0; - os.sensors[idx].uuid = 0; + if (!Sensor::del((uint8_t)idx)) handle_return(HTML_INTERNAL_ERROR); } handle_return(HTML_SUCCESS); @@ -2196,6 +2249,19 @@ uint8_t write_buf_log(uint32_t num, char *buf) { } } +/** + * Get sensor logs + * Command: /lsn?pw=xxx&[uuid=xxx|sid=xxx]&count=xxx&before=xxx&after=xxx&cursor=xxx + * + * pw: password + * uuid: sensor stable ID (1-65535; -1 for all) + * sid: sensor positional index (0-based; -1 for all) + * (uuid takes precedence if both are provided) + * count: max records to return + * before: timestamp before which records are returned + * after: timestamp after which records are returned + * cursor: number of records to skip + */ void server_log_sensor(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); @@ -2204,7 +2270,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { char *end; // Read central header - os_file_type hfile = os.open_sensor_log_header(FileOpenMode::Read); + os_file_type hfile = open_sensor_log_header(FileOpenMode::Read); if (!hfile) handle_return(HTML_INTERNAL_ERROR); SensorLogHeader hdr; file_read(hfile, &hdr, sizeof(hdr)); @@ -2245,10 +2311,19 @@ void server_log_sensor(OTF_PARAMS_DEF) { } long target_uuid = -1; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uuid"), true)) { target_uuid = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (target_uuid != -1 && (target_uuid < 1 || target_uuid > 0xFFFF)) handle_return(HTML_DATA_OUTOFBOUND); + } else if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + long sid_param = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (sid_param == -1) { + target_uuid = -1; + } else { + if (sid_param < 0 || sid_param >= os.nsensors) handle_return(HTML_DATA_OUTOFBOUND); + target_uuid = os.sensors[sid_param].uuid; + } } send_packet(OTF_PARAMS); @@ -2264,7 +2339,7 @@ void server_log_sensor(OTF_PARAMS_DEF) { for (uint16_t fi = 0; fi < total_files && count < max_count; fi++) { uint16_t file_no = (first_file + fi) % hdr.max_files; - os_file_type dfile = os.open_sensor_log(file_no, FileOpenMode::Read); + os_file_type dfile = open_sensor_log(file_no, FileOpenMode::Read); if (!dfile) continue; while (count < max_count) { @@ -2325,12 +2400,12 @@ void server_clear_sensor_log(OTF_PARAMS_DEF) { if (uuid == -1) { // Remove all log files — frees flash immediately; header recreated on next log_sensor call - os.remove_sensor_log(); + remove_sensor_log(); handle_return(HTML_SUCCESS); } // Per-sensor clear: read central header to know file layout - os_file_type hfile = os.open_sensor_log_header(FileOpenMode::Read); + os_file_type hfile = open_sensor_log_header(FileOpenMode::Read); if (!hfile) handle_return(HTML_INTERNAL_ERROR); SensorLogHeader hdr; file_read(hfile, &hdr, sizeof(hdr)); @@ -2344,7 +2419,7 @@ void server_clear_sensor_log(OTF_PARAMS_DEF) { SensorLogRecord rec; for (uint16_t fi = 0; fi < total_files; fi++) { uint16_t file_no = (first_file + fi) % hdr.max_files; - os_file_type dfile = os.open_sensor_log(file_no, FileOpenMode::ReadWrite); + os_file_type dfile = open_sensor_log(file_no, FileOpenMode::ReadWrite); if (!dfile) continue; uint32_t pos = 0; diff --git a/sensor.cpp b/sensor.cpp index 8f24c4b2d..13bd5437f 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -2,6 +2,7 @@ #include "OpenSprinkler.h" extern OpenSprinkler os; +extern char tmp_buffer[]; const char *enum_string(SensorUnitGroup group) { switch (group) { @@ -396,7 +397,7 @@ float EnsembleSensor::_get_raw_value() { uint8_t count = 0; for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - uint8_t sensor = os.find_sensor_index(this->children[i].uuid); + uint8_t sensor = Sensor::find_index(this->children[i].uuid); if (sensor < MAX_SENSORS && sensors[sensor].interval) { float value = sensors[sensor].value; value = (value * this->children[i].scale) + this->children[i].offset; @@ -511,7 +512,7 @@ SensorAdjustment::SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_c float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { - uint8_t idx = os.find_sensor_index(this->uuid); + uint8_t idx = Sensor::find_index(this->uuid); if (idx < MAX_SENSORS && sensors[idx].interval) { float value = sensors[idx].value; if (value <= this->points[0].x) return this->points[0].y; @@ -584,4 +585,277 @@ void SensorAdjustment::write(SensorAdjustment *adj, uint8_t index) { DEBUG_PRINT("Failed to open file: "); DEBUG_PRINTLN(SENADJ_FILENAME); } +} + +// --------------------------------------------------------------------------- +// Sensor file I/O +// --------------------------------------------------------------------------- + +Sensor *Sensor::parse(os_file_type file) { + static uint8_t sensor_scratchpad[sizeof(OpenSprinkler::SensorUnion)] __attribute__((aligned(4))); + static Sensor *active_sensor = nullptr; + + if (active_sensor != nullptr) { + active_sensor->~Sensor(); + active_sensor = nullptr; + } + uint32_t len = 0; + file_read(file, &len, sizeof(len)); + + if (len == 0 || len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) return nullptr; + + file_read(file, tmp_buffer, len); + file_seek(file, TMP_BUFFER_SIZE - sizeof(uint32_t) - len, FileSeekMode::Current); + + if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) return nullptr; + + SensorType sensor_type = static_cast(*tmp_buffer); + switch (sensor_type) { + case SensorType::Ensemble: + active_sensor = new (sensor_scratchpad) EnsembleSensor(os.sensors, (char*)tmp_buffer); + break; + case SensorType::ADS1115: + active_sensor = new (sensor_scratchpad) ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + break; + case SensorType::Weather: + active_sensor = new (sensor_scratchpad) WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); + break; + default: + return nullptr; + } + return active_sensor; +} + +Sensor *Sensor::get(uint8_t index) { + uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, pos, FileSeekMode::Set); + Sensor *result = Sensor::parse(file); + file_close(file); + return result; + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + return nullptr; + } +} + +void Sensor::write(Sensor *sensor, uint8_t index) { + uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; + uint32_t len = 0; + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); + if (file) { + if (sensor) { + len = sensor->serialize(tmp_buffer); + if (len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) { + len = TMP_BUFFER_SIZE - sizeof(uint32_t); + } + } + + file_seek(file, pos, FileSeekMode::Set); + file_write(file, &len, sizeof(len)); + if (sensor) file_write(file, tmp_buffer, len); + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } +} + +/** Load sensor count from sensor file */ +void Sensor::load_count() { + OpenSprinkler::nsensors = file_read_byte(SENSORS_FILENAME, 0); +} + +/** Save sensor count to sensor file */ +void Sensor::save_count() { + file_write_byte(SENSORS_FILENAME, 0, OpenSprinkler::nsensors); +} + +/** Add a sensor */ +unsigned char Sensor::add(Sensor *sensor) { + if (OpenSprinkler::nsensors >= MAX_SENSORS) return 0; + Sensor::write(sensor, OpenSprinkler::nsensors); + + // update memory + sensor_memory_t &m = OpenSprinkler::sensors[OpenSprinkler::nsensors]; + m.interval = sensor->interval; + m.flags = sensor->flags; + m.uuid = sensor->uuid; + m.next_update = 0; + m.value = sensor->get_initial_value(); + + OpenSprinkler::nsensors++; + Sensor::save_count(); + return 1; +} + +/** Modify a sensor */ +unsigned char Sensor::modify(uint8_t index, Sensor *sensor) { + if (index >= OpenSprinkler::nsensors) return 0; + Sensor::write(sensor, index); + + // update memory + sensor_memory_t &m = OpenSprinkler::sensors[index]; + m.interval = sensor->interval; + m.flags = sensor->flags; + m.uuid = sensor->uuid; + m.next_update = 0; + m.value = sensor->get_initial_value(); + + return 1; +} + +/** Delete a sensor */ +unsigned char Sensor::del(uint8_t index) { + if (index >= OpenSprinkler::nsensors) return 0; + if (OpenSprinkler::nsensors == 0) return 0; + + // erase by copying backward + for (uint8_t i = index; i < OpenSprinkler::nsensors - 1; i++) { + file_copy_block(SENSORS_FILENAME, 1 + (uint32_t)(i + 1) * TMP_BUFFER_SIZE, 1 + (uint32_t)i * TMP_BUFFER_SIZE, TMP_BUFFER_SIZE, tmp_buffer); + // also shift in-memory state + OpenSprinkler::sensors[i] = OpenSprinkler::sensors[i + 1]; + } + + OpenSprinkler::nsensors--; + Sensor::save_count(); + return 1; +} + +/** Load all sensors from file to memory */ +void Sensor::load_all() { + OpenSprinkler::lcd.clear(); + OpenSprinkler::lcd.setCursor(0, 0); + OpenSprinkler::lcd_print_pgm(PSTR("Init sensors...")); + + // 1. Check if the configuration file exists. + // If not, we need to initialize it with a 0 count. + if (!file_exists(SENSORS_FILENAME)) { + DEBUG_PRINTLN(F("Sensor files missing. Initializing...")); + OpenSprinkler::nsensors = 0; + Sensor::save_count(); + } else { + Sensor::load_count(); + } + + // 2. Proceed with existing load logic + Sensor *sensor; + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + // skip the first byte (count) + file_seek(file, 1, FileSeekMode::Set); + for (size_t i = 0; i < OpenSprinkler::nsensors; i++) { + if ((sensor = Sensor::parse(file))) { + sensor_memory_t &m = OpenSprinkler::sensors[i]; + m.interval = sensor->interval; + m.flags = sensor->flags; + m.uuid = sensor->uuid; + m.next_update = 0; + m.value = sensor->get_initial_value(); + } + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } +} + +/** Find sensor index by UUID */ +uint8_t Sensor::find_index(uint16_t uuid) { + if (uuid == SENSOR_UUID_NONE) return OpenSprinkler::nsensors; + for (uint8_t i = 0; i < OpenSprinkler::nsensors; i++) { + if (OpenSprinkler::sensors[i].uuid == uuid) return i; + } + return OpenSprinkler::nsensors; +} + +/** Sensor log performance test */ +void Sensor::test_log(uint32_t n_records) { + remove_sensor_log(); + + DEBUG_PRINTF("sensor log test: writing %lu records\n", (unsigned long)n_records); + uint32_t t0 = millis(); + + for (uint32_t i = 0; i < n_records; i++) { + if (OpenSprinkler::nsensors > 0) os.log_sensor((uint8_t)((i + 1) % OpenSprinkler::nsensors), (float)i / 1000.f); + } + + uint32_t write_ms = millis() - t0; + DEBUG_PRINTF("sensor log write: %lu ms total, %.2f ms/record\n", + (unsigned long)write_ms, + n_records ? (float)write_ms / n_records : 0.f); + + // Read pass: iterate all files in order (oldest to newest) + os_file_type hfile = open_sensor_log_header(FileOpenMode::Read); + if (!hfile) { + DEBUG_PRINTLN("sensor log test: cannot open header"); + return; + } + SensorLogHeader hdr = {}; + file_read(hfile, &hdr, sizeof(hdr)); + file_close(hfile); + if (hdr.magic != SENSOR_LOG_MAGIC || hdr.version != SENSOR_LOG_VERSION) { + DEBUG_PRINTLN("sensor log test: bad header"); + return; + } + + uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; + uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); + DEBUG_PRINTF("sensor log state: max_files=%u records_per_file=%u cur_file=%u wrapped=%u total_files=%u\n", + hdr.max_files, hdr.records_per_file, hdr.cur_file, hdr.wrapped, total_files); + + uint32_t tr = millis(); + uint32_t count = 0; + for (uint16_t fi = 0; fi < total_files; fi++) { + uint16_t file_no = (first_file + fi) % hdr.max_files; + os_file_type dfile = open_sensor_log(file_no, FileOpenMode::Read); + if (!dfile) continue; + SensorLogRecord rec; + while (file_read(dfile, &rec, sizeof(rec)) == (int)sizeof(rec)) count++; + file_close(dfile); + } + + uint32_t read_ms = millis() - tr; + DEBUG_PRINTF("sensor log read: %lu records in %lu ms (%.2f ms/record)\n", + (unsigned long)count, (unsigned long)read_ms, + count ? (float)read_ms / count : 0.f); +} + +// --------------------------------------------------------------------------- +// Sensor log file helpers +// --------------------------------------------------------------------------- + +void get_sensor_log_filename(char *buf, uint16_t file_no) { + snprintf(buf, 24, "%s%03u", SENSORS_LOG_FILENAME, file_no % 1000); +} + +os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode) { + char fname[24]; + get_sensor_log_filename(fname, file_no); + return file_open(fname, mode); +} + +os_file_type open_sensor_log_header(FileOpenMode mode) { + return file_open(SENSORS_LOG_HEADER_FILENAME, mode); +} + +void remove_sensor_log(int16_t file_no) { + char fname[24]; + if (file_no < 0) { + remove_file(SENSORS_LOG_HEADER_FILENAME); + for (uint16_t i = 0; i < SENSOR_LOG_MAX_FILES; i++) { + get_sensor_log_filename(fname, i); + remove_file(fname); + } + } else { + get_sensor_log_filename(fname, (uint16_t)file_no); + remove_file(fname); + } } \ No newline at end of file diff --git a/sensor.h b/sensor.h index b2b442179..7fc82142e 100644 --- a/sensor.h +++ b/sensor.h @@ -14,11 +14,7 @@ // No types from this file are involved in the onboard sensor logic. #include -#if defined(ARDUINO) -#include -#else #include "utils.h" -#endif #include "defines.h" #include "bfiller.h" @@ -124,6 +120,18 @@ class Sensor { float get_new_value(); uint32_t serialize(char *buf); + static Sensor *parse(os_file_type file); // statically allocated, do not delete + static Sensor *get(uint8_t index); // statically allocated, do not delete + static void write(Sensor *sensor, uint8_t index); + static void load_count(); + static void save_count(); + static unsigned char add(Sensor *sensor); + static unsigned char modify(uint8_t index, Sensor *sensor); // index is positional index + static unsigned char del(uint8_t index); // index is positional index + static void load_all(); + static uint8_t find_index(uint16_t uuid); + static void test_log(uint32_t n_records); + void virtual emit_extra_json(BufferFiller *bfill) = 0; uint32_t interval = 1; @@ -256,3 +264,9 @@ const char* get_sensor_unit_name(SensorUnit unit); const char* get_sensor_unit_short(SensorUnit unit); const SensorUnitGroup get_sensor_unit_group(SensorUnit unit); const uint32_t get_sensor_unit_index(SensorUnit unit); + +// Sensor log file helpers +void get_sensor_log_filename(char *buf, uint16_t file_no); +os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); +os_file_type open_sensor_log_header(FileOpenMode mode); +void remove_sensor_log(int16_t file_no = -1); // -1 removes header + all data files From 95212ef9b67a8f6773bd1bb745702b24bdf62a74 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 26 Apr 2026 21:53:47 -0400 Subject: [PATCH 096/115] fix sens.dat writing bug; add utility API to list and delete flash file on ESP8266 --- OpenSprinkler.cpp | 2 +- opensprinkler_server.cpp | 66 ++++++++++++++++++++++++++++++++++++++++ sensor.cpp | 44 ++++++++++++++++++++------- 3 files changed, 100 insertions(+), 12 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 82912a59e..bb580989e 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2155,7 +2155,7 @@ void OpenSprinkler::factory_reset() { file_write_byte(PROG_FILENAME, 0, 0); #if defined(USE_SENSORS) - // remove all sensor files, so they will be re-created in load_sensors() + // remove all sensor files, so they will be re-created during loading remove_file(SENSORS_FILENAME); remove_sensor_log(); remove_file(SENADJ_FILENAME); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index eefe72efc..8d0162df4 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2619,6 +2619,64 @@ void server_json_debug(OTF_PARAMS_DEF) { handle_return(HTML_OK); } +/** + * List all files + * Command: /lf?pw=xxx + * + * pw: password + * Returns a JSON array of [filename, size] + */ +#if defined(ESP8266) +void server_list_files(OTF_PARAMS_DEF) { + if(!process_password(OTF_PARAMS)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); + + bfill.emit_p(PSTR("{\"files\":[")); + bool first = true; + + // root + Dir dir = LittleFS.openDir("/"); + while (dir.next()) { + if (dir.fileName().indexOf('.') < 0) continue; + if (!first) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("[\"/$S\",$D]"), dir.fileName().c_str(), dir.fileSize()); + first = false; + if (available_ether_buffer() <= 0) send_packet(OTF_PARAMS); + } + // logs + dir = LittleFS.openDir("/logs/"); + while (dir.next()) { + if (!first) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("[\"/logs/$S\",$D]"), dir.fileName().c_str(), dir.fileSize()); + first = false; + if (available_ether_buffer() <= 0) send_packet(OTF_PARAMS); + } + + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} +#endif + +/** + * Delete a file + * Command: /df?pw=xxx&fn=filename + * + * pw: password + * fn: filename to delete + */ +#if defined(ESP8266) +void server_delete_file(OTF_PARAMS_DEF) { + if(!process_password(OTF_PARAMS)) return; + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("fn"), true)) + handle_return(HTML_DATA_MISSING); + + remove_file(tmp_buffer); + handle_return(HTML_SUCCESS); +} +#endif + /* // fill ESP8266 flash with some dummy files void server_fill_files(OTF_PARAMS_DEF) { @@ -2674,6 +2732,10 @@ const char *uris[] PROGMEM = { "ja", "pq", "db", +#if defined(ESP8266) + "lf", + "df", +#endif #if defined(USE_SENSORS) "jsn", "csn", @@ -2709,6 +2771,10 @@ URLHandler urls[] = { server_json_all, // ja server_pause_queue, // pq server_json_debug, // db +#if defined(ESP8266) + server_list_files, // lf + server_delete_file, // df +#endif #if defined(USE_SENSORS) server_json_sensors, // jsn server_change_sensor, // csn diff --git a/sensor.cpp b/sensor.cpp index 13bd5437f..e002cf537 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -646,19 +646,19 @@ void Sensor::write(Sensor *sensor, uint8_t index) { uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; uint32_t len = 0; + // Zero data area before serializing so the full TMP_BUFFER_SIZE slot is clean. + memset(tmp_buffer, 0, TMP_BUFFER_SIZE - sizeof(uint32_t)); + if (sensor) { + len = sensor->serialize(tmp_buffer); + if (len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) + len = TMP_BUFFER_SIZE - sizeof(uint32_t); + } + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); if (file) { - if (sensor) { - len = sensor->serialize(tmp_buffer); - if (len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) { - len = TMP_BUFFER_SIZE - sizeof(uint32_t); - } - } - file_seek(file, pos, FileSeekMode::Set); file_write(file, &len, sizeof(len)); - if (sensor) file_write(file, tmp_buffer, len); - + file_write(file, tmp_buffer, TMP_BUFFER_SIZE - sizeof(uint32_t)); // always write full slot file_close(file); } else { DEBUG_PRINT("Failed to open file: "); @@ -679,9 +679,31 @@ void Sensor::save_count() { /** Add a sensor */ unsigned char Sensor::add(Sensor *sensor) { if (OpenSprinkler::nsensors >= MAX_SENSORS) return 0; - Sensor::write(sensor, OpenSprinkler::nsensors); - // update memory + // Serialize into the data portion of tmp_buffer (after the 4-byte len header). + // Zero the area first so the full TMP_BUFFER_SIZE slot is clean when written. + uint32_t len = 0; + memset(tmp_buffer, 0, TMP_BUFFER_SIZE - sizeof(uint32_t)); + if (sensor) { + len = sensor->serialize(tmp_buffer); + if (len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) + len = TMP_BUFFER_SIZE - sizeof(uint32_t); + } + + // Append the new slot (always TMP_BUFFER_SIZE bytes) to the file. + // Using Append avoids seeking past EOF which fails on LittleFS. + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Append); + if (file) { + file_write(file, &len, sizeof(len)); + file_write(file, tmp_buffer, TMP_BUFFER_SIZE - sizeof(uint32_t)); + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + return 0; + } + + // update in-memory state sensor_memory_t &m = OpenSprinkler::sensors[OpenSprinkler::nsensors]; m.interval = sensor->interval; m.flags = sensor->flags; From 3d2782bfd4bc0ddc4fbf5116727032d0657a097d Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sun, 26 Apr 2026 23:55:16 -0400 Subject: [PATCH 097/115] fix bug in get_sensor_adjustment; fix where get_sensor_adjustment is called --- bfiller.h | 4 ++-- main.cpp | 24 ++++++++++++------------ opensprinkler_server.cpp | 2 +- sensor.cpp | 5 +++-- 4 files changed, 18 insertions(+), 17 deletions(-) diff --git a/bfiller.h b/bfiller.h index 9cb75ee35..4118b9654 100644 --- a/bfiller.h +++ b/bfiller.h @@ -45,8 +45,8 @@ class BufferFiller { snprintf((char*) ptr, len - position(), "%d", va_arg(ap, int)); break; case 'E': //Double - sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); - break; + snprintf((char*) ptr, len - position(), "%g", va_arg(ap, double)); + break; case 'L': // ultoa(va_arg(ap, uint32_t), (char*) ptr, 10); // TODO: check if there is a way to print uint32_t diff --git a/main.cpp b/main.cpp index 6e35a4b8b..c58c2d3c1 100644 --- a/main.cpp +++ b/main.cpp @@ -777,17 +777,18 @@ void do_loop() // check through all programs for(pid=0; pidget_adjustment_factor(os.sensors); - } - #endif bool will_delete = false; unsigned char runcount = prog.check_match(curr_time, &will_delete); if(runcount>0) { // program match found + float sensor_adj = 1.f; + #if defined(USE_SENSORS) + SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); + if (adj) { + sensor_adj = adj->get_adjustment_factor(os.sensors); + } + #endif + // check and process special program command if(process_special_program_command(prog.name, curr_time)) continue; @@ -824,13 +825,13 @@ void do_loop() continue; // TODO: compare with old code - uint32_t dur = (uint32_t)((float)prog.durations[sid] * sensor_adj); + uint32_t dur = prog.durations[sid]; // if station has non-zero water time and the station is not disabled if (dur && !(os.attrib_dis[bid]&(1<0) { dur = water_time_resolve(prog.durations[sid]); - dur = (uint32_t)((float)dur * sensor_adj); } - dur = dur * wl / 100; + dur = (uint32_t)(dur * wl / 100 * sensor_adj); if(dur>0 && !(os.attrib_dis[bid]&(1<uuid, i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); + bfill.emit_p(PSTR("{\"uuid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), sensor->uuid, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); sensor->emit_extra_json(&bfill); bfill.emit_p(PSTR("}")); sensor_count += 1; diff --git a/sensor.cpp b/sensor.cpp index e002cf537..35e2f09d1 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -521,18 +521,19 @@ float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { uint8_t i; for (i = 0; i < this->point_count - 1; i++) { - if (value >= this->points[i].x) { + if (value < this->points[i + 1].x) { break; } } sensor_adjustment_point_t left = this->points[i]; sensor_adjustment_point_t right = this->points[i + 1]; + if (right.x == left.x) return left.y; value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; - if (value < 0) return 0; + if (value < 0) value = 0; return value; } } From e4d147239da81849107a893cd167a97ab50327e5 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 27 Apr 2026 00:13:51 -0400 Subject: [PATCH 098/115] add rate limit to poll_sensors in main loop; add sensor_adjustment factor to notification channel --- main.cpp | 14 +++++++++++--- notifier.cpp | 12 +++++++----- notifier.h | 5 +++-- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/main.cpp b/main.cpp index c58c2d3c1..89fe76a8a 100644 --- a/main.cpp +++ b/main.cpp @@ -74,6 +74,7 @@ void manual_start_program(unsigned char, unsigned char, unsigned char); #define DHCP_CHECKLEASE_INTERVAL 3600L // DHCP check lease interval (in seconds) #define FLOWPOLL_INTERVAL 5 // flow poll interval (in milli-seconds) #define CURRPOLL_INTERVAL 20 // current poll interval (in milli-seconds) +#define SENSORPOLL_INTERVAL 5000 // sensor poll interval (in milli-seconds) // Define buffers: need them to be sufficiently large to cover string option reading char ether_buffer[ETHER_BUFFER_ALLOC_SIZE]; // ethernet buffer char tmp_buffer[TMP_BUFFER_ALLOC_SIZE]; // scratch buffer @@ -531,7 +532,14 @@ void do_loop() } #if defined(USE_SENSORS) - os.poll_sensors(); + { + static uint32_t sensorpoll_timeout = 0; + uint32_t tm = millis(); + if((long)(tm-sensorpoll_timeout) > 0) { + sensorpoll_timeout = tm + SENSORPOLL_INTERVAL; + os.poll_sensors(); + } + } #endif #if defined(ESP8266) @@ -853,7 +861,7 @@ void do_loop() }// if prog.durations[sid] }// for sid if(match_found) { - notif.add(NOTIFY_PROGRAM_SCHED, pid, prog.use_weather?wl:100); + notif.add(NOTIFY_PROGRAM_SCHED, pid, prog.use_weather?wl:100, 0, sensor_adj); } else { // program being skipped e.g. due to 0% watering level notif.add(NOTIFY_PROGRAM_SCHED, pid, -1, wt_restricted); @@ -1628,7 +1636,7 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo } #endif if(uwt) wl = os.iopts[IOPT_WATER_PERCENTAGE]; - notif.add(NOTIFY_PROGRAM_SCHED, pid-1, wl, 1); + notif.add(NOTIFY_PROGRAM_SCHED, pid-1, wl, 1, sensor_adj); // get station ordering from program name prog.gen_station_runorder(1, order); } diff --git a/notifier.cpp b/notifier.cpp index e9010312c..fc03fbd6e 100644 --- a/notifier.cpp +++ b/notifier.cpp @@ -59,12 +59,12 @@ void ip2string(char* str, size_t str_len, unsigned char ip[4]) { snprintf_P(str+strlen(str), str_len, PSTR("%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); } -bool NotifQueue::add(uint16_t t, uint32_t l, float f, uint8_t b) { +bool NotifQueue::add(uint16_t t, uint32_t l, float f, uint8_t b, float f2) { if (!is_notif_enabled(t)) { // if not subscribed to this type, return return false; } if(nqueuetype, node->lval, node->fval, node->bval); + push_message(node->type, node->lval, node->fval, node->bval, node->fval2); nqueue--; n--; DEBUG_PRINTF("NotifQueue::run (type %d) [%d|%d|%d]\n", node->type, nqueue, head, tail); @@ -99,7 +99,7 @@ bool NotifQueue::run(int n) { #define PUSH_TOPIC_LEN 120 #define PUSH_PAYLOAD_LEN TMP_BUFFER_SIZE -void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { +void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float fval2) { if (!is_notif_enabled(type)) { return; } @@ -345,6 +345,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { } else { strcat_P(payload, PSTR("{\"state\":1,\"wl\":")); snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR("%d"), (int)fval); + snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"sensor_adj\":%.4g"), fval2); } strcat_P(payload, PSTR("}")); } @@ -365,6 +366,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { } if(fval>0) { snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" with %d%% water level."), (int)fval); + snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" Sensor adjustment: %.4g."), fval2); } if(email_enabled) { email_message.subject += PSTR("program event"); } diff --git a/notifier.h b/notifier.h index bdc81e4a3..93c5fcb45 100644 --- a/notifier.h +++ b/notifier.h @@ -33,8 +33,9 @@ struct NotifNodeStruct { uint16_t type; uint32_t lval; float fval; + float fval2; uint8_t bval; - NotifNodeStruct(uint16_t t=0, uint32_t l=0, float f=0.f, uint8_t b=0) : type(t), lval(l), fval(f), bval(b) + NotifNodeStruct(uint16_t t=0, uint32_t l=0, float f=0.f, uint8_t b=0, float f2=0.f) : type(t), lval(l), fval(f), fval2(f2), bval(b) { } }; @@ -42,7 +43,7 @@ struct NotifNodeStruct { class NotifQueue { public: // Insert a new notification element - static bool add(uint16_t t, uint32_t l=0, float f=0.f, uint8_t b=0); + static bool add(uint16_t t, uint32_t l=0, float f=0.f, uint8_t b=0, float f2=0.f); // Clear all elements (i.e. empty the queue) static void clear(); // Run/Process elements. By default process 1 at a time. If n<=0, process all. From 09f43e682f9e533a1f9b2118eea886a85141ab93 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 27 Apr 2026 08:50:52 -0400 Subject: [PATCH 099/115] fix sensor add bug --- sensor.cpp | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/sensor.cpp b/sensor.cpp index 35e2f09d1..f95003aaf 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -681,28 +681,7 @@ void Sensor::save_count() { unsigned char Sensor::add(Sensor *sensor) { if (OpenSprinkler::nsensors >= MAX_SENSORS) return 0; - // Serialize into the data portion of tmp_buffer (after the 4-byte len header). - // Zero the area first so the full TMP_BUFFER_SIZE slot is clean when written. - uint32_t len = 0; - memset(tmp_buffer, 0, TMP_BUFFER_SIZE - sizeof(uint32_t)); - if (sensor) { - len = sensor->serialize(tmp_buffer); - if (len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) - len = TMP_BUFFER_SIZE - sizeof(uint32_t); - } - - // Append the new slot (always TMP_BUFFER_SIZE bytes) to the file. - // Using Append avoids seeking past EOF which fails on LittleFS. - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Append); - if (file) { - file_write(file, &len, sizeof(len)); - file_write(file, tmp_buffer, TMP_BUFFER_SIZE - sizeof(uint32_t)); - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - return 0; - } + Sensor::write(sensor, OpenSprinkler::nsensors); // update in-memory state sensor_memory_t &m = OpenSprinkler::sensors[OpenSprinkler::nsensors]; From 3b553982008e3574bcb3bda78f92a899f26ccc71 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Mon, 27 Apr 2026 11:23:58 -0400 Subject: [PATCH 100/115] fix sensor find_index bug; reformat sensor adj in /jp output --- notifier.cpp | 15 +++++++------- opensprinkler_server.cpp | 45 ++++++++++++++++++---------------------- sensor.cpp | 7 +++++-- 3 files changed, 32 insertions(+), 35 deletions(-) diff --git a/notifier.cpp b/notifier.cpp index fc03fbd6e..bb70c1283 100644 --- a/notifier.cpp +++ b/notifier.cpp @@ -147,7 +147,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float email_recipient= doc["recipient"]; } } - + #if defined(ESP8266) EMailSender::EMailMessage email_message; #else @@ -174,7 +174,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float topic[PUSH_TOPIC_LEN]=0; strcat(postval+strlen(postval), topic); strcat_P(postval, PSTR("], ")); - if(email_enabled) { + if(email_enabled) { strcat(topic, " "); email_message.subject = topic; // prefix the email subject with device name } @@ -264,7 +264,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float char *endptr; flow_gpm_alert_setpoint = strtod(station_name_last_five_chars, &endptr); if (endptr != station_name_last_five_chars) { - //station_name_last_five_chars was successfully converted to a number + //station_name_last_five_chars was successfully converted to a number //flow_last_gpm is actually collected and stored as pulses per minute, not gallons per minute // Alert Check - Compare flow_gpm_alert_setpoint with flow_last_gpm and enable flow_alert_flag if flow is above setpoint if ((flow_last_gpm*flowrate100/100.f) > flow_gpm_alert_setpoint) { @@ -327,7 +327,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float } } else { - //Do not send an alert. Flow was not above setpoint or setpoint not valid. + //Do not send an alert. Flow was not above setpoint or setpoint not valid. //Must force ifftt_enabled and email_enabled to false to prevent sending //Can not force os.mqtt.enabled() off, but it will not publish an mqtt message as topic\payload will be empty. ifttt_enabled=false; @@ -335,7 +335,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float } break; } - + case NOTIFY_PROGRAM_SCHED: if (os.mqtt.enabled()) { snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("program/%d"), lval); @@ -345,7 +345,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float } else { strcat_P(payload, PSTR("{\"state\":1,\"wl\":")); snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR("%d"), (int)fval); - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"sensor_adj\":%.4g"), fval2); + snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"snadj\":%.2f"), fval2*100.f); } strcat_P(payload, PSTR("}")); } @@ -365,8 +365,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float if(lval0) { - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" with %d%% water level."), (int)fval); - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" Sensor adjustment: %.4g."), fval2); + snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(". Water level: %d%%. Sensor adjustment: %.2f%%."), (int)fval, fval2*100.f); } if(email_enabled) { email_message.subject += PSTR("program event"); } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 6e430e4eb..4c8de7446 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1052,7 +1052,26 @@ void server_json_programs_main(OTF_PARAMS_DEF) { // program name strncpy(tmp_buffer, prog.name, PROGRAM_NAME_SIZE); tmp_buffer[PROGRAM_NAME_SIZE] = 0; // make sure the string ends - bfill.emit_p(PSTR("$S\",[$D,$D,$D]]"), tmp_buffer,prog.en_daterange,prog.daterange[0],prog.daterange[1]); + bfill.emit_p(PSTR("$S\",[$D,$D,$D],"), tmp_buffer,prog.en_daterange,prog.daterange[0],prog.daterange[1]); + // sensor adjustment embedded in each program entry + #if defined(USE_SENSORS) + { + SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); + if (adj) { + bfill.emit_p(PSTR("{\"flags\":$D,\"uuid\":$D,\"splits\":["), adj->flags, adj->uuid); + for (int j = 0; j < adj->point_count; j++) { + if (j) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); + } + bfill.emit_p(PSTR("]}")); + } else { + bfill.emit_p(PSTR("{}")); + } + } + #else + bfill.emit_p(PSTR("{}")); + #endif + bfill.emit_p(PSTR("]")); if(pid!=pd.nprograms-1) { bfill.emit_p(PSTR(",")); } @@ -1062,30 +1081,6 @@ void server_json_programs_main(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); } } - - #if defined(USE_SENSORS) - bfill.emit_p(PSTR("],\"adj\":[")); - uint8_t adj_count = 0; - - SensorAdjustment *adj; - for (size_t i = 0; i < pd.nprograms; i++) { - if ((adj = SensorAdjustment::read(i, pd.nprograms))) { - if (adj_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"uuid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->uuid, adj->point_count); - for (int j = 0; j < adj->point_count; j++) { - if (j) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); - } - bfill.emit_p(PSTR("]}")); - adj_count += 1; - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - } - } - #endif bfill.emit_p(PSTR("]}")); } diff --git a/sensor.cpp b/sensor.cpp index f95003aaf..fa246d82b 100644 --- a/sensor.cpp +++ b/sensor.cpp @@ -398,7 +398,7 @@ float EnsembleSensor::_get_raw_value() { for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { uint8_t sensor = Sensor::find_index(this->children[i].uuid); - if (sensor < MAX_SENSORS && sensors[sensor].interval) { + if (sensor < OpenSprinkler::nsensors && sensors[sensor].interval) { float value = sensors[sensor].value; value = (value * this->children[i].scale) + this->children[i].offset; if (value < this->children[i].min) value = this->children[i].min; @@ -725,6 +725,9 @@ unsigned char Sensor::del(uint8_t index) { } OpenSprinkler::nsensors--; + OpenSprinkler::sensors[OpenSprinkler::nsensors].interval = 0; + OpenSprinkler::sensors[OpenSprinkler::nsensors].uuid = 0; + Sensor::save_count(); return 1; } @@ -775,7 +778,7 @@ uint8_t Sensor::find_index(uint16_t uuid) { for (uint8_t i = 0; i < OpenSprinkler::nsensors; i++) { if (OpenSprinkler::sensors[i].uuid == uuid) return i; } - return OpenSprinkler::nsensors; + return MAX_SENSORS; } /** Sensor log performance test */ From 407de9f87a22ae2435133c8005397a676bb17e26 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Tue, 28 Apr 2026 10:50:22 -0400 Subject: [PATCH 101/115] update sensor log endpoint names to make them consistent with the other naming conventions --- opensprinkler_server.cpp | 43 +++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 4c8de7446..45e9905c7 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1956,7 +1956,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (is_new && sid >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); - + uint32_t type_raw = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (type_raw >= (uint32_t)SensorType::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); @@ -1974,7 +1974,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { char name[SENSOR_NAME_LEN]; snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); - + SensorType original_sensor_type = SensorType::MAX_VALUE; if (os.sensors[sid].interval) { if ((sensor = Sensor::get(sid))) { @@ -2083,7 +2083,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { children_count = i; } - + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { uint32_t action_raw = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); @@ -2186,7 +2186,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { */ void server_delete_sensor(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - + long idx = -1; bool delete_all = false; char *end; @@ -2246,7 +2246,7 @@ uint8_t write_buf_log(uint32_t num, char *buf) { /** * Get sensor logs - * Command: /lsn?pw=xxx&[uuid=xxx|sid=xxx]&count=xxx&before=xxx&after=xxx&cursor=xxx + * Command: /jsl?pw=xxx&[uuid=xxx|sid=xxx]&count=xxx&before=xxx&after=xxx&cursor=xxx * * pw: password * uuid: sensor stable ID (1-65535; -1 for all) @@ -2257,10 +2257,9 @@ uint8_t write_buf_log(uint32_t num, char *buf) { * after: timestamp after which records are returned * cursor: number of records to skip */ -void server_log_sensor(OTF_PARAMS_DEF) { +void server_json_sensor_log(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); - print_header(OTF_PARAMS); char *end; @@ -2277,9 +2276,14 @@ void server_log_sensor(OTF_PARAMS_DEF) { uint32_t max_count = 100; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { - max_count = strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (max_count > total_capacity) handle_return(HTML_DATA_OUTOFBOUND); + if (strcmp(tmp_buffer, "max") == 0 || strcmp(tmp_buffer, "all") == 0) { + max_count = total_capacity; + } else { + max_count = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (max_count == 0) handle_return(HTML_DATA_OUTOFBOUND); + if (max_count > total_capacity) max_count = total_capacity; + } } // cursor = flat sequential index from oldest record to skip before emitting @@ -2321,7 +2325,8 @@ void server_log_sensor(OTF_PARAMS_DEF) { } } - send_packet(OTF_PARAMS); + print_header(OTF_PARAMS); + res.writeBodyData("", 0); // Iterate files from oldest to newest uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; @@ -2382,11 +2387,11 @@ void server_log_sensor(OTF_PARAMS_DEF) { } -void server_clear_sensor_log(OTF_PARAMS_DEF) { +void server_delete_sensor_log(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; long uuid = -1; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uuid"), true)) { uuid = atol(tmp_buffer); if (uuid != -1 && (uuid < 1 || uuid > 0xFFFF)) handle_return(HTML_DATA_OUTOFBOUND); } else { @@ -2489,7 +2494,7 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { } } - + bfill.emit_p(PSTR("],\"enums\":{")); bfill_enum_values(PSTR("SensorUnitGroup")); bfill.emit_p(PSTR(",")); @@ -2697,8 +2702,6 @@ void server_fill_files(OTF_PARAMS_DEF) { typedef void (*URLHandler)(OTF_PARAMS_DEF); /* Server function urls - * To save RAM space, each GET command keyword is exactly - * 2 characters long, with no ending 0 * The order must exactly match the order of the * handler functions below */ @@ -2735,8 +2738,8 @@ const char *uris[] PROGMEM = { "jsn", "csn", "dsn", - "lsn", - "csl", + "jsl", + "dsl", "jsd", #endif }; @@ -2774,8 +2777,8 @@ URLHandler urls[] = { server_json_sensors, // jsn server_change_sensor, // csn server_delete_sensor, // dsn - server_log_sensor, // lsn - server_clear_sensor_log, // csl + server_json_sensor_log, // jsl + server_delete_sensor_log, // dsl server_json_sen_desc, // jsd #endif }; From 26f9881179f2dcd866381c1b5d0c84f909e583af Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Tue, 28 Apr 2026 13:44:36 -0400 Subject: [PATCH 102/115] change bfill to use res.write callback, this eliminates the need to hold the output content in ether_buffer first, instead, it directly outputs to otf buffer, which already handles content streaming. as a result, there is no need to check available_etherbuffer_size, and we can use smaller ether_buffer in the future --- bfiller.h | 39 ++++++++++-- defines.h | 12 ++-- main.cpp | 2 +- opensprinkler_server.cpp | 126 ++++++++++++--------------------------- 4 files changed, 78 insertions(+), 101 deletions(-) diff --git a/bfiller.h b/bfiller.h index 4118b9654..e83c8ad5d 100644 --- a/bfiller.h +++ b/bfiller.h @@ -11,21 +11,34 @@ #include #endif +typedef void (*bfill_flush_fn)(const char *buf, size_t len); + class BufferFiller { char *start; //!< Pointer to start of buffer char *ptr; //!< Pointer to cursor position size_t len; + bfill_flush_fn flush_fn = nullptr; + + void mid_flush() { + if (flush_fn && ptr > start) { + flush_fn(start, (size_t)(ptr - start)); + ptr = start; + *ptr = 0; + } + } public: BufferFiller () {} BufferFiller (char *buf, size_t buffer_len) { start = buf; ptr = buf; len = buffer_len; + flush_fn = nullptr; } char* buffer () const { return start; } size_t length () const { return len; } unsigned int position () const { return ptr - start; } + void set_flush(bfill_flush_fn fn) { flush_fn = fn; } void emit_p(PGM_P fmt, ...) { va_list ap; @@ -35,6 +48,7 @@ class BufferFiller { if (c == 0) break; if (c != '$') { + if ((size_t)(ptr - start) >= len - 1) mid_flush(); *ptr++ = c; continue; } @@ -52,8 +66,20 @@ class BufferFiller { // TODO: check if there is a way to print uint32_t snprintf((char*) ptr, len - position(), "%" PRIu32, va_arg(ap, uint32_t)); break; - case 'S': - strcpy((char*) ptr, va_arg(ap, const char*)); + case 'S': { + const char *s = va_arg(ap, const char*); + size_t slen = strlen(s); + if (flush_fn && (size_t)(ptr - start) + slen + 1 > len) { + mid_flush(); + if (slen >= len) { + // string larger than entire buffer: stream directly + flush_fn(s, slen); + *ptr = 0; + break; + } + } + strcpy((char*) ptr, s); + } break; case 'X': { char d = va_arg(ap, int); @@ -64,12 +90,16 @@ class BufferFiller { case 'F': { PGM_P s = va_arg(ap, PGM_P); char d; - while ((d = pgm_read_byte(s++)) != 0) - *ptr++ = d; + while ((d = pgm_read_byte(s++)) != 0) { + if ((size_t)(ptr - start) >= len - 1) mid_flush(); + *ptr++ = d; + } continue; } case 'O': { uint16_t oid = va_arg(ap, int); + if (flush_fn && (size_t)(ptr - start) + MAX_SOPTS_SIZE + 1 > len) + mid_flush(); file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); } break; @@ -81,5 +111,6 @@ class BufferFiller { } *(ptr)=0; va_end(ap); + mid_flush(); } }; diff --git a/defines.h b/defines.h index d057a5b3d..3dd92a1a8 100755 --- a/defines.h +++ b/defines.h @@ -360,7 +360,7 @@ enum { #define PIN_LATCH_VOLT_SENSE A0 // latch voltage sensing pin #define PIN_FREE_LIST {} // no free GPIO pin at the moment #define ETHER_BUFFER_SIZE 2048 - #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+(ETHER_BUFFER_SIZE/2) // allocate extra space to allow overflow when needed + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+32 // allocate slightly larger buffer in case of overflow #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above #define ETHER_SPI_CLOCK 10000000L // SPI clock for Ethernet (e.g. 10MHz) @@ -450,9 +450,8 @@ enum { #define PIN_BUTTON_3 10 // button 3 #define PIN_FREE_LIST {5,6,7,8,9,11,12,13,16,19,20,21,23,25,26} // free GPIO pins - #define ETHER_BUFFER_SIZE 16384 - #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+(ETHER_BUFFER_SIZE/2) // allocate extra space to allow overflow when needed - + #define ETHER_BUFFER_SIZE 8192 + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE #define SDA 0 #define SCL 0 @@ -476,9 +475,8 @@ enum { #define PIN_SENSOR2 0 #define PIN_RFTX 0 #define PIN_FREE_LIST {} - #define ETHER_BUFFER_SIZE 16384 - #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+(ETHER_BUFFER_SIZE/2) // allocate extra space to allow overflow when needed - + #define ETHER_BUFFER_SIZE 8192 // HTTP client send/receive (weather, notifier, remote station) + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE #endif diff --git a/main.cpp b/main.cpp index 89fe76a8a..ad9069aeb 100644 --- a/main.cpp +++ b/main.cpp @@ -76,7 +76,7 @@ void manual_start_program(unsigned char, unsigned char, unsigned char); #define CURRPOLL_INTERVAL 20 // current poll interval (in milli-seconds) #define SENSORPOLL_INTERVAL 5000 // sensor poll interval (in milli-seconds) // Define buffers: need them to be sufficiently large to cover string option reading -char ether_buffer[ETHER_BUFFER_ALLOC_SIZE]; // ethernet buffer +char ether_buffer[ETHER_BUFFER_ALLOC_SIZE]; // HTTP client send/receive buffer char tmp_buffer[TMP_BUFFER_ALLOC_SIZE]; // scratch buffer // ====== Object defines ====== diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 45e9905c7..3687dd662 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -35,7 +35,7 @@ extern OTF::OpenThingsFramework *otf; #define OTF_PARAMS_DEF const OTF::Request &req,OTF::Response &res #define OTF_PARAMS req,res #define FKV_SOURCE req -#define handle_return(x) {if(x==HTML_OK) res.writeBodyData(ether_buffer, strlen(ether_buffer)); else otf_send_result(req,res,x); return;} +#define handle_return(x) {if(x!=HTML_OK) otf_send_result(req,res,x); return;} #if defined(ESP8266) #include @@ -51,17 +51,24 @@ extern OTF::OpenThingsFramework *otf; #include "etherport.h" #endif -extern char ether_buffer[]; extern char tmp_buffer[]; +extern char ether_buffer[]; extern OpenSprinkler os; extern ProgramData pd; extern uint32_t flow_count; +static OTF::Response *current_res = nullptr; BufferFiller bfill; -/* Check available space (number of bytes) in the Ethernet buffer */ -int available_ether_buffer() { - return ETHER_BUFFER_SIZE - (int)bfill.position(); +static void bfill_flush(const char *buf, size_t len) { + if (current_res && len > 0) + current_res->writeBodyData(buf, len); +} + +void begin_response(OTF::Response &res) { + current_res = &res; + bfill = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE); + bfill.set_flush(bfill_flush); } // Define return error code @@ -164,15 +171,6 @@ unsigned char findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const ch return(i); } -void rewind_ether_buffer() { - bfill = BufferFiller(ether_buffer, ETHER_BUFFER_ALLOC_SIZE); - ether_buffer[0] = 0; -} - -void send_packet(OTF_PARAMS_DEF) { - res.writeBodyData(ether_buffer, strlen(ether_buffer)); - rewind_ether_buffer(); -} void print_header(OTF_PARAMS_DEF, bool isJson=true, int len=0) { res.writeStatus(200, F("OK")); @@ -340,10 +338,9 @@ boolean process_password(OTF_PARAMS_DEF, boolean fwv_on_fail=false) { /* if fwv_on_fail is true, output fwv if password check has failed */ if(fwv_on_fail) { - rewind_ether_buffer(); + print_header(OTF_PARAMS, true); + begin_response(res); bfill.emit_p(PSTR("{\"$F\":$D}"), iopt_json_names+0, os.iopts[0]); - print_header(OTF_PARAMS,true,strlen(ether_buffer)); - res.writeBodyChunk((char *)"%s",ether_buffer); } else { otf_send_result(OTF_PARAMS, HTML_UNAUTHORIZED); } @@ -394,9 +391,6 @@ void server_json_stations_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"$S\""), tmp_buffer); if(sid!=os.nstations-1) bfill.emit_p(PSTR(",")); - if (available_ether_buffer() <=0 ) { - send_packet(OTF_PARAMS); - } } bfill.emit_p(PSTR("],\"maxlen\":$D}"), STATION_NAME_SIZE); } @@ -404,7 +398,7 @@ void server_json_stations_main(OTF_PARAMS_DEF) { /** Output stations data */ void server_json_stations(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{")); @@ -415,7 +409,7 @@ void server_json_stations(OTF_PARAMS_DEF) { /** Output station special attribute */ void server_json_station_special(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); unsigned char sid; @@ -431,9 +425,6 @@ void server_json_station_special(OTF_PARAMS_DEF) { else {comma=1;} bfill.emit_p(PSTR("\"$D\":{\"st\":$D,\"sd\":\"$S\"}"), sid, data->type, data->sped); } - if (available_ether_buffer() <=0 ) { - send_packet(OTF_PARAMS); - } } bfill.emit_p(PSTR("}")); handle_return(HTML_OK); @@ -1018,7 +1009,7 @@ void server_json_options_main() { /** Output Options */ void server_json_options(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS,true)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{")); server_json_options_main(); @@ -1075,11 +1066,6 @@ void server_json_programs_main(OTF_PARAMS_DEF) { if(pid!=pd.nprograms-1) { bfill.emit_p(PSTR(",")); } - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } } bfill.emit_p(PSTR("]}")); } @@ -1087,7 +1073,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { /** Output program data */ void server_json_programs(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{")); server_json_programs_main(OTF_PARAMS); @@ -1096,8 +1082,8 @@ void server_json_programs(OTF_PARAMS_DEF) { /** Output script url form */ void server_view_scripturl(OTF_PARAMS_DEF) { - rewind_ether_buffer(); - print_header(OTF_PARAMS,false,strlen(ether_buffer)); + begin_response(res); + print_header(OTF_PARAMS, false); //bfill.emit_p(PSTR("
UI Source:
JavaScript:
Default:$S
Weather:
Default:$S
Password:
"), bfill.emit_p(PSTR(R"(
@@ -1197,11 +1183,6 @@ void server_json_controller_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("0],\"ps\":[")); // print ps for(sid=0;sid$F"), @@ -1367,8 +1348,8 @@ void server_change_scripturl(OTF_PARAMS_DEF) { string_remove_space(tmp_buffer); os.sopt_save(SOPT_WEATHERURL, tmp_buffer); } - rewind_ether_buffer(); - print_header(OTF_PARAMS,false,strlen(ether_buffer)); + begin_response(res); + print_header(OTF_PARAMS, false); bfill.emit_p(PSTR("$F"), htmlReturnHome); handle_return(HTML_OK); } @@ -1587,7 +1568,7 @@ void server_json_status_main() { void server_json_status(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{")); @@ -1745,7 +1726,7 @@ void server_json_log(OTF_PARAMS_DEF) { // as the log data can be large, we will use ESP8266's sendContent function to // send multiple packets of data, instead of the standard way of using send(). - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("[")); @@ -1804,11 +1785,6 @@ void server_json_log(OTF_PARAMS_DEF) { if (comma) bfill.emit_p(PSTR(",")); else {comma=1;} bfill.emit_p(PSTR("$S"), tmp_buffer); - // if the available ether buffer size is getting small - // push out a packet - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } } } @@ -1878,11 +1854,6 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { sensor->emit_extra_json(&bfill); bfill.emit_p(PSTR("}")); sensor_count += 1; - // push out a packet if available - // buffer size is getting small - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } } } @@ -1894,7 +1865,7 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { void server_json_sensors(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{")); @@ -2259,7 +2230,7 @@ uint8_t write_buf_log(uint32_t num, char *buf) { */ void server_json_sensor_log(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); + begin_response(res); char *end; @@ -2326,7 +2297,7 @@ void server_json_sensor_log(OTF_PARAMS_DEF) { } print_header(OTF_PARAMS); - res.writeBodyData("", 0); + res.writeBodyData("", 0); // ends HTTP headers; body records follow via res.write() // Iterate files from oldest to newest uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; @@ -2391,8 +2362,10 @@ void server_delete_sensor_log(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; long uuid = -1; + char *end; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uuid"), true)) { - uuid = atol(tmp_buffer); + uuid = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); if (uuid != -1 && (uuid < 1 || uuid > 0xFFFF)) handle_return(HTML_DATA_OUTOFBOUND); } else { handle_return(HTML_DATA_MISSING); @@ -2479,9 +2452,6 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { case SensorType::MAX_VALUE: break; } - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } } bfill.emit_p(PSTR("],\"units\":[")); @@ -2489,12 +2459,8 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { if (i) bfill.emit_p(PSTR(",")); SensorUnit unit = static_cast(i); bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":$D,\"index\":$D,\"value\":$D}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit), i); - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } } - bfill.emit_p(PSTR("],\"enums\":{")); bfill_enum_values(PSTR("SensorUnitGroup")); bfill.emit_p(PSTR(",")); @@ -2503,30 +2469,18 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill_enum_values(PSTR("WeatherAction")); bfill.emit_p(PSTR("}")); - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"5000\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]]")); - if (available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - bfill.emit_p(PSTR("}")); } void server_json_sen_desc(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{")); @@ -2538,21 +2492,17 @@ void server_json_sen_desc(OTF_PARAMS_DEF) /** Output all JSON data, including jc, jp, jo, js, jn */ void server_json_all(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS,true)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{\"settings\":{")); server_json_controller_main(OTF_PARAMS); - send_packet(OTF_PARAMS); bfill.emit_p(PSTR(",\"programs\":{")); server_json_programs_main(OTF_PARAMS); - send_packet(OTF_PARAMS); bfill.emit_p(PSTR(",\"options\":{")); server_json_options_main(); - send_packet(OTF_PARAMS); bfill.emit_p(PSTR(",\"status\":{")); server_json_status_main(); - send_packet(OTF_PARAMS); bfill.emit_p(PSTR(",\"stations\":{")); server_json_stations_main(OTF_PARAMS); #if defined(USE_SENSORS) @@ -2581,7 +2531,7 @@ static uint32_t freeHeap() { #endif void server_json_debug(OTF_PARAMS_DEF) { - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{\"date\":\"$S\",\"time\":\"$S\",\"heap\":$L"), __DATE__, __TIME__, @@ -2629,7 +2579,7 @@ void server_json_debug(OTF_PARAMS_DEF) { #if defined(ESP8266) void server_list_files(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; - rewind_ether_buffer(); + begin_response(res); print_header(OTF_PARAMS); bfill.emit_p(PSTR("{\"files\":[")); @@ -2642,7 +2592,6 @@ void server_list_files(OTF_PARAMS_DEF) { if (!first) bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("[\"/$S\",$D]"), dir.fileName().c_str(), dir.fileSize()); first = false; - if (available_ether_buffer() <= 0) send_packet(OTF_PARAMS); } // logs dir = LittleFS.openDir("/logs/"); @@ -2650,7 +2599,6 @@ void server_list_files(OTF_PARAMS_DEF) { if (!first) bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("[\"/logs/$S\",$D]"), dir.fileName().c_str(), dir.fileSize()); first = false; - if (available_ether_buffer() <= 0) send_packet(OTF_PARAMS); } bfill.emit_p(PSTR("]}")); From 4b302d4fe1d20e5c3e2b7934a2b9fa2b4ba8a2ed Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Tue, 28 Apr 2026 21:00:03 -0400 Subject: [PATCH 103/115] support multiple formats of /jsl output, in particular, it support a binary dump which the UI can utilize to minimize log data size --- defines.h | 4 +- opensprinkler_server.cpp | 97 +++++++++++++++++++++++----------------- 2 files changed, 58 insertions(+), 43 deletions(-) diff --git a/defines.h b/defines.h index 3dd92a1a8..22200ffc2 100755 --- a/defines.h +++ b/defines.h @@ -359,8 +359,8 @@ enum { #define PIN_CURR_SENSE A0 // current sensing pin #define PIN_LATCH_VOLT_SENSE A0 // latch voltage sensing pin #define PIN_FREE_LIST {} // no free GPIO pin at the moment - #define ETHER_BUFFER_SIZE 2048 - #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE+32 // allocate slightly larger buffer in case of overflow + #define ETHER_BUFFER_SIZE 1024 + #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above #define ETHER_SPI_CLOCK 10000000L // SPI clock for Ethernet (e.g. 10MHz) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 3687dd662..e6cb564d2 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -172,9 +172,16 @@ unsigned char findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const ch } -void print_header(OTF_PARAMS_DEF, bool isJson=true, int len=0) { +enum ContentType { CT_JSON, CT_HTML, CT_CSV, CT_BINARY }; + +void print_header(OTF_PARAMS_DEF, ContentType ct=CT_JSON, int len=0) { res.writeStatus(200, F("OK")); - res.writeHeader(F("Content-Type"), isJson?F("application/json"):F("text/html")); + switch (ct) { + case CT_JSON: res.writeHeader(F("Content-Type"), F("application/json")); break; + case CT_HTML: res.writeHeader(F("Content-Type"), F("text/html")); break; + case CT_CSV: res.writeHeader(F("Content-Type"), F("text/csv")); break; + case CT_BINARY: res.writeHeader(F("Content-Type"), F("application/octet-stream")); break; + } if(len>0) res.writeHeader(F("Content-Length"), len); res.writeHeader(F("Access-Control-Allow-Origin"), F("*")); @@ -218,7 +225,7 @@ void otf_send_result(OTF_PARAMS_DEF, unsigned char code, const char *item = NULL json += item; json += F("\""); json += F("}"); - print_header(OTF_PARAMS, true, json.length()); + print_header(OTF_PARAMS, CT_JSON, json.length()); res.writeBodyChunk((char *)"%s",json.c_str()); } @@ -260,7 +267,7 @@ void on_ap_home(OTF_PARAMS_DEF) { void on_ap_scan(OTF_PARAMS_DEF) { if(os.get_wifi_mode()!=WIFI_MODE_AP) return; - print_header(OTF_PARAMS, true, scanned_ssids.length()); + print_header(OTF_PARAMS, CT_JSON, scanned_ssids.length()); res.writeBodyChunk((char *)"%s",scanned_ssids.c_str()); } @@ -308,7 +315,7 @@ void on_ap_try_connect(OTF_PARAMS_DEF) { json += F("\"ip\":"); json += (WiFi.status() == WL_CONNECTED) ? (uint32_t)WiFi.localIP() : 0; json += F("}"); - print_header(OTF_PARAMS,true,json.length()); + print_header(OTF_PARAMS, CT_JSON, json.length()); res.writeBodyChunk((char *)"%s",json.c_str()); if(WiFi.status() == WL_CONNECTED && WiFi.localIP()) { os.iopts[IOPT_WIFI_MODE] = WIFI_MODE_STA; @@ -338,7 +345,7 @@ boolean process_password(OTF_PARAMS_DEF, boolean fwv_on_fail=false) { /* if fwv_on_fail is true, output fwv if password check has failed */ if(fwv_on_fail) { - print_header(OTF_PARAMS, true); + print_header(OTF_PARAMS); begin_response(res); bfill.emit_p(PSTR("{\"$F\":$D}"), iopt_json_names+0, os.iopts[0]); } else { @@ -1083,7 +1090,7 @@ void server_json_programs(OTF_PARAMS_DEF) { /** Output script url form */ void server_view_scripturl(OTF_PARAMS_DEF) { begin_response(res); - print_header(OTF_PARAMS, false); + print_header(OTF_PARAMS, CT_HTML); //bfill.emit_p(PSTR("
UI Source:
JavaScript:
Default:$S
Weather:
Default:$S
Password:
"), bfill.emit_p(PSTR(R"(
@@ -1225,7 +1232,7 @@ void server_json_controller(OTF_PARAMS_DEF) { void server_home(OTF_PARAMS_DEF) { begin_response(res); - print_header(OTF_PARAMS, false); + print_header(OTF_PARAMS, CT_HTML); bfill.emit_p(PSTR("$F"), @@ -1349,7 +1356,7 @@ void server_change_scripturl(OTF_PARAMS_DEF) { os.sopt_save(SOPT_WEATHERURL, tmp_buffer); } begin_response(res); - print_header(OTF_PARAMS, false); + print_header(OTF_PARAMS, CT_HTML); bfill.emit_p(PSTR("$F"), htmlReturnHome); handle_return(HTML_OK); } @@ -2217,7 +2224,7 @@ uint8_t write_buf_log(uint32_t num, char *buf) { /** * Get sensor logs - * Command: /jsl?pw=xxx&[uuid=xxx|sid=xxx]&count=xxx&before=xxx&after=xxx&cursor=xxx + * Command: /jsl?pw=xxx&[uuid=xxx|sid=xxx]&count=xxx&before=xxx&after=xxx&cursor=xxx&fmt=xxx * * pw: password * uuid: sensor stable ID (1-65535; -1 for all) @@ -2227,6 +2234,10 @@ uint8_t write_buf_log(uint32_t num, char *buf) { * before: timestamp before which records are returned * after: timestamp after which records are returned * cursor: number of records to skip + * fmt: output format: json (default), csv, binary + * json: [[uuid,ts,value],...] — JSON array of arrays + * csv: uuid,timestamp,value\n with header row; downloads as sensor_log.csv + * binary: packed SensorLogRecord structs (uint32 ts, float val, uint16 uuid) */ void server_json_sensor_log(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; @@ -2296,16 +2307,28 @@ void server_json_sensor_log(OTF_PARAMS_DEF) { } } - print_header(OTF_PARAMS); - res.writeBodyData("", 0); // ends HTTP headers; body records follow via res.write() + enum LogFmt { FMT_JSON, FMT_CSV, FMT_BINARY } logfmt = FMT_JSON; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("fmt"), true)) { + if (strcmp(tmp_buffer, "csv") == 0) logfmt = FMT_CSV; + else if (strcmp(tmp_buffer, "binary") == 0) logfmt = FMT_BINARY; + else if (strcmp(tmp_buffer, "json") != 0) handle_return(HTML_DATA_FORMATERROR); + } + + ContentType ct = (logfmt == FMT_BINARY) ? CT_BINARY : (logfmt == FMT_CSV) ? CT_CSV : CT_JSON; + print_header(OTF_PARAMS, ct); + if (logfmt == FMT_CSV) + res.writeHeader(F("Content-Disposition"), F("attachment; filename=\"sensor_log.csv\"")); + res.writeBodyData("", 0); + + if (logfmt == FMT_JSON) res.write("[", 1); + if (logfmt == FMT_CSV) res.write("uuid,timestamp,value\n", 21); // Iterate files from oldest to newest uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); SensorLogRecord rec; - char print_buf[24] = "0000,00000000,00000000\n"; - uint32_t flat_idx = 0; // sequential record counter across all files + uint32_t flat_idx = 0; uint32_t count = 0; for (uint16_t fi = 0; fi < total_files && count < max_count; fi++) { @@ -2322,38 +2345,30 @@ void server_json_sensor_log(OTF_PARAMS_DEF) { if (target_uuid > -1 && rec.uuid != (uint16_t)target_uuid) continue; if (rec.timestamp > before || rec.timestamp < after) continue; - uint32_t raw_value; - memcpy(&raw_value, &rec.value, sizeof(raw_value)); - - print_buf[0] = dec2hexchar((rec.uuid >> 12) & 0xF); - print_buf[1] = dec2hexchar((rec.uuid >> 8) & 0xF); - print_buf[2] = dec2hexchar((rec.uuid >> 4) & 0xF); - print_buf[3] = dec2hexchar(rec.uuid & 0xF); - // print_buf[4] = ',' - print_buf[5] = dec2hexchar((rec.timestamp >> 28) & 0xF); - print_buf[6] = dec2hexchar((rec.timestamp >> 24) & 0xF); - print_buf[7] = dec2hexchar((rec.timestamp >> 20) & 0xF); - print_buf[8] = dec2hexchar((rec.timestamp >> 16) & 0xF); - print_buf[9] = dec2hexchar((rec.timestamp >> 12) & 0xF); - print_buf[10] = dec2hexchar((rec.timestamp >> 8) & 0xF); - print_buf[11] = dec2hexchar((rec.timestamp >> 4) & 0xF); - print_buf[12] = dec2hexchar(rec.timestamp & 0xF); - // print_buf[13] = ',' - print_buf[14] = dec2hexchar((raw_value >> 28) & 0xF); - print_buf[15] = dec2hexchar((raw_value >> 24) & 0xF); - print_buf[16] = dec2hexchar((raw_value >> 20) & 0xF); - print_buf[17] = dec2hexchar((raw_value >> 16) & 0xF); - print_buf[18] = dec2hexchar((raw_value >> 12) & 0xF); - print_buf[19] = dec2hexchar((raw_value >> 8) & 0xF); - print_buf[20] = dec2hexchar((raw_value >> 4) & 0xF); - print_buf[21] = dec2hexchar(raw_value & 0xF); - // print_buf[22] = '\n' - res.write(print_buf, 23); + char rec_buf[40]; + int rec_len; + switch (logfmt) { + case FMT_JSON: + rec_len = snprintf(rec_buf, sizeof(rec_buf), "%s[%u,%u,%g]", + count == 0 ? "" : ",", rec.uuid, rec.timestamp, rec.value); + res.write(rec_buf, rec_len); + break; + case FMT_CSV: + rec_len = snprintf(rec_buf, sizeof(rec_buf), "%u,%u,%g\n", + rec.uuid, rec.timestamp, rec.value); + res.write(rec_buf, rec_len); + break; + case FMT_BINARY: + res.write((const char*)&rec, sizeof(rec)); + break; + } count++; } file_close(dfile); } + if (logfmt == FMT_JSON) res.write("]", 1); + handle_return(HTML_OK); } From 68c1289c604bcbb9f2c15b07b33c14448ff22579 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Wed, 29 Apr 2026 15:00:03 -0400 Subject: [PATCH 104/115] refactor sensors: put them in sensors subfolder. update sensor descriptions, streamline sensor unit, group, short name by replacing long switch-case sequence with macros; --- Makefile | 4 +- OpenSprinkler.h | 7 +- ads1115.cpp | 41 -- ads1115.h | 25 -- defines.h | 2 +- opensprinkler_server.cpp | 37 +- platformio.ini | 5 +- sensor.cpp | 866 --------------------------------------- sensor.h | 272 ------------ 9 files changed, 36 insertions(+), 1223 deletions(-) delete mode 100644 sensor.cpp delete mode 100644 sensor.h diff --git a/Makefile b/Makefile index 5413e9c02..df10045cb 100644 --- a/Makefile +++ b/Makefile @@ -6,8 +6,8 @@ LD=$(CXX) LIBS=pthread mosquitto ssl crypto i2c lgpio LDFLAGS=$(addprefix -l,$(LIBS)) BINARY=OpenSprinkler -SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp i2cd.cpp sensor.cpp ads1115.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) -HEADERS=$(wildcard *.h) $(wildcard *.hpp) +SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp i2cd.cpp ads1115.cpp $(wildcard sensors/*.cpp) $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) +HEADERS=$(wildcard *.h) $(wildcard *.hpp) $(wildcard sensors/*.h) OBJECTS=$(addsuffix .o,$(basename $(SOURCES))) .PHONY: all diff --git a/OpenSprinkler.h b/OpenSprinkler.h index d11b708ca..1e46628f7 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -68,11 +68,14 @@ #endif #if defined(USE_SENSORS) -#include "sensor.h" +#include "sensors/sensor.h" +#include "sensors/ensemble_sensor.h" +#include "sensors/weather_sensor.h" #endif #if defined(USE_ADS1115) - #include "ads1115.h" +#include "ads1115.h" +#include "sensors/ads1115_sensor.h" #endif #if defined(ESP8266) diff --git a/ads1115.cpp b/ads1115.cpp index cbfd30c62..30f39064f 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -84,45 +84,4 @@ void ADS1115::request_pin(uint8_t pin) { this->_write_register(0x01, config); } -ADS1115Sensor::ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : -Sensor(interval, min, max, scale, offset, name, unit, flags), -sensor_index(sensor_index), -pin(pin), -sensors(sensors) {} - -void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { - bfill->emit_p(PSTR("{\"pin\":$D}"), ((this->sensor_index << 2) + this->pin + 1)); -} - -void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"default\":\"1\",\"extra\":[]}]}")); -} - -float ADS1115Sensor::get_initial_value() { - return 0.0; -} - -float ADS1115Sensor::_get_raw_value() { - if (this->sensors[sensor_index] == nullptr) { - return 0.0; - } - else { - return ((float)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; - } -} - -uint32_t ADS1115Sensor::_serialize_internal(char *buf) { - uint32_t i = 0; - buf[i++] = static_cast(this->sensor_index); - buf[i++] = static_cast(this->pin); - return i; -} - -ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf) { - uint32_t i = Sensor::_deserialize(buf); - this->sensor_index = static_cast(buf[i++]); - this->pin = static_cast(buf[i++]); - this->sensors = sensors; -} - #endif \ No newline at end of file diff --git a/ads1115.h b/ads1115.h index d652afd3e..a5f186619 100644 --- a/ads1115.h +++ b/ads1115.h @@ -5,7 +5,6 @@ #if defined(USE_SENSORS) #include -#include "sensor.h" #define ADS1115_SCALE_FACTOR (6144.0 / 32768.0) @@ -53,28 +52,4 @@ class ADS1115 { }; -class ADS1115Sensor : public Sensor { - public: - ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); - ADS1115Sensor(ADS1115 **sensors, char *buf); - - void emit_extra_json(BufferFiller *bfill); - static void emit_description_json(BufferFiller *bfill); - - SensorType get_sensor_type() { - return SensorType::ADS1115; - } - - uint8_t sensor_index; - uint8_t pin; - - float get_initial_value(); - - private: - float _get_raw_value(); - uint32_t _serialize_internal(char *buf); - - ADS1115 **sensors; -}; - #endif \ No newline at end of file diff --git a/defines.h b/defines.h index 22200ffc2..b8c7e04fc 100755 --- a/defines.h +++ b/defines.h @@ -359,7 +359,7 @@ enum { #define PIN_CURR_SENSE A0 // current sensing pin #define PIN_LATCH_VOLT_SENSE A0 // latch voltage sensing pin #define PIN_FREE_LIST {} // no free GPIO pin at the moment - #define ETHER_BUFFER_SIZE 1024 + #define ETHER_BUFFER_SIZE 2048 #define ETHER_BUFFER_ALLOC_SIZE ETHER_BUFFER_SIZE #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index e6cb564d2..7da6f702e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1942,16 +1942,16 @@ void server_change_sensor(OTF_PARAMS_DEF) { SensorType sensor_type = static_cast(type_raw); Sensor *sensor = nullptr; - float min = 0; - float max = 5000; // default: 5000mV or 5V - float scale = 1; - float offset = 0; - uint32_t interval = 5; // default: 5 minutes - SensorUnit unit = SensorUnit::None; + float min = SENSOR_DEFAULT_MIN; + float max = SENSOR_DEFAULT_MAX; + float scale = SENSOR_DEFAULT_SCALE; + float offset = SENSOR_DEFAULT_OFFSET; + uint32_t interval = SENSOR_DEFAULT_INTERVAL; + SensorUnit unit = SENSOR_DEFAULT_UNIT; uint16_t flags = 0; char name[SENSOR_NAME_LEN]; - snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); + strncpy(name, SENSOR_DEFAULT_NAME, SENSOR_NAME_LEN); SensorType original_sensor_type = SensorType::MAX_VALUE; if (os.sensors[sid].interval) { @@ -2451,7 +2451,7 @@ void bfill_enum_values(const char *name) { } void server_json_sensor_description_main(OTF_PARAMS_DEF) { - bfill.emit_p(PSTR("\"sensor\":[")); + bfill.emit_p(PSTR("\"sensors\":[")); for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE); i++) { if (i) bfill.emit_p(PSTR(",")); switch (static_cast(i)) { @@ -2470,7 +2470,7 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { } bfill.emit_p(PSTR("],\"units\":[")); - for (uint8_t i = 0; i < static_cast(SensorUnit::MAX_VALUE)-1; i++) { + for (uint8_t i = 0; i < static_cast(SensorUnit::MAX_VALUE); i++) { if (i) bfill.emit_p(PSTR(",")); SensorUnit unit = static_cast(i); bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":$D,\"index\":$D,\"value\":$D}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit), i); @@ -2484,7 +2484,20 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill_enum_values(PSTR("WeatherAction")); bfill.emit_p(PSTR("}")); - bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"5000\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); + bfill.emit_p(PSTR( + ",\"args\":[" + "{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"" SENSOR_DEFAULT_NAME "\"}," + "{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_INTERVAL) "\"}," + )); + bfill.emit_p(PSTR("{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"default\":\"$D\"},"), static_cast(SENSOR_DEFAULT_UNIT)); + bfill.emit_p(PSTR( + "{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_SCALE) "\"}," + "{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_OFFSET) "\"}," + "{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_MIN) "\"}," + "{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_MAX) "\"}," + "{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_TYPE) "\"}" + "]" + )); static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]]")); @@ -2492,7 +2505,7 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("}")); } -void server_json_sen_desc(OTF_PARAMS_DEF) +void server_json_sensor_desc(OTF_PARAMS_DEF) { if(!process_password(OTF_PARAMS)) return; begin_response(res); @@ -2742,7 +2755,7 @@ URLHandler urls[] = { server_delete_sensor, // dsn server_json_sensor_log, // jsl server_delete_sensor_log, // dsl - server_json_sen_desc, // jsd + server_json_sensor_desc, // jsd #endif }; diff --git a/platformio.ini b/platformio.ini index 014839b05..02fbd0598 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,12 +18,13 @@ board = d1_mini framework = arduino extra_scripts = pre:run_prebuild.py lib_ldf_mode = deep -lib_deps = +lib_deps = https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 + ;./external/OpenThings-Framework-Firmware-Library https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.2.0 ; ignore html2raw.cpp source file for firmware compilation (external helper program) -build_src_filter = +<*> - -- +build_src_filter = +<*> + - -- upload_speed = 460800 monitor_speed = 115200 board_build.flash_mode = dio diff --git a/sensor.cpp b/sensor.cpp deleted file mode 100644 index fa246d82b..000000000 --- a/sensor.cpp +++ /dev/null @@ -1,866 +0,0 @@ -#include "sensor.h" -#include "OpenSprinkler.h" - -extern OpenSprinkler os; -extern char tmp_buffer[]; - -const char *enum_string(SensorUnitGroup group) { - switch (group) { - case SensorUnitGroup::None: - return PSTR("No Group"); - case SensorUnitGroup::Temperature: - return PSTR("Temperature"); - case SensorUnitGroup::Length: - return PSTR("Length"); - case SensorUnitGroup::Volume: - return PSTR("Volume"); - case SensorUnitGroup::Light: - return PSTR("Light"); - case SensorUnitGroup::Energy: - return PSTR("Energy"); - case SensorUnitGroup::Velocity: - return PSTR("Velocity"); - case SensorUnitGroup::Pressure: - return PSTR("Pressure"); - case SensorUnitGroup::Flow: - return PSTR("Flow"); - case SensorUnitGroup::MAX_VALUE: - return nullptr; - } - - return nullptr; -} - -const char *enum_string(EnsembleAction action) { - switch (action) { - case EnsembleAction::Min: return PSTR("Min"); - case EnsembleAction::Max: return PSTR("Max"); - case EnsembleAction::Average: return PSTR("Average"); - case EnsembleAction::Sum: return PSTR("Sum"); - case EnsembleAction::Product: return PSTR("Product"); - case EnsembleAction::MAX_VALUE: return nullptr; - } - - return nullptr; -} - -const char *enum_string(WeatherAction action) { - switch (action) { - case WeatherAction::MAX_VALUE: return nullptr; - } - - return nullptr; -} - -const char* get_sensor_unit_name(SensorUnit unit) { - switch (unit) { - case SensorUnit::None: - return PSTR("None"); - case SensorUnit::Celsius: - return PSTR("Celsius"); - case SensorUnit::Fahrenheit: - return PSTR("Fahrenheit"); - case SensorUnit::Kelvin: - return PSTR("Kelvin"); - case SensorUnit::Millimeter: - return PSTR("Millimeter"); - case SensorUnit::Centimeter: - return PSTR("Centimeter"); - case SensorUnit::Meter: - return PSTR("Meter"); - case SensorUnit::Kilometer: - return PSTR("Kilometer"); - case SensorUnit::Inch: - return PSTR("Inch"); - case SensorUnit::Foot: - return PSTR("Foot"); - case SensorUnit::Mile: - return PSTR("Mile"); - case SensorUnit::Lux: - return PSTR("Lux"); - case SensorUnit::Lumen: - return PSTR("Lumen"); - case SensorUnit::Millivolt: - return PSTR("Millivolt"); - case SensorUnit::Volt: - return PSTR("Volt"); - case SensorUnit::Milliampere: - return PSTR("Milliampere"); - case SensorUnit::Ampere: - return PSTR("Ampere"); - case SensorUnit::Percent: - return PSTR("Percent"); - case SensorUnit::MilesPerHour: - return PSTR("Miles Per Hour"); - case SensorUnit::KilometersPerHour: - return PSTR("Kilometers Per Hour"); - case SensorUnit::MetersPerSecond: - return PSTR("Meters Per Second"); - case SensorUnit::DielectricConstant: - return PSTR("Dielectric Constant"); - case SensorUnit::PartsPerMillion: - return PSTR("Parts Per Million"); - case SensorUnit::Ohm: - return PSTR("Ohm"); - case SensorUnit::Milliohm: - return PSTR("Milliohm"); - case SensorUnit::Kiloohm: - return PSTR("Kiloohm"); - case SensorUnit::Bar: - return PSTR("Bar"); - case SensorUnit::Kilopascal: - return PSTR("Kilopascal"); - case SensorUnit::Pascal: - return PSTR("Pascal"); - case SensorUnit::Torr: - return PSTR("Torr"); - case SensorUnit::LitersPerSecond: - return PSTR("Liters Per Second"); - case SensorUnit::GallonsPerSecond: - return PSTR("Gallons"); - case SensorUnit::MAX_VALUE: - return nullptr; - } - - return nullptr; -} - -const char* get_sensor_unit_short(SensorUnit unit) { - switch (unit) { - case SensorUnit::None: - return PSTR(""); - case SensorUnit::Celsius: - return PSTR("°C"); - case SensorUnit::Fahrenheit: - return PSTR("°F"); - case SensorUnit::Kelvin: - return PSTR("K"); - case SensorUnit::Millimeter: - return PSTR("mm"); - case SensorUnit::Centimeter: - return PSTR("cm"); - case SensorUnit::Meter: - return PSTR("m"); - case SensorUnit::Kilometer: - return PSTR("km"); - case SensorUnit::Inch: - return PSTR("in"); - case SensorUnit::Foot: - return PSTR("ft"); - case SensorUnit::Mile: - return PSTR("mi"); - case SensorUnit::Lux: - return PSTR("lx"); - case SensorUnit::Lumen: - return PSTR("lm"); - case SensorUnit::Millivolt: - return PSTR("mV"); - case SensorUnit::Volt: - return PSTR("V"); - case SensorUnit::Milliampere: - return PSTR("mA"); - case SensorUnit::Ampere: - return PSTR("A"); - case SensorUnit::Percent: - return PSTR("%"); - case SensorUnit::MilesPerHour: - return PSTR("mph"); - case SensorUnit::KilometersPerHour: - return PSTR("km/h"); - case SensorUnit::MetersPerSecond: - return PSTR("m/s"); - case SensorUnit::DielectricConstant: - return PSTR("-"); - case SensorUnit::PartsPerMillion: - return PSTR("ppm"); - case SensorUnit::Ohm: - return PSTR("Ω"); - case SensorUnit::Milliohm: - return PSTR("mΩ"); - case SensorUnit::Kiloohm: - return PSTR("kΩ"); - case SensorUnit::Bar: - return PSTR("bar"); - case SensorUnit::Kilopascal: - return PSTR("kPa"); - case SensorUnit::Pascal: - return PSTR("Pa"); - case SensorUnit::Torr: - return PSTR("torr"); - case SensorUnit::LitersPerSecond: - return PSTR("L/s"); - case SensorUnit::GallonsPerSecond: - return PSTR("gal/s"); - case SensorUnit::MAX_VALUE: - return nullptr; - } - - return nullptr; -} - -const SensorUnitGroup get_sensor_unit_group(SensorUnit unit) { - switch (unit) { - case SensorUnit::None: - return SensorUnitGroup::None; - case SensorUnit::Celsius: - return SensorUnitGroup::Temperature; - case SensorUnit::Fahrenheit: - return SensorUnitGroup::Temperature; - case SensorUnit::Kelvin: - return SensorUnitGroup::Temperature; - case SensorUnit::Millimeter: - return SensorUnitGroup::Length; - case SensorUnit::Centimeter: - return SensorUnitGroup::Length; - case SensorUnit::Meter: - return SensorUnitGroup::Length; - case SensorUnit::Kilometer: - return SensorUnitGroup::Length; - case SensorUnit::Inch: - return SensorUnitGroup::Length; - case SensorUnit::Foot: - return SensorUnitGroup::Length; - case SensorUnit::Mile: - return SensorUnitGroup::Length; - case SensorUnit::Lux: - return SensorUnitGroup::Light; - case SensorUnit::Lumen: - return SensorUnitGroup::Light; - case SensorUnit::Millivolt: - return SensorUnitGroup::Energy; - case SensorUnit::Volt: - return SensorUnitGroup::Energy; - case SensorUnit::Milliampere: - return SensorUnitGroup::Energy; - case SensorUnit::Ampere: - return SensorUnitGroup::Energy; - case SensorUnit::Percent: - return SensorUnitGroup::None; - case SensorUnit::MilesPerHour: - return SensorUnitGroup::Velocity; - case SensorUnit::KilometersPerHour: - return SensorUnitGroup::Velocity; - case SensorUnit::MetersPerSecond: - return SensorUnitGroup::Velocity; - case SensorUnit::DielectricConstant: - return SensorUnitGroup::Energy; - case SensorUnit::PartsPerMillion: - return SensorUnitGroup::None; - case SensorUnit::Ohm: - return SensorUnitGroup::Energy; - case SensorUnit::Milliohm: - return SensorUnitGroup::Energy; - case SensorUnit::Kiloohm: - return SensorUnitGroup::Energy; - case SensorUnit::Bar: - return SensorUnitGroup::Pressure; - case SensorUnit::Kilopascal: - return SensorUnitGroup::Pressure; - case SensorUnit::Pascal: - return SensorUnitGroup::Pressure; - case SensorUnit::Torr: - return SensorUnitGroup::Pressure; - case SensorUnit::LitersPerSecond: - return SensorUnitGroup::Flow; - case SensorUnit::GallonsPerSecond: - return SensorUnitGroup::Flow; - case SensorUnit::MAX_VALUE: - return SensorUnitGroup::MAX_VALUE; - } - - return SensorUnitGroup::MAX_VALUE; -} - -const uint32_t get_sensor_unit_index(SensorUnit unit) { - return static_cast(unit); -} - -Sensor::Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags) : - interval(interval), min(min), max(max), scale(scale), offset(offset), flags(flags), unit(unit) { - strncpy(this->name, name, SENSOR_NAME_LEN); - this->name[SENSOR_NAME_LEN - 1] = 0; -} - -Sensor::Sensor() {} - -float Sensor::get_new_value() { - float value = this->_get_raw_value(); - value = (value * this->scale) + this->offset; - if (value < this->min) value = this->min; - if (value > this->max) value = this->max; - - return value; -} - -template -uint32_t write_buf(char* buf, T val) { - std::memcpy(buf, &val, sizeof(val)); - return sizeof(val); -} - -template -T read_buf(char* buf, uint32_t* i) { - T val; - std::memcpy(&val, buf + (*i), sizeof(T)); - *i += sizeof(T); - return val; -} - - -uint32_t Sensor::serialize(char* buf) { - uint32_t i = 0; - - buf[i++] = static_cast(this->get_sensor_type()); - memcpy(buf + i, this->name, SENSOR_NAME_LEN); - i += SENSOR_NAME_LEN; - buf[i++] = static_cast(this->unit); - i += write_buf(buf + i, this->interval); - i += write_buf(buf + i, this->flags); - i += write_buf(buf + i, this->scale); - i += write_buf(buf + i, this->offset); - i += write_buf(buf + i, this->min); - i += write_buf(buf + i, this->max); - i += write_buf(buf + i, this->uuid); - - i += this->_serialize_internal(buf + i); - return i; -} - -uint32_t Sensor::_deserialize(char* buf) { - uint32_t i = 1; // Skip sensor type - - memcpy(this->name, buf + i, SENSOR_NAME_LEN); - i += SENSOR_NAME_LEN; - this->unit = static_cast(buf[i++]); - this->interval = read_buf(buf, &i); - this->flags = read_buf(buf, &i); - this->scale = read_buf(buf, &i); - this->offset = read_buf(buf, &i); - this->min = read_buf(buf, &i); - this->max = read_buf(buf, &i); - this->uuid = read_buf(buf, &i); - - return i; -} - -EnsembleSensor::EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : - Sensor(interval, min, max, scale, offset, name, unit, flags), - action(action), - sensors(sensors) { - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - if (i < children_count) { - this->children[i] = children[i]; - } - else { - this->children[i] = ensemble_children_t{ 0.f, 0.f, 0.f, 0.f, SENSOR_UUID_NONE }; - } - } -} - -void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - if (i) bfill->emit_p(PSTR(",")); - ensemble_children_t* child = &this->children[i]; - bfill->emit_p(PSTR("{\"uuid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->uuid, child->max, child->min, child->scale, child->offset); - } - bfill->emit_p(PSTR("]}")); -} - -void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); -} - -float EnsembleSensor::get_initial_value() { - switch (this->action) { - case EnsembleAction::Min: - return this->max; - break; - case EnsembleAction::Max: - return this->min; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - return 0; - break; - case EnsembleAction::Product: - return 1; - break; - default: - // Unreachable - return 0.f; - } -} - -float EnsembleSensor::_get_raw_value() { - float inital = this->get_initial_value(); - uint8_t count = 0; - - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - uint8_t sensor = Sensor::find_index(this->children[i].uuid); - if (sensor < OpenSprinkler::nsensors && sensors[sensor].interval) { - float value = sensors[sensor].value; - value = (value * this->children[i].scale) + this->children[i].offset; - if (value < this->children[i].min) value = this->children[i].min; - if (value > this->children[i].max) value = this->children[i].max; - - switch (this->action) { - case EnsembleAction::Min: - if (value < inital) inital = value; - break; - case EnsembleAction::Max: - if (value > inital) inital = value; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - inital += value; - break; - case EnsembleAction::Product: - inital *= value; - break; - default: - // Unreachable - return 0.f; - } - - count += 1; - } - } - - if (count == 0) { - return 0.f; - } - else if (this->action == EnsembleAction::Average) { - return inital / (float)count; - } - else { - return inital; - } -} - -uint32_t EnsembleSensor::_serialize_internal(char* buf) { - uint32_t i = 0; - for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - i += write_buf(buf + i, this->children[j].uuid); - i += write_buf(buf + i, this->children[j].min); - i += write_buf(buf + i, this->children[j].max); - i += write_buf(buf + i, this->children[j].scale); - i += write_buf(buf + i, this->children[j].offset); - } - - buf[i++] = static_cast(this->action); - return i; -} - -EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { - uint32_t i = Sensor::_deserialize(buf); - for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - this->children[j].uuid = read_buf(buf, &i); - this->children[j].min = read_buf(buf, &i); - this->children[j].max = read_buf(buf, &i); - this->children[j].scale = read_buf(buf, &i); - this->children[j].offset = read_buf(buf, &i); - } - - this->action = static_cast(buf[i++]); - this->sensors = sensors; -} - - -WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, WeatherGetter weather_getter, WeatherAction action) : - Sensor(interval, min, max, scale, offset, name, unit, flags), - action(action), - weather_getter(weather_getter) { -} - -void WeatherSensor::emit_extra_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"action\":$D}"), this->action); -} - -void WeatherSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"default\":\"0\",\"extra\":[]}]}")); -} - -float WeatherSensor::get_initial_value() { - return 0.f; -} - -float WeatherSensor::_get_raw_value() { - return this->weather_getter(this->action); -} - -uint32_t WeatherSensor::_serialize_internal(char* buf) { - uint32_t i = 0; - buf[i++] = static_cast(this->action); - return i; -} - -WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) { - uint32_t i = Sensor::_deserialize(buf); - this->action = static_cast(buf[i++]); -} - -SensorAdjustment::SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { - this->flags = flags; - this->uuid = uuid; - if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; - this->point_count = point_count; - for (size_t i = 0; i < point_count; i++) { - this->points[i] = points[i]; - } -} - -float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { - if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { - uint8_t idx = Sensor::find_index(this->uuid); - if (idx < MAX_SENSORS && sensors[idx].interval) { - float value = sensors[idx].value; - if (value <= this->points[0].x) return this->points[0].y; - if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; - - uint8_t i; - - for (i = 0; i < this->point_count - 1; i++) { - if (value < this->points[i + 1].x) { - break; - } - } - - sensor_adjustment_point_t left = this->points[i]; - sensor_adjustment_point_t right = this->points[i + 1]; - - if (right.x == left.x) return left.y; - - value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; - - if (value < 0) value = 0; - return value; - } - } - return 1.f; -} - -SensorAdjustment *SensorAdjustment::read(uint8_t index, uint8_t nprograms) { - static SensorAdjustment result(0, SENSOR_UUID_NONE, 0, nullptr); - - if (index >= nprograms) return nullptr; - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); - if (file) { - uint32_t pos = (uint32_t)index * SENSOR_ADJUSTMENT_SIZE; - if (file_size(file) < pos + SENSOR_ADJUSTMENT_SIZE) { - file_close(file); - return nullptr; - } - file_seek(file, pos, FileSeekMode::Set); - file_read(file, &result, SENSOR_ADJUSTMENT_SIZE); - file_close(file); - if (result.uuid != SENSOR_UUID_NONE) { - return &result; - } - } - return nullptr; -} - -void SensorAdjustment::write(SensorAdjustment *adj, uint8_t index) { - uint32_t pos = (uint32_t)SENSOR_ADJUSTMENT_SIZE * index; - - os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); - if (file) { - SensorAdjustment disabled(0, SENSOR_UUID_NONE, 0, nullptr); - - uint32_t cur_size = file_size(file); - if (cur_size < pos) { - file_seek(file, 0, FileSeekMode::End); - while (cur_size < pos) { - file_write(file, &disabled, SENSOR_ADJUSTMENT_SIZE); - cur_size += SENSOR_ADJUSTMENT_SIZE; - } - } - - file_seek(file, pos, FileSeekMode::Set); - SensorAdjustment *to_write = adj ? adj : &disabled; - file_write(file, to_write, SENSOR_ADJUSTMENT_SIZE); - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENADJ_FILENAME); - } -} - -// --------------------------------------------------------------------------- -// Sensor file I/O -// --------------------------------------------------------------------------- - -Sensor *Sensor::parse(os_file_type file) { - static uint8_t sensor_scratchpad[sizeof(OpenSprinkler::SensorUnion)] __attribute__((aligned(4))); - static Sensor *active_sensor = nullptr; - - if (active_sensor != nullptr) { - active_sensor->~Sensor(); - active_sensor = nullptr; - } - uint32_t len = 0; - file_read(file, &len, sizeof(len)); - - if (len == 0 || len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) return nullptr; - - file_read(file, tmp_buffer, len); - file_seek(file, TMP_BUFFER_SIZE - sizeof(uint32_t) - len, FileSeekMode::Current); - - if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) return nullptr; - - SensorType sensor_type = static_cast(*tmp_buffer); - switch (sensor_type) { - case SensorType::Ensemble: - active_sensor = new (sensor_scratchpad) EnsembleSensor(os.sensors, (char*)tmp_buffer); - break; - case SensorType::ADS1115: - active_sensor = new (sensor_scratchpad) ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); - break; - case SensorType::Weather: - active_sensor = new (sensor_scratchpad) WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); - break; - default: - return nullptr; - } - return active_sensor; -} - -Sensor *Sensor::get(uint8_t index) { - uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; - - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); - if (file) { - file_seek(file, pos, FileSeekMode::Set); - Sensor *result = Sensor::parse(file); - file_close(file); - return result; - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - return nullptr; - } -} - -void Sensor::write(Sensor *sensor, uint8_t index) { - uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; - uint32_t len = 0; - - // Zero data area before serializing so the full TMP_BUFFER_SIZE slot is clean. - memset(tmp_buffer, 0, TMP_BUFFER_SIZE - sizeof(uint32_t)); - if (sensor) { - len = sensor->serialize(tmp_buffer); - if (len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) - len = TMP_BUFFER_SIZE - sizeof(uint32_t); - } - - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); - if (file) { - file_seek(file, pos, FileSeekMode::Set); - file_write(file, &len, sizeof(len)); - file_write(file, tmp_buffer, TMP_BUFFER_SIZE - sizeof(uint32_t)); // always write full slot - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } -} - -/** Load sensor count from sensor file */ -void Sensor::load_count() { - OpenSprinkler::nsensors = file_read_byte(SENSORS_FILENAME, 0); -} - -/** Save sensor count to sensor file */ -void Sensor::save_count() { - file_write_byte(SENSORS_FILENAME, 0, OpenSprinkler::nsensors); -} - -/** Add a sensor */ -unsigned char Sensor::add(Sensor *sensor) { - if (OpenSprinkler::nsensors >= MAX_SENSORS) return 0; - - Sensor::write(sensor, OpenSprinkler::nsensors); - - // update in-memory state - sensor_memory_t &m = OpenSprinkler::sensors[OpenSprinkler::nsensors]; - m.interval = sensor->interval; - m.flags = sensor->flags; - m.uuid = sensor->uuid; - m.next_update = 0; - m.value = sensor->get_initial_value(); - - OpenSprinkler::nsensors++; - Sensor::save_count(); - return 1; -} - -/** Modify a sensor */ -unsigned char Sensor::modify(uint8_t index, Sensor *sensor) { - if (index >= OpenSprinkler::nsensors) return 0; - Sensor::write(sensor, index); - - // update memory - sensor_memory_t &m = OpenSprinkler::sensors[index]; - m.interval = sensor->interval; - m.flags = sensor->flags; - m.uuid = sensor->uuid; - m.next_update = 0; - m.value = sensor->get_initial_value(); - - return 1; -} - -/** Delete a sensor */ -unsigned char Sensor::del(uint8_t index) { - if (index >= OpenSprinkler::nsensors) return 0; - if (OpenSprinkler::nsensors == 0) return 0; - - // erase by copying backward - for (uint8_t i = index; i < OpenSprinkler::nsensors - 1; i++) { - file_copy_block(SENSORS_FILENAME, 1 + (uint32_t)(i + 1) * TMP_BUFFER_SIZE, 1 + (uint32_t)i * TMP_BUFFER_SIZE, TMP_BUFFER_SIZE, tmp_buffer); - // also shift in-memory state - OpenSprinkler::sensors[i] = OpenSprinkler::sensors[i + 1]; - } - - OpenSprinkler::nsensors--; - OpenSprinkler::sensors[OpenSprinkler::nsensors].interval = 0; - OpenSprinkler::sensors[OpenSprinkler::nsensors].uuid = 0; - - Sensor::save_count(); - return 1; -} - -/** Load all sensors from file to memory */ -void Sensor::load_all() { - OpenSprinkler::lcd.clear(); - OpenSprinkler::lcd.setCursor(0, 0); - OpenSprinkler::lcd_print_pgm(PSTR("Init sensors...")); - - // 1. Check if the configuration file exists. - // If not, we need to initialize it with a 0 count. - if (!file_exists(SENSORS_FILENAME)) { - DEBUG_PRINTLN(F("Sensor files missing. Initializing...")); - OpenSprinkler::nsensors = 0; - Sensor::save_count(); - } else { - Sensor::load_count(); - } - - // 2. Proceed with existing load logic - Sensor *sensor; - os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); - if (file) { - // skip the first byte (count) - file_seek(file, 1, FileSeekMode::Set); - for (size_t i = 0; i < OpenSprinkler::nsensors; i++) { - if ((sensor = Sensor::parse(file))) { - sensor_memory_t &m = OpenSprinkler::sensors[i]; - m.interval = sensor->interval; - m.flags = sensor->flags; - m.uuid = sensor->uuid; - m.next_update = 0; - m.value = sensor->get_initial_value(); - } - } - - file_close(file); - } else { - DEBUG_PRINT("Failed to open file: "); - DEBUG_PRINTLN(SENSORS_FILENAME); - } -} - -/** Find sensor index by UUID */ -uint8_t Sensor::find_index(uint16_t uuid) { - if (uuid == SENSOR_UUID_NONE) return OpenSprinkler::nsensors; - for (uint8_t i = 0; i < OpenSprinkler::nsensors; i++) { - if (OpenSprinkler::sensors[i].uuid == uuid) return i; - } - return MAX_SENSORS; -} - -/** Sensor log performance test */ -void Sensor::test_log(uint32_t n_records) { - remove_sensor_log(); - - DEBUG_PRINTF("sensor log test: writing %lu records\n", (unsigned long)n_records); - uint32_t t0 = millis(); - - for (uint32_t i = 0; i < n_records; i++) { - if (OpenSprinkler::nsensors > 0) os.log_sensor((uint8_t)((i + 1) % OpenSprinkler::nsensors), (float)i / 1000.f); - } - - uint32_t write_ms = millis() - t0; - DEBUG_PRINTF("sensor log write: %lu ms total, %.2f ms/record\n", - (unsigned long)write_ms, - n_records ? (float)write_ms / n_records : 0.f); - - // Read pass: iterate all files in order (oldest to newest) - os_file_type hfile = open_sensor_log_header(FileOpenMode::Read); - if (!hfile) { - DEBUG_PRINTLN("sensor log test: cannot open header"); - return; - } - SensorLogHeader hdr = {}; - file_read(hfile, &hdr, sizeof(hdr)); - file_close(hfile); - if (hdr.magic != SENSOR_LOG_MAGIC || hdr.version != SENSOR_LOG_VERSION) { - DEBUG_PRINTLN("sensor log test: bad header"); - return; - } - - uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; - uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); - DEBUG_PRINTF("sensor log state: max_files=%u records_per_file=%u cur_file=%u wrapped=%u total_files=%u\n", - hdr.max_files, hdr.records_per_file, hdr.cur_file, hdr.wrapped, total_files); - - uint32_t tr = millis(); - uint32_t count = 0; - for (uint16_t fi = 0; fi < total_files; fi++) { - uint16_t file_no = (first_file + fi) % hdr.max_files; - os_file_type dfile = open_sensor_log(file_no, FileOpenMode::Read); - if (!dfile) continue; - SensorLogRecord rec; - while (file_read(dfile, &rec, sizeof(rec)) == (int)sizeof(rec)) count++; - file_close(dfile); - } - - uint32_t read_ms = millis() - tr; - DEBUG_PRINTF("sensor log read: %lu records in %lu ms (%.2f ms/record)\n", - (unsigned long)count, (unsigned long)read_ms, - count ? (float)read_ms / count : 0.f); -} - -// --------------------------------------------------------------------------- -// Sensor log file helpers -// --------------------------------------------------------------------------- - -void get_sensor_log_filename(char *buf, uint16_t file_no) { - snprintf(buf, 24, "%s%03u", SENSORS_LOG_FILENAME, file_no % 1000); -} - -os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode) { - char fname[24]; - get_sensor_log_filename(fname, file_no); - return file_open(fname, mode); -} - -os_file_type open_sensor_log_header(FileOpenMode mode) { - return file_open(SENSORS_LOG_HEADER_FILENAME, mode); -} - -void remove_sensor_log(int16_t file_no) { - char fname[24]; - if (file_no < 0) { - remove_file(SENSORS_LOG_HEADER_FILENAME); - for (uint16_t i = 0; i < SENSOR_LOG_MAX_FILES; i++) { - get_sensor_log_filename(fname, i); - remove_file(fname); - } - } else { - get_sensor_log_filename(fname, (uint16_t)file_no); - remove_file(fname); - } -} \ No newline at end of file diff --git a/sensor.h b/sensor.h deleted file mode 100644 index 7fc82142e..000000000 --- a/sensor.h +++ /dev/null @@ -1,272 +0,0 @@ -#pragma once - -// External / analog sensor subsystem -// -// The types in this file (Sensor, EnsembleSensor, WeatherSensor, ADS1115Sensor, -// SensorAdjustment, etc.) model the *external* sensor board — an ADS1115-based -// I2C ADC add-on that reads analog probes (soil moisture, temperature, etc.). -// It is enabled by the USE_SENSORS compile-time guard. -// -// These are DISTINCT from the two onboard digital sensor inputs (SENSOR1 / -// SENSOR2) that are wired directly to GPIO pins. Those are simple binary -// (open/closed) or pulse inputs handled entirely in OpenSprinkler.h/.cpp via -// os.sensor1_status / os.sensor2_status and related IOPT_SENSOR* options. -// No types from this file are involved in the onboard sensor logic. - -#include -#include "utils.h" -#include "defines.h" -#include "bfiller.h" - -#define SENSOR_NAME_LEN 33 -#define SENSOR_CUSTOM_UNIT_LEN 9 - -#define SENSOR_UUID_NONE 0 // sentinel: "no sensor assigned" (0 = uninitialized/disabled) - -typedef struct { - uint32_t interval; - uint32_t next_update; - float value; - uint16_t uuid; // stable sensor identifier (0 = empty slot) - uint16_t flags; // was uint32_t; only 2 bits used, uint16_t keeps struct at 16 bytes -} sensor_memory_t; - -// Sensor log file format — both structs are tightly packed (no padding) so -// sizeof() gives the exact on-disk byte count and offsetof() gives exact field offsets. -struct __attribute__((packed)) SensorLogHeader { - uint8_t magic; // SENSOR_LOG_MAGIC - uint8_t version; // SENSOR_LOG_VERSION - uint16_t max_files; // number of data files in rotation - uint16_t records_per_file; // max records per data file - uint16_t cur_file; // index of the data file currently being written - uint8_t wrapped; // 1 once all max_files slots have been used at least once - uint8_t reserved[7]; -}; // 16 bytes - -struct __attribute__((packed)) SensorLogRecord { - uint32_t timestamp; // unix epoch seconds - float value; // sensor reading - uint16_t uuid; // sensor UUID (replaces sid+reserved, same 10-byte size) -}; - -enum class SensorType : uint8_t { - Ensemble = 0, - ADS1115, - Weather, - MAX_VALUE, -}; - -enum class SensorUnitGroup : uint8_t { - None = 0, - Temperature, - Length, - Volume, - Light, - Energy, - Velocity, - Pressure, - Flow, - MAX_VALUE, -}; - -enum class SensorUnit : uint8_t { - None = 0, - Celsius, - Fahrenheit, - Kelvin, - Millimeter, - Centimeter, - Meter, - Kilometer, - Inch, - Foot, - Mile, - Lux, - Lumen, - Millivolt, - Volt, - Milliampere, - Ampere, - Percent, - MilesPerHour, - KilometersPerHour, - MetersPerSecond, - DielectricConstant, - PartsPerMillion, - Ohm, - Milliohm, - Kiloohm, - Bar, - Kilopascal, - Pascal, - Torr, - LitersPerSecond, - GallonsPerSecond, - MAX_VALUE, -}; - -typedef enum { - SENSOR_FLAG_ENABLE = 0, - SENSOR_FLAG_LOG, - SENSOR_FLAG_COUNT -} sensor_flags; - -class Sensor { -public: - Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags); - Sensor(); - virtual ~Sensor() {} - - float get_new_value(); - uint32_t serialize(char *buf); - - static Sensor *parse(os_file_type file); // statically allocated, do not delete - static Sensor *get(uint8_t index); // statically allocated, do not delete - static void write(Sensor *sensor, uint8_t index); - static void load_count(); - static void save_count(); - static unsigned char add(Sensor *sensor); - static unsigned char modify(uint8_t index, Sensor *sensor); // index is positional index - static unsigned char del(uint8_t index); // index is positional index - static void load_all(); - static uint8_t find_index(uint16_t uuid); - static void test_log(uint32_t n_records); - - void virtual emit_extra_json(BufferFiller *bfill) = 0; - - uint32_t interval = 1; - float min = 0.f; - float max = 0.f; - float scale = 0.f; - float offset = 0.f; - uint16_t flags = 0; - uint16_t uuid = 0; // assigned by write_sensor on creation; 0 = not yet assigned - SensorUnit unit = SensorUnit::None; - char name[SENSOR_NAME_LEN] = {0}; - - SensorType virtual get_sensor_type() = 0; - float virtual get_initial_value() = 0; - - private: - float virtual _get_raw_value() = 0; - protected: - uint32_t _deserialize(char *buf); - uint32_t virtual _serialize_internal(char *buf) = 0; -}; - -enum class EnsembleAction : uint8_t { - Min = 0, - Max, - Average, - Sum, - Product, - MAX_VALUE, -}; - -typedef Sensor* (*SensorGetter)(uint8_t); - -typedef struct { - float min; - float max; - float scale; - float offset; - uint16_t uuid; // UUID of child sensor (SENSOR_UUID_NONE = unused slot) -} ensemble_children_t; - -#define ENSEMBLE_SENSOR_CHILDREN_COUNT 4 - -class EnsembleSensor : public Sensor { - public: - EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); - EnsembleSensor(sensor_memory_t *sensors, char *buf); - - void emit_extra_json(BufferFiller *bfill); - static void emit_description_json(BufferFiller *bfill); - - SensorType get_sensor_type() { - return SensorType::Ensemble; - } - - ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; - EnsembleAction action; - - float get_initial_value(); - - private: - float _get_raw_value(); - uint32_t _serialize_internal(char *buf); - - sensor_memory_t *sensors; -}; - -enum class WeatherAction { - MAX_VALUE, -}; - -typedef float (*WeatherGetter)(WeatherAction); - -class WeatherSensor : public Sensor { - public: - WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, WeatherGetter weather_getter, WeatherAction action); - WeatherSensor(WeatherGetter weather_getter, char *buf); - - void emit_extra_json(BufferFiller *bfill); - static void emit_description_json(BufferFiller *bfill); - - SensorType get_sensor_type() { - return SensorType::Weather; - } - - WeatherAction action; - - float get_initial_value(); - - private: - float _get_raw_value(); - uint32_t _serialize_internal(char *buf); - - WeatherGetter weather_getter; -}; - -typedef struct { - float x; - float y; -} sensor_adjustment_point_t; - -#define SENSOR_ADJUSTMENT_POINTS 8 - -typedef enum { - SENADJ_FLAG_ENABLE = 0, -} senadj_flags; - -class SensorAdjustment { -public: - SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); - - static SensorAdjustment *read(uint8_t index, uint8_t nprograms); // returns statically allocated object, do not delete - static void write(SensorAdjustment *adj, uint8_t index); - - float get_adjustment_factor(sensor_memory_t *sensors); - - sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; - uint16_t uuid; // sensor UUID (SENSOR_UUID_NONE = adjustment disabled) - uint8_t flags; - uint8_t point_count; -}; - -#define SENSOR_ADJUSTMENT_SIZE sizeof(SensorAdjustment) - -const char *enum_string(SensorUnitGroup group); -const char *enum_string(EnsembleAction action); -const char *enum_string(WeatherAction action); - -const char* get_sensor_unit_name(SensorUnit unit); -const char* get_sensor_unit_short(SensorUnit unit); -const SensorUnitGroup get_sensor_unit_group(SensorUnit unit); -const uint32_t get_sensor_unit_index(SensorUnit unit); - -// Sensor log file helpers -void get_sensor_log_filename(char *buf, uint16_t file_no); -os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); -os_file_type open_sensor_log_header(FileOpenMode mode); -void remove_sensor_log(int16_t file_no = -1); // -1 removes header + all data files From eeeb9befc9e9a34941ad69b0a85c7286c86ea856 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Wed, 29 Apr 2026 15:18:19 -0400 Subject: [PATCH 105/115] add missing sensors folder --- sensors/ads1115_sensor.cpp | 54 +++++ sensors/ads1115_sensor.h | 34 +++ sensors/ensemble_sensor.cpp | 139 +++++++++++ sensors/ensemble_sensor.h | 37 +++ sensors/sensor.cpp | 462 ++++++++++++++++++++++++++++++++++++ sensors/sensor.h | 264 +++++++++++++++++++++ sensors/weather_sensor.cpp | 43 ++++ sensors/weather_sensor.h | 26 ++ 8 files changed, 1059 insertions(+) create mode 100644 sensors/ads1115_sensor.cpp create mode 100644 sensors/ads1115_sensor.h create mode 100644 sensors/ensemble_sensor.cpp create mode 100644 sensors/ensemble_sensor.h create mode 100644 sensors/sensor.cpp create mode 100644 sensors/sensor.h create mode 100644 sensors/weather_sensor.cpp create mode 100644 sensors/weather_sensor.h diff --git a/sensors/ads1115_sensor.cpp b/sensors/ads1115_sensor.cpp new file mode 100644 index 000000000..b1f3044af --- /dev/null +++ b/sensors/ads1115_sensor.cpp @@ -0,0 +1,54 @@ +#include "ads1115_sensor.h" + +#if defined(USE_SENSORS) + +ADS1115Sensor::ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + sensor_index(sensor_index), + pin(pin), + sensors(sensors) {} + +void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { + bfill->emit_p(PSTR("{\"pin\":$D}"), ((this->sensor_index << 2) + this->pin + 1)); +} + +void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR( + "{\"name\":\"ADS1115 Sensor\"," + "\"args\":[" + "{\"name\":\"Pin Number\"," + "\"arg\":\"pin\"," + "\"type\":\"int::[1,16]\"," + "\"default\":\"1\"}" + "]}" + )); +} + +float ADS1115Sensor::get_initial_value() { + return 0.0; +} + +float ADS1115Sensor::_get_raw_value() { + if (this->sensors[sensor_index] == nullptr) { + return 0.0; + } + else { + return ((float)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; + } +} + +uint32_t ADS1115Sensor::_serialize_internal(char *buf) { + uint32_t i = 0; + buf[i++] = static_cast(this->sensor_index); + buf[i++] = static_cast(this->pin); + return i; +} + +ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf) { + uint32_t i = Sensor::_deserialize(buf); + this->sensor_index = static_cast(buf[i++]); + this->pin = static_cast(buf[i++]); + this->sensors = sensors; +} + +#endif diff --git a/sensors/ads1115_sensor.h b/sensors/ads1115_sensor.h new file mode 100644 index 000000000..af482fc29 --- /dev/null +++ b/sensors/ads1115_sensor.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../defines.h" + +#if defined(USE_SENSORS) + +#include "sensor.h" +#include "../ads1115.h" + +class ADS1115Sensor : public Sensor { + public: + ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(ADS1115 **sensors, char *buf); + + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); + + SensorType get_sensor_type() { + return SensorType::ADS1115; + } + + uint8_t sensor_index; + uint8_t pin; + + float get_initial_value(); + + private: + float _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + ADS1115 **sensors; +}; + +#endif diff --git a/sensors/ensemble_sensor.cpp b/sensors/ensemble_sensor.cpp new file mode 100644 index 000000000..4aecfd239 --- /dev/null +++ b/sensors/ensemble_sensor.cpp @@ -0,0 +1,139 @@ +#include "ensemble_sensor.h" +#include "../OpenSprinkler.h" + +extern OpenSprinkler os; + +EnsembleSensor::EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + action(action), + sensors(sensors) { + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + if (i < children_count) { + this->children[i] = children[i]; + } + else { + this->children[i] = ensemble_children_t{ 0.f, 0.f, 0.f, 0.f, SENSOR_UUID_NONE }; + } + } +} + +void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + if (i) bfill->emit_p(PSTR(",")); + ensemble_children_t* child = &this->children[i]; + bfill->emit_p(PSTR("{\"uuid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->uuid, child->max, child->min, child->scale, child->offset); + } + bfill->emit_p(PSTR("]}")); +} + +void EnsembleSensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR( + "{\"name\":\"Ensemble Sensor\"," + "\"args\":[" + "{\"name\":\"Argument Sensors\"," + "\"arg\":\"children\"," + "\"type\":\"array::4\"," + "\"extra\":[" + "{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\"}," + "{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"\"}," + "{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"\"}," + "{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"\"}," + "{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"\"}" + "]}," + "{\"name\":\"Ensemble Action\"," + "\"arg\":\"action\"," + "\"type\":\"enum::EnsembleAction\"," + "\"default\":\"0\"}" + "]}" + )); +} + +float EnsembleSensor::get_initial_value() { + switch (this->action) { + case EnsembleAction::Min: + return this->max; + case EnsembleAction::Max: + return this->min; + case EnsembleAction::Average: + case EnsembleAction::Sum: + return 0; + case EnsembleAction::Product: + return 1; + default: + return 0.f; + } +} + +float EnsembleSensor::_get_raw_value() { + float inital = this->get_initial_value(); + uint8_t count = 0; + + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + uint8_t sensor = Sensor::find_index(this->children[i].uuid); + if (sensor < OpenSprinkler::nsensors && sensors[sensor].interval) { + float value = sensors[sensor].value; + value = (value * this->children[i].scale) + this->children[i].offset; + if (value < this->children[i].min) value = this->children[i].min; + if (value > this->children[i].max) value = this->children[i].max; + + switch (this->action) { + case EnsembleAction::Min: + if (value < inital) inital = value; + break; + case EnsembleAction::Max: + if (value > inital) inital = value; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + inital += value; + break; + case EnsembleAction::Product: + inital *= value; + break; + default: + return 0.f; + } + + count += 1; + } + } + + if (count == 0) { + return 0.f; + } + else if (this->action == EnsembleAction::Average) { + return inital / (float)count; + } + else { + return inital; + } +} + +uint32_t EnsembleSensor::_serialize_internal(char* buf) { + uint32_t i = 0; + for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { + i += write_buf(buf + i, this->children[j].uuid); + i += write_buf(buf + i, this->children[j].min); + i += write_buf(buf + i, this->children[j].max); + i += write_buf(buf + i, this->children[j].scale); + i += write_buf(buf + i, this->children[j].offset); + } + + buf[i++] = static_cast(this->action); + return i; +} + +EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { + uint32_t i = Sensor::_deserialize(buf); + for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { + this->children[j].uuid = read_buf(buf, &i); + this->children[j].min = read_buf(buf, &i); + this->children[j].max = read_buf(buf, &i); + this->children[j].scale = read_buf(buf, &i); + this->children[j].offset = read_buf(buf, &i); + } + + this->action = static_cast(buf[i++]); + this->sensors = sensors; +} diff --git a/sensors/ensemble_sensor.h b/sensors/ensemble_sensor.h new file mode 100644 index 000000000..8517693fe --- /dev/null +++ b/sensors/ensemble_sensor.h @@ -0,0 +1,37 @@ +#pragma once + +#include "sensor.h" + +typedef struct { + float min; + float max; + float scale; + float offset; + uint16_t uuid; // UUID of child sensor (SENSOR_UUID_NONE = unused slot) +} ensemble_children_t; + +#define ENSEMBLE_SENSOR_CHILDREN_COUNT 4 + +class EnsembleSensor : public Sensor { + public: + EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); + EnsembleSensor(sensor_memory_t *sensors, char *buf); + + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); + + SensorType get_sensor_type() { + return SensorType::Ensemble; + } + + ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; + EnsembleAction action; + + float get_initial_value(); + + private: + float _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + sensor_memory_t *sensors; +}; diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp new file mode 100644 index 000000000..2413854a4 --- /dev/null +++ b/sensors/sensor.cpp @@ -0,0 +1,462 @@ +#include "sensor.h" +#include "../OpenSprinkler.h" + +extern OpenSprinkler os; +extern char tmp_buffer[]; + +const char *enum_string(SensorUnitGroup group) { + switch (group) { + #define X(id, name) case SensorUnitGroup::id: return PSTR(name); + SENSOR_UNIT_GROUP_LIST(X) + #undef X + case SensorUnitGroup::MAX_VALUE: return nullptr; + } + return nullptr; +} + +const char *enum_string(EnsembleAction action) { + switch (action) { + #define X(id, name) case EnsembleAction::id: return PSTR(name); + ENSEMBLE_ACTION_LIST(X) + #undef X + case EnsembleAction::MAX_VALUE: return nullptr; + } + return nullptr; +} + +const char *enum_string(WeatherAction action) { + switch (action) { + case WeatherAction::MAX_VALUE: return nullptr; + } + return nullptr; +} + +const char* get_sensor_unit_name(SensorUnit unit) { + switch (unit) { + #define X(id, name, sym, group) case SensorUnit::id: return PSTR(name); + SENSOR_UNIT_LIST(X) + #undef X + case SensorUnit::MAX_VALUE: return nullptr; + } + return nullptr; +} + +const char* get_sensor_unit_short(SensorUnit unit) { + switch (unit) { + #define X(id, name, sym, group) case SensorUnit::id: return PSTR(sym); + SENSOR_UNIT_LIST(X) + #undef X + case SensorUnit::MAX_VALUE: return nullptr; + } + return nullptr; +} + +const SensorUnitGroup get_sensor_unit_group(SensorUnit unit) { + switch (unit) { + #define X(id, name, sym, group) case SensorUnit::id: return SensorUnitGroup::group; + SENSOR_UNIT_LIST(X) + #undef X + case SensorUnit::MAX_VALUE: return SensorUnitGroup::MAX_VALUE; + } + return SensorUnitGroup::MAX_VALUE; +} + +const uint32_t get_sensor_unit_index(SensorUnit unit) { + return static_cast(unit); +} + +Sensor::Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags) : + interval(interval), min(min), max(max), scale(scale), offset(offset), flags(flags), unit(unit) { + strncpy(this->name, name, SENSOR_NAME_LEN); + this->name[SENSOR_NAME_LEN - 1] = 0; +} + +Sensor::Sensor() {} + +float Sensor::get_new_value() { + float value = this->_get_raw_value(); + value = (value * this->scale) + this->offset; + if (value < this->min) value = this->min; + if (value > this->max) value = this->max; + + return value; +} + +uint32_t Sensor::serialize(char* buf) { + uint32_t i = 0; + + buf[i++] = static_cast(this->get_sensor_type()); + memcpy(buf + i, this->name, SENSOR_NAME_LEN); + i += SENSOR_NAME_LEN; + buf[i++] = static_cast(this->unit); + i += write_buf(buf + i, this->interval); + i += write_buf(buf + i, this->flags); + i += write_buf(buf + i, this->scale); + i += write_buf(buf + i, this->offset); + i += write_buf(buf + i, this->min); + i += write_buf(buf + i, this->max); + i += write_buf(buf + i, this->uuid); + + i += this->_serialize_internal(buf + i); + return i; +} + +uint32_t Sensor::_deserialize(char* buf) { + uint32_t i = 1; // Skip sensor type + + memcpy(this->name, buf + i, SENSOR_NAME_LEN); + i += SENSOR_NAME_LEN; + this->unit = static_cast(buf[i++]); + this->interval = read_buf(buf, &i); + this->flags = read_buf(buf, &i); + this->scale = read_buf(buf, &i); + this->offset = read_buf(buf, &i); + this->min = read_buf(buf, &i); + this->max = read_buf(buf, &i); + this->uuid = read_buf(buf, &i); + + return i; +} + +SensorAdjustment::SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { + this->flags = flags; + this->uuid = uuid; + if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; + this->point_count = point_count; + for (size_t i = 0; i < point_count; i++) { + this->points[i] = points[i]; + } +} + +float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { + if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { + uint8_t idx = Sensor::find_index(this->uuid); + if (idx < MAX_SENSORS && sensors[idx].interval) { + float value = sensors[idx].value; + if (value <= this->points[0].x) return this->points[0].y; + if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; + + uint8_t i; + + for (i = 0; i < this->point_count - 1; i++) { + if (value < this->points[i + 1].x) { + break; + } + } + + sensor_adjustment_point_t left = this->points[i]; + sensor_adjustment_point_t right = this->points[i + 1]; + + if (right.x == left.x) return left.y; + + value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; + + if (value < 0) value = 0; + return value; + } + } + return 1.f; +} + +SensorAdjustment *SensorAdjustment::read(uint8_t index, uint8_t nprograms) { + static SensorAdjustment result(0, SENSOR_UUID_NONE, 0, nullptr); + + if (index >= nprograms) return nullptr; + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + uint32_t pos = (uint32_t)index * SENSOR_ADJUSTMENT_SIZE; + if (file_size(file) < pos + SENSOR_ADJUSTMENT_SIZE) { + file_close(file); + return nullptr; + } + file_seek(file, pos, FileSeekMode::Set); + file_read(file, &result, SENSOR_ADJUSTMENT_SIZE); + file_close(file); + if (result.uuid != SENSOR_UUID_NONE) { + return &result; + } + } + return nullptr; +} + +void SensorAdjustment::write(SensorAdjustment *adj, uint8_t index) { + uint32_t pos = (uint32_t)SENSOR_ADJUSTMENT_SIZE * index; + + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); + if (file) { + SensorAdjustment disabled(0, SENSOR_UUID_NONE, 0, nullptr); + + uint32_t cur_size = file_size(file); + if (cur_size < pos) { + file_seek(file, 0, FileSeekMode::End); + while (cur_size < pos) { + file_write(file, &disabled, SENSOR_ADJUSTMENT_SIZE); + cur_size += SENSOR_ADJUSTMENT_SIZE; + } + } + + file_seek(file, pos, FileSeekMode::Set); + SensorAdjustment *to_write = adj ? adj : &disabled; + file_write(file, to_write, SENSOR_ADJUSTMENT_SIZE); + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } +} + +// --------------------------------------------------------------------------- +// Sensor file I/O +// --------------------------------------------------------------------------- + +#include "ensemble_sensor.h" +#include "weather_sensor.h" +#include "ads1115_sensor.h" + +static void sensor_memory_init(sensor_memory_t &m, Sensor *sensor) { + m.interval = sensor->interval; + m.flags = sensor->flags; + m.uuid = sensor->uuid; + m.next_update = 0; + m.value = sensor->get_initial_value(); +} + +Sensor *Sensor::parse(os_file_type file) { + static uint8_t sensor_scratchpad[sizeof(OpenSprinkler::SensorUnion)] __attribute__((aligned(4))); + static Sensor *active_sensor = nullptr; + + if (active_sensor != nullptr) { + active_sensor->~Sensor(); + active_sensor = nullptr; + } + uint32_t len = 0; + file_read(file, &len, sizeof(len)); + + if (len == 0 || len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) return nullptr; + + file_read(file, tmp_buffer, len); + file_seek(file, TMP_BUFFER_SIZE - sizeof(uint32_t) - len, FileSeekMode::Current); + + if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) return nullptr; + + SensorType sensor_type = static_cast(*tmp_buffer); + switch (sensor_type) { + case SensorType::Ensemble: + active_sensor = new (sensor_scratchpad) EnsembleSensor(os.sensors, (char*)tmp_buffer); + break; + case SensorType::ADS1115: + active_sensor = new (sensor_scratchpad) ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + break; + case SensorType::Weather: + active_sensor = new (sensor_scratchpad) WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); + break; + default: + return nullptr; + } + return active_sensor; +} + +Sensor *Sensor::get(uint8_t index) { + uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, pos, FileSeekMode::Set); + Sensor *result = Sensor::parse(file); + file_close(file); + return result; + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + return nullptr; + } +} + +void Sensor::write(Sensor *sensor, uint8_t index) { + uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; + uint32_t len = 0; + + // Zero data area before serializing so the full TMP_BUFFER_SIZE slot is clean. + memset(tmp_buffer, 0, TMP_BUFFER_SIZE - sizeof(uint32_t)); + if (sensor) { + len = sensor->serialize(tmp_buffer); + if (len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) + len = TMP_BUFFER_SIZE - sizeof(uint32_t); + } + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); + if (file) { + file_seek(file, pos, FileSeekMode::Set); + file_write(file, &len, sizeof(len)); + file_write(file, tmp_buffer, TMP_BUFFER_SIZE - sizeof(uint32_t)); // always write full slot + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } +} + +void Sensor::load_count() { + OpenSprinkler::nsensors = file_read_byte(SENSORS_FILENAME, 0); +} + +void Sensor::save_count() { + file_write_byte(SENSORS_FILENAME, 0, OpenSprinkler::nsensors); +} + +unsigned char Sensor::add(Sensor *sensor) { + if (OpenSprinkler::nsensors >= MAX_SENSORS) return 0; + + Sensor::write(sensor, OpenSprinkler::nsensors); + sensor_memory_init(OpenSprinkler::sensors[OpenSprinkler::nsensors], sensor); + + OpenSprinkler::nsensors++; + Sensor::save_count(); + return 1; +} + +unsigned char Sensor::modify(uint8_t index, Sensor *sensor) { + if (index >= OpenSprinkler::nsensors) return 0; + Sensor::write(sensor, index); + sensor_memory_init(OpenSprinkler::sensors[index], sensor); + return 1; +} + +unsigned char Sensor::del(uint8_t index) { + if (index >= OpenSprinkler::nsensors) return 0; + if (OpenSprinkler::nsensors == 0) return 0; + + // erase by copying backward + for (uint8_t i = index; i < OpenSprinkler::nsensors - 1; i++) { + file_copy_block(SENSORS_FILENAME, 1 + (uint32_t)(i + 1) * TMP_BUFFER_SIZE, 1 + (uint32_t)i * TMP_BUFFER_SIZE, TMP_BUFFER_SIZE, tmp_buffer); + // also shift in-memory state + OpenSprinkler::sensors[i] = OpenSprinkler::sensors[i + 1]; + } + + OpenSprinkler::nsensors--; + OpenSprinkler::sensors[OpenSprinkler::nsensors].interval = 0; + OpenSprinkler::sensors[OpenSprinkler::nsensors].uuid = 0; + + Sensor::save_count(); + return 1; +} + +void Sensor::load_all() { + OpenSprinkler::lcd.clear(); + OpenSprinkler::lcd.setCursor(0, 0); + OpenSprinkler::lcd_print_pgm(PSTR("Init sensors...")); + + if (!file_exists(SENSORS_FILENAME)) { + DEBUG_PRINTLN(F("Sensor files missing. Initializing...")); + OpenSprinkler::nsensors = 0; + Sensor::save_count(); + } else { + Sensor::load_count(); + } + + Sensor *sensor; + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, 1, FileSeekMode::Set); + for (size_t i = 0; i < OpenSprinkler::nsensors; i++) { + if ((sensor = Sensor::parse(file))) + sensor_memory_init(OpenSprinkler::sensors[i], sensor); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } +} + +uint8_t Sensor::find_index(uint16_t uuid) { + if (uuid == SENSOR_UUID_NONE) return OpenSprinkler::nsensors; + for (uint8_t i = 0; i < OpenSprinkler::nsensors; i++) { + if (OpenSprinkler::sensors[i].uuid == uuid) return i; + } + return MAX_SENSORS; +} + +void Sensor::test_log(uint32_t n_records) { + remove_sensor_log(); + + DEBUG_PRINTF("sensor log test: writing %lu records\n", (unsigned long)n_records); + uint32_t t0 = millis(); + + for (uint32_t i = 0; i < n_records; i++) { + if (OpenSprinkler::nsensors > 0) os.log_sensor((uint8_t)((i + 1) % OpenSprinkler::nsensors), (float)i / 1000.f); + } + + uint32_t write_ms = millis() - t0; + DEBUG_PRINTF("sensor log write: %lu ms total, %.2f ms/record\n", + (unsigned long)write_ms, + n_records ? (float)write_ms / n_records : 0.f); + + os_file_type hfile = open_sensor_log_header(FileOpenMode::Read); + if (!hfile) { + DEBUG_PRINTLN("sensor log test: cannot open header"); + return; + } + SensorLogHeader hdr = {}; + file_read(hfile, &hdr, sizeof(hdr)); + file_close(hfile); + if (hdr.magic != SENSOR_LOG_MAGIC || hdr.version != SENSOR_LOG_VERSION) { + DEBUG_PRINTLN("sensor log test: bad header"); + return; + } + + uint16_t first_file = hdr.wrapped ? (uint16_t)((hdr.cur_file + 1) % hdr.max_files) : 0; + uint16_t total_files = hdr.wrapped ? hdr.max_files : (uint16_t)(hdr.cur_file + 1); + DEBUG_PRINTF("sensor log state: max_files=%u records_per_file=%u cur_file=%u wrapped=%u total_files=%u\n", + hdr.max_files, hdr.records_per_file, hdr.cur_file, hdr.wrapped, total_files); + + uint32_t tr = millis(); + uint32_t count = 0; + for (uint16_t fi = 0; fi < total_files; fi++) { + uint16_t file_no = (first_file + fi) % hdr.max_files; + os_file_type dfile = open_sensor_log(file_no, FileOpenMode::Read); + if (!dfile) continue; + SensorLogRecord rec; + while (file_read(dfile, &rec, sizeof(rec)) == (int)sizeof(rec)) count++; + file_close(dfile); + } + + uint32_t read_ms = millis() - tr; + DEBUG_PRINTF("sensor log read: %lu records in %lu ms (%.2f ms/record)\n", + (unsigned long)count, (unsigned long)read_ms, + count ? (float)read_ms / count : 0.f); +} + +// --------------------------------------------------------------------------- +// Sensor log file helpers +// --------------------------------------------------------------------------- + +void get_sensor_log_filename(char *buf, uint16_t file_no) { + snprintf(buf, 24, "%s%03u", SENSORS_LOG_FILENAME, file_no % 1000); +} + +os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode) { + char fname[24]; + get_sensor_log_filename(fname, file_no); + return file_open(fname, mode); +} + +os_file_type open_sensor_log_header(FileOpenMode mode) { + return file_open(SENSORS_LOG_HEADER_FILENAME, mode); +} + +void remove_sensor_log(int16_t file_no) { + char fname[24]; + if (file_no < 0) { + remove_file(SENSORS_LOG_HEADER_FILENAME); + for (uint16_t i = 0; i < SENSOR_LOG_MAX_FILES; i++) { + get_sensor_log_filename(fname, i); + remove_file(fname); + } + } else { + get_sensor_log_filename(fname, (uint16_t)file_no); + remove_file(fname); + } +} diff --git a/sensors/sensor.h b/sensors/sensor.h new file mode 100644 index 000000000..28403b3f1 --- /dev/null +++ b/sensors/sensor.h @@ -0,0 +1,264 @@ +#pragma once + +// External / analog sensor subsystem +// +// The types in this file (Sensor, SensorAdjustment, etc.) model the *external* +// sensor board — an ADS1115-based I2C ADC add-on that reads analog probes +// (soil moisture, temperature, etc.). It is enabled by the USE_SENSORS guard. +// +// These are DISTINCT from the two onboard digital sensor inputs (SENSOR1 / +// SENSOR2) that are wired directly to GPIO pins. Those are simple binary +// (open/closed) or pulse inputs handled entirely in OpenSprinkler.h/.cpp via +// os.sensor1_status / os.sensor2_status and related IOPT_SENSOR* options. +// No types from this file are involved in the onboard sensor logic. + +#include +#include "../utils.h" +#include "../defines.h" +#include "../bfiller.h" + +#define SENSOR_NAME_LEN 33 +#define SENSOR_CUSTOM_UNIT_LEN 9 + +#define SENSOR_UUID_NONE 0 // sentinel: "no sensor assigned" (0 = uninitialized/disabled) + +// New-sensor defaults — single source of truth for both server_change_sensor and /jsd +#define SENSOR_DEFAULT_NAME "New Sensor" +#define SENSOR_DEFAULT_INTERVAL 15 +#define SENSOR_DEFAULT_UNIT SensorUnit::Millivolt +#define SENSOR_DEFAULT_SCALE 1 +#define SENSOR_DEFAULT_OFFSET 0 +#define SENSOR_DEFAULT_MIN 0 +#define SENSOR_DEFAULT_MAX 5000 +#define SENSOR_DEFAULT_TYPE 1 // SensorType::ADS1115 +#define SENSOR_DEFAULT_FLAGS (1 << SENSOR_FLAG_ENABLE) +#define SENSOR_DEFAULT_FLAG_ENABLE_STR "true" // for JSON flags array in /jsd +#define SENSOR_DEFAULT_FLAG_LOG_STR "false" // for JSON flags array in /jsd + +// Two-level stringify so macro values expand before quoting (for use inside PSTR()) +#define _SENSOR_DEFAULT_STR(x) #x +#define SENSOR_DEFAULT_STR(x) _SENSOR_DEFAULT_STR(x) + +typedef struct { + uint32_t interval; + uint32_t next_update; + float value; + uint16_t uuid; // stable sensor identifier (0 = empty slot) + uint16_t flags; // was uint32_t; only 2 bits used, uint16_t keeps struct at 16 bytes +} sensor_memory_t; + +// Sensor log file format — both structs are tightly packed (no padding) so +// sizeof() gives the exact on-disk byte count and offsetof() gives exact field offsets. +struct __attribute__((packed)) SensorLogHeader { + uint8_t magic; // SENSOR_LOG_MAGIC + uint8_t version; // SENSOR_LOG_VERSION + uint16_t max_files; // number of data files in rotation + uint16_t records_per_file; // max records per data file + uint16_t cur_file; // index of the data file currently being written + uint8_t wrapped; // 1 once all max_files slots have been used at least once + uint8_t reserved[7]; +}; // 16 bytes + +struct __attribute__((packed)) SensorLogRecord { + uint32_t timestamp; // unix epoch seconds + float value; // sensor reading + uint16_t uuid; // sensor UUID (replaces sid+reserved, same 10-byte size) +}; + +enum class SensorType : uint8_t { + Ensemble = 0, + ADS1115, + Weather, + MAX_VALUE, +}; + +// X(id, display_name) +#define SENSOR_UNIT_GROUP_LIST(X) \ + X(None, "No Group") \ + X(Energy, "Energy") \ + X(Flow, "Flow") \ + X(Pressure, "Pressure") \ + X(Temperature, "Temperature") \ + X(Light, "Light") \ + X(Length, "Length") \ + X(Velocity, "Velocity") \ + X(Volume, "Volume") + +// X(id, display_name, short_symbol, group_id) +#define SENSOR_UNIT_LIST(X) \ + X(None, "None", "", None) \ + X(Percent, "Percent", "%", None) \ + X(PartsPerMillion, "Parts Per Million", "ppm", None) \ + X(Millivolt, "Millivolt", "mV", Energy) \ + X(Volt, "Volt", "V", Energy) \ + X(Milliampere, "Milliampere", "mA", Energy) \ + X(Ampere, "Ampere", "A", Energy) \ + X(Ohm, "Ohm", "Ω", Energy) \ + X(Milliohm, "Milliohm", "mΩ", Energy) \ + X(Kiloohm, "Kiloohm", "kΩ", Energy) \ + X(DielectricConstant,"Dielectric Constant","-", Energy) \ + X(LitersPerSecond, "Liters Per Second", "L/s", Flow) \ + X(GallonsPerSecond, "Gallons Per Second", "gal/s", Flow) \ + X(Kilopascal, "Kilopascal", "kPa", Pressure) \ + X(Bar, "Bar", "bar", Pressure) \ + X(Pascal, "Pascal", "Pa", Pressure) \ + X(Torr, "Torr", "torr", Pressure) \ + X(Celsius, "Celsius", "°C", Temperature) \ + X(Fahrenheit, "Fahrenheit", "°F", Temperature) \ + X(Kelvin, "Kelvin", "K", Temperature) \ + X(Lux, "Lux", "lx", Light) \ + X(Lumen, "Lumen", "lm", Light) \ + X(Millimeter, "Millimeter", "mm", Length) \ + X(Centimeter, "Centimeter", "cm", Length) \ + X(Meter, "Meter", "m", Length) \ + X(Kilometer, "Kilometer", "km", Length) \ + X(Inch, "Inch", "in", Length) \ + X(Foot, "Foot", "ft", Length) \ + X(Mile, "Mile", "mi", Length) \ + X(MetersPerSecond, "Meters Per Second", "m/s", Velocity) \ + X(KilometersPerHour, "Kilometers Per Hour","km/h", Velocity) \ + X(MilesPerHour, "Miles Per Hour", "mph", Velocity) + +enum class SensorUnitGroup : uint8_t { +#define X(id, name) id, + SENSOR_UNIT_GROUP_LIST(X) +#undef X + MAX_VALUE, +}; + +enum class SensorUnit : uint8_t { +#define X(id, name, sym, group) id, + SENSOR_UNIT_LIST(X) +#undef X + MAX_VALUE, +}; + +typedef enum { + SENSOR_FLAG_ENABLE = 0, + SENSOR_FLAG_LOG, + SENSOR_FLAG_COUNT +} sensor_flags; + +class Sensor { +public: + Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags); + Sensor(); + virtual ~Sensor() {} + + float get_new_value(); + uint32_t serialize(char *buf); + + static Sensor *parse(os_file_type file); // statically allocated, do not delete + static Sensor *get(uint8_t index); // statically allocated, do not delete + static void write(Sensor *sensor, uint8_t index); + static void load_count(); + static void save_count(); + static unsigned char add(Sensor *sensor); + static unsigned char modify(uint8_t index, Sensor *sensor); // index is positional index + static unsigned char del(uint8_t index); // index is positional index + static void load_all(); + static uint8_t find_index(uint16_t uuid); + static void test_log(uint32_t n_records); + + void virtual emit_extra_json(BufferFiller *bfill) = 0; + + uint32_t interval = 1; + float min = 0.f; + float max = 0.f; + float scale = 0.f; + float offset = 0.f; + uint16_t flags = 0; + uint16_t uuid = 0; // assigned by write_sensor on creation; 0 = not yet assigned + SensorUnit unit = SensorUnit::None; + char name[SENSOR_NAME_LEN] = {0}; + + SensorType virtual get_sensor_type() = 0; + float virtual get_initial_value() = 0; + + private: + float virtual _get_raw_value() = 0; + protected: + uint32_t _deserialize(char *buf); + uint32_t virtual _serialize_internal(char *buf) = 0; +}; + +// X(id, display_name) +#define ENSEMBLE_ACTION_LIST(X) \ + X(Min, "Min") \ + X(Max, "Max") \ + X(Average, "Average") \ + X(Sum, "Sum") \ + X(Product, "Product") + +enum class EnsembleAction : uint8_t { +#define X(id, name) id, + ENSEMBLE_ACTION_LIST(X) +#undef X + MAX_VALUE, +}; + +typedef Sensor* (*SensorGetter)(uint8_t); + +enum class WeatherAction { + MAX_VALUE, +}; + +typedef float (*WeatherGetter)(WeatherAction); + +typedef struct { + float x; + float y; +} sensor_adjustment_point_t; + +#define SENSOR_ADJUSTMENT_POINTS 8 + +typedef enum { + SENADJ_FLAG_ENABLE = 0, +} senadj_flags; + +class SensorAdjustment { +public: + SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); + + static SensorAdjustment *read(uint8_t index, uint8_t nprograms); // returns statically allocated object, do not delete + static void write(SensorAdjustment *adj, uint8_t index); + + float get_adjustment_factor(sensor_memory_t *sensors); + + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; + uint16_t uuid; // sensor UUID (SENSOR_UUID_NONE = adjustment disabled) + uint8_t flags; + uint8_t point_count; +}; + +#define SENSOR_ADJUSTMENT_SIZE sizeof(SensorAdjustment) + +// Serialization helpers used by all sensor types +template +inline uint32_t write_buf(char* buf, T val) { + memcpy(buf, &val, sizeof(val)); + return sizeof(val); +} + +template +inline T read_buf(char* buf, uint32_t* i) { + T val; + memcpy(&val, buf + (*i), sizeof(T)); + *i += sizeof(T); + return val; +} + +const char *enum_string(SensorUnitGroup group); +const char *enum_string(EnsembleAction action); +const char *enum_string(WeatherAction action); + +const char* get_sensor_unit_name(SensorUnit unit); +const char* get_sensor_unit_short(SensorUnit unit); +const SensorUnitGroup get_sensor_unit_group(SensorUnit unit); +const uint32_t get_sensor_unit_index(SensorUnit unit); + +// Sensor log file helpers +void get_sensor_log_filename(char *buf, uint16_t file_no); +os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); +os_file_type open_sensor_log_header(FileOpenMode mode); +void remove_sensor_log(int16_t file_no = -1); // -1 removes header + all data files diff --git a/sensors/weather_sensor.cpp b/sensors/weather_sensor.cpp new file mode 100644 index 000000000..1ea39a575 --- /dev/null +++ b/sensors/weather_sensor.cpp @@ -0,0 +1,43 @@ +#include "weather_sensor.h" + +WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, WeatherGetter weather_getter, WeatherAction action) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + action(action), + weather_getter(weather_getter) { +} + +void WeatherSensor::emit_extra_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"action\":$D}"), this->action); +} + +void WeatherSensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR( + "{\"name\":\"Weather Sensor\"," + "\"args\":[" + "{\"name\":\"Weather Information\"," + "\"arg\":\"action\"," + "\"type\":\"enum::WeatherAction\"," + "\"default\":\"0\"}" + "]}" + )); +} + +float WeatherSensor::get_initial_value() { + return 0.f; +} + +float WeatherSensor::_get_raw_value() { + return this->weather_getter(this->action); +} + +uint32_t WeatherSensor::_serialize_internal(char* buf) { + uint32_t i = 0; + buf[i++] = static_cast(this->action); + return i; +} + +WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) { + uint32_t i = Sensor::_deserialize(buf); + this->action = static_cast(buf[i++]); + this->weather_getter = weather_getter; +} diff --git a/sensors/weather_sensor.h b/sensors/weather_sensor.h new file mode 100644 index 000000000..6c7c4eaf6 --- /dev/null +++ b/sensors/weather_sensor.h @@ -0,0 +1,26 @@ +#pragma once + +#include "sensor.h" + +class WeatherSensor : public Sensor { + public: + WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(WeatherGetter weather_getter, char *buf); + + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); + + SensorType get_sensor_type() { + return SensorType::Weather; + } + + WeatherAction action; + + float get_initial_value(); + + private: + float _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + WeatherGetter weather_getter; +}; From dc1b290d4934866b4e97d9efd676db4970420713 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Wed, 29 Apr 2026 19:09:57 -0400 Subject: [PATCH 106/115] rename some variable and class names; remove scale and offset from base class and move them into specific classes like ADS1115 as not all sensors need scale and offset (AggregateSensor and WeatherSensor generally don't need that) --- OpenSprinkler.cpp | 4 +- OpenSprinkler.h | 4 +- opensprinkler_server.cpp | 108 +++++++++++++-------------- program.h | 2 +- sensors/ads1115_sensor.cpp | 32 ++++++-- sensors/ads1115_sensor.h | 7 +- sensors/aggregate_sensor.cpp | 135 ++++++++++++++++++++++++++++++++++ sensors/aggregate_sensor.h | 40 ++++++++++ sensors/ensemble_sensor.cpp | 139 ----------------------------------- sensors/ensemble_sensor.h | 37 ---------- sensors/sensor.cpp | 35 ++++----- sensors/sensor.h | 35 ++++----- sensors/weather_sensor.cpp | 4 +- sensors/weather_sensor.h | 2 +- 14 files changed, 297 insertions(+), 287 deletions(-) create mode 100644 sensors/aggregate_sensor.cpp create mode 100644 sensors/aggregate_sensor.h delete mode 100644 sensors/ensemble_sensor.cpp delete mode 100644 sensors/ensemble_sensor.h diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index bb580989e..0e42abdea 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2593,13 +2593,13 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { void OpenSprinkler::poll_sensors() { for (uint8_t i = 0; i < nsensors; i++) { - if (sensors[i].interval && (sensors[i].flags & (1 << SENSOR_FLAG_ENABLE))) { + if (sensors[i].interval && (sensors[i].flag & (1 << SENSOR_FLAG_ENABLE))) { if ((long)(millis() - sensors[i].next_update) > 0) { Sensor *sensor = Sensor::get(i); if (sensor) { sensors[i].value = sensor->get_new_value(); sensors[i].next_update = millis() + (sensors[i].interval * 1000 * 60); - if (sensors[i].flags & (1 << SENSOR_FLAG_LOG)) { + if (sensors[i].flag & (1 << SENSOR_FLAG_LOG)) { os.log_sensor(i, sensors[i].value); } } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 1e46628f7..702f0945e 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -69,7 +69,7 @@ #if defined(USE_SENSORS) #include "sensors/sensor.h" -#include "sensors/ensemble_sensor.h" +#include "sensors/aggregate_sensor.h" #include "sensors/weather_sensor.h" #endif @@ -250,7 +250,7 @@ class OpenSprinkler { #if defined(USE_SENSORS) union SensorUnion { ADS1115Sensor ads1115; - EnsembleSensor ensemble; + AggregateSensor aggregate; WeatherSensor weather; }; static sensor_memory_t sensors[MAX_SENSORS]; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7da6f702e..3d32096b3 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -845,13 +845,13 @@ void server_change_program(OTF_PARAMS_DEF) { char *end; SensorAdjustment *adj = nullptr; - uint32_t flags = 0; + uint32_t flag = 0; uint32_t adj_uuid = SENSOR_UUID_NONE; uint32_t point_count = 0; sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; if ((adj = SensorAdjustment::read(pid, pd.nprograms))) { - flags = adj->flags; + flag = adj->flag; adj_uuid = adj->uuid; point_count = adj->point_count; @@ -860,8 +860,8 @@ void server_change_program(OTF_PARAMS_DEF) { } } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flags"), true)) { - flags=strtoul(tmp_buffer, &end, 10); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flag"), true)) { + flag=strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } @@ -891,7 +891,7 @@ void server_change_program(OTF_PARAMS_DEF) { point_count = i; } - SensorAdjustment snadj(flags, (uint16_t)adj_uuid, point_count, points); + SensorAdjustment snadj(flag, (uint16_t)adj_uuid, point_count, points); snadj_ptr = &snadj; #endif @@ -1056,7 +1056,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { { SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); if (adj) { - bfill.emit_p(PSTR("{\"flags\":$D,\"uuid\":$D,\"splits\":["), adj->flags, adj->uuid); + bfill.emit_p(PSTR("{\"flag\":$D,\"uuid\":$D,\"splits\":["), adj->flag, adj->uuid); for (int j = 0; j < adj->point_count; j++) { if (j) bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); @@ -1857,7 +1857,7 @@ void server_json_sensors_main(OTF_PARAMS_DEF) { for (size_t i = 0; i < os.nsensors; i++) { if (os.sensors[i].interval && (sensor = Sensor::get(i))) { if (sensor_count) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"uuid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), sensor->uuid, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); + bfill.emit_p(PSTR("{\"uuid\":$D,\"name\":\"$S\",\"unit\":$D,\"flag\":$D,\"interval\":$L,\"min\":$E,\"max\":$E,\"value\":$E,\"type\":$D,\"extra\":"), sensor->uuid, sensor->name, static_cast(sensor->unit), sensor->flag, sensor->interval, sensor->min, sensor->max, os.sensors[i].value, static_cast(sensor->get_sensor_type())); sensor->emit_extra_json(&bfill); bfill.emit_p(PSTR("}")); sensor_count += 1; @@ -1888,14 +1888,14 @@ void server_json_sensors(OTF_PARAMS_DEF) * uuid: sensor stable ID (1-65535; -1 to add new) * sid: sensor positional index (0-based; -1 to add new) * (uuid takes precedence if both are provided) - * type: sensor type (0: Ensemble, 1: ADS1115, 2: Weather) + * type: sensor type (0: Aggregate, 1: ADS1115, 2: Weather) * name: sensor name - * min/max/scale/offset: calibration values + * min/max: output clamping range * interval: sampling interval in minutes * unit: sensor unit index - * flags: bitmask (bit 0: enable, bit 1: log) - * [Ensemble] children: semicolon separated list of "uuid,min,max,scale,offset;" - * [Ensemble] action: ensemble action index (0: Min, 1: Max, 2: Average, 3: Sum, 4: Product) + * flag: bitmask (bit 0: enable, bit 1: log) + * [Aggregate] children: semicolon separated list of "uuid,min,max;" + * [Aggregate] action: aggregate action index (0: Min, 1: Max, 2: Average, 3: Sum, 4: Median, 5: Range) * [ADS1115] pin: pin number (1-16) * [Weather] action: weather information index */ @@ -1944,11 +1944,9 @@ void server_change_sensor(OTF_PARAMS_DEF) { Sensor *sensor = nullptr; float min = SENSOR_DEFAULT_MIN; float max = SENSOR_DEFAULT_MAX; - float scale = SENSOR_DEFAULT_SCALE; - float offset = SENSOR_DEFAULT_OFFSET; uint32_t interval = SENSOR_DEFAULT_INTERVAL; SensorUnit unit = SENSOR_DEFAULT_UNIT; - uint16_t flags = 0; + uint16_t flag = SENSOR_DEFAULT_FLAG; char name[SENSOR_NAME_LEN]; strncpy(name, SENSOR_DEFAULT_NAME, SENSOR_NAME_LEN); @@ -1960,11 +1958,9 @@ void server_change_sensor(OTF_PARAMS_DEF) { strncpy(name, sensor->name, SENSOR_NAME_LEN); min = sensor->min; max = sensor->max; - scale = sensor->scale; - offset = sensor->offset; interval = sensor->interval; unit = sensor->unit; - flags = sensor->flags; + flag = sensor->flag; } } @@ -1984,16 +1980,6 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { - scale=strtod(tmp_buffer, &end); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) { - offset=strtod(tmp_buffer, &end); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("interval"), true)) { interval=strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); @@ -2008,26 +1994,26 @@ void server_change_sensor(OTF_PARAMS_DEF) { unit = static_cast(unit_raw); } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flags"), true)) { - flags = (uint16_t)strtoul(tmp_buffer, &end, 10); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flag"), true)) { + flag = (uint16_t)strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); } Sensor *result_sensor; switch (sensor_type) { - case SensorType::Ensemble: { + case SensorType::Aggregate: { uint8_t children_count = 0; - ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + aggregate_children_t children[AGGREGATE_SENSOR_CHILDREN_COUNT]; + for (size_t i = 0; i < AGGREGATE_SENSOR_CHILDREN_COUNT; i++) { children[i].uuid = SENSOR_UUID_NONE; } - EnsembleAction action = EnsembleAction::Min; + AggregateAction action = AggregateAction::Min; if (sensor_type == original_sensor_type) { if ((sensor = Sensor::get(sid))) { - EnsembleSensor* e = static_cast(sensor); - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + AggregateSensor* e = static_cast(sensor); + for (size_t i = 0; i < AGGREGATE_SENSOR_CHILDREN_COUNT; i++) { children[i] = e->children[i]; } @@ -2038,23 +2024,23 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("children"), true)) { unsigned int i = 0; int d; - float d1, d2, d3, d4; + float d1, d2; const char *ptr = tmp_buffer; int result; while (*ptr != '\0') { - if (i >= ENSEMBLE_SENSOR_CHILDREN_COUNT) handle_return(HTML_DATA_FORMATERROR); + if (i >= AGGREGATE_SENSOR_CHILDREN_COUNT) handle_return(HTML_DATA_FORMATERROR); - result = sscanf(ptr, "%d,%f,%f,%f,%f;", &d, &d1, &d2, &d3, &d4); + result = sscanf(ptr, "%d,%f,%f;", &d, &d1, &d2); - if (result != 5) { + if (result != 3) { handle_return(HTML_DATA_FORMATERROR); } // d is the child sensor's UUID; out-of-range values map to disabled uint16_t child_uuid = (d >= 1 && d <= 0xFFFF) ? (uint16_t)d : SENSOR_UUID_NONE; - children[i++] = ensemble_children_t {d1, d2, d3, d4, child_uuid}; + children[i++] = aggregate_children_t {d1, d2, child_uuid}; while (*ptr != '\0' && *(ptr++) != ';') {} } @@ -2065,22 +2051,26 @@ void server_change_sensor(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { uint32_t action_raw = strtol(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (action_raw >= (uint32_t)EnsembleAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); - action = static_cast(action_raw); + if (action_raw >= (uint32_t)AggregateAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + action = static_cast(action_raw); } - result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.sensors, children, children_count, action); + result_sensor = new AggregateSensor(interval, min, max, (const char*)&name, unit, flag, os.sensors, children, children_count, action); break; } case SensorType::ADS1115: { uint32_t sensor_index = 0; uint32_t sensor_pin = 0; + float scale = ADS1115_DEFAULT_SCALE; + float offset = ADS1115_DEFAULT_OFFSET; if (sensor_type == original_sensor_type) { if ((sensor = Sensor::get(sid))) { ADS1115Sensor* e = static_cast(sensor); sensor_index = e->sensor_index; sensor_pin = e->pin; + scale = e->scale; + offset = e->offset; } } @@ -2093,7 +2083,17 @@ void server_change_sensor(OTF_PARAMS_DEF) { sensor_pin = raw_sensor_pin & 0b11; } - result_sensor = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.ads1115_devices, sensor_index, sensor_pin); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { + scale = strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) { + offset = strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + result_sensor = new ADS1115Sensor(interval, min, max, (const char*)&name, unit, flag, os.ads1115_devices, sensor_index, sensor_pin, scale, offset); break; } case SensorType::Weather: { @@ -2113,7 +2113,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { action = static_cast(action_raw); } - result_sensor = new WeatherSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.get_sensor_weather_data, action); + result_sensor = new WeatherSensor(interval, min, max, (const char*)&name, unit, flag, os.get_sensor_weather_data, action); break; } @@ -2124,7 +2124,7 @@ void server_change_sensor(OTF_PARAMS_DEF) { } os.sensors[sid].interval = interval; - os.sensors[sid].flags = flags; + os.sensors[sid].flag = flag; os.sensors[sid].next_update = 0; os.sensors[sid].value = result_sensor->get_initial_value(); @@ -2455,8 +2455,8 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE); i++) { if (i) bfill.emit_p(PSTR(",")); switch (static_cast(i)) { - case SensorType::Ensemble: - EnsembleSensor::emit_description_json(&bfill); + case SensorType::Aggregate: + AggregateSensor::emit_description_json(&bfill); break; case SensorType::ADS1115: ADS1115Sensor::emit_description_json(&bfill); @@ -2479,7 +2479,7 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("],\"enums\":{")); bfill_enum_values(PSTR("SensorUnitGroup")); bfill.emit_p(PSTR(",")); - bfill_enum_values(PSTR("EnsembleAction")); + bfill_enum_values(PSTR("AggregateAction")); bfill.emit_p(PSTR(",")); bfill_enum_values(PSTR("WeatherAction")); bfill.emit_p(PSTR("}")); @@ -2491,16 +2491,16 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { )); bfill.emit_p(PSTR("{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"default\":\"$D\"},"), static_cast(SENSOR_DEFAULT_UNIT)); bfill.emit_p(PSTR( - "{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_SCALE) "\"}," - "{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_OFFSET) "\"}," "{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_MIN) "\"}," "{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_MAX) "\"}," "{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_TYPE) "\"}" "]" )); - static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here - bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]]")); + static_assert(SENSOR_FLAG_COUNT == 2); // If this fails, update the flags array below + bfill.emit_p(PSTR(",\"flags\":[{\"name\":\"Enabled\",\"default\":$D},{\"name\":\"Logging\",\"default\":$D}]"), + (SENSOR_DEFAULT_FLAG >> SENSOR_FLAG_ENABLE) & 1, + (SENSOR_DEFAULT_FLAG >> SENSOR_FLAG_LOG) & 1); bfill.emit_p(PSTR("}")); } diff --git a/program.h b/program.h index c985d1a4f..cc0c74a42 100644 --- a/program.h +++ b/program.h @@ -79,7 +79,7 @@ class ProgramStruct { unsigned char en_daterange:1; // weekly: days[0][0..6] correspond to Monday till Sunday - // single-run:days[0] and [1] store the epoch time in days of the start + // single-run:days[0] and [1] store the epoch time in days of the start // monthly: days[0][0..5] stores the day of the month (32 means last day of month) // interval: days[1] stores the interval (0 to 255), days[0] stores the starting day remainder (0 to 254) unsigned char days[2]; diff --git a/sensors/ads1115_sensor.cpp b/sensors/ads1115_sensor.cpp index b1f3044af..56b7d954e 100644 --- a/sensors/ads1115_sensor.cpp +++ b/sensors/ads1115_sensor.cpp @@ -2,14 +2,19 @@ #if defined(USE_SENSORS) -ADS1115Sensor::ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : - Sensor(interval, min, max, scale, offset, name, unit, flags), +ADS1115Sensor::ADS1115Sensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint16_t flag, ADS1115** sensors, uint8_t sensor_index, uint8_t pin, float scale, float offset) : + Sensor(interval, min, max, name, unit, flag), sensor_index(sensor_index), pin(pin), + scale(scale), + offset(offset), sensors(sensors) {} void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { - bfill->emit_p(PSTR("{\"pin\":$D}"), ((this->sensor_index << 2) + this->pin + 1)); + bfill->emit_p(PSTR("{\"pin\":$D,\"scale\":$E,\"offset\":$E}"), + ((this->sensor_index << 2) + this->pin + 1), + this->scale, + this->offset); } void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { @@ -19,7 +24,15 @@ void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { "{\"name\":\"Pin Number\"," "\"arg\":\"pin\"," "\"type\":\"int::[1,16]\"," - "\"default\":\"1\"}" + "\"default\":\"1\"}," + "{\"name\":\"Linear Scale\"," + "\"arg\":\"scale\"," + "\"type\":\"float\"," + "\"default\":\"" SENSOR_DEFAULT_STR(ADS1115_DEFAULT_SCALE) "\"}," + "{\"name\":\"Value Offset\"," + "\"arg\":\"offset\"," + "\"type\":\"float\"," + "\"default\":\"" SENSOR_DEFAULT_STR(ADS1115_DEFAULT_OFFSET) "\"}" "]}" )); } @@ -32,22 +45,25 @@ float ADS1115Sensor::_get_raw_value() { if (this->sensors[sensor_index] == nullptr) { return 0.0; } - else { - return ((float)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; - } + float raw = ((float)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; + return raw * this->scale + this->offset; } uint32_t ADS1115Sensor::_serialize_internal(char *buf) { uint32_t i = 0; buf[i++] = static_cast(this->sensor_index); buf[i++] = static_cast(this->pin); + i += write_buf(buf + i, this->scale); + i += write_buf(buf + i, this->offset); return i; } ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf) { uint32_t i = Sensor::_deserialize(buf); this->sensor_index = static_cast(buf[i++]); - this->pin = static_cast(buf[i++]); + this->pin = static_cast(buf[i++]); + this->scale = read_buf(buf, &i); + this->offset = read_buf(buf, &i); this->sensors = sensors; } diff --git a/sensors/ads1115_sensor.h b/sensors/ads1115_sensor.h index af482fc29..9ce0290d6 100644 --- a/sensors/ads1115_sensor.h +++ b/sensors/ads1115_sensor.h @@ -7,9 +7,12 @@ #include "sensor.h" #include "../ads1115.h" +#define ADS1115_DEFAULT_SCALE 1 +#define ADS1115_DEFAULT_OFFSET 0 + class ADS1115Sensor : public Sensor { public: - ADS1115Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin, float scale, float offset); ADS1115Sensor(ADS1115 **sensors, char *buf); void emit_extra_json(BufferFiller *bfill); @@ -21,6 +24,8 @@ class ADS1115Sensor : public Sensor { uint8_t sensor_index; uint8_t pin; + float scale; + float offset; float get_initial_value(); diff --git a/sensors/aggregate_sensor.cpp b/sensors/aggregate_sensor.cpp new file mode 100644 index 000000000..3620a8b75 --- /dev/null +++ b/sensors/aggregate_sensor.cpp @@ -0,0 +1,135 @@ +#include "aggregate_sensor.h" +#include "../OpenSprinkler.h" + +extern OpenSprinkler os; + +AggregateSensor::AggregateSensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint16_t flag, sensor_memory_t* sensors, aggregate_children_t* children, uint8_t children_count, AggregateAction action) : + Sensor(interval, min, max, name, unit, flag), + action(action), + sensors(sensors) { + for (size_t i = 0; i < AGGREGATE_SENSOR_CHILDREN_COUNT; i++) { + if (i < children_count) { + this->children[i] = children[i]; + } else { + this->children[i] = aggregate_children_t{ AGGREGATE_CHILD_DEFAULT_MIN, AGGREGATE_CHILD_DEFAULT_MAX, SENSOR_UUID_NONE }; + } + } +} + +void AggregateSensor::emit_extra_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); + for (size_t i = 0; i < AGGREGATE_SENSOR_CHILDREN_COUNT; i++) { + if (i) bfill->emit_p(PSTR(",")); + aggregate_children_t* child = &this->children[i]; + bfill->emit_p(PSTR("{\"uuid\":$D,\"min\":$E,\"max\":$E}"), child->uuid, child->min, child->max); + } + bfill->emit_p(PSTR("]}")); +} + +void AggregateSensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR( + "{\"name\":\"Aggregate Sensor\"," + "\"args\":[" + "{\"name\":\"Child Sensors\"," + "\"arg\":\"children\"," + "\"type\":\"array::" SENSOR_DEFAULT_STR(AGGREGATE_SENSOR_CHILDREN_COUNT) "\"," + "\"extra\":[" + "{\"name\":\"Sensor UUID\",\"arg\":\"uuid\",\"type\":\"sensor\",\"default\":\"0\"}," + "{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"-3.4e+38\"}," + "{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"3.4e+38\"}" + "]}," + "{\"name\":\"Aggregate Action\"," + "\"arg\":\"action\"," + "\"type\":\"enum::AggregateAction\"," + "\"default\":\"0\"}" + "]}" + )); +} + +float AggregateSensor::get_initial_value() { + return 0.0f; +} + +float AggregateSensor::_get_raw_value() { + float values[AGGREGATE_SENSOR_CHILDREN_COUNT]; + uint8_t count = 0; + + for (size_t i = 0; i < AGGREGATE_SENSOR_CHILDREN_COUNT; i++) { + if (this->children[i].uuid == SENSOR_UUID_NONE) continue; + uint8_t idx = Sensor::find_index(this->children[i].uuid); + if (idx >= OpenSprinkler::nsensors || !sensors[idx].interval) continue; + + float value = sensors[idx].value; + if (value < this->children[i].min) value = this->children[i].min; + if (value > this->children[i].max) value = this->children[i].max; + values[count++] = value; + } + + if (count == 0) return 0.0f; + + switch (this->action) { + case AggregateAction::Min: { + float result = values[0]; + for (uint8_t i = 1; i < count; i++) if (values[i] < result) result = values[i]; + return result; + } + case AggregateAction::Max: { + float result = values[0]; + for (uint8_t i = 1; i < count; i++) if (values[i] > result) result = values[i]; + return result; + } + case AggregateAction::Average: { + float sum = 0; + for (uint8_t i = 0; i < count; i++) sum += values[i]; + return sum / count; + } + case AggregateAction::Sum: { + float sum = 0; + for (uint8_t i = 0; i < count; i++) sum += values[i]; + return sum; + } + case AggregateAction::Median: { + // Insertion sort (up to AGGREGATE_SENSOR_CHILDREN_COUNT elements) + for (uint8_t i = 1; i < count; i++) { + float key = values[i]; + int8_t j = i - 1; + while (j >= 0 && values[j] > key) { values[j + 1] = values[j]; j--; } + values[j + 1] = key; + } + if (count % 2 == 1) return values[count / 2]; + return (values[count / 2 - 1] + values[count / 2]) / 2.0f; + } + case AggregateAction::Range: { + float lo = values[0], hi = values[0]; + for (uint8_t i = 1; i < count; i++) { + if (values[i] < lo) lo = values[i]; + if (values[i] > hi) hi = values[i]; + } + return hi - lo; + } + default: + return 0.0f; + } +} + +uint32_t AggregateSensor::_serialize_internal(char* buf) { + uint32_t i = 0; + for (size_t j = 0; j < AGGREGATE_SENSOR_CHILDREN_COUNT; j++) { + i += write_buf(buf + i, this->children[j].uuid); + i += write_buf(buf + i, this->children[j].min); + i += write_buf(buf + i, this->children[j].max); + } + buf[i++] = static_cast(this->action); + return i; +} + +AggregateSensor::AggregateSensor(sensor_memory_t* sensors, char* buf) { + uint32_t i = Sensor::_deserialize(buf); + for (size_t j = 0; j < AGGREGATE_SENSOR_CHILDREN_COUNT; j++) { + this->children[j].uuid = read_buf(buf, &i); + this->children[j].min = read_buf(buf, &i); + this->children[j].max = read_buf(buf, &i); + } + this->action = static_cast(buf[i++]); + this->sensors = sensors; +} diff --git a/sensors/aggregate_sensor.h b/sensors/aggregate_sensor.h new file mode 100644 index 000000000..2009ace26 --- /dev/null +++ b/sensors/aggregate_sensor.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include "sensor.h" + +typedef struct { + float min; + float max; + uint16_t uuid; // UUID of child sensor (SENSOR_UUID_NONE = unused slot) +} aggregate_children_t; + +#define AGGREGATE_SENSOR_CHILDREN_COUNT 8 + +// Defaults for newly-initialized child slots +#define AGGREGATE_CHILD_DEFAULT_MIN (-FLT_MAX) +#define AGGREGATE_CHILD_DEFAULT_MAX (FLT_MAX) + +class AggregateSensor : public Sensor { + public: + AggregateSensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, sensor_memory_t *sensors, aggregate_children_t *children, uint8_t children_count, AggregateAction action); + AggregateSensor(sensor_memory_t *sensors, char *buf); + + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); + + SensorType get_sensor_type() { + return SensorType::Aggregate; + } + + aggregate_children_t children[AGGREGATE_SENSOR_CHILDREN_COUNT]; + AggregateAction action; + + float get_initial_value(); + + private: + float _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + sensor_memory_t *sensors; +}; diff --git a/sensors/ensemble_sensor.cpp b/sensors/ensemble_sensor.cpp deleted file mode 100644 index 4aecfd239..000000000 --- a/sensors/ensemble_sensor.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include "ensemble_sensor.h" -#include "../OpenSprinkler.h" - -extern OpenSprinkler os; - -EnsembleSensor::EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : - Sensor(interval, min, max, scale, offset, name, unit, flags), - action(action), - sensors(sensors) { - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - if (i < children_count) { - this->children[i] = children[i]; - } - else { - this->children[i] = ensemble_children_t{ 0.f, 0.f, 0.f, 0.f, SENSOR_UUID_NONE }; - } - } -} - -void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { - bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - if (i) bfill->emit_p(PSTR(",")); - ensemble_children_t* child = &this->children[i]; - bfill->emit_p(PSTR("{\"uuid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->uuid, child->max, child->min, child->scale, child->offset); - } - bfill->emit_p(PSTR("]}")); -} - -void EnsembleSensor::emit_description_json(BufferFiller* bfill) { - bfill->emit_p(PSTR( - "{\"name\":\"Ensemble Sensor\"," - "\"args\":[" - "{\"name\":\"Argument Sensors\"," - "\"arg\":\"children\"," - "\"type\":\"array::4\"," - "\"extra\":[" - "{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\"}," - "{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"\"}," - "{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"\"}," - "{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"\"}," - "{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"\"}" - "]}," - "{\"name\":\"Ensemble Action\"," - "\"arg\":\"action\"," - "\"type\":\"enum::EnsembleAction\"," - "\"default\":\"0\"}" - "]}" - )); -} - -float EnsembleSensor::get_initial_value() { - switch (this->action) { - case EnsembleAction::Min: - return this->max; - case EnsembleAction::Max: - return this->min; - case EnsembleAction::Average: - case EnsembleAction::Sum: - return 0; - case EnsembleAction::Product: - return 1; - default: - return 0.f; - } -} - -float EnsembleSensor::_get_raw_value() { - float inital = this->get_initial_value(); - uint8_t count = 0; - - for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { - uint8_t sensor = Sensor::find_index(this->children[i].uuid); - if (sensor < OpenSprinkler::nsensors && sensors[sensor].interval) { - float value = sensors[sensor].value; - value = (value * this->children[i].scale) + this->children[i].offset; - if (value < this->children[i].min) value = this->children[i].min; - if (value > this->children[i].max) value = this->children[i].max; - - switch (this->action) { - case EnsembleAction::Min: - if (value < inital) inital = value; - break; - case EnsembleAction::Max: - if (value > inital) inital = value; - break; - case EnsembleAction::Average: - case EnsembleAction::Sum: - inital += value; - break; - case EnsembleAction::Product: - inital *= value; - break; - default: - return 0.f; - } - - count += 1; - } - } - - if (count == 0) { - return 0.f; - } - else if (this->action == EnsembleAction::Average) { - return inital / (float)count; - } - else { - return inital; - } -} - -uint32_t EnsembleSensor::_serialize_internal(char* buf) { - uint32_t i = 0; - for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - i += write_buf(buf + i, this->children[j].uuid); - i += write_buf(buf + i, this->children[j].min); - i += write_buf(buf + i, this->children[j].max); - i += write_buf(buf + i, this->children[j].scale); - i += write_buf(buf + i, this->children[j].offset); - } - - buf[i++] = static_cast(this->action); - return i; -} - -EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { - uint32_t i = Sensor::_deserialize(buf); - for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { - this->children[j].uuid = read_buf(buf, &i); - this->children[j].min = read_buf(buf, &i); - this->children[j].max = read_buf(buf, &i); - this->children[j].scale = read_buf(buf, &i); - this->children[j].offset = read_buf(buf, &i); - } - - this->action = static_cast(buf[i++]); - this->sensors = sensors; -} diff --git a/sensors/ensemble_sensor.h b/sensors/ensemble_sensor.h deleted file mode 100644 index 8517693fe..000000000 --- a/sensors/ensemble_sensor.h +++ /dev/null @@ -1,37 +0,0 @@ -#pragma once - -#include "sensor.h" - -typedef struct { - float min; - float max; - float scale; - float offset; - uint16_t uuid; // UUID of child sensor (SENSOR_UUID_NONE = unused slot) -} ensemble_children_t; - -#define ENSEMBLE_SENSOR_CHILDREN_COUNT 4 - -class EnsembleSensor : public Sensor { - public: - EnsembleSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); - EnsembleSensor(sensor_memory_t *sensors, char *buf); - - void emit_extra_json(BufferFiller *bfill); - static void emit_description_json(BufferFiller *bfill); - - SensorType get_sensor_type() { - return SensorType::Ensemble; - } - - ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; - EnsembleAction action; - - float get_initial_value(); - - private: - float _get_raw_value(); - uint32_t _serialize_internal(char *buf); - - sensor_memory_t *sensors; -}; diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp index 2413854a4..81ad8b667 100644 --- a/sensors/sensor.cpp +++ b/sensors/sensor.cpp @@ -14,12 +14,12 @@ const char *enum_string(SensorUnitGroup group) { return nullptr; } -const char *enum_string(EnsembleAction action) { +const char *enum_string(AggregateAction action) { switch (action) { - #define X(id, name) case EnsembleAction::id: return PSTR(name); - ENSEMBLE_ACTION_LIST(X) + #define X(id, name) case AggregateAction::id: return PSTR(name); + AGGREGATE_ACTION_LIST(X) #undef X - case EnsembleAction::MAX_VALUE: return nullptr; + case AggregateAction::MAX_VALUE: return nullptr; } return nullptr; } @@ -65,8 +65,8 @@ const uint32_t get_sensor_unit_index(SensorUnit unit) { return static_cast(unit); } -Sensor::Sensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags) : - interval(interval), min(min), max(max), scale(scale), offset(offset), flags(flags), unit(unit) { +Sensor::Sensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint16_t flag) : + interval(interval), min(min), max(max), flag(flag), unit(unit) { strncpy(this->name, name, SENSOR_NAME_LEN); this->name[SENSOR_NAME_LEN - 1] = 0; } @@ -75,7 +75,6 @@ Sensor::Sensor() {} float Sensor::get_new_value() { float value = this->_get_raw_value(); - value = (value * this->scale) + this->offset; if (value < this->min) value = this->min; if (value > this->max) value = this->max; @@ -90,9 +89,7 @@ uint32_t Sensor::serialize(char* buf) { i += SENSOR_NAME_LEN; buf[i++] = static_cast(this->unit); i += write_buf(buf + i, this->interval); - i += write_buf(buf + i, this->flags); - i += write_buf(buf + i, this->scale); - i += write_buf(buf + i, this->offset); + i += write_buf(buf + i, this->flag); i += write_buf(buf + i, this->min); i += write_buf(buf + i, this->max); i += write_buf(buf + i, this->uuid); @@ -108,9 +105,7 @@ uint32_t Sensor::_deserialize(char* buf) { i += SENSOR_NAME_LEN; this->unit = static_cast(buf[i++]); this->interval = read_buf(buf, &i); - this->flags = read_buf(buf, &i); - this->scale = read_buf(buf, &i); - this->offset = read_buf(buf, &i); + this->flag = read_buf(buf, &i); this->min = read_buf(buf, &i); this->max = read_buf(buf, &i); this->uuid = read_buf(buf, &i); @@ -118,8 +113,8 @@ uint32_t Sensor::_deserialize(char* buf) { return i; } -SensorAdjustment::SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { - this->flags = flags; +SensorAdjustment::SensorAdjustment(uint8_t flag, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { + this->flag = flag; this->uuid = uuid; if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; this->point_count = point_count; @@ -129,7 +124,7 @@ SensorAdjustment::SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_c } float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { - if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { + if (this->flag & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { uint8_t idx = Sensor::find_index(this->uuid); if (idx < MAX_SENSORS && sensors[idx].interval) { float value = sensors[idx].value; @@ -210,13 +205,13 @@ void SensorAdjustment::write(SensorAdjustment *adj, uint8_t index) { // Sensor file I/O // --------------------------------------------------------------------------- -#include "ensemble_sensor.h" +#include "aggregate_sensor.h" #include "weather_sensor.h" #include "ads1115_sensor.h" static void sensor_memory_init(sensor_memory_t &m, Sensor *sensor) { m.interval = sensor->interval; - m.flags = sensor->flags; + m.flag = sensor->flag; m.uuid = sensor->uuid; m.next_update = 0; m.value = sensor->get_initial_value(); @@ -242,8 +237,8 @@ Sensor *Sensor::parse(os_file_type file) { SensorType sensor_type = static_cast(*tmp_buffer); switch (sensor_type) { - case SensorType::Ensemble: - active_sensor = new (sensor_scratchpad) EnsembleSensor(os.sensors, (char*)tmp_buffer); + case SensorType::Aggregate: + active_sensor = new (sensor_scratchpad) AggregateSensor(os.sensors, (char*)tmp_buffer); break; case SensorType::ADS1115: active_sensor = new (sensor_scratchpad) ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); diff --git a/sensors/sensor.h b/sensors/sensor.h index 28403b3f1..c58a20b59 100644 --- a/sensors/sensor.h +++ b/sensors/sensor.h @@ -26,14 +26,10 @@ #define SENSOR_DEFAULT_NAME "New Sensor" #define SENSOR_DEFAULT_INTERVAL 15 #define SENSOR_DEFAULT_UNIT SensorUnit::Millivolt -#define SENSOR_DEFAULT_SCALE 1 -#define SENSOR_DEFAULT_OFFSET 0 #define SENSOR_DEFAULT_MIN 0 #define SENSOR_DEFAULT_MAX 5000 #define SENSOR_DEFAULT_TYPE 1 // SensorType::ADS1115 -#define SENSOR_DEFAULT_FLAGS (1 << SENSOR_FLAG_ENABLE) -#define SENSOR_DEFAULT_FLAG_ENABLE_STR "true" // for JSON flags array in /jsd -#define SENSOR_DEFAULT_FLAG_LOG_STR "false" // for JSON flags array in /jsd +#define SENSOR_DEFAULT_FLAG (1 << SENSOR_FLAG_ENABLE) // Two-level stringify so macro values expand before quoting (for use inside PSTR()) #define _SENSOR_DEFAULT_STR(x) #x @@ -44,7 +40,7 @@ typedef struct { uint32_t next_update; float value; uint16_t uuid; // stable sensor identifier (0 = empty slot) - uint16_t flags; // was uint32_t; only 2 bits used, uint16_t keeps struct at 16 bytes + uint16_t flag; // was uint32_t; only 2 bits used, uint16_t keeps struct at 16 bytes } sensor_memory_t; // Sensor log file format — both structs are tightly packed (no padding) so @@ -66,7 +62,7 @@ struct __attribute__((packed)) SensorLogRecord { }; enum class SensorType : uint8_t { - Ensemble = 0, + Aggregate = 0, ADS1115, Weather, MAX_VALUE, @@ -137,11 +133,11 @@ typedef enum { SENSOR_FLAG_ENABLE = 0, SENSOR_FLAG_LOG, SENSOR_FLAG_COUNT -} sensor_flags; +} sensor_flag; class Sensor { public: - Sensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags); + Sensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag); Sensor(); virtual ~Sensor() {} @@ -165,9 +161,7 @@ class Sensor { uint32_t interval = 1; float min = 0.f; float max = 0.f; - float scale = 0.f; - float offset = 0.f; - uint16_t flags = 0; + uint16_t flag = 0; uint16_t uuid = 0; // assigned by write_sensor on creation; 0 = not yet assigned SensorUnit unit = SensorUnit::None; char name[SENSOR_NAME_LEN] = {0}; @@ -183,16 +177,17 @@ class Sensor { }; // X(id, display_name) -#define ENSEMBLE_ACTION_LIST(X) \ +#define AGGREGATE_ACTION_LIST(X) \ X(Min, "Min") \ X(Max, "Max") \ X(Average, "Average") \ X(Sum, "Sum") \ - X(Product, "Product") + X(Median, "Median") \ + X(Range, "Range") -enum class EnsembleAction : uint8_t { +enum class AggregateAction : uint8_t { #define X(id, name) id, - ENSEMBLE_ACTION_LIST(X) + AGGREGATE_ACTION_LIST(X) #undef X MAX_VALUE, }; @@ -214,11 +209,11 @@ typedef struct { typedef enum { SENADJ_FLAG_ENABLE = 0, -} senadj_flags; +} senadj_flag; class SensorAdjustment { public: - SensorAdjustment(uint8_t flags, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); + SensorAdjustment(uint8_t flag, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); static SensorAdjustment *read(uint8_t index, uint8_t nprograms); // returns statically allocated object, do not delete static void write(SensorAdjustment *adj, uint8_t index); @@ -227,7 +222,7 @@ class SensorAdjustment { sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; uint16_t uuid; // sensor UUID (SENSOR_UUID_NONE = adjustment disabled) - uint8_t flags; + uint8_t flag; uint8_t point_count; }; @@ -249,7 +244,7 @@ inline T read_buf(char* buf, uint32_t* i) { } const char *enum_string(SensorUnitGroup group); -const char *enum_string(EnsembleAction action); +const char *enum_string(AggregateAction action); const char *enum_string(WeatherAction action); const char* get_sensor_unit_name(SensorUnit unit); diff --git a/sensors/weather_sensor.cpp b/sensors/weather_sensor.cpp index 1ea39a575..7889e65c9 100644 --- a/sensors/weather_sensor.cpp +++ b/sensors/weather_sensor.cpp @@ -1,7 +1,7 @@ #include "weather_sensor.h" -WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char* name, SensorUnit unit, uint16_t flags, WeatherGetter weather_getter, WeatherAction action) : - Sensor(interval, min, max, scale, offset, name, unit, flags), +WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint16_t flag, WeatherGetter weather_getter, WeatherAction action) : + Sensor(interval, min, max, name, unit, flag), action(action), weather_getter(weather_getter) { } diff --git a/sensors/weather_sensor.h b/sensors/weather_sensor.h index 6c7c4eaf6..4b9d12b11 100644 --- a/sensors/weather_sensor.h +++ b/sensors/weather_sensor.h @@ -4,7 +4,7 @@ class WeatherSensor : public Sensor { public: - WeatherSensor(uint32_t interval, float min, float max, float scale, float offset, const char *name, SensorUnit unit, uint16_t flags, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, WeatherGetter weather_getter, WeatherAction action); WeatherSensor(WeatherGetter weather_getter, char *buf); void emit_extra_json(BufferFiller *bfill); From cb82295522893264ea84cdbe6985249da9ed151f Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Wed, 29 Apr 2026 21:44:31 -0400 Subject: [PATCH 107/115] remove min/max from aggregate sensor children fields as those are not necessary --- opensprinkler_server.cpp | 10 +++++----- sensors/aggregate_sensor.cpp | 27 ++++++++++++--------------- sensors/aggregate_sensor.h | 11 +++++------ 3 files changed, 22 insertions(+), 26 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 3d32096b3..41fbe69b9 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1894,7 +1894,7 @@ void server_json_sensors(OTF_PARAMS_DEF) * interval: sampling interval in minutes * unit: sensor unit index * flag: bitmask (bit 0: enable, bit 1: log) - * [Aggregate] children: semicolon separated list of "uuid,min,max;" + * [Aggregate] children: semicolon separated list of "uuid,scale,offset;" * [Aggregate] action: aggregate action index (0: Min, 1: Max, 2: Average, 3: Sum, 4: Median, 5: Range) * [ADS1115] pin: pin number (1-16) * [Weather] action: weather information index @@ -2487,13 +2487,13 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR( ",\"args\":[" "{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"" SENSOR_DEFAULT_NAME "\"}," - "{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_INTERVAL) "\"}," + "{\"name\":\"Read Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_INTERVAL) "\"}," )); bfill.emit_p(PSTR("{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"default\":\"$D\"},"), static_cast(SENSOR_DEFAULT_UNIT)); bfill.emit_p(PSTR( - "{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_MIN) "\"}," - "{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_MAX) "\"}," - "{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_TYPE) "\"}" + "{\"name\":\"Min. Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_MIN) "\"}," + "{\"name\":\"Max. Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_MAX) "\"}," + "{\"name\":\"Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_DEFAULT_TYPE) "\"}" "]" )); diff --git a/sensors/aggregate_sensor.cpp b/sensors/aggregate_sensor.cpp index 3620a8b75..de1021383 100644 --- a/sensors/aggregate_sensor.cpp +++ b/sensors/aggregate_sensor.cpp @@ -11,7 +11,7 @@ AggregateSensor::AggregateSensor(uint32_t interval, float min, float max, const if (i < children_count) { this->children[i] = children[i]; } else { - this->children[i] = aggregate_children_t{ AGGREGATE_CHILD_DEFAULT_MIN, AGGREGATE_CHILD_DEFAULT_MAX, SENSOR_UUID_NONE }; + this->children[i] = aggregate_children_t{ AGGREGATE_CHILD_DEFAULT_SCALE, AGGREGATE_CHILD_DEFAULT_OFFSET, SENSOR_UUID_NONE }; } } } @@ -21,7 +21,7 @@ void AggregateSensor::emit_extra_json(BufferFiller* bfill) { for (size_t i = 0; i < AGGREGATE_SENSOR_CHILDREN_COUNT; i++) { if (i) bfill->emit_p(PSTR(",")); aggregate_children_t* child = &this->children[i]; - bfill->emit_p(PSTR("{\"uuid\":$D,\"min\":$E,\"max\":$E}"), child->uuid, child->min, child->max); + bfill->emit_p(PSTR("{\"uuid\":$D,\"scale\":$E,\"offset\":$E}"), child->uuid, child->scale, child->offset); } bfill->emit_p(PSTR("]}")); } @@ -34,14 +34,14 @@ void AggregateSensor::emit_description_json(BufferFiller* bfill) { "\"arg\":\"children\"," "\"type\":\"array::" SENSOR_DEFAULT_STR(AGGREGATE_SENSOR_CHILDREN_COUNT) "\"," "\"extra\":[" - "{\"name\":\"Sensor UUID\",\"arg\":\"uuid\",\"type\":\"sensor\",\"default\":\"0\"}," - "{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"float\",\"default\":\"-3.4e+38\"}," - "{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"float\",\"default\":\"3.4e+38\"}" + "{\"name\":\"Sensor UUID\",\"arg\":\"uuid\",\"type\":\"sensor\",\"default\":\"" SENSOR_DEFAULT_STR(SENSOR_UUID_NONE) "\",\"indicator\":true}," + "{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(AGGREGATE_CHILD_DEFAULT_SCALE) "\"}," + "{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"float\",\"default\":\"" SENSOR_DEFAULT_STR(AGGREGATE_CHILD_DEFAULT_OFFSET) "\"}" "]}," "{\"name\":\"Aggregate Action\"," "\"arg\":\"action\"," "\"type\":\"enum::AggregateAction\"," - "\"default\":\"0\"}" + "\"default\":\"" SENSOR_DEFAULT_STR(AGGREGATE_DEFAULT_ACTION) "\"}" "]}" )); } @@ -59,10 +59,7 @@ float AggregateSensor::_get_raw_value() { uint8_t idx = Sensor::find_index(this->children[i].uuid); if (idx >= OpenSprinkler::nsensors || !sensors[idx].interval) continue; - float value = sensors[idx].value; - if (value < this->children[i].min) value = this->children[i].min; - if (value > this->children[i].max) value = this->children[i].max; - values[count++] = value; + values[count++] = sensors[idx].value * this->children[i].scale + this->children[i].offset; } if (count == 0) return 0.0f; @@ -115,9 +112,9 @@ float AggregateSensor::_get_raw_value() { uint32_t AggregateSensor::_serialize_internal(char* buf) { uint32_t i = 0; for (size_t j = 0; j < AGGREGATE_SENSOR_CHILDREN_COUNT; j++) { + i += write_buf(buf + i, this->children[j].scale); + i += write_buf(buf + i, this->children[j].offset); i += write_buf(buf + i, this->children[j].uuid); - i += write_buf(buf + i, this->children[j].min); - i += write_buf(buf + i, this->children[j].max); } buf[i++] = static_cast(this->action); return i; @@ -126,9 +123,9 @@ uint32_t AggregateSensor::_serialize_internal(char* buf) { AggregateSensor::AggregateSensor(sensor_memory_t* sensors, char* buf) { uint32_t i = Sensor::_deserialize(buf); for (size_t j = 0; j < AGGREGATE_SENSOR_CHILDREN_COUNT; j++) { - this->children[j].uuid = read_buf(buf, &i); - this->children[j].min = read_buf(buf, &i); - this->children[j].max = read_buf(buf, &i); + this->children[j].scale = read_buf(buf, &i); + this->children[j].offset = read_buf(buf, &i); + this->children[j].uuid = read_buf(buf, &i); } this->action = static_cast(buf[i++]); this->sensors = sensors; diff --git a/sensors/aggregate_sensor.h b/sensors/aggregate_sensor.h index 2009ace26..23df4d762 100644 --- a/sensors/aggregate_sensor.h +++ b/sensors/aggregate_sensor.h @@ -1,19 +1,18 @@ #pragma once -#include #include "sensor.h" typedef struct { - float min; - float max; + float scale; // multiplier applied to child value + float offset; // added after scale uint16_t uuid; // UUID of child sensor (SENSOR_UUID_NONE = unused slot) } aggregate_children_t; #define AGGREGATE_SENSOR_CHILDREN_COUNT 8 -// Defaults for newly-initialized child slots -#define AGGREGATE_CHILD_DEFAULT_MIN (-FLT_MAX) -#define AGGREGATE_CHILD_DEFAULT_MAX (FLT_MAX) +#define AGGREGATE_CHILD_DEFAULT_SCALE 1 +#define AGGREGATE_CHILD_DEFAULT_OFFSET 0 +#define AGGREGATE_DEFAULT_ACTION 2 // AggregateAction::Average class AggregateSensor : public Sensor { public: From 1b2a9acfcaa4e7f70a2166d71dd28352bbe1b79f Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Wed, 29 Apr 2026 23:40:14 -0400 Subject: [PATCH 108/115] modify sens.dat layout to make it more future proof (in case we need to expand the base class members and subclass members --- sensors/ads1115_sensor.cpp | 22 ++++++--- sensors/ads1115_sensor.h | 2 +- sensors/aggregate_sensor.cpp | 17 ++++--- sensors/aggregate_sensor.h | 2 +- sensors/sensor.cpp | 92 +++++++++++++++++++++++++++++++----- sensors/sensor.h | 45 +++++++++++++++--- sensors/weather_sensor.cpp | 10 ++-- sensors/weather_sensor.h | 2 +- 8 files changed, 156 insertions(+), 36 deletions(-) diff --git a/sensors/ads1115_sensor.cpp b/sensors/ads1115_sensor.cpp index 56b7d954e..f2438404d 100644 --- a/sensors/ads1115_sensor.cpp +++ b/sensors/ads1115_sensor.cpp @@ -58,12 +58,22 @@ uint32_t ADS1115Sensor::_serialize_internal(char *buf) { return i; } -ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf) { - uint32_t i = Sensor::_deserialize(buf); - this->sensor_index = static_cast(buf[i++]); - this->pin = static_cast(buf[i++]); - this->scale = read_buf(buf, &i); - this->offset = read_buf(buf, &i); +ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf, uint32_t len) { + uint8_t subclass_len = 0; + uint32_t i = Sensor::_deserialize(buf, len, &subclass_len); + uint32_t end = i + subclass_len; + + this->sensor_index = 0; + this->pin = 0; + this->scale = ADS1115_DEFAULT_SCALE; + this->offset = ADS1115_DEFAULT_OFFSET; + + if (i + 1 <= end) this->sensor_index = static_cast(buf[i]); + i++; + if (i + 1 <= end) this->pin = static_cast(buf[i]); + i++; + if (i + sizeof(float) <= end) this->scale = read_buf(buf, &i); + if (i + sizeof(float) <= end) this->offset = read_buf(buf, &i); this->sensors = sensors; } diff --git a/sensors/ads1115_sensor.h b/sensors/ads1115_sensor.h index 9ce0290d6..ae7ad6628 100644 --- a/sensors/ads1115_sensor.h +++ b/sensors/ads1115_sensor.h @@ -13,7 +13,7 @@ class ADS1115Sensor : public Sensor { public: ADS1115Sensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin, float scale, float offset); - ADS1115Sensor(ADS1115 **sensors, char *buf); + ADS1115Sensor(ADS1115 **sensors, char *buf, uint32_t len); void emit_extra_json(BufferFiller *bfill); static void emit_description_json(BufferFiller *bfill); diff --git a/sensors/aggregate_sensor.cpp b/sensors/aggregate_sensor.cpp index de1021383..448005e53 100644 --- a/sensors/aggregate_sensor.cpp +++ b/sensors/aggregate_sensor.cpp @@ -120,13 +120,18 @@ uint32_t AggregateSensor::_serialize_internal(char* buf) { return i; } -AggregateSensor::AggregateSensor(sensor_memory_t* sensors, char* buf) { - uint32_t i = Sensor::_deserialize(buf); +AggregateSensor::AggregateSensor(sensor_memory_t* sensors, char* buf, uint32_t len) { + uint8_t subclass_len = 0; + uint32_t i = Sensor::_deserialize(buf, len, &subclass_len); + uint32_t end = i + subclass_len; + for (size_t j = 0; j < AGGREGATE_SENSOR_CHILDREN_COUNT; j++) { - this->children[j].scale = read_buf(buf, &i); - this->children[j].offset = read_buf(buf, &i); - this->children[j].uuid = read_buf(buf, &i); + this->children[j] = aggregate_children_t{ AGGREGATE_CHILD_DEFAULT_SCALE, AGGREGATE_CHILD_DEFAULT_OFFSET, SENSOR_UUID_NONE }; + if (i + sizeof(float) <= end) this->children[j].scale = read_buf(buf, &i); + if (i + sizeof(float) <= end) this->children[j].offset = read_buf(buf, &i); + if (i + sizeof(uint16_t) <= end) this->children[j].uuid = read_buf(buf, &i); } - this->action = static_cast(buf[i++]); + this->action = static_cast(AGGREGATE_DEFAULT_ACTION); + if (i + 1 <= end) this->action = static_cast(buf[i]); this->sensors = sensors; } diff --git a/sensors/aggregate_sensor.h b/sensors/aggregate_sensor.h index 23df4d762..fb3eafe0f 100644 --- a/sensors/aggregate_sensor.h +++ b/sensors/aggregate_sensor.h @@ -17,7 +17,7 @@ typedef struct { class AggregateSensor : public Sensor { public: AggregateSensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, sensor_memory_t *sensors, aggregate_children_t *children, uint8_t children_count, AggregateAction action); - AggregateSensor(sensor_memory_t *sensors, char *buf); + AggregateSensor(sensor_memory_t *sensors, char *buf, uint32_t len); void emit_extra_json(BufferFiller *bfill); static void emit_description_json(BufferFiller *bfill); diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp index 81ad8b667..719794a2d 100644 --- a/sensors/sensor.cpp +++ b/sensors/sensor.cpp @@ -65,6 +65,34 @@ const uint32_t get_sensor_unit_index(SensorUnit unit) { return static_cast(unit); } +static uint32_t sensor_legacy_len(SensorType type) { + // Legacy records did not store section lengths, so derive their total size + // from the fixed common payload plus each subclass' original payload. + const uint32_t legacy_header_len = 1; // sensor type only + const uint32_t legacy_ads1115_len = 1 + 1 + sizeof(float) + sizeof(float); + const uint32_t legacy_aggregate_len = + AGGREGATE_SENSOR_CHILDREN_COUNT * (sizeof(float) + sizeof(float) + sizeof(uint16_t)) + 1; + const uint32_t legacy_weather_len = 1; + + switch (type) { + case SensorType::Aggregate: + return legacy_header_len + SENSOR_COMMON_PAYLOAD_LEN + legacy_aggregate_len; + case SensorType::ADS1115: + return legacy_header_len + SENSOR_COMMON_PAYLOAD_LEN + legacy_ads1115_len; + case SensorType::Weather: + return legacy_header_len + SENSOR_COMMON_PAYLOAD_LEN + legacy_weather_len; + case SensorType::MAX_VALUE: + return 0; + } + return 0; +} + +static bool sensor_has_valid_layout(SensorType type, char *buf, uint32_t len) { + if (len == sensor_legacy_len(type)) return true; + if (len < SENSOR_RECORD_HEADER_LEN) return false; + return (uint32_t)SENSOR_RECORD_HEADER_LEN + (uint8_t)buf[1] + (uint8_t)buf[2] == len; +} + Sensor::Sensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint16_t flag) : interval(interval), min(min), max(max), flag(flag), unit(unit) { strncpy(this->name, name, SENSOR_NAME_LEN); @@ -85,6 +113,10 @@ uint32_t Sensor::serialize(char* buf) { uint32_t i = 0; buf[i++] = static_cast(this->get_sensor_type()); + uint32_t common_len_pos = i++; + uint32_t subclass_len_pos = i++; + + uint32_t common_start = i; memcpy(buf + i, this->name, SENSOR_NAME_LEN); i += SENSOR_NAME_LEN; buf[i++] = static_cast(this->unit); @@ -93,24 +125,58 @@ uint32_t Sensor::serialize(char* buf) { i += write_buf(buf + i, this->min); i += write_buf(buf + i, this->max); i += write_buf(buf + i, this->uuid); + buf[common_len_pos] = static_cast(i - common_start); + uint32_t subclass_start = i; i += this->_serialize_internal(buf + i); + buf[subclass_len_pos] = static_cast(i - subclass_start); return i; } -uint32_t Sensor::_deserialize(char* buf) { +uint32_t Sensor::_deserialize(char* buf, uint32_t len, uint8_t *subclass_len) { + SensorType type = static_cast(buf[0]); + uint32_t legacy_len = sensor_legacy_len(type); uint32_t i = 1; // Skip sensor type + uint8_t common_len = SENSOR_COMMON_PAYLOAD_LEN; + *subclass_len = 0; + + if (len != legacy_len) { + if (len < SENSOR_RECORD_HEADER_LEN) return len; + common_len = static_cast(buf[1]); + *subclass_len = static_cast(buf[2]); + if ((uint32_t)SENSOR_RECORD_HEADER_LEN + common_len + *subclass_len != len) return len; + i = SENSOR_RECORD_HEADER_LEN; + } else { + *subclass_len = static_cast(legacy_len - 1 - SENSOR_COMMON_PAYLOAD_LEN); + } + + uint32_t common_start = i; + uint32_t common_end = common_start + common_len; - memcpy(this->name, buf + i, SENSOR_NAME_LEN); + if (i + SENSOR_NAME_LEN <= common_end) { + memcpy(this->name, buf + i, SENSOR_NAME_LEN); + this->name[SENSOR_NAME_LEN - 1] = 0; + } i += SENSOR_NAME_LEN; - this->unit = static_cast(buf[i++]); - this->interval = read_buf(buf, &i); - this->flag = read_buf(buf, &i); - this->min = read_buf(buf, &i); - this->max = read_buf(buf, &i); - this->uuid = read_buf(buf, &i); - return i; + if (i + 1 <= common_end) this->unit = static_cast(buf[i]); + i++; + + if (i + sizeof(uint32_t) <= common_end) this->interval = read_buf(buf, &i); + else i += sizeof(uint32_t); + + if (i + sizeof(uint16_t) <= common_end) this->flag = read_buf(buf, &i); + else i += sizeof(uint16_t); + + if (i + sizeof(float) <= common_end) this->min = read_buf(buf, &i); + else i += sizeof(float); + + if (i + sizeof(float) <= common_end) this->max = read_buf(buf, &i); + else i += sizeof(float); + + if (i + sizeof(uint16_t) <= common_end) this->uuid = read_buf(buf, &i); + + return common_end; } SensorAdjustment::SensorAdjustment(uint8_t flag, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { @@ -236,15 +302,17 @@ Sensor *Sensor::parse(os_file_type file) { if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) return nullptr; SensorType sensor_type = static_cast(*tmp_buffer); + if (!sensor_has_valid_layout(sensor_type, (char*)tmp_buffer, len)) return nullptr; + switch (sensor_type) { case SensorType::Aggregate: - active_sensor = new (sensor_scratchpad) AggregateSensor(os.sensors, (char*)tmp_buffer); + active_sensor = new (sensor_scratchpad) AggregateSensor(os.sensors, (char*)tmp_buffer, len); break; case SensorType::ADS1115: - active_sensor = new (sensor_scratchpad) ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + active_sensor = new (sensor_scratchpad) ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer, len); break; case SensorType::Weather: - active_sensor = new (sensor_scratchpad) WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); + active_sensor = new (sensor_scratchpad) WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer, len); break; default: return nullptr; diff --git a/sensors/sensor.h b/sensors/sensor.h index c58a20b59..32384ffe3 100644 --- a/sensors/sensor.h +++ b/sensors/sensor.h @@ -20,6 +20,16 @@ #define SENSOR_NAME_LEN 33 #define SENSOR_CUSTOM_UNIT_LEN 9 +// Serialized sensor records are: type, common length, subclass length, +// common payload, subclass payload. Payloads are append-only. +#define SENSOR_RECORD_HEADER_LEN 3 + +// Common payload length for legacy records that predate length bytes: +// name[33] + unit[1] + interval[4] + flag[2] + min[4] + max[4] + uuid[2]. +// Do not change this when appending new common fields; new records carry their +// own common_len byte, while this constant is only for reading old sens.dat. +#define SENSOR_COMMON_PAYLOAD_LEN 50 + #define SENSOR_UUID_NONE 0 // sentinel: "no sensor assigned" (0 = uninitialized/disabled) // New-sensor defaults — single source of truth for both server_change_sensor and /jsd @@ -70,7 +80,7 @@ enum class SensorType : uint8_t { // X(id, display_name) #define SENSOR_UNIT_GROUP_LIST(X) \ - X(None, "No Group") \ + X(None, "No Unit") \ X(Energy, "Energy") \ X(Flow, "Flow") \ X(Pressure, "Pressure") \ @@ -78,11 +88,14 @@ enum class SensorType : uint8_t { X(Light, "Light") \ X(Length, "Length") \ X(Velocity, "Velocity") \ - X(Volume, "Volume") + X(Volume, "Volume") \ + X(Salinity, "Salinity") \ + X(Angle, "Angle") \ + X(Precipitation, "Precipitation") // X(id, display_name, short_symbol, group_id) #define SENSOR_UNIT_LIST(X) \ - X(None, "None", "", None) \ + X(None, "None", " ", None) \ X(Percent, "Percent", "%", None) \ X(PartsPerMillion, "Parts Per Million", "ppm", None) \ X(Millivolt, "Millivolt", "mV", Energy) \ @@ -92,7 +105,7 @@ enum class SensorType : uint8_t { X(Ohm, "Ohm", "Ω", Energy) \ X(Milliohm, "Milliohm", "mΩ", Energy) \ X(Kiloohm, "Kiloohm", "kΩ", Energy) \ - X(DielectricConstant,"Dielectric Constant","-", Energy) \ + X(DielectricConstant,"Dielectric Constant"," ", Energy) \ X(LitersPerSecond, "Liters Per Second", "L/s", Flow) \ X(GallonsPerSecond, "Gallons Per Second", "gal/s", Flow) \ X(Kilopascal, "Kilopascal", "kPa", Pressure) \ @@ -113,7 +126,27 @@ enum class SensorType : uint8_t { X(Mile, "Mile", "mi", Length) \ X(MetersPerSecond, "Meters Per Second", "m/s", Velocity) \ X(KilometersPerHour, "Kilometers Per Hour","km/h", Velocity) \ - X(MilesPerHour, "Miles Per Hour", "mph", Velocity) + X(MilesPerHour, "Miles Per Hour", "mph", Velocity) \ + X(Milliliter, "Milliliter", "mL", Volume) \ + X(Liter, "Liter", "L", Volume) \ + X(CubicMeter, "Cubic Meter", "m³", Volume) \ + X(Gallon, "Gallon", "gal", Volume) \ + X(CubicFoot, "Cubic Foot", "ft³", Volume) \ + X(LitersPerMinute, "Liters Per Minute", "L/min", Flow) \ + X(GallonsPerMinute, "Gallons Per Minute", "gpm", Flow) \ + X(PoundsPerSquareInch, "Pounds Per Square Inch", "psi", Pressure) \ + X(VolumetricMoistureContent, "Volumetric Moisture Content", "%VMC", None) \ + X(Watt, "Watt", "W", Energy) \ + X(Milliwatt, "Milliwatt", "mW", Energy) \ + X(WattHour, "Watt Hour", "Wh", Energy) \ + X(KilowattHour, "Kilowatt Hour", "kWh", Energy) \ + X(MicrosiemensPerCentimeter, "Microsiemens Per Centimeter", "uS/cm", Salinity) \ + X(MillisiemensPerCentimeter, "Millisiemens Per Centimeter", "mS/cm", Salinity) \ + X(Ph, "pH", "pH", Salinity) \ + X(Degree, "Degree", "deg", Angle) \ + X(Radian, "Radian", "rad", Angle) \ + X(MillimetersPerHour, "Millimeters Per Hour", "mm/h", Precipitation) \ + X(InchesPerHour, "Inches Per Hour", "in/h", Precipitation) enum class SensorUnitGroup : uint8_t { #define X(id, name) id, @@ -172,7 +205,7 @@ class Sensor { private: float virtual _get_raw_value() = 0; protected: - uint32_t _deserialize(char *buf); + uint32_t _deserialize(char *buf, uint32_t len, uint8_t *subclass_len); uint32_t virtual _serialize_internal(char *buf) = 0; }; diff --git a/sensors/weather_sensor.cpp b/sensors/weather_sensor.cpp index 7889e65c9..c262d0519 100644 --- a/sensors/weather_sensor.cpp +++ b/sensors/weather_sensor.cpp @@ -36,8 +36,12 @@ uint32_t WeatherSensor::_serialize_internal(char* buf) { return i; } -WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) { - uint32_t i = Sensor::_deserialize(buf); - this->action = static_cast(buf[i++]); +WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf, uint32_t len) { + uint8_t subclass_len = 0; + uint32_t i = Sensor::_deserialize(buf, len, &subclass_len); + uint32_t end = i + subclass_len; + + this->action = WeatherAction::MAX_VALUE; + if (i + 1 <= end) this->action = static_cast(buf[i]); this->weather_getter = weather_getter; } diff --git a/sensors/weather_sensor.h b/sensors/weather_sensor.h index 4b9d12b11..e6248f355 100644 --- a/sensors/weather_sensor.h +++ b/sensors/weather_sensor.h @@ -5,7 +5,7 @@ class WeatherSensor : public Sensor { public: WeatherSensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, WeatherGetter weather_getter, WeatherAction action); - WeatherSensor(WeatherGetter weather_getter, char *buf); + WeatherSensor(WeatherGetter weather_getter, char *buf, uint32_t len); void emit_extra_json(BufferFiller *bfill); static void emit_description_json(BufferFiller *bfill); From d92ffc18a4bf8c013cfffc4b047013f06af7bf5c Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Thu, 30 Apr 2026 11:48:17 -0400 Subject: [PATCH 109/115] add sensor::status, supporting indicator of whether sensor reading is valid/error/stable; remove get_initial_value now that sensor status is in place; add /jpa endpoint to send program adjustments (including weather-based and sensor-based) to UI to facilitate preview --- OpenSprinkler.cpp | 37 ++++++++++++++++-------- ads1115.cpp | 2 +- main.cpp | 55 +++++++++++++++--------------------- main.h | 4 +++ opensprinkler_server.cpp | 33 ++++++++++++++++++---- sensors/ads1115_sensor.cpp | 10 +++---- sensors/ads1115_sensor.h | 2 -- sensors/aggregate_sensor.cpp | 8 ++---- sensors/aggregate_sensor.h | 2 -- sensors/sensor.cpp | 19 +++++++++---- sensors/sensor.h | 22 ++++++++++----- sensors/weather_sensor.cpp | 3 -- sensors/weather_sensor.h | 2 -- 13 files changed, 117 insertions(+), 82 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 0e42abdea..7011f8140 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2593,17 +2593,32 @@ void OpenSprinkler::log_sensor(uint8_t sid, float value) { void OpenSprinkler::poll_sensors() { for (uint8_t i = 0; i < nsensors; i++) { - if (sensors[i].interval && (sensors[i].flag & (1 << SENSOR_FLAG_ENABLE))) { - if ((long)(millis() - sensors[i].next_update) > 0) { - Sensor *sensor = Sensor::get(i); - if (sensor) { - sensors[i].value = sensor->get_new_value(); - sensors[i].next_update = millis() + (sensors[i].interval * 1000 * 60); - if (sensors[i].flag & (1 << SENSOR_FLAG_LOG)) { - os.log_sensor(i, sensors[i].value); - } - } - } + sensor_memory_t &mem = sensors[i]; + if (!mem.interval || !(mem.flag & (1 << SENSOR_FLAG_ENABLE))) continue; + + if ((long)(millis() - mem.next_update) <= 0) continue; + + Sensor *sensor = Sensor::get(i); + if (!sensor) { + // Can't load sensor from disk — value is now stale + mem.status |= SENSOR_STATUS_STALE; + continue; + } + + uint8_t new_status = 0; + float new_value = sensor->get_new_value(&new_status); + + if (new_status & SENSOR_STATUS_ERROR) { + // Hardware fault — preserve last good value but flag error; clear stale + mem.status = (mem.status & SENSOR_STATUS_VALID) | SENSOR_STATUS_ERROR; + } else { + mem.value = new_value; + mem.status = new_status; // VALID + CLAMPED_* as appropriate; clears ERROR/STALE + } + mem.next_update = millis() + (mem.interval * 1000 * 60); + + if (mem.flag & (1 << SENSOR_FLAG_LOG)) { + os.log_sensor(i, mem.value); } } } diff --git a/ads1115.cpp b/ads1115.cpp index 30f39064f..02c8184cf 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -66,7 +66,7 @@ int16_t ADS1115::get_pin_value(uint8_t pin) { while (this->is_busy()) { // if ((millis() - start) > 11) { if ((millis() - start) > 18) { - return 0; + return -1; } #if defined(ESP8266) diff --git a/main.cpp b/main.cpp index ad9069aeb..88981eef6 100644 --- a/main.cpp +++ b/main.cpp @@ -789,13 +789,8 @@ void do_loop() unsigned char runcount = prog.check_match(curr_time, &will_delete); if(runcount>0) { // program match found - float sensor_adj = 1.f; - #if defined(USE_SENSORS) - SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); - if (adj) { - sensor_adj = adj->get_adjustment_factor(os.sensors); - } - #endif + unsigned char wl = get_program_water_percent(prog); + float sensor_adj = get_program_sensor_adj(pid); // check and process special program command if(process_special_program_command(prog.name, curr_time)) continue; @@ -804,24 +799,6 @@ void do_loop() unsigned char order[os.nstations]; prog.gen_station_runorder(runcount, order); - // prepare watering level - unsigned char wl = 100; // default 100% - if (prog.use_weather) { // if program is set to use weather scaling - if (wt_restricted > 0) wl = 0; // if watering restriction is active - else { - wl = os.iopts[IOPT_WATER_PERCENTAGE]; - // If historical data is enabled and interval program, overwrite watering percentage with historical one. - if (mda == 100 && prog.type == PROGRAM_TYPE_INTERVAL && md_N > 0) { - // Use interval length unless longer than available data - if ((unsigned int)prog.days[1]-1 < md_N){ - wl = md_scales[prog.days[1]-1]; - } else { - wl = md_scales[md_N-1]; - } - } - } - } - // process all selected stations for(unsigned char oi=0;oi 0. run program pid-1 */ + +uint8_t get_program_water_percent(const ProgramStruct &prog) { + if (!prog.use_weather) return 100; + if (wt_restricted > 0) return 0; + uint8_t wl = os.iopts[IOPT_WATER_PERCENTAGE]; + if (mda == 100 && prog.type == PROGRAM_TYPE_INTERVAL && md_N > 0) { + wl = ((unsigned int)prog.days[1] - 1 < md_N) ? md_scales[prog.days[1] - 1] : md_scales[md_N - 1]; + } + return wl; +} + +float get_program_sensor_adj(uint8_t pid) { +#if defined(USE_SENSORS) + SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); + if (adj) return adj->get_adjustment_factor(os.sensors); +#endif + return 1.f; +} + void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo) { boolean match_found = false; ProgramStruct prog; @@ -1629,13 +1625,8 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo unsigned char wl = 100; if ((pid>0)&&(pid<255)) { pd.read(pid-1, &prog); - #if defined(USE_SENSORS) - SensorAdjustment *adj = SensorAdjustment::read(pid-1, pd.nprograms); - if (adj) { - sensor_adj = adj->get_adjustment_factor(os.sensors); - } - #endif - if(uwt) wl = os.iopts[IOPT_WATER_PERCENTAGE]; + sensor_adj = get_program_sensor_adj(pid-1); + if(uwt) wl = get_program_water_percent(prog); notif.add(NOTIFY_PROGRAM_SCHED, pid-1, wl, 1, sensor_adj); // get station ordering from program name prog.gen_station_runorder(1, order); diff --git a/main.h b/main.h index 9a9bd1682..2b8b7fd9a 100644 --- a/main.h +++ b/main.h @@ -26,6 +26,10 @@ #pragma once +struct ProgramStruct; +uint8_t get_program_water_percent(const ProgramStruct &prog); +float get_program_sensor_adj(uint8_t pid); + void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shift=0); void turn_off_running_station_immediate(unsigned char sid, time_os_t curr_time, unsigned char shift=0); void schedule_all_stations(time_os_t curr_time, unsigned char qo=0); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 41fbe69b9..deb406b9a 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1087,6 +1087,24 @@ void server_json_programs(OTF_PARAMS_DEF) { handle_return(HTML_OK); } +/** Output per-program adjustment factors: water percent (wa), sensor adj (sa), total adj (ta) */ +void server_json_program_adj(OTF_PARAMS_DEF) { + if(!process_password(OTF_PARAMS)) return; + begin_response(res); + print_header(OTF_PARAMS); + bfill.emit_p(PSTR("{\"jpa\":[")); + ProgramStruct prog; + for(uint8_t pid=0; piduuid, sensor->name, static_cast(sensor->unit), sensor->flag, sensor->interval, sensor->min, sensor->max, os.sensors[i].value, static_cast(sensor->get_sensor_type())); + bfill.emit_p(PSTR("{\"uuid\":$D,\"name\":\"$S\",\"unit\":$D,\"flag\":$D,\"status\":$D,\"interval\":$L,\"min\":$E,\"max\":$E,\"value\":$E,\"type\":$D,\"extra\":"), sensor->uuid, sensor->name, static_cast(sensor->unit), sensor->flag, os.sensors[i].status, sensor->interval, sensor->min, sensor->max, os.sensors[i].value, static_cast(sensor->get_sensor_type())); sensor->emit_extra_json(&bfill); bfill.emit_p(PSTR("}")); sensor_count += 1; @@ -2124,9 +2142,9 @@ void server_change_sensor(OTF_PARAMS_DEF) { } os.sensors[sid].interval = interval; - os.sensors[sid].flag = flag; + os.sensors[sid].flag = static_cast(flag); os.sensors[sid].next_update = 0; - os.sensors[sid].value = result_sensor->get_initial_value(); + os.sensors[sid].value = 0.f; if (is_new) { // Assign a new UUID for this sensor @@ -2497,10 +2515,11 @@ void server_json_sensor_description_main(OTF_PARAMS_DEF) { "]" )); - static_assert(SENSOR_FLAG_COUNT == 2); // If this fails, update the flags array below - bfill.emit_p(PSTR(",\"flags\":[{\"name\":\"Enabled\",\"default\":$D},{\"name\":\"Logging\",\"default\":$D}]"), + static_assert(SENSOR_FLAG_COUNT == 3); // If this fails, update the flags array below + bfill.emit_p(PSTR(",\"flags\":[{\"name\":\"Enabled\",\"default\":$D},{\"name\":\"Logging\",\"default\":$D},{\"name\":\"Show on Home\",\"default\":$D}]"), (SENSOR_DEFAULT_FLAG >> SENSOR_FLAG_ENABLE) & 1, - (SENSOR_DEFAULT_FLAG >> SENSOR_FLAG_LOG) & 1); + (SENSOR_DEFAULT_FLAG >> SENSOR_FLAG_LOG) & 1, + (SENSOR_DEFAULT_FLAG >> SENSOR_FLAG_SHOW) & 1); bfill.emit_p(PSTR("}")); } @@ -2691,6 +2710,7 @@ const char *uris[] PROGMEM = { "mp", "up", "jp", + "jpa", "co", "jo", "sp", @@ -2730,6 +2750,7 @@ URLHandler urls[] = { server_manual_program, // mp server_moveup_program, // up server_json_programs, // jp + server_json_program_adj,// jpa server_change_options, // co server_json_options, // jo server_change_password, // sp diff --git a/sensors/ads1115_sensor.cpp b/sensors/ads1115_sensor.cpp index f2438404d..882fcead1 100644 --- a/sensors/ads1115_sensor.cpp +++ b/sensors/ads1115_sensor.cpp @@ -37,16 +37,14 @@ void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { )); } -float ADS1115Sensor::get_initial_value() { - return 0.0; -} float ADS1115Sensor::_get_raw_value() { if (this->sensors[sensor_index] == nullptr) { - return 0.0; + return NAN; } - float raw = ((float)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; - return raw * this->scale + this->offset; + int16_t counts = this->sensors[sensor_index]->get_pin_value(this->pin); + if (counts < 0) return NAN; + return (float)counts * ADS1115_SCALE_FACTOR * this->scale + this->offset; } uint32_t ADS1115Sensor::_serialize_internal(char *buf) { diff --git a/sensors/ads1115_sensor.h b/sensors/ads1115_sensor.h index ae7ad6628..d29cf00ca 100644 --- a/sensors/ads1115_sensor.h +++ b/sensors/ads1115_sensor.h @@ -27,8 +27,6 @@ class ADS1115Sensor : public Sensor { float scale; float offset; - float get_initial_value(); - private: float _get_raw_value(); uint32_t _serialize_internal(char *buf); diff --git a/sensors/aggregate_sensor.cpp b/sensors/aggregate_sensor.cpp index 448005e53..cbfa981f8 100644 --- a/sensors/aggregate_sensor.cpp +++ b/sensors/aggregate_sensor.cpp @@ -46,9 +46,6 @@ void AggregateSensor::emit_description_json(BufferFiller* bfill) { )); } -float AggregateSensor::get_initial_value() { - return 0.0f; -} float AggregateSensor::_get_raw_value() { float values[AGGREGATE_SENSOR_CHILDREN_COUNT]; @@ -58,11 +55,12 @@ float AggregateSensor::_get_raw_value() { if (this->children[i].uuid == SENSOR_UUID_NONE) continue; uint8_t idx = Sensor::find_index(this->children[i].uuid); if (idx >= OpenSprinkler::nsensors || !sensors[idx].interval) continue; + if (!(sensors[idx].status & SENSOR_STATUS_VALID)) continue; values[count++] = sensors[idx].value * this->children[i].scale + this->children[i].offset; } - if (count == 0) return 0.0f; + if (count == 0) return NAN; switch (this->action) { case AggregateAction::Min: { @@ -105,7 +103,7 @@ float AggregateSensor::_get_raw_value() { return hi - lo; } default: - return 0.0f; + return NAN; } } diff --git a/sensors/aggregate_sensor.h b/sensors/aggregate_sensor.h index fb3eafe0f..b4771889e 100644 --- a/sensors/aggregate_sensor.h +++ b/sensors/aggregate_sensor.h @@ -29,8 +29,6 @@ class AggregateSensor : public Sensor { aggregate_children_t children[AGGREGATE_SENSOR_CHILDREN_COUNT]; AggregateAction action; - float get_initial_value(); - private: float _get_raw_value(); uint32_t _serialize_internal(char *buf); diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp index 719794a2d..cc33ec9a4 100644 --- a/sensors/sensor.cpp +++ b/sensors/sensor.cpp @@ -101,10 +101,18 @@ Sensor::Sensor(uint32_t interval, float min, float max, const char* name, Sensor Sensor::Sensor() {} -float Sensor::get_new_value() { +float Sensor::get_new_value(uint8_t *status_out) { float value = this->_get_raw_value(); - if (value < this->min) value = this->min; - if (value > this->max) value = this->max; + + if (isnan(value)) { + if (status_out) *status_out = SENSOR_STATUS_ERROR; + return value; + } + + uint8_t status = SENSOR_STATUS_VALID; + if (value < this->min) { value = this->min; status |= SENSOR_STATUS_CLAMPED_LOW; } + if (value > this->max) { value = this->max; status |= SENSOR_STATUS_CLAMPED_HIGH; } + if (status_out) *status_out = status; return value; } @@ -277,10 +285,11 @@ void SensorAdjustment::write(SensorAdjustment *adj, uint8_t index) { static void sensor_memory_init(sensor_memory_t &m, Sensor *sensor) { m.interval = sensor->interval; - m.flag = sensor->flag; + m.flag = static_cast(sensor->flag); m.uuid = sensor->uuid; m.next_update = 0; - m.value = sensor->get_initial_value(); + m.value = 0.f; + m.status = 0; } Sensor *Sensor::parse(os_file_type file) { diff --git a/sensors/sensor.h b/sensors/sensor.h index 32384ffe3..e970d085c 100644 --- a/sensors/sensor.h +++ b/sensors/sensor.h @@ -49,9 +49,17 @@ typedef struct { uint32_t interval; uint32_t next_update; float value; - uint16_t uuid; // stable sensor identifier (0 = empty slot) - uint16_t flag; // was uint32_t; only 2 bits used, uint16_t keeps struct at 16 bytes -} sensor_memory_t; + uint16_t uuid; // stable sensor identifier (0 = empty slot) + uint8_t flag; // persistent config bits — synced from sens.dat on load + uint8_t status; // runtime state bits — never persisted, cleared on load +} sensor_memory_t; // 16 bytes + +// Runtime status bits (sensor_memory_t::status) — never persisted +#define SENSOR_STATUS_VALID (1 << 0) // has had at least one successful read +#define SENSOR_STATUS_ERROR (1 << 1) // last read attempt failed (hardware fault) +#define SENSOR_STATUS_STALE (1 << 2) // update window passed but read could not complete +#define SENSOR_STATUS_CLAMPED_HIGH (1 << 3) // last value was clamped to max +#define SENSOR_STATUS_CLAMPED_LOW (1 << 4) // last value was clamped to min // Sensor log file format — both structs are tightly packed (no padding) so // sizeof() gives the exact on-disk byte count and offsetof() gives exact field offsets. @@ -163,8 +171,9 @@ enum class SensorUnit : uint8_t { }; typedef enum { - SENSOR_FLAG_ENABLE = 0, - SENSOR_FLAG_LOG, + SENSOR_FLAG_ENABLE = 0, // sensor is active + SENSOR_FLAG_LOG, // write readings to log file + SENSOR_FLAG_SHOW, // show on homepage SENSOR_FLAG_COUNT } sensor_flag; @@ -174,7 +183,7 @@ class Sensor { Sensor(); virtual ~Sensor() {} - float get_new_value(); + float get_new_value(uint8_t *status_out = nullptr); uint32_t serialize(char *buf); static Sensor *parse(os_file_type file); // statically allocated, do not delete @@ -200,7 +209,6 @@ class Sensor { char name[SENSOR_NAME_LEN] = {0}; SensorType virtual get_sensor_type() = 0; - float virtual get_initial_value() = 0; private: float virtual _get_raw_value() = 0; diff --git a/sensors/weather_sensor.cpp b/sensors/weather_sensor.cpp index c262d0519..5271ac232 100644 --- a/sensors/weather_sensor.cpp +++ b/sensors/weather_sensor.cpp @@ -22,9 +22,6 @@ void WeatherSensor::emit_description_json(BufferFiller* bfill) { )); } -float WeatherSensor::get_initial_value() { - return 0.f; -} float WeatherSensor::_get_raw_value() { return this->weather_getter(this->action); diff --git a/sensors/weather_sensor.h b/sensors/weather_sensor.h index e6248f355..5bfbdbf74 100644 --- a/sensors/weather_sensor.h +++ b/sensors/weather_sensor.h @@ -16,8 +16,6 @@ class WeatherSensor : public Sensor { WeatherAction action; - float get_initial_value(); - private: float _get_raw_value(); uint32_t _serialize_internal(char *buf); From 70248d4576a884cd273b8cbee58905fd248e885c Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Thu, 30 Apr 2026 23:47:37 -0400 Subject: [PATCH 110/115] add 'usa' parameter for manual_start_program to allow applying sensor adjustment on manually started programs; update sensor::flag to uint8_t to be consistent with sensor_memory_t::flag; improve write_buf and read_buf to eliminate the need of explicitly specifying type --- main.cpp | 6 +- mqtt.cpp | 2 +- notifier.cpp | 7 +- opensprinkler_server.cpp | 20 ++++-- sensors/ads1115_sensor.cpp | 10 +-- sensors/ads1115_sensor.h | 2 +- sensors/aggregate_sensor.cpp | 14 ++-- sensors/aggregate_sensor.h | 2 +- sensors/sensor.cpp | 123 +++++++++++------------------------ sensors/sensor.h | 24 +++---- sensors/weather_sensor.cpp | 2 +- sensors/weather_sensor.h | 2 +- 12 files changed, 86 insertions(+), 128 deletions(-) diff --git a/main.cpp b/main.cpp index 88981eef6..0285e37d0 100644 --- a/main.cpp +++ b/main.cpp @@ -59,7 +59,7 @@ static uint16_t led_blink_ms = 0; const char *user_agent_string = "OpenSprinkler/" TOSTRING(OS_FW_VERSION) "#" TOSTRING(OS_FW_MINOR); -void manual_start_program(unsigned char, unsigned char, unsigned char); +void manual_start_program(unsigned char, unsigned char, unsigned char, unsigned char usa=0); // Small variations have been added to the timing values below // to minimize conflicting events @@ -1609,7 +1609,7 @@ float get_program_sensor_adj(uint8_t pid) { return 1.f; } -void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo) { +void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo, unsigned char usa) { boolean match_found = false; ProgramStruct prog; uint32_t dur; @@ -1625,7 +1625,7 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo unsigned char wl = 100; if ((pid>0)&&(pid<255)) { pd.read(pid-1, &prog); - sensor_adj = get_program_sensor_adj(pid-1); + if(usa) sensor_adj = get_program_sensor_adj(pid-1); if(uwt) wl = get_program_water_percent(prog); notif.add(NOTIFY_PROGRAM_SCHED, pid-1, wl, 1, sensor_adj); // get station ordering from program name diff --git a/mqtt.cpp b/mqtt.cpp index b1a36dd08..917d4593d 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -225,7 +225,7 @@ void manualRun(char *message){ } //handles /mp command -void manual_start_program(unsigned char, unsigned char, unsigned char); +void manual_start_program(unsigned char, unsigned char, unsigned char, unsigned char usa=0); void programStart(char *message){ if(!findKeyVal(message, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)){ DEBUG_LOGF("Program ID missing.\r\n") diff --git a/notifier.cpp b/notifier.cpp index bb70c1283..3c5bea6d5 100644 --- a/notifier.cpp +++ b/notifier.cpp @@ -343,9 +343,8 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float strcat_P(payload, PSTR("{\"state\":\"skipped\",\"wtrestr\":")); snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR("%d"), (int)bval); // if a program is skipped, also output the wt_restricted variable } else { - strcat_P(payload, PSTR("{\"state\":1,\"wl\":")); - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR("%d"), (int)fval); - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"snadj\":%.2f"), fval2*100.f); + snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR("{\"state\":1,\"wl\":%d,\"wa\":%.4g,\"sa\":%.4g,\"ta\":%.4g"), + (int)fval, fval/100.f, fval2, fval/100.f*fval2); } strcat_P(payload, PSTR("}")); } @@ -365,7 +364,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval, float if(lval0) { - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(". Water level: %d%%. Sensor adjustment: %.2f%%."), (int)fval, fval2*100.f); + snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(". Adjustments: Weather->%d%%, Sensor->%.2f%%, Total->%.2f%%."), (int)fval, fval2*100.f, fval*fval2); } if(email_enabled) { email_message.subject += PSTR("program event"); } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index deb406b9a..1f27271e7 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -564,7 +564,7 @@ uint16_t parse_listdata(char **p) { return (uint16_t)atol(tmp_buffer); } -void manual_start_program(unsigned char, unsigned char, unsigned char); +void manual_start_program(unsigned char, unsigned char, unsigned char, unsigned char usa=0); /** Manual start program * Command: /mp?pw=xxx&pid=xx&uwt=x&qo=x * @@ -589,6 +589,11 @@ void server_manual_program(OTF_PARAMS_DEF) { if(tmp_buffer[0]=='1') uwt = 1; } + unsigned char usa = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("usa"), true)) { + if(tmp_buffer[0]=='1') usa = 1; + } + unsigned char qo = QUEUE_OPTION_REPLACE; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("qo"), true)) { qo=(unsigned char)atoi(tmp_buffer); @@ -598,7 +603,7 @@ void server_manual_program(OTF_PARAMS_DEF) { reset_all_stations_immediate(); } - manual_start_program(pid+1, uwt, qo); + manual_start_program(pid+1, uwt, qo, usa); handle_return(HTML_SUCCESS); } @@ -863,6 +868,7 @@ void server_change_program(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flag"), true)) { flag=strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (flag > 0xFF) handle_return(HTML_DATA_OUTOFBOUND); } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_uuid"), true)) { @@ -881,7 +887,7 @@ void server_change_program(OTF_PARAMS_DEF) { while (*ptr != '\0') { if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); result = sscanf(ptr, "%f,%f;", &x, &y); - if (result != 2 || x <= last_x) { + if (result != 2 || !isfinite(x) || !isfinite(y) || y < 0 || x < last_x) { handle_return(HTML_DATA_FORMATERROR); } points[i++] = sensor_adjustment_point_t {x, y}; @@ -1096,10 +1102,10 @@ void server_json_program_adj(OTF_PARAMS_DEF) { ProgramStruct prog; for(uint8_t pid=0; pid(this->sensor_index); buf[i++] = static_cast(this->pin); - i += write_buf(buf + i, this->scale); - i += write_buf(buf + i, this->offset); + i += write_buf(buf + i, this->scale); + i += write_buf(buf + i, this->offset); return i; } @@ -70,8 +70,8 @@ ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf, uint32_t len) { i++; if (i + 1 <= end) this->pin = static_cast(buf[i]); i++; - if (i + sizeof(float) <= end) this->scale = read_buf(buf, &i); - if (i + sizeof(float) <= end) this->offset = read_buf(buf, &i); + read_buf(buf, &i, end, this->scale); + read_buf(buf, &i, end, this->offset); this->sensors = sensors; } diff --git a/sensors/ads1115_sensor.h b/sensors/ads1115_sensor.h index d29cf00ca..1031738d0 100644 --- a/sensors/ads1115_sensor.h +++ b/sensors/ads1115_sensor.h @@ -12,7 +12,7 @@ class ADS1115Sensor : public Sensor { public: - ADS1115Sensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin, float scale, float offset); + ADS1115Sensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint8_t flag, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin, float scale, float offset); ADS1115Sensor(ADS1115 **sensors, char *buf, uint32_t len); void emit_extra_json(BufferFiller *bfill); diff --git a/sensors/aggregate_sensor.cpp b/sensors/aggregate_sensor.cpp index cbfa981f8..ac67fe9de 100644 --- a/sensors/aggregate_sensor.cpp +++ b/sensors/aggregate_sensor.cpp @@ -3,7 +3,7 @@ extern OpenSprinkler os; -AggregateSensor::AggregateSensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint16_t flag, sensor_memory_t* sensors, aggregate_children_t* children, uint8_t children_count, AggregateAction action) : +AggregateSensor::AggregateSensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint8_t flag, sensor_memory_t* sensors, aggregate_children_t* children, uint8_t children_count, AggregateAction action) : Sensor(interval, min, max, name, unit, flag), action(action), sensors(sensors) { @@ -110,9 +110,9 @@ float AggregateSensor::_get_raw_value() { uint32_t AggregateSensor::_serialize_internal(char* buf) { uint32_t i = 0; for (size_t j = 0; j < AGGREGATE_SENSOR_CHILDREN_COUNT; j++) { - i += write_buf(buf + i, this->children[j].scale); - i += write_buf(buf + i, this->children[j].offset); - i += write_buf(buf + i, this->children[j].uuid); + i += write_buf(buf + i, this->children[j].scale); + i += write_buf(buf + i, this->children[j].offset); + i += write_buf(buf + i, this->children[j].uuid); } buf[i++] = static_cast(this->action); return i; @@ -125,9 +125,9 @@ AggregateSensor::AggregateSensor(sensor_memory_t* sensors, char* buf, uint32_t l for (size_t j = 0; j < AGGREGATE_SENSOR_CHILDREN_COUNT; j++) { this->children[j] = aggregate_children_t{ AGGREGATE_CHILD_DEFAULT_SCALE, AGGREGATE_CHILD_DEFAULT_OFFSET, SENSOR_UUID_NONE }; - if (i + sizeof(float) <= end) this->children[j].scale = read_buf(buf, &i); - if (i + sizeof(float) <= end) this->children[j].offset = read_buf(buf, &i); - if (i + sizeof(uint16_t) <= end) this->children[j].uuid = read_buf(buf, &i); + read_buf(buf, &i, end, this->children[j].scale); + read_buf(buf, &i, end, this->children[j].offset); + read_buf(buf, &i, end, this->children[j].uuid); } this->action = static_cast(AGGREGATE_DEFAULT_ACTION); if (i + 1 <= end) this->action = static_cast(buf[i]); diff --git a/sensors/aggregate_sensor.h b/sensors/aggregate_sensor.h index b4771889e..8f4dbfc97 100644 --- a/sensors/aggregate_sensor.h +++ b/sensors/aggregate_sensor.h @@ -16,7 +16,7 @@ typedef struct { class AggregateSensor : public Sensor { public: - AggregateSensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, sensor_memory_t *sensors, aggregate_children_t *children, uint8_t children_count, AggregateAction action); + AggregateSensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint8_t flag, sensor_memory_t *sensors, aggregate_children_t *children, uint8_t children_count, AggregateAction action); AggregateSensor(sensor_memory_t *sensors, char *buf, uint32_t len); void emit_extra_json(BufferFiller *bfill); diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp index cc33ec9a4..e13238fe2 100644 --- a/sensors/sensor.cpp +++ b/sensors/sensor.cpp @@ -65,35 +65,15 @@ const uint32_t get_sensor_unit_index(SensorUnit unit) { return static_cast(unit); } -static uint32_t sensor_legacy_len(SensorType type) { - // Legacy records did not store section lengths, so derive their total size - // from the fixed common payload plus each subclass' original payload. - const uint32_t legacy_header_len = 1; // sensor type only - const uint32_t legacy_ads1115_len = 1 + 1 + sizeof(float) + sizeof(float); - const uint32_t legacy_aggregate_len = - AGGREGATE_SENSOR_CHILDREN_COUNT * (sizeof(float) + sizeof(float) + sizeof(uint16_t)) + 1; - const uint32_t legacy_weather_len = 1; - - switch (type) { - case SensorType::Aggregate: - return legacy_header_len + SENSOR_COMMON_PAYLOAD_LEN + legacy_aggregate_len; - case SensorType::ADS1115: - return legacy_header_len + SENSOR_COMMON_PAYLOAD_LEN + legacy_ads1115_len; - case SensorType::Weather: - return legacy_header_len + SENSOR_COMMON_PAYLOAD_LEN + legacy_weather_len; - case SensorType::MAX_VALUE: - return 0; - } - return 0; -} - -static bool sensor_has_valid_layout(SensorType type, char *buf, uint32_t len) { - if (len == sensor_legacy_len(type)) return true; - if (len < SENSOR_RECORD_HEADER_LEN) return false; - return (uint32_t)SENSOR_RECORD_HEADER_LEN + (uint8_t)buf[1] + (uint8_t)buf[2] == len; +static bool sensor_has_valid_layout(char *buf, uint32_t slot_size, uint32_t *len_out) { + if ((uint8_t)buf[0] >= (uint8_t)SensorType::MAX_VALUE) return false; + uint32_t len = SENSOR_RECORD_HEADER_LEN + (uint8_t)buf[1] + (uint8_t)buf[2]; + if (len < SENSOR_RECORD_HEADER_LEN || len > slot_size) return false; + if (len_out) *len_out = len; + return true; } -Sensor::Sensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint16_t flag) : +Sensor::Sensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint8_t flag) : interval(interval), min(min), max(max), flag(flag), unit(unit) { strncpy(this->name, name, SENSOR_NAME_LEN); this->name[SENSOR_NAME_LEN - 1] = 0; @@ -128,11 +108,11 @@ uint32_t Sensor::serialize(char* buf) { memcpy(buf + i, this->name, SENSOR_NAME_LEN); i += SENSOR_NAME_LEN; buf[i++] = static_cast(this->unit); - i += write_buf(buf + i, this->interval); - i += write_buf(buf + i, this->flag); - i += write_buf(buf + i, this->min); - i += write_buf(buf + i, this->max); - i += write_buf(buf + i, this->uuid); + i += write_buf(buf + i, this->interval); + i += write_buf(buf + i, this->flag); + i += write_buf(buf + i, this->min); + i += write_buf(buf + i, this->max); + i += write_buf(buf + i, this->uuid); buf[common_len_pos] = static_cast(i - common_start); uint32_t subclass_start = i; @@ -142,24 +122,13 @@ uint32_t Sensor::serialize(char* buf) { } uint32_t Sensor::_deserialize(char* buf, uint32_t len, uint8_t *subclass_len) { - SensorType type = static_cast(buf[0]); - uint32_t legacy_len = sensor_legacy_len(type); - uint32_t i = 1; // Skip sensor type - uint8_t common_len = SENSOR_COMMON_PAYLOAD_LEN; - *subclass_len = 0; - - if (len != legacy_len) { - if (len < SENSOR_RECORD_HEADER_LEN) return len; - common_len = static_cast(buf[1]); - *subclass_len = static_cast(buf[2]); - if ((uint32_t)SENSOR_RECORD_HEADER_LEN + common_len + *subclass_len != len) return len; - i = SENSOR_RECORD_HEADER_LEN; - } else { - *subclass_len = static_cast(legacy_len - 1 - SENSOR_COMMON_PAYLOAD_LEN); - } + uint8_t common_len = static_cast(buf[1]); + *subclass_len = static_cast(buf[2]); - uint32_t common_start = i; - uint32_t common_end = common_start + common_len; + if ((uint32_t)SENSOR_RECORD_HEADER_LEN + common_len + *subclass_len != len) return len; + + uint32_t i = SENSOR_RECORD_HEADER_LEN; + uint32_t common_end = i + common_len; if (i + SENSOR_NAME_LEN <= common_end) { memcpy(this->name, buf + i, SENSOR_NAME_LEN); @@ -170,19 +139,11 @@ uint32_t Sensor::_deserialize(char* buf, uint32_t len, uint8_t *subclass_len) { if (i + 1 <= common_end) this->unit = static_cast(buf[i]); i++; - if (i + sizeof(uint32_t) <= common_end) this->interval = read_buf(buf, &i); - else i += sizeof(uint32_t); - - if (i + sizeof(uint16_t) <= common_end) this->flag = read_buf(buf, &i); - else i += sizeof(uint16_t); - - if (i + sizeof(float) <= common_end) this->min = read_buf(buf, &i); - else i += sizeof(float); - - if (i + sizeof(float) <= common_end) this->max = read_buf(buf, &i); - else i += sizeof(float); - - if (i + sizeof(uint16_t) <= common_end) this->uuid = read_buf(buf, &i); + read_buf(buf, &i, common_end, this->interval); + read_buf(buf, &i, common_end, this->flag); + read_buf(buf, &i, common_end, this->min); + read_buf(buf, &i, common_end, this->max); + read_buf(buf, &i, common_end, this->uuid); return common_end; } @@ -201,6 +162,7 @@ float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { if (this->flag & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { uint8_t idx = Sensor::find_index(this->uuid); if (idx < MAX_SENSORS && sensors[idx].interval) { + if (this->point_count == 0) return 1.f; float value = sensors[idx].value; if (value <= this->points[0].x) return this->points[0].y; if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; @@ -300,18 +262,13 @@ Sensor *Sensor::parse(os_file_type file) { active_sensor->~Sensor(); active_sensor = nullptr; } - uint32_t len = 0; - file_read(file, &len, sizeof(len)); - - if (len == 0 || len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) return nullptr; + const uint32_t slot_size = TMP_BUFFER_SIZE; + file_read(file, tmp_buffer, slot_size); - file_read(file, tmp_buffer, len); - file_seek(file, TMP_BUFFER_SIZE - sizeof(uint32_t) - len, FileSeekMode::Current); - - if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) return nullptr; + uint32_t len = 0; + if (!sensor_has_valid_layout((char*)tmp_buffer, slot_size, &len)) return nullptr; - SensorType sensor_type = static_cast(*tmp_buffer); - if (!sensor_has_valid_layout(sensor_type, (char*)tmp_buffer, len)) return nullptr; + SensorType sensor_type = static_cast(tmp_buffer[0]); switch (sensor_type) { case SensorType::Aggregate: @@ -330,7 +287,8 @@ Sensor *Sensor::parse(os_file_type file) { } Sensor *Sensor::get(uint8_t index) { - uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; + const uint32_t slot_size = TMP_BUFFER_SIZE; + uint32_t pos = 1 + slot_size * index; os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); if (file) { @@ -346,22 +304,16 @@ Sensor *Sensor::get(uint8_t index) { } void Sensor::write(Sensor *sensor, uint8_t index) { - uint32_t pos = 1 + (uint32_t)TMP_BUFFER_SIZE * index; - uint32_t len = 0; + const uint32_t slot_size = TMP_BUFFER_SIZE; + uint32_t pos = 1 + slot_size * index; - // Zero data area before serializing so the full TMP_BUFFER_SIZE slot is clean. - memset(tmp_buffer, 0, TMP_BUFFER_SIZE - sizeof(uint32_t)); - if (sensor) { - len = sensor->serialize(tmp_buffer); - if (len > (TMP_BUFFER_SIZE - sizeof(uint32_t))) - len = TMP_BUFFER_SIZE - sizeof(uint32_t); - } + memset(tmp_buffer, 0, slot_size); + if (sensor) sensor->serialize(tmp_buffer); os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); if (file) { file_seek(file, pos, FileSeekMode::Set); - file_write(file, &len, sizeof(len)); - file_write(file, tmp_buffer, TMP_BUFFER_SIZE - sizeof(uint32_t)); // always write full slot + file_write(file, tmp_buffer, slot_size); file_close(file); } else { DEBUG_PRINT("Failed to open file: "); @@ -399,9 +351,10 @@ unsigned char Sensor::del(uint8_t index) { if (index >= OpenSprinkler::nsensors) return 0; if (OpenSprinkler::nsensors == 0) return 0; + const uint32_t slot_size = TMP_BUFFER_SIZE; // erase by copying backward for (uint8_t i = index; i < OpenSprinkler::nsensors - 1; i++) { - file_copy_block(SENSORS_FILENAME, 1 + (uint32_t)(i + 1) * TMP_BUFFER_SIZE, 1 + (uint32_t)i * TMP_BUFFER_SIZE, TMP_BUFFER_SIZE, tmp_buffer); + file_copy_block(SENSORS_FILENAME, 1 + (uint32_t)(i + 1) * slot_size, 1 + (uint32_t)i * slot_size, slot_size, tmp_buffer); // also shift in-memory state OpenSprinkler::sensors[i] = OpenSprinkler::sensors[i + 1]; } diff --git a/sensors/sensor.h b/sensors/sensor.h index e970d085c..10f2357d4 100644 --- a/sensors/sensor.h +++ b/sensors/sensor.h @@ -13,6 +13,7 @@ // No types from this file are involved in the onboard sensor logic. #include +#include #include "../utils.h" #include "../defines.h" #include "../bfiller.h" @@ -24,11 +25,10 @@ // common payload, subclass payload. Payloads are append-only. #define SENSOR_RECORD_HEADER_LEN 3 -// Common payload length for legacy records that predate length bytes: -// name[33] + unit[1] + interval[4] + flag[2] + min[4] + max[4] + uuid[2]. -// Do not change this when appending new common fields; new records carry their -// own common_len byte, while this constant is only for reading old sens.dat. -#define SENSOR_COMMON_PAYLOAD_LEN 50 +// Current common payload length: +// name[33] + unit[1] + interval[4] + flag[1] + min[4] + max[4] + uuid[2]. +// Future common fields should be appended; new records carry their own common_len byte. +#define SENSOR_COMMON_PAYLOAD_LEN 49 #define SENSOR_UUID_NONE 0 // sentinel: "no sensor assigned" (0 = uninitialized/disabled) @@ -179,7 +179,7 @@ typedef enum { class Sensor { public: - Sensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag); + Sensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint8_t flag); Sensor(); virtual ~Sensor() {} @@ -203,7 +203,7 @@ class Sensor { uint32_t interval = 1; float min = 0.f; float max = 0.f; - uint16_t flag = 0; + uint8_t flag = 0; uint16_t uuid = 0; // assigned by write_sensor on creation; 0 = not yet assigned SensorUnit unit = SensorUnit::None; char name[SENSOR_NAME_LEN] = {0}; @@ -235,7 +235,7 @@ enum class AggregateAction : uint8_t { typedef Sensor* (*SensorGetter)(uint8_t); -enum class WeatherAction { +enum class WeatherAction : uint8_t { MAX_VALUE, }; @@ -277,11 +277,11 @@ inline uint32_t write_buf(char* buf, T val) { } template -inline T read_buf(char* buf, uint32_t* i) { - T val; - memcpy(&val, buf + (*i), sizeof(T)); +inline bool read_buf(char* buf, uint32_t* i, uint32_t end, T& out) { + bool ok = (*i + sizeof(T) <= end); + if (ok) memcpy(&out, buf + *i, sizeof(T)); *i += sizeof(T); - return val; + return ok; } const char *enum_string(SensorUnitGroup group); diff --git a/sensors/weather_sensor.cpp b/sensors/weather_sensor.cpp index 5271ac232..8f0d9abe9 100644 --- a/sensors/weather_sensor.cpp +++ b/sensors/weather_sensor.cpp @@ -1,6 +1,6 @@ #include "weather_sensor.h" -WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint16_t flag, WeatherGetter weather_getter, WeatherAction action) : +WeatherSensor::WeatherSensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint8_t flag, WeatherGetter weather_getter, WeatherAction action) : Sensor(interval, min, max, name, unit, flag), action(action), weather_getter(weather_getter) { diff --git a/sensors/weather_sensor.h b/sensors/weather_sensor.h index 5bfbdbf74..d39cbc26e 100644 --- a/sensors/weather_sensor.h +++ b/sensors/weather_sensor.h @@ -4,7 +4,7 @@ class WeatherSensor : public Sensor { public: - WeatherSensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint16_t flag, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(uint32_t interval, float min, float max, const char *name, SensorUnit unit, uint8_t flag, WeatherGetter weather_getter, WeatherAction action); WeatherSensor(WeatherGetter weather_getter, char *buf, uint32_t len); void emit_extra_json(BufferFiller *bfill); From 6f1f96f004f5a66580c4cca3dc5d8ba7c992fdeb Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Thu, 30 Apr 2026 23:49:59 -0400 Subject: [PATCH 111/115] remove redundant check --- sensors/sensor.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp index e13238fe2..504411bfa 100644 --- a/sensors/sensor.cpp +++ b/sensors/sensor.cpp @@ -125,8 +125,6 @@ uint32_t Sensor::_deserialize(char* buf, uint32_t len, uint8_t *subclass_len) { uint8_t common_len = static_cast(buf[1]); *subclass_len = static_cast(buf[2]); - if ((uint32_t)SENSOR_RECORD_HEADER_LEN + common_len + *subclass_len != len) return len; - uint32_t i = SENSOR_RECORD_HEADER_LEN; uint32_t common_end = i + common_len; From 20348964d1205931739df38022f50b847d11a2f2 Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Fri, 1 May 2026 07:19:14 -0400 Subject: [PATCH 112/115] rename SensorAdjustment::flag to reserved since for now this variable is not used (enabled can be implied from the uuid value) --- opensprinkler_server.cpp | 12 ++---------- sensors/sensor.cpp | 10 +++++----- sensors/sensor.h | 10 +++------- 3 files changed, 10 insertions(+), 22 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 1f27271e7..a06605064 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -850,13 +850,11 @@ void server_change_program(OTF_PARAMS_DEF) { char *end; SensorAdjustment *adj = nullptr; - uint32_t flag = 0; uint32_t adj_uuid = SENSOR_UUID_NONE; uint32_t point_count = 0; sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; if ((adj = SensorAdjustment::read(pid, pd.nprograms))) { - flag = adj->flag; adj_uuid = adj->uuid; point_count = adj->point_count; @@ -865,12 +863,6 @@ void server_change_program(OTF_PARAMS_DEF) { } } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flag"), true)) { - flag=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (flag > 0xFF) handle_return(HTML_DATA_OUTOFBOUND); -} - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_uuid"), true)) { adj_uuid=strtoul(tmp_buffer, &end, 10); if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); @@ -897,7 +889,7 @@ void server_change_program(OTF_PARAMS_DEF) { point_count = i; } - SensorAdjustment snadj(flag, (uint16_t)adj_uuid, point_count, points); + SensorAdjustment snadj((uint16_t)adj_uuid, point_count, points); snadj_ptr = &snadj; #endif @@ -1062,7 +1054,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { { SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); if (adj) { - bfill.emit_p(PSTR("{\"flag\":$D,\"uuid\":$D,\"splits\":["), adj->flag, adj->uuid); + bfill.emit_p(PSTR("{\"uuid\":$D,\"splits\":["), adj->uuid); for (int j = 0; j < adj->point_count; j++) { if (j) bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp index 504411bfa..db975cba1 100644 --- a/sensors/sensor.cpp +++ b/sensors/sensor.cpp @@ -146,9 +146,9 @@ uint32_t Sensor::_deserialize(char* buf, uint32_t len, uint8_t *subclass_len) { return common_end; } -SensorAdjustment::SensorAdjustment(uint8_t flag, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { - this->flag = flag; +SensorAdjustment::SensorAdjustment(uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { this->uuid = uuid; + this->reserved = 0; if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; this->point_count = point_count; for (size_t i = 0; i < point_count; i++) { @@ -157,7 +157,7 @@ SensorAdjustment::SensorAdjustment(uint8_t flag, uint16_t uuid, uint8_t point_co } float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { - if (this->flag & (1 << SENADJ_FLAG_ENABLE) && this->uuid != SENSOR_UUID_NONE) { + if (this->uuid != SENSOR_UUID_NONE) { uint8_t idx = Sensor::find_index(this->uuid); if (idx < MAX_SENSORS && sensors[idx].interval) { if (this->point_count == 0) return 1.f; @@ -188,7 +188,7 @@ float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { } SensorAdjustment *SensorAdjustment::read(uint8_t index, uint8_t nprograms) { - static SensorAdjustment result(0, SENSOR_UUID_NONE, 0, nullptr); + static SensorAdjustment result(SENSOR_UUID_NONE, 0, nullptr); if (index >= nprograms) return nullptr; os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); @@ -213,7 +213,7 @@ void SensorAdjustment::write(SensorAdjustment *adj, uint8_t index) { os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); if (file) { - SensorAdjustment disabled(0, SENSOR_UUID_NONE, 0, nullptr); + SensorAdjustment disabled(SENSOR_UUID_NONE, 0, nullptr); uint32_t cur_size = file_size(file); if (cur_size < pos) { diff --git a/sensors/sensor.h b/sensors/sensor.h index 10f2357d4..01a6ffef7 100644 --- a/sensors/sensor.h +++ b/sensors/sensor.h @@ -248,23 +248,19 @@ typedef struct { #define SENSOR_ADJUSTMENT_POINTS 8 -typedef enum { - SENADJ_FLAG_ENABLE = 0, -} senadj_flag; - class SensorAdjustment { public: - SensorAdjustment(uint8_t flag, uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); + SensorAdjustment(uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); static SensorAdjustment *read(uint8_t index, uint8_t nprograms); // returns statically allocated object, do not delete static void write(SensorAdjustment *adj, uint8_t index); float get_adjustment_factor(sensor_memory_t *sensors); - sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; uint16_t uuid; // sensor UUID (SENSOR_UUID_NONE = adjustment disabled) - uint8_t flag; uint8_t point_count; + uint8_t reserved; // reserved for future use (e.g. REQUIRE_VALID, CLAMP_OUTPUT) + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; }; #define SENSOR_ADJUSTMENT_SIZE sizeof(SensorAdjustment) From 7551b1d44d00e40d532f2ff3551b3461d7b94b8c Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Fri, 1 May 2026 13:33:13 -0400 Subject: [PATCH 113/115] revert back to use SensorAdjustment::flag as it allows indepenently turning off adjustment without losing parameters; update code to allow duplicate x value in case the user wants a sharp threshold operation --- opensprinkler_server.cpp | 102 +++++++++++++++++++-------------------- sensors/sensor.cpp | 33 +++++-------- sensors/sensor.h | 9 +++- 3 files changed, 71 insertions(+), 73 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index a06605064..c8d7f4f4a 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -783,16 +783,18 @@ void server_moveup_program(OTF_PARAMS_DEF) { /** * Change a program * Command: /cp?pw=xxx&pid=x&v=[flag,days0,days1,[start0,start1,start2,start3],[dur0,dur1,dur2..]] - * &name=x&from=x&to=x + * &name=x&from=x&to=x&snadj=flag,uuid,x0,y0,x1,y1,... * - * pw: password - * pid: program index - * flag: program flag - * start?:up to 4 start times - * dur?: station water time - * name: program name - * from: start date of the program: an integer that's (month*32+day) - * to: end date of the program, same format as from + * pw: password + * pid: program index (-1 to add new) + * v: packed program data: flag, days0, days1, start times, durations + * name: program name + * from: start date (month*32+day); 0 = no restriction + * to: end date, same format as from; 0 = no restriction + * snadj: (optional) sensor adjustment — comma-separated: enable flag (uint8), sensor UUID (uint16), + * then x,y point pairs (floats). Omit to preserve existing adjustment. + * e.g. snadj=1,5,0.0,1.0,500.0,0.5 — enabled, sensor uuid=5, two interpolation points. + * snadj=0,0 — disabled with no sensor assigned. */ const char _str_program[] PROGMEM = "Program "; void server_change_program(OTF_PARAMS_DEF) { @@ -847,50 +849,48 @@ void server_change_program(OTF_PARAMS_DEF) { SensorAdjustment *snadj_ptr = nullptr; #if defined(USE_SENSORS) - char *end; - - SensorAdjustment *adj = nullptr; - uint32_t adj_uuid = SENSOR_UUID_NONE; - uint32_t point_count = 0; - sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; - - if ((adj = SensorAdjustment::read(pid, pd.nprograms))) { - adj_uuid = adj->uuid; - point_count = adj->point_count; - - for (size_t i = 0; i < point_count; i++) { - points[i] = adj->points[i]; - } - } - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_uuid"), true)) { - adj_uuid=strtoul(tmp_buffer, &end, 10); - if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); - if (adj_uuid > 0xFFFF) handle_return(HTML_DATA_OUTOFBOUND); -} - - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_points"), true)) { - uint32_t i = 0; - float x, y; - const char *ptr = tmp_buffer; - int result; - float last_x = -std::numeric_limits::infinity();; - - while (*ptr != '\0') { - if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); - result = sscanf(ptr, "%f,%f;", &x, &y); - if (result != 2 || !isfinite(x) || !isfinite(y) || y < 0 || x < last_x) { - handle_return(HTML_DATA_FORMATERROR); + SensorAdjustment snadj(SENSOR_UUID_NONE, 0, 0, nullptr); + // snadj=flag,uuid,x0,y0,x1,y1,... — if absent, existing adjustment is left untouched + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("snadj"), true)) { + char *ptr = tmp_buffer; + char *end; + uint8_t adj_flag = 0; + uint16_t adj_uuid = SENSOR_UUID_NONE; + uint32_t point_count = 0; + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS] = {0.0, 0.0}; + uint32_t v; + + v = strtoul(ptr, &end, 10); + if (end == ptr || (*end != ',' && *end != '\0') || v > 0xFF) handle_return(HTML_DATA_FORMATERROR); + adj_flag = (uint8_t)v; + + if (*end == ',') { + ptr = end + 1; + v = strtoul(ptr, &end, 10); + if (end == ptr || (*end != ',' && *end != '\0') || v > 0xFFFF) handle_return(HTML_DATA_FORMATERROR); + adj_uuid = (uint16_t)v; + + if (*end == ',') { + ptr = end + 1; + float last_x = -std::numeric_limits::infinity(); + while (*ptr != '\0') { + if (point_count >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); + float x = strtof(ptr, &end); + if (end == ptr || *end != ',') handle_return(HTML_DATA_FORMATERROR); + ptr = end + 1; + float y = strtof(ptr, &end); + if (end == ptr || (*end != ',' && *end != '\0')) handle_return(HTML_DATA_FORMATERROR); + if (!isfinite(x) || !isfinite(y) || y < 0 || x < last_x) handle_return(HTML_DATA_FORMATERROR); + points[point_count++] = {x, y}; + last_x = x; + ptr = (*end == ',') ? end + 1 : end; + } } - points[i++] = sensor_adjustment_point_t {x, y}; - last_x = x; - while (*ptr != '\0' && *(ptr++) != ';') {} } - point_count = i; - } - SensorAdjustment snadj((uint16_t)adj_uuid, point_count, points); - snadj_ptr = &snadj; + snadj = SensorAdjustment(adj_uuid, point_count, adj_flag, points); + snadj_ptr = &snadj; + } #endif if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); @@ -1054,7 +1054,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { { SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); if (adj) { - bfill.emit_p(PSTR("{\"uuid\":$D,\"splits\":["), adj->uuid); + bfill.emit_p(PSTR("{\"flag\":$D,\"uuid\":$D,\"splits\":["), adj->flag, adj->uuid); for (int j = 0; j < adj->point_count; j++) { if (j) bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp index db975cba1..cca27615e 100644 --- a/sensors/sensor.cpp +++ b/sensors/sensor.cpp @@ -146,9 +146,10 @@ uint32_t Sensor::_deserialize(char* buf, uint32_t len, uint8_t *subclass_len) { return common_end; } -SensorAdjustment::SensorAdjustment(uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t* points) { +SensorAdjustment::SensorAdjustment(uint16_t uuid, uint8_t point_count, uint8_t flag, sensor_adjustment_point_t* points) { this->uuid = uuid; - this->reserved = 0; + this->flag = flag; + memset(this->points, 0, sizeof(this->points)); if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; this->point_count = point_count; for (size_t i = 0; i < point_count; i++) { @@ -157,26 +158,22 @@ SensorAdjustment::SensorAdjustment(uint16_t uuid, uint8_t point_count, sensor_ad } float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { - if (this->uuid != SENSOR_UUID_NONE) { + if (this->uuid != SENSOR_UUID_NONE && (this->flag & (1 << SENADJ_FLAG_ENABLE))) { uint8_t idx = Sensor::find_index(this->uuid); if (idx < MAX_SENSORS && sensors[idx].interval) { if (this->point_count == 0) return 1.f; float value = sensors[idx].value; - if (value <= this->points[0].x) return this->points[0].y; + // duplicate x values form a step: x == T maps to the rightmost point at T + if (value < this->points[0].x) return this->points[0].y; if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; - uint8_t i; - - for (i = 0; i < this->point_count - 1; i++) { - if (value < this->points[i + 1].x) { - break; - } - } + uint8_t i = 0; + while (i + 1 < this->point_count - 1 && value >= this->points[i + 1].x) i++; sensor_adjustment_point_t left = this->points[i]; sensor_adjustment_point_t right = this->points[i + 1]; - if (right.x == left.x) return left.y; + if (right.x == left.x) return right.y; value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; @@ -188,20 +185,16 @@ float SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { } SensorAdjustment *SensorAdjustment::read(uint8_t index, uint8_t nprograms) { - static SensorAdjustment result(SENSOR_UUID_NONE, 0, nullptr); + static SensorAdjustment result(SENSOR_UUID_NONE, 0, 0, nullptr); if (index >= nprograms) return nullptr; os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); if (file) { uint32_t pos = (uint32_t)index * SENSOR_ADJUSTMENT_SIZE; - if (file_size(file) < pos + SENSOR_ADJUSTMENT_SIZE) { - file_close(file); - return nullptr; - } file_seek(file, pos, FileSeekMode::Set); - file_read(file, &result, SENSOR_ADJUSTMENT_SIZE); + bool ok = (file_read(file, &result, SENSOR_ADJUSTMENT_SIZE) == (int)SENSOR_ADJUSTMENT_SIZE); file_close(file); - if (result.uuid != SENSOR_UUID_NONE) { + if (ok && result.uuid != SENSOR_UUID_NONE) { return &result; } } @@ -213,7 +206,7 @@ void SensorAdjustment::write(SensorAdjustment *adj, uint8_t index) { os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); if (file) { - SensorAdjustment disabled(SENSOR_UUID_NONE, 0, nullptr); + SensorAdjustment disabled(SENSOR_UUID_NONE, 0, 0, nullptr); uint32_t cur_size = file_size(file); if (cur_size < pos) { diff --git a/sensors/sensor.h b/sensors/sensor.h index 01a6ffef7..dfacb4b0a 100644 --- a/sensors/sensor.h +++ b/sensors/sensor.h @@ -248,9 +248,14 @@ typedef struct { #define SENSOR_ADJUSTMENT_POINTS 8 +typedef enum { + SENADJ_FLAG_ENABLE = 0, + SENADJ_FLAG_COUNT +} senadj_flag; + class SensorAdjustment { public: - SensorAdjustment(uint16_t uuid, uint8_t point_count, sensor_adjustment_point_t *points); + SensorAdjustment(uint16_t uuid, uint8_t point_count, uint8_t flag, sensor_adjustment_point_t *points); static SensorAdjustment *read(uint8_t index, uint8_t nprograms); // returns statically allocated object, do not delete static void write(SensorAdjustment *adj, uint8_t index); @@ -259,7 +264,7 @@ class SensorAdjustment { uint16_t uuid; // sensor UUID (SENSOR_UUID_NONE = adjustment disabled) uint8_t point_count; - uint8_t reserved; // reserved for future use (e.g. REQUIRE_VALID, CLAMP_OUTPUT) + uint8_t flag; // bit 0 (SENADJ_FLAG_ENABLE): enable sensor adjustment sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; }; From bf44c701a9b3143ec05fcdc1bb8928a07fbae7dd Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 2 May 2026 08:15:44 -0400 Subject: [PATCH 114/115] suport mock ads1115 for LInux/Demo; given all platforms now support sensors, remove USE_SENSORS and USE_ADS1115 macros; fix millis issue on 64-bit linux; --- OpenSprinkler.cpp | 25 ++++++++++--------------- OpenSprinkler.h | 12 ------------ ads1115.cpp | 34 ++++++++++++++++++++++++++++++---- ads1115.h | 19 +++++++++++-------- build.sh | 6 ++++-- defines.h | 14 ++++++++------ main.cpp | 19 +++++++++---------- opensprinkler_server.cpp | 14 -------------- program.cpp | 8 -------- program.h | 2 +- sensors/ads1115_sensor.cpp | 10 +++++----- sensors/ads1115_sensor.h | 6 +----- sensors/sensor.cpp | 4 ---- sensors/sensor.h | 20 ++++++++------------ utils.cpp | 14 ++++++++++---- utils.h | 4 ++-- 16 files changed, 99 insertions(+), 112 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 7011f8140..3e7c53bf5 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -29,9 +29,7 @@ #include "ArduinoJson.hpp" /** Declare static data members */ -#if defined(USE_SENSORS) sensor_memory_t OpenSprinkler::sensors[64] = {0}; -#endif OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; ConStatus OpenSprinkler::status; @@ -89,9 +87,7 @@ extern unsigned char curr_alert_sid; SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); #endif -#if defined(USE_ADS1115) - ADS1115 *OpenSprinkler::ads1115_devices[4] = {nullptr}; -#endif +ADS1115 *OpenSprinkler::ads1115_devices[4] = {nullptr}; #if defined(ESP8266) unsigned char OpenSprinkler::state = OS_STATE_INITIAL; @@ -641,7 +637,7 @@ unsigned char OpenSprinkler::start_ether() { uint32_t timeout = millis()+60000; // 60 seconds time out unsigned char timecount = 1; - while (!eth.connected() && (long)(millis()-timeout)<0) { // overflow proof + while (!eth.connected() && (int32_t)((uint32_t)millis()-timeout)<0) { // overflow proof DEBUG_PRINT("."); lcd.setCursor(13, 2); lcd.print(timecount); @@ -1018,14 +1014,17 @@ void OpenSprinkler::begin() { //DEBUG_PRINTLN(get_runtime_path()); #endif -#if defined(USE_ADS1115) for (size_t i = 0; i < 4; i++) { uint8_t address = 0x48 + i; +#if defined(ADS1115_HARDWARE) if (detect_i2c(address)) { ads1115_devices[i] = new ADS1115(address); } - } +#else + // DEMO/SIM: instantiate all four mock chips so all 16 channels are available + ads1115_devices[i] = new ADS1115(address); #endif + } } #if defined(ESP8266) @@ -1063,7 +1062,7 @@ void OpenSprinkler::latch_boost(int8_t volt) { uint32_t boost_timeout = millis() + (iopts[IOPT_BOOST_TIME]<<2); digitalWriteExt(PIN_BOOST, HIGH); // boost until either top voltage is reached or boost timeout is reached - while((long)(millis()-boost_timeout)<0 && analogRead(PIN_CURR_SENSE)read((uint8_t*)ether_buffer+pos, nbytes); pos+=nbytes; } - if((long)(millis()-stoptime)>0) { // overflow proof + if((int32_t)((uint32_t)millis()-stoptime)>0) { // overflow proof DEBUG_PRINTLN(F("host timeout occured")); //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far break; @@ -2154,12 +2153,10 @@ void OpenSprinkler::factory_reset() { // 4. write program data: just need to write a program counter: 0 file_write_byte(PROG_FILENAME, 0, 0); -#if defined(USE_SENSORS) // remove all sensor files, so they will be re-created during loading remove_file(SENSORS_FILENAME); remove_sensor_log(); remove_file(SENADJ_FILENAME); -#endif // 5. write 'done' file file_write_byte(DONE_FILENAME, 0, 1); @@ -2470,7 +2467,6 @@ void OpenSprinkler::raindelay_stop() { nvdata_save(); } -#if defined(USE_SENSORS) /** Sensor functions */ void list_all_files() { @@ -2596,7 +2592,7 @@ void OpenSprinkler::poll_sensors() { sensor_memory_t &mem = sensors[i]; if (!mem.interval || !(mem.flag & (1 << SENSOR_FLAG_ENABLE))) continue; - if ((long)(millis() - mem.next_update) <= 0) continue; + if ((int32_t)((uint32_t)millis() - mem.next_update) <= 0) continue; Sensor *sensor = Sensor::get(i); if (!sensor) { @@ -2626,7 +2622,6 @@ void OpenSprinkler::poll_sensors() { float OpenSprinkler::get_sensor_weather_data(WeatherAction action) { return NAN; // TODO make function for WeatherSensor } -#endif /** LCD and button functions */ #if defined(USE_DISPLAY) diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 702f0945e..8b0505482 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -67,16 +67,11 @@ #include "SSD1306Display.h" #endif -#if defined(USE_SENSORS) #include "sensors/sensor.h" #include "sensors/aggregate_sensor.h" #include "sensors/weather_sensor.h" -#endif - -#if defined(USE_ADS1115) #include "ads1115.h" #include "sensors/ads1115_sensor.h" -#endif #if defined(ESP8266) extern ESP8266WebServer *update_server; @@ -243,11 +238,8 @@ class OpenSprinkler { static SSD1306Display lcd; // 128x64 OLED display #endif -#if defined(USE_ADS1115) static ADS1115 *ads1115_devices[4]; -#endif -#if defined(USE_SENSORS) union SensorUnion { ADS1115Sensor ads1115; AggregateSensor aggregate; @@ -255,8 +247,6 @@ class OpenSprinkler { }; static sensor_memory_t sensors[MAX_SENSORS]; -#endif - #if defined(OSPI) static unsigned char pin_sr_data; // RPi shift register data pin to handle RPi rev. 1 #endif @@ -387,11 +377,9 @@ class OpenSprinkler { static OTCConfig otc; // -- Sensor functions -#if defined(USE_SENSORS) void log_sensor(uint8_t sid, float value); static void poll_sensors(); static float get_sensor_weather_data(WeatherAction action); -#endif // -- LCD functions #if defined(USE_DISPLAY) static void lcd_print_time(time_os_t t); // print current time diff --git a/ads1115.cpp b/ads1115.cpp index 02c8184cf..3d3fc519d 100644 --- a/ads1115.cpp +++ b/ads1115.cpp @@ -1,6 +1,7 @@ #include "ads1115.h" +#include "utils.h" // millis() on Linux/DEMO -#if defined(USE_SENSORS) +#include #if defined(ESP8266) ADS1115::ADS1115(uint8_t address, TwoWire& wire) : _address(address), _wire(&wire) {} @@ -37,7 +38,7 @@ uint16_t ADS1115::_read_register(uint8_t reg) { return 0; } -#else // OSPI +#elif defined(OSPI) ADS1115::ADS1115(uint8_t address, I2CBus& bus) : _address(address), _i2c(bus, address) {} ADS1115::ADS1115(uint8_t address) : ADS1115(address, Bus) {} @@ -58,9 +59,22 @@ void ADS1115::_write_register(uint8_t reg, uint16_t value) { uint16_t ADS1115::_read_register(uint8_t reg) { return this->swap_reg((uint16_t)(this->_i2c.read_word(reg) & 0xFFFF)); } + +#else +// Simulator/mock backend: no I2C hardware, methods that touch the chip are stubs. +// get_pin_value() below produces synthetic counts based on time + channel index. +ADS1115::ADS1115(uint8_t address) : _address(address) {} + +bool ADS1115::begin() { + return (this->_address >= 0x48) && (this->_address <= 0x4B); +} + +void ADS1115::_write_register(uint8_t reg, uint16_t value) { (void)reg; (void)value; } +uint16_t ADS1115::_read_register(uint8_t reg) { (void)reg; return 0; } #endif int16_t ADS1115::get_pin_value(uint8_t pin) { +#if defined(ADS1115_HARDWARE) this->request_pin(pin); uint32_t start = millis(); while (this->is_busy()) { @@ -77,11 +91,23 @@ int16_t ADS1115::get_pin_value(uint8_t pin) { } return this->get_value(); +#else + // Mock: per-channel slow sine, ~0.5..2.5 V, mapped to single-ended counts [0, 32767]. + float t = millis() * 0.001f; + float phase = ((this->_address - 0x48) * 4 + pin) * 0.7f; + float volts = 1.5f + 1.0f * sinf(t * 0.1f + phase); + int32_t counts = (int32_t)(volts * 1000.0f / ADS1115_SCALE_FACTOR); + if (counts < 0) counts = 0; + if (counts > 32767) counts = 32767; + return (int16_t)counts; +#endif } void ADS1115::request_pin(uint8_t pin) { +#if defined(ADS1115_HARDWARE) uint16_t config = 0x8000 | ((4 + ((uint16_t)pin)) << 12) | 0x0100 | (4 << 5); this->_write_register(0x01, config); +#else + (void)pin; +#endif } - -#endif \ No newline at end of file diff --git a/ads1115.h b/ads1115.h index a5f186619..5702091b4 100644 --- a/ads1115.h +++ b/ads1115.h @@ -2,16 +2,22 @@ #include "defines.h" -#if defined(USE_SENSORS) - #include #define ADS1115_SCALE_FACTOR (6144.0 / 32768.0) +// Hardware ADS1115 chip is wired up on ESP8266 (Arduino I2C via Wire) and OSPi +// (Linux I2C via libi2c). Other targets (DEMO/SIM) use a mock backend that +// produces synthetic counts so the sensor pipeline is exercisable end-to-end +// without real hardware. +#if defined(ESP8266) || defined(OSPI) +#define ADS1115_HARDWARE +#endif + #if defined(ESP8266) #include #include -#else +#elif defined(OSPI) #include "i2cd.h" #endif @@ -19,7 +25,7 @@ class ADS1115 { public: #if defined(ESP8266) ADS1115(uint8_t address, TwoWire& wire); -#else +#elif defined(OSPI) ADS1115(uint8_t address, I2CBus& bus); #endif ADS1115(uint8_t address); @@ -40,7 +46,7 @@ class ADS1115 { uint8_t _address; #if defined(ESP8266) TwoWire *_wire; -#else +#elif defined(OSPI) I2CDevice _i2c; uint16_t swap_reg(uint16_t val) { return (val << 8) | (val >> 8); @@ -50,6 +56,3 @@ class ADS1115 { void _write_register(uint8_t reg, uint16_t value); uint16_t _read_register(uint8_t reg); }; - - -#endif \ No newline at end of file diff --git a/build.sh b/build.sh index 5a75868dc..57a3ef730 100755 --- a/build.sh +++ b/build.sh @@ -53,7 +53,8 @@ if [ "$1" == "demo" ]; then ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - g++ -o OpenSprinkler -DDEMO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto + sens=$(ls sensors/*.cpp) + g++ -o OpenSprinkler -DDEMO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp ads1115.cpp $sens -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto else echo "Installing required libraries..." apt-get update @@ -68,7 +69,8 @@ else ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - g++ -o OpenSprinkler -DOSPI -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB + sens=$(ls sensors/*.cpp) + g++ -o OpenSprinkler -DOSPI -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp i2cd.cpp ads1115.cpp $sens -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB fi diff --git a/defines.h b/defines.h index b8c7e04fc..e8010370e 100755 --- a/defines.h +++ b/defines.h @@ -165,8 +165,14 @@ enum { #define MAX_SENSORS 64 #define SENSOR_LOG_MAGIC 0x55 #define SENSOR_LOG_VERSION 0x01 -#define SENSOR_LOG_MAX_FILES 50 // number of data files in the rotation -#define SENSOR_LOG_RECORDS_PER_FILE 819 // records per file; 819×10 B = 8 190 B fits in one 8 KB LittleFS block +#define SENSOR_LOG_MAX_FILES 50 // number of data files in the rotation +#if defined(ESP8266) + #define SENSOR_LOG_RECORDS_PER_FILE 819 // records per file; 819×10 B = 8 190 B fits in one 8 KB LittleFS block +#else + // Linux/OSPi/DEMO: filesystem doesn't penalize large appends, so a much bigger + // per-file count is cheap. 50 × 65 535 ≈ 3.3 M records ≈ 33 MB. + #define SENSOR_LOG_RECORDS_PER_FILE 65535 +#endif #define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) @@ -431,8 +437,6 @@ enum { #define V2_PIN_BOOST_SEL IOEXP_PIN+8 #define USE_DISPLAY - #define USE_ADS1115 - #define USE_SENSORS // enable external analog sensor board (ADS1115); distinct from onboard SENSOR1/SENSOR2 GPIO inputs #elif defined(OSPI) // for OSPi @@ -457,8 +461,6 @@ enum { #define SCL 0 #define USE_DISPLAY - #define USE_ADS1115 - #define USE_SENSORS // enable external analog sensor board (ADS1115); distinct from onboard SENSOR1/SENSOR2 GPIO inputs #else // for demo / simulation // use fake hardware pins diff --git a/main.cpp b/main.cpp index 0285e37d0..50be303ac 100644 --- a/main.cpp +++ b/main.cpp @@ -398,9 +398,12 @@ void do_setup() { os.setup_pd_voltage(); #endif -#if defined(USE_SENSORS) - Sensor::load_all(); +#if defined(USE_DISPLAY) + os.lcd.clear(); + os.lcd.setCursor(0, 0); + os.lcd_print_pgm(PSTR("Init sensors...")); #endif + Sensor::load_all(); pd.init(); // ProgramData init @@ -525,27 +528,25 @@ void do_loop() // handle flow sensor using polling. Maximum freq is 1/(2*FLOWPOLL_INTERVAL) // e.g. if FLOWPOLL_INTERVAL is 3ms, maximum freq is 166Hz uint32_t tm = millis(); - if((long)(tm-flowpoll_timeout) > 0) { // overflow proof timeout + if((int32_t)(tm-flowpoll_timeout) > 0) { // overflow proof timeout flowpoll_timeout = tm+FLOWPOLL_INTERVAL; flow_poll(); } } -#if defined(USE_SENSORS) { static uint32_t sensorpoll_timeout = 0; uint32_t tm = millis(); - if((long)(tm-sensorpoll_timeout) > 0) { + if((int32_t)(tm-sensorpoll_timeout) > 0) { sensorpoll_timeout = tm + SENSORPOLL_INTERVAL; os.poll_sensors(); } } -#endif #if defined(ESP8266) { uint32_t tn = millis(); - if((long)(tn-currpoll_timeout) > 0) { // overflow proof timeout + if((int32_t)(tn-currpoll_timeout) > 0) { // overflow proof timeout int16_t curr = (int16_t)os.read_current(); int16_t imax = os.get_imax(); if((imax > 0) && (curr > imax)) { @@ -629,7 +630,7 @@ void do_loop() os.state = OS_STATE_CONNECTED; connecting_timeout = 0; } else { - if((long)(millis()-connecting_timeout)>0) { + if((int32_t)((uint32_t)millis()-connecting_timeout)>0) { os.state = OS_STATE_INITIAL; WiFi.disconnect(true); DEBUG_PRINTLN(F("timeout")); @@ -1602,10 +1603,8 @@ uint8_t get_program_water_percent(const ProgramStruct &prog) { } float get_program_sensor_adj(uint8_t pid) { -#if defined(USE_SENSORS) SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); if (adj) return adj->get_adjustment_factor(os.sensors); -#endif return 1.f; } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index c8d7f4f4a..f67395a17 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -848,7 +848,6 @@ void server_change_program(OTF_PARAMS_DEF) { } SensorAdjustment *snadj_ptr = nullptr; - #if defined(USE_SENSORS) SensorAdjustment snadj(SENSOR_UUID_NONE, 0, 0, nullptr); // snadj=flag,uuid,x0,y0,x1,y1,... — if absent, existing adjustment is left untouched if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("snadj"), true)) { @@ -891,7 +890,6 @@ void server_change_program(OTF_PARAMS_DEF) { snadj = SensorAdjustment(adj_uuid, point_count, adj_flag, points); snadj_ptr = &snadj; } - #endif if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); char *pv = tmp_buffer+1; @@ -1050,7 +1048,6 @@ void server_json_programs_main(OTF_PARAMS_DEF) { tmp_buffer[PROGRAM_NAME_SIZE] = 0; // make sure the string ends bfill.emit_p(PSTR("$S\",[$D,$D,$D],"), tmp_buffer,prog.en_daterange,prog.daterange[0],prog.daterange[1]); // sensor adjustment embedded in each program entry - #if defined(USE_SENSORS) { SensorAdjustment *adj = SensorAdjustment::read(pid, pd.nprograms); if (adj) { @@ -1064,9 +1061,6 @@ void server_json_programs_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{}")); } } - #else - bfill.emit_p(PSTR("{}")); - #endif bfill.emit_p(PSTR("]")); if(pid!=pd.nprograms-1) { bfill.emit_p(PSTR(",")); @@ -1864,7 +1858,6 @@ void server_pause_queue(OTF_PARAMS_DEF) { handle_return(HTML_SUCCESS); } -#if defined(USE_SENSORS) void server_json_sensors_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"sn\":[")); uint8_t sensor_count = 0; @@ -2532,7 +2525,6 @@ void server_json_sensor_desc(OTF_PARAMS_DEF) server_json_sensor_description_main(OTF_PARAMS); handle_return(HTML_OK); } -#endif /** Output all JSON data, including jc, jp, jo, js, jn */ void server_json_all(OTF_PARAMS_DEF) { @@ -2550,12 +2542,10 @@ void server_json_all(OTF_PARAMS_DEF) { server_json_status_main(); bfill.emit_p(PSTR(",\"stations\":{")); server_json_stations_main(OTF_PARAMS); -#if defined(USE_SENSORS) bfill.emit_p(PSTR(",\"sensors\":{")); server_json_sensors_main(OTF_PARAMS); //bfill.emit_p(PSTR(",\"sensor_desc\":{")); //server_json_sensor_description_main(OTF_PARAMS); -#endif bfill.emit_p(PSTR("}")); handle_return(HTML_OK); } @@ -2728,14 +2718,12 @@ const char *uris[] PROGMEM = { "lf", "df", #endif -#if defined(USE_SENSORS) "jsn", "csn", "dsn", "jsl", "dsl", "jsd", -#endif }; // Server function handlers @@ -2768,14 +2756,12 @@ URLHandler urls[] = { server_list_files, // lf server_delete_file, // df #endif - #if defined(USE_SENSORS) server_json_sensors, // jsn server_change_sensor, // csn server_delete_sensor, // dsn server_json_sensor_log, // jsl server_delete_sensor_log, // dsl server_json_sensor_desc, // jsd - #endif }; // handle Ethernet request diff --git a/program.cpp b/program.cpp index 237f63a27..8868eff0e 100644 --- a/program.cpp +++ b/program.cpp @@ -108,9 +108,7 @@ void ProgramData::read(unsigned char pid, ProgramStruct *buf) { unsigned char ProgramData::add(ProgramStruct *buf, SensorAdjustment *adj) { if (nprograms >= MAX_NUM_PROGRAMS) return 0; file_write_block(PROG_FILENAME, buf, 1+(uint32_t)nprograms*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); -#if defined(USE_SENSORS) if (adj) SensorAdjustment::write(adj, nprograms); -#endif nprograms++; save_count(); return 1; @@ -127,14 +125,12 @@ void ProgramData::moveup(unsigned char pid) { file_read_block(PROG_FILENAME, buf2, next, PROGRAMSTRUCT_SIZE); file_write_block(PROG_FILENAME, tmp_buffer, next, PROGRAMSTRUCT_SIZE); file_write_block(PROG_FILENAME, buf2, pos, PROGRAMSTRUCT_SIZE); -#if defined(USE_SENSORS) // swap senadj entries for pid-1 and pid to match the program swap char buf3[SENSOR_ADJUSTMENT_SIZE]; file_read_block(SENADJ_FILENAME, tmp_buffer, SENSOR_ADJUSTMENT_SIZE * (pid-1), SENSOR_ADJUSTMENT_SIZE); file_read_block(SENADJ_FILENAME, buf3, SENSOR_ADJUSTMENT_SIZE * pid, SENSOR_ADJUSTMENT_SIZE); file_write_block(SENADJ_FILENAME, buf3, SENSOR_ADJUSTMENT_SIZE * (pid-1), SENSOR_ADJUSTMENT_SIZE); file_write_block(SENADJ_FILENAME, tmp_buffer, SENSOR_ADJUSTMENT_SIZE * pid, SENSOR_ADJUSTMENT_SIZE); -#endif } void ProgramData::toggle_pause(uint32_t delay) { @@ -193,9 +189,7 @@ unsigned char ProgramData::modify(unsigned char pid, ProgramStruct *buf, SensorA if (pid >= nprograms) return 0; uint32_t pos = 1+(uint32_t)pid*PROGRAMSTRUCT_SIZE; file_write_block(PROG_FILENAME, buf, pos, PROGRAMSTRUCT_SIZE); -#if defined(USE_SENSORS) if (adj) SensorAdjustment::write(adj, pid); -#endif return 1; } @@ -208,11 +202,9 @@ unsigned char ProgramData::del(unsigned char pid) { for (; pos < 1+(uint32_t)nprograms*PROGRAMSTRUCT_SIZE; pos+=PROGRAMSTRUCT_SIZE) { file_copy_block(PROG_FILENAME, pos, pos-PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE, tmp_buffer); } -#if defined(USE_SENSORS) for (int i = pid; i < nprograms-1; i++) { file_copy_block(SENADJ_FILENAME, SENSOR_ADJUSTMENT_SIZE * (i+1), SENSOR_ADJUSTMENT_SIZE * i, SENSOR_ADJUSTMENT_SIZE, tmp_buffer); } -#endif nprograms --; save_count(); return 1; diff --git a/program.h b/program.h index cc0c74a42..3e46716a7 100644 --- a/program.h +++ b/program.h @@ -31,7 +31,7 @@ #include "OpenSprinkler.h" #include "types.h" -class SensorAdjustment; // forward declaration for platforms where USE_SENSORS is not defined +class SensorAdjustment; // forward declaration to avoid pulling sensors/sensor.h into this header /** Log data structure */ struct LogStruct { diff --git a/sensors/ads1115_sensor.cpp b/sensors/ads1115_sensor.cpp index 1705d193d..22ea0c11b 100644 --- a/sensors/ads1115_sensor.cpp +++ b/sensors/ads1115_sensor.cpp @@ -1,7 +1,5 @@ #include "ads1115_sensor.h" -#if defined(USE_SENSORS) - ADS1115Sensor::ADS1115Sensor(uint32_t interval, float min, float max, const char* name, SensorUnit unit, uint8_t flag, ADS1115** sensors, uint8_t sensor_index, uint8_t pin, float scale, float offset) : Sensor(interval, min, max, name, unit, flag), sensor_index(sensor_index), @@ -19,7 +17,11 @@ void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { bfill->emit_p(PSTR( +#if defined(ESP8266) || defined(OSPI) "{\"name\":\"ADS1115 Sensor\"," +#else + "{\"name\":\"ADS1115 Sensor (simulated)\"," +#endif "\"args\":[" "{\"name\":\"Pin Number\"," "\"arg\":\"pin\"," @@ -44,7 +46,7 @@ float ADS1115Sensor::_get_raw_value() { } int16_t counts = this->sensors[sensor_index]->get_pin_value(this->pin); if (counts < 0) return NAN; - return (float)counts * ADS1115_SCALE_FACTOR * this->scale + this->offset; + return (float)counts * ADS1115_VOLTS_PER_COUNT * this->scale + this->offset; } uint32_t ADS1115Sensor::_serialize_internal(char *buf) { @@ -74,5 +76,3 @@ ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf, uint32_t len) { read_buf(buf, &i, end, this->offset); this->sensors = sensors; } - -#endif diff --git a/sensors/ads1115_sensor.h b/sensors/ads1115_sensor.h index 1031738d0..c6992eaee 100644 --- a/sensors/ads1115_sensor.h +++ b/sensors/ads1115_sensor.h @@ -1,14 +1,12 @@ #pragma once #include "../defines.h" - -#if defined(USE_SENSORS) - #include "sensor.h" #include "../ads1115.h" #define ADS1115_DEFAULT_SCALE 1 #define ADS1115_DEFAULT_OFFSET 0 +#define ADS1115_VOLTS_PER_COUNT (ADS1115_SCALE_FACTOR / 1000.0f) class ADS1115Sensor : public Sensor { public: @@ -33,5 +31,3 @@ class ADS1115Sensor : public Sensor { ADS1115 **sensors; }; - -#endif diff --git a/sensors/sensor.cpp b/sensors/sensor.cpp index cca27615e..685a2a86f 100644 --- a/sensors/sensor.cpp +++ b/sensors/sensor.cpp @@ -359,10 +359,6 @@ unsigned char Sensor::del(uint8_t index) { } void Sensor::load_all() { - OpenSprinkler::lcd.clear(); - OpenSprinkler::lcd.setCursor(0, 0); - OpenSprinkler::lcd_print_pgm(PSTR("Init sensors...")); - if (!file_exists(SENSORS_FILENAME)) { DEBUG_PRINTLN(F("Sensor files missing. Initializing...")); OpenSprinkler::nsensors = 0; diff --git a/sensors/sensor.h b/sensors/sensor.h index dfacb4b0a..9eb5acb09 100644 --- a/sensors/sensor.h +++ b/sensors/sensor.h @@ -1,16 +1,12 @@ #pragma once -// External / analog sensor subsystem +// Sensor subsystem. // -// The types in this file (Sensor, SensorAdjustment, etc.) model the *external* -// sensor board — an ADS1115-based I2C ADC add-on that reads analog probes -// (soil moisture, temperature, etc.). It is enabled by the USE_SENSORS guard. -// -// These are DISTINCT from the two onboard digital sensor inputs (SENSOR1 / -// SENSOR2) that are wired directly to GPIO pins. Those are simple binary -// (open/closed) or pulse inputs handled entirely in OpenSprinkler.h/.cpp via -// os.sensor1_status / os.sensor2_status and related IOPT_SENSOR* options. -// No types from this file are involved in the onboard sensor logic. +// Sensor is the abstract base for any periodic data source surfaced through +// the firmware's sensor list and adjustment pipeline. Concrete subclasses +// today include analog probes via ADS1115, weather-service inputs, and +// aggregates over other sensors; future ones may wrap GPIO inputs or +// system-internal signals (board temperature, RAM, etc.). #include #include @@ -35,9 +31,9 @@ // New-sensor defaults — single source of truth for both server_change_sensor and /jsd #define SENSOR_DEFAULT_NAME "New Sensor" #define SENSOR_DEFAULT_INTERVAL 15 -#define SENSOR_DEFAULT_UNIT SensorUnit::Millivolt +#define SENSOR_DEFAULT_UNIT SensorUnit::Volt #define SENSOR_DEFAULT_MIN 0 -#define SENSOR_DEFAULT_MAX 5000 +#define SENSOR_DEFAULT_MAX 5 #define SENSOR_DEFAULT_TYPE 1 // SensorType::ADS1115 #define SENSOR_DEFAULT_FLAG (1 << SENSOR_FLAG_ENABLE) diff --git a/utils.cpp b/utils.cpp index d398d509f..2d0fc0958 100644 --- a/utils.cpp +++ b/utils.cpp @@ -136,18 +136,24 @@ void initialiseEpoch() epochMicro = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)(tv.tv_usec) ; } +// millis() lives in external/OpenThings-Framework-Firmware-Library/Websocket.cpp +// so OTF stays self-contained when used as a standalone library. Reference +// implementation kept here for clarity: // uint32_t millis (void) // { // struct timeval tv ; // uint64_t now ; - +// // gettimeofday (&tv, NULL) ; // now = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; - +// // return (uint32_t)(now - epochMilli) ; // } -unsigned long micros (void) +// Arduino-compatible: us since initialiseEpoch(), wraps every ~71 minutes. +// Returns uint32_t explicitly so the 32-bit semantics are part of the API on +// every target (matches Arduino, where unsigned long is also 32-bit). +uint32_t micros (void) { struct timeval tv ; uint64_t now ; @@ -155,7 +161,7 @@ unsigned long micros (void) gettimeofday (&tv, NULL) ; now = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)tv.tv_usec ; - return (unsigned long)(now - epochMicro) ; + return (uint32_t)(now - epochMicro) ; } #if defined(OSPI) diff --git a/utils.h b/utils.h index cb2f3114a..9e6ed75f9 100644 --- a/utils.h +++ b/utils.h @@ -116,8 +116,8 @@ void str2mac(const char *_str, unsigned char mac[]); void delay(uint32_t ms); void delayMicroseconds(uint32_t us); void delayMicrosecondsHard(uint32_t us); - unsigned long millis(); - unsigned long micros(); + uint32_t millis(); + uint32_t micros(); void initialiseEpoch(); #if defined(OSPI) unsigned int detect_rpi_rev(); From eef0d432c0eecef022f3118fd8f0926cdf52951a Mon Sep 17 00:00:00 2001 From: Ray Wang Date: Sat, 2 May 2026 08:26:47 -0400 Subject: [PATCH 115/115] temporarily change the forward declaration of millis back until we changes to OTF are committed upstream --- utils.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils.h b/utils.h index 9e6ed75f9..aa9411491 100644 --- a/utils.h +++ b/utils.h @@ -116,7 +116,7 @@ void str2mac(const char *_str, unsigned char mac[]); void delay(uint32_t ms); void delayMicroseconds(uint32_t us); void delayMicrosecondsHard(uint32_t us); - uint32_t millis(); + unsigned long millis(); // TODO: change to uint32_t once upstream OTF is updated uint32_t micros(); void initialiseEpoch(); #if defined(OSPI)
UI Source: