From ef3161e9dbff4c50eb815af9e0e47dcef4775584 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 13 Jul 2019 01:24:35 +0200 Subject: [PATCH 001/281] Added sensor2 and soil moisture sensor --- OpenSprinkler.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++----- OpenSprinkler.h | 5 +++ defines.cpp | 4 +++ defines.h | 27 +++++++++++++-- main.cpp | 48 +++++++++++++++++++++++++++ 5 files changed, 156 insertions(+), 11 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 637f84bf5..b071f7ab0 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -58,6 +58,7 @@ uint16_t OpenSprinkler::baseline_current; #endif ulong OpenSprinkler::sensor_lasttime; +ulong OpenSprinkler::soil_moisture_sensed_time; ulong OpenSprinkler::flowcount_log_start; ulong OpenSprinkler::flowcount_rt; volatile ulong OpenSprinkler::flowcount_time_ms; @@ -614,7 +615,11 @@ void OpenSprinkler::begin() { PIN_SENSOR1 = V0_PIN_SENSOR1; PIN_SENSOR2 = V0_PIN_SENSOR2; PIN_RAINSENSOR = V0_PIN_RAINSENSOR; + PIN_SOILSENSOR = V0_PIN_SOILSENSOR; PIN_FLOWSENSOR = V0_PIN_FLOWSENSOR; + PIN_RAINSENSOR2 = V0_PIN_RAINSENSOR2; + PIN_SOILSENSOR2 = V0_PIN_SOILSENSOR2; + PIN_FLOWSENSOR2 = V0_PIN_FLOWSENSOR2; // on revision 0, main IOEXP and driver IOEXP are two separate PCF8574 chips if(hw_type==HW_TYPE_DC) { @@ -667,7 +672,11 @@ void OpenSprinkler::begin() { PIN_SENSOR1 = V1_PIN_SENSOR1; PIN_SENSOR2 = V1_PIN_SENSOR2; PIN_RAINSENSOR = V1_PIN_RAINSENSOR; + PIN_SOILSENSOR = V1_PIN_SOILSENSOR; PIN_FLOWSENSOR = V1_PIN_FLOWSENSOR; + PIN_RAINSENSOR2 = V1_PIN_RAINSENSOR2; + PIN_SOILSENSOR2 = V1_PIN_SOILSENSOR2; + PIN_FLOWSENSOR2 = V1_PIN_FLOWSENSOR2; } else { // revision 2 hw_rev = 2; @@ -683,7 +692,11 @@ void OpenSprinkler::begin() { PIN_SENSOR1 = V2_PIN_SENSOR1; PIN_SENSOR2 = V2_PIN_SENSOR2; PIN_RAINSENSOR = V2_PIN_RAINSENSOR; + PIN_SOILSENSOR = V2_PIN_SOILSENSOR; PIN_FLOWSENSOR = V2_PIN_FLOWSENSOR; + PIN_RAINSENSOR2 = V2_PIN_RAINSENSOR2; + PIN_SOILSENSOR2 = V2_PIN_SOILSENSOR2; + PIN_FLOWSENSOR2 = V2_PIN_FLOWSENSOR2; } } @@ -1149,20 +1162,48 @@ void OpenSprinkler::apply_all_station_bits() { /** Read rain sensor status */ void OpenSprinkler::rainsensor_status() { // options[OPTION_RS_TYPE]: 0 if normally closed, 1 if normally open - if(options[OPTION_SENSOR1_TYPE]!=SENSOR_TYPE_RAIN) return; - status.rain_sensed = (digitalReadExt(PIN_RAINSENSOR) == options[OPTION_SENSOR1_OPTION] ? 0 : 1); + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN) + status.rain_sensed = (digitalReadExt(PIN_RAINSENSOR) == options[OPTION_SENSOR1_OPTION] ? 0 : 1); +#if defined(ESP8266) + if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_RAIN) + status.rain_sensed = (digitalReadExt(PIN_RAINSENSOR2) == options[OPTION_SENSOR2_OPTION] ? 0 : 1); +#endif +} + +/** Read soil moisture sensor status */ +void OpenSprinkler::soil_moisture_sensor_status() { + // options[OPTION_RS_TYPE]: 0 if normally closed, 1 if normally open + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) + status.soil_moisture_sensed = (digitalReadExt(PIN_SOILSENSOR) == options[OPTION_SENSOR1_OPTION] ? 0 : 1); +#if defined(ESP8266) + if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) + status.soil_moisture_sensed = (digitalReadExt(PIN_SOILSENSOR2) == options[OPTION_SENSOR2_OPTION] ? 0 : 1); +#endif } + /** Return program switch status */ bool OpenSprinkler::programswitch_status(ulong curr_time) { - if(options[OPTION_SENSOR1_TYPE]!=SENSOR_TYPE_PSWITCH) return false; - static ulong keydown_time = 0; - byte val = digitalReadExt(PIN_RAINSENSOR); - if(!val && !keydown_time) keydown_time = curr_time; - else if(val && keydown_time && (curr_time > keydown_time)) { - keydown_time = 0; - return true; + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { + static ulong keydown_time = 0; + byte val = digitalReadExt(PIN_RAINSENSOR); + if(!val && !keydown_time) keydown_time = curr_time; + else if(val && keydown_time && (curr_time > keydown_time)) { + keydown_time = 0; + return true; + } + } +#if defined(ESP8266) + if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { + static ulong keydown_time = 0; + byte val = digitalReadExt(PIN_RAINSENSOR2); + if(!val && !keydown_time) keydown_time = curr_time; + else if(val && keydown_time && (curr_time > keydown_time)) { + keydown_time = 0; + return true; + } } +#endif return false; } /** Read current sensing value @@ -2129,13 +2170,37 @@ void OpenSprinkler::lcd_print_station(byte line, char c) { lcd.write(5); } lcd.setCursor(13, 1); +#ifdef ESP8266 + if(status.rain_delayed || + (status.rain_sensed && + (options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_RAIN))) { +#else if(status.rain_delayed || (status.rain_sensed && options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN)) { +#endif lcd.write(3); } +#ifdef ESP8266 + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) { +#else + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { +#endif + if (status.soil_moisture_sensed) + lcd.write(4); //?? + else if (status.soil_moisture_active) + lcd.write(5); //?? + } +#ifdef ESP8266 + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_FLOW) { +#else if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { +#endif lcd.write(6); } +#ifdef ESP8266 + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { +#else if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { +#endif lcd.write(7); } lcd.setCursor(14, 1); diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 4ca85ea20..430a26cd0 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -110,6 +110,9 @@ struct ConStatus { byte network_fails:2; // number of network fails byte mas:8; // master station index byte mas2:8; // master2 station index + + byte soil_moisture_sensed:1; // soil moisture sensor bit (when set, it indicates wet, delayed) + byte soil_moisture_active:1; // soil moisture sensor bit (when set, it indicates wet, active after delay) }; extern const char wtopts_filename[]; @@ -157,6 +160,7 @@ class OpenSprinkler { // variables for time keeping static ulong sensor_lasttime; // time when the last sensor reading is recorded + static ulong soil_moisture_sensed_time; //time when soil moisture detects wet, base for delay static volatile ulong flowcount_time_ms;// time stamp when new flow sensor click is received (in milliseconds) static ulong flowcount_rt; // flow count (for computing real-time flow rate) static ulong flowcount_log_start; // starting flow count (for logging) @@ -208,6 +212,7 @@ class OpenSprinkler { static void raindelay_start(); // start raindelay static void raindelay_stop(); // stop rain delay static void rainsensor_status();// update rainsensor status + static void soil_moisture_sensor_status(); // update soil moisture status static bool programswitch_status(ulong); // get program switch status #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) static uint16_t read_current(); // read current sensing value diff --git a/defines.cpp b/defines.cpp index 2d8033fe6..50669b97c 100644 --- a/defines.cpp +++ b/defines.cpp @@ -13,7 +13,11 @@ byte PIN_LATCH_COM = 255; byte PIN_SENSOR1 = 255; byte PIN_SENSOR2 = 255; byte PIN_RAINSENSOR = 255; +byte PIN_RAINSENSOR2 = 255; byte PIN_FLOWSENSOR = 255; +byte PIN_FLOWSENSOR2 = 255; byte PIN_IOEXP_INT = 255; +byte PIN_SOILSENSOR = 255; +byte PIN_SOILSENSOR2 = 255; #endif diff --git a/defines.h b/defines.h index b8eda719c..b96989150 100644 --- a/defines.h +++ b/defines.h @@ -78,11 +78,13 @@ typedef unsigned long ulong; #define IFTTT_WEATHER_UPDATE 0x08 #define IFTTT_REBOOT 0x10 #define IFTTT_STATION_RUN 0x20 +#define IFTTT_SOILSENSOR 0x40 /** Sensor type macro defines */ #define SENSOR_TYPE_NONE 0x00 #define SENSOR_TYPE_RAIN 0x01 // rain sensor #define SENSOR_TYPE_FLOW 0x02 // flow sensor +#define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor #define SENSOR_TYPE_PSWITCH 0xF0 // program switch #define SENSOR_TYPE_OTHER 0xFF @@ -276,6 +278,7 @@ typedef enum { #define LOGDATA_RAINDELAY 0x02 #define LOGDATA_WATERLEVEL 0x03 #define LOGDATA_FLOWSENSE 0x04 +#define LOGDATA_SOILSENSE 0x05 #undef OS_HW_VERSION @@ -325,6 +328,7 @@ typedef enum { #define PIN_SD_CS 0 // SD card chip select pin #define PIN_RAINSENSOR 11 // rain sensor is connected to pin D3 #define PIN_FLOWSENSOR 11 // flow sensor (currently shared with rain sensor, change if using a different pin) + #define PIN_SOILSENSOR 11 // soil moisture sensor (currently shared with rain sensor, change if using a different 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) @@ -394,6 +398,10 @@ typedef enum { extern byte PIN_SENSOR2; extern byte PIN_RAINSENSOR; extern byte PIN_FLOWSENSOR; + extern byte PIN_SOILSENSOR; + extern byte PIN_RAINSENSOR2; + extern byte PIN_FLOWSENSOR2; + extern byte PIN_SOILSENSOR2; extern byte PIN_IOEXP_INT; /* Original OS30 pin defines */ @@ -410,8 +418,12 @@ typedef enum { #define V0_PIN_BOOST_EN IOEXP_PIN+7 #define V0_PIN_SENSOR1 12 // sensor 1 #define V0_PIN_SENSOR2 13 // sensor 2 - #define V0_PIN_RAINSENSOR V0_PIN_SENSOR1 // for this firmware, rain and flow sensors are both assumed on sensor 1 + #define V0_PIN_RAINSENSOR V0_PIN_SENSOR1 #define V0_PIN_FLOWSENSOR V0_PIN_SENSOR1 + #define V0_PIN_SOILSENSOR V0_PIN_SENSOR1 + #define V0_PIN_RAINSENSOR2 V0_PIN_SENSOR2 + #define V0_PIN_FLOWSENSOR2 V0_PIN_SENSOR2 + #define V0_PIN_SOILSENSOR2 V0_PIN_SENSOR2 /* OS30 revision 1 pin defines */ // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i @@ -428,8 +440,12 @@ typedef enum { #define V1_PIN_LATCH_COM IOEXP_PIN+15 #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 - #define V1_PIN_RAINSENSOR V1_PIN_SENSOR1 // for this firmware, rain and flow sensors are both assumed on sensor 1 + #define V1_PIN_RAINSENSOR V1_PIN_SENSOR1 #define V1_PIN_FLOWSENSOR V1_PIN_SENSOR1 + #define V1_PIN_SOILSENSOR V1_PIN_SENSOR1 + #define V1_PIN_RAINSENSOR2 V1_PIN_SENSOR2 + #define V1_PIN_FLOWSENSOR2 V1_PIN_SENSOR2 + #define V1_PIN_SOILSENSOR2 V1_PIN_SENSOR2 /* OS30 revision 2 pin defines */ // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i @@ -445,6 +461,10 @@ typedef enum { #define V2_PIN_SENSOR2 10 // sensor 2 #define V2_PIN_RAINSENSOR V2_PIN_SENSOR1 #define V2_PIN_FLOWSENSOR V2_PIN_SENSOR1 + #define V2_PIN_SOILSENSOR V2_PIN_SENSOR1 + #define V2_PIN_RAINSENSOR2 V2_PIN_SENSOR2 + #define V2_PIN_FLOWSENSOR2 V2_PIN_SENSOR2 + #define V2_PIN_SOILSENSOR2 V2_PIN_SENSOR2 /** OSPi pin defines */ #elif defined(OSPI) @@ -457,6 +477,7 @@ typedef enum { #define PIN_SR_OE 17 // shift register output enable pin #define PIN_RAINSENSOR 14 // rain sensor #define PIN_FLOWSENSOR 14 // flow sensor (currently shared with rain sensor, change if using a different pin) + #define PIN_SOILSENSOR 14 // soil moisture sensor (currently shared with rain sensor, change if using a different pin) #define PIN_RFTX 15 // RF transmitter pin #define PIN_BUTTON_1 23 // button 1 #define PIN_BUTTON_2 24 // button 2 @@ -476,6 +497,7 @@ typedef enum { #define PIN_SR_OE 50 // P9_14, shift register output enable pin #define PIN_RAINSENSOR 48 // P9_15, rain sensor is connected to pin D3 #define PIN_FLOWSENSOR 48 // flow sensor (currently shared with rain sensor, change if using a different pin) + #define PIN_SOILSENSOR 48 // soil moisture sensor (currently shared with rain sensor, change if using a different pin) #define PIN_RFTX 51 // RF transmitter pin #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} @@ -494,6 +516,7 @@ typedef enum { #define PIN_SR_OE 0 #define PIN_RAINSENSOR 0 #define PIN_FLOWSENSOR 0 + #define PIN_SOILSENSOR 0 #define PIN_RFTX 0 #define PIN_FREE_LIST {} #define ETHER_BUFFER_SIZE 16384 diff --git a/main.cpp b/main.cpp index 8241f67e2..60c6f5bf7 100644 --- a/main.cpp +++ b/main.cpp @@ -654,7 +654,12 @@ void do_loop() } // ====== Check rain sensor status ====== +#ifdef ESP8266 + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN + || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected +#else if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected +#endif os.rainsensor_status(); if (os.old_status.rain_sensed != os.status.rain_sensed) { if (os.status.rain_sensed) { @@ -674,6 +679,35 @@ void do_loop() } } + // ====== Check soil moisture status ====== +#ifdef ESP8266 + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN + || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected +#else + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected +#endif + os.soil_moisture_sensor_status(); + if (os.old_status.soil_moisture_sensed != os.status.soil_moisture_sensed) { + if (os.status.soil_moisture_sensed) { + os.soil_moisture_sensed_time = curr_time + 120*60*60; //Delay 120min todo: add to config + push_message(IFTTT_SOILSENSOR, LOGDATA_SOILSENSE, 1); + } else { + os.soil_moisture_sensed_time = 0; + write_log(LOGDATA_SOILSENSE, curr_time); + push_message(IFTTT_SOILSENSOR, LOGDATA_SOILSENSE, 0); + } + os.old_status.soil_moisture_sensed = os.status.soil_moisture_sensed; + } + + // Delayed set of os.status.soil_moisture_active, so it's not firing during watering + if (os.status.soil_moisture_sensed && os.soil_moisture_sensed_time && curr_time>os.soil_moisture_sensed_time) { + os.status.soil_moisture_active = true; + } else { + os.status.soil_moisture_active = false; + } + } + + // ===== Check program switch status ===== if (os.programswitch_status(curr_time)) { reset_all_stations_immediate(); // immediately stop all stations @@ -1076,7 +1110,14 @@ void process_dynamic_events(ulong curr_time) { // check if rain is detected bool rain = false; bool en = os.status.enabled ? true : false; +#ifdef ESP8266 + if (os.status.rain_delayed || + (os.status.rain_sensed && + (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN || + os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN))) { +#else if (os.status.rain_delayed || (os.status.rain_sensed && os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN)) { +#endif rain = true; } @@ -1305,6 +1346,13 @@ void push_message(byte type, uint32_t lval, float fval, const char* sval) { break; + case IFTTT_SOILSENSOR: + + strcat_P(postval, PSTR("Soil sensor ")); + strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated")); + + break; + case IFTTT_FLOWSENSOR: strcat_P(postval, PSTR("Flow count: ")); itoa(lval, postval+strlen(postval), 10); From df7c0980cb63971ec7b96f8435a64ec4bb41eff6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 13 Jul 2019 01:47:42 +0200 Subject: [PATCH 002/281] RAIN->SOIL --- main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 60c6f5bf7..10095f611 100644 --- a/main.cpp +++ b/main.cpp @@ -681,10 +681,10 @@ void do_loop() // ====== Check soil moisture status ====== #ifdef ESP8266 - if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN - || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_SOIL + || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_SOIL) { // if a soil moisture sensor is connected #else - if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_SOIL) { // if a soil moisture sensor is connected #endif os.soil_moisture_sensor_status(); if (os.old_status.soil_moisture_sensed != os.status.soil_moisture_sensed) { From 3992adb7f394bc28a44b2d629a0dd59b633f8ac3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 13 Jul 2019 01:24:35 +0200 Subject: [PATCH 003/281] Added sensor2 and soil moisture sensor --- OpenSprinkler.cpp | 4181 ++++++++++++++++++++++----------------------- OpenSprinkler.h | 485 +++--- defines.h | 853 +++++---- main.cpp | 3078 +++++++++++++++++---------------- 4 files changed, 4298 insertions(+), 4299 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index b6b515254..b071f7ab0 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -20,14 +20,29 @@ * along with this program. If not, see * . */ +#if !defined(ARDUINO) +#include +#endif #include "OpenSprinkler.h" #include "server.h" #include "gpio.h" +#include "images.h" #include "testmode.h" +#if defined(ESP8266_ETHERNET) +#include "defines.h" +#include "UIPEthernet.h" +#include "UIPServer.h" +#include +#include "utils.h" +#include "server.h" +extern char ether_buffer[]; +extern UIPServer *m_server; +extern UIPEthernetClass ether; +#endif + /** Declare static data members */ -OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; ConStatus OpenSprinkler::status; ConStatus OpenSprinkler::old_status; @@ -36,536 +51,458 @@ byte OpenSprinkler::hw_rev; byte OpenSprinkler::nboards; byte OpenSprinkler::nstations; -byte OpenSprinkler::station_bits[MAX_NUM_BOARDS]; +byte OpenSprinkler::station_bits[MAX_EXT_BOARDS+1]; +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) byte OpenSprinkler::engage_booster; uint16_t OpenSprinkler::baseline_current; +#endif -ulong OpenSprinkler::sensor1_on_timer; -ulong OpenSprinkler::sensor1_off_timer; -ulong OpenSprinkler::sensor1_active_lasttime; -ulong OpenSprinkler::sensor2_on_timer; -ulong OpenSprinkler::sensor2_off_timer; -ulong OpenSprinkler::sensor2_active_lasttime; -ulong OpenSprinkler::raindelay_on_lasttime; - +ulong OpenSprinkler::sensor_lasttime; +ulong OpenSprinkler::soil_moisture_sensed_time; ulong OpenSprinkler::flowcount_log_start; ulong OpenSprinkler::flowcount_rt; +volatile ulong OpenSprinkler::flowcount_time_ms; +ulong OpenSprinkler::raindelay_start_time; byte OpenSprinkler::button_timeout; ulong OpenSprinkler::checkwt_lasttime; ulong OpenSprinkler::checkwt_success_lasttime; ulong OpenSprinkler::powerup_lasttime; -uint8_t OpenSprinkler::last_reboot_cause = REBOOT_CAUSE_NONE; byte OpenSprinkler::weather_update_flag; -// todo future: the following attribute bytes are for backward compatibility -byte OpenSprinkler::attrib_mas[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igs[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_mas2[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igs2[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_seq[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; - -extern char tmp_buffer[]; +char tmp_buffer[TMP_BUFFER_SIZE+1]; // scratch buffer + +const char wtopts_filename[] PROGMEM = WEATHER_OPTS_FILENAME; +const char stns_filename[] PROGMEM = STATION_ATTR_FILENAME; +const char ifkey_filename[] PROGMEM = IFTTT_KEY_FILENAME; +#ifdef ESP8266 +const char wifi_filename[] PROGMEM = WIFI_FILENAME; +byte OpenSprinkler::state = OS_STATE_INITIAL; +byte OpenSprinkler::prev_station_bits[MAX_EXT_BOARDS+1]; +WiFiConfig OpenSprinkler::wifi_config = {WIFI_MODE_AP, "", ""}; +IOEXP* OpenSprinkler::expanders[(MAX_EXT_BOARDS+1)/2]; +IOEXP* OpenSprinkler::mainio; +IOEXP* OpenSprinkler::drio; +RCSwitch OpenSprinkler::rfswitch; +extern ESP8266WebServer *wifi_server; extern char ether_buffer[]; +#endif -#if defined(ESP8266) - SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); - byte OpenSprinkler::state = OS_STATE_INITIAL; - byte OpenSprinkler::prev_station_bits[MAX_NUM_BOARDS]; - IOEXP* OpenSprinkler::expanders[MAX_NUM_BOARDS/2]; - IOEXP* OpenSprinkler::mainio; - IOEXP* OpenSprinkler::drio; - RCSwitch OpenSprinkler::rfswitch; - - String OpenSprinkler::wifi_ssid=""; - String OpenSprinkler::wifi_pass=""; - byte OpenSprinkler::wifi_testmode = 0; -#elif defined(ARDUINO) - LiquidCrystal OpenSprinkler::lcd; - extern SdFat sd; +#if defined(ARDUINO) && !defined(ESP8266) + LiquidCrystal OpenSprinkler::lcd; + #include + extern SdFat sd; +#elif defined(ESP8266) + #include + SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); #else - #if defined(OSPI) - byte OpenSprinkler::pin_sr_data = PIN_SR_DATA; - #endif - // todo future: LCD define for Linux-based systems + // todo: LCD define for Linux-based systems +#endif + +#if defined(OSPI) + byte OpenSprinkler::pin_sr_data = PIN_SR_DATA; #endif -/** Option json names (stored in PROGMEM to reduce RAM usage) */ +/** Option json names (stored in progmem) */ // IMPORTANT: each json name is strictly 5 characters // with 0 fillings if less #define OP_JSON_NAME_STEPSIZE 5 -// for Integer options -const char iopt_json_names[] PROGMEM = - "fwv\0\0" - "tz\0\0\0" - "ntp\0\0" - "dhcp\0" - "ip1\0\0" - "ip2\0\0" - "ip3\0\0" - "ip4\0\0" - "gw1\0\0" - "gw2\0\0" - "gw3\0\0" - "gw4\0\0" - "hp0\0\0" - "hp1\0\0" - "hwv\0\0" - "ext\0\0" - "seq\0\0" - "sdt\0\0" - "mas\0\0" - "mton\0" - "mtof\0" - "urs\0\0" - "rso\0\0" - "wl\0\0\0" - "den\0\0" - "ipas\0" - "devid" - "con\0\0" - "lit\0\0" - "dim\0\0" - "bst\0\0" - "uwt\0\0" - "ntp1\0" - "ntp2\0" - "ntp3\0" - "ntp4\0" - "lg\0\0\0" - "mas2\0" - "mton2" - "mtof2" - "fwm\0\0" - "fpr0\0" - "fpr1\0" - "re\0\0\0" - "dns1\0" - "dns2\0" - "dns3\0" - "dns4\0" - "sar\0\0" - "ife\0\0" - "sn1t\0" - "sn1o\0" - "sn2t\0" - "sn2o\0" - "sn1on" - "sn1of" - "sn2on" - "sn2of" - "subn1" - "subn2" - "subn3" - "subn4" - "wimod" - "reset" - ; - -// for String options -/* -const char sopt_json_names[] PROGMEM = - "dkey\0" - "loc\0\0" - "jsp\0\0" - "wsp\0\0" - "wtkey" - "wto\0\0" - "ifkey" - "ssid\0" - "pass\0" - "mqtt\0" - "apass"; -*/ - -/** Option promopts (stored in PROGMEM to reduce RAM usage) */ +const char op_json_names[] PROGMEM = + "fwv\0\0" + "tz\0\0\0" + "ntp\0\0" + "dhcp\0" + "ip1\0\0" + "ip2\0\0" + "ip3\0\0" + "ip4\0\0" + "gw1\0\0" + "gw2\0\0" + "gw3\0\0" + "gw4\0\0" + "hp0\0\0" + "hp1\0\0" + "hwv\0\0" + "ext\0\0" + "seq\0\0" + "sdt\0\0" + "mas\0\0" + "mton\0" + "mtof\0" + "urs\0\0" // todo: rename to sn1t + "rso\0\0" // todo: rename to sn1o + "wl\0\0\0" + "den\0\0" + "ipas\0" + "devid" + "con\0\0" + "lit\0\0" + "dim\0\0" + "bst\0\0" + "uwt\0\0" + "ntp1\0" + "ntp2\0" + "ntp3\0" + "ntp4\0" + "lg\0\0\0" + "mas2\0" + "mton2" + "mtof2" + "fwm\0\0" + "fpr0\0" + "fpr1\0" + "re\0\0\0" + "dns1\0" + "dns2\0" + "dns3\0" + "dns4\0" + "sar\0\0" + "ife\0\0" + "sn2t\0" + "sn2o\0" + "reset"; + +/** Option promopts (stored in progmem, for LCD display) */ // Each string is strictly 16 characters // with SPACE fillings if less -const char iopt_prompts[] PROGMEM = - "Firmware version" - "Time zone (GMT):" - "Enable NTP sync?" - "Enable DHCP? " - "Static.ip1: " - "Static.ip2: " - "Static.ip3: " - "Static.ip4: " - "Gateway.ip1: " - "Gateway.ip2: " - "Gateway.ip3: " - "Gateway.ip4: " - "HTTP Port: " - "----------------" - "Hardware version" - "# of exp. board:" - "----------------" - "Stn. delay (sec)" - "Master 1 (Mas1):" - "Mas1 on adjust:" - "Mas1 off adjust:" - "----------------" - "----------------" - "Watering level: " - "Device enabled? " - "Ignore password?" - "Device ID: " - "LCD contrast: " - "LCD brightness: " - "LCD dimming: " - "DC boost time: " - "Weather algo.: " - "NTP server.ip1: " - "NTP server.ip2: " - "NTP server.ip3: " - "NTP server.ip4: " - "Enable logging? " - "Master 2 (Mas2):" - "Mas2 on adjust:" - "Mas2 off adjust:" - "Firmware minor: " - "Pulse rate: " - "----------------" - "As remote ext.? " - "DNS server.ip1: " - "DNS server.ip2: " - "DNS server.ip3: " - "DNS server.ip4: " - "Special Refresh?" - "IFTTT Enable: " - "Sensor 1 type: " - "Normally open? " - "Sensor 2 type: " - "Normally open? " - "Sn1 on adjust: " - "Sn1 off adjust: " - "Sn2 on adjust: " - "Sn2 off adjust: " - "Subnet mask1: " - "Subnet mask2: " - "Subnet mask3: " - "Subnet mask4: " - "WiFi mode? " - "Factory reset? "; - -// string options do not have prompts - -/** Option maximum values (stored in PROGMEM to reduce RAM usage) */ -const byte iopt_max[] PROGMEM = { - 0, - 108, - 1, - 1, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 0, - MAX_EXT_BOARDS, - 1, - 255, - MAX_NUM_STATIONS, - 255, - 255, - 255, - 1, - 250, - 1, - 1, - 255, - 255, - 255, - 255, - 250, - 255, - 255, - 255, - 255, - 255, - 1, - MAX_NUM_STATIONS, - 255, - 255, - 0, - 255, - 255, - 1, - 255, - 255, - 255, - 255, - 1, - 255, - 255, - 1, - 255, - 1, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 1 +const char op_prompts[] PROGMEM = + "Firmware version" + "Time zone (GMT):" + "Enable NTP sync?" + "Enable DHCP? " + "Static.ip1: " + "Static.ip2: " + "Static.ip3: " + "Static.ip4: " + "Gateway.ip1: " + "Gateway.ip2: " + "Gateway.ip3: " + "Gateway.ip4: " + "HTTP Port: " + "----------------" + "Hardware version" + "# of exp. board:" + "----------------" + "Stn. delay (sec)" + "Master 1 (Mas1):" + "Mas1 on adjust:" + "Mas1 off adjust:" + "Sensor 1 type: " + "Normally open? " + "Watering level: " + "Device enabled? " + "Ignore password?" + "Device ID: " + "LCD contrast: " + "LCD brightness: " + "LCD dimming: " + "DC boost time: " + "Weather algo.: " + "NTP server.ip1: " + "NTP server.ip2: " + "NTP server.ip3: " + "NTP server.ip4: " + "Enable logging? " + "Master 2 (Mas2):" + "Mas2 on adjust:" + "Mas2 off adjust:" + "Firmware minor: " + "Pulse rate: " + "----------------" + "As remote ext.? " + "DNS server.ip1: " + "DNS server.ip2: " + "DNS server.ip3: " + "DNS server.ip4: " + "Special Refresh?" + "IFTTT Enable: " + "Sensor 2 type: " + "Normally open? " + "Factory reset? "; + +/** Option maximum values (stored in progmem) */ +const byte op_max[] PROGMEM = { + 0, + 108, + 1, + 1, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 0, + MAX_EXT_BOARDS, + 1, + 255, + MAX_NUM_STATIONS, + 255, + 255, + 255, + 1, + 250, + 1, + 1, + 255, + 255, + 255, + 255, + 250, + 255, + 255, + 255, + 255, + 255, + 1, + MAX_NUM_STATIONS, + 255, + 255, + 0, + 255, + 255, + 1, + 255, + 255, + 255, + 255, + 1, + 255, + 255, + 1, + 1 }; -// string options do not have maximum values - -/** Integer option values (stored in RAM) */ -byte OpenSprinkler::iopts[] = { - OS_FW_VERSION, // firmware version - 28, // default time zone: GMT-5 - 1, // 0: disable NTP sync, 1: enable NTP sync - 1, // 0: use static ip, 1: use dhcp - 0, // this and next 3 bytes define static ip - 0, - 0, - 0, - 0, // this and next 3 bytes define static gateway ip - 0, - 0, - 0, -#if defined(ARDUINO) // on AVR, the default HTTP port is 80 - 80, // this and next byte define http port number - 0, +/** Option values (stored in RAM) */ +byte OpenSprinkler::options[] = { + OS_FW_VERSION, // firmware version + 28, // default time zone: GMT-5 + 1, // 0: disable NTP sync, 1: enable NTP sync + 1, // 0: use static ip, 1: use dhcp + 0, // this and next 3 bytes define static ip + 0, + 0, + 0, + 0, // this and next 3 bytes define static gateway ip + 0, + 0, + 0, +#if defined(ARDUINO) // on AVR, the default HTTP port is 80 + 80, // this and next byte define http port number + 0, #else // on RPI/BBB/LINUX, the default HTTP port is 8080 - 144,// this and next byte define http port number - 31, + 144,// this and next byte define http port number + 31, #endif - OS_HW_VERSION, - 0, // number of 8-station extension board. 0: no extension boards - 1, // the option 'sequential' is now retired - 120,// station delay time (-10 minutes to 10 minutes). - 0, // index of master station. 0: no master station - 120,// master on time adjusted time (-10 minutes to 10 minutes) - 120,// master off adjusted time (-10 minutes to 10 minutes) - 0, // urs (retired) - 0, // rso (retired) - 100,// water level (default 100%), - 1, // device enable - 0, // 1: ignore password; 0: use password - 0, // device id - 150,// lcd contrast - 100,// lcd backlight - 50, // lcd dimming - 80, // boost time (only valid to DC and LATCH type) - 0, // weather algorithm (0 means not using weather algorithm) - 0, // this and the next three bytes define the ntp server ip - 0, - 0, - 0, - 1, // enable logging: 0: disable; 1: enable. - 0, // index of master2. 0: no master2 station - 120,// master2 on adjusted time - 120,// master2 off adjusted time - OS_FW_MINOR, // firmware minor version - 100,// this and next byte define flow pulse rate (100x) - 0, // default is 1.00 (100) - 0, // set as remote extension - 8, // this and the next three bytes define the custom dns server ip - 8, - 8, - 8, - 0, // special station auto refresh - 0, // ifttt enable bits - 0, // sensor 1 type (see SENSOR_TYPE macro defines) - 1, // sensor 1 option. 0: normally closed; 1: normally open. default 1. - 0, // sensor 2 type - 1, // sensor 2 option. 0: normally closed; 1: normally open. default 1. - 0, // sensor 1 on delay - 0, // sensor 1 off delay - 0, // sensor 2 on delay - 0, // sensor 2 off delay - 255,// subnet mask 1 - 255,// subnet mask 2 - 255,// subnet mask 3 - 0, - WIFI_MODE_AP, // wifi mode - 0 // reset + OS_HW_VERSION, + 0, // number of 8-station extension board. 0: no extension boards + 1, // the option 'sequential' is now retired + 120,// station delay time (-10 minutes to 10 minutes). + 0, // index of master station. 0: no master station + 120,// master on time adjusted time (-10 minutes to 10 minutes) + 120,// master off adjusted time (-10 minutes to 10 minutes) + 0, // sensor 1 type (see SENSOR_TYPE macro defines) + 0, // sensor 1 option. 0: normally closed; 1: normally open. + 100,// water level (default 100%), + 1, // device enable + 0, // 1: ignore password; 0: use password + 0, // device id + 150,// lcd contrast + 100,// lcd backlight + 50, // lcd dimming + 80, // boost time (only valid to DC and LATCH type) + 0, // weather algorithm (0 means not using weather algorithm) + 50, // this and the next three bytes define the ntp server ip + 97, + 210, + 169, + 1, // enable logging: 0: disable; 1: enable. + 0, // index of master2. 0: no master2 station + 120,// master2 on adjusted time + 120,// master2 off adjusted time + OS_FW_MINOR, // firmware minor version + 100,// this and next byte define flow pulse rate (100x) + 0, // default is 1.00 (100) + 0, // set as remote extension + 8, // this and the next three bytes define the custom dns server ip + 8, + 8, + 8, + 0, // special station auto refresh + 0, // ifttt enable bits + 0, // sensor 2 type + 0, // sensor 2 option. 0: normally closed; 1: normally open. + 0 // reset }; -/** String option values (stored in RAM) */ -const char *OpenSprinkler::sopts[] = { - DEFAULT_PASSWORD, - DEFAULT_LOCATION, - DEFAULT_JAVASCRIPT_URL, - DEFAULT_WEATHER_URL, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING -}; - -/** Weekday strings (stored in PROGMEM to reduce RAM usage) */ +/** Weekday strings (stored in progmem, for LCD display) */ static const char days_str[] PROGMEM = - "Mon\0" - "Tue\0" - "Wed\0" - "Thu\0" - "Fri\0" - "Sat\0" - "Sun\0"; + "Mon\0" + "Tue\0" + "Wed\0" + "Thu\0" + "Fri\0" + "Sat\0" + "Sun\0"; /** Calculate local time (UTC time plus time zone offset) */ time_t OpenSprinkler::now_tz() { - return now()+(int32_t)3600/4*(int32_t)(iopts[IOPT_TIMEZONE]-48); + return now()+(int32_t)3600/4*(int32_t)(options[OPTION_TIMEZONE]-48); } -#if defined(ARDUINO) // AVR network init functions +#if defined(ARDUINO) // AVR network init functions bool detect_i2c(int addr) { - Wire.beginTransmission(addr); - return (Wire.endTransmission()==0); // successful if received 0 + Wire.beginTransmission(addr); + return (Wire.endTransmission()==0); } -/** read hardware MAC into tmp_buffer */ +/** read hardware MAC */ #define MAC_CTRL_ID 0x50 -bool OpenSprinkler::load_hardware_mac(byte* buffer, bool wired) { -#if defined(ESP8266) - WiFi.macAddress((byte*)buffer); - // if requesting wired Ethernet MAC, flip the last byte to create a modified MAC - if(wired) buffer[5] = ~buffer[5]; - return true; +bool OpenSprinkler::read_hardware_mac() { +#ifdef ESP8266 +#ifdef ESP8266_ETHERNET + if (m_server) { + get_hardware_mac(); + return true; + } +#endif + WiFi.macAddress((byte*)tmp_buffer); + 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(byte ret=0;ret<6;ret++) { - buffer[ret] = Wire.read(); - } - return true; + uint8_t ret; + ret = detect_i2c(MAC_CTRL_ID); + if (ret) 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; // Request 6 bytes from the EEPROM + for (ret=0;ret<6;ret++) { + tmp_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 */ - byte 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(m_server) { delete m_server; m_server = 0; } - if(Udp) { delete Udp; Udp = 0; } - -#if defined(ESP8266) - if (start_ether()) { - m_server = new EthernetServer(httpport); - m_server->begin(); - // todo: add option to keep both ether and wifi active - WiFi.mode(WIFI_OFF); - } else { - if(wifi_server) { delete wifi_server; wifi_server = 0; } - if(get_wifi_mode()==WIFI_MODE_AP) { - wifi_server = new ESP8266WebServer(80); - } else { - wifi_server = new ESP8266WebServer(httpport); - } - } - - return 1; -#else - if(start_ether()) { - m_server = new EthernetServer(httpport); - m_server->begin(); - - Udp = new EthernetUDP(); - // Start UDP service for NTP. Avoid the same port with http - if(httpport==8888) - Udp->begin(8000); - else - Udp->begin(8888); - return 1; - } +#ifdef ESP8266 - return 0; +#ifdef ESP8266_ETHERNET + if(m_server) { + delete m_server; + m_server = 0; + } + + if (start_ether()) + { + unsigned int port = (unsigned int)(options[OPTION_HTTPPORT_1]<<8) + (unsigned int)options[OPTION_HTTPPORT_0]; +//#if defined(DEMO) + port = 80; +//#endif + m_server = new UIPServer(port); + m_server->begin(); + } #endif + lcd_print_line_clear_pgm(PSTR("Starting..."), 1); + if(wifi_server) delete wifi_server; + if(get_wifi_mode()==WIFI_MODE_AP) { + wifi_server = new ESP8266WebServer(80); + } else { + uint16_t httpport = (uint16_t)(options[OPTION_HTTPPORT_1]<<8) + (uint16_t)options[OPTION_HTTPPORT_0]; + wifi_server = new ESP8266WebServer(httpport); + } + status.has_hwmac = 1; + +#else + return start_ether(); +#endif + return 1; } -byte OpenSprinkler::start_ether() { -#if defined(ESP8266) - if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 -#endif - Ethernet.init(PIN_ETHER_CS); // make sure to call this before any Ethernet calls - load_hardware_mac((uint8_t*)tmp_buffer, true); - // detect if Enc28J60 exists - Enc28J60Network::init((uint8_t*)tmp_buffer); - uint8_t erevid = Enc28J60Network::geterevid(); - // a valid chip must have erevid > 0 and < 255 - if(erevid==0 || erevid==255) return 0; - - 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); - } - //if(Ethernet.linkStatus() != LinkON) return 0; - - return 1; +#if defined(ESP8266_ETHERNET) +void OpenSprinkler::get_hardware_mac() +{ + tmp_buffer[0] = 0x00; + tmp_buffer[1] = 0x69; + tmp_buffer[2] = 0x69; + tmp_buffer[3] = 0x2D; + tmp_buffer[4] = 0x31; + tmp_buffer[5] = options[OPTION_DEVICE_ID]; } -bool OpenSprinkler::network_connected(void) { -#if defined (ESP8266) - if(m_server) { - return (Ethernet.linkStatus()==LinkON); - } else { - return (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && state==OS_STATE_CONNECTED); - } +byte OpenSprinkler::start_ether() +{ + lcd_print_line_clear_pgm(PSTR("init ethernet..."), 1); + + ether.init(PIN_ETHER_CS); + get_hardware_mac(); + lcd_print_line_clear_pgm(PSTR("setup link..."), 1); + if(!ether.begin((uint8_t*)tmp_buffer)) return 0; + lcd_print_line_clear_pgm(PSTR("waiting link..."), 1); + if(ether.linkStatus() != LinkON) return 0; + return 1; +} #else - return (Ethernet.linkStatus()==LinkON); -#endif +byte OpenSprinkler::start_ether() +{ + lcd_print_line_clear_pgm(PSTR("Connecting..."), 1); + // new from 2.2: read hardware MAC + if(!read_hardware_mac()) + { + // if no hardware MAC exists, use software MAC + tmp_buffer[0] = 0x00; + tmp_buffer[1] = 0x69; + tmp_buffer[2] = 0x69; + tmp_buffer[3] = 0x2D; + tmp_buffer[4] = 0x31; + tmp_buffer[5] = options[OPTION_DEVICE_ID]; + } else { + // has hardware MAC chip + status.has_hwmac = 1; + } + + if(!ether.begin(ETHER_BUFFER_SIZE, (uint8_t*)tmp_buffer, PIN_ETHER_CS)) return 0; + // calculate http port number + ether.hisport = (unsigned int)(options[OPTION_HTTPPORT_1]<<8) + (unsigned int)options[OPTION_HTTPPORT_0]; + + if (options[OPTION_USE_DHCP]) { + // set up DHCP + // register with domain name "OS-xx" where xx is the last byte of the MAC address + if (!ether.dhcpSetup()) return 0; + // once we have valid DHCP IP, we write these into static IP / gateway IP + memcpy(options+OPTION_STATIC_IP1, ether.myip, 4); + memcpy(options+OPTION_GATEWAY_IP1, ether.gwip,4); + memcpy(options+OPTION_DNS_IP1, ether.dnsip, 4); + options_save(); + + } else { + // set up static IP + byte *staticip = options+OPTION_STATIC_IP1; + byte *gateway = options+OPTION_GATEWAY_IP1; + byte *dns = options+OPTION_DNS_IP1; + if (!ether.staticSetup(staticip, gateway, dns)) return 0; + } + return 1; } +#endif /** Reboot controller */ -void OpenSprinkler::reboot_dev(uint8_t cause) { - lcd_print_line_clear_pgm(PSTR("Rebooting..."), 0); - if(cause) { - nvdata.reboot_cause = cause; - nvdata_save(); - } -#if defined(ESP8266) - ESP.restart(); - //ESP.reset(); +void OpenSprinkler::reboot_dev() { + lcd_print_line_clear_pgm(PSTR("Rebooting..."), 0); +#ifdef ESP8266 + ESP.restart(); #else - resetFunc(); + resetFunc(); #endif } @@ -574,75 +511,42 @@ void OpenSprinkler::reboot_dev(uint8_t cause) { #include "etherport.h" #include #include -#include -#include #include "utils.h" #include "server.h" +extern EthernetServer *m_server; +extern char ether_buffer[]; + /** Initialize network with the given mac address and http port */ byte OpenSprinkler::start_network() { - unsigned int port = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; + unsigned int port = (unsigned int)(options[OPTION_HTTPPORT_1]<<8) + (unsigned int)options[OPTION_HTTPPORT_0]; #if defined(DEMO) - port = 80; + port = 80; #endif - if(m_server) { delete m_server; m_server = 0; } - - m_server = new EthernetServer(port); - return m_server->begin(); -} - -bool OpenSprinkler::network_connected(void) { - return true; -} - -// 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(byte* mac, bool wired) { - const char * if_names[] = { "eth0", "eth1", "wlan0", "wlan1" }; - struct ifreq ifr; - int fd; - - // Fallback to asoftware mac if interface not recognised - mac[0] = 0x00; - mac[1] = 0x69; - mac[2] = 0x69; - mac[3] = 0x2D; - mac[4] = 0x31; - mac[5] = iopts[IOPT_DEVICE_ID]; - - if (m_server == NULL) return true; - - if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) return true; + if(m_server) { + delete m_server; + m_server = 0; + } - // Returns the mac address of the first interface if multiple active - for (int i = 0; i < sizeof(if_names)/sizeof(const char *); i++) { - strncpy(ifr.ifr_name, if_names[i], sizeof(ifr.ifr_name)); - if (ioctl(fd, SIOCGIFHWADDR, &ifr) != -1) { - memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); - break; - } - } - close(fd); - return true; + m_server = new EthernetServer(port); + return m_server->begin(); } /** Reboot controller */ -void OpenSprinkler::reboot_dev(uint8_t cause) { - nvdata.reboot_cause = cause; - nvdata_save(); +void OpenSprinkler::reboot_dev() { #if defined(DEMO) - // do nothing + // do nothing #else - sync(); // add sync to prevent file corruption + sync(); // add sync to prevent file corruption reboot(RB_AUTOBOOT); #endif } /** Launch update script */ void OpenSprinkler::update_dev() { - char cmd[1000]; - sprintf(cmd, "cd %s & ./updater.sh", get_runtime_path()); - system(cmd); + char cmd[1024]; + sprintf(cmd, "cd %s & ./updater.sh", get_runtime_path()); + system(cmd); } #endif // end network init functions @@ -650,393 +554,505 @@ void OpenSprinkler::update_dev() { /** Initialize LCD */ void OpenSprinkler::lcd_start() { -#if defined(ESP8266) - // initialize SSD1306 - lcd.init(); - lcd.begin(); - flash_screen(); +#ifdef ESP8266 + // initialize SSD1306 + lcd.init(); + lcd.begin(); + flash_screen(); #else - // 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 - } + // 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 + #if OS_HW_VERSION==(OS_HW_VERSION_BASE+20) || OS_HW_VERSION==(OS_HW_VERSION_BASE+21) // 8MHz and 12MHz + TCCR1B = 0x01; + #else // 16MHz + TCCR1B = 0x02; // increase division factor for faster clock + #endif + // 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(); - +extern void flow_isr(); +extern void flow_poll(); /** Initialize pins, controller variables, LCD */ void OpenSprinkler::begin() { #if defined(ARDUINO) - Wire.begin(); // init I2C + Wire.begin(); // init I2C #endif - hw_type = HW_TYPE_UNKNOWN; - hw_rev = 0; - + hw_type = HW_TYPE_UNKNOWN; + hw_rev = 0; + #if defined(ESP8266) - /* check hardware type */ - if(detect_i2c(ACDR_I2CADDR)) hw_type = HW_TYPE_AC; - else if(detect_i2c(DCDR_I2CADDR)) hw_type = HW_TYPE_DC; - else if(detect_i2c(LADR_I2CADDR)) hw_type = HW_TYPE_LATCH; - - /* detect hardware revision type */ - if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists - /* assign revision 0 pins */ - PIN_BUTTON_1 = V0_PIN_BUTTON_1; - PIN_BUTTON_2 = V0_PIN_BUTTON_2; - PIN_BUTTON_3 = V0_PIN_BUTTON_3; - PIN_RFRX = V0_PIN_RFRX; - PIN_RFTX = V0_PIN_RFTX; - PIN_BOOST = V0_PIN_BOOST; - PIN_BOOST_EN = V0_PIN_BOOST_EN; - PIN_SENSOR1 = V0_PIN_SENSOR1; - PIN_SENSOR2 = V0_PIN_SENSOR2; - - // on revision 0, main IOEXP and driver IOEXP are two separate PCF8574 chips - if(hw_type==HW_TYPE_DC) { - drio = new PCF8574(DCDR_I2CADDR); - } else if(hw_type==HW_TYPE_LATCH) { - drio = new PCF8574(LADR_I2CADDR); - } else { - drio = new PCF8574(ACDR_I2CADDR); - } - - mainio = new PCF8574(MAIN_I2CADDR); - mainio->i2c_write(0, 0x0F); // set lower four bits of main PCF8574 (8-ch) to high - - digitalWriteExt(V0_PIN_PWR_TX, 1); // turn on TX power - digitalWriteExt(V0_PIN_PWR_RX, 1); // turn on RX power - pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); - digitalWriteExt(PIN_BOOST, LOW); - digitalWriteExt(PIN_BOOST_EN, LOW); - digitalWriteExt(PIN_LATCH_COM, LOW); - - } else { - - if(hw_type==HW_TYPE_DC) { - drio = new PCA9555(DCDR_I2CADDR); - } else if(hw_type==HW_TYPE_LATCH) { - drio = new PCA9555(LADR_I2CADDR); - } else { - drio = new PCA9555(ACDR_I2CADDR); - } - mainio = drio; - - pinMode(16, INPUT); - if(digitalRead(16)==LOW) { - // revision 1 - hw_rev = 1; - mainio->i2c_write(NXP_CONFIG_REG, V1_IO_CONFIG); - mainio->i2c_write(NXP_OUTPUT_REG, V1_IO_OUTPUT); - - PIN_BUTTON_1 = V1_PIN_BUTTON_1; - PIN_BUTTON_2 = V1_PIN_BUTTON_2; - PIN_BUTTON_3 = V1_PIN_BUTTON_3; - PIN_RFRX = V1_PIN_RFRX; - PIN_RFTX = V1_PIN_RFTX; - PIN_IOEXP_INT = V1_PIN_IOEXP_INT; - PIN_BOOST = V1_PIN_BOOST; - PIN_BOOST_EN = V1_PIN_BOOST_EN; - PIN_LATCH_COM = V1_PIN_LATCH_COM; - PIN_SENSOR1 = V1_PIN_SENSOR1; - PIN_SENSOR2 = V1_PIN_SENSOR2; - } else { - // revision 2 - hw_rev = 2; - mainio->i2c_write(NXP_CONFIG_REG, V2_IO_CONFIG); - mainio->i2c_write(NXP_OUTPUT_REG, V2_IO_OUTPUT); - - PIN_BUTTON_1 = V2_PIN_BUTTON_1; - PIN_BUTTON_2 = V2_PIN_BUTTON_2; - PIN_BUTTON_3 = V2_PIN_BUTTON_3; - PIN_RFTX = V2_PIN_RFTX; - PIN_BOOST = V2_PIN_BOOST; - PIN_BOOST_EN = V2_PIN_BOOST_EN; - PIN_LATCH_COM = V2_PIN_LATCH_COM; - PIN_SENSOR1 = V2_PIN_SENSOR1; - PIN_SENSOR2 = V2_PIN_SENSOR2; - } - } - - /* detect expanders */ - for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) - expanders[i] = NULL; - detect_expanders(); + if(detect_i2c(ACDR_I2CADDR)) hw_type = HW_TYPE_AC; + else if(detect_i2c(DCDR_I2CADDR)) hw_type = HW_TYPE_DC; + else if(detect_i2c(LADR_I2CADDR)) hw_type = HW_TYPE_LATCH; + + /* detect hardware revision type */ + if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists + DEBUG_PRINTLN("PCF8574 detected"); + /* assign revision 0 pins */ + PIN_BUTTON_1 = V0_PIN_BUTTON_1; + PIN_BUTTON_2 = V0_PIN_BUTTON_2; + PIN_BUTTON_3 = V0_PIN_BUTTON_3; + PIN_RFRX = V0_PIN_RFRX; + PIN_RFTX = V0_PIN_RFTX; + PIN_BOOST = V0_PIN_BOOST; + PIN_BOOST_EN = V0_PIN_BOOST_EN; + PIN_SENSOR1 = V0_PIN_SENSOR1; + PIN_SENSOR2 = V0_PIN_SENSOR2; + PIN_RAINSENSOR = V0_PIN_RAINSENSOR; + PIN_SOILSENSOR = V0_PIN_SOILSENSOR; + PIN_FLOWSENSOR = V0_PIN_FLOWSENSOR; + PIN_RAINSENSOR2 = V0_PIN_RAINSENSOR2; + PIN_SOILSENSOR2 = V0_PIN_SOILSENSOR2; + PIN_FLOWSENSOR2 = V0_PIN_FLOWSENSOR2; + + // on revision 0, main IOEXP and driver IOEXP are two separate PCF8574 chips + if(hw_type==HW_TYPE_DC) { + drio = new PCF8574(DCDR_I2CADDR); + } else if(hw_type==HW_TYPE_LATCH) { + drio = new PCF8574(LADR_I2CADDR); + } else { + drio = new PCF8574(ACDR_I2CADDR); + } + + mainio = new PCF8574(MAIN_I2CADDR); + mainio->i2c_write(0, 0x0F); // set lower four bits of main PCF8574 (8-ch) to high + /*pcf_write(MAIN_I2CADDR, 0x0F);*/ + + digitalWriteExt(V0_PIN_PWR_TX, 1); // turn on TX power + digitalWriteExt(V0_PIN_PWR_RX, 1); // turn on RX power + pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); + digitalWriteExt(PIN_BOOST, LOW); + digitalWriteExt(PIN_BOOST_EN, LOW); + digitalWriteExt(PIN_LATCH_COM, LOW); + + } else { + + if(hw_type==HW_TYPE_DC) { + drio = new PCA9555(DCDR_I2CADDR); + } else if(hw_type==HW_TYPE_LATCH) { + drio = new PCA9555(LADR_I2CADDR); + } else { + drio = new PCA9555(ACDR_I2CADDR); + } + mainio = drio; + + pinMode(16, INPUT); + if(digitalRead(16)==LOW) { + // revision 1 + hw_rev = 1; + mainio->i2c_write(NXP_CONFIG_REG, V1_IO_CONFIG); + mainio->i2c_write(NXP_OUTPUT_REG, V1_IO_OUTPUT); + + /* assign revision 1 pins */ + PIN_BUTTON_1 = V1_PIN_BUTTON_1; + PIN_BUTTON_2 = V1_PIN_BUTTON_2; + PIN_BUTTON_3 = V1_PIN_BUTTON_3; + PIN_RFRX = V1_PIN_RFRX; + PIN_RFTX = V1_PIN_RFTX; + PIN_IOEXP_INT = V1_PIN_IOEXP_INT; + PIN_BOOST = V1_PIN_BOOST; + PIN_BOOST_EN = V1_PIN_BOOST_EN; + PIN_LATCH_COM = V1_PIN_LATCH_COM; + PIN_SENSOR1 = V1_PIN_SENSOR1; + PIN_SENSOR2 = V1_PIN_SENSOR2; + PIN_RAINSENSOR = V1_PIN_RAINSENSOR; + PIN_SOILSENSOR = V1_PIN_SOILSENSOR; + PIN_FLOWSENSOR = V1_PIN_FLOWSENSOR; + PIN_RAINSENSOR2 = V1_PIN_RAINSENSOR2; + PIN_SOILSENSOR2 = V1_PIN_SOILSENSOR2; + PIN_FLOWSENSOR2 = V1_PIN_FLOWSENSOR2; + } else { + // revision 2 + hw_rev = 2; + mainio->i2c_write(NXP_CONFIG_REG, V2_IO_CONFIG); + mainio->i2c_write(NXP_OUTPUT_REG, V2_IO_OUTPUT); + + PIN_BUTTON_1 = V2_PIN_BUTTON_1; + PIN_BUTTON_2 = V2_PIN_BUTTON_2; + PIN_BUTTON_3 = V2_PIN_BUTTON_3; + PIN_RFTX = V2_PIN_RFTX; + PIN_BOOST = V2_PIN_BOOST; + PIN_BOOST_EN = V2_PIN_BOOST_EN; + PIN_SENSOR1 = V2_PIN_SENSOR1; + PIN_SENSOR2 = V2_PIN_SENSOR2; + PIN_RAINSENSOR = V2_PIN_RAINSENSOR; + PIN_SOILSENSOR = V2_PIN_SOILSENSOR; + PIN_FLOWSENSOR = V2_PIN_FLOWSENSOR; + PIN_RAINSENSOR2 = V2_PIN_RAINSENSOR2; + PIN_SOILSENSOR2 = V2_PIN_SOILSENSOR2; + PIN_FLOWSENSOR2 = V2_PIN_FLOWSENSOR2; + } + } + + for(byte i=0;i<(MAX_EXT_BOARDS+1)/2;i++) + 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 + // 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 // Reset all stations - clear_all_station_bits(); - apply_all_station_bits(); + clear_all_station_bits(); + apply_all_station_bits(); -#if defined(ESP8266) - // OS 3.0 has two independent sensors - pinModeExt(PIN_SENSOR1, INPUT_PULLUP); - pinModeExt(PIN_SENSOR2, INPUT_PULLUP); - +#ifdef ESP8266 + pinModeExt(PIN_SENSOR1, INPUT_PULLUP); + pinModeExt(PIN_SENSOR2, INPUT_PULLUP); #else - // pull shift register OE low to enable output - 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 + // pull shift register OE low to enable output + digitalWrite(PIN_SR_OE, LOW); + // Rain sensor port set up + pinMode(PIN_RAINSENSOR, INPUT_PULLUP); #endif - // Default controller status variables - // Static variables are assigned 0 by default - // so only need to initialize non-zero ones - status.enabled = 1; - status.safe_reboot = 0; - - old_status = status; - - nvdata.sunrise_time = 360; // 6:00am default sunrise - nvdata.sunset_time = 1080; // 6:00pm default sunset - nvdata.reboot_cause = REBOOT_CAUSE_POWERON; - - nboards = 1; - nstations = nboards*8; - - // set rf data pin - pinModeExt(PIN_RFTX, OUTPUT); - digitalWriteExt(PIN_RFTX, LOW); - -#if defined(ARDUINO) // AVR SD and LCD functions - - #if defined(ESP8266) // OS3.0 specific detections - - 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); - byte 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); - } + // Set up sensors +#if defined(ARDUINO) + #if defined(ESP8266) + /* todo: handle two sensors */ + if(mainio->type==IOEXP_TYPE_8574) { + attachInterrupt(PIN_FLOWSENSOR, flow_isr, FALLING); + } else if(mainio->type==IOEXP_TYPE_9555) { + if(hw_rev==1) { + mainio->i2c_read(NXP_INPUT_REG); // do a read to clear out current interrupt flag + attachInterrupt(PIN_IOEXP_INT, flow_isr, FALLING); + } else if(hw_rev==2) { + attachInterrupt(PIN_FLOWSENSOR, flow_isr, FALLING); + } + } + #else + //digitalWrite(PIN_RAINSENSOR, HIGH); // enabled internal pullup on rain sensor + attachInterrupt(PIN_FLOWSENSOR_INT, flow_isr, FALLING); + #endif +#else + // OSPI and OSBO use external pullups + attachInterrupt(PIN_FLOWSENSOR, "falling", flow_isr); +#endif - // 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 - - lcd_start(); - - lcd.createChar(ICON_CONNECTED, _iconimage_connected); - lcd.createChar(ICON_DISCONNECTED, _iconimage_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); - - #if defined(ESP8266) - - /* create custom characters */ - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); - - lcd.setCursor(0,0); - lcd.print(F("Init file system")); - lcd.setCursor(0,1); - if(!SPIFFS.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){} - } - - #endif - - // set button pins - // enable internal pullup - pinMode(PIN_BUTTON_1, INPUT_PULLUP); - pinMode(PIN_BUTTON_2, INPUT_PULLUP); - pinMode(PIN_BUTTON_3, INPUT_PULLUP); - - // detect and check RTC type - RTC.detect(); + // Default controller status variables + // Static variables are assigned 0 by default + // so only need to initialize non-zero ones + status.enabled = 1; + status.safe_reboot = 0; + + old_status = status; + + nvdata.sunrise_time = 360; // 6:00am default sunrise + nvdata.sunset_time = 1080; // 6:00pm default sunset + + nboards = 1; + nstations = 8; + + // set rf data pin + pinModeExt(PIN_RFTX, OUTPUT); + digitalWriteExt(PIN_RFTX, LOW); + +#if defined(ARDUINO) // AVR SD and LCD functions + + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // OS 2.3 specific detections + uint8_t ret; + + // detect hardware type + ret = detect_i2c(MAC_CTRL_ID); + if (!ret) { + Wire.requestFrom(MAC_CTRL_ID, 1); + ret = Wire.read(); + if (ret == HW_TYPE_AC || ret == HW_TYPE_DC || ret == HW_TYPE_LATCH) { + hw_type = ret; + } else { + // hardware type is not assigned + } + } + + 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; + + #elif defined(ESP8266) // OS3.0 specific detections + + status.has_curr_sense = 1; // OS3.0 has current sensing capacility + // measure baseline current + baseline_current = 100; + #endif + + lcd_start(); + + #if !defined(ESP8266) + // define lcd custom icons + byte _icon[8]; + // WiFi icon + _icon[0] = B00000; + _icon[1] = B10100; + _icon[2] = B01000; + _icon[3] = B10101; + _icon[4] = B00001; + _icon[5] = B00101; + _icon[6] = B00101; + _icon[7] = B10101; + lcd.createChar(1, _icon); + + _icon[1]=0; + _icon[2]=0; + _icon[3]=1; + lcd.createChar(0, _icon); + + // uSD card icon + _icon[1] = B00000; + _icon[2] = B11111; + _icon[3] = B10001; + _icon[4] = B11111; + _icon[5] = B10001; + _icon[6] = B10011; + _icon[7] = B11110; + lcd.createChar(2, _icon); + + // Rain icon + _icon[2] = B00110; + _icon[3] = B01001; + _icon[4] = B11111; + _icon[5] = B00000; + _icon[6] = B10101; + _icon[7] = B10101; + lcd.createChar(3, _icon); + + // Connect icon + _icon[2] = B00111; + _icon[3] = B00011; + _icon[4] = B00101; + _icon[5] = B01000; + _icon[6] = B10000; + _icon[7] = B00000; + lcd.createChar(4, _icon); + + // Remote extension icon + _icon[2] = B00000; + _icon[3] = B10001; + _icon[4] = B01011; + _icon[5] = B00101; + _icon[6] = B01001; + _icon[7] = B11110; + lcd.createChar(5, _icon); + + // Flow sensor icon + _icon[2] = B00000; + _icon[3] = B11010; + _icon[4] = B10010; + _icon[5] = B11010; + _icon[6] = B10011; + _icon[7] = B00000; + lcd.createChar(6, _icon); + + // Program switch icon + _icon[1] = B11100; + _icon[2] = B10100; + _icon[3] = B11100; + _icon[4] = B10010; + _icon[5] = B10110; + _icon[6] = B00010; + _icon[7] = B00111; + lcd.createChar(7, _icon); + + // 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)) { + status.has_sd = 1; + } + #else + /* create custom characters */ + lcd.createChar(0, _iconimage_connected); + lcd.createChar(1, _iconimage_disconnected); + lcd.createChar(2, _iconimage_sdcard); + lcd.createChar(3, _iconimage_rain); + lcd.createChar(4, _iconimage_connect); + lcd.createChar(5, _iconimage_remotext); + lcd.createChar(6, _iconimage_flow); + lcd.createChar(7, _iconimage_pswitch); + + lcd.setCursor(0,0); + lcd.print(F("Init file system")); + lcd.setCursor(0,1); + if(!SPIFFS.begin()) { + DEBUG_PRINTLN(F("SPIFFS failed")); + status.has_sd = 0; + } else { + status.has_sd = 1; + } + + state = OS_STATE_INITIAL; + #endif + + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + if(!status.has_sd) { + lcd.setCursor(0, 0); + lcd_print_pgm(PSTR("Error Code: 0x2D")); + while(1){} + } + #endif + + // set button pins + // enable internal pullup + pinMode(PIN_BUTTON_1, INPUT_PULLUP); + pinMode(PIN_BUTTON_2, INPUT_PULLUP); + pinMode(PIN_BUTTON_3, INPUT_PULLUP); + + // detect and check RTC type + RTC.detect(); #else - DEBUG_PRINTLN(get_runtime_path()); + status.has_sd = 1; + DEBUG_PRINTLN(get_runtime_path()); #endif } -#if defined(ESP8266) +#ifdef ESP8266 /** LATCH boost voltage * */ void OpenSprinkler::latch_boost() { - digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter - delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge - digitalWriteExt(PIN_BOOST, LOW); // disable boost converter + digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter + delay((int)options[OPTION_BOOST_TIME]<<2); // wait for booster to charge + digitalWriteExt(PIN_BOOST, LOW); // disable boost converter } /** Set all zones (for LATCH controller) - * This function sets all zone pins (including COM) to a specified value + * This function sets all zone pins (including COM) to a specified value */ void OpenSprinkler::latch_setallzonepins(byte value) { - digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin - // Handle driver board (on main controller) - if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board - else reg &= 0xFF00; - drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - // Handle all expansion boards - for(byte i=0;itype==IOEXP_TYPE_9555) { - expanders[i]->i2c_write(NXP_OUTPUT_REG, value?0xFFFF:0x0000); - } - } + digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin + // Handle driver board (on main controller) + if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board + else reg &= 0xFF00; + drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + // Handle all expansion boards + for(byte i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, value?0xFFFF:0x0000); + } + } } /** Set one zone (for LATCH controller) - * This function sets one specified zone pin to a specified value + * This function sets one specified zone pin to a specified value */ void OpenSprinkler::latch_setzonepin(byte sid, byte value) { - if(sid<8) { // on main controller - if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - } else { // on expander - byte bid=(sid-8)>>4; - uint16_t s=(sid-8)&0x0F; - if(expanders[bid]->type==IOEXP_TYPE_9555) { - uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); - } - } + if(sid<8) { // on main controller + if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + } else { // on expander + byte bid=(sid-8)>>4; + uint16_t s=(sid-8)&0x0F; + if(expanders[bid]->type==IOEXP_TYPE_9555) { + uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + } + } } /** LATCH open / close a station * */ void OpenSprinkler::latch_open(byte sid) { - latch_boost(); // boost voltage - latch_setallzonepins(HIGH); // set all switches to HIGH, including COM - latch_setzonepin(sid, LOW); // set the specified switch to LOW - delay(1); // delay 1 ms for all gates to stablize - digitalWriteExt(PIN_BOOST_EN, HIGH); // dump boosted voltage - delay(100); // for 100ms - latch_setzonepin(sid, HIGH); // set the specified switch back to HIGH - digitalWriteExt(PIN_BOOST_EN, LOW); // disable boosted voltage + latch_boost(); // boost voltage + latch_setallzonepins(HIGH); // set all switches to HIGH, including COM + latch_setzonepin(sid, LOW); // set the specified switch to LOW + delay(1); // delay 1 ms for all gates to stablize + digitalWriteExt(PIN_BOOST_EN, HIGH); // dump boosted voltage + delay(100); // for 100ms + latch_setzonepin(sid, HIGH); // set the specified switch back to HIGH + digitalWriteExt(PIN_BOOST_EN, LOW); // disable boosted voltage } void OpenSprinkler::latch_close(byte sid) { - latch_boost(); // boost voltage - latch_setallzonepins(LOW); // set all switches to LOW, including COM - latch_setzonepin(sid, HIGH);// set the specified switch to HIGH - delay(1); // delay 1 ms for all gates to stablize - digitalWriteExt(PIN_BOOST_EN, HIGH); // dump boosted voltage - delay(100); // for 100ms - latch_setzonepin(sid, LOW); // set the specified switch back to LOW - digitalWriteExt(PIN_BOOST_EN, LOW); // disable boosted voltage - latch_setallzonepins(HIGH); // set all switches back to HIGH + latch_boost(); // boost voltage + latch_setallzonepins(LOW); // set all switches to LOW, including COM + latch_setzonepin(sid, HIGH);// set the specified switch to HIGH + delay(1); // delay 1 ms for all gates to stablize + digitalWriteExt(PIN_BOOST_EN, HIGH); // dump boosted voltage + delay(100); // for 100ms + latch_setzonepin(sid, LOW); // set the specified switch back to LOW + digitalWriteExt(PIN_BOOST_EN, LOW); // disable boosted voltage + latch_setallzonepins(HIGH); // set all switches back to HIGH } /** * LATCH version of apply_all_station_bits */ void OpenSprinkler::latch_apply_all_station_bits() { - if(hw_type==HW_TYPE_LATCH && engage_booster) { - for(byte i=0;i>3; - byte s=i&0x07; - byte mask=(byte)1<>3; + byte s=i&0x07; + byte mask=(byte)1<type==IOEXP_TYPE_8574) { - /* revision 0 uses PCF8574 with active low logic, so all bits must be flipped */ - drio->i2c_write(NXP_OUTPUT_REG, ~station_bits[0]); - } else if(drio->type==IOEXP_TYPE_9555) { - /* revision 1 uses PCA9555 with active high logic */ - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - reg = (reg&0xFF00) | station_bits[0]; // output channels are the low 8-bit - drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - - // Handle expansion boards - for(int i=0;itype==IOEXP_TYPE_9555) { - expanders[i]->i2c_write(NXP_OUTPUT_REG, data); - } else { - expanders[i]->i2c_write(NXP_OUTPUT_REG, ~data); - } - } - } - - byte bid, s, sbits; +#ifdef ESP8266 + if(hw_type==HW_TYPE_LATCH) { + // if controller type is latching, the control mechanism is different + // hence will be handled separately + latch_apply_all_station_bits(); + } else { + // Handle DC booster + if(hw_type==HW_TYPE_DC && engage_booster) { + // for DC controller: boost voltage and enable output path + digitalWriteExt(PIN_BOOST_EN, LOW); // disfable output path + digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter + delay((int)options[OPTION_BOOST_TIME]<<2); // wait for booster to charge + digitalWriteExt(PIN_BOOST, LOW); // disable boost converter + digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path + engage_booster = 0; + } + + // Handle driver board (on main controller) + if(drio->type==IOEXP_TYPE_8574) { + /* revision 0 uses PCF8574 with active low logic, so all bits must be flipped */ + drio->i2c_write(NXP_OUTPUT_REG, ~station_bits[0]); + } else if(drio->type==IOEXP_TYPE_9555) { + /* revision 1 uses PCA9555 with active high logic */ + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + reg = (reg&0xFF00) | station_bits[0]; // output channels are the low 8-bit + drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + + // Handle expansion boards + for(int i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, data); + } else { + expanders[i]->i2c_write(NXP_OUTPUT_REG, ~data); + } + } + } + + byte bid, s, sbits; #else - digitalWrite(PIN_SR_LATCH, LOW); - byte bid, s, sbits; - - // Shift out all station bit values - // from the highest bit to the lowest - for(bid=0;bid<=MAX_EXT_BOARDS;bid++) { - if (status.enabled) - sbits = station_bits[MAX_EXT_BOARDS-bid]; - else - sbits = 0; - - for(s=0;s<8;s++) { - digitalWrite(PIN_SR_CLOCK, LOW); - #if defined(OSPI) // if OSPI, use dynamically assigned pin_sr_data - digitalWrite(pin_sr_data, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); - #else - digitalWrite(PIN_SR_DATA, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); - #endif - digitalWrite(PIN_SR_CLOCK, HIGH); - } - } - - #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 + digitalWrite(PIN_SR_LATCH, LOW); + byte bid, s, sbits; + + // Shift out all station bit values + // from the highest bit to the lowest + for(bid=0;bid<=MAX_EXT_BOARDS;bid++) { + if (status.enabled) + sbits = station_bits[MAX_EXT_BOARDS-bid]; + else + sbits = 0; + + for(s=0;s<8;s++) { + digitalWrite(PIN_SR_CLOCK, LOW); + #if defined(OSPI) // if OSPI, use dynamically assigned pin_sr_data + digitalWrite(pin_sr_data, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); + #else + digitalWrite(PIN_SR_DATA, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); + #endif + digitalWrite(PIN_SR_CLOCK, HIGH); + } + } + + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + 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)options[OPTION_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(iopts[IOPT_SPE_AUTO_REFRESH]) { - // handle refresh of RF and remote stations - // we refresh the station whose index is the current time modulo MAX_NUM_STATIONS - static byte last_sid = 0; - byte sid = now() % MAX_NUM_STATIONS; - if (sid != last_sid) { // avoid refreshing the same station twice in a roll - last_sid = sid; - bid=sid>>3; - s=sid&0x07; - switch_special_station(sid, (station_bits[bid]>>s)&0x01); - } - } + if(options[OPTION_SPE_AUTO_REFRESH]) { + // handle refresh of RF and remote stations + // we refresh the station whose index is the current time modulo MAX_NUM_STATIONS + static byte last_sid = 0; + byte sid = now() % MAX_NUM_STATIONS; + if (sid != last_sid) { // avoid refreshing the same station twice in a roll + last_sid = sid; + bid=sid>>3; + s=sid&0x07; + switch_special_station(sid, (station_bits[bid]>>s)&0x01); + } + } } /** Read rain sensor status */ -void OpenSprinkler::detect_binarysensor_status(ulong curr_time) { - // sensor_type: 0 if normally closed, 1 if normally open - if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { - pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 - byte val = digitalReadExt(PIN_SENSOR1); - status.sensor1 = (val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1; - if(status.sensor1) { - if(!sensor1_on_timer) { - // add minimum of 5 seconds on delay - ulong delay_time = (ulong)iopts[IOPT_SENSOR1_ON_DELAY]*60; - sensor1_on_timer = curr_time + (delay_time>5?delay_time:5); - sensor1_off_timer = 0; - } else { - if(curr_time > sensor1_on_timer) { - status.sensor1_active = 1; - } - } - } else { - if(!sensor1_off_timer) { - ulong delay_time = (ulong)iopts[IOPT_SENSOR1_OFF_DELAY]*60; - sensor1_off_timer = curr_time + (delay_time>5?delay_time:5); - sensor1_on_timer = 0; - } else { - if(curr_time > sensor1_off_timer) { - status.sensor1_active = 0; - } - } - } - } - -// 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) { - pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 - byte val = digitalReadExt(PIN_SENSOR2); - status.sensor2 = (val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1; - if(status.sensor2) { - if(!sensor2_on_timer) { - // add minimum of 5 seconds on delay - ulong delay_time = (ulong)iopts[IOPT_SENSOR2_ON_DELAY]*60; - sensor2_on_timer = curr_time + (delay_time>5?delay_time:5); - sensor2_off_timer = 0; - } else { - if(curr_time > sensor2_on_timer) { - status.sensor2_active = 1; - } - } - } else { - if(!sensor2_off_timer) { - ulong delay_time = (ulong)iopts[IOPT_SENSOR2_OFF_DELAY]*60; - sensor2_off_timer = curr_time + (delay_time>5?delay_time:5); - sensor2_on_timer = 0; - } else { - if(curr_time > sensor2_off_timer) { - status.sensor2_active = 0; - } - } - } - } +void OpenSprinkler::rainsensor_status() { + // options[OPTION_RS_TYPE]: 0 if normally closed, 1 if normally open + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN) + status.rain_sensed = (digitalReadExt(PIN_RAINSENSOR) == options[OPTION_SENSOR1_OPTION] ? 0 : 1); +#if defined(ESP8266) + if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_RAIN) + status.rain_sensed = (digitalReadExt(PIN_RAINSENSOR2) == options[OPTION_SENSOR2_OPTION] ? 0 : 1); +#endif +} +/** Read soil moisture sensor status */ +void OpenSprinkler::soil_moisture_sensor_status() { + // options[OPTION_RS_TYPE]: 0 if normally closed, 1 if normally open + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) + status.soil_moisture_sensed = (digitalReadExt(PIN_SOILSENSOR) == options[OPTION_SENSOR1_OPTION] ? 0 : 1); +#if defined(ESP8266) + if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) + status.soil_moisture_sensed = (digitalReadExt(PIN_SOILSENSOR2) == options[OPTION_SENSOR2_OPTION] ? 0 : 1); #endif } /** Return program switch status */ -byte OpenSprinkler::detect_programswitch_status(ulong curr_time) { - byte ret = 0; - if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { - static ulong keydown_time = 0; - pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 - status.sensor1 = (digitalReadExt(PIN_SENSOR1) != iopts[IOPT_SENSOR1_OPTION]); - byte val = (digitalReadExt(PIN_SENSOR1) == iopts[IOPT_SENSOR1_OPTION]); - if(!val && !keydown_time) keydown_time = curr_time; - else if(val && keydown_time && (curr_time > keydown_time)) { - keydown_time = 0; - ret |= 0x01; - } - } -#if defined(ESP8266) || defined(PIN_SENSOR2) - if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { - static ulong keydown_time_2 = 0; - pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 - status.sensor2 = (digitalReadExt(PIN_SENSOR2) != iopts[IOPT_SENSOR2_OPTION]); - byte val = (digitalReadExt(PIN_SENSOR2) == iopts[IOPT_SENSOR2_OPTION]); - if(!val && !keydown_time_2) keydown_time_2 = curr_time; - else if(val && keydown_time_2 && (curr_time > keydown_time_2)) { - keydown_time_2 = 0; - ret |= 0x02; - } - } +bool OpenSprinkler::programswitch_status(ulong curr_time) { + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { + static ulong keydown_time = 0; + byte val = digitalReadExt(PIN_RAINSENSOR); + if(!val && !keydown_time) keydown_time = curr_time; + else if(val && keydown_time && (curr_time > keydown_time)) { + keydown_time = 0; + return true; + } + } +#if defined(ESP8266) + if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { + static ulong keydown_time = 0; + byte val = digitalReadExt(PIN_RAINSENSOR2); + if(!val && !keydown_time) keydown_time = curr_time; + else if(val && keydown_time && (curr_time > keydown_time)) { + keydown_time = 0; + return true; + } + } #endif - return ret; + return false; } - -void OpenSprinkler::sensor_resetall() { - sensor1_on_timer = 0; - sensor1_off_timer = 0; - sensor1_active_lasttime = 0; - sensor2_on_timer = 0; - sensor2_off_timer = 0; - sensor2_active_lasttime = 0; - old_status.sensor1_active = status.sensor1_active = 0; - old_status.sensor2_active = status.sensor2_active = 0; -} - /** Read current sensing value * OpenSprinkler 2.3 and above have a 0.2 ohm current sensing resistor. * Therefore the conversion from analog reading to milli-amp is: @@ -1258,36 +1215,36 @@ 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(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) uint16_t OpenSprinkler::read_current() { - float scale = 1.0f; - if(status.has_curr_sense) { - if (hw_type == HW_TYPE_DC) { - #if defined(ESP8266) - scale = 4.88; - #else - scale = 16.11; - #endif - } else if (hw_type == HW_TYPE_AC) { - #if defined(ESP8266) - scale = 3.45; - #else - scale = 11.39; - #endif - } else { - scale = 0.0; // for other controllers, current is 0 - } - /* do an average */ - const byte K = 8; - uint16_t sum = 0; - for(byte i=0;i=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 + #ifdef 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 + unsigned int v = analogRead(PIN_EXP_SENSE); + // OpenSprinkler uses voltage divider to detect expansion boards + // Master controller has a 1.6K pull-up; + // each expansion board (8 stations) has 10K pull-down connected in parallel; + // so the exact ADC value for n expansion boards is: + // ADC = 1024 * 10 / (10 + 1.6 * n) + // For 0, 1, 2, 3, 4, 5, 6 expansion boards, the ADC values are: + // 1024, 882, 775, 691, 624, 568, 522 + // Actual threshold is taken as the midpoint between, to account for errors + int n = -1; + if (v > 953) { // 0 + n = 0; + } else if (v > 828) { // 1 + n = 1; + } else if (v > 733) { // 2 + n = 2; + } else if (v > 657) { // 3 + n = 3; + } else if (v > 596) { // 4 + n = 4; + } else if (v > 545) { // 5 + n = 5; + } else if (v > 502) { // 6 + n = 6; + } else { // cannot determine + } + return n; + #endif #else - return -1; + return -1; #endif } /** Convert hex code to ulong integer */ static ulong hex2ulong(byte *code, byte len) { - char c; - ulong v = 0; - for(byte i=0;i='0' && c<='9') { - v += (c-'0'); - } else if (c>='A' && c<='F') { - v += 10 + (c-'A'); - } else if (c>='a' && c<='f') { - v += 10 + (c-'a'); - } else { - return 0; - } - } - return v; + char c; + ulong v = 0; + for(byte i=0;i='0' && c<='9') { + v += (c-'0'); + } else if (c>='A' && c<='F') { + v += 10 + (c-'A'); + } else if (c>='a' && c<='f') { + v += 10 + (c-'a'); + } else { + return 0; + } + } + return v; } /** Parse RF code into on/off/timeing sections */ uint16_t OpenSprinkler::parse_rfstation_code(RFStationData *data, ulong* on, ulong *off) { - ulong v; - v = hex2ulong(data->on, sizeof(data->on)); - if (!v) return 0; - if (on) *on = v; + ulong v; + v = hex2ulong(data->on, sizeof(data->on)); + if (!v) return 0; + if (on) *on = v; v = hex2ulong(data->off, sizeof(data->off)); - if (!v) return 0; - if (off) *off = v; + if (!v) return 0; + if (off) *off = v; v = hex2ulong(data->timing, sizeof(data->timing)); - if (!v) return 0; - return v; + if (!v) return 0; + return v; } -/** Get station data */ -void OpenSprinkler::get_station_data(byte sid, StationData* data) { - file_read_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); +/** Get station name from NVM */ +void OpenSprinkler::get_station_name(byte sid, char tmp[]) { + tmp[STATION_NAME_SIZE]=0; + nvm_read_block(tmp, (void*)(ADDR_NVM_STN_NAMES+(int)sid*STATION_NAME_SIZE), STATION_NAME_SIZE); } -/** Set station data */ -void OpenSprinkler::set_station_data(byte sid, StationData* data) { - file_write_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); +/** Set station name to NVM */ +void OpenSprinkler::set_station_name(byte sid, char tmp[]) { + tmp[STATION_NAME_SIZE]=0; + nvm_write_block(tmp, (void*)(ADDR_NVM_STN_NAMES+(int)sid*STATION_NAME_SIZE), STATION_NAME_SIZE); } -/** Get station name */ -void OpenSprinkler::get_station_name(byte sid, char tmp[]) { - tmp[STATION_NAME_SIZE]=0; - file_read_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); +/** Save station attribute bits to NVM */ +void OpenSprinkler::station_attrib_bits_save(int addr, byte bits[]) { + nvm_write_block(bits, (void*)addr, MAX_EXT_BOARDS+1); } -/** Set station name */ -void OpenSprinkler::set_station_name(byte sid, char tmp[]) { - // todo: store the right size - tmp[STATION_NAME_SIZE]=0; - file_write_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); -} - -/** Get station type */ -byte OpenSprinkler::get_station_type(byte sid) { - return file_read_byte(STATIONS_FILENAME, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type)); -} - -/** Get station attribute */ -/*void OpenSprinkler::get_station_attrib(byte sid, StationAttrib *attrib); { - file_read_block(STATIONS_FILENAME, attrib, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); -}*/ - -/** Save all station attribs to file (backward compatibility) */ -void OpenSprinkler::attribs_save() { - // re-package attribute bits and save - byte bid, s, sid=0; - StationAttrib at; - byte ty = STN_TYPE_STANDARD; - for(bid=0;bid>s) & 1; - at.igs = (attrib_igs[bid]>>s) & 1; - at.mas2= (attrib_mas2[bid]>>s)& 1; - at.igs2= (attrib_igs2[bid]>>s) & 1; - at.igrd= (attrib_igrd[bid]>>s) & 1; - at.dis = (attrib_dis[bid]>>s) & 1; - at.seq = (attrib_seq[bid]>>s) & 1; - at.gid = 0; - file_write_block(STATIONS_FILENAME, &at, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), 1); // attribte bits are 1 byte long - if(attrib_spe[bid]>>s==0) { - // if station special bit is 0, make sure to write type STANDARD - file_write_block(STATIONS_FILENAME, &ty, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); // attribte bits are 1 byte long - } - } - } +/** Load all station attribute bits from NVM */ +void OpenSprinkler::station_attrib_bits_load(int addr, byte bits[]) { + nvm_read_block(bits, (void*)addr, MAX_EXT_BOARDS+1); } -/** Load all station attribs from file (backward compatibility) */ -void OpenSprinkler::attribs_load() { - // load and re-package attributes - byte bid, s, sid=0; - StationAttrib at; - byte ty; - memset(attrib_mas, 0, nboards); - memset(attrib_igs, 0, nboards); - memset(attrib_mas2, 0, nboards); - memset(attrib_igs2, 0, nboards); - memset(attrib_igrd, 0, nboards); - memset(attrib_dis, 0, nboards); - memset(attrib_seq, 0, nboards); - memset(attrib_spe, 0, nboards); - - for(bid=0;bidsped, value); - break; - - case STN_TYPE_REMOTE: - switch_remotestation((RemoteStationData *)pdata->sped, value); - break; - - case STN_TYPE_GPIO: - switch_gpiostation((GPIOStationData *)pdata->sped, value); - break; - - case STN_TYPE_HTTP: - switch_httpstation((HTTPStationData *)pdata->sped, value); - break; - } - } + // check station special bit + if(station_attrib_bits_read(ADDR_NVM_STNSPE+(sid>>3))&(1<<(sid&0x07))) { + // read station special data from sd card + int stepsize=sizeof(StationSpecialData); + read_from_file(stns_filename, tmp_buffer, stepsize, sid*stepsize); + StationSpecialData *stn = (StationSpecialData *)tmp_buffer; + // check station type + if(stn->type==STN_TYPE_RF) { + // transmit RF signal + switch_rfstation((RFStationData *)stn->data, value); + } else if(stn->type==STN_TYPE_REMOTE) { + // request remote station + switch_remotestation((RemoteStationData *)stn->data, value); + } +#if !defined(ARDUINO) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) + // GPIO and HTTP stations are only available for OS23 or OSPi + else if(stn->type==STN_TYPE_GPIO) { + // set GPIO pin + switch_gpiostation((GPIOStationData *)stn->data, value); + } else if(stn->type==STN_TYPE_HTTP) { + // send GET command + switch_httpstation((HTTPStationData *)stn->data, value); + } +#endif + } } /** Set station bit @@ -1499,36 +1424,40 @@ void OpenSprinkler::switch_special_station(byte sid, byte value) { * (which results in physical actions of opening/closing valves). */ byte OpenSprinkler::set_station_bit(byte sid, byte value) { - byte *data = station_bits+(sid>>3); // pointer to the station byte - byte mask = (byte)1<<(sid&0x07); // mask - if (value) { - if((*data)&mask) return 0; // if bit is already set, return no change - else { - (*data) = (*data) | mask; - engage_booster = true; // if bit is changing from 0 to 1, set engage_booster - switch_special_station(sid, 1); // handle special stations - return 1; - } - } else { - if(!((*data)&mask)) return 0; // if bit is already reset, return no change - else { - (*data) = (*data) & (~mask); - if(hw_type == HW_TYPE_LATCH) { - engage_booster = true; // if LATCH controller, engage booster when bit changes - } - switch_special_station(sid, 0); // handle special stations - return 255; - } - } - return 0; + byte *data = station_bits+(sid>>3); // pointer to the station byte + byte mask = (byte)1<<(sid&0x07); // mask + if (value) { + if((*data)&mask) return 0; // if bit is already set, return no change + else { + (*data) = (*data) | mask; +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) + engage_booster = true; // if bit is changing from 0 to 1, set engage_booster +#endif + switch_special_station(sid, 1); // handle special stations + return 1; + } + } else { + if(!((*data)&mask)) return 0; // if bit is already reset, return no change + else { + (*data) = (*data) & (~mask); +#if defined(ESP8266) + if(hw_type == HW_TYPE_LATCH) { + engage_booster = true; // if LATCH controller, engage booster when bit changes + } +#endif + switch_special_station(sid, 0); // handle special stations + return 255; + } + } + return 0; } /** Clear all station bits */ void OpenSprinkler::clear_all_station_bits() { - byte sid; - for(sid=0;sid<=MAX_NUM_STATIONS;sid++) { - set_station_bit(sid, 0); - } + byte sid; + for(sid=0;sid<=MAX_NUM_STATIONS;sid++) { + set_station_bit(sid, 0); + } } #if !defined(ARDUINO) @@ -1538,43 +1467,43 @@ int rf_gpio_fd = -1; /** Transmit one RF signal bit */ void transmit_rfbit(ulong lenH, ulong lenL) { #if defined(ARDUINO) - #if defined(ESP8266) - digitalWrite(PIN_RFTX, 1); - delayMicroseconds(lenH); - digitalWrite(PIN_RFTX, 0); - delayMicroseconds(lenL); - #else - PORT_RF |= (1<=0) { - if ((code>>i) & 1) { - transmit_rfbit(len3, len); - } else { - transmit_rfbit(len, len3); - } - i--; - }; - // send sync - transmit_rfbit(len, len31); - } + ulong len3 = len * 3; + ulong len31 = len * 31; + for(byte n=0;n<15;n++) { + int i=23; + // send code + while(i>=0) { + if ((code>>i) & 1) { + transmit_rfbit(len3, len); + } else { + transmit_rfbit(len, len3); + } + i--; + }; + // send sync + transmit_rfbit(len, len31); + } } /** Switch RF station @@ -1583,23 +1512,23 @@ void send_rfsignal(ulong code, ulong len) { * and sends it out through RF transmitter. */ void OpenSprinkler::switch_rfstation(RFStationData *data, bool turnon) { - ulong on, off; - uint16_t length = parse_rfstation_code(data, &on, &off); + ulong on, off; + uint16_t length = parse_rfstation_code(data, &on, &off); #if defined(ARDUINO) - #if defined(ESP8266) - rfswitch.enableTransmit(PIN_RFTX); - rfswitch.setProtocol(1); - rfswitch.setPulseLength(length); - rfswitch.send(turnon ? on : off, 24); - #else - send_rfsignal(turnon ? on : off, length); - #endif + #ifdef ESP8266 + rfswitch.enableTransmit(PIN_RFTX); + rfswitch.setProtocol(1); + rfswitch.setPulseLength(length); + rfswitch.send(turnon ? on : off, 24); + #else + send_rfsignal(turnon ? on : off, length); + #endif #else - // pre-open gpio file to minimize overhead - rf_gpio_fd = gpio_fd_open(PIN_RFTX); - send_rfsignal(turnon ? on : off, length); - gpio_fd_close(rf_gpio_fd); - rf_gpio_fd = -1; + // pre-open gpio file to minimize overhead + rf_gpio_fd = gpio_fd_open(PIN_RFTX); + send_rfsignal(turnon ? on : off, length); + gpio_fd_close(rf_gpio_fd); + rf_gpio_fd = -1; #endif } @@ -1610,165 +1539,22 @@ void OpenSprinkler::switch_rfstation(RFStationData *data, bool turnon) { * Third byte is either 0 or 1 for active low (GND) or high (+5V) relays */ void OpenSprinkler::switch_gpiostation(GPIOStationData *data, bool turnon) { - byte gpio = (data->pin[0] - '0') * 10 + (data->pin[1] - '0'); - byte activeState = data->active - '0'; - - pinMode(gpio, OUTPUT); - if (turnon) - digitalWrite(gpio, activeState); - else - digitalWrite(gpio, 1-activeState); -} - -/** Callback function for switching remote station */ -void remote_http_callback(char* buffer) { -/* - DEBUG_PRINTLN(buffer); -*/ -} - -#define SERVER_CONNECT_NTRIES 3 - -int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { - static byte ip[4]; - ip[0] = ip4>>24; - ip[1] = (ip4>>16)&0xff; - ip[2] = (ip4>>8)&0xff; - ip[3] = ip4&0xff; + byte gpio = (data->pin[0] - '0') * 10 + (data->pin[1] - '0'); + byte activeState = data->active - '0'; -#if defined(ARDUINO) - - Client *client; - #if defined(ESP8266) - EthernetClient etherClient; - WiFiClient wifiClient; - if(m_server) client = ðerClient; - else client = &wifiClient; - #else - EthernetClient etherClient; - client = ðerClient; - #endif - - byte nk; - for(nk=0;nkconnect(IPAddress(ip), port)) break; - delay(500); - } - if(nk==SERVER_CONNECT_NTRIES) { client->stop(); return HTTP_RQT_CONNECT_ERR; } - -#else - - EthernetClient etherClient; - EthernetClient *client = ðerClient; - if(!client->connect(ip, port)) { client->stop(); return HTTP_RQT_CONNECT_ERR; } - -#endif - - uint16_t len = strlen(p); - if(len > ETHER_BUFFER_SIZE) len = ETHER_BUFFER_SIZE; - client->write((uint8_t *)p, len); - memset(ether_buffer, 0, ETHER_BUFFER_SIZE); - uint32_t stoptime = millis()+timeout; - -#if defined(ARDUINO) - while(client->connected() || client->available()) { - if(client->available()) { - client->read((uint8_t*)ether_buffer, ETHER_BUFFER_SIZE); - } - delay(0); - if(millis()>stoptime) { - client->stop(); - return HTTP_RQT_TIMEOUT; - } - } -#else - while(millis() < stoptime) { - int len=client->read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); - if (len<=0) { - if(!client->connected()) break; - else continue; - } - } -#endif - client->stop(); - if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN; - if(callback) callback(ether_buffer); - return HTTP_RQT_SUCCESS; + pinMode(gpio, OUTPUT); + if (turnon) + digitalWrite(gpio, activeState); + else + digitalWrite(gpio, 1-activeState); } -int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { - -#if defined(ARDUINO) - - Client *client; - #if defined(ESP8266) - EthernetClient etherClient; - WiFiClient wifiClient; - if(m_server) client = ðerClient; - else client = &wifiClient; - #else - EthernetClient etherClient; - client = ðerClient; - #endif - - byte nk; - for(nk=0;nkconnect(server, port)) break; - delay(500); - } - if(nk==SERVER_CONNECT_NTRIES) { client->stop(); return HTTP_RQT_CONNECT_ERR; } - -#else - - EthernetClient etherClient; - EthernetClient *client = ðerClient; - struct hostent *host; - host = gethostbyname(server); - if (!host) { - DEBUG_PRINT("can't resolve http station - "); - DEBUG_PRINTLN(server); - return HTTP_RQT_CONNECT_ERR; - } - if(!client->connect((uint8_t*)host->h_addr, port)) { client->stop(); return HTTP_RQT_CONNECT_ERR; } - +/** Callback function for browseUrl calls */ +void httpget_callback(byte status, uint16_t off, uint16_t len) { +#if defined(SERIAL_DEBUG) + Ethernet::buffer[off+ETHER_BUFFER_SIZE-1] = 0; + DEBUG_PRINTLN((const char*) Ethernet::buffer + off); #endif - - uint16_t len = strlen(p); - if(len > ETHER_BUFFER_SIZE) len = ETHER_BUFFER_SIZE; - client->write((uint8_t *)p, len); - memset(ether_buffer, 0, ETHER_BUFFER_SIZE); - uint32_t stoptime = millis()+timeout; - -#if defined(ARDUINO) - while(client->connected() || client->available()) { - if(client->available()) { - client->read((uint8_t*)ether_buffer, ETHER_BUFFER_SIZE); - } - delay(0); - if(millis()>stoptime) { - client->stop(); - return HTTP_RQT_TIMEOUT; - } - } -#else - while(millis() < stoptime) { - int len=client->read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); - if (len<=0) { - if(!client->connected()) break; - else continue; - } - } -#endif - client->stop(); - if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN; - if(callback) callback(ether_buffer); - return HTTP_RQT_SUCCESS; -} - -int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { - char * server = strtok(server_with_port, ":"); - char * port = strtok(NULL, ":"); - return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); } /** Switch remote station @@ -1779,33 +1565,113 @@ int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*c * password as the main controller */ void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon) { - RemoteStationData copy; - memcpy((char*)©, (char*)data, sizeof(RemoteStationData)); - - uint32_t ip4 = hex2ulong(copy.ip, sizeof(copy.ip)); - uint16_t port = (uint16_t)hex2ulong(copy.port, sizeof(copy.port)); - - byte ip[4]; - ip[0] = ip4>>24; - ip[1] = (ip4>>16)&0xff; - ip[2] = (ip4>>8)&0xff; - ip[3] = ip4&0xff; - - // use tmp_buffer starting at a later location - // because remote station data is loaded at the beginning - char *p = tmp_buffer; - BufferFiller bf = p; - // MAX_NUM_STATIONS is the refresh cycle - uint16_t timer = iopts[IOPT_SPE_AUTO_REFRESH]?2*MAX_NUM_STATIONS:64800; - bf.emit_p(PSTR("GET /cm?pw=$O&sid=$D&en=$D&t=$D"), - SOPT_PASSWORD, - (int)hex2ulong(copy.sid, sizeof(copy.sid)), - turnon, timer); - bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), - ip[0],ip[1],ip[2],ip[3]); - - send_http_request(ip4, port, p, remote_http_callback); +#if defined(ARDUINO) + ulong ip = hex2ulong(data->ip, sizeof(data->ip)); + ulong port = hex2ulong(data->port, sizeof(data->port)); + + #ifdef ESP8266 + WiFiClient client; + + char *p = tmp_buffer + sizeof(RemoteStationData) + 1; + BufferFiller bf = p; + // MAX_NUM_STATIONS is the refresh cycle + uint16_t timer = options[OPTION_SPE_AUTO_REFRESH]?2*MAX_NUM_STATIONS:64800; + bf.emit_p(PSTR("GET /cm?pw=$E&sid=$D&en=$D&t=$D"), + ADDR_NVM_PASSWORD, + (int)hex2ulong(data->sid, sizeof(data->sid)), + turnon, timer); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: *\r\n\r\n")); + + byte cip[4]; + cip[0] = ip>>24; + cip[1] = (ip>>16)&0xff; + cip[2] = (ip>>8)&0xff; + cip[3] = ip&0xff; + + if(!client.connect(IPAddress(cip), port)) return; + client.write((uint8_t *)p, strlen(p)); + + bzero(ether_buffer, ETHER_BUFFER_SIZE); + + time_t timeout = now_tz() + 5; // 5 seconds timeout + while(!client.available() && now_tz() < timeout) { + } + + bzero(ether_buffer, ETHER_BUFFER_SIZE); + while(client.available()) { + client.read((uint8_t*)ether_buffer, ETHER_BUFFER_SIZE); + } + client.stop(); + //httpget_callback(0, 0, ETHER_BUFFER_SIZE); + + #else + + ether.hisip[0] = ip>>24; + ether.hisip[1] = (ip>>16)&0xff; + ether.hisip[2] = (ip>>8)&0xff; + ether.hisip[3] = ip&0xff; + + uint16_t _port = ether.hisport; // save current port number + ether.hisport = port; + + char *p = tmp_buffer + sizeof(RemoteStationData) + 1; + BufferFiller bf = (byte*)p; + // MAX_NUM_STATIONS is the refresh cycle + uint16_t timer = options[OPTION_SPE_AUTO_REFRESH]?2*MAX_NUM_STATIONS:64800; + bf.emit_p(PSTR("?pw=$E&sid=$D&en=$D&t=$D"), + ADDR_NVM_PASSWORD, + (int)hex2ulong(data->sid,sizeof(data->sid)), + turnon, timer); + ether.browseUrl(PSTR("/cm"), p, PSTR("*"), httpget_callback); + for(int l=0;l<100;l++) ether.packetLoop(ether.packetReceive()); + ether.hisport = _port; + #endif + +#else + EthernetClient client; + + uint8_t hisip[4]; + uint16_t hisport; + ulong ip = hex2ulong(data->ip, sizeof(data->ip)); + hisip[0] = ip>>24; + hisip[1] = (ip>>16)&0xff; + hisip[2] = (ip>>8)&0xff; + hisip[3] = ip&0xff; + hisport = hex2ulong(data->port, sizeof(data->port)); + + if (!client.connect(hisip, hisport)) { + client.stop(); + return; + } + + char *p = tmp_buffer + sizeof(RemoteStationData) + 1; + BufferFiller bf = p; + // MAX_NUM_STATIONS is the refresh cycle + uint16_t timer = options[OPTION_SPE_AUTO_REFRESH]?2*MAX_NUM_STATIONS:64800; + bf.emit_p(PSTR("GET /cm?pw=$E&sid=$D&en=$D&t=$D"), + ADDR_NVM_PASSWORD, + (int)hex2ulong(data->sid, sizeof(data->sid)), + turnon, timer); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: *\r\n\r\n")); + + client.write((uint8_t *)p, strlen(p)); + + bzero(ether_buffer, ETHER_BUFFER_SIZE); + + time_t timeout = now() + 5; // 5 seconds timeout + while(now() < timeout) { + int len=client.read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); + if (len<=0) { + if(!client.connected()) + break; + else + continue; + } + httpget_callback(0, 0, ETHER_BUFFER_SIZE); + } + client.stop(); +#endif } /** Switch http station @@ -1814,585 +1680,635 @@ void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon) { */ void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon) { - HTTPStationData copy; - // make a copy of the HTTP station data and work with it - memcpy((char*)©, (char*)data, sizeof(HTTPStationData)); - char * server = strtok((char *)copy.data, ","); - char * port = strtok(NULL, ","); - char * on_cmd = strtok(NULL, ","); - char * off_cmd = strtok(NULL, ","); - char * cmd = turnon ? on_cmd : off_cmd; + static HTTPStationData copy; + // make a copy of the HTTP station data and work with it + memcpy((char*)©, (char*)data, sizeof(HTTPStationData)); + char * server = strtok((char *)copy.data, ","); + char * port = strtok(NULL, ","); + char * on_cmd = strtok(NULL, ","); + char * off_cmd = strtok(NULL, ","); + char * cmd = turnon ? on_cmd : off_cmd; + +#if defined(ARDUINO) - char *p = tmp_buffer; - BufferFiller bf = p; - bf.emit_p(PSTR("GET /$S HTTP/1.0\r\nHOST: $S\r\n\r\n"), cmd, server); + #ifdef ESP8266 + + WiFiClient client; + if(!client.connect(server, atoi(port))) return; + + char getBuffer[255]; + sprintf(getBuffer, "GET /%s HTTP/1.0\r\nHOST: *\r\n\r\n", cmd); + + DEBUG_PRINTLN(getBuffer); + + client.write((uint8_t *)getBuffer, strlen(getBuffer)); + + bzero(ether_buffer, ETHER_BUFFER_SIZE); + + time_t timeout = now_tz() + 5; // 5 seconds timeout + while(!client.available() && now_tz() < timeout) { + } + + bzero(ether_buffer, ETHER_BUFFER_SIZE); + while(client.available()) { + client.read((uint8_t*)ether_buffer, ETHER_BUFFER_SIZE); + } + client.stop(); + + #else + + if(!ether.dnsLookup(server, true)) { + char *ip0 = strtok(server, "."); + char *ip1 = strtok(NULL, "."); + char *ip2 = strtok(NULL, "."); + char *ip3 = strtok(NULL, "."); + + ether.hisip[0] = ip0 ? atoi(ip0) : 0; + ether.hisip[1] = ip1 ? atoi(ip1) : 0; + ether.hisip[2] = ip2 ? atoi(ip2) : 0; + ether.hisip[3] = ip3 ? atoi(ip3) : 0; + } + + uint16_t _port = ether.hisport; + ether.hisport = atoi(port); + ether.browseUrlRamHost(PSTR("/"), cmd, server, httpget_callback); + for(int l=0;l<100;l++) ether.packetLoop(ether.packetReceive()); + ether.hisport = _port; + #endif + +#else - send_http_request(server, atoi(port), p, remote_http_callback); + EthernetClient client; + struct hostent *host; + + host = gethostbyname(server); + if (!host) { + DEBUG_PRINT("can't resolve http station - "); + DEBUG_PRINTLN(server); + return; + } + + if (!client.connect((uint8_t*)host->h_addr, atoi(port))) { + client.stop(); + return; + } + + char getBuffer[255]; + sprintf(getBuffer, "GET /%s HTTP/1.0\r\nHOST: %s\r\n\r\n", cmd, host->h_name); + client.write((uint8_t *)getBuffer, strlen(getBuffer)); + + bzero(ether_buffer, ETHER_BUFFER_SIZE); + + time_t timeout = now() + 5; // 5 seconds timeout + while(now() < timeout) { + int len=client.read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); + if (len<=0) { + if(!client.connected()) + break; + else + continue; + } + httpget_callback(0, 0, ETHER_BUFFER_SIZE); + } + + client.stop(); +#endif } /** Setup function for options */ void OpenSprinkler::options_setup() { - // Check reset conditions: - if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<219 || // fw version is invalid (<219) - !file_exists(DONE_FILENAME) || // done file doesn't exist - file_read_byte(IOPTS_FILENAME, IOPT_RESET)==0xAA) { // reset flag is on + // add 0.25 second delay to allow nvm to stablize + delay(250); + byte curr_ver = nvm_read_byte((byte*)(ADDR_NVM_OPTIONS+OPTION_FW_VERSION)); + + // check reset condition: either firmware version has changed, or reset flag is up + // if so, trigger a factory reset + if (curr_ver != OS_FW_VERSION || nvm_read_byte((byte*)(ADDR_NVM_OPTIONS+OPTION_RESET))==0xAA) { #if defined(ARDUINO) - lcd_print_line_clear_pgm(PSTR("Resetting..."), 0); - lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); + lcd_print_line_clear_pgm(PSTR("Resetting..."), 0); + lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); #else - DEBUG_PRINT("resetting options..."); -#endif - - // 0. remove existing files - if(file_read_byte(IOPTS_FILENAME, IOPT_RESET)==0xAA) { - // this is an explicit reset request, simply perform a format - #if defined(ESP8266) - SPIFFS.format(); - #else - // todo future: delete log files - #endif - } + DEBUG_PRINT("resetting options..."); +#endif + // ======== Reset NVM data ======== + int i, sn; - remove_file(DONE_FILENAME); - /*remove_file(IOPTS_FILENAME); - remove_file(SOPTS_FILENAME); - remove_file(STATIONS_FILENAME); - remove_file(NVCON_FILENAME); - remove_file(PROG_FILENAME);*/ - - // 1. write all options - iopts_save(); - // wipe out sopts file first - memset(tmp_buffer, 0, MAX_SOPTS_SIZE); - for(int i=0; iname[0]='S'; - pdata->name[3]=0; - pdata->name[4]=0; - StationAttrib at; - memset(&at, 0, sizeof(StationAttrib)); - at.mas=1; - at.seq=1; - pdata->attrib=at; // mas:1 seq:1 - pdata->type=STN_TYPE_STANDARD; - pdata->sped[0]='0'; - pdata->sped[1]=0; - for(int i=0; iname[1]='0'+(sid/10); // default station name - pdata->name[2]='0'+(sid%10); - } else { - pdata->name[1]='0'+(sid/100); - pdata->name[2]='0'+((sid%100)/10); - pdata->name[3]='0'+(sid%10); - } - file_write_block(STATIONS_FILENAME, pdata, sizeof(StationData)*i, sizeof(StationData)); - } - - attribs_load(); // load and repackage attrib bits (for backward compatibility) - - // 3. write non-volatile controller status - nvdata.reboot_cause = REBOOT_CAUSE_RESET; - nvdata_save(); - last_reboot_cause = nvdata.reboot_cause; - - // 4. write program data: just need to write a program counter: 0 - file_write_byte(PROG_FILENAME, 0, 0); - - // 5. write 'done' file - file_write_byte(DONE_FILENAME, 0, 1); - - } else { - - iopts_load(); - nvdata_load(); - last_reboot_cause = nvdata.reboot_cause; - nvdata.reboot_cause = REBOOT_CAUSE_POWERON; - nvdata_save(); - #if defined(ESP8266) - wifi_ssid = sopt_load(SOPT_STA_SSID); - wifi_pass = sopt_load(SOPT_STA_PASS); - #endif - attribs_load(); - } +#ifdef ESP8266 + //if(curr_ver!=0) // if SPIFFS has been written before, perform a full format + SPIFFS.format(); // perform a SPIFFS format +#endif + // 0. wipe out nvm + for(i=0;iTMP_BUFFER_SIZE)?TMP_BUFFER_SIZE:(NVM_SIZE-i); + nvm_write_block(tmp_buffer, (void*)i, nbytes); + } + + // 1. write non-volatile controller status + nvdata_save(); + + // 2. write string parameters + nvm_write_block(DEFAULT_PASSWORD, (void*)ADDR_NVM_PASSWORD, strlen(DEFAULT_PASSWORD)+1); + nvm_write_block(DEFAULT_LOCATION, (void*)ADDR_NVM_LOCATION, strlen(DEFAULT_LOCATION)+1); + nvm_write_block(DEFAULT_JAVASCRIPT_URL, (void*)ADDR_NVM_JAVASCRIPTURL, strlen(DEFAULT_JAVASCRIPT_URL)+1); + nvm_write_block(DEFAULT_WEATHER_URL, (void*)ADDR_NVM_WEATHERURL, strlen(DEFAULT_WEATHER_URL)+1); + nvm_write_block(DEFAULT_WEATHER_KEY, (void*)ADDR_NVM_WEATHER_KEY, strlen(DEFAULT_WEATHER_KEY)+1); + + // 3. reset station names and special attributes, default Sxx + tmp_buffer[0]='S'; + tmp_buffer[3]=0; + for(i=ADDR_NVM_STN_NAMES, sn=1; i"), 0); - lcd_print_line_clear_pgm(PSTR("Hold B3 to save"), 1); - do { - button = button_read(BUTTON_WAIT_NONE); - } while (!(button & BUTTON_FLAG_DOWN)); - lcd.clear(); - ui_set_options(0); - if (iopts[IOPT_RESET]) { - reboot_dev(REBOOT_CAUSE_NONE); - } - break; - } - - // turn on LCD backlight and contrast - lcd_set_brightness(); - lcd_set_contrast(); - - if (!button) { - // flash screen - lcd_print_line_clear_pgm(PSTR(" OpenSprinkler"),0); - lcd.setCursor((hw_type==HW_TYPE_LATCH)?2:4, 1); - lcd_print_pgm(PSTR("v")); - byte hwv = iopts[IOPT_HW_VERSION]; - lcd.print((char)('0'+(hwv/10))); - lcd.print('.'); - #if defined(ESP8266) - lcd.print(hw_rev); - #else - lcd.print((char)('0'+(hwv%10))); - #endif - switch(hw_type) { - case HW_TYPE_DC: - lcd_print_pgm(PSTR(" DC")); - break; - case HW_TYPE_LATCH: - lcd_print_pgm(PSTR(" LATCH")); - break; - default: - lcd_print_pgm(PSTR(" AC")); - } - delay(1500); - #if defined(ESP8266) - lcd.setCursor(2, 1); - lcd_print_pgm(PSTR("FW ")); - lcd.print((char)('0'+(OS_FW_VERSION/100))); - lcd.print('.'); - lcd.print((char)('0'+((OS_FW_VERSION/10)%10))); - lcd.print('.'); - lcd.print((char)('0'+(OS_FW_VERSION%10))); - lcd.print('('); - lcd.print(OS_FW_MINOR); - lcd.print(')'); - delay(1000); - #endif - } + // if BUTTON_3 is pressed during startup, enter Setup option mode + lcd_print_line_clear_pgm(PSTR("==Set Options=="), 0); + delay(DISPLAY_MSG_MS); + lcd_print_line_clear_pgm(PSTR("B1/B2:+/-, B3:->"), 0); + lcd_print_line_clear_pgm(PSTR("Hold B3 to save"), 1); + do { + button = button_read(BUTTON_WAIT_NONE); + } while (!(button & BUTTON_FLAG_DOWN)); + lcd.clear(); + ui_set_options(0); + if (options[OPTION_RESET]) { + reboot_dev(); + } + break; + } + + // turn on LCD backlight and contrast + lcd_set_brightness(); + lcd_set_contrast(); + + if (!button) { + // flash screen + lcd_print_line_clear_pgm(PSTR(" OpenSprinkler"),0); + lcd.setCursor((hw_type==HW_TYPE_LATCH)?2:4, 1); + lcd_print_pgm(PSTR("v")); + byte hwv = options[OPTION_HW_VERSION]; + lcd.print((char)('0'+(hwv/10))); + lcd.print('.'); + #ifdef ESP8266 + lcd.print(hw_rev); + #else + lcd.print((char)('0'+(hwv%10))); + #endif + switch(hw_type) { + case HW_TYPE_DC: + lcd_print_pgm(PSTR(" DC")); + break; + case HW_TYPE_LATCH: + lcd_print_pgm(PSTR(" LATCH")); + break; + default: + lcd_print_pgm(PSTR(" AC")); + } + delay(1500); + #ifdef ESP8266 + lcd.setCursor(2, 1); + lcd_print_pgm(PSTR("FW ")); + lcd.print((char)('0'+(OS_FW_VERSION/100))); + lcd.print('.'); + lcd.print((char)('0'+((OS_FW_VERSION/10)%10))); + lcd.print('.'); + lcd.print((char)('0'+(OS_FW_VERSION%10))); + lcd.print('('); + lcd.print(OS_FW_MINOR); + lcd.print(')'); + delay(1000); + #endif + } #endif } -/** Load non-volatile controller status data from file */ +/** Load non-volatile controller status data from internal NVM */ void OpenSprinkler::nvdata_load() { - file_read_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); - old_status = status; + nvm_read_block(&nvdata, (void*)ADDR_NVM_NVCONDATA, sizeof(NVConData)); + old_status = status; } -/** Save non-volatile controller status data */ +/** Save non-volatile controller status data to internal NVM */ void OpenSprinkler::nvdata_save() { - file_write_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); -} - -/** Load integer options from file */ -void OpenSprinkler::iopts_load() { - file_read_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); - nboards = iopts[IOPT_EXT_BOARDS]+1; - nstations = nboards * 8; - status.enabled = iopts[IOPT_DEVICE_ENABLE]; - iopts[IOPT_FW_VERSION] = OS_FW_VERSION; - iopts[IOPT_FW_MINOR] = OS_FW_MINOR; - /* Reject the former default 50.97.210.169 NTP IP address as - * it no longer works, yet is carried on by people's saved - * configs when they upgrade from older versions. - * IOPT_NTP_IP1 = 0 leads to the new good default behavior. */ - if (iopts[IOPT_NTP_IP1] == 50 && iopts[IOPT_NTP_IP2] == 97 && - iopts[IOPT_NTP_IP3] == 210 && iopts[IOPT_NTP_IP4] == 169) { - iopts[IOPT_NTP_IP1] = 0; - iopts[IOPT_NTP_IP2] = 0; - iopts[IOPT_NTP_IP3] = 0; - iopts[IOPT_NTP_IP4] = 0; - } -} - -/** Save integer options to file */ -void OpenSprinkler::iopts_save() { - file_write_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); - nboards = iopts[IOPT_EXT_BOARDS]+1; - nstations = nboards * 8; - status.enabled = iopts[IOPT_DEVICE_ENABLE]; + nvm_write_block(&nvdata, (void*)ADDR_NVM_NVCONDATA, sizeof(NVConData)); +} + +/** Load options from internal NVM */ +void OpenSprinkler::options_load() { + nvm_read_block(tmp_buffer, (void*)ADDR_NVM_OPTIONS, NUM_OPTIONS); + for (byte i=0; i=0; i--) { + tmp_buffer[i] = options[i]; + } + nvm_write_block(tmp_buffer, (void*)ADDR_NVM_OPTIONS, NUM_OPTIONS); + nboards = options[OPTION_EXT_BOARDS]+1; + nstations = nboards * 8; + status.enabled = options[OPTION_DEVICE_ENABLE]; +#ifdef ESP8266 + if(savewifi) { + // save WiFi config + File file = SPIFFS.open(WIFI_FILENAME, "w"); + if(file) { + file.println(wifi_config.mode); + file.println(wifi_config.ssid); + file.println(wifi_config.pass); + file.close(); + } + } +#endif } -/** Load a string option from file */ -void OpenSprinkler::sopt_load(byte oid, char *buf) { - file_read_block(SOPTS_FILENAME, buf, MAX_SOPTS_SIZE*oid, MAX_SOPTS_SIZE); - buf[MAX_SOPTS_SIZE]=0; // ensure the string ends properly -} - -/** Load a string option from file, return String */ -String OpenSprinkler::sopt_load(byte oid) { - sopt_load(oid, tmp_buffer); - String str = tmp_buffer; - return str; -} - -/** Save a string option to file */ -bool OpenSprinkler::sopt_save(byte oid, const char *buf) { - // smart save: if value hasn't changed, don't write - if(file_cmp_block(SOPTS_FILENAME, buf, (ulong)MAX_SOPTS_SIZE*oid)==0) return false; - int len = strlen(buf); - if(len>=MAX_SOPTS_SIZE) { - file_write_block(SOPTS_FILENAME, buf, (ulong)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); - } - return true; -} - // ============================== // Controller Operation Functions // ============================== /** Enable controller operation */ void OpenSprinkler::enable() { - status.enabled = 1; - iopts[IOPT_DEVICE_ENABLE] = 1; - iopts_save(); + status.enabled = 1; + options[OPTION_DEVICE_ENABLE] = 1; + options_save(); } /** Disable controller operation */ void OpenSprinkler::disable() { - status.enabled = 0; - iopts[IOPT_DEVICE_ENABLE] = 0; - iopts_save(); + status.enabled = 0; + options[OPTION_DEVICE_ENABLE] = 0; + options_save(); } /** Start rain delay */ void OpenSprinkler::raindelay_start() { - status.rain_delayed = 1; - nvdata_save(); + status.rain_delayed = 1; + nvdata_save(); } /** Stop rain delay */ void OpenSprinkler::raindelay_stop() { - status.rain_delayed = 0; - nvdata.rd_stop_time = 0; - nvdata_save(); + status.rain_delayed = 0; + nvdata.rd_stop_time = 0; + nvdata_save(); } /** LCD and button functions */ -#if defined(ARDUINO) // AVR LCD and button functions +#if defined(ARDUINO) // AVR LCD and button functions /** print a program memory string */ -#if defined(ESP8266) +#ifdef 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); - } + uint8_t c; + while((c=pgm_read_byte(str++))!= '\0') { + lcd.print((char)c); + } } /** print a program memory string to a given line with clearing */ -#if defined(ESP8266) +#ifdef ESP8266 void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P str, byte line) { #else void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P PROGMEM str, byte line) { #endif - lcd.setCursor(0, line); - uint8_t c; - int8_t cnt = 0; - while((c=pgm_read_byte(str++))!= '\0') { - lcd.print((char)c); - cnt++; - } - for(; (16-cnt) >= 0; cnt ++) lcd_print_pgm(PSTR(" ")); + lcd.setCursor(0, line); + uint8_t c; + int8_t cnt = 0; + while((c=pgm_read_byte(str++))!= '\0') { + lcd.print((char)c); + cnt++; + } + for(; (16-cnt) >= 0; cnt ++) lcd_print_pgm(PSTR(" ")); } void OpenSprinkler::lcd_print_2digit(int v) { - lcd.print((int)(v/10)); - lcd.print((int)(v%10)); + lcd.print((int)(v/10)); + lcd.print((int)(v%10)); } /** print time to a given line */ void OpenSprinkler::lcd_print_time(time_t t) { - 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_2digit(month(t)); - lcd_print_pgm(PSTR("-")); - lcd_print_2digit(day(t)); + 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_2digit(month(t)); + lcd_print_pgm(PSTR("-")); + lcd_print_2digit(day(t)); } /** print ip address */ void OpenSprinkler::lcd_print_ip(const byte *ip, byte endian) { -#if defined(ESP8266) - lcd.clear(0, 1); +#ifdef ESP8266 + lcd.clear(0, 1); #else - lcd.clear(); + lcd.clear(); #endif - lcd.setCursor(0, 0); - for (byte i=0; i<4; i++) { - lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); - if(i<3) lcd_print_pgm(PSTR(".")); - } + lcd.setCursor(0, 0); + for (byte i=0; i<4; i++) { + lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); + if(i<3) lcd_print_pgm(PSTR(".")); + } } /** print mac address */ void OpenSprinkler::lcd_print_mac(const byte *mac) { - lcd.setCursor(0, 0); - for(byte i=0; i<6; i++) { - if(i) lcd_print_pgm(PSTR("-")); - lcd.print((mac[i]>>4), HEX); - lcd.print((mac[i]&0x0F), HEX); - if(i==4) lcd.setCursor(0, 1); - } -#if defined(ESP8266) - if(m_server) { - lcd_print_pgm(PSTR(" (Ether MAC)")); - } else { - lcd_print_pgm(PSTR(" (WiFi MAC)")); - } -#else - lcd_print_pgm(PSTR(" (MAC)")); -#endif + lcd.setCursor(0, 0); + for(byte i=0; i<6; i++) { + if(i) lcd_print_pgm(PSTR("-")); + lcd.print((mac[i]>>4), HEX); + lcd.print((mac[i]&0x0F), HEX); + if(i==4) lcd.setCursor(0, 1); + } + lcd_print_pgm(PSTR(" (MAC)")); } /** print station bits */ void OpenSprinkler::lcd_print_station(byte line, char c) { - lcd.setCursor(0, line); - if (status.display_board == 0) { - lcd_print_pgm(PSTR("MC:")); // Master controller is display as 'MC' - } - else { - lcd_print_pgm(PSTR("E")); - lcd.print((int)status.display_board); - lcd_print_pgm(PSTR(":")); // extension boards are displayed as E1, E2... - } - - if (!status.enabled) { - lcd_print_line_clear_pgm(PSTR("-Disabled!-"), 1); - } else { - byte bitvalue = station_bits[status.display_board]; - for (byte s=0; s<8; s++) { - byte sid = (byte)status.display_board<<3; - sid += (s+1); - if (sid == iopts[IOPT_MASTER_STATION]) { - 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 { - lcd.print((bitvalue&1) ? c : '_'); - } - bitvalue >>= 1; - } + lcd.setCursor(0, line); + if (status.display_board == 0) { + lcd_print_pgm(PSTR("MC:")); // Master controller is display as 'MC' + } + else { + lcd_print_pgm(PSTR("E")); + lcd.print((int)status.display_board); + lcd_print_pgm(PSTR(":")); // extension boards are displayed as E1, E2... + } + + if (!status.enabled) { + lcd_print_line_clear_pgm(PSTR("-Disabled!-"), 1); + } else { + byte bitvalue = station_bits[status.display_board]; + for (byte s=0; s<8; s++) { + byte sid = (byte)status.display_board<<3; + sid += (s+1); + if (sid == options[OPTION_MASTER_STATION]) { + lcd.print((bitvalue&1) ? c : 'M'); // print master station + } else if (sid == options[OPTION_MASTER_STATION_2]) { + lcd.print((bitvalue&1) ? c : 'N'); // print master2 station + } else { + lcd.print((bitvalue&1) ? c : '_'); + } + bitvalue >>= 1; + } } lcd_print_pgm(PSTR(" ")); - - if(iopts[IOPT_REMOTE_EXT_MODE]) { - lcd.setCursor(LCD_CURSOR_REMOTEXT, 1); - lcd.write(ICON_REMOTEXT); - } - - if(status.rain_delayed) { - lcd.setCursor(LCD_CURSOR_RAINDELAY, 1); - lcd.write(ICON_RAINDELAY); - } - - // write sensor 1 icon - lcd.setCursor(LCD_CURSOR_SENSOR1, 1); - switch(iopts[IOPT_SENSOR1_TYPE]) { - case SENSOR_TYPE_RAIN: - lcd.write(status.sensor1_active?ICON_RAIN:(status.sensor1?'R':'r')); - break; - case SENSOR_TYPE_SOIL: - lcd.write(status.sensor1_active?ICON_SOIL:(status.sensor1?'S':'s')); - break; - case SENSOR_TYPE_FLOW: - lcd.write(flowcount_rt>0?'F':'f'); - break; - case SENSOR_TYPE_PSWITCH: - lcd.write(status.sensor1?'P':'p'); - break; - } - - // write sensor 2 icon - lcd.setCursor(LCD_CURSOR_SENSOR2, 1); - switch(iopts[IOPT_SENSOR2_TYPE]) { - case SENSOR_TYPE_RAIN: - lcd.write(status.sensor2_active?ICON_RAIN:(status.sensor2?'R':'r')); - break; - case SENSOR_TYPE_SOIL: - lcd.write(status.sensor2_active?ICON_SOIL:(status.sensor2?'S':'s')); - break; - // sensor2 cannot be flow sensor - /*case SENSOR_TYPE_FLOW: - lcd.write('F'); - break;*/ - case SENSOR_TYPE_PSWITCH: - lcd.write(status.sensor2?'Q':'q'); - break; - } - - lcd.setCursor(LCD_CURSOR_NETWORK, 1); -#if defined(ESP8266) - if(m_server) - lcd.write(Ethernet.linkStatus()==LinkON?ICON_ETHER_CONNECTED:ICON_ETHER_DISCONNECTED); - else - lcd.write(WiFi.status()==WL_CONNECTED?ICON_CONNECTED:ICON_DISCONNECTED); + lcd.setCursor(12, 1); + if(options[OPTION_REMOTE_EXT_MODE]) { + lcd.write(5); + } + lcd.setCursor(13, 1); +#ifdef ESP8266 + if(status.rain_delayed || + (status.rain_sensed && + (options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_RAIN))) { +#else + if(status.rain_delayed || (status.rain_sensed && options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN)) { +#endif + lcd.write(3); + } +#ifdef ESP8266 + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) { +#else + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { +#endif + if (status.soil_moisture_sensed) + lcd.write(4); //?? + else if (status.soil_moisture_active) + lcd.write(5); //?? + } +#ifdef ESP8266 + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_FLOW) { +#else + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { +#endif + lcd.write(6); + } +#ifdef ESP8266 + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { #else - lcd.write(status.network_fails>2?ICON_DISCONNECTED:ICON_CONNECTED); // if network failure detection is more than 2, display disconnect icon + if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { #endif + lcd.write(7); + } + lcd.setCursor(14, 1); + if (status.has_sd) lcd.write(2); + + lcd.setCursor(15, 1); + #ifdef ESP8266 + lcd.write(WiFi.status()==WL_CONNECTED?0:1); + #else + lcd.write(status.network_fails>2?1:0); // if network failure detection is more than 2, display disconnect icon + #endif } /** print a version number */ void OpenSprinkler::lcd_print_version(byte v) { - if(v > 99) { - lcd.print(v/100); - lcd.print("."); - } - if(v>9) { - lcd.print((v/10)%10); - lcd.print("."); - } - lcd.print(v%10); + if(v > 99) { + lcd.print(v/100); + lcd.print("."); + } + if(v>9) { + lcd.print((v/10)%10); + lcd.print("."); + } + lcd.print(v%10); } /** print an option value */ void OpenSprinkler::lcd_print_option(int i) { - // each prompt string takes 16 characters - strncpy_P0(tmp_buffer, iopt_prompts+16*i, 16); - lcd.setCursor(0, 0); - lcd.print(tmp_buffer); - lcd_print_line_clear_pgm(PSTR(""), 1); - lcd.setCursor(0, 1); - int tz; - switch(i) { - case IOPT_HW_VERSION: - lcd.print("v"); - case IOPT_FW_VERSION: - lcd_print_version(iopts[i]); - break; - case IOPT_TIMEZONE: // if this is the time zone option, do some conversion - tz = (int)iopts[i]-48; - if (tz>=0) lcd_print_pgm(PSTR("+")); - else {lcd_print_pgm(PSTR("-")); tz=-tz;} - lcd.print(tz/4); // print integer portion - lcd_print_pgm(PSTR(":")); - tz = (tz%4)*15; - if (tz==0) lcd_print_pgm(PSTR("00")); - else { - lcd.print(tz); // print fractional portion - } - break; - case IOPT_MASTER_ON_ADJ: - case IOPT_MASTER_ON_ADJ_2: - case IOPT_MASTER_OFF_ADJ: - case IOPT_MASTER_OFF_ADJ_2: - case IOPT_STATION_DELAY_TIME: - { - int16_t t=water_time_decode_signed(iopts[i]); - if(t>=0) lcd_print_pgm(PSTR("+")); - lcd.print(t); - } - break; - case IOPT_HTTPPORT_0: - lcd.print((unsigned int)(iopts[i+1]<<8)+iopts[i]); - break; - case IOPT_PULSE_RATE_0: - { - uint16_t fpr = (unsigned int)(iopts[i+1]<<8)+iopts[i]; - lcd.print(fpr/100); - lcd_print_pgm(PSTR(".")); - lcd.print((fpr/10)%10); - lcd.print(fpr%10); - } - break; - case IOPT_LCD_CONTRAST: - lcd_set_contrast(); - lcd.print((int)iopts[i]); - break; - case IOPT_LCD_BACKLIGHT: - lcd_set_brightness(); - lcd.print((int)iopts[i]); - break; - case IOPT_BOOST_TIME: - #if defined(ARDUINO) - if(hw_type==HW_TYPE_AC) { - lcd.print('-'); - } else { - lcd.print((int)iopts[i]*4); - lcd_print_pgm(PSTR(" ms")); - } - #else - lcd.print('-'); - #endif - break; - default: - // if this is a boolean option - if (pgm_read_byte(iopt_max+i)==1) - lcd_print_pgm(iopts[i] ? PSTR("Yes") : PSTR("No")); - else - lcd.print((int)iopts[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) - lcd_print_pgm(PSTR(" sec")); - + // each prompt string takes 16 characters + strncpy_P0(tmp_buffer, op_prompts+16*i, 16); + lcd.setCursor(0, 0); + lcd.print(tmp_buffer); + lcd_print_line_clear_pgm(PSTR(""), 1); + lcd.setCursor(0, 1); + int tz; + switch(i) { + case OPTION_HW_VERSION: + lcd.print("v"); + case OPTION_FW_VERSION: + lcd_print_version(options[i]); + break; + case OPTION_TIMEZONE: // if this is the time zone option, do some conversion + tz = (int)options[i]-48; + if (tz>=0) lcd_print_pgm(PSTR("+")); + else {lcd_print_pgm(PSTR("-")); tz=-tz;} + lcd.print(tz/4); // print integer portion + lcd_print_pgm(PSTR(":")); + tz = (tz%4)*15; + if (tz==0) lcd_print_pgm(PSTR("00")); + else { + lcd.print(tz); // print fractional portion + } + break; + case OPTION_MASTER_ON_ADJ: + case OPTION_MASTER_ON_ADJ_2: + case OPTION_MASTER_OFF_ADJ: + case OPTION_MASTER_OFF_ADJ_2: + case OPTION_STATION_DELAY_TIME: + { + int16_t t=water_time_decode_signed(options[i]); + if(t>=0) lcd_print_pgm(PSTR("+")); + lcd.print(t); + } + break; + case OPTION_HTTPPORT_0: + lcd.print((unsigned int)(options[i+1]<<8)+options[i]); + break; + case OPTION_PULSE_RATE_0: + { + uint16_t fpr = (unsigned int)(options[i+1]<<8)+options[i]; + lcd.print(fpr/100); + lcd_print_pgm(PSTR(".")); + lcd.print((fpr/10)%10); + lcd.print(fpr%10); + } + break; + case OPTION_LCD_CONTRAST: + lcd_set_contrast(); + lcd.print((int)options[i]); + break; + case OPTION_LCD_BACKLIGHT: + lcd_set_brightness(); + lcd.print((int)options[i]); + break; + case OPTION_BOOST_TIME: + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) + if(hw_type==HW_TYPE_AC) { + lcd.print('-'); + } else { + lcd.print((int)options[i]*4); + lcd_print_pgm(PSTR(" ms")); + } + #else + lcd.print('-'); + #endif + break; + default: + // if this is a boolean option + if (pgm_read_byte(op_max+i)==1) + lcd_print_pgm(options[i] ? PSTR("Yes") : PSTR("No")); + else + lcd.print((int)options[i]); + break; + } + if (i==OPTION_WATER_PERCENTAGE) lcd_print_pgm(PSTR("%")); + else if (i==OPTION_MASTER_ON_ADJ || i==OPTION_MASTER_OFF_ADJ || i==OPTION_MASTER_ON_ADJ_2 || i==OPTION_MASTER_OFF_ADJ_2) + lcd_print_pgm(PSTR(" sec")); + } @@ -2400,235 +2316,220 @@ void OpenSprinkler::lcd_print_option(int i) { /** wait for button */ byte OpenSprinkler::button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding) { - int hold_time = 0; + int hold_time = 0; - if (waitmode==BUTTON_WAIT_NONE || (waitmode == BUTTON_WAIT_HOLD && is_holding)) { - if (digitalReadExt(pin_butt) != 0) return BUTTON_NONE; - return butt | (is_holding ? BUTTON_FLAG_HOLD : 0); - } + if (waitmode==BUTTON_WAIT_NONE || (waitmode == BUTTON_WAIT_HOLD && is_holding)) { + if (digitalReadExt(pin_butt) != 0) return BUTTON_NONE; + return butt | (is_holding ? BUTTON_FLAG_HOLD : 0); + } - while (digitalReadExt(pin_butt) == 0 && - (waitmode == BUTTON_WAIT_RELEASE || (waitmode == BUTTON_WAIT_HOLD && hold_time= BUTTON_HOLD_MS) - butt |= BUTTON_FLAG_HOLD; - return butt; + while (digitalReadExt(pin_butt) == 0 && + (waitmode == BUTTON_WAIT_RELEASE || (waitmode == BUTTON_WAIT_HOLD && hold_time= BUTTON_HOLD_MS) + butt |= BUTTON_FLAG_HOLD; + return butt; } /** read button and returns button value 'OR'ed with flag bits */ byte OpenSprinkler::button_read(byte waitmode) { - static byte old = BUTTON_NONE; - byte curr = BUTTON_NONE; - byte is_holding = (old&BUTTON_FLAG_HOLD); - - delay(BUTTON_DELAY_MS); - - if (digitalReadExt(PIN_BUTTON_1) == 0) { - curr = button_read_busy(PIN_BUTTON_1, waitmode, BUTTON_1, is_holding); - } else if (digitalReadExt(PIN_BUTTON_2) == 0) { - curr = button_read_busy(PIN_BUTTON_2, waitmode, BUTTON_2, is_holding); - } else if (digitalReadExt(PIN_BUTTON_3) == 0) { - curr = button_read_busy(PIN_BUTTON_3, waitmode, BUTTON_3, is_holding); - } + static byte old = BUTTON_NONE; + byte curr = BUTTON_NONE; + byte is_holding = (old&BUTTON_FLAG_HOLD); + + delay(BUTTON_DELAY_MS); + + if (digitalReadExt(PIN_BUTTON_1) == 0) { + curr = button_read_busy(PIN_BUTTON_1, waitmode, BUTTON_1, is_holding); + } else if (digitalReadExt(PIN_BUTTON_2) == 0) { + curr = button_read_busy(PIN_BUTTON_2, waitmode, BUTTON_2, is_holding); + } else if (digitalReadExt(PIN_BUTTON_3) == 0) { + curr = button_read_busy(PIN_BUTTON_3, waitmode, BUTTON_3, is_holding); + } - // set flags in return value - byte ret = curr; - if (!(old&BUTTON_MASK) && (curr&BUTTON_MASK)) - ret |= BUTTON_FLAG_DOWN; - if ((old&BUTTON_MASK) && !(curr&BUTTON_MASK)) - ret |= BUTTON_FLAG_UP; + // set flags in return value + byte ret = curr; + if (!(old&BUTTON_MASK) && (curr&BUTTON_MASK)) + ret |= BUTTON_FLAG_DOWN; + if ((old&BUTTON_MASK) && !(curr&BUTTON_MASK)) + ret |= BUTTON_FLAG_UP; - old = curr; - - return ret; + old = curr; + + return ret; } /** user interface for setting options during startup */ void OpenSprinkler::ui_set_options(int oid) { - boolean finished = false; - byte button; - int i=oid; - - while(!finished) { - button = button_read(BUTTON_WAIT_HOLD); - - switch (button & BUTTON_MASK) { - case BUTTON_1: - if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || - i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || - i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || - i==IOPT_WIFI_MODE) break; // ignore non-editable options - if (pgm_read_byte(iopt_max+i) != iopts[i]) iopts[i] ++; - break; - - case BUTTON_2: - if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || - i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || - i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || - i==IOPT_WIFI_MODE) break; // ignore non-editable options - if (iopts[i] != 0) iopts[i] --; - break; - - case BUTTON_3: - if (!(button & BUTTON_FLAG_DOWN)) break; - if (button & BUTTON_FLAG_HOLD) { - // if IOPT_RESET is set to nonzero, change it to reset condition value - if (iopts[IOPT_RESET]) { - iopts[IOPT_RESET] = 0xAA; - } - // long press, save options - iopts_save(); - finished = true; - } - else { - // click, move to the next option - 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 { - i = (i+1) % NUM_IOPTS; - } - if(i==IOPT_SEQUENTIAL_RETIRED) i++; - if(i==IOPT_URS_RETIRED) i++; - if(i==IOPT_RSO_RETIRED) i++; - if (hw_type==HW_TYPE_AC && i==IOPT_BOOST_TIME) i++; // skip boost time for non-DC controller - #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; - } - - if (button != BUTTON_NONE) { - lcd_print_option(i); - } - } - lcd.noBlink(); + boolean finished = false; + byte button; + int i=oid; + + while(!finished) { + button = button_read(BUTTON_WAIT_HOLD); + + switch (button & BUTTON_MASK) { + case BUTTON_1: + if (i==OPTION_FW_VERSION || i==OPTION_HW_VERSION || i==OPTION_FW_MINOR || + i==OPTION_HTTPPORT_0 || i==OPTION_HTTPPORT_1 || + i==OPTION_PULSE_RATE_0 || i==OPTION_PULSE_RATE_1) break; // ignore non-editable options + if (pgm_read_byte(op_max+i) != options[i]) options[i] ++; + break; + + case BUTTON_2: + if (i==OPTION_FW_VERSION || i==OPTION_HW_VERSION || i==OPTION_FW_MINOR || + i==OPTION_HTTPPORT_0 || i==OPTION_HTTPPORT_1 || + i==OPTION_PULSE_RATE_0 || i==OPTION_PULSE_RATE_1) break; // ignore non-editable options + if (options[i] != 0) options[i] --; + break; + + case BUTTON_3: + if (!(button & BUTTON_FLAG_DOWN)) break; + if (button & BUTTON_FLAG_HOLD) { + // if OPTION_RESET is set to nonzero, change it to reset condition value + if (options[OPTION_RESET]) { + options[OPTION_RESET] = 0xAA; + } + // long press, save options + options_save(); + finished = true; + } + else { + // click, move to the next option + if (i==OPTION_USE_DHCP && options[i]) i += 9; // if use DHCP, skip static ip set + else if (i==OPTION_HTTPPORT_0) i+=2; // skip OPTION_HTTPPORT_1 + else if (i==OPTION_PULSE_RATE_0) i+=2; // skip OPTION_PULSE_RATE_1 + else if (i==OPTION_SENSOR1_TYPE && options[i]!=SENSOR_TYPE_RAIN) i+=2; // if sensor1 is not rain sensor, skip sensor1 option + else if (i==OPTION_SENSOR2_TYPE && options[i]!=SENSOR_TYPE_RAIN) i+=2; // if sensor2 is not rain sensor, skip sensor2 option + else if (i==OPTION_MASTER_STATION && options[i]==0) i+=3; // if not using master station, skip master on/off adjust + else if (i==OPTION_MASTER_STATION_2&& options[i]==0) i+=3; // if not using master2, skip master2 on/off adjust + else { + i = (i+1) % NUM_OPTIONS; + } + if(i==OPTION_SEQUENTIAL_RETIRED) i++; + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) + else if (hw_type==HW_TYPE_AC && i==OPTION_BOOST_TIME) i++; // skip boost time for non-DC controller + #ifdef ESP8266 + else if (lcd.type()==LCD_I2C && i==OPTION_LCD_CONTRAST) i+=3; + #else + else if (lcd.type()==LCD_I2C && i==OPTION_LCD_CONTRAST) i+=2; + #endif + #endif + } + break; + } + + if (button != BUTTON_NONE) { + lcd_print_option(i); + } + } + lcd.noBlink(); } /** 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]); - } + // set contrast is only valid for standard LCD + if (lcd.type()==LCD_STD) { + pinMode(PIN_LCD_CONTRAST, OUTPUT); + analogWrite(PIN_LCD_CONTRAST, options[OPTION_LCD_CONTRAST]); + } #endif } /** Set LCD brightness (using PWM) */ void OpenSprinkler::lcd_set_brightness(byte value) { #ifdef PIN_LCD_BACKLIGHT - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - 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]) 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]); - } - } + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + if (lcd.type()==LCD_I2C) { + if (value) lcd.backlight(); + else { + // turn off LCD backlight + // only if dimming value is set to 0 + if(!options[OPTION_LCD_DIMMING]) lcd.noBacklight(); + else lcd.backlight(); + } + } + #endif + if (lcd.type()==LCD_STD) { + pinMode(PIN_LCD_BACKLIGHT, OUTPUT); + if (value) { + analogWrite(PIN_LCD_BACKLIGHT, 255-options[OPTION_LCD_BACKLIGHT]); + } else { + analogWrite(PIN_LCD_BACKLIGHT, 255-options[OPTION_LCD_DIMMING]); + } + } #endif } -#endif // end of LCD and button functions +#endif // end of LCD and button functions -#if defined(ESP8266) +#ifdef ESP8266 #include "images.h" void OpenSprinkler::flash_screen() { - lcd.setCursor(0, -1); - lcd.print(F(" OpenSprinkler")); - lcd.drawXbm(34, 24, WiFi_Logo_width, WiFi_Logo_height, (const byte*) WiFi_Logo_image); - lcd.setCursor(0, 2); - lcd.display(); - delay(1500); - lcd.clear(); - lcd.display(); + lcd.setCursor(0, -1); + lcd.print(F(" OpenSprinkler")); + lcd.drawXbm(34, 24, WiFi_Logo_width, WiFi_Logo_height, (const byte*) WiFi_Logo_image); + lcd.setCursor(0, 2); + lcd.display(); + delay(1500); + lcd.clear(); + lcd.display(); } void OpenSprinkler::toggle_screen_led() { - static byte status = 0; - status = 1-status; - set_screen_led(!status); + static byte status = 0; + status = 1-status; + set_screen_led(!status); } void OpenSprinkler::set_screen_led(byte status) { - lcd.setColor(status ? WHITE : BLACK); - lcd.fillCircle(122, 58, 4); - lcd.display(); - lcd.setColor(WHITE); + lcd.setColor(status ? WHITE : BLACK); + lcd.fillCircle(122, 58, 4); + lcd.display(); + lcd.setColor(WHITE); } void OpenSprinkler::reset_to_ap() { - iopts[IOPT_WIFI_MODE] = WIFI_MODE_AP; - iopts_save(); - reboot_dev(REBOOT_CAUSE_RSTAP); + wifi_config.mode = WIFI_MODE_AP; + options_save(true); + reboot_dev(); } void OpenSprinkler::config_ip() { - if(iopts[IOPT_USE_DHCP] == 0) { - byte *_ip = iopts+IOPT_STATIC_IP1; - IPAddress dvip(_ip[0], _ip[1], _ip[2], _ip[3]); - if(dvip==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_GATEWAY_IP1; - IPAddress gwip(_ip[0], _ip[1], _ip[2], _ip[3]); - if(gwip==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_SUBNET_MASK1; - IPAddress subn(_ip[0], _ip[1], _ip[2], _ip[3]); - if(subn==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_DNS_IP1; - IPAddress dnsip(_ip[0], _ip[1], _ip[2], _ip[3]); - - WiFi.config(dvip, gwip, subn, dnsip); - } -} - -void OpenSprinkler::save_wifi_ip() { - if(iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED) { - memcpy(iopts+IOPT_STATIC_IP1, &(WiFi.localIP()[0]), 4); - memcpy(iopts+IOPT_GATEWAY_IP1, &(WiFi.gatewayIP()[0]),4); - memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); - memcpy(iopts+IOPT_SUBNET_MASK1, &(WiFi.subnetMask()[0]), 4); - iopts_save(); - } + if(options[OPTION_USE_DHCP] == 0) { + byte *_ip = options+OPTION_STATIC_IP1; + IPAddress dvip(_ip[0], _ip[1], _ip[2], _ip[3]); + if(dvip==(uint32_t)0x00000000) return; + + _ip = options+OPTION_GATEWAY_IP1; + IPAddress gwip(_ip[0], _ip[1], _ip[2], _ip[3]); + if(gwip==(uint32_t)0x00000000) return; + + IPAddress subn(255,255,255,0); + _ip = options+OPTION_DNS_IP1; + IPAddress dnsip(_ip[0], _ip[1], _ip[2], _ip[3]); + WiFi.config(dvip, gwip, subn, dnsip); + } } void OpenSprinkler::detect_expanders() { - for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) { - byte address = EXP_I2CADDR_BASE+i; - byte type = IOEXP::detectType(address); - if(expanders[i]!=NULL) delete expanders[i]; - if(type==IOEXP_TYPE_9555) { - expanders[i] = new PCA9555(address); - expanders[i]->i2c_write(NXP_CONFIG_REG, 0); // set all channels to output - } else if(type==IOEXP_TYPE_8575){ - expanders[i] = new PCF8575(address); - } else { - expanders[i] = new IOEXP(address); - } - } + for(byte i=0;i<(MAX_EXT_BOARDS+1)/2;i++) { + byte address = EXP_I2CADDR_BASE+i; + byte type = IOEXP::detectType(address); + if(expanders[i]!=NULL) delete expanders[i]; + if(type==IOEXP_TYPE_9555) { + expanders[i] = new PCA9555(address); + expanders[i]->i2c_write(NXP_CONFIG_REG, 0); // set all channels to output + } else if(type==IOEXP_TYPE_8575){ + expanders[i] = new PCF8575(address); + } else { + expanders[i] = new IOEXP(address); + } + } } #endif diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 01b3c25a8..430a26cd0 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -25,308 +25,265 @@ #ifndef _OPENSPRINKLER_H #define _OPENSPRINKLER_H -#include "defines.h" -#include "utils.h" -#include "gpio.h" -#include "images.h" -#include "mqtt.h" - -#if defined(ARDUINO) // headers for ESP8266 - #include - #include - #include - #include - #include "I2CRTC.h" - - #if defined(ESP8266) - #include - #include - #include "SSD1306Display.h" - #include "espconnect.h" - #else - #include - #include "LiquidCrystal.h" - #endif - +#if defined(ARDUINO) && !defined(ESP8266) // headers for AVR + #include "Arduino.h" + #include + #include + #include "LiquidCrystal.h" + #include "Time.h" + #include "DS1307RTC.h" + #include "EtherCard.h" +#elif defined(ESP8266) // headers for ESP8266 + #include + #include + #include + #include "SSD1306Display.h" + #include "i2crtc.h" + #include "espconnect.h" #else // headers for RPI/BBB/LINUX - #include - #include - #include - #include - #include - #include "etherport.h" + #include + #include + #include + #include "etherport.h" #endif // end of headers -/** Non-volatile data structure */ +#include "defines.h" +#include "utils.h" +#include "gpio.h" + +/** Non-volatile data */ 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 }; -struct StationAttrib { // station attributes - byte mas:1; - byte igs:1; // ignore sensor 1 - byte mas2:1; - byte dis:1; - byte seq:1; - byte igs2:1;// ignore sensor 2 - byte igrd:1;// ignore rain delay - byte unused:1; - - byte gid:4; // group id: reserved for the future - byte dummy:4; - byte reserved[2]; // reserved bytes for the future -}; // total is 4 bytes so far - -/** Station data structure */ -struct StationData { - char name[STATION_NAME_SIZE]; - StationAttrib attrib; - byte type; // station type - byte sped[STATION_SPECIAL_DATA_SIZE]; // special station data +/** Station special attribute data */ +struct StationSpecialData { + byte type; + byte data[STATION_SPECIAL_DATA_SIZE]; }; -/** RF station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ +/** Station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ struct RFStationData { - byte on[6]; - byte off[6]; - byte timing[4]; + byte on[6]; + byte off[6]; + byte timing[4]; +}; + +struct RFStationDataFull { + byte on[8]; + byte off[8]; + byte timing[4]; + byte protocol[4]; }; -/** Remote station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ struct RemoteStationData { - byte ip[8]; - byte port[4]; - byte sid[2]; + byte ip[8]; + byte port[4]; + byte sid[2]; }; -/** GPIO station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ struct GPIOStationData { - byte pin[2]; - byte active; + byte pin[2]; + byte active; }; -/** HTTP station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ struct HTTPStationData { - byte data[STATION_SPECIAL_DATA_SIZE]; + byte data[STATION_SPECIAL_DATA_SIZE]; }; /** Volatile controller status bits */ struct ConStatus { - byte enabled:1; // operation enable (when set, controller operation is enabled) - byte rain_delayed:1; // rain delay bit (when set, rain delay is applied) - byte sensor1:1; // sensor1 status bit (when set, sensor1 on is detected) - byte program_busy:1; // HIGH means a program is being executed currently - byte has_curr_sense:1; // HIGH means the controller has a current sensing pin - byte safe_reboot:1; // HIGH means a safe reboot has been marked - byte req_ntpsync:1; // request ntpsync - byte req_network:1; // request check network - byte display_board:5; // the board that is being displayed onto the lcd - byte network_fails:3; // number of network fails - byte mas:8; // master station index - byte mas2:8; // master2 station index - byte sensor2:1; // sensor2 status bit (when set, sensor2 on is detected) - byte sensor1_active:1; // sensor1 active bit (when set, sensor1 is activated) - byte sensor2_active:1; // sensor2 active bit (when set, sensor2 is activated) - byte req_mqtt_restart:1; // request mqtt restart + byte enabled:1; // operation enable (when set, controller operation is enabled) + byte rain_delayed:1; // rain delay bit (when set, rain delay is applied) + byte rain_sensed:1; // rain sensor bit (when set, it indicates that rain is detected) + byte program_busy:1; // HIGH means a program is being executed currently + byte has_curr_sense:1; // HIGH means the controller has a current sensing pin + byte has_sd:1; // HIGH means a microSD card is detected + byte safe_reboot:1; // HIGH means a safe reboot has been marked + byte has_hwmac:1; // has hardware MAC chip + byte req_ntpsync:1; // request ntpsync + byte req_network:1; // request check network + byte display_board:4; // the board that is being displayed onto the lcd + byte network_fails:2; // number of network fails + byte mas:8; // master station index + byte mas2:8; // master2 station index + + byte soil_moisture_sensed:1; // soil moisture sensor bit (when set, it indicates wet, delayed) + byte soil_moisture_active:1; // soil moisture sensor bit (when set, it indicates wet, active after delay) }; -extern const char iopt_json_names[]; -extern const uint8_t iopt_max[]; +extern const char wtopts_filename[]; +extern const char stns_filename[]; +extern const char ifkey_filename[]; +extern const byte op_max[]; +extern const char op_json_names[]; +#ifdef ESP8266 +struct WiFiConfig { + byte mode; + String ssid; + String pass; +}; +extern const char wifi_filename[]; +#endif class OpenSprinkler { public: - // data members -#if defined(ESP8266) - static SSD1306Display lcd; // 128x64 OLED display -#elif defined(ARDUINO) - static LiquidCrystal lcd; // 16x2 character LCD + // data members +#if defined(ARDUINO) && !defined(ESP8266) + static LiquidCrystal lcd; // 16x2 character LCD +#elif defined(ESP8266) + static SSD1306Display lcd; // 128x64 OLED display #else - // todo: LCD define for RPI/BBB + // todo: LCD define for RPI/BBB #endif #if defined(OSPI) - static byte pin_sr_data; // RPi shift register data pin - // to handle RPi rev. 1 + static byte pin_sr_data; // RPi shift register data pin + // to handle RPi rev. 1 #endif - static OSMqtt mqtt; - - static NVConData nvdata; - static ConStatus status; - static ConStatus old_status; - static byte nboards, nstations; - static byte hw_type; // hardware type - static byte hw_rev; // hardware minor - - static byte iopts[]; // integer options - static const char*sopts[]; // string options - static byte station_bits[]; // station activation bits. each byte corresponds to a board (8 stations) - // first byte-> master controller, second byte-> ext. board 1, and so on - // todo future: the following attribute bytes are for backward compatibility - static byte attrib_mas[]; - static byte attrib_igs[]; - static byte attrib_mas2[]; - static byte attrib_igs2[]; - static byte attrib_igrd[]; - static byte attrib_dis[]; - static byte attrib_seq[]; - static byte attrib_spe[]; - - // variables for time keeping - static ulong sensor1_on_timer; // time when sensor1 is detected on last time - static ulong sensor1_off_timer; // time when sensor1 is detected off last time - static ulong sensor1_active_lasttime; // most recent time sensor1 is activated - static ulong sensor2_on_timer; // time when sensor2 is detected on last time - static ulong sensor2_off_timer; // time when sensor2 is detected off last time - static ulong sensor2_active_lasttime; // most recent time sensor1 is activated - static ulong raindelay_on_lasttime; // time when the most recent rain delay started - static ulong flowcount_rt; // flow count (for computing real-time flow rate) - static ulong flowcount_log_start; // starting flow count (for logging) - - static byte button_timeout; // button timeout - static ulong checkwt_lasttime; // time when weather was checked - static ulong checkwt_success_lasttime; // time when weather check was successful - static ulong powerup_lasttime; // time when controller is powered up most recently - static uint8_t last_reboot_cause; // last reboot cause - static byte weather_update_flag; - // member functions - // -- setup - static void update_dev(); // update software for Linux instances - static void reboot_dev(uint8_t); // reboot the microcontroller - static void begin(); // initialization, must call this function before calling other functions - static byte start_network(); // initialize network with the given mac and port - static byte start_ether(); // initialize ethernet with the given mac and port - static bool network_connected(); // check if the network is up - static bool load_hardware_mac(byte* buffer, bool wired=false); // read hardware mac address - static time_t now_tz(); - // -- station names and attributes - static void get_station_data(byte sid, StationData* data); // get station data - static void set_station_data(byte sid, StationData* data); // set station data - static void get_station_name(byte sid, char buf[]); // get station name - static void set_station_name(byte sid, char buf[]); // set station name - static byte get_station_type(byte sid); // get station type - //static StationAttrib get_station_attrib(byte sid); // get station attribute - static void attribs_save(); // repackage attrib bits and save (backward compatibility) - static void attribs_load(); // load and repackage attrib bits (backward compatibility) - static uint16_t parse_rfstation_code(RFStationData *data, ulong *on, ulong *off); // parse rf code into on/off/time sections - static void switch_rfstation(RFStationData *data, bool turnon); // switch rf station - static void switch_remotestation(RemoteStationData *data, bool turnon); // switch remote station - static void switch_gpiostation(GPIOStationData *data, bool turnon); // switch gpio station - static void switch_httpstation(HTTPStationData *data, bool turnon); // switch http station - - // -- options and data storeage - static void nvdata_load(); - static void nvdata_save(); - - static void options_setup(); - static void iopts_load(); - static void iopts_save(); - static bool sopt_save(byte oid, const char *buf); - static void sopt_load(byte oid, char *buf); - static String sopt_load(byte oid); - - static byte password_verify(char *pw); // verify password - - // -- controller operation - static void enable(); // enable controller operation - static void disable(); // disable controller operation, all stations will be closed immediately - static void raindelay_start(); // start raindelay - static void raindelay_stop(); // stop rain delay - static void detect_binarysensor_status(ulong);// update binary (rain, soil) sensor status - static byte detect_programswitch_status(ulong); // get program switch status - static void sensor_resetall(); - - static uint16_t read_current(); // read current sensing value - static uint16_t baseline_current; // resting state current - - static int detect_exp(); // detect the number of expansion boards - static byte weekday_today(); // returns index of today's weekday (Monday is 0) + static NVConData nvdata; + static ConStatus status; + static ConStatus old_status; + static byte nboards, nstations; + static byte hw_type; // hardware type + static byte hw_rev; // hardware minor + + static byte options[]; // option values, max, name, and flag + + static byte station_bits[]; // station activation bits. each byte corresponds to a board (8 stations) + // first byte-> master controller, second byte-> ext. board 1, and so on + + // variables for time keeping + static ulong sensor_lasttime; // time when the last sensor reading is recorded + static ulong soil_moisture_sensed_time; //time when soil moisture detects wet, base for delay + static volatile ulong flowcount_time_ms;// time stamp when new flow sensor click is received (in milliseconds) + static ulong flowcount_rt; // flow count (for computing real-time flow rate) + static ulong flowcount_log_start; // starting flow count (for logging) + static ulong raindelay_start_time; // time when the most recent rain delay started + static byte button_timeout; // button timeout + static ulong checkwt_lasttime; // time when weather was checked + static ulong checkwt_success_lasttime; // time when weather check was successful + static ulong powerup_lasttime; // time when controller is powered up most recently + static byte weather_update_flag; + // member functions + // -- setup + static void update_dev(); // update software for Linux instances + static void reboot_dev(); // reboot the microcontroller + static void begin(); // initialization, must call this function before calling other functions + static byte start_network(); // initialize network with the given mac and port + static byte start_ether(); // initialize ethernet with the given mac and port +#if defined(ARDUINO) + static bool read_hardware_mac(); // read hardware mac address +#endif + #ifdef ESP8266_ETHERNET + static void get_hardware_mac(); + #endif + static time_t now_tz(); + // -- station names and attributes + static void get_station_name(byte sid, char buf[]); // get station name + static void set_station_name(byte sid, char buf[]); // set station name + static uint16_t parse_rfstation_code(RFStationData *data, ulong *on, ulong *off); // parse rf code into on/off/time sections + static void switch_rfstation(RFStationData *data, bool turnon); // switch rf station + static void switch_remotestation(RemoteStationData *data, bool turnon); // switch remote station + static void switch_gpiostation(GPIOStationData *data, bool turnon); // switch gpio station + static void switch_httpstation(HTTPStationData *data, bool turnon); // switch http station + static void station_attrib_bits_save(int addr, byte bits[]); // save station attribute bits to nvm + static void station_attrib_bits_load(int addr, byte bits[]); // load station attribute bits from nvm + static byte station_attrib_bits_read(int addr); // read one station attribte byte from nvm + + // -- options and data storeage + static void nvdata_load(); + static void nvdata_save(); + + static void options_setup(); + static void options_load(); + static void options_save(bool savewifi=false); + + static byte password_verify(char *pw); // verify password + + // -- controller operation + static void enable(); // enable controller operation + static void disable(); // disable controller operation, all stations will be closed immediately + static void raindelay_start(); // start raindelay + static void raindelay_stop(); // stop rain delay + static void rainsensor_status();// update rainsensor status + static void soil_moisture_sensor_status(); // update soil moisture status + static bool programswitch_status(ulong); // get program switch status +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) + static uint16_t read_current(); // read current sensing value + static uint16_t baseline_current; // resting state current +#endif + static int detect_exp(); // detect the number of expansion boards + static byte weekday_today(); // returns index of today's weekday (Monday is 0) - static byte set_station_bit(byte sid, byte value); // set station bit of one station (sid->station index, value->0/1) - static void switch_special_station(byte sid, byte value); // swtich special station - static void clear_all_station_bits(); // clear all station bits - static void apply_all_station_bits(); // apply all station bits (activate/deactive values) + static byte set_station_bit(byte sid, byte value); // set station bit of one station (sid->station index, value->0/1) + static void switch_special_station(byte sid, byte value); // swtich special station + static void clear_all_station_bits(); // clear all station bits + static void apply_all_station_bits(); // apply all station bits (activate/deactive values) - static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); - static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); - static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); - // -- LCD functions + // -- LCD functions #if defined(ARDUINO) // LCD functions for Arduino - #if defined(ESP8266) - 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, byte 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, byte line); - #endif - static void lcd_print_time(time_t t); // print current time - static void lcd_print_ip(const byte *ip, byte endian); // print ip - static void lcd_print_mac(const byte *mac); // print mac - static void lcd_print_station(byte line, char c); // print station bits of the board selected by display_board - static void lcd_print_version(byte v); // print version number - - // -- UI and buttons - static byte button_read(byte waitmode); // Read button value. options for 'waitmodes' are: - // BUTTON_WAIT_NONE, BUTTON_WAIT_RELEASE, BUTTON_WAIT_HOLD - // return values are 'OR'ed with flags - // check defines.h for details - - // -- UI functions -- - static void ui_set_options(int oid); // ui for setting options (oid-> starting option index) - static void lcd_set_brightness(byte value=1); - static void lcd_set_contrast(); - - #if defined(ESP8266) - static IOEXP *mainio, *drio; - static IOEXP *expanders[]; - static RCSwitch rfswitch; - static void detect_expanders(); - static void flash_screen(); - static void toggle_screen_led(); - static void set_screen_led(byte status); - static byte get_wifi_mode() {return wifi_testmode ? WIFI_MODE_STA : iopts[IOPT_WIFI_MODE];} - static byte wifi_testmode; - static String wifi_ssid, wifi_pass; - static void config_ip(); - static void save_wifi_ip(); - static void reset_to_ap(); - static byte state; - #endif - + #ifdef ESP8266 + 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, byte 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, byte line); + #endif + static void lcd_print_time(time_t t); // print current time + static void lcd_print_ip(const byte *ip, byte endian); // print ip + static void lcd_print_mac(const byte *mac); // print mac + static void lcd_print_station(byte line, char c); // print station bits of the board selected by display_board + static void lcd_print_version(byte v); // print version number + + // -- UI and buttons + static byte button_read(byte waitmode); // Read button value. options for 'waitmodes' are: + // BUTTON_WAIT_NONE, BUTTON_WAIT_RELEASE, BUTTON_WAIT_HOLD + // return values are 'OR'ed with flags + // check defines.h for details + + // -- UI functions -- + static void ui_set_options(int oid); // ui for setting options (oid-> starting option index) + static void lcd_set_brightness(byte value=1); + static void lcd_set_contrast(); + + #ifdef ESP8266 + static WiFiConfig wifi_config; + static IOEXP *mainio, *drio; + static IOEXP *expanders[]; + static RCSwitch rfswitch; + static void detect_expanders(); + static void flash_screen(); + static void toggle_screen_led(); + static void set_screen_led(byte status); + static byte get_wifi_mode() {return wifi_config.mode;} + static void config_ip(); + static void reset_to_ap(); + static byte state; + #endif private: - static void lcd_print_option(int i); // print an option to the lcd - static void lcd_print_2digit(int v); // print a integer in 2 digits - static void lcd_start(); - static byte button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding); - - #if defined(ESP8266) - static void latch_boost(); - static void latch_open(byte sid); - static void latch_close(byte sid); - static void latch_setzonepin(byte sid, byte value); - static void latch_setallzonepins(byte value); - static void latch_apply_all_station_bits(); - static byte prev_station_bits[]; - #endif + static void lcd_print_option(int i); // print an option to the lcd + static void lcd_print_2digit(int v); // print a integer in 2 digits + static void lcd_start(); + static byte button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding); +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) + static byte engage_booster; +#endif +#if defined(ESP8266) + static void latch_boost(); + static void latch_open(byte sid); + static void latch_close(byte sid); + static void latch_setzonepin(byte sid, byte value); + static void latch_setallzonepins(byte value); + static void latch_apply_all_station_bits(); + static byte prev_station_bits[]; +#endif #endif // LCD functions - static byte engage_booster; }; -// todo -#if defined(ARDUINO) - extern EthernetServer *m_server; - extern EthernetClient *m_client; - extern EthernetUDP *Udp; - #if defined(ESP8266) - extern ESP8266WebServer *wifi_server; - #endif -#else - extern EthernetServer *m_server; -#endif - -#endif // _OPENSPRINKLER_H +#endif // _OPENSPRINKLER_H diff --git a/defines.h b/defines.h index be6671051..b96989150 100644 --- a/defines.h +++ b/defines.h @@ -24,19 +24,23 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ESP8266_ETHERNET typedef unsigned char byte; typedef unsigned long ulong; - -#define TMP_BUFFER_SIZE 255 // scratch buffer size + +#if !defined(ARDUINO) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) + #define TMP_BUFFER_SIZE 255 // scratch buffer size +#else + #define TMP_BUFFER_SIZE 128 // scratch buffer size +#endif /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 219 // Firmware version: 219 means 2.1.9 +#define OS_FW_VERSION 218 // Firmware version: 218 means 2.1.8 // 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 3 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 @@ -50,66 +54,43 @@ typedef unsigned long ulong; #define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges #define HW_TYPE_UNKNOWN 0xFF -/** Data file names */ -#define IOPTS_FILENAME "iopts.dat" // integer options data file -#define SOPTS_FILENAME "sopts.dat" // string options data file -#define STATIONS_FILENAME "stns.dat" // stations data file -#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 +/** File names */ +#define WEATHER_OPTS_FILENAME "wtopts.txt" // weather options file +#define STATION_ATTR_FILENAME "stns.dat" // station attributes data file +#define WIFI_FILENAME "wifi.dat" // wifi credentials file +#define IFTTT_KEY_FILENAME "ifkey.txt" +#define IFTTT_KEY_MAXSIZE 128 +#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - 8) + +#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds -/** Station macro defines */ +/** Station type macro defines */ #define STN_TYPE_STANDARD 0x00 -#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station -#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station -#define STN_TYPE_GPIO 0x03 // direct GPIO station -#define STN_TYPE_HTTP 0x04 // HTTP station +#define STN_TYPE_RF 0x01 +#define STN_TYPE_REMOTE 0x02 +#define STN_TYPE_GPIO 0x03 // Support for raw connection of station to GPIO pin +#define STN_TYPE_HTTP 0x04 // Support for HTTP Get connection #define STN_TYPE_OTHER 0xFF -/** Notification macro defines */ -#define NOTIFY_PROGRAM_SCHED 0x0001 -#define NOTIFY_SENSOR1 0x0002 -#define NOTIFY_FLOWSENSOR 0x0004 -#define NOTIFY_WEATHER_UPDATE 0x0008 -#define NOTIFY_REBOOT 0x0010 -#define NOTIFY_STATION_OFF 0x0020 -#define NOTIFY_SENSOR2 0x0040 -#define NOTIFY_RAINDELAY 0x0080 -#define NOTIFY_STATION_ON 0x0100 - -/** HTTP request macro defines */ -#define HTTP_RQT_SUCCESS 0 -#define HTTP_RQT_NOT_RECEIVED -1 -#define HTTP_RQT_CONNECT_ERR -2 -#define HTTP_RQT_TIMEOUT -3 -#define HTTP_RQT_EMPTY_RETURN -4 - -/** Sensor macro defines */ +#define IFTTT_PROGRAM_SCHED 0x01 +#define IFTTT_RAINSENSOR 0x02 +#define IFTTT_FLOWSENSOR 0x04 +#define IFTTT_WEATHER_UPDATE 0x08 +#define IFTTT_REBOOT 0x10 +#define IFTTT_STATION_RUN 0x20 +#define IFTTT_SOILSENSOR 0x40 + +/** Sensor type macro defines */ #define SENSOR_TYPE_NONE 0x00 -#define SENSOR_TYPE_RAIN 0x01 // rain sensor -#define SENSOR_TYPE_FLOW 0x02 // flow sensor +#define SENSOR_TYPE_RAIN 0x01 // rain sensor +#define SENSOR_TYPE_FLOW 0x02 // flow sensor #define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor -#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor +#define SENSOR_TYPE_PSWITCH 0xF0 // program switch #define SENSOR_TYPE_OTHER 0xFF -#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds +/** WiFi related defines */ +#ifdef ESP8266 -/** Reboot cause */ -#define REBOOT_CAUSE_NONE 0 -#define REBOOT_CAUSE_RESET 1 -#define REBOOT_CAUSE_BUTTON 2 -#define REBOOT_CAUSE_RSTAP 3 -#define REBOOT_CAUSE_TIMER 4 -#define REBOOT_CAUSE_WEB 5 -#define REBOOT_CAUSE_WIFIDONE 6 -#define REBOOT_CAUSE_FWUPDATE 7 -#define REBOOT_CAUSE_WEATHER_FAIL 8 -#define REBOOT_CAUSE_NETWORK_FAIL 9 -#define REBOOT_CAUSE_NTP 10 -#define REBOOT_CAUSE_POWERON 99 - - -/** WiFi defines */ #define WIFI_MODE_AP 0xA9 #define WIFI_MODE_STA 0x2A @@ -121,349 +102,465 @@ typedef unsigned long ulong; #define LED_FAST_BLINK 100 #define LED_SLOW_BLINK 500 -/** Storage / zone expander defines */ -#if defined(ARDUINO) - #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 #endif -#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders -#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations -#define STATION_NAME_SIZE 32 // maximum number of characters in each station name -#define MAX_SOPTS_SIZE 160 // maximum string option size +/** Non-volatile memory (NVM) defines */ +#if defined(ARDUINO) && !defined(ESP8266) + +/** 2KB NVM (ATmega644) data structure: + * | | | ---STRING PARAMETERS--- | | ----STATION ATTRIBUTES----- | | + * | PROGRAM | CON | PWD | LOC | JURL | WURL | KEY | STN_NAMES | MAS | IGR | MAS2 | DIS | SEQ | SPE | OPTIONS | + * | (986) |(12) |(36) |(48) | (40) | (40) |(24) | (768) | (6) | (6) | (6) | (6) | (6) | (6) | (58) | + * | | | | | | | | | | | | | | | | + * 0 986 998 1034 1082 1122 1162 1186 1954 1960 1966 1972 1978 1984 1990 2048 + */ + +/** 4KB NVM (ATmega1284) data structure: + * | | | ---STRING PARAMETERS--- | | ----STATION ATTRIBUTES----- | | + * | PROGRAM | CON | PWD | LOC | JURL | WURL | KEY | STN_NAMES | MAS | IGR | MAS2 | DIS | SEQ | SPE | OPTIONS | + * | (2433) |(12) |(36) |(48) | (48) | (48) |(24) | (1344) | (7) | (7) | (7) | (7) | (7) | (7) | (61) | + * | | | | | | | | | | | | | | | | + * 0 2433 2445 2481 2529 2577 2625 2649 3993 4000 4007 4014 4021 4028 4035 4096 + */ + + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // for 4KB NVM + + #define MAX_EXT_BOARDS 6 // maximum number of exp. boards (each expands 8 stations) + #define MAX_NUM_STATIONS ((1+MAX_EXT_BOARDS)*8) // maximum number of stations + + #define NVM_SIZE 4096 // For AVR, nvm data is stored in EEPROM, ATmega1284 has 4K EEPROM + #define STATION_NAME_SIZE 24 // maximum number of characters in each station name + + #define MAX_PROGRAMDATA 2433 // program data + #define MAX_NVCONDATA 12 // non-volatile controller data + #define MAX_USER_PASSWORD 36 // user password + #define MAX_LOCATION 48 // location string + #define MAX_JAVASCRIPTURL 48 // javascript url + #define MAX_WEATHERURL 48 // weather script url + #define MAX_WEATHER_KEY 24 // weather api key -#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) + #else -/** Default string option values */ + #define MAX_EXT_BOARDS 5 // maximum number of exp. boards (each expands 8 stations) + #define MAX_NUM_STATIONS ((1+MAX_EXT_BOARDS)*8) // maximum number of stations + + #define NVM_SIZE 2048 // For AVR, nvm data is stored in EEPROM, ATmega644 has 2K EEPROM + #define STATION_NAME_SIZE 16 // maximum number of characters in each station name + + #define MAX_PROGRAMDATA 986 // program data + #define MAX_NVCONDATA 12 // non-volatile controller data + #define MAX_USER_PASSWORD 36 // user password + #define MAX_LOCATION 48 // location string + #define MAX_JAVASCRIPTURL 40 // javascript url + #define MAX_WEATHERURL 40 // weather script url + #define MAX_WEATHER_KEY 24 // weather api key, + + #endif + +#else // NVM defines for RPI/BBB/LINUX/ESP8266 + +/** 8KB NVM (RPI/BBB/LINUX/ESP8266) data structure: + * | | | ---STRING PARAMETERS--- | | ----STATION ATTRIBUTES----- | | + * | PROGRAM | CON | PWD | LOC | JURL | WURL | KEY | STN_NAMES | MAS | IGR | MAS2 | DIS | SEQ | SPE | OPTIONS | + * | (6127) |(12) |(36) |(48) | (48) | (48) |(24) | (1728) | (9) | (9) | (9) | (9) | (9) | (9) | (67) | + * | | | | | | | | | | | | | | | | + * 0 6127 6139 6175 6223 6271 6319 6343 8071 8080 8089 8098 8107 8116 8125 8192 + */ + + // These are kept the same as AVR for compatibility reasons + // But they can be increased if needed + #define NVM_FILENAME "nvm.dat" // for RPI/BBB, nvm data is stored in a file + + #define MAX_EXT_BOARDS 8 // maximum number of 8-station exp. boards (a 16-station expander counts as 2) + #define MAX_NUM_STATIONS ((1+MAX_EXT_BOARDS)*8) // maximum number of stations + + #define NVM_SIZE 8192 + #define STATION_NAME_SIZE 24 // maximum number of characters in each station name + + #define MAX_PROGRAMDATA 6127 // program data + #define MAX_NVCONDATA 12 // non-volatile controller data + #define MAX_USER_PASSWORD 36 // user password + #define MAX_LOCATION 48 // location string + #define MAX_JAVASCRIPTURL 48 // javascript url + #define MAX_WEATHERURL 48 // weather script url + #define MAX_WEATHER_KEY 24 // weather api key + +#endif // end of NVM defines + +/** NVM data addresses */ +#define ADDR_NVM_PROGRAMS (0) // program starting address +#define ADDR_NVM_NVCONDATA (ADDR_NVM_PROGRAMS+MAX_PROGRAMDATA) +#define ADDR_NVM_PASSWORD (ADDR_NVM_NVCONDATA+MAX_NVCONDATA) +#define ADDR_NVM_LOCATION (ADDR_NVM_PASSWORD+MAX_USER_PASSWORD) +#define ADDR_NVM_JAVASCRIPTURL (ADDR_NVM_LOCATION+MAX_LOCATION) +#define ADDR_NVM_WEATHERURL (ADDR_NVM_JAVASCRIPTURL+MAX_JAVASCRIPTURL) +#define ADDR_NVM_WEATHER_KEY (ADDR_NVM_WEATHERURL+MAX_WEATHERURL) +#define ADDR_NVM_STN_NAMES (ADDR_NVM_WEATHER_KEY+MAX_WEATHER_KEY) +#define ADDR_NVM_MAS_OP (ADDR_NVM_STN_NAMES+MAX_NUM_STATIONS*STATION_NAME_SIZE) // master op bits +#define ADDR_NVM_IGNRAIN (ADDR_NVM_MAS_OP+(MAX_EXT_BOARDS+1)) // ignore rain bits +#define ADDR_NVM_MAS_OP_2 (ADDR_NVM_IGNRAIN+(MAX_EXT_BOARDS+1)) // master2 op bits +#define ADDR_NVM_STNDISABLE (ADDR_NVM_MAS_OP_2+(MAX_EXT_BOARDS+1))// station disable bits +#define ADDR_NVM_STNSEQ (ADDR_NVM_STNDISABLE+(MAX_EXT_BOARDS+1))// station sequential bits +#define ADDR_NVM_STNSPE (ADDR_NVM_STNSEQ+(MAX_EXT_BOARDS+1)) // station special bits (i.e. non-standard stations) +#define ADDR_NVM_OPTIONS (ADDR_NVM_STNSPE+(MAX_EXT_BOARDS+1)) // options + +/** Default password, location string, weather key, script urls */ #define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA +#define DEFAULT_LOCATION "Boston,MA" +#define DEFAULT_WEATHER_KEY "" #define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" #define DEFAULT_WEATHER_URL "weather.opensprinkler.com" #define DEFAULT_IFTTT_URL "maker.ifttt.com" -#define DEFAULT_EMPTY_STRING "" /** Macro define of each option * Refer to OpenSprinkler.cpp for details on each option */ -enum { - IOPT_FW_VERSION=0,// read-only (ro) - IOPT_TIMEZONE, - IOPT_USE_NTP, - IOPT_USE_DHCP, - IOPT_STATIC_IP1, - IOPT_STATIC_IP2, - IOPT_STATIC_IP3, - IOPT_STATIC_IP4, - IOPT_GATEWAY_IP1, - IOPT_GATEWAY_IP2, - IOPT_GATEWAY_IP3, - IOPT_GATEWAY_IP4, - IOPT_HTTPPORT_0, - IOPT_HTTPPORT_1, - IOPT_HW_VERSION, //ro - IOPT_EXT_BOARDS, - IOPT_SEQUENTIAL_RETIRED, //ro - IOPT_STATION_DELAY_TIME, - IOPT_MASTER_STATION, - IOPT_MASTER_ON_ADJ, - IOPT_MASTER_OFF_ADJ, - IOPT_URS_RETIRED, // ro - IOPT_RSO_RETIRED, // ro - IOPT_WATER_PERCENTAGE, - IOPT_DEVICE_ENABLE, // editable through jc - IOPT_IGNORE_PASSWORD, - IOPT_DEVICE_ID, - IOPT_LCD_CONTRAST, - IOPT_LCD_BACKLIGHT, - IOPT_LCD_DIMMING, - IOPT_BOOST_TIME, - IOPT_USE_WEATHER, - IOPT_NTP_IP1, - IOPT_NTP_IP2, - IOPT_NTP_IP3, - IOPT_NTP_IP4, - IOPT_ENABLE_LOGGING, - IOPT_MASTER_STATION_2, - IOPT_MASTER_ON_ADJ_2, - IOPT_MASTER_OFF_ADJ_2, - IOPT_FW_MINOR, //ro - IOPT_PULSE_RATE_0, - IOPT_PULSE_RATE_1, - IOPT_REMOTE_EXT_MODE, // editable through jc - IOPT_DNS_IP1, - IOPT_DNS_IP2, - IOPT_DNS_IP3, - IOPT_DNS_IP4, - IOPT_SPE_AUTO_REFRESH, - IOPT_IFTTT_ENABLE, - IOPT_SENSOR1_TYPE, - IOPT_SENSOR1_OPTION, - IOPT_SENSOR2_TYPE, - IOPT_SENSOR2_OPTION, - IOPT_SENSOR1_ON_DELAY, - IOPT_SENSOR1_OFF_DELAY, - IOPT_SENSOR2_ON_DELAY, - IOPT_SENSOR2_OFF_DELAY, - IOPT_SUBNET_MASK1, - IOPT_SUBNET_MASK2, - IOPT_SUBNET_MASK3, - IOPT_SUBNET_MASK4, - IOPT_WIFI_MODE, //ro - IOPT_RESET, //ro - NUM_IOPTS // total number of integer options -}; - -enum { - SOPT_PASSWORD=0, - SOPT_LOCATION, - SOPT_JAVASCRIPTURL, - SOPT_WEATHERURL, - SOPT_WEATHER_OPTS, - SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT - SOPT_STA_SSID, - SOPT_STA_PASS, - SOPT_MQTT_OPTS, - //SOPT_WEATHER_KEY, - //SOPT_AP_PASS, - NUM_SOPTS // total number of string options -}; +typedef enum { + OPTION_FW_VERSION = 0, + OPTION_TIMEZONE, + OPTION_USE_NTP, + OPTION_USE_DHCP, + OPTION_STATIC_IP1, + OPTION_STATIC_IP2, + OPTION_STATIC_IP3, + OPTION_STATIC_IP4, + OPTION_GATEWAY_IP1, + OPTION_GATEWAY_IP2, + OPTION_GATEWAY_IP3, + OPTION_GATEWAY_IP4, + OPTION_HTTPPORT_0, + OPTION_HTTPPORT_1, + OPTION_HW_VERSION, + OPTION_EXT_BOARDS, + OPTION_SEQUENTIAL_RETIRED, + OPTION_STATION_DELAY_TIME, + OPTION_MASTER_STATION, + OPTION_MASTER_ON_ADJ, + OPTION_MASTER_OFF_ADJ, + OPTION_SENSOR1_TYPE, + OPTION_SENSOR1_OPTION, + OPTION_WATER_PERCENTAGE, + OPTION_DEVICE_ENABLE, + OPTION_IGNORE_PASSWORD, + OPTION_DEVICE_ID, + OPTION_LCD_CONTRAST, + OPTION_LCD_BACKLIGHT, + OPTION_LCD_DIMMING, + OPTION_BOOST_TIME, + OPTION_USE_WEATHER, + OPTION_NTP_IP1, + OPTION_NTP_IP2, + OPTION_NTP_IP3, + OPTION_NTP_IP4, + OPTION_ENABLE_LOGGING, + OPTION_MASTER_STATION_2, + OPTION_MASTER_ON_ADJ_2, + OPTION_MASTER_OFF_ADJ_2, + OPTION_FW_MINOR, + OPTION_PULSE_RATE_0, + OPTION_PULSE_RATE_1, + OPTION_REMOTE_EXT_MODE, + OPTION_DNS_IP1, + OPTION_DNS_IP2, + OPTION_DNS_IP3, + OPTION_DNS_IP4, + OPTION_SPE_AUTO_REFRESH, + OPTION_IFTTT_ENABLE, + OPTION_SENSOR2_TYPE, + OPTION_SENSOR2_OPTION, + OPTION_RESET, + NUM_OPTIONS // total number of options +} OS_OPTION_t; /** Log Data Type */ #define LOGDATA_STATION 0x00 -#define LOGDATA_SENSOR1 0x01 +#define LOGDATA_RAINSENSE 0x01 #define LOGDATA_RAINDELAY 0x02 #define LOGDATA_WATERLEVEL 0x03 #define LOGDATA_FLOWSENSE 0x04 -#define LOGDATA_SENSOR2 0x05 -#define LOGDATA_CURRENT 0x80 +#define LOGDATA_SOILSENSE 0x05 #undef OS_HW_VERSION /** Hardware defines */ -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // 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 8192 - - #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset - - #define pinModeExt pinMode - #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite - -#elif defined(ESP8266) // for ESP8266 - - #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) - #define IOEXP_PIN 0x80 // base for pins on main IO expander - #define MAIN_I2CADDR 0x20 // main IO expander I2C address - #define ACDR_I2CADDR 0x21 // ac driver I2C address - #define DCDR_I2CADDR 0x22 // dc driver I2C address - #define LADR_I2CADDR 0x23 // latch driver I2C address - #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address - #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address - - #define PIN_CURR_SENSE A0 - #define PIN_FREE_LIST {} // no free GPIO pin at the moment - #define ETHER_BUFFER_SIZE 8192 - - #define PIN_ETHER_CS 16 // ENC28J60 CS (chip select pin) is 16 on OS 3.2. - - /* To accommodate different OS30 versions, we use software defines pins */ - extern byte PIN_BUTTON_1; - extern byte PIN_BUTTON_2; - extern byte PIN_BUTTON_3; - extern byte PIN_RFRX; - extern byte PIN_RFTX; - extern byte PIN_BOOST; - extern byte PIN_BOOST_EN; - extern byte PIN_LATCH_COM; - extern byte PIN_SENSOR1; - extern byte PIN_SENSOR2; - extern byte PIN_IOEXP_INT; - - /* Original OS30 pin defines */ - //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask - // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i - #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 - #define V0_PIN_BUTTON_2 0 // button 2 - #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 - #define V0_PIN_RFRX 14 - #define V0_PIN_PWR_RX IOEXP_PIN+0 - #define V0_PIN_RFTX 16 - #define V0_PIN_PWR_TX IOEXP_PIN+2 - #define V0_PIN_BOOST IOEXP_PIN+6 - #define V0_PIN_BOOST_EN IOEXP_PIN+7 - #define V0_PIN_SENSOR1 12 // sensor 1 - #define V0_PIN_SENSOR2 13 // sensor 2 - - /* OS30 revision 1 pin defines */ - // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V1_IO_CONFIG 0x1F00 // config bits - #define V1_IO_OUTPUT 0x1F00 // output bits - #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 - #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 - #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 - #define V1_PIN_RFRX 14 - #define V1_PIN_RFTX 16 - #define V1_PIN_IOEXP_INT 12 - #define V1_PIN_BOOST IOEXP_PIN+13 - #define V1_PIN_BOOST_EN IOEXP_PIN+14 - #define V1_PIN_LATCH_COM IOEXP_PIN+15 - #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 - #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 - - /* OS30 revision 2 pin defines */ - // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V2_IO_CONFIG 0x1F00 // config bits - #define V2_IO_OUTPUT 0x1F00 // output bits - #define V2_PIN_BUTTON_1 2 // button 1 - #define V2_PIN_BUTTON_2 0 // button 2 - #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 - #define V2_PIN_RFTX 15 - #define V2_PIN_BOOST IOEXP_PIN+13 - #define V2_PIN_BOOST_EN IOEXP_PIN+14 - #define V2_PIN_LATCH_COM IOEXP_PIN+15 - #define V2_PIN_SENSOR1 3 // sensor 1 - #define V2_PIN_SENSOR2 10 // sensor 2 - -#elif defined(OSPI) // for OSPi - - #define OS_HW_VERSION OSPI_HW_VERSION_BASE - #define PIN_SR_LATCH 22 // shift register latch pin - #define PIN_SR_DATA 27 // shift register data pin - #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) - #define PIN_SR_CLOCK 4 // shift register clock pin - #define PIN_SR_OE 17 // shift register output enable pin - #define PIN_SENSOR1 14 - #define PIN_SENSOR2 23 - #define PIN_RFTX 15 // RF transmitter pin - //#define PIN_BUTTON_1 23 // button 1 - //#define PIN_BUTTON_2 24 // button 2 - //#define PIN_BUTTON_3 25 // button 3 - - #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins - #define ETHER_BUFFER_SIZE 16384 - -#elif defined(OSBO) // for OSBo - - #define OS_HW_VERSION OSBO_HW_VERSION_BASE - // these are gpio pin numbers, refer to - // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp - #define PIN_SR_LATCH 60 // P9_12, shift register latch pin - #define PIN_SR_DATA 30 // P9_11, shift register data pin - #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin - #define PIN_SR_OE 50 // P9_14, shift register output enable pin - #define PIN_SENSOR1 48 - #define PIN_RFTX 51 // RF transmitter pin - - #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} - #define ETHER_BUFFER_SIZE 16384 - -#else // for demo / simulation - // use fake hardware pins - #if defined(DEMO) - #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware - #else - #define OS_HW_VERSION SIM_HW_VERSION_BASE - #endif - #define PIN_SR_LATCH 0 - #define PIN_SR_DATA 0 - #define PIN_SR_CLOCK 0 - #define PIN_SR_OE 0 - #define PIN_SENSOR1 0 - #define PIN_SENSOR2 0 - #define PIN_RFTX 0 - #define PIN_FREE_LIST {} - #define ETHER_BUFFER_SIZE 16384 -#endif - -#if defined(ENABLE_DEBUG) /** Serial debug functions */ - - #if defined(ARDUINO) - #define DEBUG_BEGIN(x) {Serial.begin(x);} - #define DEBUG_PRINT(x) {Serial.print(x);} - #define DEBUG_PRINTLN(x) {Serial.println(x);} - #else - #include - #define DEBUG_BEGIN(x) {} /** Serial debug functions */ - inline void DEBUG_PRINT(int x) {printf("%d", x);} - inline void DEBUG_PRINT(const char*s) {printf("%s", s);} - #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} - #endif - -#else - - #define DEBUG_BEGIN(x) {} - #define DEBUG_PRINT(x) {} - #define DEBUG_PRINTLN(x) {} - -#endif - -/** Re-define avr-specific (e.g. PGM) types to use standard types */ -#if !defined(ARDUINO) - #include - #include - #include - #include - inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} - inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} - #define now() time(0) - #define pgm_read_byte(x) *(x) - #define PSTR(x) x - #define F(x) x - #define strcat_P strcat - #define strcpy_P strcpy - #define sprintf_P sprintf - #include - #define String string - using namespace std; - #define PROGMEM - typedef const char* PGM_P; - typedef unsigned char uint8_t; - typedef short int16_t; - typedef unsigned short uint16_t; - typedef bool boolean; - #define pinModeExt pinMode - #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite -#endif +#if defined(ARDUINO) && !defined(ESP8266) + + #if F_CPU==8000000L // 8M for OS20 + #define OS_HW_VERSION (OS_HW_VERSION_BASE+20) + #elif F_CPU==12000000L // 12M for OS21 + #define OS_HW_VERSION (OS_HW_VERSION_BASE+21) + #elif F_CPU==16000000L // 16M for OS22 and OS23 + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + #define OS_HW_VERSION (OS_HW_VERSION_BASE+23) + #define PIN_FREE_LIST {2,10,12,13,14,15,18,19} // Free GPIO pins + #else + #define OS_HW_VERSION (OS_HW_VERSION_BASE+22) + #endif + #endif + + // 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_SD_CS 0 // SD card chip select pin + #define PIN_RAINSENSOR 11 // rain sensor is connected to pin D3 + #define PIN_FLOWSENSOR 11 // flow sensor (currently shared with rain sensor, change if using a different pin) + #define PIN_SOILSENSOR 11 // soil moisture sensor (currently shared with rain sensor, change if using a different 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 + + // Ethernet buffer size + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + #define ETHER_BUFFER_SIZE 1400 // ATmega1284 has 16K RAM, so use a bigger buffer + #else + #define ETHER_BUFFER_SIZE 950 // ATmega644 has 4K RAM, so use a smaller buffer + #endif + + #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset + + //#define SERIAL_DEBUG + #if defined(SERIAL_DEBUG) /** Serial debug functions */ + + #define DEBUG_BEGIN(x) Serial.begin(x) + #define DEBUG_PRINT(x) Serial.print(x) + #define DEBUG_PRINTLN(x) Serial.println(x) + #define DEBUG_PRINTIP(x) ether.printIp("IP:",x) + + #else + + #define DEBUG_BEGIN(x) {} + #define DEBUG_PRINT(x) {} + #define DEBUG_PRINTLN(x) {} + #define DEBUG_PRINTIP(x) {} + + #endif + typedef unsigned char uint8_t; + typedef unsigned int uint16_t; + typedef int int16_t; + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite + +#else // Hardware defines for RPI/BBB/ESP8266 + + #if defined(ESP8266) + + #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) + #define IOEXP_PIN 0x80 // base for pins on main IO expander + #define MAIN_I2CADDR 0x20 // main IO expander I2C address + #define ACDR_I2CADDR 0x21 // ac driver I2C address + #define DCDR_I2CADDR 0x22 // dc driver I2C address + #define LADR_I2CADDR 0x23 // latch driver I2C address + #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address + #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address + + #define PIN_CURR_SENSE A0 + #define PIN_FREE_LIST {} // no free GPIO pin at the moment + #define ETHER_BUFFER_SIZE 4096 + + #define PIN_ETHER_CS 16 // Ethernet controller chip select pin, the CS (chip select pin) is 16 on OS 3.2. + + /* To accommodate different OS30 versions, we use software defines pins */ + extern byte PIN_BUTTON_1; + extern byte PIN_BUTTON_2; + extern byte PIN_BUTTON_3; + extern byte PIN_RFRX; + extern byte PIN_RFTX; + extern byte PIN_BOOST; + extern byte PIN_BOOST_EN; + extern byte PIN_LATCH_COM; + extern byte PIN_SENSOR1; + extern byte PIN_SENSOR2; + extern byte PIN_RAINSENSOR; + extern byte PIN_FLOWSENSOR; + extern byte PIN_SOILSENSOR; + extern byte PIN_RAINSENSOR2; + extern byte PIN_FLOWSENSOR2; + extern byte PIN_SOILSENSOR2; + extern byte PIN_IOEXP_INT; + + /* Original OS30 pin defines */ + //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask + // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i + #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 + #define V0_PIN_BUTTON_2 0 // button 2 + #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 + #define V0_PIN_RFRX 14 + #define V0_PIN_PWR_RX IOEXP_PIN+0 + #define V0_PIN_RFTX 16 + #define V0_PIN_PWR_TX IOEXP_PIN+2 + #define V0_PIN_BOOST IOEXP_PIN+6 + #define V0_PIN_BOOST_EN IOEXP_PIN+7 + #define V0_PIN_SENSOR1 12 // sensor 1 + #define V0_PIN_SENSOR2 13 // sensor 2 + #define V0_PIN_RAINSENSOR V0_PIN_SENSOR1 + #define V0_PIN_FLOWSENSOR V0_PIN_SENSOR1 + #define V0_PIN_SOILSENSOR V0_PIN_SENSOR1 + #define V0_PIN_RAINSENSOR2 V0_PIN_SENSOR2 + #define V0_PIN_FLOWSENSOR2 V0_PIN_SENSOR2 + #define V0_PIN_SOILSENSOR2 V0_PIN_SENSOR2 + + /* OS30 revision 1 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V1_IO_CONFIG 0x1F00 // config bits + #define V1_IO_OUTPUT 0x1F00 // output bits + #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 + #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 + #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V1_PIN_RFRX 14 + #define V1_PIN_RFTX 16 + #define V1_PIN_IOEXP_INT 12 + #define V1_PIN_BOOST IOEXP_PIN+13 + #define V1_PIN_BOOST_EN IOEXP_PIN+14 + #define V1_PIN_LATCH_COM IOEXP_PIN+15 + #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 + #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 + #define V1_PIN_RAINSENSOR V1_PIN_SENSOR1 + #define V1_PIN_FLOWSENSOR V1_PIN_SENSOR1 + #define V1_PIN_SOILSENSOR V1_PIN_SENSOR1 + #define V1_PIN_RAINSENSOR2 V1_PIN_SENSOR2 + #define V1_PIN_FLOWSENSOR2 V1_PIN_SENSOR2 + #define V1_PIN_SOILSENSOR2 V1_PIN_SENSOR2 + + /* OS30 revision 2 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V2_IO_CONFIG 0x9F00 // config bits + #define V2_IO_OUTPUT 0x9F00 // output bits + #define V2_PIN_BUTTON_1 2 // button 1 + #define V2_PIN_BUTTON_2 0 // button 2 + #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V2_PIN_RFTX 15 + #define V2_PIN_BOOST IOEXP_PIN+13 + #define V2_PIN_BOOST_EN IOEXP_PIN+14 + #define V2_PIN_SENSOR1 3 // sensor 1 + #define V2_PIN_SENSOR2 10 // sensor 2 + #define V2_PIN_RAINSENSOR V2_PIN_SENSOR1 + #define V2_PIN_FLOWSENSOR V2_PIN_SENSOR1 + #define V2_PIN_SOILSENSOR V2_PIN_SENSOR1 + #define V2_PIN_RAINSENSOR2 V2_PIN_SENSOR2 + #define V2_PIN_FLOWSENSOR2 V2_PIN_SENSOR2 + #define V2_PIN_SOILSENSOR2 V2_PIN_SENSOR2 + + /** OSPi pin defines */ + #elif defined(OSPI) + + #define OS_HW_VERSION OSPI_HW_VERSION_BASE + #define PIN_SR_LATCH 22 // shift register latch pin + #define PIN_SR_DATA 27 // shift register data pin + #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) + #define PIN_SR_CLOCK 4 // shift register clock pin + #define PIN_SR_OE 17 // shift register output enable pin + #define PIN_RAINSENSOR 14 // rain sensor + #define PIN_FLOWSENSOR 14 // flow sensor (currently shared with rain sensor, change if using a different pin) + #define PIN_SOILSENSOR 14 // soil moisture sensor (currently shared with rain sensor, change if using a different pin) + #define PIN_RFTX 15 // RF transmitter pin + #define PIN_BUTTON_1 23 // button 1 + #define PIN_BUTTON_2 24 // button 2 + #define PIN_BUTTON_3 25 // button 3 + + #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins + #define ETHER_BUFFER_SIZE 16384 + /** BBB pin defines */ + #elif defined(OSBO) + + #define OS_HW_VERSION OSBO_HW_VERSION_BASE + // these are gpio pin numbers, refer to + // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp + #define PIN_SR_LATCH 60 // P9_12, shift register latch pin + #define PIN_SR_DATA 30 // P9_11, shift register data pin + #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin + #define PIN_SR_OE 50 // P9_14, shift register output enable pin + #define PIN_RAINSENSOR 48 // P9_15, rain sensor is connected to pin D3 + #define PIN_FLOWSENSOR 48 // flow sensor (currently shared with rain sensor, change if using a different pin) + #define PIN_SOILSENSOR 48 // soil moisture sensor (currently shared with rain sensor, change if using a different pin) + #define PIN_RFTX 51 // RF transmitter pin + + #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} + #define ETHER_BUFFER_SIZE 16384 + #else + // For Linux or other software simulators + // use fake hardware pins + #if defined(DEMO) + #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware + #else + #define OS_HW_VERSION SIM_HW_VERSION_BASE + #endif + #define PIN_SR_LATCH 0 + #define PIN_SR_DATA 0 + #define PIN_SR_CLOCK 0 + #define PIN_SR_OE 0 + #define PIN_RAINSENSOR 0 + #define PIN_FLOWSENSOR 0 + #define PIN_SOILSENSOR 0 + #define PIN_RFTX 0 + #define PIN_FREE_LIST {} + #define ETHER_BUFFER_SIZE 16384 + #endif + + #define ENABLE_DEBUG + #if defined(ENABLE_DEBUG) + #if defined(ESP8266) + #define DEBUG_BEGIN(x) Serial.begin(x) + #define DEBUG_PRINT(x) Serial.print(x) + #define DEBUG_PRINTLN(x) Serial.println(x) + #else + #define DEBUG_BEGIN(x) {} /** Serial debug functions */ + inline void DEBUG_PRINT(int x) {printf("%d", x);} + inline void DEBUG_PRINT(const char*s) {printf("%s", s);} + #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} + #endif + #else + #define DEBUG_BEGIN(x) {} + #define DEBUG_PRINT(x) {} + #define DEBUG_PRINTLN(x) {} + #endif + + /** Re-define avr-specific (e.g. PGM) types to use standard types */ + #if !defined(ESP8266) + inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} + inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} + #define now() time(0) + #define pgm_read_byte(x) *(x) + #define PSTR(x) x + #define strcat_P strcat + #define strcpy_P strcpy + #define PROGMEM + typedef const char* PGM_P; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef bool boolean; + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite + #endif + +#endif // end of Hardawre defines /** Other defines */ // button values @@ -490,3 +587,5 @@ enum { #define DISPLAY_MSG_MS 2000 // message display time (milliseconds) #endif // _DEFINES_H + + diff --git a/main.cpp b/main.cpp index d4dfd7440..60c6f5bf7 100644 --- a/main.cpp +++ b/main.cpp @@ -27,47 +27,72 @@ #include "program.h" #include "weather.h" #include "server.h" -#include "mqtt.h" #if defined(ARDUINO) - EthernetServer *m_server = NULL; - EthernetClient *m_client = NULL; - EthernetUDP *Udp = NULL; - #if defined(ESP8266) - ESP8266WebServer *wifi_server = NULL; - static uint16_t led_blink_ms = LED_FAST_BLINK; - #else - SdFat sd; // SD card object - #endif - unsigned long getNtpTime(); + +#include "Wire.h" + +#ifdef ESP8266 + #include + #include "gpio.h" + #include "espconnect.h" + char ether_buffer[ETHER_BUFFER_SIZE]; + +#ifdef ESP8266_ETHERNET + #include "UIPServer.h" + #include "UIPClient.h" + #include "UIPEthernet.h" + UIPServer *m_server = 0; + UIPClient *m_client = 0; + UIPEthernetClass ether; +#endif + +#else + #include + byte Ethernet::buffer[ETHER_BUFFER_SIZE]; // Ethernet packet buffer + SdFat sd; // SD card object +#endif + +unsigned long getNtpTime(); + #else // header and defs for RPI/BBB - EthernetServer *m_server = 0; - EthernetClient *m_client = 0; + +#include +#include +#include "etherport.h" +#include "gpio.h" +char ether_buffer[ETHER_BUFFER_SIZE]; +EthernetServer *m_server = 0; +EthernetClient *m_client = 0; + #endif void reset_all_stations(); void reset_all_stations_immediate(); -void push_message(int type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); +void push_message(byte type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); void manual_start_program(byte, byte); -void remote_http_callback(char*); +void httpget_callback(byte, uint16_t, uint16_t); + // Small variations have been added to the timing values below // to minimize conflicting events -#define NTP_SYNC_INTERVAL 86413L // NYP sync interval, in units of seconds -#define RTC_SYNC_INTERVAL 3607 // RTC sync interval, 3600 secs -#define CHECK_NETWORK_INTERVAL 601 // Network checking timeout, 10 minutes -#define CHECK_WEATHER_TIMEOUT 7207L // Weather check interval: 2 hours -#define CHECK_WEATHER_SUCCESS_TIMEOUT 86400L // Weather check success interval: 24 hrs -#define LCD_BACKLIGHT_TIMEOUT 15 // LCD backlight timeout: 15 secs -#define PING_TIMEOUT 200 // Ping test timeout: 200 ms - -// Define buffers: need them to be sufficiently large to cover string option reading -char ether_buffer[ETHER_BUFFER_SIZE+TMP_BUFFER_SIZE]; // ethernet buffer -char tmp_buffer[TMP_BUFFER_SIZE+MAX_SOPTS_SIZE+1]; // scratch buffer - +#define NTP_SYNC_INTERVAL 86403L // NYP sync interval, 24 hrs +#define RTC_SYNC_INTERVAL 60 // RTC sync interval, 60 secs +#define CHECK_NETWORK_INTERVAL 601 // Network checking timeout, 10 minutes +#define CHECK_WEATHER_TIMEOUT 7201 // Weather check interval: 2 hours +#define CHECK_WEATHER_SUCCESS_TIMEOUT 86433L // Weather check success interval: 24 hrs +#define LCD_BACKLIGHT_TIMEOUT 15 // LCD backlight timeout: 15 secs +#define PING_TIMEOUT 200 // Ping test timeout: 200 ms + +extern char tmp_buffer[]; // scratch buffer + +#ifdef ESP8266 +ESP8266WebServer *wifi_server = NULL; +static uint16_t led_blink_ms = LED_FAST_BLINK; +#endif // ====== Object defines ====== OpenSprinkler os; // OpenSprinkler object -ProgramData pd; // ProgramdData object +ProgramData pd; // ProgramdData object /* ====== Robert Hillman (RAH)'s implementation of flow sensor ====== * flow_begin - time when valve turns on @@ -81,299 +106,344 @@ byte prev_flow_state = HIGH; float flow_last_gpm=0; void flow_poll() { - #if defined(ESP8266) - pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 - #endif - byte curr_flow_state = digitalReadExt(PIN_SENSOR1); - if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { // only record on falling edge - prev_flow_state = curr_flow_state; - return; - } - prev_flow_state = curr_flow_state; - ulong curr = millis(); - flow_count++; - - /* RAH implementation of flow sensor */ - if (flow_start==0) { flow_gallons=0; flow_start=curr;} // if first pulse, record time - if ((curr-flow_start)<90000) { flow_gallons=0; } // wait 90 seconds before recording flow_begin - else { if (flow_gallons==1) { flow_begin = curr;}} - flow_stop = curr; // get time in ms for stop - flow_gallons++; // increment gallon count for each poll - /* End of RAH implementation of flow sensor */ + byte curr_flow_state = digitalReadExt(PIN_FLOWSENSOR); + if(os.options[OPTION_SENSOR1_TYPE]!=SENSOR_TYPE_FLOW) return; + +#ifdef ESP8266 + if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { + prev_flow_state = curr_flow_state; + return; + } + prev_flow_state = curr_flow_state; +#endif + + ulong curr = millis(); + + if(curr < os.flowcount_time_ms+10) return; // debounce threshold: 10ms + flow_count++; + os.flowcount_time_ms = curr; + + /* RAH implementation of flow sensor */ + if (flow_start==0) { flow_gallons=0; flow_start=curr;} // if first pulse, record time + if ((curr-flow_start)<90000) { flow_gallons=0; } // wait 90 seconds before recording flow_begin + else { if (flow_gallons==1) { flow_begin = curr;}} + flow_stop = curr; // get time in ms for stop + flow_gallons++; // increment gallon count for each interrupt + /* End of RAH implementation of flow sensor */ +} + +volatile byte flow_isr_flag = false; +/** Flow sensor interrupt service routine */ +#ifdef ESP8266 + +ICACHE_RAM_ATTR void flow_isr() // for ESP8266, ISR must be marked ICACHE_RAM_ATTR +#else +void flow_isr() +#endif +{ + flow_isr_flag = true; } #if defined(ARDUINO) // ====== UI defines ====== static char ui_anim_chars[3] = {'.', 'o', 'O'}; -#define UI_STATE_DEFAULT 0 -#define UI_STATE_DISP_IP 1 -#define UI_STATE_DISP_GW 2 -#define UI_STATE_RUNPROG 3 +#define UI_STATE_DEFAULT 0 +#define UI_STATE_DISP_IP 1 +#define UI_STATE_DISP_GW 2 +#define UI_STATE_RUNPROG 3 static byte ui_state = UI_STATE_DEFAULT; static byte ui_state_runprog = 0; +#ifdef ESP8266 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); - byte button; - ulong timeout = millis()+4000; - do { - button = os.button_read(BUTTON_WAIT_NONE); - if((button&BUTTON_MASK)==BUTTON_3 && (button&BUTTON_FLAG_DOWN)) return true; - if((button&BUTTON_MASK)==BUTTON_1 && (button&BUTTON_FLAG_DOWN)) return false; - delay(10); - } while(millis() < timeout); - return false; + os.lcd_print_line_clear_pgm(str, 0); + os.lcd_print_line_clear_pgm(PSTR("(B1:No, B3:Yes)"), 1); + byte button; + ulong timeout = millis()+4000; + do { + button = os.button_read(BUTTON_WAIT_NONE); + if((button&BUTTON_MASK)==BUTTON_3 && (button&BUTTON_FLAG_DOWN)) return true; + if((button&BUTTON_MASK)==BUTTON_1 && (button&BUTTON_FLAG_DOWN)) return false; + delay(10); + } while(millis() < timeout); + return false; } +#endif void ui_state_machine() { -#if defined(ESP8266) - // process screen led - static ulong led_toggle_timeout = 0; - if(led_blink_ms) { - if(millis()>led_toggle_timeout) { - os.toggle_screen_led(); - led_toggle_timeout = millis() + led_blink_ms; - } - } -#endif - - if (!os.button_timeout) { - os.lcd_set_brightness(0); - ui_state = UI_STATE_DEFAULT; // also recover to default state - } - - // read button, if something is pressed, wait till release - byte button = os.button_read(BUTTON_WAIT_HOLD); - - if (button & BUTTON_FLAG_DOWN) { // repond only to button down events - os.button_timeout = LCD_BACKLIGHT_TIMEOUT; - os.lcd_set_brightness(1); - } else { - return; - } - - switch(ui_state) { - case UI_STATE_DEFAULT: - switch (button & BUTTON_MASK) { - case BUTTON_1: - if (button & BUTTON_FLAG_HOLD) { // holding B1 - if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B1, run a short test (internal test) - if(!ui_confirm(PSTR("Start 2s test?"))) {ui_state = UI_STATE_DEFAULT; break;} - manual_start_program(255, 0); - } else if (digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B1, display gateway IP - os.lcd.clear(0, 1); - os.lcd.setCursor(0, 0); - #if defined(ESP8266) - if (!m_server) { os.lcd.print(WiFi.gatewayIP()); } - else - #endif - { os.lcd.print(Ethernet.gatewayIP()); } - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR("(gwip)")); - ui_state = UI_STATE_DISP_IP; - } 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 - os.lcd.clear(0, 1); - os.lcd.setCursor(0, 0); - #if defined(ESP8266) - if (!m_server) { os.lcd.print(WiFi.localIP()); } - else - #endif - { os.lcd.print(Ethernet.localIP()); } - 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)")); - ui_state = UI_STATE_DISP_IP; - } - break; - case BUTTON_2: - if (button & BUTTON_FLAG_HOLD) { // holding B2 - if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B2, display external IP - os.lcd_print_ip((byte*)(&os.nvdata.external_ip), 1); - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR("(eip)")); - ui_state = UI_STATE_DISP_IP; - } else if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B2, display last successful weather call - //os.lcd.clear(0, 1); - os.lcd_print_time(os.checkwt_success_lasttime); - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR("(lswc)")); - ui_state = UI_STATE_DISP_IP; - } else { // if no other button is clicked, reboot - if(!ui_confirm(PSTR("Reboot device?"))) {ui_state = UI_STATE_DEFAULT; break;} - os.reboot_dev(REBOOT_CAUSE_BUTTON); - } - } else { // clicking B2: display MAC - os.lcd.clear(0, 1); - byte mac[6]; - #if defined(ESP8266) - os.load_hardware_mac(mac, m_server!=NULL); - #else - os.load_hardware_mac(mac); - #endif - os.lcd_print_mac(mac); - ui_state = UI_STATE_DISP_GW; - } - break; - case BUTTON_3: - if (button & BUTTON_FLAG_HOLD) { // holding B3 - if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B3, display up time - os.lcd_print_time(os.powerup_lasttime); - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR("(lupt) cause:")); - os.lcd.print(os.last_reboot_cause); - ui_state = UI_STATE_DISP_IP; - } else if(digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B3, reset to AP and reboot - #if defined(ESP8266) - if(!ui_confirm(PSTR("Reset to AP?"))) {ui_state = UI_STATE_DEFAULT; break;} - os.reset_to_ap(); - #endif - } else { // if no other button is clicked, go to Run Program main menu - os.lcd_print_line_clear_pgm(PSTR("Run a Program:"), 0); - os.lcd_print_line_clear_pgm(PSTR("Click B3 to list"), 1); - ui_state = UI_STATE_RUNPROG; - } - } else { // clicking B3: switch board display (cycle through master and all extension boards) - os.status.display_board = (os.status.display_board + 1) % (os.nboards); - } - break; - } - break; - case UI_STATE_DISP_IP: - case UI_STATE_DISP_GW: - ui_state = UI_STATE_DEFAULT; - break; - case UI_STATE_RUNPROG: - if ((button & BUTTON_MASK)==BUTTON_3) { - if (button & BUTTON_FLAG_HOLD) { - // start - manual_start_program(ui_state_runprog, 0); - ui_state = UI_STATE_DEFAULT; - } else { - ui_state_runprog = (ui_state_runprog+1) % (pd.nprograms+1); - os.lcd_print_line_clear_pgm(PSTR("Hold B3 to start"), 0); - if(ui_state_runprog > 0) { - ProgramStruct prog; - pd.read(ui_state_runprog-1, &prog); - os.lcd_print_line_clear_pgm(PSTR(" "), 1); - os.lcd.setCursor(0, 1); - os.lcd.print((int)ui_state_runprog); - os.lcd_print_pgm(PSTR(". ")); - os.lcd.print(prog.name); - } else { - os.lcd_print_line_clear_pgm(PSTR("0. Test (1 min)"), 1); - } - } +#ifdef ESP8266 + // process screen led + static ulong led_toggle_timeout = 0; + if(led_blink_ms) { + if(millis()>led_toggle_timeout) { + os.toggle_screen_led(); + led_toggle_timeout = millis() + led_blink_ms; + } + } +#endif + + if (!os.button_timeout) { + os.lcd_set_brightness(0); + ui_state = UI_STATE_DEFAULT; // also recover to default state + } + + // read button, if something is pressed, wait till release + byte button = os.button_read(BUTTON_WAIT_HOLD); + + if (button & BUTTON_FLAG_DOWN) { // repond only to button down events + os.button_timeout = LCD_BACKLIGHT_TIMEOUT; + os.lcd_set_brightness(1); + } else { + return; + } + + DEBUG_PRINT("UI_STATE="); + DEBUG_PRINTLN(ui_state); + + switch(ui_state) { + case UI_STATE_DEFAULT: + switch (button & BUTTON_MASK) { + case BUTTON_1: + DEBUG_PRINTLN("BUTTON 1"); + if (button & BUTTON_FLAG_HOLD) { // holding B1 + if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B1, run a short test (internal test) + #ifdef ESP8266 + if(!ui_confirm(PSTR("Start 2s test?"))) {ui_state = UI_STATE_DEFAULT; break;} + #endif + manual_start_program(255, 0); + } else if (digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B1, display gateway IP + #ifdef ESP8266 + os.lcd.clear(0, 1); + os.lcd.setCursor(0, 0); + #ifdef ESP8266_ETHERNET + if (m_server) + os.lcd.print(ether.gatewayIP()); + else + #endif + os.lcd.print(WiFi.gatewayIP()); + #else + os.lcd.clear(); + os.lcd_print_ip(ether.gwip, 0); + #endif + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR("(gwip)")); + ui_state = UI_STATE_DISP_IP; + } else { // if no other button is clicked, stop all zones + #ifdef ESP8266 + if(!ui_confirm(PSTR("Stop all zones?"))) {ui_state = UI_STATE_DEFAULT; break;} + #endif + reset_all_stations(); + } + } else { // clicking B1: display device IP and port + #ifdef ESP8266 + os.lcd.clear(0, 1); + os.lcd.setCursor(0, 0); + #ifdef ESP8266_ETHERNET + if (m_server) + os.lcd.print(ether.localIP()); + else + #endif + os.lcd.print(WiFi.localIP()); + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR(":")); + uint16_t httpport = (uint16_t)(os.options[OPTION_HTTPPORT_1]<<8) + (uint16_t)os.options[OPTION_HTTPPORT_0]; + os.lcd.print(httpport); + #else + os.lcd.clear(); + os.lcd_print_ip(ether.myip, 0); + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR(":")); + os.lcd.print(ether.hisport); + #endif + os.lcd_print_pgm(PSTR(" (ip:port)")); + ui_state = UI_STATE_DISP_IP; + } + break; + case BUTTON_2: + DEBUG_PRINTLN("BUTTON 2"); + if (button & BUTTON_FLAG_HOLD) { // holding B2 + if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B2, display external IP + os.lcd_print_ip((byte*)(&os.nvdata.external_ip), 1); + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR("(eip)")); + ui_state = UI_STATE_DISP_IP; + } else if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B2, display last successful weather call + //os.lcd.clear(0, 1); + os.lcd_print_time(os.checkwt_success_lasttime); + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR("(lswc)")); + ui_state = UI_STATE_DISP_IP; + } else { // if no other button is clicked, reboot + #ifdef ESP8266 + if(!ui_confirm(PSTR("Reboot device?"))) {ui_state = UI_STATE_DEFAULT; break;} + #endif + os.reboot_dev(); + } + } else { // clicking B2: display MAC + #ifdef ESP8266 + os.lcd.clear(0, 1); + byte mac[6]; + #ifdef ESP8266_ETHERNET + if (m_server) { + os.get_hardware_mac(); + memcpy(mac, tmp_buffer, 6); } - break; - } + else + #endif + WiFi.macAddress(mac); + os.lcd_print_mac(mac); + #else + os.lcd.clear(); + os.lcd_print_mac(ether.mymac); + #endif + ui_state = UI_STATE_DISP_GW; + } + break; + case BUTTON_3: + DEBUG_PRINTLN("BUTTON 3"); + if (button & BUTTON_FLAG_HOLD) { // holding B3 + if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B3, display up time + os.lcd_print_time(os.powerup_lasttime); + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR("(lupt)")); + ui_state = UI_STATE_DISP_IP; + } else if(digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B3, reset to AP and reboot + #ifdef ESP8266 + if(!ui_confirm(PSTR("Reset to AP?"))) {ui_state = UI_STATE_DEFAULT; break;} + os.reset_to_ap(); + #endif + } else { // if no other button is clicked, go to Run Program main menu + os.lcd_print_line_clear_pgm(PSTR("Run a Program:"), 0); + os.lcd_print_line_clear_pgm(PSTR("Click B3 to list"), 1); + ui_state = UI_STATE_RUNPROG; + } + } else { // clicking B3: switch board display (cycle through master and all extension boards) + os.status.display_board = (os.status.display_board + 1) % (os.nboards); + } + break; + } + break; + case UI_STATE_DISP_IP: + case UI_STATE_DISP_GW: + ui_state = UI_STATE_DEFAULT; + break; + case UI_STATE_RUNPROG: + if ((button & BUTTON_MASK)==BUTTON_3) { + if (button & BUTTON_FLAG_HOLD) { + // start + manual_start_program(ui_state_runprog, 0); + ui_state = UI_STATE_DEFAULT; + } else { + ui_state_runprog = (ui_state_runprog+1) % (pd.nprograms+1); + os.lcd_print_line_clear_pgm(PSTR("Hold B3 to start"), 0); + if(ui_state_runprog > 0) { + ProgramStruct prog; + pd.read(ui_state_runprog-1, &prog); + os.lcd_print_line_clear_pgm(PSTR(" "), 1); + os.lcd.setCursor(0, 1); + os.lcd.print((int)ui_state_runprog); + os.lcd_print_pgm(PSTR(". ")); + os.lcd.print(prog.name); + } else { + os.lcd_print_line_clear_pgm(PSTR("0. Test (1 min)"), 1); + } + } + } + break; + } } // ====================== // Setup Function // ====================== void do_setup() { - /* Clear WDT reset flag. */ -#if defined(ESP8266) - if(wifi_server) { delete wifi_server; wifi_server = NULL; } - WiFi.persistent(false); - led_blink_ms = LED_FAST_BLINK; + /* Clear WDT reset flag. */ +#ifdef ESP8266 + if(wifi_server) { delete wifi_server; wifi_server = NULL; } + WiFi.persistent(false); + led_blink_ms = LED_FAST_BLINK; #else - MCUSR &= ~(1< 15) { - // reset after 120 seconds of timeout - sysReset(); - } + wdt_timeout += 1; + // this isr is called every 8 seconds + if (wdt_timeout > 15) { + // reset after 120 seconds of timeout + sysReset(); + } } #endif #else void do_setup() { - initialiseEpoch(); // initialize time reference for millis() and micros() - os.begin(); // OpenSprinkler init - os.options_setup(); // Setup options - - pd.init(); // ProgramData init - - if (os.start_network()) { // initialize network - DEBUG_PRINTLN("network established."); - os.status.network_fails = 0; - } else { - DEBUG_PRINTLN("network failed."); - os.status.network_fails = 1; - } - os.status.req_network = 0; - - os.mqtt.init(); - os.status.req_mqtt_restart = true; + initialiseEpoch(); // initialize time reference for millis() and micros() + os.begin(); // OpenSprinkler init + os.options_setup(); // Setup options + + pd.init(); // ProgramData init + + if (os.start_network()) { // initialize network + DEBUG_PRINTLN("network established."); + os.status.network_fails = 0; + } else { + DEBUG_PRINTLN("network failed."); + os.status.network_fails = 1; + } + os.status.req_network = 0; } #endif void write_log(byte type, ulong curr_time); void schedule_all_stations(ulong curr_time); -void turn_on_station(byte sid); void turn_off_station(byte sid, ulong curr_time); void process_dynamic_events(ulong curr_time); void check_network(); @@ -381,676 +451,614 @@ void check_weather(); void perform_ntp_sync(); void delete_log(char *name); -#if defined(ESP8266) +#ifdef ESP8266 void start_server_ap(); void start_server_client(); unsigned long reboot_timer = 0; +#ifdef ESP8266_ETHERNET +void handle_web_request(char *p); #endif - +#else void handle_web_request(char *p); +#endif /** Main Loop */ void do_loop() { - // handle flow sensor using polling every 1ms (maximum freq 1/(2*1ms)=500Hz) - static ulong flowpoll_timeout=0; - if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - ulong curr = millis(); - if(curr!=flowpoll_timeout) { - flowpoll_timeout = curr; - flow_poll(); - } - } - - static ulong last_time = 0; - static ulong last_minute = 0; - - byte bid, sid, s, pid, qid, bitvalue; - ProgramStruct prog; - - os.status.mas = os.iopts[IOPT_MASTER_STATION]; - os.status.mas2= os.iopts[IOPT_MASTER_STATION_2]; - time_t curr_time = os.now_tz(); - - // ====== Process Ethernet packets ====== -#if defined(ARDUINO) // Process Ethernet packets for Arduino - #if defined(ESP8266) - static ulong connecting_timeout; - if (m_server) { // if wired Ethernet - led_blink_ms = 0; - Ethernet.maintain(); // todo: is this necessary? - EthernetClient client = m_server->available(); + /* If flow_isr_flag is on, do flow sensing. + todo: not the most efficient way, as we can't do I2C inside ISR. + need to figure out a more efficient way to do flow sensing */ + if(flow_isr_flag) { + flow_isr_flag = false; + flow_poll(); + } + + static ulong last_time = 0; + static ulong last_minute = 0; + + byte bid, sid, s, pid, qid, bitvalue; + ProgramStruct prog; + + os.status.mas = os.options[OPTION_MASTER_STATION]; + os.status.mas2= os.options[OPTION_MASTER_STATION_2]; + time_t curr_time = os.now_tz(); + // ====== Process Ethernet packets ====== +#if defined(ARDUINO) // Process Ethernet packets for Arduino + #ifdef ESP8266 + static ulong connecting_timeout; + switch(os.state) { + case OS_STATE_INITIAL: + if(os.get_wifi_mode()==WIFI_MODE_AP) { + start_server_ap(); + os.state = OS_STATE_CONNECTED; + connecting_timeout = 0; + } else { + led_blink_ms = LED_SLOW_BLINK; + start_network_sta(os.wifi_config.ssid.c_str(), os.wifi_config.pass.c_str()); + os.config_ip(); + os.state = OS_STATE_CONNECTING; + connecting_timeout = millis() + 120000L; + os.lcd.setCursor(0, -1); + os.lcd.print(F("Connecting to...")); + os.lcd.setCursor(0, 2); + os.lcd.print(os.wifi_config.ssid); + } + break; + + case OS_STATE_TRY_CONNECT: + led_blink_ms = LED_SLOW_BLINK; + start_network_sta_with_ap(os.wifi_config.ssid.c_str(), os.wifi_config.pass.c_str()); + os.config_ip(); + os.state = OS_STATE_CONNECTED; + break; + + case OS_STATE_CONNECTING: + if(WiFi.status() == WL_CONNECTED) { + led_blink_ms = 0; + os.set_screen_led(LOW); + os.lcd.clear(); + start_server_client(); + os.state = OS_STATE_CONNECTED; + connecting_timeout = 0; + } else { + if(millis()>connecting_timeout) { + os.state = OS_STATE_INITIAL; + DEBUG_PRINTLN(F("timeout")); + } + } + break; + + case OS_STATE_CONNECTED: + if(os.get_wifi_mode() == WIFI_MODE_AP) { + wifi_server->handleClient(); + connecting_timeout = 0; + if(os.get_wifi_mode()==WIFI_MODE_STA) { + // already in STA mode, waiting to reboot + break; + } + if(WiFi.status()==WL_CONNECTED && WiFi.localIP()) { + os.wifi_config.mode = WIFI_MODE_STA; + os.options_save(true); + os.reboot_dev(); + } + } + else { + if(WiFi.status() == WL_CONNECTED) { + wifi_server->handleClient(); + connecting_timeout = 0; + } else { + os.state = OS_STATE_INITIAL; + } + } + break; + } + #if defined(ESP8266_ETHERNET) + if (m_server) { + ether.maintain(); + UIPClient client = m_server->available(); if (client) { - while (true) { - int len = client.read((uint8_t*) ether_buffer, ETHER_BUFFER_SIZE); - if (len <= 0) { - if(!client.connected()) { - break; - } else { - continue; - } - - } else { - m_client = &client; - ether_buffer[len] = 0; // put a zero at the end of the packet - handle_web_request(ether_buffer); - m_client= 0; - break; - } - } - } - } else { - switch(os.state) { - case OS_STATE_INITIAL: - if(os.get_wifi_mode()==WIFI_MODE_AP) { - start_server_ap(); - os.state = OS_STATE_CONNECTED; - connecting_timeout = 0; - } else { - led_blink_ms = LED_SLOW_BLINK; - start_network_sta(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); - os.config_ip(); - os.state = OS_STATE_CONNECTING; - connecting_timeout = millis() + 120000L; - os.lcd.setCursor(0, -1); - os.lcd.print(F("Connecting to...")); - os.lcd.setCursor(0, 2); - os.lcd.print(os.wifi_ssid); - } - break; - - case OS_STATE_TRY_CONNECT: - led_blink_ms = LED_SLOW_BLINK; - start_network_sta_with_ap(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); - os.config_ip(); - os.state = OS_STATE_CONNECTED; - break; - - case OS_STATE_CONNECTING: - if(WiFi.status() == WL_CONNECTED) { - led_blink_ms = 0; - os.set_screen_led(LOW); - os.lcd.clear(); - os.save_wifi_ip(); - start_server_client(); - os.state = OS_STATE_CONNECTED; - connecting_timeout = 0; - } else { - if(millis()>connecting_timeout) { - os.state = OS_STATE_INITIAL; - DEBUG_PRINTLN(F("timeout")); - } - } - break; - - case OS_STATE_CONNECTED: - if(os.get_wifi_mode() == WIFI_MODE_AP) { - wifi_server->handleClient(); - connecting_timeout = 0; - if(os.get_wifi_mode()==WIFI_MODE_STA) { - // already in STA mode, waiting to reboot - break; - } - if(WiFi.status()==WL_CONNECTED && WiFi.localIP()) { - os.iopts[IOPT_WIFI_MODE] = WIFI_MODE_STA; - os.iopts_save(); - os.reboot_dev(REBOOT_CAUSE_WIFIDONE); - } - } - else { - if(WiFi.status() == WL_CONNECTED) { - wifi_server->handleClient(); - connecting_timeout = 0; - } else { - DEBUG_PRINTLN(F("WiFi disconnected, going back to initial")); - os.state = OS_STATE_INITIAL; - } - } - break; - } - } - - #else // AVR - - EthernetClient client = m_server->available(); - if (client) { - while(true) { - int len = client.read((uint8_t*) ether_buffer, ETHER_BUFFER_SIZE); - if (len <=0) { - if(!client.connected()) { - break; - } else { - continue; - } - } else { - m_client = &client; - ether_buffer[len] = 0; // put a zero at the end of the packet - handle_web_request(ether_buffer); - m_client = NULL; - break; - } + while (true) { + int len = client.read((uint8_t*) ether_buffer, ETHER_BUFFER_SIZE); + if (len <= 0) { + if(!client.connected()) { + break; + } else { + continue; + } + + } else { + m_client = &client; + ether_buffer[len] = 0; // put a zero at the end of the packet + handle_web_request(ether_buffer); + m_client= 0; + break; + } + } } } - - Ethernet.maintain(); - - wdt_reset(); // reset watchdog timer - wdt_timeout = 0; - #endif - - ui_state_machine(); + #endif + + #else // AVR + + uint16_t pos=ether.packetLoop(ether.packetReceive()); + if (pos>0) { // packet received + handle_web_request((char*)Ethernet::buffer+pos); + } + wdt_reset(); // reset watchdog timer + wdt_timeout = 0; + #endif + + ui_state_machine(); #else // Process Ethernet packets for RPI/BBB - EthernetClient client = m_server->available(); - if (client) { - while(true) { - int len = client.read((uint8_t*) ether_buffer, ETHER_BUFFER_SIZE); - if (len <=0) { - if(!client.connected()) { - break; - } else { - continue; - } - } else { - m_client = &client; - ether_buffer[len] = 0; // put a zero at the end of the packet - handle_web_request(ether_buffer); - m_client = 0; - break; - } - } - } -#endif // Process Ethernet packets - - // Start up MQTT when we have a network connection - if (os.status.req_mqtt_restart && os.network_connected()) { - os.mqtt.begin(); - os.status.req_mqtt_restart = false; - } - os.mqtt.loop(); - - // The main control loop runs once every second - if (curr_time != last_time) { -#if defined(ENABLE_DEBUG) - /* - #if defined(ESP8266) - { - static uint16_t lastHeap = 0; - static uint32_t lastHeapTime = 0; - uint16_t heap = ESP.getFreeHeap(); - if(heap != lastHeap) { - DEBUG_PRINT(F("Heap:")); - DEBUG_PRINT(heap); - DEBUG_PRINT("|"); - DEBUG_PRINTLN(curr_time - lastHeapTime); - lastHeap = heap; - lastHeapTime = curr_time; - } - } - #elif defined(ARDUINO) - { - extern unsigned int __bss_end; - extern unsigned int __heap_start; - extern void *__brkval; - static int last_free_memory = 0; - int free_memory; - - if((int)__brkval == 0) - free_memory = ((int)&free_memory) - ((int)&__bss_end); - else - free_memory = ((int)&free_memory) - ((int)__brkval); - if(free_memory != last_free_memory) { - DEBUG_PRINT(F("Heap:")); - DEBUG_PRINT(free_memory); - DEBUG_PRINT("|"); - last_free_memory = free_memory; - } - } - #endif - */ -#endif - - #if defined(ESP8266) - pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 - pinModeExt(PIN_SENSOR2, INPUT_PULLUP); - #endif - - last_time = curr_time; - if (os.button_timeout) os.button_timeout--; - - #if defined(ESP8266) - if(reboot_timer && millis() > reboot_timer) { - os.reboot_dev(REBOOT_CAUSE_TIMER); - } - #endif - + EthernetClient client = m_server->available(); + if (client) { + while(true) { + int len = client.read((uint8_t*) ether_buffer, ETHER_BUFFER_SIZE); + if (len <=0) { + if(!client.connected()) { + break; + } else { + continue; + } + } else { + m_client = &client; + ether_buffer[len] = 0; // put a zero at the end of the packet + handle_web_request(ether_buffer); + m_client = 0; + break; + } + } + } +#endif // Process Ethernet packets + + // The main control loop runs once every second + if (curr_time != last_time) { + last_time = curr_time; + if (os.button_timeout) os.button_timeout--; + + #ifdef ESP8266 + if(reboot_timer && millis() > reboot_timer) { + os.reboot_dev(); + } + #endif + #if defined(ARDUINO) - if (!ui_state) - os.lcd_print_time(os.now_tz()); // print time + if (!ui_state) + os.lcd_print_time(os.now_tz()); // print time #endif - // ====== Check raindelay status ====== - if (os.status.rain_delayed) { - if (curr_time >= os.nvdata.rd_stop_time) { // rain delay is over - os.raindelay_stop(); - } - } else { - if (os.nvdata.rd_stop_time > curr_time) { // rain delay starts now - os.raindelay_start(); - } - } - - // ====== Check controller status changes and write log ====== - if (os.old_status.rain_delayed != os.status.rain_delayed) { - if (os.status.rain_delayed) { - // rain delay started, record time - os.raindelay_on_lasttime = curr_time; - push_message(NOTIFY_RAINDELAY, LOGDATA_RAINDELAY, 1); - - } else { - // rain delay stopped, write log - write_log(LOGDATA_RAINDELAY, curr_time); - push_message(NOTIFY_RAINDELAY, LOGDATA_RAINDELAY, 0); - } - os.old_status.rain_delayed = os.status.rain_delayed; - } - - // ====== Check binary (i.e. rain or soil) sensor status ====== - os.detect_binarysensor_status(curr_time); - - if(os.old_status.sensor1_active != os.status.sensor1_active) { - // send notification when sensor1 becomes active - if(os.status.sensor1_active) { - os.sensor1_active_lasttime = curr_time; - push_message(NOTIFY_SENSOR1, LOGDATA_SENSOR1, 1); - } else { - write_log(LOGDATA_SENSOR1, curr_time); - push_message(NOTIFY_SENSOR1, LOGDATA_SENSOR1, 0); - } - } - os.old_status.sensor1_active = os.status.sensor1_active; - - if(os.old_status.sensor2_active != os.status.sensor2_active) { - // send notification when sensor1 becomes active - if(os.status.sensor2_active) { - os.sensor2_active_lasttime = curr_time; - push_message(NOTIFY_SENSOR2, LOGDATA_SENSOR2, 1); - } else { - write_log(LOGDATA_SENSOR2, curr_time); - push_message(NOTIFY_SENSOR2, LOGDATA_SENSOR2, 0); - } - } - os.old_status.sensor2_active = os.status.sensor2_active; - - // ===== Check program switch status ===== - byte pswitch = os.detect_programswitch_status(curr_time); - if(pswitch > 0) { - reset_all_stations_immediate(); // immediately stop all stations - } - if (pswitch & 0x01) { - if(pd.nprograms > 0) manual_start_program(1, 0); - } - if (pswitch & 0x02) { - if(pd.nprograms > 1) manual_start_program(2, 0); - } - - - // ====== Schedule program data ====== - ulong curr_minute = curr_time / 60; - boolean match_found = false; - RuntimeQueueStruct *q; - // since the granularity of start time is minute - // we only need to check once every minute - if (curr_minute != last_minute) { - last_minute = curr_minute; - // check through all programs - for(pid=0; pid>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)) - continue; - - // if station has non-zero water time and the station is not disabled - if (prog.durations[sid] && !(os.attrib_dis[bid]&(1<st = 0; - q->dur = water_time; - q->sid = sid; - q->pid = pid+1; - match_found = true; - } else { - // queue is full - } - }// if water_time - }// if prog.durations[sid] - }// for sid - if(match_found) push_message(NOTIFY_PROGRAM_SCHED, pid, prog.use_weather?os.iopts[IOPT_WATER_PERCENTAGE]:100); - }// if check_match - }// for pid - - // calculate start and end time - if (match_found) { - schedule_all_stations(curr_time); - - // For debugging: print out queued elements - /*DEBUG_PRINT("en:"); - for(q=pd.queue;qsid); - DEBUG_PRINT(","); - DEBUG_PRINT(q->dur); - DEBUG_PRINT(","); - DEBUG_PRINT(q->st); - DEBUG_PRINT("]"); - } - DEBUG_PRINTLN("");*/ - } - }//if_check_current_minute - - // ====== Run program data ====== - // Check if a program is running currently - // If so, do station run-time keeping - if (os.status.program_busy){ - // first, go through run time queue to assign queue elements to stations - q = pd.queue; - qid=0; - for(;qsid; - byte sqi=pd.station_qid[sid]; - // skip if station is already assigned a queue element - // and that queue element has an earlier start time - if(sqi<255 && pd.queue[sqi].stst) continue; - // otherwise assign the queue element to station - pd.station_qid[sid]=qid; - } - // next, go through the stations and perform time keeping - for(bid=0;bidst > 0) { - // if so, check if we should turn it off - if (curr_time >= q->st+q->dur) { - turn_off_station(sid, curr_time); - } - } - // if current station is not running, check if we should turn it on - if(!((bitvalue>>s)&1)) { - if (curr_time >= q->st && curr_time < q->st+q->dur) { - turn_on_station(sid); - } //if curr_time > scheduled_start_time - } // if current station is not running - }//end_s - }//end_bid - - // finally, go through the queue again and clear up elements marked for removal - int qi; - for(qi=pd.nqueue-1;qi>=0;qi--) { - q=pd.queue+qi; - if(!q->dur || curr_time>=q->st+q->dur) { - pd.dequeue(qi); - } - } - - // process dynamic events - process_dynamic_events(curr_time); - - // activate / deactivate valves - os.apply_all_station_bits(); - - // check through runtime queue, calculate the last stop time of sequential stations - pd.last_seq_stop_time = 0; - ulong sst; - byte re=os.iopts[IOPT_REMOTE_EXT_MODE]; - q = pd.queue; - for(;qsid; - bid = sid>>3; - s = sid&0x07; - // check if any sequential station has a valid stop time - // and the stop time must be larger than curr_time - sst = q->st + q->dur; - if (sst>curr_time) { - // only need to update last_seq_stop_time for sequential stations - if (os.attrib_seq[bid]&(1<pd.last_seq_stop_time ) ? sst : pd.last_seq_stop_time; - } - } - } - - // if the runtime queue is empty - // reset all stations - if (!pd.nqueue) { - // turn off all stations - os.clear_all_station_bits(); - os.apply_all_station_bits(); - // reset runtime - pd.reset_runtime(); - // reset program busy bit - os.status.program_busy = 0; - // log flow sensor reading if flow sensor is used - if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - write_log(LOGDATA_FLOWSENSE, curr_time); - push_message(NOTIFY_FLOWSENSOR, (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0); - } - - // 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 - } - }//if_some_program_is_running - - // handle master - if (os.status.mas>0) { - int16_t mas_on_adj = water_time_decode_signed(os.iopts[IOPT_MASTER_ON_ADJ]); - int16_t mas_off_adj= water_time_decode_signed(os.iopts[IOPT_MASTER_OFF_ADJ]); - byte masbit = 0; - - for(sid=0;sid>3; - s = sid&0x07; - // if this station is running and is set to activate master - if ((os.station_bits[bid]&(1<= q->st + mas_on_adj && - curr_time <= q->st + q->dur + mas_off_adj) { - masbit = 1; - break; - } - } - } - os.set_station_bit(os.status.mas-1, masbit); - } - // handle master2 - if (os.status.mas2>0) { - int16_t mas_on_adj_2 = water_time_decode_signed(os.iopts[IOPT_MASTER_ON_ADJ_2]); - int16_t mas_off_adj_2= water_time_decode_signed(os.iopts[IOPT_MASTER_OFF_ADJ_2]); - byte masbit2 = 0; - for(sid=0;sid>3; - s = sid&0x07; - // if this station is running and is set to activate master - if ((os.station_bits[bid]&(1<= q->st + mas_on_adj_2 && - curr_time <= q->st + q->dur + mas_off_adj_2) { - masbit2 = 1; - break; - } - } - } - os.set_station_bit(os.status.mas2-1, masbit2); - } - - // process dynamic events - process_dynamic_events(curr_time); - - // activate/deactivate valves - os.apply_all_station_bits(); + // ====== Check raindelay status ====== + if (os.status.rain_delayed) { + if (curr_time >= os.nvdata.rd_stop_time) { // rain delay is over + os.raindelay_stop(); + } + } else { + if (os.nvdata.rd_stop_time > curr_time) { // rain delay starts now + os.raindelay_start(); + } + } + + // ====== Check controller status changes and write log ====== + if (os.old_status.rain_delayed != os.status.rain_delayed) { + if (os.status.rain_delayed) { + // rain delay started, record time + os.raindelay_start_time = curr_time; + push_message(IFTTT_RAINSENSOR, LOGDATA_RAINDELAY, 1); + } else { + // rain delay stopped, write log + write_log(LOGDATA_RAINDELAY, curr_time); + push_message(IFTTT_RAINSENSOR, LOGDATA_RAINDELAY, 0); + } + os.old_status.rain_delayed = os.status.rain_delayed; + } + + // ====== Check rain sensor status ====== +#ifdef ESP8266 + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN + || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected +#else + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected +#endif + os.rainsensor_status(); + if (os.old_status.rain_sensed != os.status.rain_sensed) { + if (os.status.rain_sensed) { + // rain sensor on, record time + os.sensor_lasttime = curr_time; + push_message(IFTTT_RAINSENSOR, LOGDATA_RAINSENSE, 1); + } else { + // rain sensor off, write log + if (curr_time>os.sensor_lasttime+10) { // add a 10 second threshold + // to avoid faulty rain sensors generating + // too many log records + write_log(LOGDATA_RAINSENSE, curr_time); + push_message(IFTTT_RAINSENSOR, LOGDATA_RAINSENSE, 0); + } + } + os.old_status.rain_sensed = os.status.rain_sensed; + } + } + + // ====== Check soil moisture status ====== +#ifdef ESP8266 + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN + || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected +#else + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected +#endif + os.soil_moisture_sensor_status(); + if (os.old_status.soil_moisture_sensed != os.status.soil_moisture_sensed) { + if (os.status.soil_moisture_sensed) { + os.soil_moisture_sensed_time = curr_time + 120*60*60; //Delay 120min todo: add to config + push_message(IFTTT_SOILSENSOR, LOGDATA_SOILSENSE, 1); + } else { + os.soil_moisture_sensed_time = 0; + write_log(LOGDATA_SOILSENSE, curr_time); + push_message(IFTTT_SOILSENSOR, LOGDATA_SOILSENSE, 0); + } + os.old_status.soil_moisture_sensed = os.status.soil_moisture_sensed; + } + + // Delayed set of os.status.soil_moisture_active, so it's not firing during watering + if (os.status.soil_moisture_sensed && os.soil_moisture_sensed_time && curr_time>os.soil_moisture_sensed_time) { + os.status.soil_moisture_active = true; + } else { + os.status.soil_moisture_active = false; + } + } + + + // ===== Check program switch status ===== + if (os.programswitch_status(curr_time)) { + reset_all_stations_immediate(); // immediately stop all stations + if(pd.nprograms > 0) manual_start_program(1, 0); + } + + // ====== Schedule program data ====== + ulong curr_minute = curr_time / 60; + boolean match_found = false; + RuntimeQueueStruct *q; + // since the granularity of start time is minute + // we only need to check once every minute + if (curr_minute != last_minute) { + last_minute = curr_minute; + // check through all programs + for(pid=0; pid>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)) + continue; + + // if station has non-zero water time and the station is not disabled + if (prog.durations[sid] && !(os.station_attrib_bits_read(ADDR_NVM_STNDISABLE+bid)&(1<st = 0; + q->dur = water_time; + q->sid = sid; + q->pid = pid+1; + match_found = true; + } else { + // queue is full + } + }// if water_time + }// if prog.durations[sid] + }// for sid + if(match_found) push_message(IFTTT_PROGRAM_SCHED, pid, prog.use_weather?os.options[OPTION_WATER_PERCENTAGE]:100); + }// if check_match + }// for pid + + // calculate start and end time + if (match_found) { + schedule_all_stations(curr_time); + + // For debugging: print out queued elements + /*DEBUG_PRINT("en:"); + for(q=pd.queue;qsid); + DEBUG_PRINT(","); + DEBUG_PRINT(q->dur); + DEBUG_PRINT(","); + DEBUG_PRINT(q->st); + DEBUG_PRINT("]"); + } + DEBUG_PRINTLN("");*/ + } + }//if_check_current_minute + + // ====== Run program data ====== + // Check if a program is running currently + // If so, do station run-time keeping + if (os.status.program_busy){ + // first, go through run time queue to assign queue elements to stations + q = pd.queue; + qid=0; + for(;qsid; + byte sqi=pd.station_qid[sid]; + // skip if station is already assigned a queue element + // and that queue element has an earlier start time + if(sqi<255 && pd.queue[sqi].stst) continue; + // otherwise assign the queue element to station + pd.station_qid[sid]=qid; + } + // next, go through the stations and perform time keeping + for(bid=0;bidst > 0) { + // if so, check if we should turn it off + if (curr_time >= q->st+q->dur) { + turn_off_station(sid, curr_time); + } + } + // if current station is not running, check if we should turn it on + if(!((bitvalue>>s)&1)) { + if (curr_time >= q->st && curr_time < q->st+q->dur) { + + //turn_on_station(sid); + os.set_station_bit(sid, 1); + + // RAH implementation of flow sensor + flow_start=0; + + } //if curr_time > scheduled_start_time + } // if current station is not running + }//end_s + }//end_bid + + // finally, go through the queue again and clear up elements marked for removal + int qi; + for(qi=pd.nqueue-1;qi>=0;qi--) { + q=pd.queue+qi; + if(!q->dur || curr_time>=q->st+q->dur) { + pd.dequeue(qi); + } + } + + // process dynamic events + process_dynamic_events(curr_time); + + // activate / deactivate valves + os.apply_all_station_bits(); + + // check through runtime queue, calculate the last stop time of sequential stations + pd.last_seq_stop_time = 0; + ulong sst; + byte re=os.options[OPTION_REMOTE_EXT_MODE]; + q = pd.queue; + for(;qsid; + bid = sid>>3; + s = sid&0x07; + // check if any sequential station has a valid stop time + // and the stop time must be larger than curr_time + sst = q->st + q->dur; + if (sst>curr_time) { + // only need to update last_seq_stop_time for sequential stations + if (os.station_attrib_bits_read(ADDR_NVM_STNSEQ+bid)&(1<pd.last_seq_stop_time ) ? sst : pd.last_seq_stop_time; + } + } + } + + // if the runtime queue is empty + // reset all stations + if (!pd.nqueue) { + // turn off all stations + os.clear_all_station_bits(); + os.apply_all_station_bits(); + // reset runtime + pd.reset_runtime(); + // reset program busy bit + os.status.program_busy = 0; + // log flow sensor reading if flow sensor is used + if(os.options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + write_log(LOGDATA_FLOWSENSE, curr_time); + push_message(IFTTT_FLOWSENSOR, (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0); + } + + // in case some options have changed while executing the program + os.status.mas = os.options[OPTION_MASTER_STATION]; // update master station + os.status.mas2= os.options[OPTION_MASTER_STATION_2]; // update master2 station + } + }//if_some_program_is_running + + // handle master + if (os.status.mas>0) { + int16_t mas_on_adj = water_time_decode_signed(os.options[OPTION_MASTER_ON_ADJ]); + int16_t mas_off_adj= water_time_decode_signed(os.options[OPTION_MASTER_OFF_ADJ]); + byte masbit = 0; + os.station_attrib_bits_load(ADDR_NVM_MAS_OP, (byte*)tmp_buffer); // tmp_buffer now stores masop_bits + for(sid=0;sid>3; + s = sid&0x07; + // if this station is running and is set to activate master + if ((os.station_bits[bid]&(1<= q->st + mas_on_adj && + curr_time <= q->st + q->dur + mas_off_adj) { + masbit = 1; + break; + } + } + } + os.set_station_bit(os.status.mas-1, masbit); + } + // handle master2 + if (os.status.mas2>0) { + int16_t mas_on_adj_2 = water_time_decode_signed(os.options[OPTION_MASTER_ON_ADJ_2]); + int16_t mas_off_adj_2= water_time_decode_signed(os.options[OPTION_MASTER_OFF_ADJ_2]); + byte masbit2 = 0; + os.station_attrib_bits_load(ADDR_NVM_MAS_OP_2, (byte*)tmp_buffer); // tmp_buffer now stores masop2_bits + for(sid=0;sid>3; + s = sid&0x07; + // if this station is running and is set to activate master + if ((os.station_bits[bid]&(1<= q->st + mas_on_adj_2 && + curr_time <= q->st + q->dur + mas_off_adj_2) { + masbit2 = 1; + break; + } + } + } + os.set_station_bit(os.status.mas2-1, masbit2); + } + + // process dynamic events + process_dynamic_events(curr_time); + + // activate/deactivate valves + os.apply_all_station_bits(); #if defined(ARDUINO) - // process LCD display - if (!ui_state) { - os.lcd_print_station(1, ui_anim_chars[(unsigned long)curr_time%3]); - #if defined(ESP8266) - if(os.get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP()) { - os.lcd.setCursor(0, 2); - os.lcd.clear(2, 2); - if(os.status.program_busy) { - os.lcd.print(F("curr: ")); - uint16_t curr = os.read_current(); - os.lcd.print(curr); - os.lcd.print(F(" mA")); - } - } - #endif - } - - // check safe_reboot condition - if (os.status.safe_reboot) { - // if no program is running at the moment - if (!os.status.program_busy) { - // and if no program is scheduled to run in the next minute - bool willrun = false; - for(pid=0; pid flowcount_rt_start) ? flow_count - flowcount_rt_start: 0; - flowcount_rt_start = flow_count; - } - } - - // perform ntp sync - // instead of using curr_time, which may change due to NTP sync itself - // we use Arduino's millis() method - //if (curr_time % NTP_SYNC_INTERVAL == 0) os.status.req_ntpsync = 1; - if((millis()/1000) % NTP_SYNC_INTERVAL==0) os.status.req_ntpsync = 1; - perform_ntp_sync(); - - // check network connection - if (curr_time && (curr_time % CHECK_NETWORK_INTERVAL==0)) os.status.req_network = 1; - check_network(); - - // check weather - check_weather(); - - byte wuf = os.weather_update_flag; - if(wuf) { - if((wuf&WEATHER_UPDATE_EIP) | (wuf&WEATHER_UPDATE_WL)) { - // at the moment, we only send notification if water level or external IP changed - // the other changes, such as sunrise, sunset changes are ignored for notification - push_message(NOTIFY_WEATHER_UPDATE, (wuf&WEATHER_UPDATE_EIP)?os.nvdata.external_ip:0, - (wuf&WEATHER_UPDATE_WL)?os.iopts[IOPT_WATER_PERCENTAGE]:-1); - } - os.weather_update_flag = 0; - } - static byte reboot_notification = 1; - if(reboot_notification) { - reboot_notification = 0; - push_message(NOTIFY_REBOOT); - } - - } - - #if !defined(ARDUINO) - delay(1); // For OSPI/OSBO/LINUX, sleep 1 ms to minimize CPU usage - #endif + // real-time flow count + static ulong flowcount_rt_start = 0; + if (os.options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + if (curr_time % FLOWCOUNT_RT_WINDOW == 0) { + os.flowcount_rt = (flow_count > flowcount_rt_start) ? flow_count - flowcount_rt_start: 0; + flowcount_rt_start = flow_count; + } + } + + // perform ntp sync + // instead of using curr_time, which may change due to NTP sync itself + // we use Arduino's millis() method + //if (curr_time % NTP_SYNC_INTERVAL == 0) os.status.req_ntpsync = 1; + if((millis()/1000) % NTP_SYNC_INTERVAL==0) os.status.req_ntpsync = 1; + perform_ntp_sync(); + + // check network connection + if (curr_time && (curr_time % CHECK_NETWORK_INTERVAL==0)) os.status.req_network = 1; + check_network(); + + // check weather + check_weather(); + + byte wuf = os.weather_update_flag; + if(wuf) { + if((wuf&WEATHER_UPDATE_EIP) | (wuf&WEATHER_UPDATE_WL)) { + // at the moment, we only send notification if water level or external IP changed + // the other changes, such as sunrise, sunset changes are ignored for notification + push_message(IFTTT_WEATHER_UPDATE, (wuf&WEATHER_UPDATE_EIP)?os.nvdata.external_ip:0, + (wuf&WEATHER_UPDATE_WL)?os.options[OPTION_WATER_PERCENTAGE]:-1); + } + os.weather_update_flag = 0; + } + static byte reboot_notification = 1; + if(reboot_notification) { + reboot_notification = 0; + push_message(IFTTT_REBOOT); + } + + } + + #if !defined(ARDUINO) + delay(1); // For OSPI/OSBO/LINUX, sleep 1 ms to minimize CPU usage + #endif } /** Make weather query */ void check_weather() { - // do not check weather if - // - network check has failed, or - // - the controller is in remote extension mode - if (os.status.network_fails>0 || os.iopts[IOPT_REMOTE_EXT_MODE]) return; - if (os.status.program_busy) return; - -#if defined(ESP8266) - if (!m_server) { - if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; - } + // do not check weather if + // - network check has failed, or + // - the controller is in remote extension mode + if (os.status.network_fails>0 || os.options[OPTION_REMOTE_EXT_MODE]) return; + +#ifdef ESP8266_ETHERNET + if (!m_server) +#endif +#ifdef ESP8266 + if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; #endif - ulong ntz = os.now_tz(); - if (os.checkwt_success_lasttime && (ntz > os.checkwt_success_lasttime + CHECK_WEATHER_SUCCESS_TIMEOUT)) { - // if last successful weather call timestamp is more than allowed threshold - // and if the selected adjustment method is not manual - // reset watering percentage to 100 - // todo: the firmware currently needs to be explicitly aware of which adjustment methods - // use manual watering percentage (namely methods 0 and 2), this is not ideal - os.checkwt_success_lasttime = 0; - if(!(os.iopts[IOPT_USE_WEATHER]==0 || os.iopts[IOPT_USE_WEATHER]==2)) { - os.iopts[IOPT_WATER_PERCENTAGE] = 100; // reset watering percentage to 100% - wt_rawData[0] = 0; // reset wt_rawData and errCode - wt_errCode = HTTP_RQT_NOT_RECEIVED; - } - } else if (!os.checkwt_lasttime || (ntz > os.checkwt_lasttime + CHECK_WEATHER_TIMEOUT)) { - os.checkwt_lasttime = ntz; - GetWeather(); - } -} - -/** Turn on a station - * This function turns on a scheduled station - */ -void turn_on_station(byte sid) { - // RAH implementation of flow sensor - flow_start=0; - - if (os.set_station_bit(sid, 1)) { - push_message(NOTIFY_STATION_ON, sid); - } + ulong ntz = os.now_tz(); + if (os.checkwt_success_lasttime && (ntz > os.checkwt_success_lasttime + CHECK_WEATHER_SUCCESS_TIMEOUT)) { + // if weather check has failed to return for too long, restart network + os.checkwt_success_lasttime = 0; + // mark for safe restart + os.status.safe_reboot = 1; + return; + } + if (!os.checkwt_lasttime || (ntz > os.checkwt_lasttime + CHECK_WEATHER_TIMEOUT)) { + os.checkwt_lasttime = ntz; + GetWeather(); + } } /** Turn off a station @@ -1058,40 +1066,40 @@ void turn_on_station(byte sid) { * and writes log record */ void turn_off_station(byte sid, ulong curr_time) { - os.set_station_bit(sid, 0); - - byte qid = pd.station_qid[sid]; - // ignore if we are turning off a station that's not running or scheduled to run - if (qid>=pd.nqueue) return; - - // RAH implementation of flow sensor - if (flow_gallons>1) { - if(flow_stop<=flow_begin) flow_last_gpm = 0; - else flow_last_gpm = (float) 60000/(float)((flow_stop-flow_begin)/(flow_gallons-1)); - }// RAH calculate GPM, 1 pulse per gallon - else {flow_last_gpm = 0;} // RAH if not one gallon (two pulses) measured then record 0 gpm - - RuntimeQueueStruct *q = pd.queue+qid; - - // check if the current time is past the scheduled start time, - // 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)) { - pd.lastrun.station = sid; - pd.lastrun.program = q->pid; - pd.lastrun.duration = curr_time - q->st; - pd.lastrun.endtime = curr_time; - - // log station run - write_log(LOGDATA_STATION, curr_time); - push_message(NOTIFY_STATION_OFF, sid, pd.lastrun.duration); - } - } - - // dequeue the element - pd.dequeue(qid); - pd.station_qid[sid] = 0xFF; + os.set_station_bit(sid, 0); + + byte qid = pd.station_qid[sid]; + // ignore if we are turning off a station that's not running or scheduled to run + if (qid>=pd.nqueue) return; + + // RAH implementation of flow sensor + if (flow_gallons>1) { + if(flow_stop<=flow_begin) flow_last_gpm = 0; + else flow_last_gpm = (float) 60000/(float)((flow_stop-flow_begin)/(flow_gallons-1)); + }// RAH calculate GPM, 1 pulse per gallon + else {flow_last_gpm = 0;} // RAH if not one gallon (two pulses) measured then record 0 gpm + + RuntimeQueueStruct *q = pd.queue+qid; + + // check if the current time is past the scheduled start time, + // 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)) { + pd.lastrun.station = sid; + pd.lastrun.program = q->pid; + pd.lastrun.duration = curr_time - q->st; + pd.lastrun.endtime = curr_time; + + // log station run + write_log(LOGDATA_STATION, curr_time); + push_message(IFTTT_STATION_RUN, sid, pd.lastrun.duration); + } + } + + // dequeue the element + pd.dequeue(qid); + pd.station_qid[sid] = 0xFF; } /** Process dynamic events @@ -1099,48 +1107,42 @@ void turn_off_station(byte sid, ulong curr_time) { * and turn off stations accordingly */ void process_dynamic_events(ulong curr_time) { - // check if rain is detected - bool sn1 = false; - bool sn2 = false; - bool rd = os.status.rain_delayed; - bool en = os.status.enabled; - - if((os.iopts[IOPT_SENSOR1_TYPE] == SENSOR_TYPE_RAIN || os.iopts[IOPT_SENSOR1_TYPE] == SENSOR_TYPE_SOIL) - && os.status.sensor1_active) - sn1 = true; - - if((os.iopts[IOPT_SENSOR2_TYPE] == SENSOR_TYPE_RAIN || os.iopts[IOPT_SENSOR2_TYPE] == SENSOR_TYPE_SOIL) - && os.status.sensor2_active) - sn2 = true; - - // todo: handle sensor 2 - byte sid, s, bid, qid, igs, igs2, igrd; - for(bid=0;bidpid>=99) continue; // if this is a manually started program, proceed - if(!en) turn_off_station(sid, curr_time); // if system is disabled, turn off zone - if(rd && !(igrd&(1<pid<99) && (!en || (rain && !(rbits&(1< curr_time) { - seq_start_time = pd.last_seq_stop_time + station_delay; - } - - RuntimeQueueStruct *q = pd.queue; - byte re = os.iopts[IOPT_REMOTE_EXT_MODE]; - // go through runtime queue and calculate start time of each station - for(;qst) continue; // if this queue element has already been scheduled, skip - if(!q->dur) continue; // if the element has been marked to reset, skip - byte sid=q->sid; - byte bid=sid>>3; - byte s=sid&0x07; - - // if this is a sequential station and the controller is not in remote extension mode - // use sequential scheduling. station delay time apples - if (os.attrib_seq[bid]&(1<st = seq_start_time; - seq_start_time += q->dur; - seq_start_time += station_delay; // add station delay time - } else { - // otherwise, concurrent scheduling - q->st = con_start_time; - // stagger concurrent stations by 1 second - con_start_time++; - } - /*DEBUG_PRINT("["); - DEBUG_PRINT(sid); - DEBUG_PRINT(":"); - DEBUG_PRINT(q->st); - DEBUG_PRINT(","); - DEBUG_PRINT(q->dur); - DEBUG_PRINT("]"); - DEBUG_PRINTLN(pd.nqueue);*/ - if (!os.status.program_busy) { - os.status.program_busy = 1; // set program busy bit - // start flow count - if(os.iopts[IOPT_SENSOR1_TYPE] == SENSOR_TYPE_FLOW) { // if flow sensor is connected - os.flowcount_log_start = flow_count; - os.sensor1_active_lasttime = curr_time; - } - } - } + ulong con_start_time = curr_time + 1; // concurrent start time + ulong seq_start_time = con_start_time; // sequential start time + + int16_t station_delay = water_time_decode_signed(os.options[OPTION_STATION_DELAY_TIME]); + // if the sequential queue has stations running + if (pd.last_seq_stop_time > curr_time) { + seq_start_time = pd.last_seq_stop_time + station_delay; + } + + RuntimeQueueStruct *q = pd.queue; + byte re = os.options[OPTION_REMOTE_EXT_MODE]; + // go through runtime queue and calculate start time of each station + for(;qst) continue; // if this queue element has already been scheduled, skip + if(!q->dur) continue; // if the element has been marked to reset, skip + byte sid=q->sid; + byte bid=sid>>3; + byte s=sid&0x07; + + // if this is a sequential station and the controller is not in remote extension mode + // use sequential scheduling. station delay time apples + if (os.station_attrib_bits_read(ADDR_NVM_STNSEQ+bid)&(1<st = seq_start_time; + seq_start_time += q->dur; + seq_start_time += station_delay; // add station delay time + } else { + // otherwise, concurrent scheduling + q->st = con_start_time; + // stagger concurrent stations by 1 second + con_start_time++; + } + /*DEBUG_PRINT("["); + DEBUG_PRINT(sid); + DEBUG_PRINT(":"); + DEBUG_PRINT(q->st); + DEBUG_PRINT(","); + DEBUG_PRINT(q->dur); + DEBUG_PRINT("]"); + DEBUG_PRINTLN(pd.nqueue);*/ + if (!os.status.program_busy) { + os.status.program_busy = 1; // set program busy bit + // start flow count + if(os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_FLOW) { // if flow sensor is connected + os.flowcount_log_start = flow_count; + os.sensor_lasttime = curr_time; + } + } + } } /** Immediately reset all stations * No log records will be written */ void reset_all_stations_immediate() { - os.clear_all_station_bits(); - os.apply_all_station_bits(); - pd.reset_runtime(); + os.clear_all_station_bits(); + os.apply_all_station_bits(); + pd.reset_runtime(); } /** Reset all stations @@ -1216,11 +1218,11 @@ void reset_all_stations_immediate() { * Stations will be logged */ void reset_all_stations() { - RuntimeQueueStruct *q = pd.queue; - // go through runtime queue and assign water time to 0 - for(;qdur = 0; - } + RuntimeQueueStruct *q = pd.queue; + // go through runtime queue and assign water time to 0 + for(;qdur = 0; + } } @@ -1230,234 +1232,291 @@ void reset_all_stations() { * If pid > 0. run program pid-1 */ void manual_start_program(byte pid, byte uwt) { - boolean match_found = false; - reset_all_stations_immediate(); - ProgramStruct prog; - ulong dur; - byte sid, bid, s; - if ((pid>0)&&(pid<255)) { - pd.read(pid-1, &prog); - push_message(NOTIFY_PROGRAM_SCHED, pid-1, uwt?os.iopts[IOPT_WATER_PERCENTAGE]:100, ""); - } - for(sid=0;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)) - continue; - dur = 60; - if(pid==255) dur=2; - else if(pid>0) - dur = water_time_resolve(prog.durations[sid]); - if(uwt) { - dur = dur * os.iopts[IOPT_WATER_PERCENTAGE] / 100; - } - if(dur>0 && !(os.attrib_dis[bid]&(1<st = 0; - q->dur = dur; - q->sid = sid; - q->pid = 254; - match_found = true; - } - } - } - if(match_found) { - schedule_all_stations(os.now_tz()); - } + boolean match_found = false; + reset_all_stations_immediate(); + ProgramStruct prog; + ulong dur; + byte sid, bid, s; + if ((pid>0)&&(pid<255)) { + pd.read(pid-1, &prog); + push_message(IFTTT_PROGRAM_SCHED, pid-1, uwt?os.options[OPTION_WATER_PERCENTAGE]:100, ""); + } + for(sid=0;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)) + continue; + dur = 60; + if(pid==255) dur=2; + else if(pid>0) + dur = water_time_resolve(prog.durations[sid]); + if(uwt) { + dur = dur * os.options[OPTION_WATER_PERCENTAGE] / 100; + } + if(dur>0 && !(os.station_attrib_bits_read(ADDR_NVM_STNDISABLE+bid)&(1<st = 0; + q->dur = dur; + q->sid = sid; + q->pid = 254; + match_found = true; + } + } + } + if(match_found) { + schedule_all_stations(os.now_tz()); + } } // ========================================== // ====== PUSH NOTIFICATION FUNCTIONS ======= // ========================================== void ip2string(char* str, byte ip[4]) { - sprintf_P(str+strlen(str), PSTR("%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); + for(byte i=0;i<4;i++) { + itoa(ip[i], str+strlen(str), 10); + if(i!=3) strcat(str, "."); + } } -void push_message(int type, uint32_t lval, float fval, const char* sval) { - static char topic[TMP_BUFFER_SIZE]; - static char payload[TMP_BUFFER_SIZE]; - char* postval = tmp_buffer; - uint32_t volume; - - bool ifttt_enabled = os.iopts[IOPT_IFTTT_ENABLE]&type; - - // check if this type of event is enabled for push notification - if (!ifttt_enabled && !os.mqtt.enabled()) - return; - - if (ifttt_enabled) { - strcpy_P(postval, PSTR("{\"value1\":\"")); - } - - if (os.mqtt.enabled()) { - topic[0] = 0; - payload[0] = 0; - } +void push_message(byte type, uint32_t lval, float fval, const char* sval) { + +#if !defined(ARDUINO) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) + + static const char* server = DEFAULT_IFTTT_URL; + static char key[IFTTT_KEY_MAXSIZE]; + static char postval[TMP_BUFFER_SIZE]; + + // check if this type of event is enabled for push notification + if((os.options[OPTION_IFTTT_ENABLE]&type) == 0) return; + key[0] = 0; + read_from_file(ifkey_filename, key); + key[IFTTT_KEY_MAXSIZE-1]=0; + + if(strlen(key)==0) return; + + #if defined(ARDUINO) && !defined(ESP8266) + uint16_t _port = ether.hisport; // make a copy of the original port + ether.hisport = 80; + #endif + + strcpy_P(postval, PSTR("{\"value1\":\"")); + + switch(type) { + + case IFTTT_STATION_RUN: + + strcat_P(postval, PSTR("Station ")); + os.get_station_name(lval, postval+strlen(postval)); + strcat_P(postval, PSTR(" closed. It ran for ")); + itoa((int)fval/60, postval+strlen(postval), 10); + strcat_P(postval, PSTR(" minutes ")); + itoa((int)fval%60, postval+strlen(postval), 10); + strcat_P(postval, PSTR(" seconds.")); + if(os.options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + strcat_P(postval, PSTR(" Flow rate: ")); + #if defined(ARDUINO) + dtostrf(flow_last_gpm,5,2,postval+strlen(postval)); + #else + sprintf(tmp_buffer+strlen(tmp_buffer), "%5.2f", flow_last_gpm); + #endif + } + break; + + case IFTTT_PROGRAM_SCHED: + + if(sval) strcat_P(postval, PSTR("Manually scheduled ")); + else strcat_P(postval, PSTR("Automatically scheduled ")); + strcat_P(postval, PSTR("Program ")); + { + ProgramStruct prog; + pd.read(lval, &prog); + if(lval0) { + strcat_P(postval, PSTR("External IP updated: ")); + byte ip[4] = {(byte)((lval>>24)&0xFF), + (byte)((lval>>16)&0xFF), + (byte)((lval>>8)&0xFF), + (byte)(lval&0xFF)}; + ip2string(postval, ip); + } + if(fval>=0) { + strcat_P(postval, PSTR("Water level updated: ")); + itoa((int)fval, postval+strlen(postval), 10); + strcat_P(postval, PSTR("%.")); + } + + break; + + case IFTTT_REBOOT: + #if defined(ARDUINO) + strcat_P(postval, PSTR("Rebooted. Device IP: ")); + #ifdef ESP8266 + { + #ifdef ESP8266_ETHERNET + IPAddress _ip; + if (m_server) { + _ip = ether.localIP(); + } else { + _ip = WiFi.localIP(); + } + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + ip2string(postval, ip); + #else + IPAddress _ip = WiFi.localIP(); + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + ip2string(postval, ip); + #endif + } + #else + ip2string(postval, ether.myip); + #endif + //strcat(postval, ":"); + //itoa(_port, postval+strlen(postval), 10); + #else + strcat_P(postval, PSTR("Process restarted.")); + #endif + break; + } + + strcat_P(postval, PSTR("\"}")); + + //DEBUG_PRINTLN(postval); - switch(type) { - case NOTIFY_STATION_ON: - - // todo: add IFTTT support for this event as well - if (os.mqtt.enabled()) { - sprintf_P(topic, PSTR("opensprinkler/station/%d"), lval); - strcpy_P(payload, PSTR("{\"state\":1}")); - } - break; - - case NOTIFY_STATION_OFF: - - if (os.mqtt.enabled()) { - sprintf_P(topic, PSTR("opensprinkler/station/%d"), lval); - if (os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - sprintf_P(payload, PSTR("{\"state\":0,\"duration\":%d,\"flow\":%d.%02d}"), (int)fval, (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); - } else { - sprintf_P(payload, PSTR("{\"state\":0,\"duration\":%d}"), (int)fval); - } - } - if (ifttt_enabled) { - char name[STATION_NAME_SIZE]; - os.get_station_name(lval, name); - sprintf_P(postval+strlen(postval), PSTR("Station %s closed. It ran for %d minutes %d seconds."), name, (int)fval/60, (int)fval%60); - - if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - sprintf_P(postval+strlen(postval), PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); - } - } - break; - - case NOTIFY_PROGRAM_SCHED: - - if (ifttt_enabled) { - if (sval) strcat_P(postval, PSTR("Manually scheduled ")); - else strcat_P(postval, PSTR("Automatically scheduled ")); - strcat_P(postval, PSTR("Program ")); - { - ProgramStruct prog; - pd.read(lval, &prog); - if(lval0) { - strcat_P(postval, PSTR("External IP updated: ")); - byte ip[4] = {(byte)((lval>>24)&0xFF), - (byte)((lval>>16)&0xFF), - (byte)((lval>>8)&0xFF), - (byte)(lval&0xFF)}; - ip2string(postval, ip); - } - if(fval>=0) { - sprintf_P(postval+strlen(postval), PSTR("Water level updated: %d%%."), (int)fval); - } - } - break; - - case NOTIFY_REBOOT: - - if (os.mqtt.enabled()) { - strcpy_P(topic, PSTR("opensprinkler/system")); - strcpy_P(payload, PSTR("{\"state\":\"started\"}")); - } - if (ifttt_enabled) { - #if defined(ARDUINO) - strcat_P(postval, PSTR("Rebooted. Device IP: ")); - #if defined(ESP8266) - { - IPAddress _ip; - if (m_server) { - _ip = Ethernet.localIP(); - } else { - _ip = WiFi.localIP(); - } - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; - ip2string(postval, ip); - } - #else - ip2string(postval, &(Ethernet.localIP()[0])); - #endif - //strcat(postval, ":"); - //itoa(_port, postval+strlen(postval), 10); - #else - strcat_P(postval, PSTR("Process restarted.")); - #endif - } - break; - } - - if (os.mqtt.enabled() && strlen(topic) && strlen(payload)) - os.mqtt.publish(topic, payload); +#if defined(ARDUINO) - if (ifttt_enabled) { - strcat_P(postval, PSTR("\"}")); + #ifdef ESP8266 + Client *client; + #ifdef ESP8266_ETHERNET + if (m_server) + client = new UIPClient(); + else + #endif + client = new WiFiClient(); + + + if(!client->connect(server, 80)) { + delete client; + return; + } + + char postBuffer[1500]; + sprintf(postBuffer, "POST /trigger/sprinkler/with/key/%s HTTP/1.0\r\n" + "Host: %s\r\n" + "Accept: */*\r\n" + "Content-Length: %d\r\n" + "Content-Type: application/json\r\n" + "\r\n%s", key, server, strlen(postval), postval); + client->write((uint8_t *)postBuffer, strlen(postBuffer)); + + time_t timeout = os.now_tz() + 5; // 5 seconds timeout + while(!client->available() && os.now_tz() < timeout) { + } + + bzero(ether_buffer, ETHER_BUFFER_SIZE); + + while(client->available()) { + client->read((uint8_t*)ether_buffer, ETHER_BUFFER_SIZE); + } + client->stop(); + delete client; + //DEBUG_PRINTLN(ether_buffer); + + #else + if(!ether.dnsLookup(server, true)) { + // if DNS lookup fails, use default IP + ether.hisip[0] = 54; + ether.hisip[1] = 172; + ether.hisip[2] = 244; + ether.hisip[3] = 116; + } + + ether.httpPostVar(PSTR("/trigger/sprinkler/with/key/"), PSTR(DEFAULT_IFTTT_URL), key, postval, httpget_callback); + for(int l=0;l<100;l++) ether.packetLoop(ether.packetReceive()); + ether.hisport = _port; + #endif + +#else - //char postBuffer[1500]; - BufferFiller bf = ether_buffer; - bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" - "Host: $S\r\n" - "Accept: */*\r\n" - "Content-Length: $D\r\n" - "Content-Type: application/json\r\n\r\n$S"), - SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); + EthernetClient client; + struct hostent *host; + + host = gethostbyname(server); + if (!host) { + DEBUG_PRINT("can't resolve http station - "); + DEBUG_PRINTLN(server); + return; + } + + if (!client.connect((uint8_t*)host->h_addr, 80)) { + client.stop(); + return; + } + + char postBuffer[1500]; + sprintf(postBuffer, "POST /trigger/sprinkler/with/key/%s HTTP/1.0\r\n" + "Host: %s\r\n" + "Accept: */*\r\n" + "Content-Length: %d\r\n" + "Content-Type: application/json\r\n" + "\r\n%s", key, host->h_name, strlen(postval), postval); + client.write((uint8_t *)postBuffer, strlen(postBuffer)); + + bzero(ether_buffer, ETHER_BUFFER_SIZE); + + time_t timeout = now() + 5; // 5 seconds timeout + while(now() < timeout) { + int len=client.read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); + if (len<=0) { + if(!client.connected()) + break; + else + continue; + } + httpget_callback(0, 0, ETHER_BUFFER_SIZE); + } + + client.stop(); - os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, remote_http_callback); - } +#endif + +#endif } // ================================ @@ -1474,14 +1533,14 @@ char LOG_PREFIX[] = "./logs/"; */ void make_logfile_name(char *name) { #if defined(ARDUINO) - #if !defined(ESP8266) - sd.chdir("/"); - #endif + #ifndef ESP8266 + sd.chdir("/"); + #endif #endif - strcpy(tmp_buffer+TMP_BUFFER_SIZE-10, name); - strcpy(tmp_buffer, LOG_PREFIX); - strcat(tmp_buffer, tmp_buffer+TMP_BUFFER_SIZE-10); - strcat_P(tmp_buffer, PSTR(".txt")); + strcpy(tmp_buffer+TMP_BUFFER_SIZE-10, name); + strcpy(tmp_buffer, LOG_PREFIX); + strcat(tmp_buffer, tmp_buffer+TMP_BUFFER_SIZE-10); + strcat_P(tmp_buffer, PSTR(".txt")); } /* To save RAM space, we store log type names @@ -1490,128 +1549,122 @@ void make_logfile_name(char *name) { * so each name is 3 characters total */ static const char log_type_names[] PROGMEM = - " \0" - "s1\0" - "rd\0" - "wl\0" - "fl\0" - "s2\0" - "cu\0"; + " \0" + "rs\0" + "rd\0" + "wl\0" + "fl\0"; /** write run record to log on SD card */ void write_log(byte type, ulong curr_time) { - if (!os.iopts[IOPT_ENABLE_LOGGING]) return; + if (!os.options[OPTION_ENABLE_LOGGING]) return; - // file name will be logs/xxxxx.tx where xxxxx is the day in epoch time - ultoa(curr_time / 86400, tmp_buffer, 10); - make_logfile_name(tmp_buffer); + // file name will be logs/xxxxx.tx where xxxxx is the day in epoch time + ultoa(curr_time / 86400, tmp_buffer, 10); + make_logfile_name(tmp_buffer); - // Step 1: open file if exists, or create new otherwise, - // and move file pointer to the end + // 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) - File file = SPIFFS.open(tmp_buffer, "r+"); - if(!file) { - file = SPIFFS.open(tmp_buffer, "w"); - 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 - + if (!os.status.has_sd) return; + + #ifdef ESP8266 + File file = SPIFFS.open(tmp_buffer, "r+"); + if(!file) { + file = SPIFFS.open(tmp_buffer, "w"); + 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/BBB - 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)) { - return; - } - } - FILE *file; - file = fopen(get_filename_fullpath(tmp_buffer), "rb+"); - if(!file) { - file = fopen(get_filename_fullpath(tmp_buffer), "wb"); - if (!file) return; - } - fseek(file, 0, SEEK_END); -#endif // prepare log folder - - // Step 2: prepare data buffer - strcpy_P(tmp_buffer, PSTR("[")); - - if(type == LOGDATA_STATION) { - itoa(pd.lastrun.program, tmp_buffer+strlen(tmp_buffer), 10); - strcat_P(tmp_buffer, PSTR(",")); - itoa(pd.lastrun.station, tmp_buffer+strlen(tmp_buffer), 10); - strcat_P(tmp_buffer, PSTR(",")); - // duration is unsigned integer - ultoa((ulong)pd.lastrun.duration, tmp_buffer+strlen(tmp_buffer), 10); - } else { - ulong lvalue=0; - if(type==LOGDATA_FLOWSENSE) { - lvalue = (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0; - } - ultoa(lvalue, tmp_buffer+strlen(tmp_buffer), 10); - strcat_P(tmp_buffer, PSTR(",\"")); - strcat_P(tmp_buffer, log_type_names+type*3); - strcat_P(tmp_buffer, PSTR("\",")); - - switch(type) { - case LOGDATA_FLOWSENSE: - lvalue = (curr_time>os.sensor1_active_lasttime)?(curr_time-os.sensor1_active_lasttime):0; - break; - case LOGDATA_SENSOR1: - lvalue = (curr_time>os.sensor1_active_lasttime)?(curr_time-os.sensor1_active_lasttime):0; - break; - case LOGDATA_SENSOR2: - lvalue = (curr_time>os.sensor2_active_lasttime)?(curr_time-os.sensor2_active_lasttime):0; - break; - case LOGDATA_RAINDELAY: - lvalue = (curr_time>os.raindelay_on_lasttime)?(curr_time-os.raindelay_on_lasttime):0; - break; - case LOGDATA_WATERLEVEL: - lvalue = os.iopts[IOPT_WATER_PERCENTAGE]; - break; - } - ultoa(lvalue, tmp_buffer+strlen(tmp_buffer), 10); - } - strcat_P(tmp_buffer, PSTR(",")); - ultoa(curr_time, tmp_buffer+strlen(tmp_buffer), 10); - 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) - dtostrf(flow_last_gpm,5,2,tmp_buffer+strlen(tmp_buffer)); - #else - sprintf(tmp_buffer+strlen(tmp_buffer), "%5.2f", flow_last_gpm); - #endif - } - strcat_P(tmp_buffer, PSTR("]\r\n")); + 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)) { + return; + } + } + FILE *file; + file = fopen(get_filename_fullpath(tmp_buffer), "rb+"); + if(!file) { + file = fopen(get_filename_fullpath(tmp_buffer), "wb"); + if (!file) return; + } + fseek(file, 0, SEEK_END); +#endif // prepare log folder + + // Step 2: prepare data buffer + strcpy_P(tmp_buffer, PSTR("[")); + + if(type == LOGDATA_STATION) { + itoa(pd.lastrun.program, tmp_buffer+strlen(tmp_buffer), 10); + strcat_P(tmp_buffer, PSTR(",")); + itoa(pd.lastrun.station, tmp_buffer+strlen(tmp_buffer), 10); + strcat_P(tmp_buffer, PSTR(",")); + // duration is unsigned integer + ultoa((ulong)pd.lastrun.duration, tmp_buffer+strlen(tmp_buffer), 10); + } else { + ulong lvalue=0; + if(type==LOGDATA_FLOWSENSE) { + lvalue = (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0; + } + ultoa(lvalue, tmp_buffer+strlen(tmp_buffer), 10); + strcat_P(tmp_buffer, PSTR(",\"")); + strcat_P(tmp_buffer, log_type_names+type*3); + strcat_P(tmp_buffer, PSTR("\",")); + + switch(type) { + case LOGDATA_RAINSENSE: + case LOGDATA_FLOWSENSE: + lvalue = (curr_time>os.sensor_lasttime)?(curr_time-os.sensor_lasttime):0; + break; + case LOGDATA_RAINDELAY: + lvalue = (curr_time>os.raindelay_start_time)?(curr_time-os.raindelay_start_time):0; + break; + case LOGDATA_WATERLEVEL: + lvalue = os.options[OPTION_WATER_PERCENTAGE]; + break; + } + ultoa(lvalue, tmp_buffer+strlen(tmp_buffer), 10); + } + strcat_P(tmp_buffer, PSTR(",")); + ultoa(curr_time, tmp_buffer+strlen(tmp_buffer), 10); + if((os.options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) && (type==LOGDATA_STATION)) { + // RAH implementation of flow sensor + strcat_P(tmp_buffer, PSTR(",")); + #if defined(ARDUINO) + dtostrf(flow_last_gpm,5,2,tmp_buffer+strlen(tmp_buffer)); + #else + sprintf(tmp_buffer+strlen(tmp_buffer), "%5.2f", flow_last_gpm); + #endif + } + strcat_P(tmp_buffer, PSTR("]\r\n")); #if defined(ARDUINO) - #if defined(ESP8266) - file.write((byte*)tmp_buffer, strlen(tmp_buffer)); - #else - file.write(tmp_buffer); - #endif - file.close(); + #ifdef ESP8266 + file.write((byte*)tmp_buffer, strlen(tmp_buffer)); + #else + file.write(tmp_buffer); + #endif + file.close(); #else - fwrite(tmp_buffer, 1, strlen(tmp_buffer), file); - fclose(file); + fwrite(tmp_buffer, 1, strlen(tmp_buffer), file); + fclose(file); #endif } @@ -1620,48 +1673,49 @@ void write_log(byte type, ulong curr_time) { * If name is 'all', delete all logs */ void delete_log(char *name) { - if (!os.iopts[IOPT_ENABLE_LOGGING]) return; + if (!os.options[OPTION_ENABLE_LOGGING]) return; #if defined(ARDUINO) - - #if defined(ESP8266) - if (strncmp(name, "all", 3) == 0) { - // delete all log files - Dir dir = SPIFFS.openDir(LOG_PREFIX); - while (dir.next()) { - SPIFFS.remove(dir.fileName()); - } - } else { - // delete a single log file - make_logfile_name(name); - if(!SPIFFS.exists(tmp_buffer)) return; - SPIFFS.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 - + if (!os.status.has_sd) return; + + #ifdef ESP8266 + if (strncmp(name, "all", 3) == 0) { + // delete all log files + Dir dir = SPIFFS.openDir(LOG_PREFIX); + while (dir.next()) { + SPIFFS.remove(dir.fileName()); + } + } else { + // delete a single log file + make_logfile_name(name); + if(!SPIFFS.exists(tmp_buffer)) return; + SPIFFS.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/BBB - if (strncmp(name, "all", 3) == 0) { - // delete the log folder - rmdir(get_filename_fullpath(LOG_PREFIX)); - return; - } else { - make_logfile_name(name); - remove(get_filename_fullpath(tmp_buffer)); - } + if (strncmp(name, "all", 3) == 0) { + // delete the log folder + rmdir(get_filename_fullpath(LOG_PREFIX)); + return; + } else { + make_logfile_name(name); + remove(get_filename_fullpath(tmp_buffer)); + } #endif } @@ -1671,114 +1725,102 @@ void delete_log(char *name) { * If not, it re-initializes Ethernet controller. */ void check_network() { -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - // 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(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; - } - } +#if defined(ARDUINO) && !defined(ESP8266) + // 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(15, 1); + os.lcd.write(4); + } + + // ping gateway ip + ether.clientIcmpRequest(ether.gwip); + + ulong start = millis(); + boolean failed = true; + // wait at most PING_TIMEOUT milliseconds for ping result + do { + ether.packetLoop(ether.packetReceive()); + if (ether.packetLoopIcmpCheckReply(ether.gwip)) { + failed = false; + break; + } + } while(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.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 - // nothing to do for other platforms + // nothing to do for other platforms #endif } /** Perform NTP sync */ void perform_ntp_sync() { #if defined(ARDUINO) - // do not perform sync if this option is disabled, or if network is not available, or if a program is running - if (!os.iopts[IOPT_USE_NTP] || os.status.program_busy) return; - #if defined(ESP8266) - if (!m_server) { - if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; - } - #else - if (os.status.network_fails>0) return; - #endif - - if (os.status.req_ntpsync) { - // check if rtc is uninitialized - // 978307200 is Jan 1, 2001, 00:00:00 - boolean rtc_zero = (now()<=978307200L); - - os.status.req_ntpsync = 0; - if (!ui_state) { - os.lcd_print_line_clear_pgm(PSTR("NTP Syncing..."),1); - } - DEBUG_PRINTLN(F("NTP Syncing...")); - static ulong last_ntp_result = 0; - ulong t = getNtpTime(); - if(last_ntp_result!=0) { - if(t>last_ntp_result-3 && t0) { - setTime(t); - RTC.set(t); - DEBUG_PRINTLN(RTC.get()); - #if !defined(ESP8266) - // if rtc was uninitialized and now it is, restart - if(rtc_zero && now()>978307200L) { - os.reboot_dev(REBOOT_CAUSE_NTP); - } - #endif - } - } + // do not perform sync if this option is disabled, or if network is not available, or if a program is running + if (!os.options[OPTION_USE_NTP] || os.status.program_busy) return; + #ifdef ESP8266 + #ifdef ESP8266_ETHERNET + if (!m_server) + #endif + + if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; + #else + if (os.status.network_fails>0) return; + #endif + + if (os.status.req_ntpsync) { + // check if rtc is uninitialized + // 978307200 is Jan 1, 2001, 00:00:00 + boolean rtc_zero = (now()<=978307200L); + + os.status.req_ntpsync = 0; + if (!ui_state) { + os.lcd_print_line_clear_pgm(PSTR("NTP Syncing..."),1); + } + ulong t = getNtpTime(); + if (t>0) { + setTime(t); + RTC.set(t); + #ifndef ESP8266 + // if rtc was uninitialized and now it is, restart + if(rtc_zero && now()>978307200L) { + os.reboot_dev(); + } + #endif + } + } #else - // nothing to do here - // Linux will do this for you + // nothing to do here + // Linux will do this for you #endif } #if !defined(ARDUINO) // main function for RPI/BBB int main(int argc, char *argv[]) { - do_setup(); + do_setup(); - while(true) { - do_loop(); - } - return 0; + while(true) { + do_loop(); + } + return 0; } #endif From 889210d65dc7dfb6701a3ae91443c291af462dce Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 13 Jul 2019 01:47:42 +0200 Subject: [PATCH 004/281] RAIN->SOIL --- main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 60c6f5bf7..10095f611 100644 --- a/main.cpp +++ b/main.cpp @@ -681,10 +681,10 @@ void do_loop() // ====== Check soil moisture status ====== #ifdef ESP8266 - if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN - || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_SOIL + || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_SOIL) { // if a soil moisture sensor is connected #else - if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected + if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_SOIL) { // if a soil moisture sensor is connected #endif os.soil_moisture_sensor_status(); if (os.old_status.soil_moisture_sensed != os.status.soil_moisture_sensed) { From 61673abcd18cf2ca6eff58cc68045ba6f5be6ec6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 1 Apr 2022 09:35:12 +0200 Subject: [PATCH 005/281] lwip version - bases on Arduino 3.0.2: Needs updates version of LitteFS and lwIP_enc28j60 libs! --- I2CRTC.cpp | 4 + I2CRTC.h | 1 + OpenSprinkler.cpp | 4249 +++++++++++++----------- OpenSprinkler.h | 504 +-- build.sh | 6 +- defines.cpp | 8 +- defines.h | 861 +++-- examples/mainArduino/mainArduino.ino | 30 - gpio.cpp | 30 +- gpio.h | 5 +- images.h | 12 +- main.cpp | 3122 +++++++++-------- mainArduino.ino | 6 +- make.lin302 | 34 + make.lin32 | 4 +- make.os23 | 7 +- makeEspArduino.mk | 740 ++--- mqtt.cpp | 16 +- server.cpp => opensprinkler_server.cpp | 244 +- server.h => opensprinkler_server.h | 6 +- platformio.ini | 43 + tools/board_op.pl | 60 + tools/crash_tool.pl | 90 + tools/find_src.pl | 131 + tools/mem_use.pl | 27 + tools/obj_info.pl | 40 + tools/parse_arduino.pl | 161 + tools/py_wrap.py | 31 + tools/vscode.pl | 165 + utils.cpp | 31 +- utils.h | 4 +- weather.cpp | 8 +- 32 files changed, 5811 insertions(+), 4869 deletions(-) delete mode 100644 examples/mainArduino/mainArduino.ino create mode 100644 make.lin302 rename server.cpp => opensprinkler_server.cpp (92%) rename server.h => opensprinkler_server.h (96%) create mode 100644 platformio.ini create mode 100644 tools/board_op.pl create mode 100644 tools/crash_tool.pl create mode 100644 tools/find_src.pl create mode 100644 tools/mem_use.pl create mode 100644 tools/obj_info.pl create mode 100644 tools/parse_arduino.pl create mode 100644 tools/py_wrap.py create mode 100644 tools/vscode.pl diff --git a/I2CRTC.cpp b/I2CRTC.cpp index 26c6bea6f..6a7882734 100644 --- a/I2CRTC.cpp +++ b/I2CRTC.cpp @@ -39,6 +39,10 @@ I2CRTC::I2CRTC() Wire.begin(); } +bool I2CRTC::exists() { + return (addr!=0); +} + bool I2CRTC::detect() { addr = 0; diff --git a/I2CRTC.h b/I2CRTC.h index b9cf53fb0..7dac24335 100644 --- a/I2CRTC.h +++ b/I2CRTC.h @@ -24,6 +24,7 @@ class I2CRTC static void read(tmElements_t &tm); static void write(tmElements_t &tm); static bool detect(); + static bool exists(); private: static uint8_t dec2bcd(uint8_t num); diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index b071f7ab0..fbe7c1eeb 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -20,29 +20,14 @@ * along with this program. If not, see * . */ -#if !defined(ARDUINO) -#include -#endif #include "OpenSprinkler.h" -#include "server.h" +#include "opensprinkler_server.h" #include "gpio.h" -#include "images.h" #include "testmode.h" -#if defined(ESP8266_ETHERNET) -#include "defines.h" -#include "UIPEthernet.h" -#include "UIPServer.h" -#include -#include "utils.h" -#include "server.h" -extern char ether_buffer[]; -extern UIPServer *m_server; -extern UIPEthernetClass ether; -#endif - /** Declare static data members */ +OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; ConStatus OpenSprinkler::status; ConStatus OpenSprinkler::old_status; @@ -51,458 +36,579 @@ byte OpenSprinkler::hw_rev; byte OpenSprinkler::nboards; byte OpenSprinkler::nstations; -byte OpenSprinkler::station_bits[MAX_EXT_BOARDS+1]; -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) +byte OpenSprinkler::station_bits[MAX_NUM_BOARDS]; byte OpenSprinkler::engage_booster; uint16_t OpenSprinkler::baseline_current; -#endif -ulong OpenSprinkler::sensor_lasttime; -ulong OpenSprinkler::soil_moisture_sensed_time; +ulong OpenSprinkler::sensor1_on_timer; +ulong OpenSprinkler::sensor1_off_timer; +ulong OpenSprinkler::sensor1_active_lasttime; +ulong OpenSprinkler::sensor2_on_timer; +ulong OpenSprinkler::sensor2_off_timer; +ulong OpenSprinkler::sensor2_active_lasttime; +ulong OpenSprinkler::raindelay_on_lasttime; + ulong OpenSprinkler::flowcount_log_start; ulong OpenSprinkler::flowcount_rt; -volatile ulong OpenSprinkler::flowcount_time_ms; -ulong OpenSprinkler::raindelay_start_time; byte OpenSprinkler::button_timeout; ulong OpenSprinkler::checkwt_lasttime; ulong OpenSprinkler::checkwt_success_lasttime; ulong OpenSprinkler::powerup_lasttime; +uint8_t OpenSprinkler::last_reboot_cause = REBOOT_CAUSE_NONE; byte OpenSprinkler::weather_update_flag; -char tmp_buffer[TMP_BUFFER_SIZE+1]; // scratch buffer - -const char wtopts_filename[] PROGMEM = WEATHER_OPTS_FILENAME; -const char stns_filename[] PROGMEM = STATION_ATTR_FILENAME; -const char ifkey_filename[] PROGMEM = IFTTT_KEY_FILENAME; -#ifdef ESP8266 -const char wifi_filename[] PROGMEM = WIFI_FILENAME; -byte OpenSprinkler::state = OS_STATE_INITIAL; -byte OpenSprinkler::prev_station_bits[MAX_EXT_BOARDS+1]; -WiFiConfig OpenSprinkler::wifi_config = {WIFI_MODE_AP, "", ""}; -IOEXP* OpenSprinkler::expanders[(MAX_EXT_BOARDS+1)/2]; -IOEXP* OpenSprinkler::mainio; -IOEXP* OpenSprinkler::drio; -RCSwitch OpenSprinkler::rfswitch; -extern ESP8266WebServer *wifi_server; +// todo future: the following attribute bytes are for backward compatibility +byte OpenSprinkler::attrib_mas[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_igs[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_mas2[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_igs2[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_seq[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; + +extern char tmp_buffer[]; extern char ether_buffer[]; -#endif -#if defined(ARDUINO) && !defined(ESP8266) - LiquidCrystal OpenSprinkler::lcd; - #include - extern SdFat sd; -#elif defined(ESP8266) - #include - SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); +#if defined(ESP8266) + SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); + byte OpenSprinkler::state = OS_STATE_INITIAL; + byte OpenSprinkler::prev_station_bits[MAX_NUM_BOARDS]; + IOEXP* OpenSprinkler::expanders[MAX_NUM_BOARDS/2]; + IOEXP* OpenSprinkler::mainio; // main controller IO expander object + IOEXP* OpenSprinkler::drio; // driver board IO expander object + RCSwitch OpenSprinkler::rfswitch; + + String OpenSprinkler::wifi_ssid=""; + String OpenSprinkler::wifi_pass=""; + byte OpenSprinkler::wifi_testmode = 0; +#elif defined(ARDUINO) + LiquidCrystal OpenSprinkler::lcd; + extern SdFat sd; #else - // todo: LCD define for Linux-based systems + #if defined(OSPI) + byte OpenSprinkler::pin_sr_data = PIN_SR_DATA; + #endif + // todo future: LCD define for Linux-based systems #endif -#if defined(OSPI) - byte OpenSprinkler::pin_sr_data = PIN_SR_DATA; -#endif - -/** Option json names (stored in progmem) */ +/** Option json names (stored in PROGMEM to reduce RAM usage) */ // IMPORTANT: each json name is strictly 5 characters // with 0 fillings if less #define OP_JSON_NAME_STEPSIZE 5 -const char op_json_names[] PROGMEM = - "fwv\0\0" - "tz\0\0\0" - "ntp\0\0" - "dhcp\0" - "ip1\0\0" - "ip2\0\0" - "ip3\0\0" - "ip4\0\0" - "gw1\0\0" - "gw2\0\0" - "gw3\0\0" - "gw4\0\0" - "hp0\0\0" - "hp1\0\0" - "hwv\0\0" - "ext\0\0" - "seq\0\0" - "sdt\0\0" - "mas\0\0" - "mton\0" - "mtof\0" - "urs\0\0" // todo: rename to sn1t - "rso\0\0" // todo: rename to sn1o - "wl\0\0\0" - "den\0\0" - "ipas\0" - "devid" - "con\0\0" - "lit\0\0" - "dim\0\0" - "bst\0\0" - "uwt\0\0" - "ntp1\0" - "ntp2\0" - "ntp3\0" - "ntp4\0" - "lg\0\0\0" - "mas2\0" - "mton2" - "mtof2" - "fwm\0\0" - "fpr0\0" - "fpr1\0" - "re\0\0\0" - "dns1\0" - "dns2\0" - "dns3\0" - "dns4\0" - "sar\0\0" - "ife\0\0" - "sn2t\0" - "sn2o\0" - "reset"; - -/** Option promopts (stored in progmem, for LCD display) */ +// for Integer options +const char iopt_json_names[] PROGMEM = + "fwv\0\0" + "tz\0\0\0" + "ntp\0\0" + "dhcp\0" + "ip1\0\0" + "ip2\0\0" + "ip3\0\0" + "ip4\0\0" + "gw1\0\0" + "gw2\0\0" + "gw3\0\0" + "gw4\0\0" + "hp0\0\0" + "hp1\0\0" + "hwv\0\0" + "ext\0\0" + "seq\0\0" + "sdt\0\0" + "mas\0\0" + "mton\0" + "mtof\0" + "urs\0\0" + "rso\0\0" + "wl\0\0\0" + "den\0\0" + "ipas\0" + "devid" + "con\0\0" + "lit\0\0" + "dim\0\0" + "bst\0\0" + "uwt\0\0" + "ntp1\0" + "ntp2\0" + "ntp3\0" + "ntp4\0" + "lg\0\0\0" + "mas2\0" + "mton2" + "mtof2" + "fwm\0\0" + "fpr0\0" + "fpr1\0" + "re\0\0\0" + "dns1\0" + "dns2\0" + "dns3\0" + "dns4\0" + "sar\0\0" + "ife\0\0" + "sn1t\0" + "sn1o\0" + "sn2t\0" + "sn2o\0" + "sn1on" + "sn1of" + "sn2on" + "sn2of" + "subn1" + "subn2" + "subn3" + "subn4" + "wimod" + "reset" + ; + +// for String options +/* +const char sopt_json_names[] PROGMEM = + "dkey\0" + "loc\0\0" + "jsp\0\0" + "wsp\0\0" + "wtkey" + "wto\0\0" + "ifkey" + "ssid\0" + "pass\0" + "mqtt\0" + "apass"; +*/ + +/** Option promopts (stored in PROGMEM to reduce RAM usage) */ // Each string is strictly 16 characters // with SPACE fillings if less -const char op_prompts[] PROGMEM = - "Firmware version" - "Time zone (GMT):" - "Enable NTP sync?" - "Enable DHCP? " - "Static.ip1: " - "Static.ip2: " - "Static.ip3: " - "Static.ip4: " - "Gateway.ip1: " - "Gateway.ip2: " - "Gateway.ip3: " - "Gateway.ip4: " - "HTTP Port: " - "----------------" - "Hardware version" - "# of exp. board:" - "----------------" - "Stn. delay (sec)" - "Master 1 (Mas1):" - "Mas1 on adjust:" - "Mas1 off adjust:" - "Sensor 1 type: " - "Normally open? " - "Watering level: " - "Device enabled? " - "Ignore password?" - "Device ID: " - "LCD contrast: " - "LCD brightness: " - "LCD dimming: " - "DC boost time: " - "Weather algo.: " - "NTP server.ip1: " - "NTP server.ip2: " - "NTP server.ip3: " - "NTP server.ip4: " - "Enable logging? " - "Master 2 (Mas2):" - "Mas2 on adjust:" - "Mas2 off adjust:" - "Firmware minor: " - "Pulse rate: " - "----------------" - "As remote ext.? " - "DNS server.ip1: " - "DNS server.ip2: " - "DNS server.ip3: " - "DNS server.ip4: " - "Special Refresh?" - "IFTTT Enable: " - "Sensor 2 type: " - "Normally open? " - "Factory reset? "; - -/** Option maximum values (stored in progmem) */ -const byte op_max[] PROGMEM = { - 0, - 108, - 1, - 1, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 0, - MAX_EXT_BOARDS, - 1, - 255, - MAX_NUM_STATIONS, - 255, - 255, - 255, - 1, - 250, - 1, - 1, - 255, - 255, - 255, - 255, - 250, - 255, - 255, - 255, - 255, - 255, - 1, - MAX_NUM_STATIONS, - 255, - 255, - 0, - 255, - 255, - 1, - 255, - 255, - 255, - 255, - 1, - 255, - 255, - 1, - 1 +const char iopt_prompts[] PROGMEM = + "Firmware version" + "Time zone (GMT):" + "Enable NTP sync?" + "Enable DHCP? " + "Static.ip1: " + "Static.ip2: " + "Static.ip3: " + "Static.ip4: " + "Gateway.ip1: " + "Gateway.ip2: " + "Gateway.ip3: " + "Gateway.ip4: " + "HTTP Port: " + "----------------" + "Hardware version" + "# of exp. board:" + "----------------" + "Stn. delay (sec)" + "Master 1 (Mas1):" + "Mas1 on adjust:" + "Mas1 off adjust:" + "----------------" + "----------------" + "Watering level: " + "Device enabled? " + "Ignore password?" + "Device ID: " + "LCD contrast: " + "LCD brightness: " + "LCD dimming: " + "DC boost time: " + "Weather algo.: " + "NTP server.ip1: " + "NTP server.ip2: " + "NTP server.ip3: " + "NTP server.ip4: " + "Enable logging? " + "Master 2 (Mas2):" + "Mas2 on adjust:" + "Mas2 off adjust:" + "Firmware minor: " + "Pulse rate: " + "----------------" + "As remote ext.? " + "DNS server.ip1: " + "DNS server.ip2: " + "DNS server.ip3: " + "DNS server.ip4: " + "Special Refresh?" + "IFTTT Enable: " + "Sensor 1 type: " + "Normally open? " + "Sensor 2 type: " + "Normally open? " + "Sn1 on adjust: " + "Sn1 off adjust: " + "Sn2 on adjust: " + "Sn2 off adjust: " + "Subnet mask1: " + "Subnet mask2: " + "Subnet mask3: " + "Subnet mask4: " + "WiFi mode? " + "Factory reset? "; + +// string options do not have prompts + +/** Option maximum values (stored in PROGMEM to reduce RAM usage) */ +const byte iopt_max[] PROGMEM = { + 0, + 108, + 1, + 1, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 0, + MAX_EXT_BOARDS, + 1, + 255, + MAX_NUM_STATIONS, + 255, + 255, + 255, + 1, + 250, + 1, + 1, + 255, + 255, + 255, + 255, + 250, + 255, + 255, + 255, + 255, + 255, + 1, + MAX_NUM_STATIONS, + 255, + 255, + 0, + 255, + 255, + 1, + 255, + 255, + 255, + 255, + 1, + 255, + 255, + 1, + 255, + 1, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 1 }; -/** Option values (stored in RAM) */ -byte OpenSprinkler::options[] = { - OS_FW_VERSION, // firmware version - 28, // default time zone: GMT-5 - 1, // 0: disable NTP sync, 1: enable NTP sync - 1, // 0: use static ip, 1: use dhcp - 0, // this and next 3 bytes define static ip - 0, - 0, - 0, - 0, // this and next 3 bytes define static gateway ip - 0, - 0, - 0, -#if defined(ARDUINO) // on AVR, the default HTTP port is 80 - 80, // this and next byte define http port number - 0, +// string options do not have maximum values + +/** Integer option values (stored in RAM) */ +byte OpenSprinkler::iopts[] = { + OS_FW_VERSION, // firmware version + 28, // default time zone: GMT-5 + 1, // 0: disable NTP sync, 1: enable NTP sync + 1, // 0: use static ip, 1: use dhcp + 0, // this and next 3 bytes define static ip + 0, + 0, + 0, + 0, // this and next 3 bytes define static gateway ip + 0, + 0, + 0, +#if defined(ARDUINO) // on AVR, the default HTTP port is 80 + 80, // this and next byte define http port number + 0, #else // on RPI/BBB/LINUX, the default HTTP port is 8080 - 144,// this and next byte define http port number - 31, + 144,// this and next byte define http port number + 31, #endif - OS_HW_VERSION, - 0, // number of 8-station extension board. 0: no extension boards - 1, // the option 'sequential' is now retired - 120,// station delay time (-10 minutes to 10 minutes). - 0, // index of master station. 0: no master station - 120,// master on time adjusted time (-10 minutes to 10 minutes) - 120,// master off adjusted time (-10 minutes to 10 minutes) - 0, // sensor 1 type (see SENSOR_TYPE macro defines) - 0, // sensor 1 option. 0: normally closed; 1: normally open. - 100,// water level (default 100%), - 1, // device enable - 0, // 1: ignore password; 0: use password - 0, // device id - 150,// lcd contrast - 100,// lcd backlight - 50, // lcd dimming - 80, // boost time (only valid to DC and LATCH type) - 0, // weather algorithm (0 means not using weather algorithm) - 50, // this and the next three bytes define the ntp server ip - 97, - 210, - 169, - 1, // enable logging: 0: disable; 1: enable. - 0, // index of master2. 0: no master2 station - 120,// master2 on adjusted time - 120,// master2 off adjusted time - OS_FW_MINOR, // firmware minor version - 100,// this and next byte define flow pulse rate (100x) - 0, // default is 1.00 (100) - 0, // set as remote extension - 8, // this and the next three bytes define the custom dns server ip - 8, - 8, - 8, - 0, // special station auto refresh - 0, // ifttt enable bits - 0, // sensor 2 type - 0, // sensor 2 option. 0: normally closed; 1: normally open. - 0 // reset + OS_HW_VERSION, + 0, // number of 8-station extension board. 0: no extension boards + 1, // the option 'sequential' is now retired + 120,// station delay time (-10 minutes to 10 minutes). + 0, // index of master station. 0: no master station + 120,// master on time adjusted time (-10 minutes to 10 minutes) + 120,// master off adjusted time (-10 minutes to 10 minutes) + 0, // urs (retired) + 0, // rso (retired) + 100,// water level (default 100%), + 1, // device enable + 0, // 1: ignore password; 0: use password + 0, // device id + 150,// lcd contrast + 100,// lcd backlight + 50, // lcd dimming + 80, // boost time (only valid to DC and LATCH type) + 0, // weather algorithm (0 means not using weather algorithm) + 0, // this and the next three bytes define the ntp server ip + 0, + 0, + 0, + 1, // enable logging: 0: disable; 1: enable. + 0, // index of master2. 0: no master2 station + 120,// master2 on adjusted time + 120,// master2 off adjusted time + OS_FW_MINOR, // firmware minor version + 100,// this and next byte define flow pulse rate (100x) + 0, // default is 1.00 (100) + 0, // set as remote extension + 8, // this and the next three bytes define the custom dns server ip + 8, + 8, + 8, + 0, // special station auto refresh + 0, // ifttt enable bits + 0, // sensor 1 type (see SENSOR_TYPE macro defines) + 1, // sensor 1 option. 0: normally closed; 1: normally open. default 1. + 0, // sensor 2 type + 1, // sensor 2 option. 0: normally closed; 1: normally open. default 1. + 0, // sensor 1 on delay + 0, // sensor 1 off delay + 0, // sensor 2 on delay + 0, // sensor 2 off delay + 255,// subnet mask 1 + 255,// subnet mask 2 + 255,// subnet mask 3 + 0, + WIFI_MODE_AP, // wifi mode + 0 // reset +}; + +/** String option values (stored in RAM) */ +const char *OpenSprinkler::sopts[] = { + DEFAULT_PASSWORD, + DEFAULT_LOCATION, + DEFAULT_JAVASCRIPT_URL, + DEFAULT_WEATHER_URL, + DEFAULT_EMPTY_STRING, + DEFAULT_EMPTY_STRING, + DEFAULT_EMPTY_STRING, + DEFAULT_EMPTY_STRING, + DEFAULT_EMPTY_STRING, + DEFAULT_EMPTY_STRING, + DEFAULT_EMPTY_STRING, + DEFAULT_EMPTY_STRING, + DEFAULT_EMPTY_STRING }; -/** Weekday strings (stored in progmem, for LCD display) */ +/** Weekday strings (stored in PROGMEM to reduce RAM usage) */ static const char days_str[] PROGMEM = - "Mon\0" - "Tue\0" - "Wed\0" - "Thu\0" - "Fri\0" - "Sat\0" - "Sun\0"; + "Mon\0" + "Tue\0" + "Wed\0" + "Thu\0" + "Fri\0" + "Sat\0" + "Sun\0"; /** Calculate local time (UTC time plus time zone offset) */ time_t OpenSprinkler::now_tz() { - return now()+(int32_t)3600/4*(int32_t)(options[OPTION_TIMEZONE]-48); + return now()+(int32_t)3600/4*(int32_t)(iopts[IOPT_TIMEZONE]-48); } -#if defined(ARDUINO) // AVR network init functions +#if defined(ARDUINO) // AVR network init functions bool detect_i2c(int addr) { - Wire.beginTransmission(addr); - return (Wire.endTransmission()==0); + Wire.beginTransmission(addr); + return (Wire.endTransmission()==0); // successful if received 0 } -/** read hardware MAC */ +/** read hardware MAC into tmp_buffer */ #define MAC_CTRL_ID 0x50 -bool OpenSprinkler::read_hardware_mac() { -#ifdef ESP8266 -#ifdef ESP8266_ETHERNET - if (m_server) { - get_hardware_mac(); - return true; - } -#endif - WiFi.macAddress((byte*)tmp_buffer); - return true; +bool OpenSprinkler::load_hardware_mac(byte* buffer, bool wired) { +#if defined(ESP8266) + WiFi.macAddress((byte*)buffer); + // if requesting wired Ethernet MAC, flip the last byte to create a modified MAC + if(wired) buffer[5] = ~buffer[5]; + return true; #else - uint8_t ret; - ret = detect_i2c(MAC_CTRL_ID); - if (ret) 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; // Request 6 bytes from the EEPROM - for (ret=0;ret<6;ret++) { - tmp_buffer[ret] = Wire.read(); - } - return true; + // 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(byte 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 */ + byte 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(m_server) { delete m_server; m_server = NULL; } +#endif -#ifdef ESP8266 + if (start_ether()) { -#ifdef ESP8266_ETHERNET - if(m_server) { - delete m_server; - m_server = 0; - } +#if defined(ESP8266) + if(w_server) { delete w_server; w_server = NULL; } + w_server = new ESP8266WebServer(httpport); + useEth = true; +#else // AVR + m_server = new EthernetServer(httpport); + m_server->begin(); + useEth = true; +#endif + + //udp = new EthernetUDP(); + //udp->begin((httpport==8000) ? 8888 : 8000); // start udp on a different port than httpport - if (start_ether()) - { - unsigned int port = (unsigned int)(options[OPTION_HTTPPORT_1]<<8) + (unsigned int)options[OPTION_HTTPPORT_0]; -//#if defined(DEMO) - port = 80; +//#if defined(ESP8266) + // turn off WiFi when ether is active + // todo: add option to keep both ether and wifi active + // lwip: WiFi.mode(WIFI_OFF); //#endif - m_server = new UIPServer(port); - m_server->begin(); - } + return 1; + + } else { + +#if defined(ESP8266) + if(w_server) { delete w_server; w_server = NULL; } + if(get_wifi_mode()==WIFI_MODE_AP) { + w_server = new ESP8266WebServer(80); + } else { + w_server = new ESP8266WebServer(httpport); + } + + //udp = new WiFiUDP(); + //udp->begin((httpport==8000) ? 8888 : 8000); // start udp on a different port than httpport + + return 1; #endif - lcd_print_line_clear_pgm(PSTR("Starting..."), 1); - if(wifi_server) delete wifi_server; - if(get_wifi_mode()==WIFI_MODE_AP) { - wifi_server = new ESP8266WebServer(80); - } else { - uint16_t httpport = (uint16_t)(options[OPTION_HTTPPORT_1]<<8) + (uint16_t)options[OPTION_HTTPPORT_0]; - wifi_server = new ESP8266WebServer(httpport); + } + + return 0; +} + +byte OpenSprinkler::start_ether() { +#if defined(ESP8266) + if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 + + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(4000000); + + eth.setDefault(); + load_hardware_mac((uint8_t*)tmp_buffer, true); + + if(!eth.begin((uint8_t*)tmp_buffer)) return 0; + + lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); + + // todo: lwip add timeout + while (!eth.connected()) { + DEBUG_PRINT("."); + delay(1000); } - status.has_hwmac = 1; - + + DEBUG_PRINTLN(); + DEBUG_PRINT("eth.ip:"); + DEBUG_PRINTLN(eth.localIP()); + DEBUG_PRINT("eth.dns:"); + DEBUG_PRINTLN(WiFi.dnsIP()); + + if (iopts[IOPT_USE_DHCP]) { + memcpy(iopts+IOPT_STATIC_IP1, &(eth.localIP()[0]), 4); + memcpy(iopts+IOPT_GATEWAY_IP1, &(eth.gatewayIP()[0]),4); + memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); // todo: lwip need dns ip + memcpy(iopts+IOPT_SUBNET_MASK1, &(eth.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); + eth.config(staticip, gateway, subn, dns); + } + + return 1; + #else - return start_ether(); -#endif - return 1; -} + 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); + } -#if defined(ESP8266_ETHERNET) -void OpenSprinkler::get_hardware_mac() -{ - tmp_buffer[0] = 0x00; - tmp_buffer[1] = 0x69; - tmp_buffer[2] = 0x69; - tmp_buffer[3] = 0x2D; - tmp_buffer[4] = 0x31; - tmp_buffer[5] = options[OPTION_DEVICE_ID]; + return 1; +#endif } -byte OpenSprinkler::start_ether() -{ - lcd_print_line_clear_pgm(PSTR("init ethernet..."), 1); - - ether.init(PIN_ETHER_CS); - get_hardware_mac(); - lcd_print_line_clear_pgm(PSTR("setup link..."), 1); - if(!ether.begin((uint8_t*)tmp_buffer)) return 0; - lcd_print_line_clear_pgm(PSTR("waiting link..."), 1); - if(ether.linkStatus() != LinkON) return 0; - return 1; -} +bool OpenSprinkler::network_connected(void) { +#if defined (ESP8266) + if(useEth) return eth.connected(); // todo: fix this + else + return (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && state==OS_STATE_CONNECTED); #else -byte OpenSprinkler::start_ether() -{ - lcd_print_line_clear_pgm(PSTR("Connecting..."), 1); - // new from 2.2: read hardware MAC - if(!read_hardware_mac()) - { - // if no hardware MAC exists, use software MAC - tmp_buffer[0] = 0x00; - tmp_buffer[1] = 0x69; - tmp_buffer[2] = 0x69; - tmp_buffer[3] = 0x2D; - tmp_buffer[4] = 0x31; - tmp_buffer[5] = options[OPTION_DEVICE_ID]; - } else { - // has hardware MAC chip - status.has_hwmac = 1; - } - - if(!ether.begin(ETHER_BUFFER_SIZE, (uint8_t*)tmp_buffer, PIN_ETHER_CS)) return 0; - // calculate http port number - ether.hisport = (unsigned int)(options[OPTION_HTTPPORT_1]<<8) + (unsigned int)options[OPTION_HTTPPORT_0]; - - if (options[OPTION_USE_DHCP]) { - // set up DHCP - // register with domain name "OS-xx" where xx is the last byte of the MAC address - if (!ether.dhcpSetup()) return 0; - // once we have valid DHCP IP, we write these into static IP / gateway IP - memcpy(options+OPTION_STATIC_IP1, ether.myip, 4); - memcpy(options+OPTION_GATEWAY_IP1, ether.gwip,4); - memcpy(options+OPTION_DNS_IP1, ether.dnsip, 4); - options_save(); - - } else { - // set up static IP - byte *staticip = options+OPTION_STATIC_IP1; - byte *gateway = options+OPTION_GATEWAY_IP1; - byte *dns = options+OPTION_DNS_IP1; - if (!ether.staticSetup(staticip, gateway, dns)) return 0; - } - return 1; -} + return (Ethernet.linkStatus()==LinkON); #endif +} /** Reboot controller */ -void OpenSprinkler::reboot_dev() { - lcd_print_line_clear_pgm(PSTR("Rebooting..."), 0); -#ifdef ESP8266 - ESP.restart(); +void OpenSprinkler::reboot_dev(uint8_t cause) { + lcd_print_line_clear_pgm(PSTR("Rebooting..."), 0); + if(cause) { + nvdata.reboot_cause = cause; + nvdata_save(); + } +#if defined(ESP8266) + ESP.restart(); + //ESP.reset(); #else - resetFunc(); + resetFunc(); #endif } @@ -511,42 +617,75 @@ void OpenSprinkler::reboot_dev() { #include "etherport.h" #include #include +#include +#include #include "utils.h" -#include "server.h" - -extern EthernetServer *m_server; -extern char ether_buffer[]; +#include "opensprinkler_server.h" /** Initialize network with the given mac address and http port */ byte OpenSprinkler::start_network() { - unsigned int port = (unsigned int)(options[OPTION_HTTPPORT_1]<<8) + (unsigned int)options[OPTION_HTTPPORT_0]; + unsigned int port = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; #if defined(DEMO) - port = 80; + port = 80; #endif - if(m_server) { - delete m_server; - m_server = 0; - } + if(m_server) { delete m_server; m_server = 0; } + + m_server = new EthernetServer(port); + return m_server->begin(); +} - m_server = new EthernetServer(port); - return m_server->begin(); +bool OpenSprinkler::network_connected(void) { + return true; +} + +// 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(byte* mac, bool wired) { + const char * if_names[] = { "eth0", "eth1", "wlan0", "wlan1" }; + struct ifreq ifr; + int fd; + + // Fallback to asoftware mac if interface not recognised + mac[0] = 0x00; + mac[1] = 0x69; + mac[2] = 0x69; + mac[3] = 0x2D; + mac[4] = 0x31; + mac[5] = iopts[IOPT_DEVICE_ID]; + + if (m_server == NULL) return true; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) return true; + + // Returns the mac address of the first interface if multiple active + for (int i = 0; i < sizeof(if_names)/sizeof(const char *); i++) { + strncpy(ifr.ifr_name, if_names[i], sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) != -1) { + memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); + break; + } + } + close(fd); + return true; } /** Reboot controller */ -void OpenSprinkler::reboot_dev() { +void OpenSprinkler::reboot_dev(uint8_t cause) { + nvdata.reboot_cause = cause; + nvdata_save(); #if defined(DEMO) - // do nothing + // do nothing #else - sync(); // add sync to prevent file corruption + sync(); // add sync to prevent file corruption reboot(RB_AUTOBOOT); #endif } /** Launch update script */ void OpenSprinkler::update_dev() { - char cmd[1024]; - sprintf(cmd, "cd %s & ./updater.sh", get_runtime_path()); - system(cmd); + char cmd[1000]; + sprintf(cmd, "cd %s & ./updater.sh", get_runtime_path()); + system(cmd); } #endif // end network init functions @@ -554,505 +693,447 @@ void OpenSprinkler::update_dev() { /** Initialize LCD */ void OpenSprinkler::lcd_start() { -#ifdef ESP8266 - // initialize SSD1306 - lcd.init(); - lcd.begin(); - flash_screen(); +#if defined(ESP8266) + // initialize SSD1306 + lcd.init(); + lcd.begin(); + flash_screen(); #else - // 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 - #if OS_HW_VERSION==(OS_HW_VERSION_BASE+20) || OS_HW_VERSION==(OS_HW_VERSION_BASE+21) // 8MHz and 12MHz - TCCR1B = 0x01; - #else // 16MHz - TCCR1B = 0x02; // increase division factor for faster clock - #endif - // turn on LCD backlight and contrast - lcd_set_brightness(); - lcd_set_contrast(); - } else { - // for I2C LCD, we don't need to do anything - } + // 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(); -extern void flow_poll(); +//extern void flow_isr(); + /** Initialize pins, controller variables, LCD */ void OpenSprinkler::begin() { #if defined(ARDUINO) - Wire.begin(); // init I2C + Wire.begin(); // init I2C #endif - hw_type = HW_TYPE_UNKNOWN; - hw_rev = 0; - + hw_type = HW_TYPE_UNKNOWN; + hw_rev = 0; + #if defined(ESP8266) - if(detect_i2c(ACDR_I2CADDR)) hw_type = HW_TYPE_AC; - else if(detect_i2c(DCDR_I2CADDR)) hw_type = HW_TYPE_DC; - else if(detect_i2c(LADR_I2CADDR)) hw_type = HW_TYPE_LATCH; - - /* detect hardware revision type */ - if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists - DEBUG_PRINTLN("PCF8574 detected"); - /* assign revision 0 pins */ - PIN_BUTTON_1 = V0_PIN_BUTTON_1; - PIN_BUTTON_2 = V0_PIN_BUTTON_2; - PIN_BUTTON_3 = V0_PIN_BUTTON_3; - PIN_RFRX = V0_PIN_RFRX; - PIN_RFTX = V0_PIN_RFTX; - PIN_BOOST = V0_PIN_BOOST; - PIN_BOOST_EN = V0_PIN_BOOST_EN; - PIN_SENSOR1 = V0_PIN_SENSOR1; - PIN_SENSOR2 = V0_PIN_SENSOR2; - PIN_RAINSENSOR = V0_PIN_RAINSENSOR; - PIN_SOILSENSOR = V0_PIN_SOILSENSOR; - PIN_FLOWSENSOR = V0_PIN_FLOWSENSOR; - PIN_RAINSENSOR2 = V0_PIN_RAINSENSOR2; - PIN_SOILSENSOR2 = V0_PIN_SOILSENSOR2; - PIN_FLOWSENSOR2 = V0_PIN_FLOWSENSOR2; - - // on revision 0, main IOEXP and driver IOEXP are two separate PCF8574 chips - if(hw_type==HW_TYPE_DC) { - drio = new PCF8574(DCDR_I2CADDR); - } else if(hw_type==HW_TYPE_LATCH) { - drio = new PCF8574(LADR_I2CADDR); - } else { - drio = new PCF8574(ACDR_I2CADDR); - } - - mainio = new PCF8574(MAIN_I2CADDR); - mainio->i2c_write(0, 0x0F); // set lower four bits of main PCF8574 (8-ch) to high - /*pcf_write(MAIN_I2CADDR, 0x0F);*/ - - digitalWriteExt(V0_PIN_PWR_TX, 1); // turn on TX power - digitalWriteExt(V0_PIN_PWR_RX, 1); // turn on RX power - pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); - digitalWriteExt(PIN_BOOST, LOW); - digitalWriteExt(PIN_BOOST_EN, LOW); - digitalWriteExt(PIN_LATCH_COM, LOW); - - } else { - - if(hw_type==HW_TYPE_DC) { - drio = new PCA9555(DCDR_I2CADDR); - } else if(hw_type==HW_TYPE_LATCH) { - drio = new PCA9555(LADR_I2CADDR); - } else { - drio = new PCA9555(ACDR_I2CADDR); - } - mainio = drio; - - pinMode(16, INPUT); - if(digitalRead(16)==LOW) { - // revision 1 - hw_rev = 1; - mainio->i2c_write(NXP_CONFIG_REG, V1_IO_CONFIG); - mainio->i2c_write(NXP_OUTPUT_REG, V1_IO_OUTPUT); - - /* assign revision 1 pins */ - PIN_BUTTON_1 = V1_PIN_BUTTON_1; - PIN_BUTTON_2 = V1_PIN_BUTTON_2; - PIN_BUTTON_3 = V1_PIN_BUTTON_3; - PIN_RFRX = V1_PIN_RFRX; - PIN_RFTX = V1_PIN_RFTX; - PIN_IOEXP_INT = V1_PIN_IOEXP_INT; - PIN_BOOST = V1_PIN_BOOST; - PIN_BOOST_EN = V1_PIN_BOOST_EN; - PIN_LATCH_COM = V1_PIN_LATCH_COM; - PIN_SENSOR1 = V1_PIN_SENSOR1; - PIN_SENSOR2 = V1_PIN_SENSOR2; - PIN_RAINSENSOR = V1_PIN_RAINSENSOR; - PIN_SOILSENSOR = V1_PIN_SOILSENSOR; - PIN_FLOWSENSOR = V1_PIN_FLOWSENSOR; - PIN_RAINSENSOR2 = V1_PIN_RAINSENSOR2; - PIN_SOILSENSOR2 = V1_PIN_SOILSENSOR2; - PIN_FLOWSENSOR2 = V1_PIN_FLOWSENSOR2; - } else { - // revision 2 - hw_rev = 2; - mainio->i2c_write(NXP_CONFIG_REG, V2_IO_CONFIG); - mainio->i2c_write(NXP_OUTPUT_REG, V2_IO_OUTPUT); - - PIN_BUTTON_1 = V2_PIN_BUTTON_1; - PIN_BUTTON_2 = V2_PIN_BUTTON_2; - PIN_BUTTON_3 = V2_PIN_BUTTON_3; - PIN_RFTX = V2_PIN_RFTX; - PIN_BOOST = V2_PIN_BOOST; - PIN_BOOST_EN = V2_PIN_BOOST_EN; - PIN_SENSOR1 = V2_PIN_SENSOR1; - PIN_SENSOR2 = V2_PIN_SENSOR2; - PIN_RAINSENSOR = V2_PIN_RAINSENSOR; - PIN_SOILSENSOR = V2_PIN_SOILSENSOR; - PIN_FLOWSENSOR = V2_PIN_FLOWSENSOR; - PIN_RAINSENSOR2 = V2_PIN_RAINSENSOR2; - PIN_SOILSENSOR2 = V2_PIN_SOILSENSOR2; - PIN_FLOWSENSOR2 = V2_PIN_FLOWSENSOR2; - } - } - - for(byte i=0;i<(MAX_EXT_BOARDS+1)/2;i++) - expanders[i] = NULL; - detect_expanders(); + /* check hardware type */ + if(detect_i2c(ACDR_I2CADDR)) hw_type = HW_TYPE_AC; + else if(detect_i2c(DCDR_I2CADDR)) hw_type = HW_TYPE_DC; + else if(detect_i2c(LADR_I2CADDR)) hw_type = HW_TYPE_LATCH; + + /* detect hardware revision type */ + if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists + /* assign revision 0 pins */ + PIN_BUTTON_1 = V0_PIN_BUTTON_1; + PIN_BUTTON_2 = V0_PIN_BUTTON_2; + PIN_BUTTON_3 = V0_PIN_BUTTON_3; + PIN_RFRX = V0_PIN_RFRX; + PIN_RFTX = V0_PIN_RFTX; + PIN_BOOST = V0_PIN_BOOST; + PIN_BOOST_EN = V0_PIN_BOOST_EN; + PIN_SENSOR1 = V0_PIN_SENSOR1; + PIN_SENSOR2 = V0_PIN_SENSOR2; + + // on revision 0, main IOEXP and driver IOEXP are two separate PCF8574 chips + if(hw_type==HW_TYPE_DC) { + drio = new PCF8574(DCDR_I2CADDR); + } else if(hw_type==HW_TYPE_LATCH) { + drio = new PCF8574(LADR_I2CADDR); + } else { + drio = new PCF8574(ACDR_I2CADDR); + } + + mainio = new PCF8574(MAIN_I2CADDR); + mainio->i2c_write(0, 0x0F); // set lower four bits of main PCF8574 (8-ch) to high + + digitalWriteExt(V0_PIN_PWR_TX, 1); // turn on TX power + digitalWriteExt(V0_PIN_PWR_RX, 1); // turn on RX power + pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); + digitalWriteExt(PIN_BOOST, LOW); + digitalWriteExt(PIN_BOOST_EN, LOW); + digitalWriteExt(PIN_LATCH_COM, LOW); + + } else { + + if(hw_type==HW_TYPE_DC) { + drio = new PCA9555(DCDR_I2CADDR); + } else if(hw_type==HW_TYPE_LATCH) { + drio = new PCA9555(LADR_I2CADDR); + } else { + drio = new PCA9555(ACDR_I2CADDR); + } + mainio = drio; + + pinMode(16, INPUT); + if(digitalRead(16)==LOW) { + // revision 1 + hw_rev = 1; + mainio->i2c_write(NXP_CONFIG_REG, V1_IO_CONFIG); + mainio->i2c_write(NXP_OUTPUT_REG, V1_IO_OUTPUT); + + PIN_BUTTON_1 = V1_PIN_BUTTON_1; + PIN_BUTTON_2 = V1_PIN_BUTTON_2; + PIN_BUTTON_3 = V1_PIN_BUTTON_3; + PIN_RFRX = V1_PIN_RFRX; + PIN_RFTX = V1_PIN_RFTX; + PIN_IOEXP_INT = V1_PIN_IOEXP_INT; + PIN_BOOST = V1_PIN_BOOST; + PIN_BOOST_EN = V1_PIN_BOOST_EN; + PIN_LATCH_COM = V1_PIN_LATCH_COM; + PIN_SENSOR1 = V1_PIN_SENSOR1; + PIN_SENSOR2 = V1_PIN_SENSOR2; + } else { + // revision 2 + hw_rev = 2; + mainio->i2c_write(NXP_CONFIG_REG, V2_IO_CONFIG); + mainio->i2c_write(NXP_OUTPUT_REG, V2_IO_OUTPUT); + + PIN_BUTTON_1 = V2_PIN_BUTTON_1; + PIN_BUTTON_2 = V2_PIN_BUTTON_2; + PIN_BUTTON_3 = V2_PIN_BUTTON_3; + PIN_RFTX = V2_PIN_RFTX; + PIN_BOOST = V2_PIN_BOOST; + PIN_BOOST_EN = V2_PIN_BOOST_EN; + PIN_LATCH_COMK = V2_PIN_LATCH_COMK; // os3.2latch uses H-bridge separate cathode and anode design + PIN_LATCH_COMA = V2_PIN_LATCH_COMA; + PIN_SENSOR1 = V2_PIN_SENSOR1; + PIN_SENSOR2 = V2_PIN_SENSOR2; + } + } + /* detect expanders */ + for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) + 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 + + // 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 // Reset all stations - clear_all_station_bits(); - apply_all_station_bits(); + clear_all_station_bits(); + apply_all_station_bits(); -#ifdef ESP8266 - pinModeExt(PIN_SENSOR1, INPUT_PULLUP); - pinModeExt(PIN_SENSOR2, INPUT_PULLUP); -#else - // pull shift register OE low to enable output - digitalWrite(PIN_SR_OE, LOW); - // Rain sensor port set up - pinMode(PIN_RAINSENSOR, INPUT_PULLUP); -#endif +#if defined(ESP8266) + // OS 3.0 has two independent sensors + pinModeExt(PIN_SENSOR1, INPUT_PULLUP); + pinModeExt(PIN_SENSOR2, INPUT_PULLUP); - // Set up sensors -#if defined(ARDUINO) - #if defined(ESP8266) - /* todo: handle two sensors */ - if(mainio->type==IOEXP_TYPE_8574) { - attachInterrupt(PIN_FLOWSENSOR, flow_isr, FALLING); - } else if(mainio->type==IOEXP_TYPE_9555) { - if(hw_rev==1) { - mainio->i2c_read(NXP_INPUT_REG); // do a read to clear out current interrupt flag - attachInterrupt(PIN_IOEXP_INT, flow_isr, FALLING); - } else if(hw_rev==2) { - attachInterrupt(PIN_FLOWSENSOR, flow_isr, FALLING); - } - } - #else - //digitalWrite(PIN_RAINSENSOR, HIGH); // enabled internal pullup on rain sensor - attachInterrupt(PIN_FLOWSENSOR_INT, flow_isr, FALLING); - #endif #else - // OSPI and OSBO use external pullups - attachInterrupt(PIN_FLOWSENSOR, "falling", flow_isr); + // pull shift register OE low to enable output + 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 + // Static variables are assigned 0 by default + // so only need to initialize non-zero ones + status.enabled = 1; + status.safe_reboot = 0; + + old_status = status; + + nvdata.sunrise_time = 360; // 6:00am default sunrise + nvdata.sunset_time = 1080; // 6:00pm default sunset + nvdata.reboot_cause = REBOOT_CAUSE_POWERON; + + nboards = 1; + nstations = nboards*8; + + // set rf data pin + pinModeExt(PIN_RFTX, OUTPUT); + digitalWriteExt(PIN_RFTX, LOW); + +#if defined(ARDUINO) // AVR SD and LCD functions + + #if defined(ESP8266) // OS3.0 specific detections + + status.has_curr_sense = 1; // OS3.0 has current sensing capacility + // measure baseline current + baseline_current = 80; - // Default controller status variables - // Static variables are assigned 0 by default - // so only need to initialize non-zero ones - status.enabled = 1; - status.safe_reboot = 0; - - old_status = status; - - nvdata.sunrise_time = 360; // 6:00am default sunrise - nvdata.sunset_time = 1080; // 6:00pm default sunset - - nboards = 1; - nstations = 8; - - // set rf data pin - pinModeExt(PIN_RFTX, OUTPUT); - digitalWriteExt(PIN_RFTX, LOW); - -#if defined(ARDUINO) // AVR SD and LCD functions - - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // OS 2.3 specific detections - uint8_t ret; - - // detect hardware type - ret = detect_i2c(MAC_CTRL_ID); - if (!ret) { - Wire.requestFrom(MAC_CTRL_ID, 1); - ret = Wire.read(); - if (ret == HW_TYPE_AC || ret == HW_TYPE_DC || ret == HW_TYPE_LATCH) { - hw_type = ret; - } else { - // hardware type is not assigned - } - } - - 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; - - #elif defined(ESP8266) // OS3.0 specific detections - - status.has_curr_sense = 1; // OS3.0 has current sensing capacility - // measure baseline current - baseline_current = 100; - #endif - - lcd_start(); - - #if !defined(ESP8266) - // define lcd custom icons - byte _icon[8]; - // WiFi icon - _icon[0] = B00000; - _icon[1] = B10100; - _icon[2] = B01000; - _icon[3] = B10101; - _icon[4] = B00001; - _icon[5] = B00101; - _icon[6] = B00101; - _icon[7] = B10101; - lcd.createChar(1, _icon); - - _icon[1]=0; - _icon[2]=0; - _icon[3]=1; - lcd.createChar(0, _icon); - - // uSD card icon - _icon[1] = B00000; - _icon[2] = B11111; - _icon[3] = B10001; - _icon[4] = B11111; - _icon[5] = B10001; - _icon[6] = B10011; - _icon[7] = B11110; - lcd.createChar(2, _icon); - - // Rain icon - _icon[2] = B00110; - _icon[3] = B01001; - _icon[4] = B11111; - _icon[5] = B00000; - _icon[6] = B10101; - _icon[7] = B10101; - lcd.createChar(3, _icon); - - // Connect icon - _icon[2] = B00111; - _icon[3] = B00011; - _icon[4] = B00101; - _icon[5] = B01000; - _icon[6] = B10000; - _icon[7] = B00000; - lcd.createChar(4, _icon); - - // Remote extension icon - _icon[2] = B00000; - _icon[3] = B10001; - _icon[4] = B01011; - _icon[5] = B00101; - _icon[6] = B01001; - _icon[7] = B11110; - lcd.createChar(5, _icon); - - // Flow sensor icon - _icon[2] = B00000; - _icon[3] = B11010; - _icon[4] = B10010; - _icon[5] = B11010; - _icon[6] = B10011; - _icon[7] = B00000; - lcd.createChar(6, _icon); - - // Program switch icon - _icon[1] = B11100; - _icon[2] = B10100; - _icon[3] = B11100; - _icon[4] = B10010; - _icon[5] = B10110; - _icon[6] = B00010; - _icon[7] = B00111; - lcd.createChar(7, _icon); - - // 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)) { - status.has_sd = 1; - } - #else - /* create custom characters */ - lcd.createChar(0, _iconimage_connected); - lcd.createChar(1, _iconimage_disconnected); - lcd.createChar(2, _iconimage_sdcard); - lcd.createChar(3, _iconimage_rain); - lcd.createChar(4, _iconimage_connect); - lcd.createChar(5, _iconimage_remotext); - lcd.createChar(6, _iconimage_flow); - lcd.createChar(7, _iconimage_pswitch); - - lcd.setCursor(0,0); - lcd.print(F("Init file system")); - lcd.setCursor(0,1); - if(!SPIFFS.begin()) { - DEBUG_PRINTLN(F("SPIFFS failed")); - status.has_sd = 0; - } else { - status.has_sd = 1; - } - - state = OS_STATE_INITIAL; - #endif - - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - if(!status.has_sd) { - lcd.setCursor(0, 0); - lcd_print_pgm(PSTR("Error Code: 0x2D")); - while(1){} - } - #endif - - // set button pins - // enable internal pullup - pinMode(PIN_BUTTON_1, INPUT_PULLUP); - pinMode(PIN_BUTTON_2, INPUT_PULLUP); - pinMode(PIN_BUTTON_3, INPUT_PULLUP); - - // detect and check RTC type - RTC.detect(); + #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); + byte 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 + + lcd_start(); + + lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); + lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_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); + + #if defined(ESP8266) + + /* create custom characters */ + lcd.createChar(ICON_WIFI_CONNECTED, _iconimage_wifi_connected); + lcd.createChar(ICON_WIFI_DISCONNECTED, _iconimage_wifi_disconnected); + + 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){} + } + + #endif + + // set button pins + // enable internal pullup + pinMode(PIN_BUTTON_1, INPUT_PULLUP); + pinMode(PIN_BUTTON_2, INPUT_PULLUP); + pinMode(PIN_BUTTON_3, INPUT_PULLUP); + + // detect and check RTC type + RTC.detect(); #else - status.has_sd = 1; - DEBUG_PRINTLN(get_runtime_path()); + DEBUG_PRINTLN(get_runtime_path()); #endif } -#ifdef ESP8266 +#if defined(ESP8266) /** LATCH boost voltage * */ void OpenSprinkler::latch_boost() { - digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter - delay((int)options[OPTION_BOOST_TIME]<<2); // wait for booster to charge - digitalWriteExt(PIN_BOOST, LOW); // disable boost converter + digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter + delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge + digitalWriteExt(PIN_BOOST, LOW); // disable boost converter } /** Set all zones (for LATCH controller) - * This function sets all zone pins (including COM) to a specified value + * This function sets all zone pins (including COM) to a specified value */ void OpenSprinkler::latch_setallzonepins(byte value) { - digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin - // Handle driver board (on main controller) - if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board - else reg &= 0xFF00; - drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - // Handle all expansion boards - for(byte i=0;itype==IOEXP_TYPE_9555) { - expanders[i]->i2c_write(NXP_OUTPUT_REG, value?0xFFFF:0x0000); - } - } + digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin + // Handle driver board (on main controller) + if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board + else reg &= 0xFF00; + drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + // Handle all expansion boards + for(byte i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, value?0xFFFF:0x0000); + } + } +} + +void OpenSprinkler::latch_disable_alloutputs_v2() { + digitalWriteExt(PIN_LATCH_COMA, LOW); + digitalWriteExt(PIN_LATCH_COMK, LOW); + + // latch v2 has a pca9555 the lowest 8 bits of which control all h-bridge anode pins + drio->i2c_write(NXP_OUTPUT_REG, drio->i2c_read(NXP_OUTPUT_REG) & 0xFF00); + // latch v2 has a 74hc595 which controls all h-bridge cathode pins + drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, 0x00); + + // todo: handle expander } /** Set one zone (for LATCH controller) - * This function sets one specified zone pin to a specified value + * This function sets one specified zone pin to a specified value */ void OpenSprinkler::latch_setzonepin(byte sid, byte value) { - if(sid<8) { // on main controller - if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - } else { // on expander - byte bid=(sid-8)>>4; - uint16_t s=(sid-8)&0x0F; - if(expanders[bid]->type==IOEXP_TYPE_9555) { - uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); - } - } + if(sid<8) { // on main controller + if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + } else { // on expander + byte bid=(sid-8)>>4; + uint16_t s=(sid-8)&0x0F; + if(expanders[bid]->type==IOEXP_TYPE_9555) { + uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + } + } +} + +void OpenSprinkler::latch_setzoneoutput_v2(byte sid, byte A, byte K) { + if(A==HIGH && K==HIGH) return; // A and K must not be HIGH at the same time + + if(sid<8) { // on main controller + // v2 latch driver has one PCA9555, the lowest 8-bits of which control all anode pins + // and one 74HC595, which controls all cathod pins + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); + if(A) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + + drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, K ? (1<>3; - byte s=i&0x07; - byte mask=(byte)1<>3; + byte s=i&0x07; + byte mask=(byte)1<type==IOEXP_TYPE_8574) { - /* revision 0 uses PCF8574 with active low logic, so all bits must be flipped */ - drio->i2c_write(NXP_OUTPUT_REG, ~station_bits[0]); - } else if(drio->type==IOEXP_TYPE_9555) { - /* revision 1 uses PCA9555 with active high logic */ - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - reg = (reg&0xFF00) | station_bits[0]; // output channels are the low 8-bit - drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - - // Handle expansion boards - for(int i=0;itype==IOEXP_TYPE_9555) { - expanders[i]->i2c_write(NXP_OUTPUT_REG, data); - } else { - expanders[i]->i2c_write(NXP_OUTPUT_REG, ~data); - } - } - } - - byte bid, s, sbits; +#if defined(ESP8266) + if(hw_type==HW_TYPE_LATCH) { + // if controller type is latching, the control mechanism is different + // hence will be handled separately + latch_apply_all_station_bits(); + } else { + // Handle DC booster + if(hw_type==HW_TYPE_DC && engage_booster) { + // for DC controller: boost voltage and enable output path + digitalWriteExt(PIN_BOOST_EN, LOW); // disfable output path + digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter + delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge + digitalWriteExt(PIN_BOOST, LOW); // disable boost converter + digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path + engage_booster = 0; + } + + // Handle driver board (on main controller) + if(drio->type==IOEXP_TYPE_8574) { + /* revision 0 uses PCF8574 with active low logic, so all bits must be flipped */ + drio->i2c_write(NXP_OUTPUT_REG, ~station_bits[0]); + } else if(drio->type==IOEXP_TYPE_9555) { + /* revision 1 uses PCA9555 with active high logic */ + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + reg = (reg&0xFF00) | station_bits[0]; // output channels are the low 8-bit + drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + + // Handle expansion boards + for(int i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, data); + } else { + expanders[i]->i2c_write(NXP_OUTPUT_REG, ~data); + } + } + } + + byte bid, s, sbits; #else - digitalWrite(PIN_SR_LATCH, LOW); - byte bid, s, sbits; - - // Shift out all station bit values - // from the highest bit to the lowest - for(bid=0;bid<=MAX_EXT_BOARDS;bid++) { - if (status.enabled) - sbits = station_bits[MAX_EXT_BOARDS-bid]; - else - sbits = 0; - - for(s=0;s<8;s++) { - digitalWrite(PIN_SR_CLOCK, LOW); - #if defined(OSPI) // if OSPI, use dynamically assigned pin_sr_data - digitalWrite(pin_sr_data, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); - #else - digitalWrite(PIN_SR_DATA, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); - #endif - digitalWrite(PIN_SR_CLOCK, HIGH); - } - } + digitalWrite(PIN_SR_LATCH, LOW); + byte bid, s, sbits; + + // Shift out all station bit values + // from the highest bit to the lowest + for(bid=0;bid<=MAX_EXT_BOARDS;bid++) { + if (status.enabled) + sbits = station_bits[MAX_EXT_BOARDS-bid]; + else + sbits = 0; + + for(s=0;s<8;s++) { + digitalWrite(PIN_SR_CLOCK, LOW); + #if defined(OSPI) // if OSPI, use dynamically assigned pin_sr_data + digitalWrite(pin_sr_data, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); + #else + digitalWrite(PIN_SR_DATA, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); + #endif + digitalWrite(PIN_SR_CLOCK, HIGH); + } + } - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - 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)options[OPTION_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 + #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(options[OPTION_SPE_AUTO_REFRESH]) { - // handle refresh of RF and remote stations - // we refresh the station whose index is the current time modulo MAX_NUM_STATIONS - static byte last_sid = 0; - byte sid = now() % MAX_NUM_STATIONS; - if (sid != last_sid) { // avoid refreshing the same station twice in a roll - last_sid = sid; - bid=sid>>3; - s=sid&0x07; - switch_special_station(sid, (station_bits[bid]>>s)&0x01); - } - } + if(iopts[IOPT_SPE_AUTO_REFRESH]) { + // handle refresh of RF and remote stations + // we refresh the station that's next in line + static byte next_sid_to_refresh = MAX_NUM_STATIONS>>1; + static byte lastnow = 0; + byte _now = (now() & 0xFF); + if (lastnow != _now) { // perform this no more than once per second + lastnow = _now; + next_sid_to_refresh = (next_sid_to_refresh+1) % MAX_NUM_STATIONS; + bid=next_sid_to_refresh>>3; + s=next_sid_to_refresh&0x07; + switch_special_station(next_sid_to_refresh, (station_bits[bid]>>s)&0x01); + } + } } /** Read rain sensor status */ -void OpenSprinkler::rainsensor_status() { - // options[OPTION_RS_TYPE]: 0 if normally closed, 1 if normally open - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN) - status.rain_sensed = (digitalReadExt(PIN_RAINSENSOR) == options[OPTION_SENSOR1_OPTION] ? 0 : 1); -#if defined(ESP8266) - if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_RAIN) - status.rain_sensed = (digitalReadExt(PIN_RAINSENSOR2) == options[OPTION_SENSOR2_OPTION] ? 0 : 1); -#endif -} +void OpenSprinkler::detect_binarysensor_status(ulong curr_time) { + // sensor_type: 0 if normally closed, 1 if normally open + if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { + if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + byte val = digitalReadExt(PIN_SENSOR1); + status.sensor1 = (val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1; + if(status.sensor1) { + if(!sensor1_on_timer) { + // add minimum of 5 seconds on delay + ulong delay_time = (ulong)iopts[IOPT_SENSOR1_ON_DELAY]*60; + sensor1_on_timer = curr_time + (delay_time>5?delay_time:5); + sensor1_off_timer = 0; + } else { + if(curr_time > sensor1_on_timer) { + status.sensor1_active = 1; + } + } + } else { + if(!sensor1_off_timer) { + ulong delay_time = (ulong)iopts[IOPT_SENSOR1_OFF_DELAY]*60; + sensor1_off_timer = curr_time + (delay_time>5?delay_time:5); + sensor1_on_timer = 0; + } else { + if(curr_time > sensor1_off_timer) { + status.sensor1_active = 0; + } + } + } + } + +// 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) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 + byte val = digitalReadExt(PIN_SENSOR2); + status.sensor2 = (val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1; + if(status.sensor2) { + if(!sensor2_on_timer) { + // add minimum of 5 seconds on delay + ulong delay_time = (ulong)iopts[IOPT_SENSOR2_ON_DELAY]*60; + sensor2_on_timer = curr_time + (delay_time>5?delay_time:5); + sensor2_off_timer = 0; + } else { + if(curr_time > sensor2_on_timer) { + status.sensor2_active = 1; + } + } + } else { + if(!sensor2_off_timer) { + ulong delay_time = (ulong)iopts[IOPT_SENSOR2_OFF_DELAY]*60; + sensor2_off_timer = curr_time + (delay_time>5?delay_time:5); + sensor2_on_timer = 0; + } else { + if(curr_time > sensor2_off_timer) { + status.sensor2_active = 0; + } + } + } + } -/** Read soil moisture sensor status */ -void OpenSprinkler::soil_moisture_sensor_status() { - // options[OPTION_RS_TYPE]: 0 if normally closed, 1 if normally open - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) - status.soil_moisture_sensed = (digitalReadExt(PIN_SOILSENSOR) == options[OPTION_SENSOR1_OPTION] ? 0 : 1); -#if defined(ESP8266) - if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) - status.soil_moisture_sensed = (digitalReadExt(PIN_SOILSENSOR2) == options[OPTION_SENSOR2_OPTION] ? 0 : 1); #endif } - /** Return program switch status */ -bool OpenSprinkler::programswitch_status(ulong curr_time) { - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { - static ulong keydown_time = 0; - byte val = digitalReadExt(PIN_RAINSENSOR); - if(!val && !keydown_time) keydown_time = curr_time; - else if(val && keydown_time && (curr_time > keydown_time)) { - keydown_time = 0; - return true; - } - } -#if defined(ESP8266) - if(options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { - static ulong keydown_time = 0; - byte val = digitalReadExt(PIN_RAINSENSOR2); - if(!val && !keydown_time) keydown_time = curr_time; - else if(val && keydown_time && (curr_time > keydown_time)) { - keydown_time = 0; - return true; - } - } +byte OpenSprinkler::detect_programswitch_status(ulong curr_time) { + byte ret = 0; + if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { + static byte sensor1_hist = 0; + if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + status.sensor1 = (digitalReadExt(PIN_SENSOR1) != iopts[IOPT_SENSOR1_OPTION]); // is switch activated? + sensor1_hist = (sensor1_hist<<1) | status.sensor1; + // basic noise filtering: only trigger if sensor matches pattern: + // i.e. two consecutive lows followed by two consecutive highs + if((sensor1_hist&0b1111) == 0b0011) { + ret |= 0x01; + } + } +#if defined(ESP8266) || defined(PIN_SENSOR2) + if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { + static byte sensor2_hist = 0; + if(hw_rev==2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 + status.sensor2 = (digitalReadExt(PIN_SENSOR2) != iopts[IOPT_SENSOR2_OPTION]); // is sensor activated? + sensor2_hist = (sensor2_hist<<1) | status.sensor2; + if((sensor2_hist&0b1111) == 0b0011) { + ret |= 0x02; + } + } #endif - return false; + return ret; +} + +void OpenSprinkler::sensor_resetall() { + sensor1_on_timer = 0; + sensor1_off_timer = 0; + sensor1_active_lasttime = 0; + sensor2_on_timer = 0; + sensor2_off_timer = 0; + sensor2_active_lasttime = 0; + old_status.sensor1_active = status.sensor1_active = 0; + old_status.sensor2_active = status.sensor2_active = 0; } + /** Read current sensing value * OpenSprinkler 2.3 and above have a 0.2 ohm current sensing resistor. * Therefore the conversion from analog reading to milli-amp is: @@ -1215,36 +1354,36 @@ bool OpenSprinkler::programswitch_status(ulong curr_time) { * ESP8266's analog reference voltage is 1.0 instead of 3.3, therefore * it's further discounted by 1/3.3 */ -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) +#if defined(ARDUINO) uint16_t OpenSprinkler::read_current() { - float scale = 1.0f; - if(status.has_curr_sense) { - if (hw_type == HW_TYPE_DC) { - #if defined(ESP8266) - scale = 4.88; - #else - scale = 16.11; - #endif - } else if (hw_type == HW_TYPE_AC) { - #if defined(ESP8266) - scale = 3.45; - #else - scale = 11.39; - #endif - } else { - scale = 0.0; // for other controllers, current is 0 - } - /* do an average */ - const byte K = 5; - uint16_t sum = 0; - for(byte i=0;i=0;n--) { - if(detect_i2c(EXP_I2CADDR_BASE+n)) break; - } - return (n+1)*2; - #else - unsigned int v = analogRead(PIN_EXP_SENSE); - // OpenSprinkler uses voltage divider to detect expansion boards - // Master controller has a 1.6K pull-up; - // each expansion board (8 stations) has 10K pull-down connected in parallel; - // so the exact ADC value for n expansion boards is: - // ADC = 1024 * 10 / (10 + 1.6 * n) - // For 0, 1, 2, 3, 4, 5, 6 expansion boards, the ADC values are: - // 1024, 882, 775, 691, 624, 568, 522 - // Actual threshold is taken as the midpoint between, to account for errors - int n = -1; - if (v > 953) { // 0 - n = 0; - } else if (v > 828) { // 1 - n = 1; - } else if (v > 733) { // 2 - n = 2; - } else if (v > 657) { // 3 - n = 3; - } else if (v > 596) { // 4 - n = 4; - } else if (v > 545) { // 5 - n = 5; - } else if (v > 502) { // 6 - n = 6; - } else { // cannot determine - } - return n; - #endif + #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; + return -1; #endif } /** Convert hex code to ulong integer */ static ulong hex2ulong(byte *code, byte len) { - char c; - ulong v = 0; - for(byte i=0;i='0' && c<='9') { - v += (c-'0'); - } else if (c>='A' && c<='F') { - v += 10 + (c-'A'); - } else if (c>='a' && c<='f') { - v += 10 + (c-'a'); - } else { - return 0; - } - } - return v; + char c; + ulong v = 0; + for(byte i=0;i='0' && c<='9') { + v += (c-'0'); + } else if (c>='A' && c<='F') { + v += 10 + (c-'A'); + } else if (c>='a' && c<='f') { + v += 10 + (c-'a'); + } else { + return 0; + } + } + return v; } /** Parse RF code into on/off/timeing sections */ uint16_t OpenSprinkler::parse_rfstation_code(RFStationData *data, ulong* on, ulong *off) { - ulong v; - v = hex2ulong(data->on, sizeof(data->on)); - if (!v) return 0; - if (on) *on = v; + ulong v; + v = hex2ulong(data->on, sizeof(data->on)); + if (!v) return 0; + if (on) *on = v; v = hex2ulong(data->off, sizeof(data->off)); - if (!v) return 0; - if (off) *off = v; + if (!v) return 0; + if (off) *off = v; v = hex2ulong(data->timing, sizeof(data->timing)); - if (!v) return 0; - return v; + if (!v) return 0; + return v; } -/** Get station name from NVM */ -void OpenSprinkler::get_station_name(byte sid, char tmp[]) { - tmp[STATION_NAME_SIZE]=0; - nvm_read_block(tmp, (void*)(ADDR_NVM_STN_NAMES+(int)sid*STATION_NAME_SIZE), STATION_NAME_SIZE); +/** Get station data */ +void OpenSprinkler::get_station_data(byte sid, StationData* data) { + file_read_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); } -/** Set station name to NVM */ -void OpenSprinkler::set_station_name(byte sid, char tmp[]) { - tmp[STATION_NAME_SIZE]=0; - nvm_write_block(tmp, (void*)(ADDR_NVM_STN_NAMES+(int)sid*STATION_NAME_SIZE), STATION_NAME_SIZE); +/** Set station data */ +void OpenSprinkler::set_station_data(byte sid, StationData* data) { + file_write_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); } -/** Save station attribute bits to NVM */ -void OpenSprinkler::station_attrib_bits_save(int addr, byte bits[]) { - nvm_write_block(bits, (void*)addr, MAX_EXT_BOARDS+1); +/** Get station name */ +void OpenSprinkler::get_station_name(byte sid, char tmp[]) { + tmp[STATION_NAME_SIZE]=0; + file_read_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); } -/** Load all station attribute bits from NVM */ -void OpenSprinkler::station_attrib_bits_load(int addr, byte bits[]) { - nvm_read_block(bits, (void*)addr, MAX_EXT_BOARDS+1); +/** Set station name */ +void OpenSprinkler::set_station_name(byte sid, char tmp[]) { + // todo: store the right size + tmp[STATION_NAME_SIZE]=0; + file_write_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); +} + +/** Get station type */ +byte OpenSprinkler::get_station_type(byte sid) { + return file_read_byte(STATIONS_FILENAME, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type)); +} + +/** Get station attribute */ +/*void OpenSprinkler::get_station_attrib(byte sid, StationAttrib *attrib); { + file_read_block(STATIONS_FILENAME, attrib, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); +}*/ + +/** Save all station attribs to file (backward compatibility) */ +void OpenSprinkler::attribs_save() { + // re-package attribute bits and save + byte bid, s, sid=0; + StationAttrib at, at0; + byte ty = STN_TYPE_STANDARD, ty0; + for(bid=0;bid>s) & 1; + at.igs = (attrib_igs[bid]>>s) & 1; + at.mas2= (attrib_mas2[bid]>>s)& 1; + at.igs2= (attrib_igs2[bid]>>s) & 1; + at.igrd= (attrib_igrd[bid]>>s) & 1; + at.dis = (attrib_dis[bid]>>s) & 1; + at.seq = (attrib_seq[bid]>>s) & 1; + at.gid = 0; + // only write if content has changed + file_read_block(STATIONS_FILENAME, &at0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), 1); + if(*((byte*)&at) != *((byte*)&at0)) + file_write_block(STATIONS_FILENAME, &at, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), 1); // attribte bits are 1 byte long + if(attrib_spe[bid]>>s==0) { + // if station special bit is 0, make sure to write type STANDARD + // only write if content has changed + file_read_block(STATIONS_FILENAME, &ty0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); + if(ty!=ty0) + file_write_block(STATIONS_FILENAME, &ty, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); // attribte bits are 1 byte long + } + } + } } -/** Read one station attribute byte from NVM */ -byte OpenSprinkler::station_attrib_bits_read(int addr) { - return nvm_read_byte((byte*)addr); +/** Load all station attribs from file (backward compatibility) */ +void OpenSprinkler::attribs_load() { + // load and re-package attributes + byte bid, s, sid=0; + StationAttrib at; + byte ty; + memset(attrib_mas, 0, nboards); + memset(attrib_igs, 0, nboards); + memset(attrib_mas2, 0, nboards); + memset(attrib_igs2, 0, nboards); + memset(attrib_igrd, 0, nboards); + memset(attrib_dis, 0, nboards); + memset(attrib_seq, 0, nboards); + memset(attrib_spe, 0, nboards); + + for(bid=0;bid>3))&(1<<(sid&0x07))) { - // read station special data from sd card - int stepsize=sizeof(StationSpecialData); - read_from_file(stns_filename, tmp_buffer, stepsize, sid*stepsize); - StationSpecialData *stn = (StationSpecialData *)tmp_buffer; - // check station type - if(stn->type==STN_TYPE_RF) { - // transmit RF signal - switch_rfstation((RFStationData *)stn->data, value); - } else if(stn->type==STN_TYPE_REMOTE) { - // request remote station - switch_remotestation((RemoteStationData *)stn->data, value); - } -#if !defined(ARDUINO) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) - // GPIO and HTTP stations are only available for OS23 or OSPi - else if(stn->type==STN_TYPE_GPIO) { - // set GPIO pin - switch_gpiostation((GPIOStationData *)stn->data, value); - } else if(stn->type==STN_TYPE_HTTP) { - // send GET command - switch_httpstation((HTTPStationData *)stn->data, value); - } -#endif - } + // check if this is a special station + byte stype = get_station_type(sid); + if(stype!=STN_TYPE_STANDARD) { + // read station data + StationData *pdata=(StationData*) tmp_buffer; + get_station_data(sid, pdata); + switch(stype) { + + case STN_TYPE_RF: + switch_rfstation((RFStationData *)pdata->sped, value); + break; + + case STN_TYPE_REMOTE: + switch_remotestation((RemoteStationData *)pdata->sped, value); + break; + + case STN_TYPE_GPIO: + switch_gpiostation((GPIOStationData *)pdata->sped, value); + break; + + case STN_TYPE_HTTP: + switch_httpstation((HTTPStationData *)pdata->sped, value); + break; + } + } } /** Set station bit @@ -1424,40 +1601,36 @@ void OpenSprinkler::switch_special_station(byte sid, byte value) { * (which results in physical actions of opening/closing valves). */ byte OpenSprinkler::set_station_bit(byte sid, byte value) { - byte *data = station_bits+(sid>>3); // pointer to the station byte - byte mask = (byte)1<<(sid&0x07); // mask - if (value) { - if((*data)&mask) return 0; // if bit is already set, return no change - else { - (*data) = (*data) | mask; -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) - engage_booster = true; // if bit is changing from 0 to 1, set engage_booster -#endif - switch_special_station(sid, 1); // handle special stations - return 1; - } - } else { - if(!((*data)&mask)) return 0; // if bit is already reset, return no change - else { - (*data) = (*data) & (~mask); -#if defined(ESP8266) - if(hw_type == HW_TYPE_LATCH) { - engage_booster = true; // if LATCH controller, engage booster when bit changes - } -#endif - switch_special_station(sid, 0); // handle special stations - return 255; - } - } - return 0; + byte *data = station_bits+(sid>>3); // pointer to the station byte + byte mask = (byte)1<<(sid&0x07); // mask + if (value) { + if((*data)&mask) return 0; // if bit is already set, return no change + else { + (*data) = (*data) | mask; + engage_booster = true; // if bit is changing from 0 to 1, set engage_booster + switch_special_station(sid, 1); // handle special stations + return 1; + } + } else { + if(!((*data)&mask)) return 0; // if bit is already reset, return no change + else { + (*data) = (*data) & (~mask); + if(hw_type == HW_TYPE_LATCH) { + engage_booster = true; // if LATCH controller, engage booster when bit changes + } + switch_special_station(sid, 0); // handle special stations + return 255; + } + } + return 0; } /** Clear all station bits */ void OpenSprinkler::clear_all_station_bits() { - byte sid; - for(sid=0;sid<=MAX_NUM_STATIONS;sid++) { - set_station_bit(sid, 0); - } + byte sid; + for(sid=0;sid<=MAX_NUM_STATIONS;sid++) { + set_station_bit(sid, 0); + } } #if !defined(ARDUINO) @@ -1467,43 +1640,43 @@ int rf_gpio_fd = -1; /** Transmit one RF signal bit */ void transmit_rfbit(ulong lenH, ulong lenL) { #if defined(ARDUINO) - #ifdef ESP8266 - digitalWrite(PIN_RFTX, 1); - delayMicroseconds(lenH); - digitalWrite(PIN_RFTX, 0); - delayMicroseconds(lenL); - #else - PORT_RF |= (1<=0) { - if ((code>>i) & 1) { - transmit_rfbit(len3, len); - } else { - transmit_rfbit(len, len3); - } - i--; - }; - // send sync - transmit_rfbit(len, len31); - } + ulong len3 = len * 3; + ulong len31 = len * 31; + for(byte n=0;n<15;n++) { + int i=23; + // send code + while(i>=0) { + if ((code>>i) & 1) { + transmit_rfbit(len3, len); + } else { + transmit_rfbit(len, len3); + } + i--; + }; + // send sync + transmit_rfbit(len, len31); + } } /** Switch RF station @@ -1512,23 +1685,23 @@ void send_rfsignal(ulong code, ulong len) { * and sends it out through RF transmitter. */ void OpenSprinkler::switch_rfstation(RFStationData *data, bool turnon) { - ulong on, off; - uint16_t length = parse_rfstation_code(data, &on, &off); + ulong on, off; + uint16_t length = parse_rfstation_code(data, &on, &off); #if defined(ARDUINO) - #ifdef ESP8266 - rfswitch.enableTransmit(PIN_RFTX); - rfswitch.setProtocol(1); - rfswitch.setPulseLength(length); - rfswitch.send(turnon ? on : off, 24); - #else - send_rfsignal(turnon ? on : off, length); - #endif + #if defined(ESP8266) + rfswitch.enableTransmit(PIN_RFTX); + rfswitch.setProtocol(1); + rfswitch.setPulseLength(length); + rfswitch.send(turnon ? on : off, 24); + #else + send_rfsignal(turnon ? on : off, length); + #endif #else - // pre-open gpio file to minimize overhead - rf_gpio_fd = gpio_fd_open(PIN_RFTX); - send_rfsignal(turnon ? on : off, length); - gpio_fd_close(rf_gpio_fd); - rf_gpio_fd = -1; + // pre-open gpio file to minimize overhead + rf_gpio_fd = gpio_fd_open(PIN_RFTX); + send_rfsignal(turnon ? on : off, length); + gpio_fd_close(rf_gpio_fd); + rf_gpio_fd = -1; #endif } @@ -1539,22 +1712,153 @@ void OpenSprinkler::switch_rfstation(RFStationData *data, bool turnon) { * Third byte is either 0 or 1 for active low (GND) or high (+5V) relays */ void OpenSprinkler::switch_gpiostation(GPIOStationData *data, bool turnon) { - byte gpio = (data->pin[0] - '0') * 10 + (data->pin[1] - '0'); - byte activeState = data->active - '0'; + byte gpio = (data->pin[0] - '0') * 10 + (data->pin[1] - '0'); + byte activeState = data->active - '0'; + + pinMode(gpio, OUTPUT); + if (turnon) + digitalWrite(gpio, activeState); + else + digitalWrite(gpio, 1-activeState); +} - pinMode(gpio, OUTPUT); - if (turnon) - digitalWrite(gpio, activeState); - else - digitalWrite(gpio, 1-activeState); +/** Callback function for switching remote station */ +void remote_http_callback(char* buffer) { +/* + DEBUG_PRINTLN(buffer); +*/ } -/** Callback function for browseUrl calls */ -void httpget_callback(byte status, uint16_t off, uint16_t len) { -#if defined(SERIAL_DEBUG) - Ethernet::buffer[off+ETHER_BUFFER_SIZE-1] = 0; - DEBUG_PRINTLN((const char*) Ethernet::buffer + off); +extern void ip2string(char* str, byte ip[4]); + +int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { + +#if defined(ARDUINO) + + Client *client; + #if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; + #else + EthernetClient etherClient; + client = ðerClient; + #endif + + IPAddress _ip; + if (!WiFi.hostByName(server, _ip, timeout)) { + DEBUG_PRINT("DNS resolve Error! "); + DEBUG_PRINT(server); + DEBUG_PRINTLN(" ?"); + return HTTP_RQT_DNS_ERROR; + } + + DEBUG_PRINT(server); + DEBUG_PRINT("="); + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + char postval[16] = ""; + ip2string(postval, ip); + DEBUG_PRINTLN(postval); + + #define HTTP_CONNECT_NTRIES 5 + byte tries = 0; + client->setTimeout(timeout); + do { + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINT(port); + DEBUG_PRINT("("); + DEBUG_PRINT(tries); + DEBUG_PRINTLN(")"); + + if(client->connect(ip, port)==1) break; + tries++; + } while(triesstop(); + return HTTP_RQT_CONNECT_ERR; + } +#else + + EthernetClient etherClient; + EthernetClient *client = ðerClient; + struct hostent *host; + host = gethostbyname(server); + if (!host) { return HTTP_RQT_CONNECT_ERR; } + if(!client->connect((uint8_t*)host->h_addr, port)) { + DEBUG_PRINT(F("Cannot connect to ")); + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINTLN(port); + client->stop(); + return HTTP_RQT_CONNECT_ERR; + } + #endif + + uint16_t len = strlen(p); + if(len > ETHER_BUFFER_SIZE) len = ETHER_BUFFER_SIZE; + if(client->connected()) { + client->write((uint8_t *)p, len); + } else { + DEBUG_PRINTLN(F("clint no longer connected")); + } + memset(ether_buffer, 0, ETHER_BUFFER_SIZE); + uint32_t stoptime = millis()+timeout; + +/*#if defined(ARDUINO) + while(client->available()==0) { + if(millis()>stoptime) { + client->stop(); + return HTTP_RQT_TIMEOUT; + } + } + int nbytes = client->available(); + if(nbytes>0) { + if(nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE; + client->read((uint8_t*)ether_buffer, nbytes); + } + +#else + while(client->connected()) { + int len=client->read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); + if (len<=0) continue; + if(millis()>stoptime) { + client->stop(); + return HTTP_RQT_TIMEOUT; + } + } +#endif*/ + + while(client->available()==0) { + if(millis()>stoptime) { + client->stop(); + return HTTP_RQT_TIMEOUT; + } + } + int nbytes = client->available(); + if(nbytes>0) { + if(nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE; + client->read((uint8_t*)ether_buffer, nbytes); + } + + client->stop(); + if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN; + if(callback) callback(ether_buffer); + return HTTP_RQT_SUCCESS; +} + +int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip4>>24, (ip4>>16)&0xff, (ip4>>8)&0xff, ip4&0xff); + return send_http_request(server, port, p, callback, timeout); +} + +int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { + char * server = strtok(server_with_port, ":"); + char * port = strtok(NULL, ":"); + return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); } /** Switch remote station @@ -1565,113 +1869,34 @@ void httpget_callback(byte status, uint16_t off, uint16_t len) { * password as the main controller */ void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon) { -#if defined(ARDUINO) + RemoteStationData copy; + memcpy((char*)©, (char*)data, sizeof(RemoteStationData)); - ulong ip = hex2ulong(data->ip, sizeof(data->ip)); - ulong port = hex2ulong(data->port, sizeof(data->port)); - - #ifdef ESP8266 - WiFiClient client; - - char *p = tmp_buffer + sizeof(RemoteStationData) + 1; - BufferFiller bf = p; - // MAX_NUM_STATIONS is the refresh cycle - uint16_t timer = options[OPTION_SPE_AUTO_REFRESH]?2*MAX_NUM_STATIONS:64800; - bf.emit_p(PSTR("GET /cm?pw=$E&sid=$D&en=$D&t=$D"), - ADDR_NVM_PASSWORD, - (int)hex2ulong(data->sid, sizeof(data->sid)), - turnon, timer); - bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: *\r\n\r\n")); - - byte cip[4]; - cip[0] = ip>>24; - cip[1] = (ip>>16)&0xff; - cip[2] = (ip>>8)&0xff; - cip[3] = ip&0xff; - - if(!client.connect(IPAddress(cip), port)) return; - client.write((uint8_t *)p, strlen(p)); - - bzero(ether_buffer, ETHER_BUFFER_SIZE); - - time_t timeout = now_tz() + 5; // 5 seconds timeout - while(!client.available() && now_tz() < timeout) { - } + uint32_t ip4 = hex2ulong(copy.ip, sizeof(copy.ip)); + uint16_t port = (uint16_t)hex2ulong(copy.port, sizeof(copy.port)); - bzero(ether_buffer, ETHER_BUFFER_SIZE); - while(client.available()) { - client.read((uint8_t*)ether_buffer, ETHER_BUFFER_SIZE); - } - client.stop(); - //httpget_callback(0, 0, ETHER_BUFFER_SIZE); - - #else - - ether.hisip[0] = ip>>24; - ether.hisip[1] = (ip>>16)&0xff; - ether.hisip[2] = (ip>>8)&0xff; - ether.hisip[3] = ip&0xff; - - uint16_t _port = ether.hisport; // save current port number - ether.hisport = port; - - char *p = tmp_buffer + sizeof(RemoteStationData) + 1; - BufferFiller bf = (byte*)p; - // MAX_NUM_STATIONS is the refresh cycle - uint16_t timer = options[OPTION_SPE_AUTO_REFRESH]?2*MAX_NUM_STATIONS:64800; - bf.emit_p(PSTR("?pw=$E&sid=$D&en=$D&t=$D"), - ADDR_NVM_PASSWORD, - (int)hex2ulong(data->sid,sizeof(data->sid)), - turnon, timer); - ether.browseUrl(PSTR("/cm"), p, PSTR("*"), httpget_callback); - for(int l=0;l<100;l++) ether.packetLoop(ether.packetReceive()); - ether.hisport = _port; - #endif - -#else - EthernetClient client; - - uint8_t hisip[4]; - uint16_t hisport; - ulong ip = hex2ulong(data->ip, sizeof(data->ip)); - hisip[0] = ip>>24; - hisip[1] = (ip>>16)&0xff; - hisip[2] = (ip>>8)&0xff; - hisip[3] = ip&0xff; - hisport = hex2ulong(data->port, sizeof(data->port)); - - if (!client.connect(hisip, hisport)) { - client.stop(); - return; - } + byte ip[4]; + ip[0] = ip4>>24; + ip[1] = (ip4>>16)&0xff; + ip[2] = (ip4>>8)&0xff; + ip[3] = ip4&0xff; + + // use tmp_buffer starting at a later location + // because remote station data is loaded at the beginning + char *p = tmp_buffer; + BufferFiller bf = p; + // if auto refresh is enabled, we give a fixed duration each time, and auto refresh will renew it periodically + // if no auto refresh, we will give the maximum allowed duration, and station will be turned off when off command is sent + uint16_t timer = iopts[IOPT_SPE_AUTO_REFRESH]?4*MAX_NUM_STATIONS:64800; + bf.emit_p(PSTR("GET /cm?pw=$O&sid=$D&en=$D&t=$D"), + SOPT_PASSWORD, + (int)hex2ulong(copy.sid, sizeof(copy.sid)), + turnon, timer); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), + ip[0],ip[1],ip[2],ip[3]); + + send_http_request(ip4, port, p, remote_http_callback); - char *p = tmp_buffer + sizeof(RemoteStationData) + 1; - BufferFiller bf = p; - // MAX_NUM_STATIONS is the refresh cycle - uint16_t timer = options[OPTION_SPE_AUTO_REFRESH]?2*MAX_NUM_STATIONS:64800; - bf.emit_p(PSTR("GET /cm?pw=$E&sid=$D&en=$D&t=$D"), - ADDR_NVM_PASSWORD, - (int)hex2ulong(data->sid, sizeof(data->sid)), - turnon, timer); - bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: *\r\n\r\n")); - - client.write((uint8_t *)p, strlen(p)); - - bzero(ether_buffer, ETHER_BUFFER_SIZE); - - time_t timeout = now() + 5; // 5 seconds timeout - while(now() < timeout) { - int len=client.read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); - if (len<=0) { - if(!client.connected()) - break; - else - continue; - } - httpget_callback(0, 0, ETHER_BUFFER_SIZE); - } - client.stop(); -#endif } /** Switch http station @@ -1680,350 +1905,289 @@ void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon) { */ void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon) { - static HTTPStationData copy; - // make a copy of the HTTP station data and work with it - memcpy((char*)©, (char*)data, sizeof(HTTPStationData)); - char * server = strtok((char *)copy.data, ","); - char * port = strtok(NULL, ","); - char * on_cmd = strtok(NULL, ","); - char * off_cmd = strtok(NULL, ","); - char * cmd = turnon ? on_cmd : off_cmd; + HTTPStationData copy; + // make a copy of the HTTP station data and work with it + memcpy((char*)©, (char*)data, sizeof(HTTPStationData)); + char * server = strtok((char *)copy.data, ","); + char * port = strtok(NULL, ","); + char * on_cmd = strtok(NULL, ","); + char * off_cmd = strtok(NULL, ","); + char * cmd = turnon ? on_cmd : off_cmd; -#if defined(ARDUINO) + char *p = tmp_buffer; + BufferFiller bf = p; - #ifdef ESP8266 - - WiFiClient client; - if(!client.connect(server, atoi(port))) return; - - char getBuffer[255]; - sprintf(getBuffer, "GET /%s HTTP/1.0\r\nHOST: *\r\n\r\n", cmd); - - DEBUG_PRINTLN(getBuffer); - - client.write((uint8_t *)getBuffer, strlen(getBuffer)); - - bzero(ether_buffer, ETHER_BUFFER_SIZE); - - time_t timeout = now_tz() + 5; // 5 seconds timeout - while(!client.available() && now_tz() < timeout) { - } + if(cmd==NULL || server==NULL) return; // proceed only if cmd and server are valid - bzero(ether_buffer, ETHER_BUFFER_SIZE); - while(client.available()) { - client.read((uint8_t*)ether_buffer, ETHER_BUFFER_SIZE); - } - client.stop(); - - #else - - if(!ether.dnsLookup(server, true)) { - char *ip0 = strtok(server, "."); - char *ip1 = strtok(NULL, "."); - char *ip2 = strtok(NULL, "."); - char *ip3 = strtok(NULL, "."); - - ether.hisip[0] = ip0 ? atoi(ip0) : 0; - ether.hisip[1] = ip1 ? atoi(ip1) : 0; - ether.hisip[2] = ip2 ? atoi(ip2) : 0; - ether.hisip[3] = ip3 ? atoi(ip3) : 0; - } + bf.emit_p(PSTR("GET /$S HTTP/1.0\r\nHOST: $S\r\n\r\n"), cmd, server); + + send_http_request(server, atoi(port), p, remote_http_callback); +} - uint16_t _port = ether.hisport; - ether.hisport = atoi(port); - ether.browseUrlRamHost(PSTR("/"), cmd, server, httpget_callback); - for(int l=0;l<100;l++) ether.packetLoop(ether.packetReceive()); - ether.hisport = _port; - #endif - +/** Prepare factory reset */ +void OpenSprinkler::pre_factory_reset() { + // for ESP8266: wipe out flash + #if defined(ESP8266) + lcd_print_line_clear_pgm(PSTR("Wiping flash.."), 0); + lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); + LittleFS.format(); + #else + // remove 'done' file as an indicator for reset + // todo os2.3 and ospi: delete log files and/or wipe SD card + remove_file(DONE_FILENAME); + #endif +} + +/** Factory reset */ +void OpenSprinkler::factory_reset() { +#if defined(ARDUINO) + lcd_print_line_clear_pgm(PSTR("Factory reset"), 0); + lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); #else + DEBUG_PRINT("factory reset..."); +#endif + + // 1. reset integer options (by saving default values) + iopts_save(); + // reset string options by first wiping the file clean then write default values + memset(tmp_buffer, 0, MAX_SOPTS_SIZE); + for(int i=0; iname[0]='S'; + pdata->name[3]=0; + pdata->name[4]=0; + StationAttrib at; + memset(&at, 0, sizeof(StationAttrib)); + at.mas=1; + at.seq=1; + pdata->attrib=at; // mas:1 seq:1 + pdata->type=STN_TYPE_STANDARD; + pdata->sped[0]='0'; + pdata->sped[1]=0; + for(int i=0; iname[1]='0'+(sid/10); // default station name + pdata->name[2]='0'+(sid%10); + } else { + pdata->name[1]='0'+(sid/100); + pdata->name[2]='0'+((sid%100)/10); + pdata->name[3]='0'+(sid%10); + } + file_write_block(STATIONS_FILENAME, pdata, sizeof(StationData)*i, sizeof(StationData)); + } - host = gethostbyname(server); - if (!host) { - DEBUG_PRINT("can't resolve http station - "); - DEBUG_PRINTLN(server); - return; - } + attribs_load(); // load and repackage attrib bits (for backward compatibility) - if (!client.connect((uint8_t*)host->h_addr, atoi(port))) { - client.stop(); - return; - } + // 3. write non-volatile controller status + nvdata.reboot_cause = REBOOT_CAUSE_RESET; + nvdata_save(); + last_reboot_cause = nvdata.reboot_cause; - char getBuffer[255]; - sprintf(getBuffer, "GET /%s HTTP/1.0\r\nHOST: %s\r\n\r\n", cmd, host->h_name); - client.write((uint8_t *)getBuffer, strlen(getBuffer)); - - bzero(ether_buffer, ETHER_BUFFER_SIZE); - - time_t timeout = now() + 5; // 5 seconds timeout - while(now() < timeout) { - int len=client.read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); - if (len<=0) { - if(!client.connected()) - break; - else - continue; - } - httpget_callback(0, 0, ETHER_BUFFER_SIZE); - } + // 4. write program data: just need to write a program counter: 0 + file_write_byte(PROG_FILENAME, 0, 0); - client.stop(); -#endif + // 5. write 'done' file + file_write_byte(DONE_FILENAME, 0, 1); } /** Setup function for options */ void OpenSprinkler::options_setup() { - // add 0.25 second delay to allow nvm to stablize - delay(250); - - byte curr_ver = nvm_read_byte((byte*)(ADDR_NVM_OPTIONS+OPTION_FW_VERSION)); - - // check reset condition: either firmware version has changed, or reset flag is up - // if so, trigger a factory reset - if (curr_ver != OS_FW_VERSION || nvm_read_byte((byte*)(ADDR_NVM_OPTIONS+OPTION_RESET))==0xAA) { -#if defined(ARDUINO) - lcd_print_line_clear_pgm(PSTR("Resetting..."), 0); - lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); -#else - DEBUG_PRINT("resetting options..."); -#endif - // ======== Reset NVM data ======== - int i, sn; - -#ifdef ESP8266 - //if(curr_ver!=0) // if SPIFFS has been written before, perform a full format - SPIFFS.format(); // perform a SPIFFS format -#endif - // 0. wipe out nvm - for(i=0;iTMP_BUFFER_SIZE)?TMP_BUFFER_SIZE:(NVM_SIZE-i); - nvm_write_block(tmp_buffer, (void*)i, nbytes); - } - - // 1. write non-volatile controller status - nvdata_save(); - - // 2. write string parameters - nvm_write_block(DEFAULT_PASSWORD, (void*)ADDR_NVM_PASSWORD, strlen(DEFAULT_PASSWORD)+1); - nvm_write_block(DEFAULT_LOCATION, (void*)ADDR_NVM_LOCATION, strlen(DEFAULT_LOCATION)+1); - nvm_write_block(DEFAULT_JAVASCRIPT_URL, (void*)ADDR_NVM_JAVASCRIPTURL, strlen(DEFAULT_JAVASCRIPT_URL)+1); - nvm_write_block(DEFAULT_WEATHER_URL, (void*)ADDR_NVM_WEATHERURL, strlen(DEFAULT_WEATHER_URL)+1); - nvm_write_block(DEFAULT_WEATHER_KEY, (void*)ADDR_NVM_WEATHER_KEY, strlen(DEFAULT_WEATHER_KEY)+1); - - // 3. reset station names and special attributes, default Sxx - tmp_buffer[0]='S'; - tmp_buffer[3]=0; - for(i=ADDR_NVM_STN_NAMES, sn=1; i"), 0); - lcd_print_line_clear_pgm(PSTR("Hold B3 to save"), 1); - do { - button = button_read(BUTTON_WAIT_NONE); - } while (!(button & BUTTON_FLAG_DOWN)); - lcd.clear(); - ui_set_options(0); - if (options[OPTION_RESET]) { - reboot_dev(); - } - break; - } + // if BUTTON_3 is pressed during startup, enter Setup option mode + lcd_print_line_clear_pgm(PSTR("==Set Options=="), 0); + delay(DISPLAY_MSG_MS); + lcd_print_line_clear_pgm(PSTR("B1/B2:+/-, B3:->"), 0); + lcd_print_line_clear_pgm(PSTR("Hold B3 to save"), 1); + do { + button = button_read(BUTTON_WAIT_NONE); + } while (!(button & BUTTON_FLAG_DOWN)); + lcd.clear(); + ui_set_options(0); + if (iopts[IOPT_RESET]) { + pre_factory_reset(); + reboot_dev(REBOOT_CAUSE_RESET); + } + break; + } - // turn on LCD backlight and contrast - lcd_set_brightness(); - lcd_set_contrast(); - - if (!button) { - // flash screen - lcd_print_line_clear_pgm(PSTR(" OpenSprinkler"),0); - lcd.setCursor((hw_type==HW_TYPE_LATCH)?2:4, 1); - lcd_print_pgm(PSTR("v")); - byte hwv = options[OPTION_HW_VERSION]; - lcd.print((char)('0'+(hwv/10))); - lcd.print('.'); - #ifdef ESP8266 - lcd.print(hw_rev); - #else - lcd.print((char)('0'+(hwv%10))); - #endif - switch(hw_type) { - case HW_TYPE_DC: - lcd_print_pgm(PSTR(" DC")); - break; - case HW_TYPE_LATCH: - lcd_print_pgm(PSTR(" LATCH")); - break; - default: - lcd_print_pgm(PSTR(" AC")); - } - delay(1500); - #ifdef ESP8266 - lcd.setCursor(2, 1); - lcd_print_pgm(PSTR("FW ")); - lcd.print((char)('0'+(OS_FW_VERSION/100))); - lcd.print('.'); - lcd.print((char)('0'+((OS_FW_VERSION/10)%10))); - lcd.print('.'); - lcd.print((char)('0'+(OS_FW_VERSION%10))); - lcd.print('('); - lcd.print(OS_FW_MINOR); - lcd.print(')'); - delay(1000); - #endif - } + // turn on LCD backlight and contrast + lcd_set_brightness(); + lcd_set_contrast(); + + if (!button) { + // flash screen + lcd_print_line_clear_pgm(PSTR(" OpenSprinkler"),0); + lcd.setCursor((hw_type==HW_TYPE_LATCH)?2:4, 1); + lcd_print_pgm(PSTR("v")); + byte hwv = iopts[IOPT_HW_VERSION]; + lcd.print((char)('0'+(hwv/10))); + lcd.print('.'); + #if defined(ESP8266) + lcd.print(hw_rev); + #else + lcd.print((char)('0'+(hwv%10))); + #endif + switch(hw_type) { + case HW_TYPE_DC: + lcd_print_pgm(PSTR(" DC")); + break; + case HW_TYPE_LATCH: + lcd_print_pgm(PSTR(" LATCH")); + break; + default: + lcd_print_pgm(PSTR(" AC")); + } + delay(1500); + #if defined(ARDUINO) + lcd.setCursor(2, 1); + lcd_print_pgm(PSTR("FW ")); + lcd.print((char)('0'+(OS_FW_VERSION/100))); + lcd.print('.'); + lcd.print((char)('0'+((OS_FW_VERSION/10)%10))); + lcd.print('.'); + lcd.print((char)('0'+(OS_FW_VERSION%10))); + lcd.print('('); + lcd.print(OS_FW_MINOR); + lcd.print(')'); + delay(1000); + #endif + } #endif } -/** Load non-volatile controller status data from internal NVM */ +/** Load non-volatile controller status data from file */ void OpenSprinkler::nvdata_load() { - nvm_read_block(&nvdata, (void*)ADDR_NVM_NVCONDATA, sizeof(NVConData)); - old_status = status; + file_read_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); + old_status = status; } -/** Save non-volatile controller status data to internal NVM */ +/** Save non-volatile controller status data */ void OpenSprinkler::nvdata_save() { - nvm_write_block(&nvdata, (void*)ADDR_NVM_NVCONDATA, sizeof(NVConData)); + file_write_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); +} + +/** Load integer options from file */ +void OpenSprinkler::iopts_load() { + file_read_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); + nboards = iopts[IOPT_EXT_BOARDS]+1; + nstations = nboards * 8; + status.enabled = iopts[IOPT_DEVICE_ENABLE]; + iopts[IOPT_FW_VERSION] = OS_FW_VERSION; + iopts[IOPT_FW_MINOR] = OS_FW_MINOR; + /* Reject the former default 50.97.210.169 NTP IP address as + * it no longer works, yet is carried on by people's saved + * configs when they upgrade from older versions. + * IOPT_NTP_IP1 = 0 leads to the new good default behavior. */ + if (iopts[IOPT_NTP_IP1] == 50 && iopts[IOPT_NTP_IP2] == 97 && + iopts[IOPT_NTP_IP3] == 210 && iopts[IOPT_NTP_IP4] == 169) { + iopts[IOPT_NTP_IP1] = 0; + iopts[IOPT_NTP_IP2] = 0; + iopts[IOPT_NTP_IP3] = 0; + iopts[IOPT_NTP_IP4] = 0; + } } -/** Load options from internal NVM */ -void OpenSprinkler::options_load() { - nvm_read_block(tmp_buffer, (void*)ADDR_NVM_OPTIONS, NUM_OPTIONS); - for (byte i=0; i=0; i--) { - tmp_buffer[i] = options[i]; - } - nvm_write_block(tmp_buffer, (void*)ADDR_NVM_OPTIONS, NUM_OPTIONS); - nboards = options[OPTION_EXT_BOARDS]+1; - nstations = nboards * 8; - status.enabled = options[OPTION_DEVICE_ENABLE]; -#ifdef ESP8266 - if(savewifi) { - // save WiFi config - File file = SPIFFS.open(WIFI_FILENAME, "w"); - if(file) { - file.println(wifi_config.mode); - file.println(wifi_config.ssid); - file.println(wifi_config.pass); - file.close(); - } - } -#endif +/** Load a string option from file */ +void OpenSprinkler::sopt_load(byte oid, char *buf) { + file_read_block(SOPTS_FILENAME, buf, MAX_SOPTS_SIZE*oid, MAX_SOPTS_SIZE); + buf[MAX_SOPTS_SIZE]=0; // ensure the string ends properly +} + +/** Load a string option from file, return String */ +String OpenSprinkler::sopt_load(byte oid) { + sopt_load(oid, tmp_buffer); + String str = tmp_buffer; + return str; +} + +/** Save a string option to file */ +bool OpenSprinkler::sopt_save(byte oid, const char *buf) { + // smart save: if value hasn't changed, don't write + if(file_cmp_block(SOPTS_FILENAME, buf, (ulong)MAX_SOPTS_SIZE*oid)==0) return false; + int len = strlen(buf); + if(len>=MAX_SOPTS_SIZE) { + file_write_block(SOPTS_FILENAME, buf, (ulong)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); + } + return true; } // ============================== @@ -2032,504 +2196,535 @@ void OpenSprinkler::options_save(bool savewifi) { /** Enable controller operation */ void OpenSprinkler::enable() { - status.enabled = 1; - options[OPTION_DEVICE_ENABLE] = 1; - options_save(); + status.enabled = 1; + iopts[IOPT_DEVICE_ENABLE] = 1; + iopts_save(); } /** Disable controller operation */ void OpenSprinkler::disable() { - status.enabled = 0; - options[OPTION_DEVICE_ENABLE] = 0; - options_save(); + status.enabled = 0; + iopts[IOPT_DEVICE_ENABLE] = 0; + iopts_save(); } /** Start rain delay */ void OpenSprinkler::raindelay_start() { - status.rain_delayed = 1; - nvdata_save(); + status.rain_delayed = 1; + nvdata_save(); } /** Stop rain delay */ void OpenSprinkler::raindelay_stop() { - status.rain_delayed = 0; - nvdata.rd_stop_time = 0; - nvdata_save(); + status.rain_delayed = 0; + nvdata.rd_stop_time = 0; + nvdata_save(); } /** LCD and button functions */ -#if defined(ARDUINO) // AVR LCD and button functions +#if defined(ARDUINO) // AVR LCD and button functions /** print a program memory string */ -#ifdef ESP8266 +#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); - } + uint8_t c; + while((c=pgm_read_byte(str++))!= '\0') { + lcd.print((char)c); + } } /** print a program memory string to a given line with clearing */ -#ifdef ESP8266 +#if defined(ESP8266) void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P str, byte line) { #else void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P PROGMEM str, byte line) { #endif - lcd.setCursor(0, line); - uint8_t c; - int8_t cnt = 0; - while((c=pgm_read_byte(str++))!= '\0') { - lcd.print((char)c); - cnt++; - } - for(; (16-cnt) >= 0; cnt ++) lcd_print_pgm(PSTR(" ")); + lcd.setCursor(0, line); + uint8_t c; + int8_t cnt = 0; + while((c=pgm_read_byte(str++))!= '\0') { + lcd.print((char)c); + cnt++; + } + for(; (16-cnt) >= 0; cnt ++) lcd_print_pgm(PSTR(" ")); } void OpenSprinkler::lcd_print_2digit(int v) { - lcd.print((int)(v/10)); - lcd.print((int)(v%10)); + lcd.print((int)(v/10)); + lcd.print((int)(v%10)); } /** print time to a given line */ void OpenSprinkler::lcd_print_time(time_t t) { - 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_2digit(month(t)); - lcd_print_pgm(PSTR("-")); - lcd_print_2digit(day(t)); + 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_2digit(month(t)); + lcd_print_pgm(PSTR("-")); + lcd_print_2digit(day(t)); } /** print ip address */ void OpenSprinkler::lcd_print_ip(const byte *ip, byte endian) { -#ifdef ESP8266 - lcd.clear(0, 1); +#if defined(ESP8266) + lcd.clear(0, 1); #else - lcd.clear(); + lcd.clear(); #endif - lcd.setCursor(0, 0); - for (byte i=0; i<4; i++) { - lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); - if(i<3) lcd_print_pgm(PSTR(".")); - } + lcd.setCursor(0, 0); + for (byte i=0; i<4; i++) { + lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); + if(i<3) lcd_print_pgm(PSTR(".")); + } } /** print mac address */ void OpenSprinkler::lcd_print_mac(const byte *mac) { - lcd.setCursor(0, 0); - for(byte i=0; i<6; i++) { - if(i) lcd_print_pgm(PSTR("-")); - lcd.print((mac[i]>>4), HEX); - lcd.print((mac[i]&0x0F), HEX); - if(i==4) lcd.setCursor(0, 1); - } - lcd_print_pgm(PSTR(" (MAC)")); + lcd.setCursor(0, 0); + for(byte i=0; i<6; i++) { + if(i) lcd_print_pgm(PSTR("-")); + lcd.print((mac[i]>>4), HEX); + lcd.print((mac[i]&0x0F), HEX); + if(i==4) lcd.setCursor(0, 1); + } + if(useEth) { + lcd_print_pgm(PSTR(" (Ether MAC)")); + } else { + lcd_print_pgm(PSTR(" (WiFi MAC)")); + } } /** print station bits */ void OpenSprinkler::lcd_print_station(byte line, char c) { - lcd.setCursor(0, line); - if (status.display_board == 0) { - lcd_print_pgm(PSTR("MC:")); // Master controller is display as 'MC' - } - else { - lcd_print_pgm(PSTR("E")); - lcd.print((int)status.display_board); - lcd_print_pgm(PSTR(":")); // extension boards are displayed as E1, E2... - } + lcd.setCursor(0, line); + if (status.display_board == 0) { + lcd_print_pgm(PSTR("MC:")); // Master controller is display as 'MC' + } + else { + lcd_print_pgm(PSTR("E")); + lcd.print((int)status.display_board); + lcd_print_pgm(PSTR(":")); // extension boards are displayed as E1, E2... + } - if (!status.enabled) { - lcd_print_line_clear_pgm(PSTR("-Disabled!-"), 1); - } else { - byte bitvalue = station_bits[status.display_board]; - for (byte s=0; s<8; s++) { - byte sid = (byte)status.display_board<<3; - sid += (s+1); - if (sid == options[OPTION_MASTER_STATION]) { - lcd.print((bitvalue&1) ? c : 'M'); // print master station - } else if (sid == options[OPTION_MASTER_STATION_2]) { - lcd.print((bitvalue&1) ? c : 'N'); // print master2 station - } else { - lcd.print((bitvalue&1) ? c : '_'); - } - bitvalue >>= 1; - } + if (!status.enabled) { + lcd_print_line_clear_pgm(PSTR("-Disabled!-"), 1); + } else { + byte bitvalue = station_bits[status.display_board]; + for (byte s=0; s<8; s++) { + byte sid = (byte)status.display_board<<3; + sid += (s+1); + if (sid == iopts[IOPT_MASTER_STATION]) { + 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 { + lcd.print((bitvalue&1) ? c : '_'); + } + bitvalue >>= 1; + } } lcd_print_pgm(PSTR(" ")); - lcd.setCursor(12, 1); - if(options[OPTION_REMOTE_EXT_MODE]) { - lcd.write(5); - } - lcd.setCursor(13, 1); -#ifdef ESP8266 - if(status.rain_delayed || - (status.rain_sensed && - (options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_RAIN))) { -#else - if(status.rain_delayed || (status.rain_sensed && options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_RAIN)) { -#endif - lcd.write(3); - } -#ifdef ESP8266 - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) { -#else - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { -#endif - if (status.soil_moisture_sensed) - lcd.write(4); //?? - else if (status.soil_moisture_active) - lcd.write(5); //?? - } -#ifdef ESP8266 - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_FLOW) { -#else - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { -#endif - lcd.write(6); - } -#ifdef ESP8266 - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH || options[OPTION_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { + + if(iopts[IOPT_REMOTE_EXT_MODE]) { + lcd.setCursor(LCD_CURSOR_REMOTEXT, 1); + lcd.write(ICON_REMOTEXT); + } + + if(status.rain_delayed) { + lcd.setCursor(LCD_CURSOR_RAINDELAY, 1); + lcd.write(ICON_RAINDELAY); + } + + // write sensor 1 icon + lcd.setCursor(LCD_CURSOR_SENSOR1, 1); + switch(iopts[IOPT_SENSOR1_TYPE]) { + case SENSOR_TYPE_RAIN: + lcd.write(status.sensor1_active?ICON_RAIN:(status.sensor1?'R':'r')); + break; + case SENSOR_TYPE_SOIL: + lcd.write(status.sensor1_active?ICON_SOIL:(status.sensor1?'S':'s')); + break; + case SENSOR_TYPE_FLOW: + lcd.write(flowcount_rt>0?'F':'f'); + break; + case SENSOR_TYPE_PSWITCH: + lcd.write(status.sensor1?'P':'p'); + break; + } + + // write sensor 2 icon + lcd.setCursor(LCD_CURSOR_SENSOR2, 1); + switch(iopts[IOPT_SENSOR2_TYPE]) { + case SENSOR_TYPE_RAIN: + lcd.write(status.sensor2_active?ICON_RAIN:(status.sensor2?'R':'r')); + break; + case SENSOR_TYPE_SOIL: + lcd.write(status.sensor2_active?ICON_SOIL:(status.sensor2?'S':'s')); + break; + // sensor2 cannot be flow sensor + /*case SENSOR_TYPE_FLOW: + lcd.write('F'); + break;*/ + case SENSOR_TYPE_PSWITCH: + lcd.write(status.sensor2?'Q':'q'); + break; + } + + lcd.setCursor(LCD_CURSOR_NETWORK, 1); +#if defined(ESP8266) + if(useEth) { + lcd.write(eth.connected()?ICON_ETHER_CONNECTED:ICON_ETHER_DISCONNECTED); // todo: need to detect ether status + } + else + lcd.write(WiFi.status()==WL_CONNECTED?ICON_WIFI_CONNECTED:ICON_WIFI_DISCONNECTED); #else - if(options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { + lcd.write(status.network_fails>2?ICON_ETHER_DISCONNECTED:ICON_ETHER_CONNECTED); // if network failure detection is more than 2, display disconnect icon #endif - lcd.write(7); - } - lcd.setCursor(14, 1); - if (status.has_sd) lcd.write(2); - - lcd.setCursor(15, 1); - #ifdef ESP8266 - lcd.write(WiFi.status()==WL_CONNECTED?0:1); - #else - lcd.write(status.network_fails>2?1:0); // if network failure detection is more than 2, display disconnect icon - #endif } /** print a version number */ void OpenSprinkler::lcd_print_version(byte v) { - if(v > 99) { - lcd.print(v/100); - lcd.print("."); - } - if(v>9) { - lcd.print((v/10)%10); - lcd.print("."); - } - lcd.print(v%10); + if(v > 99) { + lcd.print(v/100); + lcd.print("."); + } + if(v>9) { + lcd.print((v/10)%10); + lcd.print("."); + } + lcd.print(v%10); } /** print an option value */ void OpenSprinkler::lcd_print_option(int i) { - // each prompt string takes 16 characters - strncpy_P0(tmp_buffer, op_prompts+16*i, 16); - lcd.setCursor(0, 0); - lcd.print(tmp_buffer); - lcd_print_line_clear_pgm(PSTR(""), 1); - lcd.setCursor(0, 1); - int tz; - switch(i) { - case OPTION_HW_VERSION: - lcd.print("v"); - case OPTION_FW_VERSION: - lcd_print_version(options[i]); - break; - case OPTION_TIMEZONE: // if this is the time zone option, do some conversion - tz = (int)options[i]-48; - if (tz>=0) lcd_print_pgm(PSTR("+")); - else {lcd_print_pgm(PSTR("-")); tz=-tz;} - lcd.print(tz/4); // print integer portion - lcd_print_pgm(PSTR(":")); - tz = (tz%4)*15; - if (tz==0) lcd_print_pgm(PSTR("00")); - else { - lcd.print(tz); // print fractional portion - } - break; - case OPTION_MASTER_ON_ADJ: - case OPTION_MASTER_ON_ADJ_2: - case OPTION_MASTER_OFF_ADJ: - case OPTION_MASTER_OFF_ADJ_2: - case OPTION_STATION_DELAY_TIME: - { - int16_t t=water_time_decode_signed(options[i]); - if(t>=0) lcd_print_pgm(PSTR("+")); - lcd.print(t); - } - break; - case OPTION_HTTPPORT_0: - lcd.print((unsigned int)(options[i+1]<<8)+options[i]); - break; - case OPTION_PULSE_RATE_0: - { - uint16_t fpr = (unsigned int)(options[i+1]<<8)+options[i]; - lcd.print(fpr/100); - lcd_print_pgm(PSTR(".")); - lcd.print((fpr/10)%10); - lcd.print(fpr%10); - } - break; - case OPTION_LCD_CONTRAST: - lcd_set_contrast(); - lcd.print((int)options[i]); - break; - case OPTION_LCD_BACKLIGHT: - lcd_set_brightness(); - lcd.print((int)options[i]); - break; - case OPTION_BOOST_TIME: - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) - if(hw_type==HW_TYPE_AC) { - lcd.print('-'); - } else { - lcd.print((int)options[i]*4); - lcd_print_pgm(PSTR(" ms")); - } - #else - lcd.print('-'); - #endif - break; - default: - // if this is a boolean option - if (pgm_read_byte(op_max+i)==1) - lcd_print_pgm(options[i] ? PSTR("Yes") : PSTR("No")); - else - lcd.print((int)options[i]); - break; - } - if (i==OPTION_WATER_PERCENTAGE) lcd_print_pgm(PSTR("%")); - else if (i==OPTION_MASTER_ON_ADJ || i==OPTION_MASTER_OFF_ADJ || i==OPTION_MASTER_ON_ADJ_2 || i==OPTION_MASTER_OFF_ADJ_2) - lcd_print_pgm(PSTR(" sec")); - -} + // each prompt string takes 16 characters + strncpy_P0(tmp_buffer, iopt_prompts+16*i, 16); + lcd.setCursor(0, 0); + lcd.print(tmp_buffer); + lcd_print_line_clear_pgm(PSTR(""), 1); + lcd.setCursor(0, 1); + int tz; + switch(i) { + case IOPT_HW_VERSION: + lcd.print("v"); + case IOPT_FW_VERSION: + lcd_print_version(iopts[i]); + break; + case IOPT_TIMEZONE: // if this is the time zone option, do some conversion + tz = (int)iopts[i]-48; + if (tz>=0) lcd_print_pgm(PSTR("+")); + else {lcd_print_pgm(PSTR("-")); tz=-tz;} + lcd.print(tz/4); // print integer portion + lcd_print_pgm(PSTR(":")); + tz = (tz%4)*15; + if (tz==0) lcd_print_pgm(PSTR("00")); + else { + lcd.print(tz); // print fractional portion + } + break; + case IOPT_MASTER_ON_ADJ: + case IOPT_MASTER_ON_ADJ_2: + case IOPT_MASTER_OFF_ADJ: + case IOPT_MASTER_OFF_ADJ_2: + case IOPT_STATION_DELAY_TIME: + { + int16_t t=water_time_decode_signed(iopts[i]); + if(t>=0) lcd_print_pgm(PSTR("+")); + lcd.print(t); + } + break; + case IOPT_HTTPPORT_0: + lcd.print((unsigned int)(iopts[i+1]<<8)+iopts[i]); + break; + case IOPT_PULSE_RATE_0: + { + uint16_t fpr = (unsigned int)(iopts[i+1]<<8)+iopts[i]; + lcd.print(fpr/100); + lcd_print_pgm(PSTR(".")); + lcd.print((fpr/10)%10); + lcd.print(fpr%10); + } + break; + case IOPT_LCD_CONTRAST: + lcd_set_contrast(); + lcd.print((int)iopts[i]); + break; + case IOPT_LCD_BACKLIGHT: + lcd_set_brightness(); + lcd.print((int)iopts[i]); + break; + case IOPT_BOOST_TIME: + #if defined(ARDUINO) + if(hw_type==HW_TYPE_AC) { + lcd.print('-'); + } else { + lcd.print((int)iopts[i]*4); + lcd_print_pgm(PSTR(" ms")); + } + #else + lcd.print('-'); + #endif + break; + default: + // if this is a boolean option + if (pgm_read_byte(iopt_max+i)==1) + lcd_print_pgm(iopts[i] ? PSTR("Yes") : PSTR("No")); + else + lcd.print((int)iopts[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) + lcd_print_pgm(PSTR(" sec")); +} /** Button functions */ /** wait for button */ byte OpenSprinkler::button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding) { - int hold_time = 0; + int hold_time = 0; - if (waitmode==BUTTON_WAIT_NONE || (waitmode == BUTTON_WAIT_HOLD && is_holding)) { - if (digitalReadExt(pin_butt) != 0) return BUTTON_NONE; - return butt | (is_holding ? BUTTON_FLAG_HOLD : 0); - } + if (waitmode==BUTTON_WAIT_NONE || (waitmode == BUTTON_WAIT_HOLD && is_holding)) { + if (digitalReadExt(pin_butt) != 0) return BUTTON_NONE; + return butt | (is_holding ? BUTTON_FLAG_HOLD : 0); + } - while (digitalReadExt(pin_butt) == 0 && - (waitmode == BUTTON_WAIT_RELEASE || (waitmode == BUTTON_WAIT_HOLD && hold_time= BUTTON_HOLD_MS) - butt |= BUTTON_FLAG_HOLD; - return butt; + while (digitalReadExt(pin_butt) == 0 && + (waitmode == BUTTON_WAIT_RELEASE || (waitmode == BUTTON_WAIT_HOLD && hold_time= BUTTON_HOLD_MS) + butt |= BUTTON_FLAG_HOLD; + return butt; } /** read button and returns button value 'OR'ed with flag bits */ byte OpenSprinkler::button_read(byte waitmode) { - static byte old = BUTTON_NONE; - byte curr = BUTTON_NONE; - byte is_holding = (old&BUTTON_FLAG_HOLD); - - delay(BUTTON_DELAY_MS); - - if (digitalReadExt(PIN_BUTTON_1) == 0) { - curr = button_read_busy(PIN_BUTTON_1, waitmode, BUTTON_1, is_holding); - } else if (digitalReadExt(PIN_BUTTON_2) == 0) { - curr = button_read_busy(PIN_BUTTON_2, waitmode, BUTTON_2, is_holding); - } else if (digitalReadExt(PIN_BUTTON_3) == 0) { - curr = button_read_busy(PIN_BUTTON_3, waitmode, BUTTON_3, is_holding); - } + static byte old = BUTTON_NONE; + byte curr = BUTTON_NONE; + byte is_holding = (old&BUTTON_FLAG_HOLD); + + delay(BUTTON_DELAY_MS); + + if (digitalReadExt(PIN_BUTTON_1) == 0) { + curr = button_read_busy(PIN_BUTTON_1, waitmode, BUTTON_1, is_holding); + } else if (digitalReadExt(PIN_BUTTON_2) == 0) { + curr = button_read_busy(PIN_BUTTON_2, waitmode, BUTTON_2, is_holding); + } else if (digitalReadExt(PIN_BUTTON_3) == 0) { + curr = button_read_busy(PIN_BUTTON_3, waitmode, BUTTON_3, is_holding); + } + + // set flags in return value + byte ret = curr; + if (!(old&BUTTON_MASK) && (curr&BUTTON_MASK)) + ret |= BUTTON_FLAG_DOWN; + if ((old&BUTTON_MASK) && !(curr&BUTTON_MASK)) + ret |= BUTTON_FLAG_UP; - // set flags in return value - byte ret = curr; - if (!(old&BUTTON_MASK) && (curr&BUTTON_MASK)) - ret |= BUTTON_FLAG_DOWN; - if ((old&BUTTON_MASK) && !(curr&BUTTON_MASK)) - ret |= BUTTON_FLAG_UP; + old = curr; - old = curr; - - return ret; + return ret; } /** user interface for setting options during startup */ void OpenSprinkler::ui_set_options(int oid) { - boolean finished = false; - byte button; - int i=oid; - - while(!finished) { - button = button_read(BUTTON_WAIT_HOLD); - - switch (button & BUTTON_MASK) { - case BUTTON_1: - if (i==OPTION_FW_VERSION || i==OPTION_HW_VERSION || i==OPTION_FW_MINOR || - i==OPTION_HTTPPORT_0 || i==OPTION_HTTPPORT_1 || - i==OPTION_PULSE_RATE_0 || i==OPTION_PULSE_RATE_1) break; // ignore non-editable options - if (pgm_read_byte(op_max+i) != options[i]) options[i] ++; - break; - - case BUTTON_2: - if (i==OPTION_FW_VERSION || i==OPTION_HW_VERSION || i==OPTION_FW_MINOR || - i==OPTION_HTTPPORT_0 || i==OPTION_HTTPPORT_1 || - i==OPTION_PULSE_RATE_0 || i==OPTION_PULSE_RATE_1) break; // ignore non-editable options - if (options[i] != 0) options[i] --; - break; - - case BUTTON_3: - if (!(button & BUTTON_FLAG_DOWN)) break; - if (button & BUTTON_FLAG_HOLD) { - // if OPTION_RESET is set to nonzero, change it to reset condition value - if (options[OPTION_RESET]) { - options[OPTION_RESET] = 0xAA; - } - // long press, save options - options_save(); - finished = true; - } - else { - // click, move to the next option - if (i==OPTION_USE_DHCP && options[i]) i += 9; // if use DHCP, skip static ip set - else if (i==OPTION_HTTPPORT_0) i+=2; // skip OPTION_HTTPPORT_1 - else if (i==OPTION_PULSE_RATE_0) i+=2; // skip OPTION_PULSE_RATE_1 - else if (i==OPTION_SENSOR1_TYPE && options[i]!=SENSOR_TYPE_RAIN) i+=2; // if sensor1 is not rain sensor, skip sensor1 option - else if (i==OPTION_SENSOR2_TYPE && options[i]!=SENSOR_TYPE_RAIN) i+=2; // if sensor2 is not rain sensor, skip sensor2 option - else if (i==OPTION_MASTER_STATION && options[i]==0) i+=3; // if not using master station, skip master on/off adjust - else if (i==OPTION_MASTER_STATION_2&& options[i]==0) i+=3; // if not using master2, skip master2 on/off adjust - else { - i = (i+1) % NUM_OPTIONS; - } - if(i==OPTION_SEQUENTIAL_RETIRED) i++; - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) - else if (hw_type==HW_TYPE_AC && i==OPTION_BOOST_TIME) i++; // skip boost time for non-DC controller - #ifdef ESP8266 - else if (lcd.type()==LCD_I2C && i==OPTION_LCD_CONTRAST) i+=3; - #else - else if (lcd.type()==LCD_I2C && i==OPTION_LCD_CONTRAST) i+=2; - #endif - #endif - } - break; - } - - if (button != BUTTON_NONE) { - lcd_print_option(i); - } - } - lcd.noBlink(); + boolean finished = false; + byte button; + int i=oid; + + while(!finished) { + button = button_read(BUTTON_WAIT_HOLD); + + switch (button & BUTTON_MASK) { + case BUTTON_1: + if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || + i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || + i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || + i==IOPT_WIFI_MODE) break; // ignore non-editable options + if (pgm_read_byte(iopt_max+i) != iopts[i]) iopts[i] ++; + break; + + case BUTTON_2: + if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || + i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || + i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || + i==IOPT_WIFI_MODE) break; // ignore non-editable options + if (iopts[i] != 0) iopts[i] --; + break; + + case BUTTON_3: + if (!(button & BUTTON_FLAG_DOWN)) break; + if (button & BUTTON_FLAG_HOLD) { + // long press, save options + iopts_save(); + finished = true; + } + else { + // click, move to the next option + 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 { + i = (i+1) % NUM_IOPTS; + } + if(i==IOPT_SEQUENTIAL_RETIRED) i++; + if(i==IOPT_URS_RETIRED) i++; + if(i==IOPT_RSO_RETIRED) i++; + if (hw_type==HW_TYPE_AC && i==IOPT_BOOST_TIME) i++; // skip boost time for non-DC controller +#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; + } + + if (button != BUTTON_NONE) { + lcd_print_option(i); + } + } + lcd.noBlink(); } /** 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, options[OPTION_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(byte value) { -#ifdef PIN_LCD_BACKLIGHT - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - if (lcd.type()==LCD_I2C) { - if (value) lcd.backlight(); - else { - // turn off LCD backlight - // only if dimming value is set to 0 - if(!options[OPTION_LCD_DIMMING]) lcd.noBacklight(); - else lcd.backlight(); - } - } - #endif - if (lcd.type()==LCD_STD) { - pinMode(PIN_LCD_BACKLIGHT, OUTPUT); - if (value) { - analogWrite(PIN_LCD_BACKLIGHT, 255-options[OPTION_LCD_BACKLIGHT]); - } else { - analogWrite(PIN_LCD_BACKLIGHT, 255-options[OPTION_LCD_DIMMING]); - } - } +#if defined(PIN_LCD_BACKLIGHT) + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + 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(ESP8266) + 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 } -#endif // end of LCD and button functions +#endif // end of LCD and button functions -#ifdef ESP8266 +#if defined(ESP8266) #include "images.h" void OpenSprinkler::flash_screen() { - lcd.setCursor(0, -1); - lcd.print(F(" OpenSprinkler")); - lcd.drawXbm(34, 24, WiFi_Logo_width, WiFi_Logo_height, (const byte*) WiFi_Logo_image); - lcd.setCursor(0, 2); - lcd.display(); - delay(1500); - lcd.clear(); - lcd.display(); + lcd.setCursor(0, -1); + lcd.print(F(" OpenSprinkler")); + lcd.drawXbm(34, 24, WiFi_Logo_width, WiFi_Logo_height, (const byte*) WiFi_Logo_image); + lcd.setCursor(0, 2); + lcd.display(); + delay(1500); + lcd.clear(); + lcd.display(); } void OpenSprinkler::toggle_screen_led() { - static byte status = 0; - status = 1-status; - set_screen_led(!status); + static byte status = 0; + status = 1-status; + set_screen_led(!status); } void OpenSprinkler::set_screen_led(byte status) { - lcd.setColor(status ? WHITE : BLACK); - lcd.fillCircle(122, 58, 4); - lcd.display(); - lcd.setColor(WHITE); + lcd.setColor(status ? WHITE : BLACK); + lcd.fillCircle(122, 58, 4); + lcd.display(); + lcd.setColor(WHITE); } void OpenSprinkler::reset_to_ap() { - wifi_config.mode = WIFI_MODE_AP; - options_save(true); - reboot_dev(); + iopts[IOPT_WIFI_MODE] = WIFI_MODE_AP; + iopts_save(); + reboot_dev(REBOOT_CAUSE_RSTAP); } void OpenSprinkler::config_ip() { - if(options[OPTION_USE_DHCP] == 0) { - byte *_ip = options+OPTION_STATIC_IP1; - IPAddress dvip(_ip[0], _ip[1], _ip[2], _ip[3]); - if(dvip==(uint32_t)0x00000000) return; - - _ip = options+OPTION_GATEWAY_IP1; - IPAddress gwip(_ip[0], _ip[1], _ip[2], _ip[3]); - if(gwip==(uint32_t)0x00000000) return; - - IPAddress subn(255,255,255,0); - _ip = options+OPTION_DNS_IP1; - IPAddress dnsip(_ip[0], _ip[1], _ip[2], _ip[3]); - WiFi.config(dvip, gwip, subn, dnsip); - } + if(iopts[IOPT_USE_DHCP] == 0) { + byte *_ip = iopts+IOPT_STATIC_IP1; + IPAddress dvip(_ip[0], _ip[1], _ip[2], _ip[3]); + if(dvip==(uint32_t)0x00000000) return; + + _ip = iopts+IOPT_GATEWAY_IP1; + IPAddress gwip(_ip[0], _ip[1], _ip[2], _ip[3]); + if(gwip==(uint32_t)0x00000000) return; + + _ip = iopts+IOPT_SUBNET_MASK1; + IPAddress subn(_ip[0], _ip[1], _ip[2], _ip[3]); + if(subn==(uint32_t)0x00000000) return; + + _ip = iopts+IOPT_DNS_IP1; + IPAddress dnsip(_ip[0], _ip[1], _ip[2], _ip[3]); + + WiFi.config(dvip, gwip, subn, dnsip); + } +} + +void OpenSprinkler::save_wifi_ip() { + // todo: handle wired ethernet + if(iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED) { + memcpy(iopts+IOPT_STATIC_IP1, &(WiFi.localIP()[0]), 4); + memcpy(iopts+IOPT_GATEWAY_IP1, &(WiFi.gatewayIP()[0]),4); + memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); + memcpy(iopts+IOPT_SUBNET_MASK1, &(WiFi.subnetMask()[0]), 4); + iopts_save(); + } } void OpenSprinkler::detect_expanders() { - for(byte i=0;i<(MAX_EXT_BOARDS+1)/2;i++) { - byte address = EXP_I2CADDR_BASE+i; - byte type = IOEXP::detectType(address); - if(expanders[i]!=NULL) delete expanders[i]; - if(type==IOEXP_TYPE_9555) { - expanders[i] = new PCA9555(address); - expanders[i]->i2c_write(NXP_CONFIG_REG, 0); // set all channels to output - } else if(type==IOEXP_TYPE_8575){ - expanders[i] = new PCF8575(address); - } else { - expanders[i] = new IOEXP(address); - } - } + for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) { + byte address = EXP_I2CADDR_BASE+i; + byte type = IOEXP::detectType(address); + if(expanders[i]!=NULL) delete expanders[i]; + if(type==IOEXP_TYPE_9555) { + expanders[i] = new PCA9555(address); + expanders[i]->i2c_write(NXP_CONFIG_REG, 0); // set all channels to output + } else if(type==IOEXP_TYPE_8575){ + expanders[i] = new PCF8575(address); + } else { + expanders[i] = new IOEXP(address); + } + } } #endif diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 430a26cd0..252330a5e 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -25,265 +25,327 @@ #ifndef _OPENSPRINKLER_H #define _OPENSPRINKLER_H -#if defined(ARDUINO) && !defined(ESP8266) // headers for AVR - #include "Arduino.h" - #include - #include - #include "LiquidCrystal.h" - #include "Time.h" - #include "DS1307RTC.h" - #include "EtherCard.h" -#elif defined(ESP8266) // headers for ESP8266 - #include - #include - #include - #include "SSD1306Display.h" - #include "i2crtc.h" - #include "espconnect.h" -#else // headers for RPI/BBB/LINUX - #include - #include - #include - #include "etherport.h" -#endif // end of headers - #include "defines.h" #include "utils.h" #include "gpio.h" - -/** Non-volatile data */ +#include "images.h" +#include "mqtt.h" + +#if defined(ARDUINO) // headers for ESP8266 + #include + #include + #include + #include "I2CRTC.h" + + #if defined(ESP8266) + #include + #include + #include + #include + #include "SSD1306Display.h" + #include "espconnect.h" + #else + #include + #include "LiquidCrystal.h" + #include + #endif + +#else // headers for RPI/BBB/LINUX + #include + #include + #include + #include + #include + #include "etherport.h" +#endif // end of headers + +#if defined(ARDUINO) + #if defined(ESP8266) + extern ESP8266WebServer *w_server; + extern ENC28J60lwIP eth; + #else + extern EthernetServer *m_server; + #endif + extern bool useEth; +#endif + +/** 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 + 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 }; -/** Station special attribute data */ -struct StationSpecialData { - byte type; - byte data[STATION_SPECIAL_DATA_SIZE]; -}; +struct StationAttrib { // station attributes + byte mas:1; + byte igs:1; // ignore sensor 1 + byte mas2:1; + byte dis:1; + byte seq:1; + byte igs2:1;// ignore sensor 2 + byte igrd:1;// ignore rain delay + byte unused:1; + + byte gid:4; // group id: reserved for the future + byte dummy:4; + byte reserved[2]; // reserved bytes for the future +}; // total is 4 bytes so far -/** Station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ -struct RFStationData { - byte on[6]; - byte off[6]; - byte timing[4]; +/** Station data structure */ +struct StationData { + char name[STATION_NAME_SIZE]; + StationAttrib attrib; + byte type; // station type + byte sped[STATION_SPECIAL_DATA_SIZE]; // special station data }; -struct RFStationDataFull { - byte on[8]; - byte off[8]; - byte timing[4]; - byte protocol[4]; +/** RF station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ +struct RFStationData { + byte on[6]; + byte off[6]; + byte timing[4]; }; +/** Remote station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ struct RemoteStationData { - byte ip[8]; - byte port[4]; - byte sid[2]; + byte ip[8]; + byte port[4]; + byte sid[2]; }; +/** GPIO station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ struct GPIOStationData { - byte pin[2]; - byte active; + byte pin[2]; + byte active; }; +/** HTTP station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ struct HTTPStationData { - byte data[STATION_SPECIAL_DATA_SIZE]; + byte data[STATION_SPECIAL_DATA_SIZE]; }; /** Volatile controller status bits */ struct ConStatus { - byte enabled:1; // operation enable (when set, controller operation is enabled) - byte rain_delayed:1; // rain delay bit (when set, rain delay is applied) - byte rain_sensed:1; // rain sensor bit (when set, it indicates that rain is detected) - byte program_busy:1; // HIGH means a program is being executed currently - byte has_curr_sense:1; // HIGH means the controller has a current sensing pin - byte has_sd:1; // HIGH means a microSD card is detected - byte safe_reboot:1; // HIGH means a safe reboot has been marked - byte has_hwmac:1; // has hardware MAC chip - byte req_ntpsync:1; // request ntpsync - byte req_network:1; // request check network - byte display_board:4; // the board that is being displayed onto the lcd - byte network_fails:2; // number of network fails - byte mas:8; // master station index - byte mas2:8; // master2 station index - - byte soil_moisture_sensed:1; // soil moisture sensor bit (when set, it indicates wet, delayed) - byte soil_moisture_active:1; // soil moisture sensor bit (when set, it indicates wet, active after delay) + byte enabled:1; // operation enable (when set, controller operation is enabled) + byte rain_delayed:1; // rain delay bit (when set, rain delay is applied) + byte sensor1:1; // sensor1 status bit (when set, sensor1 on is detected) + byte program_busy:1; // HIGH means a program is being executed currently + byte has_curr_sense:1; // HIGH means the controller has a current sensing pin + byte safe_reboot:1; // HIGH means a safe reboot has been marked + byte req_ntpsync:1; // request ntpsync + byte req_network:1; // request check network + byte display_board:5; // the board that is being displayed onto the lcd + byte network_fails:3; // number of network fails + byte mas:8; // master station index + byte mas2:8; // master2 station index + byte sensor2:1; // sensor2 status bit (when set, sensor2 on is detected) + byte sensor1_active:1; // sensor1 active bit (when set, sensor1 is activated) + byte sensor2_active:1; // sensor2 active bit (when set, sensor2 is activated) + byte req_mqtt_restart:1; // request mqtt restart }; -extern const char wtopts_filename[]; -extern const char stns_filename[]; -extern const char ifkey_filename[]; -extern const byte op_max[]; -extern const char op_json_names[]; -#ifdef ESP8266 -struct WiFiConfig { - byte mode; - String ssid; - String pass; -}; -extern const char wifi_filename[]; -#endif +extern const char iopt_json_names[]; +extern const uint8_t iopt_max[]; class OpenSprinkler { public: - // data members -#if defined(ARDUINO) && !defined(ESP8266) - static LiquidCrystal lcd; // 16x2 character LCD -#elif defined(ESP8266) - static SSD1306Display lcd; // 128x64 OLED display + // data members +#if defined(ESP8266) + static SSD1306Display lcd; // 128x64 OLED display +#elif defined(ARDUINO) + static LiquidCrystal lcd; // 16x2 character LCD #else - // todo: LCD define for RPI/BBB + // todo: LCD define for RPI/BBB #endif #if defined(OSPI) - static byte pin_sr_data; // RPi shift register data pin - // to handle RPi rev. 1 + static byte pin_sr_data; // RPi shift register data pin + // to handle RPi rev. 1 #endif - static NVConData nvdata; - static ConStatus status; - static ConStatus old_status; - static byte nboards, nstations; - static byte hw_type; // hardware type - static byte hw_rev; // hardware minor - - static byte options[]; // option values, max, name, and flag - - static byte station_bits[]; // station activation bits. each byte corresponds to a board (8 stations) - // first byte-> master controller, second byte-> ext. board 1, and so on - - // variables for time keeping - static ulong sensor_lasttime; // time when the last sensor reading is recorded - static ulong soil_moisture_sensed_time; //time when soil moisture detects wet, base for delay - static volatile ulong flowcount_time_ms;// time stamp when new flow sensor click is received (in milliseconds) - static ulong flowcount_rt; // flow count (for computing real-time flow rate) - static ulong flowcount_log_start; // starting flow count (for logging) - static ulong raindelay_start_time; // time when the most recent rain delay started - static byte button_timeout; // button timeout - static ulong checkwt_lasttime; // time when weather was checked - static ulong checkwt_success_lasttime; // time when weather check was successful - static ulong powerup_lasttime; // time when controller is powered up most recently - static byte weather_update_flag; - // member functions - // -- setup - static void update_dev(); // update software for Linux instances - static void reboot_dev(); // reboot the microcontroller - static void begin(); // initialization, must call this function before calling other functions - static byte start_network(); // initialize network with the given mac and port - static byte start_ether(); // initialize ethernet with the given mac and port -#if defined(ARDUINO) - static bool read_hardware_mac(); // read hardware mac address -#endif - #ifdef ESP8266_ETHERNET - static void get_hardware_mac(); - #endif - static time_t now_tz(); - // -- station names and attributes - static void get_station_name(byte sid, char buf[]); // get station name - static void set_station_name(byte sid, char buf[]); // set station name - static uint16_t parse_rfstation_code(RFStationData *data, ulong *on, ulong *off); // parse rf code into on/off/time sections - static void switch_rfstation(RFStationData *data, bool turnon); // switch rf station - static void switch_remotestation(RemoteStationData *data, bool turnon); // switch remote station - static void switch_gpiostation(GPIOStationData *data, bool turnon); // switch gpio station - static void switch_httpstation(HTTPStationData *data, bool turnon); // switch http station - static void station_attrib_bits_save(int addr, byte bits[]); // save station attribute bits to nvm - static void station_attrib_bits_load(int addr, byte bits[]); // load station attribute bits from nvm - static byte station_attrib_bits_read(int addr); // read one station attribte byte from nvm - - // -- options and data storeage - static void nvdata_load(); - static void nvdata_save(); - - static void options_setup(); - static void options_load(); - static void options_save(bool savewifi=false); - - static byte password_verify(char *pw); // verify password - - // -- controller operation - static void enable(); // enable controller operation - static void disable(); // disable controller operation, all stations will be closed immediately - static void raindelay_start(); // start raindelay - static void raindelay_stop(); // stop rain delay - static void rainsensor_status();// update rainsensor status - static void soil_moisture_sensor_status(); // update soil moisture status - static bool programswitch_status(ulong); // get program switch status -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) - static uint16_t read_current(); // read current sensing value - static uint16_t baseline_current; // resting state current -#endif - static int detect_exp(); // detect the number of expansion boards - static byte weekday_today(); // returns index of today's weekday (Monday is 0) + static OSMqtt mqtt; + + static NVConData nvdata; + static ConStatus status; + static ConStatus old_status; + static byte nboards, nstations; + static byte hw_type; // hardware type + static byte hw_rev; // hardware minor + + static byte iopts[]; // integer options + static const char*sopts[]; // string options + static byte station_bits[]; // station activation bits. each byte corresponds to a board (8 stations) + // first byte-> master controller, second byte-> ext. board 1, and so on + // todo future: the following attribute bytes are for backward compatibility + static byte attrib_mas[]; + static byte attrib_igs[]; + static byte attrib_mas2[]; + static byte attrib_igs2[]; + static byte attrib_igrd[]; + static byte attrib_dis[]; + static byte attrib_seq[]; + static byte attrib_spe[]; + + // variables for time keeping + static ulong sensor1_on_timer; // time when sensor1 is detected on last time + static ulong sensor1_off_timer; // time when sensor1 is detected off last time + static ulong sensor1_active_lasttime; // most recent time sensor1 is activated + static ulong sensor2_on_timer; // time when sensor2 is detected on last time + static ulong sensor2_off_timer; // time when sensor2 is detected off last time + static ulong sensor2_active_lasttime; // most recent time sensor1 is activated + static ulong raindelay_on_lasttime; // time when the most recent rain delay started + static ulong flowcount_rt; // flow count (for computing real-time flow rate) + static ulong flowcount_log_start; // starting flow count (for logging) + + static byte button_timeout; // button timeout + static ulong checkwt_lasttime; // time when weather was checked + static ulong checkwt_success_lasttime; // time when weather check was successful + static ulong powerup_lasttime; // time when controller is powered up most recently + static uint8_t last_reboot_cause; // last reboot cause + static byte weather_update_flag; + // member functions + // -- setup + static void update_dev(); // update software for Linux instances + static void reboot_dev(uint8_t); // reboot the microcontroller + static void begin(); // initialization, must call this function before calling other functions + static byte start_network(); // initialize network with the given mac and port + static byte start_ether(); // initialize ethernet with the given mac and port + static bool network_connected(); // check if the network is up + static bool load_hardware_mac(byte* buffer, bool wired=false); // read hardware mac address + static time_t now_tz(); + // -- station names and attributes + static void get_station_data(byte sid, StationData* data); // get station data + static void set_station_data(byte sid, StationData* data); // set station data + static void get_station_name(byte sid, char buf[]); // get station name + static void set_station_name(byte sid, char buf[]); // set station name + static byte get_station_type(byte sid); // get station type + //static StationAttrib get_station_attrib(byte sid); // get station attribute + static void attribs_save(); // repackage attrib bits and save (backward compatibility) + static void attribs_load(); // load and repackage attrib bits (backward compatibility) + static uint16_t parse_rfstation_code(RFStationData *data, ulong *on, ulong *off); // parse rf code into on/off/time sections + static void switch_rfstation(RFStationData *data, bool turnon); // switch rf station + static void switch_remotestation(RemoteStationData *data, bool turnon); // switch remote station + static void switch_gpiostation(GPIOStationData *data, bool turnon); // switch gpio station + static void switch_httpstation(HTTPStationData *data, bool turnon); // switch http station + + // -- options and data storeage + static void nvdata_load(); + static void nvdata_save(); + + static void options_setup(); + static void pre_factory_reset(); + static void factory_reset(); + static void iopts_load(); + static void iopts_save(); + static bool sopt_save(byte oid, const char *buf); + static void sopt_load(byte oid, char *buf); + static String sopt_load(byte oid); + + static byte password_verify(char *pw); // verify password + + // -- controller operation + static void enable(); // enable controller operation + static void disable(); // disable controller operation, all stations will be closed immediately + static void raindelay_start(); // start raindelay + static void raindelay_stop(); // stop rain delay + static void detect_binarysensor_status(ulong);// update binary (rain, soil) sensor status + static byte detect_programswitch_status(ulong); // get program switch status + static void sensor_resetall(); + + static uint16_t read_current(); // read current sensing value + static uint16_t baseline_current; // resting state current - static byte set_station_bit(byte sid, byte value); // set station bit of one station (sid->station index, value->0/1) - static void switch_special_station(byte sid, byte value); // swtich special station - static void clear_all_station_bits(); // clear all station bits - static void apply_all_station_bits(); // apply all station bits (activate/deactive values) + static int detect_exp(); // detect the number of expansion boards + static byte weekday_today(); // returns index of today's weekday (Monday is 0) - // -- LCD functions + static byte set_station_bit(byte sid, byte value); // set station bit of one station (sid->station index, value->0/1) + static void switch_special_station(byte sid, byte value); // swtich special station + static void clear_all_station_bits(); // clear all station bits + static void apply_all_station_bits(); // apply all station bits (activate/deactive values) + + static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); + static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); + static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); + // -- LCD functions #if defined(ARDUINO) // LCD functions for Arduino - #ifdef ESP8266 - 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, byte 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, byte line); - #endif - static void lcd_print_time(time_t t); // print current time - static void lcd_print_ip(const byte *ip, byte endian); // print ip - static void lcd_print_mac(const byte *mac); // print mac - static void lcd_print_station(byte line, char c); // print station bits of the board selected by display_board - static void lcd_print_version(byte v); // print version number - - // -- UI and buttons - static byte button_read(byte waitmode); // Read button value. options for 'waitmodes' are: - // BUTTON_WAIT_NONE, BUTTON_WAIT_RELEASE, BUTTON_WAIT_HOLD - // return values are 'OR'ed with flags - // check defines.h for details - - // -- UI functions -- - static void ui_set_options(int oid); // ui for setting options (oid-> starting option index) - static void lcd_set_brightness(byte value=1); - static void lcd_set_contrast(); - - #ifdef ESP8266 - static WiFiConfig wifi_config; - static IOEXP *mainio, *drio; - static IOEXP *expanders[]; - static RCSwitch rfswitch; - static void detect_expanders(); - static void flash_screen(); - static void toggle_screen_led(); - static void set_screen_led(byte status); - static byte get_wifi_mode() {return wifi_config.mode;} - static void config_ip(); - static void reset_to_ap(); - static byte state; - #endif + #if defined(ESP8266) + 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, byte 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, byte line); + #endif + static void lcd_print_time(time_t t); // print current time + static void lcd_print_ip(const byte *ip, byte endian); // print ip + static void lcd_print_mac(const byte *mac); // print mac + static void lcd_print_station(byte line, char c); // print station bits of the board selected by display_board + static void lcd_print_version(byte v); // print version number + + static String time2str(uint32_t t) { + uint16_t h = hour(t); + uint16_t m = minute(t); + uint16_t s = second(t); + String str = ""; + str+=h/10; + str+=h%10; + str+=":"; + str+=m/10; + str+=m%10; + str+=":"; + str+=s/10; + str+=s%10; + return str; + } + // -- UI and buttons + static byte button_read(byte waitmode); // Read button value. options for 'waitmodes' are: + // BUTTON_WAIT_NONE, BUTTON_WAIT_RELEASE, BUTTON_WAIT_HOLD + // return values are 'OR'ed with flags + // check defines.h for details + + // -- UI functions -- + static void ui_set_options(int oid); // ui for setting options (oid-> starting option index) + static void lcd_set_brightness(byte value=1); + static void lcd_set_contrast(); + + #if defined(ESP8266) + static IOEXP *mainio, *drio; + static IOEXP *expanders[]; + static RCSwitch rfswitch; + static void detect_expanders(); + static void flash_screen(); + static void toggle_screen_led(); + static void set_screen_led(byte status); + static byte get_wifi_mode() { if (useEth) return WIFI_MODE_STA; else return wifi_testmode ? WIFI_MODE_STA : iopts[IOPT_WIFI_MODE];} + static byte wifi_testmode; + static String wifi_ssid, wifi_pass; + static void config_ip(); + static void save_wifi_ip(); + static void reset_to_ap(); + static byte state; + #endif + private: - static void lcd_print_option(int i); // print an option to the lcd - static void lcd_print_2digit(int v); // print a integer in 2 digits - static void lcd_start(); - static byte button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding); -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) - static byte engage_booster; -#endif -#if defined(ESP8266) - static void latch_boost(); - static void latch_open(byte sid); - static void latch_close(byte sid); - static void latch_setzonepin(byte sid, byte value); - static void latch_setallzonepins(byte value); - static void latch_apply_all_station_bits(); - static byte prev_station_bits[]; -#endif + static void lcd_print_option(int i); // print an option to the lcd + static void lcd_print_2digit(int v); // print a integer in 2 digits + static void lcd_start(); + static byte button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding); + + #if defined(ESP8266) + static void latch_boost(); + static void latch_open(byte sid); + static void latch_close(byte sid); + static void latch_setzonepin(byte sid, byte value); + static void latch_setallzonepins(byte value); + static void latch_disable_alloutputs_v2(); + static void latch_setzoneoutput_v2(byte sid, byte A, byte K); + static void latch_apply_all_station_bits(); + static byte prev_station_bits[]; + #endif #endif // LCD functions + static byte engage_booster; }; -#endif // _OPENSPRINKLER_H +#endif // _OPENSPRINKLER_H diff --git a/build.sh b/build.sh index dc68cc222..9be398ce4 100755 --- a/build.sh +++ b/build.sh @@ -14,17 +14,17 @@ if [ "$1" == "demo" ]; then echo "Installing required libraries..." apt-get install -y libmosquitto-dev echo "Compiling firmware..." - g++ -o OpenSprinkler -DDEMO -m32 main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto + g++ -o OpenSprinkler -DDEMO -m32 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto elif [ "$1" == "osbo" ]; then echo "Installing required libraries..." apt-get install -y libmosquitto-dev echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSBO main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto + g++ -o OpenSprinkler -DOSBO main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto else echo "Installing required libraries..." apt-get install -y libmosquitto-dev echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto + g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto fi if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then diff --git a/defines.cpp b/defines.cpp index 50669b97c..726f628e2 100644 --- a/defines.cpp +++ b/defines.cpp @@ -10,14 +10,10 @@ byte PIN_RFTX = 255; byte PIN_BOOST = 255; byte PIN_BOOST_EN = 255; byte PIN_LATCH_COM = 255; +byte PIN_LATCH_COMA = 255; +byte PIN_LATCH_COMK = 255; byte PIN_SENSOR1 = 255; byte PIN_SENSOR2 = 255; -byte PIN_RAINSENSOR = 255; -byte PIN_RAINSENSOR2 = 255; -byte PIN_FLOWSENSOR = 255; -byte PIN_FLOWSENSOR2 = 255; byte PIN_IOEXP_INT = 255; -byte PIN_SOILSENSOR = 255; -byte PIN_SOILSENSOR2 = 255; #endif diff --git a/defines.h b/defines.h index b96989150..8d900a1e6 100644 --- a/defines.h +++ b/defines.h @@ -24,23 +24,19 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ESP8266_ETHERNET +#define ENABLE_DEBUG // enable serial debug typedef unsigned char byte; typedef unsigned long ulong; - -#if !defined(ARDUINO) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) - #define TMP_BUFFER_SIZE 255 // scratch buffer size -#else - #define TMP_BUFFER_SIZE 128 // scratch buffer size -#endif + +#define TMP_BUFFER_SIZE 255 // scratch buffer size /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 218 // Firmware version: 218 means 2.1.8 +#define OS_FW_VERSION 220 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 3 // Firmware minor version +#define OS_FW_MINOR 104 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 @@ -54,43 +50,68 @@ typedef unsigned long ulong; #define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges #define HW_TYPE_UNKNOWN 0xFF -/** File names */ -#define WEATHER_OPTS_FILENAME "wtopts.txt" // weather options file -#define STATION_ATTR_FILENAME "stns.dat" // station attributes data file -#define WIFI_FILENAME "wifi.dat" // wifi credentials file -#define IFTTT_KEY_FILENAME "ifkey.txt" -#define IFTTT_KEY_MAXSIZE 128 -#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - 8) - -#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds +/** Data file names */ +#define IOPTS_FILENAME "iopts.dat" // integer options data file +#define SOPTS_FILENAME "sopts.dat" // string options data file +#define STATIONS_FILENAME "stns.dat" // stations data file +#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 -/** Station type macro defines */ +/** Station macro defines */ #define STN_TYPE_STANDARD 0x00 -#define STN_TYPE_RF 0x01 -#define STN_TYPE_REMOTE 0x02 -#define STN_TYPE_GPIO 0x03 // Support for raw connection of station to GPIO pin -#define STN_TYPE_HTTP 0x04 // Support for HTTP Get connection +#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station +#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station +#define STN_TYPE_GPIO 0x03 // direct GPIO station +#define STN_TYPE_HTTP 0x04 // HTTP station #define STN_TYPE_OTHER 0xFF -#define IFTTT_PROGRAM_SCHED 0x01 -#define IFTTT_RAINSENSOR 0x02 -#define IFTTT_FLOWSENSOR 0x04 -#define IFTTT_WEATHER_UPDATE 0x08 -#define IFTTT_REBOOT 0x10 -#define IFTTT_STATION_RUN 0x20 -#define IFTTT_SOILSENSOR 0x40 - -/** Sensor type macro defines */ +/** Notification macro defines */ +#define NOTIFY_PROGRAM_SCHED 0x0001 +#define NOTIFY_SENSOR1 0x0002 +#define NOTIFY_FLOWSENSOR 0x0004 +#define NOTIFY_WEATHER_UPDATE 0x0008 +#define NOTIFY_REBOOT 0x0010 +#define NOTIFY_STATION_OFF 0x0020 +#define NOTIFY_SENSOR2 0x0040 +#define NOTIFY_RAINDELAY 0x0080 +#define NOTIFY_STATION_ON 0x0100 + +/** HTTP request macro defines */ +#define HTTP_RQT_SUCCESS 0 +#define HTTP_RQT_NOT_RECEIVED -1 +#define HTTP_RQT_CONNECT_ERR -2 +#define HTTP_RQT_TIMEOUT -3 +#define HTTP_RQT_EMPTY_RETURN -4 +#define HTTP_RQT_DNS_ERROR -5 + +/** Sensor macro defines */ #define SENSOR_TYPE_NONE 0x00 -#define SENSOR_TYPE_RAIN 0x01 // rain sensor -#define SENSOR_TYPE_FLOW 0x02 // flow sensor +#define SENSOR_TYPE_RAIN 0x01 // rain sensor +#define SENSOR_TYPE_FLOW 0x02 // flow sensor #define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor -#define SENSOR_TYPE_PSWITCH 0xF0 // program switch +#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor #define SENSOR_TYPE_OTHER 0xFF -/** WiFi related defines */ -#ifdef ESP8266 +#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds +/** Reboot cause */ +#define REBOOT_CAUSE_NONE 0 +#define REBOOT_CAUSE_RESET 1 +#define REBOOT_CAUSE_BUTTON 2 +#define REBOOT_CAUSE_RSTAP 3 +#define REBOOT_CAUSE_TIMER 4 +#define REBOOT_CAUSE_WEB 5 +#define REBOOT_CAUSE_WIFIDONE 6 +#define REBOOT_CAUSE_FWUPDATE 7 +#define REBOOT_CAUSE_WEATHER_FAIL 8 +#define REBOOT_CAUSE_NETWORK_FAIL 9 +#define REBOOT_CAUSE_NTP 10 +#define REBOOT_CAUSE_PROGRAM 11 +#define REBOOT_CAUSE_POWERON 99 + + +/** WiFi defines */ #define WIFI_MODE_AP 0xA9 #define WIFI_MODE_STA 0x2A @@ -102,465 +123,355 @@ typedef unsigned long ulong; #define LED_FAST_BLINK 100 #define LED_SLOW_BLINK 500 +/** Storage / zone expander defines */ +#if defined(ARDUINO) + #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 #endif -/** Non-volatile memory (NVM) defines */ -#if defined(ARDUINO) && !defined(ESP8266) - -/** 2KB NVM (ATmega644) data structure: - * | | | ---STRING PARAMETERS--- | | ----STATION ATTRIBUTES----- | | - * | PROGRAM | CON | PWD | LOC | JURL | WURL | KEY | STN_NAMES | MAS | IGR | MAS2 | DIS | SEQ | SPE | OPTIONS | - * | (986) |(12) |(36) |(48) | (40) | (40) |(24) | (768) | (6) | (6) | (6) | (6) | (6) | (6) | (58) | - * | | | | | | | | | | | | | | | | - * 0 986 998 1034 1082 1122 1162 1186 1954 1960 1966 1972 1978 1984 1990 2048 - */ - -/** 4KB NVM (ATmega1284) data structure: - * | | | ---STRING PARAMETERS--- | | ----STATION ATTRIBUTES----- | | - * | PROGRAM | CON | PWD | LOC | JURL | WURL | KEY | STN_NAMES | MAS | IGR | MAS2 | DIS | SEQ | SPE | OPTIONS | - * | (2433) |(12) |(36) |(48) | (48) | (48) |(24) | (1344) | (7) | (7) | (7) | (7) | (7) | (7) | (61) | - * | | | | | | | | | | | | | | | | - * 0 2433 2445 2481 2529 2577 2625 2649 3993 4000 4007 4014 4021 4028 4035 4096 - */ - - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // for 4KB NVM - - #define MAX_EXT_BOARDS 6 // maximum number of exp. boards (each expands 8 stations) - #define MAX_NUM_STATIONS ((1+MAX_EXT_BOARDS)*8) // maximum number of stations - - #define NVM_SIZE 4096 // For AVR, nvm data is stored in EEPROM, ATmega1284 has 4K EEPROM - #define STATION_NAME_SIZE 24 // maximum number of characters in each station name - - #define MAX_PROGRAMDATA 2433 // program data - #define MAX_NVCONDATA 12 // non-volatile controller data - #define MAX_USER_PASSWORD 36 // user password - #define MAX_LOCATION 48 // location string - #define MAX_JAVASCRIPTURL 48 // javascript url - #define MAX_WEATHERURL 48 // weather script url - #define MAX_WEATHER_KEY 24 // weather api key +#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders +#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations +#define STATION_NAME_SIZE 32 // maximum number of characters in each station name +#define MAX_SOPTS_SIZE 160 // maximum string option size - #else +#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) - #define MAX_EXT_BOARDS 5 // maximum number of exp. boards (each expands 8 stations) - #define MAX_NUM_STATIONS ((1+MAX_EXT_BOARDS)*8) // maximum number of stations - - #define NVM_SIZE 2048 // For AVR, nvm data is stored in EEPROM, ATmega644 has 2K EEPROM - #define STATION_NAME_SIZE 16 // maximum number of characters in each station name - - #define MAX_PROGRAMDATA 986 // program data - #define MAX_NVCONDATA 12 // non-volatile controller data - #define MAX_USER_PASSWORD 36 // user password - #define MAX_LOCATION 48 // location string - #define MAX_JAVASCRIPTURL 40 // javascript url - #define MAX_WEATHERURL 40 // weather script url - #define MAX_WEATHER_KEY 24 // weather api key, - - #endif - -#else // NVM defines for RPI/BBB/LINUX/ESP8266 - -/** 8KB NVM (RPI/BBB/LINUX/ESP8266) data structure: - * | | | ---STRING PARAMETERS--- | | ----STATION ATTRIBUTES----- | | - * | PROGRAM | CON | PWD | LOC | JURL | WURL | KEY | STN_NAMES | MAS | IGR | MAS2 | DIS | SEQ | SPE | OPTIONS | - * | (6127) |(12) |(36) |(48) | (48) | (48) |(24) | (1728) | (9) | (9) | (9) | (9) | (9) | (9) | (67) | - * | | | | | | | | | | | | | | | | - * 0 6127 6139 6175 6223 6271 6319 6343 8071 8080 8089 8098 8107 8116 8125 8192 - */ - - // These are kept the same as AVR for compatibility reasons - // But they can be increased if needed - #define NVM_FILENAME "nvm.dat" // for RPI/BBB, nvm data is stored in a file - - #define MAX_EXT_BOARDS 8 // maximum number of 8-station exp. boards (a 16-station expander counts as 2) - #define MAX_NUM_STATIONS ((1+MAX_EXT_BOARDS)*8) // maximum number of stations - - #define NVM_SIZE 8192 - #define STATION_NAME_SIZE 24 // maximum number of characters in each station name - - #define MAX_PROGRAMDATA 6127 // program data - #define MAX_NVCONDATA 12 // non-volatile controller data - #define MAX_USER_PASSWORD 36 // user password - #define MAX_LOCATION 48 // location string - #define MAX_JAVASCRIPTURL 48 // javascript url - #define MAX_WEATHERURL 48 // weather script url - #define MAX_WEATHER_KEY 24 // weather api key - -#endif // end of NVM defines - -/** NVM data addresses */ -#define ADDR_NVM_PROGRAMS (0) // program starting address -#define ADDR_NVM_NVCONDATA (ADDR_NVM_PROGRAMS+MAX_PROGRAMDATA) -#define ADDR_NVM_PASSWORD (ADDR_NVM_NVCONDATA+MAX_NVCONDATA) -#define ADDR_NVM_LOCATION (ADDR_NVM_PASSWORD+MAX_USER_PASSWORD) -#define ADDR_NVM_JAVASCRIPTURL (ADDR_NVM_LOCATION+MAX_LOCATION) -#define ADDR_NVM_WEATHERURL (ADDR_NVM_JAVASCRIPTURL+MAX_JAVASCRIPTURL) -#define ADDR_NVM_WEATHER_KEY (ADDR_NVM_WEATHERURL+MAX_WEATHERURL) -#define ADDR_NVM_STN_NAMES (ADDR_NVM_WEATHER_KEY+MAX_WEATHER_KEY) -#define ADDR_NVM_MAS_OP (ADDR_NVM_STN_NAMES+MAX_NUM_STATIONS*STATION_NAME_SIZE) // master op bits -#define ADDR_NVM_IGNRAIN (ADDR_NVM_MAS_OP+(MAX_EXT_BOARDS+1)) // ignore rain bits -#define ADDR_NVM_MAS_OP_2 (ADDR_NVM_IGNRAIN+(MAX_EXT_BOARDS+1)) // master2 op bits -#define ADDR_NVM_STNDISABLE (ADDR_NVM_MAS_OP_2+(MAX_EXT_BOARDS+1))// station disable bits -#define ADDR_NVM_STNSEQ (ADDR_NVM_STNDISABLE+(MAX_EXT_BOARDS+1))// station sequential bits -#define ADDR_NVM_STNSPE (ADDR_NVM_STNSEQ+(MAX_EXT_BOARDS+1)) // station special bits (i.e. non-standard stations) -#define ADDR_NVM_OPTIONS (ADDR_NVM_STNSPE+(MAX_EXT_BOARDS+1)) // options - -/** Default password, location string, weather key, script urls */ +/** Default string option values */ #define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "Boston,MA" -#define DEFAULT_WEATHER_KEY "" +#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA #define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" #define DEFAULT_WEATHER_URL "weather.opensprinkler.com" #define DEFAULT_IFTTT_URL "maker.ifttt.com" +#define DEFAULT_EMPTY_STRING "" /** Macro define of each option * Refer to OpenSprinkler.cpp for details on each option */ -typedef enum { - OPTION_FW_VERSION = 0, - OPTION_TIMEZONE, - OPTION_USE_NTP, - OPTION_USE_DHCP, - OPTION_STATIC_IP1, - OPTION_STATIC_IP2, - OPTION_STATIC_IP3, - OPTION_STATIC_IP4, - OPTION_GATEWAY_IP1, - OPTION_GATEWAY_IP2, - OPTION_GATEWAY_IP3, - OPTION_GATEWAY_IP4, - OPTION_HTTPPORT_0, - OPTION_HTTPPORT_1, - OPTION_HW_VERSION, - OPTION_EXT_BOARDS, - OPTION_SEQUENTIAL_RETIRED, - OPTION_STATION_DELAY_TIME, - OPTION_MASTER_STATION, - OPTION_MASTER_ON_ADJ, - OPTION_MASTER_OFF_ADJ, - OPTION_SENSOR1_TYPE, - OPTION_SENSOR1_OPTION, - OPTION_WATER_PERCENTAGE, - OPTION_DEVICE_ENABLE, - OPTION_IGNORE_PASSWORD, - OPTION_DEVICE_ID, - OPTION_LCD_CONTRAST, - OPTION_LCD_BACKLIGHT, - OPTION_LCD_DIMMING, - OPTION_BOOST_TIME, - OPTION_USE_WEATHER, - OPTION_NTP_IP1, - OPTION_NTP_IP2, - OPTION_NTP_IP3, - OPTION_NTP_IP4, - OPTION_ENABLE_LOGGING, - OPTION_MASTER_STATION_2, - OPTION_MASTER_ON_ADJ_2, - OPTION_MASTER_OFF_ADJ_2, - OPTION_FW_MINOR, - OPTION_PULSE_RATE_0, - OPTION_PULSE_RATE_1, - OPTION_REMOTE_EXT_MODE, - OPTION_DNS_IP1, - OPTION_DNS_IP2, - OPTION_DNS_IP3, - OPTION_DNS_IP4, - OPTION_SPE_AUTO_REFRESH, - OPTION_IFTTT_ENABLE, - OPTION_SENSOR2_TYPE, - OPTION_SENSOR2_OPTION, - OPTION_RESET, - NUM_OPTIONS // total number of options -} OS_OPTION_t; +enum { + IOPT_FW_VERSION=0,// read-only (ro) + IOPT_TIMEZONE, + IOPT_USE_NTP, + IOPT_USE_DHCP, + IOPT_STATIC_IP1, + IOPT_STATIC_IP2, + IOPT_STATIC_IP3, + IOPT_STATIC_IP4, + IOPT_GATEWAY_IP1, + IOPT_GATEWAY_IP2, + IOPT_GATEWAY_IP3, + IOPT_GATEWAY_IP4, + IOPT_HTTPPORT_0, + IOPT_HTTPPORT_1, + IOPT_HW_VERSION, //ro + IOPT_EXT_BOARDS, + IOPT_SEQUENTIAL_RETIRED, //ro + IOPT_STATION_DELAY_TIME, + IOPT_MASTER_STATION, + IOPT_MASTER_ON_ADJ, + IOPT_MASTER_OFF_ADJ, + IOPT_URS_RETIRED, // ro + IOPT_RSO_RETIRED, // ro + IOPT_WATER_PERCENTAGE, + IOPT_DEVICE_ENABLE, // editable through jc + IOPT_IGNORE_PASSWORD, + IOPT_DEVICE_ID, + IOPT_LCD_CONTRAST, + IOPT_LCD_BACKLIGHT, + IOPT_LCD_DIMMING, + IOPT_BOOST_TIME, + IOPT_USE_WEATHER, + IOPT_NTP_IP1, + IOPT_NTP_IP2, + IOPT_NTP_IP3, + IOPT_NTP_IP4, + IOPT_ENABLE_LOGGING, + IOPT_MASTER_STATION_2, + IOPT_MASTER_ON_ADJ_2, + IOPT_MASTER_OFF_ADJ_2, + IOPT_FW_MINOR, //ro + IOPT_PULSE_RATE_0, + IOPT_PULSE_RATE_1, + IOPT_REMOTE_EXT_MODE, // editable through jc + IOPT_DNS_IP1, + IOPT_DNS_IP2, + IOPT_DNS_IP3, + IOPT_DNS_IP4, + IOPT_SPE_AUTO_REFRESH, + IOPT_IFTTT_ENABLE, + IOPT_SENSOR1_TYPE, + IOPT_SENSOR1_OPTION, + IOPT_SENSOR2_TYPE, + IOPT_SENSOR2_OPTION, + IOPT_SENSOR1_ON_DELAY, + IOPT_SENSOR1_OFF_DELAY, + IOPT_SENSOR2_ON_DELAY, + IOPT_SENSOR2_OFF_DELAY, + IOPT_SUBNET_MASK1, + IOPT_SUBNET_MASK2, + IOPT_SUBNET_MASK3, + IOPT_SUBNET_MASK4, + IOPT_WIFI_MODE, //ro + IOPT_RESET, //ro + NUM_IOPTS // total number of integer options +}; + +enum { + SOPT_PASSWORD=0, + SOPT_LOCATION, + SOPT_JAVASCRIPTURL, + SOPT_WEATHERURL, + SOPT_WEATHER_OPTS, + SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT + SOPT_STA_SSID, + SOPT_STA_PASS, + SOPT_MQTT_OPTS, + //SOPT_WEATHER_KEY, + //SOPT_AP_PASS, + NUM_SOPTS // total number of string options +}; /** Log Data Type */ #define LOGDATA_STATION 0x00 -#define LOGDATA_RAINSENSE 0x01 +#define LOGDATA_SENSOR1 0x01 #define LOGDATA_RAINDELAY 0x02 #define LOGDATA_WATERLEVEL 0x03 #define LOGDATA_FLOWSENSE 0x04 -#define LOGDATA_SOILSENSE 0x05 +#define LOGDATA_SENSOR2 0x05 +#define LOGDATA_CURRENT 0x80 #undef OS_HW_VERSION /** Hardware defines */ -#if defined(ARDUINO) && !defined(ESP8266) - - #if F_CPU==8000000L // 8M for OS20 - #define OS_HW_VERSION (OS_HW_VERSION_BASE+20) - #elif F_CPU==12000000L // 12M for OS21 - #define OS_HW_VERSION (OS_HW_VERSION_BASE+21) - #elif F_CPU==16000000L // 16M for OS22 and OS23 - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - #define OS_HW_VERSION (OS_HW_VERSION_BASE+23) - #define PIN_FREE_LIST {2,10,12,13,14,15,18,19} // Free GPIO pins - #else - #define OS_HW_VERSION (OS_HW_VERSION_BASE+22) - #endif - #endif - - // 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_SD_CS 0 // SD card chip select pin - #define PIN_RAINSENSOR 11 // rain sensor is connected to pin D3 - #define PIN_FLOWSENSOR 11 // flow sensor (currently shared with rain sensor, change if using a different pin) - #define PIN_SOILSENSOR 11 // soil moisture sensor (currently shared with rain sensor, change if using a different 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 - - // Ethernet buffer size - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - #define ETHER_BUFFER_SIZE 1400 // ATmega1284 has 16K RAM, so use a bigger buffer - #else - #define ETHER_BUFFER_SIZE 950 // ATmega644 has 4K RAM, so use a smaller buffer - #endif - - #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset - - //#define SERIAL_DEBUG - #if defined(SERIAL_DEBUG) /** Serial debug functions */ - - #define DEBUG_BEGIN(x) Serial.begin(x) - #define DEBUG_PRINT(x) Serial.print(x) - #define DEBUG_PRINTLN(x) Serial.println(x) - #define DEBUG_PRINTIP(x) ether.printIp("IP:",x) - - #else - - #define DEBUG_BEGIN(x) {} - #define DEBUG_PRINT(x) {} - #define DEBUG_PRINTLN(x) {} - #define DEBUG_PRINTIP(x) {} - - #endif - typedef unsigned char uint8_t; - typedef unsigned int uint16_t; - typedef int int16_t; - #define pinModeExt pinMode - #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite - -#else // Hardware defines for RPI/BBB/ESP8266 - - #if defined(ESP8266) - - #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) - #define IOEXP_PIN 0x80 // base for pins on main IO expander - #define MAIN_I2CADDR 0x20 // main IO expander I2C address - #define ACDR_I2CADDR 0x21 // ac driver I2C address - #define DCDR_I2CADDR 0x22 // dc driver I2C address - #define LADR_I2CADDR 0x23 // latch driver I2C address - #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address - #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address - - #define PIN_CURR_SENSE A0 - #define PIN_FREE_LIST {} // no free GPIO pin at the moment - #define ETHER_BUFFER_SIZE 4096 - - #define PIN_ETHER_CS 16 // Ethernet controller chip select pin, the CS (chip select pin) is 16 on OS 3.2. - - /* To accommodate different OS30 versions, we use software defines pins */ - extern byte PIN_BUTTON_1; - extern byte PIN_BUTTON_2; - extern byte PIN_BUTTON_3; - extern byte PIN_RFRX; - extern byte PIN_RFTX; - extern byte PIN_BOOST; - extern byte PIN_BOOST_EN; - extern byte PIN_LATCH_COM; - extern byte PIN_SENSOR1; - extern byte PIN_SENSOR2; - extern byte PIN_RAINSENSOR; - extern byte PIN_FLOWSENSOR; - extern byte PIN_SOILSENSOR; - extern byte PIN_RAINSENSOR2; - extern byte PIN_FLOWSENSOR2; - extern byte PIN_SOILSENSOR2; - extern byte PIN_IOEXP_INT; - - /* Original OS30 pin defines */ - //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask - // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i - #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 - #define V0_PIN_BUTTON_2 0 // button 2 - #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 - #define V0_PIN_RFRX 14 - #define V0_PIN_PWR_RX IOEXP_PIN+0 - #define V0_PIN_RFTX 16 - #define V0_PIN_PWR_TX IOEXP_PIN+2 - #define V0_PIN_BOOST IOEXP_PIN+6 - #define V0_PIN_BOOST_EN IOEXP_PIN+7 - #define V0_PIN_SENSOR1 12 // sensor 1 - #define V0_PIN_SENSOR2 13 // sensor 2 - #define V0_PIN_RAINSENSOR V0_PIN_SENSOR1 - #define V0_PIN_FLOWSENSOR V0_PIN_SENSOR1 - #define V0_PIN_SOILSENSOR V0_PIN_SENSOR1 - #define V0_PIN_RAINSENSOR2 V0_PIN_SENSOR2 - #define V0_PIN_FLOWSENSOR2 V0_PIN_SENSOR2 - #define V0_PIN_SOILSENSOR2 V0_PIN_SENSOR2 - - /* OS30 revision 1 pin defines */ - // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V1_IO_CONFIG 0x1F00 // config bits - #define V1_IO_OUTPUT 0x1F00 // output bits - #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 - #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 - #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 - #define V1_PIN_RFRX 14 - #define V1_PIN_RFTX 16 - #define V1_PIN_IOEXP_INT 12 - #define V1_PIN_BOOST IOEXP_PIN+13 - #define V1_PIN_BOOST_EN IOEXP_PIN+14 - #define V1_PIN_LATCH_COM IOEXP_PIN+15 - #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 - #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 - #define V1_PIN_RAINSENSOR V1_PIN_SENSOR1 - #define V1_PIN_FLOWSENSOR V1_PIN_SENSOR1 - #define V1_PIN_SOILSENSOR V1_PIN_SENSOR1 - #define V1_PIN_RAINSENSOR2 V1_PIN_SENSOR2 - #define V1_PIN_FLOWSENSOR2 V1_PIN_SENSOR2 - #define V1_PIN_SOILSENSOR2 V1_PIN_SENSOR2 - - /* OS30 revision 2 pin defines */ - // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V2_IO_CONFIG 0x9F00 // config bits - #define V2_IO_OUTPUT 0x9F00 // output bits - #define V2_PIN_BUTTON_1 2 // button 1 - #define V2_PIN_BUTTON_2 0 // button 2 - #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 - #define V2_PIN_RFTX 15 - #define V2_PIN_BOOST IOEXP_PIN+13 - #define V2_PIN_BOOST_EN IOEXP_PIN+14 - #define V2_PIN_SENSOR1 3 // sensor 1 - #define V2_PIN_SENSOR2 10 // sensor 2 - #define V2_PIN_RAINSENSOR V2_PIN_SENSOR1 - #define V2_PIN_FLOWSENSOR V2_PIN_SENSOR1 - #define V2_PIN_SOILSENSOR V2_PIN_SENSOR1 - #define V2_PIN_RAINSENSOR2 V2_PIN_SENSOR2 - #define V2_PIN_FLOWSENSOR2 V2_PIN_SENSOR2 - #define V2_PIN_SOILSENSOR2 V2_PIN_SENSOR2 - - /** OSPi pin defines */ - #elif defined(OSPI) - - #define OS_HW_VERSION OSPI_HW_VERSION_BASE - #define PIN_SR_LATCH 22 // shift register latch pin - #define PIN_SR_DATA 27 // shift register data pin - #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) - #define PIN_SR_CLOCK 4 // shift register clock pin - #define PIN_SR_OE 17 // shift register output enable pin - #define PIN_RAINSENSOR 14 // rain sensor - #define PIN_FLOWSENSOR 14 // flow sensor (currently shared with rain sensor, change if using a different pin) - #define PIN_SOILSENSOR 14 // soil moisture sensor (currently shared with rain sensor, change if using a different pin) - #define PIN_RFTX 15 // RF transmitter pin - #define PIN_BUTTON_1 23 // button 1 - #define PIN_BUTTON_2 24 // button 2 - #define PIN_BUTTON_3 25 // button 3 - - #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins - #define ETHER_BUFFER_SIZE 16384 - /** BBB pin defines */ - #elif defined(OSBO) - - #define OS_HW_VERSION OSBO_HW_VERSION_BASE - // these are gpio pin numbers, refer to - // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp - #define PIN_SR_LATCH 60 // P9_12, shift register latch pin - #define PIN_SR_DATA 30 // P9_11, shift register data pin - #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin - #define PIN_SR_OE 50 // P9_14, shift register output enable pin - #define PIN_RAINSENSOR 48 // P9_15, rain sensor is connected to pin D3 - #define PIN_FLOWSENSOR 48 // flow sensor (currently shared with rain sensor, change if using a different pin) - #define PIN_SOILSENSOR 48 // soil moisture sensor (currently shared with rain sensor, change if using a different pin) - #define PIN_RFTX 51 // RF transmitter pin - - #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} - #define ETHER_BUFFER_SIZE 16384 - #else - // For Linux or other software simulators - // use fake hardware pins - #if defined(DEMO) - #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware - #else - #define OS_HW_VERSION SIM_HW_VERSION_BASE - #endif - #define PIN_SR_LATCH 0 - #define PIN_SR_DATA 0 - #define PIN_SR_CLOCK 0 - #define PIN_SR_OE 0 - #define PIN_RAINSENSOR 0 - #define PIN_FLOWSENSOR 0 - #define PIN_SOILSENSOR 0 - #define PIN_RFTX 0 - #define PIN_FREE_LIST {} - #define ETHER_BUFFER_SIZE 16384 - #endif - - #define ENABLE_DEBUG - #if defined(ENABLE_DEBUG) - #if defined(ESP8266) - #define DEBUG_BEGIN(x) Serial.begin(x) - #define DEBUG_PRINT(x) Serial.print(x) - #define DEBUG_PRINTLN(x) Serial.println(x) - #else - #define DEBUG_BEGIN(x) {} /** Serial debug functions */ - inline void DEBUG_PRINT(int x) {printf("%d", x);} - inline void DEBUG_PRINT(const char*s) {printf("%s", s);} - #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} - #endif - #else - #define DEBUG_BEGIN(x) {} - #define DEBUG_PRINT(x) {} - #define DEBUG_PRINTLN(x) {} - #endif - - /** Re-define avr-specific (e.g. PGM) types to use standard types */ - #if !defined(ESP8266) - inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} - inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} - #define now() time(0) - #define pgm_read_byte(x) *(x) - #define PSTR(x) x - #define strcat_P strcat - #define strcpy_P strcpy - #define PROGMEM - typedef const char* PGM_P; - typedef unsigned char uint8_t; - typedef short int16_t; - typedef unsigned short uint16_t; - typedef bool boolean; - #define pinModeExt pinMode - #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite - #endif - -#endif // end of Hardawre defines +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // 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 + +#elif defined(ESP8266) // for ESP8266 + + #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) + #define IOEXP_PIN 0x80 // base for pins on main IO expander + #define MAIN_I2CADDR 0x20 // main IO expander I2C address + #define ACDR_I2CADDR 0x21 // ac driver I2C address + #define DCDR_I2CADDR 0x22 // dc driver I2C address + #define LADR_I2CADDR 0x23 // latch driver I2C address + #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address + #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address + + #define PIN_CURR_SENSE A0 + #define PIN_FREE_LIST {} // no free GPIO pin at the moment + #define ETHER_BUFFER_SIZE 2048 + + #define PIN_ETHER_CS 16 // ENC28J60 CS (chip select pin) is 16 on OS 3.2. + + /* To accommodate different OS30 versions, we use software defines pins */ + extern byte PIN_BUTTON_1; + extern byte PIN_BUTTON_2; + extern byte PIN_BUTTON_3; + extern byte PIN_RFRX; + extern byte PIN_RFTX; + extern byte PIN_BOOST; + extern byte PIN_BOOST_EN; + extern byte PIN_LATCH_COM; + extern byte PIN_LATCH_COMA; + extern byte PIN_LATCH_COMK; + extern byte PIN_SENSOR1; + extern byte PIN_SENSOR2; + extern byte PIN_IOEXP_INT; + + /* Original OS30 pin defines */ + //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask + // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i + #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 + #define V0_PIN_BUTTON_2 0 // button 2 + #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 + #define V0_PIN_RFRX 14 + #define V0_PIN_PWR_RX IOEXP_PIN+0 + #define V0_PIN_RFTX 16 + #define V0_PIN_PWR_TX IOEXP_PIN+2 + #define V0_PIN_BOOST IOEXP_PIN+6 + #define V0_PIN_BOOST_EN IOEXP_PIN+7 + #define V0_PIN_SENSOR1 12 // sensor 1 + #define V0_PIN_SENSOR2 13 // sensor 2 + + /* OS30 revision 1 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V1_IO_CONFIG 0x1F00 // config bits + #define V1_IO_OUTPUT 0x1F00 // output bits + #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 + #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 + #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V1_PIN_RFRX 14 + #define V1_PIN_RFTX 16 + #define V1_PIN_IOEXP_INT 12 + #define V1_PIN_BOOST IOEXP_PIN+13 + #define V1_PIN_BOOST_EN IOEXP_PIN+14 + #define V1_PIN_LATCH_COM IOEXP_PIN+15 + #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 + #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 + + /* OS30 revision 2 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V2_IO_CONFIG 0x1000 // config bits + #define V2_IO_OUTPUT 0x1E00 // output bits + #define V2_PIN_BUTTON_1 2 // button 1 + #define V2_PIN_BUTTON_2 0 // button 2 + #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V2_PIN_RFTX 15 + #define V2_PIN_BOOST IOEXP_PIN+13 + #define V2_PIN_BOOST_EN IOEXP_PIN+14 + #define V2_PIN_LATCH_COMA IOEXP_PIN+8 // latch COM+ (anode) + #define V2_PIN_SRLAT IOEXP_PIN+9 // shift register latch + #define V2_PIN_SRCLK IOEXP_PIN+10 // shift register clock + #define V2_PIN_SRDAT IOEXP_PIN+11 // shift register data + #define V2_PIN_LATCH_COMK IOEXP_PIN+15 // latch COM- (cathode) + #define V2_PIN_SENSOR1 3 // sensor 1 + #define V2_PIN_SENSOR2 10 // sensor 2 + +#elif defined(OSPI) // for OSPi + + #define OS_HW_VERSION OSPI_HW_VERSION_BASE + #define PIN_SR_LATCH 22 // shift register latch pin + #define PIN_SR_DATA 27 // shift register data pin + #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) + #define PIN_SR_CLOCK 4 // shift register clock pin + #define PIN_SR_OE 17 // shift register output enable pin + #define PIN_SENSOR1 14 + #define PIN_SENSOR2 23 + #define PIN_RFTX 15 // RF transmitter pin + //#define PIN_BUTTON_1 23 // button 1 + //#define PIN_BUTTON_2 24 // button 2 + //#define PIN_BUTTON_3 25 // button 3 + + #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins + #define ETHER_BUFFER_SIZE 16384 + +#elif defined(OSBO) // for OSBo + + #define OS_HW_VERSION OSBO_HW_VERSION_BASE + // these are gpio pin numbers, refer to + // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp + #define PIN_SR_LATCH 60 // P9_12, shift register latch pin + #define PIN_SR_DATA 30 // P9_11, shift register data pin + #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin + #define PIN_SR_OE 50 // P9_14, shift register output enable pin + #define PIN_SENSOR1 48 + #define PIN_RFTX 51 // RF transmitter pin + + #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} + #define ETHER_BUFFER_SIZE 16384 + +#else // for demo / simulation + // use fake hardware pins + #if defined(DEMO) + #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware + #else + #define OS_HW_VERSION SIM_HW_VERSION_BASE + #endif + #define PIN_SR_LATCH 0 + #define PIN_SR_DATA 0 + #define PIN_SR_CLOCK 0 + #define PIN_SR_OE 0 + #define PIN_SENSOR1 0 + #define PIN_SENSOR2 0 + #define PIN_RFTX 0 + #define PIN_FREE_LIST {} + #define ETHER_BUFFER_SIZE 16384 +#endif + +#if defined(ENABLE_DEBUG) /** Serial debug functions */ + + #if defined(ARDUINO) + #define DEBUG_BEGIN(x) {Serial.begin(x);} + #define DEBUG_PRINT(x) {Serial.print(x);} + #define DEBUG_PRINTLN(x) {Serial.println(x);} + #else + #include + #define DEBUG_BEGIN(x) {} /** Serial debug functions */ + inline void DEBUG_PRINT(int x) {printf("%d", x);} + inline void DEBUG_PRINT(const char*s) {printf("%s", s);} + #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} + #endif + +#else + + #define DEBUG_BEGIN(x) {} + #define DEBUG_PRINT(x) {} + #define DEBUG_PRINTLN(x) {} + +#endif + +/** Re-define avr-specific (e.g. PGM) types to use standard types */ +#if !defined(ARDUINO) + #include + #include + #include + #include + inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} + inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} + #define now() time(0) + #define pgm_read_byte(x) *(x) + #define PSTR(x) x + #define F(x) x + #define strcat_P strcat + #define strcpy_P strcpy + #define sprintf_P sprintf + #include + #define String string + using namespace std; + #define PROGMEM + typedef const char* PGM_P; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef bool boolean; + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite +#endif /** Other defines */ // button values @@ -587,5 +498,3 @@ typedef enum { #define DISPLAY_MSG_MS 2000 // message display time (milliseconds) #endif // _DEFINES_H - - diff --git a/examples/mainArduino/mainArduino.ino b/examples/mainArduino/mainArduino.ino deleted file mode 100644 index 318ee60b1..000000000 --- a/examples/mainArduino/mainArduino.ino +++ /dev/null @@ -1,30 +0,0 @@ -#include - -#if defined(ESP8266) - struct tcp_pcb; - extern struct tcp_pcb* tcp_tw_pcbs; - extern "C" void tcp_abort (struct tcp_pcb* pcb); - void tcpCleanup() { // losing bytes work around - while(tcp_tw_pcbs) { tcp_abort(tcp_tw_pcbs); } - } -#else - #include -#endif - -#include "OpenSprinkler.h" - -extern OpenSprinkler os; - -void do_setup(); -void do_loop(); - -void setup() { - do_setup(); -} - -void loop() { - do_loop(); -#if defined(ESP8266) - tcpCleanup(); -#endif -} diff --git a/gpio.cpp b/gpio.cpp index 49c4afefa..8afd1bbfd 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -62,7 +62,7 @@ uint16_t PCA9555::i2c_read(uint8_t reg) { Wire.beginTransmission(address); Wire.write(reg); Wire.endTransmission(); - if(Wire.requestFrom(address, (uint8_t)2) != 2) {return 0xFFFF;} + if(Wire.requestFrom(address, (uint8_t)2) != 2) {return 0xFFFF; Serial.println("GPIO error");} uint16_t data0 = Wire.read(); uint16_t data1 = Wire.read(); return data0+(data1<<8); @@ -77,6 +77,34 @@ void PCA9555::i2c_write(uint8_t reg, uint16_t v){ Wire.endTransmission(); } +void PCA9555::shift_out(uint8_t plat, uint8_t pclk, uint8_t pdat, uint8_t v) { + if(plat - #include "gpio.h" - #include "espconnect.h" - char ether_buffer[ETHER_BUFFER_SIZE]; - -#ifdef ESP8266_ETHERNET - #include "UIPServer.h" - #include "UIPClient.h" - #include "UIPEthernet.h" - UIPServer *m_server = 0; - UIPClient *m_client = 0; - UIPEthernetClass ether; -#endif - -#else - #include - byte Ethernet::buffer[ETHER_BUFFER_SIZE]; // Ethernet packet buffer - SdFat sd; // SD card object -#endif - -unsigned long getNtpTime(); +#if defined(ARDUINO) + #if defined(ESP8266) + #include + ESP8266WebServer *w_server = NULL; // due to lwIP, both WiFi and wired use the unified w_server variable + ENC28J60lwIP eth(PIN_ETHER_CS); + bool useEth = false; + static uint16_t led_blink_ms = LED_FAST_BLINK; + #else + EthernetServer *m_server = NULL; + EthernetClient *m_client = NULL; + SdFat sd; // SD card object + bool useEth = true; + #endif + unsigned long getNtpTime(); #else // header and defs for RPI/BBB - -#include -#include -#include "etherport.h" -#include "gpio.h" -char ether_buffer[ETHER_BUFFER_SIZE]; -EthernetServer *m_server = 0; -EthernetClient *m_client = 0; - + EthernetServer *m_server = 0; + EthernetClient *m_client = 0; #endif void reset_all_stations(); void reset_all_stations_immediate(); -void push_message(byte type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); +void push_message(int type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); void manual_start_program(byte, byte); -void httpget_callback(byte, uint16_t, uint16_t); - +void remote_http_callback(char*); // Small variations have been added to the timing values below // to minimize conflicting events -#define NTP_SYNC_INTERVAL 86403L // NYP sync interval, 24 hrs -#define RTC_SYNC_INTERVAL 60 // RTC sync interval, 60 secs -#define CHECK_NETWORK_INTERVAL 601 // Network checking timeout, 10 minutes -#define CHECK_WEATHER_TIMEOUT 7201 // Weather check interval: 2 hours -#define CHECK_WEATHER_SUCCESS_TIMEOUT 86433L // Weather check success interval: 24 hrs -#define LCD_BACKLIGHT_TIMEOUT 15 // LCD backlight timeout: 15 secs -#define PING_TIMEOUT 200 // Ping test timeout: 200 ms - -extern char tmp_buffer[]; // scratch buffer - -#ifdef ESP8266 -ESP8266WebServer *wifi_server = NULL; -static uint16_t led_blink_ms = LED_FAST_BLINK; -#endif +#define NTP_SYNC_INTERVAL 86413L // NTP sync interval (in seconds) +#define CHECK_NETWORK_INTERVAL 601 // Network checking timeout (in seconds) +#define CHECK_WEATHER_TIMEOUT 21613L // Weather check interval (in seconds) +#define CHECK_WEATHER_SUCCESS_TIMEOUT 86400L // Weather check success interval (in seconds) +#define LCD_BACKLIGHT_TIMEOUT 15 // LCD backlight timeout (in seconds)) +#define PING_TIMEOUT 200 // Ping test timeout (in ms) +#define UI_STATE_MACHINE_INTERVAL 50 // how often does ui_state_machine run (in ms) +#define CLIENT_READ_TIMEOUT 5 // client read timeout (in seconds) +#define DHCP_CHECKLEASE_INTERVAL 3600L // DHCP check lease interval (in 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 + // ====== Object defines ====== OpenSprinkler os; // OpenSprinkler object -ProgramData pd; // ProgramdData object +ProgramData pd; // ProgramdData object /* ====== Robert Hillman (RAH)'s implementation of flow sensor ====== * flow_begin - time when valve turns on @@ -105,960 +85,964 @@ ulong flow_count = 0; byte prev_flow_state = HIGH; float flow_last_gpm=0; -void flow_poll() { - byte curr_flow_state = digitalReadExt(PIN_FLOWSENSOR); - if(os.options[OPTION_SENSOR1_TYPE]!=SENSOR_TYPE_FLOW) return; - -#ifdef ESP8266 - if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { - prev_flow_state = curr_flow_state; - return; - } - prev_flow_state = curr_flow_state; -#endif - - ulong curr = millis(); - - if(curr < os.flowcount_time_ms+10) return; // debounce threshold: 10ms - flow_count++; - os.flowcount_time_ms = curr; - - /* RAH implementation of flow sensor */ - if (flow_start==0) { flow_gallons=0; flow_start=curr;} // if first pulse, record time - if ((curr-flow_start)<90000) { flow_gallons=0; } // wait 90 seconds before recording flow_begin - else { if (flow_gallons==1) { flow_begin = curr;}} - flow_stop = curr; // get time in ms for stop - flow_gallons++; // increment gallon count for each interrupt - /* End of RAH implementation of flow sensor */ -} - -volatile byte flow_isr_flag = false; -/** Flow sensor interrupt service routine */ -#ifdef ESP8266 +uint32_t reboot_timer = 0; -ICACHE_RAM_ATTR void flow_isr() // for ESP8266, ISR must be marked ICACHE_RAM_ATTR -#else -void flow_isr() -#endif -{ - flow_isr_flag = true; +void flow_poll() { + #if defined(ESP8266) + if(os.hw_rev == 2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + #endif + byte curr_flow_state = digitalReadExt(PIN_SENSOR1); + if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { // only record on falling edge + prev_flow_state = curr_flow_state; + return; + } + prev_flow_state = curr_flow_state; + ulong curr = millis(); + flow_count++; + + /* RAH implementation of flow sensor */ + if (flow_start==0) { flow_gallons=0; flow_start=curr;} // if first pulse, record time + if ((curr-flow_start)<90000) { flow_gallons=0; } // wait 90 seconds before recording flow_begin + else { if (flow_gallons==1) { flow_begin = curr;}} + flow_stop = curr; // get time in ms for stop + flow_gallons++; // increment gallon count for each poll + /* End of RAH implementation of flow sensor */ } #if defined(ARDUINO) // ====== UI defines ====== static char ui_anim_chars[3] = {'.', 'o', 'O'}; -#define UI_STATE_DEFAULT 0 -#define UI_STATE_DISP_IP 1 -#define UI_STATE_DISP_GW 2 -#define UI_STATE_RUNPROG 3 +#define UI_STATE_DEFAULT 0 +#define UI_STATE_DISP_IP 1 +#define UI_STATE_DISP_GW 2 +#define UI_STATE_RUNPROG 3 static byte ui_state = UI_STATE_DEFAULT; static byte ui_state_runprog = 0; -#ifdef ESP8266 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); - byte button; - ulong timeout = millis()+4000; - do { - button = os.button_read(BUTTON_WAIT_NONE); - if((button&BUTTON_MASK)==BUTTON_3 && (button&BUTTON_FLAG_DOWN)) return true; - if((button&BUTTON_MASK)==BUTTON_1 && (button&BUTTON_FLAG_DOWN)) return false; - delay(10); - } while(millis() < timeout); - return false; + os.lcd_print_line_clear_pgm(str, 0); + os.lcd_print_line_clear_pgm(PSTR("(B1:No, B3:Yes)"), 1); + byte button; + ulong start = millis(); + do { + button = os.button_read(BUTTON_WAIT_NONE); + if((button&BUTTON_MASK)==BUTTON_3 && (button&BUTTON_FLAG_DOWN)) return true; + if((button&BUTTON_MASK)==BUTTON_1 && (button&BUTTON_FLAG_DOWN)) return false; + delay(10); + } while(millis() - start < 2500); + return false; } -#endif void ui_state_machine() { - -#ifdef ESP8266 - // process screen led - static ulong led_toggle_timeout = 0; - if(led_blink_ms) { - if(millis()>led_toggle_timeout) { - os.toggle_screen_led(); - led_toggle_timeout = millis() + led_blink_ms; - } - } -#endif - - if (!os.button_timeout) { - os.lcd_set_brightness(0); - ui_state = UI_STATE_DEFAULT; // also recover to default state - } - - // read button, if something is pressed, wait till release - byte button = os.button_read(BUTTON_WAIT_HOLD); - - if (button & BUTTON_FLAG_DOWN) { // repond only to button down events - os.button_timeout = LCD_BACKLIGHT_TIMEOUT; - os.lcd_set_brightness(1); - } else { - return; - } - - DEBUG_PRINT("UI_STATE="); - DEBUG_PRINTLN(ui_state); - - switch(ui_state) { - case UI_STATE_DEFAULT: - switch (button & BUTTON_MASK) { - case BUTTON_1: - DEBUG_PRINTLN("BUTTON 1"); - if (button & BUTTON_FLAG_HOLD) { // holding B1 - if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B1, run a short test (internal test) - #ifdef ESP8266 - if(!ui_confirm(PSTR("Start 2s test?"))) {ui_state = UI_STATE_DEFAULT; break;} - #endif - manual_start_program(255, 0); - } else if (digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B1, display gateway IP - #ifdef ESP8266 - os.lcd.clear(0, 1); - os.lcd.setCursor(0, 0); - #ifdef ESP8266_ETHERNET - if (m_server) - os.lcd.print(ether.gatewayIP()); - else - #endif - os.lcd.print(WiFi.gatewayIP()); - #else - os.lcd.clear(); - os.lcd_print_ip(ether.gwip, 0); - #endif - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR("(gwip)")); - ui_state = UI_STATE_DISP_IP; - } else { // if no other button is clicked, stop all zones - #ifdef ESP8266 - if(!ui_confirm(PSTR("Stop all zones?"))) {ui_state = UI_STATE_DEFAULT; break;} - #endif - reset_all_stations(); - } - } else { // clicking B1: display device IP and port - #ifdef ESP8266 - os.lcd.clear(0, 1); - os.lcd.setCursor(0, 0); - #ifdef ESP8266_ETHERNET - if (m_server) - os.lcd.print(ether.localIP()); - else - #endif - os.lcd.print(WiFi.localIP()); - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR(":")); - uint16_t httpport = (uint16_t)(os.options[OPTION_HTTPPORT_1]<<8) + (uint16_t)os.options[OPTION_HTTPPORT_0]; - os.lcd.print(httpport); - #else - os.lcd.clear(); - os.lcd_print_ip(ether.myip, 0); - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR(":")); - os.lcd.print(ether.hisport); - #endif - os.lcd_print_pgm(PSTR(" (ip:port)")); - ui_state = UI_STATE_DISP_IP; - } - break; - case BUTTON_2: - DEBUG_PRINTLN("BUTTON 2"); - if (button & BUTTON_FLAG_HOLD) { // holding B2 - if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B2, display external IP - os.lcd_print_ip((byte*)(&os.nvdata.external_ip), 1); - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR("(eip)")); - ui_state = UI_STATE_DISP_IP; - } else if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B2, display last successful weather call - //os.lcd.clear(0, 1); - os.lcd_print_time(os.checkwt_success_lasttime); - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR("(lswc)")); - ui_state = UI_STATE_DISP_IP; - } else { // if no other button is clicked, reboot - #ifdef ESP8266 - if(!ui_confirm(PSTR("Reboot device?"))) {ui_state = UI_STATE_DEFAULT; break;} - #endif - os.reboot_dev(); - } - } else { // clicking B2: display MAC - #ifdef ESP8266 - os.lcd.clear(0, 1); - byte mac[6]; - #ifdef ESP8266_ETHERNET - if (m_server) { - os.get_hardware_mac(); - memcpy(mac, tmp_buffer, 6); + + // to avoid ui_state_machine taking too much computation time + // we run it only every UI_STATE_MACHINE_INTERVAL ms + static uint32_t last_usm = 0; + if(millis() - last_usm <= UI_STATE_MACHINE_INTERVAL) { return; } + last_usm = millis(); + +#if defined(ESP8266) + // process screen led + static ulong led_toggle_timeout = 0; + if(led_blink_ms) { + if(millis()>led_toggle_timeout) { + os.toggle_screen_led(); + led_toggle_timeout = millis() + led_blink_ms; } - else - #endif - WiFi.macAddress(mac); - os.lcd_print_mac(mac); - #else - os.lcd.clear(); - os.lcd_print_mac(ether.mymac); - #endif - ui_state = UI_STATE_DISP_GW; - } - break; - case BUTTON_3: - DEBUG_PRINTLN("BUTTON 3"); - if (button & BUTTON_FLAG_HOLD) { // holding B3 - if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B3, display up time - os.lcd_print_time(os.powerup_lasttime); - os.lcd.setCursor(0, 1); - os.lcd_print_pgm(PSTR("(lupt)")); - ui_state = UI_STATE_DISP_IP; - } else if(digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B3, reset to AP and reboot - #ifdef ESP8266 - if(!ui_confirm(PSTR("Reset to AP?"))) {ui_state = UI_STATE_DEFAULT; break;} - os.reset_to_ap(); - #endif - } else { // if no other button is clicked, go to Run Program main menu - os.lcd_print_line_clear_pgm(PSTR("Run a Program:"), 0); - os.lcd_print_line_clear_pgm(PSTR("Click B3 to list"), 1); - ui_state = UI_STATE_RUNPROG; - } - } else { // clicking B3: switch board display (cycle through master and all extension boards) - os.status.display_board = (os.status.display_board + 1) % (os.nboards); - } - break; - } - break; - case UI_STATE_DISP_IP: - case UI_STATE_DISP_GW: - ui_state = UI_STATE_DEFAULT; - break; - case UI_STATE_RUNPROG: - if ((button & BUTTON_MASK)==BUTTON_3) { - if (button & BUTTON_FLAG_HOLD) { - // start - manual_start_program(ui_state_runprog, 0); - ui_state = UI_STATE_DEFAULT; - } else { - ui_state_runprog = (ui_state_runprog+1) % (pd.nprograms+1); - os.lcd_print_line_clear_pgm(PSTR("Hold B3 to start"), 0); - if(ui_state_runprog > 0) { - ProgramStruct prog; - pd.read(ui_state_runprog-1, &prog); - os.lcd_print_line_clear_pgm(PSTR(" "), 1); - os.lcd.setCursor(0, 1); - os.lcd.print((int)ui_state_runprog); - os.lcd_print_pgm(PSTR(". ")); - os.lcd.print(prog.name); - } else { - os.lcd_print_line_clear_pgm(PSTR("0. Test (1 min)"), 1); - } - } - } - break; - } + } +#endif + + if (!os.button_timeout) { + os.lcd_set_brightness(0); + ui_state = UI_STATE_DEFAULT; // also recover to default state + } + + // read button, if something is pressed, wait till release + byte button = os.button_read(BUTTON_WAIT_HOLD); + + if (button & BUTTON_FLAG_DOWN) { // repond only to button down events + os.button_timeout = LCD_BACKLIGHT_TIMEOUT; + os.lcd_set_brightness(1); + } else { + return; + } + + switch(ui_state) { + case UI_STATE_DEFAULT: + switch (button & BUTTON_MASK) { + case BUTTON_1: + if (button & BUTTON_FLAG_HOLD) { // holding B1 + if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B1, run a short test (internal test) + if(!ui_confirm(PSTR("Start 2s test?"))) {ui_state = UI_STATE_DEFAULT; break;} + manual_start_program(255, 0); + } else if (digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B1, display gateway IP + os.lcd.clear(0, 1); + os.lcd.setCursor(0, 0); + #if defined(ESP8266) + if (useEth) { os.lcd.print(eth.gatewayIP()); } + else { os.lcd.print(WiFi.gatewayIP()); } + #else + { os.lcd.print(Ethernet.gatewayIP()); } + #endif + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR("(gwip)")); + ui_state = UI_STATE_DISP_IP; + } 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 + os.lcd.clear(0, 1); + os.lcd.setCursor(0, 0); + #if defined(ESP8266) + if (useEth) { os.lcd.print(eth.localIP()); } + else { os.lcd.print(WiFi.localIP()); } + #else + { os.lcd.print(Ethernet.localIP()); } + #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)")); + ui_state = UI_STATE_DISP_IP; + } + break; + case BUTTON_2: + if (button & BUTTON_FLAG_HOLD) { // holding B2 + if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B2, display external IP + os.lcd_print_ip((byte*)(&os.nvdata.external_ip), 1); + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR("(eip)")); + ui_state = UI_STATE_DISP_IP; + } else if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B2, display last successful weather call + //os.lcd.clear(0, 1); + os.lcd_print_time(os.checkwt_success_lasttime); + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR("(lswc)")); + ui_state = UI_STATE_DISP_IP; + } else { // if no other button is clicked, reboot + if(!ui_confirm(PSTR("Reboot device?"))) {ui_state = UI_STATE_DEFAULT; break;} + os.reboot_dev(REBOOT_CAUSE_BUTTON); + } + } else { // clicking B2: display MAC + os.lcd.clear(0, 1); + byte mac[6]; + os.load_hardware_mac(mac, useEth); + os.lcd_print_mac(mac); + ui_state = UI_STATE_DISP_GW; + } + break; + case BUTTON_3: + if (button & BUTTON_FLAG_HOLD) { // holding B3 + if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B3, display up time + os.lcd_print_time(os.powerup_lasttime); + os.lcd.setCursor(0, 1); + os.lcd_print_pgm(PSTR("(lupt) cause:")); + os.lcd.print(os.last_reboot_cause); + ui_state = UI_STATE_DISP_IP; + } else if(digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B3, reset to AP and reboot + #if defined(ESP8266) + if(!ui_confirm(PSTR("Reset to AP?"))) {ui_state = UI_STATE_DEFAULT; break;} + os.reset_to_ap(); + #endif + } else { // if no other button is clicked, go to Run Program main menu + os.lcd_print_line_clear_pgm(PSTR("Run a Program:"), 0); + os.lcd_print_line_clear_pgm(PSTR("Click B3 to list"), 1); + ui_state = UI_STATE_RUNPROG; + } + } else { // clicking B3: switch board display (cycle through master and all extension boards) + os.status.display_board = (os.status.display_board + 1) % (os.nboards); + } + break; + } + break; + case UI_STATE_DISP_IP: + case UI_STATE_DISP_GW: + ui_state = UI_STATE_DEFAULT; + break; + case UI_STATE_RUNPROG: + if ((button & BUTTON_MASK)==BUTTON_3) { + if (button & BUTTON_FLAG_HOLD) { + // start + manual_start_program(ui_state_runprog, 0); + ui_state = UI_STATE_DEFAULT; + } else { + ui_state_runprog = (ui_state_runprog+1) % (pd.nprograms+1); + os.lcd_print_line_clear_pgm(PSTR("Hold B3 to start"), 0); + if(ui_state_runprog > 0) { + ProgramStruct prog; + pd.read(ui_state_runprog-1, &prog); + os.lcd_print_line_clear_pgm(PSTR(" "), 1); + os.lcd.setCursor(0, 1); + os.lcd.print((int)ui_state_runprog); + os.lcd_print_pgm(PSTR(". ")); + os.lcd.print(prog.name); + } else { + os.lcd_print_line_clear_pgm(PSTR("0. Test (1 min)"), 1); + } + } + } + break; + } } // ====================== // Setup Function // ====================== void do_setup() { - /* Clear WDT reset flag. */ -#ifdef ESP8266 - if(wifi_server) { delete wifi_server; wifi_server = NULL; } - WiFi.persistent(false); - led_blink_ms = LED_FAST_BLINK; + /* Clear WDT reset flag. */ +#if defined(ESP8266) + if(w_server) { delete w_server; w_server = NULL; } + WiFi.persistent(false); + led_blink_ms = LED_FAST_BLINK; #else - MCUSR &= ~(1< 15) { - // reset after 120 seconds of timeout - sysReset(); - } + wdt_timeout += 1; + // this isr is called every 8 seconds + if (wdt_timeout > 15) { + // reset after 120 seconds of timeout + sysReset(); + } } #endif #else void do_setup() { - initialiseEpoch(); // initialize time reference for millis() and micros() - os.begin(); // OpenSprinkler init - os.options_setup(); // Setup options - - pd.init(); // ProgramData init - - if (os.start_network()) { // initialize network - DEBUG_PRINTLN("network established."); - os.status.network_fails = 0; - } else { - DEBUG_PRINTLN("network failed."); - os.status.network_fails = 1; - } - os.status.req_network = 0; + initialiseEpoch(); // initialize time reference for millis() and micros() + os.begin(); // OpenSprinkler init + os.options_setup(); // Setup options + + pd.init(); // ProgramData init + + if (os.start_network()) { // initialize network + DEBUG_PRINTLN("network established."); + os.status.network_fails = 0; + } else { + DEBUG_PRINTLN("network failed."); + os.status.network_fails = 1; + } + os.status.req_network = 0; + + os.mqtt.init(); + os.status.req_mqtt_restart = true; } #endif void write_log(byte type, ulong curr_time); void schedule_all_stations(ulong curr_time); +void turn_on_station(byte sid); void turn_off_station(byte sid, ulong curr_time); void process_dynamic_events(ulong curr_time); void check_network(); void check_weather(); -void perform_ntp_sync(); +bool process_special_program_command(const char*, uint32_t curr_time); +bool perform_ntp_sync(); void delete_log(char *name); -#ifdef ESP8266 +#if defined(ESP8266) void start_server_ap(); void start_server_client(); -unsigned long reboot_timer = 0; -#ifdef ESP8266_ETHERNET -void handle_web_request(char *p); #endif -#else + void handle_web_request(char *p); -#endif /** Main Loop */ void do_loop() { - /* If flow_isr_flag is on, do flow sensing. - todo: not the most efficient way, as we can't do I2C inside ISR. - need to figure out a more efficient way to do flow sensing */ - if(flow_isr_flag) { - flow_isr_flag = false; - flow_poll(); - } - - static ulong last_time = 0; - static ulong last_minute = 0; - - byte bid, sid, s, pid, qid, bitvalue; - ProgramStruct prog; - - os.status.mas = os.options[OPTION_MASTER_STATION]; - os.status.mas2= os.options[OPTION_MASTER_STATION_2]; - time_t curr_time = os.now_tz(); - // ====== Process Ethernet packets ====== -#if defined(ARDUINO) // Process Ethernet packets for Arduino - #ifdef ESP8266 - static ulong connecting_timeout; - switch(os.state) { - case OS_STATE_INITIAL: - if(os.get_wifi_mode()==WIFI_MODE_AP) { - start_server_ap(); - os.state = OS_STATE_CONNECTED; - connecting_timeout = 0; - } else { - led_blink_ms = LED_SLOW_BLINK; - start_network_sta(os.wifi_config.ssid.c_str(), os.wifi_config.pass.c_str()); - os.config_ip(); - os.state = OS_STATE_CONNECTING; - connecting_timeout = millis() + 120000L; - os.lcd.setCursor(0, -1); - os.lcd.print(F("Connecting to...")); - os.lcd.setCursor(0, 2); - os.lcd.print(os.wifi_config.ssid); - } - break; - - case OS_STATE_TRY_CONNECT: - led_blink_ms = LED_SLOW_BLINK; - start_network_sta_with_ap(os.wifi_config.ssid.c_str(), os.wifi_config.pass.c_str()); - os.config_ip(); - os.state = OS_STATE_CONNECTED; - break; - - case OS_STATE_CONNECTING: - if(WiFi.status() == WL_CONNECTED) { - led_blink_ms = 0; - os.set_screen_led(LOW); - os.lcd.clear(); - start_server_client(); - os.state = OS_STATE_CONNECTED; - connecting_timeout = 0; - } else { - if(millis()>connecting_timeout) { - os.state = OS_STATE_INITIAL; - DEBUG_PRINTLN(F("timeout")); - } - } - break; - - case OS_STATE_CONNECTED: - if(os.get_wifi_mode() == WIFI_MODE_AP) { - wifi_server->handleClient(); - connecting_timeout = 0; - if(os.get_wifi_mode()==WIFI_MODE_STA) { - // already in STA mode, waiting to reboot - break; - } - if(WiFi.status()==WL_CONNECTED && WiFi.localIP()) { - os.wifi_config.mode = WIFI_MODE_STA; - os.options_save(true); - os.reboot_dev(); - } - } - else { - if(WiFi.status() == WL_CONNECTED) { - wifi_server->handleClient(); - connecting_timeout = 0; - } else { - os.state = OS_STATE_INITIAL; - } - } - break; - } - #if defined(ESP8266_ETHERNET) - if (m_server) { - ether.maintain(); - UIPClient client = m_server->available(); - if (client) { - while (true) { - int len = client.read((uint8_t*) ether_buffer, ETHER_BUFFER_SIZE); - if (len <= 0) { - if(!client.connected()) { - break; - } else { - continue; - } - - } else { - m_client = &client; - ether_buffer[len] = 0; // put a zero at the end of the packet - handle_web_request(ether_buffer); - m_client= 0; - break; - } - } + // handle flow sensor using polling every 1ms (maximum freq 1/(2*1ms)=500Hz) + static ulong flowpoll_timeout=0; + if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + ulong curr = millis(); + if(curr!=flowpoll_timeout) { + flowpoll_timeout = curr; + flow_poll(); } } - #endif - - #else // AVR - - uint16_t pos=ether.packetLoop(ether.packetReceive()); - if (pos>0) { // packet received - handle_web_request((char*)Ethernet::buffer+pos); - } - wdt_reset(); // reset watchdog timer - wdt_timeout = 0; - #endif - - ui_state_machine(); + + static ulong last_time = 0; + static ulong last_minute = 0; + + byte bid, sid, s, pid, qid, bitvalue; + ProgramStruct prog; + + os.status.mas = os.iopts[IOPT_MASTER_STATION]; + os.status.mas2= os.iopts[IOPT_MASTER_STATION_2]; + time_t curr_time = os.now_tz(); + + // ====== Process Ethernet packets ====== +#if defined(ARDUINO) // Process Ethernet packets for Arduino + #if defined(ESP8266) + static ulong connecting_timeout; + switch(os.state) { + case OS_STATE_INITIAL: + if(useEth) { + led_blink_ms = 0; + os.set_screen_led(LOW); + os.lcd.clear(); + os.save_wifi_ip(); + start_server_client(); + os.state = OS_STATE_CONNECTED; + connecting_timeout = 0; + } + else if(os.get_wifi_mode()==WIFI_MODE_AP) { + start_server_ap(); + os.state = OS_STATE_CONNECTED; + connecting_timeout = 0; + } else { + led_blink_ms = LED_SLOW_BLINK; + start_network_sta(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); + os.config_ip(); + os.state = OS_STATE_CONNECTING; + connecting_timeout = millis() + 120000L; + os.lcd.setCursor(0, -1); + os.lcd.print(F("Connecting to...")); + os.lcd.setCursor(0, 2); + os.lcd.print(os.wifi_ssid); + } + break; + + case OS_STATE_TRY_CONNECT: + led_blink_ms = LED_SLOW_BLINK; + start_network_sta_with_ap(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); + os.config_ip(); + os.state = OS_STATE_CONNECTED; + break; + + case OS_STATE_CONNECTING: + if(WiFi.status() == WL_CONNECTED) { + led_blink_ms = 0; + os.set_screen_led(LOW); + os.lcd.clear(); + os.save_wifi_ip(); + start_server_client(); + os.state = OS_STATE_CONNECTED; + connecting_timeout = 0; + } else { + if(millis()>connecting_timeout) { + os.state = OS_STATE_INITIAL; + WiFi.disconnect(true); + DEBUG_PRINTLN(F("timeout")); + } + } + break; + + case OS_STATE_CONNECTED: + if(os.get_wifi_mode() == WIFI_MODE_AP) { + w_server->handleClient(); + connecting_timeout = 0; + if(os.get_wifi_mode()==WIFI_MODE_STA) { + // already in STA mode, waiting to reboot + break; + } + if(WiFi.status()==WL_CONNECTED && WiFi.localIP()) { + os.iopts[IOPT_WIFI_MODE] = WIFI_MODE_STA; + os.iopts_save(); + os.reboot_dev(REBOOT_CAUSE_WIFIDONE); + } + } else { + if(useEth || WiFi.status() == WL_CONNECTED) { + w_server->handleClient(); + connecting_timeout = 0; + } else { + DEBUG_PRINTLN(F("WiFi disconnected, going back to initial")); + os.state = OS_STATE_INITIAL; + WiFi.disconnect(true); + } + } + 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; + while(client.connected() && now() < cli_timeout) { + size_t size = client.available(); + if(size>0) { + if(size>ETHER_BUFFER_SIZE) size=ETHER_BUFFER_SIZE; + int len = client.read((uint8_t*) ether_buffer, size); + if(len>0) { + m_client = &client; + ether_buffer[len] = 0; // properly end the buffer + handle_web_request(ether_buffer); + m_client = NULL; + break; + } + } + } + client.stop(); + } + + wdt_reset(); // reset watchdog timer + wdt_timeout = 0; + + #endif + + ui_state_machine(); #else // Process Ethernet packets for RPI/BBB - EthernetClient client = m_server->available(); - if (client) { - while(true) { - int len = client.read((uint8_t*) ether_buffer, ETHER_BUFFER_SIZE); - if (len <=0) { - if(!client.connected()) { - break; - } else { - continue; - } - } else { - m_client = &client; - ether_buffer[len] = 0; // put a zero at the end of the packet - handle_web_request(ether_buffer); - m_client = 0; - break; - } - } - } -#endif // Process Ethernet packets - - // The main control loop runs once every second - if (curr_time != last_time) { - last_time = curr_time; - if (os.button_timeout) os.button_timeout--; - - #ifdef ESP8266 - if(reboot_timer && millis() > reboot_timer) { - os.reboot_dev(); - } - #endif - + EthernetClient client = m_server->available(); + if (client) { + while(true) { + int len = client.read((uint8_t*) ether_buffer, ETHER_BUFFER_SIZE); + if (len <=0) { + if(!client.connected()) { + break; + } else { + continue; + } + } else { + m_client = &client; + ether_buffer[len] = 0; // put a zero at the end of the packet + handle_web_request(ether_buffer); + m_client = 0; + break; + } + } + } +#endif // Process Ethernet packets + + // Start up MQTT when we have a network connection + if (os.status.req_mqtt_restart && os.network_connected()) { + DEBUG_PRINTLN(F("req_mqtt_restart")); + os.mqtt.begin(); + os.status.req_mqtt_restart = false; + } + os.mqtt.loop(); + + // The main control loop runs once every second + if (curr_time != last_time) { + + #if defined(ESP8266) + if(os.hw_rev==2) { + pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + pinModeExt(PIN_SENSOR2, INPUT_PULLUP); + } + #endif + + last_time = curr_time; + if (os.button_timeout) os.button_timeout--; + #if defined(ARDUINO) - if (!ui_state) - os.lcd_print_time(os.now_tz()); // print time + if (!ui_state) + os.lcd_print_time(os.now_tz()); // print time #endif - // ====== Check raindelay status ====== - if (os.status.rain_delayed) { - if (curr_time >= os.nvdata.rd_stop_time) { // rain delay is over - os.raindelay_stop(); - } - } else { - if (os.nvdata.rd_stop_time > curr_time) { // rain delay starts now - os.raindelay_start(); - } - } - - // ====== Check controller status changes and write log ====== - if (os.old_status.rain_delayed != os.status.rain_delayed) { - if (os.status.rain_delayed) { - // rain delay started, record time - os.raindelay_start_time = curr_time; - push_message(IFTTT_RAINSENSOR, LOGDATA_RAINDELAY, 1); - } else { - // rain delay stopped, write log - write_log(LOGDATA_RAINDELAY, curr_time); - push_message(IFTTT_RAINSENSOR, LOGDATA_RAINDELAY, 0); - } - os.old_status.rain_delayed = os.status.rain_delayed; - } - - // ====== Check rain sensor status ====== -#ifdef ESP8266 - if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN - || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected -#else - if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN) { // if a rain sensor is connected -#endif - os.rainsensor_status(); - if (os.old_status.rain_sensed != os.status.rain_sensed) { - if (os.status.rain_sensed) { - // rain sensor on, record time - os.sensor_lasttime = curr_time; - push_message(IFTTT_RAINSENSOR, LOGDATA_RAINSENSE, 1); - } else { - // rain sensor off, write log - if (curr_time>os.sensor_lasttime+10) { // add a 10 second threshold - // to avoid faulty rain sensors generating - // too many log records - write_log(LOGDATA_RAINSENSE, curr_time); - push_message(IFTTT_RAINSENSOR, LOGDATA_RAINSENSE, 0); - } - } - os.old_status.rain_sensed = os.status.rain_sensed; - } - } - - // ====== Check soil moisture status ====== -#ifdef ESP8266 - if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_SOIL - || os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_SOIL) { // if a soil moisture sensor is connected -#else - if (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_SOIL) { // if a soil moisture sensor is connected -#endif - os.soil_moisture_sensor_status(); - if (os.old_status.soil_moisture_sensed != os.status.soil_moisture_sensed) { - if (os.status.soil_moisture_sensed) { - os.soil_moisture_sensed_time = curr_time + 120*60*60; //Delay 120min todo: add to config - push_message(IFTTT_SOILSENSOR, LOGDATA_SOILSENSE, 1); - } else { - os.soil_moisture_sensed_time = 0; - write_log(LOGDATA_SOILSENSE, curr_time); - push_message(IFTTT_SOILSENSOR, LOGDATA_SOILSENSE, 0); - } - os.old_status.soil_moisture_sensed = os.status.soil_moisture_sensed; - } - - // Delayed set of os.status.soil_moisture_active, so it's not firing during watering - if (os.status.soil_moisture_sensed && os.soil_moisture_sensed_time && curr_time>os.soil_moisture_sensed_time) { - os.status.soil_moisture_active = true; - } else { - os.status.soil_moisture_active = false; - } - } - - - // ===== Check program switch status ===== - if (os.programswitch_status(curr_time)) { - reset_all_stations_immediate(); // immediately stop all stations - if(pd.nprograms > 0) manual_start_program(1, 0); - } - - // ====== Schedule program data ====== - ulong curr_minute = curr_time / 60; - boolean match_found = false; - RuntimeQueueStruct *q; - // since the granularity of start time is minute - // we only need to check once every minute - if (curr_minute != last_minute) { - last_minute = curr_minute; - // check through all programs - for(pid=0; pid>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)) - continue; - - // if station has non-zero water time and the station is not disabled - if (prog.durations[sid] && !(os.station_attrib_bits_read(ADDR_NVM_STNDISABLE+bid)&(1<st = 0; - q->dur = water_time; - q->sid = sid; - q->pid = pid+1; - match_found = true; - } else { - // queue is full - } - }// if water_time - }// if prog.durations[sid] - }// for sid - if(match_found) push_message(IFTTT_PROGRAM_SCHED, pid, prog.use_weather?os.options[OPTION_WATER_PERCENTAGE]:100); - }// if check_match - }// for pid - - // calculate start and end time - if (match_found) { - schedule_all_stations(curr_time); - - // For debugging: print out queued elements - /*DEBUG_PRINT("en:"); - for(q=pd.queue;qsid); - DEBUG_PRINT(","); - DEBUG_PRINT(q->dur); - DEBUG_PRINT(","); - DEBUG_PRINT(q->st); - DEBUG_PRINT("]"); - } - DEBUG_PRINTLN("");*/ - } - }//if_check_current_minute - - // ====== Run program data ====== - // Check if a program is running currently - // If so, do station run-time keeping - if (os.status.program_busy){ - // first, go through run time queue to assign queue elements to stations - q = pd.queue; - qid=0; - for(;qsid; - byte sqi=pd.station_qid[sid]; - // skip if station is already assigned a queue element - // and that queue element has an earlier start time - if(sqi<255 && pd.queue[sqi].stst) continue; - // otherwise assign the queue element to station - pd.station_qid[sid]=qid; - } - // next, go through the stations and perform time keeping - for(bid=0;bidst > 0) { - // if so, check if we should turn it off - if (curr_time >= q->st+q->dur) { - turn_off_station(sid, curr_time); - } - } - // if current station is not running, check if we should turn it on - if(!((bitvalue>>s)&1)) { - if (curr_time >= q->st && curr_time < q->st+q->dur) { - - //turn_on_station(sid); - os.set_station_bit(sid, 1); - - // RAH implementation of flow sensor - flow_start=0; - - } //if curr_time > scheduled_start_time - } // if current station is not running - }//end_s - }//end_bid - - // finally, go through the queue again and clear up elements marked for removal - int qi; - for(qi=pd.nqueue-1;qi>=0;qi--) { - q=pd.queue+qi; - if(!q->dur || curr_time>=q->st+q->dur) { - pd.dequeue(qi); - } - } - - // process dynamic events - process_dynamic_events(curr_time); - - // activate / deactivate valves - os.apply_all_station_bits(); - - // check through runtime queue, calculate the last stop time of sequential stations - pd.last_seq_stop_time = 0; - ulong sst; - byte re=os.options[OPTION_REMOTE_EXT_MODE]; - q = pd.queue; - for(;qsid; - bid = sid>>3; - s = sid&0x07; - // check if any sequential station has a valid stop time - // and the stop time must be larger than curr_time - sst = q->st + q->dur; - if (sst>curr_time) { - // only need to update last_seq_stop_time for sequential stations - if (os.station_attrib_bits_read(ADDR_NVM_STNSEQ+bid)&(1<pd.last_seq_stop_time ) ? sst : pd.last_seq_stop_time; - } - } - } - - // if the runtime queue is empty - // reset all stations - if (!pd.nqueue) { - // turn off all stations - os.clear_all_station_bits(); - os.apply_all_station_bits(); - // reset runtime - pd.reset_runtime(); - // reset program busy bit - os.status.program_busy = 0; - // log flow sensor reading if flow sensor is used - if(os.options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - write_log(LOGDATA_FLOWSENSE, curr_time); - push_message(IFTTT_FLOWSENSOR, (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0); - } - - // in case some options have changed while executing the program - os.status.mas = os.options[OPTION_MASTER_STATION]; // update master station - os.status.mas2= os.options[OPTION_MASTER_STATION_2]; // update master2 station - } - }//if_some_program_is_running - - // handle master - if (os.status.mas>0) { - int16_t mas_on_adj = water_time_decode_signed(os.options[OPTION_MASTER_ON_ADJ]); - int16_t mas_off_adj= water_time_decode_signed(os.options[OPTION_MASTER_OFF_ADJ]); - byte masbit = 0; - os.station_attrib_bits_load(ADDR_NVM_MAS_OP, (byte*)tmp_buffer); // tmp_buffer now stores masop_bits - for(sid=0;sid>3; - s = sid&0x07; - // if this station is running and is set to activate master - if ((os.station_bits[bid]&(1<= q->st + mas_on_adj && - curr_time <= q->st + q->dur + mas_off_adj) { - masbit = 1; - break; - } - } - } - os.set_station_bit(os.status.mas-1, masbit); - } - // handle master2 - if (os.status.mas2>0) { - int16_t mas_on_adj_2 = water_time_decode_signed(os.options[OPTION_MASTER_ON_ADJ_2]); - int16_t mas_off_adj_2= water_time_decode_signed(os.options[OPTION_MASTER_OFF_ADJ_2]); - byte masbit2 = 0; - os.station_attrib_bits_load(ADDR_NVM_MAS_OP_2, (byte*)tmp_buffer); // tmp_buffer now stores masop2_bits - for(sid=0;sid>3; - s = sid&0x07; - // if this station is running and is set to activate master - if ((os.station_bits[bid]&(1<= q->st + mas_on_adj_2 && - curr_time <= q->st + q->dur + mas_off_adj_2) { - masbit2 = 1; - break; - } - } - } - os.set_station_bit(os.status.mas2-1, masbit2); - } - - // process dynamic events - process_dynamic_events(curr_time); - - // activate/deactivate valves - os.apply_all_station_bits(); + // ====== Check raindelay status ====== + if (os.status.rain_delayed) { + if (curr_time >= os.nvdata.rd_stop_time) { // rain delay is over + os.raindelay_stop(); + } + } else { + if (os.nvdata.rd_stop_time > curr_time) { // rain delay starts now + os.raindelay_start(); + } + } + + // ====== Check controller status changes and write log ====== + if (os.old_status.rain_delayed != os.status.rain_delayed) { + if (os.status.rain_delayed) { + // rain delay started, record time + os.raindelay_on_lasttime = curr_time; + push_message(NOTIFY_RAINDELAY, LOGDATA_RAINDELAY, 1); + + } else { + // rain delay stopped, write log + write_log(LOGDATA_RAINDELAY, curr_time); + push_message(NOTIFY_RAINDELAY, LOGDATA_RAINDELAY, 0); + } + os.old_status.rain_delayed = os.status.rain_delayed; + } + + // ====== Check binary (i.e. rain or soil) sensor status ====== + os.detect_binarysensor_status(curr_time); + + if(os.old_status.sensor1_active != os.status.sensor1_active) { + // send notification when sensor1 becomes active + if(os.status.sensor1_active) { + os.sensor1_active_lasttime = curr_time; + push_message(NOTIFY_SENSOR1, LOGDATA_SENSOR1, 1); + } else { + write_log(LOGDATA_SENSOR1, curr_time); + push_message(NOTIFY_SENSOR1, LOGDATA_SENSOR1, 0); + } + } + os.old_status.sensor1_active = os.status.sensor1_active; + + if(os.old_status.sensor2_active != os.status.sensor2_active) { + // send notification when sensor1 becomes active + if(os.status.sensor2_active) { + os.sensor2_active_lasttime = curr_time; + push_message(NOTIFY_SENSOR2, LOGDATA_SENSOR2, 1); + } else { + write_log(LOGDATA_SENSOR2, curr_time); + push_message(NOTIFY_SENSOR2, LOGDATA_SENSOR2, 0); + } + } + os.old_status.sensor2_active = os.status.sensor2_active; + + // ===== Check program switch status ===== + byte pswitch = os.detect_programswitch_status(curr_time); + if(pswitch > 0) { + reset_all_stations_immediate(); // immediately stop all stations + } + if (pswitch & 0x01) { + if(pd.nprograms > 0) manual_start_program(1, 0); + } + if (pswitch & 0x02) { + if(pd.nprograms > 1) manual_start_program(2, 0); + } + + + // ====== Schedule program data ====== + ulong curr_minute = curr_time / 60; + boolean match_found = false; + RuntimeQueueStruct *q; + // since the granularity of start time is minute + // we only need to check once every minute + if (curr_minute != last_minute) { + last_minute = curr_minute; + // check through all programs + for(pid=0; pid>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)) + continue; + + // if station has non-zero water time and the station is not disabled + if (prog.durations[sid] && !(os.attrib_dis[bid]&(1<st = 0; + q->dur = water_time; + q->sid = sid; + q->pid = pid+1; + match_found = true; + } else { + // queue is full + } + }// if water_time + }// if prog.durations[sid] + }// for sid + if(match_found) { + push_message(NOTIFY_PROGRAM_SCHED, pid, prog.use_weather?os.iopts[IOPT_WATER_PERCENTAGE]:100); + } + }// if check_match + }// for pid + + // calculate start and end time + if (match_found) { + schedule_all_stations(curr_time); + + // For debugging: print out queued elements + /*DEBUG_PRINT("en:"); + for(q=pd.queue;qsid); + DEBUG_PRINT(","); + DEBUG_PRINT(q->dur); + DEBUG_PRINT(","); + DEBUG_PRINT(q->st); + DEBUG_PRINT("]"); + } + DEBUG_PRINTLN("");*/ + } + }//if_check_current_minute + + // ====== Run program data ====== + // Check if a program is running currently + // If so, do station run-time keeping + if (os.status.program_busy){ + // first, go through run time queue to assign queue elements to stations + q = pd.queue; + qid=0; + for(;qsid; + byte sqi=pd.station_qid[sid]; + // skip if station is already assigned a queue element + // and that queue element has an earlier start time + if(sqi<255 && pd.queue[sqi].stst) continue; + // otherwise assign the queue element to station + pd.station_qid[sid]=qid; + } + // next, go through the stations and perform time keeping + for(bid=0;bidst > 0) { + // if so, check if we should turn it off + if (curr_time >= q->st+q->dur) { + turn_off_station(sid, curr_time); + } + } + // if current station is not running, check if we should turn it on + if(!((bitvalue>>s)&1)) { + if (curr_time >= q->st && curr_time < q->st+q->dur) { + turn_on_station(sid); + } //if curr_time > scheduled_start_time + } // if current station is not running + }//end_s + }//end_bid + + // finally, go through the queue again and clear up elements marked for removal + int qi; + for(qi=pd.nqueue-1;qi>=0;qi--) { + q=pd.queue+qi; + if(!q->dur || curr_time>=q->st+q->dur) { + pd.dequeue(qi); + } + } + + // process dynamic events + process_dynamic_events(curr_time); + + // activate / deactivate valves + os.apply_all_station_bits(); + + // check through runtime queue, calculate the last stop time of sequential stations + pd.last_seq_stop_time = 0; + ulong sst; + byte re=os.iopts[IOPT_REMOTE_EXT_MODE]; + q = pd.queue; + for(;qsid; + bid = sid>>3; + s = sid&0x07; + // check if any sequential station has a valid stop time + // and the stop time must be larger than curr_time + sst = q->st + q->dur; + if (sst>curr_time) { + // only need to update last_seq_stop_time for sequential stations + if (os.attrib_seq[bid]&(1<pd.last_seq_stop_time ) ? sst : pd.last_seq_stop_time; + } + } + } + + // if the runtime queue is empty + // reset all stations + if (!pd.nqueue) { + // turn off all stations + os.clear_all_station_bits(); + os.apply_all_station_bits(); + // reset runtime + pd.reset_runtime(); + // reset program busy bit + os.status.program_busy = 0; + // log flow sensor reading if flow sensor is used + if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + write_log(LOGDATA_FLOWSENSE, curr_time); + push_message(NOTIFY_FLOWSENSOR, (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0); + } + + // 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 + } + }//if_some_program_is_running + + // handle master + if (os.status.mas>0) { + int16_t mas_on_adj = water_time_decode_signed(os.iopts[IOPT_MASTER_ON_ADJ]); + int16_t mas_off_adj= water_time_decode_signed(os.iopts[IOPT_MASTER_OFF_ADJ]); + byte masbit = 0; + + for(sid=0;sid>3; + s = sid&0x07; + // if this station is running and is set to activate master + if ((os.station_bits[bid]&(1<= q->st + mas_on_adj && + curr_time <= q->st + q->dur + mas_off_adj) { + masbit = 1; + break; + } + } + } + os.set_station_bit(os.status.mas-1, masbit); + } + // handle master2 + if (os.status.mas2>0) { + int16_t mas_on_adj_2 = water_time_decode_signed(os.iopts[IOPT_MASTER_ON_ADJ_2]); + int16_t mas_off_adj_2= water_time_decode_signed(os.iopts[IOPT_MASTER_OFF_ADJ_2]); + byte masbit2 = 0; + for(sid=0;sid>3; + s = sid&0x07; + // if this station is running and is set to activate master + if ((os.station_bits[bid]&(1<= q->st + mas_on_adj_2 && + curr_time <= q->st + q->dur + mas_off_adj_2) { + masbit2 = 1; + break; + } + } + } + os.set_station_bit(os.status.mas2-1, masbit2); + } + + // process dynamic events + process_dynamic_events(curr_time); + + // activate/deactivate valves + os.apply_all_station_bits(); #if defined(ARDUINO) - // process LCD display - if (!ui_state) { - os.lcd_print_station(1, ui_anim_chars[(unsigned long)curr_time%3]); - #ifdef ESP8266 - if(os.get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP()) { - os.lcd.setCursor(0, 2); - os.lcd.clear(2, 2); - if(os.status.program_busy) { - os.lcd.print(F("curr: ")); - uint16_t curr = os.read_current(); - os.lcd.print(curr); - os.lcd.print(F(" mA")); - } - } - #endif - } - - // check safe_reboot condition - if (os.status.safe_reboot) { - // if no program is running at the moment - if (!os.status.program_busy) { - // and if no program is scheduled to run in the next minute - bool willrun = false; - for(pid=0; pid flowcount_rt_start) ? flow_count - flowcount_rt_start: 0; - flowcount_rt_start = flow_count; - } - } - - // perform ntp sync - // instead of using curr_time, which may change due to NTP sync itself - // we use Arduino's millis() method - //if (curr_time % NTP_SYNC_INTERVAL == 0) os.status.req_ntpsync = 1; - if((millis()/1000) % NTP_SYNC_INTERVAL==0) os.status.req_ntpsync = 1; - perform_ntp_sync(); - - // check network connection - if (curr_time && (curr_time % CHECK_NETWORK_INTERVAL==0)) os.status.req_network = 1; - check_network(); - - // check weather - check_weather(); - - byte wuf = os.weather_update_flag; - if(wuf) { - if((wuf&WEATHER_UPDATE_EIP) | (wuf&WEATHER_UPDATE_WL)) { - // at the moment, we only send notification if water level or external IP changed - // the other changes, such as sunrise, sunset changes are ignored for notification - push_message(IFTTT_WEATHER_UPDATE, (wuf&WEATHER_UPDATE_EIP)?os.nvdata.external_ip:0, - (wuf&WEATHER_UPDATE_WL)?os.options[OPTION_WATER_PERCENTAGE]:-1); - } - os.weather_update_flag = 0; - } - static byte reboot_notification = 1; - if(reboot_notification) { - reboot_notification = 0; - push_message(IFTTT_REBOOT); - } - - } - - #if !defined(ARDUINO) - delay(1); // For OSPI/OSBO/LINUX, sleep 1 ms to minimize CPU usage - #endif + // handle reboot request + // check safe_reboot condition + if (os.status.safe_reboot && (curr_time > reboot_timer)) { + // if no program is running at the moment + if (!os.status.program_busy) { + // and if no program is scheduled to run in the next minute + bool willrun = false; + for(pid=0; pid reboot_timer)) { + os.reboot_dev(REBOOT_CAUSE_TIMER); + } + + // real-time flow count + static ulong flowcount_rt_start = 0; + if (os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + if (curr_time % FLOWCOUNT_RT_WINDOW == 0) { + os.flowcount_rt = (flow_count > flowcount_rt_start) ? flow_count - flowcount_rt_start: 0; + flowcount_rt_start = flow_count; + } + } + + // perform ntp sync + // instead of using curr_time, which may change due to NTP sync itself + // we use Arduino's millis() method + if (curr_time % NTP_SYNC_INTERVAL == 0) os.status.req_ntpsync = 1; + //if((millis()/1000) % NTP_SYNC_INTERVAL==15) os.status.req_ntpsync = 1; + perform_ntp_sync(); + + // check network connection + if (curr_time && (curr_time % CHECK_NETWORK_INTERVAL==0)) os.status.req_network = 1; + check_network(); + + // check weather + check_weather(); + + byte wuf = os.weather_update_flag; + if(wuf) { + if((wuf&WEATHER_UPDATE_EIP) | (wuf&WEATHER_UPDATE_WL)) { + // at the moment, we only send notification if water level or external IP changed + // the other changes, such as sunrise, sunset changes are ignored for notification + push_message(NOTIFY_WEATHER_UPDATE, (wuf&WEATHER_UPDATE_EIP)?os.nvdata.external_ip:0, + (wuf&WEATHER_UPDATE_WL)?os.iopts[IOPT_WATER_PERCENTAGE]:-1); + } + os.weather_update_flag = 0; + } + static byte reboot_notification = 1; + if(reboot_notification) { + reboot_notification = 0; + push_message(NOTIFY_REBOOT); + } + } + + #if !defined(ARDUINO) + delay(1); // For OSPI/OSBO/LINUX, sleep 1 ms to minimize CPU usage + #endif +} + +/** Check and process special program command */ +bool process_special_program_command(const char* pname, uint32_t curr_time) { + if(pname[0]==':') { // special command start with : + if(strncmp(pname, ":>reboot_now", 12) == 0) { + os.status.safe_reboot = 0; // reboot regardless of program status + reboot_timer = curr_time + 65; // set a timer to reboot in 65 seconds + // this is to avoid the same command being executed again right after reboot + return true; + } else if(strncmp(pname, ":>reboot", 8) == 0) { + os.status.safe_reboot = 1; // by default reboot should only happen when controller is idle + reboot_timer = curr_time + 65; // set a timer to reboot in 65 seconds + // this is to avoid the same command being executed again right after reboot + return true; + } + } + return false; } /** Make weather query */ void check_weather() { - // do not check weather if - // - network check has failed, or - // - the controller is in remote extension mode - if (os.status.network_fails>0 || os.options[OPTION_REMOTE_EXT_MODE]) return; - -#ifdef ESP8266_ETHERNET - if (!m_server) -#endif -#ifdef ESP8266 - if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; + // do not check weather if + // - network check has failed, or + // - the controller is in remote extension mode + if (os.status.network_fails>0 || os.iopts[IOPT_REMOTE_EXT_MODE]) return; + if (os.status.program_busy) return; + +#if defined(ESP8266) + if (!useEth) { // todo: what about useEth==true? + if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; + } #endif - ulong ntz = os.now_tz(); - if (os.checkwt_success_lasttime && (ntz > os.checkwt_success_lasttime + CHECK_WEATHER_SUCCESS_TIMEOUT)) { - // if weather check has failed to return for too long, restart network - os.checkwt_success_lasttime = 0; - // mark for safe restart - os.status.safe_reboot = 1; - return; - } - if (!os.checkwt_lasttime || (ntz > os.checkwt_lasttime + CHECK_WEATHER_TIMEOUT)) { - os.checkwt_lasttime = ntz; - GetWeather(); - } + ulong ntz = os.now_tz(); + if (os.checkwt_success_lasttime && (ntz > os.checkwt_success_lasttime + CHECK_WEATHER_SUCCESS_TIMEOUT)) { + // if last successful weather call timestamp is more than allowed threshold + // and if the selected adjustment method is not manual + // reset watering percentage to 100 + // todo: the firmware currently needs to be explicitly aware of which adjustment methods + // use manual watering percentage (namely methods 0 and 2), this is not ideal + os.checkwt_success_lasttime = 0; + if(!(os.iopts[IOPT_USE_WEATHER]==0 || os.iopts[IOPT_USE_WEATHER]==2)) { + os.iopts[IOPT_WATER_PERCENTAGE] = 100; // reset watering percentage to 100% + wt_rawData[0] = 0; // reset wt_rawData and errCode + wt_errCode = HTTP_RQT_NOT_RECEIVED; + } + } 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(); + if (wt_errCode == HTTP_RQT_DNS_ERROR) + os.checkwt_lasttime = 0; + } +} + +/** Turn on a station + * This function turns on a scheduled station + */ +void turn_on_station(byte sid) { + // RAH implementation of flow sensor + flow_start=0; + + if (os.set_station_bit(sid, 1)) { + push_message(NOTIFY_STATION_ON, sid); + } } /** Turn off a station @@ -1066,40 +1050,40 @@ void check_weather() { * and writes log record */ void turn_off_station(byte sid, ulong curr_time) { - os.set_station_bit(sid, 0); - - byte qid = pd.station_qid[sid]; - // ignore if we are turning off a station that's not running or scheduled to run - if (qid>=pd.nqueue) return; - - // RAH implementation of flow sensor - if (flow_gallons>1) { - if(flow_stop<=flow_begin) flow_last_gpm = 0; - else flow_last_gpm = (float) 60000/(float)((flow_stop-flow_begin)/(flow_gallons-1)); - }// RAH calculate GPM, 1 pulse per gallon - else {flow_last_gpm = 0;} // RAH if not one gallon (two pulses) measured then record 0 gpm - - RuntimeQueueStruct *q = pd.queue+qid; - - // check if the current time is past the scheduled start time, - // 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)) { - pd.lastrun.station = sid; - pd.lastrun.program = q->pid; - pd.lastrun.duration = curr_time - q->st; - pd.lastrun.endtime = curr_time; - - // log station run - write_log(LOGDATA_STATION, curr_time); - push_message(IFTTT_STATION_RUN, sid, pd.lastrun.duration); - } - } - - // dequeue the element - pd.dequeue(qid); - pd.station_qid[sid] = 0xFF; + os.set_station_bit(sid, 0); + + byte qid = pd.station_qid[sid]; + // ignore if we are turning off a station that's not running or scheduled to run + if (qid>=pd.nqueue) return; + + // RAH implementation of flow sensor + if (flow_gallons>1) { + if(flow_stop<=flow_begin) flow_last_gpm = 0; + else flow_last_gpm = (float) 60000/(float)((flow_stop-flow_begin)/(flow_gallons-1)); + }// RAH calculate GPM, 1 pulse per gallon + else {flow_last_gpm = 0;} // RAH if not one gallon (two pulses) measured then record 0 gpm + + RuntimeQueueStruct *q = pd.queue+qid; + + // check if the current time is past the scheduled start time, + // 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)) { + pd.lastrun.station = sid; + pd.lastrun.program = q->pid; + pd.lastrun.duration = curr_time - q->st; + pd.lastrun.endtime = curr_time; + + // log station run + write_log(LOGDATA_STATION, curr_time); + push_message(NOTIFY_STATION_OFF, sid, pd.lastrun.duration); + } + } + + // dequeue the element + pd.dequeue(qid); + pd.station_qid[sid] = 0xFF; } /** Process dynamic events @@ -1107,42 +1091,48 @@ void turn_off_station(byte sid, ulong curr_time) { * and turn off stations accordingly */ void process_dynamic_events(ulong curr_time) { - // check if rain is detected - bool rain = false; - bool en = os.status.enabled ? true : false; -#ifdef ESP8266 - if (os.status.rain_delayed || - (os.status.rain_sensed && - (os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN || - os.options[OPTION_SENSOR2_TYPE] == SENSOR_TYPE_RAIN))) { -#else - if (os.status.rain_delayed || (os.status.rain_sensed && os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_RAIN)) { -#endif - rain = true; - } - - byte sid, s, bid, qid, rbits; - for(bid=0;bidpid<99) && (!en || (rain && !(rbits&(1<pid>=99) continue; // if this is a manually started program, proceed + if(!en) turn_off_station(sid, curr_time); // if system is disabled, turn off zone + if(rd && !(igrd&(1< curr_time) { - seq_start_time = pd.last_seq_stop_time + station_delay; - } - - RuntimeQueueStruct *q = pd.queue; - byte re = os.options[OPTION_REMOTE_EXT_MODE]; - // go through runtime queue and calculate start time of each station - for(;qst) continue; // if this queue element has already been scheduled, skip - if(!q->dur) continue; // if the element has been marked to reset, skip - byte sid=q->sid; - byte bid=sid>>3; - byte s=sid&0x07; - - // if this is a sequential station and the controller is not in remote extension mode - // use sequential scheduling. station delay time apples - if (os.station_attrib_bits_read(ADDR_NVM_STNSEQ+bid)&(1<st = seq_start_time; - seq_start_time += q->dur; - seq_start_time += station_delay; // add station delay time - } else { - // otherwise, concurrent scheduling - q->st = con_start_time; - // stagger concurrent stations by 1 second - con_start_time++; - } - /*DEBUG_PRINT("["); - DEBUG_PRINT(sid); - DEBUG_PRINT(":"); - DEBUG_PRINT(q->st); - DEBUG_PRINT(","); - DEBUG_PRINT(q->dur); - DEBUG_PRINT("]"); - DEBUG_PRINTLN(pd.nqueue);*/ - if (!os.status.program_busy) { - os.status.program_busy = 1; // set program busy bit - // start flow count - if(os.options[OPTION_SENSOR1_TYPE] == SENSOR_TYPE_FLOW) { // if flow sensor is connected - os.flowcount_log_start = flow_count; - os.sensor_lasttime = curr_time; - } - } - } + ulong con_start_time = curr_time + 1; // concurrent start time + ulong seq_start_time = con_start_time; // sequential start time + + int16_t station_delay = water_time_decode_signed(os.iopts[IOPT_STATION_DELAY_TIME]); + // if the sequential queue has stations running + if (pd.last_seq_stop_time > curr_time) { + seq_start_time = pd.last_seq_stop_time + station_delay; + } + + RuntimeQueueStruct *q = pd.queue; + byte re = os.iopts[IOPT_REMOTE_EXT_MODE]; + // go through runtime queue and calculate start time of each station + for(;qst) continue; // if this queue element has already been scheduled, skip + if(!q->dur) continue; // if the element has been marked to reset, skip + byte sid=q->sid; + byte bid=sid>>3; + byte s=sid&0x07; + + // if this is a sequential station and the controller is not in remote extension mode + // use sequential scheduling. station delay time apples + if (os.attrib_seq[bid]&(1<st = seq_start_time; + seq_start_time += q->dur; + seq_start_time += station_delay; // add station delay time + } else { + // otherwise, concurrent scheduling + q->st = con_start_time; + // stagger concurrent stations by 1 second + con_start_time++; + } + /*DEBUG_PRINT("["); + DEBUG_PRINT(sid); + DEBUG_PRINT(":"); + DEBUG_PRINT(q->st); + DEBUG_PRINT(","); + DEBUG_PRINT(q->dur); + DEBUG_PRINT("]"); + DEBUG_PRINTLN(pd.nqueue);*/ + if (!os.status.program_busy) { + os.status.program_busy = 1; // set program busy bit + // start flow count + if(os.iopts[IOPT_SENSOR1_TYPE] == SENSOR_TYPE_FLOW) { // if flow sensor is connected + os.flowcount_log_start = flow_count; + os.sensor1_active_lasttime = curr_time; + } + } + } } /** Immediately reset all stations * No log records will be written */ void reset_all_stations_immediate() { - os.clear_all_station_bits(); - os.apply_all_station_bits(); - pd.reset_runtime(); + os.clear_all_station_bits(); + os.apply_all_station_bits(); + pd.reset_runtime(); } /** Reset all stations @@ -1218,11 +1208,11 @@ void reset_all_stations_immediate() { * Stations will be logged */ void reset_all_stations() { - RuntimeQueueStruct *q = pd.queue; - // go through runtime queue and assign water time to 0 - for(;qdur = 0; - } + RuntimeQueueStruct *q = pd.queue; + // go through runtime queue and assign water time to 0 + for(;qdur = 0; + } } @@ -1232,291 +1222,235 @@ void reset_all_stations() { * If pid > 0. run program pid-1 */ void manual_start_program(byte pid, byte uwt) { - boolean match_found = false; - reset_all_stations_immediate(); - ProgramStruct prog; - ulong dur; - byte sid, bid, s; - if ((pid>0)&&(pid<255)) { - pd.read(pid-1, &prog); - push_message(IFTTT_PROGRAM_SCHED, pid-1, uwt?os.options[OPTION_WATER_PERCENTAGE]:100, ""); - } - for(sid=0;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)) - continue; - dur = 60; - if(pid==255) dur=2; - else if(pid>0) - dur = water_time_resolve(prog.durations[sid]); - if(uwt) { - dur = dur * os.options[OPTION_WATER_PERCENTAGE] / 100; - } - if(dur>0 && !(os.station_attrib_bits_read(ADDR_NVM_STNDISABLE+bid)&(1<st = 0; - q->dur = dur; - q->sid = sid; - q->pid = 254; - match_found = true; - } - } - } - if(match_found) { - schedule_all_stations(os.now_tz()); - } + boolean match_found = false; + reset_all_stations_immediate(); + ProgramStruct prog; + ulong dur; + byte sid, bid, s; + if ((pid>0)&&(pid<255)) { + pd.read(pid-1, &prog); + push_message(NOTIFY_PROGRAM_SCHED, pid-1, uwt?os.iopts[IOPT_WATER_PERCENTAGE]:100, ""); + } + for(sid=0;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)) + continue; + dur = 60; + if(pid==255) dur=2; + else if(pid>0) + dur = water_time_resolve(prog.durations[sid]); + if(uwt) { + dur = dur * os.iopts[IOPT_WATER_PERCENTAGE] / 100; + } + if(dur>0 && !(os.attrib_dis[bid]&(1<st = 0; + q->dur = dur; + q->sid = sid; + q->pid = 254; + match_found = true; + } + } + } + if(match_found) { + schedule_all_stations(os.now_tz()); + } } // ========================================== // ====== PUSH NOTIFICATION FUNCTIONS ======= // ========================================== void ip2string(char* str, byte ip[4]) { - for(byte i=0;i<4;i++) { - itoa(ip[i], str+strlen(str), 10); - if(i!=3) strcat(str, "."); - } + sprintf_P(str+strlen(str), PSTR("%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); } -void push_message(byte type, uint32_t lval, float fval, const char* sval) { - -#if !defined(ARDUINO) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) || defined(ESP8266) - - static const char* server = DEFAULT_IFTTT_URL; - static char key[IFTTT_KEY_MAXSIZE]; - static char postval[TMP_BUFFER_SIZE]; - - // check if this type of event is enabled for push notification - if((os.options[OPTION_IFTTT_ENABLE]&type) == 0) return; - key[0] = 0; - read_from_file(ifkey_filename, key); - key[IFTTT_KEY_MAXSIZE-1]=0; - - if(strlen(key)==0) return; - - #if defined(ARDUINO) && !defined(ESP8266) - uint16_t _port = ether.hisport; // make a copy of the original port - ether.hisport = 80; - #endif - - strcpy_P(postval, PSTR("{\"value1\":\"")); - - switch(type) { - - case IFTTT_STATION_RUN: - - strcat_P(postval, PSTR("Station ")); - os.get_station_name(lval, postval+strlen(postval)); - strcat_P(postval, PSTR(" closed. It ran for ")); - itoa((int)fval/60, postval+strlen(postval), 10); - strcat_P(postval, PSTR(" minutes ")); - itoa((int)fval%60, postval+strlen(postval), 10); - strcat_P(postval, PSTR(" seconds.")); - if(os.options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - strcat_P(postval, PSTR(" Flow rate: ")); - #if defined(ARDUINO) - dtostrf(flow_last_gpm,5,2,postval+strlen(postval)); - #else - sprintf(tmp_buffer+strlen(tmp_buffer), "%5.2f", flow_last_gpm); - #endif - } - break; - - case IFTTT_PROGRAM_SCHED: - - if(sval) strcat_P(postval, PSTR("Manually scheduled ")); - else strcat_P(postval, PSTR("Automatically scheduled ")); - strcat_P(postval, PSTR("Program ")); - { - ProgramStruct prog; - pd.read(lval, &prog); - if(lval0) { - strcat_P(postval, PSTR("External IP updated: ")); - byte ip[4] = {(byte)((lval>>24)&0xFF), - (byte)((lval>>16)&0xFF), - (byte)((lval>>8)&0xFF), - (byte)(lval&0xFF)}; - ip2string(postval, ip); - } - if(fval>=0) { - strcat_P(postval, PSTR("Water level updated: ")); - itoa((int)fval, postval+strlen(postval), 10); - strcat_P(postval, PSTR("%.")); - } - - break; - - case IFTTT_REBOOT: - #if defined(ARDUINO) - strcat_P(postval, PSTR("Rebooted. Device IP: ")); - #ifdef ESP8266 - { - #ifdef ESP8266_ETHERNET - IPAddress _ip; - if (m_server) { - _ip = ether.localIP(); - } else { - _ip = WiFi.localIP(); - } - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; - ip2string(postval, ip); - #else - IPAddress _ip = WiFi.localIP(); - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; - ip2string(postval, ip); - #endif - } - #else - ip2string(postval, ether.myip); - #endif - //strcat(postval, ":"); - //itoa(_port, postval+strlen(postval), 10); - #else - strcat_P(postval, PSTR("Process restarted.")); - #endif - break; - } - - strcat_P(postval, PSTR("\"}")); - - //DEBUG_PRINTLN(postval); +void push_message(int type, uint32_t lval, float fval, const char* sval) { + static char topic[TMP_BUFFER_SIZE]; + static char payload[TMP_BUFFER_SIZE]; + char* postval = tmp_buffer; + uint32_t volume; -#if defined(ARDUINO) + bool ifttt_enabled = os.iopts[IOPT_IFTTT_ENABLE]&type; - #ifdef ESP8266 - Client *client; - #ifdef ESP8266_ETHERNET - if (m_server) - client = new UIPClient(); - else - #endif - client = new WiFiClient(); - - - if(!client->connect(server, 80)) { - delete client; - return; - } - - char postBuffer[1500]; - sprintf(postBuffer, "POST /trigger/sprinkler/with/key/%s HTTP/1.0\r\n" - "Host: %s\r\n" - "Accept: */*\r\n" - "Content-Length: %d\r\n" - "Content-Type: application/json\r\n" - "\r\n%s", key, server, strlen(postval), postval); - client->write((uint8_t *)postBuffer, strlen(postBuffer)); - - time_t timeout = os.now_tz() + 5; // 5 seconds timeout - while(!client->available() && os.now_tz() < timeout) { - } - - bzero(ether_buffer, ETHER_BUFFER_SIZE); - - while(client->available()) { - client->read((uint8_t*)ether_buffer, ETHER_BUFFER_SIZE); - } - client->stop(); - delete client; - //DEBUG_PRINTLN(ether_buffer); - - #else - if(!ether.dnsLookup(server, true)) { - // if DNS lookup fails, use default IP - ether.hisip[0] = 54; - ether.hisip[1] = 172; - ether.hisip[2] = 244; - ether.hisip[3] = 116; - } - - ether.httpPostVar(PSTR("/trigger/sprinkler/with/key/"), PSTR(DEFAULT_IFTTT_URL), key, postval, httpget_callback); - for(int l=0;l<100;l++) ether.packetLoop(ether.packetReceive()); - ether.hisport = _port; - #endif - -#else + // check if this type of event is enabled for push notification + if (!ifttt_enabled && !os.mqtt.enabled()) + return; - EthernetClient client; - struct hostent *host; - - host = gethostbyname(server); - if (!host) { - DEBUG_PRINT("can't resolve http station - "); - DEBUG_PRINTLN(server); - return; - } - - if (!client.connect((uint8_t*)host->h_addr, 80)) { - client.stop(); - return; - } - - char postBuffer[1500]; - sprintf(postBuffer, "POST /trigger/sprinkler/with/key/%s HTTP/1.0\r\n" - "Host: %s\r\n" - "Accept: */*\r\n" - "Content-Length: %d\r\n" - "Content-Type: application/json\r\n" - "\r\n%s", key, host->h_name, strlen(postval), postval); - client.write((uint8_t *)postBuffer, strlen(postBuffer)); - - bzero(ether_buffer, ETHER_BUFFER_SIZE); - - time_t timeout = now() + 5; // 5 seconds timeout - while(now() < timeout) { - int len=client.read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); - if (len<=0) { - if(!client.connected()) - break; - else - continue; - } - httpget_callback(0, 0, ETHER_BUFFER_SIZE); - } - - client.stop(); + if (ifttt_enabled) { + strcpy_P(postval, PSTR("{\"value1\":\"")); + } -#endif - -#endif + if (os.mqtt.enabled()) { + topic[0] = 0; + payload[0] = 0; + } + + switch(type) { + case NOTIFY_STATION_ON: + + // todo: add IFTTT support for this event as well + if (os.mqtt.enabled()) { + sprintf_P(topic, PSTR("opensprinkler/station/%d"), lval); + strcpy_P(payload, PSTR("{\"state\":1}")); + } + break; + + case NOTIFY_STATION_OFF: + + if (os.mqtt.enabled()) { + sprintf_P(topic, PSTR("opensprinkler/station/%d"), lval); + if (os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + sprintf_P(payload, PSTR("{\"state\":0,\"duration\":%d,\"flow\":%d.%02d}"), (int)fval, (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); + } else { + sprintf_P(payload, PSTR("{\"state\":0,\"duration\":%d}"), (int)fval); + } + } + if (ifttt_enabled) { + char name[STATION_NAME_SIZE]; + os.get_station_name(lval, name); + sprintf_P(postval+strlen(postval), PSTR("Station %s closed. It ran for %d minutes %d seconds."), name, (int)fval/60, (int)fval%60); + + if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + sprintf_P(postval+strlen(postval), PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); + } + } + break; + + case NOTIFY_PROGRAM_SCHED: + + if (ifttt_enabled) { + if (sval) strcat_P(postval, PSTR("Manually scheduled ")); + else strcat_P(postval, PSTR("Automatically scheduled ")); + strcat_P(postval, PSTR("Program ")); + { + ProgramStruct prog; + pd.read(lval, &prog); + if(lval0) { + strcat_P(postval, PSTR("External IP updated: ")); + byte ip[4] = {(byte)((lval>>24)&0xFF), + (byte)((lval>>16)&0xFF), + (byte)((lval>>8)&0xFF), + (byte)(lval&0xFF)}; + ip2string(postval, ip); + } + if(fval>=0) { + sprintf_P(postval+strlen(postval), PSTR("Water level updated: %d%%."), (int)fval); + } + } + break; + + case NOTIFY_REBOOT: + + if (os.mqtt.enabled()) { + strcpy_P(topic, PSTR("opensprinkler/system")); + strcpy_P(payload, PSTR("{\"state\":\"started\"}")); + } + if (ifttt_enabled) { + #if defined(ARDUINO) + strcat_P(postval, PSTR("Rebooted. Device IP: ")); + #if defined(ESP8266) + { + IPAddress _ip; + if (useEth) { + //_ip = Ethernet.localIP(); + _ip = eth.localIP(); + } else { + _ip = WiFi.localIP(); + } + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + ip2string(postval, ip); + } + #else + ip2string(postval, &(Ethernet.localIP()[0])); + #endif + //strcat(postval, ":"); + //itoa(_port, postval+strlen(postval), 10); + #else + strcat_P(postval, PSTR("Process restarted.")); + #endif + } + break; + } + + if (os.mqtt.enabled() && strlen(topic) && strlen(payload)) + os.mqtt.publish(topic, payload); + + if (ifttt_enabled) { + strcat_P(postval, PSTR("\"}")); + + //char postBuffer[1500]; + BufferFiller bf = ether_buffer; + bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" + "Host: $S\r\n" + "Accept: */*\r\n" + "Content-Length: $D\r\n" + "Content-Type: application/json\r\n\r\n$S"), + SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); + + os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, remote_http_callback); + } } // ================================ @@ -1533,14 +1467,14 @@ char LOG_PREFIX[] = "./logs/"; */ void make_logfile_name(char *name) { #if defined(ARDUINO) - #ifndef ESP8266 - sd.chdir("/"); - #endif + #if !defined(ESP8266) + sd.chdir("/"); + #endif #endif - strcpy(tmp_buffer+TMP_BUFFER_SIZE-10, name); - strcpy(tmp_buffer, LOG_PREFIX); - strcat(tmp_buffer, tmp_buffer+TMP_BUFFER_SIZE-10); - strcat_P(tmp_buffer, PSTR(".txt")); + strcpy(tmp_buffer+TMP_BUFFER_SIZE-10, name); + strcpy(tmp_buffer, LOG_PREFIX); + strcat(tmp_buffer, tmp_buffer+TMP_BUFFER_SIZE-10); + strcat_P(tmp_buffer, PSTR(".txt")); } /* To save RAM space, we store log type names @@ -1549,122 +1483,128 @@ void make_logfile_name(char *name) { * so each name is 3 characters total */ static const char log_type_names[] PROGMEM = - " \0" - "rs\0" - "rd\0" - "wl\0" - "fl\0"; + " \0" + "s1\0" + "rd\0" + "wl\0" + "fl\0" + "s2\0" + "cu\0"; /** write run record to log on SD card */ void write_log(byte type, ulong curr_time) { - if (!os.options[OPTION_ENABLE_LOGGING]) return; + if (!os.iopts[IOPT_ENABLE_LOGGING]) return; - // file name will be logs/xxxxx.tx where xxxxx is the day in epoch time - ultoa(curr_time / 86400, tmp_buffer, 10); - make_logfile_name(tmp_buffer); + // file name will be logs/xxxxx.tx where xxxxx is the day in epoch time + ultoa(curr_time / 86400, tmp_buffer, 10); + make_logfile_name(tmp_buffer); - // Step 1: open file if exists, or create new otherwise, - // and move file pointer to the end + // 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 (!os.status.has_sd) return; - - #ifdef ESP8266 - File file = SPIFFS.open(tmp_buffer, "r+"); - if(!file) { - file = SPIFFS.open(tmp_buffer, "w"); - 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 - + + #if defined(ESP8266) + File file = LittleFS.open(tmp_buffer, "r+"); + if(!file) { + file = LittleFS.open(tmp_buffer, "w"); + 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/BBB - 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)) { - return; - } - } - FILE *file; - file = fopen(get_filename_fullpath(tmp_buffer), "rb+"); - if(!file) { - file = fopen(get_filename_fullpath(tmp_buffer), "wb"); - if (!file) return; - } - fseek(file, 0, SEEK_END); -#endif // prepare log folder - - // Step 2: prepare data buffer - strcpy_P(tmp_buffer, PSTR("[")); - - if(type == LOGDATA_STATION) { - itoa(pd.lastrun.program, tmp_buffer+strlen(tmp_buffer), 10); - strcat_P(tmp_buffer, PSTR(",")); - itoa(pd.lastrun.station, tmp_buffer+strlen(tmp_buffer), 10); - strcat_P(tmp_buffer, PSTR(",")); - // duration is unsigned integer - ultoa((ulong)pd.lastrun.duration, tmp_buffer+strlen(tmp_buffer), 10); - } else { - ulong lvalue=0; - if(type==LOGDATA_FLOWSENSE) { - lvalue = (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0; - } - ultoa(lvalue, tmp_buffer+strlen(tmp_buffer), 10); - strcat_P(tmp_buffer, PSTR(",\"")); - strcat_P(tmp_buffer, log_type_names+type*3); - strcat_P(tmp_buffer, PSTR("\",")); - - switch(type) { - case LOGDATA_RAINSENSE: - case LOGDATA_FLOWSENSE: - lvalue = (curr_time>os.sensor_lasttime)?(curr_time-os.sensor_lasttime):0; - break; - case LOGDATA_RAINDELAY: - lvalue = (curr_time>os.raindelay_start_time)?(curr_time-os.raindelay_start_time):0; - break; - case LOGDATA_WATERLEVEL: - lvalue = os.options[OPTION_WATER_PERCENTAGE]; - break; - } - ultoa(lvalue, tmp_buffer+strlen(tmp_buffer), 10); - } - strcat_P(tmp_buffer, PSTR(",")); - ultoa(curr_time, tmp_buffer+strlen(tmp_buffer), 10); - if((os.options[OPTION_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) && (type==LOGDATA_STATION)) { - // RAH implementation of flow sensor - strcat_P(tmp_buffer, PSTR(",")); - #if defined(ARDUINO) - dtostrf(flow_last_gpm,5,2,tmp_buffer+strlen(tmp_buffer)); - #else - sprintf(tmp_buffer+strlen(tmp_buffer), "%5.2f", flow_last_gpm); - #endif - } - strcat_P(tmp_buffer, PSTR("]\r\n")); + 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)) { + return; + } + } + FILE *file; + file = fopen(get_filename_fullpath(tmp_buffer), "rb+"); + if(!file) { + file = fopen(get_filename_fullpath(tmp_buffer), "wb"); + if (!file) return; + } + fseek(file, 0, SEEK_END); +#endif // prepare log folder + + // Step 2: prepare data buffer + strcpy_P(tmp_buffer, PSTR("[")); + + if(type == LOGDATA_STATION) { + itoa(pd.lastrun.program, tmp_buffer+strlen(tmp_buffer), 10); + strcat_P(tmp_buffer, PSTR(",")); + itoa(pd.lastrun.station, tmp_buffer+strlen(tmp_buffer), 10); + strcat_P(tmp_buffer, PSTR(",")); + // duration is unsigned integer + ultoa((ulong)pd.lastrun.duration, tmp_buffer+strlen(tmp_buffer), 10); + } else { + ulong lvalue=0; + if(type==LOGDATA_FLOWSENSE) { + lvalue = (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0; + } + ultoa(lvalue, tmp_buffer+strlen(tmp_buffer), 10); + strcat_P(tmp_buffer, PSTR(",\"")); + strcat_P(tmp_buffer, log_type_names+type*3); + strcat_P(tmp_buffer, PSTR("\",")); + + switch(type) { + case LOGDATA_FLOWSENSE: + lvalue = (curr_time>os.sensor1_active_lasttime)?(curr_time-os.sensor1_active_lasttime):0; + break; + case LOGDATA_SENSOR1: + lvalue = (curr_time>os.sensor1_active_lasttime)?(curr_time-os.sensor1_active_lasttime):0; + break; + case LOGDATA_SENSOR2: + lvalue = (curr_time>os.sensor2_active_lasttime)?(curr_time-os.sensor2_active_lasttime):0; + break; + case LOGDATA_RAINDELAY: + lvalue = (curr_time>os.raindelay_on_lasttime)?(curr_time-os.raindelay_on_lasttime):0; + break; + case LOGDATA_WATERLEVEL: + lvalue = os.iopts[IOPT_WATER_PERCENTAGE]; + break; + } + ultoa(lvalue, tmp_buffer+strlen(tmp_buffer), 10); + } + strcat_P(tmp_buffer, PSTR(",")); + ultoa(curr_time, tmp_buffer+strlen(tmp_buffer), 10); + 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) + dtostrf(flow_last_gpm,5,2,tmp_buffer+strlen(tmp_buffer)); + #else + sprintf(tmp_buffer+strlen(tmp_buffer), "%5.2f", flow_last_gpm); + #endif + } + strcat_P(tmp_buffer, PSTR("]\r\n")); #if defined(ARDUINO) - #ifdef ESP8266 - file.write((byte*)tmp_buffer, strlen(tmp_buffer)); - #else - file.write(tmp_buffer); - #endif - file.close(); + #if defined(ESP8266) + file.write((byte*)tmp_buffer, strlen(tmp_buffer)); + #else + file.write(tmp_buffer); + #endif + file.close(); #else - fwrite(tmp_buffer, 1, strlen(tmp_buffer), file); - fclose(file); + fwrite(tmp_buffer, 1, strlen(tmp_buffer), file); + fclose(file); #endif } @@ -1673,154 +1613,202 @@ void write_log(byte type, ulong curr_time) { * If name is 'all', delete all logs */ void delete_log(char *name) { - if (!os.options[OPTION_ENABLE_LOGGING]) return; + if (!os.iopts[IOPT_ENABLE_LOGGING]) return; #if defined(ARDUINO) - if (!os.status.has_sd) return; - - #ifdef ESP8266 - if (strncmp(name, "all", 3) == 0) { - // delete all log files - Dir dir = SPIFFS.openDir(LOG_PREFIX); - while (dir.next()) { - SPIFFS.remove(dir.fileName()); - } - } else { - // delete a single log file - make_logfile_name(name); - if(!SPIFFS.exists(tmp_buffer)) return; - SPIFFS.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 - + + #if defined(ESP8266) + if (strncmp(name, "all", 3) == 0) { + // delete all log files + Dir dir = LittleFS.openDir(LOG_PREFIX); + while (dir.next()) { + LittleFS.remove(dir.fileName()); + } + } else { + // delete a single log file + make_logfile_name(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/BBB - if (strncmp(name, "all", 3) == 0) { - // delete the log folder - rmdir(get_filename_fullpath(LOG_PREFIX)); - return; - } else { - make_logfile_name(name); - remove(get_filename_fullpath(tmp_buffer)); - } + if (strncmp(name, "all", 3) == 0) { + // delete the log folder + rmdir(get_filename_fullpath(LOG_PREFIX)); + return; + } else { + make_logfile_name(name); + remove(get_filename_fullpath(tmp_buffer)); + } #endif } + /** Perform network check * This function pings the router * to check if it's still online. * If not, it re-initializes Ethernet controller. */ void check_network() { -#if defined(ARDUINO) && !defined(ESP8266) - // 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(15, 1); - os.lcd.write(4); - } - - // ping gateway ip - ether.clientIcmpRequest(ether.gwip); - - ulong start = millis(); - boolean failed = true; - // wait at most PING_TIMEOUT milliseconds for ping result - do { - ether.packetLoop(ether.packetReceive()); - if (ether.packetLoopIcmpCheckReply(ether.gwip)) { - failed = false; - break; - } - } while(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.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 - // nothing to do for other platforms +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + // 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) { + DEBUG_PRINT(F("check_network begin")); + 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(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; + } + DEBUG_PRINT(F("check_network end. failed=%s", failed)); + } +#endif +#if defined(ARDUINO) +#if defined(ESP8266) +#if defined(CHECK_NET) + 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.print(">"); + } + + boolean failed = false; + if (!useEth) { //WIFI: + if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; + failed = !Ping.ping(WiFi.gatewayIP(), 1); + } else { //Ethernet + //if (!eth.connected()) return; + //failed = !Ping.ping(eth.gatewayIP(), 1); + //os.status.req_ntpsync = 1; + //failed = !perform_ntp_sync(); + failed = !eth.connected(); + } + + DEBUG_PRINT(F("check_network: failed=")); + DEBUG_PRINTLN(failed); + + 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; + } + } +#endif +#endif #endif } + + /** Perform NTP sync */ -void perform_ntp_sync() { +bool perform_ntp_sync() { #if defined(ARDUINO) - // do not perform sync if this option is disabled, or if network is not available, or if a program is running - if (!os.options[OPTION_USE_NTP] || os.status.program_busy) return; - #ifdef ESP8266 - #ifdef ESP8266_ETHERNET - if (!m_server) - #endif - - if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; - #else - if (os.status.network_fails>0) return; - #endif - - if (os.status.req_ntpsync) { - // check if rtc is uninitialized - // 978307200 is Jan 1, 2001, 00:00:00 - boolean rtc_zero = (now()<=978307200L); - - os.status.req_ntpsync = 0; - if (!ui_state) { - os.lcd_print_line_clear_pgm(PSTR("NTP Syncing..."),1); - } - ulong t = getNtpTime(); - if (t>0) { - setTime(t); - RTC.set(t); - #ifndef ESP8266 - // if rtc was uninitialized and now it is, restart - if(rtc_zero && now()>978307200L) { - os.reboot_dev(); - } - #endif - } - } + // 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 true; + // do not perform ntp if network is not connected + if (!os.network_connected()) return true; + + if (os.status.req_ntpsync) { + os.status.req_ntpsync = 0; + if (!ui_state) { + os.lcd_print_line_clear_pgm(PSTR("NTP Syncing..."),1); + } + DEBUG_PRINTLN(F("NTP Syncing...")); + static ulong last_ntp_result = 0; + ulong t = getNtpTime(); + if(last_ntp_result>3 && t>last_ntp_result-3 && t0) { + setTime(t); + RTC.set(t); + DEBUG_PRINTLN(RTC.get()); + return true; + } + } + return false; #else - // nothing to do here - // Linux will do this for you + // nothing to do here + // Linux will do this for you + return true; #endif } #if !defined(ARDUINO) // main function for RPI/BBB int main(int argc, char *argv[]) { - do_setup(); + do_setup(); - while(true) { - do_loop(); - } - return 0; + while(true) { + do_loop(); + } + return 0; } #endif diff --git a/mainArduino.ino b/mainArduino.ino index 318ee60b1..1aa649f70 100644 --- a/mainArduino.ino +++ b/mainArduino.ino @@ -1,6 +1,7 @@ #include -#if defined(ESP8266) +//#if defined(ESP8266) +#if 0 struct tcp_pcb; extern struct tcp_pcb* tcp_tw_pcbs; extern "C" void tcp_abort (struct tcp_pcb* pcb); @@ -24,7 +25,8 @@ void setup() { void loop() { do_loop(); -#if defined(ESP8266) +//#if defined(ESP8266) +#if 0 tcpCleanup(); #endif } diff --git a/make.lin302 b/make.lin302 new file mode 100644 index 000000000..680556453 --- /dev/null +++ b/make.lin302 @@ -0,0 +1,34 @@ +SKETCH = ./mainArduino.ino +LIBS = . \ + $(ESP_LIBS)/Wire \ + $(ESP_LIBS)/SPI \ + $(ESP_LIBS)/ESP8266WiFi \ + $(ESP_LIBS)/ESP8266WebServer \ + $(ESP_LIBS)/ESP8266mDNS \ + /data/libs/LittleFS \ + /data/libs/lwIP_enc28j60 \ + /data/libs/SSD1306 \ + /data/libs/rc-switch \ + /data/libs/pubsubclient \ + +ESP_ROOT = /data/esp8266_3.0.2/ +ESPCORE_VERSION = 302 +BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) + +UPLOAD_SPEED = 460800 +UPLOAD_VERB = -v +# for OS3.0 revision 1: reset mode is nodemcu +# UPLOAD_RESET = nodemcu +# Uncomment the line below for OS3.0 revision 0: reset mode is ck +# UPLOAD_RESET = ck + +FLASH_DEF = 4M3M +FLASH_MODE = dio +FLASH_SPEED = 80 +F_CPU = 160000000L + +BOARD = generic + +EXCLUDE_DIRS = ./build-1284 + +include ./makeEspArduino.mk diff --git a/make.lin32 b/make.lin32 index 4406e2040..ac5140679 100644 --- a/make.lin32 +++ b/make.lin32 @@ -7,7 +7,7 @@ LIBS = . \ $(ESP_LIBS)/ESP8266mDNS \ ~/Arduino/libraries/SSD1306 \ ~/Arduino/libraries/rc-switch \ - ~/Arduino/libraries/UIPEthernet \ + ~/Arduino/libraries/EthernetENC \ ~/Arduino/libraries/pubsubclient \ ESP_ROOT = $(HOME)/esp8266_2.7.4/ @@ -17,7 +17,7 @@ BUILD_ROOT = /tmp/$(MAIN_NAME) UPLOAD_SPEED = 460800 UPLOAD_VERB = -v # for OS3.0 revision 1: reset mode is nodemcu -UPLOAD_RESET = nodemcu +#UPLOAD_RESET = nodemcu # Uncomment the line below for OS3.0 revision 0: reset mode is ck # UPLOAD_RESET = ck diff --git a/make.os23 b/make.os23 index 45658d530..1acf2a25c 100644 --- a/make.os23 +++ b/make.os23 @@ -1,12 +1,15 @@ OFLAG = -Os ARDMK_DIR = . -ARDUINO_DIR = $(HOME)/arduino-1.8.5 +ARDUINO_DIR = $(HOME)/arduino-1.8.15 ALTERNATE_CORE_PATH = $(HOME)/.arduino15/packages/MightyCore/hardware/avr/2.0.5 +# If compiling on macOS, use the following instead +#ARDUINO_DIR = $(HOME)/Documents/Arduino/libraries +#ALTERNATE_CORE_PATH = $(HOME)/Library/Arduino15/packages/MightyCore/hardware/avr/2.0.5 BOARD_TAG = 1284 MCU = atmega1284p VARIANT = sanguino F_CPU = 16000000L -ARDUINO_LIBS = UIPEthernet Wire SdFat SPI pubsubclient +ARDUINO_LIBS = EthernetENC Wire SdFat SPI pubsubclient MONITOR_PORT = /dev/ttyUSB0 MONITOR_BAUDRATE = 115200 include ./Arduino.mk diff --git a/makeEspArduino.mk b/makeEspArduino.mk index b09091e90..c494e6038 100644 --- a/makeEspArduino.mk +++ b/makeEspArduino.mk @@ -7,35 +7,54 @@ # General and full license information is available at: # https://github.com/plerup/makeEspArduino # -# Copyright (c) 2016-2018 Peter Lerup. All rights reserved. +# Copyright (c) 2016-2021 Peter Lerup. All rights reserved. # #==================================================================================== -#==================================================================================== -# Project specific values -#==================================================================================== +START_TIME := $(shell date +%s) +__THIS_FILE := $(abspath $(lastword $(MAKEFILE_LIST))) +__TOOLS_DIR := $(dir $(__THIS_FILE))tools +OS ?= $(shell uname -s) + +# Include possible operating system specfic settings +-include $(dir $(__THIS_FILE))/os/$(OS).mk -# Include possible project makefile. This can be used to override the defaults below +# Include possible global user settings +CONFIG_ROOT ?= $(if $(XDG_CONFIG_HOME),$(XDG_CONFIG_HOME),$(HOME)/.config) +-include $(CONFIG_ROOT)/makeEspArduino/config.mk + +# Include possible project specific settings -include $(firstword $(PROJ_CONF) $(dir $(SKETCH))config.mk) -#=== Default values not available in the Arduino configuration files +# Build threads, default is using all the PC cpus +BUILD_THREADS ?= $(shell nproc) +MAKEFLAGS += -j $(BUILD_THREADS) -CHIP ?= esp8266 +# Build verbosity, silent by default +ifndef VERBOSE + MAKEFLAGS += --silent +endif -# Set chip specific default board unless specified -BOARD ?= $(if $(filter $(CHIP), esp32),esp32,generic) +# ESP chip family type +CHIP ?= esp8266 +UC_CHIP := $(shell perl -e "print uc $(CHIP)") +IS_ESP32 := $(if $(filter-out esp32,$(CHIP)),,1) # Serial flashing parameters -UPLOAD_PORT ?= $(shell ls -1tr /dev/tty*USB* 2>/dev/null | tail -1) -UPLOAD_PORT := $(if $(UPLOAD_PORT),$(UPLOAD_PORT),/dev/ttyS0) +UPLOAD_PORT_MATCH ?= /dev/ttyU* +UPLOAD_PORT ?= $(shell ls -1tr $(UPLOAD_PORT_MATCH) 2>/dev/null | tail -1) + +# Monitor definitions +MONITOR_SPEED ?= 115200 +MONITOR_PORT ?= $(UPLOAD_PORT) +MONITOR_PAR ?= --rts=0 --dtr=0 +MONITOR_COM ?= $(if $(NO_PY_WRAP),python3,$(PY_WRAP)) -m serial.tools.miniterm $(MONITOR_PAR) $(MONITOR_PORT) $(MONITOR_SPEED) # OTA parameters OTA_ADDR ?= -OTA_PORT ?= $(if $(filter $(CHIP), esp32),3232,8266) +OTA_PORT ?= $(if $(IS_ESP32),3232,8266) OTA_PWD ?= - OTA_ARGS = --progress --ip="$(OTA_ADDR)" --port="$(OTA_PORT)" - ifneq ($(OTA_PWD),) OTA_ARGS += --auth="$(OTA_PWD)" endif @@ -45,90 +64,88 @@ HTTP_ADDR ?= HTTP_URI ?= /update HTTP_PWD ?= user HTTP_USR ?= password +HTTP_OPT ?= --progress-bar -o /dev/null # Output directory BUILD_ROOT ?= /tmp/mkESP BUILD_DIR ?= $(BUILD_ROOT)/$(MAIN_NAME)_$(BOARD) -# File system source directory +# File system and corresponding disk directories +FS_TYPE ?= spiffs FS_DIR ?= $(dir $(SKETCH))data -FS_REST_DIR ?= $(BUILD_DIR)/file_system - -# Bootloader -BOOT_LOADER ?= $(ESP_ROOT)/bootloaders/eboot/eboot.elf - -#==================================================================================== -# Standard build logic and values -#==================================================================================== - -START_TIME := $(shell date +%s) -OS ?= $(shell uname -s) +FS_RESTORE_DIR ?= $(BUILD_DIR)/file_system # Utility functions git_description = $(shell git -C $(1) describe --tags --always --dirty 2>/dev/null || echo Unknown) time_string = $(shell date +$(1)) -ifeq ($(OS), Darwin) - find_files = $(shell find -E $2 -regex ".*\.($1)" | sed 's/\/\//\//') -else - find_files = $(shell find $2 -regextype posix-egrep -regex ".*\.($1)") -endif +find_files = $(shell find $2 | awk '/.*\.($1)$$/') # ESP Arduino directories ifndef ESP_ROOT # Location not defined, find and use possible version in the Arduino IDE installation - ifeq ($(OS), Windows_NT) - ARDUINO_ROOT = $(shell cygpath -m $(LOCALAPPDATA)/Arduino15) - else ifeq ($(OS), Darwin) - ARDUINO_ROOT = $(HOME)/Library/Arduino15 - else - ARDUINO_ROOT = $(HOME)/.arduino15 - endif + ARDUINO_ROOT ?= $(HOME)/.arduino15 ARDUINO_ESP_ROOT = $(ARDUINO_ROOT)/packages/$(CHIP) - ESP_ROOT := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/hardware/$(CHIP)/*)) + ESP_ROOT := $(if $(ARDUINO_HW_ESP_ROOT),$(ARDUINO_HW_ESP_ROOT),$(lastword $(wildcard $(ARDUINO_ESP_ROOT)/hardware/$(CHIP)/*))) ifeq ($(ESP_ROOT),) $(error No installed version of $(CHIP) Arduino found) endif - ARDUINO_LIBS = $(shell grep -o "sketchbook.path=.*" $(ARDUINO_ROOT)/preferences.txt 2>/dev/null | cut -f2- -d=)/libraries + ARDUINO_LIBS ?= $(shell grep -o "sketchbook.path=.*" $(ARDUINO_ROOT)/preferences.txt 2>/dev/null | cut -f2- -d=)/libraries ESP_ARDUINO_VERSION := $(notdir $(ESP_ROOT)) # Find used version of compiler and tools COMP_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/xtensa-*/*)) - ESPTOOL_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/esptool*/*)) - MKSPIFFS_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/mkspiffs/*/*)) + MK_FS_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/mk$(FS_TYPE)/*/mk$(FS_TYPE))) + PYTHON3_PATH := $(lastword $(wildcard $(ARDUINO_ESP_ROOT)/tools/python3/*)) else - # Location defined, assume it is a git clone + # Location defined, assume that it is a git clone ESP_ARDUINO_VERSION = $(call git_description,$(ESP_ROOT)) - MKSPIFFS_PATH := $(lastword $(wildcard $(ESP_ROOT)/tools/mkspiffs/*)) + MK_FS_PATH := $(lastword $(wildcard $(ESP_ROOT)/tools/mk$(FS_TYPE)/mk$(FS_TYPE))) + PYTHON3_PATH := $(wildcard $(ESP_ROOT)/tools/python3) endif ESP_LIBS = $(ESP_ROOT)/libraries SDK_ROOT = $(ESP_ROOT)/tools/sdk TOOLS_ROOT = $(ESP_ROOT)/tools -ifeq ($(shell grep -o "$(BOARD).name" $(ESP_ROOT)/boards.txt 2>/dev/null),) - $(error Invalid board: $(BOARD)) -endif +# The esp8266 tools directory contains the python3 executable as well as some modules +# Use these to avoid additional python installation requirements here +PYTHON3_PATH := $(if $(PYTHON3_PATH),$(PYTHON3_PATH),$(dir $(shell which python3 2>/dev/null))) +PY_WRAP = $(PYTHON3_PATH)/python3 $(__TOOLS_DIR)/py_wrap.py $(TOOLS_ROOT) +NO_PY_WRAP ?= $(if $(IS_ESP32),1,) +# Validate the selected version of ESP Arduino ifeq ($(wildcard $(ESP_ROOT)/cores/$(CHIP)),) $(error $(ESP_ROOT) is not a vaild directory for $(CHIP)) endif -ESPTOOL ?= $(shell which esptool.py 2>/dev/null || which esptool 2>/dev/null) -ifneq ($(ESPTOOL),) - # esptool exists in path, overide defaults and use it for esp8266 flash operations - ifeq ($(CHIP),esp8266) - ESPTOOL_COM = $(ESPTOOL) - UPLOAD_COM = $(ESPTOOL_PATTERN) -a soft_reset write_flash 0x00000 $(BUILD_DIR)/$(MAIN_NAME).bin - FS_UPLOAD_COM = $(ESPTOOL_PATTERN) -a soft_reset write_flash $(SPIFFS_START) $(FS_IMAGE) - endif +# Set possible default board variant and validate +BOARD_OP = perl $(__TOOLS_DIR)/board_op.pl $(ESP_ROOT)/boards.txt "$(CPU)" +ifeq ($(BOARD),) + BOARD := $(if $(IS_ESP32),esp32,generic) +else ifeq ($(shell $(BOARD_OP) $(BOARD) check),) + $(error Invalid board: $(BOARD)) endif -ESPTOOL_PATTERN = echo Using: $(UPLOAD_PORT) @ $(UPLOAD_SPEED) && "$(ESPTOOL_COM)" --baud=$(UPLOAD_SPEED) --port $(UPLOAD_PORT) --chip $(CHIP) -ifeq ($(MAKECMDGOALS),help) - DEMO=1 +# Handle esptool variants +ESPTOOL_EXT = $(if $(IS_ESP32),,.py) +ESPTOOL ?= $(if $(NO_PY_WRAP),$(ESP_ROOT)/tools/esptool/esptool$(ESPTOOL_EXT),$(PY_WRAP) esptool) +ESPTOOL_COM ?= $(ESPTOOL) --baud=$(UPLOAD_SPEED) --port $(UPLOAD_PORT) --chip $(CHIP) +ifeq ($(IS_ESP32),) + # esp8266, use esptool directly instead of via tools/upload.py in order to avoid speed restrictions currently implied there + UPLOAD_COM = $(ESPTOOL_COM) $(UPLOAD_RESET) write_flash 0x00000 $(BUILD_DIR)/$(MAIN_NAME).bin + FS_UPLOAD_COM = $(ESPTOOL_COM) $(UPLOAD_RESET) write_flash $(SPIFFS_START) $(FS_IMAGE) +endif + +# Detect if the specified goal involves building or not +GOALS := $(if $(MAKECMDGOALS),$(MAKECMDGOALS),all) +BUILDING := $(if $(filter $(GOALS), monitor list_boards list_flash_defs list_lwip set_git_version install help tools_dir preproc info),,1) + +# Sketch (main program) selection +ifeq ($(BUILDING),) + SKETCH = /dev/null endif ifdef DEMO - SKETCH := $(if $(filter $(CHIP), esp32),$(ESP_LIBS)/WiFi/examples/WiFiScan/WiFiScan.ino,$(ESP_LIBS)/ESP8266WebServer/examples/HelloServer/HelloServer.ino) + SKETCH := $(if $(IS_ESP32),$(ESP_LIBS)/WiFi/examples/WiFiScan/WiFiScan.ino,$(ESP_LIBS)/ESP8266WiFi/examples/WiFiScan/WiFiScan.ino) endif -SKETCH ?= $(wildcard *.ino *.pde) +SKETCH ?= $(abspath $(wildcard *.ino *.pde)) ifeq ($(SKETCH),) $(error No sketch specified or found. Use "DEMO=1" for testing) endif @@ -141,24 +158,12 @@ SRC_GIT_VERSION := $(call git_description,$(dir $(SKETCH))) SKETCH_NAME := $(basename $(notdir $(SKETCH))) MAIN_NAME ?= $(SKETCH_NAME) MAIN_EXE ?= $(BUILD_DIR)/$(MAIN_NAME).bin -FS_IMAGE ?= $(BUILD_DIR)/FS.spiffs - -ifeq ($(OS), Windows_NT) - # Adjust some paths for cygwin - BUILD_DIR := $(shell cygpath -m $(BUILD_DIR)) - SKETCH := $(shell cygpath -m $(SKETCH)) - ifdef ARDUINO_LIBS - ARDUINO_LIBS := $(shell cygpath -m $(ARDUINO_LIBS)) - endif -endif +FS_IMAGE ?= $(BUILD_DIR)/FS.bin # Build file extensions OBJ_EXT = .o DEP_EXT = .d -# Auto generated makefile with Arduino definitions -ARDUINO_MK = $(BUILD_DIR)/arduino.mk - # Special tool definitions OTA_TOOL ?= python $(TOOLS_ROOT)/espota.py HTTP_TOOL ?= curl @@ -168,55 +173,39 @@ CORE_DIR = $(ESP_ROOT)/cores/$(CHIP) CORE_SRC := $(call find_files,S|c|cpp,$(CORE_DIR)) CORE_OBJ := $(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(CORE_SRC))) CORE_LIB = $(BUILD_DIR)/arduino.ar +USER_OBJ_LIB = $(BUILD_DIR)/user_obj.ar -SKETCH_DIR = $(dir $(SKETCH)) -# User defined compilation units and directories -ifeq ($(LIBS),) - # Automatically find directories with header files used by the sketch - LIBS := $(shell perl -e 'use File::Find;@d = split(" ", shift);while (<>) {$$f{"$$1"} = 1 if /^\s*\#include\s+[<"]([^>"]+)/;}find(sub {if ($$f{$$_}){print $$File::Find::dir," ";$$f{$$_}=0;}}, @d);' \ - "$(CUSTOM_LIBS) $(ESP_LIBS) $(ARDUINO_LIBS)" $(SKETCH) $(call find_files,S|c|cpp,$(SKETCH_DIR))) - ifneq ($(findstring /examples/,$(realpath $(SKETCH))),) - # Assume library example sketch, add the library directory unless it is an Arduino basic example - EX_LIB := $(shell perl -e 'print $$ARGV[0] if $$ARGV[0] =~ s/\/examples\/(?!\d\d\.).+//' $(realpath $(SKETCH))) - ifneq ($(EX_LIB),) - ifneq ($(wildcard $(EX_LIB)/src),) - # Library in src sub directory - EX_LIB := $(EX_LIB)/src - else - # Library at root. Avoid getting files from other examples - EXCLUDE_DIRS ?= $(EX_LIB)/examples - endif - LIBS += $(EX_LIB) - endif - endif -endif +# Find project specific source files and include directories +SRC_LIST = $(BUILD_DIR)/src_list.mk +FIND_SRC_CMD = $(__TOOLS_DIR)/find_src.pl +$(SRC_LIST): $(MAKEFILE_LIST) $(FIND_SRC_CMD) | $(BUILD_DIR) + $(if $(BUILDING),echo "- Finding all involved files for the build ...",) + perl $(FIND_SRC_CMD) "$(EXCLUDE_DIRS)" $(SKETCH) "$(CUSTOM_LIBS)" "$(LIBS)" $(ESP_LIBS) $(ARDUINO_LIBS) >$(SRC_LIST) + +-include $(SRC_LIST) -IGNORE_PATTERN := $(foreach dir,$(EXCLUDE_DIRS),$(dir)/%) -USER_INC := $(filter-out $(IGNORE_PATTERN),$(call find_files,h|hpp,$(SKETCH_DIR) $(dir $(LIBS)))) -USER_SRC := $(SKETCH) $(filter-out $(IGNORE_PATTERN),$(call find_files,S|c|cpp$(USER_SRC_PATTERN),$(SKETCH_DIR) $(LIBS))) -# Object file suffix seems to be significant for the linker... -USER_OBJ := $(subst .ino,_.cpp,$(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(USER_SRC)))) +# Use sketch copy with correct C++ extension +SKETCH_CPP = $(BUILD_DIR)/$(notdir $(SKETCH)).cpp +USER_SRC := $(subst $(SKETCH),$(SKETCH_CPP),$(USER_SRC)) + +USER_OBJ := $(patsubst %,$(BUILD_DIR)/%$(OBJ_EXT),$(notdir $(USER_SRC))) USER_DIRS := $(sort $(dir $(USER_SRC))) -USER_INC_DIRS := $(sort $(dir $(USER_INC))) -USER_LIBS := $(filter-out $(IGNORE_PATTERN),$(call find_files,a,$(SKETCH_DIR) $(LIBS))) # Use first flash definition for the board as default -FLASH_DEF_MATCH = $(if $(filter $(CHIP), esp32),build\.flash_size=(\S+),menu\.(?:FlashSize|eesz)\.([^\.]+)=(.+)) -FLASH_DEF ?= $(shell cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) {if (/^$(BOARD)\.$(FLASH_DEF_MATCH)/){ print "$$1"; exit;}}') +FLASH_DEF ?= $(shell $(BOARD_OP) $(BOARD) first_flash) # Same method for LwIPVariant -LWIP_VARIANT ?= $(shell cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) {if (/^$(BOARD)\.menu\.(?:LwIPVariant|ip)\.([^\.=]+)=/){ print "$$1"; exit;}}') +LWIP_VARIANT ?= $(shell $(BOARD_OP) $(BOARD) first_lwip) # Handle possible changed state i.e. make command line parameters or changed git versions -ifeq ($(OS), Darwin) - CMD_LINE := $(shell ps $$PPID -o command | tail -1) -else - CMD_LINE := $(shell tr "\0" " " $(ARDUINO_MK) +ARDUINO_MK = $(BUILD_DIR)/arduino.mk +OS_NAME ?= linux +ARDUINO_DESC := $(shell find -L $(ESP_ROOT) -maxdepth 1 -name "*.txt" | sort) +$(ARDUINO_MK): $(ARDUINO_DESC) $(MAKEFILE_LIST) $(__TOOLS_DIR)/parse_arduino.pl | $(BUILD_DIR) + $(if $(BUILDING),echo "- Parsing Arduino configuration files ...",) + perl $(__TOOLS_DIR)/parse_arduino.pl $(BOARD) '$(FLASH_DEF)' '$(OS_NAME)' '$(LWIP_VARIANT)' $(ARDUINO_EXTRA_DESC) $(ARDUINO_DESC) >$(ARDUINO_MK) -include $(ARDUINO_MK) @@ -237,79 +229,103 @@ INCLUDE_DIRS += $(CORE_DIR) $(ESP_ROOT)/variants/$(INCLUDE_VARIANT) $(BUILD_DIR) C_INCLUDES := $(foreach dir,$(INCLUDE_DIRS) $(USER_INC_DIRS),-I$(dir)) VPATH += $(shell find $(CORE_DIR) -type d) $(USER_DIRS) -# Automatically generated build information data -# Makes the build date and git descriptions at the actual build event available as string constants in the program +# Automatically generated build information data source file +# Makes the build date and git descriptions at the time of actual build event +# available as string constants in the program BUILD_INFO_H = $(BUILD_DIR)/buildinfo.h BUILD_INFO_CPP = $(BUILD_DIR)/buildinfo.c++ BUILD_INFO_OBJ = $(BUILD_INFO_CPP)$(OBJ_EXT) +BUILD_DATE = $(call time_string,"%Y-%m-%d") +BUILD_TIME = $(call time_string,"%H:%M:%S") $(BUILD_INFO_H): | $(BUILD_DIR) - echo "typedef struct { const char *date, *time, *src_version, *env_version;} _tBuildInfo; extern _tBuildInfo _BuildInfo;" >$@ + @echo "typedef struct { const char *date, *time, *src_version, *env_version; } _tBuildInfo; extern _tBuildInfo _BuildInfo;" >$@ -# ccache? -ifeq ($(USE_CCACHE), 1) +# Use ccache if it is available and not explicitly disabled (USE_CCACHE=0) +USE_CCACHE ?= $(if $(shell which ccache 2>/dev/null),1,0) +ifeq ($(USE_CCACHE),1) C_COM_PREFIX = ccache CPP_COM_PREFIX = $(C_COM_PREFIX) endif -# Build rules for the different source file types -$(BUILD_DIR)/%.cpp$(OBJ_EXT): %.cpp $(BUILD_INFO_H) $(ARDUINO_MK) - echo $(" >$@ + cat $(abspath $<) >>$@ -$(BUILD_DIR)/%.pde$(OBJ_EXT): %.pde $(BUILD_INFO_H) $(ARDUINO_MK) - echo $(' >$(BUILD_INFO_CPP) - echo '_tBuildInfo _BuildInfo = {"$(BUILD_DATE)","$(BUILD_TIME)","$(SRC_GIT_VERSION)","$(ESP_ARDUINO_VERSION)"};' >>$(BUILD_INFO_CPP) +# Linking the executable +$(MAIN_EXE): $(CORE_LIB) $(USER_LIBS) $(USER_OBJ_DEP) + @echo Linking $(MAIN_EXE) + $(PRELINK) + @echo " Versions: $(SRC_GIT_VERSION), $(ESP_ARDUINO_VERSION)" + @echo '#include ' >$(BUILD_INFO_CPP) + @echo '_tBuildInfo _BuildInfo = {"$(BUILD_DATE)","$(BUILD_TIME)","$(SRC_GIT_VERSION)","$(ESP_ARDUINO_VERSION)"};' >>$(BUILD_INFO_CPP) $(CPP_COM) $(BUILD_INFO_CPP) -o $(BUILD_INFO_OBJ) $(LD_COM) $(LD_EXTRA) $(GEN_PART_COM) - $(ELF2BIN_COM) - $(SIZE_COM) | perl -e "$$MEM_USAGE" "$(MEM_FLASH)" "$(MEM_RAM)" + $(OBJCOPY) + $(SIZE_COM) | perl $(__TOOLS_DIR)/mem_use.pl "$(MEM_FLASH)" "$(MEM_RAM)" ifneq ($(LWIP_INFO),) - printf "LwIPVariant: $(LWIP_INFO)\n" + @printf "LwIPVariant: $(LWIP_INFO)\n" endif ifneq ($(FLASH_INFO),) - printf "Flash size: $(FLASH_INFO)\n\n" + @printf "Flash size: $(FLASH_INFO)\n\n" endif - perl -e 'print "Build complete. Elapsed time: ", time()-$(START_TIME), " seconds\n\n"' + @perl -e 'print "Build complete. Elapsed time: ", time()-$(START_TIME), " seconds\n\n"' +# Flashing operations +CHECK_PORT := $(if $(UPLOAD_PORT),\ + @echo === Using upload port: $(UPLOAD_PORT) @ $(UPLOAD_SPEED),\ + @echo "*** Upload port not found or defined" && exit 1) upload flash: all + $(CHECK_PORT) $(UPLOAD_COM) ota: all ifeq ($(OTA_ADDR),) - echo == Error: Address of device must be specified via OTA_ADDR + @echo == Error: Address of device must be specified via OTA_ADDR exit 1 endif $(OTA_PRE_COM) @@ -317,301 +333,237 @@ endif http: all ifeq ($(HTTP_ADDR),) - echo == Error: Address of device must be specified via HTTP_ADDR + @echo == Error: Address of device must be specified via HTTP_ADDR exit 1 endif - $(HTTP_TOOL) --verbose -F image=@$(MAIN_EXE) --user $(HTTP_USR):$(HTTP_PWD) http://$(HTTP_ADDR)$(HTTP_URI) - echo "\n" + $(HTTP_TOOL) $(HTTP_OPT) -F image=@$(MAIN_EXE) --user $(HTTP_USR):$(HTTP_PWD) http://$(HTTP_ADDR)$(HTTP_URI) + @echo "\n" -$(FS_IMAGE): $(ARDUINO_MK) $(wildcard $(FS_DIR)/*) - echo Generating filesystem image: $(FS_IMAGE) - $(MKSPIFFS_COM) +$(FS_IMAGE): $(ARDUINO_MK) $(shell find $(FS_DIR)/ 2>/dev/null) +ifeq ($(SPIFFS_SIZE),) + @echo == Error: No file system specified in FLASH_DEF + exit 1 +endif + @echo Generating file system image: $(FS_IMAGE) + $(MK_FS_COM) fs: $(FS_IMAGE) upload_fs flash_fs: $(FS_IMAGE) + $(CHECK_PORT) $(FS_UPLOAD_COM) ota_fs: $(FS_IMAGE) ifeq ($(OTA_ADDR),) - echo == Error: Address of device must be specified via OTA_ADDR + @echo == Error: Address of device must be specified via OTA_ADDR exit 1 endif $(OTA_TOOL) $(OTA_ARGS) --spiffs --file="$(FS_IMAGE)" run: flash - python -m serial.tools.miniterm --rts=0 --dtr=0 $(UPLOAD_PORT) 115200 + $(MONITOR_COM) + +monitor: +ifeq ($(MONITOR_PORT),) + @echo "*** Monitor port not found or defined" && exit 1 +endif + $(MONITOR_COM) FLASH_FILE ?= $(BUILD_DIR)/esp_flash.bin dump_flash: - echo Dumping flash memory to file: $(FLASH_FILE) - $(ESPTOOL_PATTERN) read_flash 0 $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";$$mem_size=$$1*1024;$$mem_size*=1024 if $$2 eq "M";print $$mem_size;' $(FLASH_DEF)) $(FLASH_FILE) + $(CHECK_PORT) + @echo Dumping flash memory to file: $(FLASH_FILE) + $(ESPTOOL_COM) read_flash 0 $(shell perl -e 'shift =~ /(\d+)([MK])/ || die "Invalid memory size\n";$$mem_size=$$1*1024;$$mem_size*=1024 if $$2 eq "M";print $$mem_size;' $(FLASH_DEF)) $(FLASH_FILE) dump_fs: - echo Dumping flash file system to directory: $(FS_REST_DIR) - -$(ESPTOOL_PATTERN) read_flash $(SPIFFS_START) $(SPIFFS_SIZE) $(FS_IMAGE) - mkdir -p $(FS_REST_DIR) - echo - echo == Files == - $(RESTSPIFFS_COM) + $(CHECK_PORT) + @echo Dumping flash file system to directory: $(FS_RESTORE_DIR) + -$(ESPTOOL_COM) read_flash $(SPIFFS_START) $(SPIFFS_SIZE) $(FS_IMAGE) + mkdir -p $(FS_RESTORE_DIR) + @echo + @echo == Files == + $(RESTORE_FS_COM) restore_flash: - echo Restoring flash memory from file: $(FLASH_FILE) - $(ESPTOOL_PATTERN) -a soft_reset write_flash 0 $(FLASH_FILE) + $(CHECK_PORT) + @echo Restoring flash memory from file: $(FLASH_FILE) + $(ESPTOOL_COM) -a soft_reset write_flash 0 $(FLASH_FILE) erase_flash: - $(ESPTOOL_PATTERN) erase_flash + $(CHECK_PORT) + $(ESPTOOL_COM) erase_flash +# Building library instead of executable LIB_OUT_FILE ?= $(BUILD_DIR)/$(MAIN_NAME).a .PHONY: lib lib: $(LIB_OUT_FILE) -$(LIB_OUT_FILE): $(filter-out $(BUILD_DIR)/$(MAIN_NAME)_.cpp$(OBJ_EXT),$(USER_OBJ)) - echo Building library $(LIB_OUT_FILE) +$(LIB_OUT_FILE): $(filter-out $(BUILD_DIR)/$(MAIN_NAME).cpp$(OBJ_EXT),$(USER_OBJ)) + @echo Building library $(LIB_OUT_FILE) rm -f $(LIB_OUT_FILE) $(LIB_COM) cru $(LIB_OUT_FILE) $^ +# Miscellaneous operations clean: - echo Removing all build files + @echo Removing all build files rm -rf "$(BUILD_DIR)" $(FILES_TO_CLEAN) list_boards: - echo === Available boards === - cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^([\w\-]+)\.name=(.+)/){ print sprintf("%-20s %s\n", $$1,$$2);} }' + $(BOARD_OP) $(BOARD) list_names -list_lib: - echo === User specific libraries === - perl -e 'foreach (@ARGV) {print "$$_\n"}' "* Include directories:" $(USER_INC_DIRS) "* Library source files:" $(USER_SRC) +list_lib: $(SRC_LIST) + perl -e 'foreach (@ARGV) {print "$$_\n"}' "===== Include directories =====" $(USER_INC_DIRS) "===== Source files =====" $(USER_SRC) list_flash_defs: - echo === Memory configurations for board: $(BOARD) === - cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^$(BOARD)\.$(FLASH_DEF_MATCH)/){ print sprintf("%-10s %s\n", $$1,$$2);} }' + $(BOARD_OP) $(BOARD) list_flash list_lwip: - echo === lwip configurations for board: $(BOARD) === - cat $(ESP_ROOT)/boards.txt | perl -e 'while (<>) { if (/^$(BOARD)\.menu\.(?:LwIPVariant|ip)\.(\w+)=(.+)/){ print sprintf("%-10s %s\n", $$1,$$2);} }' - -help: $(ARDUINO_MK) - echo - echo "Generic makefile for building Arduino esp8266 and esp32 projects" - echo "This file can either be used directly or included from another makefile" - echo "" - echo "The following targets are available:" - echo " all (default) Build the project application" - echo " clean Remove all intermediate build files" - echo " lib Build a library with all involved object files" - echo " flash Build and and flash the project application" - echo " flash_fs Build and and flash file system (when applicable)" - echo " ota Build and and flash via OTA" - echo " Params: OTA_ADDR, OTA_PORT and OTA_PWD" - echo " ota_fs Build and and flash file system via OTA" - echo " http Build and and flash via http (curl)" - echo " Params: HTTP_ADDR, HTTP_URI, HTTP_PWD and HTTP_USR" - echo " dump_flash Dump the whole board flash memory to a file" - echo " restore_flash Restore flash memory from a previously dumped file" - echo " dump_fs Extract all files from the flash file system" - echo " Params: FS_DUMP_DIR" - echo " erase_flash Erase the whole flash" - echo " list_lib Show a list of used library files and include paths" - echo "Configurable parameters:" - echo " SKETCH Main source file" - echo " If not specified the first sketch in current" - echo " directory will be used." - echo " LIBS Includes in the sketch file of libraries from within" - echo " the ESP Arduino directories are automatically" - echo " detected. If this is not enough, define this" - echo " variable with all libraries or directories needed." - echo " CHIP Set to esp8266 or esp32. Default: '$(CHIP)'" - echo " BOARD Name of the target board. Default: '$(BOARD)'" - echo " Use 'list_boards' to get list of available ones" - echo " FLASH_DEF Flash partitioning info. Default '$(FLASH_DEF)'" - echo " Use 'list_flash_defs' to get list of available ones" - echo " BUILD_DIR Directory for intermediate build files." - echo " Default '$(BUILD_DIR)'" - echo " BUILD_EXTRA_FLAGS Additional parameters for the compilation commands" - echo " FS_DIR File system root directory" - echo " UPLOAD_PORT Serial flashing port name. Default: '$(UPLOAD_PORT)'" - echo " UPLOAD_SPEED Serial flashing baud rate. Default: '$(UPLOAD_SPEED)'" - echo " FLASH_FILE File name for dump and restore flash operations" - echo " Default: '$(FLASH_FILE)'" - echo " LWIP_VARIANT Use specified variant of the lwip library when applicable" - echo " Use 'list_lwip' to get list of available ones" - echo " Default: $(LWIP_VARIANT) ($(LWIP_INFO))" - echo " VERBOSE Set to 1 to get full printout of the build" - echo " BUILD_THREADS Number of parallel build threads" - echo " Default: Maximum possible, based on number of CPUs" - echo " USE_CCACHE Set to 1 to use ccache in the build" - echo + $(BOARD_OP) $(BOARD) list_lwip -$(BUILD_DIR): - mkdir -p $(BUILD_DIR) +# Update the git version of the esp Arduino repo +set_git_version: +ifeq ($(REQ_GIT_VERSION),) + @echo == Error: Version tag must be specified via REQ_GIT_VERSION + exit 1 +endif + @echo == Setting $(ESP_ROOT) to $(REQ_GIT_VERSION) ... + git -C $(ESP_ROOT) checkout -fq --recurse-submodules $(REQ_GIT_VERSION) + git -C $(ESP_ROOT) clean -fdxq -f + git -C $(ESP_ROOT) submodule update --init + git -C $(ESP_ROOT) submodule foreach -q --recursive git clean -xfd + cd $(ESP_ROOT)/tools; ./get.py -q + +# Generate a Visual Studio Code configuration and launch +BIN_DIR = /usr/local/bin +_MAKE_COM = make -f $(__THIS_FILE) ESP_ROOT=$(ESP_ROOT) +ifeq ($(CHIP),esp32) + _MAKE_COM += CHIP=esp32 + _SCRIPT = espmake32 +else + _SCRIPT = espmake +endif +vscode: all + perl $(__TOOLS_DIR)/vscode.pl -n $(MAIN_NAME) -m "$(_MAKE_COM)" -w "$(VS_CODE_DIR)" -i "$(VSCODE_INC_EXTRA)" -p "$(VSCODE_PROJ_NAME)" $(CPP_COM) + +# Create shortcut command for running this file +install: + @echo Creating command \"$(_SCRIPT)\" in $(BIN_DIR) + sudo sh -c 'echo $(_MAKE_COM) "\"\$$@\"" >$(BIN_DIR)/$(_SCRIPT)' + sudo chmod +x $(BIN_DIR)/$(_SCRIPT) + +# Just return the path of the tools directory (intended to be used to find vscode.pl above from othe makefiles) +tools_dir: + @echo $(__TOOLS_DIR) + +# Show ram memory usage per variable +ram_usage: $(MAIN_EXE) + $(shell find $(TOOLS_ROOT) | grep 'gcc-nm') -Clrtd --size-sort $(BUILD_DIR)/$(MAIN_NAME).elf | grep -i ' [b] ' + +# Show ram and flash usage per object files used in the build +OBJ_INFO_FORM ?= 0 +OBJ_INFO_SORT ?= 1 +obj_info: $(MAIN_EXE) + perl $(__TOOLS_DIR)/obj_info.pl "$(shell find $(TOOLS_ROOT) | grep 'elf-size$$')" "$(OBJ_INFO_FORM)" "$(OBJ_INFO_SORT)" $(BUILD_DIR)/*.o + +# Analyze crash log +crash: $(MAIN_EXE) + perl $(__TOOLS_DIR)/crash_tool.pl $(ESP_ROOT) $(BUILD_DIR)/$(MAIN_NAME).elf + +# Run compiler preprocessor to get full expanded source for a file +preproc: +ifeq ($(SRC_FILE),) + $(error SRC_FILE must be defined) +endif + $(CPP_COM) -E $(SRC_FILE) +# Main default rule, build the executable .PHONY: all -all: $(BUILD_DIR) $(ARDUINO_MK) $(BUILD_INFO_H) prebuild $(MAIN_EXE) +all: $(BUILD_DIR) $(ARDUINO_MK) prebuild $(MAIN_EXE) +# Prebuild is currently only mandatory for esp32 +USE_PREBUILD ?= $(if $(IS_ESP32),1,) prebuild: -ifdef USE_PREBUILD - $(CORE_PREBUILD) +ifneq ($(USE_PREBUILD),) + $(PREBUILD) endif - $(SKETCH_PREBUILD) -# Include all available dependencies +help: $(ARDUINO_MK) + @echo + @echo "Generic makefile for building Arduino esp8266 and esp32 projects" + @echo "This file can either be used directly or included from another makefile" + @echo "" + @echo "The following targets are available:" + @echo " all (default) Build the project application" + @echo " clean Remove all intermediate build files" + @echo " lib Build a library with all involved object files" + @echo " flash Build and and flash the project application" + @echo " flash_fs Build and and flash file system (when applicable)" + @echo " ota Build and and flash via OTA" + @echo " Params: OTA_ADDR, OTA_PORT and OTA_PWD" + @echo " ota_fs Build and and flash file system via OTA" + @echo " http Build and and flash via http (curl)" + @echo " Params: HTTP_ADDR, HTTP_URI, HTTP_PWD and HTTP_USR" + @echo " dump_flash Dump the whole board flash memory to a file" + @echo " restore_flash Restore flash memory from a previously dumped file" + @echo " dump_fs Extract all files from the flash file system" + @echo " Params: FS_DUMP_DIR" + @echo " erase_flash Erase the whole flash (use with care!)" + @echo " list_lib Show a list of used solurce files and include directories" + @echo " set_git_version Setup ESP Arduino git repo to a the tag version" + @echo " specified via REQ_GIT_VERSION" + @echo " install Create the commands \"espmake\" and \"espmake32\"" + @echo " vscode Create config file for Visual Studio Code and launch" + @echo " ram_usage Show global variables RAM usage" + @echo " obj_info Show memory usage per object file" + @echo " monitor Start serial monitor on the upload port" + @echo " run Build flash and start serial monitor" + @echo " crash Analyze stack trace from a crash" + @echo " preproc Run compiler preprocessor on source file" + @echo " specified via SRC_FILE" + @echo " info Show location and version of used esp Arduino" + @echo "Configurable parameters:" + @echo " SKETCH Main source file" + @echo " If not specified the first sketch in current" + @echo " directory will be used." + @echo " LIBS Use this variable to declare additional directories" + @echo " and/or files which should be included in the build" + @echo " CHIP Set to esp8266 or esp32. Default: '$(CHIP)'" + @echo " BOARD Name of the target board. Default: '$(BOARD)'" + @echo " Use 'list_boards' to get list of available ones" + @echo " FLASH_DEF Flash partitioning info. Default '$(FLASH_DEF)'" + @echo " Use 'list_flash_defs' to get list of available ones" + @echo " BUILD_DIR Directory for intermediate build files." + @echo " Default '$(BUILD_DIR)'" + @echo " BUILD_EXTRA_FLAGS Additional parameters for the compilation commands" + @echo " COMP_WARNINGS Compilation warning options. Default: $(COMP_WARNINGS)" + @echo " FS_TYPE File system type. Default: $(FS_TYPE)" + @echo " FS_DIR File system root directory" + @echo " UPLOAD_PORT Serial flashing port name. Default: '$(UPLOAD_PORT)'" + @echo " UPLOAD_SPEED Serial flashing baud rate. Default: '$(UPLOAD_SPEED)'" + @echo " MONITOR_SPEED Baud rate for the monitor. Default: '$(MONITOR_SPEED)'" + @echo " FLASH_FILE File name for dump and restore flash operations" + @echo " Default: '$(FLASH_FILE)'" + @echo " LWIP_VARIANT Use specified variant of the lwip library when applicable" + @echo " Use 'list_lwip' to get list of available ones" + @echo " Default: $(LWIP_VARIANT) ($(LWIP_INFO))" + @echo " VERBOSE Set to 1 to get full printout of the build" + @echo " BUILD_THREADS Number of parallel build threads" + @echo " Default: Maximum possible, based on number of CPUs" + @echo " USE_CCACHE Set to 0 to disable ccache when it is available" + @echo " NO_USER_OBJ_LIB Set to 1 to disable putting all object files into an archive" + @echo + +# Show installation information +info: + echo == Build info + echo " CHIP: $(CHIP)" + echo " ESP_ROOT: $(ESP_ROOT)" + echo " Version: $(ESP_ARDUINO_VERSION)" + echo " Threads: $(BUILD_THREADS)" + echo " Upload port: $(UPLOAD_PORT)" + +# Include all available dependencies from the previous compilation -include $(wildcard $(BUILD_DIR)/*$(DEP_EXT)) DEFAULT_GOAL ?= all .DEFAULT_GOAL := $(DEFAULT_GOAL) -ifeq ($(OS), Darwin) - BUILD_THREADS ?= $(shell sysctl -n hw.ncpu) -else - BUILD_THREADS ?= $(shell nproc) -endif -MAKEFLAGS += -j $(BUILD_THREADS) - -ifndef VERBOSE - # Set silent mode as default - MAKEFLAGS += --silent -endif - -# Inline Perl scripts - -# Parse Arduino definitions and build commands from the descriptions -define PARSE_ARDUINO -my $$board = shift; -my $$flashSize = shift; -my $$os = shift; -$$os =~ s/Windows_NT/windows/; -$$os =~ s/Linux/linux/; -$$os =~ s/Darwin/macosx/; -my $$lwipvariant = shift; -my %v; - -sub def_var { - my ($$name, $$var) = @_; - print "$$var ?= $$v{$$name}\n"; - $$v{$$name} = "\$$($$var)"; -} - -$$v{'runtime.platform.path'} = '$$(ESP_ROOT)'; -$$v{'includes'} = '$$(C_INCLUDES)'; -$$v{'runtime.ide.version'} = '10605'; -$$v{'build.arch'} = uc('$(CHIP)'); -$$v{'build.project_name'} = '$$(MAIN_NAME)'; -$$v{'build.path'} = '$$(BUILD_DIR)'; -$$v{'object_files'} = '$$^ $$(BUILD_INFO_OBJ)'; -$$v{'archive_file_path'} = '$$(CORE_LIB)'; - -foreach my $$fn (@ARGV) { - open($$f, $$fn) || die "Failed to open: $$fn\n"; - while (<$$f>) { - s/\s+$$//; - s/\.esptool_py\./.esptool./g; - next unless /^(\w[\w\-\.]+)=(.*)/; - my ($$key, $$val) =($$1, $$2); - $$board_defined = 1 if $$key eq "$$board.name"; - $$key =~ s/$$board\.menu\.(?:FlashSize|eesz)\.$$flashSize\.//; - $$key =~ s/$$board\.menu\.CpuFrequency\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.(?:FlashFreq|xtal)\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.UploadSpeed\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.baud\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.ResetMethod\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.FlashMode\.[^\.]+\.//; - $$key =~ s/$$board\.menu\.(?:LwIPVariant|ip)\.$$lwipvariant\.//; - $$key =~ s/^$$board\.//; - $$v{$$key} ||= $$val; - $$v{$$1} = $$v{$$key} if $$key =~ /(.+)\.$$os$$/; - } - close($$f); -} -$$v{'runtime.tools.xtensa-lx106-elf-gcc.path'} ||= '$$(COMP_PATH)'; -$$v{'runtime.tools.xtensa-esp32-elf-gcc.path'} ||= '$$(COMP_PATH)'; -$$v{'runtime.tools.esptool.path'} ||= '$$(ESPTOOL_PATH)'; - -die "* Unknown board $$board\n" unless $$board_defined; -print "# Board definitions\n"; -def_var('build.code_debug', 'CORE_DEBUG_LEVEL'); -def_var('build.f_cpu', 'F_CPU'); -def_var('build.flash_mode', 'FLASH_MODE'); -def_var('build.flash_freq', 'FLASH_SPEED'); -def_var('upload.resetmethod', 'UPLOAD_RESET'); -def_var('upload.speed', 'UPLOAD_SPEED'); -def_var('compiler.warning_flags', 'COMP_WARNINGS'); -$$v{'serial.port'} = '$$(UPLOAD_PORT)'; -$$v{'recipe.objcopy.hex.pattern'} =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$$(BOOT_LOADER)/; -$$v{'recipe.objcopy.hex.1.pattern'} =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$$(BOOT_LOADER)/; -$$v{'recipe.hooks.linking.prelink.1.pattern'} =~ s/\{build.vtable_flags\}/\$$(VTABLE_FLAGS)/; -$$v{'tools.esptool.upload.pattern'} =~ s/\{(cmd|path)\}/\{tools.esptool.$$1\}/g; -$$v{'compiler.cpreprocessor.flags'} .= " \$$(C_PRE_PROC_FLAGS)"; -$$v{'build.extra_flags'} .= " \$$(BUILD_EXTRA_FLAGS)"; - -foreach my $$key (sort keys %v) { - while ($$v{$$key} =~/\{/) { - $$v{$$key} =~ s/\{([\w\-\.]+)\}/$$v{$$1}/; - $$v{$$key} =~ s/""//; - } - $$v{$$key} =~ s/ -o\s+$$//; - $$v{$$key} =~ s/(-D\w+=)"([^"]+)"/$$1\\"$$2\\"/g; -} - -print "INCLUDE_VARIANT = $$v{'build.variant'}\n"; -print "# Commands\n"; -print "C_COM=\$$(C_COM_PREFIX) $$v{'recipe.c.o.pattern'}\n"; -print "CPP_COM=\$$(CPP_COM_PREFIX) $$v{'recipe.cpp.o.pattern'}\n"; -print "S_COM=$$v{'recipe.S.o.pattern'}\n"; -print "LIB_COM=\"$$v{'compiler.path'}$$v{'compiler.ar.cmd'}\"\n"; -print "CORE_LIB_COM=$$v{'recipe.ar.pattern'}\n"; -print "LD_COM=$$v{'recipe.c.combine.pattern'}\n"; -print "PART_FILE?=$$1\n" if $$v{'recipe.objcopy.eep.pattern'} =~ /\"([^\"]+\.csv)\"/; -$$v{'recipe.objcopy.eep.pattern'} =~ s/\"([^\"]+\.csv)\"/\$$(PART_FILE)/; -print "GEN_PART_COM=$$v{'recipe.objcopy.eep.pattern'}\n"; -print "ELF2BIN_COM=", $$v{'recipe.objcopy.hex.pattern'} || $$v{'recipe.objcopy.hex.1.pattern'}, "\n"; -print "SIZE_COM=$$v{'recipe.size.pattern'}\n"; -print "ESPTOOL_COM?=$$v{'tools.esptool.path'}/$$v{'tools.esptool.cmd'}\n"; -print "UPLOAD_COM?=$$v{'tools.esptool.upload.pattern'}\n"; - -if ($$v{'build.spiffs_start'}) { - print "SPIFFS_START?=$$v{'build.spiffs_start'}\n"; - my $$spiffs_size = sprintf("0x%X", hex($$v{'build.spiffs_end'})-hex($$v{'build.spiffs_start'})); - print "SPIFFS_SIZE?=$$spiffs_size\n"; -} elsif ($$v{'build.partitions'}) { - print "COMMA=,\n"; - print "SPIFFS_SPEC:=\$$(subst \$$(COMMA), ,\$$(shell grep spiffs \$$(PART_FILE)))\n"; - print "SPIFFS_START:=\$$(word 4,\$$(SPIFFS_SPEC))\n"; - print "SPIFFS_SIZE:=\$$(word 5,\$$(SPIFFS_SPEC))\n"; -} -$$v{'build.spiffs_blocksize'} ||= "4096"; -print "SPIFFS_BLOCK_SIZE?=$$v{'build.spiffs_blocksize'}\n"; -print "MKSPIFFS_COM?=\"\$$(MKSPIFFS_PATH)\" -b \$$(SPIFFS_BLOCK_SIZE) -s \$$(SPIFFS_SIZE) -c \$$(FS_DIR) \$$(FS_IMAGE)\n"; -print "RESTSPIFFS_COM?=\"\$$(MKSPIFFS_PATH)\" -b \$$(SPIFFS_BLOCK_SIZE) -s \$$(SPIFFS_SIZE) -u \$$(FS_REST_DIR) \$$(FS_IMAGE)\n"; - -my $$fs_upload_com = $$v{'tools.esptool.upload.pattern'}; -$$fs_upload_com =~ s/(.+ -ca) .+/$$1 \$$(SPIFFS_START) -cf \$$(FS_IMAGE)/; -$$fs_upload_com =~ s/(.+ --flash_size detect) .+/$$1 \$$(SPIFFS_START) \$$(FS_IMAGE)/; -print "FS_UPLOAD_COM?=$$fs_upload_com\n"; -my $$val = $$v{'recipe.hooks.core.prebuild.1.pattern'}; -$$val =~ s/bash -c "(.+)"/$$1/; -$$val =~ s/(#define .+0x)(\`)/"\\$$1\"$$2/; -$$val =~ s/(\\)//; -print "CORE_PREBUILD=$$val\n"; -print "SKETCH_PREBUILD=$$v{'recipe.hooks.sketch.prebuild.1.pattern'}\n"; -print "VTABLE_FLAGS?=$$v{'build.vtable_flags'}\n"; -print "LINK_PREBUILD=$$v{'recipe.hooks.linking.prelink.1.pattern'}\n"; -print "MEM_FLASH=$$v{'recipe.size.regex'}\n"; -print "MEM_RAM=$$v{'recipe.size.regex.data'}\n"; -$$flash_info = $$v{'menu.FlashSize.' . $$flashSize} || $$v{'menu.eesz.' . $$flashSize}; -print "FLASH_INFO=$$flash_info\n"; -print "LWIP_INFO=", $$v{'menu.LwIPVariant.' . $$lwipvariant} || $$v{'menu.ip.' . $$lwipvariant}, "\n"; -endef -export PARSE_ARDUINO - -# Convert memory information -define MEM_USAGE -$$fp = shift; -$$rp = shift; -while (<>) { - $$r += $$1 if /$$rp/; - $$f += $$1 if /$$fp/; -} -print "\nMemory usage\n"; -print sprintf(" %-6s %6d bytes\n" x 2 ."\n", "Ram:", $$r, "Flash:", $$f); -endef -export MEM_USAGE diff --git a/mqtt.cpp b/mqtt.cpp index 9176e4c38..bd5a4e5c6 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -25,8 +25,10 @@ #include #if defined(ESP8266) #include + #include + #else + #include #endif - #include #include struct PubSubClient *mqtt_client = NULL; @@ -100,7 +102,11 @@ void OSMqtt::init(void) { #if defined(ARDUINO) uint8_t mac[6] = {0}; - os.load_hardware_mac(mac, m_server!=NULL); + #if defined(ESP8266) + os.load_hardware_mac(mac, useEth); + #else + os.load_hardware_mac(mac, true); + #endif snprintf(id, MQTT_MAX_ID_LEN, "OS-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); #endif @@ -213,8 +219,9 @@ void OSMqtt::loop(void) { #if defined(ESP8266) WiFiClient wifiClient; + #else + EthernetClient ethClient; #endif - EthernetClient ethClient; int OSMqtt::_init(void) { Client * client = NULL; @@ -222,8 +229,7 @@ int OSMqtt::_init(void) { if (mqtt_client) { delete mqtt_client; mqtt_client = 0; } #if defined(ESP8266) - if (m_server) client = ðClient; - else client = &wifiClient; + client = &wifiClient; #else client = ðClient; #endif diff --git a/server.cpp b/opensprinkler_server.cpp similarity index 92% rename from server.cpp rename to opensprinkler_server.cpp index cb18a8a6c..23cd670cf 100644 --- a/server.cpp +++ b/opensprinkler_server.cpp @@ -23,7 +23,7 @@ #include "OpenSprinkler.h" #include "program.h" -#include "server.h" +#include "opensprinkler_server.h" #include "weather.h" #include "mqtt.h" @@ -33,14 +33,12 @@ #if defined(ESP8266) #include + #include #include "espconnect.h" - extern ESP8266WebServer *wifi_server; - extern EthernetServer *m_server; - extern EthernetClient *m_client; - - // Due to using ESP8266WebServer in WiFi mode, the return mechanism is different when it's in WiFi mode vs. wired Ethernet (i.e. when m_client!=NULL) - #define handle_return(x) {if(m_client) {return_code=x; return;} else {if(x==HTML_OK) server_send_content(); else server_send_result(x); wifi_server->client().stop(); return;}} + extern ESP8266WebServer *w_server; + extern ENC28J60lwIP eth; + #define handle_return(x) {if(x==HTML_OK) server_send_content(); else server_send_result(x); w_server->client().stop(); return;} #else @@ -162,14 +160,14 @@ byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,b uint8_t found=0; #if defined(ESP8266) // for ESP8266: there are two cases: - // case 1: if str is NULL, we assume the key-val to search is already parsed in wifi_server + // case 1: if str is NULL, we assume the key-val to search is already parsed in w_server if(str==NULL) { char _key[10]; if(key_in_pgm) strcpy_P(_key, key); else strcpy(_key, key); - if(wifi_server->hasArg(_key)) { + if(w_server->hasArg(_key)) { // copy value to buffer, and make sure it ends properly - strncpy(strbuf, wifi_server->arg(_key).c_str(), maxlen); + strncpy(strbuf, w_server->arg(_key).c_str(), maxlen); strbuf[maxlen-1]=0; found=1; } else { @@ -249,13 +247,8 @@ void rewind_ether_buffer() { void send_packet(bool final=false) { #if defined(ESP8266) - if (m_client) { - m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); - if (final) { m_client->stop(); } - } else { - wifi_server->sendContent(ether_buffer); - if(final) { wifi_server->client().stop(); } - } + w_server->sendContent(ether_buffer); + if(final) { w_server->client().stop(); } rewind_ether_buffer(); return; #else @@ -281,16 +274,14 @@ String toHMS(ulong t) { } void server_send_content() { - if (m_client) { return; } - wifi_server->sendContent(ether_buffer); - wifi_server->client().stop(); + w_server->sendContent(ether_buffer); + w_server->client().stop(); rewind_ether_buffer(); } void server_send_html(String html) { - if (m_client) { return; } - wifi_server->send(200, "text/html", html); - wifi_server->client().stop(); + w_server->send(200, "text/html", html); + w_server->client().stop(); } void server_send_result(byte code) { @@ -308,8 +299,8 @@ void server_send_result(byte code, const char* item) { } /*bool get_value_by_key(const char* key, long& val) { - if(wifi_server->hasArg(key)) { - val = wifi_server->arg(key).toInt(); + if(w_server->hasArg(key)) { + val = w_server->arg(key).toInt(); return true; } else { return false; @@ -317,8 +308,8 @@ void server_send_result(byte code, const char* item) { } bool get_value_by_key(const char* key, String& val) { - if(wifi_server->hasArg(key)) { - val = wifi_server->arg(key); + if(w_server->hasArg(key)) { + val = w_server->arg(key); return true; } else { return false; @@ -377,9 +368,9 @@ void on_ap_scan() { void on_ap_change_config() { if(os.get_wifi_mode()!=WIFI_MODE_AP) return; - if(wifi_server->hasArg("ssid")&&wifi_server->arg("ssid").length()!=0) { - os.wifi_ssid = wifi_server->arg("ssid"); - os.wifi_pass = wifi_server->arg("pass"); + if(w_server->hasArg("ssid")&&w_server->arg("ssid").length()!=0) { + os.wifi_ssid = w_server->arg("ssid"); + os.wifi_pass = w_server->arg("pass"); os.sopt_save(SOPT_STA_SSID, os.wifi_ssid.c_str()); os.sopt_save(SOPT_STA_PASS, os.wifi_pass.c_str()); server_send_result(HTML_SUCCESS); @@ -418,16 +409,17 @@ boolean check_password(char *p) return true; #endif if (os.iopts[IOPT_IGNORE_PASSWORD]) return true; +#if !defined(ESP8266) if (m_client && !p) { p = get_buffer; } +#endif if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pw"), true)) { urlDecode(tmp_buffer); if (os.password_verify(tmp_buffer)) return true; } #if defined(ESP8266) - if(m_client) { return false; } /* some pages will output fwv if password check has failed */ if(fwv_on_fail) { rewind_ether_buffer(); @@ -544,8 +536,6 @@ void server_change_stations() { #if defined(ESP8266) char* p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char* p = get_buffer; #endif @@ -593,9 +583,7 @@ void server_change_stations() { } if (!found || activeState > 1) handle_return(HTML_DATA_OUTOFBOUND); } else if (tmp_buffer[0] == STN_TYPE_HTTP) { - #if defined(ESP8266) // ESP8266 performs automatic decoding so no need to do it again - if(m_server) urlDecode(tmp_buffer + 1); - #else + #if !defined(ESP8266) urlDecode(tmp_buffer + 1); #endif if (strlen(tmp_buffer+1) > sizeof(HTTPStationData)) { @@ -614,7 +602,6 @@ void server_change_stations() { } os.attribs_save(); - handle_return(HTML_SUCCESS); } @@ -647,8 +634,6 @@ void server_manual_program() { #if defined(ESP8266) char* p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -685,8 +670,6 @@ void server_change_runonce() { #if defined(ESP8266) char* p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; if(!findKeyVal(p,tmp_buffer,TMP_BUFFER_SIZE, "t", false)) handle_return(HTML_DATA_MISSING); char *pv = tmp_buffer+1; #else @@ -750,8 +733,6 @@ void server_delete_program() { #if defined(ESP8266) char *p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -781,8 +762,6 @@ void server_moveup_program() { #if defined(ESP8266) char *p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -815,8 +794,6 @@ void server_change_program() { #if defined(ESP8266) char *p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -902,7 +879,7 @@ void server_change_program() { } // process interval day remainder (relative-> absolute) - if (prog.type == PROGRAM_TYPE_INTERVAL && prog.days[1] > 1) { + if (prog.type == PROGRAM_TYPE_INTERVAL && prog.days[1] >= 1) { pd.drem_to_absolute(prog.days); } @@ -1000,7 +977,7 @@ void server_json_programs_main() { ProgramStruct prog; for(pid=0;pid 1) { + if (prog.type == PROGRAM_TYPE_INTERVAL && prog.days[1] >= 1) { pd.drem_to_relative(prog.days); } @@ -1088,7 +1065,7 @@ void server_json_controller_main() { #endif byte mac[6] = {0}; - os.load_hardware_mac(mac, m_server!=NULL); + os.load_hardware_mac(mac, useEth); bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"wtdata\":$S,\"wterr\":$D,"), @@ -1188,10 +1165,8 @@ void server_change_values() { #if defined(ESP8266) char *p = NULL; - extern unsigned long reboot_timer; + extern uint32_t reboot_timer; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -1207,7 +1182,8 @@ void server_change_values() if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rbt"), true) && atoi(tmp_buffer) > 0) { #if defined(ESP8266) - reboot_timer = millis() + 1000; + os.status.safe_reboot = 0; + reboot_timer = os.now_tz() + 2; handle_return(HTML_SUCCESS); #else print_html_standard_header(); @@ -1273,8 +1249,6 @@ void server_change_scripturl() { #if defined(ESP8266) char *p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -1312,8 +1286,6 @@ void server_change_options() #if defined(ESP8266) char *p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -1380,6 +1352,7 @@ void server_change_options() if (os.sopt_save(SOPT_WEATHER_OPTS, tmp_buffer)) { weather_change = true; // if wto has changed } + //DEBUG_PRINTLN(os.sopt_load(SOPT_WEATHER_OPTS)); } keyfound = 0; @@ -1474,8 +1447,6 @@ void server_change_password() { #if defined(ESP8266) char* p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char* p = get_buffer; #endif @@ -1528,8 +1499,6 @@ void server_change_manual() { #if defined(ESP8266) char *p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -1623,8 +1592,6 @@ void server_json_log() { #if defined(ESP8266) char *p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -1664,7 +1631,7 @@ void server_json_log() { rewind_ether_buffer(); print_json_header(false); //bfill.emit_p(PSTR("$F$F$F$F\r\n"), html200OK, htmlContentJSON, htmlAccessControl, htmlNoCache); - //wifi_server->sendContent(ether_buffer); + //w_server->sendContent(ether_buffer); #else print_json_header(false); #endif @@ -1677,7 +1644,7 @@ void server_json_log() { make_logfile_name(tmp_buffer); #if defined(ESP8266) - File file = SPIFFS.open(tmp_buffer, "r"); + File file = LittleFS.open(tmp_buffer, "r"); if(!file) continue; #elif defined(ARDUINO) if (!sd.exists(tmp_buffer)) continue; @@ -1760,8 +1727,6 @@ void server_delete_log() { #if defined(ESP8266) char *p = NULL; if(!process_password()) return; - if (m_client) - p = get_buffer; #else char *p = get_buffer; #endif @@ -1815,7 +1780,7 @@ void server_json_debug() { #if defined(ESP8266) (uint16_t)ESP.getFreeHeap()); FSInfo fs_info; - SPIFFS.info(fs_info); + LittleFS.info(fs_info); bfill.emit_p(PSTR(",\"flash\":$D,\"used\":$D}"), fs_info.totalBytes, fs_info.usedBytes); #else (uint16_t)freeHeap()); @@ -1917,7 +1882,7 @@ void on_sta_upload_fin() { void on_ap_upload_fin() { on_sta_upload_fin(); } void on_sta_upload() { - HTTPUpload& upload = wifi_server->upload(); + HTTPUpload& upload = w_server->upload(); if(upload.status == UPLOAD_FILE_START){ WiFiUDP::stopAll(); DEBUG_PRINT(F("upload: ")); @@ -1946,7 +1911,7 @@ void on_sta_upload() { } void on_ap_upload() { - HTTPUpload& upload = wifi_server->upload(); + HTTPUpload& upload = w_server->upload(); if(upload.status == UPLOAD_FILE_START){ DEBUG_PRINT(F("upload: ")); DEBUG_PRINTLN(upload.filename); @@ -1973,12 +1938,12 @@ void on_ap_upload() { } void start_server_client() { - if(!wifi_server) return; + if(!w_server) return; - wifi_server->on("/", server_home); // handle home page - wifi_server->on("/index.html", server_home); - wifi_server->on("/update", HTTP_GET, on_sta_update); // handle firmware update - wifi_server->on("/update", HTTP_POST, on_sta_upload_fin, on_sta_upload); + w_server->on("/", server_home); // handle home page + w_server->on("/index.html", server_home); + w_server->on("/update", HTTP_GET, on_sta_update); // handle firmware update + w_server->on("/update", HTTP_POST, on_sta_upload_fin, on_sta_upload); // set up all other handlers char uri[4]; @@ -1987,25 +1952,25 @@ void start_server_client() { for(int i=0;ion(uri, urls[i]); + w_server->on(uri, urls[i]); } - wifi_server->begin(); + w_server->begin(); } void start_server_ap() { - if(!wifi_server) return; + if(!w_server) return; scanned_ssids = scan_network(); String ap_ssid = get_ap_ssid(); start_network_ap(ap_ssid.c_str(), NULL); delay(500); - wifi_server->on("/", on_ap_home); - wifi_server->on("/jsap", on_ap_scan); - wifi_server->on("/ccap", on_ap_change_config); - wifi_server->on("/jtap", on_ap_try_connect); - wifi_server->on("/update", HTTP_GET, on_ap_update); - wifi_server->on("/update", HTTP_POST, on_ap_upload_fin, on_ap_upload); - wifi_server->onNotFound(on_ap_home); + w_server->on("/", on_ap_home); + w_server->on("/jsap", on_ap_scan); + w_server->on("/ccap", on_ap_change_config); + w_server->on("/jtap", on_ap_try_connect); + w_server->on("/update", HTTP_GET, on_ap_update); + w_server->on("/update", HTTP_POST, on_ap_upload_fin, on_ap_upload); + w_server->onNotFound(on_ap_home); // set up all other handlers char uri[4]; @@ -2014,10 +1979,10 @@ void start_server_ap() { for(int i=0;ion(uri, urls[i]); + w_server->on(uri, urls[i]); } - wifi_server->begin(); + w_server->begin(); os.lcd.setCursor(0, -1); os.lcd.print(F("OSAP:")); os.lcd.print(ap_ssid); @@ -2087,11 +2052,11 @@ void handle_web_request(char *p) { } } if (ret == -1) { - if (m_client) - m_client->stop(); #if defined(ESP8266) - else - wifi_server->client().stop(); + w_server->client().stop(); +#else + if (m_client) + m_client->stop(); #endif return; } @@ -2120,16 +2085,62 @@ void handle_web_request(char *p) { } #if defined(ARDUINO) +#define NTP_NTRIES 10 /** NTP sync request */ +#if defined(ESP8266) +ulong getNtpTime() { + static bool configured = false; + if(!configured) { + byte ntpip[4] = { + os.iopts[IOPT_NTP_IP1], + os.iopts[IOPT_NTP_IP2], + os.iopts[IOPT_NTP_IP3], + os.iopts[IOPT_NTP_IP4]}; // todo: handle changes to ntpip dynamically + if (!os.iopts[IOPT_NTP_IP1] || os.iopts[IOPT_NTP_IP1] == '0') { + DEBUG_PRINTLN(F("using default time servers")); + configTime(0, 0, "time.google.com", "time.nist.gov", "time.windows.com"); + } else { + DEBUG_PRINTLN(F("using custom time server")); + String ntp = IPAddress(ntpip[0],ntpip[1],ntpip[2],ntpip[3]).toString(); + configTime(0, 0, ntp.c_str(), "time.google.com", "time.nist.gov"); + } + configured = true; + } + byte tries = 0; + ulong gt = 0; + while(tries1577836800UL) break; + else gt = 0; + delay(1000); + tries++; + } + 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 + UDP *udp = new EthernetUDP(); + #define NTP_PACKET_SIZE 48 #define NTP_PORT 123 - #define NTP_NTRIES 3 - + #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 byte packetBuffer[NTP_PACKET_SIZE]; byte ntpip[4] = { os.iopts[IOPT_NTP_IP1], @@ -2138,8 +2149,10 @@ ulong getNtpTime() { os.iopts[IOPT_NTP_IP4]}; byte tries=0; ulong gt = 0; - do { + while(triesbegin(port); + memset(packetBuffer, 0, NTP_PACKET_SIZE); packetBuffer[0] = 0b11100011; // LI, Version, Mode packetBuffer[1] = 0; // Stratum, or type of clock @@ -2150,21 +2163,35 @@ ulong getNtpTime() { packetBuffer[13] = 0x4E; packetBuffer[14] = 49; packetBuffer[15] = 52; - // by default use pool.ntp.org if ntp ip is unset + + // use one of the public NTP servers if ntp ip is unset + DEBUG_PRINT(F("ntp: ")); + int ret; if (!os.iopts[IOPT_NTP_IP1] || os.iopts[IOPT_NTP_IP1] == '0') { - DEBUG_PRINTLN(F("pool.ntp.org")); - udp->beginPacket("pool.ntp.org", NTP_PORT); + DEBUG_PRINT(public_ntp_servers[sidx]); + ret = udp->beginPacket(public_ntp_servers[sidx], NTP_PORT); } else { DEBUG_PRINTLN(IPAddress(ntpip[0],ntpip[1],ntpip[2],ntpip[3])); - udp->beginPacket(ntpip, NTP_PORT); + ret = udp->beginPacket(ntpip, NTP_PORT); + } + if(ret!=1) { + DEBUG_PRINT(F(" not available (ret: ")); + DEBUG_PRINT(ret); + DEBUG_PRINTLN(")"); + udp->stop(); + tries++; + sidx=(sidx+1)%N_PUBLIC_SERVERS; + continue; + } else { + DEBUG_PRINTLN(F(" connected")); } udp->write(packetBuffer, NTP_PACKET_SIZE); udp->endPacket(); // end of sendNtpPacket // process response - ulong timeout = millis()+1000; + ulong timeout = millis()+2000; while(millis() < timeout) { if(udp->parsePacket()) { udp->read(packetBuffer, NTP_PACKET_SIZE); @@ -2174,12 +2201,21 @@ ulong getNtpTime() { ulong seventyYears = 2208988800UL; ulong gt = secsSince1900 - seventyYears; // check validity: has to be larger than 1/1/2020 12:00:00 - if(gt>1577836800UL) return gt; + if(gt>1577836800UL) { + udp->stop(); + delete udp; + return gt; + } } } - tries ++; - } while(triesstop(); + sidx=(sidx+1)%N_PUBLIC_SERVERS; + } + if(tries==NTP_NTRIES) {DEBUG_PRINTLN(F("NTP failed!!"));} + udp->stop(); + delete udp; return 0; } #endif +#endif diff --git a/server.h b/opensprinkler_server.h similarity index 96% rename from server.h rename to opensprinkler_server.h index 49599abb1..42873900b 100644 --- a/server.h +++ b/opensprinkler_server.h @@ -21,8 +21,8 @@ * . */ -#ifndef _SERVER_H -#define _SERVER_H +#ifndef _OPENSPRINKLER_SERVER_H +#define _OPENSPRINKLER_SERVER_H #if !defined(ARDUINO) #include @@ -94,4 +94,4 @@ class BufferFiller { }; -#endif // _SERVER_H +#endif // _OPENSPRINKLER_SERVER_H diff --git a/platformio.ini b/platformio.ini new file mode 100644 index 000000000..44aa7fc3d --- /dev/null +++ b/platformio.ini @@ -0,0 +1,43 @@ +; PlatformIO Project Configuration File +; +; Build options: build flags, source filter +; Upload options: custom upload port, speed and extra flags +; Library options: dependencies, extra library storages +; Advanced options: extra scripting +; +; Please visit documentation for the other options and examples +; https://docs.platformio.org/page/projectconf.html + +; adapt to the already existing folder structure. +; usually there's src/ and include/ folders. +; redirect them both. +[platformio] +src_dir = . +include_dir = . + +[env:d1_mini_lite] +platform = espressif8266 +board = d1_mini_lite +framework = arduino +lib_ldf_mode = deep +lib_deps = + EthernetENC=https://github.com/jandrassy/EthernetENC/archive/refs/tags/2.0.1.zip + sui77/rc-switch @ ^2.6.3 + https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip + knolleary/PubSubClient @ ^2.8 +; ignore html2raw.cpp source file for firmware compilation (external helper program) +src_filter = +<*> - + +[env:sanguino_atmega1284p] +platform = atmelavr +board = ATmega1284P +board_build.f_cpu = 16000000L +board_build.variant = sanguino +framework = arduino +lib_ldf_mode = deep +lib_deps = + EthernetENC=https://github.com/jandrassy/EthernetENC/archive/refs/tags/2.0.1.zip + knolleary/PubSubClient @ ^2.8 + greiman/SdFat @ 1.0.7 + Wire +src_filter = +<*> - diff --git a/tools/board_op.pl b/tools/board_op.pl new file mode 100644 index 000000000..567c7cb30 --- /dev/null +++ b/tools/board_op.pl @@ -0,0 +1,60 @@ +#!/usr/bin/env perl +#==================================================================================== +# board_op.pl +# +# Performs search operations on the Arduino boards file +# +# +# This file is part of makeESPArduino +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2020 Peter Lerup. All rights reserved. +# +#==================================================================================== + +use strict; + + +my $file_name = shift; +my $cpu = shift; +my $board_name = shift; +my $op = shift; + +my $flash_def_match = $cpu eq "esp32" ? '\.build\.flash_size=(\S+)' : '\.menu\.(?:FlashSize|eesz)\.([^\.]+)=(.+)'; +my $lwip_def_match = '\.menu\.(?:LwIPVariant|ip)\.(\w+)=(.+)'; + +my $boards_file; +local($/); +open($boards_file, $file_name) || die "Failed to open: $file_name\n"; +my $board_conf = <$boards_file>; +close($boards_file); + +my $result; +if ($op eq "first") { + $result = $1 if $board_conf =~ /(\w+)\.name=/; +} elsif ($op eq "check") { + $result = $board_conf =~ /$board_name\.name/; +} elsif ($op eq "first_flash") { + $result = $1 if $board_conf =~ /$board_name$flash_def_match/; +} elsif ($op eq "first_lwip") { + $result = $1 if $board_conf =~ /$board_name$lwip_def_match/; +} elsif ($op eq "list_names") { + print "=== Available boards ===\n"; + foreach (split("\n", $board_conf)) { + print sprintf("%-20s %s\n", $1, $2) if /^([\w\-]+)\.name=(.+)/; + } +} elsif ($op eq "list_flash") { + print "=== Memory configurations for board: $board_name ===\n"; + foreach (split("\n", $board_conf)) { + print sprintf("%-10s %s\n", $1, $2) if /$board_name$flash_def_match/; + } +} elsif ($op eq "list_lwip") { + print "=== lwip configurations for board: $board_name ===\n"; + foreach (split("\n", $board_conf)) { + print sprintf("%-10s %s\n", $1, $2) if /$board_name$lwip_def_match/; + } +} + +print $result; \ No newline at end of file diff --git a/tools/crash_tool.pl b/tools/crash_tool.pl new file mode 100644 index 000000000..a739e7695 --- /dev/null +++ b/tools/crash_tool.pl @@ -0,0 +1,90 @@ +#!/usr/bin/env perl +#==================================================================================== +# crash_tool.pl +# +# Analyzes crash dumps for esp8266 and esp32 +# Completely based on the work in these two repos: +# https://github.com/me-no-dev/EspExceptionDecoder +# https://github.com/littleyoda/EspStackTraceDecoder +# +# This file is part of makeESPArduino +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2020 Peter Lerup. All rights reserved. +# +#==================================================================================== + +use strict; +use File::Find; +use Term::ANSIColor qw(:constants); +local $Term::ANSIColor::AUTORESET = 1; + +my $max_width = `tput cols`; + +my ($esp_root, $elf_file_name) = @ARGV; + +my $addr2line; +finddepth(sub { $addr2line = $File::Find::name if (/addr2line$/); }, $esp_root); +die("Failed to locate addr2line\n") unless $addr2line; + +my @exceptions = ( +"Illegal instruction", +"SYSCALL instruction", +"InstructionFetchError: Processor internal physical address or data error during instruction fetch", +"LoadStoreError: Processor internal physical address or data error during load or store", +"Level1Interrupt: Level-1 interrupt as indicated by set level-1 bits in the INTERRUPT register", +"Alloca: MOVSP instruction, if caller's registers are not in the register file", +"IntegerDivideByZero: QUOS, QUOU, REMS, or REMU divisor operand is zero", +"reserved", +"Privileged: Attempt to execute a privileged operation when CRING ? 0", +"LoadStoreAlignmentCause: Load or store to an unaligned address", +"reserved", +"reserved", +"InstrPIFDataError: PIF data error during instruction fetch", +"LoadStorePIFDataError: Synchronous PIF data error during LoadStore access", +"InstrPIFAddrError: PIF address error during instruction fetch", +"LoadStorePIFAddrError: Synchronous PIF address error during LoadStore access", +"InstTLBMiss: Error during Instruction TLB refill", +"InstTLBMultiHit: Multiple instruction TLB entries matched", +"InstFetchPrivilege: An instruction fetch referenced a virtual address at a ring level less than CRING", +"reserved", +"InstFetchProhibited: An instruction fetch referenced a page mapped with an attribute that does not permit instruction fetch", +"reserved", +"reserved", +"reserved", +"LoadStoreTLBMiss: Error during TLB refill for a load or store", +"LoadStoreTLBMultiHit: Multiple TLB entries matched for a load or store", +"LoadStorePrivilege: A load or store referenced a virtual address at a ring level less than CRING", +"reserved", +"LoadProhibited: A load referenced a page mapped with an attribute that does not permit loads", +"StoreProhibited: A store referenced a page mapped with an attribute that does not permit stores" +); + +print BOLD GREEN "Paste your stack trace here!\n\n"; +my @addr; +my $reason; +while () { + last if /<< $max_width-2; + print " $path\n"; +} +print "\n"; + + diff --git a/tools/find_src.pl b/tools/find_src.pl new file mode 100644 index 000000000..a3d5bae83 --- /dev/null +++ b/tools/find_src.pl @@ -0,0 +1,131 @@ +#!/usr/bin/env perl +#==================================================================================== +# find_src.pl +# +# Search for source files and required header file directories +# +# This file is part of makeESPArduino +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2016-2020 Peter Lerup. All rights reserved. +# +#==================================================================================== + +use strict; +use File::Basename; + +my %src_files; +my %inc_dirs; +my %user_libs; +my @search_dirs; +my %src_dirs; +my %checked_files; + +sub uniq { + my %seen; + grep !$seen{$_}++, @_; +} + +#-------------------------------------------------------------------- + +sub find_inc { + # Recursively find include statements + my $file_name = shift; + open(my $f, $file_name) || return; + $inc_dirs{dirname($file_name)}++; + while (<$f>) { + next unless /^\s*\#include\s*[<"]([^>"]+)/; + my $match = $1; + next if $checked_files{$match}; + $checked_files{$match}++; + for (my $i = 0; $i < @search_dirs; $i++) { + my $inc_file = "$search_dirs[$i]/$match"; + next unless -f $inc_file; + find_inc($inc_file); + my $dir = dirname($inc_file); + if (!$src_dirs{$dir}) { + # Add all source files in this directory + # Can not only search for file with same name as sometimes + # the actual implementation of the header has another name + foreach my $src (glob("$dir/*.cpp $dir/*.c $dir/*.S")) { + $src_files{$src}++; + find_inc($src); + } + } + last; + } + } + close($f); +} + +#-------------------------------------------------------------------- + +my $exclude_match = shift; + +# Parameters are within quotes to delay possible wildcard file name expansions +my @libs = split(" ", "@ARGV"); + +if ($libs[0] =~ /(.+)\/examples\//) { + # The sketch is an example, add the corresponding src directory to the library list if it exists + my $src_dir = "$1/src"; + push(@libs, $src_dir) if -d $src_dir; +} + +# First find possible explicit library source or achive files from the the specified list +for (my $i = 0; $i < @libs; $i++ ) { + my $path = $libs[$i]; + if (!-d $path) { + # File specification + my $wildcard = $path =~ /\*/; + if (!-e $path && !$wildcard) { + print STDERR "* Warning: Ignoring non existing file specification $path\n"; + next; + } + $libs[$i] = dirname($path); + # Mark as known source directory, except for sketch directory + $src_dirs{$libs[$i]}++ if $i; + if ($path =~ /\.(a|lib)$/) { + # Library file + $user_libs{$path}++; + } elsif ($wildcard) { + # Wildcard source files + foreach my $src (glob($path)) { + $src_files{$src}++; + } + } else { + # Single source file + $src_files{$path}++; + } + } +} +@libs = uniq(@libs); + +# Expand all sub directories of the specified library directories +# Keep the original order, hence stored in array and not hash +# These directories will be included in the search for used header files +my $dir_spec = join(" ", @libs); +foreach (`find $dir_spec -type d 2>/dev/null`) { + chomp; + s/\/$//; + next if /LittleFS\/lib/; # Fix for now + push(@search_dirs, $_) unless $exclude_match && /$exclude_match/; +} +@search_dirs = uniq(@search_dirs); + +# Search for used header files in all the specified source files +my @spec_src = keys %src_files; +foreach (@spec_src) { + find_inc($_); +} + +# Print the result as makefile variable definitions +print "USER_INC_DIRS = "; +# Keep order +foreach (@search_dirs) { + print "$_ " if $inc_dirs{$_}; +} +print "\n"; +print "USER_SRC = ", join(" ", sort(keys %src_files)), "\n"; +print "USER_LIBS = ", join(" ", keys %user_libs), "\n" diff --git a/tools/mem_use.pl b/tools/mem_use.pl new file mode 100644 index 000000000..7bbcdbeae --- /dev/null +++ b/tools/mem_use.pl @@ -0,0 +1,27 @@ +#!/usr/bin/env perl +#==================================================================================== +# mem_use.pl +# +# Shows summary of flash and RAM memory +# +# This file is part of makeESPArduino +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2016-2021 Peter Lerup. All rights reserved. +# +#==================================================================================== + +use strict; + +my $flash_sections = shift; +my $ram_sections = shift; +my $flash_tot = 0; +my $ram_tot = 0; +while (<>) { + $flash_tot += $1 if /$flash_sections/; + $ram_tot += $1 if /$ram_sections/; +} +print "\nMemory summary\n"; +print sprintf(" %-6s %6d bytes\n" x 2 ."\n", "RAM:", $ram_tot, "Flash:", $flash_tot); diff --git a/tools/obj_info.pl b/tools/obj_info.pl new file mode 100644 index 000000000..ba0df9680 --- /dev/null +++ b/tools/obj_info.pl @@ -0,0 +1,40 @@ +#!/usr/bin/env perl +#==================================================================================== +# obj_info.pl +# +# Show memory usage for object files +# +# This file is part of makeESPArduino +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2021 Peter Lerup. All rights reserved. +# +#==================================================================================== + +use strict; + +my $elf_size = shift; +my $form = shift == "1" ? "%s\t%s\t%s\t%s\t%s\t%s\n" : "%-38.38s %7s %7s %7s %7s %7s\n"; +my $sort_index = shift; +print sprintf($form, "File", "Flash", "RAM", "data", "rodata", "bss"); +print "-" x 78, "\n" unless $form =~ /\t/; + +my %info; +while (my $obj_file = shift) { + next unless $obj_file =~ /.+\/([\w\.]+)\.o$/; + my $name = $1; + for (my $i = 0; $i < 5; $i++) { $info{$name}[$i] = 0; } + foreach (split("\n", `$elf_size -A $obj_file`)) { + $info{$name}[0] += $1 if /(?:\.irom0\.text|\.text|\.text1|\.data|\.rodata)\S*\s+([0-9]+).*/; + $info{$name}[2] += $1 if /^.data\S*\s+([0-9]+).*/; + $info{$name}[3] += $1 if /^.rodata\S*\s+([0-9]+).*/; + $info{$name}[4] += $1 if /^.bss\S*\s+([0-9]+).*/; + } + $info{$name}[1] = $info{$name}[2] + $info{$name}[3] + $info{$name}[4]; +} +foreach (sort { $info{$b}[$sort_index] <=> $info{$a}[$sort_index] or $info{$b}[0] <=> $info{$a}[0] } keys %info) { + print sprintf($form, $_, $info{$_}[0], $info{$_}[1], $info{$_}[2], $info{$_}[3], $info{$_}[4]); +} + diff --git a/tools/parse_arduino.pl b/tools/parse_arduino.pl new file mode 100644 index 000000000..2355e7cce --- /dev/null +++ b/tools/parse_arduino.pl @@ -0,0 +1,161 @@ +#!/usr/bin/env perl +#==================================================================================== +# parse_arduino.pl +# +# Parses Arduino configuration files and writes the content +# of a corresponding makefile +# +# This file is part of makeESPArduino +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2016-2021 Peter Lerup. All rights reserved. +# +#==================================================================================== + +use strict; + +my $board = shift; +my $flashSize = shift; +my $os = shift; +my $lwipvariant = shift; +my %vars; + +sub def_var { + my ($name, $var) = @_; + print "$var ?= $vars{$name}\n"; + $vars{$name} = "\$($var)"; +} + +sub multi_com { + my ($match ) = @_; + my @result; + foreach my $name (sort keys %vars) { + push(@result, $vars{$name}) if $name =~ /^$match$/; + } + return join(" && \\\n", @result); +} + +# Some defaults +$vars{'runtime.platform.path'} = '$(ESP_ROOT)'; +$vars{'includes'} = '$(C_INCLUDES)'; +$vars{'runtime.ide.version'} = '10605'; +$vars{'build.arch'} = '$(UC_CHIP)'; +$vars{'build.project_name'} = '$(MAIN_NAME)'; +$vars{'build.path'} = '$(BUILD_DIR)'; +$vars{'object_files'} = '$^ $(BUILD_INFO_OBJ)'; +$vars{'archive_file_path'} = '$(CORE_LIB)'; +$vars{'build.sslflags'} = '$(SSL_FLAGS)'; +$vars{'build.mmuflags'} = '$(MMU_FLAGS)'; +$vars{'build.vtable_flags'} = '$(VTABLE_FLAGS)'; +$vars{'build.source.path'} = '$(dir $(SKETCH))'; + +# Parse the files and define the corresponsing variables +my $board_defined; +foreach my $fn (@ARGV) { + my $f; + open($f, $fn) || die "Failed to open: $fn\n"; + while (<$f>) { + s/\s+$//; + s/\.esptool_py\./.esptool./g; + next unless /^(\w[\w\-\.]+)=(.*)/; + my ($key, $val) =($1, $2); + $board_defined = 1 if $key eq "$board.name"; + # Truncation of some variable names is needed + $key =~ s/$board\.menu\.(?:FlashSize|eesz)\.$flashSize\.//; + $key =~ s/$board\.menu\.CpuFrequency\.[^\.]+\.//; + $key =~ s/$board\.menu\.(?:FlashFreq|xtal)\.[^\.]+\.//; + $key =~ s/$board\.menu\.UploadSpeed\.[^\.]+\.//; + $key =~ s/$board\.menu\.baud\.[^\.]+\.//; + $key =~ s/$board\.menu\.ResetMethod\.[^\.]+\.//; + $key =~ s/$board\.menu\.FlashMode\.[^\.]+\.//; + $key =~ s/$board\.menu\.(?:LwIPVariant|ip)\.$lwipvariant\.//; + $key =~ s/^$board\.//; + $vars{$key} ||= $val; + $vars{$1} = $vars{$key} if $key =~ /(.+)\.$os$/; + } + close($f); +} +# Some additional defaults may be needed +$vars{'runtime.tools.xtensa-lx106-elf-gcc.path'} ||= '$(COMP_PATH)'; +$vars{'runtime.tools.xtensa-esp32-elf-gcc.path'} ||= '$(COMP_PATH)'; +$vars{'runtime.tools.python3.path'} ||= '$(PYTHON3_PATH)'; + +die "* Unknown board $board\n" unless $board_defined; +print "# Board definitions\n"; +def_var('build.code_debug', 'CORE_DEBUG_LEVEL'); +def_var('build.f_cpu', 'F_CPU'); +def_var('build.flash_mode', 'FLASH_MODE'); +def_var('build.flash_freq', 'FLASH_SPEED'); +def_var('upload.resetmethod', 'UPLOAD_RESET'); +def_var('upload.speed', 'UPLOAD_SPEED'); +def_var('compiler.warning_flags', 'COMP_WARNINGS'); +$vars{'serial.port'} = '$(UPLOAD_PORT)'; +$vars{'tools.esptool.upload.pattern'} =~ s/\{(cmd|path)\}/\{tools.esptool.$1\}/g; +$vars{'compiler.cpreprocessor.flags'} .= " \$(C_PRE_PROC_FLAGS)"; +$vars{'build.extra_flags'} .= " \$(BUILD_EXTRA_FLAGS)"; + +# Some additional replacements +foreach my $key (sort keys %vars) { + while ($vars{$key} =~/\{/) { + $vars{$key} =~ s/\{([\w\-\.]+)\}/$vars{$1}/; + $vars{$key} =~ s/""//; + } + $vars{$key} =~ s/ -o\s+$//; + $vars{$key} =~ s/(-D\w+=)"([^"]+)"/$1\\"$2\\"/g; +} + +# Print the makefile content +my $val; +print "INCLUDE_VARIANT = $vars{'build.variant'}\n"; +print "VTABLE_FLAGS?=-DVTABLES_IN_FLASH\n"; +print "MMU_FLAGS?=-DMMU_IRAM_SIZE=0x8000 -DMMU_ICACHE_SIZE=0x8000\n"; +print "SSL_FLAGS?=\n"; +print "BOOT_LOADER?=\$(ESP_ROOT)/bootloaders/eboot/eboot.elf\n"; +print "# Commands\n"; +print "C_COM=\$(C_COM_PREFIX) $vars{'recipe.c.o.pattern'}\n"; +print "CPP_COM=\$(CPP_COM_PREFIX) $vars{'recipe.cpp.o.pattern'}\n"; +print "S_COM=$vars{'recipe.S.o.pattern'}\n"; +print "LIB_COM=\"$vars{'compiler.path'}$vars{'compiler.ar.cmd'}\"\n"; +print "CORE_LIB_COM=$vars{'recipe.ar.pattern'}\n"; +print "LD_COM=$vars{'recipe.c.combine.pattern'}\n"; +print "PART_FILE?=\$(ESP_ROOT)/tools/partitions/default.csv\n"; +$val = $vars{'recipe.objcopy.eep.pattern'} || $vars{'recipe.objcopy.partitions.bin.pattern'}; +$val =~ s/\"([^\"]+\.csv)\"/\$(PART_FILE)/; +print "GEN_PART_COM=$val\n"; +($val = multi_com('recipe\.objcopy\.hex.*\.pattern')) =~ s/[^"]+\/bootloaders\/eboot\/eboot.elf/\$(BOOT_LOADER)/; +$val ||= multi_com('recipe\.objcopy\.bin.*\.pattern'); +print "OBJCOPY=$val\n"; +print "SIZE_COM=$vars{'recipe.size.pattern'}\n"; +print "UPLOAD_COM?=$vars{'tools.esptool.upload.pattern'} $vars{'tools.esptool.upload.pattern_args'}\n"; + +if ($vars{'build.spiffs_start'}) { + print "SPIFFS_START?=$vars{'build.spiffs_start'}\n"; + my $spiffs_size = sprintf("0x%X", hex($vars{'build.spiffs_end'})-hex($vars{'build.spiffs_start'})); + print "SPIFFS_SIZE?=$spiffs_size\n"; +} elsif ($vars{'build.partitions'}) { + print "COMMA=,\n"; + print "SPIFFS_SPEC:=\$(subst \$(COMMA), ,\$(shell grep spiffs \$(PART_FILE)))\n"; + print "SPIFFS_START:=\$(word 4,\$(SPIFFS_SPEC))\n"; + print "SPIFFS_SIZE:=\$(word 5,\$(SPIFFS_SPEC))\n"; +} +$vars{'build.spiffs_blocksize'} ||= "4096"; +print "SPIFFS_BLOCK_SIZE?=$vars{'build.spiffs_blocksize'}\n"; +print "MK_FS_COM?=\"\$(MK_FS_PATH)\" -b \$(SPIFFS_BLOCK_SIZE) -s \$(SPIFFS_SIZE) -c \$(FS_DIR) \$(FS_IMAGE)\n"; +print "RESTORE_FS_COM?=\"\$(MK_FS_PATH)\" -b \$(SPIFFS_BLOCK_SIZE) -s \$(SPIFFS_SIZE) -u \$(FS_RESTORE_DIR) \$(FS_IMAGE)\n"; + +my $fs_upload_com = $vars{'tools.esptool.upload.pattern'} . " $vars{'tools.esptool.upload.pattern_args'}"; +$fs_upload_com =~ s/(.+ -ca) .+/$1 \$(SPIFFS_START) -cf \$(FS_IMAGE)/; +$fs_upload_com =~ s/(.+ --flash_size detect) .+/$1 \$(SPIFFS_START) \$(FS_IMAGE)/; +print "FS_UPLOAD_COM?=$fs_upload_com\n"; +$val = multi_com('recipe\.hooks*\.prebuild.*\.pattern'); +$val =~ s/bash -c "(.+)"/$1/g; +$val =~ s/(#define .+0x)(\`)/"\\$1\"$2/; +print "PREBUILD=$val\n"; +print "PRELINK=", multi_com('recipe\.hooks\.linking\.prelink.*\.pattern'), "\n"; +print "MEM_FLASH=$vars{'recipe.size.regex'}\n"; +print "MEM_RAM=$vars{'recipe.size.regex.data'}\n"; +my $flash_info = $vars{'menu.FlashSize.' . $flashSize} || $vars{'menu.eesz.' . $flashSize}; +print "FLASH_INFO=$flash_info\n"; +print "LWIP_INFO=", $vars{'menu.LwIPVariant.' . $lwipvariant} || $vars{'menu.ip.' . $lwipvariant}, "\n"; diff --git a/tools/py_wrap.py b/tools/py_wrap.py new file mode 100644 index 000000000..77da724eb --- /dev/null +++ b/tools/py_wrap.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 +#==================================================================================== +# py_wrap.pl +# +# Wrapper for python scripts in the esp8266 Arduino tools directory +# +# This file is part of makeESPArduino +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2021 Peter Lerup. All rights reserved. +# +#==================================================================================== + +import sys +import os + +sys.argv.pop(0) +root_dir = sys.argv.pop(0) +is_module = sys.argv[0] == "-m" +if is_module: + sys.argv.pop(0) +script = sys.argv[0] +# Include the required module directories to the path +sys.path.insert(0, root_dir + "/pyserial") +sys.path.insert(0, root_dir + "/esptool") +exec("import " + script) +if not is_module: + sys.argv.pop(0) +exec(script + ".main(sys.argv)") diff --git a/tools/vscode.pl b/tools/vscode.pl new file mode 100644 index 000000000..791f9c1f3 --- /dev/null +++ b/tools/vscode.pl @@ -0,0 +1,165 @@ +#!/usr/bin/env perl +#==================================================================================== +# vscode.pl +# +# Generates Visual Studio Code properties and task config files +# based on the compile command line and then starts VS Code +# +# This file is part of makeESPArduino +# License: LGPL 2.1 +# General and full license information is available at: +# https://github.com/plerup/makeEspArduino +# +# Copyright (c) 2020 Peter Lerup. All rights reserved. +# +#==================================================================================== + +use strict; +use Cwd; +use JSON::PP; +use Getopt::Std; +use File::Basename; + +sub file_to_string { + local($/); + my $f; + open($f, $_[0]) || return ""; + my $res = <$f>; + close($f); + return $res; +} + +#-------------------------------------------------------------------- + +sub string_to_file { + my $f; + open($f, ">$_[0]") || return 0; + print $f $_[1]; + close($f); + return 1; +} + +#-------------------------------------------------------------------- + +sub find_dir_upwards { + my $match = $_[0]; + my $dir = Cwd::abs_path("."); + while ($dir ne "/" && $dir ne $ENV{'HOME'}) { + my $test = $dir . "/$match"; + return $test if -e $test; + $dir = dirname($dir); + } + return Cwd::abs_path(".") . "/$match"; +} + +#-------------------------------------------------------------------- + +sub make_portable { + # Use variables in paths when possible + $_[0] =~ s/$_[1]/\$\{workspaceFolder\}/g; + $_[0] =~ s/$ENV{'HOME'}/\$\{env:HOME\}/g; +} + +#-------------------------------------------------------------------- + +# Parameters +my %opts; +getopts('n:m:w:d:i:p:', \%opts); +my $name = $opts{n} || "Linux"; +my $make_com = $opts{m} || "espmake"; +my $workspace_dir = $opts{w}; +my $proj_file = $opts{p}; +my $cwd = $opts{d} || getcwd; +my $comp_path = shift; +$comp_path = shift if $comp_path eq "ccache"; + +my $config_dir_name = ".vscode"; +$workspace_dir ||= dirname(find_dir_upwards($config_dir_name)); +$proj_file ||= (glob("$workspace_dir/*.code-workspace"))[0]; +my $config_dir = "$workspace_dir/$config_dir_name"; +mkdir($config_dir); + +# == C & C++ configuration +my @defines; +my @includes; +my $prop_file_name = "$config_dir/c_cpp_properties.json"; +my $prop_json = file_to_string($prop_file_name) || '{"version": 4, "configurations": []}'; + +# Build this configuration from command line defines and includes +while ($_ = shift) { + $_ .= shift if ($_ eq "-D"); + if (/-D\s*(\S+)/) { + # May be a quoted value + my $def = $1; + $def =~ s/\"/\\\"/g; + push(@defines, "\"$def\"") + } + push(@includes, "\"" . Cwd::abs_path($1) . "\"") if /-I\s*(\S+)/ && -e $1; +} +# Optional additional include directories +foreach (split(" ", $opts{i})) { + push(@includes, "\"$_\""); +} + +# Build corresponding json +my $def = join(',', @defines); +my $inc = join(',', @includes); +my $this_prop_json = <<"EOT"; +{ + "name": "$name", + "includePath": [$inc], + "defines": [$def], + "compilerPath": "$comp_path" +} +EOT +make_portable($this_prop_json, $workspace_dir); + +# Insert or replace this configuration +my $json_ref = decode_json($prop_json); +my $configs = $$json_ref{'configurations'}; +my $ind = 0; +foreach my $conf (@$configs) { + last if $$conf{'name'} eq $name; + $ind++; +} +$$configs[$ind] = decode_json($this_prop_json); +string_to_file($prop_file_name, JSON::PP->new->pretty->encode(\%$json_ref)); + +# == Add a task with the current name +my $this_task_json = <<"EOT"; +{ + "label": "$name", + "type": "shell", + "command": "$make_com", + "options": {"cwd": "$cwd"}, + "problemMatcher": ["\$gcc"], + "group": "build" +} +EOT +make_portable($this_task_json, $workspace_dir); +my $this_task = decode_json($this_task_json); +my $task_file_name = "$config_dir/tasks.json"; +my $task_json = file_to_string($task_file_name) || '{"version": "2.0.0", "tasks": []}'; +$json_ref = decode_json($task_json); +my $tasks = $$json_ref{'tasks'}; +my $found; +for (my $i = 0; !$found && $i < scalar(@$tasks); $i++) { + if ($$tasks[$i]{'label'} eq $name) { + # A task with this name exists, make sure that possible default build setting is kept + $found = 1; + $$this_task{'group'} = $$tasks[$i]{'group'}; + $$tasks[$i] = $this_task; + } +} +push(@$tasks, $this_task) if !$found; +string_to_file($task_file_name, JSON::PP->new->pretty->encode(\%$json_ref)); + + +# Launch Visual Studio Code +$proj_file ||= $workspace_dir; +print "Starting VS Code - $proj_file ...\n"; +# Remove all MAKE variables to avoid conflict when building inside VS Code +foreach my $var (keys %ENV) { + $ENV{$var} = undef if $var =~ /^MAKE/; +} +system("code $proj_file"); diff --git a/utils.cpp b/utils.cpp index 40049c8a0..2bed17789 100644 --- a/utils.cpp +++ b/utils.cpp @@ -29,6 +29,7 @@ extern OpenSprinkler os; #if defined(ESP8266) #include + #include #else #include #include "SdFat.h" @@ -169,21 +170,22 @@ unsigned int detect_rpi_rev() { #endif +/* void write_to_file(const char *fn, const char *data, ulong size, ulong pos, bool trunc) { #if defined(ESP8266) File f; if(trunc) { - f = SPIFFS.open(fn, "w"); + f = LittleFS.open(fn, "w"); } else { - f = SPIFFS.open(fn, "r+"); - if(!f) f = SPIFFS.open(fn, "w"); + f = LittleFS.open(fn, "r+"); + if(!f) f = LittleFS.open(fn, "w"); } if(!f) return; if(pos) f.seek(pos, SeekSet); if(size==0) { - f.write((byte*)" ", 1); // hack to circumvent SPIFFS bug involving writing empty file + f.write((byte*)" ", 1); // hack to circumvent FS bug involving writing empty file } else { f.write((byte*)data, size); } @@ -221,7 +223,7 @@ void write_to_file(const char *fn, const char *data, ulong size, ulong pos, bool void read_from_file(const char *fn, char *data, ulong maxsize, ulong pos) { #if defined(ESP8266) - File f = SPIFFS.open(fn, "r"); + File f = LittleFS.open(fn, "r"); if(!f) { data[0]=0; return; // return with empty string @@ -229,7 +231,7 @@ void read_from_file(const char *fn, char *data, ulong maxsize, ulong pos) { if(pos) f.seek(pos, SeekSet); int len = f.read((byte*)data, maxsize); if(len>0) data[len]=0; - if(len==1 && data[0]==' ') data[0] = 0; // hack to circumvent SPIFFS bug involving writing empty file + if(len==1 && data[0]==' ') data[0] = 0; // hack to circumvent FS bug involving writing empty file data[maxsize-1]=0; f.close(); return; @@ -275,12 +277,13 @@ void read_from_file(const char *fn, char *data, ulong maxsize, ulong pos) { #endif } +*/ void remove_file(const char *fn) { #if defined(ESP8266) - if(!SPIFFS.exists(fn)) return; - SPIFFS.remove(fn); + if(!LittleFS.exists(fn)) return; + LittleFS.remove(fn); #elif defined(ARDUINO) @@ -298,7 +301,7 @@ void remove_file(const char *fn) { bool file_exists(const char *fn) { #if defined(ESP8266) - return SPIFFS.exists(fn); + return LittleFS.exists(fn); #elif defined(ARDUINO) @@ -320,7 +323,7 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { #if defined(ESP8266) // do not use File.readBytes or readBytesUntil because it's very slow - File f = SPIFFS.open(fn, "r"); + File f = LittleFS.open(fn, "r"); if(f) { f.seek(pos, SeekSet); f.read((byte*)dst, len); @@ -352,8 +355,8 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { #if defined(ESP8266) - File f = SPIFFS.open(fn, "r+"); - if(!f) f = SPIFFS.open(fn, "w"); + File f = LittleFS.open(fn, "r+"); + if(!f) f = LittleFS.open(fn, "w"); if(f) { f.seek(pos, SeekSet); f.write((byte*)src, len); @@ -392,7 +395,7 @@ void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) if(tmp==NULL) { return; } #if defined(ESP8266) - File f = SPIFFS.open(fn, "r+"); + File f = LittleFS.open(fn, "r+"); if(!f) return; f.seek(from, SeekSet); f.read((byte*)tmp, len); @@ -430,7 +433,7 @@ void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) byte file_cmp_block(const char *fn, const char *buf, ulong pos) { #if defined(ESP8266) - File f = SPIFFS.open(fn, "r"); + File f = LittleFS.open(fn, "r"); if(f) { f.seek(pos, SeekSet); char c = f.read(); diff --git a/utils.h b/utils.h index f448b8b90..0e5784cac 100644 --- a/utils.h +++ b/utils.h @@ -35,8 +35,8 @@ #include "defines.h" // File reading/writing functions -void write_to_file(const char *fname, const char *data, ulong size, ulong pos=0, bool trunc=true); -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, 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); diff --git a/weather.cpp b/weather.cpp index 23809cb91..b11e86f82 100644 --- a/weather.cpp +++ b/weather.cpp @@ -24,7 +24,7 @@ #include #include "OpenSprinkler.h" #include "utils.h" -#include "server.h" +#include "opensprinkler_server.h" #include "weather.h" extern OpenSprinkler os; // OpenSprinkler object @@ -125,15 +125,15 @@ static void getweather_callback(char* buffer) { } static void getweather_callback_with_peel_header(char* buffer) { + DEBUG_PRINTLN(buffer); peel_http_header(buffer); getweather_callback(buffer); } void GetWeather() { #if defined(ESP8266) - if(!m_server) { + if (!useEth) if (os.state!=OS_STATE_CONNECTED || WiFi.status()!=WL_CONNECTED) return; - } #endif // use temp buffer to construct get command BufferFiller bf = tmp_buffer; @@ -175,6 +175,8 @@ void GetWeather() { strcat(ether_buffer, host); strcat(ether_buffer, "\r\n\r\n"); + DEBUG_PRINTLN(ether_buffer); + wt_errCode = HTTP_RQT_NOT_RECEIVED; int ret = os.send_http_request(host, ether_buffer, getweather_callback_with_peel_header); if(ret!=HTTP_RQT_SUCCESS) { From 0b70b8b776e76435db5dfc227e06da196ff5efe3 Mon Sep 17 00:00:00 2001 From: OpenSprinklerShop <49987292+opensprinklershop@users.noreply.github.com> Date: Fri, 1 Apr 2022 10:23:16 +0200 Subject: [PATCH 006/281] Update README.txt lwip --- README.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.txt b/README.txt index db30fc39d..f15a6ab1b 100755 --- a/README.txt +++ b/README.txt @@ -14,3 +14,15 @@ https://openthings.freshdesk.com/support/solutions/articles/5000631599-installin Questions and comments: http://www.opensprinkler.com ============================================ + + +***************************************************************** UPDATE 01.04.2022 ************************************* +This is the lwip version from OpenSprinkler branch dev/os220 + +we found out that the lwip_enc28j60 had some bugs, so use better the lwip_enc28j60 from here: +https://github.com/esp8266/Arduino/pull/8376 + +Also it is better to use the updated version of LitteFS from here: +https://github.com/littlefs-project/littlefs + +************************************************************************************************************************* From e1af75305fa4711b13054f07c26fb03d04df59e2 Mon Sep 17 00:00:00 2001 From: OpenSprinklerShop <49987292+opensprinklershop@users.noreply.github.com> Date: Fri, 1 Apr 2022 10:25:24 +0200 Subject: [PATCH 007/281] Update README.txt --- README.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index f15a6ab1b..136269aed 100755 --- a/README.txt +++ b/README.txt @@ -16,7 +16,7 @@ http://www.opensprinkler.com ============================================ -***************************************************************** UPDATE 01.04.2022 ************************************* +************************************************************** UPDATE 01.04.2022 ************************************* This is the lwip version from OpenSprinkler branch dev/os220 we found out that the lwip_enc28j60 had some bugs, so use better the lwip_enc28j60 from here: @@ -25,4 +25,4 @@ https://github.com/esp8266/Arduino/pull/8376 Also it is better to use the updated version of LitteFS from here: https://github.com/littlefs-project/littlefs -************************************************************************************************************************* +********************************************************************************************************************** From 81eaf3bc7b0f6d3b17c4c656a9ae35b5428a1008 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 2 Apr 2022 09:39:46 +0200 Subject: [PATCH 008/281] Version 105: Register check enc_28j60, dhcp_renew und check_network --- defines.h | 2 +- main.cpp | 59 +++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 54 insertions(+), 7 deletions(-) diff --git a/defines.h b/defines.h index 8d900a1e6..6de9e5e49 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 104 // Firmware minor version +#define OS_FW_MINOR 105 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 diff --git a/main.cpp b/main.cpp index 05f43a08c..11f841782 100644 --- a/main.cpp +++ b/main.cpp @@ -33,6 +33,7 @@ #if defined(ARDUINO) #if defined(ESP8266) #include + extern "C" struct netif* eagle_lwip_getif (int netif_index); ESP8266WebServer *w_server = NULL; // due to lwIP, both WiFi and wired use the unified w_server variable ENC28J60lwIP eth(PIN_ETHER_CS); bool useEth = false; @@ -397,6 +398,7 @@ void delete_log(char *name); #if defined(ESP8266) void start_server_ap(); void start_server_client(); +bool check_enc28j60(); #endif void handle_web_request(char *p); @@ -939,6 +941,29 @@ void do_loop() } } + // dhcp and hw check: + static unsigned long dhcp_timeout = 0; + if(curr_time > dhcp_timeout) { + if (useEth) { + netif* intf = (netif*) eth.getNetIf(); + dhcp_renew(intf); + + if (dhcp_timeout > 0 && !check_enc28j60()) { + eth.resetEther(); + if (!eth.connected()) { + os.nvdata.reboot_cause = REBOOT_CAUSE_NETWORK_FAIL; + os.status.safe_reboot = 1; + } + } + } + else if (WiFi.status() == WL_CONNECTED && os.get_wifi_mode()==WIFI_MODE_STA) { + netif* intf = eagle_lwip_getif(STATION_IF); + dhcp_renew(intf); + } + dhcp_timeout = curr_time + DHCP_CHECKLEASE_INTERVAL; + } + + // perform ntp sync // instead of using curr_time, which may change due to NTP sync itself // we use Arduino's millis() method @@ -1713,7 +1738,7 @@ void check_network() { #endif #if defined(ARDUINO) #if defined(ESP8266) -#if defined(CHECK_NET) + if (os.status.program_busy) {return;} // check network condition periodically @@ -1733,9 +1758,9 @@ void check_network() { } else { //Ethernet //if (!eth.connected()) return; //failed = !Ping.ping(eth.gatewayIP(), 1); - //os.status.req_ntpsync = 1; - //failed = !perform_ntp_sync(); - failed = !eth.connected(); + os.status.req_ntpsync = 1; + failed = !getNtpTime(); + //failed = !eth.connected(); } DEBUG_PRINT(F("check_network: failed=")); @@ -1760,10 +1785,32 @@ void check_network() { } #endif #endif -#endif } - +#if defined(ARDUINO) +#if defined(ESP8266) +#define NET_ENC28J60_EIR 0x1C +#define NET_ENC28J60_ESTAT 0x1D +#define NET_ENC28J60_ECON1 0x1F +#define NET_ENC28J60_EIR_RXERIF 0x01 +#define NET_ENC28J60_ESTAT_BUFFER 0x40 +#define NET_ENC28J60_ECON1_RXEN 0x04 +bool check_enc28j60() +{ + uint8_t stateEconRxen = eth.readreg((uint8_t) NET_ENC28J60_ECON1) & NET_ENC28J60_ECON1_RXEN; + // ESTAT.BUFFER rised on TX or RX error + // I think the test of this register is not necessary - EIR.RXERIF state checking may be enough + uint8_t stateEstatBuffer = eth.readreg((uint8_t) NET_ENC28J60_ESTAT) & NET_ENC28J60_ESTAT_BUFFER; + // EIR.RXERIF set on RX error + uint8_t stateEirRxerif = eth.readreg((uint8_t) NET_ENC28J60_EIR) & NET_ENC28J60_EIR_RXERIF; + if (!stateEconRxen || (stateEstatBuffer && stateEirRxerif)) { + DEBUG_PRINTLN(F("ENC28J60 FAILED - REBOOT!")) + return false; + } + return true; +} +#endif +#endif /** Perform NTP sync */ bool perform_ntp_sync() { From ad2c2a36aac789614518192c42947eb7a1283984 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 26 May 2022 23:44:54 +0200 Subject: [PATCH 009/281] - static ip init fix - MaxCurrent Check, Limit Power to MAX_CURRENT (3010mA) --- OpenSprinkler.cpp | 3 ++- defines.h | 4 +++- main.cpp | 6 ++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index fbe7c1eeb..a79fb5c46 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -534,7 +534,8 @@ byte OpenSprinkler::start_ether() { lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); // todo: lwip add timeout - while (!eth.connected()) { + int n = iopts[IOPT_USE_DHCP]?30:2; + while (!eth.connected() && n-- >0) { DEBUG_PRINT("."); delay(1000); } diff --git a/defines.h b/defines.h index 6de9e5e49..e607de076 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 105 // Firmware minor version +#define OS_FW_MINOR 106 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 @@ -110,6 +110,8 @@ typedef unsigned long ulong; #define REBOOT_CAUSE_PROGRAM 11 #define REBOOT_CAUSE_POWERON 99 +/** Too much current */ +#define MAX_CURRENT 3010 //Max mA /** WiFi defines */ #define WIFI_MODE_AP 0xA9 diff --git a/main.cpp b/main.cpp index 11f841782..1ab9a1053 100644 --- a/main.cpp +++ b/main.cpp @@ -903,6 +903,12 @@ void do_loop() uint16_t curr = os.read_current(); os.lcd.print(curr); os.lcd.print(F(" mA")); + + //Stop all stations if power usage is higher than MAX_CURRENT: + if (curr >= MAX_CURRENT) { + reset_all_stations_immediate(); + write_log(LOGDATA_CURRENT, curr_time); + } } } #endif From 337ddf08e57bfdc067fc06c7f4912a0dc6ef63df Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 7 Jun 2022 22:34:53 +0200 Subject: [PATCH 010/281] When using a Fixed IP don't call dhcp_renew --- main.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 1ab9a1053..1218d6fca 100644 --- a/main.cpp +++ b/main.cpp @@ -952,17 +952,27 @@ void do_loop() if(curr_time > dhcp_timeout) { if (useEth) { netif* intf = (netif*) eth.getNetIf(); - dhcp_renew(intf); + if (os.iopts[IOPT_USE_DHCP]) + dhcp_renew(intf); - if (dhcp_timeout > 0 && !check_enc28j60()) { + if (dhcp_timeout > 0 && !check_enc28j60()) { //ENC28J60 REGISTER CHECK!! + DEBUG_PRINT("Reconnect"); eth.resetEther(); + + // todo: lwip add timeout + int n = os.iopts[IOPT_USE_DHCP]?30:2; + while (!eth.connected() && n-- >0) { + DEBUG_PRINT("."); + delay(1000); + } + if (!eth.connected()) { os.nvdata.reboot_cause = REBOOT_CAUSE_NETWORK_FAIL; os.status.safe_reboot = 1; } } } - else if (WiFi.status() == WL_CONNECTED && os.get_wifi_mode()==WIFI_MODE_STA) { + else if (os.iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED && os.get_wifi_mode()==WIFI_MODE_STA) { netif* intf = eagle_lwip_getif(STATION_IF); dhcp_renew(intf); } From 37d7bebef0b0c95cdc84f6cc3441ee58b78d5280 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 24 Jul 2022 23:54:16 +0200 Subject: [PATCH 011/281] =?UTF-8?q?2.2.0.108=20Beseitigt=20eine=20m=C3=B6g?= =?UTF-8?q?liche=20Bootloop-Situation=20beim=20=C3=84ndern=20der=20Konfigu?= =?UTF-8?q?ration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenSprinkler.cpp | 24 ++++++++++++++++-------- defines.h | 2 +- main.cpp | 4 ++++ make.lin302 | 2 +- utils.cpp | 9 ++++++++- 5 files changed, 30 insertions(+), 11 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index a79fb5c46..873c7d437 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -529,6 +529,14 @@ byte OpenSprinkler::start_ether() { eth.setDefault(); load_hardware_mac((uint8_t*)tmp_buffer, true); + if (!iopts[IOPT_USE_DHCP]) { + IPAddress staticip(iopts+IOPT_STATIC_IP1); + IPAddress gateway(iopts+IOPT_GATEWAY_IP1); + IPAddress dns(iopts+IOPT_DNS_IP1); + IPAddress subn(iopts+IOPT_SUBNET_MASK1); + eth.config(staticip, gateway, subn, dns); + } + if(!eth.begin((uint8_t*)tmp_buffer)) return 0; lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); @@ -552,12 +560,6 @@ byte OpenSprinkler::start_ether() { memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); // todo: lwip need dns ip memcpy(iopts+IOPT_SUBNET_MASK1, &(eth.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); - eth.config(staticip, gateway, subn, dns); } return 1; @@ -925,6 +927,7 @@ void OpenSprinkler::begin() { baseline_current = 0; #endif + lcd_start(); @@ -2002,23 +2005,28 @@ void OpenSprinkler::factory_reset() { /** Setup function for options */ void OpenSprinkler::options_setup() { + DEBUG_PRINT("option_setup..."); // Check reset conditions: - if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<219 || // fw version is invalid (<219) - !file_exists(DONE_FILENAME)) { // done file doesn't exist + if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<220 || // fw version is invalid (<219) + !file_exists(DONE_FILENAME)) { // done file doesn't exist factory_reset(); } else { iopts_load(); + nvdata_load(); + last_reboot_cause = nvdata.reboot_cause; nvdata.reboot_cause = REBOOT_CAUSE_POWERON; nvdata_save(); + #if defined(ESP8266) wifi_ssid = sopt_load(SOPT_STA_SSID); wifi_pass = sopt_load(SOPT_STA_PASS); #endif + attribs_load(); } diff --git a/defines.h b/defines.h index e607de076..33ddf3563 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 106 // Firmware minor version +#define OS_FW_MINOR 108 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 diff --git a/main.cpp b/main.cpp index 1218d6fca..249eced4e 100644 --- a/main.cpp +++ b/main.cpp @@ -365,11 +365,15 @@ ISR(WDT_vect) void do_setup() { initialiseEpoch(); // initialize time reference for millis() and micros() + DEBUG_PRINT("do_setup1..."); os.begin(); // OpenSprinkler init + DEBUG_PRINT("do_setup2..."); os.options_setup(); // Setup options + DEBUG_PRINT("do_setup3..."); pd.init(); // ProgramData init + DEBUG_PRINT("do_setup4..."); if (os.start_network()) { // initialize network DEBUG_PRINTLN("network established."); os.status.network_fails = 0; diff --git a/make.lin302 b/make.lin302 index 680556453..a12e3967d 100644 --- a/make.lin302 +++ b/make.lin302 @@ -5,7 +5,7 @@ LIBS = . \ $(ESP_LIBS)/ESP8266WiFi \ $(ESP_LIBS)/ESP8266WebServer \ $(ESP_LIBS)/ESP8266mDNS \ - /data/libs/LittleFS \ + $(ESO_LIBS)/LittleFS \ /data/libs/lwIP_enc28j60 \ /data/libs/SSD1306 \ /data/libs/rc-switch \ diff --git a/utils.cpp b/utils.cpp index 2bed17789..e63f9f0ed 100644 --- a/utils.cpp +++ b/utils.cpp @@ -301,7 +301,14 @@ void remove_file(const char *fn) { bool file_exists(const char *fn) { #if defined(ESP8266) - return LittleFS.exists(fn); + //return LittleFS.exists(fn); + File f = LittleFS.open(fn, "r"); + if (f) { + f.close(); + return true; + } + return false; + #elif defined(ARDUINO) From 6bf080219c8ccb8e0cc4857d10740f487e91f196 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 30 Jul 2022 23:27:50 +0200 Subject: [PATCH 012/281] =?UTF-8?q?Version=20.109=20mit=20ge=C3=A4nderten?= =?UTF-8?q?=20Build=20Flags=20und=20zus=C3=A4tzlicher=20Wiederholung=20bei?= =?UTF-8?q?m=20fehlgeschlagenen=20Formatieren=20f=C3=BCr=20LittleFS?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- OpenSprinkler.cpp | 8 +++++++- defines.h | 2 +- main.cpp | 13 +++++++++---- make.lin302 | 3 ++- platformio.ini | 4 +++- 5 files changed, 22 insertions(+), 8 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 873c7d437..74e50461b 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1934,7 +1934,13 @@ void OpenSprinkler::pre_factory_reset() { #if defined(ESP8266) lcd_print_line_clear_pgm(PSTR("Wiping flash.."), 0); lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); - LittleFS.format(); + while (!LittleFS.format()) { + DEBUG_PRINT("ERROR FORMATTING LitteFS"); + delay(100); + LittleFS.end(); + LittleFS.begin(); + delay(100); + } #else // remove 'done' file as an indicator for reset // todo os2.3 and ospi: delete log files and/or wipe SD card diff --git a/defines.h b/defines.h index 33ddf3563..fac96058e 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 108 // Firmware minor version +#define OS_FW_MINOR 109 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 diff --git a/main.cpp b/main.cpp index 249eced4e..f51a27537 100644 --- a/main.cpp +++ b/main.cpp @@ -307,14 +307,20 @@ void do_setup() { DEBUG_PRINTLN(F("started")); os.begin(); // OpenSprinkler init + DEBUG_PRINTLN("do_setup1..."); os.options_setup(); // Setup options + DEBUG_PRINTLN("do_setup2..."); pd.init(); // ProgramData init + DEBUG_PRINTLN("do_setup3..."); // set time using RTC if it exists if(RTC.exists()) setTime(RTC.get()); + DEBUG_PRINTLN("do_setup4..."); + os.lcd_print_time(os.now_tz()); // display time to LCD os.powerup_lasttime = os.now_tz(); + DEBUG_PRINTLN("do_setup5..."); #if !defined(ESP8266) // enable WDT @@ -342,6 +348,8 @@ void do_setup() { os.apply_all_station_bits(); // reset station bits os.button_timeout = LCD_BACKLIGHT_TIMEOUT; + DEBUG_PRINTLN("do_setup6..."); + } // Arduino software reset function @@ -365,15 +373,11 @@ ISR(WDT_vect) void do_setup() { initialiseEpoch(); // initialize time reference for millis() and micros() - DEBUG_PRINT("do_setup1..."); os.begin(); // OpenSprinkler init - DEBUG_PRINT("do_setup2..."); os.options_setup(); // Setup options - DEBUG_PRINT("do_setup3..."); pd.init(); // ProgramData init - DEBUG_PRINT("do_setup4..."); if (os.start_network()) { // initialize network DEBUG_PRINTLN("network established."); os.status.network_fails = 0; @@ -1765,6 +1769,7 @@ void check_network() { if (os.status.req_network) { os.status.req_network = 0; + DEBUG_PRINT(F("check_network begin")); // change LCD icon to indicate it's checking network if (!ui_state) { os.lcd.setCursor(LCD_CURSOR_NETWORK, 1); diff --git a/make.lin302 b/make.lin302 index a12e3967d..74e99206c 100644 --- a/make.lin302 +++ b/make.lin302 @@ -5,7 +5,7 @@ LIBS = . \ $(ESP_LIBS)/ESP8266WiFi \ $(ESP_LIBS)/ESP8266WebServer \ $(ESP_LIBS)/ESP8266mDNS \ - $(ESO_LIBS)/LittleFS \ + $(ESP_LIBS)/LittleFS \ /data/libs/lwIP_enc28j60 \ /data/libs/SSD1306 \ /data/libs/rc-switch \ @@ -28,6 +28,7 @@ FLASH_SPEED = 80 F_CPU = 160000000L BOARD = generic +board_build.filesystem = littlefs EXCLUDE_DIRS = ./build-1284 diff --git a/platformio.ini b/platformio.ini index 44aa7fc3d..ba014ec6e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,15 +18,17 @@ include_dir = . [env:d1_mini_lite] platform = espressif8266 board = d1_mini_lite +board_build.filesystem = littlefs framework = arduino lib_ldf_mode = deep lib_deps = - EthernetENC=https://github.com/jandrassy/EthernetENC/archive/refs/tags/2.0.1.zip sui77/rc-switch @ ^2.6.3 https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 ; ignore html2raw.cpp source file for firmware compilation (external helper program) src_filter = +<*> - +build_flags = + -Teagle.flash.4m3m.ld [env:sanguino_atmega1284p] platform = atmelavr From 35f0e020302ab88c55d002b5b93434c01870c4aa Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 7 Sep 2022 22:20:16 +0200 Subject: [PATCH 013/281] Version 111 WiFi.setAutoReconnect(true); Weather-check 1min before start Mqtt: delay before reconnect --- defines.h | 2 +- main.cpp | 14 ++++++++++++++ mqtt.cpp | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/defines.h b/defines.h index fac96058e..b9f6c1763 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 109 // Firmware minor version +#define OS_FW_MINOR 111 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 diff --git a/main.cpp b/main.cpp index f51a27537..6451f9831 100644 --- a/main.cpp +++ b/main.cpp @@ -453,6 +453,7 @@ void do_loop() start_server_ap(); os.state = OS_STATE_CONNECTED; connecting_timeout = 0; + WiFi.setAutoReconnect(true); } else { led_blink_ms = LED_SLOW_BLINK; start_network_sta(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); @@ -463,6 +464,7 @@ void do_loop() os.lcd.print(F("Connecting to...")); os.lcd.setCursor(0, 2); os.lcd.print(os.wifi_ssid); + WiFi.setAutoReconnect(true); } break; @@ -674,8 +676,20 @@ void do_loop() if (curr_minute != last_minute) { last_minute = curr_minute; // check through all programs + + // Check weather 60s before program start: for(pid=0; pid os.checkwt_lasttime + 30*60)) { + os.checkwt_lasttime = 0; + os.checkwt_success_lasttime = 0; + check_weather(); + } + break; + } + if(prog.check_match(curr_time)) { // program match found // check and process special program command diff --git a/mqtt.cpp b/mqtt.cpp index bd5a4e5c6..e4f3e238e 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -259,6 +259,7 @@ int OSMqtt::_connect(void) { state = mqtt_client->connect(_id, NULL, NULL, MQTT_AVAILABILITY_TOPIC, 0, true, MQTT_OFFLINE_PAYLOAD); if(state) break; tries++; + delay(10); } while(tries Date: Sat, 15 Oct 2022 13:44:16 +0200 Subject: [PATCH 014/281] Update 112 Fixed Weather Service (incomplete) RS485 Sensor-Support --- OpenSprinkler.cpp | 40 ++++ OpenSprinkler.h | 6 +- defines.h | 4 +- main.cpp | 10 +- opensprinkler_server.cpp | 177 ++++++++++++++++++ sensors.cpp | 386 +++++++++++++++++++++++++++++++++++++++ sensors.h | 100 ++++++++++ utils.cpp | 33 ++++ utils.h | 2 + 9 files changed, 752 insertions(+), 6 deletions(-) create mode 100644 sensors.cpp create mode 100644 sensors.h diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 74e50461b..ac37c0f03 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -627,6 +627,7 @@ void OpenSprinkler::reboot_dev(uint8_t cause) { /** Initialize network with the given mac address and http port */ byte OpenSprinkler::start_network() { +#if defined(ESP8266) unsigned int port = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; #if defined(DEMO) port = 80; @@ -635,6 +636,9 @@ byte OpenSprinkler::start_network() { m_server = new EthernetServer(port); return m_server->begin(); +#else + return 1; +#endif } bool OpenSprinkler::network_connected(void) { @@ -644,6 +648,7 @@ bool OpenSprinkler::network_connected(void) { // 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(byte* mac, bool wired) { +#if defined(ESP8266) const char * if_names[] = { "eth0", "eth1", "wlan0", "wlan1" }; struct ifreq ifr; int fd; @@ -669,6 +674,7 @@ bool OpenSprinkler::load_hardware_mac(byte* mac, bool wired) { } } close(fd); +#endif return true; } @@ -1835,6 +1841,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* } #endif*/ +/*#if defined(ARDUINO) while(client->available()==0) { if(millis()>stoptime) { client->stop(); @@ -1846,7 +1853,40 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* if(nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE; client->read((uint8_t*)ether_buffer, nbytes); } +#endif*/ + + + int pos = 0; +#if defined(ARDUINO) + while(pos < ETHER_BUFFER_SIZE) { + int nbytes = client->available(); + if(nbytes>0) { + if(pos+nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE-pos; // cannot read more than buffer size + client->read((uint8_t*)ether_buffer+pos, nbytes); + pos+=nbytes; + } else if (!client->connected()) + break; + yield(); + if(millis()>stoptime) { + DEBUG_PRINTLN(F("host timeout occured")); + //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far + break; + } + } +#else + while(client->connected()) { + int len=client->read((uint8_t *)ether_buffer+pos, ETHER_BUFFER_SIZE); + if (len<=0) continue; + pos+=len; + if(millis()>stoptime) { + DEBUG_PRINTLN(F("host timeout occured")); + //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far + break; + } + } +#endif + ether_buffer[pos]=0; // properly end buffer with 0 client->stop(); if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN; if(callback) callback(ether_buffer); diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 252330a5e..1814fae10 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -266,9 +266,9 @@ class OpenSprinkler { static void clear_all_station_bits(); // clear all station bits static void apply_all_station_bits(); // apply all station bits (activate/deactive values) - static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); - static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); - static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); + static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); // -- LCD functions #if defined(ARDUINO) // LCD functions for Arduino #if defined(ESP8266) diff --git a/defines.h b/defines.h index b9f6c1763..97440ffcc 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 111 // Firmware minor version +#define OS_FW_MINOR 112 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 @@ -57,6 +57,8 @@ 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 SENSOR_FILENAME "sensor.dat" // analog sensor filename +#define SENSORLOG_FILENAME "sensorlog.dat" // analog sensor log filename /** Station macro defines */ #define STN_TYPE_STANDARD 0x00 diff --git a/main.cpp b/main.cpp index 6451f9831..bb9dd7bc6 100644 --- a/main.cpp +++ b/main.cpp @@ -969,6 +969,7 @@ void do_loop() } } +#if defined(ESP8266) // dhcp and hw check: static unsigned long dhcp_timeout = 0; if(curr_time > dhcp_timeout) { @@ -1000,7 +1001,7 @@ void do_loop() } dhcp_timeout = curr_time + DHCP_CHECKLEASE_INTERVAL; } - +#endif // perform ntp sync // instead of using curr_time, which may change due to NTP sync itself @@ -1234,7 +1235,12 @@ void schedule_all_stations(ulong curr_time) { // otherwise, concurrent scheduling q->st = con_start_time; // stagger concurrent stations by 1 second - con_start_time++; + //con_start_time++; + // stagger concurrent stations by delay time + if (station_delay>0) + con_start_time += station_delay; + else + con_start_time++; } /*DEBUG_PRINT("["); DEBUG_PRINT(sid); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 23cd670cf..02aa9a93a 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -26,6 +26,7 @@ #include "opensprinkler_server.h" #include "weather.h" #include "mqtt.h" +#include "sensors.h" // External variables defined in main ion file #if defined(ARDUINO) @@ -1065,7 +1066,11 @@ void server_json_controller_main() { #endif byte mac[6] = {0}; +#if defined(ESP8266) os.load_hardware_mac(mac, useEth); +#else + os.load_hardware_mac(mac, false; +#endif bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"wtdata\":$S,\"wterr\":$D,"), @@ -1739,6 +1744,168 @@ void server_delete_log() { handle_return(HTML_SUCCESS); } +/** + * Modus RS485 Sensor config + * {"nr":1,"type":1,"name":"myname","ip":123456789,"port":3000,"id":1,"ri":1000,"enable":1,"log":1} + */ +void server_sensor_config() { +#if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type + + if (type == 0) { + sensor_delete(nr); + handle_return(HTML_SUCCESS); + return; + } + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) + handle_return(HTML_DATA_MISSING); + char name[30]; + strlcpy(name, tmp_buffer, sizeof(name)-1); // Sensor nr + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ip"), true)) + handle_return(HTML_DATA_MISSING); + uint32_t ip = strtoul(tmp_buffer, NULL, 0); // Sensor ip + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("port"), true)) + handle_return(HTML_DATA_MISSING); + uint port = strtoul(tmp_buffer, NULL, 0); // Sensor port + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("id"), true)) + handle_return(HTML_DATA_MISSING); + uint id = strtoul(tmp_buffer, NULL, 0); // Sensor modbus id + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ri"), true)) + handle_return(HTML_DATA_MISSING); + uint ri = strtoul(tmp_buffer, NULL, 0); // Read Interval (s) + + uint enable = 1; + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enable"), true)) + enable = strtoul(tmp_buffer, NULL, 0); // 1=enable/0=disable + + uint log = 1; + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) + log = strtoul(tmp_buffer, NULL, 0); // 1=logging enabled/0=logging disabled + + sensor_define(nr, name, type, ip, port, id, ri, enable, log); + + handle_return(HTML_SUCCESS); +} + +/** + * @brief return a 485 sensor + * + */ +void server_sensor_get() { +#if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + Sensor_t *sensor = sensor_by_nr(nr); + if (!sensor) + { + server_send_result(255); + return; + } + + print_json_header(false); + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$F,\"enable\":$D,\"log\":$D}"), + sensor->nr, + sensor->type, + sensor->name, + sensor->ip, + sensor->port, + sensor->id, + sensor->read_interval, + sensor->last_native_data, + sensor->last_data, + sensor->enable, + sensor->log); + + handle_return(HTML_OK); +} + +/** + * @brief Lists all sensors + * + */ +void server_sensor_list() { +#if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + + Sensor_t *sensor = sensors; + print_json_header(true); + bfill.emit_p(PSTR("{\"count\":$D},"), sensor_count()); + + bfill.emit_p(PSTR("[")); + while (sensor) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$F,\"enable\":$D,\"log\":$D}"), + sensor->nr, + sensor->type, + sensor->name, + sensor->ip, + sensor->port, + sensor->id, + sensor->read_interval, + sensor->last_native_data, + sensor->last_data, + sensor->enable, + sensor->log); + sensor = sensor ->next; + if (sensor) + bfill.emit_p(PSTR(",")); + send_packet(); + } + bfill.emit_p(PSTR("]")); + bfill.emit_p(PSTR("}")); + + handle_return(HTML_OK); +} + +void serverlog_list() { + #if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + + + print_json_header(true); +} + +void serverlog_clear() { + #if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + + handle_return(HTML_OK); +} + /** Output all JSON data, including jc, jp, jo, js, jn */ void server_json_all() { #if defined(ESP8266) @@ -1820,6 +1987,11 @@ const char _url_keys[] PROGMEM = "su" "cu" "ja" + "sc" + "sl" + "sg" + "so" + "sn" #if defined(ARDUINO) "db" #endif @@ -1848,6 +2020,11 @@ URLHandler urls[] = { server_view_scripturl, // su server_change_scripturl,// cu server_json_all, // ja + server_sensor_config, // sc + server_sensor_list, // sl + server_sensor_get, // sg + serverlog_list, // so + serverlog_clear, // sn #if defined(ARDUINO) server_json_debug, // db #endif diff --git a/sensors.cpp b/sensors.cpp new file mode 100644 index 000000000..61143b12f --- /dev/null +++ b/sensors.cpp @@ -0,0 +1,386 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * Utility functions + * Sep 2022 @ OpenSprinklerShop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#include +#include "defines.h" +#include "utils.h" +#include "sensors.h" +#include "OpenSprinkler.h" + +uint16_t CRC16 (const uint8_t *nData, uint16_t wLength) +{ + static const uint16_t wCRCTable[] = { + 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, + 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, + 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, + 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, + 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, + 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, + 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, + 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, + 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, + 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, + 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, + 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, + 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, + 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, + 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, + 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, + 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, + 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, + 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, + 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, + 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, + 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, + 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, + 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, + 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, + 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, + 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, + 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, + 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, + 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, + 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, + 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 }; + + uint8_t nTemp; + uint16_t wCRCWord = 0xFFFF; + + while (wLength--) + { + nTemp = *nData++ ^ wCRCWord; + wCRCWord >>= 8; + wCRCWord ^= wCRCTable[(nTemp & 0xFF)]; + } + return wCRCWord; +} // End: CRC16 + +/** + * @brief delete a sensor + * + * @param nr + */ +void sensor_delete(uint nr) { + + Sensor_t *sensor = sensors; + Sensor_t *last = NULL; + while (sensor) { + if (sensor->nr == nr) { + if (last == NULL) + sensors = sensor->next; + else + last->next = sensor->next; + delete sensor; + return; + } + last = sensor; + sensor = sensor->next; + } + + sensor_save(); +} + +/** + * @brief define or insert a sensor + * + * @param nr > 0 + * @param type > 0 add/modify, 0=delete + * @param ip + * @param port + * @param id + */ +void sensor_define(uint nr, char *name, uint type, uint32_t ip, uint port, uint id, uint ri, byte enable, byte log) { + + if (nr == 0 || type == 0) + return; + + Sensor_t *sensor = sensors; + Sensor_t *last = NULL; + while (sensor) { + if (sensor->nr == nr) { //Modify existing + sensor->type = type; + strlcpy(sensor->name, name, sizeof(sensor->name)-1); + sensor->ip = ip; + sensor->port = port; + sensor->id = id; + sensor->read_interval = ri; + sensor->enable = enable; + sensor->log = log; + return; + } + + if (sensor->nr > nr) + break; + + last = sensor; + sensor = sensor->next; + } + + //Insert new + Sensor_t *new_sensor = new Sensor_t; + memset(new_sensor, 0, sizeof(Sensor_t)); + new_sensor->type = type; + strlcpy(sensor->name, name, sizeof(sensor->name)-1); + new_sensor->ip = ip; + new_sensor->port = port; + new_sensor->id = id; + new_sensor->read_interval = ri; + new_sensor->enable = enable; + new_sensor->log = log; + if (last == NULL) { + sensors = new_sensor; + new_sensor->next = sensor; + } else { + new_sensor->next = last->next; + last->next = new_sensor; + } + + sensor_save(); +} + +/** + * @brief initial load stored sensor definitions + * + */ +void sensor_load() { + + sensors = NULL; + if (!file_exists(SENSOR_FILENAME)) + return; + + ulong pos = 0; + Sensor_t *last = NULL; + while (true) { + Sensor_t *sensor = new Sensor_t; + memset(sensor, 0, sizeof(Sensor_t)); + file_read_block (SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); + if (sensor->nr == 0) { + delete sensor; + return; + } + if (last == NULL) { + sensors = sensor; + last = sensor; + } + else { + last->next = sensor; + last = sensor; + } + sensor->next = NULL; + pos += SENSOR_STORE_SIZE; + } + +} + +/** + * @brief Store sensor data + * + */ +void sensor_save() { + if (sensors == NULL && file_exists(SENSOR_FILENAME)) + remove_file(SENSOR_FILENAME); + + ulong pos = 0; + Sensor_t *sensor = sensors; + while (sensor) { + file_write_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); + sensor = sensor->next; + pos += SENSOR_STORE_SIZE; + } + +} + +uint sensor_count() { + Sensor_t *sensor = sensors; + uint count = 0; + while (sensor) { + count++; + sensor = sensor->next; + } + return count; +} + +Sensor_t *sensor_by_nr(uint nr) { + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->nr == nr) + return sensor; + sensor = sensor->next; + } + return NULL; +} + +Sensor_t *sensor_by_idx(uint idx) { + Sensor_t *sensor = sensors; + uint count = 0; + while (sensor) { + if (count == idx) + return sensor; + count++; + sensor = sensor->next; + } + return NULL; +} + +void sensorlog_add(SensorLog_t *sensorlog) { + file_write_block(SENSORLOG_FILENAME, sensorlog, file_size(SENSORLOG_FILENAME), SENSORLOG_STORE_SIZE); +} + +uint64_t sensorlog_size() { + return file_size(SENSORLOG_FILENAME) / SENSORLOG_STORE_SIZE; +} + +void sensorlog_clear_all() { + remove_file(SENSORLOG_FILENAME); +} + +SensorLog_t *sensorlog_load(ulong idx) { + SensorLog_t *sensorlog = new SensorLog_t; + file_read_block(SENSORLOG_FILENAME, sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); + return sensorlog; +} + +int read_sensor(Sensor_t *sensor, uint32_t *result) { + +if (!sensor || !sensor->enable) + return HTTP_RQT_NOT_RECEIVED; + +sensor->last_read = millis(); + +#if defined(ARDUINO) + + Client *client; + #if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; + #else + EthernetClient etherClient; + client = ðerClient; + #endif + +#else + EthernetClient etherClient; + EthernetClient *client = ðerClient; +#endif + + IPAddress _ip(sensor->ip); + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + + if(!client->connect(ip, sensor->port)) { + DEBUG_PRINT(F("Cannot connect to ")); + DEBUG_PRINT(_ip[0]); DEBUG_PRINT("."); + DEBUG_PRINT(_ip[1]); DEBUG_PRINT("."); + DEBUG_PRINT(_ip[2]); DEBUG_PRINT("."); + DEBUG_PRINT(_ip[3]); DEBUG_PRINT(":"); + DEBUG_PRINTLN(sensor->port); + client->stop(); + return HTTP_RQT_CONNECT_ERR; + } + + uint8_t buffer[50]; + int len = 0; + boolean addCrc16 = true; + switch(sensor->type) + { + case SENSOR_SMT100_MODBUS_RTU_MOIS: + buffer[0] = sensor->id; //Modbus ID + buffer[1] = 0x03; //Read Holding Registers + buffer[2] = 0x00; + buffer[3] = 0x01; //soil moisture is at address 0x0001 + buffer[4] = 0x00; + buffer[5] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) + len = 6; + break; + + case SENSOR_SMT100_MODBUS_RTU_TEMP: + buffer[0] = sensor->id; + buffer[1] = 0x03; //Read Holding Registers + buffer[2] = 0x00; + buffer[3] = 0x00; //temperature is at address 0x0000 + buffer[4] = 0x00; + buffer[5] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) + len = 6; + break; + + default: + client->stop(); + return HTTP_RQT_NOT_RECEIVED; + } + if (addCrc16) { + uint16_t crc = CRC16(buffer, len); + buffer[len+0] = (0xFF00&crc)>>8; + buffer[len+1] = (0x00FF&crc); + len += 2; + } + + client->write(buffer, len); + client->flush(); + + //Read result: + switch(sensor->type) + { + case SENSOR_SMT100_MODBUS_RTU_MOIS: + case SENSOR_SMT100_MODBUS_RTU_TEMP: + int n = client->read(buffer, 7); + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + if (n != 7) { + DEBUG_PRINT(F(" returned ")); + DEBUG_PRINT(n); + DEBUG_PRINT(F(" bytes??")); + return n == 0 ? HTTP_RQT_EMPTY_RETURN:HTTP_RQT_TIMEOUT; + } + if ((buffer[0] != sensor->id && sensor->id != 253)) { //253 is broadcast + DEBUG_PRINT(F(" returned sensor id ")); + DEBUG_PRINT((int)buffer[0]); + return HTTP_RQT_NOT_RECEIVED; + } + uint16_t crc = (buffer[5] << 8) | buffer[6]; + if (crc != CRC16(buffer, 5)) { + DEBUG_PRINT(F(" crc error!")); + return HTTP_RQT_NOT_RECEIVED; + } + + //Valid result: + sensor->last_native_data = (buffer[3] << 8) | buffer[4]; + DEBUG_PRINT(F(" native: ")); + DEBUG_PRINT(sensor->last_native_data); + + //Convert to readable value: + switch(sensor->type) + { + case SENSOR_SMT100_MODBUS_RTU_MOIS: //Equation: soil moisture [vol.%]= (16Bit_soil_moisture_value / 100) + sensor->last_data = (sensor->last_native_data / 100); + DEBUG_PRINT(F(" soil moisture %: ")); + break; + case SENSOR_SMT100_MODBUS_RTU_TEMP: //Equation: temperature [°C]= (16Bit_temperature_value / 100)-100 + sensor->last_data = (sensor->last_native_data / 100) - 100; + DEBUG_PRINT(F(" temperature °C: ")); + break; + } + DEBUG_PRINT(sensor->last_data); + return HTTP_RQT_SUCCESS; + } + + return HTTP_RQT_NOT_RECEIVED; +} + diff --git a/sensors.h b/sensors.h new file mode 100644 index 000000000..b4f7ac47e --- /dev/null +++ b/sensors.h @@ -0,0 +1,100 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * sensors header file + * Sep 2022 @ OpenSprinklerShop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _SENSORS_H +#define _SENSORS_H + +#if defined(ARDUINO) + #include +#else // headers for RPI/BBB + #include + #include + #include + +#endif +#include "defines.h" +#include "utils.h" +#include + +//Sensor types: +#define SENSOR_NONE 0 //None or deleted sensor +#define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode +#define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode + +#define SENSOR_GROUP_MIN 1000 //Sensor group with min value +#define SENSOR_GROUP_MAX 1001 //Sensor group with max value +#define SENSOR_GROUP_AVG 1002 //Sensor group with avg value +#define SENSOR_GROUP_SUM 1003 //Sensor group with sum value + + +//Definition of a sensor +typedef struct Sensor { + uint nr; //1..n sensor-nr, 0=deleted + char name[30]; + uint type; //1..n type definition, 0=deleted + uint32_t ip; + uint port; + uint id; //modbus id + uint read_interval; // seconds + uint32_t last_native_data; // last native sensor data + double last_data; // last converted sensor data + byte enable:1; + byte log:1; + uint32_t undef; //for later + //unstored + ulong last_read; //millis + Sensor *next; +} Sensor_t; +#define SENSOR_STORE_SIZE (sizeof(Sensor_t)-sizeof(Sensor_t*)-sizeof(ulong)) + +//Definition of a log data +typedef struct SensorLog { + uint nr; //sensor-nr + ulong time; + uint32_t native_data; + double data; +} SensorLog_t; +#define SENSORLOG_STORE_SIZE (sizeof(SensorLog_t)) + +//All sensors: +static Sensor_t *sensors = NULL; + +//Utils: +uint16_t CRC16 (const uint8_t *nData, uint16_t wLength); + +//Sensor API functions: +void sensor_delete(uint nr); +void sensor_define(uint nr, char *name, uint type, uint32_t ip, uint port, uint id, uint ri, byte enabled, byte log); +void sensor_load(); +void sensor_save(); +uint sensor_count(); +Sensor_t *sensor_by_nr(uint nr); +Sensor_t *sensor_by_idx(uint idx); + +int read_sensor(Sensor_t *sensor); //sensor value goes to last_native_data/last_data + +//Sensorlog API functions: +void sensorlog_add(SensorLog *sensorlog); +void sensorlog_clear_all(); +SensorLog_t *sensorlog_load(ulong pos); + + +#endif // _SENSORS_H diff --git a/utils.cpp b/utils.cpp index e63f9f0ed..0824e13cf 100644 --- a/utils.cpp +++ b/utils.cpp @@ -326,6 +326,39 @@ bool file_exists(const char *fn) { } // file functions +ulong file_size(const char *fn) { + ulong size = 0; +#if defined(ESP8266) + + // do not use File.readBytes or readBytesUntil because it's very slow + File f = LittleFS.open(fn, "r"); + if(f) { + size = f.size(); + f.close(); + } + +#elif defined(ARDUINO) + + sd.chdir("/"); + SdFile file; + if(file.open(fn, O_READ)) { + size = file.size(); + file.close(); + } + +#else + + FILE *fp = fopen(get_filename_fullpath(fn), "rb"); + if(fp) { + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fclose(fp); + } + +#endif + return size; +} + 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 0e5784cac..0c6c80526 100644 --- a/utils.h +++ b/utils.h @@ -40,6 +40,8 @@ void remove_file(const char *fname); bool file_exists(const char *fname); +ulong file_size(const char *fn); + 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 8f2cb95b08df612e6536f5fba519e08e6de36200 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 15 Oct 2022 23:33:44 +0200 Subject: [PATCH 015/281] sensor groups added --- opensprinkler_server.cpp | 12 ++++++--- sensors.cpp | 57 +++++++++++++++++++++++++++++++++++++--- sensors.h | 5 +++- 3 files changed, 66 insertions(+), 8 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 02aa9a93a..5f1d3f515 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1769,6 +1769,10 @@ void server_sensor_config() { return; } + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("group"), true)) + handle_return(HTML_DATA_MISSING); + uint group = strtoul(tmp_buffer, NULL, 0); // Sensor group + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) handle_return(HTML_DATA_MISSING); char name[30]; @@ -1798,7 +1802,7 @@ void server_sensor_config() { if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) log = strtoul(tmp_buffer, NULL, 0); // 1=logging enabled/0=logging disabled - sensor_define(nr, name, type, ip, port, id, ri, enable, log); + sensor_define(nr, name, type, group, ip, port, id, ri, enable, log); handle_return(HTML_SUCCESS); } @@ -1826,9 +1830,10 @@ void server_sensor_get() { } print_json_header(false); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$F,\"enable\":$D,\"log\":$D}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$F,\"enable\":$D,\"log\":$D}"), sensor->nr, sensor->type, + sensor->group, sensor->name, sensor->ip, sensor->port, @@ -1860,9 +1865,10 @@ void server_sensor_list() { bfill.emit_p(PSTR("[")); while (sensor) { - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$F,\"enable\":$D,\"log\":$D}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$F,\"enable\":$D,\"log\":$D}"), sensor->nr, sensor->type, + sensor->group, sensor->name, sensor->ip, sensor->port, diff --git a/sensors.cpp b/sensors.cpp index 61143b12f..a167cd7db 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -103,11 +103,12 @@ void sensor_delete(uint nr) { * * @param nr > 0 * @param type > 0 add/modify, 0=delete + * @param group group assignment * @param ip * @param port * @param id */ -void sensor_define(uint nr, char *name, uint type, uint32_t ip, uint port, uint id, uint ri, byte enable, byte log) { +void sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, byte enable, byte log) { if (nr == 0 || type == 0) return; @@ -117,6 +118,7 @@ void sensor_define(uint nr, char *name, uint type, uint32_t ip, uint port, uint while (sensor) { if (sensor->nr == nr) { //Modify existing sensor->type = type; + sensor->group = group; strlcpy(sensor->name, name, sizeof(sensor->name)-1); sensor->ip = ip; sensor->port = port; @@ -138,6 +140,7 @@ void sensor_define(uint nr, char *name, uint type, uint32_t ip, uint port, uint Sensor_t *new_sensor = new Sensor_t; memset(new_sensor, 0, sizeof(Sensor_t)); new_sensor->type = type; + new_sensor->group = group; strlcpy(sensor->name, name, sizeof(sensor->name)-1); new_sensor->ip = ip; new_sensor->port = port; @@ -260,10 +263,10 @@ SensorLog_t *sensorlog_load(ulong idx) { int read_sensor(Sensor_t *sensor, uint32_t *result) { -if (!sensor || !sensor->enable) - return HTTP_RQT_NOT_RECEIVED; + if (!sensor || !sensor->enable) + return HTTP_RQT_NOT_RECEIVED; -sensor->last_read = millis(); + sensor->last_read = millis(); #if defined(ARDUINO) @@ -384,3 +387,49 @@ sensor->last_read = millis(); return HTTP_RQT_NOT_RECEIVED; } +void sensor_update_groups() { + Sensor_t *sensor = sensors; + + while (sensor) { + switch(sensor->type) { + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: { + int nr = sensor->nr; + Sensor_t *group = sensors; + double value = 0; + int n = 0; + while (group) { + if (group->nr != nr && group->group == nr && group->enable) { + switch(sensor->type) { + case SENSOR_GROUP_MIN: + if (n++ == 0) value = group->last_data; + else if (group->last_data < value) value = group->last_data; + break; + case SENSOR_GROUP_MAX: + if (n++ == 0) value = group->last_data; + else if (group->last_data > value) value = group->last_data; + break; + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + n++; + value += group->last_data; + break; + } + } + group = group->next; + } + if (sensor->type == SENSOR_GROUP_AVG && n>0) { + value = value / n; + } + sensor->last_data = value; + sensor->last_native_data = 0; + sensor->last_read = millis(); + break; + } + } + + sensor = sensor->next; + } +} diff --git a/sensors.h b/sensors.h index b4f7ac47e..df585a73a 100644 --- a/sensors.h +++ b/sensors.h @@ -50,6 +50,7 @@ typedef struct Sensor { uint nr; //1..n sensor-nr, 0=deleted char name[30]; uint type; //1..n type definition, 0=deleted + uint group; // group assignment,0=no group uint32_t ip; uint port; uint id; //modbus id @@ -82,10 +83,12 @@ uint16_t CRC16 (const uint8_t *nData, uint16_t wLength); //Sensor API functions: void sensor_delete(uint nr); -void sensor_define(uint nr, char *name, uint type, uint32_t ip, uint port, uint id, uint ri, byte enabled, byte log); +void sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, byte enabled, byte log); void sensor_load(); void sensor_save(); uint sensor_count(); +void sensor_update_groups(); + Sensor_t *sensor_by_nr(uint nr); Sensor_t *sensor_by_idx(uint idx); From a2e5757a002d0effdc9b5f88d3a1bd2a9e42c106 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 29 Oct 2022 01:05:55 +0200 Subject: [PATCH 016/281] Sensor Api working --- OpenSprinkler.cpp | 2 +- defines.h | 1 + main.cpp | 7 +- opensprinkler_server.cpp | 252 +++++++++++++++++++++++++++++++---- opensprinkler_server.h | 7 +- sensors.cpp | 274 +++++++++++++++++++++++++++------------ sensors.h | 32 ++++- utils.cpp | 3 +- 8 files changed, 463 insertions(+), 115 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index ac37c0f03..ab04e23ad 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1866,7 +1866,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* pos+=nbytes; } else if (!client->connected()) break; - yield(); + delay(5); if(millis()>stoptime) { DEBUG_PRINTLN(F("host timeout occured")); diff --git a/defines.h b/defines.h index 97440ffcc..25bd29193 100644 --- a/defines.h +++ b/defines.h @@ -58,6 +58,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 SENSOR_FILENAME "sensor.dat" // analog sensor filename +#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename #define SENSORLOG_FILENAME "sensorlog.dat" // analog sensor log filename /** Station macro defines */ diff --git a/main.cpp b/main.cpp index bb9dd7bc6..a17caf19a 100644 --- a/main.cpp +++ b/main.cpp @@ -28,6 +28,7 @@ #include "weather.h" #include "opensprinkler_server.h" #include "mqtt.h" +#include "sensors.h" #if defined(ARDUINO) @@ -349,7 +350,7 @@ void do_setup() { os.button_timeout = LCD_BACKLIGHT_TIMEOUT; DEBUG_PRINTLN("do_setup6..."); - + sensor_load(); } // Arduino software reset function @@ -389,6 +390,7 @@ void do_setup() { os.mqtt.init(); os.status.req_mqtt_restart = true; + sensor_load(); } #endif @@ -912,6 +914,9 @@ void do_loop() // activate/deactivate valves os.apply_all_station_bits(); + // read analog sensors + read_all_sensors(); + #if defined(ARDUINO) // process LCD display if (!ui_state) { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 5f1d3f515..66c85bdee 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1745,8 +1745,9 @@ void server_delete_log() { } /** + * sc * Modus RS485 Sensor config - * {"nr":1,"type":1,"name":"myname","ip":123456789,"port":3000,"id":1,"ri":1000,"enable":1,"log":1} + * {"nr":1,"type":1,"group":0,"name":"myname","ip":123456789,"port":3000,"id":1,"ri":1000,"enable":1,"log":1} */ void server_sensor_config() { #if defined(ESP8266) @@ -1755,6 +1756,9 @@ void server_sensor_config() { #else char *p = get_buffer; #endif + + DEBUG_PRINTLN("server_sensor_config"); + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr @@ -1766,7 +1770,6 @@ void server_sensor_config() { if (type == 0) { sensor_delete(nr); handle_return(HTML_SUCCESS); - return; } if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("group"), true)) @@ -1802,12 +1805,41 @@ void server_sensor_config() { if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) log = strtoul(tmp_buffer, NULL, 0); // 1=logging enabled/0=logging disabled - sensor_define(nr, name, type, group, ip, port, id, ri, enable, log); + int res = sensor_define(nr, name, type, group, ip, port, id, ri, enable, log); + res = res >= HTTP_RQT_SUCCESS?HTML_SUCCESS:(256+res); + handle_return(res); +} - handle_return(HTML_SUCCESS); +/** + * sa + * Modus RS485 Sensor set address help function + * {"nr":1,"id":1} + */ +void server_set_sensor_address() { +#if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN("server_set_sensor_address"); + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("id"), true)) + handle_return(HTML_DATA_MISSING); + uint id = strtoul(tmp_buffer, NULL, 0); // Sensor modbus id + + int res = set_sensor_address(sensor_by_nr(nr), id); + res = res >= HTTP_RQT_SUCCESS?HTML_SUCCESS:(256+res); + handle_return(res); } /** + * sg * @brief return a 485 sensor * */ @@ -1818,6 +1850,9 @@ void server_sensor_get() { #else char *p = get_buffer; #endif + + DEBUG_PRINTLN("server_sensor_get"); + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr @@ -1829,8 +1864,14 @@ void server_sensor_get() { return; } +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else print_json_header(false); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$F,\"enable\":$D,\"log\":$D}"), +#endif + + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$E,\"enable\":$D,\"log\":$D,\"last\":$L}"), sensor->nr, sensor->type, sensor->group, @@ -1842,12 +1883,57 @@ void server_sensor_get() { sensor->last_native_data, sensor->last_data, sensor->enable, - sensor->log); + sensor->log, + sensor->last_read); handle_return(HTML_OK); } /** + * sr + * @brief read now and return status and last data + * + */ +void server_sensor_readnow() { + #if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN("server_sensor_readnow"); + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + Sensor_t *sensor = sensor_by_nr(nr); + if (!sensor) + { + server_send_result(255); + return; + } + + int status = read_sensor(sensor); + +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else + print_json_header(false); +#endif + + bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"lnd\":$L,\"ld\":$E}"), + sensor->nr, + status, + sensor->last_native_data, + sensor->last_data); + + handle_return(HTML_OK); +} +/** + * sl * @brief Lists all sensors * */ @@ -1859,13 +1945,24 @@ void server_sensor_list() { char *p = get_buffer; #endif - Sensor_t *sensor = sensors; - print_json_header(true); - bfill.emit_p(PSTR("{\"count\":$D},"), sensor_count()); + DEBUG_PRINTLN("server_sensor_list"); + DEBUG_PRINT("server_count: "); + DEBUG_PRINTLN(sensor_count()); - bfill.emit_p(PSTR("[")); - while (sensor) { - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$F,\"enable\":$D,\"log\":$D}"), +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else + print_json_header(false); +#endif + + int count = sensor_count(); + bfill.emit_p(PSTR("{\"count\":$D,"), count); + + bfill.emit_p(PSTR("\"sensors\":[")); + for (int i = 0; i < count; i++) { + Sensor_t *sensor = sensor_by_idx(i); + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$E,\"enable\":$D,\"log\":$D,\"last\":$L}"), sensor->nr, sensor->type, sensor->group, @@ -1877,11 +1974,15 @@ void server_sensor_list() { sensor->last_native_data, sensor->last_data, sensor->enable, - sensor->log); - sensor = sensor ->next; - if (sensor) + sensor->log, + sensor->last_read); + if (i < count-1) bfill.emit_p(PSTR(",")); - send_packet(); + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(); + } } bfill.emit_p(PSTR("]")); bfill.emit_p(PSTR("}")); @@ -1889,26 +1990,127 @@ void server_sensor_list() { handle_return(HTML_OK); } -void serverlog_list() { - #if defined(ESP8266) +/** + * so + * @brief output sensorlog + * + */ +void server_sensorlog_list() { +#if defined(ESP8266) char *p = NULL; if(!process_password()) return; #else char *p = get_buffer; #endif + ulong log_size = sensorlog_size(); + + DEBUG_PRINTLN("server_sensorlog_list"); + + //start / max: + ulong startAt = 0; + ulong maxResults = log_size; + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("start"), true)) // Log start + startAt = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) // Log Lines count + maxResults = strtoul(tmp_buffer, NULL, 0); + + //Filters: + uint nr = 0; + uint type = 0; + ulong after = 0; + ulong before = 0; + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr + nr = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) // Filter log for sensor-type + type = strtoul(tmp_buffer, NULL, 0); - print_json_header(true); + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // Filter time after + after = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // Filter time before + before = strtoul(tmp_buffer, NULL, 0); + +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else + print_json_header(false); +#endif + bfill.emit_p(PSTR("[")); + + ulong count = 0; + SensorLog_t sensorlog; + Sensor_t *sensor = NULL; + for (ulong idx = startAt; idx < log_size; idx++) { + sensorlog_load(idx, &sensorlog); + + if (nr && sensorlog.nr != nr) + continue; + + if (after && sensorlog.time <= after) + continue; + + if (before && sensorlog.time >= before) + continue; + + if (!sensor || sensor->nr != sensorlog.nr) + sensor = sensor_by_nr(sensorlog.nr); + uint sensor_type = sensor?sensor->type:0; + if (type && sensor_type != type) + continue; + + if (count > 0) + bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"native_data\":$L,\"data\":$E}"), + sensorlog.nr, //sensor-nr + sensor_type, //sensor-type + sensorlog.time, //timestamp + sensorlog.native_data, //native data + sensorlog.data); + + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(); + } + if (++count >= maxResults) + break; + } + bfill.emit_p(PSTR("]")); + + handle_return(HTML_OK); } -void serverlog_clear() { - #if defined(ESP8266) +/** + * sn + * @brief Delete/Clear Sensor log + * + */ +void server_sensorlog_clear() { +#if defined(ESP8266) char *p = NULL; if(!process_password()) return; #else char *p = get_buffer; #endif + DEBUG_PRINTLN("server_sensorlog_clear"); + +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else + print_json_header(false); +#endif + + ulong log_size = sensorlog_size(); + + sensorlog_clear_all(); + + bfill.emit_p(PSTR("{\"deleted\":$L}"), log_size); + handle_return(HTML_OK); } @@ -1996,6 +2198,8 @@ const char _url_keys[] PROGMEM = "sc" "sl" "sg" + "sr" + "sa" "so" "sn" #if defined(ARDUINO) @@ -2029,8 +2233,10 @@ URLHandler urls[] = { server_sensor_config, // sc server_sensor_list, // sl server_sensor_get, // sg - serverlog_list, // so - serverlog_clear, // sn + server_sensor_readnow, // sr + server_set_sensor_address, // sa + server_sensorlog_list, // so + server_sensorlog_clear, // sn #if defined(ARDUINO) server_json_debug, // db #endif diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 42873900b..feacd868d 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -54,6 +54,9 @@ class BufferFiller { //wtoa(va_arg(ap, uint16_t), (char*) ptr); itoa(va_arg(ap, int), (char*) ptr, 10); // ray break; + case 'E': //Double + sprintf((char*) ptr, "%f", va_arg(ap, double)); + break; case 'L': //ltoa(va_arg(ap, long), (char*) ptr, 10); ultoa(va_arg(ap, long), (char*) ptr, 10); // ray @@ -65,8 +68,8 @@ class BufferFiller { 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; @@ -77,8 +80,8 @@ class BufferFiller { 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; diff --git a/sensors.cpp b/sensors.cpp index a167cd7db..31295fddf 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -23,54 +23,25 @@ #include "defines.h" #include "utils.h" #include "sensors.h" +#include "program.h" #include "OpenSprinkler.h" -uint16_t CRC16 (const uint8_t *nData, uint16_t wLength) -{ - static const uint16_t wCRCTable[] = { - 0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, - 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500, 0xC5C1, 0xC481, 0x0440, - 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, - 0x0A00, 0xCAC1, 0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, - 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81, 0x1A40, - 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, - 0x1400, 0xD4C1, 0xD581, 0x1540, 0xD701, 0x17C0, 0x1680, 0xD641, - 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, - 0xF001, 0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, - 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0, 0x3480, 0xF441, - 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, - 0xFA01, 0x3AC0, 0x3B80, 0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, - 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41, - 0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, - 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700, 0xE7C1, 0xE681, 0x2640, - 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, - 0xA001, 0x60C0, 0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, - 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480, 0xA441, - 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, - 0xAA01, 0x6AC0, 0x6B80, 0xAB41, 0x6900, 0xA9C1, 0xA881, 0x6840, - 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, - 0xBE01, 0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, - 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1, 0xB681, 0x7640, - 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, - 0x5000, 0x90C1, 0x9181, 0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, - 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440, - 0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, - 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901, 0x59C0, 0x5880, 0x9841, - 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, - 0x4E00, 0x8EC1, 0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, - 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680, 0x8641, - 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040 }; - - uint8_t nTemp; - uint16_t wCRCWord = 0xFFFF; - - while (wLength--) - { - nTemp = *nData++ ^ wCRCWord; - wCRCWord >>= 8; - wCRCWord ^= wCRCTable[(nTemp & 0xFF)]; - } - return wCRCWord; + uint16_t CRC16 (byte buf[], int len) { + uint16_t crc = 0xFFFF; + + for (int pos = 0; pos < len; pos++) { + crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } + else // Else LSB is not set + crc >>= 1; // Just shift right + } + } + // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes) + return crc; } // End: CRC16 /** @@ -78,7 +49,7 @@ uint16_t CRC16 (const uint8_t *nData, uint16_t wLength) * * @param nr */ -void sensor_delete(uint nr) { +int sensor_delete(uint nr) { Sensor_t *sensor = sensors; Sensor_t *last = NULL; @@ -89,13 +60,13 @@ void sensor_delete(uint nr) { else last->next = sensor->next; delete sensor; - return; + sensor_save(); + return HTTP_RQT_SUCCESS; } last = sensor; sensor = sensor->next; } - - sensor_save(); + return HTTP_RQT_NOT_RECEIVED; } /** @@ -108,10 +79,10 @@ void sensor_delete(uint nr) { * @param port * @param id */ -void sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, byte enable, byte log) { +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, byte enable, byte log) { if (nr == 0 || type == 0) - return; + return HTTP_RQT_NOT_RECEIVED; Sensor_t *sensor = sensors; Sensor_t *last = NULL; @@ -126,7 +97,8 @@ void sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint sensor->read_interval = ri; sensor->enable = enable; sensor->log = log; - return; + sensor_save(); + return HTTP_RQT_SUCCESS; } if (sensor->nr > nr) @@ -135,13 +107,13 @@ void sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint last = sensor; sensor = sensor->next; } - //Insert new Sensor_t *new_sensor = new Sensor_t; memset(new_sensor, 0, sizeof(Sensor_t)); + new_sensor->nr = nr; new_sensor->type = type; new_sensor->group = group; - strlcpy(sensor->name, name, sizeof(sensor->name)-1); + strlcpy(new_sensor->name, name, sizeof(new_sensor->name)-1); new_sensor->ip = ip; new_sensor->port = port; new_sensor->id = id; @@ -155,8 +127,8 @@ void sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->next = last->next; last->next = new_sensor; } - sensor_save(); + return HTTP_RQT_SUCCESS; } /** @@ -164,33 +136,33 @@ void sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint * */ void sensor_load() { - + DEBUG_PRINTLN("sensor_load"); sensors = NULL; if (!file_exists(SENSOR_FILENAME)) return; + DEBUG_PRINTLN("sensor_load2"); + ulong pos = 0; Sensor_t *last = NULL; while (true) { Sensor_t *sensor = new Sensor_t; memset(sensor, 0, sizeof(Sensor_t)); file_read_block (SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); - if (sensor->nr == 0) { + if (!sensor->nr || !sensor->type) { + DEBUG_PRINTLN("sensor_load3"); + delete sensor; - return; - } - if (last == NULL) { - sensors = sensor; - last = sensor; - } - else { - last->next = sensor; - last = sensor; + break; } + if (!last) sensors = sensor; + else last->next = sensor; + last = sensor; sensor->next = NULL; pos += SENSOR_STORE_SIZE; + DEBUG_PRINTLN("sensor_load4"); } - + DEBUG_PRINTLN("sensor_load5"); } /** @@ -198,17 +170,22 @@ void sensor_load() { * */ void sensor_save() { - if (sensors == NULL && file_exists(SENSOR_FILENAME)) + DEBUG_PRINTLN("sensor_save1"); + if (!sensors && file_exists(SENSOR_FILENAME)) remove_file(SENSOR_FILENAME); + DEBUG_PRINTLN("sensor_save2"); + ulong pos = 0; Sensor_t *sensor = sensors; while (sensor) { + DEBUG_PRINTLN("sensor_save3"); file_write_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); sensor = sensor->next; pos += SENSOR_STORE_SIZE; + DEBUG_PRINTLN("sensor_save4"); } - + DEBUG_PRINTLN("sensor_save5"); } uint sensor_count() { @@ -247,7 +224,19 @@ void sensorlog_add(SensorLog_t *sensorlog) { file_write_block(SENSORLOG_FILENAME, sensorlog, file_size(SENSORLOG_FILENAME), SENSORLOG_STORE_SIZE); } -uint64_t sensorlog_size() { +void sensorlog_add(Sensor_t *sensor, ulong time) { + if (sensor->data_ok) { + SensorLog_t sensorlog; + sensorlog.nr = sensor->nr; + sensorlog.time = time; + sensorlog.native_data = sensor->last_native_data; + sensorlog.data = sensor->last_data; + sensorlog_add(&sensorlog); + } +} + + +ulong sensorlog_size() { return file_size(SENSORLOG_FILENAME) / SENSORLOG_STORE_SIZE; } @@ -257,16 +246,40 @@ void sensorlog_clear_all() { SensorLog_t *sensorlog_load(ulong idx) { SensorLog_t *sensorlog = new SensorLog_t; + return sensorlog_load(idx, sensorlog); +} + +SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog) { file_read_block(SENSORLOG_FILENAME, sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); return sensorlog; } -int read_sensor(Sensor_t *sensor, uint32_t *result) { +void read_all_sensors() { + if (!sensors || os.status.network_fails>0 || os.iopts[IOPT_REMOTE_EXT_MODE]) return; + + ulong time = os.now_tz(); + Sensor_t *sensor = sensors; + + while (sensor) { + if (time >= sensor->last_read + sensor->read_interval) { + if (read_sensor(sensor) == HTTP_RQT_SUCCESS) { + sensorlog_add(sensor, time); + } + } + sensor = sensor->next; + } + sensor_update_groups(); +} + +int read_sensor(Sensor_t *sensor) { if (!sensor || !sensor->enable) return HTTP_RQT_NOT_RECEIVED; - sensor->last_read = millis(); + sensor->last_read = os.now_tz(); + + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); #if defined(ARDUINO) @@ -298,7 +311,7 @@ int read_sensor(Sensor_t *sensor, uint32_t *result) { return HTTP_RQT_CONNECT_ERR; } - uint8_t buffer[50]; + uint8_t buffer[10]; int len = 0; boolean addCrc16 = true; switch(sensor->type) @@ -329,13 +342,26 @@ int read_sensor(Sensor_t *sensor, uint32_t *result) { } if (addCrc16) { uint16_t crc = CRC16(buffer, len); - buffer[len+0] = (0xFF00&crc)>>8; - buffer[len+1] = (0x00FF&crc); + buffer[len+0] = (0x00FF&crc); + buffer[len+1] = (0xFF00&crc)>>8; len += 2; } client->write(buffer, len); client->flush(); + + while (true) { + if (client->available()) + break; + if (os.now_tz() > sensor->last_read+SENSOR_READ_TIMEOUT) { + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINT(F(" timeout read!")); + return HTTP_RQT_TIMEOUT; + } + delay(5); + } //Read result: switch(sensor->type) @@ -357,7 +383,7 @@ int read_sensor(Sensor_t *sensor, uint32_t *result) { DEBUG_PRINT((int)buffer[0]); return HTTP_RQT_NOT_RECEIVED; } - uint16_t crc = (buffer[5] << 8) | buffer[6]; + uint16_t crc = (buffer[6] << 8) | buffer[5]; if (crc != CRC16(buffer, 5)) { DEBUG_PRINT(F(" crc error!")); return HTTP_RQT_NOT_RECEIVED; @@ -372,15 +398,17 @@ int read_sensor(Sensor_t *sensor, uint32_t *result) { switch(sensor->type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: //Equation: soil moisture [vol.%]= (16Bit_soil_moisture_value / 100) - sensor->last_data = (sensor->last_native_data / 100); + sensor->last_data = ((double)sensor->last_native_data / 100); + sensor->data_ok = true; DEBUG_PRINT(F(" soil moisture %: ")); break; case SENSOR_SMT100_MODBUS_RTU_TEMP: //Equation: temperature [°C]= (16Bit_temperature_value / 100)-100 - sensor->last_data = (sensor->last_native_data / 100) - 100; + sensor->last_data = ((double)sensor->last_native_data / 100) - 100; + sensor->data_ok = true; DEBUG_PRINT(F(" temperature °C: ")); break; } - DEBUG_PRINT(sensor->last_data); + DEBUG_PRINTLN(sensor->last_data); return HTTP_RQT_SUCCESS; } @@ -401,7 +429,7 @@ void sensor_update_groups() { double value = 0; int n = 0; while (group) { - if (group->nr != nr && group->group == nr && group->enable) { + if (group->nr != nr && group->group == nr && group->enable && group->data_ok) { switch(sensor->type) { case SENSOR_GROUP_MIN: if (n++ == 0) value = group->last_data; @@ -425,7 +453,8 @@ void sensor_update_groups() { } sensor->last_data = value; sensor->last_native_data = 0; - sensor->last_read = millis(); + sensor->last_read = os.now_tz(); + sensor->data_ok = n>0; break; } } @@ -433,3 +462,84 @@ void sensor_update_groups() { sensor = sensor->next; } } + +int set_sensor_address(Sensor_t *sensor, byte new_address) { + if (!sensor) + return HTTP_RQT_NOT_RECEIVED; + + switch(sensor->type) + { + case SENSOR_SMT100_MODBUS_RTU_MOIS: + case SENSOR_SMT100_MODBUS_RTU_TEMP: + uint8_t buffer[10]; + int len = 8; + buffer[0] = sensor->id; + buffer[1] = 0x06; + buffer[2] = 0x00; + buffer[3] = 0x04; + buffer[4] = 0x00; + buffer[5] = new_address; + uint16_t crc = CRC16(buffer, 6); + buffer[6] = (0x00FF&crc); + buffer[7] = (0xFF00&crc)>>8; + len = 8; + +#if defined(ARDUINO) + Client *client; + #if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; + #else + EthernetClient etherClient; + client = ðerClient; + #endif +#else + EthernetClient etherClient; + EthernetClient *client = ðerClient; +#endif + + IPAddress _ip(sensor->ip); + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + + if(!client->connect(ip, sensor->port)) { + DEBUG_PRINT(F("Cannot connect to ")); + DEBUG_PRINT(_ip[0]); DEBUG_PRINT("."); + DEBUG_PRINT(_ip[1]); DEBUG_PRINT("."); + DEBUG_PRINT(_ip[2]); DEBUG_PRINT("."); + DEBUG_PRINT(_ip[3]); DEBUG_PRINT(":"); + DEBUG_PRINTLN(sensor->port); + client->stop(); + return HTTP_RQT_CONNECT_ERR; + } + + client->write(buffer, len); + client->flush(); + + //Read result: + int n = client->read(buffer, 8); + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + if (n != 8) { + DEBUG_PRINT(F(" returned ")); + DEBUG_PRINT(n); + DEBUG_PRINT(F(" bytes??")); + return n == 0 ? HTTP_RQT_EMPTY_RETURN:HTTP_RQT_TIMEOUT; + } + if ((buffer[0] != sensor->id && sensor->id != 253)) { //253 is broadcast + DEBUG_PRINT(F(" returned sensor id ")); + DEBUG_PRINT((int)buffer[0]); + return HTTP_RQT_NOT_RECEIVED; + } + crc = (buffer[6] | buffer[7] << 8); + if (crc != CRC16(buffer, 6)) { + DEBUG_PRINT(F(" crc error!")); + return HTTP_RQT_NOT_RECEIVED; + } + //Read OK: + sensor->id = new_address; + sensor_save(); + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} \ No newline at end of file diff --git a/sensors.h b/sensors.h index df585a73a..ad91c396b 100644 --- a/sensors.h +++ b/sensors.h @@ -44,6 +44,7 @@ #define SENSOR_GROUP_AVG 1002 //Sensor group with avg value #define SENSOR_GROUP_SUM 1003 //Sensor group with sum value +#define SENSOR_READ_TIMEOUT 3000 //ms //Definition of a sensor typedef struct Sensor { @@ -59,7 +60,8 @@ typedef struct Sensor { double last_data; // last converted sensor data byte enable:1; byte log:1; - uint32_t undef; //for later + byte data_ok:1; + byte undef[32]; //for later //unstored ulong last_read; //millis Sensor *next; @@ -75,29 +77,49 @@ typedef struct SensorLog { } SensorLog_t; #define SENSORLOG_STORE_SIZE (sizeof(SensorLog_t)) +//Sensor to program data +typedef struct ProgSensor { + uint sensor; + double offset; + double factor; + double divider; + byte undef[32]; //for later +} ProgSensor_t; + + //All sensors: static Sensor_t *sensors = NULL; +//Program sensor data +static ProgSensor_t *progSensor = NULL; + //Utils: -uint16_t CRC16 (const uint8_t *nData, uint16_t wLength); +uint16_t CRC16 (byte buf[], int len); //Sensor API functions: -void sensor_delete(uint nr); -void sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, byte enabled, byte log); +int sensor_delete(uint nr); +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, byte enabled, byte log); void sensor_load(); void sensor_save(); uint sensor_count(); void sensor_update_groups(); +void read_all_sensors(); + Sensor_t *sensor_by_nr(uint nr); Sensor_t *sensor_by_idx(uint idx); int read_sensor(Sensor_t *sensor); //sensor value goes to last_native_data/last_data //Sensorlog API functions: -void sensorlog_add(SensorLog *sensorlog); +void sensorlog_add(SensorLog_t *sensorlog); +void sensorlog_add(Sensor_t *sensor, ulong time); void sensorlog_clear_all(); SensorLog_t *sensorlog_load(ulong pos); +SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog); +ulong sensorlog_size(); +//Set Sensor Address +int set_sensor_address(Sensor_t *sensor, byte new_address); #endif // _SENSORS_H diff --git a/utils.cpp b/utils.cpp index 0824e13cf..070b3ae51 100644 --- a/utils.cpp +++ b/utils.cpp @@ -124,6 +124,7 @@ void initialiseEpoch() ulong millis (void) { struct timeval tv ; + uint64_t now ; gettimeofday (&tv, NULL) ; @@ -282,7 +283,7 @@ void read_from_file(const char *fn, char *data, ulong maxsize, ulong pos) { void remove_file(const char *fn) { #if defined(ESP8266) - if(!LittleFS.exists(fn)) return; + if(!file_exists(fn)) return; LittleFS.remove(fn); #elif defined(ARDUINO) From e404865c31daeb0b6cd542b4a49db792ebf49b2e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 2 Nov 2022 00:22:15 +0100 Subject: [PATCH 017/281] Water adjustments calculation added (incomplete...) --- sensors.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++++++++++-- sensors.h | 45 ++++++++++++--- 2 files changed, 186 insertions(+), 14 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 31295fddf..909841d82 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -120,12 +120,12 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->read_interval = ri; new_sensor->enable = enable; new_sensor->log = log; - if (last == NULL) { - sensors = new_sensor; - new_sensor->next = sensor; - } else { + if (last) { new_sensor->next = last->next; last->next = new_sensor; + } else { + new_sensor->next = sensors; + sensors = new_sensor; } sensor_save(); return HTTP_RQT_SUCCESS; @@ -542,4 +542,149 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { return HTTP_RQT_SUCCESS; } return HTTP_RQT_NOT_RECEIVED; -} \ No newline at end of file +} + +double calc_linear(ProgSensorAdjust_t *p, Sensor_t *sensor) { + +// min max factor1 factor2 +// 10..90 -> 5..1 factor1 > factor2 +// a b c d +// (b-sensorData) / (b-a) * (c-d) + d +// +// 10..90 -> 1..5 factor1 < factor2 +// a b c d +// (sensorData-a) / (b-a) * (d-c) + c + + double sensorData = sensor->last_data; + // Limit to min/max: + if (sensorData < p->min) sensorData = p->min; + if (sensorData > p->max) sensorData = p->max; + + //Calculate: + if (p->factor1 > p->factor2) { // invers scaling factor: + return (p->max - sensorData) / (p->max - p->min) * (p->factor1 - p->factor2) + p->factor2; + } else { // upscaling factor: + return (sensorData - p->min) / (p->max - p->min) * (p->factor2 - p->factor1) + p->factor1; + } +} + +double calc_digital_min(ProgSensorAdjust_t *p, Sensor_t *sensor) { + return sensor->last_data <= p->min? 1:0; +} + +double calc_digital_max(ProgSensorAdjust_t *p, Sensor_t *sensor) { + return sensor->last_data >= p->max? 1:0; +} + +/** + * @brief calculate adjustment + * + * @param prog + * @return double + */ +double calc_sensor_watering(uint prog) { + double result = 1; + ProgSensorAdjust_t *p = progSensorAdjusts; + + while (p) { + if (p->prog == prog) { + Sensor_t *sensor = sensor_by_nr(p->sensor); + if (sensor && sensor->enable && sensor->data_ok) { + + double res = 0; + switch(p->type) { + case PROG_NONE: res = 1; break; + case PROG_LINEAR: res = calc_linear(p, sensor); break; + case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor); break; + case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor); break; + default: res = 0; + } + + result = result * res; + } + } + + p = p->next; + } + + return result; +} + +int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max) { + ProgSensorAdjust_t *p = progSensorAdjusts; + + ProgSensorAdjust_t *last = NULL; + + while (p) { + if (p->nr == nr) { + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->factor1 = factor1; + p->factor2 = factor2; + p->min = min; + p->max = max; + prog_adjust_save(); + return HTTP_RQT_SUCCESS; + } + + if (p->nr > nr) + break; + + last = p; + p = p->next; + } + + p = new ProgSensorAdjust_t; + p->nr = nr; + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->factor1 = factor1; + p->factor2 = factor2; + p->min = min; + p->max = max; + if (last) { + p->next = last->next; + last->next = p; + } else { + p->next = progSensorAdjusts; + progSensorAdjusts = p; + } + + prog_adjust_save(); + return HTTP_RQT_SUCCESS; +} + +int prog_adjust_delete(uint nr) { + ProgSensorAdjust_t *p = progSensorAdjusts; + + ProgSensorAdjust_t *last = NULL; + + while (p) { + if (p->nr == nr) { + if (last) + last->next = p->next; + else + progSensorAdjusts = p->next; + delete p; + prog_adjust_save(); + return HTTP_RQT_SUCCESS; + } + last = p; + p = p->next; + } + return HTTP_RQT_NOT_RECEIVED; +} + +void prog_adjust_save() { + +} + +void prog_adjust_load() { + +} + +void prog_adjust_count() { + +} diff --git a/sensors.h b/sensors.h index ad91c396b..ca57e7fe2 100644 --- a/sensors.h +++ b/sensors.h @@ -78,20 +78,40 @@ typedef struct SensorLog { #define SENSORLOG_STORE_SIZE (sizeof(SensorLog_t)) //Sensor to program data -typedef struct ProgSensor { - uint sensor; - double offset; - double factor; - double divider; +//Adjustment is formula +// min max factor1 factor2 +// 10..90 -> 5..1 factor1 > factor2 +// a b c d +// (b-sensorData) / (b-a) * (c-d) + d +// +// 10..90 -> 1..5 factor1 < factor2 +// a b c d +// (sensorData-a) / (b-a) * (d-c) + c + +#define PROG_NONE 0 //No adjustment (delete) +#define PROG_LINEAR 1 //formula see above +#define PROG_DIGITAL_MIN 2 //1=under or equal min, 0=above +#define PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below + +typedef struct ProgSensorAdjust { + uint nr; + uint type; //PROG_XYZ type=0 -->delete + uint sensor; //sensor-nr + uint prog; //program-nr + double factor1; + double factor2; + double min; + double max; byte undef[32]; //for later -} ProgSensor_t; - + ProgSensorAdjust *next; +} ProgSensorAdjust_t; +#define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) //All sensors: static Sensor_t *sensors = NULL; //Program sensor data -static ProgSensor_t *progSensor = NULL; +static ProgSensorAdjust_t *progSensorAdjusts = NULL; //Utils: uint16_t CRC16 (byte buf[], int len); @@ -119,7 +139,14 @@ SensorLog_t *sensorlog_load(ulong pos); SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog); ulong sensorlog_size(); -//Set Sensor Address +//Set Sensor Address for SMT100: int set_sensor_address(Sensor_t *sensor, byte new_address); +//Calc watering adjustment: +int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max); +int prog_adjust_delete(uint nr); +void prog_adjust_save(); +void prog_adjust_load(); +void prog_adjust_count(); +double calc_sensor_watering(uint prog); #endif // _SENSORS_H From b932e97a99ad76af9ed26d450f827389789e8958 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 11 Nov 2022 13:06:23 +0100 Subject: [PATCH 018/281] Analog sensor board test commit --- main.cpp | 3 + make.lin302 | 1 + opensprinkler_server.cpp | 109 ++++++++++++++++++++++-- sensors.cpp | 174 +++++++++++++++++++++++++++++++++++++-- sensors.h | 36 +++++--- 5 files changed, 298 insertions(+), 25 deletions(-) diff --git a/main.cpp b/main.cpp index a17caf19a..93c471349 100644 --- a/main.cpp +++ b/main.cpp @@ -351,6 +351,7 @@ void do_setup() { os.button_timeout = LCD_BACKLIGHT_TIMEOUT; DEBUG_PRINTLN("do_setup6..."); sensor_load(); + prog_adjust_load(); } // Arduino software reset function @@ -717,6 +718,8 @@ void do_loop() // do not water water_time = 0; } + // Analog sensor water time adjustments: + water_time = (ulong)(water_time * calc_sensor_watering(pid)); if (water_time) { // check if water time is still valid diff --git a/make.lin302 b/make.lin302 index 74e99206c..1ba67b7de 100644 --- a/make.lin302 +++ b/make.lin302 @@ -10,6 +10,7 @@ LIBS = . \ /data/libs/SSD1306 \ /data/libs/rc-switch \ /data/libs/pubsubclient \ + /data/libs/ADS1015 \ ESP_ROOT = /data/esp8266_3.0.2/ ESPCORE_VERSION = 302 diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 66c85bdee..25285024e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1757,7 +1757,7 @@ void server_sensor_config() { char *p = get_buffer; #endif - DEBUG_PRINTLN("server_sensor_config"); + DEBUG_PRINTLN(PSTR("server_sensor_config")); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -1823,7 +1823,7 @@ void server_set_sensor_address() { char *p = get_buffer; #endif - DEBUG_PRINTLN("server_set_sensor_address"); + DEBUG_PRINTLN(PSTR("server_set_sensor_address")); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -1851,7 +1851,7 @@ void server_sensor_get() { char *p = get_buffer; #endif - DEBUG_PRINTLN("server_sensor_get"); + DEBUG_PRINTLN(PSTR("server_sensor_get")); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -1902,7 +1902,7 @@ void server_sensor_readnow() { char *p = get_buffer; #endif - DEBUG_PRINTLN("server_sensor_readnow"); + DEBUG_PRINTLN(PSTR("server_sensor_readnow")); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -1945,8 +1945,8 @@ void server_sensor_list() { char *p = get_buffer; #endif - DEBUG_PRINTLN("server_sensor_list"); - DEBUG_PRINT("server_count: "); + DEBUG_PRINTLN(PSTR("server_sensor_list")); + DEBUG_PRINT(PSTR("server_count: ")); DEBUG_PRINTLN(sensor_count()); #if defined(ESP8266) @@ -2004,7 +2004,7 @@ void server_sensorlog_list() { #endif ulong log_size = sensorlog_size(); - DEBUG_PRINTLN("server_sensorlog_list"); + DEBUG_PRINTLN(PSTR("server_sensorlog_list")); //start / max: ulong startAt = 0; @@ -2096,7 +2096,7 @@ void server_sensorlog_clear() { char *p = get_buffer; #endif - DEBUG_PRINTLN("server_sensorlog_clear"); + DEBUG_PRINTLN(PSTR("server_sensorlog_clear")); #if defined(ESP8266) rewind_ether_buffer(); @@ -2114,6 +2114,95 @@ void server_sensorlog_clear() { handle_return(HTML_OK); } +/** + * sb + * define a program adjustment +*/ +void server_sensorprog_config() { +#if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(PSTR("server_sensorprog_config")); + //uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Adjustment type + + if (type == 0) { + prog_adjust_delete(nr); + handle_return(HTML_SUCCESS); + } + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + handle_return(HTML_DATA_MISSING); + uint sensor = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + handle_return(HTML_DATA_MISSING); + uint prog = strtoul(tmp_buffer, NULL, 0); // Program nr + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) + handle_return(HTML_DATA_MISSING); + double factor1 = atof(tmp_buffer); // Factor 1 + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor2"), true)) + handle_return(HTML_DATA_MISSING); + double factor2 = atof(tmp_buffer); // Factor 2 + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) + handle_return(HTML_DATA_MISSING); + double min = atof(tmp_buffer); // Min value + + if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) + handle_return(HTML_DATA_MISSING); + double max = atof(tmp_buffer); // Max value + + int res = prog_adjust_define(nr, type, sensor, prog, factor1, factor2, min, max); + res = res >= HTTP_RQT_SUCCESS?HTML_SUCCESS:(256+res); + handle_return(res); +} + +/** + * sd + * Program calc + **/ +void server_sensorprog_calc() { +#if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(PSTR("server_sensorprog_calc")); + //uint nr or uint prog + + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { + uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + double adj = calc_sensor_watering_by_nr(nr); + bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); + handle_return(HTML_OK); + } + + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) { + uint prog = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + double adj = calc_sensor_watering(prog); + bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); + handle_return(HTML_OK); + } + + handle_return(HTML_DATA_MISSING); +} + /** Output all JSON data, including jc, jp, jo, js, jn */ void server_json_all() { #if defined(ESP8266) @@ -2202,6 +2291,8 @@ const char _url_keys[] PROGMEM = "sa" "so" "sn" + "sb" + "sd" #if defined(ARDUINO) "db" #endif @@ -2237,6 +2328,8 @@ URLHandler urls[] = { server_set_sensor_address, // sa server_sensorlog_list, // so server_sensorlog_clear, // sn + server_sensorprog_config, // sb + server_sensorprog_calc, // sd #if defined(ARDUINO) server_json_debug, // db #endif diff --git a/sensors.cpp b/sensors.cpp index 909841d82..5d4771010 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -271,16 +271,61 @@ void read_all_sensors() { sensor_update_groups(); } -int read_sensor(Sensor_t *sensor) { +/** + * Read ADS1015 sensors +*/ +int read_sensor_adc(Sensor_t *sensor) { + if (!sensor || !sensor->enable) return HTTP_RQT_NOT_RECEIVED; + + //Init + Detect: + ADCSensor_t *adc = adcSensors; + while (adc) { + if (adc->i2c == sensor->port) { //0x48 / 0x49 + break; + } + adc = adc->next; + } - if (!sensor || !sensor->enable) + if (!adc) { + adc = new ADCSensor_t; + adc->i2c = sensor->port; + adc->active = adc->adc.begin(adc->i2c); + adc->next = adcSensors; + adcSensors = adc; + } + + if (!adc->active) return HTTP_RQT_NOT_RECEIVED; - sensor->last_read = os.now_tz(); - DEBUG_PRINT(F("Reading sensor ")); - DEBUG_PRINTLN(sensor->name); + //Read values: + sensor->last_native_data = adc->adc.getSingleEnded(sensor->id); + sensor->last_data = adc->adc.getSingleEndedMillivolts(sensor->id); + sensor->data_ok = true; + + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINT(F(" returned ")); + DEBUG_PRINT(F(" native: ")); + DEBUG_PRINT(sensor->last_native_data); + DEBUG_PRINT(F(" effective: ")); + DEBUG_PRINTLN(sensor->last_data); + + return HTTP_RQT_SUCCESS; +} + +int read_sensor_ospi(Sensor_t *sensor) { + if (!sensor || !sensor->enable) return HTTP_RQT_NOT_RECEIVED; + + //currently not implemented + + return HTTP_RQT_SUCCESS; +} +/** + * Read ip connected sensors +*/ +int read_sensor_ip(Sensor_t *sensor) { #if defined(ARDUINO) Client *client; @@ -415,6 +460,39 @@ int read_sensor(Sensor_t *sensor) { return HTTP_RQT_NOT_RECEIVED; } +/** + * read a sensor +*/ +int read_sensor(Sensor_t *sensor) { + + if (!sensor || !sensor->enable) + return HTTP_RQT_NOT_RECEIVED; + + sensor->last_read = os.now_tz(); + + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); + + switch(sensor->type) + { + case SENSOR_SMT100_MODBUS_RTU_MOIS: + case SENSOR_SMT100_MODBUS_RTU_TEMP: + return read_sensor_ip(sensor); + + case SENSOR_ANALOG_EXTENSION_BOARD: + return read_sensor_adc(sensor); + + case SENSOR_OSPI_ANALOG_INPUTS: + return read_sensor_ospi(sensor); + + default: return HTTP_RQT_NOT_RECEIVED; + } +} + +/** + * @brief Update group values + * + */ void sensor_update_groups() { Sensor_t *sensor = sensors; @@ -610,6 +688,41 @@ double calc_sensor_watering(uint prog) { return result; } +/** + * @brief calculate adjustment + * + * @param nr + * @return double + */ +double calc_sensor_watering_by_nr(uint nr) { + double result = 1; + ProgSensorAdjust_t *p = progSensorAdjusts; + + while (p) { + if (p->nr == nr) { + Sensor_t *sensor = sensor_by_nr(p->sensor); + if (sensor && sensor->enable && sensor->data_ok) { + + double res = 0; + switch(p->type) { + case PROG_NONE: res = 1; break; + case PROG_LINEAR: res = calc_linear(p, sensor); break; + case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor); break; + case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor); break; + default: res = 0; + } + + result = result * res; + } + break; + } + + p = p->next; + } + + return result; +} + int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max) { ProgSensorAdjust_t *p = progSensorAdjusts; @@ -678,13 +791,60 @@ int prog_adjust_delete(uint nr) { } void prog_adjust_save() { + DEBUG_PRINTLN("sensor_save1"); + if (!progSensorAdjusts && file_exists(PROG_SENSOR_FILENAME)) + remove_file(PROG_SENSOR_FILENAME); + + DEBUG_PRINTLN("sensor_save2"); + ulong pos = 0; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + DEBUG_PRINTLN("sensor_save3"); + file_write_block(PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); + pa = pa->next; + pos += PROGSENSOR_STORE_SIZE; + DEBUG_PRINTLN("sensor_save4"); + } + DEBUG_PRINTLN("sensor_save5"); } void prog_adjust_load() { + DEBUG_PRINTLN("prog_adjust_load"); + progSensorAdjusts = NULL; + if (!file_exists(PROG_SENSOR_FILENAME)) + return; + + DEBUG_PRINTLN("prog_adjust_load2"); + ulong pos = 0; + ProgSensorAdjust_t *last = NULL; + while (true) { + ProgSensorAdjust_t *pa = new ProgSensorAdjust_t; + memset(pa, 0, sizeof(ProgSensorAdjust_t)); + file_read_block (PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); + if (!pa->nr || !pa->type) { + DEBUG_PRINTLN("prog_adjust_load3"); + + delete pa; + break; + } + if (!last) progSensorAdjusts = pa; + else last->next = pa; + last = pa; + pa->next = NULL; + pos += PROGSENSOR_STORE_SIZE; + DEBUG_PRINTLN("prog_adjust_load4"); + } + DEBUG_PRINTLN("prog_adjust_load5"); } -void prog_adjust_count() { - +uint prog_adjust_count() { + uint count = 0; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + count++; + pa = pa->next; + } + return count; } diff --git a/sensors.h b/sensors.h index ca57e7fe2..6f9b67020 100644 --- a/sensors.h +++ b/sensors.h @@ -33,11 +33,15 @@ #include "defines.h" #include "utils.h" #include +#include //Sensor types: #define SENSOR_NONE 0 //None or deleted sensor #define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode #define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode +#define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 +#define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input +//#define SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler #define SENSOR_GROUP_MIN 1000 //Sensor group with min value #define SENSOR_GROUP_MAX 1001 //Sensor group with max value @@ -48,14 +52,14 @@ //Definition of a sensor typedef struct Sensor { - uint nr; //1..n sensor-nr, 0=deleted - char name[30]; - uint type; //1..n type definition, 0=deleted + uint nr; // 1..n sensor-nr, 0=deleted + char name[30]; // name + uint type; // 1..n type definition, 0=deleted uint group; // group assignment,0=no group - uint32_t ip; - uint port; - uint id; //modbus id - uint read_interval; // seconds + uint32_t ip; // tcp-ip + uint port; // tcp-port / ADC: I2C Address 0x48/0x49 + uint id; // modbus id / ADC: channel + uint read_interval; // seconds uint32_t last_native_data; // last native sensor data double last_data; // last converted sensor data byte enable:1; @@ -94,10 +98,10 @@ typedef struct SensorLog { #define PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below typedef struct ProgSensorAdjust { - uint nr; + uint nr; //adjust-nr 1..x uint type; //PROG_XYZ type=0 -->delete uint sensor; //sensor-nr - uint prog; //program-nr + uint prog; //program-nr=pid double factor1; double factor2; double min; @@ -107,9 +111,19 @@ typedef struct ProgSensorAdjust { } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) +typedef struct ADCSensor { + uint i2c; + byte active; + ADS1015 adc; + ADCSensor *next; +} ADCSensor_t; + //All sensors: static Sensor_t *sensors = NULL; +//ADS1015 48+49: +static ADCSensor_t *adcSensors = NULL; + //Program sensor data static ProgSensorAdjust_t *progSensorAdjusts = NULL; @@ -147,6 +161,8 @@ int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, double factor int prog_adjust_delete(uint nr); void prog_adjust_save(); void prog_adjust_load(); -void prog_adjust_count(); +uint prog_adjust_count(); double calc_sensor_watering(uint prog); +double calc_sensor_watering_by_nr(uint nr); + #endif // _SENSORS_H From c2401b80ac06e9a77c2f22f268a24b996037b226 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 15 Nov 2022 16:48:06 +0100 Subject: [PATCH 019/281] Added ADS1015 support --- main.cpp | 13 ++----- make.lin302 | 3 +- opensprinkler_server.cpp | 34 ++++++++-------- sensors.cpp | 83 +++++++++++++++------------------------- sensors.h | 10 ----- 5 files changed, 53 insertions(+), 90 deletions(-) diff --git a/main.cpp b/main.cpp index 93c471349..4f24c4531 100644 --- a/main.cpp +++ b/main.cpp @@ -308,20 +308,15 @@ void do_setup() { DEBUG_PRINTLN(F("started")); os.begin(); // OpenSprinkler init - DEBUG_PRINTLN("do_setup1..."); os.options_setup(); // Setup options - DEBUG_PRINTLN("do_setup2..."); pd.init(); // ProgramData init - DEBUG_PRINTLN("do_setup3..."); // set time using RTC if it exists if(RTC.exists()) setTime(RTC.get()); - DEBUG_PRINTLN("do_setup4..."); os.lcd_print_time(os.now_tz()); // display time to LCD os.powerup_lasttime = os.now_tz(); - DEBUG_PRINTLN("do_setup5..."); #if !defined(ESP8266) // enable WDT @@ -349,7 +344,7 @@ void do_setup() { os.apply_all_station_bits(); // reset station bits os.button_timeout = LCD_BACKLIGHT_TIMEOUT; - DEBUG_PRINTLN("do_setup6..."); + sensor_load(); prog_adjust_load(); } @@ -381,10 +376,10 @@ void do_setup() { pd.init(); // ProgramData init if (os.start_network()) { // initialize network - DEBUG_PRINTLN("network established."); + DEBUG_PRINTLN(F("network established.")); os.status.network_fails = 0; } else { - DEBUG_PRINTLN("network failed."); + DEBUG_PRINTLN(F("network failed.")); os.status.network_fails = 1; } os.status.req_network = 0; @@ -987,7 +982,7 @@ void do_loop() dhcp_renew(intf); if (dhcp_timeout > 0 && !check_enc28j60()) { //ENC28J60 REGISTER CHECK!! - DEBUG_PRINT("Reconnect"); + DEBUG_PRINT(F("Reconnect")); eth.resetEther(); // todo: lwip add timeout diff --git a/make.lin302 b/make.lin302 index 1ba67b7de..200bb84e8 100644 --- a/make.lin302 +++ b/make.lin302 @@ -23,13 +23,14 @@ UPLOAD_VERB = -v # Uncomment the line below for OS3.0 revision 0: reset mode is ck # UPLOAD_RESET = ck -FLASH_DEF = 4M3M +FLASH_DEF = 4M2M FLASH_MODE = dio FLASH_SPEED = 80 F_CPU = 160000000L BOARD = generic board_build.filesystem = littlefs +FS_TYPE = littlefs EXCLUDE_DIRS = ./build-1284 diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 25285024e..4c7fa7ac9 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1757,7 +1757,7 @@ void server_sensor_config() { char *p = get_buffer; #endif - DEBUG_PRINTLN(PSTR("server_sensor_config")); + DEBUG_PRINTLN(F("server_sensor_config")); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -1823,7 +1823,7 @@ void server_set_sensor_address() { char *p = get_buffer; #endif - DEBUG_PRINTLN(PSTR("server_set_sensor_address")); + DEBUG_PRINTLN(F("server_set_sensor_address")); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -1851,7 +1851,7 @@ void server_sensor_get() { char *p = get_buffer; #endif - DEBUG_PRINTLN(PSTR("server_sensor_get")); + DEBUG_PRINTLN(F("server_sensor_get")); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -1902,7 +1902,7 @@ void server_sensor_readnow() { char *p = get_buffer; #endif - DEBUG_PRINTLN(PSTR("server_sensor_readnow")); + DEBUG_PRINTLN(F("server_sensor_readnow")); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -1945,8 +1945,8 @@ void server_sensor_list() { char *p = get_buffer; #endif - DEBUG_PRINTLN(PSTR("server_sensor_list")); - DEBUG_PRINT(PSTR("server_count: ")); + DEBUG_PRINTLN(F("server_sensor_list")); + DEBUG_PRINT(F("server_count: ")); DEBUG_PRINTLN(sensor_count()); #if defined(ESP8266) @@ -2004,7 +2004,7 @@ void server_sensorlog_list() { #endif ulong log_size = sensorlog_size(); - DEBUG_PRINTLN(PSTR("server_sensorlog_list")); + DEBUG_PRINTLN(F("server_sensorlog_list")); //start / max: ulong startAt = 0; @@ -2096,7 +2096,7 @@ void server_sensorlog_clear() { char *p = get_buffer; #endif - DEBUG_PRINTLN(PSTR("server_sensorlog_clear")); + DEBUG_PRINTLN(F("server_sensorlog_clear")); #if defined(ESP8266) rewind_ether_buffer(); @@ -2126,7 +2126,7 @@ void server_sensorprog_config() { char *p = get_buffer; #endif - DEBUG_PRINTLN(PSTR("server_sensorprog_config")); + DEBUG_PRINTLN(F("server_sensorprog_config")); //uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) @@ -2183,19 +2183,19 @@ void server_sensorprog_calc() { char *p = get_buffer; #endif - DEBUG_PRINTLN(PSTR("server_sensorprog_calc")); - //uint nr or uint prog + DEBUG_PRINTLN(F("server_sensorprog_calc")); + //uint nr or uint prog - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { - uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr - double adj = calc_sensor_watering_by_nr(nr); + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { + uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + double adj = calc_sensor_watering_by_nr(nr); bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); handle_return(HTML_OK); } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) { - uint prog = strtoul(tmp_buffer, NULL, 0); // Adjustment nr - double adj = calc_sensor_watering(prog); + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) { + uint prog = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + double adj = calc_sensor_watering(prog); bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); handle_return(HTML_OK); } diff --git a/sensors.cpp b/sensors.cpp index 5d4771010..eb1e430bb 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -22,9 +22,9 @@ #include #include "defines.h" #include "utils.h" -#include "sensors.h" #include "program.h" #include "OpenSprinkler.h" +#include "sensors.h" uint16_t CRC16 (byte buf[], int len) { uint16_t crc = 0xFFFF; @@ -136,13 +136,11 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint * */ void sensor_load() { - DEBUG_PRINTLN("sensor_load"); + DEBUG_PRINTLN(F("sensor_load")); sensors = NULL; if (!file_exists(SENSOR_FILENAME)) return; - DEBUG_PRINTLN("sensor_load2"); - ulong pos = 0; Sensor_t *last = NULL; while (true) { @@ -150,8 +148,6 @@ void sensor_load() { memset(sensor, 0, sizeof(Sensor_t)); file_read_block (SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); if (!sensor->nr || !sensor->type) { - DEBUG_PRINTLN("sensor_load3"); - delete sensor; break; } @@ -160,9 +156,7 @@ void sensor_load() { last = sensor; sensor->next = NULL; pos += SENSOR_STORE_SIZE; - DEBUG_PRINTLN("sensor_load4"); } - DEBUG_PRINTLN("sensor_load5"); } /** @@ -170,25 +164,21 @@ void sensor_load() { * */ void sensor_save() { - DEBUG_PRINTLN("sensor_save1"); + DEBUG_PRINTLN(F("sensor_save")); if (!sensors && file_exists(SENSOR_FILENAME)) remove_file(SENSOR_FILENAME); - DEBUG_PRINTLN("sensor_save2"); - ulong pos = 0; Sensor_t *sensor = sensors; while (sensor) { - DEBUG_PRINTLN("sensor_save3"); file_write_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); sensor = sensor->next; pos += SENSOR_STORE_SIZE; - DEBUG_PRINTLN("sensor_save4"); } - DEBUG_PRINTLN("sensor_save5"); } uint sensor_count() { + DEBUG_PRINTLN(F("sensor_count")); Sensor_t *sensor = sensors; uint count = 0; while (sensor) { @@ -199,6 +189,7 @@ uint sensor_count() { } Sensor_t *sensor_by_nr(uint nr) { + DEBUG_PRINTLN(F("sensor_by_nr")); Sensor_t *sensor = sensors; while (sensor) { if (sensor->nr == nr) @@ -209,6 +200,7 @@ Sensor_t *sensor_by_nr(uint nr) { } Sensor_t *sensor_by_idx(uint idx) { + DEBUG_PRINTLN(F("sensor_by_idx")); Sensor_t *sensor = sensors; uint count = 0; while (sensor) { @@ -221,6 +213,7 @@ Sensor_t *sensor_by_idx(uint idx) { } void sensorlog_add(SensorLog_t *sensorlog) { + DEBUG_PRINTLN(F("sensorlog_add")); file_write_block(SENSORLOG_FILENAME, sensorlog, file_size(SENSORLOG_FILENAME), SENSORLOG_STORE_SIZE); } @@ -237,10 +230,14 @@ void sensorlog_add(Sensor_t *sensor, ulong time) { ulong sensorlog_size() { - return file_size(SENSORLOG_FILENAME) / SENSORLOG_STORE_SIZE; + DEBUG_PRINT(F("sensorlog_size ")); + ulong size = file_size(SENSORLOG_FILENAME) / SENSORLOG_STORE_SIZE; + DEBUG_PRINTLN(size); + return size; } void sensorlog_clear_all() { + DEBUG_PRINTLN(F("sensorlog_clear_all")); remove_file(SENSORLOG_FILENAME); } @@ -250,11 +247,13 @@ SensorLog_t *sensorlog_load(ulong idx) { } SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog) { + DEBUG_PRINTLN(F("sensorlog_load")); file_read_block(SENSORLOG_FILENAME, sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); return sensorlog; } void read_all_sensors() { + DEBUG_PRINTLN(F("read_all_sensors")); if (!sensors || os.status.network_fails>0 || os.iopts[IOPT_REMOTE_EXT_MODE]) return; ulong time = os.now_tz(); @@ -275,46 +274,36 @@ void read_all_sensors() { * Read ADS1015 sensors */ int read_sensor_adc(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_adc")); if (!sensor || !sensor->enable) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: - ADCSensor_t *adc = adcSensors; - while (adc) { - if (adc->i2c == sensor->port) { //0x48 / 0x49 - break; - } - adc = adc->next; - } - - if (!adc) { - adc = new ADCSensor_t; - adc->i2c = sensor->port; - adc->active = adc->adc.begin(adc->i2c); - adc->next = adcSensors; - adcSensors = adc; - } - - if (!adc->active) + ADS1015 adc; + bool active = adc.begin(sensor->port); + if (active) + adc.setGain(ADS1015_CONFIG_PGA_1); + DEBUG_PRINT(F("adc sensor found=")); + DEBUG_PRINTLN(active); + + if (!active) return HTTP_RQT_NOT_RECEIVED; - //Read values: - sensor->last_native_data = adc->adc.getSingleEnded(sensor->id); - sensor->last_data = adc->adc.getSingleEndedMillivolts(sensor->id); + sensor->last_native_data = adc.getSingleEnded(sensor->id); + sensor->last_data = adc.getSingleEndedMillivolts(sensor->id); sensor->data_ok = true; - DEBUG_PRINT(F("Sensor ")); - DEBUG_PRINT(sensor->nr); - DEBUG_PRINT(F(" returned ")); - DEBUG_PRINT(F(" native: ")); + DEBUG_PRINT(F("adc sensor values: ")); DEBUG_PRINT(sensor->last_native_data); - DEBUG_PRINT(F(" effective: ")); + DEBUG_PRINT(","); DEBUG_PRINTLN(sensor->last_data); + return HTTP_RQT_SUCCESS; } int read_sensor_ospi(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_ospi")); if (!sensor || !sensor->enable) return HTTP_RQT_NOT_RECEIVED; //currently not implemented @@ -791,32 +780,24 @@ int prog_adjust_delete(uint nr) { } void prog_adjust_save() { - DEBUG_PRINTLN("sensor_save1"); if (!progSensorAdjusts && file_exists(PROG_SENSOR_FILENAME)) remove_file(PROG_SENSOR_FILENAME); - - DEBUG_PRINTLN("sensor_save2"); ulong pos = 0; ProgSensorAdjust_t *pa = progSensorAdjusts; while (pa) { - DEBUG_PRINTLN("sensor_save3"); file_write_block(PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); pa = pa->next; pos += PROGSENSOR_STORE_SIZE; - DEBUG_PRINTLN("sensor_save4"); } - DEBUG_PRINTLN("sensor_save5"); } void prog_adjust_load() { - DEBUG_PRINTLN("prog_adjust_load"); + DEBUG_PRINTLN(F("prog_adjust_load")); progSensorAdjusts = NULL; if (!file_exists(PROG_SENSOR_FILENAME)) return; - DEBUG_PRINTLN("prog_adjust_load2"); - ulong pos = 0; ProgSensorAdjust_t *last = NULL; while (true) { @@ -824,8 +805,6 @@ void prog_adjust_load() { memset(pa, 0, sizeof(ProgSensorAdjust_t)); file_read_block (PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); if (!pa->nr || !pa->type) { - DEBUG_PRINTLN("prog_adjust_load3"); - delete pa; break; } @@ -834,9 +813,7 @@ void prog_adjust_load() { last = pa; pa->next = NULL; pos += PROGSENSOR_STORE_SIZE; - DEBUG_PRINTLN("prog_adjust_load4"); } - DEBUG_PRINTLN("prog_adjust_load5"); } uint prog_adjust_count() { diff --git a/sensors.h b/sensors.h index 6f9b67020..c88fd928d 100644 --- a/sensors.h +++ b/sensors.h @@ -111,19 +111,9 @@ typedef struct ProgSensorAdjust { } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) -typedef struct ADCSensor { - uint i2c; - byte active; - ADS1015 adc; - ADCSensor *next; -} ADCSensor_t; - //All sensors: static Sensor_t *sensors = NULL; -//ADS1015 48+49: -static ADCSensor_t *adcSensors = NULL; - //Program sensor data static ProgSensorAdjust_t *progSensorAdjusts = NULL; From 9fa0931a447596033cc4b75544f1bfcf551720db Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 15 Nov 2022 23:52:16 +0100 Subject: [PATCH 020/281] Working plattformio.ini , new sensors: SMT50 Moisture+Temp, changed lnd to nativedata, changed ld to data --- OpenSprinkler.cpp | 1 + defines.h | 2 +- defines.h~ | 502 +++++++++++++++++++++++++++++++ main.cpp | 2 +- make.lin302 | 2 +- make.lin302m | 34 +++ make.lin302m~ | 34 +++ make.lin302~ | 36 +++ opensprinkler-git.code-workspace | 14 + opensprinkler_server.cpp | 18 +- platformio.ini | 19 +- sensors.cpp | 32 +- sensors.h | 16 +- 13 files changed, 667 insertions(+), 45 deletions(-) create mode 100644 defines.h~ create mode 100644 make.lin302m create mode 100644 make.lin302m~ create mode 100644 make.lin302~ create mode 100644 opensprinkler-git.code-workspace diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index ab04e23ad..33175b48c 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -25,6 +25,7 @@ #include "opensprinkler_server.h" #include "gpio.h" #include "testmode.h" +#include "images.h" /** Declare static data members */ OSMqtt OpenSprinkler::mqtt; diff --git a/defines.h b/defines.h index 25bd29193..7a4b30fde 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 112 // Firmware minor version +#define OS_FW_MINOR 113 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 diff --git a/defines.h~ b/defines.h~ new file mode 100644 index 000000000..fac96058e --- /dev/null +++ b/defines.h~ @@ -0,0 +1,502 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * OpenSprinkler macro defines and hardware pin assignments + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _DEFINES_H +#define _DEFINES_H + +#define ENABLE_DEBUG // enable serial debug + +typedef unsigned char byte; +typedef unsigned long ulong; + +#define TMP_BUFFER_SIZE 255 // scratch buffer size + +/** Firmware version, hardware version, and maximal values */ +#define OS_FW_VERSION 220 // Firmware version: 220 means 2.2.0 + // if this number is different from the one stored in non-volatile memory + // a device reset will be automatically triggered + +#define OS_FW_MINOR 109 // Firmware minor version + +/** Hardware version base numbers */ +#define OS_HW_VERSION_BASE 0x00 +#define OSPI_HW_VERSION_BASE 0x40 +#define OSBO_HW_VERSION_BASE 0x80 +#define SIM_HW_VERSION_BASE 0xC0 + +/** Hardware type macro defines */ +#define HW_TYPE_AC 0xAC // standard 24VAC for 24VAC solenoids only, with triacs +#define HW_TYPE_DC 0xDC // DC powered, for both DC and 24VAC solenoids, with boost converter and MOSFETs +#define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges +#define HW_TYPE_UNKNOWN 0xFF + +/** Data file names */ +#define IOPTS_FILENAME "iopts.dat" // integer options data file +#define SOPTS_FILENAME "sopts.dat" // string options data file +#define STATIONS_FILENAME "stns.dat" // stations data file +#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 + +/** Station macro defines */ +#define STN_TYPE_STANDARD 0x00 +#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station +#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station +#define STN_TYPE_GPIO 0x03 // direct GPIO station +#define STN_TYPE_HTTP 0x04 // HTTP station +#define STN_TYPE_OTHER 0xFF + +/** Notification macro defines */ +#define NOTIFY_PROGRAM_SCHED 0x0001 +#define NOTIFY_SENSOR1 0x0002 +#define NOTIFY_FLOWSENSOR 0x0004 +#define NOTIFY_WEATHER_UPDATE 0x0008 +#define NOTIFY_REBOOT 0x0010 +#define NOTIFY_STATION_OFF 0x0020 +#define NOTIFY_SENSOR2 0x0040 +#define NOTIFY_RAINDELAY 0x0080 +#define NOTIFY_STATION_ON 0x0100 + +/** HTTP request macro defines */ +#define HTTP_RQT_SUCCESS 0 +#define HTTP_RQT_NOT_RECEIVED -1 +#define HTTP_RQT_CONNECT_ERR -2 +#define HTTP_RQT_TIMEOUT -3 +#define HTTP_RQT_EMPTY_RETURN -4 +#define HTTP_RQT_DNS_ERROR -5 + +/** Sensor macro defines */ +#define SENSOR_TYPE_NONE 0x00 +#define SENSOR_TYPE_RAIN 0x01 // rain sensor +#define SENSOR_TYPE_FLOW 0x02 // flow sensor +#define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor +#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor +#define SENSOR_TYPE_OTHER 0xFF + +#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds + +/** Reboot cause */ +#define REBOOT_CAUSE_NONE 0 +#define REBOOT_CAUSE_RESET 1 +#define REBOOT_CAUSE_BUTTON 2 +#define REBOOT_CAUSE_RSTAP 3 +#define REBOOT_CAUSE_TIMER 4 +#define REBOOT_CAUSE_WEB 5 +#define REBOOT_CAUSE_WIFIDONE 6 +#define REBOOT_CAUSE_FWUPDATE 7 +#define REBOOT_CAUSE_WEATHER_FAIL 8 +#define REBOOT_CAUSE_NETWORK_FAIL 9 +#define REBOOT_CAUSE_NTP 10 +#define REBOOT_CAUSE_PROGRAM 11 +#define REBOOT_CAUSE_POWERON 99 + +/** Too much current */ +#define MAX_CURRENT 3010 //Max mA + +/** WiFi defines */ +#define WIFI_MODE_AP 0xA9 +#define WIFI_MODE_STA 0x2A + +#define OS_STATE_INITIAL 0 +#define OS_STATE_CONNECTING 1 +#define OS_STATE_CONNECTED 2 +#define OS_STATE_TRY_CONNECT 3 + +#define LED_FAST_BLINK 100 +#define LED_SLOW_BLINK 500 + +/** Storage / zone expander defines */ +#if defined(ARDUINO) + #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 +#endif + +#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders +#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations +#define STATION_NAME_SIZE 32 // maximum number of characters in each station name +#define MAX_SOPTS_SIZE 160 // maximum string option size + +#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) + +/** Default string option values */ +#define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' +#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA +#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" +#define DEFAULT_WEATHER_URL "weather.opensprinkler.com" +#define DEFAULT_IFTTT_URL "maker.ifttt.com" +#define DEFAULT_EMPTY_STRING "" + +/** Macro define of each option + * Refer to OpenSprinkler.cpp for details on each option + */ +enum { + IOPT_FW_VERSION=0,// read-only (ro) + IOPT_TIMEZONE, + IOPT_USE_NTP, + IOPT_USE_DHCP, + IOPT_STATIC_IP1, + IOPT_STATIC_IP2, + IOPT_STATIC_IP3, + IOPT_STATIC_IP4, + IOPT_GATEWAY_IP1, + IOPT_GATEWAY_IP2, + IOPT_GATEWAY_IP3, + IOPT_GATEWAY_IP4, + IOPT_HTTPPORT_0, + IOPT_HTTPPORT_1, + IOPT_HW_VERSION, //ro + IOPT_EXT_BOARDS, + IOPT_SEQUENTIAL_RETIRED, //ro + IOPT_STATION_DELAY_TIME, + IOPT_MASTER_STATION, + IOPT_MASTER_ON_ADJ, + IOPT_MASTER_OFF_ADJ, + IOPT_URS_RETIRED, // ro + IOPT_RSO_RETIRED, // ro + IOPT_WATER_PERCENTAGE, + IOPT_DEVICE_ENABLE, // editable through jc + IOPT_IGNORE_PASSWORD, + IOPT_DEVICE_ID, + IOPT_LCD_CONTRAST, + IOPT_LCD_BACKLIGHT, + IOPT_LCD_DIMMING, + IOPT_BOOST_TIME, + IOPT_USE_WEATHER, + IOPT_NTP_IP1, + IOPT_NTP_IP2, + IOPT_NTP_IP3, + IOPT_NTP_IP4, + IOPT_ENABLE_LOGGING, + IOPT_MASTER_STATION_2, + IOPT_MASTER_ON_ADJ_2, + IOPT_MASTER_OFF_ADJ_2, + IOPT_FW_MINOR, //ro + IOPT_PULSE_RATE_0, + IOPT_PULSE_RATE_1, + IOPT_REMOTE_EXT_MODE, // editable through jc + IOPT_DNS_IP1, + IOPT_DNS_IP2, + IOPT_DNS_IP3, + IOPT_DNS_IP4, + IOPT_SPE_AUTO_REFRESH, + IOPT_IFTTT_ENABLE, + IOPT_SENSOR1_TYPE, + IOPT_SENSOR1_OPTION, + IOPT_SENSOR2_TYPE, + IOPT_SENSOR2_OPTION, + IOPT_SENSOR1_ON_DELAY, + IOPT_SENSOR1_OFF_DELAY, + IOPT_SENSOR2_ON_DELAY, + IOPT_SENSOR2_OFF_DELAY, + IOPT_SUBNET_MASK1, + IOPT_SUBNET_MASK2, + IOPT_SUBNET_MASK3, + IOPT_SUBNET_MASK4, + IOPT_WIFI_MODE, //ro + IOPT_RESET, //ro + NUM_IOPTS // total number of integer options +}; + +enum { + SOPT_PASSWORD=0, + SOPT_LOCATION, + SOPT_JAVASCRIPTURL, + SOPT_WEATHERURL, + SOPT_WEATHER_OPTS, + SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT + SOPT_STA_SSID, + SOPT_STA_PASS, + SOPT_MQTT_OPTS, + //SOPT_WEATHER_KEY, + //SOPT_AP_PASS, + NUM_SOPTS // total number of string options +}; + +/** Log Data Type */ +#define LOGDATA_STATION 0x00 +#define LOGDATA_SENSOR1 0x01 +#define LOGDATA_RAINDELAY 0x02 +#define LOGDATA_WATERLEVEL 0x03 +#define LOGDATA_FLOWSENSE 0x04 +#define LOGDATA_SENSOR2 0x05 +#define LOGDATA_CURRENT 0x80 + +#undef OS_HW_VERSION + +/** Hardware defines */ +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // 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 + +#elif defined(ESP8266) // for ESP8266 + + #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) + #define IOEXP_PIN 0x80 // base for pins on main IO expander + #define MAIN_I2CADDR 0x20 // main IO expander I2C address + #define ACDR_I2CADDR 0x21 // ac driver I2C address + #define DCDR_I2CADDR 0x22 // dc driver I2C address + #define LADR_I2CADDR 0x23 // latch driver I2C address + #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address + #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address + + #define PIN_CURR_SENSE A0 + #define PIN_FREE_LIST {} // no free GPIO pin at the moment + #define ETHER_BUFFER_SIZE 2048 + + #define PIN_ETHER_CS 16 // ENC28J60 CS (chip select pin) is 16 on OS 3.2. + + /* To accommodate different OS30 versions, we use software defines pins */ + extern byte PIN_BUTTON_1; + extern byte PIN_BUTTON_2; + extern byte PIN_BUTTON_3; + extern byte PIN_RFRX; + extern byte PIN_RFTX; + extern byte PIN_BOOST; + extern byte PIN_BOOST_EN; + extern byte PIN_LATCH_COM; + extern byte PIN_LATCH_COMA; + extern byte PIN_LATCH_COMK; + extern byte PIN_SENSOR1; + extern byte PIN_SENSOR2; + extern byte PIN_IOEXP_INT; + + /* Original OS30 pin defines */ + //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask + // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i + #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 + #define V0_PIN_BUTTON_2 0 // button 2 + #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 + #define V0_PIN_RFRX 14 + #define V0_PIN_PWR_RX IOEXP_PIN+0 + #define V0_PIN_RFTX 16 + #define V0_PIN_PWR_TX IOEXP_PIN+2 + #define V0_PIN_BOOST IOEXP_PIN+6 + #define V0_PIN_BOOST_EN IOEXP_PIN+7 + #define V0_PIN_SENSOR1 12 // sensor 1 + #define V0_PIN_SENSOR2 13 // sensor 2 + + /* OS30 revision 1 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V1_IO_CONFIG 0x1F00 // config bits + #define V1_IO_OUTPUT 0x1F00 // output bits + #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 + #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 + #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V1_PIN_RFRX 14 + #define V1_PIN_RFTX 16 + #define V1_PIN_IOEXP_INT 12 + #define V1_PIN_BOOST IOEXP_PIN+13 + #define V1_PIN_BOOST_EN IOEXP_PIN+14 + #define V1_PIN_LATCH_COM IOEXP_PIN+15 + #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 + #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 + + /* OS30 revision 2 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V2_IO_CONFIG 0x1000 // config bits + #define V2_IO_OUTPUT 0x1E00 // output bits + #define V2_PIN_BUTTON_1 2 // button 1 + #define V2_PIN_BUTTON_2 0 // button 2 + #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V2_PIN_RFTX 15 + #define V2_PIN_BOOST IOEXP_PIN+13 + #define V2_PIN_BOOST_EN IOEXP_PIN+14 + #define V2_PIN_LATCH_COMA IOEXP_PIN+8 // latch COM+ (anode) + #define V2_PIN_SRLAT IOEXP_PIN+9 // shift register latch + #define V2_PIN_SRCLK IOEXP_PIN+10 // shift register clock + #define V2_PIN_SRDAT IOEXP_PIN+11 // shift register data + #define V2_PIN_LATCH_COMK IOEXP_PIN+15 // latch COM- (cathode) + #define V2_PIN_SENSOR1 3 // sensor 1 + #define V2_PIN_SENSOR2 10 // sensor 2 + +#elif defined(OSPI) // for OSPi + + #define OS_HW_VERSION OSPI_HW_VERSION_BASE + #define PIN_SR_LATCH 22 // shift register latch pin + #define PIN_SR_DATA 27 // shift register data pin + #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) + #define PIN_SR_CLOCK 4 // shift register clock pin + #define PIN_SR_OE 17 // shift register output enable pin + #define PIN_SENSOR1 14 + #define PIN_SENSOR2 23 + #define PIN_RFTX 15 // RF transmitter pin + //#define PIN_BUTTON_1 23 // button 1 + //#define PIN_BUTTON_2 24 // button 2 + //#define PIN_BUTTON_3 25 // button 3 + + #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins + #define ETHER_BUFFER_SIZE 16384 + +#elif defined(OSBO) // for OSBo + + #define OS_HW_VERSION OSBO_HW_VERSION_BASE + // these are gpio pin numbers, refer to + // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp + #define PIN_SR_LATCH 60 // P9_12, shift register latch pin + #define PIN_SR_DATA 30 // P9_11, shift register data pin + #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin + #define PIN_SR_OE 50 // P9_14, shift register output enable pin + #define PIN_SENSOR1 48 + #define PIN_RFTX 51 // RF transmitter pin + + #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} + #define ETHER_BUFFER_SIZE 16384 + +#else // for demo / simulation + // use fake hardware pins + #if defined(DEMO) + #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware + #else + #define OS_HW_VERSION SIM_HW_VERSION_BASE + #endif + #define PIN_SR_LATCH 0 + #define PIN_SR_DATA 0 + #define PIN_SR_CLOCK 0 + #define PIN_SR_OE 0 + #define PIN_SENSOR1 0 + #define PIN_SENSOR2 0 + #define PIN_RFTX 0 + #define PIN_FREE_LIST {} + #define ETHER_BUFFER_SIZE 16384 +#endif + +#if defined(ENABLE_DEBUG) /** Serial debug functions */ + + #if defined(ARDUINO) + #define DEBUG_BEGIN(x) {Serial.begin(x);} + #define DEBUG_PRINT(x) {Serial.print(x);} + #define DEBUG_PRINTLN(x) {Serial.println(x);} + #else + #include + #define DEBUG_BEGIN(x) {} /** Serial debug functions */ + inline void DEBUG_PRINT(int x) {printf("%d", x);} + inline void DEBUG_PRINT(const char*s) {printf("%s", s);} + #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} + #endif + +#else + + #define DEBUG_BEGIN(x) {} + #define DEBUG_PRINT(x) {} + #define DEBUG_PRINTLN(x) {} + +#endif + +/** Re-define avr-specific (e.g. PGM) types to use standard types */ +#if !defined(ARDUINO) + #include + #include + #include + #include + inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} + inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} + #define now() time(0) + #define pgm_read_byte(x) *(x) + #define PSTR(x) x + #define F(x) x + #define strcat_P strcat + #define strcpy_P strcpy + #define sprintf_P sprintf + #include + #define String string + using namespace std; + #define PROGMEM + typedef const char* PGM_P; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef bool boolean; + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite +#endif + +/** Other defines */ +// button values +#define BUTTON_1 0x01 +#define BUTTON_2 0x02 +#define BUTTON_3 0x04 + +// button status values +#define BUTTON_NONE 0x00 // no button pressed +#define BUTTON_MASK 0x0F // button status mask +#define BUTTON_FLAG_HOLD 0x80 // long hold flag +#define BUTTON_FLAG_DOWN 0x40 // down flag +#define BUTTON_FLAG_UP 0x20 // up flag + +// button timing values +#define BUTTON_DELAY_MS 1 // short delay (milliseconds) +#define BUTTON_HOLD_MS 1000 // long hold expiration time (milliseconds) + +// button mode values +#define BUTTON_WAIT_NONE 0 // do not wait, return value immediately +#define BUTTON_WAIT_RELEASE 1 // wait until button is release +#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/main.cpp b/main.cpp index 4f24c4531..28504019b 100644 --- a/main.cpp +++ b/main.cpp @@ -983,7 +983,7 @@ void do_loop() if (dhcp_timeout > 0 && !check_enc28j60()) { //ENC28J60 REGISTER CHECK!! DEBUG_PRINT(F("Reconnect")); - eth.resetEther(); + //eth.resetEther(); // todo: lwip add timeout int n = os.iopts[IOPT_USE_DHCP]?30:2; diff --git a/make.lin302 b/make.lin302 index 200bb84e8..286afa04a 100644 --- a/make.lin302 +++ b/make.lin302 @@ -10,7 +10,7 @@ LIBS = . \ /data/libs/SSD1306 \ /data/libs/rc-switch \ /data/libs/pubsubclient \ - /data/libs/ADS1015 \ + /data/libs/ADS1X15 \ ESP_ROOT = /data/esp8266_3.0.2/ ESPCORE_VERSION = 302 diff --git a/make.lin302m b/make.lin302m new file mode 100644 index 000000000..4917f9790 --- /dev/null +++ b/make.lin302m @@ -0,0 +1,34 @@ +SKETCH = ./mainArduino.ino +LIBS = . \ + $(ESP_LIBS)/Wire \ + $(ESP_LIBS)/SPI \ + $(ESP_LIBS)/ESP8266WiFi \ + $(ESP_LIBS)/ESP8266WebServer \ + $(ESP_LIBS)/ESP8266mDNS \ + $(ESP_LIBS)/LittleFS \ + $(ESP_LIBS)/lwIP_enc28j60 \ + /data/libs/SSD1306 \ + /data/libs/rc-switch \ + /data/libs/pubsubclient \ + +ESP_ROOT = /data/esp8266_3.0.2-master +ESPCORE_VERSION = 302 +BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) + +UPLOAD_SPEED = 460800 +UPLOAD_VERB = -v +# for OS3.0 revision 1: reset mode is nodemcu +# UPLOAD_RESET = nodemcu +# Uncomment the line below for OS3.0 revision 0: reset mode is ck +# UPLOAD_RESET = ck + +FLASH_DEF = 4M3M +FLASH_MODE = dio +FLASH_SPEED = 80 +F_CPU = 160000000L + +BOARD = generic + +EXCLUDE_DIRS = ./build-1284 + +include ./makeEspArduino.mk diff --git a/make.lin302m~ b/make.lin302m~ new file mode 100644 index 000000000..680556453 --- /dev/null +++ b/make.lin302m~ @@ -0,0 +1,34 @@ +SKETCH = ./mainArduino.ino +LIBS = . \ + $(ESP_LIBS)/Wire \ + $(ESP_LIBS)/SPI \ + $(ESP_LIBS)/ESP8266WiFi \ + $(ESP_LIBS)/ESP8266WebServer \ + $(ESP_LIBS)/ESP8266mDNS \ + /data/libs/LittleFS \ + /data/libs/lwIP_enc28j60 \ + /data/libs/SSD1306 \ + /data/libs/rc-switch \ + /data/libs/pubsubclient \ + +ESP_ROOT = /data/esp8266_3.0.2/ +ESPCORE_VERSION = 302 +BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) + +UPLOAD_SPEED = 460800 +UPLOAD_VERB = -v +# for OS3.0 revision 1: reset mode is nodemcu +# UPLOAD_RESET = nodemcu +# Uncomment the line below for OS3.0 revision 0: reset mode is ck +# UPLOAD_RESET = ck + +FLASH_DEF = 4M3M +FLASH_MODE = dio +FLASH_SPEED = 80 +F_CPU = 160000000L + +BOARD = generic + +EXCLUDE_DIRS = ./build-1284 + +include ./makeEspArduino.mk diff --git a/make.lin302~ b/make.lin302~ new file mode 100644 index 000000000..1ba67b7de --- /dev/null +++ b/make.lin302~ @@ -0,0 +1,36 @@ +SKETCH = ./mainArduino.ino +LIBS = . \ + $(ESP_LIBS)/Wire \ + $(ESP_LIBS)/SPI \ + $(ESP_LIBS)/ESP8266WiFi \ + $(ESP_LIBS)/ESP8266WebServer \ + $(ESP_LIBS)/ESP8266mDNS \ + $(ESP_LIBS)/LittleFS \ + /data/libs/lwIP_enc28j60 \ + /data/libs/SSD1306 \ + /data/libs/rc-switch \ + /data/libs/pubsubclient \ + /data/libs/ADS1015 \ + +ESP_ROOT = /data/esp8266_3.0.2/ +ESPCORE_VERSION = 302 +BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) + +UPLOAD_SPEED = 460800 +UPLOAD_VERB = -v +# for OS3.0 revision 1: reset mode is nodemcu +# UPLOAD_RESET = nodemcu +# Uncomment the line below for OS3.0 revision 0: reset mode is ck +# UPLOAD_RESET = ck + +FLASH_DEF = 4M3M +FLASH_MODE = dio +FLASH_SPEED = 80 +F_CPU = 160000000L + +BOARD = generic +board_build.filesystem = littlefs + +EXCLUDE_DIRS = ./build-1284 + +include ./makeEspArduino.mk diff --git a/opensprinkler-git.code-workspace b/opensprinkler-git.code-workspace new file mode 100644 index 000000000..397456c6a --- /dev/null +++ b/opensprinkler-git.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "..\\libs" + }, + { + "path": "..\\esp8266_3.0.2\\libraries" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 4c7fa7ac9..4d4c5ed88 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1664,7 +1664,7 @@ void server_json_log() { while(true) { #if defined(ESP8266) // do not use file.readBytes or readBytesUntil because it's very slow - int res = file_fgets(file, tmp_buffer, TMP_BUFFER_SIZE); + res = file_fgets(file, tmp_buffer, TMP_BUFFER_SIZE); if (res <= 0) { file.close(); break; @@ -1871,7 +1871,7 @@ void server_sensor_get() { print_json_header(false); #endif - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$E,\"enable\":$D,\"log\":$D,\"last\":$L}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"enable\":$D,\"log\":$D,\"last\":$L}"), sensor->nr, sensor->type, sensor->group, @@ -1924,7 +1924,7 @@ void server_sensor_readnow() { print_json_header(false); #endif - bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"lnd\":$L,\"ld\":$E}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"nativedata\":$L,\"data\":$E}"), sensor->nr, status, sensor->last_native_data, @@ -1939,10 +1939,10 @@ void server_sensor_readnow() { */ void server_sensor_list() { #if defined(ESP8266) - char *p = NULL; + //char *p = NULL; if(!process_password()) return; #else - char *p = get_buffer; + //char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensor_list")); @@ -1962,7 +1962,7 @@ void server_sensor_list() { bfill.emit_p(PSTR("\"sensors\":[")); for (int i = 0; i < count; i++) { Sensor_t *sensor = sensor_by_idx(i); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"lnd\":$L,\"ld\":$E,\"enable\":$D,\"log\":$D,\"last\":$L}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"enable\":$D,\"log\":$D,\"last\":$L}"), sensor->nr, sensor->type, sensor->group, @@ -2063,7 +2063,7 @@ void server_sensorlog_list() { if (count > 0) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"native_data\":$L,\"data\":$E}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"nativedata\":$L,\"data\":$E}"), sensorlog.nr, //sensor-nr sensor_type, //sensor-type sensorlog.time, //timestamp @@ -2090,10 +2090,10 @@ void server_sensorlog_list() { */ void server_sensorlog_clear() { #if defined(ESP8266) - char *p = NULL; + //char *p = NULL; if(!process_password()) return; #else - char *p = get_buffer; + //char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensorlog_clear")); diff --git a/platformio.ini b/platformio.ini index ba014ec6e..220558d8b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,24 +22,13 @@ board_build.filesystem = littlefs framework = arduino lib_ldf_mode = deep lib_deps = + https://github.com/dancol90/ESP8266Ping sui77/rc-switch @ ^2.6.3 https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 + RobTillaart/ADS1X15 + ; ignore html2raw.cpp source file for firmware compilation (external helper program) -src_filter = +<*> - +build_src_filter = +<*> - build_flags = -Teagle.flash.4m3m.ld - -[env:sanguino_atmega1284p] -platform = atmelavr -board = ATmega1284P -board_build.f_cpu = 16000000L -board_build.variant = sanguino -framework = arduino -lib_ldf_mode = deep -lib_deps = - EthernetENC=https://github.com/jandrassy/EthernetENC/archive/refs/tags/2.0.1.zip - knolleary/PubSubClient @ ^2.8 - greiman/SdFat @ 1.0.7 - Wire -src_filter = +<*> - diff --git a/sensors.cpp b/sensors.cpp index eb1e430bb..c98a1a59d 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -26,6 +26,12 @@ #include "OpenSprinkler.h" #include "sensors.h" +//All sensors: +static Sensor_t *sensors = NULL; + +//Program sensor data +static ProgSensorAdjust_t *progSensorAdjusts = NULL; + uint16_t CRC16 (byte buf[], int len) { uint16_t crc = 0xFFFF; @@ -278,10 +284,10 @@ int read_sensor_adc(Sensor_t *sensor) { if (!sensor || !sensor->enable) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: - ADS1015 adc; - bool active = adc.begin(sensor->port); + ADS1015 adc(sensor->port); + bool active = adc.begin(); if (active) - adc.setGain(ADS1015_CONFIG_PGA_1); + adc.setGain(1); DEBUG_PRINT(F("adc sensor found=")); DEBUG_PRINTLN(active); @@ -289,8 +295,16 @@ int read_sensor_adc(Sensor_t *sensor) { return HTTP_RQT_NOT_RECEIVED; //Read values: - sensor->last_native_data = adc.getSingleEnded(sensor->id); - sensor->last_data = adc.getSingleEndedMillivolts(sensor->id); + sensor->last_native_data = adc.readADC(sensor->id); + sensor->last_data = adc.toVoltage(sensor->last_native_data); + + if (sensor->type == SENSOR_SMT50_MOIS) { // SMT50 VWC [%] = (U * 50) : 3 + sensor->last_data = (sensor->last_data * 50) / 3; + } + if (sensor->type == SENSOR_SMT50_TEMP) { // SMT50 T [°C] = (U – 0,5) * 100 + sensor->last_data = (sensor->last_data - 0.5) * 100; + } + sensor->data_ok = true; DEBUG_PRINT(F("adc sensor values: ")); @@ -469,10 +483,12 @@ int read_sensor(Sensor_t *sensor) { return read_sensor_ip(sensor); case SENSOR_ANALOG_EXTENSION_BOARD: + case SENSOR_SMT50_MOIS: //SMT50 VWC [%] = (U * 50) : 3 + case SENSOR_SMT50_TEMP: //SMT50 T [°C] = (U – 0,5) * 100 return read_sensor_adc(sensor); - case SENSOR_OSPI_ANALOG_INPUTS: - return read_sensor_ospi(sensor); + //case SENSOR_OSPI_ANALOG_INPUTS: + // return read_sensor_ospi(sensor); default: return HTTP_RQT_NOT_RECEIVED; } @@ -491,7 +507,7 @@ void sensor_update_groups() { case SENSOR_GROUP_MAX: case SENSOR_GROUP_AVG: case SENSOR_GROUP_SUM: { - int nr = sensor->nr; + uint nr = sensor->nr; Sensor_t *group = sensors; double value = 0; int n = 0; diff --git a/sensors.h b/sensors.h index c88fd928d..302d863c3 100644 --- a/sensors.h +++ b/sensors.h @@ -24,6 +24,7 @@ #if defined(ARDUINO) #include + #include #else // headers for RPI/BBB #include #include @@ -32,15 +33,16 @@ #endif #include "defines.h" #include "utils.h" -#include -#include +#include //Sensor types: #define SENSOR_NONE 0 //None or deleted sensor #define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode #define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode -#define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 -#define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input +#define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 - voltage mode 0..4V +#define SENSOR_SMT50_MOIS 11 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_SMT50_TEMP 12 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 T [°C] = (U – 0,5) * 100 +//#define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input //#define SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler #define SENSOR_GROUP_MIN 1000 //Sensor group with min value @@ -111,12 +113,6 @@ typedef struct ProgSensorAdjust { } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) -//All sensors: -static Sensor_t *sensors = NULL; - -//Program sensor data -static ProgSensorAdjust_t *progSensorAdjusts = NULL; - //Utils: uint16_t CRC16 (byte buf[], int len); From 4eefe6802ad3e32c95bba879517115302af0e1cb Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 17 Nov 2022 00:24:42 +0100 Subject: [PATCH 021/281] List Program adjustments (se) and List supported sensor types (sf) added --- opensprinkler_server.cpp | 152 +++++++++++++++++++++++++++++++++++++++ sensors.cpp | 20 ++++++ sensors.h | 2 + 3 files changed, 174 insertions(+) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 4d4c5ed88..36abad828 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1762,6 +1762,7 @@ void server_sensor_config() { if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); @@ -2132,6 +2133,8 @@ void server_sensorprog_config() { if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + if (nr == 0) + handle_return(HTML_DATA_MISSING); if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); @@ -2171,6 +2174,151 @@ void server_sensorprog_config() { handle_return(res); } +/** + * se + * define a program adjustment +*/ +void server_sensorprog_list() { +#if defined(ESP8266) + char *p = NULL; + if(!process_password()) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorprog_list")); + + uint nr = 0; + int prog = -1; + + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + prog = strtoul(tmp_buffer, NULL, 0); + +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else + print_json_header(false); +#endif + + uint n = prog_adjust_count(); + uint idx = 0; + uint count = 0; + while (idx < n) { + ProgSensorAdjust_t *p = prog_adjust_by_idx(idx++); + if (nr > 0 && p->nr != nr) + continue; + if (prog >= 0 && p->prog != prog) + continue; + count++; + } + + bfill.emit_p(PSTR("{\"count\": $D,"), count); + + bfill.emit_p(PSTR("\"progAdjust\": [")); + idx = 0; + count = 0; + + while (idx < n) { + ProgSensorAdjust_t *p = prog_adjust_by_idx(idx++); + if (nr > 0 && p->nr != nr) + continue; + if (prog >= 0 && p->prog != prog) + continue; + + if (count++ > 0) + bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E}"), + p->nr, + p->type, + p->sensor, + p->prog, + p->factor1, + p->factor2, + p->min, + p->max); + + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(); + } + } + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + +const int sensor_types[] = { + SENSOR_SMT100_MODBUS_RTU_MOIS, + SENSOR_SMT100_MODBUS_RTU_TEMP, + SENSOR_ANALOG_EXTENSION_BOARD, + SENSOR_SMT50_MOIS, + SENSOR_SMT50_TEMP, + //SENSOR_OSPI_ANALOG_INPUTS, + //SENSOR_REMOTE, + SENSOR_GROUP_MIN, + SENSOR_GROUP_MAX, + SENSOR_GROUP_AVG, + SENSOR_GROUP_SUM, +}; + +const char* sensor_names[] = { + "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", + "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", + "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - voltage mode 0..4V", + "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - SMT50 moisture mode", + "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - SMT50 temperature mode", + //"OSPi analog input", + //"Remote sensor of an remote opensprinkler, + "Sensor group with min value", + "Sensor group with max value", + "Sensor group with avg value", + "Sensor group with sum value", +}; + +/** + * sf + * List supported sensor types + **/ +void server_sensor_types() { +#if defined(ESP8266) + //char *p = NULL; + if(!process_password()) return; +#else + //char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensor_types")); + +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else + print_json_header(false); +#endif + + bfill.emit_p(PSTR("{\"sensorTypes\":[")); + + for (int i = 0; i < sizeof(sensor_types)/sizeof(int); i++) + { + if (i > 0) + bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\"}"), sensor_types[i], sensor_names[i]); + + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(); + } + } + bfill.emit_p(PSTR("]}")); + + handle_return(HTML_OK); +} + /** * sd * Program calc @@ -2293,6 +2441,8 @@ const char _url_keys[] PROGMEM = "sn" "sb" "sd" + "se" + "sf" #if defined(ARDUINO) "db" #endif @@ -2330,6 +2480,8 @@ URLHandler urls[] = { server_sensorlog_clear, // sn server_sensorprog_config, // sb server_sensorprog_calc, // sd + server_sensorprog_list, // se + server_sensor_types, // sf #if defined(ARDUINO) server_json_debug, // db #endif diff --git a/sensors.cpp b/sensors.cpp index c98a1a59d..a55030061 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -841,3 +841,23 @@ uint prog_adjust_count() { } return count; } + +ProgSensorAdjust_t *prog_adjust_by_nr(uint nr) { + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + if (pa->nr == nr) return pa; + pa = pa->next; + } + return NULL; +} + +ProgSensorAdjust_t *prog_adjust_by_idx(uint idx) { + ProgSensorAdjust_t *pa = progSensorAdjusts; + int idxCounter = 0; + while (pa) { + if (idxCounter++ == idx) return pa; + pa = pa->next; + } + return NULL; +} + diff --git a/sensors.h b/sensors.h index 302d863c3..fe4e56a55 100644 --- a/sensors.h +++ b/sensors.h @@ -148,6 +148,8 @@ int prog_adjust_delete(uint nr); void prog_adjust_save(); void prog_adjust_load(); uint prog_adjust_count(); +ProgSensorAdjust_t *prog_adjust_by_nr(uint nr); +ProgSensorAdjust_t *prog_adjust_by_idx(uint idx); double calc_sensor_watering(uint prog); double calc_sensor_watering_by_nr(uint nr); From 892facad46e1f728120bcf6a4bd3aa3c151e5cc3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 19 Nov 2022 01:32:08 +0100 Subject: [PATCH 022/281] - list sensor types - remote sensor - sensor API help file --- Sensor API.txt | 74 ++++++++++++++++++++++++++++++++++++++++ opensprinkler_server.cpp | 4 +-- sensors.cpp | 35 ++++++++++++++++++- sensors.h | 7 ++-- 4 files changed, 115 insertions(+), 5 deletions(-) create mode 100644 Sensor API.txt diff --git a/Sensor API.txt b/Sensor API.txt new file mode 100644 index 000000000..e7b1f7024 --- /dev/null +++ b/Sensor API.txt @@ -0,0 +1,74 @@ +Sensor API + + the ip-address + the MD5 encrypted password + + +Create Sensors (sc): +creates, modifies or deletes a sensor. +"nr" for a unique number >= 1 +"type" for the sensor-type, see sf. type=0 deletes the sensor +"group" for group assignment, a nr of another sensor with type=SENSOR_GROUP_YXZ +"name" a name +"ip" for the ip-address, only for network connected sensors. All others use ip=0 +"port" for the ip-port-address, only for network connected sensors. All others use port=0 execpt ADC Sensors for the I2C address (currently only 72/73) +examples: +http:///sc?pw=&nr=1&type=1&group=0&name=SMT100-Mois&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 +http:///sc?pw=&nr=2&type=2&group=0&name=SMT100-Temp&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 +http:///sc?pw=&nr=3&type=11&group=0&name=SMT50-Mois&ip=0&port=72&id=0&ri=60&enable=1&log=1 +http:///sc?pw=&nr=4&type=12&group=0&name=SMT50-Temp&ip=0&port=72&id=1&ri=60&enable=1&log=1 + + +List Sensors (sl): +lists the current sensors +examples: +http:///sl?pw= + + +Get last Sensor values (sg) +returns last read sensor values +examples: +http:///sg?pw=&nr=1 + +Read sensor now (sr): +executes sensor read and returns the values +examples: +http:///sr?pw=&nr=1 + +Set sensor address for SMT100 (sa): +Only for SMT100: Set modbus address +Disconnect all other modbus sensors, so that only one sensor is connected. Sets the modbus address for sensor nr to id +examples: +http:///sa?pw=&nr=1&id=1 + +Dump Sensor Log (so): +dumps the sensor log +examples: +http:///so?pw= +http:///so?pw=&start=0&max=100&nr=1&type=1&before=1666915277&after=1666915277 + +Clear Sensor Log (sn): +clears the sensor log +examples: +http:///sn?pw= + +Program adjustments (sb): +defines program adjustments +"nr" adjustment-nr +"type" adjustment-type (0=delete, 1=linear, 2=digital min, 3=digital max) +"sensor" sensor-nr +"prog" programm-nr +"factor1", "factor2", "min", "max" formular-values +examples: +http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 + +List Program adjustments (se) +lists the current program adjustments +examples: +http:///se?pw= +&nr=1&prog=1 + +List supported sensor types (sf) +lists supported sensor types +examples: +http:///sf?pw= \ No newline at end of file diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 36abad828..1f04d6143 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2258,7 +2258,7 @@ const int sensor_types[] = { SENSOR_SMT50_MOIS, SENSOR_SMT50_TEMP, //SENSOR_OSPI_ANALOG_INPUTS, - //SENSOR_REMOTE, + SENSOR_REMOTE, SENSOR_GROUP_MIN, SENSOR_GROUP_MAX, SENSOR_GROUP_AVG, @@ -2272,7 +2272,7 @@ const char* sensor_names[] = { "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - SMT50 moisture mode", "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - SMT50 temperature mode", //"OSPi analog input", - //"Remote sensor of an remote opensprinkler, + "Remote sensor of an remote opensprinkler", "Sensor group with min value", "Sensor group with max value", "Sensor group with avg value", diff --git a/sensors.cpp b/sensors.cpp index a55030061..9073bf1fd 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -24,6 +24,7 @@ #include "utils.h" #include "program.h" #include "OpenSprinkler.h" +#include "OpenSprinkler_server.h" #include "sensors.h" //All sensors: @@ -325,6 +326,34 @@ int read_sensor_ospi(Sensor_t *sensor) { return HTTP_RQT_SUCCESS; } +extern byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); + +int read_sensor_http(Sensor_t *sensor) { + IPAddress _ip(sensor->ip); + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + + char *p = tmp_buffer; + BufferFiller bf = p; + + bf.emit_p(PSTR("GET /cm?pw=$O&sr=$D"), + SOPT_PASSWORD, sensor->id); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), + ip[0],ip[1],ip[2],ip[3]); + + if (os.send_http_request(sensor->ip, sensor->port, p) == HTTP_RQT_SUCCESS) { + + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nativedata"), true)) { + sensor->last_native_data = strtoul(tmp_buffer, NULL, 0); + } + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("data"), true)) { + sensor->last_data = atof(tmp_buffer); + sensor->data_ok = true; + } + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_EMPTY_RETURN; +} + /** * Read ip connected sensors */ @@ -361,7 +390,7 @@ int read_sensor_ip(Sensor_t *sensor) { uint8_t buffer[10]; int len = 0; - boolean addCrc16 = true; + boolean addCrc16 = false; switch(sensor->type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: @@ -372,6 +401,7 @@ int read_sensor_ip(Sensor_t *sensor) { buffer[4] = 0x00; buffer[5] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) len = 6; + addCrc16 = true; break; case SENSOR_SMT100_MODBUS_RTU_TEMP: @@ -382,6 +412,7 @@ int read_sensor_ip(Sensor_t *sensor) { buffer[4] = 0x00; buffer[5] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) len = 6; + addCrc16 = true; break; default: @@ -489,6 +520,8 @@ int read_sensor(Sensor_t *sensor) { //case SENSOR_OSPI_ANALOG_INPUTS: // return read_sensor_ospi(sensor); + case SENSOR_REMOTE: + return read_sensor_http(sensor); default: return HTTP_RQT_NOT_RECEIVED; } diff --git a/sensors.h b/sensors.h index fe4e56a55..3c342787a 100644 --- a/sensors.h +++ b/sensors.h @@ -42,8 +42,8 @@ #define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 - voltage mode 0..4V #define SENSOR_SMT50_MOIS 11 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 VWC [%] = (U * 50) : 3 #define SENSOR_SMT50_TEMP 12 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 T [°C] = (U – 0,5) * 100 -//#define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input -//#define SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler +#define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input +#define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler #define SENSOR_GROUP_MIN 1000 //Sensor group with min value #define SENSOR_GROUP_MAX 1001 //Sensor group with max value @@ -113,6 +113,9 @@ typedef struct ProgSensorAdjust { } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) +extern char ether_buffer[]; +extern char tmp_buffer[]; + //Utils: uint16_t CRC16 (byte buf[], int len); From dc34bbad2aa815da5ab0c54f42c48be379ce9ad8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 23 Nov 2022 22:46:38 +0100 Subject: [PATCH 023/281] 1. Update Sensor API 2. new /sh (list supported adjustments) and /du (System used/free space) commands 3. utils: added file append method file_append_block --- Sensor API.txt | 75 ++++++++++++++++++++++--- opensprinkler_server.cpp | 115 ++++++++++++++++++++++++++++++++++++--- platformio.ini | 12 ++-- sensors.cpp | 13 ++++- sensors.h | 1 + utils.cpp | 37 +++++++++++++ utils.h | 1 + 7 files changed, 232 insertions(+), 22 deletions(-) diff --git a/Sensor API.txt b/Sensor API.txt index e7b1f7024..ab2bdfdbd 100644 --- a/Sensor API.txt +++ b/Sensor API.txt @@ -1,7 +1,8 @@ Sensor API +This api documentation is part of the opensprinkler - the ip-address - the MD5 encrypted password + the ip-address, for example 192.168.0.55 + the MD5 encrypted password, opendoor=a6d82bced638de3def1e9bbb4983225c Create Sensors (sc): @@ -12,20 +13,50 @@ creates, modifies or deletes a sensor. "name" a name "ip" for the ip-address, only for network connected sensors. All others use ip=0 "port" for the ip-port-address, only for network connected sensors. All others use port=0 execpt ADC Sensors for the I2C address (currently only 72/73) +"id" sub-id, e.a. modbus address or subid +"ri" read interval in seconds +"enable" 0=sensor disabled, 1=sensor enabled +"log" 0=logging disabled, 1=logging enabled + examples: http:///sc?pw=&nr=1&type=1&group=0&name=SMT100-Mois&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 http:///sc?pw=&nr=2&type=2&group=0&name=SMT100-Temp&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 http:///sc?pw=&nr=3&type=11&group=0&name=SMT50-Mois&ip=0&port=72&id=0&ri=60&enable=1&log=1 http:///sc?pw=&nr=4&type=12&group=0&name=SMT50-Temp&ip=0&port=72&id=1&ri=60&enable=1&log=1 +ip: dec=4261456064 = hex=FE00A8C0 = +FE = 254 +00 = 000 +A8 = 168 +C0 = 192 + = 192.168.000.254 + +For the Truebner SMT100 RS485 Modbus you need a RS485 Modbus RTU over TCP converter. +Set ip/port for the converter, e.a PUSR USR-W610 in transparent modus. + +For the analog ports of the extension board (including SMT50) id is 0x48=72 for the first 4 ports (with id 0-3) +and 0x49=73 for the second 4 ports (also with id 0-3) + +type: +SENSOR_NONE 0 //None or deleted sensor +SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode +SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode +SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 - voltage mode 0..4V +SENSOR_SMT50_MOIS 11 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 VWC [%] = (U * 50) : 3 +SENSOR_SMT50_TEMP 12 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 T [°C] = (U – 0,5) * 100 +SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input (currenty not implemented!) +SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler (ip+port remote OS, id=sensor-nr) +SENSOR_GROUP_MIN 1000 //Sensor group with min value +SENSOR_GROUP_MAX 1001 //Sensor group with max value +SENSOR_GROUP_AVG 1002 //Sensor group with avg value +SENSOR_GROUP_SUM 1003 //Sensor group with sum value List Sensors (sl): lists the current sensors examples: http:///sl?pw= - -Get last Sensor values (sg) +Get last Sensor values (sg): returns last read sensor values examples: http:///sg?pw=&nr=1 @@ -62,13 +93,43 @@ defines program adjustments examples: http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 -List Program adjustments (se) +type: +PROG_NONE 0 //No adjustment (delete) +PROG_LINEAR 1 //formula +PROG_DIGITAL_MIN 2 //1=under or equal min, 0=above +PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below + +formula: + min max factor1 factor2 + 10..90 -> 5..1 factor1 > factor2 + a b c d + (b-sensorData) / (b-a) * (c-d) + d + + 10..90 -> 1..5 factor1 < factor2 + a b c d + (sensorData-a) / (b-a) * (d-c) + c + +min/max is the used range of the sensor (for example min=10 max=80) +factor1/factor2 is the calculated adjustment (for example factor1=2 factor2=0) +So a sensordata of 10 will be a adjustment of factor 2 (200%) or +a sensordata of 80 will be a adjustment of factor 0 (0%) +everything beetween will be linear scaled in the range of 0..2 + +List Program adjustments (se): lists the current program adjustments examples: http:///se?pw= &nr=1&prog=1 -List supported sensor types (sf) +List supported sensor types (sf): lists supported sensor types examples: -http:///sf?pw= \ No newline at end of file +http:///sf?pw= + +List supported program adjustments (sh): +http:///sh?pw= + +System used and free space (du): +http:///du?pw= + + diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 1f04d6143..a57c7696a 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2039,7 +2039,8 @@ void server_sensorlog_list() { #else print_json_header(false); #endif - bfill.emit_p(PSTR("[")); + bfill.emit_p(PSTR("{\"logsize\":$D,\"filesize\":$D,\"log\":["), + log_size, sensorlog_filesize()); ulong count = 0; SensorLog_t sensorlog; @@ -2079,7 +2080,7 @@ void server_sensorlog_list() { if (++count >= maxResults) break; } - bfill.emit_p(PSTR("]")); + bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); } @@ -2211,7 +2212,7 @@ void server_sensorprog_list() { ProgSensorAdjust_t *p = prog_adjust_by_idx(idx++); if (nr > 0 && p->nr != nr) continue; - if (prog >= 0 && p->prog != prog) + if (prog >= 0 && p->prog != (uint)prog) continue; count++; } @@ -2226,7 +2227,7 @@ void server_sensorprog_list() { ProgSensorAdjust_t *p = prog_adjust_by_idx(idx++); if (nr > 0 && p->nr != nr) continue; - if (prog >= 0 && p->prog != prog) + if (prog >= 0 && p->prog != (uint)prog) continue; if (count++ > 0) @@ -2300,9 +2301,9 @@ void server_sensor_types() { print_json_header(false); #endif - bfill.emit_p(PSTR("{\"sensorTypes\":[")); + bfill.emit_p(PSTR("{\"count\":$D,\"sensorTypes\":["), sizeof(sensor_types)/sizeof(int)); - for (int i = 0; i < sizeof(sensor_types)/sizeof(int); i++) + for (uint i = 0; i < sizeof(sensor_types)/sizeof(int); i++) { if (i > 0) bfill.emit_p(PSTR(",")); @@ -2319,6 +2320,44 @@ void server_sensor_types() { handle_return(HTML_OK); } +/** + * du + * List supported sensor types + **/ +void server_usage() { +#if defined(ESP8266) + //char *p = NULL; + if(!process_password()) return; +#else + //char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_usage")); + +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else + print_json_header(false); +#endif + + struct FSInfo fsinfo; + + boolean ok = LittleFS.info(fsinfo); + + bfill.emit_p(PSTR("{\"status\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D}"), + ok, + fsinfo.totalBytes, + fsinfo.usedBytes, + fsinfo.totalBytes-fsinfo.usedBytes, + fsinfo.blockSize, + fsinfo.pageSize, + fsinfo.maxOpenFiles, + fsinfo.maxPathLength); + + handle_return(HTML_OK); +} + /** * sd * Program calc @@ -2351,6 +2390,62 @@ void server_sensorprog_calc() { handle_return(HTML_DATA_MISSING); } +const int prog_types[] = { + PROG_NONE, + PROG_LINEAR, + PROG_DIGITAL_MIN, + PROG_DIGITAL_MAX, +}; + +const char* prog_names[] = { + "No Adjustment", + "Linear scaling", + "Digital under min", + "Digital over max", +}; + +/** + * sh + * List supported adjustment types + */ +void server_sensorprog_types() +{ +#if defined(ESP8266) + //char *p = NULL; + if(!process_password()) return; +#else + //char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorprog_types")); + +#if defined(ESP8266) + rewind_ether_buffer(); + print_json_header(false); +#else + print_json_header(false); +#endif + + bfill.emit_p(PSTR("{\"count\":$D,\"progTypes\":["), sizeof(prog_types)/sizeof(int)); + + for (uint i = 0; i < sizeof(prog_types)/sizeof(int); i++) + { + + if (i > 0) + bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\"}"), prog_types[i], prog_names[i]); + + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(); + } + } + bfill.emit_p(PSTR("]}")); + + handle_return(HTML_OK); +} + /** Output all JSON data, including jc, jp, jo, js, jn */ void server_json_all() { #if defined(ESP8266) @@ -2443,6 +2538,8 @@ const char _url_keys[] PROGMEM = "sd" "se" "sf" + "du" + "sh" #if defined(ARDUINO) "db" #endif @@ -2482,6 +2579,8 @@ URLHandler urls[] = { server_sensorprog_calc, // sd server_sensorprog_list, // se server_sensor_types, // sf + server_usage, // du + server_sensorprog_types, // sh #if defined(ARDUINO) server_json_debug, // db #endif @@ -2583,7 +2682,7 @@ void start_server_client() { char uri[4]; uri[0]='/'; uri[3]=0; - for(int i=0;ion(uri, urls[i]); @@ -2610,7 +2709,7 @@ void start_server_ap() { char uri[4]; uri[0]='/'; uri[3]=0; - for(int i=0;ion(uri, urls[i]); diff --git a/platformio.ini b/platformio.ini index 220558d8b..e0c787923 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,9 +15,9 @@ src_dir = . include_dir = . -[env:d1_mini_lite] +[env:d1_mini] platform = espressif8266 -board = d1_mini_lite +board = d1_mini board_build.filesystem = littlefs framework = arduino lib_ldf_mode = deep @@ -30,5 +30,9 @@ lib_deps = ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - -build_flags = - -Teagle.flash.4m3m.ld +upload_speed = 460800 +monitor_speed=115200 +board_build.flash_mode = dio +board_build.ldscript = eagle.flash.4m3m.ld +board_build.f_cpu = 160000000L +board_build.f_flash = 80000000L diff --git a/sensors.cpp b/sensors.cpp index 9073bf1fd..4448deb75 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -220,8 +220,9 @@ Sensor_t *sensor_by_idx(uint idx) { } void sensorlog_add(SensorLog_t *sensorlog) { - DEBUG_PRINTLN(F("sensorlog_add")); - file_write_block(SENSORLOG_FILENAME, sensorlog, file_size(SENSORLOG_FILENAME), SENSORLOG_STORE_SIZE); + DEBUG_PRINT(F("sensorlog_add ")); + file_append_block(SENSORLOG_FILENAME, sensorlog, SENSORLOG_STORE_SIZE); + DEBUG_PRINT(sensorlog_filesize()); } void sensorlog_add(Sensor_t *sensor, ulong time) { @@ -235,6 +236,12 @@ void sensorlog_add(Sensor_t *sensor, ulong time) { } } +ulong sensorlog_filesize() { + DEBUG_PRINT(F("sensorlog_filesize ")); + ulong size = file_size(SENSORLOG_FILENAME); + DEBUG_PRINTLN(size); + return size; +} ulong sensorlog_size() { DEBUG_PRINT(F("sensorlog_size ")); @@ -886,7 +893,7 @@ ProgSensorAdjust_t *prog_adjust_by_nr(uint nr) { ProgSensorAdjust_t *prog_adjust_by_idx(uint idx) { ProgSensorAdjust_t *pa = progSensorAdjusts; - int idxCounter = 0; + uint idxCounter = 0; while (pa) { if (idxCounter++ == idx) return pa; pa = pa->next; diff --git a/sensors.h b/sensors.h index 3c342787a..1b48ebce5 100644 --- a/sensors.h +++ b/sensors.h @@ -140,6 +140,7 @@ void sensorlog_add(Sensor_t *sensor, ulong time); void sensorlog_clear_all(); SensorLog_t *sensorlog_load(ulong pos); SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog); +ulong sensorlog_filesize(); ulong sensorlog_size(); //Set Sensor Address for SMT100: diff --git a/utils.cpp b/utils.cpp index 070b3ae51..ee8369e68 100644 --- a/utils.cpp +++ b/utils.cpp @@ -430,6 +430,43 @@ void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { } +void file_append_block(const char *fn, const void *src, ulong len) { +#if defined(ESP8266) + + File f = LittleFS.open(fn, "r+"); + if(!f) f = LittleFS.open(fn, "w"); + if(f) { + f.seek(0, SeekEnd); + f.write((byte*)src, len); + f.close(); + } + +#elif defined(ARDUINO) + + sd.chdir("/"); + SdFile file; + int ret = file.open(fn, O_CREAT | O_RDWR); + if(!ret) return; + file.seekEnd(0); + file.write(src, len); + file.close(); + +#else + + FILE *fp = fopen(get_filename_fullpath(fn), "rb+"); + if(!fp) { + fp = fopen(get_filename_fullpath(fn), "wb+"); + } + if(fp) { + fseek(fp, 0, SEEK_END); //this fails silently without the above change + fwrite(src, 1, len, fp); + fclose(fp); + } + +#endif + +} + void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) { // assume tmp buffer is provided and is larger than len // todo future: if tmp buffer is not provided, do byte-to-byte copy diff --git a/utils.h b/utils.h index 0c6c80526..03b4b74f5 100644 --- a/utils.h +++ b/utils.h @@ -44,6 +44,7 @@ ulong file_size(const char *fn); 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_append_block(const char *fname, const void *src, ulong len); void file_copy_block (const char *fname, ulong from, ulong to, ulong len, void *tmp=0); byte file_read_byte (const char *fname, ulong pos); void file_write_byte(const char *fname, ulong pos, byte v); From af2cce45a8c23e47dc23067db4a47d1dd424bf0c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 7 Dec 2022 22:57:27 +0100 Subject: [PATCH 024/281] sg+sr with optional nr parameter updated api documentation --- Sensor API.txt | 12 +++-- opensprinkler_server.cpp | 107 ++++++++++++++++++++++++--------------- 2 files changed, 72 insertions(+), 47 deletions(-) diff --git a/Sensor API.txt b/Sensor API.txt index ab2bdfdbd..b29591f94 100644 --- a/Sensor API.txt +++ b/Sensor API.txt @@ -12,7 +12,7 @@ creates, modifies or deletes a sensor. "group" for group assignment, a nr of another sensor with type=SENSOR_GROUP_YXZ "name" a name "ip" for the ip-address, only for network connected sensors. All others use ip=0 -"port" for the ip-port-address, only for network connected sensors. All others use port=0 execpt ADC Sensors for the I2C address (currently only 72/73) +"port" for the ip-port-address, only for network connected sensors. All others use port=0 except ADC Sensors for the I2C address (currently only 72/73) "id" sub-id, e.a. modbus address or subid "ri" read interval in seconds "enable" 0=sensor disabled, 1=sensor enabled @@ -44,7 +44,7 @@ SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TC SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 - voltage mode 0..4V SENSOR_SMT50_MOIS 11 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 VWC [%] = (U * 50) : 3 SENSOR_SMT50_TEMP 12 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 T [°C] = (U – 0,5) * 100 -SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input (currenty not implemented!) +SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input (currently not implemented!) SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler (ip+port remote OS, id=sensor-nr) SENSOR_GROUP_MIN 1000 //Sensor group with min value SENSOR_GROUP_MAX 1001 //Sensor group with max value @@ -59,11 +59,13 @@ http:///sl?pw= Get last Sensor values (sg): returns last read sensor values examples: +http:///sg?pw= http:///sg?pw=&nr=1 Read sensor now (sr): executes sensor read and returns the values examples: +http:///sr?pw= http:///sr?pw=&nr=1 Set sensor address for SMT100 (sa): @@ -88,8 +90,8 @@ defines program adjustments "nr" adjustment-nr "type" adjustment-type (0=delete, 1=linear, 2=digital min, 3=digital max) "sensor" sensor-nr -"prog" programm-nr -"factor1", "factor2", "min", "max" formular-values +"prog" program-nr +"factor1", "factor2", "min", "max" formula-values examples: http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 @@ -113,7 +115,7 @@ min/max is the used range of the sensor (for example min=10 max=80) factor1/factor2 is the calculated adjustment (for example factor1=2 factor2=0) So a sensordata of 10 will be a adjustment of factor 2 (200%) or a sensordata of 80 will be a adjustment of factor 0 (0%) -everything beetween will be linear scaled in the range of 0..2 +everything between will be linear scaled in the range of 0..2 List Program adjustments (se): lists the current program adjustments diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index a57c7696a..9637b0cf9 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1841,7 +1841,7 @@ void server_set_sensor_address() { /** * sg - * @brief return a 485 sensor + * @brief return one or all last sensor values * */ void server_sensor_get() { @@ -1854,16 +1854,9 @@ void server_sensor_get() { DEBUG_PRINTLN(F("server_sensor_get")); - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) - handle_return(HTML_DATA_MISSING); - uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr - - Sensor_t *sensor = sensor_by_nr(nr); - if (!sensor) - { - server_send_result(255); - return; - } + uint nr = 0; + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr #if defined(ESP8266) rewind_ether_buffer(); @@ -1872,21 +1865,34 @@ void server_sensor_get() { print_json_header(false); #endif - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"enable\":$D,\"log\":$D,\"last\":$L}"), - sensor->nr, - sensor->type, - sensor->group, - sensor->name, - sensor->ip, - sensor->port, - sensor->id, - sensor->read_interval, - sensor->last_native_data, - sensor->last_data, - sensor->enable, - sensor->log, - sensor->last_read); + bfill.emit_p(PSTR("{\"datas\":[")); + uint count = sensor_count(); + for (uint i = 0; i < count; i++) { + + Sensor_t *sensor = sensor_by_idx(i); + if (!sensor) + { + server_send_result(255); + return; + } + + if (nr != 0 && nr != sensor->nr) + continue; + bfill.emit_p(PSTR("{\"nr\":$D,\"nativedata\":$L,\"data\":$E,\"last\":$L}"), + sensor->nr, + sensor->last_native_data, + sensor->last_data, + sensor->last_read); + if (i < count-1) + bfill.emit_p(PSTR(",")); + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(); + } + } + bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); } @@ -1905,18 +1911,9 @@ void server_sensor_readnow() { DEBUG_PRINTLN(F("server_sensor_readnow")); - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) - handle_return(HTML_DATA_MISSING); - uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr - - Sensor_t *sensor = sensor_by_nr(nr); - if (!sensor) - { - server_send_result(255); - return; - } - - int status = read_sensor(sensor); + uint nr = 0; + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr #if defined(ESP8266) rewind_ether_buffer(); @@ -1925,14 +1922,40 @@ void server_sensor_readnow() { print_json_header(false); #endif - bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"nativedata\":$L,\"data\":$E}"), - sensor->nr, - status, - sensor->last_native_data, - sensor->last_data); + bfill.emit_p(PSTR("{\"datas\":[")); + uint count = sensor_count(); + for (uint i = 0; i < count; i++) { + + Sensor_t *sensor = sensor_by_idx(i); + if (!sensor) + { + server_send_result(255); + return; + } + + if (nr != 0 && nr != sensor->nr) + continue; + + int status = read_sensor(sensor); + + bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"nativedata\":$L,\"data\":$E}"), + sensor->nr, + status, + sensor->last_native_data, + sensor->last_data); + if (i < count-1) + bfill.emit_p(PSTR(",")); + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(); + } + } + bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); } + /** * sl * @brief Lists all sensors From 1571ef31e6568d2b3d5d2089eb8cf8a644457c96 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 26 Dec 2022 02:14:43 +0100 Subject: [PATCH 025/281] Sensor API added flags / show option Sensor API added unit and unitid --- defines.h | 4 +- opensprinkler_server.cpp | 155 ++++++++++++++++++++++++++++----------- sensors.cpp | 140 +++++++++++++++++++++++++++++------ sensors.h | 44 +++++++++-- 4 files changed, 267 insertions(+), 76 deletions(-) diff --git a/defines.h b/defines.h index 7a4b30fde..1b0b15da8 100644 --- a/defines.h +++ b/defines.h @@ -32,11 +32,11 @@ typedef unsigned long ulong; #define TMP_BUFFER_SIZE 255 // scratch buffer size /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 220 // Firmware version: 220 means 2.2.0 +#define OS_FW_VERSION 230 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 113 // Firmware minor version +#define OS_FW_MINOR 1 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 9637b0cf9..83472af51 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1806,7 +1806,12 @@ void server_sensor_config() { if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) log = strtoul(tmp_buffer, NULL, 0); // 1=logging enabled/0=logging disabled - int res = sensor_define(nr, name, type, group, ip, port, id, ri, enable, log); + uint show = 1; + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("show"), true)) + show = strtoul(tmp_buffer, NULL, 0); // 1=show enabled/0=show disabled + + SensorFlags_t flags = {.enable=enable, .log=log, .show=show}; + int res = sensor_define(nr, name, type, group, ip, port, id, ri, flags); res = res >= HTTP_RQT_SUCCESS?HTML_SUCCESS:(256+res); handle_return(res); } @@ -1879,10 +1884,12 @@ void server_sensor_get() { if (nr != 0 && nr != sensor->nr) continue; - bfill.emit_p(PSTR("{\"nr\":$D,\"nativedata\":$L,\"data\":$E,\"last\":$L}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"last\":$L}"), sensor->nr, sensor->last_native_data, sensor->last_data, + getSensorUnit(sensor), + getSensorUnitId(sensor), sensor->last_read); if (i < count-1) bfill.emit_p(PSTR(",")); @@ -1938,11 +1945,13 @@ void server_sensor_readnow() { int status = read_sensor(sensor); - bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"nativedata\":$L,\"data\":$E}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), sensor->nr, status, sensor->last_native_data, - sensor->last_data); + sensor->last_data, + getSensorUnit(sensor), + getSensorUnitId(sensor)); if (i < count-1) bfill.emit_p(PSTR(",")); @@ -1956,6 +1965,37 @@ void server_sensor_readnow() { handle_return(HTML_OK); } +void sensorconfig_json() { + int count = sensor_count(); + for (int i = 0; i < count; i++) { + Sensor_t *sensor = sensor_by_idx(i); + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"last\":$L}"), + sensor->nr, + sensor->type, + sensor->group, + sensor->name, + sensor->ip, + sensor->port, + sensor->id, + sensor->read_interval, + sensor->last_native_data, + sensor->last_data, + getSensorUnit(sensor), + getSensorUnitId(sensor), + sensor->flags.enable, + sensor->flags.log, + sensor->flags.show, + sensor->last_read); + if (i < count-1) + bfill.emit_p(PSTR(",")); + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(); + } + } +} + /** * sl * @brief Lists all sensors @@ -1982,32 +2022,8 @@ void server_sensor_list() { int count = sensor_count(); bfill.emit_p(PSTR("{\"count\":$D,"), count); - bfill.emit_p(PSTR("\"sensors\":[")); - for (int i = 0; i < count; i++) { - Sensor_t *sensor = sensor_by_idx(i); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"enable\":$D,\"log\":$D,\"last\":$L}"), - sensor->nr, - sensor->type, - sensor->group, - sensor->name, - sensor->ip, - sensor->port, - sensor->id, - sensor->read_interval, - sensor->last_native_data, - sensor->last_data, - sensor->enable, - sensor->log, - sensor->last_read); - if (i < count-1) - bfill.emit_p(PSTR(",")); - // if available ether buffer is getting small - // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(); - } - } + sensorconfig_json(); bfill.emit_p(PSTR("]")); bfill.emit_p(PSTR("}")); @@ -2088,12 +2104,14 @@ void server_sensorlog_list() { if (count > 0) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"nativedata\":$L,\"data\":$E}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), sensorlog.nr, //sensor-nr sensor_type, //sensor-type sensorlog.time, //timestamp sensorlog.native_data, //native data - sensorlog.data); + sensorlog.data, + getSensorUnit(sensor), + getSensorUnitId(sensor)); // if available ether buffer is getting small // send out a packet @@ -2198,6 +2216,28 @@ void server_sensorprog_config() { handle_return(res); } +void progconfig_json(ProgSensorAdjust_t *p) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E}"), + p->nr, + p->type, + p->sensor, + p->prog, + p->factor1, + p->factor2, + p->min, + p->max); +} + +void progconfig_json() { + uint count = prog_adjust_count(); + for (uint i = 0; i < count; i++) { + ProgSensorAdjust_t *p = prog_adjust_by_idx(i); + progconfig_json(p); + if (i < count-1) + bfill.emit_p(PSTR(",")); + } +} + /** * se * define a program adjustment @@ -2255,16 +2295,7 @@ void server_sensorprog_list() { if (count++ > 0) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E}"), - p->nr, - p->type, - p->sensor, - p->prog, - p->factor1, - p->factor2, - p->min, - p->max); - + progconfig_json(p); // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { @@ -2330,7 +2361,9 @@ void server_sensor_types() { { if (i > 0) bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\"}"), sensor_types[i], sensor_names[i]); + byte unitid = getSensorUnitId(sensor_types[i]); + bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), + sensor_types[i], sensor_names[i], sensor_unitNames[unitid], unitid); // if available ether buffer is getting small // send out a packet @@ -2469,6 +2502,40 @@ void server_sensorprog_types() handle_return(HTML_OK); } +/** + * sx + * @brief backup sensor configuration + * + */ +void server_sensorconfig_backup() +{ +#if defined(ESP8266) + if(!process_password(true)) return; + rewind_ether_buffer(); +#endif + print_json_header(); + bfill.emit_p(PSTR("\"sensors\":[")); + sensorconfig_json(); + bfill.emit_p(PSTR("],")); + send_packet(); + bfill.emit_p(PSTR("\"progadjust\":[")); + progconfig_json(); + bfill.emit_p(PSTR("]")); + send_packet(); + bfill.emit_p(PSTR("}")); + handle_return(HTML_OK); +} + +/** + * sy + * @brief restore sensor configuration + * + */ +void server_sensorconfig_restore() +{ + +} + /** Output all JSON data, including jc, jp, jo, js, jn */ void server_json_all() { #if defined(ESP8266) @@ -2563,6 +2630,8 @@ const char _url_keys[] PROGMEM = "sf" "du" "sh" + "sx" + "sy" #if defined(ARDUINO) "db" #endif @@ -2604,6 +2673,8 @@ URLHandler urls[] = { server_sensor_types, // sf server_usage, // du server_sensorprog_types, // sh + server_sensorconfig_backup, // sx + server_sensorconfig_restore, // sy #if defined(ARDUINO) server_json_debug, // db #endif diff --git a/sensors.cpp b/sensors.cpp index 4448deb75..d329714af 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -86,7 +86,7 @@ int sensor_delete(uint nr) { * @param port * @param id */ -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, byte enable, byte log) { +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, SensorFlags_t flags) { if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; @@ -102,8 +102,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint sensor->port = port; sensor->id = id; sensor->read_interval = ri; - sensor->enable = enable; - sensor->log = log; + sensor->flags = flags; sensor_save(); return HTTP_RQT_SUCCESS; } @@ -125,8 +124,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->port = port; new_sensor->id = id; new_sensor->read_interval = ri; - new_sensor->enable = enable; - new_sensor->log = log; + new_sensor->flags = flags; if (last) { new_sensor->next = last->next; last->next = new_sensor; @@ -219,21 +217,30 @@ Sensor_t *sensor_by_idx(uint idx) { return NULL; } -void sensorlog_add(SensorLog_t *sensorlog) { - DEBUG_PRINT(F("sensorlog_add ")); - file_append_block(SENSORLOG_FILENAME, sensorlog, SENSORLOG_STORE_SIZE); - DEBUG_PRINT(sensorlog_filesize()); +bool sensorlog_add(SensorLog_t *sensorlog) { + if (checkDiskFree()) { + DEBUG_PRINT(F("sensorlog_add ")); + file_append_block(SENSORLOG_FILENAME, sensorlog, SENSORLOG_STORE_SIZE); + DEBUG_PRINT(sensorlog_filesize()); + return true; + } + return false; } -void sensorlog_add(Sensor_t *sensor, ulong time) { - if (sensor->data_ok) { +bool sensorlog_add(Sensor_t *sensor, ulong time) { + if (sensor->flags.data_ok && sensor->flags.log) { SensorLog_t sensorlog; sensorlog.nr = sensor->nr; sensorlog.time = time; sensorlog.native_data = sensor->last_native_data; sensorlog.data = sensor->last_data; - sensorlog_add(&sensorlog); + if (!sensorlog_add(&sensorlog)) { + sensor->flags.log = 0; + return false; + } + return true; } + return false; } ulong sensorlog_filesize() { @@ -289,7 +296,7 @@ void read_all_sensors() { */ int read_sensor_adc(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_adc")); - if (!sensor || !sensor->enable) return HTTP_RQT_NOT_RECEIVED; + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: ADS1015 adc(sensor->port); @@ -313,7 +320,7 @@ int read_sensor_adc(Sensor_t *sensor) { sensor->last_data = (sensor->last_data - 0.5) * 100; } - sensor->data_ok = true; + sensor->flags.data_ok = true; DEBUG_PRINT(F("adc sensor values: ")); DEBUG_PRINT(sensor->last_native_data); @@ -326,7 +333,7 @@ int read_sensor_adc(Sensor_t *sensor) { int read_sensor_ospi(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_ospi")); - if (!sensor || !sensor->enable) return HTTP_RQT_NOT_RECEIVED; + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; //currently not implemented @@ -342,7 +349,7 @@ int read_sensor_http(Sensor_t *sensor) { char *p = tmp_buffer; BufferFiller bf = p; - bf.emit_p(PSTR("GET /cm?pw=$O&sr=$D"), + bf.emit_p(PSTR("GET /sg?pw=$O&nr=$D"), SOPT_PASSWORD, sensor->id); bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0],ip[1],ip[2],ip[3]); @@ -354,8 +361,11 @@ int read_sensor_http(Sensor_t *sensor) { } if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("data"), true)) { sensor->last_data = atof(tmp_buffer); - sensor->data_ok = true; + sensor->flags.data_ok = true; } + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unitid"), true)) { + sensor->unitid = strtoul(tmp_buffer, NULL, 0); + } return HTTP_RQT_SUCCESS; } return HTTP_RQT_EMPTY_RETURN; @@ -485,12 +495,12 @@ int read_sensor_ip(Sensor_t *sensor) { { case SENSOR_SMT100_MODBUS_RTU_MOIS: //Equation: soil moisture [vol.%]= (16Bit_soil_moisture_value / 100) sensor->last_data = ((double)sensor->last_native_data / 100); - sensor->data_ok = true; + sensor->flags.data_ok = true; DEBUG_PRINT(F(" soil moisture %: ")); break; case SENSOR_SMT100_MODBUS_RTU_TEMP: //Equation: temperature [°C]= (16Bit_temperature_value / 100)-100 sensor->last_data = ((double)sensor->last_native_data / 100) - 100; - sensor->data_ok = true; + sensor->flags.data_ok = true; DEBUG_PRINT(F(" temperature °C: ")); break; } @@ -506,7 +516,7 @@ int read_sensor_ip(Sensor_t *sensor) { */ int read_sensor(Sensor_t *sensor) { - if (!sensor || !sensor->enable) + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; sensor->last_read = os.now_tz(); @@ -552,7 +562,7 @@ void sensor_update_groups() { double value = 0; int n = 0; while (group) { - if (group->nr != nr && group->group == nr && group->enable && group->data_ok) { + if (group->nr != nr && group->group == nr && group->flags.enable && group->flags.data_ok) { switch(sensor->type) { case SENSOR_GROUP_MIN: if (n++ == 0) value = group->last_data; @@ -577,7 +587,7 @@ void sensor_update_groups() { sensor->last_data = value; sensor->last_native_data = 0; sensor->last_read = os.now_tz(); - sensor->data_ok = n>0; + sensor->flags.data_ok = n>0; break; } } @@ -712,7 +722,7 @@ double calc_sensor_watering(uint prog) { while (p) { if (p->prog == prog) { Sensor_t *sensor = sensor_by_nr(p->sensor); - if (sensor && sensor->enable && sensor->data_ok) { + if (sensor && sensor->flags.enable && sensor->flags.data_ok) { double res = 0; switch(p->type) { @@ -746,7 +756,7 @@ double calc_sensor_watering_by_nr(uint nr) { while (p) { if (p->nr == nr) { Sensor_t *sensor = sensor_by_nr(p->sensor); - if (sensor && sensor->enable && sensor->data_ok) { + if (sensor && sensor->flags.enable && sensor->flags.data_ok) { double res = 0; switch(p->type) { @@ -901,3 +911,85 @@ ProgSensorAdjust_t *prog_adjust_by_idx(uint idx) { return NULL; } +ulong diskFree() { + struct FSInfo fsinfo; + LittleFS.info(fsinfo); + return fsinfo.totalBytes-fsinfo.usedBytes; +} + +bool checkDiskFree() { + if (diskFree() < MIN_DISK_FREE) { + DEBUG_PRINT(F("fs has low space!")); + return false; + } + return true; +} + +const char* getSensorUnit(Sensor_t *sensor) { + if (!sensor) + return sensor_unitNames[0]; + + return sensor_unitNames[getSensorUnitId(sensor)]; +} + +boolean sensor_isgroup(Sensor_t *sensor) { + if (!sensor) + return false; + + switch(sensor->type) { + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: return true; + + default: return false; + } +} + +byte getSensorUnitId(int type) { + switch(type) { + case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; + case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; + case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; + case SENSOR_SMT50_MOIS: return UNIT_PERCENT; + case SENSOR_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; + + default: return UNIT_NONE; + } +} + +byte getSensorUnitId(Sensor_t *sensor) { + if (!sensor) + return 0; + + switch(sensor->type) { + case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; + case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; + case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; + case SENSOR_SMT50_MOIS: return UNIT_PERCENT; + case SENSOR_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; + case SENSOR_REMOTE: return sensor->unitid; + + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + + for (int i = 0; i < 100; i++) { + Sensor_t *sen = sensors; + while (sen) { + if (sen != sensor && sen->group > 0 && sen->group == sensor->nr) { + if (!sensor_isgroup(sen)) + return getSensorUnitId(sen); + sensor = sen; + break; + } + sen = sen->next; + } + } + + default: return UNIT_NONE; + } +} diff --git a/sensors.h b/sensors.h index 1b48ebce5..0b56690a3 100644 --- a/sensors.h +++ b/sensors.h @@ -52,6 +52,15 @@ #define SENSOR_READ_TIMEOUT 3000 //ms +#define MIN_DISK_FREE 8192 //8Kb min + +typedef struct SensorFlags { + uint enable:1; + uint log:1; + uint data_ok:1; + uint show:1; +} SensorFlags_t; + //Definition of a sensor typedef struct Sensor { uint nr; // 1..n sensor-nr, 0=deleted @@ -64,15 +73,14 @@ typedef struct Sensor { uint read_interval; // seconds uint32_t last_native_data; // last native sensor data double last_data; // last converted sensor data - byte enable:1; - byte log:1; - byte data_ok:1; - byte undef[32]; //for later + SensorFlags_t flags; // Flags see obove + byte undef[32]; //for later //unstored + byte unitid; ulong last_read; //millis Sensor *next; } Sensor_t; -#define SENSOR_STORE_SIZE (sizeof(Sensor_t)-sizeof(Sensor_t*)-sizeof(ulong)) +#define SENSOR_STORE_SIZE (sizeof(Sensor_t)-sizeof(Sensor_t*)-sizeof(ulong)-sizeof(byte)) //Definition of a log data typedef struct SensorLog { @@ -113,6 +121,22 @@ typedef struct ProgSensorAdjust { } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) +#define UNIT_NONE 0 +#define UNIT_PERCENT 1 +#define UNIT_DEGREE 2 +#define UNIT_FAHRENHEIT 3 +#define UNIT_VOLT 4 + +//Unitnames +static const char* sensor_unitNames[] { + "", "%", "°C", "°F", "V", +// 0 1 2 3 4 +}; + +const char* getSensorUnit(Sensor_t *sensor); +byte getSensorUnitId(int type); +byte getSensorUnitId(Sensor_t *sensor); + extern char ether_buffer[]; extern char tmp_buffer[]; @@ -121,10 +145,11 @@ uint16_t CRC16 (byte buf[], int len); //Sensor API functions: int sensor_delete(uint nr); -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, byte enabled, byte log); +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, SensorFlags_t flags); void sensor_load(); void sensor_save(); uint sensor_count(); +boolean sensor_isgroup(Sensor_t *sensor); void sensor_update_groups(); void read_all_sensors(); @@ -135,8 +160,8 @@ Sensor_t *sensor_by_idx(uint idx); int read_sensor(Sensor_t *sensor); //sensor value goes to last_native_data/last_data //Sensorlog API functions: -void sensorlog_add(SensorLog_t *sensorlog); -void sensorlog_add(Sensor_t *sensor, ulong time); +bool sensorlog_add(SensorLog_t *sensorlog); +bool sensorlog_add(Sensor_t *sensor, ulong time); void sensorlog_clear_all(); SensorLog_t *sensorlog_load(ulong pos); SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog); @@ -157,4 +182,7 @@ ProgSensorAdjust_t *prog_adjust_by_idx(uint idx); double calc_sensor_watering(uint prog); double calc_sensor_watering_by_nr(uint nr); +ulong diskFree(); +bool checkDiskFree(); //true: disk space Ok, false: Out of disk space + #endif // _SENSORS_H From 6c64ebba882e03638be89d0831be97663d63dbc1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 27 Dec 2022 15:01:58 +0100 Subject: [PATCH 026/281] Added effective program adjustment output for se added simple sensor type for 0..3,3V to 0..100% output --- opensprinkler_server.cpp | 16 +++++++++----- opensprinkler_server.h | 2 +- sensors.cpp | 46 ++++++++++++++++++++++++---------------- sensors.h | 5 +++-- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 83472af51..5eee93a27 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2216,8 +2216,8 @@ void server_sensorprog_config() { handle_return(res); } -void progconfig_json(ProgSensorAdjust_t *p) { - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E}"), +void progconfig_json(ProgSensorAdjust_t *p, double current) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E, \"current\":$E}"), p->nr, p->type, p->sensor, @@ -2225,14 +2225,16 @@ void progconfig_json(ProgSensorAdjust_t *p) { p->factor1, p->factor2, p->min, - p->max); + p->max, + current); } void progconfig_json() { uint count = prog_adjust_count(); for (uint i = 0; i < count; i++) { ProgSensorAdjust_t *p = prog_adjust_by_idx(i); - progconfig_json(p); + double current = calc_sensor_watering_by_nr(p->nr); + progconfig_json(p, current); if (i < count-1) bfill.emit_p(PSTR(",")); } @@ -2293,9 +2295,11 @@ void server_sensorprog_list() { if (prog >= 0 && p->prog != (uint)prog) continue; + double current = calc_sensor_watering_by_nr(p->nr); + if (count++ > 0) bfill.emit_p(PSTR(",")); - progconfig_json(p); + progconfig_json(p, current); // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { @@ -2310,6 +2314,7 @@ const int sensor_types[] = { SENSOR_SMT100_MODBUS_RTU_MOIS, SENSOR_SMT100_MODBUS_RTU_TEMP, SENSOR_ANALOG_EXTENSION_BOARD, + SENSOR_ANALOG_EXTENSION_BOARD_P, SENSOR_SMT50_MOIS, SENSOR_SMT50_TEMP, //SENSOR_OSPI_ANALOG_INPUTS, @@ -2324,6 +2329,7 @@ const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - voltage mode 0..4V", + "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - 0..3.3V to 0..100%", "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - SMT50 moisture mode", "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - SMT50 temperature mode", //"OSPi analog input", diff --git a/opensprinkler_server.h b/opensprinkler_server.h index feacd868d..a7c40125d 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -55,7 +55,7 @@ class BufferFiller { itoa(va_arg(ap, int), (char*) ptr, 10); // ray break; case 'E': //Double - sprintf((char*) ptr, "%f", va_arg(ap, double)); + sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); break; case 'L': //ltoa(va_arg(ap, long), (char*) ptr, 10); diff --git a/sensors.cpp b/sensors.cpp index d329714af..0272262ff 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -90,6 +90,8 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; + if (ri < 10) + ri = 10; Sensor_t *sensor = sensors; Sensor_t *last = NULL; @@ -313,11 +315,16 @@ int read_sensor_adc(Sensor_t *sensor) { sensor->last_native_data = adc.readADC(sensor->id); sensor->last_data = adc.toVoltage(sensor->last_native_data); - if (sensor->type == SENSOR_SMT50_MOIS) { // SMT50 VWC [%] = (U * 50) : 3 - sensor->last_data = (sensor->last_data * 50) / 3; - } - if (sensor->type == SENSOR_SMT50_TEMP) { // SMT50 T [°C] = (U – 0,5) * 100 - sensor->last_data = (sensor->last_data - 0.5) * 100; + switch(sensor->type) { + case SENSOR_SMT50_MOIS: // SMT50 VWC [%] = (U * 50) : 3 + sensor->last_data = (sensor->last_data * 50.0) / 3.0; + break; + case SENSOR_SMT50_TEMP: // SMT50 T [°C] = (U – 0,5) * 100 + sensor->last_data = (sensor->last_data - 0.5) * 100.0; + break; + case SENSOR_ANALOG_EXTENSION_BOARD_P: // 0..3,3V -> 0..100% + sensor->last_data = sensor->last_data * 100.0 / 3.3; + break; } sensor->flags.data_ok = true; @@ -531,6 +538,7 @@ int read_sensor(Sensor_t *sensor) { return read_sensor_ip(sensor); case SENSOR_ANALOG_EXTENSION_BOARD: + case SENSOR_ANALOG_EXTENSION_BOARD_P: case SENSOR_SMT50_MOIS: //SMT50 VWC [%] = (U * 50) : 3 case SENSOR_SMT50_TEMP: //SMT50 T [°C] = (U – 0,5) * 100 return read_sensor_adc(sensor); @@ -948,12 +956,13 @@ boolean sensor_isgroup(Sensor_t *sensor) { byte getSensorUnitId(int type) { switch(type) { - case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; - case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; - case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; - case SENSOR_SMT50_MOIS: return UNIT_PERCENT; - case SENSOR_SMT50_TEMP: return UNIT_DEGREE; - case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; + case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; + case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; + case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; + case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; + case SENSOR_SMT50_MOIS: return UNIT_PERCENT; + case SENSOR_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; default: return UNIT_NONE; } @@ -964,13 +973,14 @@ byte getSensorUnitId(Sensor_t *sensor) { return 0; switch(sensor->type) { - case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; - case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; - case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; - case SENSOR_SMT50_MOIS: return UNIT_PERCENT; - case SENSOR_SMT50_TEMP: return UNIT_DEGREE; - case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; - case SENSOR_REMOTE: return sensor->unitid; + case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; + case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; + case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; + case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; + case SENSOR_SMT50_MOIS: return UNIT_PERCENT; + case SENSOR_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; + case SENSOR_REMOTE: return sensor->unitid; case SENSOR_GROUP_MIN: case SENSOR_GROUP_MAX: diff --git a/sensors.h b/sensors.h index 0b56690a3..118be0346 100644 --- a/sensors.h +++ b/sensors.h @@ -40,8 +40,9 @@ #define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode #define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode #define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 - voltage mode 0..4V -#define SENSOR_SMT50_MOIS 11 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 VWC [%] = (U * 50) : 3 -#define SENSOR_SMT50_TEMP 12 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 T [°C] = (U – 0,5) * 100 +#define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board 2xADS1015 48/49 - percent 0..3.3V to 0..100% +#define SENSOR_SMT50_MOIS 15 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_SMT50_TEMP 16 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 T [°C] = (U – 0,5) * 100 #define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input #define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler From 092b0bec06bfa2672b636977a3071a8bdcd3fffb Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 28 Dec 2022 15:22:00 +0100 Subject: [PATCH 027/281] Fixt sensor timeout issue --- sensors.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 0272262ff..a0843a017 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -398,6 +398,12 @@ int read_sensor_ip(Sensor_t *sensor) { EthernetClient *client = ðerClient; #endif + sensor->flags.data_ok = false; + if (!sensor->ip || !sensor->port) { + sensor->flags.enable = false; + return HTTP_RQT_CONNECT_ERR; + } + IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; @@ -453,10 +459,11 @@ int read_sensor_ip(Sensor_t *sensor) { client->write(buffer, len); client->flush(); + uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; while (true) { if (client->available()) break; - if (os.now_tz() > sensor->last_read+SENSOR_READ_TIMEOUT) { + if (millis() >= stoptime) { client->stop(); DEBUG_PRINT(F("Sensor ")); DEBUG_PRINT(sensor->nr); @@ -638,10 +645,15 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { EthernetClient etherClient; EthernetClient *client = ðerClient; #endif + sensor->flags.data_ok = false; + if (!sensor->ip || !sensor->port) { + sensor->flags.enable = false; + return HTTP_RQT_CONNECT_ERR; + } IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; - + if(!client->connect(ip, sensor->port)) { DEBUG_PRINT(F("Cannot connect to ")); DEBUG_PRINT(_ip[0]); DEBUG_PRINT("."); From 66c3d3020766b300fff5b1187eefe802b11200f3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 13 Jan 2023 00:59:55 +0100 Subject: [PATCH 028/281] Analog Sensor API V1.0 Release --- I2CRTC.cpp | 39 +- LiquidCrystal.cpp | 96 +-- LiquidCrystal.h | 18 +- OpenSprinkler.cpp | 738 ++++++++++------- OpenSprinkler.h | 202 +++-- README.txt | 12 - SSD1306Display.h | 12 +- Sensor API.txt | 36 +- TimeLib.cpp | 118 +-- TimeLib.h | 60 +- build.sh | 9 +- defines.h | 122 +-- espconnect.cpp | 53 +- espconnect.h | 4 +- gpio.cpp | 40 +- gpio.h | 15 +- html/ap_home.html | 76 +- html/ap_update.html | 5 +- html/sta_update.html | 7 +- htmls.h | 78 +- images.h | 36 +- main.cpp | 729 ++++++++-------- mainArduino.ino | 20 - make.lin302 | 23 +- make.lin32 | 2 +- make.os23 | 2 +- mqtt.cpp | 94 ++- opensprinkler_server.cpp | 1693 +++++++++++++++++++++----------------- opensprinkler_server.h | 12 +- platformio.ini | 23 +- program.cpp | 86 +- program.h | 109 +-- sensors.cpp | 11 +- sensors.h | 10 +- utils.cpp | 206 ++--- utils.h | 16 +- weather.cpp | 70 +- weather.h | 19 +- 38 files changed, 2653 insertions(+), 2248 deletions(-) diff --git a/I2CRTC.cpp b/I2CRTC.cpp index 6a7882734..f3e1f9edf 100644 --- a/I2CRTC.cpp +++ b/I2CRTC.cpp @@ -58,7 +58,7 @@ bool I2CRTC::detect() } // PUBLIC FUNCTIONS -time_t I2CRTC::get() // Aquire data from buffer and convert to time_t +time_t I2CRTC::get() // Aquire data from buffer and convert to time_t { tmElements_t tm; read(tm); @@ -69,9 +69,6 @@ void I2CRTC::set(time_t t) { tmElements_t tm; breakTime(t, tm); - //tm.Second |= 0x80; // stop the clock ray: removed this step - //write(tm); - //tm.Second &= 0x7f; // start the clock ray: moved to write function write(tm); } @@ -83,25 +80,25 @@ void I2CRTC::read( tmElements_t &tm) if(addr == PCF8563_ADDR) { Wire.write((uint8_t)0x02); - Wire.endTransmission(); + Wire.endTransmission(); Wire.requestFrom(addr, (uint8_t)7); tm.Second = bcd2dec(Wire.read() & 0x7f); tm.Minute = bcd2dec(Wire.read() & 0x7f); - tm.Hour = bcd2dec(Wire.read() & 0x3f); // mask assumes 24hr clock + tm.Hour = bcd2dec(Wire.read() & 0x3f); // mask assumes 24hr clock tm.Day = bcd2dec(Wire.read() & 0x3f); tm.Wday = bcd2dec(Wire.read() & 0x07); - tm.Month = bcd2dec(Wire.read() & 0x1f); // fix bug for MCP7940 + tm.Month = bcd2dec(Wire.read() & 0x1f); // fix bug for MCP7940 tm.Year = (bcd2dec(Wire.read())); } else { Wire.write((uint8_t)0x00); - Wire.endTransmission(); + Wire.endTransmission(); Wire.requestFrom(addr, (uint8_t)7); tm.Second = bcd2dec(Wire.read() & 0x7f); tm.Minute = bcd2dec(Wire.read() & 0x7f); - tm.Hour = bcd2dec(Wire.read() & 0x3f); // mask assumes 24hr clock + tm.Hour = bcd2dec(Wire.read() & 0x3f); // mask assumes 24hr clock tm.Wday = bcd2dec(Wire.read() & 0x07); tm.Day = bcd2dec(Wire.read() & 0x3f); - tm.Month = bcd2dec(Wire.read() & 0x1f); // fix bug for MCP7940 + tm.Month = bcd2dec(Wire.read() & 0x1f); // fix bug for MCP7940 tm.Year = y2kYearToTm((bcd2dec(Wire.read()))); } } @@ -112,11 +109,11 @@ void I2CRTC::write(tmElements_t &tm) switch(addr) { case DS1307_ADDR: Wire.beginTransmission(addr); - Wire.write((uint8_t)0x00); // reset register pointer + Wire.write((uint8_t)0x00); // reset register pointer Wire.write(dec2bcd(tm.Second) & 0x7f); // ray: start clock by setting CH bit low Wire.write(dec2bcd(tm.Minute)); - Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format - Wire.write(dec2bcd(tm.Wday)); + Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format + Wire.write(dec2bcd(tm.Wday)); Wire.write(dec2bcd(tm.Day)); Wire.write(dec2bcd(tm.Month)); Wire.write(dec2bcd(tmYearToY2k(tm.Year))); @@ -124,11 +121,11 @@ void I2CRTC::write(tmElements_t &tm) break; case MCP7940_ADDR: Wire.beginTransmission(addr); - Wire.write((uint8_t)0x00); // reset register pointer - Wire.write(dec2bcd(tm.Second) | 0x80); // ray: start clock by setting ST bit high + Wire.write((uint8_t)0x00); // reset register pointer + Wire.write(dec2bcd(tm.Second) | 0x80); // ray: start clock by setting ST bit high Wire.write(dec2bcd(tm.Minute)); - Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format - Wire.write(dec2bcd(tm.Wday) | 0x08); // ray: turn on battery backup by setting VBATEN bit high + Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format + Wire.write(dec2bcd(tm.Wday) | 0x08); // ray: turn on battery backup by setting VBATEN bit high Wire.write(dec2bcd(tm.Day)); Wire.write(dec2bcd(tm.Month)); Wire.write(dec2bcd(tmYearToY2k(tm.Year))); @@ -136,17 +133,17 @@ void I2CRTC::write(tmElements_t &tm) break; case PCF8563_ADDR: Wire.beginTransmission(addr); - Wire.write((uint8_t)0x02); // reset register pointer + Wire.write((uint8_t)0x02); // reset register pointer Wire.write(dec2bcd(tm.Second) & 0x7f); Wire.write(dec2bcd(tm.Minute)); - Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format + Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format Wire.write(dec2bcd(tm.Day)); - Wire.write(dec2bcd(tm.Wday)); + Wire.write(dec2bcd(tm.Wday)); Wire.write(dec2bcd(tm.Month)); Wire.write(dec2bcd(tm.Year)); Wire.endTransmission(); break; - } + } } // Convert Decimal to Binary Coded Decimal (BCD) diff --git a/LiquidCrystal.cpp b/LiquidCrystal.cpp index 42a150b32..85adc0ea7 100644 --- a/LiquidCrystal.cpp +++ b/LiquidCrystal.cpp @@ -8,21 +8,21 @@ // 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 +// 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). +// LiquidCrystal constructor is called) void LiquidCrystal::begin() { if (_type == LCD_I2C) { @@ -31,7 +31,7 @@ void LiquidCrystal::begin() { // 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); + delay(50); // Now we pull both RS and R/W low to begin commands expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1) @@ -50,32 +50,32 @@ void LiquidCrystal::begin() { delayMicroseconds(4500); // wait min 4.1ms // third go! - write4bits(0x03 << 4); + write4bits(0x03 << 4); delayMicroseconds(150); // finally, set to 4-bit interface - write4bits(0x02 << 4); + write4bits(0x02 << 4); // set # lines, font size, etc. - command(LCD_FUNCTIONSET | _displayfunction); - + 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) { + + if (_type == LCD_STD) { _displayfunction |= LCD_2LINE; _numlines = 2; _currline = 0; @@ -83,14 +83,14 @@ void LiquidCrystal::begin() { // 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); + 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) { + 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 @@ -103,13 +103,13 @@ void LiquidCrystal::begin() { // second try write4bits(0x03); delayMicroseconds(4500); // wait min 4.1ms - + // third go! - write4bits(0x03); + write4bits(0x03); delayMicroseconds(150); // finally, set to 4-bit interface - write4bits(0x02); + write4bits(0x02); } else { // this is according to the hitachi HD44780 datasheet // page 45 figure 23 @@ -127,10 +127,10 @@ void LiquidCrystal::begin() { } // finally, set # lines, font size, etc. - command(LCD_FUNCTIONSET | _displayfunction); + command(LCD_FUNCTIONSET | _displayfunction); // turn the display on with no cursor or blinking default - _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; + _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; display(); // clear it off @@ -150,16 +150,16 @@ void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t en _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[3] = d3; _data_pins[4] = d4; _data_pins[5] = d5; _data_pins[6] = d6; - _data_pins[7] = d7; - + _data_pins[7] = d7; + Wire.begin(); _type = LCD_STD; @@ -171,7 +171,7 @@ void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t en //Wire.write(0x00); uint8_t ret2 = Wire.endTransmission(); - if (!ret1 || !ret2) _type = LCD_I2C; + if (!ret1 || !ret2) _type = LCD_I2C; if (_type == LCD_I2C) { if(!ret1) _addr = LCD_I2C_ADDR1; else _addr = LCD_I2C_ADDR2; @@ -179,16 +179,16 @@ void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t en _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) { + if (_rw_pin != 255) { pinMode(_rw_pin, OUTPUT); } pinMode(_enable_pin, OUTPUT); - + } _displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; @@ -219,7 +219,7 @@ void LiquidCrystal::setCursor(uint8_t col, uint8_t row) if (row >= _numlines) { row = _numlines-1; } - } + } command(LCD_SETDDRAMADDR | (col + row_offsets[row])); } @@ -327,16 +327,16 @@ void LiquidCrystal::send(uint8_t value, uint8_t mode) { uint8_t highnib=value&0xf0; uint8_t lownib=(value<<4)&0xf0; write4bits((highnib)|mode); - write4bits((lownib)|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) { + if (_rw_pin != 255) { digitalWrite(_rw_pin, LOW); } - + write4bits(value>>4); write4bits(value); } @@ -357,23 +357,23 @@ void LiquidCrystal::write4bits(uint8_t value) { } } -void LiquidCrystal::expanderWrite(uint8_t _data){ +void LiquidCrystal::expanderWrite(uint8_t _data){ Wire.beginTransmission(_addr); Wire.write((int)(_data) | _backlightval); - Wire.endTransmission(); + 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); + delayMicroseconds(1); digitalWrite(_enable_pin, HIGH); delayMicroseconds(1); // enable pulse must be >450ns digitalWrite(_enable_pin, LOW); diff --git a/LiquidCrystal.h b/LiquidCrystal.h index 1b9d55779..89ee10ec9 100644 --- a/LiquidCrystal.h +++ b/LiquidCrystal.h @@ -48,12 +48,12 @@ #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 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_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 @@ -85,15 +85,15 @@ class LiquidCrystal : public Print { //void createChar(uint8_t, uint8_t[]); void createChar(uint8_t, PGM_P ptr); - void setCursor(uint8_t, uint8_t); + 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; + + using Print::write; private: void send(uint8_t, uint8_t); void write4bits(uint8_t); @@ -107,7 +107,7 @@ class LiquidCrystal : public Print { uint8_t _charsize; uint8_t _backlightval; - uint8_t _type; // LCD type. 0: standard; 1: I2C + 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. diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 33175b48c..8d02f7a00 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -25,16 +25,16 @@ #include "opensprinkler_server.h" #include "gpio.h" #include "testmode.h" -#include "images.h" +#include "program.h" /** Declare static data members */ OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; ConStatus OpenSprinkler::status; ConStatus OpenSprinkler::old_status; + byte OpenSprinkler::hw_type; byte OpenSprinkler::hw_rev; - byte OpenSprinkler::nboards; byte OpenSprinkler::nstations; byte OpenSprinkler::station_bits[MAX_NUM_BOARDS]; @@ -48,6 +48,7 @@ ulong OpenSprinkler::sensor2_on_timer; ulong OpenSprinkler::sensor2_off_timer; ulong OpenSprinkler::sensor2_active_lasttime; ulong OpenSprinkler::raindelay_on_lasttime; +ulong OpenSprinkler::pause_timer; ulong OpenSprinkler::flowcount_log_start; ulong OpenSprinkler::flowcount_rt; @@ -65,11 +66,13 @@ byte OpenSprinkler::attrib_mas2[MAX_NUM_BOARDS]; byte OpenSprinkler::attrib_igs2[MAX_NUM_BOARDS]; byte OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; byte OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_seq[MAX_NUM_BOARDS]; byte OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_grp[MAX_NUM_STATIONS]; +byte OpenSprinkler::masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; extern char tmp_buffer[]; extern char ether_buffer[]; +extern ProgramData pd; #if defined(ESP8266) SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); @@ -79,9 +82,12 @@ extern char ether_buffer[]; IOEXP* OpenSprinkler::mainio; // main controller IO expander object IOEXP* OpenSprinkler::drio; // driver board IO expander object RCSwitch OpenSprinkler::rfswitch; + OTCConfig OpenSprinkler::otc; String OpenSprinkler::wifi_ssid=""; String OpenSprinkler::wifi_pass=""; + byte OpenSprinkler::wifi_bssid[6]={0}; + byte OpenSprinkler::wifi_channel=255; byte OpenSprinkler::wifi_testmode = 0; #elif defined(ARDUINO) LiquidCrystal OpenSprinkler::lcd; @@ -165,23 +171,7 @@ const char iopt_json_names[] PROGMEM = "reset" ; -// for String options -/* -const char sopt_json_names[] PROGMEM = - "dkey\0" - "loc\0\0" - "jsp\0\0" - "wsp\0\0" - "wtkey" - "wto\0\0" - "ifkey" - "ssid\0" - "pass\0" - "mqtt\0" - "apass"; -*/ - -/** Option promopts (stored in PROGMEM to reduce RAM usage) */ +/** Option prompts (stored in PROGMEM to reduce RAM usage) */ // Each string is strictly 16 characters // with SPACE fillings if less const char iopt_prompts[] PROGMEM = @@ -326,17 +316,17 @@ const byte iopt_max[] PROGMEM = { byte OpenSprinkler::iopts[] = { OS_FW_VERSION, // firmware version 28, // default time zone: GMT-5 - 1, // 0: disable NTP sync, 1: enable NTP sync - 1, // 0: use static ip, 1: use dhcp - 0, // this and next 3 bytes define static ip + 1, // 0: disable NTP sync, 1: enable NTP sync + 1, // 0: use static ip, 1: use dhcp + 0, // this and next 3 bytes define static ip 0, 0, 0, - 0, // this and next 3 bytes define static gateway ip + 0, // this and next 3 bytes define static gateway ip 0, 0, 0, -#if defined(ARDUINO) // on AVR, the default HTTP port is 80 +#if defined(ARDUINO) // on AVR, the default HTTP port is 80 80, // this and next byte define http port number 0, #else // on RPI/BBB/LINUX, the default HTTP port is 8080 @@ -344,55 +334,55 @@ byte OpenSprinkler::iopts[] = { 31, #endif OS_HW_VERSION, - 0, // number of 8-station extension board. 0: no extension boards - 1, // the option 'sequential' is now retired + 0, // number of 8-station extension board. 0: no extension boards + 1, // the option 'sequential' is now retired 120,// station delay time (-10 minutes to 10 minutes). - 0, // index of master station. 0: no master station + 0, // index of master station. 0: no master station 120,// master on time adjusted time (-10 minutes to 10 minutes) 120,// master off adjusted time (-10 minutes to 10 minutes) - 0, // urs (retired) - 0, // rso (retired) + 0, // urs (retired) + 0, // rso (retired) 100,// water level (default 100%), - 1, // device enable - 0, // 1: ignore password; 0: use password - 0, // device id + 1, // device enable + 0, // 1: ignore password; 0: use password + 0, // device id 150,// lcd contrast 100,// lcd backlight - 50, // lcd dimming + 15, // lcd dimming 80, // boost time (only valid to DC and LATCH type) - 0, // weather algorithm (0 means not using weather algorithm) + 0, // weather algorithm (0 means not using weather algorithm) 0, // this and the next three bytes define the ntp server ip 0, 0, 0, - 1, // enable logging: 0: disable; 1: enable. - 0, // index of master2. 0: no master2 station + 1, // enable logging: 0: disable; 1: enable. + 0, // index of master2. 0: no master2 station 120,// master2 on adjusted time 120,// master2 off adjusted time OS_FW_MINOR, // firmware minor version 100,// this and next byte define flow pulse rate (100x) - 0, // default is 1.00 (100) - 0, // set as remote extension - 8, // this and the next three bytes define the custom dns server ip + 0, // default is 1.00 (100) + 0, // set as remote extension + 8, // this and the next three bytes define the custom dns server ip 8, 8, 8, - 0, // special station auto refresh - 0, // ifttt enable bits - 0, // sensor 1 type (see SENSOR_TYPE macro defines) - 1, // sensor 1 option. 0: normally closed; 1: normally open. default 1. - 0, // sensor 2 type - 1, // sensor 2 option. 0: normally closed; 1: normally open. default 1. - 0, // sensor 1 on delay - 0, // sensor 1 off delay - 0, // sensor 2 on delay - 0, // sensor 2 off delay + 0, // special station auto refresh + 0, // ifttt enable bits + 0, // sensor 1 type (see SENSOR_TYPE macro defines) + 1, // sensor 1 option. 0: normally closed; 1: normally open. default 1. + 0, // sensor 2 type + 1, // sensor 2 option. 0: normally closed; 1: normally open. default 1. + 0, // sensor 1 on delay + 0, // sensor 1 off delay + 0, // sensor 2 on delay + 0, // sensor 2 off delay 255,// subnet mask 1 255,// subnet mask 2 255,// subnet mask 3 0, WIFI_MODE_AP, // wifi mode - 0 // reset + 0 // reset }; /** String option values (stored in RAM) */ @@ -401,15 +391,14 @@ const char *OpenSprinkler::sopts[] = { DEFAULT_LOCATION, DEFAULT_JAVASCRIPT_URL, DEFAULT_WEATHER_URL, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING, - DEFAULT_EMPTY_STRING + DEFAULT_EMPTY_STRING, // SOPT_WEATHER_OPTS + DEFAULT_EMPTY_STRING, // SOPT_IFTTT_KEY + DEFAULT_EMPTY_STRING, // SOPT_STA_SSID + DEFAULT_EMPTY_STRING, // SOPT_STA_PASS + DEFAULT_EMPTY_STRING, // SOPT_MQTT_OPTS + DEFAULT_EMPTY_STRING, // SOPT_OTC_OPTS + DEFAULT_DEVICE_NAME, + DEFAULT_EMPTY_STRING, // SOPT_STA_BSSID_CHL }; /** Weekday strings (stored in PROGMEM to reduce RAM usage) */ @@ -427,7 +416,7 @@ time_t OpenSprinkler::now_tz() { return now()+(int32_t)3600/4*(int32_t)(iopts[IOPT_TIMEZONE]-48); } -#if defined(ARDUINO) // AVR network init functions +#if defined(ARDUINO) bool detect_i2c(int addr) { Wire.beginTransmission(addr); @@ -470,89 +459,77 @@ void(* resetFunc) (void) = 0; // AVR software reset function byte 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(m_server) { delete m_server; m_server = NULL; } -#endif - - if (start_ether()) { #if defined(ESP8266) - if(w_server) { delete w_server; w_server = NULL; } - w_server = new ESP8266WebServer(httpport); - useEth = true; -#else // AVR - m_server = new EthernetServer(httpport); - m_server->begin(); + + if (start_ether()) { useEth = true; -#endif + } else { + useEth = false; + } - //udp = new EthernetUDP(); - //udp->begin((httpport==8000) ? 8888 : 8000); // start udp on a different port than httpport + if((useEth || get_wifi_mode()==WIFI_MODE_STA) && otc.en>0 && otc.token.length()>=32) { + otf = new OTF::OpenThingsFramework(httpport, otc.server, otc.port, otc.token, false, ether_buffer, ETHER_BUFFER_SIZE); + DEBUG_PRINTLN(F("Started OTF with remote connection")); + } else { + otf = new OTF::OpenThingsFramework(httpport, ether_buffer, ETHER_BUFFER_SIZE); + DEBUG_PRINTLN(F("Started OTF with just local connection")); + } + extern DNSServer *dns; + if(get_wifi_mode() == WIFI_MODE_AP) dns = new DNSServer(); + if(update_server) { delete update_server; update_server = NULL; } + update_server = new ESP8266WebServer(8080); + DEBUG_PRINT(F("Started update server")); + return 1; -//#if defined(ESP8266) - // turn off WiFi when ether is active - // todo: add option to keep both ether and wifi active - // lwip: WiFi.mode(WIFI_OFF); -//#endif +#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 { - -#if defined(ESP8266) - if(w_server) { delete w_server; w_server = NULL; } - if(get_wifi_mode()==WIFI_MODE_AP) { - w_server = new ESP8266WebServer(80); - } else { - w_server = new ESP8266WebServer(httpport); - } - - //udp = new WiFiUDP(); - //udp->begin((httpport==8000) ? 8888 : 8000); // start udp on a different port than httpport - - return 1; -#endif - + useEth = false; + return 0; } - return 0; +#endif } byte OpenSprinkler::start_ether() { #if defined(ESP8266) - if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 + if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); SPI.setFrequency(4000000); - eth.setDefault(); load_hardware_mac((uint8_t*)tmp_buffer, true); - - if (!iopts[IOPT_USE_DHCP]) { + if (iopts[IOPT_USE_DHCP]==0) { // config static IP before calling eth.begin IPAddress staticip(iopts+IOPT_STATIC_IP1); IPAddress gateway(iopts+IOPT_GATEWAY_IP1); IPAddress dns(iopts+IOPT_DNS_IP1); IPAddress subn(iopts+IOPT_SUBNET_MASK1); eth.config(staticip, gateway, subn, dns); } - + eth.setDefault(); if(!eth.begin((uint8_t*)tmp_buffer)) return 0; - lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); - // todo: lwip add timeout - int n = iopts[IOPT_USE_DHCP]?30:2; - while (!eth.connected() && n-- >0) { - DEBUG_PRINT("."); - delay(1000); - } - - DEBUG_PRINTLN(); - DEBUG_PRINT("eth.ip:"); - DEBUG_PRINTLN(eth.localIP()); - DEBUG_PRINT("eth.dns:"); + ulong timeout = millis()+30000; // 30 seconds time out + while (!eth.connected()) { + DEBUG_PRINT("."); + delay(1000); + if(millis()>timeout) return 0; + } + + DEBUG_PRINTLN(); + DEBUG_PRINT("eth.ip:"); + DEBUG_PRINTLN(eth.localIP()); + DEBUG_PRINT("eth.dns:"); DEBUG_PRINTLN(WiFi.dnsIP()); if (iopts[IOPT_USE_DHCP]) { @@ -566,7 +543,7 @@ byte OpenSprinkler::start_ether() { return 1; #else - Ethernet.init(PIN_ETHER_CS); // make sure to call this before any Ethernet calls + 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); @@ -593,7 +570,7 @@ byte OpenSprinkler::start_ether() { bool OpenSprinkler::network_connected(void) { #if defined (ESP8266) - if(useEth) return eth.connected(); // todo: fix this + if(useEth) return true; // todo: lwip currently does not have a way to check link status else return (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && state==OS_STATE_CONNECTED); #else @@ -610,7 +587,6 @@ void OpenSprinkler::reboot_dev(uint8_t cause) { } #if defined(ESP8266) ESP.restart(); - //ESP.reset(); #else resetFunc(); #endif @@ -628,18 +604,18 @@ void OpenSprinkler::reboot_dev(uint8_t cause) { /** Initialize network with the given mac address and http port */ byte OpenSprinkler::start_network() { -#if defined(ESP8266) unsigned int port = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; #if defined(DEMO) +#if defined(HTTP_PORT) + port = HTTP_PORT; +#else port = 80; +#endif #endif if(m_server) { delete m_server; m_server = 0; } m_server = new EthernetServer(port); return m_server->begin(); -#else - return 1; -#endif } bool OpenSprinkler::network_connected(void) { @@ -649,7 +625,6 @@ bool OpenSprinkler::network_connected(void) { // 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(byte* mac, bool wired) { -#if defined(ESP8266) const char * if_names[] = { "eth0", "eth1", "wlan0", "wlan1" }; struct ifreq ifr; int fd; @@ -675,7 +650,6 @@ bool OpenSprinkler::load_hardware_mac(byte* mac, bool wired) { } } close(fd); -#endif return true; } @@ -694,7 +668,7 @@ void OpenSprinkler::reboot_dev(uint8_t cause) { /** Launch update script */ void OpenSprinkler::update_dev() { char cmd[1000]; - sprintf(cmd, "cd %s & ./updater.sh", get_runtime_path()); + sprintf(cmd, "cd %s && ./updater.sh", get_runtime_path()); system(cmd); } #endif // end network init functions @@ -740,7 +714,7 @@ void OpenSprinkler::begin() { hw_type = HW_TYPE_UNKNOWN; hw_rev = 0; -#if defined(ESP8266) +#if defined(ESP8266) // ESP8266 specific initializations /* check hardware type */ if(detect_i2c(ACDR_I2CADDR)) hw_type = HW_TYPE_AC; @@ -831,6 +805,7 @@ void OpenSprinkler::begin() { for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) expanders[i] = NULL; detect_expanders(); + #else // shift register setup @@ -851,7 +826,7 @@ void OpenSprinkler::begin() { // if this is revision 1, use PIN_SR_DATA_ALT pinMode(pin_sr_data, OUTPUT); #else - pinMode(PIN_SR_DATA, OUTPUT); + pinMode(PIN_SR_DATA, OUTPUT); #endif #endif @@ -883,8 +858,8 @@ void OpenSprinkler::begin() { old_status = status; - nvdata.sunrise_time = 360; // 6:00am default sunrise - nvdata.sunset_time = 1080; // 6:00pm default sunset + nvdata.sunrise_time = 360; // 6:00am default sunrise + nvdata.sunset_time = 1080; // 6:00pm default sunset nvdata.reboot_cause = REBOOT_CAUSE_POWERON; nboards = 1; @@ -894,11 +869,11 @@ void OpenSprinkler::begin() { pinModeExt(PIN_RFTX, OUTPUT); digitalWriteExt(PIN_RFTX, LOW); -#if defined(ARDUINO) // AVR SD and LCD functions +#if defined(ARDUINO) // AVR SD and LCD functions - #if defined(ESP8266) // OS3.0 specific detections + #if defined(ESP8266) // OS3.0 specific detections - status.has_curr_sense = 1; // OS3.0 has current sensing capacility + status.has_curr_sense = 1; // OS3.0 has current sensing capacility // measure baseline current baseline_current = 80; @@ -934,12 +909,16 @@ void OpenSprinkler::begin() { baseline_current = 0; #endif - lcd_start(); - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); + #if defined(ESP8266) + lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); + lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); + #else + lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_connected); + lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_disconnected); + #endif lcd.createChar(ICON_REMOTEXT, _iconimage_remotext); lcd.createChar(ICON_RAINDELAY, _iconimage_raindelay); lcd.createChar(ICON_RAIN, _iconimage_rain); @@ -997,25 +976,25 @@ void OpenSprinkler::begin() { * */ void OpenSprinkler::latch_boost() { - digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter - delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge - digitalWriteExt(PIN_BOOST, LOW); // disable boost converter + digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter + delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge + digitalWriteExt(PIN_BOOST, LOW); // disable boost converter } /** Set all zones (for LATCH controller) - * This function sets all zone pins (including COM) to a specified value + * This function sets all zone pins (including COM) to a specified value */ void OpenSprinkler::latch_setallzonepins(byte value) { - digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin + digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin // Handle driver board (on main controller) if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board else reg &= 0xFF00; drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register } // Handle all expansion boards - for(byte i=0;itype==IOEXP_TYPE_9555) { expanders[i]->i2c_write(NXP_OUTPUT_REG, value?0xFFFF:0x0000); } @@ -1035,21 +1014,21 @@ void OpenSprinkler::latch_disable_alloutputs_v2() { } /** Set one zone (for LATCH controller) - * This function sets one specified zone pin to a specified value + * This function sets one specified zone pin to a specified value */ void OpenSprinkler::latch_setzonepin(byte sid, byte value) { if(sid<8) { // on main controller if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); // write value to register + drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register } - } else { // on expander + } else { // on expander byte bid=(sid-8)>>4; uint16_t s=(sid-8)&0x0F; if(expanders[bid]->type==IOEXP_TYPE_9555) { - uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value + uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); @@ -1090,13 +1069,13 @@ void OpenSprinkler::latch_open(byte sid) { digitalWriteExt(PIN_BOOST_EN, LOW); // disabled output boosted voltage path latch_disable_alloutputs_v2(); // disable all output pins } else { - latch_boost(); // boost voltage - latch_setallzonepins(HIGH); // set all switches to HIGH, including COM + latch_boost(); // boost voltage + latch_setallzonepins(HIGH); // set all switches to HIGH, including COM latch_setzonepin(sid, LOW); // set the specified switch to LOW delay(1); // delay 1 ms for all gates to stablize digitalWriteExt(PIN_BOOST_EN, HIGH); // dump boosted voltage - delay(100); // for 100ms - latch_setzonepin(sid, HIGH); // set the specified switch back to HIGH + delay(100); // for 100ms + latch_setzonepin(sid, HIGH); // set the specified switch back to HIGH digitalWriteExt(PIN_BOOST_EN, LOW); // disable boosted voltage } } @@ -1113,15 +1092,15 @@ void OpenSprinkler::latch_close(byte sid) { digitalWriteExt(PIN_BOOST_EN, LOW); // disable output boosted voltage path latch_disable_alloutputs_v2(); // disable all output pins } else { - latch_boost(); // boost voltage - latch_setallzonepins(LOW); // set all switches to LOW, including COM + latch_boost(); // boost voltage + latch_setallzonepins(LOW); // set all switches to LOW, including COM latch_setzonepin(sid, HIGH);// set the specified switch to HIGH delay(1); // delay 1 ms for all gates to stablize digitalWriteExt(PIN_BOOST_EN, HIGH); // dump boosted voltage - delay(100); // for 100ms - latch_setzonepin(sid, LOW); // set the specified switch back to LOW + delay(100); // for 100ms + latch_setzonepin(sid, LOW); // set the specified switch back to LOW digitalWriteExt(PIN_BOOST_EN, LOW); // disable boosted voltage - latch_setallzonepins(HIGH); // set all switches back to HIGH + latch_setallzonepins(HIGH); // set all switches back to HIGH } } @@ -1163,10 +1142,10 @@ void OpenSprinkler::apply_all_station_bits() { if(hw_type==HW_TYPE_DC && engage_booster) { // for DC controller: boost voltage and enable output path digitalWriteExt(PIN_BOOST_EN, LOW); // disfable output path - digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter - delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge - digitalWriteExt(PIN_BOOST, LOW); // disable boost converter - digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path + digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter + delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge + digitalWriteExt(PIN_BOOST, LOW); // disable boost converter + digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path engage_booster = 0; } @@ -1176,7 +1155,7 @@ void OpenSprinkler::apply_all_station_bits() { drio->i2c_write(NXP_OUTPUT_REG, ~station_bits[0]); } else if(drio->type==IOEXP_TYPE_9555) { /* revision 1 uses PCA9555 with active high logic */ - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value reg = (reg&0xFF00) | station_bits[0]; // output channels are the low 8-bit drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register } @@ -1220,12 +1199,12 @@ void OpenSprinkler::apply_all_station_bits() { #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, 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_BOOST_EN, HIGH); // enable output path digitalWrite(PIN_SR_LATCH, HIGH); engage_booster = 0; } else { @@ -1236,19 +1215,31 @@ void OpenSprinkler::apply_all_station_bits() { #endif #endif - if(iopts[IOPT_SPE_AUTO_REFRESH]) { // handle refresh of RF and remote stations // we refresh the station that's next in line static byte next_sid_to_refresh = MAX_NUM_STATIONS>>1; static byte lastnow = 0; - byte _now = (now() & 0xFF); - if (lastnow != _now) { // perform this no more than once per second + ulong curr_time = now_tz(); + byte _now = (curr_time & 0xFF); + if (lastnow != _now) { // perform this no more than once per second lastnow = _now; next_sid_to_refresh = (next_sid_to_refresh+1) % MAX_NUM_STATIONS; - bid=next_sid_to_refresh>>3; - s=next_sid_to_refresh&0x07; - switch_special_station(next_sid_to_refresh, (station_bits[bid]>>s)&0x01); + byte bid=next_sid_to_refresh>>3,s=next_sid_to_refresh&0x07; + if(os.attrib_spe[bid]&(1<>3; + s=next_sid_to_refresh&0x07; + bool on = (station_bits[bid]>>s)&0x01; + uint16_t dur = 0; + if(on) { + byte sqi=pd.station_qid[next_sid_to_refresh]; + RuntimeQueueStruct *q=pd.queue+sqi; + if(sqi<255 && q->st>0 && q->st+q->dur>curr_time) { + dur = q->st+q->dur-curr_time; + } + } + switch_special_station(next_sid_to_refresh, on, dur); + } } } } @@ -1382,7 +1373,7 @@ uint16_t OpenSprinkler::read_current() { scale = 11.39; #endif } else { - scale = 0.0; // for other controllers, current is 0 + scale = 0.0; // for other controllers, current is 0 } /* do an average */ const byte K = 8; @@ -1465,9 +1456,11 @@ void OpenSprinkler::get_station_data(byte sid, StationData* data) { } /** Set station data */ +/* void OpenSprinkler::set_station_data(byte sid, StationData* data) { file_write_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); } +*/ /** Get station name */ void OpenSprinkler::get_station_name(byte sid, char tmp[]) { @@ -1479,7 +1472,12 @@ void OpenSprinkler::get_station_name(byte sid, char tmp[]) { void OpenSprinkler::set_station_name(byte sid, char tmp[]) { // todo: store the right size tmp[STATION_NAME_SIZE]=0; - file_write_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); + char n0[STATION_NAME_SIZE+1]; + get_station_name(sid, n0); + size_t len = strlen(n0); + if(len!=strlen(tmp) || memcmp(n0, tmp, len)!=0) { // only write if the name has changed + file_write_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); + } } /** Get station type */ @@ -1487,37 +1485,92 @@ byte OpenSprinkler::get_station_type(byte sid) { return file_read_byte(STATIONS_FILENAME, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type)); } -/** Get station attribute */ -/*void OpenSprinkler::get_station_attrib(byte sid, StationAttrib *attrib); { - file_read_block(STATIONS_FILENAME, attrib, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); -}*/ +byte OpenSprinkler::is_sequential_station(byte sid) { + return attrib_grp[sid] != PARALLEL_GROUP_ID; +} + +byte OpenSprinkler::is_master_station(byte sid) { + for (byte mas = 0; mas < NUM_MASTER_ZONES; mas++) { + if (get_master_id(mas) && (get_master_id(mas) - 1 == sid)) { + return 1; + } + } + return 0; +} + +byte OpenSprinkler::is_running(byte sid) { + return station_bits[(sid >> 3)] >> (sid & 0x07) & 1; +} + +byte OpenSprinkler::get_master_id(byte mas) { + return masters[mas][MASOPT_SID]; +} + +int16_t OpenSprinkler::get_on_adj(byte mas) { + return water_time_decode_signed(masters[mas][MASOPT_ON_ADJ]); +} + +int16_t OpenSprinkler::get_off_adj(byte mas) { + return water_time_decode_signed(masters[mas][MASOPT_OFF_ADJ]); +} + +byte OpenSprinkler::bound_to_master(byte sid, byte mas) { + byte bid = sid >> 3; + byte s = sid & 0x07; + byte attributes = 0; + + switch (mas) { + case MASTER_1: + attributes= attrib_mas[bid]; + break; + case MASTER_2: + attributes = attrib_mas2[bid]; + break; + default: + break; + } + + return attributes & (1 << s); +} + +byte OpenSprinkler::get_station_gid(byte sid) { + return attrib_grp[sid]; +} + +void OpenSprinkler::set_station_gid(byte sid, byte gid) { + attrib_grp[sid] = gid; +} /** Save all station attribs to file (backward compatibility) */ void OpenSprinkler::attribs_save() { // re-package attribute bits and save byte bid, s, sid=0; StationAttrib at, at0; + memset(&at, 0, sizeof(StationAttrib)); byte ty = STN_TYPE_STANDARD, ty0; - for(bid=0;bid>s) & 1; at.igs = (attrib_igs[bid]>>s) & 1; at.mas2= (attrib_mas2[bid]>>s)& 1; at.igs2= (attrib_igs2[bid]>>s) & 1; at.igrd= (attrib_igrd[bid]>>s) & 1; at.dis = (attrib_dis[bid]>>s) & 1; - at.seq = (attrib_seq[bid]>>s) & 1; - at.gid = 0; - // only write if content has changed - file_read_block(STATIONS_FILENAME, &at0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), 1); - if(*((byte*)&at) != *((byte*)&at0)) - file_write_block(STATIONS_FILENAME, &at, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), 1); // attribte bits are 1 byte long + at.gid = get_station_gid(sid); + set_station_gid(sid, at.gid); + + // only write if content has changed: this is important for LittleFS as otherwise the overhead is too large + file_read_block(STATIONS_FILENAME, &at0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); + if(memcmp(&at,&at0,sizeof(StationAttrib))!=0) { + file_write_block(STATIONS_FILENAME, &at, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); // attribte bits are 1 byte long + } if(attrib_spe[bid]>>s==0) { // if station special bit is 0, make sure to write type STANDARD // only write if content has changed file_read_block(STATIONS_FILENAME, &ty0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); - if(ty!=ty0) + if(ty!=ty0) { file_write_block(STATIONS_FILENAME, &ty, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); // attribte bits are 1 byte long + } } } } @@ -1535,8 +1588,8 @@ void OpenSprinkler::attribs_load() { memset(attrib_igs2, 0, nboards); memset(attrib_igrd, 0, nboards); memset(attrib_dis, 0, nboards); - memset(attrib_seq, 0, nboards); memset(attrib_spe, 0, nboards); + memset(attrib_grp, 0, MAX_NUM_STATIONS); for(bid=0;bid>3,s=sid&0x07; + if(!(os.attrib_spe[bid]&(1<sped, value); + switch_remotestation((RemoteStationData *)pdata->sped, value, dur); break; case STN_TYPE_GPIO: @@ -1611,15 +1666,15 @@ void OpenSprinkler::switch_special_station(byte sid, byte value) { * You have to call apply_all_station_bits next to apply the bits * (which results in physical actions of opening/closing valves). */ -byte OpenSprinkler::set_station_bit(byte sid, byte value) { +byte OpenSprinkler::set_station_bit(byte sid, byte value, uint16_t dur) { byte *data = station_bits+(sid>>3); // pointer to the station byte byte mask = (byte)1<<(sid&0x07); // mask if (value) { - if((*data)&mask) return 0; // if bit is already set, return no change + if((*data)&mask) return 0; // if bit is already set, return no change else { (*data) = (*data) | mask; engage_booster = true; // if bit is changing from 0 to 1, set engage_booster - switch_special_station(sid, 1); // handle special stations + switch_special_station(sid, 1, dur); // handle special stations return 1; } } else { @@ -1627,7 +1682,7 @@ byte OpenSprinkler::set_station_bit(byte sid, byte value) { else { (*data) = (*data) & (~mask); if(hw_type == HW_TYPE_LATCH) { - engage_booster = true; // if LATCH controller, engage booster when bit changes + engage_booster = true; // if LATCH controller, engage booster when bit changes } switch_special_station(sid, 0); // handle special stations return 255; @@ -1740,8 +1795,6 @@ void remote_http_callback(char* buffer) { */ } -extern void ip2string(char* str, byte ip[4]); - int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { #if defined(ARDUINO) @@ -1755,24 +1808,8 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* client = ðerClient; #endif - IPAddress _ip; - if (!WiFi.hostByName(server, _ip, timeout)) { - DEBUG_PRINT("DNS resolve Error! "); - DEBUG_PRINT(server); - DEBUG_PRINTLN(" ?"); - return HTTP_RQT_DNS_ERROR; - } - - DEBUG_PRINT(server); - DEBUG_PRINT("="); - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; - char postval[16] = ""; - ip2string(postval, ip); - DEBUG_PRINTLN(postval); - - #define HTTP_CONNECT_NTRIES 5 + #define HTTP_CONNECT_NTRIES 3 byte tries = 0; - client->setTimeout(timeout); do { DEBUG_PRINT(server); DEBUG_PRINT(":"); @@ -1781,7 +1818,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* DEBUG_PRINT(tries); DEBUG_PRINTLN(")"); - if(client->connect(ip, port)==1) break; + if(client->connect(server, port)==1) break; tries++; } while(triesconnect((uint8_t*)host->h_addr, port)) { - DEBUG_PRINT(F("Cannot connect to ")); - DEBUG_PRINT(server); - DEBUG_PRINT(":"); - DEBUG_PRINTLN(port); + DEBUG_PRINT(F("failed.")); client->stop(); return HTTP_RQT_CONNECT_ERR; } @@ -1818,62 +1855,27 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* memset(ether_buffer, 0, ETHER_BUFFER_SIZE); uint32_t stoptime = millis()+timeout; -/*#if defined(ARDUINO) - while(client->available()==0) { - if(millis()>stoptime) { - client->stop(); - return HTTP_RQT_TIMEOUT; - } - } - int nbytes = client->available(); - if(nbytes>0) { - if(nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE; - client->read((uint8_t*)ether_buffer, nbytes); - } - -#else - while(client->connected()) { - int len=client->read((uint8_t *)ether_buffer, ETHER_BUFFER_SIZE); - if (len<=0) continue; - if(millis()>stoptime) { - client->stop(); - return HTTP_RQT_TIMEOUT; - } - } -#endif*/ - -/*#if defined(ARDUINO) - while(client->available()==0) { - if(millis()>stoptime) { - client->stop(); - return HTTP_RQT_TIMEOUT; - } - } - int nbytes = client->available(); - if(nbytes>0) { - if(nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE; - client->read((uint8_t*)ether_buffer, nbytes); - } -#endif*/ - - int pos = 0; #if defined(ARDUINO) - while(pos < ETHER_BUFFER_SIZE) { + // 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 + while(true) { int nbytes = client->available(); if(nbytes>0) { if(pos+nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE-pos; // cannot read more than buffer size client->read((uint8_t*)ether_buffer+pos, nbytes); pos+=nbytes; - } else if (!client->connected()) - break; - delay(5); - + } if(millis()>stoptime) { DEBUG_PRINTLN(F("host timeout occured")); //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far break; } + if(!client->connected() && !client->available()) { + //DEBUG_PRINTLN(F("host disconnected")); + break; + } } #else while(client->connected()) { @@ -1896,7 +1898,12 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { char server[20]; - sprintf(server, "%d.%d.%d.%d", ip4>>24, (ip4>>16)&0xff, (ip4>>8)&0xff, ip4&0xff); + byte ip[4]; + ip[0] = ip4>>24; + ip[1] = (ip4>>16)&0xff; + ip[2] = (ip4>>8)&0xff; + ip[3] = ip4&0xff; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); return send_http_request(server, port, p, callback, timeout); } @@ -1913,7 +1920,7 @@ int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*c * The remote controller is assumed to have the same * password as the main controller */ -void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon) { +void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon, uint16_t dur) { RemoteStationData copy; memcpy((char*)©, (char*)data, sizeof(RemoteStationData)); @@ -1930,9 +1937,18 @@ void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon) { // because remote station data is loaded at the beginning char *p = tmp_buffer; BufferFiller bf = p; - // if auto refresh is enabled, we give a fixed duration each time, and auto refresh will renew it periodically - // if no auto refresh, we will give the maximum allowed duration, and station will be turned off when off command is sent - uint16_t timer = iopts[IOPT_SPE_AUTO_REFRESH]?4*MAX_NUM_STATIONS:64800; + // 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 + // if no auto refresh, we will give the maximum allowed duration, and station will be turned off when off command is sent + uint16_t timer = 0; + if(turnon) { + if(dur>0) { + timer = dur; + } else { + timer = iopts[IOPT_SPE_AUTO_REFRESH]?4*MAX_NUM_STATIONS:64800; + } + } bf.emit_p(PSTR("GET /cm?pw=$O&sid=$D&en=$D&t=$D"), SOPT_PASSWORD, (int)hex2ulong(copy.sid, sizeof(copy.sid)), @@ -1940,8 +1956,9 @@ void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon) { bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0],ip[1],ip[2],ip[3]); - send_http_request(ip4, port, p, remote_http_callback); - + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + send_http_request(server, port, p, remote_http_callback); } /** Switch http station @@ -1975,13 +1992,7 @@ void OpenSprinkler::pre_factory_reset() { #if defined(ESP8266) lcd_print_line_clear_pgm(PSTR("Wiping flash.."), 0); lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); - while (!LittleFS.format()) { - DEBUG_PRINT("ERROR FORMATTING LitteFS"); - delay(100); - LittleFS.end(); - LittleFS.begin(); - delay(100); - } + LittleFS.format(); #else // remove 'done' file as an indicator for reset // todo os2.3 and ospi: delete log files and/or wipe SD card @@ -2017,8 +2028,7 @@ void OpenSprinkler::factory_reset() { StationAttrib at; memset(&at, 0, sizeof(StationAttrib)); at.mas=1; - at.seq=1; - pdata->attrib=at; // mas:1 seq:1 + pdata->attrib=at; // mas:1 pdata->type=STN_TYPE_STANDARD; pdata->sped[0]='0'; pdata->sped[1]=0; @@ -2049,31 +2059,65 @@ void OpenSprinkler::factory_reset() { file_write_byte(DONE_FILENAME, 0, 1); } +#define str(s) #s +#define xstr(s) str(s) + +/** Parse OTC configuration */ +#if defined(ESP8266) +void OpenSprinkler::parse_otc_config() { + char server[MAX_SOPTS_SIZE+1] = {0}; + char token[MAX_SOPTS_SIZE+1] = {0}; + int port = DEFAULT_OTC_PORT; + int en = 0; + + char *config = tmp_buffer; + sopt_load(SOPT_OTC_OPTS, config); + if (*config != 0) { + sscanf(config, "\"en\":%d,\"token\":\"%" xstr(MAX_SOPTS_SIZE) "[^\"]\",\"server\":\"%" xstr(MAX_SOPTS_SIZE) "[^\"]\",\"port\":%d", + &en, token, server, &port); + token[MAX_SOPTS_SIZE] = 0; + server[MAX_SOPTS_SIZE] = 0; + } + otc.en = en; + otc.token = String(token); + otc.server = String(server); + otc.port = 80; +} +#endif + /** Setup function for options */ void OpenSprinkler::options_setup() { - DEBUG_PRINT("option_setup..."); // Check reset conditions: - if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<220 || // fw version is invalid (<219) - !file_exists(DONE_FILENAME)) { // done file doesn't exist + if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<220 || // fw version is invalid (<219) + !file_exists(DONE_FILENAME)) { // done file doesn't exist factory_reset(); } else { iopts_load(); - nvdata_load(); - last_reboot_cause = nvdata.reboot_cause; nvdata.reboot_cause = REBOOT_CAUSE_POWERON; nvdata_save(); - #if defined(ESP8266) wifi_ssid = sopt_load(SOPT_STA_SSID); wifi_pass = sopt_load(SOPT_STA_PASS); + sopt_load(SOPT_STA_BSSID_CHL, tmp_buffer); + if(tmp_buffer[0]!=0) { + char *mac = strchr(tmp_buffer, '@'); + if(mac!=NULL && isValidMAC(tmp_buffer)) { // check if bssid is valid MAC + *mac=0; // terminate MAC string + int chl = atoi(mac+1); + if(chl>=0 && chl<=255) { + str2mac(tmp_buffer, wifi_bssid); + wifi_channel = chl; + } + } + } + parse_otc_config(); #endif - attribs_load(); } @@ -2190,6 +2234,8 @@ void OpenSprinkler::nvdata_save() { file_write_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); } +void load_wt_monthly(char* wto); + /** Load integer options from file */ void OpenSprinkler::iopts_load() { file_read_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); @@ -2198,17 +2244,32 @@ void OpenSprinkler::iopts_load() { status.enabled = iopts[IOPT_DEVICE_ENABLE]; iopts[IOPT_FW_VERSION] = OS_FW_VERSION; iopts[IOPT_FW_MINOR] = OS_FW_MINOR; - /* Reject the former default 50.97.210.169 NTP IP address as - * it no longer works, yet is carried on by people's saved - * configs when they upgrade from older versions. - * IOPT_NTP_IP1 = 0 leads to the new good default behavior. */ - if (iopts[IOPT_NTP_IP1] == 50 && iopts[IOPT_NTP_IP2] == 97 && - iopts[IOPT_NTP_IP3] == 210 && iopts[IOPT_NTP_IP4] == 169) { - iopts[IOPT_NTP_IP1] = 0; - iopts[IOPT_NTP_IP2] = 0; - iopts[IOPT_NTP_IP3] = 0; - iopts[IOPT_NTP_IP4] = 0; - } + /* Reject the former default 50.97.210.169 NTP IP address as + * it no longer works, yet is carried on by people's saved + * configs when they upgrade from older versions. + * IOPT_NTP_IP1 = 0 leads to the new good default behavior. */ + if (iopts[IOPT_NTP_IP1] == 50 && iopts[IOPT_NTP_IP2] == 97 && + iopts[IOPT_NTP_IP3] == 210 && iopts[IOPT_NTP_IP4] == 169) { + iopts[IOPT_NTP_IP1] = 0; + iopts[IOPT_NTP_IP2] = 0; + iopts[IOPT_NTP_IP3] = 0; + iopts[IOPT_NTP_IP4] = 0; + } + populate_master(); + sopt_load(SOPT_WEATHER_OPTS, tmp_buffer); + if(iopts[IOPT_USE_WEATHER]==WEATHER_METHOD_MONTHLY) { + load_wt_monthly(tmp_buffer); + } +} + +void OpenSprinkler::populate_master() { + masters[MASTER_1][MASOPT_SID] = iopts[IOPT_MASTER_STATION]; + masters[MASTER_1][MASOPT_ON_ADJ] = iopts[IOPT_MASTER_ON_ADJ]; + masters[MASTER_1][MASOPT_OFF_ADJ] = iopts[IOPT_MASTER_OFF_ADJ]; + + 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]; } /** Save integer options to file */ @@ -2222,7 +2283,7 @@ void OpenSprinkler::iopts_save() { /** Load a string option from file */ void OpenSprinkler::sopt_load(byte oid, char *buf) { file_read_block(SOPTS_FILENAME, buf, MAX_SOPTS_SIZE*oid, MAX_SOPTS_SIZE); - buf[MAX_SOPTS_SIZE]=0; // ensure the string ends properly + buf[MAX_SOPTS_SIZE]=0; // ensure the string ends properly } /** Load a string option from file, return String */ @@ -2316,6 +2377,9 @@ void OpenSprinkler::lcd_print_2digit(int v) /** print time to a given line */ void OpenSprinkler::lcd_print_time(time_t t) { +#if defined(ESP8266) + lcd.setAutoDisplay(false); +#endif lcd.setCursor(0, 0); lcd_print_2digit(hour(t)); lcd_print_pgm(PSTR(":")); @@ -2327,6 +2391,10 @@ void OpenSprinkler::lcd_print_time(time_t t) lcd_print_2digit(month(t)); lcd_print_pgm(PSTR("-")); lcd_print_2digit(day(t)); +#if defined(ESP8266) + lcd.display(); + lcd.setAutoDisplay(true); +#endif } /** print ip address */ @@ -2360,19 +2428,20 @@ void OpenSprinkler::lcd_print_mac(const byte *mac) { } /** print station bits */ -void OpenSprinkler::lcd_print_station(byte line, char c) { - lcd.setCursor(0, line); +void OpenSprinkler::lcd_print_screen(char c) { +#if defined(ESP8266) + 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_pgm(PSTR("MC:")); // Master controller is display as 'MC' - } - else { - lcd_print_pgm(PSTR("E")); + lcd.print(F("MC:")); // Master controller is display as 'MC' + } else { + lcd.print(F("E")); lcd.print((int)status.display_board); - lcd_print_pgm(PSTR(":")); // extension boards are displayed as E1, E2... + lcd.print(F(":")); // extension boards are displayed as E1, E2... } - if (!status.enabled) { - lcd_print_line_clear_pgm(PSTR("-Disabled!-"), 1); + lcd.print(F("-Disabled!-")); } else { byte bitvalue = station_bits[status.display_board]; for (byte s=0; s<8; s++) { @@ -2388,17 +2457,13 @@ void OpenSprinkler::lcd_print_station(byte line, char c) { bitvalue >>= 1; } } - lcd_print_pgm(PSTR(" ")); + //lcd.print(F(" ")); - if(iopts[IOPT_REMOTE_EXT_MODE]) { - lcd.setCursor(LCD_CURSOR_REMOTEXT, 1); - lcd.write(ICON_REMOTEXT); - } + lcd.setCursor(LCD_CURSOR_REMOTEXT, 1); + lcd.write(iopts[IOPT_REMOTE_EXT_MODE]?ICON_REMOTEXT:' '); - if(status.rain_delayed) { - lcd.setCursor(LCD_CURSOR_RAINDELAY, 1); - lcd.write(ICON_RAINDELAY); - } + lcd.setCursor(LCD_CURSOR_RAINDELAY, 1); + lcd.write((status.rain_delayed || status.pause_state)?ICON_RAINDELAY:' '); // write sensor 1 icon lcd.setCursor(LCD_CURSOR_SENSOR1, 1); @@ -2415,6 +2480,9 @@ void OpenSprinkler::lcd_print_station(byte line, char c) { case SENSOR_TYPE_PSWITCH: lcd.write(status.sensor1?'P':'p'); break; + default: + lcd.write(' '); + break; } // write sensor 2 icon @@ -2433,6 +2501,9 @@ void OpenSprinkler::lcd_print_station(byte line, char c) { case SENSOR_TYPE_PSWITCH: lcd.write(status.sensor2?'Q':'q'); break; + default: + lcd.write(' '); + break; } lcd.setCursor(LCD_CURSOR_NETWORK, 1); @@ -2445,6 +2516,35 @@ void OpenSprinkler::lcd_print_station(byte line, char c) { #else 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(ESP8266) + + if(useEth || (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP())) { + lcd.setCursor(0, -1); + if(status.rain_delayed) { + lcd.print(F(" ")); + } else if(status.pause_state) { + lcd.print(F("")); + } else if(status.program_busy) { + lcd.print(F(" ")); + } else { + lcd.print(F(" (System Idle) ")); + } + + lcd.setCursor(2, 2); + if(status.program_busy && !status.pause_state) { + //lcd.print(F("Curr: ")); + lcd.print(read_current()); + lcd.print(F(" mA ")); + } else { + lcd.clear(2, 2); + } + } +#endif +#if defined(ESP8266) + lcd.display(); + lcd.setAutoDisplay(true); +#endif } /** print a version number */ @@ -2643,7 +2743,7 @@ void OpenSprinkler::ui_set_options(int oid) if(i==IOPT_URS_RETIRED) i++; if(i==IOPT_RSO_RETIRED) i++; if (hw_type==HW_TYPE_AC && i==IOPT_BOOST_TIME) i++; // skip boost time for non-DC controller -#if defined(ESP8266) + #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; @@ -2702,7 +2802,7 @@ void OpenSprinkler::lcd_set_brightness(byte value) { } #endif } -#endif // end of LCD and button functions +#endif // end of LCD and button functions #if defined(ESP8266) #include "images.h" diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 1814fae10..f5d4771ba 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -31,65 +31,70 @@ #include "images.h" #include "mqtt.h" -#if defined(ARDUINO) // headers for ESP8266 +#if defined(ARDUINO) // headers for Arduino #include #include #include #include "I2CRTC.h" - #if defined(ESP8266) + #if defined(ESP8266) // for ESP8266 #include #include #include #include + #include + #include + #include #include "SSD1306Display.h" #include "espconnect.h" - #else + #else // for AVR #include - #include "LiquidCrystal.h" #include + #include "LiquidCrystal.h" #endif - + #else // headers for RPI/BBB/LINUX #include #include #include - #include - #include + #include + #include #include "etherport.h" #endif // end of headers #if defined(ARDUINO) #if defined(ESP8266) - extern ESP8266WebServer *w_server; + extern ESP8266WebServer *update_server; + extern OTF::OpenThingsFramework *otf; extern ENC28J60lwIP eth; #else extern EthernetServer *m_server; #endif extern bool useEth; +#else + extern EthernetServer *m_server; #endif /** 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 }; -struct StationAttrib { // station attributes +struct StationAttrib { // station attributes byte mas:1; - byte igs:1; // ignore sensor 1 + byte igs:1; // ignore sensor 1 byte mas2:1; byte dis:1; - byte seq:1; - byte igs2:1;// ignore sensor 2 - byte igrd:1;// ignore rain delay - byte unused:1; - - byte gid:4; // group id: reserved for the future - byte dummy:4; + byte seq:1; // this bit is retired and replaced by sequential group id + byte igs2:1; // ignore sensor 2 + byte igrd:1; // ignore rain delay + byte igpu:1; // todo: ignore pause + + byte gid; // sequential group id byte reserved[2]; // reserved bytes for the future }; // total is 4 bytes so far @@ -128,22 +133,31 @@ struct HTTPStationData { /** Volatile controller status bits */ struct ConStatus { - byte enabled:1; // operation enable (when set, controller operation is enabled) - byte rain_delayed:1; // rain delay bit (when set, rain delay is applied) - byte sensor1:1; // sensor1 status bit (when set, sensor1 on is detected) - byte program_busy:1; // HIGH means a program is being executed currently - byte has_curr_sense:1; // HIGH means the controller has a current sensing pin - byte safe_reboot:1; // HIGH means a safe reboot has been marked - byte req_ntpsync:1; // request ntpsync - byte req_network:1; // request check network - byte display_board:5; // the board that is being displayed onto the lcd - byte network_fails:3; // number of network fails - byte mas:8; // master station index - byte mas2:8; // master2 station index - byte sensor2:1; // sensor2 status bit (when set, sensor2 on is detected) - byte sensor1_active:1; // sensor1 active bit (when set, sensor1 is activated) - byte sensor2_active:1; // sensor2 active bit (when set, sensor2 is activated) - byte req_mqtt_restart:1; // request mqtt restart + byte enabled:1; // operation enable (when set, controller operation is enabled) + byte rain_delayed:1; // rain delay bit (when set, rain delay is applied) + byte sensor1:1; // sensor1 status bit (when set, sensor1 on is detected) + byte program_busy:1; // HIGH means a program is being executed currently + byte has_curr_sense:1; // HIGH means the controller has a current sensing pin + byte safe_reboot:1; // HIGH means a safe reboot has been marked + byte req_ntpsync:1; // request ntpsync + byte req_network:1; // request check network + byte display_board:5; // the board that is being displayed onto the lcd + byte network_fails:3; // number of network fails + byte mas:8; // master station index + byte mas2:8; // master2 station index + byte sensor2:1; // sensor2 status bit (when set, sensor2 on is detected) + byte sensor1_active:1; // sensor1 active bit (when set, sensor1 is activated) + byte sensor2_active:1; // sensor2 active bit (when set, sensor2 is activated) + byte req_mqtt_restart:1;// request mqtt restart + byte pause_state:1; // pause station runs +}; + +/** OTF configuration */ +struct OTCConfig { + byte en; + String token; + String server; + uint32_t port; }; extern const char iopt_json_names[]; @@ -154,16 +168,15 @@ class OpenSprinkler { // data members #if defined(ESP8266) - static SSD1306Display lcd; // 128x64 OLED display + static SSD1306Display lcd; // 128x64 OLED display #elif defined(ARDUINO) - static LiquidCrystal lcd; // 16x2 character LCD + static LiquidCrystal lcd; // 16x2 character LCD #else // todo: LCD define for RPI/BBB #endif #if defined(OSPI) - static byte pin_sr_data; // RPi shift register data pin - // to handle RPi rev. 1 + static byte pin_sr_data; // RPi shift register data pin to handle RPi rev. 1 #endif static OSMqtt mqtt; @@ -172,12 +185,12 @@ class OpenSprinkler { static ConStatus status; static ConStatus old_status; static byte nboards, nstations; - static byte hw_type; // hardware type - static byte hw_rev; // hardware minor + static byte hw_type; // hardware type + static byte hw_rev; // hardware minor static byte iopts[]; // integer options static const char*sopts[]; // string options - static byte station_bits[]; // station activation bits. each byte corresponds to a board (8 stations) + static byte station_bits[]; // station activation bits. each byte corresponds to a board (8 stations) // first byte-> master controller, second byte-> ext. board 1, and so on // todo future: the following attribute bytes are for backward compatibility static byte attrib_mas[]; @@ -186,35 +199,37 @@ class OpenSprinkler { static byte attrib_igs2[]; static byte attrib_igrd[]; static byte attrib_dis[]; - static byte attrib_seq[]; static byte attrib_spe[]; - + static byte attrib_grp[]; + static byte masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; + // variables for time keeping - static ulong sensor1_on_timer; // time when sensor1 is detected on last time + static ulong sensor1_on_timer; // time when sensor1 is detected on last time static ulong sensor1_off_timer; // time when sensor1 is detected off last time static ulong sensor1_active_lasttime; // most recent time sensor1 is activated - static ulong sensor2_on_timer; // time when sensor2 is detected on last time + static ulong sensor2_on_timer; // time when sensor2 is detected on last time static ulong sensor2_off_timer; // time when sensor2 is detected off last time - static ulong sensor2_active_lasttime; // most recent time sensor1 is activated + static ulong sensor2_active_lasttime; // most recent time sensor1 is activated static ulong raindelay_on_lasttime; // time when the most recent rain delay started - static ulong flowcount_rt; // flow count (for computing real-time flow rate) + 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 byte button_timeout; // button timeout - static ulong checkwt_lasttime; // time when weather was checked + static byte button_timeout; // button timeout + static ulong checkwt_lasttime; // time when weather was checked static ulong checkwt_success_lasttime; // time when weather check was successful - static ulong powerup_lasttime; // time when controller is powered up most recently - static uint8_t last_reboot_cause; // last reboot cause - static byte weather_update_flag; + static ulong powerup_lasttime; // time when controller is powered up most recently + static uint8_t last_reboot_cause; // last reboot cause + static byte weather_update_flag; // member functions // -- setup - static void update_dev(); // update software for Linux instances - static void reboot_dev(uint8_t); // reboot the microcontroller - static void begin(); // initialization, must call this function before calling other functions - static byte start_network(); // initialize network with the given mac and port - static byte start_ether(); // initialize ethernet with the given mac and port - static bool network_connected(); // check if the network is up - static bool load_hardware_mac(byte* buffer, bool wired=false); // read hardware mac address + static void update_dev(); // update software for Linux instances + static void reboot_dev(uint8_t); // reboot the microcontroller + static void begin(); // initialization, must call this function before calling other functions + static byte start_network(); // initialize network with the given mac and port + static byte start_ether(); // initialize ethernet with the given mac and port + static bool network_connected(); // check if the network is up + static bool load_hardware_mac(byte* buffer, bool wired=false); // read hardware mac address static time_t now_tz(); // -- station names and attributes static void get_station_data(byte sid, StationData* data); // get station data @@ -222,12 +237,22 @@ class OpenSprinkler { static void get_station_name(byte sid, char buf[]); // get station name static void set_station_name(byte sid, char buf[]); // set station name static byte get_station_type(byte sid); // get station type + static byte is_sequential_station(byte sid); + static byte is_master_station(byte sid); + static byte bound_to_master(byte sid, byte mas); + static byte get_master_id(byte mas); + static int16_t get_on_adj(byte mas); + static int16_t get_off_adj(byte mas); + static byte is_running(byte sid); + static byte get_station_gid(byte sid); + static void set_station_gid(byte sid, byte gid); + //static StationAttrib get_station_attrib(byte sid); // get station attribute static void attribs_save(); // repackage attrib bits and save (backward compatibility) static void attribs_load(); // load and repackage attrib bits (backward compatibility) static uint16_t parse_rfstation_code(RFStationData *data, ulong *on, ulong *off); // parse rf code into on/off/time sections static void switch_rfstation(RFStationData *data, bool turnon); // switch rf station - static void switch_remotestation(RemoteStationData *data, bool turnon); // switch remote station + static void switch_remotestation(RemoteStationData *data, bool turnon, uint16_t dur=0); // switch remote station static void switch_gpiostation(GPIOStationData *data, bool turnon); // switch gpio station static void switch_httpstation(HTTPStationData *data, bool turnon); // switch http station @@ -243,46 +268,46 @@ class OpenSprinkler { static bool sopt_save(byte oid, const char *buf); static void sopt_load(byte oid, char *buf); static String sopt_load(byte oid); + static void populate_master(); + static byte password_verify(const char *pw); // verify password - static byte password_verify(char *pw); // verify password - // -- controller operation - static void enable(); // enable controller operation - static void disable(); // disable controller operation, all stations will be closed immediately - static void raindelay_start(); // start raindelay - static void raindelay_stop(); // stop rain delay + static void enable(); // enable controller operation + static void disable(); // disable controller operation, all stations will be closed immediately + static void raindelay_start(); // start raindelay + static void raindelay_stop(); // stop rain delay static void detect_binarysensor_status(ulong);// update binary (rain, soil) sensor status static byte detect_programswitch_status(ulong); // get program switch status static void sensor_resetall(); - + static uint16_t read_current(); // read current sensing value static uint16_t baseline_current; // resting state current - static int detect_exp(); // detect the number of expansion boards - static byte weekday_today(); // returns index of today's weekday (Monday is 0) + static int detect_exp(); // detect the number of expansion boards + static byte weekday_today(); // returns index of today's weekday (Monday is 0) - static byte set_station_bit(byte sid, byte value); // set station bit of one station (sid->station index, value->0/1) - static void switch_special_station(byte sid, byte value); // swtich special station + static byte set_station_bit(byte sid, byte value, uint16_t dur=0); // set station bit of one station (sid->station index, value->0/1) + static void switch_special_station(byte sid, byte value, uint16_t dur=0); // swtich special station static void clear_all_station_bits(); // clear all station bits static void apply_all_station_bits(); // apply all station bits (activate/deactive values) static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); - static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); // -- LCD functions #if defined(ARDUINO) // LCD functions for Arduino #if defined(ESP8266) 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, byte line); #else - static void lcd_print_pgm(PGM_P PROGMEM str); // print a program memory string + 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, byte line); #endif - static void lcd_print_time(time_t t); // print current time - static void lcd_print_ip(const byte *ip, byte endian); // print ip - static void lcd_print_mac(const byte *mac); // print mac - static void lcd_print_station(byte line, char c); // print station bits of the board selected by display_board - static void lcd_print_version(byte v); // print version number + static void lcd_print_time(time_t t); // print current time + static void lcd_print_ip(const byte *ip, byte endian); // print ip + static void lcd_print_mac(const byte *mac); // print mac + static void lcd_print_screen(char c); // print station bits of the board selected by display_board + static void lcd_print_version(byte v); // print version number static String time2str(uint32_t t) { uint16_t h = hour(t); @@ -311,29 +336,32 @@ class OpenSprinkler { static void lcd_set_contrast(); #if defined(ESP8266) + static OTCConfig otc; static IOEXP *mainio, *drio; static IOEXP *expanders[]; static RCSwitch rfswitch; static void detect_expanders(); static void flash_screen(); static void toggle_screen_led(); - static void set_screen_led(byte status); + static void set_screen_led(byte status); static byte get_wifi_mode() { if (useEth) return WIFI_MODE_STA; else return wifi_testmode ? WIFI_MODE_STA : iopts[IOPT_WIFI_MODE];} static byte wifi_testmode; static String wifi_ssid, wifi_pass; + static byte wifi_bssid[6], wifi_channel; static void config_ip(); static void save_wifi_ip(); static void reset_to_ap(); static byte state; #endif - + private: - static void lcd_print_option(int i); // print an option to the lcd - static void lcd_print_2digit(int v); // print a integer in 2 digits + static void lcd_print_option(int i); // print an option to the lcd + static void lcd_print_2digit(int v); // print a integer in 2 digits static void lcd_start(); static byte button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding); #if defined(ESP8266) + static void parse_otc_config(); static void latch_boost(); static void latch_open(byte sid); static void latch_close(byte sid); @@ -348,4 +376,4 @@ class OpenSprinkler { static byte engage_booster; }; -#endif // _OPENSPRINKLER_H +#endif // _OPENSPRINKLER_H diff --git a/README.txt b/README.txt index 136269aed..db30fc39d 100755 --- a/README.txt +++ b/README.txt @@ -14,15 +14,3 @@ https://openthings.freshdesk.com/support/solutions/articles/5000631599-installin Questions and comments: http://www.opensprinkler.com ============================================ - - -************************************************************** UPDATE 01.04.2022 ************************************* -This is the lwip version from OpenSprinkler branch dev/os220 - -we found out that the lwip_enc28j60 had some bugs, so use better the lwip_enc28j60 from here: -https://github.com/esp8266/Arduino/pull/8376 - -Also it is better to use the updated version of LitteFS from here: -https://github.com/littlefs-project/littlefs - -********************************************************************************************************************** diff --git a/SSD1306Display.h b/SSD1306Display.h index b5bd3986e..c59866db0 100644 --- a/SSD1306Display.h +++ b/SSD1306Display.h @@ -7,7 +7,7 @@ #include "font.h" #include "images.h" -#define LCD_STD 0 // Standard LCD +#define LCD_STD 0 // Standard LCD #define LCD_I2C 1 class SSD1306Display : public SSD1306{ @@ -32,7 +32,7 @@ class SSD1306Display : public SSD1306{ 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*/} @@ -55,23 +55,25 @@ class SSD1306Display : public SSD1306{ drawString(cx, cy, String((char)c)); } cx += fontWidth; - display(); // todo: not very efficient + 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); + fillRect(cx, cy, fontWidth*nc, fontHeight); setColor(WHITE); drawString(cx, cy, String(s)); cx += fontWidth*nc; - display(); // todo: not very efficient + if(auto_display) display(); // todo: not very efficient return nc; } void createChar(byte idx, PGM_P ptr) { if(idx>=0&&idx/sc?pw=&nr=1&type=1&group=0&name=SMT100-Mois&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 http:///sc?pw=&nr=2&type=2&group=0&name=SMT100-Temp&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 -http:///sc?pw=&nr=3&type=11&group=0&name=SMT50-Mois&ip=0&port=72&id=0&ri=60&enable=1&log=1 -http:///sc?pw=&nr=4&type=12&group=0&name=SMT50-Temp&ip=0&port=72&id=1&ri=60&enable=1&log=1 +http:///sc?pw=&nr=3&type=11&group=0&name=SMT50-Mois&ip=0&id=0&ri=60&enable=1&log=1 +http:///sc?pw=&nr=4&type=12&group=0&name=SMT50-Temp&ip=0&id=1&ri=60&enable=1&log=1 ip: dec=4261456064 = hex=FE00A8C0 = FE = 254 @@ -34,22 +34,22 @@ C0 = 192 For the Truebner SMT100 RS485 Modbus you need a RS485 Modbus RTU over TCP converter. Set ip/port for the converter, e.a PUSR USR-W610 in transparent modus. -For the analog ports of the extension board (including SMT50) id is 0x48=72 for the first 4 ports (with id 0-3) -and 0x49=73 for the second 4 ports (also with id 0-3) +For the analog ports of the extension board (including SMT50) use id 0..7 type: -SENSOR_NONE 0 //None or deleted sensor -SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode -SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode -SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 - voltage mode 0..4V -SENSOR_SMT50_MOIS 11 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 VWC [%] = (U * 50) : 3 -SENSOR_SMT50_TEMP 12 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 T [°C] = (U – 0,5) * 100 -SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input (currently not implemented!) -SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler (ip+port remote OS, id=sensor-nr) -SENSOR_GROUP_MIN 1000 //Sensor group with min value -SENSOR_GROUP_MAX 1001 //Sensor group with max value -SENSOR_GROUP_AVG 1002 //Sensor group with avg value -SENSOR_GROUP_SUM 1003 //Sensor group with sum value +SENSOR_NONE 0 None or deleted sensor +SENSOR_SMT100_MODBUS_RTU_MOIS 1 Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode +SENSOR_SMT100_MODBUS_RTU_TEMP 2 Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode +SENSOR_ANALOG_EXTENSION_BOARD 10 New OpenSprinkler analog extension board x8 - voltage mode 0..4V +SENSOR_ANALOG_EXTENSION_BOARD_P 11 New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% +SENSOR_SMT50_MOIS 15 New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 +SENSOR_SMT50_TEMP 16 New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +SENSOR_OSPI_ANALOG_INPUTS 20 Old OSPi analog input +SENSOR_REMOTE 100 Remote sensor of an remote opensprinkler +SENSOR_GROUP_MIN 1000 Sensor group with min value +SENSOR_GROUP_MAX 1001 Sensor group with max value +SENSOR_GROUP_AVG 1002 Sensor group with avg value +SENSOR_GROUP_SUM 1003 Sensor group with sum value List Sensors (sl): lists the current sensors diff --git a/TimeLib.cpp b/TimeLib.cpp index c8dbed535..bf3faf0f2 100644 --- a/TimeLib.cpp +++ b/TimeLib.cpp @@ -15,7 +15,7 @@ You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - + 1.0 6 Jan 2010 - initial release 1.1 12 Feb 2010 - fixed leap year calculation error 1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) @@ -28,35 +28,35 @@ */ #if ARDUINO >= 100 -#include +#include #else -#include +#include #endif #include "TimeLib.h" -static tmElements_t tm; // a cache of time elements -static time_t cacheTime; // the time the cache was updated +static tmElements_t tm; // a cache of time elements +static time_t cacheTime; // the time the cache was updated static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds void refreshCache(time_t t) { if (t != cacheTime) { - breakTime(t, tm); - cacheTime = t; + breakTime(t, tm); + cacheTime = t; } } -int hour() { // the hour now - return hour(now()); +int hour() { // the hour now + return hour(now()); } int hour(time_t t) { // the hour for the given time refreshCache(t); - return tm.Hour; + return tm.Hour; } int hourFormat12() { // the hour now in 12 hour format - return hourFormat12(now()); + return hourFormat12(now()); } int hourFormat12(time_t t) { // the hour for the given time in 12 hour format @@ -70,41 +70,41 @@ int hourFormat12(time_t t) { // the hour for the given time in 12 hour format } uint8_t isAM() { // returns true if time now is AM - return !isPM(now()); + return !isPM(now()); } uint8_t isAM(time_t t) { // returns true if given time is AM - return !isPM(t); + return !isPM(t); } uint8_t isPM() { // returns true if PM - return isPM(now()); + return isPM(now()); } uint8_t isPM(time_t t) { // returns true if PM - return (hour(t) >= 12); + return (hour(t) >= 12); } int minute() { - return minute(now()); + return minute(now()); } int minute(time_t t) { // the minute for the given time refreshCache(t); - return tm.Minute; + return tm.Minute; } int second() { - return second(now()); + return second(now()); } -int second(time_t t) { // the second for the given time +int second(time_t t) { // the second for the given time refreshCache(t); return tm.Second; } int day(){ - return(day(now())); + return(day(now())); } int day(time_t t) { // the day for the given time (0-6) @@ -112,26 +112,26 @@ int day(time_t t) { // the day for the given time (0-6) return tm.Day; } -int weekday() { // Sunday is day 1 - return weekday(now()); +int weekday() { // Sunday is day 1 + return weekday(now()); } int weekday(time_t t) { refreshCache(t); return tm.Wday; } - + int month(){ - return month(now()); + return month(now()); } -int month(time_t t) { // the month for the given time +int month(time_t t) { // the month for the given time refreshCache(t); return tm.Month; } -int year() { // as in Processing, the full four digit year: (2009, 2010 etc) - return year(now()); +int year() { // as in Processing, the full four digit year: (2009, 2010 etc) + return year(now()); } int year(time_t t) { // the year for the given time @@ -139,7 +139,7 @@ int year(time_t t) { // the year for the given time return tmYearToCalendar(tm.Year); } -/*============================================================================*/ +/*============================================================================*/ /* functions to convert to and from system time */ /* These are for interfacing with time serivces and are not normally needed in a sketch */ @@ -147,7 +147,7 @@ int year(time_t t) { // the year for the given time #define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 - + void breakTime(time_t timeInput, tmElements_t &tm){ // break the given time_t into time components // this is a more compact version of the C library localtime function @@ -165,18 +165,18 @@ void breakTime(time_t timeInput, tmElements_t &tm){ time /= 60; // now it is hours tm.Hour = time % 24; time /= 24; // now it is days - tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 - - year = 0; + tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; days = 0; while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { year++; } - tm.Year = year; // year is offset from 1970 - + tm.Year = year; // year is offset from 1970 + days -= LEAP_YEAR(year) ? 366 : 365; time -= days; // now it is days in this year, starting at 0 - + days=0; month=0; monthLength=0; @@ -190,22 +190,22 @@ void breakTime(time_t timeInput, tmElements_t &tm){ } else { monthLength = monthDays[month]; } - + if (time >= monthLength) { time -= monthLength; } else { break; } } - tm.Month = month + 1; // jan is month 1 - tm.Day = time + 1; // day of month + tm.Month = month + 1; // jan is month 1 + tm.Day = time + 1; // day of month } -time_t makeTime(tmElements_t &tm){ -// assemble time elements into time_t +time_t makeTime(tmElements_t &tm){ +// assemble time elements into time_t // note year argument is offset from 1970 (see macros in time.h to convert to other formats) // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 - + int i; uint32_t seconds; @@ -213,13 +213,13 @@ time_t makeTime(tmElements_t &tm){ seconds= tm.Year*(SECS_PER_DAY * 365); for (i = 0; i < tm.Year; i++) { if (LEAP_YEAR(i)) { - seconds += SECS_PER_DAY; // add extra days for leap years + seconds += SECS_PER_DAY; // add extra days for leap years } } - + // add days for this year, months start from 1 for (i = 1; i < tm.Month; i++) { - if ( (i == 2) && LEAP_YEAR(tm.Year)) { + if ( (i == 2) && LEAP_YEAR(tm.Year)) { seconds += SECS_PER_DAY * 29; } else { seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 @@ -229,9 +229,9 @@ time_t makeTime(tmElements_t &tm){ seconds+= tm.Hour * SECS_PER_HOUR; seconds+= tm.Minute * SECS_PER_MIN; seconds+= tm.Second; - return (time_t)seconds; + return (time_t)seconds; } -/*=====================================================*/ +/*=====================================================*/ /* Low level system time functions */ static uint32_t sysTime = 0; @@ -243,7 +243,7 @@ getExternalTime getTimePtr; // pointer to external sync function //setExternalTime setTimePtr; // not used in this version #ifdef TIME_DRIFT_INFO // define this to get drift data -time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync +time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync #endif @@ -252,9 +252,9 @@ time_t now() { while (millis() - prevMillis >= 1000) { // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference sysTime++; - prevMillis += 1000; + prevMillis += 1000; #ifdef TIME_DRIFT_INFO - sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift + sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift #endif } if (nextSyncTime <= sysTime) { @@ -267,29 +267,29 @@ time_t now() { Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; } } - } + } return (time_t)sysTime; } -void setTime(time_t t) { +void setTime(time_t t) { #ifdef TIME_DRIFT_INFO - if(sysUnsyncedTime == 0) - sysUnsyncedTime = t; // store the time of the first call to set a valid Time + if(sysUnsyncedTime == 0) + sysUnsyncedTime = t; // store the time of the first call to set a valid Time #endif - sysTime = (uint32_t)t; + sysTime = (uint32_t)t; nextSyncTime = (uint32_t)t + syncInterval; Status = timeSet; - prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) -} + prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) +} void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ - // year can be given as full four digit year or two digts (2010 or 10 for 2010); + // year can be given as full four digit year or two digts (2010 or 10 for 2010); //it is converted to years since 1970 if( yr > 99) yr = yr - 1970; else - yr += 30; + yr += 30; tm.Year = yr; tm.Month = mnth; tm.Day = dy; @@ -310,7 +310,7 @@ timeStatus_t timeStatus() { } void setSyncProvider( getExternalTime getTimeFunction){ - getTimePtr = getTimeFunction; + getTimePtr = getTimeFunction; nextSyncTime = sysTime; now(); // this will sync the clock } diff --git a/TimeLib.h b/TimeLib.h index 5e1f1566d..bbb1a83e2 100644 --- a/TimeLib.h +++ b/TimeLib.h @@ -5,7 +5,7 @@ /* July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) - fixed daysToTime_t macro (thanks maniacbug) -*/ +*/ #ifndef _Time_h #ifdef __cplusplus @@ -40,23 +40,23 @@ typedef enum { typedef enum { tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields -} tmByteFields; +} tmByteFields; -typedef struct { - uint8_t Second; - uint8_t Minute; - uint8_t Hour; +typedef struct { + uint8_t Second; + uint8_t Minute; + uint8_t Hour; uint8_t Wday; // day of week, sunday is day 1 uint8_t Day; - uint8_t Month; - uint8_t Year; // offset from 1970; + uint8_t Month; + uint8_t Year; // offset from 1970; } tmElements_t, TimeElements, *tmElementsPtr_t; -//convenience macros to convert to and from tm years -#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year +//convenience macros to convert to and from tm years +#define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year #define CalendarYrToTm(Y) ((Y) - 1970) #define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 -#define y2kYearToTm(Y) ((Y) + 30) +#define y2kYearToTm(Y) ((Y) + 30) typedef time_t(*getExternalTime)(); //typedef void (*setExternalTime)(const time_t); // not used in this version @@ -71,32 +71,32 @@ typedef time_t(*getExternalTime)(); #define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK) #define SECS_PER_YEAR (SECS_PER_WEEK * 52UL) #define SECS_YR_2000 (946684800UL) // the time at the start of y2k - + /* Useful Macros for getting elapsed time */ -#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) -#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) +#define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) +#define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) #define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) #define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday #define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 -#define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight +#define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight // The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 // Always set the correct time before settting alarms #define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day -#define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day -#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 +#define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day +#define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that wee starts on day 1 #define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time #define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time /* Useful Macros for converting elapsed time to a time_t */ -#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) -#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) +#define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) +#define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) #define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 -#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) +#define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) /*============================================================================*/ /* time and date functions */ -int hour(); // the hour now +int hour(); // the hour now int hour(time_t t); // the hour for the given time int hourFormat12(); // the hour now in 12 hour format int hourFormat12(time_t t); // the hour for the given time in 12 hour format @@ -104,31 +104,31 @@ uint8_t isAM(); // returns true if time now is AM uint8_t isAM(time_t t); // returns true the given time is AM uint8_t isPM(); // returns true if time now is PM uint8_t isPM(time_t t); // returns true the given time is PM -int minute(); // the minute now +int minute(); // the minute now int minute(time_t t); // the minute for the given time -int second(); // the second now +int second(); // the second now int second(time_t t); // the second for the given time -int day(); // the day now +int day(); // the day now int day(time_t t); // the day for the given time -int weekday(); // the weekday now (Sunday is day 1) -int weekday(time_t t); // the weekday for the given time +int weekday(); // the weekday now (Sunday is day 1) +int weekday(time_t t); // the weekday for the given time int month(); // the month now (Jan is month 1) int month(time_t t); // the month for the given time -int year(); // the full four digit year: (2009, 2010 etc) +int year(); // the full four digit year: (2009, 2010 etc) int year(time_t t); // the year for the given time -time_t now(); // return the current time as seconds since Jan 1 1970 +time_t now(); // return the current time as seconds since Jan 1 1970 void setTime(time_t t); void setTime(int hr,int min,int sec,int day, int month, int yr); void adjustTime(long adjustment); -/* date strings */ +/* date strings */ #define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) char* monthStr(uint8_t month); char* dayStr(uint8_t day); char* monthShortStr(uint8_t month); char* dayShortStr(uint8_t day); - + /* time sync functions */ timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider diff --git a/build.sh b/build.sh index 9be398ce4..8c5df9f54 100755 --- a/build.sh +++ b/build.sh @@ -14,7 +14,7 @@ if [ "$1" == "demo" ]; then echo "Installing required libraries..." apt-get install -y libmosquitto-dev echo "Compiling firmware..." - g++ -o OpenSprinkler -DDEMO -m32 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto + g++ -o OpenSprinkler -DDEMO -std=c++14 -m32 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto elif [ "$1" == "osbo" ]; then echo "Installing required libraries..." apt-get install -y libmosquitto-dev @@ -22,7 +22,14 @@ elif [ "$1" == "osbo" ]; then g++ -o OpenSprinkler -DOSBO main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto else echo "Installing required libraries..." + apt-get update apt-get install -y libmosquitto-dev + apt-get install -y raspi-gpio + if ! command -v raspi-gpio &> /dev/null + then + echo "Command raspi-gpio is required and is not installed" + exit 0 + fi echo "Compiling firmware..." g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto fi diff --git a/defines.h b/defines.h index 1b0b15da8..623170fab 100644 --- a/defines.h +++ b/defines.h @@ -28,21 +28,21 @@ typedef unsigned char byte; typedef unsigned long ulong; - + #define TMP_BUFFER_SIZE 255 // scratch buffer size /** Firmware version, hardware version, and maximal values */ #define OS_FW_VERSION 230 // Firmware version: 220 means 2.2.0 - // if this number is different from the one stored in non-volatile memory - // a device reset will be automatically triggered + // if this number is different from the one stored in non-volatile memory + // a device reset will be automatically triggered #define OS_FW_MINOR 1 // Firmware minor version /** Hardware version base numbers */ -#define OS_HW_VERSION_BASE 0x00 -#define OSPI_HW_VERSION_BASE 0x40 -#define OSBO_HW_VERSION_BASE 0x80 -#define SIM_HW_VERSION_BASE 0xC0 +#define OS_HW_VERSION_BASE 0x00 // OpenSprinkler +#define OSPI_HW_VERSION_BASE 0x40 // OpenSprinkler Pi +#define OSBO_HW_VERSION_BASE 0x80 // OpenSprinkler Beagle +#define SIM_HW_VERSION_BASE 0xC0 // simulation hardware /** Hardware type macro defines */ #define HW_TYPE_AC 0xAC // standard 24VAC for 24VAC solenoids only, with triacs @@ -62,7 +62,7 @@ typedef unsigned long ulong; #define SENSORLOG_FILENAME "sensorlog.dat" // analog sensor log filename /** Station macro defines */ -#define STN_TYPE_STANDARD 0x00 +#define STN_TYPE_STANDARD 0x00 // standard solenoid station #define STN_TYPE_RF 0x01 // Radio Frequency (RF) station #define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station #define STN_TYPE_GPIO 0x03 // direct GPIO station @@ -81,22 +81,21 @@ typedef unsigned long ulong; #define NOTIFY_STATION_ON 0x0100 /** HTTP request macro defines */ -#define HTTP_RQT_SUCCESS 0 -#define HTTP_RQT_NOT_RECEIVED -1 -#define HTTP_RQT_CONNECT_ERR -2 -#define HTTP_RQT_TIMEOUT -3 -#define HTTP_RQT_EMPTY_RETURN -4 -#define HTTP_RQT_DNS_ERROR -5 +#define HTTP_RQT_SUCCESS 0 +#define HTTP_RQT_NOT_RECEIVED 1 +#define HTTP_RQT_CONNECT_ERR 2 +#define HTTP_RQT_TIMEOUT 3 +#define HTTP_RQT_EMPTY_RETURN 4 /** Sensor macro defines */ #define SENSOR_TYPE_NONE 0x00 -#define SENSOR_TYPE_RAIN 0x01 // rain sensor -#define SENSOR_TYPE_FLOW 0x02 // flow sensor +#define SENSOR_TYPE_RAIN 0x01 // rain sensor +#define SENSOR_TYPE_FLOW 0x02 // flow sensor #define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor -#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor +#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor #define SENSOR_TYPE_OTHER 0xFF -#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds +#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds /** Reboot cause */ #define REBOOT_CAUSE_NONE 0 @@ -110,11 +109,9 @@ typedef unsigned long ulong; #define REBOOT_CAUSE_WEATHER_FAIL 8 #define REBOOT_CAUSE_NETWORK_FAIL 9 #define REBOOT_CAUSE_NTP 10 -#define REBOOT_CAUSE_PROGRAM 11 +#define REBOOT_CAUSE_PROGRAM 11 #define REBOOT_CAUSE_POWERON 99 -/** Too much current */ -#define MAX_CURRENT 3010 //Max mA /** WiFi defines */ #define WIFI_MODE_AP 0xA9 @@ -124,6 +121,7 @@ typedef unsigned long ulong; #define OS_STATE_CONNECTING 1 #define OS_STATE_CONNECTED 2 #define OS_STATE_TRY_CONNECT 3 +#define OS_STATE_WAIT_REBOOT 4 #define LED_FAST_BLINK 100 #define LED_SLOW_BLINK 500 @@ -132,7 +130,7 @@ typedef unsigned long ulong; #if defined(ARDUINO) #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 + #define MAX_EXT_BOARDS 24 // allow more zones for linux-based firmwares #endif #define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders @@ -144,12 +142,43 @@ typedef unsigned long ulong; /** Default string option values */ #define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA +#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA #define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" #define DEFAULT_WEATHER_URL "weather.opensprinkler.com" #define DEFAULT_IFTTT_URL "maker.ifttt.com" +#define DEFAULT_OTC_SERVER "ws.cloud.openthings.io" +#define DEFAULT_OTC_PORT 80 +#define DEFAULT_DEVICE_NAME "My OpenSprinkler" #define DEFAULT_EMPTY_STRING "" +/* Weather Adjustment Methods */ +enum { + WEATHER_METHOD_MANUAL = 0, + WEATHER_METHOD_ZIMMERMAN, + WEATHER_METHOD_AUTORAINDELY, + WEATHER_METHOD_ETO, + WEATHER_METHOD_MONTHLY, + NUM_WEATHER_METHODS +}; + +/* Master */ +enum { + MASTER_1 = 0, + MASTER_2, + NUM_MASTER_ZONES, +}; + +enum { + MASOPT_SID = 0, + MASOPT_ON_ADJ, + MASOPT_OFF_ADJ, + NUM_MASTER_OPTS, +}; + +// Sequential Groups +#define NUM_SEQ_GROUPS 4 +#define PARALLEL_GROUP_ID 255 + /** Macro define of each option * Refer to OpenSprinkler.cpp for details on each option */ @@ -205,7 +234,7 @@ enum { IOPT_SPE_AUTO_REFRESH, IOPT_IFTTT_ENABLE, IOPT_SENSOR1_TYPE, - IOPT_SENSOR1_OPTION, + IOPT_SENSOR1_OPTION, IOPT_SENSOR2_TYPE, IOPT_SENSOR2_OPTION, IOPT_SENSOR1_ON_DELAY, @@ -227,13 +256,14 @@ enum { SOPT_JAVASCRIPTURL, SOPT_WEATHERURL, SOPT_WEATHER_OPTS, - SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT + SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT SOPT_STA_SSID, SOPT_STA_PASS, SOPT_MQTT_OPTS, - //SOPT_WEATHER_KEY, - //SOPT_AP_PASS, - NUM_SOPTS // total number of string options + SOPT_OTC_OPTS, + SOPT_DEVICE_NAME, + SOPT_STA_BSSID_CHL, // wifi extra info: bssid and channel + NUM_SOPTS // total number of string options }; /** Log Data Type */ @@ -251,7 +281,7 @@ enum { #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // 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 + #define PIN_FREE_LIST {2,10,12,13,14,15,18,19} // Free GPIO pins // hardware pins #define PIN_BUTTON_1 31 // button 1 @@ -280,7 +310,7 @@ enum { #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_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) @@ -293,7 +323,7 @@ enum { #define pinModeExt pinMode #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite + #define digitalWriteExt digitalWrite #elif defined(ESP8266) // for ESP8266 @@ -310,9 +340,9 @@ enum { #define PIN_FREE_LIST {} // no free GPIO pin at the moment #define ETHER_BUFFER_SIZE 2048 - #define PIN_ETHER_CS 16 // ENC28J60 CS (chip select pin) is 16 on OS 3.2. + #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above - /* To accommodate different OS30 versions, we use software defines pins */ + /* To accommodate different OS30 versions, we use software defines pins */ extern byte PIN_BUTTON_1; extern byte PIN_BUTTON_2; extern byte PIN_BUTTON_3; @@ -342,7 +372,7 @@ enum { #define V0_PIN_SENSOR1 12 // sensor 1 #define V0_PIN_SENSOR2 13 // sensor 2 - /* OS30 revision 1 pin defines */ + /* OS31 pin defines */ // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i #define V1_IO_CONFIG 0x1F00 // config bits #define V1_IO_OUTPUT 0x1F00 // output bits @@ -358,7 +388,7 @@ enum { #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 - /* OS30 revision 2 pin defines */ + /* OS32 pin defines */ // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i #define V2_IO_CONFIG 0x1000 // config bits #define V2_IO_OUTPUT 0x1E00 // output bits @@ -384,14 +414,14 @@ enum { #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) #define PIN_SR_CLOCK 4 // shift register clock pin #define PIN_SR_OE 17 // shift register output enable pin - #define PIN_SENSOR1 14 - #define PIN_SENSOR2 23 + #define PIN_SENSOR1 14 + #define PIN_SENSOR2 23 #define PIN_RFTX 15 // RF transmitter pin //#define PIN_BUTTON_1 23 // button 1 //#define PIN_BUTTON_2 24 // button 2 //#define PIN_BUTTON_3 25 // button 3 - #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins + #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins #define ETHER_BUFFER_SIZE 16384 #elif defined(OSBO) // for OSBo @@ -403,7 +433,7 @@ enum { #define PIN_SR_DATA 30 // P9_11, shift register data pin #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin #define PIN_SR_OE 50 // P9_14, shift register output enable pin - #define PIN_SENSOR1 48 + #define PIN_SENSOR1 48 #define PIN_RFTX 51 // RF transmitter pin #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} @@ -420,10 +450,10 @@ enum { #define PIN_SR_DATA 0 #define PIN_SR_CLOCK 0 #define PIN_SR_OE 0 - #define PIN_SENSOR1 0 - #define PIN_SENSOR2 0 - #define PIN_RFTX 0 - #define PIN_FREE_LIST {} + #define PIN_SENSOR1 0 + #define PIN_SENSOR2 0 + #define PIN_RFTX 0 + #define PIN_FREE_LIST {} #define ETHER_BUFFER_SIZE 16384 #endif @@ -440,7 +470,7 @@ enum { inline void DEBUG_PRINT(const char*s) {printf("%s", s);} #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} #endif - + #else #define DEBUG_BEGIN(x) {} @@ -448,7 +478,7 @@ enum { #define DEBUG_PRINTLN(x) {} #endif - + /** Re-define avr-specific (e.g. PGM) types to use standard types */ #if !defined(ARDUINO) #include @@ -460,7 +490,7 @@ enum { #define now() time(0) #define pgm_read_byte(x) *(x) #define PSTR(x) x - #define F(x) x + #define F(x) x #define strcat_P strcat #define strcpy_P strcpy #define sprintf_P sprintf diff --git a/espconnect.cpp b/espconnect.cpp index c019aefd6..279f3b3dd 100644 --- a/espconnect.cpp +++ b/espconnect.cpp @@ -21,52 +21,51 @@ #include "espconnect.h" -const char html_ap_redirect[] PROGMEM = "

WiFi config saved. Now switching to station mode.

"; - String scan_network() { WiFi.mode(WIFI_STA); WiFi.disconnect(); byte n = WiFi.scanNetworks(); - String wirelessinfo; - if (n>32) n = 32; // limit to 32 ssids max - //Maintain old format of wireless network JSON for mobile app compat - wirelessinfo = "{\"ssids\":["; + String json; + if (n>40) n = 40; // limit to 40 ssids max + // maintain old format of wireless network JSON for mobile app compat + json = "{\"ssids\":["; for(int i=0;i. + * . */ - + #include "gpio.h" #if defined(ARDUINO) @@ -33,26 +33,26 @@ byte IOEXP::detectType(uint8_t address) { Wire.beginTransmission(address); if(Wire.endTransmission()!=0) return IOEXP_TYPE_NONEXIST; // this I2C address does not exist - + Wire.beginTransmission(address); Wire.write(NXP_INVERT_REG); // ask for polarity register Wire.endTransmission(); - + if(Wire.requestFrom(address, (uint8_t)2) != 2) return IOEXP_TYPE_UNKNOWN; uint8_t low = Wire.read(); uint8_t high = Wire.read(); if(low==0x00 && high==0x00) { return IOEXP_TYPE_9555; // PCA9555 has polarity register which inits to 0 } - return IOEXP_TYPE_8575; + return IOEXP_TYPE_8575; } void PCA9555::pinMode(uint8_t pin, uint8_t IOMode) { uint16_t config = i2c_read(NXP_CONFIG_REG); if(IOMode == OUTPUT) { - config &= ~(1 << pin); // config bit set to 0 for output pin + config &= ~(1 << pin); // config bit set to 0 for output pin } else { - config |= (1 << pin); // config bit set to 1 for input pin + config |= (1 << pin); // config bit set to 1 for input pin } i2c_write(NXP_CONFIG_REG, config); } @@ -62,7 +62,7 @@ uint16_t PCA9555::i2c_read(uint8_t reg) { Wire.beginTransmission(address); Wire.write(reg); Wire.endTransmission(); - if(Wire.requestFrom(address, (uint8_t)2) != 2) {return 0xFFFF; Serial.println("GPIO error");} + if(Wire.requestFrom(address, (uint8_t)2) != 2) {return 0xFFFF; DEBUG_PRINTLN("GPIO error");} uint16_t data0 = Wire.read(); uint16_t data1 = Wire.read(); return data0+(data1<<8); @@ -79,14 +79,14 @@ void PCA9555::i2c_write(uint8_t reg, uint16_t v){ void PCA9555::shift_out(uint8_t plat, uint8_t pclk, uint8_t pdat, uint8_t v) { if(plat=IOEXP_PIN) { os.mainio->digitalWrite(pin-IOEXP_PIN, value); - /* - // a pin on IO expander - byte data=pcf_read(MAIN_I2CADDR); - if(value) data|=(1<<(pin-IOEXP_PIN)); - else data&=~(1<<(pin-IOEXP_PIN)); - data |= MAIN_INPUTMASK; // make sure to enforce 1 for input pins - pcf_write(MAIN_I2CADDR, data);*/ } else { digitalWrite(pin, value); } @@ -291,7 +284,8 @@ void pinMode(int pin, byte mode) { #if defined(OSPI) if(mode==INPUT_PULLUP) { char cmd[BUFFER_MAX]; - snprintf(cmd, BUFFER_MAX, "gpio -g mode %d up", pin); + //snprintf(cmd, BUFFER_MAX, "gpio -g mode %d up", pin); + snprintf(cmd, BUFFER_MAX, "raspi-gpio set %d pu", pin); system(cmd); } #endif diff --git a/gpio.h b/gpio.h index a49890412..67f825666 100644 --- a/gpio.h +++ b/gpio.h @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * . + * . */ #ifndef GPIO_H @@ -45,13 +45,13 @@ class IOEXP { public: IOEXP(uint8_t addr=255) { address = addr; type = IOEXP_TYPE_NONEXIST; } - + virtual void pinMode(uint8_t pin, uint8_t IOMode) { } virtual uint16_t i2c_read(uint8_t reg) { return 0xFFFF; } virtual void i2c_write(uint8_t reg, uint16_t v) { } // software implementation of shift register out virtual void shift_out(uint8_t plat, uint8_t pclk, uint8_t pdat, uint8_t v) { } - + void digitalWrite(uint16_t v) { i2c_write(NXP_OUTPUT_REG, v); } @@ -100,7 +100,7 @@ class PCF8575 : public IOEXP { class PCF8574 : public IOEXP { public: PCF8574(uint8_t addr) { address = addr; type = IOEXP_TYPE_8574; } - void pinMode(uint8_t pin, uint8_t IOMode) { + void pinMode(uint8_t pin, uint8_t IOMode) { if(IOMode!=OUTPUT) inputmask |= (1< - OpenSprinkler WiFi Config - - +OpenSprinkler WiFi Config - + OpenSprinkler WiFi Config

- - - +
Detected SSIDsStrengthPower Level
(Scanning...)
+ +
Detected SSIDBSSIDSignalCh.
(Scanning...)
-

- - - - +
+
(Your WiFi SSID)
(Your WiFi Password)
+ + + + - +
SSID:
Password:
BSSID:
Channel:

- + \ No newline at end of file diff --git a/html/ap_update.html b/html/ap_update.html index 2ceca5c4b..c6434c134 100644 --- a/html/ap_update.html +++ b/html/ap_update.html @@ -49,9 +49,8 @@ } } }; - xhr.open('POST', 'update', true); + xhr.open('POST', '//' + window.location.hostname + ':8080' + window.location.pathname, true); xhr.send(fd); }); - - + \ No newline at end of file diff --git a/html/sta_update.html b/html/sta_update.html index 07434bc0e..2671a39bd 100644 --- a/html/sta_update.html +++ b/html/sta_update.html @@ -20,7 +20,7 @@

© OpenSprinkler (www.opensprinkler.com)

-
+ - - + \ No newline at end of file diff --git a/htmls.h b/htmls.h index 8f6fb7820..a4eeb2bc8 100644 --- a/htmls.h +++ b/htmls.h @@ -1,28 +1,26 @@ -const char ap_home_html[] PROGMEM = R"( -OpenSprinkler WiFi Config - - +const char ap_home_html[] PROGMEM = R"(OpenSprinkler WiFi Config - + OpenSprinkler WiFi Config

- - - +
Detected SSIDsStrengthPower Level
(Scanning...)
+ +
Detected SSIDBSSIDSignalCh.
(Scanning...)
-

- - - - +
+
(Your WiFi SSID)
(Your WiFi Password)
+ + + + - +
SSID:
Password:
BSSID:
Channel:

@@ -151,7 +165,7 @@ const char sta_update_html[] PROGMEM = R"(

© OpenSprinkler (www.opensprinkler.com)

-
+ diff --git a/images.h b/images.h index 29af8c5f6..e9f871066 100644 --- a/images.h +++ b/images.h @@ -17,17 +17,17 @@ enum { enum { LCD_CURSOR_REMOTEXT = 11, LCD_CURSOR_RAINDELAY,// 12 - LCD_CURSOR_SENSOR1, // 13 - LCD_CURSOR_SENSOR2, // 14 - LCD_CURSOR_NETWORK // 15 + LCD_CURSOR_SENSOR1, // 13 + 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 + LCD_CURSOR_SENSOR1, // 14 + LCD_CURSOR_NETWORK // 15 }; #endif @@ -83,43 +83,42 @@ const char _iconimage_remotext[] PROGMEM = { 0x62, 0x54, 0x48, 0x44, 0x22, 0x1F, 0x00, 0x00, }; - + const char _iconimage_raindelay[] PROGMEM = { 0x00, 0x00, 0x00, 0x1C, 0x22, 0x49, 0x49, 0x49, 0x59, 0x41, 0x41, 0x41, 0x22, 0x1C, 0x00, 0x00, - }; + }; const char _iconimage_rain[] PROGMEM = { 0x00, 0x00, 0x00, 0x00, 0x18, 0x24, 0x3E, 0x00, 0x2A, 0x2A, 0x00, 0x2A, 0x2A, 0x00, 0x00, 0x00, - }; - + }; + const char _iconimage_soil[] PROGMEM = { 0x00, 0x00, 0x10, 0x10, 0x28, 0x28, 0x44, 0x44, 0x8A, 0x8A, 0x92, 0x44, 0x38, 0x00, 0xC6, 0x38, }; - + const char _iconimage_ether_connected[] PROGMEM = { 0x00, 0x00, 0x00, 0x38, 0x28, 0x38, 0x10, 0x10, 0xFE, 0x44, 0x44, 0xEE, 0xAA, 0xEE, 0x00, 0x00, }; - + const char _iconimage_ether_disconnected[] PROGMEM = { 0x00, 0x00, 0x11, 0x0A, 0x04, 0xEA, 0xB1, 0xE0, 0x40, 0xFE, 0x44, 0xEE, 0xAA, 0xEE, 0x00, 0x00, }; - - + /* const char _iconimage_flow[] PROGMEM = { @@ -127,14 +126,14 @@ const char _iconimage_flow[] PROGMEM = { 0x03, 0x0F, 0x0F, 0x03, 0x1B, 0x18, 0x18, 0x18, 0x78, 0x78, 0x00, 0x00, - }; + }; const char _iconimage_pswitch[] PROGMEM = { 0x00, 0x00, 0x1E, 0x12, 0x12, 0x12, 0x1E, 0x02, 0x22, 0x32, 0x22, 0x20, 0x20, 0x70, 0x00, 0x00, - }; + }; */ @@ -204,7 +203,7 @@ const char _iconimage_soil[] PROGMEM = { B10001, B10001, B01110 -}; +}; /* const char _iconimage_flow[] PROGMEM = { @@ -230,11 +229,10 @@ const char _iconimage_pswitch[] PROGMEM = { }; // todo - + */ - -#else +#else #endif diff --git a/main.cpp b/main.cpp index 28504019b..b03dedf26 100644 --- a/main.cpp +++ b/main.cpp @@ -30,14 +30,13 @@ #include "mqtt.h" #include "sensors.h" - #if defined(ARDUINO) #if defined(ESP8266) - #include - extern "C" struct netif* eagle_lwip_getif (int netif_index); - ESP8266WebServer *w_server = NULL; // due to lwIP, both WiFi and wired use the unified w_server variable - ENC28J60lwIP eth(PIN_ETHER_CS); - bool useEth = false; + ESP8266WebServer *update_server = NULL; + OTF::OpenThingsFramework *otf = NULL; + DNSServer *dns = NULL; + ENC28J60lwIP eth(PIN_ETHER_CS); // ENC28J60 lwip for wired Ether + bool useEth = false; // tracks whether we are using WiFi or wired Ether connection static uint16_t led_blink_ms = LED_FAST_BLINK; #else EthernetServer *m_server = NULL; @@ -59,14 +58,14 @@ void remote_http_callback(char*); // Small variations have been added to the timing values below // to minimize conflicting events -#define NTP_SYNC_INTERVAL 86413L // NTP sync interval (in seconds) -#define CHECK_NETWORK_INTERVAL 601 // Network checking timeout (in seconds) -#define CHECK_WEATHER_TIMEOUT 21613L // Weather check interval (in seconds) +#define NTP_SYNC_INTERVAL 86413L // NTP sync interval (in seconds) +#define CHECK_NETWORK_INTERVAL 601 // Network checking timeout (in seconds) +#define CHECK_WEATHER_TIMEOUT 21613L // Weather check interval (in seconds) #define CHECK_WEATHER_SUCCESS_TIMEOUT 86400L // Weather check success interval (in seconds) -#define LCD_BACKLIGHT_TIMEOUT 15 // LCD backlight timeout (in seconds)) -#define PING_TIMEOUT 200 // Ping test timeout (in ms) -#define UI_STATE_MACHINE_INTERVAL 50 // how often does ui_state_machine run (in ms) -#define CLIENT_READ_TIMEOUT 5 // client read timeout (in seconds) +#define LCD_BACKLIGHT_TIMEOUT 15 // LCD backlight timeout (in seconds)) +#define PING_TIMEOUT 200 // Ping test timeout (in ms) +#define UI_STATE_MACHINE_INTERVAL 50 // how often does ui_state_machine run (in ms) +#define CLIENT_READ_TIMEOUT 5 // client read timeout (in seconds) #define DHCP_CHECKLEASE_INTERVAL 3600L // DHCP check lease interval (in 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 @@ -74,7 +73,7 @@ char tmp_buffer[TMP_BUFFER_SIZE*2]; // scratch buffer, make it twice as large to // ====== Object defines ====== OpenSprinkler os; // OpenSprinkler object -ProgramData pd; // ProgramdData object +ProgramData pd; // ProgramdData object /* ====== Robert Hillman (RAH)'s implementation of flow sensor ====== * flow_begin - time when valve turns on @@ -94,7 +93,7 @@ void flow_poll() { if(os.hw_rev == 2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 #endif byte curr_flow_state = digitalReadExt(PIN_SENSOR1); - if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { // only record on falling edge + if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { // only record on falling edge prev_flow_state = curr_flow_state; return; } @@ -103,7 +102,7 @@ void flow_poll() { flow_count++; /* RAH implementation of flow sensor */ - if (flow_start==0) { flow_gallons=0; flow_start=curr;} // if first pulse, record time + if (flow_start==0) { flow_gallons=0; flow_start=curr;} // if first pulse, record time if ((curr-flow_start)<90000) { flow_gallons=0; } // wait 90 seconds before recording flow_begin else { if (flow_gallons==1) { flow_begin = curr;}} flow_stop = curr; // get time in ms for stop @@ -115,10 +114,10 @@ void flow_poll() { // ====== UI defines ====== static char ui_anim_chars[3] = {'.', 'o', 'O'}; -#define UI_STATE_DEFAULT 0 -#define UI_STATE_DISP_IP 1 -#define UI_STATE_DISP_GW 2 -#define UI_STATE_RUNPROG 3 +#define UI_STATE_DEFAULT 0 +#define UI_STATE_DISP_IP 1 +#define UI_STATE_DISP_GW 2 +#define UI_STATE_RUNPROG 3 static byte ui_state = UI_STATE_DEFAULT; static byte ui_state_runprog = 0; @@ -158,13 +157,13 @@ void ui_state_machine() { if (!os.button_timeout) { os.lcd_set_brightness(0); - ui_state = UI_STATE_DEFAULT; // also recover to default state + ui_state = UI_STATE_DEFAULT; // also recover to default state } // read button, if something is pressed, wait till release byte button = os.button_read(BUTTON_WAIT_HOLD); - if (button & BUTTON_FLAG_DOWN) { // repond only to button down events + if (button & BUTTON_FLAG_DOWN) { // repond only to button down events os.button_timeout = LCD_BACKLIGHT_TIMEOUT; os.lcd_set_brightness(1); } else { @@ -175,7 +174,7 @@ void ui_state_machine() { case UI_STATE_DEFAULT: switch (button & BUTTON_MASK) { case BUTTON_1: - if (button & BUTTON_FLAG_HOLD) { // holding B1 + if (button & BUTTON_FLAG_HOLD) { // holding B1 if (digitalReadExt(PIN_BUTTON_3)==0) { // if B3 is pressed while holding B1, run a short test (internal test) if(!ui_confirm(PSTR("Start 2s test?"))) {ui_state = UI_STATE_DEFAULT; break;} manual_start_program(255, 0); @@ -183,19 +182,19 @@ void ui_state_machine() { os.lcd.clear(0, 1); os.lcd.setCursor(0, 0); #if defined(ESP8266) - if (useEth) { os.lcd.print(eth.gatewayIP()); } + if (useEth) { os.lcd.print(eth.gatewayIP()); } else { os.lcd.print(WiFi.gatewayIP()); } #else - { os.lcd.print(Ethernet.gatewayIP()); } + { os.lcd.print(Ethernet.gatewayIP()); } #endif os.lcd.setCursor(0, 1); os.lcd_print_pgm(PSTR("(gwip)")); ui_state = UI_STATE_DISP_IP; - } else { // if no other button is clicked, stop all zones + } 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 + } else { // clicking B1: display device IP and port os.lcd.clear(0, 1); os.lcd.setCursor(0, 0); #if defined(ESP8266) @@ -213,7 +212,7 @@ void ui_state_machine() { } break; case BUTTON_2: - if (button & BUTTON_FLAG_HOLD) { // holding B2 + if (button & BUTTON_FLAG_HOLD) { // holding B2 if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B2, display external IP os.lcd_print_ip((byte*)(&os.nvdata.external_ip), 1); os.lcd.setCursor(0, 1); @@ -225,11 +224,11 @@ void ui_state_machine() { os.lcd.setCursor(0, 1); os.lcd_print_pgm(PSTR("(lswc)")); ui_state = UI_STATE_DISP_IP; - } else { // if no other button is clicked, reboot + } else { // if no other button is clicked, reboot if(!ui_confirm(PSTR("Reboot device?"))) {ui_state = UI_STATE_DEFAULT; break;} os.reboot_dev(REBOOT_CAUSE_BUTTON); } - } else { // clicking B2: display MAC + } else { // clicking B2: display MAC os.lcd.clear(0, 1); byte mac[6]; os.load_hardware_mac(mac, useEth); @@ -238,24 +237,24 @@ void ui_state_machine() { } break; case BUTTON_3: - if (button & BUTTON_FLAG_HOLD) { // holding B3 - if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B3, display up time + if (button & BUTTON_FLAG_HOLD) { // holding B3 + if (digitalReadExt(PIN_BUTTON_1)==0) { // if B1 is pressed while holding B3, display up time os.lcd_print_time(os.powerup_lasttime); os.lcd.setCursor(0, 1); os.lcd_print_pgm(PSTR("(lupt) cause:")); os.lcd.print(os.last_reboot_cause); ui_state = UI_STATE_DISP_IP; - } else if(digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B3, reset to AP and reboot + } else if(digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B3, reset to AP and reboot #if defined(ESP8266) if(!ui_confirm(PSTR("Reset to AP?"))) {ui_state = UI_STATE_DEFAULT; break;} os.reset_to_ap(); #endif - } else { // if no other button is clicked, go to Run Program main menu + } else { // if no other button is clicked, go to Run Program main menu os.lcd_print_line_clear_pgm(PSTR("Run a Program:"), 0); os.lcd_print_line_clear_pgm(PSTR("Click B3 to list"), 1); ui_state = UI_STATE_RUNPROG; } - } else { // clicking B3: switch board display (cycle through master and all extension boards) + } else { // clicking B3: switch board display (cycle through master and all extension boards) os.status.display_board = (os.status.display_board + 1) % (os.nboards); } break; @@ -297,7 +296,6 @@ void ui_state_machine() { void do_setup() { /* Clear WDT reset flag. */ #if defined(ESP8266) - if(w_server) { delete w_server; w_server = NULL; } WiFi.persistent(false); led_blink_ms = LED_FAST_BLINK; #else @@ -307,14 +305,13 @@ void do_setup() { DEBUG_BEGIN(115200); DEBUG_PRINTLN(F("started")); - os.begin(); // OpenSprinkler init + os.begin(); // OpenSprinkler init os.options_setup(); // Setup options - pd.init(); // ProgramData init + pd.init(); // ProgramData init // set time using RTC if it exists if(RTC.exists()) setTime(RTC.get()); - os.lcd_print_time(os.now_tz()); // display time to LCD os.powerup_lasttime = os.now_tz(); @@ -343,8 +340,14 @@ void do_setup() { os.apply_all_station_bits(); // reset station bits - os.button_timeout = LCD_BACKLIGHT_TIMEOUT; + // because at reboot we don't know if special stations + // are in OFF state, here we explicitly turn them off + for(byte sid=0;sidsetErrorReplyCode(DNSReplyCode::NoError); + dns->start(53, "*", WiFi.softAPIP()); os.state = OS_STATE_CONNECTED; connecting_timeout = 0; - WiFi.setAutoReconnect(true); } else { led_blink_ms = LED_SLOW_BLINK; - start_network_sta(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); + if(os.sopt_load(SOPT_STA_BSSID_CHL).length()>0 && os.wifi_channel<255) { + start_network_sta(os.wifi_ssid.c_str(), os.wifi_pass.c_str(), (int32_t)os.wifi_channel, os.wifi_bssid); + } + else + start_network_sta(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); os.config_ip(); os.state = OS_STATE_CONNECTING; connecting_timeout = millis() + 120000L; @@ -462,13 +483,16 @@ void do_loop() os.lcd.print(F("Connecting to...")); os.lcd.setCursor(0, 2); os.lcd.print(os.wifi_ssid); - WiFi.setAutoReconnect(true); } break; case OS_STATE_TRY_CONNECT: led_blink_ms = LED_SLOW_BLINK; - start_network_sta_with_ap(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); + if(os.sopt_load(SOPT_STA_BSSID_CHL).length()>0 && os.wifi_channel<255) { + start_network_sta_with_ap(os.wifi_ssid.c_str(), os.wifi_pass.c_str(), (int32_t)os.wifi_channel, os.wifi_bssid); + } + else + start_network_sta_with_ap(os.wifi_ssid.c_str(), os.wifi_pass.c_str()); os.config_ip(); os.state = OS_STATE_CONNECTED; break; @@ -491,24 +515,34 @@ void do_loop() } break; + case OS_STATE_WAIT_REBOOT: + if(dns) dns->processNextRequest(); + if(otf) otf->loop(); + if(update_server) update_server->handleClient(); + break; + case OS_STATE_CONNECTED: if(os.get_wifi_mode() == WIFI_MODE_AP) { - w_server->handleClient(); + dns->processNextRequest(); + update_server->handleClient(); + otf->loop(); connecting_timeout = 0; if(os.get_wifi_mode()==WIFI_MODE_STA) { // already in STA mode, waiting to reboot break; } - if(WiFi.status()==WL_CONNECTED && WiFi.localIP()) { - os.iopts[IOPT_WIFI_MODE] = WIFI_MODE_STA; - os.iopts_save(); - os.reboot_dev(REBOOT_CAUSE_WIFIDONE); + if(WiFi.status()==WL_CONNECTED && WiFi.localIP() && reboot_timer!=0) { + DEBUG_PRINTLN(F("STA connected, set up reboot timer")); + reboot_timer = os.now_tz() + 10; + //os.reboot_dev(REBOOT_CAUSE_WIFIDONE); } } else { if(useEth || WiFi.status() == WL_CONNECTED) { - w_server->handleClient(); + update_server->handleClient(); + otf->loop(); connecting_timeout = 0; } else { + // todo: better handling of WiFi disconnection DEBUG_PRINTLN(F("WiFi disconnected, going back to initial")); os.state = OS_STATE_INITIAL; WiFi.disconnect(true); @@ -534,7 +568,7 @@ void do_loop() int len = client.read((uint8_t*) ether_buffer, size); if(len>0) { m_client = &client; - ether_buffer[len] = 0; // properly end the buffer + ether_buffer[len] = 0; // properly end the buffer handle_web_request(ether_buffer); m_client = NULL; break; @@ -544,7 +578,7 @@ void do_loop() client.stop(); } - wdt_reset(); // reset watchdog timer + wdt_reset(); // reset watchdog timer wdt_timeout = 0; #endif @@ -564,7 +598,7 @@ void do_loop() } } else { m_client = &client; - ether_buffer[len] = 0; // put a zero at the end of the packet + ether_buffer[len] = 0; // put a zero at the end of the packet handle_web_request(ether_buffer); m_client = 0; break; @@ -596,16 +630,16 @@ void do_loop() #if defined(ARDUINO) if (!ui_state) - os.lcd_print_time(os.now_tz()); // print time + os.lcd_print_time(curr_time); // print time #endif // ====== Check raindelay status ====== if (os.status.rain_delayed) { - if (curr_time >= os.nvdata.rd_stop_time) { // rain delay is over + if (curr_time >= os.nvdata.rd_stop_time) { // rain delay is over os.raindelay_stop(); } } else { - if (os.nvdata.rd_stop_time > curr_time) { // rain delay starts now + if (os.nvdata.rd_stop_time > curr_time) { // rain delay starts now os.raindelay_start(); } } @@ -664,7 +698,6 @@ void do_loop() if(pd.nprograms > 1) manual_start_program(2, 0); } - // ====== Schedule program data ====== ulong curr_minute = curr_time / 60; boolean match_found = false; @@ -673,9 +706,10 @@ void do_loop() // we only need to check once every minute if (curr_minute != last_minute) { last_minute = curr_minute; - // check through all programs - // Check weather 60s before program start: + apply_monthly_adjustment(curr_time); // check and apply monthly adjustment here, if it's selected + + // check through all programs for(pid=0; pid> s) & 1)) { + if (curr_time >= q->st && curr_time < q->st+q->dur) { + turn_on_station(sid, q->st+q->dur-curr_time); // the last parameter is expected run time + } //if curr_time > scheduled_start_time + } // if current station is not running + + // check if this station should be turned off if (q->st > 0) { - // if so, check if we should turn it off if (curr_time >= q->st+q->dur) { turn_off_station(sid, curr_time); } } - // if current station is not running, check if we should turn it on - if(!((bitvalue>>s)&1)) { - if (curr_time >= q->st && curr_time < q->st+q->dur) { - turn_on_station(sid); - } //if curr_time > scheduled_start_time - } // if current station is not running }//end_s }//end_bid @@ -805,7 +841,7 @@ void do_loop() int qi; for(qi=pd.nqueue-1;qi>=0;qi--) { q=pd.queue+qi; - if(!q->dur || curr_time>=q->st+q->dur) { + if(!q->dur || curr_time >= q->deque_time) { pd.dequeue(qi); } } @@ -817,7 +853,7 @@ void do_loop() os.apply_all_station_bits(); // check through runtime queue, calculate the last stop time of sequential stations - pd.last_seq_stop_time = 0; + memset(pd.last_seq_stop_times, 0, sizeof(ulong)*NUM_SEQ_GROUPS); ulong sst; byte re=os.iopts[IOPT_REMOTE_EXT_MODE]; q = pd.queue; @@ -825,13 +861,14 @@ void do_loop() sid = q->sid; bid = sid>>3; s = sid&0x07; + gid = os.get_station_gid(sid); // check if any sequential station has a valid stop time // and the stop time must be larger than curr_time sst = q->st + q->dur; if (sst>curr_time) { // only need to update last_seq_stop_time for sequential stations - if (os.attrib_seq[bid]&(1<pd.last_seq_stop_time ) ? sst : pd.last_seq_stop_time; + if (os.is_sequential_station(sid) && !re) { + pd.last_seq_stop_times[gid] = (sst > pd.last_seq_stop_times[gid]) ? sst : pd.last_seq_stop_times[gid]; } } } @@ -842,10 +879,10 @@ void do_loop() // turn off all stations os.clear_all_station_bits(); os.apply_all_station_bits(); - // reset runtime - pd.reset_runtime(); - // reset program busy bit - os.status.program_busy = 0; + pd.reset_runtime(); // reset runtime + os.status.program_busy = 0; // reset program busy bit + pd.clear_pause(); // TODO: what if pause hasn't expired and a new program is scheduled to run? + // log flow sensor reading if flow sensor is used if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { write_log(LOGDATA_FLOWSENSE, curr_time); @@ -859,53 +896,43 @@ void do_loop() }//if_some_program_is_running // handle master - if (os.status.mas>0) { - int16_t mas_on_adj = water_time_decode_signed(os.iopts[IOPT_MASTER_ON_ADJ]); - int16_t mas_off_adj= water_time_decode_signed(os.iopts[IOPT_MASTER_OFF_ADJ]); - byte masbit = 0; - - for(sid=0;sid>3; - s = sid&0x07; - // if this station is running and is set to activate master - if ((os.station_bits[bid]&(1<= q->st + mas_on_adj && + for (byte mas = MASTER_1; mas < NUM_MASTER_ZONES; mas++) { + + byte mas_id = os.masters[mas][MASOPT_SID]; + + if (mas_id) { // if this master station is set + int16_t mas_on_adj = os.get_on_adj(mas); + int16_t mas_off_adj = os.get_off_adj(mas); + + byte masbit = 0; + + for(sid = 0; sid < os.nstations; sid++) { + // skip if this is the master station + if (mas_id == sid + 1) continue; + + q = pd.queue + pd.station_qid[sid]; + + if (os.bound_to_master(q->sid, mas)) { + // check if timing is within the acceptable range + if (curr_time >= q->st + mas_on_adj && curr_time <= q->st + q->dur + mas_off_adj) { - masbit = 1; - break; + masbit = 1; + break; + } } } + os.set_station_bit(mas_id - 1, masbit); } - os.set_station_bit(os.status.mas-1, masbit); } - // handle master2 - if (os.status.mas2>0) { - int16_t mas_on_adj_2 = water_time_decode_signed(os.iopts[IOPT_MASTER_ON_ADJ_2]); - int16_t mas_off_adj_2= water_time_decode_signed(os.iopts[IOPT_MASTER_OFF_ADJ_2]); - byte masbit2 = 0; - for(sid=0;sid>3; - s = sid&0x07; - // if this station is running and is set to activate master - if ((os.station_bits[bid]&(1<= q->st + mas_on_adj_2 && - curr_time <= q->st + q->dur + mas_off_adj_2) { - masbit2 = 1; - break; - } - } + + if (os.status.pause_state) { + if (os.pause_timer > 0) { + os.pause_timer--; + } else { + os.clear_all_station_bits(); + pd.clear_pause(); } - os.set_station_bit(os.status.mas2-1, masbit2); } - // process dynamic events process_dynamic_events(curr_time); @@ -914,30 +941,10 @@ void do_loop() // read analog sensors read_all_sensors(); - + #if defined(ARDUINO) // process LCD display - if (!ui_state) { - os.lcd_print_station(1, ui_anim_chars[(unsigned long)curr_time%3]); - #if defined(ESP8266) - if(os.get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP()) { - os.lcd.setCursor(0, 2); - os.lcd.clear(2, 2); - if(os.status.program_busy) { - os.lcd.print(F("curr: ")); - uint16_t curr = os.read_current(); - os.lcd.print(curr); - os.lcd.print(F(" mA")); - - //Stop all stations if power usage is higher than MAX_CURRENT: - if (curr >= MAX_CURRENT) { - reset_all_stations_immediate(); - write_log(LOGDATA_CURRENT, curr_time); - } - } - } - #endif - } + if (!ui_state) { os.lcd_print_screen(ui_anim_chars[(unsigned long)curr_time%3]); } #endif @@ -972,40 +979,6 @@ void do_loop() } } -#if defined(ESP8266) - // dhcp and hw check: - static unsigned long dhcp_timeout = 0; - if(curr_time > dhcp_timeout) { - if (useEth) { - netif* intf = (netif*) eth.getNetIf(); - if (os.iopts[IOPT_USE_DHCP]) - dhcp_renew(intf); - - if (dhcp_timeout > 0 && !check_enc28j60()) { //ENC28J60 REGISTER CHECK!! - DEBUG_PRINT(F("Reconnect")); - //eth.resetEther(); - - // todo: lwip add timeout - int n = os.iopts[IOPT_USE_DHCP]?30:2; - while (!eth.connected() && n-- >0) { - DEBUG_PRINT("."); - delay(1000); - } - - if (!eth.connected()) { - os.nvdata.reboot_cause = REBOOT_CAUSE_NETWORK_FAIL; - os.status.safe_reboot = 1; - } - } - } - else if (os.iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED && os.get_wifi_mode()==WIFI_MODE_STA) { - netif* intf = eagle_lwip_getif(STATION_IF); - dhcp_renew(intf); - } - dhcp_timeout = curr_time + DHCP_CHECKLEASE_INTERVAL; - } -#endif - // perform ntp sync // instead of using curr_time, which may change due to NTP sync itself // we use Arduino's millis() method @@ -1020,14 +993,10 @@ void do_loop() // check weather check_weather(); - byte wuf = os.weather_update_flag; - if(wuf) { - if((wuf&WEATHER_UPDATE_EIP) | (wuf&WEATHER_UPDATE_WL)) { - // at the moment, we only send notification if water level or external IP changed - // the other changes, such as sunrise, sunset changes are ignored for notification - push_message(NOTIFY_WEATHER_UPDATE, (wuf&WEATHER_UPDATE_EIP)?os.nvdata.external_ip:0, - (wuf&WEATHER_UPDATE_WL)?os.iopts[IOPT_WATER_PERCENTAGE]:-1); - } + if(os.weather_update_flag & WEATHER_UPDATE_WL) { + // at the moment, we only send notification if water level changed + // the other changes, such as sunrise, sunset changes are ignored for notification + push_message(NOTIFY_WEATHER_UPDATE, 0, os.iopts[IOPT_WATER_PERCENTAGE]); os.weather_update_flag = 0; } static byte reboot_notification = 1; @@ -1077,12 +1046,12 @@ void check_weather() { ulong ntz = os.now_tz(); if (os.checkwt_success_lasttime && (ntz > os.checkwt_success_lasttime + CHECK_WEATHER_SUCCESS_TIMEOUT)) { // if last successful weather call timestamp is more than allowed threshold - // and if the selected adjustment method is not manual + // and if the selected adjustment method is not one of the manual methods // reset watering percentage to 100 - // todo: the firmware currently needs to be explicitly aware of which adjustment methods - // use manual watering percentage (namely methods 0 and 2), this is not ideal + // todo: the firmware currently needs to be explicitly aware of which adjustment methods, this is not ideal os.checkwt_success_lasttime = 0; - if(!(os.iopts[IOPT_USE_WEATHER]==0 || os.iopts[IOPT_USE_WEATHER]==2)) { + byte method = os.iopts[IOPT_USE_WEATHER]; + if(!(method==WEATHER_METHOD_MANUAL || method==WEATHER_METHOD_AUTORAINDELY || method==WEATHER_METHOD_MONTHLY)) { os.iopts[IOPT_WATER_PERCENTAGE] = 100; // reset watering percentage to 100% wt_rawData[0] = 0; // reset wt_rawData and errCode wt_errCode = HTTP_RQT_NOT_RECEIVED; @@ -1095,62 +1064,115 @@ void check_weather() { } #endif GetWeather(); - if (wt_errCode == HTTP_RQT_DNS_ERROR) - os.checkwt_lasttime = 0; } } /** Turn on a station * This function turns on a scheduled station */ -void turn_on_station(byte sid) { +void turn_on_station(byte sid, ulong duration) { // RAH implementation of flow sensor flow_start=0; - if (os.set_station_bit(sid, 1)) { - push_message(NOTIFY_STATION_ON, sid); + if (os.set_station_bit(sid, 1, duration)) { + push_message(NOTIFY_STATION_ON, sid, duration); + } +} + +// after removing element q, update remaining stations in its group +void handle_shift_remaining_stations(RuntimeQueueStruct* q, byte gid, ulong curr_time) { + RuntimeQueueStruct *s = pd.queue; + ulong q_end_time = q->st + q->dur; + ulong remainder = 0; + + if (q_end_time > curr_time) { // remainder is non-zero + remainder = (q->st < curr_time) ? q_end_time - curr_time : q->dur; + for ( ; s < pd.queue + pd.nqueue; s++) { + + // ignore station to be removed and stations in other groups + if (s == q || os.get_station_gid(s->sid) != gid || !os.is_sequential_station(s->sid)) { + continue; + } + + // only shift stations following current station + if (s->st >= q_end_time) { + s->st -= remainder; + s->deque_time -= remainder; + } + } } + pd.last_seq_stop_times[gid] -= remainder; + pd.last_seq_stop_times[gid] += 1; } /** Turn off a station * This function turns off a scheduled station - * and writes log record + * writes a log record and determines if + * the station should be removed from the queue */ -void turn_off_station(byte sid, ulong curr_time) { - os.set_station_bit(sid, 0); +void turn_off_station(byte sid, ulong curr_time, byte shift) { byte qid = pd.station_qid[sid]; - // ignore if we are turning off a station that's not running or scheduled to run - if (qid>=pd.nqueue) return; + // ignore request if trying to turn off a zone that's not even in the queue + if (qid >= pd.nqueue) { + return; + } + RuntimeQueueStruct *q = pd.queue + qid; + byte force_dequeue = 0; + byte station_bit = os.is_running(sid); + byte gid = os.get_station_gid(q->sid); + + if (shift && os.is_sequential_station(sid) && !os.iopts[IOPT_REMOTE_EXT_MODE]) { + handle_shift_remaining_stations(q, gid, curr_time); + } + + if (curr_time >= q->deque_time) { + if (station_bit) { + force_dequeue = 1; + } else { // if already off just remove from the queue + pd.dequeue(qid); + pd.station_qid[sid] = 0xFF; + return; + } + } else if (curr_time >= q->st + q->dur) { // end time and dequeue time are not equal due to master handling + if (!station_bit) { return; } + } //else { return; } + + os.set_station_bit(sid, 0); // RAH implementation of flow sensor - if (flow_gallons>1) { - if(flow_stop<=flow_begin) flow_last_gpm = 0; - else flow_last_gpm = (float) 60000/(float)((flow_stop-flow_begin)/(flow_gallons-1)); + if (flow_gallons > 1) { + if(flow_stop <= flow_begin) flow_last_gpm = 0; + else flow_last_gpm = (float) 60000 / (float)((flow_stop-flow_begin) / (flow_gallons - 1)); }// RAH calculate GPM, 1 pulse per gallon else {flow_last_gpm = 0;} // RAH if not one gallon (two pulses) measured then record 0 gpm - RuntimeQueueStruct *q = pd.queue+qid; - // check if the current time is past the scheduled start time, // because we may be turning off a station that hasn't started yet - if (curr_time > q->st) { + 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)) { pd.lastrun.station = sid; pd.lastrun.program = q->pid; pd.lastrun.duration = curr_time - q->st; pd.lastrun.endtime = curr_time; // log station run - write_log(LOGDATA_STATION, curr_time); + write_log(LOGDATA_STATION, curr_time); // LOG_TODO push_message(NOTIFY_STATION_OFF, sid, pd.lastrun.duration); } } - // dequeue the element - pd.dequeue(qid); - pd.station_qid[sid] = 0xFF; + // make necessary adjustments to sequential time stamps + int16_t station_delay = water_time_decode_signed(os.iopts[IOPT_STATION_DELAY_TIME]); + if (q->st + q->dur + station_delay == pd.last_seq_stop_times[gid]) { // if removing last station in group + pd.last_seq_stop_times[gid] = 0; + } + + if (force_dequeue) { + pd.dequeue(qid); + pd.station_qid[sid] = 0xFF; + } } /** Process dynamic events @@ -1193,13 +1215,45 @@ void process_dynamic_events(ulong curr_time) { if(qid==255) continue; RuntimeQueueStruct *q = pd.queue + qid; - if(q->pid>=99) continue; // if this is a manually started program, proceed - if(!en) turn_off_station(sid, curr_time); // if system is disabled, turn off zone - if(rd && !(igrd&(1<pid>=99) continue; // if this is a manually started program, proceed + if(!en) {q->deque_time=curr_time; turn_off_station(sid, curr_time);} // if system is disabled, turn off zone + if(rd && !(igrd&(1<deque_time=curr_time; turn_off_station(sid, curr_time);} // if rain delay is on and zone does not ignore rain delay, turn it off + if(sn1&& !(igs &(1<deque_time=curr_time; turn_off_station(sid, curr_time);} // if sensor1 is on and zone does not ignore sensor1, turn it off + if(sn2&& !(igs2&(1<deque_time=curr_time; turn_off_station(sid, curr_time);} // if sensor2 is on and zone does not ignore sensor2, turn it off + } + } +} + +/* Scheduler + * this function determines the appropriate start and dequeue times + * of stations bound to master stations with on and off adjustments + */ +void handle_master_adjustments(ulong curr_time, RuntimeQueueStruct *q) { + + int16_t start_adj = 0; + int16_t dequeue_adj = 0; + + for (byte mas = MASTER_1; mas < NUM_MASTER_ZONES; mas++) { + + byte masid = os.masters[mas][MASOPT_SID]; + + if (masid && os.bound_to_master(q->sid, mas)) { + + int16_t mas_on_adj = os.get_on_adj(mas); + int16_t mas_off_adj = os.get_off_adj(mas); + + start_adj = min(start_adj, mas_on_adj); + dequeue_adj = max(dequeue_adj, mas_off_adj); } } + + // in case of negative master on adjustment + // push back station's start time to allow sufficient time to turn on master + if (q->st - curr_time < abs(start_adj)) { + q->st += abs(start_adj); + } + + q->deque_time = q->st + q->dur + dequeue_adj; } /** Scheduler @@ -1207,52 +1261,45 @@ void process_dynamic_events(ulong curr_time) { * and schedules the start time of each station */ void schedule_all_stations(ulong curr_time) { - - ulong con_start_time = curr_time + 1; // concurrent start time - ulong seq_start_time = con_start_time; // sequential start time - + ulong con_start_time = curr_time + 1; // 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; + } int16_t station_delay = water_time_decode_signed(os.iopts[IOPT_STATION_DELAY_TIME]); - // if the sequential queue has stations running - if (pd.last_seq_stop_time > curr_time) { - seq_start_time = pd.last_seq_stop_time + station_delay; + ulong seq_start_times[NUM_SEQ_GROUPS]; // sequential start times + for(byte i=0;i curr_time) { + seq_start_times[i] = pd.last_seq_stop_times[i] + station_delay; + } } - RuntimeQueueStruct *q = pd.queue; byte re = os.iopts[IOPT_REMOTE_EXT_MODE]; + byte gid; + // go through runtime queue and calculate start time of each station for(;qst) continue; // if this queue element has already been scheduled, skip if(!q->dur) continue; // if the element has been marked to reset, skip - byte sid=q->sid; - byte bid=sid>>3; - byte s=sid&0x07; - - // if this is a sequential station and the controller is not in remote extension mode - // use sequential scheduling. station delay time apples - if (os.attrib_seq[bid]&(1<st = seq_start_time; - seq_start_time += q->dur; - seq_start_time += station_delay; // add station delay time + gid = os.get_station_gid(q->sid); + + // use sequential scheduling per sequential group + // apply station delay time + if (os.is_sequential_station(q->sid) && !re) { + q->st = seq_start_times[gid]; + seq_start_times[gid] += q->dur; + seq_start_times[gid] += station_delay; // add station delay time } else { // otherwise, concurrent scheduling q->st = con_start_time; // stagger concurrent stations by 1 second - //con_start_time++; - // stagger concurrent stations by delay time - if (station_delay>0) - con_start_time += station_delay; - else - con_start_time++; + con_start_time++; } - /*DEBUG_PRINT("["); - DEBUG_PRINT(sid); - DEBUG_PRINT(":"); - DEBUG_PRINT(q->st); - DEBUG_PRINT(","); - DEBUG_PRINT(q->dur); - DEBUG_PRINT("]"); - DEBUG_PRINTLN(pd.nqueue);*/ + + handle_master_adjustments(curr_time, q); + if (!os.status.program_busy) { os.status.program_busy = 1; // set program busy bit // start flow count @@ -1271,6 +1318,7 @@ void reset_all_stations_immediate() { os.clear_all_station_bits(); os.apply_all_station_bits(); pd.reset_runtime(); + pd.clear_pause(); } /** Reset all stations @@ -1310,7 +1358,7 @@ void manual_start_program(byte pid, byte uwt) { if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) continue; dur = 60; - if(pid==255) dur=2; + if(pid==255) dur=2; else if(pid>0) dur = water_time_resolve(prog.durations[sid]); if(uwt) { @@ -1352,7 +1400,9 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { return; if (ifttt_enabled) { - strcpy_P(postval, PSTR("{\"value1\":\"")); + strcpy_P(postval, PSTR("{\"value1\":\"On site [")); + os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval)); + strcat_P(postval, PSTR("], ")); } if (os.mqtt.enabled()) { @@ -1363,11 +1413,13 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { switch(type) { case NOTIFY_STATION_ON: - // todo: add IFTTT support for this event as well if (os.mqtt.enabled()) { sprintf_P(topic, PSTR("opensprinkler/station/%d"), lval); - strcpy_P(payload, PSTR("{\"state\":1}")); + sprintf_P(payload, PSTR("{\"state\":1,\"duration\":%d}"), (int)fval); } + + // todo: add IFTTT support for this event as well. + // currently no support due to the number of events exceeds 8 so need to use more than 1 byte break; case NOTIFY_STATION_OFF: @@ -1381,9 +1433,10 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { } } if (ifttt_enabled) { - char name[STATION_NAME_SIZE]; - os.get_station_name(lval, name); - sprintf_P(postval+strlen(postval), PSTR("Station %s closed. It ran for %d minutes %d seconds."), name, (int)fval/60, (int)fval%60); + strcat_P(postval, PSTR("station [")); + os.get_station_name(lval, postval+strlen(postval)); + strcat_P(postval, PSTR("] closed. It ran for ")); + sprintf_P(postval+strlen(postval), PSTR(" %d minutes %d seconds."), (int)fval/60, (int)fval%60); if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { sprintf_P(postval+strlen(postval), PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); @@ -1394,8 +1447,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { case NOTIFY_PROGRAM_SCHED: if (ifttt_enabled) { - if (sval) strcat_P(postval, PSTR("Manually scheduled ")); - else strcat_P(postval, PSTR("Automatically scheduled ")); + if (sval) strcat_P(postval, PSTR("manually scheduled ")); + else strcat_P(postval, PSTR("automatically scheduled ")); strcat_P(postval, PSTR("Program ")); { ProgramStruct prog; @@ -1413,7 +1466,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval); } if (ifttt_enabled) { - strcat_P(postval, PSTR("Sensor 1 ")); + strcat_P(postval, PSTR("sensor 1 ")); strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); } break; @@ -1425,7 +1478,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval); } if (ifttt_enabled) { - strcat_P(postval, PSTR("Sensor 2 ")); + strcat_P(postval, PSTR("sensor 2 ")); strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); } break; @@ -1437,7 +1490,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval); } if (ifttt_enabled) { - strcat_P(postval, PSTR("Rain delay ")); + strcat_P(postval, PSTR("rain delay ")); strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); } break; @@ -1449,10 +1502,10 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { volume = lval*volume; if (os.mqtt.enabled()) { strcpy_P(topic, PSTR("opensprinkler/sensor/flow")); - sprintf_P(payload, PSTR("{\"count\":%lu,\"volume\":%d.%02d}"), lval, (int)volume/100, (int)volume%100); + sprintf_P(payload, PSTR("{\"count\":%u,\"volume\":%d.%02d}"), lval, (int)volume/100, (int)volume%100); } if (ifttt_enabled) { - sprintf_P(postval+strlen(postval), PSTR("Flow count: %lu, volume: %d.%02d"), lval, (int)volume/100, (int)volume%100); + sprintf_P(postval+strlen(postval), PSTR("Flow count: %u, volume: %d.%02d"), lval, (int)volume/100, (int)volume%100); } break; @@ -1460,7 +1513,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { if (ifttt_enabled) { if(lval>0) { - strcat_P(postval, PSTR("External IP updated: ")); + strcat_P(postval, PSTR("external IP updated: ")); byte ip[4] = {(byte)((lval>>24)&0xFF), (byte)((lval>>16)&0xFF), (byte)((lval>>8)&0xFF), @@ -1468,7 +1521,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { ip2string(postval, ip); } if(fval>=0) { - sprintf_P(postval+strlen(postval), PSTR("Water level updated: %d%%."), (int)fval); + sprintf_P(postval+strlen(postval), PSTR("water level updated: %d%%."), (int)fval); } } break; @@ -1481,7 +1534,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { } if (ifttt_enabled) { #if defined(ARDUINO) - strcat_P(postval, PSTR("Rebooted. Device IP: ")); + strcat_P(postval, PSTR("rebooted. Device IP: ")); #if defined(ESP8266) { IPAddress _ip; @@ -1500,7 +1553,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { //strcat(postval, ":"); //itoa(_port, postval+strlen(postval), 10); #else - strcat_P(postval, PSTR("Process restarted.")); + strcat_P(postval, PSTR("process restarted.")); #endif } break; @@ -1543,7 +1596,7 @@ void make_logfile_name(char *name) { sd.chdir("/"); #endif #endif - strcpy(tmp_buffer+TMP_BUFFER_SIZE-10, 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); strcat(tmp_buffer, tmp_buffer+TMP_BUFFER_SIZE-10); strcat_P(tmp_buffer, PSTR(".txt")); @@ -1579,6 +1632,13 @@ void write_log(byte type, ulong curr_time) { #if defined(ESP8266) 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(byte i=0;i<7;i++) delete_log_oldest(); + } file = LittleFS.open(tmp_buffer, "w"); if(!file) return; } @@ -1669,7 +1729,7 @@ void write_log(byte type, ulong curr_time) { #if defined(ARDUINO) #if defined(ESP8266) - file.write((byte*)tmp_buffer, strlen(tmp_buffer)); + file.write((const uint8_t*)tmp_buffer, strlen(tmp_buffer)); #else file.write(tmp_buffer); #endif @@ -1680,6 +1740,28 @@ void write_log(byte type, ulong curr_time) { #endif } +#if defined(ESP8266) +bool delete_log_oldest() { + Dir dir = LittleFS.openDir(LOG_PREFIX); + time_t oldest_t = ULONG_MAX; + String oldest_fn; + while (dir.next()) { + time_t t = dir.fileCreationTime(); + if(t0) { + DEBUG_PRINT(F("deleting ")) + DEBUG_PRINTLN(LOG_PREFIX+oldest_fn); + LittleFS.remove(LOG_PREFIX+oldest_fn); + return true; + } else { + return false; + } +} +#endif /** Delete log file * If name is 'all', delete all logs @@ -1693,7 +1775,7 @@ void delete_log(char *name) { // delete all log files Dir dir = LittleFS.openDir(LOG_PREFIX); while (dir.next()) { - LittleFS.remove(dir.fileName()); + LittleFS.remove(LOG_PREFIX+dir.fileName()); } } else { // delete a single log file @@ -1730,7 +1812,6 @@ void delete_log(char *name) { #endif } - /** Perform network check * This function pings the router * to check if it's still online. @@ -1743,7 +1824,6 @@ void check_network() { // check network condition periodically if (os.status.req_network) { - DEBUG_PRINT(F("check_network begin")); os.status.req_network = 0; // change LCD icon to indicate it's checking network if (!ui_state) { @@ -1780,93 +1860,19 @@ void check_network() { if (os.start_network()) os.status.network_fails=0; } - DEBUG_PRINT(F("check_network end. failed=%s", failed)); } -#endif -#if defined(ARDUINO) -#if defined(ESP8266) - - if (os.status.program_busy) {return;} - - // check network condition periodically - if (os.status.req_network) { - os.status.req_network = 0; - - DEBUG_PRINT(F("check_network begin")); - // change LCD icon to indicate it's checking network - if (!ui_state) { - os.lcd.setCursor(LCD_CURSOR_NETWORK, 1); - os.lcd.print(">"); - } - - boolean failed = false; - if (!useEth) { //WIFI: - if (os.get_wifi_mode()!=WIFI_MODE_STA || WiFi.status()!=WL_CONNECTED || os.state!=OS_STATE_CONNECTED) return; - failed = !Ping.ping(WiFi.gatewayIP(), 1); - } else { //Ethernet - //if (!eth.connected()) return; - //failed = !Ping.ping(eth.gatewayIP(), 1); - os.status.req_ntpsync = 1; - failed = !getNtpTime(); - //failed = !eth.connected(); - } - - DEBUG_PRINT(F("check_network: failed=")); - DEBUG_PRINTLN(failed); - - 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; - } - } -#endif +#else + // nothing to do for other platforms #endif } -#if defined(ARDUINO) -#if defined(ESP8266) -#define NET_ENC28J60_EIR 0x1C -#define NET_ENC28J60_ESTAT 0x1D -#define NET_ENC28J60_ECON1 0x1F -#define NET_ENC28J60_EIR_RXERIF 0x01 -#define NET_ENC28J60_ESTAT_BUFFER 0x40 -#define NET_ENC28J60_ECON1_RXEN 0x04 -bool check_enc28j60() -{ - uint8_t stateEconRxen = eth.readreg((uint8_t) NET_ENC28J60_ECON1) & NET_ENC28J60_ECON1_RXEN; - // ESTAT.BUFFER rised on TX or RX error - // I think the test of this register is not necessary - EIR.RXERIF state checking may be enough - uint8_t stateEstatBuffer = eth.readreg((uint8_t) NET_ENC28J60_ESTAT) & NET_ENC28J60_ESTAT_BUFFER; - // EIR.RXERIF set on RX error - uint8_t stateEirRxerif = eth.readreg((uint8_t) NET_ENC28J60_EIR) & NET_ENC28J60_EIR_RXERIF; - if (!stateEconRxen || (stateEstatBuffer && stateEirRxerif)) { - DEBUG_PRINTLN(F("ENC28J60 FAILED - REBOOT!")) - return false; - } - return true; -} -#endif -#endif - /** Perform NTP sync */ -bool perform_ntp_sync() { +void perform_ntp_sync() { #if defined(ARDUINO) // 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 true; + if (!os.iopts[IOPT_USE_NTP] || os.status.program_busy) return; // do not perform ntp if network is not connected - if (!os.network_connected()) return true; + if (!os.network_connected()) return; if (os.status.req_ntpsync) { os.status.req_ntpsync = 0; @@ -1886,14 +1892,11 @@ bool perform_ntp_sync() { setTime(t); RTC.set(t); DEBUG_PRINTLN(RTC.get()); - return true; } } - return false; #else // nothing to do here // Linux will do this for you - return true; #endif } diff --git a/mainArduino.ino b/mainArduino.ino index 1aa649f70..702f3683c 100644 --- a/mainArduino.ino +++ b/mainArduino.ino @@ -1,21 +1,5 @@ -#include - -//#if defined(ESP8266) -#if 0 - struct tcp_pcb; - extern struct tcp_pcb* tcp_tw_pcbs; - extern "C" void tcp_abort (struct tcp_pcb* pcb); - void tcpCleanup() { // losing bytes work around - while(tcp_tw_pcbs) { tcp_abort(tcp_tw_pcbs); } - } -#else - #include -#endif - #include "OpenSprinkler.h" -extern OpenSprinkler os; - void do_setup(); void do_loop(); @@ -25,8 +9,4 @@ void setup() { void loop() { do_loop(); -//#if defined(ESP8266) -#if 0 - tcpCleanup(); -#endif } diff --git a/make.lin302 b/make.lin302 index 286afa04a..81501fdfc 100644 --- a/make.lin302 +++ b/make.lin302 @@ -6,22 +6,21 @@ LIBS = . \ $(ESP_LIBS)/ESP8266WebServer \ $(ESP_LIBS)/ESP8266mDNS \ $(ESP_LIBS)/LittleFS \ - /data/libs/lwIP_enc28j60 \ - /data/libs/SSD1306 \ - /data/libs/rc-switch \ - /data/libs/pubsubclient \ - /data/libs/ADS1X15 \ + $(ESP_LIBS)/lwIP_enc28j60 \ + $(ESP_LIBS)/Ticker \ + $(ESP_LIBS)/DNSServer \ + $(HOME)/Arduino/libraries/SSD1306 \ + $(HOME)/Arduino/libraries/rc-switch \ + $(HOME)/Arduino/libraries/pubsubclient \ + $(HOME)/Arduino/libraries/OTF-Controller-Library \ + $(HOME)/Arduino/libraries/WebSockets \ -ESP_ROOT = /data/esp8266_3.0.2/ +ESP_ROOT = $(HOME)/esp8266_3.0/ ESPCORE_VERSION = 302 -BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) +BUILD_ROOT = /tmp/$(MAIN_NAME) UPLOAD_SPEED = 460800 UPLOAD_VERB = -v -# for OS3.0 revision 1: reset mode is nodemcu -# UPLOAD_RESET = nodemcu -# Uncomment the line below for OS3.0 revision 0: reset mode is ck -# UPLOAD_RESET = ck FLASH_DEF = 4M2M FLASH_MODE = dio @@ -29,8 +28,6 @@ FLASH_SPEED = 80 F_CPU = 160000000L BOARD = generic -board_build.filesystem = littlefs -FS_TYPE = littlefs EXCLUDE_DIRS = ./build-1284 diff --git a/make.lin32 b/make.lin32 index ac5140679..26606dbd7 100644 --- a/make.lin32 +++ b/make.lin32 @@ -7,7 +7,7 @@ LIBS = . \ $(ESP_LIBS)/ESP8266mDNS \ ~/Arduino/libraries/SSD1306 \ ~/Arduino/libraries/rc-switch \ - ~/Arduino/libraries/EthernetENC \ + ~/Arduino/libraries/UIPEthernet \ ~/Arduino/libraries/pubsubclient \ ESP_ROOT = $(HOME)/esp8266_2.7.4/ diff --git a/make.os23 b/make.os23 index 1acf2a25c..47c3673ec 100644 --- a/make.os23 +++ b/make.os23 @@ -9,7 +9,7 @@ BOARD_TAG = 1284 MCU = atmega1284p VARIANT = sanguino F_CPU = 16000000L -ARDUINO_LIBS = EthernetENC Wire SdFat SPI pubsubclient +ARDUINO_LIBS = UIPEthernet Wire SdFat SPI pubsubclient MONITOR_PORT = /dev/ttyUSB0 MONITOR_BAUDRATE = 115200 include ./Arduino.mk diff --git a/mqtt.cpp b/mqtt.cpp index e4f3e238e..6732e707f 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -25,9 +25,8 @@ #include #if defined(ESP8266) #include - #include #else - #include + #include #endif #include @@ -49,21 +48,21 @@ #if defined(ENABLE_DEBUG) #if defined(ARDUINO) #include "TimeLib.h" - #define DEBUG_PRINTF(msg, ...) {Serial.printf(msg, ##__VA_ARGS__);} - #define DEBUG_TIMESTAMP(msg, ...) {time_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));} + #define DEBUG_PRINTF(msg, ...) {Serial.printf(msg, ##__VA_ARGS__);} + #define DEBUG_TIMESTAMP(msg, ...) {time_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 #include - #define DEBUG_PRINTF(msg, ...) {printf(msg, ##__VA_ARGS__);} - #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);} + #define DEBUG_PRINTF(msg, ...) {printf(msg, ##__VA_ARGS__);} + #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__);} + #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 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;} #else - #define DEBUG_PRINTF(msg, ...) {} - #define DEBUG_LOGF(msg, ...) {} - #define DEBUG_DURATION() {} + #define DEBUG_PRINTF(msg, ...) {} + #define DEBUG_LOGF(msg, ...) {} + #define DEBUG_DURATION() {} #endif #define str(s) #s @@ -72,28 +71,28 @@ extern OpenSprinkler os; extern char tmp_buffer[]; -#define MQTT_KEEPALIVE 60 -#define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config -#define MQTT_MAX_HOST_LEN 50 // Note: App is set to max 50 chars for broker name -#define MQTT_MAX_USERNAME_LEN 32 // Note: App is set to max 32 chars for username -#define MQTT_MAX_PASSWORD_LEN 32 // Note: App is set to max 32 chars for password -#define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit -#define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts - -#define MQTT_ROOT_TOPIC "opensprinkler" -#define MQTT_AVAILABILITY_TOPIC MQTT_ROOT_TOPIC "/availability" -#define MQTT_ONLINE_PAYLOAD "online" -#define MQTT_OFFLINE_PAYLOAD "offline" - -#define MQTT_SUCCESS 0 // Returned when function operated successfully -#define MQTT_ERROR 1 // Returned whan function failed - -char OSMqtt::_id[MQTT_MAX_ID_LEN + 1] = {0}; // Id to identify the client to the broker -char OSMqtt::_host[MQTT_MAX_HOST_LEN + 1] = {0}; // IP or host name of the broker -char OSMqtt::_username[MQTT_MAX_USERNAME_LEN + 1] = {0}; // username to connect to the broker -char OSMqtt::_password[MQTT_MAX_PASSWORD_LEN + 1] = {0}; // password to connect to the broker -int OSMqtt::_port = MQTT_DEFAULT_PORT; // Port of the broker (default 1883) -bool OSMqtt::_enabled = false; // Flag indicating whether MQTT is enabled +#define MQTT_KEEPALIVE 60 +#define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config +#define MQTT_MAX_HOST_LEN 50 // Note: App is set to max 50 chars for broker name +#define MQTT_MAX_USERNAME_LEN 32 // Note: App is set to max 32 chars for username +#define MQTT_MAX_PASSWORD_LEN 32 // Note: App is set to max 32 chars for password +#define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit +#define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts + +#define MQTT_ROOT_TOPIC "opensprinkler" +#define MQTT_AVAILABILITY_TOPIC MQTT_ROOT_TOPIC "/availability" +#define MQTT_ONLINE_PAYLOAD "online" +#define MQTT_OFFLINE_PAYLOAD "offline" + +#define MQTT_SUCCESS 0 // Returned when function operated successfully +#define MQTT_ERROR 1 // Returned whan function failed + +char OSMqtt::_id[MQTT_MAX_ID_LEN + 1] = {0}; // Id to identify the client to the broker +char OSMqtt::_host[MQTT_MAX_HOST_LEN + 1] = {0}; // IP or host name of the broker +char OSMqtt::_username[MQTT_MAX_USERNAME_LEN + 1] = {0}; // username to connect to the broker +char OSMqtt::_password[MQTT_MAX_PASSWORD_LEN + 1] = {0}; // password to connect to the broker +int OSMqtt::_port = MQTT_DEFAULT_PORT; // Port of the broker (default 1883) +bool OSMqtt::_enabled = false; // Flag indicating whether MQTT is enabled // Initialise the client libraries and event handlers. void OSMqtt::init(void) { @@ -106,7 +105,7 @@ void OSMqtt::init(void) { os.load_hardware_mac(mac, useEth); #else os.load_hardware_mac(mac, true); - #endif + #endif snprintf(id, MQTT_MAX_ID_LEN, "OS-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); #endif @@ -259,7 +258,6 @@ int OSMqtt::_connect(void) { state = mqtt_client->connect(_id, NULL, NULL, MQTT_AVAILABILITY_TOPIC, 0, true, MQTT_OFFLINE_PAYLOAD); if(state) break; tries++; - delay(10); } while(tries #include #include "espconnect.h" - - extern ESP8266WebServer *w_server; + + extern ESP8266WebServer *update_server; + extern OTF::OpenThingsFramework *otf; extern ENC28J60lwIP eth; - #define handle_return(x) {if(x==HTML_OK) server_send_content(); else server_send_result(x); w_server->client().stop(); return;} + #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.writeBodyChunk((char *)"%s",ether_buffer); else otf_send_result(req,res,x); return;} #else #include "SdFat.h" extern SdFat sd; extern EthernetClient *m_client; + #define OTF_PARAMS_DEF + #define OTF_PARAMS + #define FKV_SOURCE p #define handle_return(x) {return_code=x; return;} #endif @@ -56,6 +63,9 @@ #include "etherport.h" extern EthernetClient *m_client; + #define OTF_PARAMS_DEF + #define OTF_PARAMS + #define FKV_SOURCE p #define handle_return(x) {return_code=x; return;} #endif @@ -66,13 +76,15 @@ extern OpenSprinkler os; extern ProgramData pd; extern ulong flow_count; +#if !defined(ESP8266) static byte return_code; static char* get_buffer = NULL; +#endif BufferFiller bfill; void schedule_all_stations(ulong curr_time); -void turn_off_station(byte sid, ulong curr_time); +void turn_off_station(byte sid, ulong curr_time, byte shift=0); void process_dynamic_events(ulong curr_time); void check_network(time_t curr_time); void check_weather(time_t curr_time); @@ -89,100 +101,71 @@ int available_ether_buffer() { } // Define return error code -#define HTML_OK 0x00 -#define HTML_SUCCESS 0x01 -#define HTML_UNAUTHORIZED 0x02 -#define HTML_MISMATCH 0x03 -#define HTML_DATA_MISSING 0x10 -#define HTML_DATA_OUTOFBOUND 0x11 -#define HTML_DATA_FORMATERROR 0x12 -#define HTML_RFCODE_ERROR 0x13 -#define HTML_PAGE_NOT_FOUND 0x20 -#define HTML_NOT_PERMITTED 0x30 -#define HTML_UPLOAD_FAILED 0x40 -#define HTML_REDIRECT_HOME 0xFF +#define HTML_OK 0x00 +#define HTML_SUCCESS 0x01 +#define HTML_UNAUTHORIZED 0x02 +#define HTML_MISMATCH 0x03 +#define HTML_DATA_MISSING 0x10 +#define HTML_DATA_OUTOFBOUND 0x11 +#define HTML_DATA_FORMATERROR 0x12 +#define HTML_RFCODE_ERROR 0x13 +#define HTML_PAGE_NOT_FOUND 0x20 +#define HTML_NOT_PERMITTED 0x30 +#define HTML_UPLOAD_FAILED 0x40 +#define HTML_REDIRECT_HOME 0xFF +#if !defined(ESP8266) static const char html200OK[] PROGMEM = "HTTP/1.1 200 OK\r\n" ; -static const char htmlCacheCtrl[] PROGMEM = - "Cache-Control: max-age=604800, public\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" ; - -static const char htmlContentJSON[] PROGMEM = - "Content-Type: application/json\r\n" - "Connection: close\r\n" -; +#endif static const char htmlMobileHeader[] PROGMEM = - "\r\n" + "" ; static const char htmlReturnHome[] PROGMEM = "\n" ; -void print_html_standard_header() { - bfill.emit_p(PSTR("$F$F$F$F\r\n"), html200OK, htmlContentHTML, htmlNoCache, htmlAccessControl); - // todo: streamline this part as well - /*m_client->write((const uint8_t *)html200OK, strlen(html200OK)); - m_client->write((const uint8_t *)htmlContentHTML, strlen(htmlContentHTML)); - m_client->write((const uint8_t *)htmlNoCache, strlen(htmlNoCache)); - m_client->write((const uint8_t *)htmlAccessControl, strlen(htmlAccessControl)); - m_client->write((const uint8_t *)"\r\n", 2);*/ -} - -void print_json_header(bool bracket=true) { - bfill.emit_p(PSTR("$F$F$F$F\r\n"), html200OK, htmlContentJSON, htmlAccessControl, htmlNoCache); - if(bracket) bfill.emit_p(PSTR("{")); - // todo: streamline - /*m_client->write((const uint8_t *)html200OK, strlen(html200OK)); - m_client->write((const uint8_t *)htmlContentJSON, strlen(htmlContentJSON)); - m_client->write((const uint8_t *)htmlNoCache, strlen(htmlNoCache)); - m_client->write((const uint8_t *)htmlAccessControl, strlen(htmlAccessControl)); - if(bracket) m_client->write((const uint8_t *)"\r\n{", 3); - else m_client->write((const uint8_t *)"\r\n", 2);*/ -} - -byte 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; #if defined(ESP8266) - // for ESP8266: there are two cases: - // case 1: if str is NULL, we assume the key-val to search is already parsed in w_server - if(str==NULL) { - char _key[10]; - if(key_in_pgm) strcpy_P(_key, key); - else strcpy(_key, key); - if(w_server->hasArg(_key)) { - // copy value to buffer, and make sure it ends properly - strncpy(strbuf, w_server->arg(_key).c_str(), maxlen); - strbuf[maxlen-1]=0; - found=1; - } else { - strbuf[0]=0; - } - if (keyfound) *keyfound = found; +byte findKeyVal (const OTF::Request &req,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL) { + char* result = key_in_pgm?req.getQueryParameter((const __FlashStringHelper *)key):req.getQueryParameter(key); + if(result!=NULL) { + strncpy(strbuf, result, maxlen); + strbuf[maxlen-1]=0; + if(keyfound) *keyfound=1; return strlen(strbuf); + } else { + if(keyfound) *keyfound=0; } + return 0; +} #endif - // case 2: otherwise, assume the key-val is stored in str +byte 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; const char *kp; + if(str==NULL||strbuf==NULL||key==NULL) {return 0;} kp=key; -#if defined(ARDUINO) if (key_in_pgm) { // key is in program memory space while(*str && *str!=' ' && *str!='\n' && found==0){ @@ -200,11 +183,7 @@ byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,b } str++; } - } - else -#endif - // for Linux, key_in_pgm is always false - { + } else { while(*str && *str!=' ' && *str!='\n' && found==0){ if (*str == *kp){ kp++; @@ -246,18 +225,13 @@ void rewind_ether_buffer() { ether_buffer[0] = 0; } -void send_packet(bool final=false) { +void send_packet(OTF_PARAMS_DEF) { #if defined(ESP8266) - w_server->sendContent(ether_buffer); - if(final) { w_server->client().stop(); } - rewind_ether_buffer(); - return; + res.writeBodyChunk((char *)"%s",ether_buffer); #else m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); - if(final) { m_client->stop(); } - rewind_ether_buffer(); - return; #endif + rewind_ether_buffer(); } char dec2hexchar(byte dec) { @@ -266,87 +240,61 @@ char dec2hexchar(byte dec) { } #if defined(ESP8266) -String two_digits(uint8_t x) { - return String(x/10) + (x%10); -} - -String toHMS(ulong t) { - return two_digits(t/3600)+":"+two_digits((t/60)%60)+":"+two_digits(t%60); -} - -void server_send_content() { - w_server->sendContent(ether_buffer); - w_server->client().stop(); - rewind_ether_buffer(); +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")); + if(len>0) + res.writeHeader(F("Content-Length"), len); + res.writeHeader(F("Access-Control-Allow-Origin"), F("*")); + res.writeHeader(F("Cache-Control"), F("max-age=0, no-cache, no-store, must-revalidate")); + res.writeHeader(F("Connection"), F("close")); } - -void server_send_html(String html) { - w_server->send(200, "text/html", html); - w_server->client().stop(); -} - -void server_send_result(byte code) { - rewind_ether_buffer(); - print_json_header(false); - bfill.emit_p(PSTR("{\"result\":$D}"), code); - server_send_content(); +#else +void print_header(bool isJson=true) { + bfill.emit_p(PSTR("$F$F$F$F\r\n"), html200OK, isJson?htmlContentJSON:htmlContentHTML, htmlAccessControl, htmlNoCache); } +#endif -void server_send_result(byte code, const char* item) { - rewind_ether_buffer(); - print_json_header(false); - bfill.emit_p(PSTR("{\"result\":$D,\"item\":\"$S\"}"), code, item); - server_send_content(); -} +#if defined(ESP8266) -/*bool get_value_by_key(const char* key, long& val) { - if(w_server->hasArg(key)) { - val = w_server->arg(key).toInt(); - return true; - } else { - return false; - } +String two_digits(uint8_t x) { + return String(x/10) + (x%10); } -bool get_value_by_key(const char* key, String& val) { - if(w_server->hasArg(key)) { - val = w_server->arg(key); - return true; - } else { - return false; - } +String toHMS(ulong t) { + return two_digits(t/3600)+":"+two_digits((t/60)%60)+":"+two_digits(t%60); } -void append_key_value(String& html, const char* key, const ulong value) { - html += "\""; - html += key; - html += "\":"; - html += value; - html += ","; +void otf_send_result(OTF_PARAMS_DEF, byte code, const char *item = NULL) { + String json = F("{\"result\":"); + json += code; + if (!item) item = ""; + json += F(",\"item\":\""); + json += item; + json += F("\""); + json += F("}"); + print_header(OTF_PARAMS, true, json.length()); + res.writeBodyChunk((char *)"%s",json.c_str()); } -void append_key_value(String& html, const char* key, const int16_t value) { - html += "\""; - html += key; - html += "\":"; - html += value; - html += ","; +void update_server_send_result(byte code, const char* item = NULL) { + String json = F("{\"result\":"); + json += code; + if (!item) item = ""; + json += F(",\"item\":\""); + json += item; + json += F("\""); + json += F("}"); + update_server->sendHeader("Access-Control-Allow-Origin", "*"); // from esp8266 2.4 this has to be sent explicitly + update_server->send(200, "application/json", json); } -void append_key_value(String& html, const char* key, const String& value) { - html += "\""; - html += key; - html += "\":\""; - html += value; - html += "\","; -}*/ - String get_ap_ssid() { static String ap_ssid; if(!ap_ssid.length()) { byte mac[6]; WiFi.macAddress(mac); - ap_ssid += "OS_"; + ap_ssid = "OS_"; for(byte i=3;i<6;i++) { ap_ssid += dec2hexchar((mac[i]>>4)&0x0F); ap_ssid += dec2hexchar(mac[i]&0x0F); @@ -357,51 +305,77 @@ String get_ap_ssid() { static String scanned_ssids; -void on_ap_home() { +void on_ap_home(OTF_PARAMS_DEF) { if(os.get_wifi_mode()!=WIFI_MODE_AP) return; - server_send_html(FPSTR(ap_home_html)); + print_header(OTF_PARAMS, false, strlen_P((char*)ap_home_html)); + res.writeBodyChunk((char *) "%s", ap_home_html); } -void on_ap_scan() { +void on_ap_scan(OTF_PARAMS_DEF) { if(os.get_wifi_mode()!=WIFI_MODE_AP) return; - server_send_html(scanned_ssids); + print_header(OTF_PARAMS, true, scanned_ssids.length()); + res.writeBodyChunk((char *)"%s",scanned_ssids.c_str()); } -void on_ap_change_config() { +void on_ap_change_config(OTF_PARAMS_DEF) { if(os.get_wifi_mode()!=WIFI_MODE_AP) return; - if(w_server->hasArg("ssid")&&w_server->arg("ssid").length()!=0) { - os.wifi_ssid = w_server->arg("ssid"); - os.wifi_pass = w_server->arg("pass"); + char *ssid = req.getQueryParameter("ssid"); + if(ssid!=NULL&&strlen(ssid)!=0) { + os.wifi_ssid = ssid; + os.wifi_pass = req.getQueryParameter("pass"); + char *extra = req.getQueryParameter("extra"); + if(extra!=NULL) { // bssid and channel are in the format of xx:xx:xx:xx:xx:xx@ch + char *mac = strchr(extra, '@'); // search for symbol @ + if(mac==NULL || !isValidMAC(extra)) { // if not found or if MAC is invalid + otf_send_result(OTF_PARAMS, HTML_DATA_FORMATERROR, "bssid"); + return; + } + int chl = atoi(mac+1); // convert ch to integer + if(!(chl>=0 && chl<=255)) { // chl must be less than 255 + otf_send_result(OTF_PARAMS, HTML_DATA_OUTOFBOUND, "channel"); + return; + } + os.sopt_save(SOPT_STA_BSSID_CHL, extra); // save string to flash first + *mac=0; // terminate bssid string + str2mac(extra, os.wifi_bssid); // update controller variables + os.wifi_channel = chl; + } else { + os.sopt_save(SOPT_STA_BSSID_CHL, DEFAULT_EMPTY_STRING); // if extra is not present, write empty string + } os.sopt_save(SOPT_STA_SSID, os.wifi_ssid.c_str()); os.sopt_save(SOPT_STA_PASS, os.wifi_pass.c_str()); - server_send_result(HTML_SUCCESS); + otf_send_result(OTF_PARAMS, HTML_SUCCESS, nullptr); os.state = OS_STATE_TRY_CONNECT; os.lcd.setCursor(0, 2); os.lcd.print(F("Connecting...")); } else { - server_send_result(HTML_DATA_MISSING, "ssid"); + otf_send_result(OTF_PARAMS, HTML_DATA_MISSING, "ssid"); } } -void on_ap_try_connect() { +void reboot_in(uint32_t ms); + +void on_ap_try_connect(OTF_PARAMS_DEF) { if(os.get_wifi_mode()!=WIFI_MODE_AP) return; - ulong ip = (WiFi.status()==WL_CONNECTED)?(uint32_t)WiFi.localIP():0; - String html = "{\"ip\":"; - html += ip; - html += "}"; - server_send_html(html); + String json = "{"; + json += F("\"ip\":"); + json += (WiFi.status() == WL_CONNECTED) ? (uint32_t)WiFi.localIP() : 0; + json += F("}"); + print_header(OTF_PARAMS,true,json.length()); + res.writeBodyChunk((char *)"%s",json.c_str()); if(WiFi.status() == WL_CONNECTED && WiFi.localIP()) { - // IP received by client, restart - //os.reboot_dev(REBOOT_CAUSE_WIFIDONE); - } + os.iopts[IOPT_WIFI_MODE] = WIFI_MODE_STA; + os.iopts_save(); + DEBUG_PRINTLN(F("IP received by client, restart.")); + reboot_in(1000); + } } - #endif /** Check and verify password */ #if defined(ESP8266) -boolean process_password(boolean fwv_on_fail=false, char *p = NULL) +boolean process_password(OTF_PARAMS_DEF, boolean fwv_on_fail=false) #else boolean check_password(char *p) #endif @@ -410,31 +384,36 @@ boolean check_password(char *p) return true; #endif if (os.iopts[IOPT_IGNORE_PASSWORD]) return true; + #if !defined(ESP8266) if (m_client && !p) { p = get_buffer; - } -#endif - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pw"), true)) { + } + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pw"), true)) { urlDecode(tmp_buffer); - if (os.password_verify(tmp_buffer)) - return true; + if (os.password_verify(tmp_buffer)) return true; } -#if defined(ESP8266) - /* some pages will output fwv if password check has failed */ +#else + /*if(req.isCloudRequest()){ // password is not required if this is coming from cloud connection + return true; + }*/ + const char *pw = req.getQueryParameter("pw"); + if(pw != NULL && os.password_verify(pw)) return true; + + /* if fwv_on_fail is true, output fwv if password check has failed */ if(fwv_on_fail) { rewind_ether_buffer(); - print_json_header(); - bfill.emit_p(PSTR("\"$F\":$D}"), iopt_json_names+0, os.iopts[0]); - server_send_content(); + 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 { - server_send_result(HTML_UNAUTHORIZED); + otf_send_result(OTF_PARAMS, HTML_UNAUTHORIZED); } #endif return false; } -void server_json_stations_attrib(const char* name, byte *attrib) +void server_json_board_attrib(const char* name, byte *attrib) { bfill.emit_p(PSTR("\"$F\":["), name); for(byte i=0;i>3,s=sid&0x07; + if(os.attrib_spe[bid]&(1<type, data->sped); } if (available_ether_buffer() <=0 ) { - send_packet(); - } + send_packet(OTF_PARAMS); + } } bfill.emit_p(PSTR("}")); handle_return(HTML_OK); } - -void server_change_stations_attrib(char *p, char header, byte *attrib) +#if defined(ESP8266) +void server_change_board_attrib(const OTF::Request &req, char header, byte *attrib) +#else +void server_change_board_attrib(char *p, char header, byte *attrib) +#endif { char tbuf2[5] = {0, 0, 0, 0, 0}; byte bid; tbuf2[0]=header; for(bid=0;bidos.nstations) handle_return(HTML_DATA_OUTOFBOUND); - if(findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("st"), true) && - findKeyVal(p, tmp_buffer+1, TMP_BUFFER_SIZE-1, PSTR("sd"), true)) { + if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("st"), true) && + findKeyVal(FKV_SOURCE, tmp_buffer+1, TMP_BUFFER_SIZE-1, PSTR("sd"), true)) { tmp_buffer[0]-='0'; tmp_buffer[STATION_SPECIAL_DATA_SIZE] = 0; @@ -582,7 +606,9 @@ void server_change_stations() { for (byte i = 0; i < sizeof(gpioList) && found == false; i++) { if (gpioList[i] == gpio) found = true; } - if (!found || activeState > 1) handle_return(HTML_DATA_OUTOFBOUND); + if (!found || activeState > 1) { + handle_return(HTML_DATA_OUTOFBOUND); + } } else if (tmp_buffer[0] == STN_TYPE_HTTP) { #if !defined(ESP8266) urlDecode(tmp_buffer + 1); @@ -601,6 +627,8 @@ void server_change_stations() { } } + // handle special attribute after parameters have been processed + server_change_board_attrib(FKV_SOURCE, 'p', os.attrib_spe); os.attribs_save(); handle_return(HTML_SUCCESS); @@ -631,15 +659,14 @@ void manual_start_program(byte, byte); * pid: program index (0 refers to the first program) * uwt: use weather (i.e. watering percentage) */ -void server_manual_program() { +void server_manual_program(OTF_PARAMS_DEF) { #if defined(ESP8266) - char* p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; #endif - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); int pid=atoi(tmp_buffer); @@ -648,7 +675,7 @@ void server_manual_program() { } byte uwt = 0; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uwt"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uwt"), true)) { if(tmp_buffer[0]=='1') uwt = 1; } @@ -667,15 +694,14 @@ void server_manual_program() { * pw: password * t: station water time */ -void server_change_runonce() { +void server_change_runonce(OTF_PARAMS_DEF) { #if defined(ESP8266) - char* p = NULL; - if(!process_password()) return; - if(!findKeyVal(p,tmp_buffer,TMP_BUFFER_SIZE, "t", false)) handle_return(HTML_DATA_MISSING); + 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=[ @@ -730,14 +756,13 @@ void server_change_runonce() { * pw: password * pid:program index (-1 will delete all programs) */ -void server_delete_program() { +void server_delete_program(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; #endif - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); int pid=atoi(tmp_buffer); @@ -759,17 +784,16 @@ void server_delete_program() { * pw: password * pid: program index (must be 1 or larger, because we can't move up program 0) */ -void server_moveup_program() { +void server_moveup_program(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; #endif - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); - + int pid=atoi(tmp_buffer); if (!(pid>=1 && pid< pd.nprograms)) handle_return(HTML_DATA_OUTOFBOUND); @@ -781,7 +805,8 @@ void server_moveup_program() { /** * Change a program - * Command: /cp?pw=xxx&pid=x&v=[flag,days0,days1,[start0,start1,start2,start3],[dur0,dur1,dur2..]]&name=x + * Command: /cp?pw=xxx&pid=x&v=[flag,days0,days1,[start0,start1,start2,start3],[dur0,dur1,dur2..]] + * &name=x&from=x&to=x * * pw: password * pid: program index @@ -789,12 +814,13 @@ void server_moveup_program() { * 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 */ const char _str_program[] PROGMEM = "Program "; -void server_change_program() { +void server_change_program(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; #endif @@ -804,40 +830,58 @@ void server_change_program() { ProgramStruct prog; // parse program index - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); - + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); + int pid=atoi(tmp_buffer); if (!(pid>=-1 && pid< pd.nprograms)) handle_return(HTML_DATA_OUTOFBOUND); // check if "en" parameter is present - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { if(pid<0) handle_return(HTML_DATA_OUTOFBOUND); pd.set_flagbit(pid, PROGRAMSTRUCT_EN_BIT, (tmp_buffer[0]=='0')?0:1); handle_return(HTML_SUCCESS); } // check if "uwt" parameter is present - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uwt"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("uwt"), true)) { if(pid<0) handle_return(HTML_DATA_OUTOFBOUND); pd.set_flagbit(pid, PROGRAMSTRUCT_UWT_BIT, (tmp_buffer[0]=='0')?0:1); handle_return(HTML_SUCCESS); } - + // parse program name - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) { urlDecode(tmp_buffer); + strReplace(tmp_buffer, '\"', '\''); + strReplace(tmp_buffer, '\\', '/'); strncpy(prog.name, tmp_buffer, PROGRAM_NAME_SIZE); } else { strcpy_P(prog.name, _str_program); itoa((pid==-1)? (pd.nprograms+1): (pid+1), prog.name+8, 10); } + // parse program start date and end date + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("from"), true)) { + int16_t date = atoi(tmp_buffer); + if(!isValidDate(date)) handle_return(HTML_DATA_OUTOFBOUND); + prog.daterange[0] = date; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("to"), true)) { + date = atoi(tmp_buffer); + if(!isValidDate(date)) handle_return(HTML_DATA_OUTOFBOUND); + prog.daterange[1] = date; + } else { + handle_return(HTML_DATA_MISSING); + } + } + +#if !defined(ESP8266) // do a full string decoding if(p) urlDecode(p); +#endif #if defined(ESP8266) - if(!findKeyVal(p,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); - char *pv = tmp_buffer+1; + if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); + char *pv = tmp_buffer+1; #else // parse ad-hoc v=[... // search for the start of v=[ @@ -854,7 +898,7 @@ void server_change_program() { if(!found) handle_return(HTML_DATA_MISSING); pv+=3; #endif - + // parse headers *(char*)(&prog) = parse_listdata(&pv); prog.days[0]= parse_listdata(&pv); @@ -903,20 +947,20 @@ void server_json_options_main() { (oid>=IOPT_SUBNET_MASK1 && oid<=IOPT_SUBNET_MASK4)) 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 || oid==IOPT_STATION_DELAY_TIME) { v=water_time_decode_signed(v); } - + #if defined(ARDUINO) if (oid==IOPT_BOOST_TIME) { if (os.hw_type==HW_TYPE_AC || os.hw_type==HW_TYPE_UNKNOWN) continue; @@ -925,15 +969,15 @@ void server_json_options_main() { #else if (oid==IOPT_BOOST_TIME) continue; #endif - + #if defined(ESP8266) if (oid==IOPT_HW_VERSION) { v+=os.hw_rev; // for OS3.x, add hardware revision number } #endif - + if (oid==IOPT_SEQUENTIAL_RETIRED || oid==IOPT_URS_RETIRED || oid==IOPT_RSO_RETIRED) continue; - + #if defined(ARDUINO) #if defined(ESP8266) // for SSD1306, we can't adjust contrast or backlight @@ -956,21 +1000,33 @@ void server_json_options_main() { bfill.emit_p(PSTR(",")); } - bfill.emit_p(PSTR(",\"dexp\":$D,\"mexp\":$D,\"hwt\":$D}"), os.detect_exp(), MAX_EXT_BOARDS, os.hw_type); + bfill.emit_p(PSTR(",\"dexp\":$D,\"mexp\":$D,\"hwt\":$D,"), os.detect_exp(), MAX_EXT_BOARDS, os.hw_type); + // print master array + byte masid, optidx; + bfill.emit_p(PSTR("\"ms\":[")); + for (masid = 0; masid < NUM_MASTER_ZONES; masid++) { + for (optidx = 0; optidx < NUM_MASTER_OPTS; optidx++) { + bfill.emit_p(PSTR("$D"), os.masters[masid][optidx]); + bfill.emit_p((masid == NUM_MASTER_ZONES - 1 && optidx == NUM_MASTER_OPTS - 1) ? PSTR("]}") : PSTR(",")); + } + } } /** Output Options */ -void server_json_options() { +void server_json_options(OTF_PARAMS_DEF) { #if defined(ESP8266) - if(!process_password(true)) return; + if(!process_password(OTF_PARAMS,true)) return; rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); #endif - print_json_header(); + bfill.emit_p(PSTR("{")); server_json_options_main(); handle_return(HTML_OK); } -void server_json_programs_main() { +void server_json_programs_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"nprogs\":$D,\"nboards\":$D,\"mnp\":$D,\"mnst\":$D,\"pnsize\":$D,\"pd\":["), pd.nprograms, os.nboards, MAX_NUM_PROGRAMS, MAX_NUM_STARTTIMES, PROGRAM_NAME_SIZE); @@ -997,51 +1053,62 @@ void server_json_programs_main() { // 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"), tmp_buffer); + bfill.emit_p(PSTR("$S\",[$D,$D,$D]]"), tmp_buffer,prog.en_daterange,prog.daterange[0],prog.daterange[1]); if(pid!=pd.nprograms-1) { - bfill.emit_p(PSTR("\"],")); - } else { - bfill.emit_p(PSTR("\"]")); + bfill.emit_p(PSTR(",")); } // push out a packet if available // buffer size is getting small if (available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } } bfill.emit_p(PSTR("]}")); } /** Output program data */ -void server_json_programs() { +void server_json_programs(OTF_PARAMS_DEF) { #if defined(ESP8266) - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); #endif - - print_json_header(); - server_json_programs_main(); + bfill.emit_p(PSTR("{")); + server_json_programs_main(OTF_PARAMS); handle_return(HTML_OK); } /** Output script url form */ -void server_view_scripturl() { -#if defined(ESP8266) - // no authenticaion needed +void server_view_scripturl(OTF_PARAMS_DEF) { rewind_ether_buffer(); -#endif - - print_html_standard_header(); - bfill.emit_p(PSTR("
JavaScript:
Default:$S
Weather:
Default:$S
Password:
"), SOPT_JAVASCRIPTURL, DEFAULT_JAVASCRIPT_URL, SOPT_WEATHERURL, DEFAULT_WEATHER_URL); +#if defined(ESP8266) + 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"(
+ + + + + +
UI Source:
Weather:
Password:
+ +)"), + MAX_SOPTS_SIZE, SOPT_JAVASCRIPTURL, MAX_SOPTS_SIZE, SOPT_WEATHERURL, DEFAULT_JAVASCRIPT_URL, DEFAULT_WEATHER_URL); handle_return(HTML_OK); } -void server_json_controller_main() { +void server_json_controller_main(OTF_PARAMS_DEF) { byte bid, sid; ulong curr_time = os.now_tz(); bfill.emit_p(PSTR("\"devt\":$L,\"nbrd\":$D,\"en\":$D,\"sn1\":$D,\"sn2\":$D,\"rd\":$D,\"rdst\":$L," "\"sunrise\":$D,\"sunset\":$D,\"eip\":$L,\"lwc\":$L,\"lswc\":$L," - "\"lupt\":$L,\"lrbtc\":$D,\"lrun\":[$D,$D,$D,$L],"), + "\"lupt\":$L,\"lrbtc\":$D,\"lrun\":[$D,$D,$D,$L],\"pq\":$D,\"pt\":$L,\"nq\":$D,"), curr_time, os.nboards, os.status.enabled, @@ -1059,21 +1126,25 @@ void server_json_controller_main() { pd.lastrun.station, pd.lastrun.program, pd.lastrun.duration, - pd.lastrun.endtime); + pd.lastrun.endtime, + os.status.pause_state, + os.pause_timer, + pd.nqueue); #if defined(ESP8266) - bfill.emit_p(PSTR("\"RSSI\":$D,"), (int16_t)WiFi.RSSI()); + bfill.emit_p(PSTR("\"RSSI\":$D,\"otc\":{$O},\"otcs\":$D,"), (int16_t)WiFi.RSSI(), SOPT_OTC_OPTS, otf->getCloudStatus()); #endif byte mac[6] = {0}; -#if defined(ESP8266) +#if defined(ARDUINO) os.load_hardware_mac(mac, useEth); #else - os.load_hardware_mac(mac, false; + os.load_hardware_mac(mac, true); #endif + bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"wtdata\":$S,\"wterr\":$D,"), + bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","), SOPT_LOCATION, SOPT_JAVASCRIPTURL, SOPT_WEATHERURL, @@ -1081,7 +1152,8 @@ void server_json_controller_main() { SOPT_IFTTT_KEY, SOPT_MQTT_OPTS, strlen(wt_rawData)==0?"{}":wt_rawData, - wt_errCode); + wt_errCode, + SOPT_DEVICE_NAME); #if defined(ARDUINO) if(os.status.has_curr_sense) { @@ -1093,7 +1165,7 @@ void server_json_controller_main() { if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { bfill.emit_p(PSTR("\"flcrt\":$L,\"flwrt\":$D,"), os.flowcount_rt, FLOWCOUNT_RT_WINDOW); } - + bfill.emit_p(PSTR("\"sbits\":[")); // print sbits for(bid=0;bid= q->st) ? (q->st+q->dur-curr_time) : q->dur; if(rem>65535) rem = 0; } - bfill.emit_p(PSTR("[$D,$L,$L]"), (qid<255)?q->pid:0, rem, (qid<255)?q->st:0); + bfill.emit_p(PSTR("[$D,$L,$L,$D]"), + (qid<255)?q->pid:0, rem, (qid<255)?q->st:0, os.attrib_grp[sid]); bfill.emit_p((sid\n\n\n$F\n\n\n"), + bfill.emit_p(PSTR("var ver=$D,ipas=$D;"), OS_FW_VERSION, os.iopts[IOPT_IGNORE_PASSWORD]); - bfill.emit_p(PSTR("\n\n"), SOPT_JAVASCRIPTURL); + bfill.emit_p(PSTR(""), SOPT_JAVASCRIPTURL); handle_return(HTML_OK); } @@ -1166,44 +1252,44 @@ void server_home() * ap: reset to ap (ESP8266 only) * update: launch update script (for OSPi/OSBo/Linux only) */ -void server_change_values() +void server_change_values(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; extern uint32_t reboot_timer; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; -#endif - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rsn"), true)) { +#endif + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rsn"), true)) { reset_all_stations(); } #if !defined(ARDUINO) - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("update"), true) && atoi(tmp_buffer) > 0) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("update"), true) && atoi(tmp_buffer) > 0) { os.update_dev(); } #endif - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rbt"), true) && atoi(tmp_buffer) > 0) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rbt"), true) && atoi(tmp_buffer) > 0) { #if defined(ESP8266) os.status.safe_reboot = 0; - reboot_timer = os.now_tz() + 2; + reboot_timer = os.now_tz() + 1; handle_return(HTML_SUCCESS); #else - print_html_standard_header(); + print_header(false); //bfill.emit_p(PSTR("Rebooting...")); - send_packet(true); + send_packet(); + m_client->stop(); os.reboot_dev(REBOOT_CAUSE_WEB); #endif } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { if (tmp_buffer[0]=='1' && !os.status.enabled) os.enable(); else if (tmp_buffer[0]=='0' && os.status.enabled) os.disable(); } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rd"), true)) { + 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; @@ -1213,7 +1299,7 @@ void server_change_values() } else handle_return(HTML_DATA_OUTOFBOUND); } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("re"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("re"), true)) { if (tmp_buffer[0]=='1' && !os.iopts[IOPT_REMOTE_EXT_MODE]) { os.iopts[IOPT_REMOTE_EXT_MODE] = 1; os.iopts_save(); @@ -1222,11 +1308,11 @@ void server_change_values() os.iopts_save(); } } - + #if defined(ESP8266) - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ap"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ap"), true)) { os.reset_to_ap(); - } + } #endif handle_return(HTML_SUCCESS); } @@ -1250,31 +1336,37 @@ void string_remove_space(char *src) { * pw: password * jsp: Javascript path */ -void server_change_scripturl() { +void server_change_scripturl(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif - + #if defined(DEMO) handle_return(HTML_REDIRECT_HOME); #endif - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("jsp"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("jsp"), true)) { urlDecode(tmp_buffer); tmp_buffer[TMP_BUFFER_SIZE]=0; // make sure we don't exceed the maximum size // trim unwanted space characters string_remove_space(tmp_buffer); os.sopt_save(SOPT_JAVASCRIPTURL, tmp_buffer); } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wsp"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wsp"), true)) { urlDecode(tmp_buffer); tmp_buffer[TMP_BUFFER_SIZE]=0; string_remove_space(tmp_buffer); os.sopt_save(SOPT_WEATHERURL, tmp_buffer); } +#if defined(ESP8266) + 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 } /** @@ -1286,12 +1378,11 @@ void server_change_scripturl() { * loc: location * ttt: manual time (applicable only if ntp=0) */ -void server_change_options() +void server_change_options(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif @@ -1315,12 +1406,12 @@ void server_change_options() continue; prev_value = os.iopts[oid]; max_value = pgm_read_byte(iopt_max+oid); - + // will no longer support oxx option names // json name only char tbuf2[6]; strncpy_P0(tbuf2, iopt_json_names+oid*5, 5); - if(findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, tbuf2)) { + 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 || @@ -1334,9 +1425,9 @@ void server_change_options() os.iopts[oid] = v; } else { err = 1; - } + } } - + if (os.iopts[oid] != prev_value) { // if value has changed if (oid==IOPT_TIMEZONE || oid==IOPT_USE_NTP) time_change = true; if (oid>=IOPT_NTP_IP1 && oid<=IOPT_NTP_IP4) time_change = true; @@ -1345,32 +1436,46 @@ void server_change_options() } } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("loc"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("loc"), true)) { urlDecode(tmp_buffer); if (os.sopt_save(SOPT_LOCATION, tmp_buffer)) { // if location string has changed weather_change = true; } } uint8_t keyfound = 0; - if(findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wto"), true)) { + if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wto"), true)) { urlDecode(tmp_buffer); if (os.sopt_save(SOPT_WEATHER_OPTS, tmp_buffer)) { - weather_change = true; // if wto has changed + if(os.iopts[IOPT_USE_WEATHER]==WEATHER_METHOD_MONTHLY) { + load_wt_monthly(tmp_buffer); + apply_monthly_adjustment(os.now_tz()); + } else { + weather_change = true; // if wto has changed + } } //DEBUG_PRINTLN(os.sopt_load(SOPT_WEATHER_OPTS)); } - + keyfound = 0; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ifkey"), true, &keyfound)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ifkey"), true, &keyfound)) { urlDecode(tmp_buffer); os.sopt_save(SOPT_IFTTT_KEY, tmp_buffer); } else if (keyfound) { tmp_buffer[0]=0; os.sopt_save(SOPT_IFTTT_KEY, tmp_buffer); } - + + keyfound = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("otc"), true, &keyfound)) { + urlDecode(tmp_buffer); + os.sopt_save(SOPT_OTC_OPTS, tmp_buffer); + } else if (keyfound) { + tmp_buffer[0]=0; + os.sopt_save(SOPT_OTC_OPTS, tmp_buffer); + } + keyfound = 0; - if(findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mqtt"), true, &keyfound)) { + if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mqtt"), true, &keyfound)) { urlDecode(tmp_buffer); os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); os.status.req_mqtt_restart = true; @@ -1380,30 +1485,15 @@ void server_change_options() os.status.req_mqtt_restart = true; } - /* - // wtkey is retired - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wtkey"), true, &keyfound)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("dname"), true)) { urlDecode(tmp_buffer); - if (os.sopt_save(SOPT_WEATHER_KEY, tmp_buffer)) { // if weather key has changed - weather_change = true; - } - } else if (keyfound) { - tmp_buffer[0]=0; - os.sopt_save(SOPT_WEATHER_KEY, tmp_buffer); + strReplace(tmp_buffer, '\"', '\''); + strReplace(tmp_buffer, '\\', '/'); + os.sopt_save(SOPT_DEVICE_NAME, tmp_buffer); } - - keyfound = 0; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("blynk"), true, &keyfound)) { - urlDecode(tmp_buffer); - os.sopt_save(SOPT_BLYNK_TOKEN, tmp_buffer); - } else if (keyfound) { - tmp_buffer[0]=0; - os.sopt_save(SOPT_BLYNK_TOKEN, tmp_buffer); - } - */ // if not using NTP and manually setting time - if (!os.iopts[IOPT_USE_NTP] && findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ttt"), true)) { + if (!os.iopts[IOPT_USE_NTP] && findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ttt"), true)) { unsigned long t; t = strtoul(tmp_buffer, NULL, 0); // before chaging time, reset all stations to avoid messing up with timing @@ -1416,16 +1506,17 @@ void server_change_options() if (err) handle_return(HTML_DATA_OUTOFBOUND); os.iopts_save(); + os.populate_master(); if(time_change) { os.status.req_ntpsync = 1; } if(weather_change) { - os.iopts[IOPT_WATER_PERCENTAGE] = 100; // reset watering percentage to 100% - wt_rawData[0] = 0; // reset wt_rawData and errCode - wt_errCode = HTTP_RQT_NOT_RECEIVED; - os.checkwt_lasttime = 0; // force weather update + os.iopts[IOPT_WATER_PERCENTAGE] = 100; // reset watering percentage to 100% + wt_rawData[0] = 0; // reset wt_rawData and errCode + wt_errCode = HTTP_RQT_NOT_RECEIVED; + os.checkwt_lasttime = 0; // force weather update } if(sensor_change) { @@ -1443,21 +1534,20 @@ void server_change_options() * npw: new password * cpw: confirm new password */ -void server_change_password() { +void server_change_password(OTF_PARAMS_DEF) { #if defined(DEMO) - handle_return(HTML_SUCCESS); // do not allow chnaging password for demo + handle_return(HTML_SUCCESS); // do not allow chnaging password for demo return; #endif #if defined(ESP8266) - char* p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else char* p = get_buffer; #endif - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("npw"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("npw"), true)) { char tbuf2[TMP_BUFFER_SIZE]; - if (findKeyVal(p, tbuf2, TMP_BUFFER_SIZE, PSTR("cpw"), true) && strncmp(tmp_buffer, tbuf2, TMP_BUFFER_SIZE) == 0) { + if (findKeyVal(FKV_SOURCE, tbuf2, TMP_BUFFER_SIZE, PSTR("cpw"), true) && strncmp(tmp_buffer, tbuf2, TMP_BUFFER_SIZE) == 0) { urlDecode(tmp_buffer); os.sopt_save(SOPT_PASSWORD, tmp_buffer); handle_return(HTML_SUCCESS); @@ -1480,36 +1570,40 @@ void server_json_status_main() { } /** Output station status */ -void server_json_status() +void server_json_status(OTF_PARAMS_DEF) { #if defined(ESP8266) - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); #endif - print_json_header(); + + bfill.emit_p(PSTR("{")); server_json_status_main(); handle_return(HTML_OK); } /** * Test station (previously manual operation) - * Command: /cm?pw=xxx&sid=x&en=x&t=x + * Command: /cm?pw=xxx&sid=x&en=x&t=x&ssta=x * * pw: password * sid:station index (starting from 0) * en: enable (0 or 1) * t: timer (required if en=1) + * ssta: shift remaining stations */ -void server_change_manual() { +void server_change_manual(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; #endif int sid=-1; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { sid=atoi(tmp_buffer); if (sid<0 || sid>=os.nstations) handle_return(HTML_DATA_OUTOFBOUND); } else { @@ -1517,7 +1611,7 @@ void server_change_manual() { } byte en=0; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { en=atoi(tmp_buffer); } else { handle_return(HTML_DATA_MISSING); @@ -1526,7 +1620,7 @@ void server_change_manual() { uint16_t timer=0; unsigned long curr_time = os.now_tz(); if (en) { // if turning on a station, must provide timer - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("t"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("t"), true)) { timer=(uint16_t)atol(tmp_buffer); if (timer==0 || timer>64800) { handle_return(HTML_DATA_OUTOFBOUND); @@ -1540,9 +1634,9 @@ void server_change_manual() { RuntimeQueueStruct *q = NULL; byte sqi = pd.station_qid[sid]; // check if the station already has a schedule - if (sqi!=0xFF) { // if so, we will overwrite the schedule + if (sqi!=0xFF) { // if so, we will overwrite the schedule q = pd.queue+sqi; - } else { // otherwise create a new queue element + } else { // otherwise create a new queue element q = pd.enqueue(); } // if the queue is not full @@ -1550,7 +1644,7 @@ void server_change_manual() { q->st = 0; q->dur = timer; q->sid = sid; - q->pid = 99; // testing stations are assigned program index 99 + q->pid = 99; // testing stations are assigned program index 99 schedule_all_stations(curr_time); } else { handle_return(HTML_NOT_PERMITTED); @@ -1559,7 +1653,14 @@ void server_change_manual() { handle_return(HTML_DATA_MISSING); } } else { // turn off station - turn_off_station(sid, curr_time); + byte ssta = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ssta"), true)) { + ssta = atoi(tmp_buffer); + } + // mark station for removal + RuntimeQueueStruct *q = pd.queue + pd.station_qid[sid]; + q->deque_time = curr_time; + turn_off_station(sid, curr_time, ssta); } handle_return(HTML_SUCCESS); } @@ -1583,20 +1684,19 @@ int file_fgets(File file, char* buf, int maxsize) { * Get log data * Command: /jl?start=x&end=x&hist=x&type=x * - * hist: history (past n days) - * when hist is speceified, the start - * and end parameters below will be ignored + * hist: history (past n days) + * when hist is speceified, the start + * and end parameters below will be ignored * start: start time (epoch time) - * end: end time (epoch time) - * type: type of log records (optional) - * rs, rd, wl - * if unspecified, output all records + * end: end time (epoch time) + * type: type of log records (optional) + * rs, rd, wl + * if unspecified, output all records */ -void server_json_log() { +void server_json_log(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; #endif @@ -1604,7 +1704,7 @@ void server_json_log() { unsigned int start, end; // past n day history - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("hist"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("hist"), true)) { int hist = atoi(tmp_buffer); if (hist< 0 || hist > 365) handle_return(HTML_DATA_OUTOFBOUND); end = os.now_tz() / 86400L; @@ -1612,12 +1712,12 @@ void server_json_log() { } else { - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("start"), true)) handle_return(HTML_DATA_MISSING); + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("start"), true)) handle_return(HTML_DATA_MISSING); start = strtoul(tmp_buffer, NULL, 0) / 86400L; - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("end"), true)) handle_return(HTML_DATA_MISSING); - + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("end"), true)) handle_return(HTML_DATA_MISSING); + end = strtoul(tmp_buffer, NULL, 0) / 86400L; // start must be prior to end, and can't retrieve more than 365 days of data @@ -1627,18 +1727,16 @@ void server_json_log() { // extract the type parameter char type[4] = {0}; bool type_specified = false; - if (findKeyVal(p, type, 4, PSTR("type"), true)) + if (findKeyVal(FKV_SOURCE, type, 4, PSTR("type"), true)) type_specified = true; #if defined(ESP8266) // 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_json_header(false); - //bfill.emit_p(PSTR("$F$F$F$F\r\n"), html200OK, htmlContentJSON, htmlAccessControl, htmlNoCache); - //w_server->sendContent(ether_buffer); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif bfill.emit_p(PSTR("[")); @@ -1659,30 +1757,29 @@ void server_json_log() { FILE *file = fopen(get_filename_fullpath(tmp_buffer), "rb"); if(!file) continue; #endif // prepare to open log file - - int res; + int result; while(true) { #if defined(ESP8266) // do not use file.readBytes or readBytesUntil because it's very slow - res = file_fgets(file, tmp_buffer, TMP_BUFFER_SIZE); - if (res <= 0) { + result = file_fgets(file, tmp_buffer, TMP_BUFFER_SIZE); + if (result <= 0) { file.close(); break; } - tmp_buffer[res]=0; + tmp_buffer[result]=0; #elif defined(ARDUINO) - res = file.fgets(tmp_buffer, TMP_BUFFER_SIZE); - if (res <= 0) { + result = file.fgets(tmp_buffer, TMP_BUFFER_SIZE); + if (result <= 0) { file.close(); break; } #else if(fgets(tmp_buffer, TMP_BUFFER_SIZE, file)) { - res = strlen(tmp_buffer); + result = strlen(tmp_buffer); } else { - res = 0; + result = 0; } - if (res <= 0) { + if (result <= 0) { fclose(file); break; } @@ -1697,7 +1794,7 @@ void server_json_log() { tmp_buffer[TMP_BUFFER_SIZE-1]=0; // make sure the search will end while(*ptype && *ptype != ',') ptype++; if(*ptype != ',') continue; // didn't find comma, move on - ptype++; // move past comma + ptype++; // move past comma if (type_specified && strncmp(type, ptype+1, 2)) continue; @@ -1711,7 +1808,7 @@ void server_json_log() { // if the available ether buffer size is getting small // push out a packet if (available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } } } @@ -1722,21 +1819,20 @@ void server_json_log() { /** * Delete log * Command: /dl?pw=xxx&day=xxx - * /dl?pw=xxx&day=all + * /dl?pw=xxx&day=all * * pw: password * day:day (epoch time / 86400) * if day=all: delete all log files) */ -void server_delete_log() { +void server_delete_log(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("day"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("day"), true)) handle_return(HTML_DATA_MISSING); delete_log(tmp_buffer); @@ -1744,27 +1840,147 @@ void server_delete_log() { handle_return(HTML_SUCCESS); } +/** + * Command: "/pq?pw=x&dur=x" + * dur: duration (in units of seconds) + */ +void server_pause_queue(OTF_PARAMS_DEF) { +#if defined(ESP8266) + 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("dur"), true)) { + duration = strtoul(tmp_buffer, NULL, 0); + } + + pd.toggle_pause(duration); + + handle_return(HTML_SUCCESS); +} + +/** Output all JSON data, including jc, jp, jo, js, jn */ +void server_json_all(OTF_PARAMS_DEF) { +#if defined(ESP8266) + 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); + bfill.emit_p(PSTR("}")); + handle_return(HTML_OK); +} + +#if defined(ARDUINO) + +#if !defined(ESP8266) +static int freeHeap () { + extern int __heap_start, *__brkval; + int v; + return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); +} +#endif + +void server_json_debug(OTF_PARAMS_DEF) { +#if defined(ESP8266) + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + bfill.emit_p(PSTR("{\"date\":\"$S\",\"time\":\"$S\",\"heap\":$D"), __DATE__, __TIME__, +#if defined(ESP8266) + (uint16_t)ESP.getFreeHeap()); + FSInfo fs_info; + LittleFS.info(fs_info); + bfill.emit_p(PSTR(",\"flash\":$D,\"used\":$D,\"rssi\":$D,\"bssid\":\"$S\",\"bssidchl\":\"$O\"}"), + fs_info.totalBytes, fs_info.usedBytes, 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 + (uint16_t)freeHeap()); + bfill.emit_p(PSTR("}")); +#endif + handle_return(HTML_OK); +} +#endif + +/* +// fill ESP8266 flash with some dummy files +void server_fill_files(OTF_PARAMS_DEF) { + memset(ether_buffer, 65, 75); + ether_buffer[75] = 0; + FSInfo fs_info; + for(int index=1;index<64;index++) { + itoa(index, tmp_buffer, 10); + make_logfile_name(tmp_buffer); + DEBUG_PRINT(F("creating ")); + DEBUG_PRINT(tmp_buffer); + File file = LittleFS.open(tmp_buffer, "w"); + file.write(ether_buffer, strlen(ether_buffer)); + file.close(); + DEBUG_PRINTLN(F(" done. ")); + LittleFS.info(fs_info); + DEBUG_PRINTLN(fs_info.usedBytes); + } + handle_return(HTML_SUCCESS); +} +*/ + + /** * sc * Modus RS485 Sensor config * {"nr":1,"type":1,"group":0,"name":"myname","ip":123456789,"port":3000,"id":1,"ri":1000,"enable":1,"log":1} */ -void server_sensor_config() { +void server_sensor_config(OTF_PARAMS_DEF) +{ #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensor_config")); - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr if (nr == 0) handle_return(HTML_DATA_MISSING); - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type @@ -1773,47 +1989,47 @@ void server_sensor_config() { handle_return(HTML_SUCCESS); } - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("group"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("group"), true)) handle_return(HTML_DATA_MISSING); uint group = strtoul(tmp_buffer, NULL, 0); // Sensor group - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) handle_return(HTML_DATA_MISSING); char name[30]; strlcpy(name, tmp_buffer, sizeof(name)-1); // Sensor nr - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ip"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ip"), true)) handle_return(HTML_DATA_MISSING); uint32_t ip = strtoul(tmp_buffer, NULL, 0); // Sensor ip - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("port"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("port"), true)) handle_return(HTML_DATA_MISSING); uint port = strtoul(tmp_buffer, NULL, 0); // Sensor port - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("id"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("id"), true)) handle_return(HTML_DATA_MISSING); uint id = strtoul(tmp_buffer, NULL, 0); // Sensor modbus id - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ri"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ri"), true)) handle_return(HTML_DATA_MISSING); uint ri = strtoul(tmp_buffer, NULL, 0); // Read Interval (s) uint enable = 1; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enable"), true)) + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enable"), true)) enable = strtoul(tmp_buffer, NULL, 0); // 1=enable/0=disable uint log = 1; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) log = strtoul(tmp_buffer, NULL, 0); // 1=logging enabled/0=logging disabled - uint show = 1; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("show"), true)) + uint show = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("show"), true)) show = strtoul(tmp_buffer, NULL, 0); // 1=show enabled/0=show disabled SensorFlags_t flags = {.enable=enable, .log=log, .show=show}; - int res = sensor_define(nr, name, type, group, ip, port, id, ri, flags); - res = res >= HTTP_RQT_SUCCESS?HTML_SUCCESS:(256+res); - handle_return(res); + int ret = sensor_define(nr, name, type, group, ip, port, id, ri, flags); + ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); } /** @@ -1821,27 +2037,26 @@ void server_sensor_config() { * Modus RS485 Sensor set address help function * {"nr":1,"id":1} */ -void server_set_sensor_address() { +void server_set_sensor_address(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_set_sensor_address")); - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("id"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("id"), true)) handle_return(HTML_DATA_MISSING); uint id = strtoul(tmp_buffer, NULL, 0); // Sensor modbus id - int res = set_sensor_address(sensor_by_nr(nr), id); - res = res >= HTTP_RQT_SUCCESS?HTML_SUCCESS:(256+res); - handle_return(res); + int ret = set_sensor_address(sensor_by_nr(nr), id); + ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); } /** @@ -1849,41 +2064,39 @@ void server_set_sensor_address() { * @brief return one or all last sensor values * */ -void server_sensor_get() { +void server_sensor_get(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensor_get")); uint nr = 0; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif bfill.emit_p(PSTR("{\"datas\":[")); uint count = sensor_count(); + bool first = true; for (uint i = 0; i < count; i++) { Sensor_t *sensor = sensor_by_idx(i); - if (!sensor) - { - server_send_result(255); - return; - } - - if (nr != 0 && nr != sensor->nr) + if (!sensor || (nr != 0 && nr != sensor->nr)) continue; + if (first) first = false; else bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"nr\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"last\":$L}"), sensor->nr, sensor->last_native_data, @@ -1891,12 +2104,10 @@ void server_sensor_get() { getSensorUnit(sensor), getSensorUnitId(sensor), sensor->last_read); - if (i < count-1) - bfill.emit_p(PSTR(",")); // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } } bfill.emit_p(PSTR("]}")); @@ -1908,43 +2119,41 @@ void server_sensor_get() { * @brief read now and return status and last data * */ -void server_sensor_readnow() { - #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else +void server_sensor_readnow(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensor_readnow")); uint nr = 0; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif bfill.emit_p(PSTR("{\"datas\":[")); uint count = sensor_count(); + bool first = true; for (uint i = 0; i < count; i++) { Sensor_t *sensor = sensor_by_idx(i); - if (!sensor) - { - server_send_result(255); - return; - } - - if (nr != 0 && nr != sensor->nr) + if (!sensor || (nr != 0 && nr != sensor->nr)) continue; int status = read_sensor(sensor); + if (first) first = false; else bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), sensor->nr, status, @@ -1953,22 +2162,24 @@ void server_sensor_readnow() { getSensorUnit(sensor), getSensorUnitId(sensor)); - if (i < count-1) - bfill.emit_p(PSTR(",")); // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } } bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); } -void sensorconfig_json() { +void sensorconfig_json(OTF_PARAMS_DEF) { int count = sensor_count(); + bool first = true; for (int i = 0; i < count; i++) { Sensor_t *sensor = sensor_by_idx(i); + + if (first) first = false; else bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"last\":$L}"), sensor->nr, sensor->type, @@ -1986,12 +2197,10 @@ void sensorconfig_json() { sensor->flags.log, sensor->flags.show, sensor->last_read); - if (i < count-1) - bfill.emit_p(PSTR(",")); // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } } } @@ -2001,12 +2210,11 @@ void sensorconfig_json() { * @brief Lists all sensors * */ -void server_sensor_list() { +void server_sensor_list(OTF_PARAMS_DEF) { #if defined(ESP8266) - //char *p = NULL; - if(!process_password()) return; -#else - //char *p = get_buffer; + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensor_list")); @@ -2014,16 +2222,18 @@ void server_sensor_list() { DEBUG_PRINTLN(sensor_count()); #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif int count = sensor_count(); bfill.emit_p(PSTR("{\"count\":$D,"), count); bfill.emit_p(PSTR("\"sensors\":[")); - sensorconfig_json(); + sensorconfig_json(OTF_PARAMS); bfill.emit_p(PSTR("]")); bfill.emit_p(PSTR("}")); @@ -2035,13 +2245,13 @@ void server_sensor_list() { * @brief output sensorlog * */ -void server_sensorlog_list() { +void server_sensorlog_list(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif + ulong log_size = sensorlog_size(); DEBUG_PRINTLN(F("server_sensorlog_list")); @@ -2049,10 +2259,10 @@ void server_sensorlog_list() { //start / max: ulong startAt = 0; ulong maxResults = log_size; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("start"), true)) // Log start + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("start"), true)) // Log start startAt = strtoul(tmp_buffer, NULL, 0); - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) // Log Lines count + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) // Log Lines count maxResults = strtoul(tmp_buffer, NULL, 0); //Filters: @@ -2060,24 +2270,27 @@ void server_sensorlog_list() { uint type = 0; ulong after = 0; ulong before = 0; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr nr = strtoul(tmp_buffer, NULL, 0); - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) // Filter log for sensor-type + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) // Filter log for sensor-type type = strtoul(tmp_buffer, NULL, 0); - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // Filter time after + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // Filter time after after = strtoul(tmp_buffer, NULL, 0); - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // Filter time before + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // Filter time before before = strtoul(tmp_buffer, NULL, 0); #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif + bfill.emit_p(PSTR("{\"logsize\":$D,\"filesize\":$D,\"log\":["), log_size, sensorlog_filesize()); @@ -2116,7 +2329,7 @@ void server_sensorlog_list() { // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } if (++count >= maxResults) break; @@ -2131,21 +2344,22 @@ void server_sensorlog_list() { * @brief Delete/Clear Sensor log * */ -void server_sensorlog_clear() { +void server_sensorlog_clear(OTF_PARAMS_DEF) { #if defined(ESP8266) - //char *p = NULL; - if(!process_password()) return; -#else - //char *p = get_buffer; + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensorlog_clear")); #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif ulong log_size = sensorlog_size(); @@ -2161,24 +2375,23 @@ void server_sensorlog_clear() { * sb * define a program adjustment */ -void server_sensorprog_config() { +void server_sensorprog_config(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensorprog_config")); //uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr if (nr == 0) handle_return(HTML_DATA_MISSING); - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); uint type = strtoul(tmp_buffer, NULL, 0); // Adjustment type @@ -2187,33 +2400,33 @@ void server_sensorprog_config() { handle_return(HTML_SUCCESS); } - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) handle_return(HTML_DATA_MISSING); uint sensor = strtoul(tmp_buffer, NULL, 0); // Sensor nr - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) handle_return(HTML_DATA_MISSING); uint prog = strtoul(tmp_buffer, NULL, 0); // Program nr - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) handle_return(HTML_DATA_MISSING); double factor1 = atof(tmp_buffer); // Factor 1 - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor2"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor2"), true)) handle_return(HTML_DATA_MISSING); double factor2 = atof(tmp_buffer); // Factor 2 - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) handle_return(HTML_DATA_MISSING); double min = atof(tmp_buffer); // Min value - if (!findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) handle_return(HTML_DATA_MISSING); double max = atof(tmp_buffer); // Max value - int res = prog_adjust_define(nr, type, sensor, prog, factor1, factor2, min, max); - res = res >= HTTP_RQT_SUCCESS?HTML_SUCCESS:(256+res); - handle_return(res); + int ret = prog_adjust_define(nr, type, sensor, prog, factor1, factor2, min, max); + ret = ret >= HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); } void progconfig_json(ProgSensorAdjust_t *p, double current) { @@ -2231,12 +2444,14 @@ void progconfig_json(ProgSensorAdjust_t *p, double current) { void progconfig_json() { uint count = prog_adjust_count(); + bool first = true; for (uint i = 0; i < count; i++) { ProgSensorAdjust_t *p = prog_adjust_by_idx(i); double current = calc_sensor_watering_by_nr(p->nr); + + if (first) first = false; else bfill.emit_p(PSTR(",")); + progconfig_json(p, current); - if (i < count-1) - bfill.emit_p(PSTR(",")); } } @@ -2244,11 +2459,10 @@ void progconfig_json() { * se * define a program adjustment */ -void server_sensorprog_list() { +void server_sensorprog_list(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; -#else + if(!process_password(OTF_PARAMS)) return; +#else char *p = get_buffer; #endif @@ -2257,17 +2471,19 @@ void server_sensorprog_list() { uint nr = 0; int prog = -1; - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) nr = strtoul(tmp_buffer, NULL, 0); - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) prog = strtoul(tmp_buffer, NULL, 0); #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif uint n = prog_adjust_count(); @@ -2303,7 +2519,7 @@ void server_sensorprog_list() { // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } } bfill.emit_p(PSTR("]}")); @@ -2328,10 +2544,10 @@ const int sensor_types[] = { const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", - "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - voltage mode 0..4V", - "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - 0..3.3V to 0..100%", - "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - SMT50 moisture mode", - "OpenSprinkler analog extension board 2xADS1015 0x48/0x49 - SMT50 temperature mode", + "OpenSprinkler analog extension board 2xADS1015 x8 - voltage mode 0..4V", + "OpenSprinkler analog extension board 2xADS1015 x8 - 0..3.3V to 0..100%", + "OpenSprinkler analog extension board 2xADS1015 x8 - SMT50 moisture mode", + "OpenSprinkler analog extension board 2xADS1015 x8 - SMT50 temperature mode", //"OSPi analog input", "Remote sensor of an remote opensprinkler", "Sensor group with min value", @@ -2344,21 +2560,22 @@ const char* sensor_names[] = { * sf * List supported sensor types **/ -void server_sensor_types() { +void server_sensor_types(OTF_PARAMS_DEF) { #if defined(ESP8266) - //char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else - //char *p = get_buffer; + char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensor_types")); #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif bfill.emit_p(PSTR("{\"count\":$D,\"sensorTypes\":["), sizeof(sensor_types)/sizeof(int)); @@ -2374,7 +2591,7 @@ void server_sensor_types() { // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } } bfill.emit_p(PSTR("]}")); @@ -2386,21 +2603,22 @@ void server_sensor_types() { * du * List supported sensor types **/ -void server_usage() { +void server_usage(OTF_PARAMS_DEF) { #if defined(ESP8266) - //char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else - //char *p = get_buffer; + char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_usage")); #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif struct FSInfo fsinfo; @@ -2424,25 +2642,24 @@ void server_usage() { * sd * Program calc **/ -void server_sensorprog_calc() { +void server_sensorprog_calc(OTF_PARAMS_DEF) { #if defined(ESP8266) - char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else - char *p = get_buffer; + char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensorprog_calc")); //uint nr or uint prog - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr double adj = calc_sensor_watering_by_nr(nr); bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); handle_return(HTML_OK); } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) { uint prog = strtoul(tmp_buffer, NULL, 0); // Adjustment nr double adj = calc_sensor_watering(prog); bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); @@ -2470,22 +2687,22 @@ const char* prog_names[] = { * sh * List supported adjustment types */ -void server_sensorprog_types() -{ +void server_sensorprog_types(OTF_PARAMS_DEF) { #if defined(ESP8266) - //char *p = NULL; - if(!process_password()) return; + if(!process_password(OTF_PARAMS)) return; #else - //char *p = get_buffer; + char *p = get_buffer; #endif DEBUG_PRINTLN(F("server_sensorprog_types")); #if defined(ESP8266) + // 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_json_header(false); + print_header(OTF_PARAMS); #else - print_json_header(false); + print_header(); #endif bfill.emit_p(PSTR("{\"count\":$D,\"progTypes\":["), sizeof(prog_types)/sizeof(int)); @@ -2500,7 +2717,7 @@ void server_sensorprog_types() // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { - send_packet(); + send_packet(OTF_PARAMS); } } bfill.emit_p(PSTR("]}")); @@ -2513,21 +2730,30 @@ void server_sensorprog_types() * @brief backup sensor configuration * */ -void server_sensorconfig_backup() -{ +void server_sensorconfig_backup(OTF_PARAMS_DEF) { #if defined(ESP8266) - if(!process_password(true)) return; + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + +#if defined(ESP8266) + // 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 - print_json_header(); + bfill.emit_p(PSTR("\"sensors\":[")); - sensorconfig_json(); + sensorconfig_json(OTF_PARAMS); bfill.emit_p(PSTR("],")); - send_packet(); + send_packet(OTF_PARAMS); bfill.emit_p(PSTR("\"progadjust\":[")); progconfig_json(); bfill.emit_p(PSTR("]")); - send_packet(); + send_packet(OTF_PARAMS); bfill.emit_p(PSTR("}")); handle_return(HTML_OK); } @@ -2537,63 +2763,19 @@ void server_sensorconfig_backup() * @brief restore sensor configuration * */ -void server_sensorconfig_restore() -{ - -} - -/** Output all JSON data, including jc, jp, jo, js, jn */ -void server_json_all() { +void server_sensorconfig_restore(OTF_PARAMS_DEF) { #if defined(ESP8266) - if(!process_password(true)) return; - rewind_ether_buffer(); + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; #endif - print_json_header(); - bfill.emit_p(PSTR("\"settings\":{")); - server_json_controller_main(); - send_packet(); - bfill.emit_p(PSTR(",\"programs\":{")); - server_json_programs_main(); - send_packet(); - bfill.emit_p(PSTR(",\"options\":{")); - server_json_options_main(); - send_packet(); - bfill.emit_p(PSTR(",\"status\":{")); - server_json_status_main(); - send_packet(); - bfill.emit_p(PSTR(",\"stations\":{")); - server_json_stations_main(); - bfill.emit_p(PSTR("}")); - handle_return(HTML_OK); -} -#if defined(ARDUINO) && !defined(ESP8266) -static int freeHeap () { - extern int __heap_start, *__brkval; - int v; - return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); -} -#endif -#if defined(ARDUINO) -void server_json_debug() { - rewind_ether_buffer(); - print_json_header(); - bfill.emit_p(PSTR("\"date\":\"$S\",\"time\":\"$S\",\"heap\":$D"), __DATE__, __TIME__, -#if defined(ESP8266) - (uint16_t)ESP.getFreeHeap()); - FSInfo fs_info; - LittleFS.info(fs_info); - bfill.emit_p(PSTR(",\"flash\":$D,\"used\":$D}"), fs_info.totalBytes, fs_info.usedBytes); - #else - (uint16_t)freeHeap()); - bfill.emit_p(PSTR("}")); - #endif - handle_return(HTML_OK); + } -#endif -typedef void (*URLHandler)(void); + +typedef void (*URLHandler)(OTF_PARAMS_DEF); /* Server function urls * To save RAM space, each GET command keyword is exactly @@ -2623,6 +2805,7 @@ const char _url_keys[] PROGMEM = "su" "cu" "ja" + "pq" "sc" "sl" "sg" @@ -2638,34 +2821,36 @@ const char _url_keys[] PROGMEM = "sh" "sx" "sy" -#if defined(ARDUINO) +#if defined(ARDUINO) "db" -#endif + //"ff" +#endif ; // Server function handlers URLHandler urls[] = { - server_change_values, // cv + 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_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_json_status, // js + server_change_manual, // cm server_change_stations, // cs - server_json_stations, // jn + server_json_stations, // jn server_json_station_special,// je - server_json_log, // jl - server_delete_log, // dl - server_view_scripturl, // su + server_json_log, // jl + server_delete_log, // dl + server_view_scripturl, // su server_change_scripturl,// cu - server_json_all, // ja + server_json_all, // ja + server_pause_queue, // pq server_sensor_config, // sc server_sensor_list, // sl server_sensor_get, // sg @@ -2681,43 +2866,51 @@ URLHandler urls[] = { server_sensorprog_types, // sh server_sensorconfig_backup, // sx server_sensorconfig_restore, // sy -#if defined(ARDUINO) - server_json_debug, // db -#endif +#if defined(ARDUINO) + server_json_debug, // db + //server_fill_files, +#endif }; // handle Ethernet request #if defined(ESP8266) -void on_ap_update() { - String html = FPSTR(ap_update_html); - server_send_html(html); +void on_ap_update(OTF_PARAMS_DEF) { + print_header(OTF_PARAMS, false, strlen_P((char*)ap_update_html)); + res.writeBodyChunk((char *) "%s", ap_update_html); } -void on_sta_update() { - String html = FPSTR(sta_update_html); - server_send_html(html); +void on_sta_update(OTF_PARAMS_DEF) { + if(req.isCloudRequest()) otf_send_result(OTF_PARAMS, HTML_NOT_PERMITTED, "fw update"); + else { + print_header(OTF_PARAMS, false, strlen_P((char*)sta_update_html)); + res.writeBodyChunk((char *) "%s", sta_update_html); + } } void on_sta_upload_fin() { - if(!process_password()) { + if(!(update_server->hasArg("pw") && os.password_verify(update_server->arg("pw").c_str()))) { + update_server_send_result(HTML_UNAUTHORIZED); Update.end(false); return; } // finish update and check error if(!Update.end(true) || Update.hasError()) { - handle_return(HTML_UPLOAD_FAILED); + update_server_send_result(HTML_UPLOAD_FAILED); + //handle_return(HTML_UPLOAD_FAILED); } - - server_send_result(HTML_SUCCESS); + + update_server_send_result(HTML_SUCCESS); os.reboot_dev(REBOOT_CAUSE_FWUPDATE); } void on_ap_upload_fin() { on_sta_upload_fin(); } void on_sta_upload() { - HTTPUpload& upload = w_server->upload(); + HTTPUpload& upload = update_server->upload(); if(upload.status == UPLOAD_FILE_START){ - WiFiUDP::stopAll(); + // todo: + // WiFiUDP::stopAll(); + //mqtt_client->disconnect(); DEBUG_PRINT(F("upload: ")); DEBUG_PRINTLN(upload.filename); uint32_t maxSketchSpace = (ESP.getFreeSketchSpace()-0x1000)&0xFFFFF000; @@ -2725,44 +2918,17 @@ void on_sta_upload() { DEBUG_PRINT(F("begin failed ")); DEBUG_PRINTLN(maxSketchSpace); } - - } else if(upload.status == UPLOAD_FILE_WRITE) { - DEBUG_PRINT("."); - if(Update.write(upload.buf, upload.currentSize) != upload.currentSize) { - DEBUG_PRINTLN(F("size mismatch")); - } - - } else if(upload.status == UPLOAD_FILE_END) { - - DEBUG_PRINTLN(F("completed")); - - } else if(upload.status == UPLOAD_FILE_ABORTED){ - Update.end(); - DEBUG_PRINTLN(F("aborted")); - } - delay(0); -} -void on_ap_upload() { - HTTPUpload& upload = w_server->upload(); - if(upload.status == UPLOAD_FILE_START){ - DEBUG_PRINT(F("upload: ")); - DEBUG_PRINTLN(upload.filename); - uint32_t maxSketchSpace = (ESP.getFreeSketchSpace()-0x1000)&0xFFFFF000; - if(!Update.begin(maxSketchSpace)) { - DEBUG_PRINTLN(F("begin failed")); - DEBUG_PRINTLN(maxSketchSpace); - } } else if(upload.status == UPLOAD_FILE_WRITE) { DEBUG_PRINT("."); if(Update.write(upload.buf, upload.currentSize) != upload.currentSize) { DEBUG_PRINTLN(F("size mismatch")); } - + } else if(upload.status == UPLOAD_FILE_END) { - + DEBUG_PRINTLN(F("completed")); - + } else if(upload.status == UPLOAD_FILE_ABORTED){ Update.end(); DEBUG_PRINTLN(F("aborted")); @@ -2770,52 +2936,54 @@ void on_ap_upload() { delay(0); } +void on_ap_upload() { on_sta_upload(); } + void start_server_client() { - if(!w_server) return; - - w_server->on("/", server_home); // handle home page - w_server->on("/index.html", server_home); - w_server->on("/update", HTTP_GET, on_sta_update); // handle firmware update - w_server->on("/update", HTTP_POST, on_sta_upload_fin, on_sta_upload); - + if(!otf) return; + + otf->on("/", server_home); // handle home page + otf->on("/index.html", server_home); + 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); + // set up all other handlers char uri[4]; uri[0]='/'; uri[3]=0; - for(uint i=0;ion(uri, urls[i]); + otf->on(uri, urls[i]); } - w_server->begin(); + update_server->begin(); } void start_server_ap() { - if(!w_server) return; - + if(!otf) return; + scanned_ssids = scan_network(); String ap_ssid = get_ap_ssid(); start_network_ap(ap_ssid.c_str(), NULL); delay(500); - w_server->on("/", on_ap_home); - w_server->on("/jsap", on_ap_scan); - w_server->on("/ccap", on_ap_change_config); - w_server->on("/jtap", on_ap_try_connect); - w_server->on("/update", HTTP_GET, on_ap_update); - w_server->on("/update", HTTP_POST, on_ap_upload_fin, on_ap_upload); - w_server->onNotFound(on_ap_home); + otf->on("/", on_ap_home); + otf->on("/jsap", on_ap_scan); + otf->on("/ccap", on_ap_change_config); + otf->on("/jtap", on_ap_try_connect); + otf->on("/update", on_ap_update, OTF::HTTP_GET); + update_server->on("/update", HTTP_POST, on_ap_upload_fin, on_ap_upload); + otf->onMissingPage(on_ap_home); + update_server->begin(); // set up all other handlers char uri[4]; uri[0]='/'; uri[3]=0; - for(uint i=0;ion(uri, urls[i]); + otf->on(uri, urls[i]); } - - w_server->begin(); + os.lcd.setCursor(0, -1); os.lcd.print(F("OSAP:")); os.lcd.print(ap_ssid); @@ -2825,6 +2993,8 @@ void start_server_ap() { #endif +#if !defined(ESP8266) +// This funtion is only used for non-ESP8266 platforms void handle_web_request(char *p) { rewind_ether_buffer(); @@ -2834,8 +3004,9 @@ void handle_web_request(char *p) { char *dat = com+3; if(com[0]==' ') { - server_home(); // home page handler - send_packet(true); + server_home(); // home page handler + send_packet(); + m_client->stop(); } else { // server funtion handlers byte i; @@ -2851,14 +3022,10 @@ void handle_web_request(char *p) { (urls[i])(); ret = return_code; } else if ((com[0]=='j' && com[1]=='o') || - (com[0]=='j' && com[1]=='a')) { // for /jo and /ja we output fwv if password fails -#if defined(ESP8266) - if(process_password(false, dat)==false) { -#else + (com[0]=='j' && com[1]=='a')) { // for /jo and /ja we output fwv if password fails if(check_password(dat)==false) { -#endif - print_json_header(); - bfill.emit_p(PSTR("\"$F\":$D}"), + print_header(); + bfill.emit_p(PSTR("{\"$F\":$D}"), iopt_json_names+0, os.iopts[0]); ret = HTML_OK; } else { @@ -2872,11 +3039,7 @@ void handle_web_request(char *p) { ret = return_code; } else { // first check password -#if defined(ESP8266) - if(process_password(false, dat)==false) { -#else if(check_password(dat)==false) { -#endif ret = HTML_UNAUTHORIZED; } else { get_buffer = dat; @@ -2885,24 +3048,20 @@ void handle_web_request(char *p) { } } if (ret == -1) { -#if defined(ESP8266) - w_server->client().stop(); -#else - if (m_client) - m_client->stop(); -#endif + if (m_client) + m_client->stop(); return; - } + } switch(ret) { case HTML_OK: break; case HTML_REDIRECT_HOME: - print_html_standard_header(); + print_header(false); bfill.emit_p(PSTR("$F"), htmlReturnHome); break; default: - print_json_header(); - bfill.emit_p(PSTR("\"result\":$D}"), ret); + print_header(); + bfill.emit_p(PSTR("{\"result\":$D}"), ret); } break; } @@ -2910,17 +3069,21 @@ void handle_web_request(char *p) { if(i==sizeof(urls)/sizeof(URLHandler)) { // no server funtion found - print_json_header(); - bfill.emit_p(PSTR("\"result\":$D}"), HTML_PAGE_NOT_FOUND); + print_header(); + bfill.emit_p(PSTR("{\"result\":$D}"), HTML_PAGE_NOT_FOUND); } - send_packet(true); + send_packet(); + m_client->stop(); } } +#endif #if defined(ARDUINO) #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() { static bool configured = false; if(!configured) { @@ -2943,8 +3106,6 @@ ulong getNtpTime() { ulong gt = 0; while(tries1577836800UL) break; else gt = 0; delay(1000); @@ -2965,7 +3126,7 @@ ulong getNtpTime() { #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", @@ -2973,7 +3134,7 @@ ulong getNtpTime() { "time.cloudflare.com", "pool.ntp.org" }; static uint8_t sidx = 0; - + static byte packetBuffer[NTP_PACKET_SIZE]; byte ntpip[4] = { os.iopts[IOPT_NTP_IP1], @@ -2982,23 +3143,24 @@ ulong getNtpTime() { os.iopts[IOPT_NTP_IP4]}; byte tries=0; ulong gt = 0; + ulong startt = millis(); while(triesbegin(port); memset(packetBuffer, 0, NTP_PACKET_SIZE); - packetBuffer[0] = 0b11100011; // LI, Version, Mode - packetBuffer[1] = 0; // Stratum, or type of clock - packetBuffer[2] = 6; // Polling Interval + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval packetBuffer[3] = 0xEC; // Peer Clock Precision // 8 bytes of zero for Root Delay & Root Dispersion - packetBuffer[12] = 49; - packetBuffer[13] = 0x4E; - packetBuffer[14] = 49; - packetBuffer[15] = 52; - + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + // use one of the public NTP servers if ntp ip is unset - + DEBUG_PRINT(F("ntp: ")); int ret; if (!os.iopts[IOPT_NTP_IP1] || os.iopts[IOPT_NTP_IP1] == '0') { @@ -3022,7 +3184,7 @@ ulong getNtpTime() { udp->write(packetBuffer, NTP_PACKET_SIZE); udp->endPacket(); // end of sendNtpPacket - + // process response ulong timeout = millis()+2000; while(millis() < timeout) { @@ -3037,6 +3199,9 @@ ulong getNtpTime() { if(gt>1577836800UL) { udp->stop(); delete udp; + DEBUG_PRINT(F("took ")); + DEBUG_PRINT(millis()-startt); + DEBUG_PRINTLN(F("ms")); return gt; } } @@ -3044,7 +3209,7 @@ ulong getNtpTime() { tries++; udp->stop(); sidx=(sidx+1)%N_PUBLIC_SERVERS; - } + } if(tries==NTP_NTRIES) {DEBUG_PRINTLN(F("NTP failed!!"));} udp->stop(); delete udp; diff --git a/opensprinkler_server.h b/opensprinkler_server.h index a7c40125d..bdf383ae5 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -18,9 +18,9 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * . + * . */ - + #ifndef _OPENSPRINKLER_SERVER_H #define _OPENSPRINKLER_SERVER_H @@ -56,7 +56,7 @@ class BufferFiller { break; case 'E': //Double sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); - break; + break; case 'L': //ltoa(va_arg(ap, long), (char*) ptr, 10); ultoa(va_arg(ap, long), (char*) ptr, 10); // ray @@ -68,8 +68,8 @@ class BufferFiller { char d = va_arg(ap, int); *ptr++ = dec2hexchar((d >> 4) & 0x0F); *ptr++ = dec2hexchar(d & 0x0F); - continue; } + continue; case 'F': { PGM_P s = va_arg(ap, PGM_P); char d; @@ -80,15 +80,15 @@ class BufferFiller { case 'O': { uint16_t oid = va_arg(ap, int); file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); - break; } + break; default: *ptr++ = c; continue; } ptr += strlen((char*) ptr); } - *(ptr)=0; + *(ptr)=0; va_end(ap); } diff --git a/platformio.ini b/platformio.ini index e0c787923..bc6bdcffb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,21 +18,36 @@ include_dir = . [env:d1_mini] platform = espressif8266 board = d1_mini -board_build.filesystem = littlefs framework = arduino lib_ldf_mode = deep lib_deps = - https://github.com/dancol90/ESP8266Ping sui77/rc-switch @ ^2.6.3 https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 + https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library/archive/refs/heads/master.zip + https://github.com/Links2004/arduinoWebSockets/archive/refs/tags/2.3.5.zip RobTillaart/ADS1X15 - ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - upload_speed = 460800 monitor_speed=115200 board_build.flash_mode = dio -board_build.ldscript = eagle.flash.4m3m.ld +board_build.ldscript = eagle.flash.4m2m.ld board_build.f_cpu = 160000000L board_build.f_flash = 80000000L + +;[env:sanguino_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 +; greiman/SdFat @ 1.0.7 +; Wire +;build_src_filter = +<*> - +;monitor_speed=115200 + diff --git a/program.cpp b/program.cpp index e7d57319d..3930326d5 100644 --- a/program.cpp +++ b/program.cpp @@ -18,16 +18,16 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * . + * . */ #include #include "program.h" #if !defined(SECS_PER_DAY) -#define SECS_PER_MIN (60UL) +#define SECS_PER_MIN (60UL) #define SECS_PER_HOUR (3600UL) -#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) +#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) #endif // Declare static data members @@ -36,7 +36,8 @@ byte ProgramData::nqueue = 0; RuntimeQueueStruct ProgramData::queue[RUNTIME_QUEUE_SIZE]; byte ProgramData::station_qid[MAX_NUM_STATIONS]; LogStruct ProgramData::lastrun; -ulong ProgramData::last_seq_stop_time; +ulong ProgramData::last_seq_stop_times[NUM_SEQ_GROUPS]; + extern char tmp_buffer[]; void ProgramData::init() { @@ -45,9 +46,9 @@ void ProgramData::init() { } void ProgramData::reset_runtime() { - memset(station_qid, 0xFF, MAX_NUM_STATIONS); // reset station qid to 0xFF + memset(station_qid, 0xFF, MAX_NUM_STATIONS); // reset station qid to 0xFF nqueue = 0; - last_seq_stop_time = 0; + memset(last_seq_stop_times, 0, sizeof(last_seq_stop_times)); } /** Insert a new element to the queue @@ -124,6 +125,59 @@ void ProgramData::moveup(byte pid) { file_write_block(PROG_FILENAME, buf2, pos, PROGRAMSTRUCT_SIZE); } +void ProgramData::toggle_pause(ulong delay) { + if (os.status.pause_state) { // was paused + resume_stations(); + } else { + //os.clear_all_station_bits(); // TODO: this will affect logging + os.pause_timer = delay; + set_pause(); + } + os.status.pause_state = !os.status.pause_state; +} + +void turn_off_station(byte sid, ulong curr_time, byte shift=0); + +void ProgramData::set_pause() { + RuntimeQueueStruct *q = queue; + ulong curr_t = os.now_tz(); + + for (; q < queue + nqueue; q++) { + + turn_off_station(q->sid, curr_t); + if (curr_t>=q->st+q->dur) { // already finished running + continue; + } else if (curr_t>=q->st) { // currently running + q->dur -= (curr_t - q->st); // adjust remaining run time + q->st = curr_t + os.pause_timer; // push back start time + } else { // scheduled but not running yet + q->st += os.pause_timer; + } + q->deque_time += os.pause_timer; + byte gid = os.get_station_gid(q->sid); + if (q->st + q->dur > last_seq_stop_times[gid]) { + last_seq_stop_times[gid] = q->st + q->dur; // update last_seq_stop_times of the corresponding group + } + } +} + +void ProgramData::resume_stations() { + RuntimeQueueStruct *q = queue; + for (; q < queue + nqueue; q++) { + q->st -= os.pause_timer; + q->deque_time -= os.pause_timer; + q->st += 1; // adjust by 1 second to give time for scheduler + q->deque_time += 1; + } + clear_pause(); +} + +void ProgramData::clear_pause() { + os.status.pause_state = 0; + os.pause_timer = 0; + memset(last_seq_stop_times, 0, sizeof(last_seq_stop_times)); +} + /** Modify a program */ byte ProgramData::modify(byte pid, ProgramStruct *buf) { if (pid >= nprograms) return 0; @@ -174,8 +228,8 @@ int16_t ProgramStruct::starttime_decode(int16_t t) { /** Check if a given time matches the program's start day */ byte ProgramStruct::check_day_match(time_t t) { -#if defined(ARDUINO) // get current time from Arduino - byte weekday_t = weekday(t); // weekday ranges from [0,6] within Sunday being 1 +#if defined(ARDUINO) // get current time from Arduino + byte weekday_t = weekday(t); // weekday ranges from [0,6] within Sunday being 1 byte day_t = day(t); byte month_t = month(t); #else // get current time from RPI/BBB @@ -183,12 +237,23 @@ byte ProgramStruct::check_day_match(time_t t) { struct tm *ti = gmtime(&ct); byte weekday_t = (ti->tm_wday+1)%7; // tm_wday ranges from [0,6] with Sunday being 0 byte day_t = ti->tm_mday; - byte month_t = ti->tm_mon+1; // tm_mon ranges from [0,11] + byte month_t = ti->tm_mon+1; // tm_mon ranges from [0,11] #endif // get current time byte wd = (weekday_t+5)%7; byte dt = day_t; + if(en_daterange) { // check date range if enabled + int16_t currdate = date_encode(month_t, day_t); + // depending on whether daterange[0] or [1] is smaller: + if(daterange[0]<=daterange[1]) { + if(currdatedaterange[1]) return 0; + } else { + // this is the case where the defined range crosses the end of the year + if(currdate>daterange[1] && currdate. + * . */ #ifndef _PROGRAM_H #define _PROGRAM_H -#define MAX_NUM_PROGRAMS 40 // maximum number of programs -#define MAX_NUM_STARTTIMES 4 -#define PROGRAM_NAME_SIZE 32 -#define RUNTIME_QUEUE_SIZE MAX_NUM_STATIONS -#define PROGRAMSTRUCT_SIZE sizeof(ProgramStruct) +#define MAX_NUM_PROGRAMS 40 // maximum number of programs +#define MAX_NUM_STARTTIMES 4 +#define PROGRAM_NAME_SIZE 32 +#define RUNTIME_QUEUE_SIZE MAX_NUM_STATIONS +#define PROGRAMSTRUCT_SIZE sizeof(ProgramStruct) #include "OpenSprinkler.h" /** Log data structure */ @@ -40,71 +40,72 @@ struct LogStruct { uint32_t endtime; }; -#define PROGRAM_TYPE_WEEKLY 0 +#define PROGRAM_TYPE_WEEKLY 0 #define PROGRAM_TYPE_BIWEEKLY 1 -#define PROGRAM_TYPE_MONTHLY 2 +#define PROGRAM_TYPE_MONTHLY 2 #define PROGRAM_TYPE_INTERVAL 3 #define STARTTIME_SUNRISE_BIT 14 -#define STARTTIME_SUNSET_BIT 13 -#define STARTTIME_SIGN_BIT 12 +#define STARTTIME_SUNSET_BIT 13 +#define STARTTIME_SIGN_BIT 12 -#define PROGRAMSTRUCT_EN_BIT 0 +#define PROGRAMSTRUCT_EN_BIT 0 #define PROGRAMSTRUCT_UWT_BIT 1 /** Program data structure */ class ProgramStruct { public: - byte enabled :1; // HIGH means the program is enabled - + byte enabled:1; // HIGH means the program is enabled + // weather data - byte use_weather: 1; - + byte use_weather:1; + // odd/even restriction: // 0->none, 1->odd day (except 31st and Feb 29th) // 2->even day, 3->N/A - byte oddeven :2; - + byte oddeven:2; + // schedule type: // 0: weekly, 1->biweekly, 2->monthly, 3->interval - byte type :2; - + byte type:2; + // starttime type: // 0: repeating (give start time, repeat every, number of repeats) // 1: fixed start time (give arbitrary start times up to MAX_NUM_STARTTIMEs) - byte starttime_type: 1; + byte starttime_type:1; + + // enable date range + byte en_daterange:1; - // misc. data - byte dummy1: 1; - - // weekly: days[0][0..6] correspond to Monday till Sunday + // weekly: days[0][0..6] correspond to Monday till Sunday // bi-weekly:days[0][0..6] and [1][0..6] store two weeks // monthly: days[0][0..5] stores the day of the month (32 means last day of month) // interval: days[0] stores the interval (0 to 255), days[1] stores the starting day remainder (0 to 254) - byte days[2]; - + byte days[2]; + // When the program is a fixed start time type: - // up to MAX_NUM_STARTTIMES fixed start times + // up to MAX_NUM_STARTTIMES fixed start times // When the program is a repeating type: - // starttimes[0]: start time - // starttimes[1]: repeat count - // starttimes[2]: repeat every + // starttimes[0]: start time + // starttimes[1]: repeat count + // starttimes[2]: repeat every // Start time structure: - // bit 15 : not used, reserved - // if bit 14 == 1 : sunrise time +/- offset (by lowest 12 bits) - // or bit 13 == 1 : sunset time +/- offset (by lowest 12 bits) - // bit 12 : sign, 0 is positive, 1 is negative - // bit 11 : not used, reserved - // else: standard start time (value between 0 to 1440, by bits 0 to 10) + // bit 15 : not used, reserved + // if bit 14 == 1 : sunrise time +/- offset (by lowest 12 bits) + // or bit 13 == 1 : sunset time +/- offset (by lowest 12 bits) + // bit 12 : sign, 0 is positive, 1 is negative + // bit 11 : not used, reserved + // else: standard start time (value between 0 to 1440, by bits 0 to 10) int16_t starttimes[MAX_NUM_STARTTIMES]; uint16_t durations[MAX_NUM_STATIONS]; // duration / water time of each station - + char name[PROGRAM_NAME_SIZE]; + int16_t daterange[2] = {MIN_ENCODED_DATE, MAX_ENCODED_DATE}; // date range: start date, end date byte check_match(time_t t); int16_t starttime_decode(int16_t t); - + protected: byte check_day_match(time_t t); @@ -115,24 +116,30 @@ extern OpenSprinkler os; class RuntimeQueueStruct { public: - ulong st; // start time + ulong st; // start time uint16_t dur; // water time - byte sid; - byte pid; + byte sid; + byte pid; + ulong deque_time; // deque time, which can be larger than st+dur to allow positive master off adjustment time }; class ProgramData { -public: +public: static RuntimeQueueStruct queue[]; - static byte nqueue; // number of queue elements - static byte station_qid[]; // this array stores the queue element index for each scheduled station - static byte nprograms; // number of programs + static byte nqueue; // number of queue elements + static byte station_qid[]; // this array stores the queue element index for each scheduled station + static byte nprograms; // number of programs static LogStruct lastrun; - static ulong last_seq_stop_time; // the last stop time of a sequential station - + static ulong 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 set_pause(); + static void resume_stations(); + static void clear_pause(); + static void reset_runtime(); static RuntimeQueueStruct* enqueue(); // this returns a pointer to the next available slot in the queue - static void dequeue(byte qid); // this removes an element from the queue + static void dequeue(byte qid); // this removes an element from the queue static void init(); static void eraseall(); @@ -140,13 +147,13 @@ class ProgramData { static byte add(ProgramStruct *buf); static byte modify(byte pid, ProgramStruct *buf); static byte set_flagbit(byte pid, byte bid, byte value); - static void moveup(byte pid); + static void moveup(byte pid); static byte del(byte pid); static void drem_to_relative(byte days[2]); // absolute to relative reminder conversion static void drem_to_absolute(byte days[2]); -private: +private: static void load_count(); static void save_count(); }; -#endif // _PROGRAM_H +#endif // _PROGRAM_H diff --git a/sensors.cpp b/sensors.cpp index a0843a017..c629fdffb 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -299,9 +299,13 @@ void read_all_sensors() { int read_sensor_adc(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_adc")); if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - + if (sensor->id >= 8) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: - ADS1015 adc(sensor->port); + + int port = sensor->id < 4? 72 : 73; + int id = id % 4; + + ADS1015 adc(port); bool active = adc.begin(); if (active) adc.setGain(1); @@ -312,7 +316,7 @@ int read_sensor_adc(Sensor_t *sensor) { return HTTP_RQT_NOT_RECEIVED; //Read values: - sensor->last_native_data = adc.readADC(sensor->id); + sensor->last_native_data = adc.readADC(id); sensor->last_data = adc.toVoltage(sensor->last_native_data); switch(sensor->type) { @@ -334,7 +338,6 @@ int read_sensor_adc(Sensor_t *sensor) { DEBUG_PRINT(","); DEBUG_PRINTLN(sensor->last_data); - return HTTP_RQT_SUCCESS; } diff --git a/sensors.h b/sensors.h index 118be0346..f6d4d7887 100644 --- a/sensors.h +++ b/sensors.h @@ -39,10 +39,10 @@ #define SENSOR_NONE 0 //None or deleted sensor #define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode #define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode -#define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board 2xADS1015 48/49 - voltage mode 0..4V -#define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board 2xADS1015 48/49 - percent 0..3.3V to 0..100% -#define SENSOR_SMT50_MOIS 15 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 VWC [%] = (U * 50) : 3 -#define SENSOR_SMT50_TEMP 16 //New OpenSprinkler analog extension board 2xADS1015 48/49 - SMT50 T [°C] = (U – 0,5) * 100 +#define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board x8 - voltage mode 0..4V +#define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% +#define SENSOR_SMT50_MOIS 15 //New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_SMT50_TEMP 16 //New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 #define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input #define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler @@ -69,7 +69,7 @@ typedef struct Sensor { uint type; // 1..n type definition, 0=deleted uint group; // group assignment,0=no group uint32_t ip; // tcp-ip - uint port; // tcp-port / ADC: I2C Address 0x48/0x49 + uint port; // tcp-port / ADC: I2C Address 0x48/0x49 or 0/1 uint id; // modbus id / ADC: channel uint read_interval; // seconds uint32_t last_native_data; // last native sensor data diff --git a/utils.cpp b/utils.cpp index ee8369e68..f670f54a2 100644 --- a/utils.cpp +++ b/utils.cpp @@ -18,14 +18,14 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * . + * */ #include "utils.h" #include "OpenSprinkler.h" extern OpenSprinkler os; -#if defined(ARDUINO) // Arduino +#if defined(ARDUINO) // Arduino #if defined(ESP8266) #include @@ -73,7 +73,7 @@ void delay(ulong howLong) { struct timespec sleeper, dummy ; - sleeper.tv_sec = (time_t)(howLong / 1000) ; + sleeper.tv_sec = (time_t)(howLong / 1000) ; sleeper.tv_nsec = (long)(howLong % 1000) * 1000000 ; nanosleep (&sleeper, &dummy) ; @@ -84,7 +84,7 @@ void delayMicrosecondsHard (ulong howLong) struct timeval tNow, tLong, tEnd ; gettimeofday (&tNow, NULL) ; - tLong.tv_sec = howLong / 1000000 ; + tLong.tv_sec = howLong / 1000000 ; tLong.tv_usec = howLong % 1000000 ; timeradd (&tNow, &tLong, &tEnd) ; @@ -100,11 +100,11 @@ void delayMicroseconds (ulong howLong) /**/ if (howLong == 0) return ; - else if (howLong < 100) + else if (howLong < 100) delayMicrosecondsHard (howLong) ; else { - sleeper.tv_sec = wSecs ; + sleeper.tv_sec = wSecs ; sleeper.tv_nsec = (long)(uSecs * 1000L) ; nanosleep (&sleeper, NULL) ; } @@ -117,14 +117,13 @@ void initialiseEpoch() struct timeval tv ; gettimeofday (&tv, NULL) ; - epochMilli = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; + epochMilli = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; epochMicro = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)(tv.tv_usec) ; } ulong millis (void) { struct timeval tv ; - uint64_t now ; gettimeofday (&tv, NULL) ; @@ -171,119 +170,11 @@ unsigned int detect_rpi_rev() { #endif -/* -void write_to_file(const char *fn, const char *data, ulong size, ulong pos, bool trunc) { - -#if defined(ESP8266) - - File f; - if(trunc) { - f = LittleFS.open(fn, "w"); - } else { - f = LittleFS.open(fn, "r+"); - if(!f) f = LittleFS.open(fn, "w"); - } - if(!f) return; - if(pos) f.seek(pos, SeekSet); - if(size==0) { - f.write((byte*)" ", 1); // hack to circumvent FS bug involving writing empty file - } else { - f.write((byte*)data, size); - } - f.close(); - -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - int flag = O_CREAT | O_RDWR; - if(trunc) flag |= O_TRUNC; - int ret = file.open(fn, flag); - if(!ret) return; - file.seekSet(pos); - file.write(data, size); - file.close(); - -#else - - FILE *file; - if(trunc) { - file = fopen(get_filename_fullpath(fn), "wb"); - } else { - file = fopen(get_filename_fullpath(fn), "r+b"); - if(!file) file = fopen(get_filename_fullpath(fn), "wb"); - } - if(!file) return; - fseek(file, pos, SEEK_SET); - fwrite(data, 1, size, file); - fclose(file); - -#endif -} - -void read_from_file(const char *fn, char *data, ulong maxsize, ulong pos) { -#if defined(ESP8266) - - File f = LittleFS.open(fn, "r"); - if(!f) { - data[0]=0; - return; // return with empty string - } - if(pos) f.seek(pos, SeekSet); - int len = f.read((byte*)data, maxsize); - if(len>0) data[len]=0; - if(len==1 && data[0]==' ') data[0] = 0; // hack to circumvent FS bug involving writing empty file - data[maxsize-1]=0; - f.close(); - return; - -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - int ret = file.open(fn, O_READ); - if(!ret) { - data[0]=0; - return; // return with empty string - } - file.seekSet(pos); - ret = file.fgets(data, maxsize); - data[maxsize-1]=0; - file.close(); - return; - -#else - - FILE *file; - file = fopen(get_filename_fullpath(fn), "rb"); - if(!file) { - data[0] = 0; - return; - } - - int res; - fseek(file, pos, SEEK_SET); - if(fgets(data, maxsize, file)) { - res = strlen(data); - } else { - res = 0; - } - if (res <= 0) { - data[0] = 0; - } - - data[maxsize-1]=0; - fclose(file); - return; - -#endif -} -*/ void remove_file(const char *fn) { #if defined(ESP8266) - if(!file_exists(fn)) return; + if(!LittleFS.exists(fn)) return; LittleFS.remove(fn); #elif defined(ARDUINO) @@ -310,7 +201,6 @@ bool file_exists(const char *fn) { } return false; - #elif defined(ARDUINO) sd.chdir("/"); @@ -360,10 +250,11 @@ ulong file_size(const char *fn) { return size; } +// file functions void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { #if defined(ESP8266) - // do not use File.readBytes or readBytesUntil because it's very slow + // do not use File.readBytes or readBytesUntil because it's very slow File f = LittleFS.open(fn, "r"); if(f) { f.seek(pos, SeekSet); @@ -388,7 +279,7 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { fseek(fp, pos, SEEK_SET); fread(dst, 1, len, fp); fclose(fp); - } + } #endif } @@ -550,7 +441,7 @@ byte file_cmp_block(const char *fn, const char *buf, ulong pos) { } fclose(fp); return (*buf==c)?0:1; - } + } #endif return 1; @@ -664,3 +555,76 @@ void peel_http_header(char* buffer) { // remove the HTTP header i++; } } + +void strReplace(char *str, char c, char r) { + for(byte i=0;i12) return false; + if(d<1 || d>month_days[m-1]) return false; + return true; +} + +bool isValidDate(uint16_t date) { + if (date < MIN_ENCODED_DATE || date > MAX_ENCODED_DATE) { + return false; + } + byte month = date >> 5; + byte day = date & 31; + return isValidDate(month, day); +} + +#if defined(ESP8266) +byte hex2dec(const char *hex) { + return strtol(hex, NULL, 16); +} + +bool isHex(char c) { + if(c>='0' && c<='9') return true; + if(c>='a' && c<='f') return true; + if(c>='A' && c<='F') return true; + return false; +} + +bool isValidMAC(const char *_mac) { + char mac[18], *hex; + strncpy(mac, _mac, 18); + mac[17] = 0; + byte count = 0; + hex = strtok(mac, ":"); + if(strlen(hex)!=2) return false; + if(!isHex(hex[0]) || !isHex(hex[1])) return false; + count++; + while(true) { + hex = strtok(NULL, ":"); + if(hex==NULL) break; + if(strlen(hex)!=2) return false; + if(!isHex(hex[0]) || !isHex(hex[1])) return false; + count++; + yield(); + } + if(count!=6) return false; + else return true; +} + +void str2mac(const char *_str, byte mac[]) { + char str[18], *hex; + strncpy(str, _str, 18); + str[17] = 0; + byte count=0; + hex = strtok(str, ":"); + mac[count] = hex2dec(hex); + count++; + while(true) { + hex = strtok(NULL, ":"); + if(hex==NULL) break; + mac[count++] = hex2dec(hex); + yield(); + } +} +#endif \ No newline at end of file diff --git a/utils.h b/utils.h index 03b4b74f5..939d89b5b 100644 --- a/utils.h +++ b/utils.h @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * . + * . */ #ifndef _UTILS_H @@ -47,7 +47,7 @@ void file_write_block(const char *fname, const void *src, ulong pos, ulong len); void file_append_block(const char *fname, const void *src, ulong len); void file_copy_block (const char *fname, ulong from, ulong to, ulong len, void *tmp=0); byte file_read_byte (const char *fname, ulong pos); -void file_write_byte(const char *fname, ulong pos, byte v); +void file_write_byte(const char *fname, ulong pos, byte v); byte file_cmp_block(const char *fname, const char *buf, ulong pos); // misc. string and time converstion functions @@ -57,6 +57,18 @@ byte water_time_encode_signed(int16_t i); int16_t water_time_decode_signed(byte i); void urlDecode(char *); void peel_http_header(char*); +void strReplace(char *, char c, char r); + +#define date_encode(m,d) ((m<<5)+d) +#define MIN_ENCODED_DATE date_encode(1,1) +#define MAX_ENCODED_DATE date_encode(12, 31) +bool isValidDate(uint16_t date); +#if defined(ESP8266) +byte hex2dec(const char *hex); +bool isHex(char c); +bool isValidMAC(const char *_mac); +void str2mac(const char *_str, byte mac[]); +#endif #if defined(ARDUINO) diff --git a/weather.cpp b/weather.cpp index b11e86f82..7f2b7f702 100644 --- a/weather.cpp +++ b/weather.cpp @@ -18,7 +18,7 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * . + * . */ #include @@ -32,6 +32,7 @@ extern char tmp_buffer[]; extern char ether_buffer[]; char wt_rawData[TMP_BUFFER_SIZE]; int wt_errCode = HTTP_RQT_NOT_RECEIVED; +byte wt_monthly[12] = {100,100,100,100,100,100,100,100,100,100,100,100}; byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); void write_log(byte type, ulong curr_time); @@ -42,6 +43,7 @@ void write_log(byte type, ulong curr_time); static void getweather_callback(char* buffer) { char *p = buffer; + DEBUG_PRINTLN(p); /* scan the buffer until the first & symbol */ while(*p && *p!='&') { p++; @@ -49,13 +51,12 @@ static void getweather_callback(char* buffer) { if (*p != '&') return; int v; bool save_nvdata = false; - // first check errCode, only update lswc timestamp if errCode is 0 if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("errCode"), true)) { wt_errCode = atoi(tmp_buffer); if(wt_errCode==0) os.checkwt_success_lasttime = os.now_tz(); } - + // then only parse scale if errCode is 0 if (wt_errCode==0 && findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { v = atoi(tmp_buffer); @@ -63,10 +64,10 @@ static void getweather_callback(char* buffer) { // only save if the value has changed os.iopts[IOPT_WATER_PERCENTAGE] = v; os.iopts_save(); - os.weather_update_flag |= WEATHER_UPDATE_WL; + os.weather_update_flag |= WEATHER_UPDATE_WL; } - } - + } + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sunrise"), true)) { v = atoi(tmp_buffer); if (v>=0 && v<=1440 && v != os.nvdata.sunrise_time) { @@ -80,8 +81,8 @@ static void getweather_callback(char* buffer) { v = atoi(tmp_buffer); if (v>=0 && v<=1440 && v != os.nvdata.sunset_time) { os.nvdata.sunset_time = v; - save_nvdata = true; - os.weather_update_flag |= WEATHER_UPDATE_SUNSET; + save_nvdata = true; + os.weather_update_flag |= WEATHER_UPDATE_SUNSET; } } @@ -89,11 +90,11 @@ static void getweather_callback(char* buffer) { uint32_t l = strtoul(tmp_buffer, NULL, 0); if(l != os.nvdata.external_ip) { os.nvdata.external_ip = l; - save_nvdata = true; + save_nvdata = true; os.weather_update_flag |= WEATHER_UPDATE_EIP; } } - + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("tz"), true)) { v = atoi(tmp_buffer); if (v>=0 && v<= 108) { @@ -105,7 +106,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) { @@ -117,15 +118,14 @@ static void getweather_callback(char* buffer) { } if (findKeyVal(p, wt_rawData, TMP_BUFFER_SIZE, PSTR("rawData"), true)) { - wt_rawData[TMP_BUFFER_SIZE-1]=0; // make sure the buffer ends properly + wt_rawData[TMP_BUFFER_SIZE-1]=0; // make sure the buffer ends properly } - + if(save_nvdata) os.nvdata_save(); write_log(LOGDATA_WATERLEVEL, os.checkwt_success_lasttime); } static void getweather_callback_with_peel_header(char* buffer) { - DEBUG_PRINTLN(buffer); peel_http_header(buffer); getweather_callback(buffer); } @@ -137,19 +137,22 @@ void GetWeather() { #endif // use temp buffer to construct get command BufferFiller bf = 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; bf.emit_p(PSTR("$D?loc=$O&wto=$O&fwv=$D"), - (int) os.iopts[IOPT_USE_WEATHER], + method, SOPT_LOCATION, SOPT_WEATHER_OPTS, (int)os.iopts[IOPT_FW_VERSION]); char *src=tmp_buffer+strlen(tmp_buffer); char *dst=tmp_buffer+TMP_BUFFER_SIZE-12; - + char c; // url encode. convert SPACE to %20 // copy reversely from the end because we are potentially expanding - // the string size + // the string size while(src!=tmp_buffer) { c = *src--; if(c==' ') { @@ -165,7 +168,7 @@ void GetWeather() { strcpy(ether_buffer, "GET /"); strcat(ether_buffer, dst); // because dst is part of tmp_buffer, - // must load weather url AFTER dst is copied to ether_buffer + // must load weather url AFTER dst is copied to ether_buffer // load weather url to tmp_buffer char *host = tmp_buffer; @@ -175,8 +178,6 @@ void GetWeather() { strcat(ether_buffer, host); strcat(ether_buffer, "\r\n\r\n"); - DEBUG_PRINTLN(ether_buffer); - wt_errCode = HTTP_RQT_NOT_RECEIVED; int ret = os.send_http_request(host, ether_buffer, getweather_callback_with_peel_header); if(ret!=HTTP_RQT_SUCCESS) { @@ -184,3 +185,32 @@ void GetWeather() { // if wt_errCode > 0, the call is successful but weather script may return error } } + +void load_wt_monthly(char* wto) { + byte i; + int p[12]; + for(i=0;i<12;i++) p[i]=100; // init all to 100 + sscanf(wto, "\"scales\":[%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d]", p,p+1,p+2,p+3,p+4,p+5,p+6,p+7,p+8,p+9,p+10,p+11); + for(i=0;i<12;i++) { + if(p[i]<0) p[i]=0; + if(p[i]>250) p[i]=250; + wt_monthly[i]=p[i]; + } +} + +void apply_monthly_adjustment(ulong curr_time) { + // ====== Check monthly water percentage ====== + if(os.iopts[IOPT_USE_WEATHER]==WEATHER_METHOD_MONTHLY) { +#if defined(ARDUINO) + byte m = month(curr_time)-1; +#else + time_t ct = curr_time; + struct tm *ti = gmtime(&ct); + byte m = ti->tm_mon; // tm_mon ranges from [0,11] +#endif + if(os.iopts[IOPT_WATER_PERCENTAGE]!=wt_monthly[m]) { + os.iopts[IOPT_WATER_PERCENTAGE]=wt_monthly[m]; + os.iopts_save(); + } + } +} \ No newline at end of file diff --git a/weather.h b/weather.h index 9e2aa8731..29bfa88e0 100644 --- a/weather.h +++ b/weather.h @@ -18,22 +18,25 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * . + * */ #ifndef _WEATHER_H #define _WEATHER_H -#define WEATHER_UPDATE_SUNRISE 0x01 -#define WEATHER_UPDATE_SUNSET 0x02 -#define WEATHER_UPDATE_EIP 0x04 -#define WEATHER_UPDATE_WL 0x08 -#define WEATHER_UPDATE_TZ 0x10 -#define WEATHER_UPDATE_RD 0x20 +#define WEATHER_UPDATE_SUNRISE 0x01 +#define WEATHER_UPDATE_SUNSET 0x02 +#define WEATHER_UPDATE_EIP 0x04 +#define WEATHER_UPDATE_WL 0x08 +#define WEATHER_UPDATE_TZ 0x10 +#define WEATHER_UPDATE_RD 0x20 void GetWeather(); extern char wt_rawData[]; extern int wt_errCode; -#endif // _WEATHER_H +extern byte wt_monthly[]; +void load_wt_monthly(char* wto); +void apply_monthly_adjustment(ulong curr_time); +#endif // _WEATHER_H From 930de19ce7e6bd5e4a9f32e3d7e2af94a86df797 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 13 Jan 2023 08:21:50 +0100 Subject: [PATCH 029/281] Added dhcp and enc28J60 check --- examples/mainArduino/mainArduino.ino | 12 ++++++ main.cpp | 59 ++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 examples/mainArduino/mainArduino.ino diff --git a/examples/mainArduino/mainArduino.ino b/examples/mainArduino/mainArduino.ino new file mode 100644 index 000000000..702f3683c --- /dev/null +++ b/examples/mainArduino/mainArduino.ino @@ -0,0 +1,12 @@ +#include "OpenSprinkler.h" + +void do_setup(); +void do_loop(); + +void setup() { + do_setup(); +} + +void loop() { + do_loop(); +} diff --git a/main.cpp b/main.cpp index b03dedf26..7f922a28a 100644 --- a/main.cpp +++ b/main.cpp @@ -32,6 +32,7 @@ #if defined(ARDUINO) #if defined(ESP8266) + extern "C" struct netif* eagle_lwip_getif (int netif_index); ESP8266WebServer *update_server = NULL; OTF::OpenThingsFramework *otf = NULL; DNSServer *dns = NULL; @@ -422,6 +423,7 @@ void reboot_in(uint32_t ms) { reboot_ticker.once_ms(ms, ESP.restart); } } +bool check_enc28j60(); #else void handle_web_request(char *p); #endif @@ -979,6 +981,40 @@ void do_loop() } } +#if defined(ESP8266) + // dhcp and hw check: + static unsigned long dhcp_timeout = 0; + if(curr_time > dhcp_timeout) { + if (useEth) { + netif* intf = (netif*) eth.getNetIf(); + if (os.iopts[IOPT_USE_DHCP]) + dhcp_renew(intf); + + if (dhcp_timeout > 0 && !check_enc28j60()) { //ENC28J60 REGISTER CHECK!! + DEBUG_PRINT(F("Reconnect")); + //eth.resetEther(); + + // todo: lwip add timeout + int n = os.iopts[IOPT_USE_DHCP]?30:2; + while (!eth.connected() && n-- >0) { + DEBUG_PRINT("."); + delay(1000); + } + + if (!eth.connected()) { + os.nvdata.reboot_cause = REBOOT_CAUSE_NETWORK_FAIL; + os.status.safe_reboot = 1; + } + } + } + else if (os.iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED && os.get_wifi_mode()==WIFI_MODE_STA) { + netif* intf = eagle_lwip_getif(STATION_IF); + dhcp_renew(intf); + } + dhcp_timeout = curr_time + DHCP_CHECKLEASE_INTERVAL; + } +#endif + // perform ntp sync // instead of using curr_time, which may change due to NTP sync itself // we use Arduino's millis() method @@ -1866,6 +1902,29 @@ void check_network() { #endif } +#if defined(ESP8266) +#define NET_ENC28J60_EIR 0x1C +#define NET_ENC28J60_ESTAT 0x1D +#define NET_ENC28J60_ECON1 0x1F +#define NET_ENC28J60_EIR_RXERIF 0x01 +#define NET_ENC28J60_ESTAT_BUFFER 0x40 +#define NET_ENC28J60_ECON1_RXEN 0x04 +bool check_enc28j60() +{ + uint8_t stateEconRxen = eth.readreg((uint8_t) NET_ENC28J60_ECON1) & NET_ENC28J60_ECON1_RXEN; + // ESTAT.BUFFER rised on TX or RX error + // I think the test of this register is not necessary - EIR.RXERIF state checking may be enough + uint8_t stateEstatBuffer = eth.readreg((uint8_t) NET_ENC28J60_ESTAT) & NET_ENC28J60_ESTAT_BUFFER; + // EIR.RXERIF set on RX error + uint8_t stateEirRxerif = eth.readreg((uint8_t) NET_ENC28J60_EIR) & NET_ENC28J60_EIR_RXERIF; + if (!stateEconRxen || (stateEstatBuffer && stateEirRxerif)) { + DEBUG_PRINTLN(F("ENC28J60 FAILED - REBOOT!")) + return false; + } + return true; +} +#endif + /** Perform NTP sync */ void perform_ntp_sync() { #if defined(ARDUINO) From 7bdcd0a9c0efbc9fc6d6e766c9007f42256c21d3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 16 Jan 2023 23:10:42 +0100 Subject: [PATCH 030/281] Fix ADC sensor id --- defines.h.bak | 535 ++++++++++++++++++++++++++++++++++++++++++++++++++ sensors.cpp | 2 +- 2 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 defines.h.bak diff --git a/defines.h.bak b/defines.h.bak new file mode 100644 index 000000000..bcdbfc44f --- /dev/null +++ b/defines.h.bak @@ -0,0 +1,535 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * OpenSprinkler macro defines and hardware pin assignments + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _DEFINES_H +#define _DEFINES_H + +//#define ENABLE_DEBUG // enable serial debug + +typedef unsigned char byte; +typedef unsigned long ulong; + +#define TMP_BUFFER_SIZE 255 // scratch buffer size + +/** Firmware version, hardware version, and maximal values */ +#define OS_FW_VERSION 230 // Firmware version: 220 means 2.2.0 + // if this number is different from the one stored in non-volatile memory + // a device reset will be automatically triggered + +#define OS_FW_MINOR 1 // Firmware minor version + +/** Hardware version base numbers */ +#define OS_HW_VERSION_BASE 0x00 // OpenSprinkler +#define OSPI_HW_VERSION_BASE 0x40 // OpenSprinkler Pi +#define OSBO_HW_VERSION_BASE 0x80 // OpenSprinkler Beagle +#define SIM_HW_VERSION_BASE 0xC0 // simulation hardware + +/** Hardware type macro defines */ +#define HW_TYPE_AC 0xAC // standard 24VAC for 24VAC solenoids only, with triacs +#define HW_TYPE_DC 0xDC // DC powered, for both DC and 24VAC solenoids, with boost converter and MOSFETs +#define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges +#define HW_TYPE_UNKNOWN 0xFF + +/** Data file names */ +#define IOPTS_FILENAME "iopts.dat" // integer options data file +#define SOPTS_FILENAME "sopts.dat" // string options data file +#define STATIONS_FILENAME "stns.dat" // stations data file +#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 SENSOR_FILENAME "sensor.dat" // analog sensor filename +#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename +#define SENSORLOG_FILENAME "sensorlog.dat" // analog sensor log filename + +/** Station macro defines */ +#define STN_TYPE_STANDARD 0x00 // standard solenoid station +#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station +#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station +#define STN_TYPE_GPIO 0x03 // direct GPIO station +#define STN_TYPE_HTTP 0x04 // HTTP station +#define STN_TYPE_OTHER 0xFF + +/** Notification macro defines */ +#define NOTIFY_PROGRAM_SCHED 0x0001 +#define NOTIFY_SENSOR1 0x0002 +#define NOTIFY_FLOWSENSOR 0x0004 +#define NOTIFY_WEATHER_UPDATE 0x0008 +#define NOTIFY_REBOOT 0x0010 +#define NOTIFY_STATION_OFF 0x0020 +#define NOTIFY_SENSOR2 0x0040 +#define NOTIFY_RAINDELAY 0x0080 +#define NOTIFY_STATION_ON 0x0100 + +/** HTTP request macro defines */ +#define HTTP_RQT_SUCCESS 0 +#define HTTP_RQT_NOT_RECEIVED 1 +#define HTTP_RQT_CONNECT_ERR 2 +#define HTTP_RQT_TIMEOUT 3 +#define HTTP_RQT_EMPTY_RETURN 4 + +/** Sensor macro defines */ +#define SENSOR_TYPE_NONE 0x00 +#define SENSOR_TYPE_RAIN 0x01 // rain sensor +#define SENSOR_TYPE_FLOW 0x02 // flow sensor +#define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor +#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor +#define SENSOR_TYPE_OTHER 0xFF + +#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds + +/** Reboot cause */ +#define REBOOT_CAUSE_NONE 0 +#define REBOOT_CAUSE_RESET 1 +#define REBOOT_CAUSE_BUTTON 2 +#define REBOOT_CAUSE_RSTAP 3 +#define REBOOT_CAUSE_TIMER 4 +#define REBOOT_CAUSE_WEB 5 +#define REBOOT_CAUSE_WIFIDONE 6 +#define REBOOT_CAUSE_FWUPDATE 7 +#define REBOOT_CAUSE_WEATHER_FAIL 8 +#define REBOOT_CAUSE_NETWORK_FAIL 9 +#define REBOOT_CAUSE_NTP 10 +#define REBOOT_CAUSE_PROGRAM 11 +#define REBOOT_CAUSE_POWERON 99 + + +/** WiFi defines */ +#define WIFI_MODE_AP 0xA9 +#define WIFI_MODE_STA 0x2A + +#define OS_STATE_INITIAL 0 +#define OS_STATE_CONNECTING 1 +#define OS_STATE_CONNECTED 2 +#define OS_STATE_TRY_CONNECT 3 +#define OS_STATE_WAIT_REBOOT 4 + +#define LED_FAST_BLINK 100 +#define LED_SLOW_BLINK 500 + +/** Storage / zone expander defines */ +#if defined(ARDUINO) + #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 +#endif + +#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders +#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations +#define STATION_NAME_SIZE 32 // maximum number of characters in each station name +#define MAX_SOPTS_SIZE 160 // maximum string option size + +#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) + +/** Default string option values */ +#define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' +#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA +#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" +#define DEFAULT_WEATHER_URL "weather.opensprinkler.com" +#define DEFAULT_IFTTT_URL "maker.ifttt.com" +#define DEFAULT_OTC_SERVER "ws.cloud.openthings.io" +#define DEFAULT_OTC_PORT 80 +#define DEFAULT_DEVICE_NAME "My OpenSprinkler" +#define DEFAULT_EMPTY_STRING "" + +/* Weather Adjustment Methods */ +enum { + WEATHER_METHOD_MANUAL = 0, + WEATHER_METHOD_ZIMMERMAN, + WEATHER_METHOD_AUTORAINDELY, + WEATHER_METHOD_ETO, + WEATHER_METHOD_MONTHLY, + NUM_WEATHER_METHODS +}; + +/* Master */ +enum { + MASTER_1 = 0, + MASTER_2, + NUM_MASTER_ZONES, +}; + +enum { + MASOPT_SID = 0, + MASOPT_ON_ADJ, + MASOPT_OFF_ADJ, + NUM_MASTER_OPTS, +}; + +// Sequential Groups +#define NUM_SEQ_GROUPS 4 +#define PARALLEL_GROUP_ID 255 + +/** Macro define of each option + * Refer to OpenSprinkler.cpp for details on each option + */ +enum { + IOPT_FW_VERSION=0,// read-only (ro) + IOPT_TIMEZONE, + IOPT_USE_NTP, + IOPT_USE_DHCP, + IOPT_STATIC_IP1, + IOPT_STATIC_IP2, + IOPT_STATIC_IP3, + IOPT_STATIC_IP4, + IOPT_GATEWAY_IP1, + IOPT_GATEWAY_IP2, + IOPT_GATEWAY_IP3, + IOPT_GATEWAY_IP4, + IOPT_HTTPPORT_0, + IOPT_HTTPPORT_1, + IOPT_HW_VERSION, //ro + IOPT_EXT_BOARDS, + IOPT_SEQUENTIAL_RETIRED, //ro + IOPT_STATION_DELAY_TIME, + IOPT_MASTER_STATION, + IOPT_MASTER_ON_ADJ, + IOPT_MASTER_OFF_ADJ, + IOPT_URS_RETIRED, // ro + IOPT_RSO_RETIRED, // ro + IOPT_WATER_PERCENTAGE, + IOPT_DEVICE_ENABLE, // editable through jc + IOPT_IGNORE_PASSWORD, + IOPT_DEVICE_ID, + IOPT_LCD_CONTRAST, + IOPT_LCD_BACKLIGHT, + IOPT_LCD_DIMMING, + IOPT_BOOST_TIME, + IOPT_USE_WEATHER, + IOPT_NTP_IP1, + IOPT_NTP_IP2, + IOPT_NTP_IP3, + IOPT_NTP_IP4, + IOPT_ENABLE_LOGGING, + IOPT_MASTER_STATION_2, + IOPT_MASTER_ON_ADJ_2, + IOPT_MASTER_OFF_ADJ_2, + IOPT_FW_MINOR, //ro + IOPT_PULSE_RATE_0, + IOPT_PULSE_RATE_1, + IOPT_REMOTE_EXT_MODE, // editable through jc + IOPT_DNS_IP1, + IOPT_DNS_IP2, + IOPT_DNS_IP3, + IOPT_DNS_IP4, + IOPT_SPE_AUTO_REFRESH, + IOPT_IFTTT_ENABLE, + IOPT_SENSOR1_TYPE, + IOPT_SENSOR1_OPTION, + IOPT_SENSOR2_TYPE, + IOPT_SENSOR2_OPTION, + IOPT_SENSOR1_ON_DELAY, + IOPT_SENSOR1_OFF_DELAY, + IOPT_SENSOR2_ON_DELAY, + IOPT_SENSOR2_OFF_DELAY, + IOPT_SUBNET_MASK1, + IOPT_SUBNET_MASK2, + IOPT_SUBNET_MASK3, + IOPT_SUBNET_MASK4, + IOPT_WIFI_MODE, //ro + IOPT_RESET, //ro + NUM_IOPTS // total number of integer options +}; + +enum { + SOPT_PASSWORD=0, + SOPT_LOCATION, + SOPT_JAVASCRIPTURL, + SOPT_WEATHERURL, + SOPT_WEATHER_OPTS, + SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT + SOPT_STA_SSID, + SOPT_STA_PASS, + SOPT_MQTT_OPTS, + SOPT_OTC_OPTS, + SOPT_DEVICE_NAME, + SOPT_STA_BSSID_CHL, // wifi extra info: bssid and channel + NUM_SOPTS // total number of string options +}; + +/** Log Data Type */ +#define LOGDATA_STATION 0x00 +#define LOGDATA_SENSOR1 0x01 +#define LOGDATA_RAINDELAY 0x02 +#define LOGDATA_WATERLEVEL 0x03 +#define LOGDATA_FLOWSENSE 0x04 +#define LOGDATA_SENSOR2 0x05 +#define LOGDATA_CURRENT 0x80 + +#undef OS_HW_VERSION + +/** Hardware defines */ +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // 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 + +#elif defined(ESP8266) // for ESP8266 + + #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) + #define IOEXP_PIN 0x80 // base for pins on main IO expander + #define MAIN_I2CADDR 0x20 // main IO expander I2C address + #define ACDR_I2CADDR 0x21 // ac driver I2C address + #define DCDR_I2CADDR 0x22 // dc driver I2C address + #define LADR_I2CADDR 0x23 // latch driver I2C address + #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address + #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address + + #define PIN_CURR_SENSE A0 + #define PIN_FREE_LIST {} // no free GPIO pin at the moment + #define ETHER_BUFFER_SIZE 2048 + + #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above + + /* To accommodate different OS30 versions, we use software defines pins */ + extern byte PIN_BUTTON_1; + extern byte PIN_BUTTON_2; + extern byte PIN_BUTTON_3; + extern byte PIN_RFRX; + extern byte PIN_RFTX; + extern byte PIN_BOOST; + extern byte PIN_BOOST_EN; + extern byte PIN_LATCH_COM; + extern byte PIN_LATCH_COMA; + extern byte PIN_LATCH_COMK; + extern byte PIN_SENSOR1; + extern byte PIN_SENSOR2; + extern byte PIN_IOEXP_INT; + + /* Original OS30 pin defines */ + //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask + // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i + #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 + #define V0_PIN_BUTTON_2 0 // button 2 + #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 + #define V0_PIN_RFRX 14 + #define V0_PIN_PWR_RX IOEXP_PIN+0 + #define V0_PIN_RFTX 16 + #define V0_PIN_PWR_TX IOEXP_PIN+2 + #define V0_PIN_BOOST IOEXP_PIN+6 + #define V0_PIN_BOOST_EN IOEXP_PIN+7 + #define V0_PIN_SENSOR1 12 // sensor 1 + #define V0_PIN_SENSOR2 13 // sensor 2 + + /* OS31 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V1_IO_CONFIG 0x1F00 // config bits + #define V1_IO_OUTPUT 0x1F00 // output bits + #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 + #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 + #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V1_PIN_RFRX 14 + #define V1_PIN_RFTX 16 + #define V1_PIN_IOEXP_INT 12 + #define V1_PIN_BOOST IOEXP_PIN+13 + #define V1_PIN_BOOST_EN IOEXP_PIN+14 + #define V1_PIN_LATCH_COM IOEXP_PIN+15 + #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 + #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 + + /* OS32 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V2_IO_CONFIG 0x1000 // config bits + #define V2_IO_OUTPUT 0x1E00 // output bits + #define V2_PIN_BUTTON_1 2 // button 1 + #define V2_PIN_BUTTON_2 0 // button 2 + #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V2_PIN_RFTX 15 + #define V2_PIN_BOOST IOEXP_PIN+13 + #define V2_PIN_BOOST_EN IOEXP_PIN+14 + #define V2_PIN_LATCH_COMA IOEXP_PIN+8 // latch COM+ (anode) + #define V2_PIN_SRLAT IOEXP_PIN+9 // shift register latch + #define V2_PIN_SRCLK IOEXP_PIN+10 // shift register clock + #define V2_PIN_SRDAT IOEXP_PIN+11 // shift register data + #define V2_PIN_LATCH_COMK IOEXP_PIN+15 // latch COM- (cathode) + #define V2_PIN_SENSOR1 3 // sensor 1 + #define V2_PIN_SENSOR2 10 // sensor 2 + +#elif defined(OSPI) // for OSPi + + #define OS_HW_VERSION OSPI_HW_VERSION_BASE + #define PIN_SR_LATCH 22 // shift register latch pin + #define PIN_SR_DATA 27 // shift register data pin + #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) + #define PIN_SR_CLOCK 4 // shift register clock pin + #define PIN_SR_OE 17 // shift register output enable pin + #define PIN_SENSOR1 14 + #define PIN_SENSOR2 23 + #define PIN_RFTX 15 // RF transmitter pin + //#define PIN_BUTTON_1 23 // button 1 + //#define PIN_BUTTON_2 24 // button 2 + //#define PIN_BUTTON_3 25 // button 3 + + #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins + #define ETHER_BUFFER_SIZE 16384 + +#elif defined(OSBO) // for OSBo + + #define OS_HW_VERSION OSBO_HW_VERSION_BASE + // these are gpio pin numbers, refer to + // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp + #define PIN_SR_LATCH 60 // P9_12, shift register latch pin + #define PIN_SR_DATA 30 // P9_11, shift register data pin + #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin + #define PIN_SR_OE 50 // P9_14, shift register output enable pin + #define PIN_SENSOR1 48 + #define PIN_RFTX 51 // RF transmitter pin + + #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} + #define ETHER_BUFFER_SIZE 16384 + +#else // for demo / simulation + // use fake hardware pins + #if defined(DEMO) + #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware + #else + #define OS_HW_VERSION SIM_HW_VERSION_BASE + #endif + #define PIN_SR_LATCH 0 + #define PIN_SR_DATA 0 + #define PIN_SR_CLOCK 0 + #define PIN_SR_OE 0 + #define PIN_SENSOR1 0 + #define PIN_SENSOR2 0 + #define PIN_RFTX 0 + #define PIN_FREE_LIST {} + #define ETHER_BUFFER_SIZE 16384 +#endif + +#if defined(ENABLE_DEBUG) /** Serial debug functions */ + + #if defined(ARDUINO) + #define DEBUG_BEGIN(x) {Serial.begin(x);} + #define DEBUG_PRINT(x) {Serial.print(x);} + #define DEBUG_PRINTLN(x) {Serial.println(x);} + #else + #include + #define DEBUG_BEGIN(x) {} /** Serial debug functions */ + inline void DEBUG_PRINT(int x) {printf("%d", x);} + inline void DEBUG_PRINT(const char*s) {printf("%s", s);} + #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} + #endif + +#else + + #define DEBUG_BEGIN(x) {} + #define DEBUG_PRINT(x) {} + #define DEBUG_PRINTLN(x) {} + +#endif + +/** Re-define avr-specific (e.g. PGM) types to use standard types */ +#if !defined(ARDUINO) + #include + #include + #include + #include + inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} + inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} + #define now() time(0) + #define pgm_read_byte(x) *(x) + #define PSTR(x) x + #define F(x) x + #define strcat_P strcat + #define strcpy_P strcpy + #define sprintf_P sprintf + #include + #define String string + using namespace std; + #define PROGMEM + typedef const char* PGM_P; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef bool boolean; + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite +#endif + +/** Other defines */ +// button values +#define BUTTON_1 0x01 +#define BUTTON_2 0x02 +#define BUTTON_3 0x04 + +// button status values +#define BUTTON_NONE 0x00 // no button pressed +#define BUTTON_MASK 0x0F // button status mask +#define BUTTON_FLAG_HOLD 0x80 // long hold flag +#define BUTTON_FLAG_DOWN 0x40 // down flag +#define BUTTON_FLAG_UP 0x20 // up flag + +// button timing values +#define BUTTON_DELAY_MS 1 // short delay (milliseconds) +#define BUTTON_HOLD_MS 1000 // long hold expiration time (milliseconds) + +// button mode values +#define BUTTON_WAIT_NONE 0 // do not wait, return value immediately +#define BUTTON_WAIT_RELEASE 1 // wait until button is release +#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/sensors.cpp b/sensors.cpp index c629fdffb..0592a6438 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -303,7 +303,7 @@ int read_sensor_adc(Sensor_t *sensor) { //Init + Detect: int port = sensor->id < 4? 72 : 73; - int id = id % 4; + int id = sensor->id % 4; ADS1015 adc(port); bool active = adc.begin(); From 1955089070965681aa0fc3bc4093e435a64fa92e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 16 Jan 2023 23:12:15 +0100 Subject: [PATCH 031/281] *.bak to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 84c2ec0b3..cdc87026e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ testmode.h build-1284/* .vscode .pio +*.bak From bc71c28b85e4ce1e210ea11686fdbea740040182 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 17 Jan 2023 00:27:38 +0100 Subject: [PATCH 032/281] Added Ping check (icmp ping) --- defines.h | 2 +- main.cpp | 109 ++++++++++++++++++++++++++++++++++++++++++++++++- platformio.ini | 1 + 3 files changed, 110 insertions(+), 2 deletions(-) diff --git a/defines.h b/defines.h index bcdbfc44f..623170fab 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned char byte; typedef unsigned long ulong; diff --git a/main.cpp b/main.cpp index 7f922a28a..12694dde3 100644 --- a/main.cpp +++ b/main.cpp @@ -32,7 +32,10 @@ #if defined(ARDUINO) #if defined(ESP8266) + #include + #include extern "C" struct netif* eagle_lwip_getif (int netif_index); + Pinger *pinger = NULL; ESP8266WebServer *update_server = NULL; OTF::OpenThingsFramework *otf = NULL; DNSServer *dns = NULL; @@ -1898,11 +1901,115 @@ void check_network() { } } #else - // nothing to do for other platforms + if (os.status.program_busy) {return;} + + 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('>'); + } + + if (!pinger) { + pinger = new Pinger(); +#if defined(ENABLE_DEBUG) + pinger->OnReceive([](const PingerResponse& response) { + if (response.ReceivedResponse) { + Serial.printf( + "Reply from %s: bytes=%d time=%lums TTL=%d\r\n", + response.DestIPAddress.toString().c_str(), + response.EchoMessageSize - sizeof(struct icmp_echo_hdr), + response.ResponseTime, + response.TimeToLive); + } else { + Serial.printf("Request timed out.\r\n"); + } + + // Return true to continue the ping sequence. + // If current event returns false, the ping sequence is interrupted. + return true; + }); +#endif + + pinger->OnEnd([](const PingerResponse &response) { +#if defined(ENABLE_DEBUG) + // Evaluate lost packet percentage + float loss = 100; + if(response.TotalReceivedResponses > 0) { + loss = (response.TotalSentRequests - response.TotalReceivedResponses) * 100 / response.TotalSentRequests; + } + + // Print packet trip data + Serial.printf("Ping statistics for %s:\r\n", + response.DestIPAddress.toString().c_str()); + Serial.printf(" Packets: Sent = %lu, Received = %lu, Lost = %lu (%.2f%% loss),\r\n", + response.TotalSentRequests, + response.TotalReceivedResponses, + response.TotalSentRequests - response.TotalReceivedResponses, + loss); + + // Print time information + if(response.TotalReceivedResponses > 0) + { + Serial.printf("Approximate round trip times in milli-seconds:\r\n"); + Serial.printf(" Minimum = %lums, Maximum = %lums, Average = %.2fms\r\n", + response.MinResponseTime, + response.MaxResponseTime, + response.AvgResponseTime); + } + + // Print host data + Serial.printf("Destination host data:\r\n"); + Serial.printf(" IP address: %s\r\n", + response.DestIPAddress.toString().c_str()); + if(response.DestMacAddress != nullptr) { + Serial.printf(" MAC address: " MACSTR "\r\n", + MAC2STR(response.DestMacAddress->addr)); + } + if(response.DestHostname != "") { + Serial.printf(" DNS name: %s\r\n", + response.DestHostname.c_str()); + } +#endif + boolean failed = response.TotalSentRequests > response.TotalReceivedResponses; + + 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; + } + + return true; + }); + } + if (useEth && (!eth.connected() || !eth.gatewayIP() || !eth.gatewayIP().isSet())) + return; + if (!useEth && (!WiFi.isConnected() || !WiFi.gatewayIP() || !WiFi.gatewayIP().isSet() || os.get_wifi_mode()==WIFI_MODE_AP)) + return; + + if(!pinger->Ping(useEth?eth.gatewayIP() : WiFi.gatewayIP())) { +#if defined(ENABLE_DEBUG) + Serial.println("Error during last ping command."); +#endif + } + } #endif } #if defined(ESP8266) + #define NET_ENC28J60_EIR 0x1C #define NET_ENC28J60_ESTAT 0x1D #define NET_ENC28J60_ECON1 0x1F diff --git a/platformio.ini b/platformio.ini index bc6bdcffb..1f9a8db9e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -27,6 +27,7 @@ lib_deps = https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library/archive/refs/heads/master.zip https://github.com/Links2004/arduinoWebSockets/archive/refs/tags/2.3.5.zip RobTillaart/ADS1X15 + https://github.com/bluemurder/esp8266-ping ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - upload_speed = 460800 From 03cd8252940cf54c664c416410b6e2d284a94226 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 26 Jan 2023 23:39:10 +0100 Subject: [PATCH 033/281] Start OSPi compat --- opensprinkler_server.cpp | 4 ++++ sensors.cpp | 8 ++++++++ sensors.h | 4 ++++ 3 files changed, 16 insertions(+) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 1ed26094a..168bfe36e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2529,10 +2529,12 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { const int sensor_types[] = { SENSOR_SMT100_MODBUS_RTU_MOIS, SENSOR_SMT100_MODBUS_RTU_TEMP, +#if defined(ESP8266) SENSOR_ANALOG_EXTENSION_BOARD, SENSOR_ANALOG_EXTENSION_BOARD_P, SENSOR_SMT50_MOIS, SENSOR_SMT50_TEMP, +#endif //SENSOR_OSPI_ANALOG_INPUTS, SENSOR_REMOTE, SENSOR_GROUP_MIN, @@ -2544,10 +2546,12 @@ const int sensor_types[] = { const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", +#if defined(ESP8266) "OpenSprinkler analog extension board 2xADS1015 x8 - voltage mode 0..4V", "OpenSprinkler analog extension board 2xADS1015 x8 - 0..3.3V to 0..100%", "OpenSprinkler analog extension board 2xADS1015 x8 - SMT50 moisture mode", "OpenSprinkler analog extension board 2xADS1015 x8 - SMT50 temperature mode", +#endif //"OSPi analog input", "Remote sensor of an remote opensprinkler", "Sensor group with min value", diff --git a/sensors.cpp b/sensors.cpp index 0592a6438..95a63d7aa 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -293,6 +293,7 @@ void read_all_sensors() { sensor_update_groups(); } +#if defined(ESP8266) /** * Read ADS1015 sensors */ @@ -340,6 +341,7 @@ int read_sensor_adc(Sensor_t *sensor) { return HTTP_RQT_SUCCESS; } +#endif int read_sensor_ospi(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_ospi")); @@ -547,11 +549,13 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_SMT100_MODBUS_RTU_TEMP: return read_sensor_ip(sensor); +#if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: case SENSOR_ANALOG_EXTENSION_BOARD_P: case SENSOR_SMT50_MOIS: //SMT50 VWC [%] = (U * 50) : 3 case SENSOR_SMT50_TEMP: //SMT50 T [°C] = (U – 0,5) * 100 return read_sensor_adc(sensor); +#endif //case SENSOR_OSPI_ANALOG_INPUTS: // return read_sensor_ospi(sensor); @@ -973,10 +977,12 @@ byte getSensorUnitId(int type) { switch(type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; +#if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; case SENSOR_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_SMT50_TEMP: return UNIT_DEGREE; +#endif case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; default: return UNIT_NONE; @@ -990,10 +996,12 @@ byte getSensorUnitId(Sensor_t *sensor) { switch(sensor->type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; +#if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; case SENSOR_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_SMT50_TEMP: return UNIT_DEGREE; +#endif case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; case SENSOR_REMOTE: return sensor->unitid; diff --git a/sensors.h b/sensors.h index f6d4d7887..9ed3601a6 100644 --- a/sensors.h +++ b/sensors.h @@ -33,16 +33,20 @@ #endif #include "defines.h" #include "utils.h" +#if defined(ESP8266) #include +#endif //Sensor types: #define SENSOR_NONE 0 //None or deleted sensor #define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode #define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode +#if defined(ESP8266) #define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board x8 - voltage mode 0..4V #define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% #define SENSOR_SMT50_MOIS 15 //New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 #define SENSOR_SMT50_TEMP 16 //New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +#endif #define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input #define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler From 66dc372572c3d9d71c0b32016956a68388008cc0 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 26 Jan 2023 23:56:26 +0100 Subject: [PATCH 034/281] Next step OSPi compat --- main.cpp | 3 ++- opensprinkler_server.cpp | 10 ++++++++-- sensors.cpp | 4 ++-- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/main.cpp b/main.cpp index 12694dde3..79a170850 100644 --- a/main.cpp +++ b/main.cpp @@ -1900,7 +1900,8 @@ void check_network() { os.status.network_fails=0; } } -#else +#endif +#if defined(ESP8266) if (os.status.program_busy) {return;} if (os.status.req_network) { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 168bfe36e..7a5f9e61e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1995,8 +1995,12 @@ void server_sensor_config(OTF_PARAMS_DEF) if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) handle_return(HTML_DATA_MISSING); + urlDecode(tmp_buffer); + strReplace(tmp_buffer, '\"', '\''); + strReplace(tmp_buffer, '\\', '/'); char name[30]; - strlcpy(name, tmp_buffer, sizeof(name)-1); // Sensor nr + + strncpy(name, tmp_buffer, sizeof(name)-1); // Sensor nr if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ip"), true)) handle_return(HTML_DATA_MISSING); @@ -2625,6 +2629,8 @@ void server_usage(OTF_PARAMS_DEF) { print_header(); #endif +#if defined(ESP8266) + struct FSInfo fsinfo; boolean ok = LittleFS.info(fsinfo); @@ -2638,7 +2644,7 @@ void server_usage(OTF_PARAMS_DEF) { fsinfo.pageSize, fsinfo.maxOpenFiles, fsinfo.maxPathLength); - +#endif handle_return(HTML_OK); } diff --git a/sensors.cpp b/sensors.cpp index 95a63d7aa..6c43988fb 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -99,7 +99,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint if (sensor->nr == nr) { //Modify existing sensor->type = type; sensor->group = group; - strlcpy(sensor->name, name, sizeof(sensor->name)-1); + strncpy(sensor->name, name, sizeof(sensor->name)-1); sensor->ip = ip; sensor->port = port; sensor->id = id; @@ -121,7 +121,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->nr = nr; new_sensor->type = type; new_sensor->group = group; - strlcpy(new_sensor->name, name, sizeof(new_sensor->name)-1); + strncpy(new_sensor->name, name, sizeof(new_sensor->name)-1); new_sensor->ip = ip; new_sensor->port = port; new_sensor->id = id; From 4b301bdc18f648735c48194aebf683b26a5f74b8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 27 Jan 2023 00:05:19 +0100 Subject: [PATCH 035/281] Another OSPi compat --- build.sh | 6 +++--- sensors.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index 8c5df9f54..a356a4fc9 100755 --- a/build.sh +++ b/build.sh @@ -14,12 +14,12 @@ if [ "$1" == "demo" ]; then echo "Installing required libraries..." apt-get install -y libmosquitto-dev echo "Compiling firmware..." - g++ -o OpenSprinkler -DDEMO -std=c++14 -m32 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto + g++ -o OpenSprinkler -DDEMO -std=c++14 -m32 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -lpthread -lmosquitto elif [ "$1" == "osbo" ]; then echo "Installing required libraries..." apt-get install -y libmosquitto-dev echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSBO main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto + g++ -o OpenSprinkler -DOSBO main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -lpthread -lmosquitto else echo "Installing required libraries..." apt-get update @@ -31,7 +31,7 @@ else exit 0 fi echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -lpthread -lmosquitto + g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -lpthread -lmosquitto fi if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then diff --git a/sensors.cpp b/sensors.cpp index 6c43988fb..c88174388 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -24,7 +24,7 @@ #include "utils.h" #include "program.h" #include "OpenSprinkler.h" -#include "OpenSprinkler_server.h" +#include "opensprinkler_server.h" #include "sensors.h" //All sensors: From e62df05dd2f18b576ec2ddfecec2edcb74ac290f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 27 Jan 2023 00:27:28 +0100 Subject: [PATCH 036/281] Next OSPi sensor compat try --- sensors.cpp | 32 +++++++++++++++++++++++++++++--- sensors.h | 2 ++ 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index c88174388..a9da56320 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -355,8 +355,16 @@ int read_sensor_ospi(Sensor_t *sensor) { extern byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); int read_sensor_http(Sensor_t *sensor) { +#if defined(ESP8266) IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; +#else + byte ip[4]; + ip[0] = (byte)((sensor->ip >> 24) &0xFF); + ip[1] = (byte)((sensor->ip >> 16) &0xFF); + ip[2] = (byte)((sensor->ip >> 08) &0xFF); + ip[3] = (byte)((sensor->ip & &0xFF)); +#endif char *p = tmp_buffer; BufferFiller bf = p; @@ -409,8 +417,16 @@ int read_sensor_ip(Sensor_t *sensor) { return HTTP_RQT_CONNECT_ERR; } +#if defined(ESP8266) IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; +#else + byte ip[4]; + ip[0] = (byte)((sensor->ip >> 24) &0xFF); + ip[1] = (byte)((sensor->ip >> 16) &0xFF); + ip[2] = (byte)((sensor->ip >> 08) &0xFF); + ip[3] = (byte)((sensor->ip & &0xFF)); +#endif if(!client->connect(ip, sensor->port)) { DEBUG_PRINT(F("Cannot connect to ")); @@ -462,8 +478,9 @@ int read_sensor_ip(Sensor_t *sensor) { } client->write(buffer, len); +#if defined(ESP8266) client->flush(); - +#endif uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; while (true) { if (client->available()) @@ -658,9 +675,16 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { return HTTP_RQT_CONNECT_ERR; } - IPAddress _ip(sensor->ip); +#if defined(ESP8266) + IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; - +#else + byte ip[4]; + ip[0] = (byte)((sensor->ip >> 24) &0xFF); + ip[1] = (byte)((sensor->ip >> 16) &0xFF); + ip[2] = (byte)((sensor->ip >> 08) &0xFF); + ip[3] = (byte)((sensor->ip & &0xFF)); +#endif if(!client->connect(ip, sensor->port)) { DEBUG_PRINT(F("Cannot connect to ")); DEBUG_PRINT(_ip[0]); DEBUG_PRINT("."); @@ -938,6 +962,7 @@ ProgSensorAdjust_t *prog_adjust_by_idx(uint idx) { return NULL; } +#if defined(ESP8266) ulong diskFree() { struct FSInfo fsinfo; LittleFS.info(fsinfo); @@ -951,6 +976,7 @@ bool checkDiskFree() { } return true; } +#endif const char* getSensorUnit(Sensor_t *sensor) { if (!sensor) diff --git a/sensors.h b/sensors.h index 9ed3601a6..41c144279 100644 --- a/sensors.h +++ b/sensors.h @@ -187,7 +187,9 @@ ProgSensorAdjust_t *prog_adjust_by_idx(uint idx); double calc_sensor_watering(uint prog); double calc_sensor_watering_by_nr(uint nr); +#if defined(ESP8266) ulong diskFree(); bool checkDiskFree(); //true: disk space Ok, false: Out of disk space +#endif #endif // _SENSORS_H From 0bec617b9004a742c6ab04183430f5408b1d51d7 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 27 Jan 2023 00:32:12 +0100 Subject: [PATCH 037/281] Fix ip convert OSPi --- sensors.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index a9da56320..89a8ba706 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -220,13 +220,17 @@ Sensor_t *sensor_by_idx(uint idx) { } bool sensorlog_add(SensorLog_t *sensorlog) { +#if defined(ESP8266) if (checkDiskFree()) { +#endif DEBUG_PRINT(F("sensorlog_add ")); file_append_block(SENSORLOG_FILENAME, sensorlog, SENSORLOG_STORE_SIZE); DEBUG_PRINT(sensorlog_filesize()); return true; +#if defined(ESP8266) } return false; +#endif } bool sensorlog_add(Sensor_t *sensor, ulong time) { @@ -362,8 +366,8 @@ int read_sensor_http(Sensor_t *sensor) { byte ip[4]; ip[0] = (byte)((sensor->ip >> 24) &0xFF); ip[1] = (byte)((sensor->ip >> 16) &0xFF); - ip[2] = (byte)((sensor->ip >> 08) &0xFF); - ip[3] = (byte)((sensor->ip & &0xFF)); + ip[2] = (byte)((sensor->ip >> 8) &0xFF); + ip[3] = (byte)((sensor->ip &0xFF)); #endif char *p = tmp_buffer; @@ -424,8 +428,8 @@ int read_sensor_ip(Sensor_t *sensor) { byte ip[4]; ip[0] = (byte)((sensor->ip >> 24) &0xFF); ip[1] = (byte)((sensor->ip >> 16) &0xFF); - ip[2] = (byte)((sensor->ip >> 08) &0xFF); - ip[3] = (byte)((sensor->ip & &0xFF)); + ip[2] = (byte)((sensor->ip >> 8) &0xFF); + ip[3] = (byte)((sensor->ip &0xFF)); #endif if(!client->connect(ip, sensor->port)) { @@ -682,21 +686,23 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { byte ip[4]; ip[0] = (byte)((sensor->ip >> 24) &0xFF); ip[1] = (byte)((sensor->ip >> 16) &0xFF); - ip[2] = (byte)((sensor->ip >> 08) &0xFF); - ip[3] = (byte)((sensor->ip & &0xFF)); + ip[2] = (byte)((sensor->ip >> 8) &0xFF); + ip[3] = (byte)((sensor->ip &0xFF)); #endif if(!client->connect(ip, sensor->port)) { DEBUG_PRINT(F("Cannot connect to ")); - DEBUG_PRINT(_ip[0]); DEBUG_PRINT("."); - DEBUG_PRINT(_ip[1]); DEBUG_PRINT("."); - DEBUG_PRINT(_ip[2]); DEBUG_PRINT("."); - DEBUG_PRINT(_ip[3]); DEBUG_PRINT(":"); + DEBUG_PRINT(ip[0]); DEBUG_PRINT("."); + DEBUG_PRINT(ip[1]); DEBUG_PRINT("."); + DEBUG_PRINT(ip[2]); DEBUG_PRINT("."); + DEBUG_PRINT(ip[3]); DEBUG_PRINT(":"); DEBUG_PRINTLN(sensor->port); client->stop(); return HTTP_RQT_CONNECT_ERR; } +#if defined(ESP8266) client->write(buffer, len); +#endif client->flush(); //Read result: From 36b0f8c64597e1fffc49f2896e338abdc802ff6e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 27 Jan 2023 00:38:54 +0100 Subject: [PATCH 038/281] OSPi compat --- sensors.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 89a8ba706..41c7f3919 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -434,10 +434,10 @@ int read_sensor_ip(Sensor_t *sensor) { if(!client->connect(ip, sensor->port)) { DEBUG_PRINT(F("Cannot connect to ")); - DEBUG_PRINT(_ip[0]); DEBUG_PRINT("."); - DEBUG_PRINT(_ip[1]); DEBUG_PRINT("."); - DEBUG_PRINT(_ip[2]); DEBUG_PRINT("."); - DEBUG_PRINT(_ip[3]); DEBUG_PRINT(":"); + DEBUG_PRINT(ip[0]); DEBUG_PRINT("."); + DEBUG_PRINT(ip[1]); DEBUG_PRINT("."); + DEBUG_PRINT(ip[2]); DEBUG_PRINT("."); + DEBUG_PRINT(ip[3]); DEBUG_PRINT(":"); DEBUG_PRINTLN(sensor->port); client->stop(); return HTTP_RQT_CONNECT_ERR; @@ -700,10 +700,10 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { return HTTP_RQT_CONNECT_ERR; } -#if defined(ESP8266) client->write(buffer, len); -#endif +#if defined(ESP8266) client->flush(); +#endif //Read result: int n = client->read(buffer, 8); From 1a5f82b5507bd2530471573238466c37c0b4a4bb Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 27 Jan 2023 00:43:49 +0100 Subject: [PATCH 039/281] Tryfix peek for OSPi --- sensors.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 41c7f3919..a69cea53b 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -484,7 +484,6 @@ int read_sensor_ip(Sensor_t *sensor) { client->write(buffer, len); #if defined(ESP8266) client->flush(); -#endif uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; while (true) { if (client->available()) @@ -498,6 +497,21 @@ int read_sensor_ip(Sensor_t *sensor) { } delay(5); } +#else + uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; + while (true) { + if (client->peek() != -1)) + break; + if (millis() >= stoptime) { + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINT(F(" timeout read!")); + return HTTP_RQT_TIMEOUT; + } + delay(5); + } +#endif //Read result: switch(sensor->type) From 39b7253475f1f0d39e70e247de47baaebb528244 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 27 Jan 2023 00:49:56 +0100 Subject: [PATCH 040/281] read data OSPi compat --- sensors.cpp | 57 ++++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index a69cea53b..32a809838 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -484,33 +484,6 @@ int read_sensor_ip(Sensor_t *sensor) { client->write(buffer, len); #if defined(ESP8266) client->flush(); - uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; - while (true) { - if (client->available()) - break; - if (millis() >= stoptime) { - client->stop(); - DEBUG_PRINT(F("Sensor ")); - DEBUG_PRINT(sensor->nr); - DEBUG_PRINT(F(" timeout read!")); - return HTTP_RQT_TIMEOUT; - } - delay(5); - } -#else - uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; - while (true) { - if (client->peek() != -1)) - break; - if (millis() >= stoptime) { - client->stop(); - DEBUG_PRINT(F("Sensor ")); - DEBUG_PRINT(sensor->nr); - DEBUG_PRINT(F(" timeout read!")); - return HTTP_RQT_TIMEOUT; - } - delay(5); - } #endif //Read result: @@ -518,7 +491,37 @@ int read_sensor_ip(Sensor_t *sensor) { { case SENSOR_SMT100_MODBUS_RTU_MOIS: case SENSOR_SMT100_MODBUS_RTU_TEMP: +#if defined(ESP8266) + uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; + while (true) { + if (client->available()) + break; + if (millis() >= stoptime) { + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINT(F(" timeout read!")); + return HTTP_RQT_TIMEOUT; + } + delay(5); + } int n = client->read(buffer, 7); +#else + int n = 0; + while (true) { + n = client->read(buffer, 7); + if (n > 0) + break; + if (millis() >= stoptime) { + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINT(F(" timeout read!")); + return HTTP_RQT_TIMEOUT; + } + delay(5); + } +#endif client->stop(); DEBUG_PRINT(F("Sensor ")); DEBUG_PRINT(sensor->nr); From 23ce723e51200c03b7706e5bc90e9b7255c3ff8c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 27 Jan 2023 00:53:34 +0100 Subject: [PATCH 041/281] Typo fix --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 32a809838..fbb8a9808 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -491,8 +491,8 @@ int read_sensor_ip(Sensor_t *sensor) { { case SENSOR_SMT100_MODBUS_RTU_MOIS: case SENSOR_SMT100_MODBUS_RTU_TEMP: -#if defined(ESP8266) uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; +#if defined(ESP8266) while (true) { if (client->available()) break; From d7be92969e3b86632e9e4f6fa60ae4aee575e705 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 30 Jan 2023 23:48:02 +0100 Subject: [PATCH 042/281] Added support for OpenThingsFramework - big result data --- main.cpp | 8 +++++--- opensprinkler_server.cpp | 14 +++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/main.cpp b/main.cpp index 79a170850..9c202c2e0 100644 --- a/main.cpp +++ b/main.cpp @@ -944,9 +944,6 @@ void do_loop() // activate/deactivate valves os.apply_all_station_bits(); - // read analog sensors - read_all_sensors(); - #if defined(ARDUINO) // process LCD display if (!ui_state) { os.lcd_print_screen(ui_anim_chars[(unsigned long)curr_time%3]); } @@ -1038,6 +1035,11 @@ void do_loop() push_message(NOTIFY_WEATHER_UPDATE, 0, os.iopts[IOPT_WATER_PERCENTAGE]); os.weather_update_flag = 0; } + + // read analog sensors + if (curr_time && os.network_connected() && os.checkwt_success_lasttime) + read_all_sensors(); + static byte reboot_notification = 1; if(reboot_notification) { reboot_notification = 0; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7a5f9e61e..89ceceb26 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -227,10 +227,13 @@ void rewind_ether_buffer() { void send_packet(OTF_PARAMS_DEF) { #if defined(ESP8266) + if (!res.willFit(bfill.position())) + res.flush(); res.writeBodyChunk((char *)"%s",ether_buffer); #else m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); #endif + rewind_ether_buffer(); } @@ -2329,7 +2332,16 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { sensorlog.data, getSensorUnit(sensor), getSensorUnitId(sensor)); - + + DEBUG_PRINT(F("Sensorlog2: ")); + DEBUG_PRINT(res.isValid()?1:0); + DEBUG_PRINT(" "); + DEBUG_PRINT(count); + DEBUG_PRINT(" "); + DEBUG_PRINT(available_ether_buffer()); + DEBUG_PRINT(" "); + DEBUG_PRINTLN(res.getLength()); + // if available ether buffer is getting small // send out a packet if(available_ether_buffer() <= 0) { From 6fb6b0506859b333d065a3e43a3bd14e5245d3ec Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 1 Feb 2023 00:40:19 +0100 Subject: [PATCH 043/281] Added support for Vegetronix VH400, THERM200 and AQUAPLUMB --- opensprinkler_server.cpp | 6 ++++++ sensors.cpp | 42 +++++++++++++++++++++++++++++++++++++--- sensors.h | 3 +++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 89ceceb26..7d48045da 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2550,6 +2550,9 @@ const int sensor_types[] = { SENSOR_ANALOG_EXTENSION_BOARD_P, SENSOR_SMT50_MOIS, SENSOR_SMT50_TEMP, + SENSOR_VH400, + SENSOR_THERM200, + SENSOR_AQUAPLUMB, #endif //SENSOR_OSPI_ANALOG_INPUTS, SENSOR_REMOTE, @@ -2567,6 +2570,9 @@ const char* sensor_names[] = { "OpenSprinkler analog extension board 2xADS1015 x8 - 0..3.3V to 0..100%", "OpenSprinkler analog extension board 2xADS1015 x8 - SMT50 moisture mode", "OpenSprinkler analog extension board 2xADS1015 x8 - SMT50 temperature mode", + "OpenSprinkler analog extension board 2xADS1015 x8 - Vegetronix VH400", + "OpenSprinkler analog extension board 2xADS1015 x8 - Vegetronix THERM200", + "OpenSprinkler analog extension board 2xADS1015 x8 - Vegetronix AquaPlumb", #endif //"OSPi analog input", "Remote sensor of an remote opensprinkler", diff --git a/sensors.cpp b/sensors.cpp index fbb8a9808..39bc5a3dd 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -323,16 +323,43 @@ int read_sensor_adc(Sensor_t *sensor) { //Read values: sensor->last_native_data = adc.readADC(id); sensor->last_data = adc.toVoltage(sensor->last_native_data); + double v = sensor->last_data; switch(sensor->type) { case SENSOR_SMT50_MOIS: // SMT50 VWC [%] = (U * 50) : 3 - sensor->last_data = (sensor->last_data * 50.0) / 3.0; + sensor->last_data = (v * 50.0) / 3.0; break; case SENSOR_SMT50_TEMP: // SMT50 T [°C] = (U – 0,5) * 100 - sensor->last_data = (sensor->last_data - 0.5) * 100.0; + sensor->last_data = (v - 0.5) * 100.0; break; case SENSOR_ANALOG_EXTENSION_BOARD_P: // 0..3,3V -> 0..100% - sensor->last_data = sensor->last_data * 100.0 / 3.3; + sensor->last_data = v * 100.0 / 3.3; + if (sensor->last_data < 0) + sensor->last_data = 0; + else if (sensor->last_data > 100) + sensor->last_data = 100; + break; + case SENSOR_VH400: //http://vegetronix.com/Products/VH400/VH400-Piecewise-Curve + if (v <= 1.1) // 0 to 1.1V VWC= 10*V-1 + sensor->last_data = 10 * v - 1; + else if (v < 1.3) // 1.1V to 1.3V VWC= 25*V- 17.5 + sensor->last_data = 25 * v - 17.5; + else if (v < 1.82) // 1.3V to 1.82V VWC= 48.08*V- 47.5 + sensor->last_data = 48.08 * v - 47.5; + else if (v < 2.2) // 1.82V to 2.2V VWC= 26.32*V- 7.89 + sensor->last_data = 26.32 * v - 7.89; + else // 2.2V - 3.0V VWC= 62.5*V - 87.5 + sensor->last_data = 62.5 * v - 87.5; + break; + case SENSOR_THERM200: //http://vegetronix.com/Products/THERM200/ + sensor->last_data = v * 41.67 - 40; + break; + case SENSOR_AQUAPLUMB: //http://vegetronix.com/Products/AquaPlumb/ + sensor->last_data = v * 100.0 / 3.0; // 0..3V -> 0..100% + if (sensor->last_data < 0) + sensor->last_data = 0; + else if (sensor->last_data > 100) + sensor->last_data = 100; break; } @@ -592,6 +619,9 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_ANALOG_EXTENSION_BOARD_P: case SENSOR_SMT50_MOIS: //SMT50 VWC [%] = (U * 50) : 3 case SENSOR_SMT50_TEMP: //SMT50 T [°C] = (U – 0,5) * 100 + case SENSOR_VH400: + case SENSOR_THERM200: + case SENSOR_AQUAPLUMB: return read_sensor_adc(sensor); #endif @@ -1031,6 +1061,9 @@ byte getSensorUnitId(int type) { case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; case SENSOR_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_VH400: return UNIT_PERCENT; + case SENSOR_THERM200: return UNIT_DEGREE; + case SENSOR_AQUAPLUMB: return UNIT_PERCENT; #endif case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; @@ -1050,6 +1083,9 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; case SENSOR_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_VH400: return UNIT_PERCENT; + case SENSOR_THERM200: return UNIT_DEGREE; + case SENSOR_AQUAPLUMB: return UNIT_PERCENT; #endif case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; case SENSOR_REMOTE: return sensor->unitid; diff --git a/sensors.h b/sensors.h index 41c144279..94c076ddc 100644 --- a/sensors.h +++ b/sensors.h @@ -46,6 +46,9 @@ #define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% #define SENSOR_SMT50_MOIS 15 //New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 #define SENSOR_SMT50_TEMP 16 //New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +#define SENSOR_VH400 17 //New OpenSprinkler analog extension board x8 - Vegetronix VH400 +#define SENSOR_THERM200 18 //New OpenSprinkler analog extension board x8 - Vegetronix THERM200 +#define SENSOR_AQUAPLUMB 19 //New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb #endif #define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input #define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler From e705a3fe5095ca408e52206c4d60a547964e31ff Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 13 Feb 2023 23:18:03 +0100 Subject: [PATCH 044/281] Added SMT100 Analog support --- opensprinkler_server.cpp | 40 +++++++++++++++++++++++++++++++++------- sensors.cpp | 15 +++++++++++++++ sensors.h | 12 ++++++++---- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7d48045da..8a2927ce1 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2277,6 +2277,8 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { uint type = 0; ulong after = 0; ulong before = 0; + ulong lastHours = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr nr = strtoul(tmp_buffer, NULL, 0); @@ -2289,6 +2291,12 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // Filter time before before = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lasthours"), true)) // Filter time before + lastHours = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lastdays"), true)) // Filter time before + lastHours = strtoul(tmp_buffer, NULL, 0) * 24 + lastHours; + #if defined(ESP8266) // 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(). @@ -2304,6 +2312,19 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { ulong count = 0; SensorLog_t sensorlog; Sensor_t *sensor = NULL; + + //lastHours: find limit for this + if (lastHours > 0 && log_size > 0) { + ulong timeLimit = os.now_tz() - lastHours * 60 * 60; //seconds + for (ulong idx = log_size-1; idx >= 0; idx--) { + sensorlog_load(idx, &sensorlog); + if (sensorlog.time < timeLimit) { + startAt = idx+1; + break; + } + } + } + for (ulong idx = startAt; idx < log_size; idx++) { sensorlog_load(idx, &sensorlog); @@ -2550,6 +2571,8 @@ const int sensor_types[] = { SENSOR_ANALOG_EXTENSION_BOARD_P, SENSOR_SMT50_MOIS, SENSOR_SMT50_TEMP, + SENSOR_SMT100_ANALOG_MOIS, + SENSOR_SMT100_ANALOG_TEMP, SENSOR_VH400, SENSOR_THERM200, SENSOR_AQUAPLUMB, @@ -2566,13 +2589,16 @@ const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", #if defined(ESP8266) - "OpenSprinkler analog extension board 2xADS1015 x8 - voltage mode 0..4V", - "OpenSprinkler analog extension board 2xADS1015 x8 - 0..3.3V to 0..100%", - "OpenSprinkler analog extension board 2xADS1015 x8 - SMT50 moisture mode", - "OpenSprinkler analog extension board 2xADS1015 x8 - SMT50 temperature mode", - "OpenSprinkler analog extension board 2xADS1015 x8 - Vegetronix VH400", - "OpenSprinkler analog extension board 2xADS1015 x8 - Vegetronix THERM200", - "OpenSprinkler analog extension board 2xADS1015 x8 - Vegetronix AquaPlumb", + "OpenSprinkler analog extension board 2xADS1x15 x8 - voltage mode 0..4V", + "OpenSprinkler analog extension board 2xADS1x15 x8 - 0..3.3V to 0..100%", + "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT50 moisture mode", + "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT50 temperature mode", + "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT100-analog moisture mode", + "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT100-analog temperature mode", + + "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix VH400", + "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix THERM200", + "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix AquaPlumb", #endif //"OSPi analog input", "Remote sensor of an remote opensprinkler", diff --git a/sensors.cpp b/sensors.cpp index 39bc5a3dd..c35816d69 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -339,6 +339,13 @@ int read_sensor_adc(Sensor_t *sensor) { else if (sensor->last_data > 100) sensor->last_data = 100; break; + case SENSOR_SMT100_ANALOG_MOIS: // 0..3V -> 0..100% + sensor->last_data = v * 100.0 / 3; + break; + case SENSOR_SMT100_ANALOG_TEMP: // 0..3V -> -40°C..60°C + sensor->last_data = v * 100.0 / 3 - 40; + break; + case SENSOR_VH400: //http://vegetronix.com/Products/VH400/VH400-Piecewise-Curve if (v <= 1.1) // 0 to 1.1V VWC= 10*V-1 sensor->last_data = 10 * v - 1; @@ -619,6 +626,8 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_ANALOG_EXTENSION_BOARD_P: case SENSOR_SMT50_MOIS: //SMT50 VWC [%] = (U * 50) : 3 case SENSOR_SMT50_TEMP: //SMT50 T [°C] = (U – 0,5) * 100 + case SENSOR_SMT100_ANALOG_MOIS: //SMT100 Analog Moisture + case SENSOR_SMT100_ANALOG_TEMP: //SMT100 Analog Temperature case SENSOR_VH400: case SENSOR_THERM200: case SENSOR_AQUAPLUMB: @@ -1061,6 +1070,9 @@ byte getSensorUnitId(int type) { case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; case SENSOR_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_SMT100_ANALOG_MOIS: return UNIT_PERCENT; + case SENSOR_SMT100_ANALOG_TEMP: return UNIT_DEGREE; + case SENSOR_VH400: return UNIT_PERCENT; case SENSOR_THERM200: return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; @@ -1083,6 +1095,9 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; case SENSOR_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_SMT100_ANALOG_MOIS: return UNIT_PERCENT; + case SENSOR_SMT100_ANALOG_TEMP: return UNIT_DEGREE; + case SENSOR_VH400: return UNIT_PERCENT; case SENSOR_THERM200: return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; diff --git a/sensors.h b/sensors.h index 94c076ddc..2a4de995f 100644 --- a/sensors.h +++ b/sensors.h @@ -46,11 +46,15 @@ #define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% #define SENSOR_SMT50_MOIS 15 //New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 #define SENSOR_SMT50_TEMP 16 //New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 -#define SENSOR_VH400 17 //New OpenSprinkler analog extension board x8 - Vegetronix VH400 -#define SENSOR_THERM200 18 //New OpenSprinkler analog extension board x8 - Vegetronix THERM200 -#define SENSOR_AQUAPLUMB 19 //New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb +#define SENSOR_SMT100_ANALOG_MOIS 17 //New OpenSprinkler analog extension board x8 - SMT100 VWC [%] = (U * 100) : 3 +#define SENSOR_SMT100_ANALOG_TEMP 18 //New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U * 100) : 3 - 40 + +#define SENSOR_VH400 30 //New OpenSprinkler analog extension board x8 - Vegetronix VH400 +#define SENSOR_THERM200 31 //New OpenSprinkler analog extension board x8 - Vegetronix THERM200 +#define SENSOR_AQUAPLUMB 32 //New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb + #endif -#define SENSOR_OSPI_ANALOG_INPUTS 20 //Old OSPi analog input +#define SENSOR_OSPI_ANALOG_INPUTS 50 //Old OSPi analog input #define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler #define SENSOR_GROUP_MIN 1000 //Sensor group with min value From 2d2837de1f7e6bf316882def1dde718e36ad9c6b Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 16 Feb 2023 00:19:55 +0100 Subject: [PATCH 045/281] Fix log crash --- opensprinkler_server.cpp | 18 +++++------------- sensors.cpp | 2 +- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8a2927ce1..f01f4d25d 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2291,10 +2291,10 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // Filter time before before = strtoul(tmp_buffer, NULL, 0); - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lasthours"), true)) // Filter time before + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lasthours"), true)) // Filter last hours lastHours = strtoul(tmp_buffer, NULL, 0); - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lastdays"), true)) // Filter time before + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lastdays"), true)) // Filter last days lastHours = strtoul(tmp_buffer, NULL, 0) * 24 + lastHours; #if defined(ESP8266) @@ -2315,8 +2315,9 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { //lastHours: find limit for this if (lastHours > 0 && log_size > 0) { - ulong timeLimit = os.now_tz() - lastHours * 60 * 60; //seconds - for (ulong idx = log_size-1; idx >= 0; idx--) { + time_t timeLimit = os.now_tz() - lastHours * 60 * 60; //seconds + DEBUG_PRINTLN(F("lastHours")); + for (ulong idx = log_size-1; idx > 0; idx--) { sensorlog_load(idx, &sensorlog); if (sensorlog.time < timeLimit) { startAt = idx+1; @@ -2353,15 +2354,6 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { sensorlog.data, getSensorUnit(sensor), getSensorUnitId(sensor)); - - DEBUG_PRINT(F("Sensorlog2: ")); - DEBUG_PRINT(res.isValid()?1:0); - DEBUG_PRINT(" "); - DEBUG_PRINT(count); - DEBUG_PRINT(" "); - DEBUG_PRINT(available_ether_buffer()); - DEBUG_PRINT(" "); - DEBUG_PRINTLN(res.getLength()); // if available ether buffer is getting small // send out a packet diff --git a/sensors.cpp b/sensors.cpp index c35816d69..e6e67a06c 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -234,7 +234,7 @@ bool sensorlog_add(SensorLog_t *sensorlog) { } bool sensorlog_add(Sensor_t *sensor, ulong time) { - if (sensor->flags.data_ok && sensor->flags.log) { + if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { SensorLog_t sensorlog; sensorlog.nr = sensor->nr; sensorlog.time = time; From 3e770e8669b7eee8dc325ef938e3050b70ec5545 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 22 Feb 2023 23:37:04 +0100 Subject: [PATCH 046/281] Log Switching: Analog sensor data is logged in 2 files with max 8000 entries. If one is full, the other is cleared and used --- defines.h | 3 --- opensprinkler_server.cpp | 19 ++++++++++---- platformio.ini | 2 +- sensors.cpp | 56 ++++++++++++++++++++++++++++++++++++---- sensors.h | 15 ++++++++--- 5 files changed, 77 insertions(+), 18 deletions(-) diff --git a/defines.h b/defines.h index 623170fab..ca4b6a16a 100644 --- a/defines.h +++ b/defines.h @@ -57,9 +57,6 @@ 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 SENSOR_FILENAME "sensor.dat" // analog sensor filename -#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename -#define SENSORLOG_FILENAME "sensorlog.dat" // analog sensor log filename /** Station macro defines */ #define STN_TYPE_STANDARD 0x00 // standard solenoid station diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index f01f4d25d..d0293d2e2 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2315,15 +2315,24 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { //lastHours: find limit for this if (lastHours > 0 && log_size > 0) { - time_t timeLimit = os.now_tz() - lastHours * 60 * 60; //seconds + after = os.now_tz() - lastHours * 60 * 60; //seconds DEBUG_PRINTLN(F("lastHours")); - for (ulong idx = log_size-1; idx > 0; idx--) { + + ulong a = 0; + ulong b = (log_size-1) / 2; + ulong lastIdx = 0; + while (true) { + ulong idx = (b-a)/2+a; sensorlog_load(idx, &sensorlog); - if (sensorlog.time < timeLimit) { - startAt = idx+1; - break; + if (sensorlog.time < after) { + a = idx; + } else if (sensorlog.time > after) { + b = idx; } + if (a >= b || idx == lastIdx) break; + lastIdx = idx; } + startAt = lastIdx; } for (ulong idx = startAt; idx < log_size; idx++) { diff --git a/platformio.ini b/platformio.ini index 1f9a8db9e..0c9a08a09 100644 --- a/platformio.ini +++ b/platformio.ini @@ -24,7 +24,7 @@ lib_deps = sui77/rc-switch @ ^2.6.3 https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 - https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library/archive/refs/heads/master.zip + https://github.com/OpenSprinklerShop/OpenThings-Framework-Firmware-Library https://github.com/Links2004/arduinoWebSockets/archive/refs/tags/2.3.5.zip RobTillaart/ADS1X15 https://github.com/bluemurder/esp8266-ping diff --git a/sensors.cpp b/sensors.cpp index e6e67a06c..95b92cd35 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -33,6 +33,12 @@ static Sensor_t *sensors = NULL; //Program sensor data static ProgSensorAdjust_t *progSensorAdjusts = NULL; +const char* sensor_unitNames[] { + "", "%", "°C", "°F", "V", +// 0 1 2 3 4 +}; +byte logFileSwitch = 0; //0=use smaler File, 1=LOG1, 2=LOG2 + uint16_t CRC16 (byte buf[], int len) { uint16_t crc = 0xFFFF; @@ -219,12 +225,36 @@ Sensor_t *sensor_by_idx(uint idx) { return NULL; } +void checkLogSwitch() { + if (logFileSwitch == 0) { // Check file size, use smallest + ulong size1 = file_size(SENSORLOG_FILENAME1); + ulong size2 = file_size(SENSORLOG_FILENAME2); + if (size1 < size2) + logFileSwitch = 1; + else + logFileSwitch = 2; + } +} + +void checkLogSwitchAfterWrite() { + ulong size = file_size(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2); + if ((size / SENSORLOG_STORE_SIZE) >= MAX_LOG_SIZE) { // switch logs if max reached + if (logFileSwitch == 1) + logFileSwitch = 2; + else + logFileSwitch = 1; + remove_file(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2); + } +} + bool sensorlog_add(SensorLog_t *sensorlog) { #if defined(ESP8266) if (checkDiskFree()) { #endif DEBUG_PRINT(F("sensorlog_add ")); - file_append_block(SENSORLOG_FILENAME, sensorlog, SENSORLOG_STORE_SIZE); + checkLogSwitch(); + file_append_block(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2, sensorlog, SENSORLOG_STORE_SIZE); + checkLogSwitchAfterWrite(); DEBUG_PRINT(sensorlog_filesize()); return true; #if defined(ESP8266) @@ -251,21 +281,23 @@ bool sensorlog_add(Sensor_t *sensor, ulong time) { ulong sensorlog_filesize() { DEBUG_PRINT(F("sensorlog_filesize ")); - ulong size = file_size(SENSORLOG_FILENAME); + ulong size = file_size(SENSORLOG_FILENAME1) + file_size(SENSORLOG_FILENAME2); DEBUG_PRINTLN(size); return size; } ulong sensorlog_size() { DEBUG_PRINT(F("sensorlog_size ")); - ulong size = file_size(SENSORLOG_FILENAME) / SENSORLOG_STORE_SIZE; + ulong size = (file_size(SENSORLOG_FILENAME1) + file_size(SENSORLOG_FILENAME2)) / SENSORLOG_STORE_SIZE; DEBUG_PRINTLN(size); return size; } void sensorlog_clear_all() { DEBUG_PRINTLN(F("sensorlog_clear_all")); - remove_file(SENSORLOG_FILENAME); + remove_file(SENSORLOG_FILENAME1); + remove_file(SENSORLOG_FILENAME2); + logFileSwitch = 1; } SensorLog_t *sensorlog_load(ulong idx) { @@ -275,7 +307,21 @@ SensorLog_t *sensorlog_load(ulong idx) { SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog) { DEBUG_PRINTLN(F("sensorlog_load")); - file_read_block(SENSORLOG_FILENAME, sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); + + //Map lower idx to the other log file + checkLogSwitch(); + const char *flast = logFileSwitch==1?SENSORLOG_FILENAME2:SENSORLOG_FILENAME1; + const char *fcur = logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2; + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + const char *f; + if (idx >= size) { + idx -= size; + f = fcur; + } else { + f = flast; + } + + file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); return sensorlog; } diff --git a/sensors.h b/sensors.h index 2a4de995f..6e3e9ed66 100644 --- a/sensors.h +++ b/sensors.h @@ -37,6 +37,15 @@ #include #endif +//Files +#define SENSOR_FILENAME "sensor.dat" // analog sensor filename +#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename +#define SENSORLOG_FILENAME1 "sensorlog.dat" // analog sensor log filename +#define SENSORLOG_FILENAME2 "sensorlog2.dat" // analog sensor log filename2 + +//MaxLogSize +#define MAX_LOG_SIZE 8000 + //Sensor types: #define SENSOR_NONE 0 //None or deleted sensor #define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode @@ -140,10 +149,8 @@ typedef struct ProgSensorAdjust { #define UNIT_VOLT 4 //Unitnames -static const char* sensor_unitNames[] { - "", "%", "°C", "°F", "V", -// 0 1 2 3 4 -}; +extern const char* sensor_unitNames[]; +extern byte logFileSwitch; //0=use smaler File, 1=LOG1, 2=LOG2 const char* getSensorUnit(Sensor_t *sensor); byte getSensorUnitId(int type); From b998a40f0b602229ed500db51cd6b5c923240951 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 24 Feb 2023 10:20:37 +0100 Subject: [PATCH 047/281] fix remote sensor read --- sensors.cpp | 76 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 95b92cd35..dff4323b8 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -38,6 +38,8 @@ const char* sensor_unitNames[] { // 0 1 2 3 4 }; byte logFileSwitch = 0; //0=use smaler File, 1=LOG1, 2=LOG2 +ulong logFileSize1 = 0; +ulong logFileSize2 = 0; uint16_t CRC16 (byte buf[], int len) { uint16_t crc = 0xFFFF; @@ -227,9 +229,9 @@ Sensor_t *sensor_by_idx(uint idx) { void checkLogSwitch() { if (logFileSwitch == 0) { // Check file size, use smallest - ulong size1 = file_size(SENSORLOG_FILENAME1); - ulong size2 = file_size(SENSORLOG_FILENAME2); - if (size1 < size2) + logFileSize1 = file_size(SENSORLOG_FILENAME1); + logFileSize2 = file_size(SENSORLOG_FILENAME2); + if (logFileSize1 < logFileSize2) logFileSwitch = 1; else logFileSwitch = 2; @@ -245,6 +247,8 @@ void checkLogSwitchAfterWrite() { logFileSwitch = 1; remove_file(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2); } + logFileSize1 = file_size(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2); + logFileSize2 = file_size(logFileSwitch==2?SENSORLOG_FILENAME2:SENSORLOG_FILENAME1); } bool sensorlog_add(SensorLog_t *sensorlog) { @@ -281,14 +285,15 @@ bool sensorlog_add(Sensor_t *sensor, ulong time) { ulong sensorlog_filesize() { DEBUG_PRINT(F("sensorlog_filesize ")); - ulong size = file_size(SENSORLOG_FILENAME1) + file_size(SENSORLOG_FILENAME2); + checkLogSwitch(); + ulong size = logFileSize1+logFileSize2; DEBUG_PRINTLN(size); return size; } ulong sensorlog_size() { DEBUG_PRINT(F("sensorlog_size ")); - ulong size = (file_size(SENSORLOG_FILENAME1) + file_size(SENSORLOG_FILENAME2)) / SENSORLOG_STORE_SIZE; + ulong size = sensorlog_filesize() / SENSORLOG_STORE_SIZE; DEBUG_PRINTLN(size); return size; } @@ -298,6 +303,8 @@ void sensorlog_clear_all() { remove_file(SENSORLOG_FILENAME1); remove_file(SENSORLOG_FILENAME2); logFileSwitch = 1; + logFileSize1 = 0; + logFileSize2 = 0; } SensorLog_t *sensorlog_load(ulong idx) { @@ -312,7 +319,7 @@ SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog) { checkLogSwitch(); const char *flast = logFileSwitch==1?SENSORLOG_FILENAME2:SENSORLOG_FILENAME1; const char *fcur = logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2; - ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + ulong size = (logFileSwitch==1?logFileSize2:logFileSize1) / SENSORLOG_STORE_SIZE; const char *f; if (idx >= size) { idx -= size; @@ -345,7 +352,7 @@ void read_all_sensors() { #if defined(ESP8266) /** - * Read ADS1015 sensors + * Read ADS1115 sensors */ int read_sensor_adc(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_adc")); @@ -356,7 +363,7 @@ int read_sensor_adc(Sensor_t *sensor) { int port = sensor->id < 4? 72 : 73; int id = sensor->id % 4; - ADS1015 adc(port); + ADS1115 adc(port); bool active = adc.begin(); if (active) adc.setGain(1); @@ -436,7 +443,23 @@ int read_sensor_ospi(Sensor_t *sensor) { return HTTP_RQT_SUCCESS; } -extern byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); +bool extract(char *s, char *buf, int maxlen) { + s = strstr(s, ":"); + if (!s) return false; + s++; + while (*s == ' ') s++; //skip spaces + char *e = strstr(s, ","); + char *f = strstr(s, "}"); + if (!e && !f) return false; + if (f && f < e) e = f; + int l = e-s; + if (l < 1 || l > maxlen) + return false; + strncpy(buf, s, l); + buf[l] = 0; + DEBUG_PRINTLN(buf); + return true; +} int read_sensor_http(Sensor_t *sensor) { #if defined(ESP8266) @@ -450,6 +473,8 @@ int read_sensor_http(Sensor_t *sensor) { ip[3] = (byte)((sensor->ip &0xFF)); #endif + DEBUG_PRINTLN(F("read_sensor_http")); + char *p = tmp_buffer; BufferFiller bf = p; @@ -458,18 +483,31 @@ int read_sensor_http(Sensor_t *sensor) { bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0],ip[1],ip[2],ip[3]); - if (os.send_http_request(sensor->ip, sensor->port, p) == HTTP_RQT_SUCCESS) { - - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nativedata"), true)) { - sensor->last_native_data = strtoul(tmp_buffer, NULL, 0); + DEBUG_PRINTLN(p); + + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + if (os.send_http_request(server, sensor->port, p) == HTTP_RQT_SUCCESS) { + DEBUG_PRINTLN("Send Ok"); + p = ether_buffer; + DEBUG_PRINTLN(p); + + char buf[20]; + char *s = strstr(p, "\"nativedata\":"); + if (s && extract(s, buf, sizeof(buf))) { + sensor->last_native_data = strtoul(buf, NULL, 0); } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("data"), true)) { - sensor->last_data = atof(tmp_buffer); - sensor->flags.data_ok = true; + s = strstr(p, "\"data\":"); + if (s && extract(s, buf, sizeof(buf))) { + sensor->last_data = strtod(buf, NULL); + sensor->flags.data_ok = tr } - if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unitid"), true)) { - sensor->unitid = strtoul(tmp_buffer, NULL, 0); - } + s = strstr(p, "\"unitid\":"); + if (s && extract(s, buf, sizeof(buf))) { + sensor->unitid = atoi(buf); + } + return HTTP_RQT_SUCCESS; } return HTTP_RQT_EMPTY_RETURN; From f59da7b5dea37f0c356e78fab3c6b33fda9456e6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 24 Feb 2023 10:21:22 +0100 Subject: [PATCH 048/281] fix remote sensor read --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index dff4323b8..c98272200 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -501,7 +501,7 @@ int read_sensor_http(Sensor_t *sensor) { s = strstr(p, "\"data\":"); if (s && extract(s, buf, sizeof(buf))) { sensor->last_data = strtod(buf, NULL); - sensor->flags.data_ok = tr + sensor->flags.data_ok = true; } s = strstr(p, "\"unitid\":"); if (s && extract(s, buf, sizeof(buf))) { From e67bc82deb092368cca2559b1ee2b739978b2222 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 28 Feb 2023 00:36:26 +0100 Subject: [PATCH 049/281] Added download log as CSV --- opensprinkler_server.cpp | 70 ++++++++++++++++++++++++++++++++-------- 1 file changed, 56 insertions(+), 14 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index d0293d2e2..a97a25241 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -258,6 +258,24 @@ void print_header(bool isJson=true) { } #endif +#if defined(ESP8266) +void print_header_download(OTF_PARAMS_DEF, int len=0) { + res.writeStatus(200, F("OK")); + res.writeHeader(F("Content-Type"), F("text/plain")); + res.writeHeader(F("Content-Disposition"), F("attachment; filename=\"log.csv\";")); + if(len>0) + res.writeHeader(F("Content-Length"), len); + res.writeHeader(F("Access-Control-Allow-Origin"), F("*")); + res.writeHeader(F("Cache-Control"), F("max-age=0, no-cache, no-store, must-revalidate")); + res.writeHeader(F("Connection"), F("close")); +} +#else + +void print_header_download() { + bfill.emit_p(PSTR("$F$F$F$F$F\r\n"), html200OK, "Content-Type: text/plain", "Content-Disposition: attachment; filename=\"log.txt\";", htmlAccessControl, htmlNoCache); +} +#endif + #if defined(ESP8266) String two_digits(uint8_t x) { @@ -2278,6 +2296,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { ulong after = 0; ulong before = 0; ulong lastHours = 0; + bool isjson = true; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr nr = strtoul(tmp_buffer, NULL, 0); @@ -2297,17 +2316,24 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lastdays"), true)) // Filter last days lastHours = strtoul(tmp_buffer, NULL, 0) * 24 + lastHours; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("csv"), true)) // Filter last days + isjson = atoi(tmp_buffer) == 0; + #if defined(ESP8266) // 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); + if (isjson) print_header(OTF_PARAMS); else print_header_download(OTF_PARAMS); #else - print_header(); + if (isjson) print_header(); else print_header_download(); #endif - bfill.emit_p(PSTR("{\"logsize\":$D,\"filesize\":$D,\"log\":["), - log_size, sensorlog_filesize()); + if (isjson) { + bfill.emit_p(PSTR("{\"logsize\":$D,\"filesize\":$D,\"log\":["), + log_size, sensorlog_filesize()); + } else { + bfill.emit_p(PSTR("nr;type;time;nativedata;data;unit;unitid\r\n")); + } ulong count = 0; SensorLog_t sensorlog; @@ -2353,16 +2379,30 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (type && sensor_type != type) continue; - if (count > 0) + if (count > 0 && isjson) { bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), - sensorlog.nr, //sensor-nr - sensor_type, //sensor-type - sensorlog.time, //timestamp - sensorlog.native_data, //native data - sensorlog.data, - getSensorUnit(sensor), - getSensorUnitId(sensor)); + } + + if (isjson) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), + sensorlog.nr, //sensor-nr + sensor_type, //sensor-type + sensorlog.time, //timestamp + sensorlog.native_data, //native data + sensorlog.data, + getSensorUnit(sensor), + getSensorUnitId(sensor)); + } else { + bfill.emit_p(PSTR("$D;$D;$L;$L;$E;$S;$D\r\n"), + sensorlog.nr, //sensor-nr + sensor_type, //sensor-type + sensorlog.time, //timestamp + sensorlog.native_data, //native data + sensorlog.data, + getSensorUnit(sensor), + getSensorUnitId(sensor)); + } + // if available ether buffer is getting small // send out a packet @@ -2372,7 +2412,9 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (++count >= maxResults) break; } - bfill.emit_p(PSTR("]}")); + + if (isjson) + bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); } From 3a84c20eda5b1f7caccc00f3456fff7e5f98a88a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 4 Mar 2023 13:15:52 +0100 Subject: [PATCH 050/281] Fix Program adjustment (prog1 = prog0) --- main.cpp | 2 +- sensors.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 9c202c2e0..d8166ce7c 100644 --- a/main.cpp +++ b/main.cpp @@ -754,7 +754,7 @@ void do_loop() } // Analog sensor water time adjustments: - water_time = (ulong)(water_time * calc_sensor_watering(pid)); + water_time = (ulong)((double)water_time * calc_sensor_watering(pid)); if (water_time) { // check if water time is still valid diff --git a/sensors.cpp b/sensors.cpp index c98272200..d05e58f18 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -917,7 +917,7 @@ double calc_sensor_watering(uint prog) { ProgSensorAdjust_t *p = progSensorAdjusts; while (p) { - if (p->prog == prog) { + if (p->prog-1 == prog) { Sensor_t *sensor = sensor_by_nr(p->sensor); if (sensor && sensor->flags.enable && sensor->flags.data_ok) { From 0ed3df8584fc07b03b9bbb5b1f69cf82e5f64b31 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 10 Mar 2023 23:43:38 +0100 Subject: [PATCH 051/281] Fixed logging switch: Rotating log with 2x 8000 entries, when file 1 reached 8000, we switch to a new file 2. --- sensors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index d05e58f18..b84c45162 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -247,8 +247,8 @@ void checkLogSwitchAfterWrite() { logFileSwitch = 1; remove_file(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2); } - logFileSize1 = file_size(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2); - logFileSize2 = file_size(logFileSwitch==2?SENSORLOG_FILENAME2:SENSORLOG_FILENAME1); + logFileSize1 = file_size(SENSORLOG_FILENAME1); + logFileSize2 = file_size(SENSORLOG_FILENAME2); } bool sensorlog_add(SensorLog_t *sensorlog) { From b550d721a8b06245222ec960605d503a68de706a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 20 Mar 2023 00:14:28 +0100 Subject: [PATCH 052/281] Fix: Deleted last sensor or program adjustment re-appears after reboot --- sensors.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index b84c45162..43d709855 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -33,7 +33,7 @@ static Sensor_t *sensors = NULL; //Program sensor data static ProgSensorAdjust_t *progSensorAdjusts = NULL; -const char* sensor_unitNames[] { +const char* sensor_unitNames[] { "", "%", "°C", "°F", "V", // 0 1 2 3 4 }; @@ -43,7 +43,7 @@ ulong logFileSize2 = 0; uint16_t CRC16 (byte buf[], int len) { uint16_t crc = 0xFFFF; - + for (int pos = 0; pos < len; pos++) { crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc for (int i = 8; i != 0; i--) { // Loop over each bit @@ -180,7 +180,7 @@ void sensor_load() { */ void sensor_save() { DEBUG_PRINTLN(F("sensor_save")); - if (!sensors && file_exists(SENSOR_FILENAME)) + if (file_exists(SENSOR_FILENAME)) remove_file(SENSOR_FILENAME); ulong pos = 0; @@ -1043,7 +1043,7 @@ int prog_adjust_delete(uint nr) { } void prog_adjust_save() { - if (!progSensorAdjusts && file_exists(PROG_SENSOR_FILENAME)) + if (file_exists(PROG_SENSOR_FILENAME)) remove_file(PROG_SENSOR_FILENAME); ulong pos = 0; From 9a4bd96d55a0d6d332cb327bab4caef569aab8bc Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 20 Mar 2023 00:21:05 +0100 Subject: [PATCH 053/281] Fix: if a group has logging enabled, logging is also stored now --- sensors.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/sensors.cpp b/sensors.cpp index 43d709855..f52e83eb1 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -723,6 +723,13 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_REMOTE: return read_sensor_http(sensor); + //Return true for logging: + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + return HTTP_RQT_SUCCESS; + default: return HTTP_RQT_NOT_RECEIVED; } } From 6a26b0617889bb66f42f4e9d4a49ab56b38166bb Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 6 Apr 2023 01:33:47 +0200 Subject: [PATCH 054/281] Added Support for ApexCharts Day, Week and Month --- opensprinkler_server.cpp | 149 ++++++++----- sensors.cpp | 458 ++++++++++++++++++++++++++++++--------- sensors.h | 26 ++- 3 files changed, 466 insertions(+), 167 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index a97a25241..f6c6e82ff 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -227,7 +227,7 @@ void rewind_ether_buffer() { void send_packet(OTF_PARAMS_DEF) { #if defined(ESP8266) - if (!res.willFit(bfill.position())) + if (bfill.position() > 8192 || !res.willFit(bfill.position())) res.flush(); res.writeBodyChunk((char *)"%s",ether_buffer); #else @@ -2277,10 +2277,16 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { char *p = get_buffer; #endif - ulong log_size = sensorlog_size(); - DEBUG_PRINTLN(F("server_sensorlog_list")); + uint8_t log = LOG_STD; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Log type 0=DAY 1=WEEK 2=MONTH + log = strtoul(tmp_buffer, NULL, 0); + if (log > LOG_MONTH) + log = LOG_STD; + ulong log_size = sensorlog_size(log); + //start / max: ulong startAt = 0; ulong maxResults = log_size; @@ -2297,6 +2303,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { ulong before = 0; ulong lastHours = 0; bool isjson = true; + bool shortcsv = false; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr nr = strtoul(tmp_buffer, NULL, 0); @@ -2316,8 +2323,11 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lastdays"), true)) // Filter last days lastHours = strtoul(tmp_buffer, NULL, 0) * 24 + lastHours; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("csv"), true)) // Filter last days - isjson = atoi(tmp_buffer) == 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("csv"), true)) { // Filter last days + int csv = atoi(tmp_buffer); + isjson = csv == 0; + shortcsv = csv == 2; + } #if defined(ESP8266) // as the log data can be large, we will use ESP8266's sendContent function to @@ -2329,14 +2339,18 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { #endif if (isjson) { - bfill.emit_p(PSTR("{\"logsize\":$D,\"filesize\":$D,\"log\":["), - log_size, sensorlog_filesize()); + bfill.emit_p(PSTR("{\"logtype\":$D,\"logsize\":$D,\"filesize\":$D,\"log\":["), + log, log_size, sensorlog_filesize(log)); } else { - bfill.emit_p(PSTR("nr;type;time;nativedata;data;unit;unitid\r\n")); + if (shortcsv) + bfill.emit_p(PSTR("nr;time;data\r\n")); + else + bfill.emit_p(PSTR("nr;type;time;nativedata;data;unit;unitid\r\n")); } + #define BLOCKSIZE 64 ulong count = 0; - SensorLog_t sensorlog; + SensorLog_t *sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); Sensor_t *sensor = NULL; //lastHours: find limit for this @@ -2349,10 +2363,10 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { ulong lastIdx = 0; while (true) { ulong idx = (b-a)/2+a; - sensorlog_load(idx, &sensorlog); - if (sensorlog.time < after) { + sensorlog_load(log, idx, sensorlog); + if (sensorlog->time < after) { a = idx; - } else if (sensorlog.time > after) { + } else if (sensorlog->time > after) { b = idx; } if (a >= b || idx == lastIdx) break; @@ -2361,60 +2375,74 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { startAt = lastIdx; } - for (ulong idx = startAt; idx < log_size; idx++) { - sensorlog_load(idx, &sensorlog); + uint sensor_type = 0; - if (nr && sensorlog.nr != nr) - continue; + ulong idx = startAt; + while (idx < log_size) { + int n = sensorlog_load2(log, idx, BLOCKSIZE, sensorlog); + if (n <= 0) break; - if (after && sensorlog.time <= after) - continue; + for (int i = 0; i < n; i++) { + idx++; + if (nr && sensorlog[i].nr != nr) + continue; - if (before && sensorlog.time >= before) - continue; + if (after && sensorlog[i].time <= after) + continue; - if (!sensor || sensor->nr != sensorlog.nr) - sensor = sensor_by_nr(sensorlog.nr); - uint sensor_type = sensor?sensor->type:0; - if (type && sensor_type != type) - continue; + if (before && sensorlog[i].time >= before) + continue; + + if (!shortcsv || type) { + if (!sensor || sensor->nr != sensorlog[i].nr) + sensor = sensor_by_nr(sensorlog[i].nr); + sensor_type = sensor?sensor->type:0; + if (type && sensor_type != type) + continue; + } - if (count > 0 && isjson) { - bfill.emit_p(PSTR(",")); - } + if (count > 0 && isjson) { + bfill.emit_p(PSTR(",")); + } - if (isjson) { - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), - sensorlog.nr, //sensor-nr + if (isjson) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), + sensorlog[i].nr, //sensor-nr sensor_type, //sensor-type - sensorlog.time, //timestamp - sensorlog.native_data, //native data - sensorlog.data, + sensorlog[i].time, //timestamp + sensorlog[i].native_data, //native data + sensorlog[i].data, getSensorUnit(sensor), getSensorUnitId(sensor)); - } else { - bfill.emit_p(PSTR("$D;$D;$L;$L;$E;$S;$D\r\n"), - sensorlog.nr, //sensor-nr - sensor_type, //sensor-type - sensorlog.time, //timestamp - sensorlog.native_data, //native data - sensorlog.data, - getSensorUnit(sensor), - getSensorUnitId(sensor)); - } - - - // if available ether buffer is getting small - // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); + } else { + if (shortcsv) + bfill.emit_p(PSTR("$D;$L;$E\r\n"), + sensorlog[i].nr, //sensor-nr + sensorlog[i].time, //timestamp + sensorlog[i].data); + else + bfill.emit_p(PSTR("$D;$D;$L;$L;$E;$S;$D\r\n"), + sensorlog[i].nr, //sensor-nr + sensor_type, //sensor-type + sensorlog[i].time, //timestamp + sensorlog[i].native_data, //native data + sensorlog[i].data, + getSensorUnit(sensor), + getSensorUnitId(sensor)); + } + // if available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + if (++count >= maxResults) + break; } - if (++count >= maxResults) - break; } if (isjson) bfill.emit_p(PSTR("]}")); + free(sensorlog); handle_return(HTML_OK); } @@ -2430,6 +2458,9 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { #else char *p = get_buffer; #endif + int log = -1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Filter log for sensor-nr + log = atoi(tmp_buffer); DEBUG_PRINTLN(F("server_sensorlog_clear")); @@ -2442,11 +2473,19 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { print_header(); #endif - ulong log_size = sensorlog_size(); + ulong log_size = sensorlog_size(LOG_STD); + ulong log_sizeW = sensorlog_size(LOG_WEEK); + ulong log_sizeM = sensorlog_size(LOG_MONTH); - sensorlog_clear_all(); + if (log == -1) { + sensorlog_clear_all(); + bfill.emit_p(PSTR("{\"deleted\":$L,\"deleted_week\":$L,\"deleted_month\":$L}"), log_size, log_sizeW, log_sizeM); + } + else { + sensorlog_clear(log==LOG_STD, log==LOG_WEEK, log==LOG_MONTH); + bfill.emit_p(PSTR("{\"deleted\":$L}"), log==LOG_STD?log_size:log=LOG_WEEK?log_sizeW:log_sizeM); + } - bfill.emit_p(PSTR("{\"deleted\":$L}"), log_size); handle_return(HTML_OK); } diff --git a/sensors.cpp b/sensors.cpp index f52e83eb1..dcfced650 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -37,11 +37,9 @@ const char* sensor_unitNames[] { "", "%", "°C", "°F", "V", // 0 1 2 3 4 }; -byte logFileSwitch = 0; //0=use smaler File, 1=LOG1, 2=LOG2 -ulong logFileSize1 = 0; -ulong logFileSize2 = 0; +byte logFileSwitch[3] = {0,0,0}; //0=use smaller File, 1=LOG1, 2=LOG2 - uint16_t CRC16 (byte buf[], int len) { +uint16_t CRC16 (byte buf[], int len) { uint16_t crc = 0xFFFF; for (int pos = 0; pos < len; pos++) { @@ -151,7 +149,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint * */ void sensor_load() { - DEBUG_PRINTLN(F("sensor_load")); + //DEBUG_PRINTLN(F("sensor_load")); sensors = NULL; if (!file_exists(SENSOR_FILENAME)) return; @@ -179,7 +177,7 @@ void sensor_load() { * */ void sensor_save() { - DEBUG_PRINTLN(F("sensor_save")); + //DEBUG_PRINTLN(F("sensor_save")); if (file_exists(SENSOR_FILENAME)) remove_file(SENSOR_FILENAME); @@ -193,7 +191,7 @@ void sensor_save() { } uint sensor_count() { - DEBUG_PRINTLN(F("sensor_count")); + //DEBUG_PRINTLN(F("sensor_count")); Sensor_t *sensor = sensors; uint count = 0; while (sensor) { @@ -204,7 +202,7 @@ uint sensor_count() { } Sensor_t *sensor_by_nr(uint nr) { - DEBUG_PRINTLN(F("sensor_by_nr")); + //DEBUG_PRINTLN(F("sensor_by_nr")); Sensor_t *sensor = sensors; while (sensor) { if (sensor->nr == nr) @@ -215,7 +213,7 @@ Sensor_t *sensor_by_nr(uint nr) { } Sensor_t *sensor_by_idx(uint idx) { - DEBUG_PRINTLN(F("sensor_by_idx")); + //DEBUG_PRINTLN(F("sensor_by_idx")); Sensor_t *sensor = sensors; uint count = 0; while (sensor) { @@ -227,39 +225,74 @@ Sensor_t *sensor_by_idx(uint idx) { return NULL; } -void checkLogSwitch() { - if (logFileSwitch == 0) { // Check file size, use smallest - logFileSize1 = file_size(SENSORLOG_FILENAME1); - logFileSize2 = file_size(SENSORLOG_FILENAME2); - if (logFileSize1 < logFileSize2) - logFileSwitch = 1; - else - logFileSwitch = 2; - } +// LOGGING METHODS: + +/** + * @brief getlogfile name + * + * @param log + * @return const char* + */ +const char *getlogfile(uint8_t log) { + bool sw = logFileSwitch[log]; + switch (log) { + case 0: return sw?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2; + case 1: return sw?SENSORLOG_FILENAME_WEEK1:SENSORLOG_FILENAME_WEEK2; + case 2: return sw?SENSORLOG_FILENAME_MONTH1:SENSORLOG_FILENAME_MONTH2; + } + return ""; +} + +/** + * @brief getlogfile name2 (opposite file) + * + * @param log + * @return const char* + */ +const char *getlogfile2(uint8_t log) { + bool sw = logFileSwitch[log]; + switch (log) { + case 0: return sw?SENSORLOG_FILENAME2:SENSORLOG_FILENAME1; + case 1: return sw?SENSORLOG_FILENAME_WEEK2:SENSORLOG_FILENAME_WEEK1; + case 2: return sw?SENSORLOG_FILENAME_MONTH2:SENSORLOG_FILENAME_MONTH1; + } + return ""; +} + + +void checkLogSwitch(uint8_t log) { + if (logFileSwitch[log] == 0) { // Check file size, use smallest + ulong logFileSize1 = file_size(getlogfile(log)); + ulong logFileSize2 = file_size(getlogfile2(log)); + if (logFileSize1 < logFileSize2) + logFileSwitch[log] = 1; + else + logFileSwitch[log] = 2; + } } -void checkLogSwitchAfterWrite() { - ulong size = file_size(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2); +void checkLogSwitchAfterWrite(uint8_t log) { + ulong size = file_size(getlogfile(log)); if ((size / SENSORLOG_STORE_SIZE) >= MAX_LOG_SIZE) { // switch logs if max reached - if (logFileSwitch == 1) - logFileSwitch = 2; + if (logFileSwitch[log] == 1) + logFileSwitch[log] = 2; else - logFileSwitch = 1; - remove_file(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2); + logFileSwitch[log] = 1; + remove_file(getlogfile(log)); } - logFileSize1 = file_size(SENSORLOG_FILENAME1); - logFileSize2 = file_size(SENSORLOG_FILENAME2); } -bool sensorlog_add(SensorLog_t *sensorlog) { +bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog) { #if defined(ESP8266) if (checkDiskFree()) { #endif DEBUG_PRINT(F("sensorlog_add ")); - checkLogSwitch(); - file_append_block(logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2, sensorlog, SENSORLOG_STORE_SIZE); - checkLogSwitchAfterWrite(); - DEBUG_PRINT(sensorlog_filesize()); + DEBUG_PRINT(log); + checkLogSwitch(log); + file_append_block(getlogfile(log), sensorlog, SENSORLOG_STORE_SIZE); + checkLogSwitchAfterWrite(log); + DEBUG_PRINT(F("=")); + DEBUG_PRINT(sensorlog_filesize(log)); return true; #if defined(ESP8266) } @@ -267,14 +300,14 @@ bool sensorlog_add(SensorLog_t *sensorlog) { #endif } -bool sensorlog_add(Sensor_t *sensor, ulong time) { +bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { SensorLog_t sensorlog; sensorlog.nr = sensor->nr; sensorlog.time = time; sensorlog.native_data = sensor->last_native_data; sensorlog.data = sensor->last_data; - if (!sensorlog_add(&sensorlog)) { + if (!sensorlog_add(log, &sensorlog)) { sensor->flags.log = 0; return false; } @@ -283,43 +316,60 @@ bool sensorlog_add(Sensor_t *sensor, ulong time) { return false; } -ulong sensorlog_filesize() { - DEBUG_PRINT(F("sensorlog_filesize ")); - checkLogSwitch(); - ulong size = logFileSize1+logFileSize2; - DEBUG_PRINTLN(size); +ulong sensorlog_filesize(uint8_t log) { + //DEBUG_PRINT(F("sensorlog_filesize ")); + checkLogSwitch(log); + ulong size = file_size(getlogfile(log))+file_size(getlogfile2(log)); + //DEBUG_PRINTLN(size); return size; } -ulong sensorlog_size() { - DEBUG_PRINT(F("sensorlog_size ")); - ulong size = sensorlog_filesize() / SENSORLOG_STORE_SIZE; - DEBUG_PRINTLN(size); +ulong sensorlog_size(uint8_t log) { + //DEBUG_PRINT(F("sensorlog_size ")); + ulong size = sensorlog_filesize(log) / SENSORLOG_STORE_SIZE; + //DEBUG_PRINTLN(size); return size; } void sensorlog_clear_all() { - DEBUG_PRINTLN(F("sensorlog_clear_all")); - remove_file(SENSORLOG_FILENAME1); - remove_file(SENSORLOG_FILENAME2); - logFileSwitch = 1; - logFileSize1 = 0; - logFileSize2 = 0; + sensorlog_clear(true, true, true); } -SensorLog_t *sensorlog_load(ulong idx) { +void sensorlog_clear(bool std, bool week, bool month) { + DEBUG_PRINTLN(F("sensorlog_clear ")); + DEBUG_PRINT(std); + DEBUG_PRINT(week); + DEBUG_PRINT(month); + if (std) { + remove_file(SENSORLOG_FILENAME1); + remove_file(SENSORLOG_FILENAME2); + logFileSwitch[LOG_STD] = 1; + } + if (week) { + remove_file(SENSORLOG_FILENAME_WEEK1); + remove_file(SENSORLOG_FILENAME_WEEK2); + logFileSwitch[LOG_WEEK] = 1; + } + if (month) { + remove_file(SENSORLOG_FILENAME_MONTH1); + remove_file(SENSORLOG_FILENAME_MONTH2); + logFileSwitch[LOG_MONTH] = 1; + } +} + +SensorLog_t *sensorlog_load(uint8_t log, ulong idx) { SensorLog_t *sensorlog = new SensorLog_t; - return sensorlog_load(idx, sensorlog); + return sensorlog_load(log, idx, sensorlog); } -SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog) { - DEBUG_PRINTLN(F("sensorlog_load")); +SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t* sensorlog) { + //DEBUG_PRINTLN(F("sensorlog_load")); //Map lower idx to the other log file - checkLogSwitch(); - const char *flast = logFileSwitch==1?SENSORLOG_FILENAME2:SENSORLOG_FILENAME1; - const char *fcur = logFileSwitch==1?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2; - ulong size = (logFileSwitch==1?logFileSize2:logFileSize1) / SENSORLOG_STORE_SIZE; + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + ulong size = (logFileSwitch[log]==1?file_size(getlogfile2(log)):file_size(getlogfile(log))) / SENSORLOG_STORE_SIZE; const char *f; if (idx >= size) { idx -= size; @@ -332,6 +382,196 @@ SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog) { return sensorlog; } +int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t* sensorlog) { + //DEBUG_PRINTLN(F("sensorlog_load")); + + //Map lower idx to the other log file + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + bool sw = logFileSwitch[log]==1; + ulong size = file_size(sw?getlogfile2(log):getlogfile(log)) / SENSORLOG_STORE_SIZE; + const char *f; + if (idx >= size) { + idx -= size; + f = fcur; + size = file_size(sw?getlogfile(log):getlogfile2(log)) / SENSORLOG_STORE_SIZE; + } else { + f = flast; + } + + file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, count * SENSORLOG_STORE_SIZE); + if (idx+count > size) + count = size-idx; + return count; +} + +ulong findLogPosition(uint8_t log, ulong after) { + ulong log_size = sensorlog_size(log); + ulong a = 0; + ulong b = (log_size-1) / 2; + ulong lastIdx = 0; + SensorLog_t sensorlog; + while (true) { + ulong idx = (b-a)/2+a; + sensorlog_load(log, idx, &sensorlog); + if (sensorlog.time < after) { + a = idx; + } else if (sensorlog.time > after) { + b = idx; + } + if (a >= b || idx == lastIdx) break; + lastIdx = idx; + } + return lastIdx; +} + +// 1/4 of a day: 6*60*60 +#define BLOCKSIZE 64 +#define CALCRANGE_WEEK 21600 +#define CALCRANGE_MONTH 172800 +static ulong next_week_calc = 0; +static ulong next_month_calc = 0; + +/** +Calculate week+month Data +We store only the average value of 6 hours utc +**/ +void calc_sensorlogs() +{ + if (!sensors || timeStatus() != timeSet) + return; + + time_t time = os.now_tz(); + time_t last_day = time; + ulong log_size = sensorlog_size(LOG_STD); + if (log_size == 0) + return; + + if (time >= next_week_calc) { + DEBUG_PRINTLN(F("calc_sensorlogs WEEK start")); + SensorLog_t *sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); + ulong size = sensorlog_size(LOG_WEEK); + if (size == 0) { + sensorlog_load(LOG_STD, 0, sensorlog); + last_day = sensorlog->time; + } + else + { + sensorlog_load(LOG_WEEK, size - 1, sensorlog); // last record + last_day = sensorlog->time + CALCRANGE_WEEK; // Skip last Range + } + time_t fromdate = (last_day / CALCRANGE_WEEK) * CALCRANGE_WEEK; + time_t todate = fromdate + CALCRANGE_WEEK; + + // 4 blocks per day + + while (todate < time) { + ulong startidx = findLogPosition(LOG_STD, fromdate); + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->flags.enable && sensor->flags.log) { + ulong idx = startidx; + double data = 0; + ulong n = 0; + bool done = false; + while (!done) { + int n = sensorlog_load2(LOG_STD, idx, BLOCKSIZE, sensorlog); + if (n <= 0) break; + for (int i = 0; i < n; i++) { + idx++; + if (sensorlog[i].time >= todate) { + done = true; + break; + } + if (sensorlog[i].nr == sensor->nr) { + data += sensorlog[i].data; + n++; + } + } + } + if (n > 0) + { + sensorlog.nr = sensor->nr; + sensorlog.time = fromdate; + sensorlog.data = data / (double)n; + sensorlog.native_data = 0; + sensorlog_add(LOG_WEEK, &sensorlog); + } + } + sensor = sensor->next; + } + fromdate += CALCRANGE_WEEK; + todate += CALCRANGE_WEEK; + } + next_week_calc = todate; + DEBUG_PRINTLN(F("calc_sensorlogs WEEK end")); + } + + if (time >= next_month_calc) { + SensorLog_t sensorlog; + log_size = sensorlog_size(LOG_WEEK); + if (log_size <= 0) + return; + + DEBUG_PRINTLN(F("calc_sensorlogs MONTH start")); + ulong size = sensorlog_size(LOG_MONTH); + if (size == 0) + { + sensorlog_load(LOG_WEEK, 0, &sensorlog); + last_day = sensorlog.time; + } + else + { + sensorlog_load(LOG_MONTH, size - 1, &sensorlog); // last record + last_day = sensorlog.time + CALCRANGE_MONTH; // Skip last Range + } + time_t fromdate = (last_day / CALCRANGE_MONTH) * CALCRANGE_MONTH; + time_t todate = fromdate + CALCRANGE_MONTH; + // 4 blocks per day + + while (todate < time) + { + ulong startidx = findLogPosition(LOG_WEEK, fromdate); + Sensor_t *sensor = sensors; + while (sensor) + { + if (sensor->flags.enable && sensor->flags.log) + { + ulong idx = startidx; + double data = 0; + ulong n = 0; + while (idx < log_size) + { + sensorlog_load(LOG_WEEK, idx, &sensorlog); + if (sensorlog.time >= todate) + break; + if (sensorlog.nr == sensor->nr) + { + data += sensorlog.data; + n++; + } + idx++; + } + if (n > 0) + { + sensorlog.nr = sensor->nr; + sensorlog.time = fromdate; + sensorlog.data = data / (double)n; + sensorlog.native_data = 0; + sensorlog_add(LOG_MONTH, &sensorlog); + } + } + sensor = sensor->next; + } + fromdate += CALCRANGE_MONTH; + todate += CALCRANGE_MONTH; + } + next_month_calc = todate; + DEBUG_PRINTLN(F("calc_sensorlogs MONTH end")); + } +} + void read_all_sensors() { DEBUG_PRINTLN(F("read_all_sensors")); if (!sensors || os.status.network_fails>0 || os.iopts[IOPT_REMOTE_EXT_MODE]) return; @@ -342,12 +582,14 @@ void read_all_sensors() { while (sensor) { if (time >= sensor->last_read + sensor->read_interval) { if (read_sensor(sensor) == HTTP_RQT_SUCCESS) { - sensorlog_add(sensor, time); + sensorlog_add(LOG_STD, sensor, time); } } sensor = sensor->next; } sensor_update_groups(); + + calc_sensorlogs(); } #if defined(ESP8266) @@ -457,7 +699,7 @@ bool extract(char *s, char *buf, int maxlen) { return false; strncpy(buf, s, l); buf[l] = 0; - DEBUG_PRINTLN(buf); + //DEBUG_PRINTLN(buf); return true; } @@ -694,15 +936,16 @@ int read_sensor(Sensor_t *sensor) { if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - sensor->last_read = os.now_tz(); - DEBUG_PRINT(F("Reading sensor ")); DEBUG_PRINTLN(sensor->name); + ulong time = os.now_tz(); + switch(sensor->type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: case SENSOR_SMT100_MODBUS_RTU_TEMP: + sensor->last_read = time; return read_sensor_ip(sensor); #if defined(ESP8266) @@ -715,21 +958,16 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_VH400: case SENSOR_THERM200: case SENSOR_AQUAPLUMB: + sensor->last_read = time; return read_sensor_adc(sensor); #endif //case SENSOR_OSPI_ANALOG_INPUTS: // return read_sensor_ospi(sensor); case SENSOR_REMOTE: + sensor->last_read = time; return read_sensor_http(sensor); - //Return true for logging: - case SENSOR_GROUP_MIN: - case SENSOR_GROUP_MAX: - case SENSOR_GROUP_AVG: - case SENSOR_GROUP_SUM: - return HTTP_RQT_SUCCESS; - default: return HTTP_RQT_NOT_RECEIVED; } } @@ -738,50 +976,60 @@ int read_sensor(Sensor_t *sensor) { * @brief Update group values * */ -void sensor_update_groups() { - Sensor_t *sensor = sensors; +void sensor_update_groups() +{ + Sensor_t *sensor = sensors; - while (sensor) { - switch(sensor->type) { - case SENSOR_GROUP_MIN: - case SENSOR_GROUP_MAX: - case SENSOR_GROUP_AVG: - case SENSOR_GROUP_SUM: { - uint nr = sensor->nr; - Sensor_t *group = sensors; - double value = 0; - int n = 0; - while (group) { - if (group->nr != nr && group->group == nr && group->flags.enable && group->flags.data_ok) { - switch(sensor->type) { - case SENSOR_GROUP_MIN: - if (n++ == 0) value = group->last_data; - else if (group->last_data < value) value = group->last_data; - break; - case SENSOR_GROUP_MAX: - if (n++ == 0) value = group->last_data; - else if (group->last_data > value) value = group->last_data; - break; - case SENSOR_GROUP_AVG: - case SENSOR_GROUP_SUM: - n++; - value += group->last_data; - break; + ulong time = os.now_tz(); + + while (sensor) + { + if (time >= sensor->last_read + sensor->read_interval) { + switch (sensor->type) { + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: { + uint nr = sensor->nr; + Sensor_t *group = sensors; + double value = 0; + int n = 0; + while (group) { + if (group->nr != nr && group->group == nr && group->flags.enable && group->flags.data_ok) { + switch (sensor->type) { + case SENSOR_GROUP_MIN: + if (n++ == 0) + value = group->last_data; + else if (group->last_data < value) + value = group->last_data; + break; + case SENSOR_GROUP_MAX: + if (n++ == 0) + value = group->last_data; + else if (group->last_data > value) + value = group->last_data; + break; + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + n++; + value += group->last_data; + break; + } } + group = group->next; } - group = group->next; - } - if (sensor->type == SENSOR_GROUP_AVG && n>0) { - value = value / n; + if (sensor->type == SENSOR_GROUP_AVG && n > 0) { + value = value / (double)n; + } + sensor->last_data = value; + sensor->last_native_data = 0; + sensor->last_read = time; + sensor->flags.data_ok = n > 0; + sensorlog_add(LOG_STD, sensor, time); + break; } - sensor->last_data = value; - sensor->last_native_data = 0; - sensor->last_read = os.now_tz(); - sensor->flags.data_ok = n>0; - break; } } - sensor = sensor->next; } } diff --git a/sensors.h b/sensors.h index 6e3e9ed66..e3987c9f1 100644 --- a/sensors.h +++ b/sensors.h @@ -43,8 +43,15 @@ #define SENSORLOG_FILENAME1 "sensorlog.dat" // analog sensor log filename #define SENSORLOG_FILENAME2 "sensorlog2.dat" // analog sensor log filename2 +#define SENSORLOG_FILENAME_WEEK1 "sensorlogW1.dat" // analog sensor log filename for week average +#define SENSORLOG_FILENAME_WEEK2 "sensorlogW2.dat" // analog sensor log filename2 for week average +#define SENSORLOG_FILENAME_MONTH1 "sensorlogM1.dat" // analog sensor log filename for month average +#define SENSORLOG_FILENAME_MONTH2 "sensorlogM2.dat" // analog sensor log filename2 for month average + //MaxLogSize #define MAX_LOG_SIZE 8000 +#define MAX_LOG_SIZE_WEEK 2000 +#define MAX_LOG_SIZE_MONTH 1000 //Sensor types: #define SENSOR_NONE 0 //None or deleted sensor @@ -150,7 +157,6 @@ typedef struct ProgSensorAdjust { //Unitnames extern const char* sensor_unitNames[]; -extern byte logFileSwitch; //0=use smaler File, 1=LOG1, 2=LOG2 const char* getSensorUnit(Sensor_t *sensor); byte getSensorUnitId(int type); @@ -179,13 +185,19 @@ Sensor_t *sensor_by_idx(uint idx); int read_sensor(Sensor_t *sensor); //sensor value goes to last_native_data/last_data //Sensorlog API functions: -bool sensorlog_add(SensorLog_t *sensorlog); -bool sensorlog_add(Sensor_t *sensor, ulong time); +#define LOG_STD 0 +#define LOG_WEEK 1 +#define LOG_MONTH 2 +bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog); +bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time); void sensorlog_clear_all(); -SensorLog_t *sensorlog_load(ulong pos); -SensorLog_t *sensorlog_load(ulong idx, SensorLog_t* sensorlog); -ulong sensorlog_filesize(); -ulong sensorlog_size(); +void sensorlog_clear(bool std, bool week, bool month); +SensorLog_t *sensorlog_load(uint8_t log, ulong pos); +SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t* sensorlog); +int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t* sensorlog); +ulong sensorlog_filesize(uint8_t log); +ulong sensorlog_size(uint8_t log); +ulong findLogPosition(uint8_t log, ulong after); //Set Sensor Address for SMT100: int set_sensor_address(Sensor_t *sensor, byte new_address); From 7d2766faa0d80d1901b9bf3d6b4a576602acbaa7 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 6 Apr 2023 12:39:12 +0200 Subject: [PATCH 055/281] 1. sensor configuration with "data_ok" flag 2. Faster week+month stats calculation --- opensprinkler_server.cpp | 3 +- sensors.cpp | 63 +++++++++++++++++++++++----------------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index f6c6e82ff..a48ce8c38 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2205,7 +2205,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { if (first) first = false; else bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"last\":$L}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L}"), sensor->nr, sensor->type, sensor->group, @@ -2221,6 +2221,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { sensor->flags.enable, sensor->flags.log, sensor->flags.show, + sensor->flags.data_ok, sensor->last_read); // if available ether buffer is getting small // send out a packet diff --git a/sensors.cpp b/sensors.cpp index dcfced650..3226a8e95 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -448,9 +448,11 @@ void calc_sensorlogs() if (log_size == 0) return; + SensorLog_t *sensorlog = NULL; + if (time >= next_week_calc) { DEBUG_PRINTLN(F("calc_sensorlogs WEEK start")); - SensorLog_t *sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); + sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); ulong size = sensorlog_size(LOG_WEEK); if (size == 0) { sensorlog_load(LOG_STD, 0, sensorlog); @@ -492,11 +494,11 @@ void calc_sensorlogs() } if (n > 0) { - sensorlog.nr = sensor->nr; - sensorlog.time = fromdate; - sensorlog.data = data / (double)n; - sensorlog.native_data = 0; - sensorlog_add(LOG_WEEK, &sensorlog); + sensorlog->nr = sensor->nr; + sensorlog->time = fromdate; + sensorlog->data = data / (double)n; + sensorlog->native_data = 0; + sensorlog_add(LOG_WEEK, sensorlog); } } sensor = sensor->next; @@ -509,7 +511,10 @@ void calc_sensorlogs() } if (time >= next_month_calc) { - SensorLog_t sensorlog; + + if (!sensorlog) + sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); + log_size = sensorlog_size(LOG_WEEK); if (log_size <= 0) return; @@ -518,13 +523,13 @@ void calc_sensorlogs() ulong size = sensorlog_size(LOG_MONTH); if (size == 0) { - sensorlog_load(LOG_WEEK, 0, &sensorlog); - last_day = sensorlog.time; + sensorlog_load(LOG_WEEK, 0, sensorlog); + last_day = sensorlog->time; } else { - sensorlog_load(LOG_MONTH, size - 1, &sensorlog); // last record - last_day = sensorlog.time + CALCRANGE_MONTH; // Skip last Range + sensorlog_load(LOG_MONTH, size - 1, sensorlog); // last record + last_day = sensorlog->time + CALCRANGE_MONTH; // Skip last Range } time_t fromdate = (last_day / CALCRANGE_MONTH) * CALCRANGE_MONTH; time_t todate = fromdate + CALCRANGE_MONTH; @@ -541,25 +546,29 @@ void calc_sensorlogs() ulong idx = startidx; double data = 0; ulong n = 0; - while (idx < log_size) - { - sensorlog_load(LOG_WEEK, idx, &sensorlog); - if (sensorlog.time >= todate) - break; - if (sensorlog.nr == sensor->nr) - { - data += sensorlog.data; - n++; + bool done = false; + while (!done) { + int n = sensorlog_load2(LOG_WEEK, idx, BLOCKSIZE, sensorlog); + if (n <= 0) break; + for (int i = 0; i < n; i++) { + idx++; + if (sensorlog[i].time >= todate) { + done = true; + break; + } + if (sensorlog[i].nr == sensor->nr) { + data += sensorlog[i].data; + n++; + } } - idx++; } if (n > 0) { - sensorlog.nr = sensor->nr; - sensorlog.time = fromdate; - sensorlog.data = data / (double)n; - sensorlog.native_data = 0; - sensorlog_add(LOG_MONTH, &sensorlog); + sensorlog->nr = sensor->nr; + sensorlog->time = fromdate; + sensorlog->data = data / (double)n; + sensorlog->native_data = 0; + sensorlog_add(LOG_MONTH, sensorlog); } } sensor = sensor->next; @@ -570,6 +579,8 @@ void calc_sensorlogs() next_month_calc = todate; DEBUG_PRINTLN(F("calc_sensorlogs MONTH end")); } + if (sensorlog) + free(sensorlog); } void read_all_sensors() { From b523a8cb8c9442a8e3dafb90c85f0409daa712af Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 12 Apr 2023 23:24:06 +0200 Subject: [PATCH 056/281] Fix weeklog + monthlog calculation --- opensprinkler_server.cpp | 2 +- sensors.cpp | 19 +++++++++---------- utils.cpp | 10 ++++++---- utils.h | 2 +- 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index a48ce8c38..0945aab88 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2360,7 +2360,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("lastHours")); ulong a = 0; - ulong b = (log_size-1) / 2; + ulong b = log_size-1; ulong lastIdx = 0; while (true) { ulong idx = (b-a)/2+a; diff --git a/sensors.cpp b/sensors.cpp index 3226a8e95..2e879f9d8 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -400,16 +400,14 @@ int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t* sensorlog) { f = flast; } - file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, count * SENSORLOG_STORE_SIZE); - if (idx+count > size) - count = size-idx; - return count; + ulong result = file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, count * SENSORLOG_STORE_SIZE); + return result / SENSORLOG_STORE_SIZE; } ulong findLogPosition(uint8_t log, ulong after) { ulong log_size = sensorlog_size(log); ulong a = 0; - ulong b = (log_size-1) / 2; + ulong b = log_size-1; ulong lastIdx = 0; SensorLog_t sensorlog; while (true) { @@ -442,14 +440,15 @@ void calc_sensorlogs() if (!sensors || timeStatus() != timeSet) return; - time_t time = os.now_tz(); - time_t last_day = time; ulong log_size = sensorlog_size(LOG_STD); if (log_size == 0) return; SensorLog_t *sensorlog = NULL; + time_t time = os.now_tz(); + time_t last_day = time; + if (time >= next_week_calc) { DEBUG_PRINTLN(F("calc_sensorlogs WEEK start")); sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); @@ -923,17 +922,17 @@ int read_sensor_ip(Sensor_t *sensor) { { case SENSOR_SMT100_MODBUS_RTU_MOIS: //Equation: soil moisture [vol.%]= (16Bit_soil_moisture_value / 100) sensor->last_data = ((double)sensor->last_native_data / 100); - sensor->flags.data_ok = true; + sensor->flags.data_ok = sensor->last_native_data < 10000; DEBUG_PRINT(F(" soil moisture %: ")); break; case SENSOR_SMT100_MODBUS_RTU_TEMP: //Equation: temperature [°C]= (16Bit_temperature_value / 100)-100 sensor->last_data = ((double)sensor->last_native_data / 100) - 100; - sensor->flags.data_ok = true; + sensor->flags.data_ok = sensor->last_native_data > 7000; DEBUG_PRINT(F(" temperature °C: ")); break; } DEBUG_PRINTLN(sensor->last_data); - return HTTP_RQT_SUCCESS; + return sensor->flags.data_ok?HTTP_RQT_SUCCESS:HTTP_RQT_NOT_RECEIVED; } return HTTP_RQT_NOT_RECEIVED; diff --git a/utils.cpp b/utils.cpp index f670f54a2..aae48b79d 100644 --- a/utils.cpp +++ b/utils.cpp @@ -251,14 +251,15 @@ ulong file_size(const char *fn) { } // file functions -void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { +ulong file_read_block(const char *fn, void *dst, ulong pos, ulong len) { + ulong result = 0; #if defined(ESP8266) // do not use File.readBytes or readBytesUntil because it's very slow File f = LittleFS.open(fn, "r"); if(f) { f.seek(pos, SeekSet); - f.read((byte*)dst, len); + result = f.read((byte*)dst, len); f.close(); } @@ -268,7 +269,7 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { SdFile file; if(file.open(fn, O_READ)) { file.seekSet(pos); - file.read(dst, len); + result = file.read(dst, len); file.close(); } @@ -277,11 +278,12 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { FILE *fp = fopen(get_filename_fullpath(fn), "rb"); if(fp) { fseek(fp, pos, SEEK_SET); - fread(dst, 1, len, fp); + result = fread(dst, 1, len, fp); fclose(fp); } #endif + return result; } void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { diff --git a/utils.h b/utils.h index 939d89b5b..1a51cb3be 100644 --- a/utils.h +++ b/utils.h @@ -42,7 +42,7 @@ bool file_exists(const char *fname); ulong file_size(const char *fn); -void file_read_block (const char *fname, void *dst, ulong pos, ulong len); +ulong 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_append_block(const char *fname, const void *src, ulong len); void file_copy_block (const char *fname, ulong from, ulong to, ulong len, void *tmp=0); From 5fdf1b67d5df5bbff8c6d5c333349901c97ead2c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 13 Apr 2023 23:33:40 +0200 Subject: [PATCH 057/281] Fix Week/Month calculation, if some days are missing --- sensors.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 2e879f9d8..ff5df6d01 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -292,7 +292,7 @@ bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog) { file_append_block(getlogfile(log), sensorlog, SENSORLOG_STORE_SIZE); checkLogSwitchAfterWrite(log); DEBUG_PRINT(F("=")); - DEBUG_PRINT(sensorlog_filesize(log)); + DEBUG_PRINTLN(sensorlog_filesize(log)); return true; #if defined(ESP8266) } @@ -418,10 +418,10 @@ ulong findLogPosition(uint8_t log, ulong after) { } else if (sensorlog.time > after) { b = idx; } - if (a >= b || idx == lastIdx) break; + if (a >= b || idx == lastIdx) return idx; lastIdx = idx; } - return lastIdx; + return 0; } // 1/4 of a day: 6*60*60 @@ -477,9 +477,9 @@ void calc_sensorlogs() ulong n = 0; bool done = false; while (!done) { - int n = sensorlog_load2(LOG_STD, idx, BLOCKSIZE, sensorlog); - if (n <= 0) break; - for (int i = 0; i < n; i++) { + int sn = sensorlog_load2(LOG_STD, idx, BLOCKSIZE, sensorlog); + if (sn <= 0) break; + for (int i = 0; i < sn; i++) { idx++; if (sensorlog[i].time >= todate) { done = true; @@ -547,9 +547,9 @@ void calc_sensorlogs() ulong n = 0; bool done = false; while (!done) { - int n = sensorlog_load2(LOG_WEEK, idx, BLOCKSIZE, sensorlog); - if (n <= 0) break; - for (int i = 0; i < n; i++) { + int sn = sensorlog_load2(LOG_WEEK, idx, BLOCKSIZE, sensorlog); + if (sn <= 0) break; + for (int i = 0; i < sn; i++) { idx++; if (sensorlog[i].time >= todate) { done = true; From c6977714619c331f2b0e18e5521d924ec4c3ef46 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 14 Apr 2023 09:11:53 +0200 Subject: [PATCH 058/281] Fix week/month calc --- sensors.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index ff5df6d01..eba8680d3 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -369,7 +369,7 @@ SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t* sensorlog) { checkLogSwitch(log); const char *flast = getlogfile2(log); const char *fcur = getlogfile(log); - ulong size = (logFileSwitch[log]==1?file_size(getlogfile2(log)):file_size(getlogfile(log))) / SENSORLOG_STORE_SIZE; + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; const char *f; if (idx >= size) { idx -= size; @@ -389,19 +389,22 @@ int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t* sensorlog) { checkLogSwitch(log); const char *flast = getlogfile2(log); const char *fcur = getlogfile(log); - bool sw = logFileSwitch[log]==1; - ulong size = file_size(sw?getlogfile2(log):getlogfile(log)) / SENSORLOG_STORE_SIZE; + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; const char *f; if (idx >= size) { idx -= size; f = fcur; - size = file_size(sw?getlogfile(log):getlogfile2(log)) / SENSORLOG_STORE_SIZE; + size = file_size(f) / SENSORLOG_STORE_SIZE; } else { f = flast; } - ulong result = file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, count * SENSORLOG_STORE_SIZE); - return result / SENSORLOG_STORE_SIZE; + if (idx+count > size) + count = size-idx; + if (count <= 0) + return 0; + file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, count * SENSORLOG_STORE_SIZE); + return count; } ulong findLogPosition(uint8_t log, ulong after) { From 605fec15788448a256f36454ee8312df4669a10f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 14 Apr 2023 16:08:35 +0200 Subject: [PATCH 059/281] Fix large logs downloading --- opensprinkler_server.cpp | 45 +++++++++------------------------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 0945aab88..2bcb047d0 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2129,11 +2129,7 @@ void server_sensor_get(OTF_PARAMS_DEF) { getSensorUnit(sensor), getSensorUnitId(sensor), sensor->last_read); - // if available ether buffer is getting small - // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + send_packet(OTF_PARAMS); } bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); @@ -2187,11 +2183,7 @@ void server_sensor_readnow(OTF_PARAMS_DEF) { getSensorUnit(sensor), getSensorUnitId(sensor)); - // if available ether buffer is getting small - // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + send_packet(OTF_PARAMS); } bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); @@ -2223,11 +2215,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { sensor->flags.show, sensor->flags.data_ok, sensor->last_read); - // if available ether buffer is getting small - // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + send_packet(OTF_PARAMS); } } @@ -2433,12 +2421,12 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { } // if available ether buffer is getting small // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } - if (++count >= maxResults) + if (count++ >= maxResults) break; + send_packet(OTF_PARAMS); } + if (count >= maxResults) + break; } if (isjson) @@ -2636,11 +2624,7 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { if (count++ > 0) bfill.emit_p(PSTR(",")); progconfig_json(p, current); - // if available ether buffer is getting small - // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + send_packet(OTF_PARAMS); } bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); @@ -2722,12 +2706,7 @@ void server_sensor_types(OTF_PARAMS_DEF) { byte unitid = getSensorUnitId(sensor_types[i]); bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), sensor_types[i], sensor_names[i], sensor_unitNames[unitid], unitid); - - // if available ether buffer is getting small - // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + send_packet(OTF_PARAMS); } bfill.emit_p(PSTR("]}")); @@ -2851,11 +2830,7 @@ void server_sensorprog_types(OTF_PARAMS_DEF) { bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\"}"), prog_types[i], prog_names[i]); - // if available ether buffer is getting small - // send out a packet - if(available_ether_buffer() <= 0) { - send_packet(OTF_PARAMS); - } + send_packet(OTF_PARAMS); } bfill.emit_p(PSTR("]}")); From 78f01e70fa8de30e4fe8685fe96e88f19e0b3794 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 30 Apr 2023 01:06:24 +0200 Subject: [PATCH 060/281] Added weather data as a sensor for logging weather --- defines.h | 2 +- espconnect.cpp | 4 + opensprinkler_server.cpp | 16 +++- sensors.cpp | 174 ++++++++++++++++++++++++++++++++++++++- sensors.h | 24 ++++-- 5 files changed, 211 insertions(+), 9 deletions(-) diff --git a/defines.h b/defines.h index ca4b6a16a..1120e0f48 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 1 // Firmware minor version +#define OS_FW_MINOR 123 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/espconnect.cpp b/espconnect.cpp index 279f3b3dd..af32da632 100644 --- a/espconnect.cpp +++ b/espconnect.cpp @@ -22,6 +22,7 @@ #include "espconnect.h" String scan_network() { + WiFi.setOutputPower(20.5); WiFi.mode(WIFI_STA); WiFi.disconnect(); byte n = WiFi.scanNetworks(); @@ -51,6 +52,7 @@ String scan_network() { void start_network_ap(const char *ssid, const char *pass) { if(!ssid) return; + WiFi.setOutputPower(20.5); if(pass) WiFi.softAP(ssid, pass); else WiFi.softAP(ssid); WiFi.mode(WIFI_AP_STA); // start in AP_STA mode @@ -59,12 +61,14 @@ void start_network_ap(const char *ssid, const char *pass) { void start_network_sta_with_ap(const char *ssid, const char *pass, int32_t channel, const byte *bssid) { if(!ssid || !pass) return; + WiFi.setOutputPower(20.5); if(WiFi.getMode()!=WIFI_AP_STA) WiFi.mode(WIFI_AP_STA); WiFi.begin(ssid, pass, channel, bssid); } void start_network_sta(const char *ssid, const char *pass, int32_t channel, const byte *bssid) { if(!ssid || !pass) return; + WiFi.setOutputPower(20.5); if(WiFi.getMode()!=WIFI_STA) WiFi.mode(WIFI_STA); WiFi.begin(ssid, pass, channel, bssid); } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 2bcb047d0..d382e4360 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2645,7 +2645,14 @@ const int sensor_types[] = { SENSOR_AQUAPLUMB, #endif //SENSOR_OSPI_ANALOG_INPUTS, - SENSOR_REMOTE, + SENSOR_REMOTE, + SENSOR_WEATHER_TEMP_F, + SENSOR_WEATHER_TEMP_C, + SENSOR_WEATHER_HUM, + SENSOR_WEATHER_PRECIP_IN, + SENSOR_WEATHER_PRECIP_MM, + SENSOR_WEATHER_WIND_MPH, + SENSOR_WEATHER_WIND_KMH, SENSOR_GROUP_MIN, SENSOR_GROUP_MAX, SENSOR_GROUP_AVG, @@ -2669,6 +2676,13 @@ const char* sensor_names[] = { #endif //"OSPi analog input", "Remote sensor of an remote opensprinkler", + "Weather data - temperature (°F)", + "Weather data - temperature (°C)", + "Weather data - humidity (%)", + "Weather data - precip (inch)", + "Weather data - precip (mm)", + "Weather data - wind (mph)", + "Weather data - wind (kmh)", "Sensor group with min value", "Sensor group with max value", "Sensor group with avg value", diff --git a/sensors.cpp b/sensors.cpp index eba8680d3..fda071ccf 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -26,6 +26,9 @@ #include "OpenSprinkler.h" #include "opensprinkler_server.h" #include "sensors.h" +#include "weather.h" + +byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); //All sensors: static Sensor_t *sensors = NULL; @@ -34,11 +37,19 @@ static Sensor_t *sensors = NULL; static ProgSensorAdjust_t *progSensorAdjusts = NULL; const char* sensor_unitNames[] { - "", "%", "°C", "°F", "V", -// 0 1 2 3 4 + "", "%", "°C", "°F", "V", "%", "in", "mm", "mph", "kmh" +// 0 1 2 3 4 5 6 7 8 9 }; byte logFileSwitch[3] = {0,0,0}; //0=use smaller File, 1=LOG1, 2=LOG2 +//Weather +time_t last_weather_time = 0; +bool current_weather_ok = false; +double current_temp = 0.0; +double current_humidity = 0.0; +double current_precip = 0.0; +double current_wind = 0.0; + uint16_t CRC16 (byte buf[], int len) { uint16_t crc = 0xFFFF; @@ -981,6 +992,56 @@ int read_sensor(Sensor_t *sensor) { sensor->last_read = time; return read_sensor_http(sensor); + case SENSOR_WEATHER_TEMP_F: + case SENSOR_WEATHER_TEMP_C: + case SENSOR_WEATHER_HUM: + case SENSOR_WEATHER_PRECIP_IN: + case SENSOR_WEATHER_PRECIP_MM: + case SENSOR_WEATHER_WIND_MPH: + case SENSOR_WEATHER_WIND_KMH: + { + GetSensorWeather(); + if (current_weather_ok) { + sensor->last_read = time; + sensor->last_native_data = 0; + sensor->flags.data_ok = 1; + + switch(sensor->type) + { + case SENSOR_WEATHER_TEMP_F: { + sensor->last_data = current_temp; + break; + } + case SENSOR_WEATHER_TEMP_C: { + sensor->last_data = (current_temp - 32.0) / 1.8; + break; + } + case SENSOR_WEATHER_HUM: { + sensor->last_data = current_humidity; + break; + } + case SENSOR_WEATHER_PRECIP_IN: { + sensor->last_data = current_precip; + break; + } + case SENSOR_WEATHER_PRECIP_MM: { + sensor->last_data = current_precip * 25.4; + break; + } + case SENSOR_WEATHER_WIND_MPH: { + sensor->last_data = current_wind; + break; + } + case SENSOR_WEATHER_WIND_KMH: { + sensor->last_data = current_wind * 1.609344; + break; + } + } + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; + } + default: return HTTP_RQT_NOT_RECEIVED; } } @@ -1431,6 +1492,14 @@ byte getSensorUnitId(int type) { #endif case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; + case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; + case SENSOR_WEATHER_TEMP_C: return UNIT_DEGREE; + case SENSOR_WEATHER_HUM: return UNIT_HUM_PERCENT; + case SENSOR_WEATHER_PRECIP_IN: return UNIT_INCH; + case SENSOR_WEATHER_PRECIP_MM: return UNIT_MM; + case SENSOR_WEATHER_WIND_MPH: return UNIT_MPH; + case SENSOR_WEATHER_WIND_KMH: return UNIT_KMH; + default: return UNIT_NONE; } } @@ -1457,6 +1526,14 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; case SENSOR_REMOTE: return sensor->unitid; + case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; + case SENSOR_WEATHER_TEMP_C: return UNIT_DEGREE; + case SENSOR_WEATHER_HUM: return UNIT_HUM_PERCENT; + case SENSOR_WEATHER_PRECIP_IN: return UNIT_INCH; + case SENSOR_WEATHER_PRECIP_MM: return UNIT_MM; + case SENSOR_WEATHER_WIND_MPH: return UNIT_MPH; + case SENSOR_WEATHER_WIND_KMH: return UNIT_KMH; + case SENSOR_GROUP_MIN: case SENSOR_GROUP_MAX: case SENSOR_GROUP_AVG: @@ -1478,3 +1555,96 @@ byte getSensorUnitId(Sensor_t *sensor) { default: return UNIT_NONE; } } + +void GetSensorWeather() { +#if defined(ESP8266) + if (!useEth) + if (os.state!=OS_STATE_CONNECTED || WiFi.status()!=WL_CONNECTED) return; +#endif + time_t time = os.now_tz(); + if (last_weather_time == 0) + last_weather_time = time - 59*60; + + if (time < last_weather_time + 60*60) + return; + + // use temp buffer to construct get command + BufferFiller bf = tmp_buffer; + bf.emit_p(PSTR("weatherData?loc=$O"), SOPT_LOCATION); + + char *src=tmp_buffer+strlen(tmp_buffer); + char *dst=tmp_buffer+TMP_BUFFER_SIZE-12; + + char c; + // url encode. convert SPACE to %20 + // copy reversely from the end because we are potentially expanding + // the string size + while(src!=tmp_buffer) { + c = *src--; + if(c==' ') { + *dst-- = '0'; + *dst-- = '2'; + *dst-- = '%'; + } else { + *dst-- = c; + } + }; + *dst = *src; + + strcpy(ether_buffer, "GET /"); + strcat(ether_buffer, dst); + // because dst is part of tmp_buffer, + // must load weather url AFTER dst is copied to ether_buffer + + // load weather url to tmp_buffer + char *host = tmp_buffer; + os.sopt_load(SOPT_WEATHERURL, host); + + strcat(ether_buffer, " HTTP/1.0\r\nHOST: "); + strcat(ether_buffer, host); + strcat(ether_buffer, "\r\n\r\n"); + + DEBUG_PRINTLN(F("GetSensorWeather")); + DEBUG_PRINTLN(ether_buffer); + + int ret = os.send_http_request(host, ether_buffer, NULL); + if(ret == HTTP_RQT_SUCCESS) { + last_weather_time = time; + DEBUG_PRINTLN(ether_buffer); + + char buf[20]; + char *s = strstr(ether_buffer, "\"temp\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_temp = atof(buf); + } + s = strstr(ether_buffer, "\"humidity\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_humidity = atof(buf); + } + s = strstr(ether_buffer, "\"precip\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_precip = atof(buf); + } + s = strstr(ether_buffer, "\"wind\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_wind = atof(buf); + } + char tmp[10]; + DEBUG_PRINT("temp: "); + dtostrf(current_temp, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("humidity: "); + dtostrf(current_humidity, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("precip: "); + dtostrf(current_precip, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("wind: "); + dtostrf(current_wind, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + + current_weather_ok = true; + } else { + current_weather_ok = false; + } +} diff --git a/sensors.h b/sensors.h index e3987c9f1..07d4b59a0 100644 --- a/sensors.h +++ b/sensors.h @@ -72,6 +72,13 @@ #endif #define SENSOR_OSPI_ANALOG_INPUTS 50 //Old OSPi analog input #define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler +#define SENSOR_WEATHER_TEMP_F 101 //Weather service - temperature (Fahrenheit) +#define SENSOR_WEATHER_TEMP_C 102 //Weather service - temperature (Celcius) +#define SENSOR_WEATHER_HUM 103 //Weather service - humidity (%) +#define SENSOR_WEATHER_PRECIP_IN 105 //Weather service - precip (inch) +#define SENSOR_WEATHER_PRECIP_MM 106 //Weather service - precip (mm) +#define SENSOR_WEATHER_WIND_MPH 107 //Weather service - wind (mph) +#define SENSOR_WEATHER_WIND_KMH 108 //Weather service - wind (kmh) #define SENSOR_GROUP_MIN 1000 //Sensor group with min value #define SENSOR_GROUP_MAX 1001 //Sensor group with max value @@ -149,11 +156,16 @@ typedef struct ProgSensorAdjust { } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) -#define UNIT_NONE 0 -#define UNIT_PERCENT 1 -#define UNIT_DEGREE 2 -#define UNIT_FAHRENHEIT 3 -#define UNIT_VOLT 4 +#define UNIT_NONE 0 +#define UNIT_PERCENT 1 +#define UNIT_DEGREE 2 +#define UNIT_FAHRENHEIT 3 +#define UNIT_VOLT 4 +#define UNIT_HUM_PERCENT 5 +#define UNIT_INCH 6 +#define UNIT_MM 7 +#define UNIT_MPH 8 +#define UNIT_KMH 9 //Unitnames extern const char* sensor_unitNames[]; @@ -213,6 +225,8 @@ ProgSensorAdjust_t *prog_adjust_by_idx(uint idx); double calc_sensor_watering(uint prog); double calc_sensor_watering_by_nr(uint nr); +void GetSensorWeather(); + #if defined(ESP8266) ulong diskFree(); bool checkDiskFree(); //true: disk space Ok, false: Out of disk space From e22369b132b7ce0732780f431da83fc3bf0d81e9 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 7 May 2023 02:28:42 +0200 Subject: [PATCH 061/281] Sensor values published by MQTT and IFTTT --- defines.h | 2 +- sensors.cpp | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/defines.h b/defines.h index 1120e0f48..0b20f047d 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 123 // Firmware minor version +#define OS_FW_MINOR 125 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/sensors.cpp b/sensors.cpp index fda071ccf..b6ef0bfec 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -27,6 +27,7 @@ #include "opensprinkler_server.h" #include "sensors.h" #include "weather.h" +#include "mqtt.h" byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); @@ -596,18 +597,63 @@ void calc_sensorlogs() free(sensorlog); } +void sensor_remote_http_callback(char*) { +//unused +} + +void push_message(bool ok, Sensor_t *sensor) { + static char topic[TMP_BUFFER_SIZE]; + static char payload[TMP_BUFFER_SIZE]; + char* postval = tmp_buffer; + + if (os.mqtt.enabled()) { + strcpy_P(topic, PSTR("opensprinkler/analogsensor/")); + strncat(topic, sensor->name, sizeof(topic)-1); + sprintf_P(payload, PSTR("{\"nr\":%u,\"type\":%u,\"data_ok\":%u,\"time\":%u,\"value\":%d.%02d,\"unit\":\"%s\"}"), + sensor->nr, sensor->type, sensor->flags.data_ok, sensor->last_read, (int)sensor->last_data, (int)(sensor->last_data*100)%100, getSensorUnit(sensor)); + + os.mqtt.publish(topic, payload); + } + if (os.iopts[IOPT_IFTTT_ENABLE]) { + strcpy_P(postval, PSTR("{\"value1\":\"On site [")); + os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval)); + strcat_P(postval, PSTR("], ")); + + strcat_P(postval, PSTR("analogsensor ")); + sprintf_P(postval+strlen(postval), PSTR("nr: %u, type: %u, data_ok: %u, time: %u, value: %d.%02d, unit: %s"), + sensor->nr, sensor->type, sensor->flags.data_ok, sensor->last_read, (int)sensor->last_data, (int)(sensor->last_data*100)%100, getSensorUnit(sensor)); + strcat_P(postval, PSTR("\"}")); + + //char postBuffer[1500]; + BufferFiller bf = ether_buffer; + bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" + "Host: $S\r\n" + "Accept: */*\r\n" + "Content-Length: $D\r\n" + "Content-Type: application/json\r\n\r\n$S"), + SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); + + os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, sensor_remote_http_callback); + } +} + void read_all_sensors() { - DEBUG_PRINTLN(F("read_all_sensors")); + //DEBUG_PRINTLN(F("read_all_sensors")); if (!sensors || os.status.network_fails>0 || os.iopts[IOPT_REMOTE_EXT_MODE]) return; ulong time = os.now_tz(); + + if (time < os.powerup_lasttime+30) return; //wait 30s before first sensor read + Sensor_t *sensor = sensors; while (sensor) { if (time >= sensor->last_read + sensor->read_interval) { - if (read_sensor(sensor) == HTTP_RQT_SUCCESS) { + bool ok = read_sensor(sensor) == HTTP_RQT_SUCCESS; + if (ok) { sensorlog_add(LOG_STD, sensor, time); } + push_message(ok, sensor); } sensor = sensor->next; } @@ -628,6 +674,7 @@ int read_sensor_adc(Sensor_t *sensor) { int port = sensor->id < 4? 72 : 73; int id = sensor->id % 4; + sensor->flags.data_ok = false; ADS1115 adc(port); bool active = adc.begin(); @@ -1004,7 +1051,7 @@ int read_sensor(Sensor_t *sensor) { if (current_weather_ok) { sensor->last_read = time; sensor->last_native_data = 0; - sensor->flags.data_ok = 1; + sensor->flags.data_ok = true; switch(sensor->type) { From 028a1fb2756fd8f57432db1106ec6bd7288a3d8e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 7 May 2023 23:19:11 +0200 Subject: [PATCH 062/281] - added mqtt checks --- sensors.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index b6ef0bfec..ee656e294 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -601,7 +601,10 @@ void sensor_remote_http_callback(char*) { //unused } -void push_message(bool ok, Sensor_t *sensor) { +void push_message(Sensor_t *sensor) { + if (!sensor || !sensor->last_read) + return; + static char topic[TMP_BUFFER_SIZE]; static char payload[TMP_BUFFER_SIZE]; char* postval = tmp_buffer; @@ -649,11 +652,10 @@ void read_all_sensors() { while (sensor) { if (time >= sensor->last_read + sensor->read_interval) { - bool ok = read_sensor(sensor) == HTTP_RQT_SUCCESS; - if (ok) { + if (read_sensor(sensor) == HTTP_RQT_SUCCESS) { sensorlog_add(LOG_STD, sensor, time); } - push_message(ok, sensor); + push_message(sensor); } sensor = sensor->next; } From 7b8edf5aa236897902952c442793cd04c23d4ced Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 8 May 2023 23:16:20 +0200 Subject: [PATCH 063/281] V 2.3.0(126): Ping check only reboots, if we received a minimum of 1 ping-back. --- defines.h | 2 +- main.cpp | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/defines.h b/defines.h index 0b20f047d..16db9151d 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 125 // Firmware minor version +#define OS_FW_MINOR 126 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index d8166ce7c..0706b7cea 100644 --- a/main.cpp +++ b/main.cpp @@ -91,6 +91,7 @@ byte prev_flow_state = HIGH; float flow_last_gpm=0; uint32_t reboot_timer = 0; +uint32_t ping_ok = 0; void flow_poll() { #if defined(ESP8266) @@ -1977,6 +1978,12 @@ void check_network() { #endif boolean failed = response.TotalSentRequests > response.TotalReceivedResponses; + //Idee: If we never received a ping response, then the gateway is blocked. + // So only reboot if we failed 3 times and we never received any ping response. + ping_ok += response.TotalReceivedResponses; + if (!ping_ok) + return true; + if (failed) { if(os.status.network_fails<3) os.status.network_fails++; // clamp it to 6 From 46e8fe81989a66be18dbf585cd42b91f6f664a56 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 30 May 2023 23:35:38 +0200 Subject: [PATCH 064/281] changed 0-3.3V 100% sensor to "level %", fixed wifi lost after 1h --- defines.h | 2 +- main.cpp | 8 ++++---- platformio.ini | 2 +- sensors.cpp | 17 ++++++++++++++--- sensors.h | 1 + 5 files changed, 21 insertions(+), 9 deletions(-) diff --git a/defines.h b/defines.h index 16db9151d..e383208e5 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 126 // Firmware minor version +#define OS_FW_MINOR 127 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index 0706b7cea..7a8a0dc82 100644 --- a/main.cpp +++ b/main.cpp @@ -1008,10 +1008,10 @@ void do_loop() } } } - else if (os.iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED && os.get_wifi_mode()==WIFI_MODE_STA) { - netif* intf = eagle_lwip_getif(STATION_IF); - dhcp_renew(intf); - } + //else if (os.iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED && os.get_wifi_mode()==WIFI_MODE_STA) { + // netif* intf = eagle_lwip_getif(STATION_IF); + // dhcp_renew(intf); + //} dhcp_timeout = curr_time + DHCP_CHECKLEASE_INTERVAL; } #endif diff --git a/platformio.ini b/platformio.ini index 0c9a08a09..a136569a9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -25,7 +25,7 @@ lib_deps = https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 https://github.com/OpenSprinklerShop/OpenThings-Framework-Firmware-Library - https://github.com/Links2004/arduinoWebSockets/archive/refs/tags/2.3.5.zip + https://github.com/OpenSprinklerShop/arduinoWebSockets RobTillaart/ADS1X15 https://github.com/bluemurder/esp8266-ping ; ignore html2raw.cpp source file for firmware compilation (external helper program) diff --git a/sensors.cpp b/sensors.cpp index ee656e294..e6e717251 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -38,8 +38,19 @@ static Sensor_t *sensors = NULL; static ProgSensorAdjust_t *progSensorAdjusts = NULL; const char* sensor_unitNames[] { - "", "%", "°C", "°F", "V", "%", "in", "mm", "mph", "kmh" -// 0 1 2 3 4 5 6 7 8 9 + "", "%", "°C", "°F", "V", "%", "in", "mm", "mph", "kmh", "%" +// 0 1 2 3 4 5 6 7 8 9 10 +// 0=Nothing +// 1=Soil moisture +// 2=degree celsius temperature +// 3=degree fahrenheit temperature +// 4=Volt V +// 5=Humidity % +// 6=Rain inch +// 7=Rain mm +// 8=Wind mph +// 9=Wind kmh +// 10=Level % }; byte logFileSwitch[3] = {0,0,0}; //0=use smaller File, 1=LOG1, 2=LOG2 @@ -1562,7 +1573,7 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; #if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; - case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; + case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_LEVEL; case SENSOR_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_SMT50_TEMP: return UNIT_DEGREE; case SENSOR_SMT100_ANALOG_MOIS: return UNIT_PERCENT; diff --git a/sensors.h b/sensors.h index 07d4b59a0..9f8e61816 100644 --- a/sensors.h +++ b/sensors.h @@ -166,6 +166,7 @@ typedef struct ProgSensorAdjust { #define UNIT_MM 7 #define UNIT_MPH 8 #define UNIT_KMH 9 +#define UNIT_LEVEL 10 //Unitnames extern const char* sensor_unitNames[]; From 18caa046abab1e24de6bfe571086c51fb182b11e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 5 Jun 2023 00:02:43 +0200 Subject: [PATCH 065/281] Added fix from 2.2.0(2) --- OpenSprinkler.cpp | 5772 ++++++++++++++++++++-------------------- defines.h | 2 +- lib-patch/enc28j60.cpp | 871 ++++++ lib-patch/enc28j60.h | 189 ++ lib-patch/readme.txt | 0 main.cpp | 7 +- platformio.ini | 1 + 7 files changed, 3953 insertions(+), 2889 deletions(-) create mode 100644 lib-patch/enc28j60.cpp create mode 100644 lib-patch/enc28j60.h create mode 100644 lib-patch/readme.txt diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 8d02f7a00..73f9426f5 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1,2886 +1,2886 @@ -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * OpenSprinkler library - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#include "OpenSprinkler.h" -#include "opensprinkler_server.h" -#include "gpio.h" -#include "testmode.h" -#include "program.h" - -/** Declare static data members */ -OSMqtt OpenSprinkler::mqtt; -NVConData OpenSprinkler::nvdata; -ConStatus OpenSprinkler::status; -ConStatus OpenSprinkler::old_status; - -byte OpenSprinkler::hw_type; -byte OpenSprinkler::hw_rev; -byte OpenSprinkler::nboards; -byte OpenSprinkler::nstations; -byte OpenSprinkler::station_bits[MAX_NUM_BOARDS]; -byte OpenSprinkler::engage_booster; -uint16_t OpenSprinkler::baseline_current; - -ulong OpenSprinkler::sensor1_on_timer; -ulong OpenSprinkler::sensor1_off_timer; -ulong OpenSprinkler::sensor1_active_lasttime; -ulong OpenSprinkler::sensor2_on_timer; -ulong OpenSprinkler::sensor2_off_timer; -ulong OpenSprinkler::sensor2_active_lasttime; -ulong OpenSprinkler::raindelay_on_lasttime; -ulong OpenSprinkler::pause_timer; - -ulong OpenSprinkler::flowcount_log_start; -ulong OpenSprinkler::flowcount_rt; -byte OpenSprinkler::button_timeout; -ulong OpenSprinkler::checkwt_lasttime; -ulong OpenSprinkler::checkwt_success_lasttime; -ulong OpenSprinkler::powerup_lasttime; -uint8_t OpenSprinkler::last_reboot_cause = REBOOT_CAUSE_NONE; -byte OpenSprinkler::weather_update_flag; - -// todo future: the following attribute bytes are for backward compatibility -byte OpenSprinkler::attrib_mas[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igs[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_mas2[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igs2[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_grp[MAX_NUM_STATIONS]; -byte OpenSprinkler::masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; - -extern char tmp_buffer[]; -extern char ether_buffer[]; -extern ProgramData pd; - -#if defined(ESP8266) - SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); - byte OpenSprinkler::state = OS_STATE_INITIAL; - byte OpenSprinkler::prev_station_bits[MAX_NUM_BOARDS]; - IOEXP* OpenSprinkler::expanders[MAX_NUM_BOARDS/2]; - IOEXP* OpenSprinkler::mainio; // main controller IO expander object - IOEXP* OpenSprinkler::drio; // driver board IO expander object - RCSwitch OpenSprinkler::rfswitch; - OTCConfig OpenSprinkler::otc; - - String OpenSprinkler::wifi_ssid=""; - String OpenSprinkler::wifi_pass=""; - byte OpenSprinkler::wifi_bssid[6]={0}; - byte OpenSprinkler::wifi_channel=255; - byte OpenSprinkler::wifi_testmode = 0; -#elif defined(ARDUINO) - LiquidCrystal OpenSprinkler::lcd; - extern SdFat sd; -#else - #if defined(OSPI) - byte OpenSprinkler::pin_sr_data = PIN_SR_DATA; - #endif - // todo future: LCD define for Linux-based systems -#endif - -/** Option json names (stored in PROGMEM to reduce RAM usage) */ -// IMPORTANT: each json name is strictly 5 characters -// with 0 fillings if less -#define OP_JSON_NAME_STEPSIZE 5 -// for Integer options -const char iopt_json_names[] PROGMEM = - "fwv\0\0" - "tz\0\0\0" - "ntp\0\0" - "dhcp\0" - "ip1\0\0" - "ip2\0\0" - "ip3\0\0" - "ip4\0\0" - "gw1\0\0" - "gw2\0\0" - "gw3\0\0" - "gw4\0\0" - "hp0\0\0" - "hp1\0\0" - "hwv\0\0" - "ext\0\0" - "seq\0\0" - "sdt\0\0" - "mas\0\0" - "mton\0" - "mtof\0" - "urs\0\0" - "rso\0\0" - "wl\0\0\0" - "den\0\0" - "ipas\0" - "devid" - "con\0\0" - "lit\0\0" - "dim\0\0" - "bst\0\0" - "uwt\0\0" - "ntp1\0" - "ntp2\0" - "ntp3\0" - "ntp4\0" - "lg\0\0\0" - "mas2\0" - "mton2" - "mtof2" - "fwm\0\0" - "fpr0\0" - "fpr1\0" - "re\0\0\0" - "dns1\0" - "dns2\0" - "dns3\0" - "dns4\0" - "sar\0\0" - "ife\0\0" - "sn1t\0" - "sn1o\0" - "sn2t\0" - "sn2o\0" - "sn1on" - "sn1of" - "sn2on" - "sn2of" - "subn1" - "subn2" - "subn3" - "subn4" - "wimod" - "reset" - ; - -/** Option prompts (stored in PROGMEM to reduce RAM usage) */ -// Each string is strictly 16 characters -// with SPACE fillings if less -const char iopt_prompts[] PROGMEM = - "Firmware version" - "Time zone (GMT):" - "Enable NTP sync?" - "Enable DHCP? " - "Static.ip1: " - "Static.ip2: " - "Static.ip3: " - "Static.ip4: " - "Gateway.ip1: " - "Gateway.ip2: " - "Gateway.ip3: " - "Gateway.ip4: " - "HTTP Port: " - "----------------" - "Hardware version" - "# of exp. board:" - "----------------" - "Stn. delay (sec)" - "Master 1 (Mas1):" - "Mas1 on adjust:" - "Mas1 off adjust:" - "----------------" - "----------------" - "Watering level: " - "Device enabled? " - "Ignore password?" - "Device ID: " - "LCD contrast: " - "LCD brightness: " - "LCD dimming: " - "DC boost time: " - "Weather algo.: " - "NTP server.ip1: " - "NTP server.ip2: " - "NTP server.ip3: " - "NTP server.ip4: " - "Enable logging? " - "Master 2 (Mas2):" - "Mas2 on adjust:" - "Mas2 off adjust:" - "Firmware minor: " - "Pulse rate: " - "----------------" - "As remote ext.? " - "DNS server.ip1: " - "DNS server.ip2: " - "DNS server.ip3: " - "DNS server.ip4: " - "Special Refresh?" - "IFTTT Enable: " - "Sensor 1 type: " - "Normally open? " - "Sensor 2 type: " - "Normally open? " - "Sn1 on adjust: " - "Sn1 off adjust: " - "Sn2 on adjust: " - "Sn2 off adjust: " - "Subnet mask1: " - "Subnet mask2: " - "Subnet mask3: " - "Subnet mask4: " - "WiFi mode? " - "Factory reset? "; - -// string options do not have prompts - -/** Option maximum values (stored in PROGMEM to reduce RAM usage) */ -const byte iopt_max[] PROGMEM = { - 0, - 108, - 1, - 1, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 0, - MAX_EXT_BOARDS, - 1, - 255, - MAX_NUM_STATIONS, - 255, - 255, - 255, - 1, - 250, - 1, - 1, - 255, - 255, - 255, - 255, - 250, - 255, - 255, - 255, - 255, - 255, - 1, - MAX_NUM_STATIONS, - 255, - 255, - 0, - 255, - 255, - 1, - 255, - 255, - 255, - 255, - 1, - 255, - 255, - 1, - 255, - 1, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 1 -}; - -// string options do not have maximum values - -/** Integer option values (stored in RAM) */ -byte OpenSprinkler::iopts[] = { - OS_FW_VERSION, // firmware version - 28, // default time zone: GMT-5 - 1, // 0: disable NTP sync, 1: enable NTP sync - 1, // 0: use static ip, 1: use dhcp - 0, // this and next 3 bytes define static ip - 0, - 0, - 0, - 0, // this and next 3 bytes define static gateway ip - 0, - 0, - 0, -#if defined(ARDUINO) // on AVR, the default HTTP port is 80 - 80, // this and next byte define http port number - 0, -#else // on RPI/BBB/LINUX, the default HTTP port is 8080 - 144,// this and next byte define http port number - 31, -#endif - OS_HW_VERSION, - 0, // number of 8-station extension board. 0: no extension boards - 1, // the option 'sequential' is now retired - 120,// station delay time (-10 minutes to 10 minutes). - 0, // index of master station. 0: no master station - 120,// master on time adjusted time (-10 minutes to 10 minutes) - 120,// master off adjusted time (-10 minutes to 10 minutes) - 0, // urs (retired) - 0, // rso (retired) - 100,// water level (default 100%), - 1, // device enable - 0, // 1: ignore password; 0: use password - 0, // device id - 150,// lcd contrast - 100,// lcd backlight - 15, // lcd dimming - 80, // boost time (only valid to DC and LATCH type) - 0, // weather algorithm (0 means not using weather algorithm) - 0, // this and the next three bytes define the ntp server ip - 0, - 0, - 0, - 1, // enable logging: 0: disable; 1: enable. - 0, // index of master2. 0: no master2 station - 120,// master2 on adjusted time - 120,// master2 off adjusted time - OS_FW_MINOR, // firmware minor version - 100,// this and next byte define flow pulse rate (100x) - 0, // default is 1.00 (100) - 0, // set as remote extension - 8, // this and the next three bytes define the custom dns server ip - 8, - 8, - 8, - 0, // special station auto refresh - 0, // ifttt enable bits - 0, // sensor 1 type (see SENSOR_TYPE macro defines) - 1, // sensor 1 option. 0: normally closed; 1: normally open. default 1. - 0, // sensor 2 type - 1, // sensor 2 option. 0: normally closed; 1: normally open. default 1. - 0, // sensor 1 on delay - 0, // sensor 1 off delay - 0, // sensor 2 on delay - 0, // sensor 2 off delay - 255,// subnet mask 1 - 255,// subnet mask 2 - 255,// subnet mask 3 - 0, - WIFI_MODE_AP, // wifi mode - 0 // reset -}; - -/** String option values (stored in RAM) */ -const char *OpenSprinkler::sopts[] = { - DEFAULT_PASSWORD, - DEFAULT_LOCATION, - DEFAULT_JAVASCRIPT_URL, - DEFAULT_WEATHER_URL, - DEFAULT_EMPTY_STRING, // SOPT_WEATHER_OPTS - DEFAULT_EMPTY_STRING, // SOPT_IFTTT_KEY - DEFAULT_EMPTY_STRING, // SOPT_STA_SSID - DEFAULT_EMPTY_STRING, // SOPT_STA_PASS - DEFAULT_EMPTY_STRING, // SOPT_MQTT_OPTS - DEFAULT_EMPTY_STRING, // SOPT_OTC_OPTS - DEFAULT_DEVICE_NAME, - DEFAULT_EMPTY_STRING, // SOPT_STA_BSSID_CHL -}; - -/** Weekday strings (stored in PROGMEM to reduce RAM usage) */ -static const char days_str[] PROGMEM = - "Mon\0" - "Tue\0" - "Wed\0" - "Thu\0" - "Fri\0" - "Sat\0" - "Sun\0"; - -/** Calculate local time (UTC time plus time zone offset) */ -time_t OpenSprinkler::now_tz() { - return now()+(int32_t)3600/4*(int32_t)(iopts[IOPT_TIMEZONE]-48); -} - -#if defined(ARDUINO) - -bool detect_i2c(int addr) { - Wire.beginTransmission(addr); - return (Wire.endTransmission()==0); // successful if received 0 -} - -/** read hardware MAC into tmp_buffer */ -#define MAC_CTRL_ID 0x50 -bool OpenSprinkler::load_hardware_mac(byte* buffer, bool wired) { -#if defined(ESP8266) - WiFi.macAddress((byte*)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(byte 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 */ - -byte 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; - } else { - useEth = false; - } - - if((useEth || get_wifi_mode()==WIFI_MODE_STA) && otc.en>0 && otc.token.length()>=32) { - otf = new OTF::OpenThingsFramework(httpport, otc.server, otc.port, otc.token, false, ether_buffer, ETHER_BUFFER_SIZE); - DEBUG_PRINTLN(F("Started OTF with remote connection")); - } else { - otf = new OTF::OpenThingsFramework(httpport, ether_buffer, ETHER_BUFFER_SIZE); - DEBUG_PRINTLN(F("Started OTF with just local connection")); - } - extern DNSServer *dns; - if(get_wifi_mode() == WIFI_MODE_AP) dns = new DNSServer(); - if(update_server) { delete update_server; update_server = NULL; } - update_server = new ESP8266WebServer(8080); - 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 -} - -byte OpenSprinkler::start_ether() { -#if defined(ESP8266) - if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 - - SPI.begin(); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(4000000); - - load_hardware_mac((uint8_t*)tmp_buffer, true); - if (iopts[IOPT_USE_DHCP]==0) { // config static IP before calling eth.begin - IPAddress staticip(iopts+IOPT_STATIC_IP1); - IPAddress gateway(iopts+IOPT_GATEWAY_IP1); - IPAddress dns(iopts+IOPT_DNS_IP1); - IPAddress subn(iopts+IOPT_SUBNET_MASK1); - eth.config(staticip, gateway, subn, dns); - } - eth.setDefault(); - if(!eth.begin((uint8_t*)tmp_buffer)) return 0; - lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); - - ulong timeout = millis()+30000; // 30 seconds time out - while (!eth.connected()) { - DEBUG_PRINT("."); - delay(1000); - if(millis()>timeout) return 0; - } - - DEBUG_PRINTLN(); - DEBUG_PRINT("eth.ip:"); - DEBUG_PRINTLN(eth.localIP()); - DEBUG_PRINT("eth.dns:"); - DEBUG_PRINTLN(WiFi.dnsIP()); - - if (iopts[IOPT_USE_DHCP]) { - memcpy(iopts+IOPT_STATIC_IP1, &(eth.localIP()[0]), 4); - memcpy(iopts+IOPT_GATEWAY_IP1, &(eth.gatewayIP()[0]),4); - memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); // todo: lwip need dns ip - memcpy(iopts+IOPT_SUBNET_MASK1, &(eth.subnetMask()[0]), 4); - iopts_save(); - } - - return 1; - -#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 true; // todo: lwip currently does not have a way to check link status - else - return (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && state==OS_STATE_CONNECTED); -#else - return (Ethernet.linkStatus()==LinkON); -#endif -} - -/** Reboot controller */ -void OpenSprinkler::reboot_dev(uint8_t cause) { - lcd_print_line_clear_pgm(PSTR("Rebooting..."), 0); - if(cause) { - nvdata.reboot_cause = cause; - nvdata_save(); - } -#if defined(ESP8266) - ESP.restart(); -#else - resetFunc(); -#endif -} - -#else // RPI/BBB/LINUX network init functions - -#include "etherport.h" -#include -#include -#include -#include -#include "utils.h" -#include "opensprinkler_server.h" - -/** Initialize network with the given mac address and http port */ -byte OpenSprinkler::start_network() { - unsigned int port = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; -#if defined(DEMO) -#if defined(HTTP_PORT) - port = HTTP_PORT; -#else - port = 80; -#endif -#endif - if(m_server) { delete m_server; m_server = 0; } - - m_server = new EthernetServer(port); - return m_server->begin(); -} - -bool OpenSprinkler::network_connected(void) { - return true; -} - -// 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(byte* mac, bool wired) { - const char * if_names[] = { "eth0", "eth1", "wlan0", "wlan1" }; - struct ifreq ifr; - int fd; - - // Fallback to asoftware mac if interface not recognised - mac[0] = 0x00; - mac[1] = 0x69; - mac[2] = 0x69; - mac[3] = 0x2D; - mac[4] = 0x31; - mac[5] = iopts[IOPT_DEVICE_ID]; - - if (m_server == NULL) return true; - - if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) return true; - - // Returns the mac address of the first interface if multiple active - for (int i = 0; i < sizeof(if_names)/sizeof(const char *); i++) { - strncpy(ifr.ifr_name, if_names[i], sizeof(ifr.ifr_name)); - if (ioctl(fd, SIOCGIFHWADDR, &ifr) != -1) { - memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); - break; - } - } - close(fd); - return true; -} - -/** Reboot controller */ -void OpenSprinkler::reboot_dev(uint8_t cause) { - nvdata.reboot_cause = cause; - nvdata_save(); -#if defined(DEMO) - // do nothing -#else - sync(); // add sync to prevent file corruption - reboot(RB_AUTOBOOT); -#endif -} - -/** Launch update script */ -void OpenSprinkler::update_dev() { - char cmd[1000]; - sprintf(cmd, "cd %s && ./updater.sh", get_runtime_path()); - system(cmd); -} -#endif // end network init functions - -#if defined(ARDUINO) -/** Initialize LCD */ -void OpenSprinkler::lcd_start() { - -#if defined(ESP8266) - // initialize SSD1306 - lcd.init(); - lcd.begin(); - flash_screen(); -#else - // 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 - - /* check hardware type */ - if(detect_i2c(ACDR_I2CADDR)) hw_type = HW_TYPE_AC; - else if(detect_i2c(DCDR_I2CADDR)) hw_type = HW_TYPE_DC; - else if(detect_i2c(LADR_I2CADDR)) hw_type = HW_TYPE_LATCH; - - /* detect hardware revision type */ - if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists - /* assign revision 0 pins */ - PIN_BUTTON_1 = V0_PIN_BUTTON_1; - PIN_BUTTON_2 = V0_PIN_BUTTON_2; - PIN_BUTTON_3 = V0_PIN_BUTTON_3; - PIN_RFRX = V0_PIN_RFRX; - PIN_RFTX = V0_PIN_RFTX; - PIN_BOOST = V0_PIN_BOOST; - PIN_BOOST_EN = V0_PIN_BOOST_EN; - PIN_SENSOR1 = V0_PIN_SENSOR1; - PIN_SENSOR2 = V0_PIN_SENSOR2; - - // on revision 0, main IOEXP and driver IOEXP are two separate PCF8574 chips - if(hw_type==HW_TYPE_DC) { - drio = new PCF8574(DCDR_I2CADDR); - } else if(hw_type==HW_TYPE_LATCH) { - drio = new PCF8574(LADR_I2CADDR); - } else { - drio = new PCF8574(ACDR_I2CADDR); - } - - mainio = new PCF8574(MAIN_I2CADDR); - mainio->i2c_write(0, 0x0F); // set lower four bits of main PCF8574 (8-ch) to high - - digitalWriteExt(V0_PIN_PWR_TX, 1); // turn on TX power - digitalWriteExt(V0_PIN_PWR_RX, 1); // turn on RX power - pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); - digitalWriteExt(PIN_BOOST, LOW); - digitalWriteExt(PIN_BOOST_EN, LOW); - digitalWriteExt(PIN_LATCH_COM, LOW); - - } else { - - if(hw_type==HW_TYPE_DC) { - drio = new PCA9555(DCDR_I2CADDR); - } else if(hw_type==HW_TYPE_LATCH) { - drio = new PCA9555(LADR_I2CADDR); - } else { - drio = new PCA9555(ACDR_I2CADDR); - } - mainio = drio; - - pinMode(16, INPUT); - if(digitalRead(16)==LOW) { - // revision 1 - hw_rev = 1; - mainio->i2c_write(NXP_CONFIG_REG, V1_IO_CONFIG); - mainio->i2c_write(NXP_OUTPUT_REG, V1_IO_OUTPUT); - - PIN_BUTTON_1 = V1_PIN_BUTTON_1; - PIN_BUTTON_2 = V1_PIN_BUTTON_2; - PIN_BUTTON_3 = V1_PIN_BUTTON_3; - PIN_RFRX = V1_PIN_RFRX; - PIN_RFTX = V1_PIN_RFTX; - PIN_IOEXP_INT = V1_PIN_IOEXP_INT; - PIN_BOOST = V1_PIN_BOOST; - PIN_BOOST_EN = V1_PIN_BOOST_EN; - PIN_LATCH_COM = V1_PIN_LATCH_COM; - PIN_SENSOR1 = V1_PIN_SENSOR1; - PIN_SENSOR2 = V1_PIN_SENSOR2; - } else { - // revision 2 - hw_rev = 2; - mainio->i2c_write(NXP_CONFIG_REG, V2_IO_CONFIG); - mainio->i2c_write(NXP_OUTPUT_REG, V2_IO_OUTPUT); - - PIN_BUTTON_1 = V2_PIN_BUTTON_1; - PIN_BUTTON_2 = V2_PIN_BUTTON_2; - PIN_BUTTON_3 = V2_PIN_BUTTON_3; - PIN_RFTX = V2_PIN_RFTX; - PIN_BOOST = V2_PIN_BOOST; - PIN_BOOST_EN = V2_PIN_BOOST_EN; - PIN_LATCH_COMK = V2_PIN_LATCH_COMK; // os3.2latch uses H-bridge separate cathode and anode design - PIN_LATCH_COMA = V2_PIN_LATCH_COMA; - PIN_SENSOR1 = V2_PIN_SENSOR1; - PIN_SENSOR2 = V2_PIN_SENSOR2; - } - } - - /* detect expanders */ - for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) - 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 - - // Reset all stations - clear_all_station_bits(); - apply_all_station_bits(); - -#if defined(ESP8266) - // OS 3.0 has two independent sensors - pinModeExt(PIN_SENSOR1, INPUT_PULLUP); - pinModeExt(PIN_SENSOR2, INPUT_PULLUP); - -#else - // pull shift register OE low to enable output - 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 - // Static variables are assigned 0 by default - // so only need to initialize non-zero ones - status.enabled = 1; - status.safe_reboot = 0; - - old_status = status; - - nvdata.sunrise_time = 360; // 6:00am default sunrise - nvdata.sunset_time = 1080; // 6:00pm default sunset - nvdata.reboot_cause = REBOOT_CAUSE_POWERON; - - nboards = 1; - nstations = nboards*8; - - // set rf data pin - pinModeExt(PIN_RFTX, OUTPUT); - digitalWriteExt(PIN_RFTX, LOW); - -#if defined(ARDUINO) // AVR SD and LCD functions - - #if defined(ESP8266) // OS3.0 specific detections - - 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); - byte 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 - - lcd_start(); - - #if defined(ESP8266) - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); - #else - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_disconnected); - #endif - lcd.createChar(ICON_REMOTEXT, _iconimage_remotext); - lcd.createChar(ICON_RAINDELAY, _iconimage_raindelay); - lcd.createChar(ICON_RAIN, _iconimage_rain); - lcd.createChar(ICON_SOIL, _iconimage_soil); - - #if defined(ESP8266) - - /* create custom characters */ - lcd.createChar(ICON_WIFI_CONNECTED, _iconimage_wifi_connected); - lcd.createChar(ICON_WIFI_DISCONNECTED, _iconimage_wifi_disconnected); - - 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){} - } - - #endif - - // set button pins - // enable internal pullup - pinMode(PIN_BUTTON_1, INPUT_PULLUP); - pinMode(PIN_BUTTON_2, INPUT_PULLUP); - pinMode(PIN_BUTTON_3, INPUT_PULLUP); - - // detect and check RTC type - RTC.detect(); - -#else - DEBUG_PRINTLN(get_runtime_path()); -#endif -} - -#if defined(ESP8266) -/** LATCH boost voltage - * - */ -void OpenSprinkler::latch_boost() { - digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter - delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge - digitalWriteExt(PIN_BOOST, LOW); // disable boost converter -} - -/** Set all zones (for LATCH controller) - * This function sets all zone pins (including COM) to a specified value - */ -void OpenSprinkler::latch_setallzonepins(byte value) { - digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin - // Handle driver board (on main controller) - if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board - else reg &= 0xFF00; - drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - // Handle all expansion boards - for(byte i=0;itype==IOEXP_TYPE_9555) { - expanders[i]->i2c_write(NXP_OUTPUT_REG, value?0xFFFF:0x0000); - } - } -} - -void OpenSprinkler::latch_disable_alloutputs_v2() { - digitalWriteExt(PIN_LATCH_COMA, LOW); - digitalWriteExt(PIN_LATCH_COMK, LOW); - - // latch v2 has a pca9555 the lowest 8 bits of which control all h-bridge anode pins - drio->i2c_write(NXP_OUTPUT_REG, drio->i2c_read(NXP_OUTPUT_REG) & 0xFF00); - // latch v2 has a 74hc595 which controls all h-bridge cathode pins - drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, 0x00); - - // todo: handle expander -} - -/** Set one zone (for LATCH controller) - * This function sets one specified zone pin to a specified value - */ -void OpenSprinkler::latch_setzonepin(byte sid, byte value) { - if(sid<8) { // on main controller - if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - } else { // on expander - byte bid=(sid-8)>>4; - uint16_t s=(sid-8)&0x0F; - if(expanders[bid]->type==IOEXP_TYPE_9555) { - uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); - } - } -} - -void OpenSprinkler::latch_setzoneoutput_v2(byte sid, byte A, byte K) { - if(A==HIGH && K==HIGH) return; // A and K must not be HIGH at the same time - - if(sid<8) { // on main controller - // v2 latch driver has one PCA9555, the lowest 8-bits of which control all anode pins - // and one 74HC595, which controls all cathod pins - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); - if(A) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); - - drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, K ? (1<>3; - byte s=i&0x07; - byte mask=(byte)1<type==IOEXP_TYPE_8574) { - /* revision 0 uses PCF8574 with active low logic, so all bits must be flipped */ - drio->i2c_write(NXP_OUTPUT_REG, ~station_bits[0]); - } else if(drio->type==IOEXP_TYPE_9555) { - /* revision 1 uses PCA9555 with active high logic */ - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - reg = (reg&0xFF00) | station_bits[0]; // output channels are the low 8-bit - drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - - // Handle expansion boards - for(int i=0;itype==IOEXP_TYPE_9555) { - expanders[i]->i2c_write(NXP_OUTPUT_REG, data); - } else { - expanders[i]->i2c_write(NXP_OUTPUT_REG, ~data); - } - } - } - - byte bid, s, sbits; -#else - digitalWrite(PIN_SR_LATCH, LOW); - byte bid, s, sbits; - - // Shift out all station bit values - // from the highest bit to the lowest - for(bid=0;bid<=MAX_EXT_BOARDS;bid++) { - if (status.enabled) - sbits = station_bits[MAX_EXT_BOARDS-bid]; - else - sbits = 0; - - for(s=0;s<8;s++) { - digitalWrite(PIN_SR_CLOCK, LOW); - #if defined(OSPI) // if OSPI, use dynamically assigned pin_sr_data - digitalWrite(pin_sr_data, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); - #else - digitalWrite(PIN_SR_DATA, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); - #endif - digitalWrite(PIN_SR_CLOCK, HIGH); - } - } - - #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(iopts[IOPT_SPE_AUTO_REFRESH]) { - // handle refresh of RF and remote stations - // we refresh the station that's next in line - static byte next_sid_to_refresh = MAX_NUM_STATIONS>>1; - static byte lastnow = 0; - ulong curr_time = now_tz(); - byte _now = (curr_time & 0xFF); - if (lastnow != _now) { // perform this no more than once per second - lastnow = _now; - next_sid_to_refresh = (next_sid_to_refresh+1) % MAX_NUM_STATIONS; - byte bid=next_sid_to_refresh>>3,s=next_sid_to_refresh&0x07; - if(os.attrib_spe[bid]&(1<>3; - s=next_sid_to_refresh&0x07; - bool on = (station_bits[bid]>>s)&0x01; - uint16_t dur = 0; - if(on) { - byte sqi=pd.station_qid[next_sid_to_refresh]; - RuntimeQueueStruct *q=pd.queue+sqi; - if(sqi<255 && q->st>0 && q->st+q->dur>curr_time) { - dur = q->st+q->dur-curr_time; - } - } - switch_special_station(next_sid_to_refresh, on, dur); - } - } - } -} - -/** Read rain sensor status */ -void OpenSprinkler::detect_binarysensor_status(ulong curr_time) { - // sensor_type: 0 if normally closed, 1 if normally open - if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { - if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 - byte val = digitalReadExt(PIN_SENSOR1); - status.sensor1 = (val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1; - if(status.sensor1) { - if(!sensor1_on_timer) { - // add minimum of 5 seconds on delay - ulong delay_time = (ulong)iopts[IOPT_SENSOR1_ON_DELAY]*60; - sensor1_on_timer = curr_time + (delay_time>5?delay_time:5); - sensor1_off_timer = 0; - } else { - if(curr_time > sensor1_on_timer) { - status.sensor1_active = 1; - } - } - } else { - if(!sensor1_off_timer) { - ulong delay_time = (ulong)iopts[IOPT_SENSOR1_OFF_DELAY]*60; - sensor1_off_timer = curr_time + (delay_time>5?delay_time:5); - sensor1_on_timer = 0; - } else { - if(curr_time > sensor1_off_timer) { - status.sensor1_active = 0; - } - } - } - } - -// 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) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 - byte val = digitalReadExt(PIN_SENSOR2); - status.sensor2 = (val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1; - if(status.sensor2) { - if(!sensor2_on_timer) { - // add minimum of 5 seconds on delay - ulong delay_time = (ulong)iopts[IOPT_SENSOR2_ON_DELAY]*60; - sensor2_on_timer = curr_time + (delay_time>5?delay_time:5); - sensor2_off_timer = 0; - } else { - if(curr_time > sensor2_on_timer) { - status.sensor2_active = 1; - } - } - } else { - if(!sensor2_off_timer) { - ulong delay_time = (ulong)iopts[IOPT_SENSOR2_OFF_DELAY]*60; - sensor2_off_timer = curr_time + (delay_time>5?delay_time:5); - sensor2_on_timer = 0; - } else { - if(curr_time > sensor2_off_timer) { - status.sensor2_active = 0; - } - } - } - } - -#endif -} - -/** Return program switch status */ -byte OpenSprinkler::detect_programswitch_status(ulong curr_time) { - byte ret = 0; - if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { - static byte sensor1_hist = 0; - if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 - status.sensor1 = (digitalReadExt(PIN_SENSOR1) != iopts[IOPT_SENSOR1_OPTION]); // is switch activated? - sensor1_hist = (sensor1_hist<<1) | status.sensor1; - // basic noise filtering: only trigger if sensor matches pattern: - // i.e. two consecutive lows followed by two consecutive highs - if((sensor1_hist&0b1111) == 0b0011) { - ret |= 0x01; - } - } -#if defined(ESP8266) || defined(PIN_SENSOR2) - if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { - static byte sensor2_hist = 0; - if(hw_rev==2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 - status.sensor2 = (digitalReadExt(PIN_SENSOR2) != iopts[IOPT_SENSOR2_OPTION]); // is sensor activated? - sensor2_hist = (sensor2_hist<<1) | status.sensor2; - if((sensor2_hist&0b1111) == 0b0011) { - ret |= 0x02; - } - } -#endif - return ret; -} - -void OpenSprinkler::sensor_resetall() { - sensor1_on_timer = 0; - sensor1_off_timer = 0; - sensor1_active_lasttime = 0; - sensor2_on_timer = 0; - sensor2_off_timer = 0; - sensor2_active_lasttime = 0; - old_status.sensor1_active = status.sensor1_active = 0; - old_status.sensor2_active = status.sensor2_active = 0; -} - -/** Read current sensing value - * OpenSprinkler 2.3 and above have a 0.2 ohm current sensing resistor. - * Therefore the conversion from analog reading to milli-amp is: - * (r/1024)*3.3*1000/0.2 (DC-powered controller) - * AC-powered controller has a built-in precision rectifier to sense - * the peak AC current. Therefore the actual current is discounted by 0.707 - * ESP8266's analog reference voltage is 1.0 instead of 3.3, therefore - * it's further discounted by 1/3.3 - */ -#if defined(ARDUINO) -uint16_t OpenSprinkler::read_current() { - float scale = 1.0f; - if(status.has_curr_sense) { - if (hw_type == HW_TYPE_DC) { - #if defined(ESP8266) - scale = 4.88; - #else - scale = 16.11; - #endif - } else if (hw_type == HW_TYPE_AC) { - #if defined(ESP8266) - scale = 3.45; - #else - scale = 11.39; - #endif - } else { - scale = 0.0; // for other controllers, current is 0 - } - /* do an average */ - const byte K = 8; - uint16_t sum = 0; - for(byte i=0;i=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 -} - -/** Convert hex code to ulong integer */ -static ulong hex2ulong(byte *code, byte len) { - char c; - ulong v = 0; - for(byte i=0;i='0' && c<='9') { - v += (c-'0'); - } else if (c>='A' && c<='F') { - v += 10 + (c-'A'); - } else if (c>='a' && c<='f') { - v += 10 + (c-'a'); - } else { - return 0; - } - } - return v; -} - -/** Parse RF code into on/off/timeing sections */ -uint16_t OpenSprinkler::parse_rfstation_code(RFStationData *data, ulong* on, ulong *off) { - ulong v; - v = hex2ulong(data->on, sizeof(data->on)); - if (!v) return 0; - if (on) *on = v; - v = hex2ulong(data->off, sizeof(data->off)); - if (!v) return 0; - if (off) *off = v; - v = hex2ulong(data->timing, sizeof(data->timing)); - if (!v) return 0; - return v; -} - -/** Get station data */ -void OpenSprinkler::get_station_data(byte sid, StationData* data) { - file_read_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); -} - -/** Set station data */ -/* -void OpenSprinkler::set_station_data(byte sid, StationData* data) { - file_write_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); -} -*/ - -/** Get station name */ -void OpenSprinkler::get_station_name(byte sid, char tmp[]) { - tmp[STATION_NAME_SIZE]=0; - file_read_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); -} - -/** Set station name */ -void OpenSprinkler::set_station_name(byte sid, char tmp[]) { - // todo: store the right size - tmp[STATION_NAME_SIZE]=0; - char n0[STATION_NAME_SIZE+1]; - get_station_name(sid, n0); - size_t len = strlen(n0); - if(len!=strlen(tmp) || memcmp(n0, tmp, len)!=0) { // only write if the name has changed - file_write_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); - } -} - -/** Get station type */ -byte OpenSprinkler::get_station_type(byte sid) { - return file_read_byte(STATIONS_FILENAME, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type)); -} - -byte OpenSprinkler::is_sequential_station(byte sid) { - return attrib_grp[sid] != PARALLEL_GROUP_ID; -} - -byte OpenSprinkler::is_master_station(byte sid) { - for (byte mas = 0; mas < NUM_MASTER_ZONES; mas++) { - if (get_master_id(mas) && (get_master_id(mas) - 1 == sid)) { - return 1; - } - } - return 0; -} - -byte OpenSprinkler::is_running(byte sid) { - return station_bits[(sid >> 3)] >> (sid & 0x07) & 1; -} - -byte OpenSprinkler::get_master_id(byte mas) { - return masters[mas][MASOPT_SID]; -} - -int16_t OpenSprinkler::get_on_adj(byte mas) { - return water_time_decode_signed(masters[mas][MASOPT_ON_ADJ]); -} - -int16_t OpenSprinkler::get_off_adj(byte mas) { - return water_time_decode_signed(masters[mas][MASOPT_OFF_ADJ]); -} - -byte OpenSprinkler::bound_to_master(byte sid, byte mas) { - byte bid = sid >> 3; - byte s = sid & 0x07; - byte attributes = 0; - - switch (mas) { - case MASTER_1: - attributes= attrib_mas[bid]; - break; - case MASTER_2: - attributes = attrib_mas2[bid]; - break; - default: - break; - } - - return attributes & (1 << s); -} - -byte OpenSprinkler::get_station_gid(byte sid) { - return attrib_grp[sid]; -} - -void OpenSprinkler::set_station_gid(byte sid, byte gid) { - attrib_grp[sid] = gid; -} - -/** Save all station attribs to file (backward compatibility) */ -void OpenSprinkler::attribs_save() { - // re-package attribute bits and save - byte bid, s, sid=0; - StationAttrib at, at0; - memset(&at, 0, sizeof(StationAttrib)); - byte ty = STN_TYPE_STANDARD, ty0; - for(bid=0;bid>s) & 1; - at.igs = (attrib_igs[bid]>>s) & 1; - at.mas2= (attrib_mas2[bid]>>s)& 1; - at.igs2= (attrib_igs2[bid]>>s) & 1; - at.igrd= (attrib_igrd[bid]>>s) & 1; - at.dis = (attrib_dis[bid]>>s) & 1; - at.gid = get_station_gid(sid); - set_station_gid(sid, at.gid); - - // only write if content has changed: this is important for LittleFS as otherwise the overhead is too large - file_read_block(STATIONS_FILENAME, &at0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); - if(memcmp(&at,&at0,sizeof(StationAttrib))!=0) { - file_write_block(STATIONS_FILENAME, &at, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); // attribte bits are 1 byte long - } - if(attrib_spe[bid]>>s==0) { - // if station special bit is 0, make sure to write type STANDARD - // only write if content has changed - file_read_block(STATIONS_FILENAME, &ty0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); - if(ty!=ty0) { - file_write_block(STATIONS_FILENAME, &ty, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); // attribte bits are 1 byte long - } - } - } - } -} - -/** Load all station attribs from file (backward compatibility) */ -void OpenSprinkler::attribs_load() { - // load and re-package attributes - byte bid, s, sid=0; - StationAttrib at; - byte ty; - memset(attrib_mas, 0, nboards); - memset(attrib_igs, 0, nboards); - memset(attrib_mas2, 0, nboards); - memset(attrib_igs2, 0, nboards); - memset(attrib_igrd, 0, nboards); - memset(attrib_dis, 0, nboards); - memset(attrib_spe, 0, nboards); - memset(attrib_grp, 0, MAX_NUM_STATIONS); - - for(bid=0;bid>3,s=sid&0x07; - if(!(os.attrib_spe[bid]&(1<sped, value); - break; - - case STN_TYPE_REMOTE: - switch_remotestation((RemoteStationData *)pdata->sped, value, dur); - break; - - case STN_TYPE_GPIO: - switch_gpiostation((GPIOStationData *)pdata->sped, value); - break; - - case STN_TYPE_HTTP: - switch_httpstation((HTTPStationData *)pdata->sped, value); - break; - } - } -} - -/** Set station bit - * This function sets/resets the corresponding station bit variable - * You have to call apply_all_station_bits next to apply the bits - * (which results in physical actions of opening/closing valves). - */ -byte OpenSprinkler::set_station_bit(byte sid, byte value, uint16_t dur) { - byte *data = station_bits+(sid>>3); // pointer to the station byte - byte mask = (byte)1<<(sid&0x07); // mask - if (value) { - if((*data)&mask) return 0; // if bit is already set, return no change - else { - (*data) = (*data) | mask; - engage_booster = true; // if bit is changing from 0 to 1, set engage_booster - switch_special_station(sid, 1, dur); // handle special stations - return 1; - } - } else { - if(!((*data)&mask)) return 0; // if bit is already reset, return no change - else { - (*data) = (*data) & (~mask); - if(hw_type == HW_TYPE_LATCH) { - engage_booster = true; // if LATCH controller, engage booster when bit changes - } - switch_special_station(sid, 0); // handle special stations - return 255; - } - } - return 0; -} - -/** Clear all station bits */ -void OpenSprinkler::clear_all_station_bits() { - byte sid; - for(sid=0;sid<=MAX_NUM_STATIONS;sid++) { - set_station_bit(sid, 0); - } -} - -#if !defined(ARDUINO) -int rf_gpio_fd = -1; -#endif - -/** Transmit one RF signal bit */ -void transmit_rfbit(ulong lenH, ulong lenL) { -#if defined(ARDUINO) - #if defined(ESP8266) - digitalWrite(PIN_RFTX, 1); - delayMicroseconds(lenH); - digitalWrite(PIN_RFTX, 0); - delayMicroseconds(lenL); - #else - PORT_RF |= (1<=0) { - if ((code>>i) & 1) { - transmit_rfbit(len3, len); - } else { - transmit_rfbit(len, len3); - } - i--; - }; - // send sync - transmit_rfbit(len, len31); - } -} - -/** Switch RF station - * This function takes a RF code, - * parses it into signals and timing, - * and sends it out through RF transmitter. - */ -void OpenSprinkler::switch_rfstation(RFStationData *data, bool turnon) { - ulong on, off; - uint16_t length = parse_rfstation_code(data, &on, &off); -#if defined(ARDUINO) - #if defined(ESP8266) - rfswitch.enableTransmit(PIN_RFTX); - rfswitch.setProtocol(1); - rfswitch.setPulseLength(length); - rfswitch.send(turnon ? on : off, 24); - #else - send_rfsignal(turnon ? on : off, length); - #endif -#else - // pre-open gpio file to minimize overhead - rf_gpio_fd = gpio_fd_open(PIN_RFTX); - send_rfsignal(turnon ? on : off, length); - gpio_fd_close(rf_gpio_fd); - rf_gpio_fd = -1; -#endif - -} - -/** Switch GPIO station - * Special data for GPIO Station is three bytes of ascii decimal (not hex) - * First two bytes are zero padded GPIO pin number. - * Third byte is either 0 or 1 for active low (GND) or high (+5V) relays - */ -void OpenSprinkler::switch_gpiostation(GPIOStationData *data, bool turnon) { - byte gpio = (data->pin[0] - '0') * 10 + (data->pin[1] - '0'); - byte activeState = data->active - '0'; - - pinMode(gpio, OUTPUT); - if (turnon) - digitalWrite(gpio, activeState); - else - digitalWrite(gpio, 1-activeState); -} - -/** Callback function for switching remote station */ -void remote_http_callback(char* buffer) { -/* - DEBUG_PRINTLN(buffer); -*/ -} - -int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { - -#if defined(ARDUINO) - - Client *client; - #if defined(ESP8266) - WiFiClient wifiClient; - client = &wifiClient; - #else - EthernetClient etherClient; - client = ðerClient; - #endif - - #define HTTP_CONNECT_NTRIES 3 - byte tries = 0; - do { - DEBUG_PRINT(server); - DEBUG_PRINT(":"); - DEBUG_PRINT(port); - DEBUG_PRINT("("); - DEBUG_PRINT(tries); - DEBUG_PRINTLN(")"); - - if(client->connect(server, port)==1) break; - tries++; - } while(triesstop(); - return HTTP_RQT_CONNECT_ERR; - } -#else - - EthernetClient etherClient; - EthernetClient *client = ðerClient; - struct hostent *host; - DEBUG_PRINT(server); - DEBUG_PRINT(":"); - DEBUG_PRINTLN(port); - host = gethostbyname(server); - if (!host) { return HTTP_RQT_CONNECT_ERR; } - if(!client->connect((uint8_t*)host->h_addr, port)) { - DEBUG_PRINT(F("failed.")); - client->stop(); - return HTTP_RQT_CONNECT_ERR; - } - -#endif - - uint16_t len = strlen(p); - if(len > ETHER_BUFFER_SIZE) len = ETHER_BUFFER_SIZE; - if(client->connected()) { - client->write((uint8_t *)p, len); - } else { - DEBUG_PRINTLN(F("clint no longer connected")); - } - memset(ether_buffer, 0, ETHER_BUFFER_SIZE); - uint32_t stoptime = millis()+timeout; - - int pos = 0; -#if defined(ARDUINO) - // 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 - while(true) { - int nbytes = client->available(); - if(nbytes>0) { - if(pos+nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE-pos; // cannot read more than buffer size - client->read((uint8_t*)ether_buffer+pos, nbytes); - pos+=nbytes; - } - if(millis()>stoptime) { - DEBUG_PRINTLN(F("host timeout occured")); - //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far - break; - } - if(!client->connected() && !client->available()) { - //DEBUG_PRINTLN(F("host disconnected")); - break; - } - } -#else - while(client->connected()) { - int len=client->read((uint8_t *)ether_buffer+pos, ETHER_BUFFER_SIZE); - if (len<=0) continue; - pos+=len; - if(millis()>stoptime) { - DEBUG_PRINTLN(F("host timeout occured")); - //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far - break; - } - } -#endif - ether_buffer[pos]=0; // properly end buffer with 0 - client->stop(); - if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN; - if(callback) callback(ether_buffer); - return HTTP_RQT_SUCCESS; -} - -int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { - char server[20]; - byte ip[4]; - ip[0] = ip4>>24; - ip[1] = (ip4>>16)&0xff; - ip[2] = (ip4>>8)&0xff; - ip[3] = ip4&0xff; - sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return send_http_request(server, port, p, callback, timeout); -} - -int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { - char * server = strtok(server_with_port, ":"); - char * port = strtok(NULL, ":"); - return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); -} - -/** Switch remote station - * This function takes a remote station code, - * parses it into remote IP, port, station index, - * and makes a HTTP GET request. - * The remote controller is assumed to have the same - * password as the main controller - */ -void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon, uint16_t dur) { - RemoteStationData copy; - memcpy((char*)©, (char*)data, sizeof(RemoteStationData)); - - uint32_t ip4 = hex2ulong(copy.ip, sizeof(copy.ip)); - uint16_t port = (uint16_t)hex2ulong(copy.port, sizeof(copy.port)); - - byte ip[4]; - ip[0] = ip4>>24; - ip[1] = (ip4>>16)&0xff; - ip[2] = (ip4>>8)&0xff; - ip[3] = ip4&0xff; - - // use tmp_buffer starting at a later location - // because remote station data is loaded at the beginning - char *p = tmp_buffer; - BufferFiller bf = p; - // 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 - // if no auto refresh, we will give the maximum allowed duration, and station will be turned off when off command is sent - uint16_t timer = 0; - if(turnon) { - if(dur>0) { - timer = dur; - } else { - timer = iopts[IOPT_SPE_AUTO_REFRESH]?4*MAX_NUM_STATIONS:64800; - } - } - bf.emit_p(PSTR("GET /cm?pw=$O&sid=$D&en=$D&t=$D"), - SOPT_PASSWORD, - (int)hex2ulong(copy.sid, sizeof(copy.sid)), - turnon, timer); - bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), - ip[0],ip[1],ip[2],ip[3]); - - char server[20]; - sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - send_http_request(server, port, p, remote_http_callback); -} - -/** Switch http station - * This function takes an http station code, - * parses it into a server name and two HTTP GET requests. - */ -void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon) { - - HTTPStationData copy; - // make a copy of the HTTP station data and work with it - memcpy((char*)©, (char*)data, sizeof(HTTPStationData)); - char * server = strtok((char *)copy.data, ","); - char * port = strtok(NULL, ","); - char * on_cmd = strtok(NULL, ","); - char * off_cmd = strtok(NULL, ","); - char * cmd = turnon ? on_cmd : off_cmd; - - char *p = tmp_buffer; - BufferFiller bf = p; - - if(cmd==NULL || server==NULL) return; // proceed only if cmd and server are valid - - bf.emit_p(PSTR("GET /$S HTTP/1.0\r\nHOST: $S\r\n\r\n"), cmd, server); - - send_http_request(server, atoi(port), p, remote_http_callback); -} - -/** Prepare factory reset */ -void OpenSprinkler::pre_factory_reset() { - // for ESP8266: wipe out flash - #if defined(ESP8266) - lcd_print_line_clear_pgm(PSTR("Wiping flash.."), 0); - lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); - LittleFS.format(); - #else - // remove 'done' file as an indicator for reset - // todo os2.3 and ospi: delete log files and/or wipe SD card - remove_file(DONE_FILENAME); - #endif -} - -/** Factory reset */ -void OpenSprinkler::factory_reset() { -#if defined(ARDUINO) - lcd_print_line_clear_pgm(PSTR("Factory reset"), 0); - lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); -#else - DEBUG_PRINT("factory reset..."); -#endif - - // 1. reset integer options (by saving default values) - iopts_save(); - // reset string options by first wiping the file clean then write default values - memset(tmp_buffer, 0, MAX_SOPTS_SIZE); - for(int i=0; iname[0]='S'; - pdata->name[3]=0; - pdata->name[4]=0; - StationAttrib at; - memset(&at, 0, sizeof(StationAttrib)); - at.mas=1; - pdata->attrib=at; // mas:1 - pdata->type=STN_TYPE_STANDARD; - pdata->sped[0]='0'; - pdata->sped[1]=0; - for(int i=0; iname[1]='0'+(sid/10); // default station name - pdata->name[2]='0'+(sid%10); - } else { - pdata->name[1]='0'+(sid/100); - pdata->name[2]='0'+((sid%100)/10); - pdata->name[3]='0'+(sid%10); - } - file_write_block(STATIONS_FILENAME, pdata, sizeof(StationData)*i, sizeof(StationData)); - } - - attribs_load(); // load and repackage attrib bits (for backward compatibility) - - // 3. write non-volatile controller status - nvdata.reboot_cause = REBOOT_CAUSE_RESET; - nvdata_save(); - last_reboot_cause = nvdata.reboot_cause; - - // 4. write program data: just need to write a program counter: 0 - file_write_byte(PROG_FILENAME, 0, 0); - - // 5. write 'done' file - file_write_byte(DONE_FILENAME, 0, 1); -} - -#define str(s) #s -#define xstr(s) str(s) - -/** Parse OTC configuration */ -#if defined(ESP8266) -void OpenSprinkler::parse_otc_config() { - char server[MAX_SOPTS_SIZE+1] = {0}; - char token[MAX_SOPTS_SIZE+1] = {0}; - int port = DEFAULT_OTC_PORT; - int en = 0; - - char *config = tmp_buffer; - sopt_load(SOPT_OTC_OPTS, config); - if (*config != 0) { - sscanf(config, "\"en\":%d,\"token\":\"%" xstr(MAX_SOPTS_SIZE) "[^\"]\",\"server\":\"%" xstr(MAX_SOPTS_SIZE) "[^\"]\",\"port\":%d", - &en, token, server, &port); - token[MAX_SOPTS_SIZE] = 0; - server[MAX_SOPTS_SIZE] = 0; - } - otc.en = en; - otc.token = String(token); - otc.server = String(server); - otc.port = 80; -} -#endif - -/** Setup function for options */ -void OpenSprinkler::options_setup() { - - // Check reset conditions: - if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<220 || // fw version is invalid (<219) - !file_exists(DONE_FILENAME)) { // done file doesn't exist - - factory_reset(); - - } else { - - iopts_load(); - nvdata_load(); - last_reboot_cause = nvdata.reboot_cause; - nvdata.reboot_cause = REBOOT_CAUSE_POWERON; - nvdata_save(); - #if defined(ESP8266) - wifi_ssid = sopt_load(SOPT_STA_SSID); - wifi_pass = sopt_load(SOPT_STA_PASS); - sopt_load(SOPT_STA_BSSID_CHL, tmp_buffer); - if(tmp_buffer[0]!=0) { - char *mac = strchr(tmp_buffer, '@'); - if(mac!=NULL && isValidMAC(tmp_buffer)) { // check if bssid is valid MAC - *mac=0; // terminate MAC string - int chl = atoi(mac+1); - if(chl>=0 && chl<=255) { - str2mac(tmp_buffer, wifi_bssid); - wifi_channel = chl; - } - } - } - parse_otc_config(); - #endif - attribs_load(); - } - -#if defined(ARDUINO) // handle AVR buttons - byte button = button_read(BUTTON_WAIT_NONE); - - switch(button & BUTTON_MASK) { - - case BUTTON_1: - // if BUTTON_1 is pressed during startup, go to 'reset all options' - ui_set_options(IOPT_RESET); - if (iopts[IOPT_RESET]) { - pre_factory_reset(); - reboot_dev(REBOOT_CAUSE_RESET); - } - 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); - lcd_print_line_clear_pgm(PSTR(" B3:proceed"), 1); - do { - button = button_read(BUTTON_WAIT_NONE); - } while(!((button&BUTTON_MASK)==BUTTON_3 && (button&BUTTON_FLAG_DOWN))); - // set test mode parameters - - //iopts[IOPT_WIFI_MODE] = WIFI_MODE_STA; - wifi_testmode = 1; - #if defined(TESTMODE_SSID) - wifi_ssid = TESTMODE_SSID; - wifi_pass = TESTMODE_PASS; - #else - wifi_ssid = "ostest"; - wifi_pass = "opendoor"; - #endif - button = 0; - #endif - - break; - - case BUTTON_3: - // if BUTTON_3 is pressed during startup, enter Setup option mode - lcd_print_line_clear_pgm(PSTR("==Set Options=="), 0); - delay(DISPLAY_MSG_MS); - lcd_print_line_clear_pgm(PSTR("B1/B2:+/-, B3:->"), 0); - lcd_print_line_clear_pgm(PSTR("Hold B3 to save"), 1); - do { - button = button_read(BUTTON_WAIT_NONE); - } while (!(button & BUTTON_FLAG_DOWN)); - lcd.clear(); - ui_set_options(0); - if (iopts[IOPT_RESET]) { - pre_factory_reset(); - reboot_dev(REBOOT_CAUSE_RESET); - } - break; - } - - // turn on LCD backlight and contrast - lcd_set_brightness(); - lcd_set_contrast(); - - if (!button) { - // flash screen - lcd_print_line_clear_pgm(PSTR(" OpenSprinkler"),0); - lcd.setCursor((hw_type==HW_TYPE_LATCH)?2:4, 1); - lcd_print_pgm(PSTR("v")); - byte hwv = iopts[IOPT_HW_VERSION]; - lcd.print((char)('0'+(hwv/10))); - lcd.print('.'); - #if defined(ESP8266) - lcd.print(hw_rev); - #else - lcd.print((char)('0'+(hwv%10))); - #endif - switch(hw_type) { - case HW_TYPE_DC: - lcd_print_pgm(PSTR(" DC")); - break; - case HW_TYPE_LATCH: - lcd_print_pgm(PSTR(" LATCH")); - break; - default: - lcd_print_pgm(PSTR(" AC")); - } - delay(1500); - #if defined(ARDUINO) - lcd.setCursor(2, 1); - lcd_print_pgm(PSTR("FW ")); - lcd.print((char)('0'+(OS_FW_VERSION/100))); - lcd.print('.'); - lcd.print((char)('0'+((OS_FW_VERSION/10)%10))); - lcd.print('.'); - lcd.print((char)('0'+(OS_FW_VERSION%10))); - lcd.print('('); - lcd.print(OS_FW_MINOR); - lcd.print(')'); - delay(1000); - #endif - } -#endif -} - -/** Load non-volatile controller status data from file */ -void OpenSprinkler::nvdata_load() { - file_read_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); - old_status = status; -} - -/** Save non-volatile controller status data */ -void OpenSprinkler::nvdata_save() { - file_write_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); -} - -void load_wt_monthly(char* wto); - -/** Load integer options from file */ -void OpenSprinkler::iopts_load() { - file_read_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); - nboards = iopts[IOPT_EXT_BOARDS]+1; - nstations = nboards * 8; - status.enabled = iopts[IOPT_DEVICE_ENABLE]; - iopts[IOPT_FW_VERSION] = OS_FW_VERSION; - iopts[IOPT_FW_MINOR] = OS_FW_MINOR; - /* Reject the former default 50.97.210.169 NTP IP address as - * it no longer works, yet is carried on by people's saved - * configs when they upgrade from older versions. - * IOPT_NTP_IP1 = 0 leads to the new good default behavior. */ - if (iopts[IOPT_NTP_IP1] == 50 && iopts[IOPT_NTP_IP2] == 97 && - iopts[IOPT_NTP_IP3] == 210 && iopts[IOPT_NTP_IP4] == 169) { - iopts[IOPT_NTP_IP1] = 0; - iopts[IOPT_NTP_IP2] = 0; - iopts[IOPT_NTP_IP3] = 0; - iopts[IOPT_NTP_IP4] = 0; - } - populate_master(); - sopt_load(SOPT_WEATHER_OPTS, tmp_buffer); - if(iopts[IOPT_USE_WEATHER]==WEATHER_METHOD_MONTHLY) { - load_wt_monthly(tmp_buffer); - } -} - -void OpenSprinkler::populate_master() { - masters[MASTER_1][MASOPT_SID] = iopts[IOPT_MASTER_STATION]; - masters[MASTER_1][MASOPT_ON_ADJ] = iopts[IOPT_MASTER_ON_ADJ]; - masters[MASTER_1][MASOPT_OFF_ADJ] = iopts[IOPT_MASTER_OFF_ADJ]; - - 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]; -} - -/** Save integer options to file */ -void OpenSprinkler::iopts_save() { - file_write_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); - nboards = iopts[IOPT_EXT_BOARDS]+1; - nstations = nboards * 8; - status.enabled = iopts[IOPT_DEVICE_ENABLE]; -} - -/** Load a string option from file */ -void OpenSprinkler::sopt_load(byte oid, char *buf) { - file_read_block(SOPTS_FILENAME, buf, MAX_SOPTS_SIZE*oid, MAX_SOPTS_SIZE); - buf[MAX_SOPTS_SIZE]=0; // ensure the string ends properly -} - -/** Load a string option from file, return String */ -String OpenSprinkler::sopt_load(byte oid) { - sopt_load(oid, tmp_buffer); - String str = tmp_buffer; - return str; -} - -/** Save a string option to file */ -bool OpenSprinkler::sopt_save(byte oid, const char *buf) { - // smart save: if value hasn't changed, don't write - if(file_cmp_block(SOPTS_FILENAME, buf, (ulong)MAX_SOPTS_SIZE*oid)==0) return false; - int len = strlen(buf); - if(len>=MAX_SOPTS_SIZE) { - file_write_block(SOPTS_FILENAME, buf, (ulong)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); - } - return true; -} - -// ============================== -// Controller Operation Functions -// ============================== - -/** Enable controller operation */ -void OpenSprinkler::enable() { - status.enabled = 1; - iopts[IOPT_DEVICE_ENABLE] = 1; - iopts_save(); -} - -/** Disable controller operation */ -void OpenSprinkler::disable() { - status.enabled = 0; - iopts[IOPT_DEVICE_ENABLE] = 0; - iopts_save(); -} - -/** Start rain delay */ -void OpenSprinkler::raindelay_start() { - status.rain_delayed = 1; - nvdata_save(); -} - -/** Stop rain delay */ -void OpenSprinkler::raindelay_stop() { - status.rain_delayed = 0; - nvdata.rd_stop_time = 0; - nvdata_save(); -} - -/** LCD and button functions */ -#if defined(ARDUINO) // AVR 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); - } -} - -/** print a program memory string to a given line with clearing */ -#if defined(ESP8266) -void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P str, byte line) { -#else -void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P PROGMEM str, byte line) { -#endif - lcd.setCursor(0, line); - uint8_t c; - int8_t cnt = 0; - while((c=pgm_read_byte(str++))!= '\0') { - lcd.print((char)c); - cnt++; - } - for(; (16-cnt) >= 0; cnt ++) lcd_print_pgm(PSTR(" ")); -} - -void OpenSprinkler::lcd_print_2digit(int v) -{ - lcd.print((int)(v/10)); - lcd.print((int)(v%10)); -} - -/** print time to a given line */ -void OpenSprinkler::lcd_print_time(time_t t) -{ -#if defined(ESP8266) - 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_2digit(month(t)); - lcd_print_pgm(PSTR("-")); - lcd_print_2digit(day(t)); -#if defined(ESP8266) - lcd.display(); - lcd.setAutoDisplay(true); -#endif -} - -/** print ip address */ -void OpenSprinkler::lcd_print_ip(const byte *ip, byte endian) { -#if defined(ESP8266) - lcd.clear(0, 1); -#else - lcd.clear(); -#endif - lcd.setCursor(0, 0); - for (byte i=0; i<4; i++) { - lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); - if(i<3) lcd_print_pgm(PSTR(".")); - } -} - -/** print mac address */ -void OpenSprinkler::lcd_print_mac(const byte *mac) { - lcd.setCursor(0, 0); - for(byte i=0; i<6; i++) { - if(i) lcd_print_pgm(PSTR("-")); - lcd.print((mac[i]>>4), HEX); - lcd.print((mac[i]&0x0F), HEX); - if(i==4) lcd.setCursor(0, 1); - } - if(useEth) { - lcd_print_pgm(PSTR(" (Ether MAC)")); - } else { - lcd_print_pgm(PSTR(" (WiFi MAC)")); - } -} - -/** print station bits */ -void OpenSprinkler::lcd_print_screen(char c) { -#if defined(ESP8266) - 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' - } else { - lcd.print(F("E")); - lcd.print((int)status.display_board); - lcd.print(F(":")); // extension boards are displayed as E1, E2... - } - if (!status.enabled) { - lcd.print(F("-Disabled!-")); - } else { - byte bitvalue = station_bits[status.display_board]; - for (byte s=0; s<8; s++) { - byte sid = (byte)status.display_board<<3; - sid += (s+1); - if (sid == iopts[IOPT_MASTER_STATION]) { - 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 { - lcd.print((bitvalue&1) ? c : '_'); - } - bitvalue >>= 1; - } - } - //lcd.print(F(" ")); - - lcd.setCursor(LCD_CURSOR_REMOTEXT, 1); - lcd.write(iopts[IOPT_REMOTE_EXT_MODE]?ICON_REMOTEXT:' '); - - lcd.setCursor(LCD_CURSOR_RAINDELAY, 1); - lcd.write((status.rain_delayed || status.pause_state)?ICON_RAINDELAY:' '); - - // write sensor 1 icon - lcd.setCursor(LCD_CURSOR_SENSOR1, 1); - switch(iopts[IOPT_SENSOR1_TYPE]) { - case SENSOR_TYPE_RAIN: - lcd.write(status.sensor1_active?ICON_RAIN:(status.sensor1?'R':'r')); - break; - case SENSOR_TYPE_SOIL: - lcd.write(status.sensor1_active?ICON_SOIL:(status.sensor1?'S':'s')); - break; - case SENSOR_TYPE_FLOW: - lcd.write(flowcount_rt>0?'F':'f'); - break; - case SENSOR_TYPE_PSWITCH: - lcd.write(status.sensor1?'P':'p'); - break; - default: - lcd.write(' '); - break; - } - - // write sensor 2 icon - lcd.setCursor(LCD_CURSOR_SENSOR2, 1); - switch(iopts[IOPT_SENSOR2_TYPE]) { - case SENSOR_TYPE_RAIN: - lcd.write(status.sensor2_active?ICON_RAIN:(status.sensor2?'R':'r')); - break; - case SENSOR_TYPE_SOIL: - lcd.write(status.sensor2_active?ICON_SOIL:(status.sensor2?'S':'s')); - break; - // sensor2 cannot be flow sensor - /*case SENSOR_TYPE_FLOW: - lcd.write('F'); - break;*/ - case SENSOR_TYPE_PSWITCH: - lcd.write(status.sensor2?'Q':'q'); - break; - default: - lcd.write(' '); - break; - } - - lcd.setCursor(LCD_CURSOR_NETWORK, 1); -#if defined(ESP8266) - if(useEth) { - lcd.write(eth.connected()?ICON_ETHER_CONNECTED:ICON_ETHER_DISCONNECTED); // todo: need to detect ether status - } - else - lcd.write(WiFi.status()==WL_CONNECTED?ICON_WIFI_CONNECTED:ICON_WIFI_DISCONNECTED); -#else - 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(ESP8266) - - if(useEth || (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP())) { - lcd.setCursor(0, -1); - if(status.rain_delayed) { - lcd.print(F(" ")); - } else if(status.pause_state) { - lcd.print(F("")); - } else if(status.program_busy) { - lcd.print(F(" ")); - } else { - lcd.print(F(" (System Idle) ")); - } - - lcd.setCursor(2, 2); - if(status.program_busy && !status.pause_state) { - //lcd.print(F("Curr: ")); - lcd.print(read_current()); - lcd.print(F(" mA ")); - } else { - lcd.clear(2, 2); - } - } -#endif -#if defined(ESP8266) - lcd.display(); - lcd.setAutoDisplay(true); -#endif -} - -/** print a version number */ -void OpenSprinkler::lcd_print_version(byte v) { - if(v > 99) { - lcd.print(v/100); - lcd.print("."); - } - if(v>9) { - lcd.print((v/10)%10); - lcd.print("."); - } - lcd.print(v%10); -} - -/** print an option value */ -void OpenSprinkler::lcd_print_option(int i) { - // each prompt string takes 16 characters - strncpy_P0(tmp_buffer, iopt_prompts+16*i, 16); - lcd.setCursor(0, 0); - lcd.print(tmp_buffer); - lcd_print_line_clear_pgm(PSTR(""), 1); - lcd.setCursor(0, 1); - int tz; - switch(i) { - case IOPT_HW_VERSION: - lcd.print("v"); - case IOPT_FW_VERSION: - lcd_print_version(iopts[i]); - break; - case IOPT_TIMEZONE: // if this is the time zone option, do some conversion - tz = (int)iopts[i]-48; - if (tz>=0) lcd_print_pgm(PSTR("+")); - else {lcd_print_pgm(PSTR("-")); tz=-tz;} - lcd.print(tz/4); // print integer portion - lcd_print_pgm(PSTR(":")); - tz = (tz%4)*15; - if (tz==0) lcd_print_pgm(PSTR("00")); - else { - lcd.print(tz); // print fractional portion - } - break; - case IOPT_MASTER_ON_ADJ: - case IOPT_MASTER_ON_ADJ_2: - case IOPT_MASTER_OFF_ADJ: - case IOPT_MASTER_OFF_ADJ_2: - case IOPT_STATION_DELAY_TIME: - { - int16_t t=water_time_decode_signed(iopts[i]); - if(t>=0) lcd_print_pgm(PSTR("+")); - lcd.print(t); - } - break; - case IOPT_HTTPPORT_0: - lcd.print((unsigned int)(iopts[i+1]<<8)+iopts[i]); - break; - case IOPT_PULSE_RATE_0: - { - uint16_t fpr = (unsigned int)(iopts[i+1]<<8)+iopts[i]; - lcd.print(fpr/100); - lcd_print_pgm(PSTR(".")); - lcd.print((fpr/10)%10); - lcd.print(fpr%10); - } - break; - case IOPT_LCD_CONTRAST: - lcd_set_contrast(); - lcd.print((int)iopts[i]); - break; - case IOPT_LCD_BACKLIGHT: - lcd_set_brightness(); - lcd.print((int)iopts[i]); - break; - case IOPT_BOOST_TIME: - #if defined(ARDUINO) - if(hw_type==HW_TYPE_AC) { - lcd.print('-'); - } else { - lcd.print((int)iopts[i]*4); - lcd_print_pgm(PSTR(" ms")); - } - #else - lcd.print('-'); - #endif - break; - default: - // if this is a boolean option - if (pgm_read_byte(iopt_max+i)==1) - lcd_print_pgm(iopts[i] ? PSTR("Yes") : PSTR("No")); - else - lcd.print((int)iopts[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) - lcd_print_pgm(PSTR(" sec")); - -} - -/** Button functions */ -/** wait for button */ -byte OpenSprinkler::button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding) { - - int hold_time = 0; - - if (waitmode==BUTTON_WAIT_NONE || (waitmode == BUTTON_WAIT_HOLD && is_holding)) { - if (digitalReadExt(pin_butt) != 0) return BUTTON_NONE; - return butt | (is_holding ? BUTTON_FLAG_HOLD : 0); - } - - while (digitalReadExt(pin_butt) == 0 && - (waitmode == BUTTON_WAIT_RELEASE || (waitmode == BUTTON_WAIT_HOLD && hold_time= BUTTON_HOLD_MS) - butt |= BUTTON_FLAG_HOLD; - return butt; - -} - -/** read button and returns button value 'OR'ed with flag bits */ -byte OpenSprinkler::button_read(byte waitmode) -{ - static byte old = BUTTON_NONE; - byte curr = BUTTON_NONE; - byte is_holding = (old&BUTTON_FLAG_HOLD); - - delay(BUTTON_DELAY_MS); - - if (digitalReadExt(PIN_BUTTON_1) == 0) { - curr = button_read_busy(PIN_BUTTON_1, waitmode, BUTTON_1, is_holding); - } else if (digitalReadExt(PIN_BUTTON_2) == 0) { - curr = button_read_busy(PIN_BUTTON_2, waitmode, BUTTON_2, is_holding); - } else if (digitalReadExt(PIN_BUTTON_3) == 0) { - curr = button_read_busy(PIN_BUTTON_3, waitmode, BUTTON_3, is_holding); - } - - // set flags in return value - byte ret = curr; - if (!(old&BUTTON_MASK) && (curr&BUTTON_MASK)) - ret |= BUTTON_FLAG_DOWN; - if ((old&BUTTON_MASK) && !(curr&BUTTON_MASK)) - ret |= BUTTON_FLAG_UP; - - old = curr; - - return ret; -} - -/** user interface for setting options during startup */ -void OpenSprinkler::ui_set_options(int oid) -{ - boolean finished = false; - byte button; - int i=oid; - - while(!finished) { - button = button_read(BUTTON_WAIT_HOLD); - - switch (button & BUTTON_MASK) { - case BUTTON_1: - if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || - i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || - i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || - i==IOPT_WIFI_MODE) break; // ignore non-editable options - if (pgm_read_byte(iopt_max+i) != iopts[i]) iopts[i] ++; - break; - - case BUTTON_2: - if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || - i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || - i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || - i==IOPT_WIFI_MODE) break; // ignore non-editable options - if (iopts[i] != 0) iopts[i] --; - break; - - case BUTTON_3: - if (!(button & BUTTON_FLAG_DOWN)) break; - if (button & BUTTON_FLAG_HOLD) { - // long press, save options - iopts_save(); - finished = true; - } - else { - // click, move to the next option - 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 { - i = (i+1) % NUM_IOPTS; - } - if(i==IOPT_SEQUENTIAL_RETIRED) i++; - if(i==IOPT_URS_RETIRED) i++; - if(i==IOPT_RSO_RETIRED) i++; - if (hw_type==HW_TYPE_AC && i==IOPT_BOOST_TIME) i++; // skip boost time for non-DC controller - #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; - } - - if (button != BUTTON_NONE) { - lcd_print_option(i); - } - } - lcd.noBlink(); -} - -/** 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(byte value) { -#if defined(PIN_LCD_BACKLIGHT) - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - 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(ESP8266) - 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 -} -#endif // end of LCD and button functions - -#if defined(ESP8266) -#include "images.h" -void OpenSprinkler::flash_screen() { - lcd.setCursor(0, -1); - lcd.print(F(" OpenSprinkler")); - lcd.drawXbm(34, 24, WiFi_Logo_width, WiFi_Logo_height, (const byte*) WiFi_Logo_image); - lcd.setCursor(0, 2); - lcd.display(); - delay(1500); - lcd.clear(); - lcd.display(); -} - -void OpenSprinkler::toggle_screen_led() { - static byte status = 0; - status = 1-status; - set_screen_led(!status); -} - -void OpenSprinkler::set_screen_led(byte status) { - lcd.setColor(status ? WHITE : BLACK); - lcd.fillCircle(122, 58, 4); - lcd.display(); - lcd.setColor(WHITE); -} - -void OpenSprinkler::reset_to_ap() { - iopts[IOPT_WIFI_MODE] = WIFI_MODE_AP; - iopts_save(); - reboot_dev(REBOOT_CAUSE_RSTAP); -} - -void OpenSprinkler::config_ip() { - if(iopts[IOPT_USE_DHCP] == 0) { - byte *_ip = iopts+IOPT_STATIC_IP1; - IPAddress dvip(_ip[0], _ip[1], _ip[2], _ip[3]); - if(dvip==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_GATEWAY_IP1; - IPAddress gwip(_ip[0], _ip[1], _ip[2], _ip[3]); - if(gwip==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_SUBNET_MASK1; - IPAddress subn(_ip[0], _ip[1], _ip[2], _ip[3]); - if(subn==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_DNS_IP1; - IPAddress dnsip(_ip[0], _ip[1], _ip[2], _ip[3]); - - WiFi.config(dvip, gwip, subn, dnsip); - } -} - -void OpenSprinkler::save_wifi_ip() { - // todo: handle wired ethernet - if(iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED) { - memcpy(iopts+IOPT_STATIC_IP1, &(WiFi.localIP()[0]), 4); - memcpy(iopts+IOPT_GATEWAY_IP1, &(WiFi.gatewayIP()[0]),4); - memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); - memcpy(iopts+IOPT_SUBNET_MASK1, &(WiFi.subnetMask()[0]), 4); - iopts_save(); - } -} - -void OpenSprinkler::detect_expanders() { - for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) { - byte address = EXP_I2CADDR_BASE+i; - byte type = IOEXP::detectType(address); - if(expanders[i]!=NULL) delete expanders[i]; - if(type==IOEXP_TYPE_9555) { - expanders[i] = new PCA9555(address); - expanders[i]->i2c_write(NXP_CONFIG_REG, 0); // set all channels to output - } else if(type==IOEXP_TYPE_8575){ - expanders[i] = new PCF8575(address); - } else { - expanders[i] = new IOEXP(address); - } - } -} -#endif +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * OpenSprinkler library + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#include "OpenSprinkler.h" +#include "opensprinkler_server.h" +#include "gpio.h" +#include "testmode.h" +#include "program.h" + +/** Declare static data members */ +OSMqtt OpenSprinkler::mqtt; +NVConData OpenSprinkler::nvdata; +ConStatus OpenSprinkler::status; +ConStatus OpenSprinkler::old_status; + +byte OpenSprinkler::hw_type; +byte OpenSprinkler::hw_rev; +byte OpenSprinkler::nboards; +byte OpenSprinkler::nstations; +byte OpenSprinkler::station_bits[MAX_NUM_BOARDS]; +byte OpenSprinkler::engage_booster; +uint16_t OpenSprinkler::baseline_current; + +ulong OpenSprinkler::sensor1_on_timer; +ulong OpenSprinkler::sensor1_off_timer; +ulong OpenSprinkler::sensor1_active_lasttime; +ulong OpenSprinkler::sensor2_on_timer; +ulong OpenSprinkler::sensor2_off_timer; +ulong OpenSprinkler::sensor2_active_lasttime; +ulong OpenSprinkler::raindelay_on_lasttime; +ulong OpenSprinkler::pause_timer; + +ulong OpenSprinkler::flowcount_log_start; +ulong OpenSprinkler::flowcount_rt; +byte OpenSprinkler::button_timeout; +ulong OpenSprinkler::checkwt_lasttime; +ulong OpenSprinkler::checkwt_success_lasttime; +ulong OpenSprinkler::powerup_lasttime; +uint8_t OpenSprinkler::last_reboot_cause = REBOOT_CAUSE_NONE; +byte OpenSprinkler::weather_update_flag; + +// todo future: the following attribute bytes are for backward compatibility +byte OpenSprinkler::attrib_mas[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_igs[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_mas2[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_igs2[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; +byte OpenSprinkler::attrib_grp[MAX_NUM_STATIONS]; +byte OpenSprinkler::masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; + +extern char tmp_buffer[]; +extern char ether_buffer[]; +extern ProgramData pd; + +#if defined(ESP8266) + SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); + byte OpenSprinkler::state = OS_STATE_INITIAL; + byte OpenSprinkler::prev_station_bits[MAX_NUM_BOARDS]; + IOEXP* OpenSprinkler::expanders[MAX_NUM_BOARDS/2]; + IOEXP* OpenSprinkler::mainio; // main controller IO expander object + IOEXP* OpenSprinkler::drio; // driver board IO expander object + RCSwitch OpenSprinkler::rfswitch; + OTCConfig OpenSprinkler::otc; + + String OpenSprinkler::wifi_ssid=""; + String OpenSprinkler::wifi_pass=""; + byte OpenSprinkler::wifi_bssid[6]={0}; + byte OpenSprinkler::wifi_channel=255; + byte OpenSprinkler::wifi_testmode = 0; +#elif defined(ARDUINO) + LiquidCrystal OpenSprinkler::lcd; + extern SdFat sd; +#else + #if defined(OSPI) + byte OpenSprinkler::pin_sr_data = PIN_SR_DATA; + #endif + // todo future: LCD define for Linux-based systems +#endif + +/** Option json names (stored in PROGMEM to reduce RAM usage) */ +// IMPORTANT: each json name is strictly 5 characters +// with 0 fillings if less +#define OP_JSON_NAME_STEPSIZE 5 +// for Integer options +const char iopt_json_names[] PROGMEM = + "fwv\0\0" + "tz\0\0\0" + "ntp\0\0" + "dhcp\0" + "ip1\0\0" + "ip2\0\0" + "ip3\0\0" + "ip4\0\0" + "gw1\0\0" + "gw2\0\0" + "gw3\0\0" + "gw4\0\0" + "hp0\0\0" + "hp1\0\0" + "hwv\0\0" + "ext\0\0" + "seq\0\0" + "sdt\0\0" + "mas\0\0" + "mton\0" + "mtof\0" + "urs\0\0" + "rso\0\0" + "wl\0\0\0" + "den\0\0" + "ipas\0" + "devid" + "con\0\0" + "lit\0\0" + "dim\0\0" + "bst\0\0" + "uwt\0\0" + "ntp1\0" + "ntp2\0" + "ntp3\0" + "ntp4\0" + "lg\0\0\0" + "mas2\0" + "mton2" + "mtof2" + "fwm\0\0" + "fpr0\0" + "fpr1\0" + "re\0\0\0" + "dns1\0" + "dns2\0" + "dns3\0" + "dns4\0" + "sar\0\0" + "ife\0\0" + "sn1t\0" + "sn1o\0" + "sn2t\0" + "sn2o\0" + "sn1on" + "sn1of" + "sn2on" + "sn2of" + "subn1" + "subn2" + "subn3" + "subn4" + "wimod" + "reset" + ; + +/** Option prompts (stored in PROGMEM to reduce RAM usage) */ +// Each string is strictly 16 characters +// with SPACE fillings if less +const char iopt_prompts[] PROGMEM = + "Firmware version" + "Time zone (GMT):" + "Enable NTP sync?" + "Enable DHCP? " + "Static.ip1: " + "Static.ip2: " + "Static.ip3: " + "Static.ip4: " + "Gateway.ip1: " + "Gateway.ip2: " + "Gateway.ip3: " + "Gateway.ip4: " + "HTTP Port: " + "----------------" + "Hardware version" + "# of exp. board:" + "----------------" + "Stn. delay (sec)" + "Master 1 (Mas1):" + "Mas1 on adjust:" + "Mas1 off adjust:" + "----------------" + "----------------" + "Watering level: " + "Device enabled? " + "Ignore password?" + "Device ID: " + "LCD contrast: " + "LCD brightness: " + "LCD dimming: " + "DC boost time: " + "Weather algo.: " + "NTP server.ip1: " + "NTP server.ip2: " + "NTP server.ip3: " + "NTP server.ip4: " + "Enable logging? " + "Master 2 (Mas2):" + "Mas2 on adjust:" + "Mas2 off adjust:" + "Firmware minor: " + "Pulse rate: " + "----------------" + "As remote ext.? " + "DNS server.ip1: " + "DNS server.ip2: " + "DNS server.ip3: " + "DNS server.ip4: " + "Special Refresh?" + "IFTTT Enable: " + "Sensor 1 type: " + "Normally open? " + "Sensor 2 type: " + "Normally open? " + "Sn1 on adjust: " + "Sn1 off adjust: " + "Sn2 on adjust: " + "Sn2 off adjust: " + "Subnet mask1: " + "Subnet mask2: " + "Subnet mask3: " + "Subnet mask4: " + "WiFi mode? " + "Factory reset? "; + +// string options do not have prompts + +/** Option maximum values (stored in PROGMEM to reduce RAM usage) */ +const byte iopt_max[] PROGMEM = { + 0, + 108, + 1, + 1, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 0, + MAX_EXT_BOARDS, + 1, + 255, + MAX_NUM_STATIONS, + 255, + 255, + 255, + 1, + 250, + 1, + 1, + 255, + 255, + 255, + 255, + 250, + 255, + 255, + 255, + 255, + 255, + 1, + MAX_NUM_STATIONS, + 255, + 255, + 0, + 255, + 255, + 1, + 255, + 255, + 255, + 255, + 1, + 255, + 255, + 1, + 255, + 1, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 255, + 1 +}; + +// string options do not have maximum values + +/** Integer option values (stored in RAM) */ +byte OpenSprinkler::iopts[] = { + OS_FW_VERSION, // firmware version + 28, // default time zone: GMT-5 + 1, // 0: disable NTP sync, 1: enable NTP sync + 1, // 0: use static ip, 1: use dhcp + 0, // this and next 3 bytes define static ip + 0, + 0, + 0, + 0, // this and next 3 bytes define static gateway ip + 0, + 0, + 0, +#if defined(ARDUINO) // on AVR, the default HTTP port is 80 + 80, // this and next byte define http port number + 0, +#else // on RPI/BBB/LINUX, the default HTTP port is 8080 + 144,// this and next byte define http port number + 31, +#endif + OS_HW_VERSION, + 0, // number of 8-station extension board. 0: no extension boards + 1, // the option 'sequential' is now retired + 120,// station delay time (-10 minutes to 10 minutes). + 0, // index of master station. 0: no master station + 120,// master on time adjusted time (-10 minutes to 10 minutes) + 120,// master off adjusted time (-10 minutes to 10 minutes) + 0, // urs (retired) + 0, // rso (retired) + 100,// water level (default 100%), + 1, // device enable + 0, // 1: ignore password; 0: use password + 0, // device id + 150,// lcd contrast + 100,// lcd backlight + 15, // lcd dimming + 80, // boost time (only valid to DC and LATCH type) + 0, // weather algorithm (0 means not using weather algorithm) + 0, // this and the next three bytes define the ntp server ip + 0, + 0, + 0, + 1, // enable logging: 0: disable; 1: enable. + 0, // index of master2. 0: no master2 station + 120,// master2 on adjusted time + 120,// master2 off adjusted time + OS_FW_MINOR, // firmware minor version + 100,// this and next byte define flow pulse rate (100x) + 0, // default is 1.00 (100) + 0, // set as remote extension + 8, // this and the next three bytes define the custom dns server ip + 8, + 8, + 8, + 0, // special station auto refresh + 0, // ifttt enable bits + 0, // sensor 1 type (see SENSOR_TYPE macro defines) + 1, // sensor 1 option. 0: normally closed; 1: normally open. default 1. + 0, // sensor 2 type + 1, // sensor 2 option. 0: normally closed; 1: normally open. default 1. + 0, // sensor 1 on delay + 0, // sensor 1 off delay + 0, // sensor 2 on delay + 0, // sensor 2 off delay + 255,// subnet mask 1 + 255,// subnet mask 2 + 255,// subnet mask 3 + 0, + WIFI_MODE_AP, // wifi mode + 0 // reset +}; + +/** String option values (stored in RAM) */ +const char *OpenSprinkler::sopts[] = { + DEFAULT_PASSWORD, + DEFAULT_LOCATION, + DEFAULT_JAVASCRIPT_URL, + DEFAULT_WEATHER_URL, + DEFAULT_EMPTY_STRING, // SOPT_WEATHER_OPTS + DEFAULT_EMPTY_STRING, // SOPT_IFTTT_KEY + DEFAULT_EMPTY_STRING, // SOPT_STA_SSID + DEFAULT_EMPTY_STRING, // SOPT_STA_PASS + DEFAULT_EMPTY_STRING, // SOPT_MQTT_OPTS + DEFAULT_EMPTY_STRING, // SOPT_OTC_OPTS + DEFAULT_DEVICE_NAME, + DEFAULT_EMPTY_STRING, // SOPT_STA_BSSID_CHL +}; + +/** Weekday strings (stored in PROGMEM to reduce RAM usage) */ +static const char days_str[] PROGMEM = + "Mon\0" + "Tue\0" + "Wed\0" + "Thu\0" + "Fri\0" + "Sat\0" + "Sun\0"; + +/** Calculate local time (UTC time plus time zone offset) */ +time_t OpenSprinkler::now_tz() { + return now()+(int32_t)3600/4*(int32_t)(iopts[IOPT_TIMEZONE]-48); +} + +#if defined(ARDUINO) + +bool detect_i2c(int addr) { + Wire.beginTransmission(addr); + return (Wire.endTransmission()==0); // successful if received 0 +} + +/** read hardware MAC into tmp_buffer */ +#define MAC_CTRL_ID 0x50 +bool OpenSprinkler::load_hardware_mac(byte* buffer, bool wired) { +#if defined(ESP8266) + WiFi.macAddress((byte*)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(byte 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 */ + +byte 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; + } else { + useEth = false; + } + + if((useEth || get_wifi_mode()==WIFI_MODE_STA) && otc.en>0 && otc.token.length()>=32) { + otf = new OTF::OpenThingsFramework(httpport, otc.server, otc.port, otc.token, false, ether_buffer, ETHER_BUFFER_SIZE); + DEBUG_PRINTLN(F("Started OTF with remote connection")); + } else { + otf = new OTF::OpenThingsFramework(httpport, ether_buffer, ETHER_BUFFER_SIZE); + DEBUG_PRINTLN(F("Started OTF with just local connection")); + } + extern DNSServer *dns; + if(get_wifi_mode() == WIFI_MODE_AP) dns = new DNSServer(); + if(update_server) { delete update_server; update_server = NULL; } + update_server = new ESP8266WebServer(8080); + 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 +} + +byte OpenSprinkler::start_ether() { +#if defined(ESP8266) + if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 + + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(4000000); + + load_hardware_mac((uint8_t*)tmp_buffer, true); + if (iopts[IOPT_USE_DHCP]==0) { // config static IP before calling eth.begin + IPAddress staticip(iopts+IOPT_STATIC_IP1); + IPAddress gateway(iopts+IOPT_GATEWAY_IP1); + IPAddress dns(iopts+IOPT_DNS_IP1); + IPAddress subn(iopts+IOPT_SUBNET_MASK1); + eth.config(staticip, gateway, subn, dns); + } + eth.setDefault(); + if(!eth.begin((uint8_t*)tmp_buffer)) return 0; + lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); + + ulong timeout = millis()+45000; // 45 seconds time out + while (!eth.connected()) { + DEBUG_PRINT("."); + delay(1000); + if(millis()>timeout) return 0; + } + + DEBUG_PRINTLN(); + DEBUG_PRINT("eth.ip:"); + DEBUG_PRINTLN(eth.localIP()); + DEBUG_PRINT("eth.dns:"); + DEBUG_PRINTLN(WiFi.dnsIP()); + + if (iopts[IOPT_USE_DHCP]) { + memcpy(iopts+IOPT_STATIC_IP1, &(eth.localIP()[0]), 4); + memcpy(iopts+IOPT_GATEWAY_IP1, &(eth.gatewayIP()[0]),4); + memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); // todo: lwip need dns ip + memcpy(iopts+IOPT_SUBNET_MASK1, &(eth.subnetMask()[0]), 4); + iopts_save(); + } + + return 1; + +#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 true; // todo: lwip currently does not have a way to check link status + else + return (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && state==OS_STATE_CONNECTED); +#else + return (Ethernet.linkStatus()==LinkON); +#endif +} + +/** Reboot controller */ +void OpenSprinkler::reboot_dev(uint8_t cause) { + lcd_print_line_clear_pgm(PSTR("Rebooting..."), 0); + if(cause) { + nvdata.reboot_cause = cause; + nvdata_save(); + } +#if defined(ESP8266) + ESP.restart(); +#else + resetFunc(); +#endif +} + +#else // RPI/BBB/LINUX network init functions + +#include "etherport.h" +#include +#include +#include +#include +#include "utils.h" +#include "opensprinkler_server.h" + +/** Initialize network with the given mac address and http port */ +byte OpenSprinkler::start_network() { + unsigned int port = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; +#if defined(DEMO) +#if defined(HTTP_PORT) + port = HTTP_PORT; +#else + port = 80; +#endif +#endif + if(m_server) { delete m_server; m_server = 0; } + + m_server = new EthernetServer(port); + return m_server->begin(); +} + +bool OpenSprinkler::network_connected(void) { + return true; +} + +// 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(byte* mac, bool wired) { + const char * if_names[] = { "eth0", "eth1", "wlan0", "wlan1" }; + struct ifreq ifr; + int fd; + + // Fallback to asoftware mac if interface not recognised + mac[0] = 0x00; + mac[1] = 0x69; + mac[2] = 0x69; + mac[3] = 0x2D; + mac[4] = 0x31; + mac[5] = iopts[IOPT_DEVICE_ID]; + + if (m_server == NULL) return true; + + if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) return true; + + // Returns the mac address of the first interface if multiple active + for (int i = 0; i < sizeof(if_names)/sizeof(const char *); i++) { + strncpy(ifr.ifr_name, if_names[i], sizeof(ifr.ifr_name)); + if (ioctl(fd, SIOCGIFHWADDR, &ifr) != -1) { + memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); + break; + } + } + close(fd); + return true; +} + +/** Reboot controller */ +void OpenSprinkler::reboot_dev(uint8_t cause) { + nvdata.reboot_cause = cause; + nvdata_save(); +#if defined(DEMO) + // do nothing +#else + sync(); // add sync to prevent file corruption + reboot(RB_AUTOBOOT); +#endif +} + +/** Launch update script */ +void OpenSprinkler::update_dev() { + char cmd[1000]; + sprintf(cmd, "cd %s && ./updater.sh", get_runtime_path()); + system(cmd); +} +#endif // end network init functions + +#if defined(ARDUINO) +/** Initialize LCD */ +void OpenSprinkler::lcd_start() { + +#if defined(ESP8266) + // initialize SSD1306 + lcd.init(); + lcd.begin(); + flash_screen(); +#else + // 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 + + /* check hardware type */ + if(detect_i2c(ACDR_I2CADDR)) hw_type = HW_TYPE_AC; + else if(detect_i2c(DCDR_I2CADDR)) hw_type = HW_TYPE_DC; + else if(detect_i2c(LADR_I2CADDR)) hw_type = HW_TYPE_LATCH; + + /* detect hardware revision type */ + if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists + /* assign revision 0 pins */ + PIN_BUTTON_1 = V0_PIN_BUTTON_1; + PIN_BUTTON_2 = V0_PIN_BUTTON_2; + PIN_BUTTON_3 = V0_PIN_BUTTON_3; + PIN_RFRX = V0_PIN_RFRX; + PIN_RFTX = V0_PIN_RFTX; + PIN_BOOST = V0_PIN_BOOST; + PIN_BOOST_EN = V0_PIN_BOOST_EN; + PIN_SENSOR1 = V0_PIN_SENSOR1; + PIN_SENSOR2 = V0_PIN_SENSOR2; + + // on revision 0, main IOEXP and driver IOEXP are two separate PCF8574 chips + if(hw_type==HW_TYPE_DC) { + drio = new PCF8574(DCDR_I2CADDR); + } else if(hw_type==HW_TYPE_LATCH) { + drio = new PCF8574(LADR_I2CADDR); + } else { + drio = new PCF8574(ACDR_I2CADDR); + } + + mainio = new PCF8574(MAIN_I2CADDR); + mainio->i2c_write(0, 0x0F); // set lower four bits of main PCF8574 (8-ch) to high + + digitalWriteExt(V0_PIN_PWR_TX, 1); // turn on TX power + digitalWriteExt(V0_PIN_PWR_RX, 1); // turn on RX power + pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); + digitalWriteExt(PIN_BOOST, LOW); + digitalWriteExt(PIN_BOOST_EN, LOW); + digitalWriteExt(PIN_LATCH_COM, LOW); + + } else { + + if(hw_type==HW_TYPE_DC) { + drio = new PCA9555(DCDR_I2CADDR); + } else if(hw_type==HW_TYPE_LATCH) { + drio = new PCA9555(LADR_I2CADDR); + } else { + drio = new PCA9555(ACDR_I2CADDR); + } + mainio = drio; + + pinMode(16, INPUT); + if(digitalRead(16)==LOW) { + // revision 1 + hw_rev = 1; + mainio->i2c_write(NXP_CONFIG_REG, V1_IO_CONFIG); + mainio->i2c_write(NXP_OUTPUT_REG, V1_IO_OUTPUT); + + PIN_BUTTON_1 = V1_PIN_BUTTON_1; + PIN_BUTTON_2 = V1_PIN_BUTTON_2; + PIN_BUTTON_3 = V1_PIN_BUTTON_3; + PIN_RFRX = V1_PIN_RFRX; + PIN_RFTX = V1_PIN_RFTX; + PIN_IOEXP_INT = V1_PIN_IOEXP_INT; + PIN_BOOST = V1_PIN_BOOST; + PIN_BOOST_EN = V1_PIN_BOOST_EN; + PIN_LATCH_COM = V1_PIN_LATCH_COM; + PIN_SENSOR1 = V1_PIN_SENSOR1; + PIN_SENSOR2 = V1_PIN_SENSOR2; + } else { + // revision 2 + hw_rev = 2; + mainio->i2c_write(NXP_CONFIG_REG, V2_IO_CONFIG); + mainio->i2c_write(NXP_OUTPUT_REG, V2_IO_OUTPUT); + + PIN_BUTTON_1 = V2_PIN_BUTTON_1; + PIN_BUTTON_2 = V2_PIN_BUTTON_2; + PIN_BUTTON_3 = V2_PIN_BUTTON_3; + PIN_RFTX = V2_PIN_RFTX; + PIN_BOOST = V2_PIN_BOOST; + PIN_BOOST_EN = V2_PIN_BOOST_EN; + PIN_LATCH_COMK = V2_PIN_LATCH_COMK; // os3.2latch uses H-bridge separate cathode and anode design + PIN_LATCH_COMA = V2_PIN_LATCH_COMA; + PIN_SENSOR1 = V2_PIN_SENSOR1; + PIN_SENSOR2 = V2_PIN_SENSOR2; + } + } + + /* detect expanders */ + for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) + 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 + + // Reset all stations + clear_all_station_bits(); + apply_all_station_bits(); + +#if defined(ESP8266) + // OS 3.0 has two independent sensors + pinModeExt(PIN_SENSOR1, INPUT_PULLUP); + pinModeExt(PIN_SENSOR2, INPUT_PULLUP); + +#else + // pull shift register OE low to enable output + 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 + // Static variables are assigned 0 by default + // so only need to initialize non-zero ones + status.enabled = 1; + status.safe_reboot = 0; + + old_status = status; + + nvdata.sunrise_time = 360; // 6:00am default sunrise + nvdata.sunset_time = 1080; // 6:00pm default sunset + nvdata.reboot_cause = REBOOT_CAUSE_POWERON; + + nboards = 1; + nstations = nboards*8; + + // set rf data pin + pinModeExt(PIN_RFTX, OUTPUT); + digitalWriteExt(PIN_RFTX, LOW); + +#if defined(ARDUINO) // AVR SD and LCD functions + + #if defined(ESP8266) // OS3.0 specific detections + + 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); + byte 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 + + lcd_start(); + + #if defined(ESP8266) + lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); + lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); + #else + lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_connected); + lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_disconnected); + #endif + lcd.createChar(ICON_REMOTEXT, _iconimage_remotext); + lcd.createChar(ICON_RAINDELAY, _iconimage_raindelay); + lcd.createChar(ICON_RAIN, _iconimage_rain); + lcd.createChar(ICON_SOIL, _iconimage_soil); + + #if defined(ESP8266) + + /* create custom characters */ + lcd.createChar(ICON_WIFI_CONNECTED, _iconimage_wifi_connected); + lcd.createChar(ICON_WIFI_DISCONNECTED, _iconimage_wifi_disconnected); + + 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){} + } + + #endif + + // set button pins + // enable internal pullup + pinMode(PIN_BUTTON_1, INPUT_PULLUP); + pinMode(PIN_BUTTON_2, INPUT_PULLUP); + pinMode(PIN_BUTTON_3, INPUT_PULLUP); + + // detect and check RTC type + RTC.detect(); + +#else + DEBUG_PRINTLN(get_runtime_path()); +#endif +} + +#if defined(ESP8266) +/** LATCH boost voltage + * + */ +void OpenSprinkler::latch_boost() { + digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter + delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge + digitalWriteExt(PIN_BOOST, LOW); // disable boost converter +} + +/** Set all zones (for LATCH controller) + * This function sets all zone pins (including COM) to a specified value + */ +void OpenSprinkler::latch_setallzonepins(byte value) { + digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin + // Handle driver board (on main controller) + if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board + else reg &= 0xFF00; + drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + // Handle all expansion boards + for(byte i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, value?0xFFFF:0x0000); + } + } +} + +void OpenSprinkler::latch_disable_alloutputs_v2() { + digitalWriteExt(PIN_LATCH_COMA, LOW); + digitalWriteExt(PIN_LATCH_COMK, LOW); + + // latch v2 has a pca9555 the lowest 8 bits of which control all h-bridge anode pins + drio->i2c_write(NXP_OUTPUT_REG, drio->i2c_read(NXP_OUTPUT_REG) & 0xFF00); + // latch v2 has a 74hc595 which controls all h-bridge cathode pins + drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, 0x00); + + // todo: handle expander +} + +/** Set one zone (for LATCH controller) + * This function sets one specified zone pin to a specified value + */ +void OpenSprinkler::latch_setzonepin(byte sid, byte value) { + if(sid<8) { // on main controller + if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + } else { // on expander + byte bid=(sid-8)>>4; + uint16_t s=(sid-8)&0x0F; + if(expanders[bid]->type==IOEXP_TYPE_9555) { + uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + } + } +} + +void OpenSprinkler::latch_setzoneoutput_v2(byte sid, byte A, byte K) { + if(A==HIGH && K==HIGH) return; // A and K must not be HIGH at the same time + + if(sid<8) { // on main controller + // v2 latch driver has one PCA9555, the lowest 8-bits of which control all anode pins + // and one 74HC595, which controls all cathod pins + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); + if(A) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + + drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, K ? (1<>3; + byte s=i&0x07; + byte mask=(byte)1<type==IOEXP_TYPE_8574) { + /* revision 0 uses PCF8574 with active low logic, so all bits must be flipped */ + drio->i2c_write(NXP_OUTPUT_REG, ~station_bits[0]); + } else if(drio->type==IOEXP_TYPE_9555) { + /* revision 1 uses PCA9555 with active high logic */ + uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value + reg = (reg&0xFF00) | station_bits[0]; // output channels are the low 8-bit + drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register + } + + // Handle expansion boards + for(int i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, data); + } else { + expanders[i]->i2c_write(NXP_OUTPUT_REG, ~data); + } + } + } + + byte bid, s, sbits; +#else + digitalWrite(PIN_SR_LATCH, LOW); + byte bid, s, sbits; + + // Shift out all station bit values + // from the highest bit to the lowest + for(bid=0;bid<=MAX_EXT_BOARDS;bid++) { + if (status.enabled) + sbits = station_bits[MAX_EXT_BOARDS-bid]; + else + sbits = 0; + + for(s=0;s<8;s++) { + digitalWrite(PIN_SR_CLOCK, LOW); + #if defined(OSPI) // if OSPI, use dynamically assigned pin_sr_data + digitalWrite(pin_sr_data, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); + #else + digitalWrite(PIN_SR_DATA, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); + #endif + digitalWrite(PIN_SR_CLOCK, HIGH); + } + } + + #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(iopts[IOPT_SPE_AUTO_REFRESH]) { + // handle refresh of RF and remote stations + // we refresh the station that's next in line + static byte next_sid_to_refresh = MAX_NUM_STATIONS>>1; + static byte lastnow = 0; + ulong curr_time = now_tz(); + byte _now = (curr_time & 0xFF); + if (lastnow != _now) { // perform this no more than once per second + lastnow = _now; + next_sid_to_refresh = (next_sid_to_refresh+1) % MAX_NUM_STATIONS; + byte bid=next_sid_to_refresh>>3,s=next_sid_to_refresh&0x07; + if(os.attrib_spe[bid]&(1<>3; + s=next_sid_to_refresh&0x07; + bool on = (station_bits[bid]>>s)&0x01; + uint16_t dur = 0; + if(on) { + byte sqi=pd.station_qid[next_sid_to_refresh]; + RuntimeQueueStruct *q=pd.queue+sqi; + if(sqi<255 && q->st>0 && q->st+q->dur>curr_time) { + dur = q->st+q->dur-curr_time; + } + } + switch_special_station(next_sid_to_refresh, on, dur); + } + } + } +} + +/** Read rain sensor status */ +void OpenSprinkler::detect_binarysensor_status(ulong curr_time) { + // sensor_type: 0 if normally closed, 1 if normally open + if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { + if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + byte val = digitalReadExt(PIN_SENSOR1); + status.sensor1 = (val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1; + if(status.sensor1) { + if(!sensor1_on_timer) { + // add minimum of 5 seconds on delay + ulong delay_time = (ulong)iopts[IOPT_SENSOR1_ON_DELAY]*60; + sensor1_on_timer = curr_time + (delay_time>5?delay_time:5); + sensor1_off_timer = 0; + } else { + if(curr_time > sensor1_on_timer) { + status.sensor1_active = 1; + } + } + } else { + if(!sensor1_off_timer) { + ulong delay_time = (ulong)iopts[IOPT_SENSOR1_OFF_DELAY]*60; + sensor1_off_timer = curr_time + (delay_time>5?delay_time:5); + sensor1_on_timer = 0; + } else { + if(curr_time > sensor1_off_timer) { + status.sensor1_active = 0; + } + } + } + } + +// 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) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 + byte val = digitalReadExt(PIN_SENSOR2); + status.sensor2 = (val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1; + if(status.sensor2) { + if(!sensor2_on_timer) { + // add minimum of 5 seconds on delay + ulong delay_time = (ulong)iopts[IOPT_SENSOR2_ON_DELAY]*60; + sensor2_on_timer = curr_time + (delay_time>5?delay_time:5); + sensor2_off_timer = 0; + } else { + if(curr_time > sensor2_on_timer) { + status.sensor2_active = 1; + } + } + } else { + if(!sensor2_off_timer) { + ulong delay_time = (ulong)iopts[IOPT_SENSOR2_OFF_DELAY]*60; + sensor2_off_timer = curr_time + (delay_time>5?delay_time:5); + sensor2_on_timer = 0; + } else { + if(curr_time > sensor2_off_timer) { + status.sensor2_active = 0; + } + } + } + } + +#endif +} + +/** Return program switch status */ +byte OpenSprinkler::detect_programswitch_status(ulong curr_time) { + byte ret = 0; + if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { + static byte sensor1_hist = 0; + if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + status.sensor1 = (digitalReadExt(PIN_SENSOR1) != iopts[IOPT_SENSOR1_OPTION]); // is switch activated? + sensor1_hist = (sensor1_hist<<1) | status.sensor1; + // basic noise filtering: only trigger if sensor matches pattern: + // i.e. two consecutive lows followed by two consecutive highs + if((sensor1_hist&0b1111) == 0b0011) { + ret |= 0x01; + } + } +#if defined(ESP8266) || defined(PIN_SENSOR2) + if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { + static byte sensor2_hist = 0; + if(hw_rev==2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 + status.sensor2 = (digitalReadExt(PIN_SENSOR2) != iopts[IOPT_SENSOR2_OPTION]); // is sensor activated? + sensor2_hist = (sensor2_hist<<1) | status.sensor2; + if((sensor2_hist&0b1111) == 0b0011) { + ret |= 0x02; + } + } +#endif + return ret; +} + +void OpenSprinkler::sensor_resetall() { + sensor1_on_timer = 0; + sensor1_off_timer = 0; + sensor1_active_lasttime = 0; + sensor2_on_timer = 0; + sensor2_off_timer = 0; + sensor2_active_lasttime = 0; + old_status.sensor1_active = status.sensor1_active = 0; + old_status.sensor2_active = status.sensor2_active = 0; +} + +/** Read current sensing value + * OpenSprinkler 2.3 and above have a 0.2 ohm current sensing resistor. + * Therefore the conversion from analog reading to milli-amp is: + * (r/1024)*3.3*1000/0.2 (DC-powered controller) + * AC-powered controller has a built-in precision rectifier to sense + * the peak AC current. Therefore the actual current is discounted by 0.707 + * ESP8266's analog reference voltage is 1.0 instead of 3.3, therefore + * it's further discounted by 1/3.3 + */ +#if defined(ARDUINO) +uint16_t OpenSprinkler::read_current() { + float scale = 1.0f; + if(status.has_curr_sense) { + if (hw_type == HW_TYPE_DC) { + #if defined(ESP8266) + scale = 4.88; + #else + scale = 16.11; + #endif + } else if (hw_type == HW_TYPE_AC) { + #if defined(ESP8266) + scale = 3.45; + #else + scale = 11.39; + #endif + } else { + scale = 0.0; // for other controllers, current is 0 + } + /* do an average */ + const byte K = 8; + uint16_t sum = 0; + for(byte i=0;i=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 +} + +/** Convert hex code to ulong integer */ +static ulong hex2ulong(byte *code, byte len) { + char c; + ulong v = 0; + for(byte i=0;i='0' && c<='9') { + v += (c-'0'); + } else if (c>='A' && c<='F') { + v += 10 + (c-'A'); + } else if (c>='a' && c<='f') { + v += 10 + (c-'a'); + } else { + return 0; + } + } + return v; +} + +/** Parse RF code into on/off/timeing sections */ +uint16_t OpenSprinkler::parse_rfstation_code(RFStationData *data, ulong* on, ulong *off) { + ulong v; + v = hex2ulong(data->on, sizeof(data->on)); + if (!v) return 0; + if (on) *on = v; + v = hex2ulong(data->off, sizeof(data->off)); + if (!v) return 0; + if (off) *off = v; + v = hex2ulong(data->timing, sizeof(data->timing)); + if (!v) return 0; + return v; +} + +/** Get station data */ +void OpenSprinkler::get_station_data(byte sid, StationData* data) { + file_read_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); +} + +/** Set station data */ +/* +void OpenSprinkler::set_station_data(byte sid, StationData* data) { + file_write_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); +} +*/ + +/** Get station name */ +void OpenSprinkler::get_station_name(byte sid, char tmp[]) { + tmp[STATION_NAME_SIZE]=0; + file_read_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); +} + +/** Set station name */ +void OpenSprinkler::set_station_name(byte sid, char tmp[]) { + // todo: store the right size + tmp[STATION_NAME_SIZE]=0; + char n0[STATION_NAME_SIZE+1]; + get_station_name(sid, n0); + size_t len = strlen(n0); + if(len!=strlen(tmp) || memcmp(n0, tmp, len)!=0) { // only write if the name has changed + file_write_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); + } +} + +/** Get station type */ +byte OpenSprinkler::get_station_type(byte sid) { + return file_read_byte(STATIONS_FILENAME, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type)); +} + +byte OpenSprinkler::is_sequential_station(byte sid) { + return attrib_grp[sid] != PARALLEL_GROUP_ID; +} + +byte OpenSprinkler::is_master_station(byte sid) { + for (byte mas = 0; mas < NUM_MASTER_ZONES; mas++) { + if (get_master_id(mas) && (get_master_id(mas) - 1 == sid)) { + return 1; + } + } + return 0; +} + +byte OpenSprinkler::is_running(byte sid) { + return station_bits[(sid >> 3)] >> (sid & 0x07) & 1; +} + +byte OpenSprinkler::get_master_id(byte mas) { + return masters[mas][MASOPT_SID]; +} + +int16_t OpenSprinkler::get_on_adj(byte mas) { + return water_time_decode_signed(masters[mas][MASOPT_ON_ADJ]); +} + +int16_t OpenSprinkler::get_off_adj(byte mas) { + return water_time_decode_signed(masters[mas][MASOPT_OFF_ADJ]); +} + +byte OpenSprinkler::bound_to_master(byte sid, byte mas) { + byte bid = sid >> 3; + byte s = sid & 0x07; + byte attributes = 0; + + switch (mas) { + case MASTER_1: + attributes= attrib_mas[bid]; + break; + case MASTER_2: + attributes = attrib_mas2[bid]; + break; + default: + break; + } + + return attributes & (1 << s); +} + +byte OpenSprinkler::get_station_gid(byte sid) { + return attrib_grp[sid]; +} + +void OpenSprinkler::set_station_gid(byte sid, byte gid) { + attrib_grp[sid] = gid; +} + +/** Save all station attribs to file (backward compatibility) */ +void OpenSprinkler::attribs_save() { + // re-package attribute bits and save + byte bid, s, sid=0; + StationAttrib at, at0; + memset(&at, 0, sizeof(StationAttrib)); + byte ty = STN_TYPE_STANDARD, ty0; + for(bid=0;bid>s) & 1; + at.igs = (attrib_igs[bid]>>s) & 1; + at.mas2= (attrib_mas2[bid]>>s)& 1; + at.igs2= (attrib_igs2[bid]>>s) & 1; + at.igrd= (attrib_igrd[bid]>>s) & 1; + at.dis = (attrib_dis[bid]>>s) & 1; + at.gid = get_station_gid(sid); + set_station_gid(sid, at.gid); + + // only write if content has changed: this is important for LittleFS as otherwise the overhead is too large + file_read_block(STATIONS_FILENAME, &at0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); + if(memcmp(&at,&at0,sizeof(StationAttrib))!=0) { + file_write_block(STATIONS_FILENAME, &at, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); // attribte bits are 1 byte long + } + if(attrib_spe[bid]>>s==0) { + // if station special bit is 0, make sure to write type STANDARD + // only write if content has changed + file_read_block(STATIONS_FILENAME, &ty0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); + if(ty!=ty0) { + file_write_block(STATIONS_FILENAME, &ty, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); // attribte bits are 1 byte long + } + } + } + } +} + +/** Load all station attribs from file (backward compatibility) */ +void OpenSprinkler::attribs_load() { + // load and re-package attributes + byte bid, s, sid=0; + StationAttrib at; + byte ty; + memset(attrib_mas, 0, nboards); + memset(attrib_igs, 0, nboards); + memset(attrib_mas2, 0, nboards); + memset(attrib_igs2, 0, nboards); + memset(attrib_igrd, 0, nboards); + memset(attrib_dis, 0, nboards); + memset(attrib_spe, 0, nboards); + memset(attrib_grp, 0, MAX_NUM_STATIONS); + + for(bid=0;bid>3,s=sid&0x07; + if(!(os.attrib_spe[bid]&(1<sped, value); + break; + + case STN_TYPE_REMOTE: + switch_remotestation((RemoteStationData *)pdata->sped, value, dur); + break; + + case STN_TYPE_GPIO: + switch_gpiostation((GPIOStationData *)pdata->sped, value); + break; + + case STN_TYPE_HTTP: + switch_httpstation((HTTPStationData *)pdata->sped, value); + break; + } + } +} + +/** Set station bit + * This function sets/resets the corresponding station bit variable + * You have to call apply_all_station_bits next to apply the bits + * (which results in physical actions of opening/closing valves). + */ +byte OpenSprinkler::set_station_bit(byte sid, byte value, uint16_t dur) { + byte *data = station_bits+(sid>>3); // pointer to the station byte + byte mask = (byte)1<<(sid&0x07); // mask + if (value) { + if((*data)&mask) return 0; // if bit is already set, return no change + else { + (*data) = (*data) | mask; + engage_booster = true; // if bit is changing from 0 to 1, set engage_booster + switch_special_station(sid, 1, dur); // handle special stations + return 1; + } + } else { + if(!((*data)&mask)) return 0; // if bit is already reset, return no change + else { + (*data) = (*data) & (~mask); + if(hw_type == HW_TYPE_LATCH) { + engage_booster = true; // if LATCH controller, engage booster when bit changes + } + switch_special_station(sid, 0); // handle special stations + return 255; + } + } + return 0; +} + +/** Clear all station bits */ +void OpenSprinkler::clear_all_station_bits() { + byte sid; + for(sid=0;sid<=MAX_NUM_STATIONS;sid++) { + set_station_bit(sid, 0); + } +} + +#if !defined(ARDUINO) +int rf_gpio_fd = -1; +#endif + +/** Transmit one RF signal bit */ +void transmit_rfbit(ulong lenH, ulong lenL) { +#if defined(ARDUINO) + #if defined(ESP8266) + digitalWrite(PIN_RFTX, 1); + delayMicroseconds(lenH); + digitalWrite(PIN_RFTX, 0); + delayMicroseconds(lenL); + #else + PORT_RF |= (1<=0) { + if ((code>>i) & 1) { + transmit_rfbit(len3, len); + } else { + transmit_rfbit(len, len3); + } + i--; + }; + // send sync + transmit_rfbit(len, len31); + } +} + +/** Switch RF station + * This function takes a RF code, + * parses it into signals and timing, + * and sends it out through RF transmitter. + */ +void OpenSprinkler::switch_rfstation(RFStationData *data, bool turnon) { + ulong on, off; + uint16_t length = parse_rfstation_code(data, &on, &off); +#if defined(ARDUINO) + #if defined(ESP8266) + rfswitch.enableTransmit(PIN_RFTX); + rfswitch.setProtocol(1); + rfswitch.setPulseLength(length); + rfswitch.send(turnon ? on : off, 24); + #else + send_rfsignal(turnon ? on : off, length); + #endif +#else + // pre-open gpio file to minimize overhead + rf_gpio_fd = gpio_fd_open(PIN_RFTX); + send_rfsignal(turnon ? on : off, length); + gpio_fd_close(rf_gpio_fd); + rf_gpio_fd = -1; +#endif + +} + +/** Switch GPIO station + * Special data for GPIO Station is three bytes of ascii decimal (not hex) + * First two bytes are zero padded GPIO pin number. + * Third byte is either 0 or 1 for active low (GND) or high (+5V) relays + */ +void OpenSprinkler::switch_gpiostation(GPIOStationData *data, bool turnon) { + byte gpio = (data->pin[0] - '0') * 10 + (data->pin[1] - '0'); + byte activeState = data->active - '0'; + + pinMode(gpio, OUTPUT); + if (turnon) + digitalWrite(gpio, activeState); + else + digitalWrite(gpio, 1-activeState); +} + +/** Callback function for switching remote station */ +void remote_http_callback(char* buffer) { +/* + DEBUG_PRINTLN(buffer); +*/ +} + +int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { + +#if defined(ARDUINO) + + Client *client; + #if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; + #else + EthernetClient etherClient; + client = ðerClient; + #endif + + #define HTTP_CONNECT_NTRIES 3 + byte tries = 0; + do { + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINT(port); + DEBUG_PRINT("("); + DEBUG_PRINT(tries); + DEBUG_PRINTLN(")"); + + if(client->connect(server, port)==1) break; + tries++; + } while(triesstop(); + return HTTP_RQT_CONNECT_ERR; + } +#else + + EthernetClient etherClient; + EthernetClient *client = ðerClient; + struct hostent *host; + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINTLN(port); + host = gethostbyname(server); + if (!host) { return HTTP_RQT_CONNECT_ERR; } + if(!client->connect((uint8_t*)host->h_addr, port)) { + DEBUG_PRINT(F("failed.")); + client->stop(); + return HTTP_RQT_CONNECT_ERR; + } + +#endif + + uint16_t len = strlen(p); + if(len > ETHER_BUFFER_SIZE) len = ETHER_BUFFER_SIZE; + if(client->connected()) { + client->write((uint8_t *)p, len); + } else { + DEBUG_PRINTLN(F("clint no longer connected")); + } + memset(ether_buffer, 0, ETHER_BUFFER_SIZE); + uint32_t stoptime = millis()+timeout; + + int pos = 0; +#if defined(ARDUINO) + // 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 + while(true) { + int nbytes = client->available(); + if(nbytes>0) { + if(pos+nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE-pos; // cannot read more than buffer size + client->read((uint8_t*)ether_buffer+pos, nbytes); + pos+=nbytes; + } + if(millis()>stoptime) { + DEBUG_PRINTLN(F("host timeout occured")); + //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far + break; + } + if(!client->connected() && !client->available()) { + //DEBUG_PRINTLN(F("host disconnected")); + break; + } + } +#else + while(client->connected()) { + int len=client->read((uint8_t *)ether_buffer+pos, ETHER_BUFFER_SIZE); + if (len<=0) continue; + pos+=len; + if(millis()>stoptime) { + DEBUG_PRINTLN(F("host timeout occured")); + //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far + break; + } + } +#endif + ether_buffer[pos]=0; // properly end buffer with 0 + client->stop(); + if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN; + if(callback) callback(ether_buffer); + return HTTP_RQT_SUCCESS; +} + +int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { + char server[20]; + byte ip[4]; + ip[0] = ip4>>24; + ip[1] = (ip4>>16)&0xff; + ip[2] = (ip4>>8)&0xff; + ip[3] = ip4&0xff; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return send_http_request(server, port, p, callback, timeout); +} + +int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { + char * server = strtok(server_with_port, ":"); + char * port = strtok(NULL, ":"); + return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); +} + +/** Switch remote station + * This function takes a remote station code, + * parses it into remote IP, port, station index, + * and makes a HTTP GET request. + * The remote controller is assumed to have the same + * password as the main controller + */ +void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon, uint16_t dur) { + RemoteStationData copy; + memcpy((char*)©, (char*)data, sizeof(RemoteStationData)); + + uint32_t ip4 = hex2ulong(copy.ip, sizeof(copy.ip)); + uint16_t port = (uint16_t)hex2ulong(copy.port, sizeof(copy.port)); + + byte ip[4]; + ip[0] = ip4>>24; + ip[1] = (ip4>>16)&0xff; + ip[2] = (ip4>>8)&0xff; + ip[3] = ip4&0xff; + + // use tmp_buffer starting at a later location + // because remote station data is loaded at the beginning + char *p = tmp_buffer; + BufferFiller bf = p; + // 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 + // if no auto refresh, we will give the maximum allowed duration, and station will be turned off when off command is sent + uint16_t timer = 0; + if(turnon) { + if(dur>0) { + timer = dur; + } else { + timer = iopts[IOPT_SPE_AUTO_REFRESH]?4*MAX_NUM_STATIONS:64800; + } + } + bf.emit_p(PSTR("GET /cm?pw=$O&sid=$D&en=$D&t=$D"), + SOPT_PASSWORD, + (int)hex2ulong(copy.sid, sizeof(copy.sid)), + turnon, timer); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), + ip[0],ip[1],ip[2],ip[3]); + + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + send_http_request(server, port, p, remote_http_callback); +} + +/** Switch http station + * This function takes an http station code, + * parses it into a server name and two HTTP GET requests. + */ +void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon) { + + HTTPStationData copy; + // make a copy of the HTTP station data and work with it + memcpy((char*)©, (char*)data, sizeof(HTTPStationData)); + char * server = strtok((char *)copy.data, ","); + char * port = strtok(NULL, ","); + char * on_cmd = strtok(NULL, ","); + char * off_cmd = strtok(NULL, ","); + char * cmd = turnon ? on_cmd : off_cmd; + + char *p = tmp_buffer; + BufferFiller bf = p; + + if(cmd==NULL || server==NULL) return; // proceed only if cmd and server are valid + + bf.emit_p(PSTR("GET /$S HTTP/1.0\r\nHOST: $S\r\n\r\n"), cmd, server); + + send_http_request(server, atoi(port), p, remote_http_callback); +} + +/** Prepare factory reset */ +void OpenSprinkler::pre_factory_reset() { + // for ESP8266: wipe out flash + #if defined(ESP8266) + lcd_print_line_clear_pgm(PSTR("Wiping flash.."), 0); + lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); + LittleFS.format(); + #else + // remove 'done' file as an indicator for reset + // todo os2.3 and ospi: delete log files and/or wipe SD card + remove_file(DONE_FILENAME); + #endif +} + +/** Factory reset */ +void OpenSprinkler::factory_reset() { +#if defined(ARDUINO) + lcd_print_line_clear_pgm(PSTR("Factory reset"), 0); + lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); +#else + DEBUG_PRINT("factory reset..."); +#endif + + // 1. reset integer options (by saving default values) + iopts_save(); + // reset string options by first wiping the file clean then write default values + memset(tmp_buffer, 0, MAX_SOPTS_SIZE); + for(int i=0; iname[0]='S'; + pdata->name[3]=0; + pdata->name[4]=0; + StationAttrib at; + memset(&at, 0, sizeof(StationAttrib)); + at.mas=1; + pdata->attrib=at; // mas:1 + pdata->type=STN_TYPE_STANDARD; + pdata->sped[0]='0'; + pdata->sped[1]=0; + for(int i=0; iname[1]='0'+(sid/10); // default station name + pdata->name[2]='0'+(sid%10); + } else { + pdata->name[1]='0'+(sid/100); + pdata->name[2]='0'+((sid%100)/10); + pdata->name[3]='0'+(sid%10); + } + file_write_block(STATIONS_FILENAME, pdata, sizeof(StationData)*i, sizeof(StationData)); + } + + attribs_load(); // load and repackage attrib bits (for backward compatibility) + + // 3. write non-volatile controller status + nvdata.reboot_cause = REBOOT_CAUSE_RESET; + nvdata_save(); + last_reboot_cause = nvdata.reboot_cause; + + // 4. write program data: just need to write a program counter: 0 + file_write_byte(PROG_FILENAME, 0, 0); + + // 5. write 'done' file + file_write_byte(DONE_FILENAME, 0, 1); +} + +#define str(s) #s +#define xstr(s) str(s) + +/** Parse OTC configuration */ +#if defined(ESP8266) +void OpenSprinkler::parse_otc_config() { + char server[MAX_SOPTS_SIZE+1] = {0}; + char token[MAX_SOPTS_SIZE+1] = {0}; + int port = DEFAULT_OTC_PORT; + int en = 0; + + char *config = tmp_buffer; + sopt_load(SOPT_OTC_OPTS, config); + if (*config != 0) { + sscanf(config, "\"en\":%d,\"token\":\"%" xstr(MAX_SOPTS_SIZE) "[^\"]\",\"server\":\"%" xstr(MAX_SOPTS_SIZE) "[^\"]\",\"port\":%d", + &en, token, server, &port); + token[MAX_SOPTS_SIZE] = 0; + server[MAX_SOPTS_SIZE] = 0; + } + otc.en = en; + otc.token = String(token); + otc.server = String(server); + otc.port = 80; +} +#endif + +/** Setup function for options */ +void OpenSprinkler::options_setup() { + + // Check reset conditions: + if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<220 || // fw version is invalid (<219) + !file_exists(DONE_FILENAME)) { // done file doesn't exist + + factory_reset(); + + } else { + + iopts_load(); + nvdata_load(); + last_reboot_cause = nvdata.reboot_cause; + nvdata.reboot_cause = REBOOT_CAUSE_POWERON; + nvdata_save(); + #if defined(ESP8266) + wifi_ssid = sopt_load(SOPT_STA_SSID); + wifi_pass = sopt_load(SOPT_STA_PASS); + sopt_load(SOPT_STA_BSSID_CHL, tmp_buffer); + if(tmp_buffer[0]!=0) { + char *mac = strchr(tmp_buffer, '@'); + if(mac!=NULL && isValidMAC(tmp_buffer)) { // check if bssid is valid MAC + *mac=0; // terminate MAC string + int chl = atoi(mac+1); + if(chl>=0 && chl<=255) { + str2mac(tmp_buffer, wifi_bssid); + wifi_channel = chl; + } + } + } + parse_otc_config(); + #endif + attribs_load(); + } + +#if defined(ARDUINO) // handle AVR buttons + byte button = button_read(BUTTON_WAIT_NONE); + + switch(button & BUTTON_MASK) { + + case BUTTON_1: + // if BUTTON_1 is pressed during startup, go to 'reset all options' + ui_set_options(IOPT_RESET); + if (iopts[IOPT_RESET]) { + pre_factory_reset(); + reboot_dev(REBOOT_CAUSE_RESET); + } + 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); + lcd_print_line_clear_pgm(PSTR(" B3:proceed"), 1); + do { + button = button_read(BUTTON_WAIT_NONE); + } while(!((button&BUTTON_MASK)==BUTTON_3 && (button&BUTTON_FLAG_DOWN))); + // set test mode parameters + + //iopts[IOPT_WIFI_MODE] = WIFI_MODE_STA; + wifi_testmode = 1; + #if defined(TESTMODE_SSID) + wifi_ssid = TESTMODE_SSID; + wifi_pass = TESTMODE_PASS; + #else + wifi_ssid = "ostest"; + wifi_pass = "opendoor"; + #endif + button = 0; + #endif + + break; + + case BUTTON_3: + // if BUTTON_3 is pressed during startup, enter Setup option mode + lcd_print_line_clear_pgm(PSTR("==Set Options=="), 0); + delay(DISPLAY_MSG_MS); + lcd_print_line_clear_pgm(PSTR("B1/B2:+/-, B3:->"), 0); + lcd_print_line_clear_pgm(PSTR("Hold B3 to save"), 1); + do { + button = button_read(BUTTON_WAIT_NONE); + } while (!(button & BUTTON_FLAG_DOWN)); + lcd.clear(); + ui_set_options(0); + if (iopts[IOPT_RESET]) { + pre_factory_reset(); + reboot_dev(REBOOT_CAUSE_RESET); + } + break; + } + + // turn on LCD backlight and contrast + lcd_set_brightness(); + lcd_set_contrast(); + + if (!button) { + // flash screen + lcd_print_line_clear_pgm(PSTR(" OpenSprinkler"),0); + lcd.setCursor((hw_type==HW_TYPE_LATCH)?2:4, 1); + lcd_print_pgm(PSTR("v")); + byte hwv = iopts[IOPT_HW_VERSION]; + lcd.print((char)('0'+(hwv/10))); + lcd.print('.'); + #if defined(ESP8266) + lcd.print(hw_rev); + #else + lcd.print((char)('0'+(hwv%10))); + #endif + switch(hw_type) { + case HW_TYPE_DC: + lcd_print_pgm(PSTR(" DC")); + break; + case HW_TYPE_LATCH: + lcd_print_pgm(PSTR(" LATCH")); + break; + default: + lcd_print_pgm(PSTR(" AC")); + } + delay(1500); + #if defined(ARDUINO) + lcd.setCursor(2, 1); + lcd_print_pgm(PSTR("FW ")); + lcd.print((char)('0'+(OS_FW_VERSION/100))); + lcd.print('.'); + lcd.print((char)('0'+((OS_FW_VERSION/10)%10))); + lcd.print('.'); + lcd.print((char)('0'+(OS_FW_VERSION%10))); + lcd.print('('); + lcd.print(OS_FW_MINOR); + lcd.print(')'); + delay(1000); + #endif + } +#endif +} + +/** Load non-volatile controller status data from file */ +void OpenSprinkler::nvdata_load() { + file_read_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); + old_status = status; +} + +/** Save non-volatile controller status data */ +void OpenSprinkler::nvdata_save() { + file_write_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); +} + +void load_wt_monthly(char* wto); + +/** Load integer options from file */ +void OpenSprinkler::iopts_load() { + file_read_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); + nboards = iopts[IOPT_EXT_BOARDS]+1; + nstations = nboards * 8; + status.enabled = iopts[IOPT_DEVICE_ENABLE]; + iopts[IOPT_FW_VERSION] = OS_FW_VERSION; + iopts[IOPT_FW_MINOR] = OS_FW_MINOR; + /* Reject the former default 50.97.210.169 NTP IP address as + * it no longer works, yet is carried on by people's saved + * configs when they upgrade from older versions. + * IOPT_NTP_IP1 = 0 leads to the new good default behavior. */ + if (iopts[IOPT_NTP_IP1] == 50 && iopts[IOPT_NTP_IP2] == 97 && + iopts[IOPT_NTP_IP3] == 210 && iopts[IOPT_NTP_IP4] == 169) { + iopts[IOPT_NTP_IP1] = 0; + iopts[IOPT_NTP_IP2] = 0; + iopts[IOPT_NTP_IP3] = 0; + iopts[IOPT_NTP_IP4] = 0; + } + populate_master(); + sopt_load(SOPT_WEATHER_OPTS, tmp_buffer); + if(iopts[IOPT_USE_WEATHER]==WEATHER_METHOD_MONTHLY) { + load_wt_monthly(tmp_buffer); + } +} + +void OpenSprinkler::populate_master() { + masters[MASTER_1][MASOPT_SID] = iopts[IOPT_MASTER_STATION]; + masters[MASTER_1][MASOPT_ON_ADJ] = iopts[IOPT_MASTER_ON_ADJ]; + masters[MASTER_1][MASOPT_OFF_ADJ] = iopts[IOPT_MASTER_OFF_ADJ]; + + 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]; +} + +/** Save integer options to file */ +void OpenSprinkler::iopts_save() { + file_write_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); + nboards = iopts[IOPT_EXT_BOARDS]+1; + nstations = nboards * 8; + status.enabled = iopts[IOPT_DEVICE_ENABLE]; +} + +/** Load a string option from file */ +void OpenSprinkler::sopt_load(byte oid, char *buf) { + file_read_block(SOPTS_FILENAME, buf, MAX_SOPTS_SIZE*oid, MAX_SOPTS_SIZE); + buf[MAX_SOPTS_SIZE]=0; // ensure the string ends properly +} + +/** Load a string option from file, return String */ +String OpenSprinkler::sopt_load(byte oid) { + sopt_load(oid, tmp_buffer); + String str = tmp_buffer; + return str; +} + +/** Save a string option to file */ +bool OpenSprinkler::sopt_save(byte oid, const char *buf) { + // smart save: if value hasn't changed, don't write + if(file_cmp_block(SOPTS_FILENAME, buf, (ulong)MAX_SOPTS_SIZE*oid)==0) return false; + int len = strlen(buf); + if(len>=MAX_SOPTS_SIZE) { + file_write_block(SOPTS_FILENAME, buf, (ulong)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); + } + return true; +} + +// ============================== +// Controller Operation Functions +// ============================== + +/** Enable controller operation */ +void OpenSprinkler::enable() { + status.enabled = 1; + iopts[IOPT_DEVICE_ENABLE] = 1; + iopts_save(); +} + +/** Disable controller operation */ +void OpenSprinkler::disable() { + status.enabled = 0; + iopts[IOPT_DEVICE_ENABLE] = 0; + iopts_save(); +} + +/** Start rain delay */ +void OpenSprinkler::raindelay_start() { + status.rain_delayed = 1; + nvdata_save(); +} + +/** Stop rain delay */ +void OpenSprinkler::raindelay_stop() { + status.rain_delayed = 0; + nvdata.rd_stop_time = 0; + nvdata_save(); +} + +/** LCD and button functions */ +#if defined(ARDUINO) // AVR 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); + } +} + +/** print a program memory string to a given line with clearing */ +#if defined(ESP8266) +void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P str, byte line) { +#else +void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P PROGMEM str, byte line) { +#endif + lcd.setCursor(0, line); + uint8_t c; + int8_t cnt = 0; + while((c=pgm_read_byte(str++))!= '\0') { + lcd.print((char)c); + cnt++; + } + for(; (16-cnt) >= 0; cnt ++) lcd_print_pgm(PSTR(" ")); +} + +void OpenSprinkler::lcd_print_2digit(int v) +{ + lcd.print((int)(v/10)); + lcd.print((int)(v%10)); +} + +/** print time to a given line */ +void OpenSprinkler::lcd_print_time(time_t t) +{ +#if defined(ESP8266) + 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_2digit(month(t)); + lcd_print_pgm(PSTR("-")); + lcd_print_2digit(day(t)); +#if defined(ESP8266) + lcd.display(); + lcd.setAutoDisplay(true); +#endif +} + +/** print ip address */ +void OpenSprinkler::lcd_print_ip(const byte *ip, byte endian) { +#if defined(ESP8266) + lcd.clear(0, 1); +#else + lcd.clear(); +#endif + lcd.setCursor(0, 0); + for (byte i=0; i<4; i++) { + lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); + if(i<3) lcd_print_pgm(PSTR(".")); + } +} + +/** print mac address */ +void OpenSprinkler::lcd_print_mac(const byte *mac) { + lcd.setCursor(0, 0); + for(byte i=0; i<6; i++) { + if(i) lcd_print_pgm(PSTR("-")); + lcd.print((mac[i]>>4), HEX); + lcd.print((mac[i]&0x0F), HEX); + if(i==4) lcd.setCursor(0, 1); + } + if(useEth) { + lcd_print_pgm(PSTR(" (Ether MAC)")); + } else { + lcd_print_pgm(PSTR(" (WiFi MAC)")); + } +} + +/** print station bits */ +void OpenSprinkler::lcd_print_screen(char c) { +#if defined(ESP8266) + 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' + } else { + lcd.print(F("E")); + lcd.print((int)status.display_board); + lcd.print(F(":")); // extension boards are displayed as E1, E2... + } + if (!status.enabled) { + lcd.print(F("-Disabled!-")); + } else { + byte bitvalue = station_bits[status.display_board]; + for (byte s=0; s<8; s++) { + byte sid = (byte)status.display_board<<3; + sid += (s+1); + if (sid == iopts[IOPT_MASTER_STATION]) { + 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 { + lcd.print((bitvalue&1) ? c : '_'); + } + bitvalue >>= 1; + } + } + //lcd.print(F(" ")); + + lcd.setCursor(LCD_CURSOR_REMOTEXT, 1); + lcd.write(iopts[IOPT_REMOTE_EXT_MODE]?ICON_REMOTEXT:' '); + + lcd.setCursor(LCD_CURSOR_RAINDELAY, 1); + lcd.write((status.rain_delayed || status.pause_state)?ICON_RAINDELAY:' '); + + // write sensor 1 icon + lcd.setCursor(LCD_CURSOR_SENSOR1, 1); + switch(iopts[IOPT_SENSOR1_TYPE]) { + case SENSOR_TYPE_RAIN: + lcd.write(status.sensor1_active?ICON_RAIN:(status.sensor1?'R':'r')); + break; + case SENSOR_TYPE_SOIL: + lcd.write(status.sensor1_active?ICON_SOIL:(status.sensor1?'S':'s')); + break; + case SENSOR_TYPE_FLOW: + lcd.write(flowcount_rt>0?'F':'f'); + break; + case SENSOR_TYPE_PSWITCH: + lcd.write(status.sensor1?'P':'p'); + break; + default: + lcd.write(' '); + break; + } + + // write sensor 2 icon + lcd.setCursor(LCD_CURSOR_SENSOR2, 1); + switch(iopts[IOPT_SENSOR2_TYPE]) { + case SENSOR_TYPE_RAIN: + lcd.write(status.sensor2_active?ICON_RAIN:(status.sensor2?'R':'r')); + break; + case SENSOR_TYPE_SOIL: + lcd.write(status.sensor2_active?ICON_SOIL:(status.sensor2?'S':'s')); + break; + // sensor2 cannot be flow sensor + /*case SENSOR_TYPE_FLOW: + lcd.write('F'); + break;*/ + case SENSOR_TYPE_PSWITCH: + lcd.write(status.sensor2?'Q':'q'); + break; + default: + lcd.write(' '); + break; + } + + lcd.setCursor(LCD_CURSOR_NETWORK, 1); +#if defined(ESP8266) + if(useEth) { + lcd.write(eth.connected()?ICON_ETHER_CONNECTED:ICON_ETHER_DISCONNECTED); // todo: need to detect ether status + } + else + lcd.write(WiFi.status()==WL_CONNECTED?ICON_WIFI_CONNECTED:ICON_WIFI_DISCONNECTED); +#else + 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(ESP8266) + + if(useEth || (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP())) { + lcd.setCursor(0, -1); + if(status.rain_delayed) { + lcd.print(F(" ")); + } else if(status.pause_state) { + lcd.print(F("")); + } else if(status.program_busy) { + lcd.print(F(" ")); + } else { + lcd.print(F(" (System Idle) ")); + } + + lcd.setCursor(2, 2); + if(status.program_busy && !status.pause_state) { + //lcd.print(F("Curr: ")); + lcd.print(read_current()); + lcd.print(F(" mA ")); + } else { + lcd.clear(2, 2); + } + } +#endif +#if defined(ESP8266) + lcd.display(); + lcd.setAutoDisplay(true); +#endif +} + +/** print a version number */ +void OpenSprinkler::lcd_print_version(byte v) { + if(v > 99) { + lcd.print(v/100); + lcd.print("."); + } + if(v>9) { + lcd.print((v/10)%10); + lcd.print("."); + } + lcd.print(v%10); +} + +/** print an option value */ +void OpenSprinkler::lcd_print_option(int i) { + // each prompt string takes 16 characters + strncpy_P0(tmp_buffer, iopt_prompts+16*i, 16); + lcd.setCursor(0, 0); + lcd.print(tmp_buffer); + lcd_print_line_clear_pgm(PSTR(""), 1); + lcd.setCursor(0, 1); + int tz; + switch(i) { + case IOPT_HW_VERSION: + lcd.print("v"); + case IOPT_FW_VERSION: + lcd_print_version(iopts[i]); + break; + case IOPT_TIMEZONE: // if this is the time zone option, do some conversion + tz = (int)iopts[i]-48; + if (tz>=0) lcd_print_pgm(PSTR("+")); + else {lcd_print_pgm(PSTR("-")); tz=-tz;} + lcd.print(tz/4); // print integer portion + lcd_print_pgm(PSTR(":")); + tz = (tz%4)*15; + if (tz==0) lcd_print_pgm(PSTR("00")); + else { + lcd.print(tz); // print fractional portion + } + break; + case IOPT_MASTER_ON_ADJ: + case IOPT_MASTER_ON_ADJ_2: + case IOPT_MASTER_OFF_ADJ: + case IOPT_MASTER_OFF_ADJ_2: + case IOPT_STATION_DELAY_TIME: + { + int16_t t=water_time_decode_signed(iopts[i]); + if(t>=0) lcd_print_pgm(PSTR("+")); + lcd.print(t); + } + break; + case IOPT_HTTPPORT_0: + lcd.print((unsigned int)(iopts[i+1]<<8)+iopts[i]); + break; + case IOPT_PULSE_RATE_0: + { + uint16_t fpr = (unsigned int)(iopts[i+1]<<8)+iopts[i]; + lcd.print(fpr/100); + lcd_print_pgm(PSTR(".")); + lcd.print((fpr/10)%10); + lcd.print(fpr%10); + } + break; + case IOPT_LCD_CONTRAST: + lcd_set_contrast(); + lcd.print((int)iopts[i]); + break; + case IOPT_LCD_BACKLIGHT: + lcd_set_brightness(); + lcd.print((int)iopts[i]); + break; + case IOPT_BOOST_TIME: + #if defined(ARDUINO) + if(hw_type==HW_TYPE_AC) { + lcd.print('-'); + } else { + lcd.print((int)iopts[i]*4); + lcd_print_pgm(PSTR(" ms")); + } + #else + lcd.print('-'); + #endif + break; + default: + // if this is a boolean option + if (pgm_read_byte(iopt_max+i)==1) + lcd_print_pgm(iopts[i] ? PSTR("Yes") : PSTR("No")); + else + lcd.print((int)iopts[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) + lcd_print_pgm(PSTR(" sec")); + +} + +/** Button functions */ +/** wait for button */ +byte OpenSprinkler::button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding) { + + int hold_time = 0; + + if (waitmode==BUTTON_WAIT_NONE || (waitmode == BUTTON_WAIT_HOLD && is_holding)) { + if (digitalReadExt(pin_butt) != 0) return BUTTON_NONE; + return butt | (is_holding ? BUTTON_FLAG_HOLD : 0); + } + + while (digitalReadExt(pin_butt) == 0 && + (waitmode == BUTTON_WAIT_RELEASE || (waitmode == BUTTON_WAIT_HOLD && hold_time= BUTTON_HOLD_MS) + butt |= BUTTON_FLAG_HOLD; + return butt; + +} + +/** read button and returns button value 'OR'ed with flag bits */ +byte OpenSprinkler::button_read(byte waitmode) +{ + static byte old = BUTTON_NONE; + byte curr = BUTTON_NONE; + byte is_holding = (old&BUTTON_FLAG_HOLD); + + delay(BUTTON_DELAY_MS); + + if (digitalReadExt(PIN_BUTTON_1) == 0) { + curr = button_read_busy(PIN_BUTTON_1, waitmode, BUTTON_1, is_holding); + } else if (digitalReadExt(PIN_BUTTON_2) == 0) { + curr = button_read_busy(PIN_BUTTON_2, waitmode, BUTTON_2, is_holding); + } else if (digitalReadExt(PIN_BUTTON_3) == 0) { + curr = button_read_busy(PIN_BUTTON_3, waitmode, BUTTON_3, is_holding); + } + + // set flags in return value + byte ret = curr; + if (!(old&BUTTON_MASK) && (curr&BUTTON_MASK)) + ret |= BUTTON_FLAG_DOWN; + if ((old&BUTTON_MASK) && !(curr&BUTTON_MASK)) + ret |= BUTTON_FLAG_UP; + + old = curr; + + return ret; +} + +/** user interface for setting options during startup */ +void OpenSprinkler::ui_set_options(int oid) +{ + boolean finished = false; + byte button; + int i=oid; + + while(!finished) { + button = button_read(BUTTON_WAIT_HOLD); + + switch (button & BUTTON_MASK) { + case BUTTON_1: + if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || + i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || + i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || + i==IOPT_WIFI_MODE) break; // ignore non-editable options + if (pgm_read_byte(iopt_max+i) != iopts[i]) iopts[i] ++; + break; + + case BUTTON_2: + if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || + i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || + i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || + i==IOPT_WIFI_MODE) break; // ignore non-editable options + if (iopts[i] != 0) iopts[i] --; + break; + + case BUTTON_3: + if (!(button & BUTTON_FLAG_DOWN)) break; + if (button & BUTTON_FLAG_HOLD) { + // long press, save options + iopts_save(); + finished = true; + } + else { + // click, move to the next option + 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 { + i = (i+1) % NUM_IOPTS; + } + if(i==IOPT_SEQUENTIAL_RETIRED) i++; + if(i==IOPT_URS_RETIRED) i++; + if(i==IOPT_RSO_RETIRED) i++; + if (hw_type==HW_TYPE_AC && i==IOPT_BOOST_TIME) i++; // skip boost time for non-DC controller + #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; + } + + if (button != BUTTON_NONE) { + lcd_print_option(i); + } + } + lcd.noBlink(); +} + +/** 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(byte value) { +#if defined(PIN_LCD_BACKLIGHT) + #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + 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(ESP8266) + 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 +} +#endif // end of LCD and button functions + +#if defined(ESP8266) +#include "images.h" +void OpenSprinkler::flash_screen() { + lcd.setCursor(0, -1); + lcd.print(F(" OpenSprinkler")); + lcd.drawXbm(34, 24, WiFi_Logo_width, WiFi_Logo_height, (const byte*) WiFi_Logo_image); + lcd.setCursor(0, 2); + lcd.display(); + delay(1500); + lcd.clear(); + lcd.display(); +} + +void OpenSprinkler::toggle_screen_led() { + static byte status = 0; + status = 1-status; + set_screen_led(!status); +} + +void OpenSprinkler::set_screen_led(byte status) { + lcd.setColor(status ? WHITE : BLACK); + lcd.fillCircle(122, 58, 4); + lcd.display(); + lcd.setColor(WHITE); +} + +void OpenSprinkler::reset_to_ap() { + iopts[IOPT_WIFI_MODE] = WIFI_MODE_AP; + iopts_save(); + reboot_dev(REBOOT_CAUSE_RSTAP); +} + +void OpenSprinkler::config_ip() { + if(iopts[IOPT_USE_DHCP] == 0) { + byte *_ip = iopts+IOPT_STATIC_IP1; + IPAddress dvip(_ip[0], _ip[1], _ip[2], _ip[3]); + if(dvip==(uint32_t)0x00000000) return; + + _ip = iopts+IOPT_GATEWAY_IP1; + IPAddress gwip(_ip[0], _ip[1], _ip[2], _ip[3]); + if(gwip==(uint32_t)0x00000000) return; + + _ip = iopts+IOPT_SUBNET_MASK1; + IPAddress subn(_ip[0], _ip[1], _ip[2], _ip[3]); + if(subn==(uint32_t)0x00000000) return; + + _ip = iopts+IOPT_DNS_IP1; + IPAddress dnsip(_ip[0], _ip[1], _ip[2], _ip[3]); + + WiFi.config(dvip, gwip, subn, dnsip); + } +} + +void OpenSprinkler::save_wifi_ip() { + // todo: handle wired ethernet + if(iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED) { + memcpy(iopts+IOPT_STATIC_IP1, &(WiFi.localIP()[0]), 4); + memcpy(iopts+IOPT_GATEWAY_IP1, &(WiFi.gatewayIP()[0]),4); + memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); + memcpy(iopts+IOPT_SUBNET_MASK1, &(WiFi.subnetMask()[0]), 4); + iopts_save(); + } +} + +void OpenSprinkler::detect_expanders() { + for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) { + byte address = EXP_I2CADDR_BASE+i; + byte type = IOEXP::detectType(address); + if(expanders[i]!=NULL) delete expanders[i]; + if(type==IOEXP_TYPE_9555) { + expanders[i] = new PCA9555(address); + expanders[i]->i2c_write(NXP_CONFIG_REG, 0); // set all channels to output + } else if(type==IOEXP_TYPE_8575){ + expanders[i] = new PCF8575(address); + } else { + expanders[i] = new IOEXP(address); + } + } +} +#endif diff --git a/defines.h b/defines.h index e383208e5..f6a46bcbe 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned char byte; typedef unsigned long ulong; diff --git a/lib-patch/enc28j60.cpp b/lib-patch/enc28j60.cpp new file mode 100644 index 000000000..1154890f9 --- /dev/null +++ b/lib-patch/enc28j60.cpp @@ -0,0 +1,871 @@ +/* + Copyright (c) 2012-2013, Thingsquare, http://www.thingsquare.com/. + Copyright (c) 2016, Nicholas Humfrey + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +// original sources: https://github.com/njh/EtherSia/tree/master/src/enc28j60.cpp + +#include +#include + +#include +#include +#include +#include + +#include "enc28j60.h" + + +void serial_printf(const char *fmt, ...) +{ + char buf[128]; + va_list args; + va_start(args, fmt); + vsnprintf(buf, 128, fmt, args); + va_end(args); + Serial.print(buf); +} + +#define DEBUG 0 +#if DEBUG + #define PRINTF(...) printf(__VA_ARGS__) +#else + #define PRINTF(...) do { (void)0; } while (0) +#endif + +#define EIE 0x1b +#define EIR 0x1c +#define ESTAT 0x1d +#define ECON2 0x1e +#define ECON1 0x1f + +#define ESTAT_CLKRDY 0x01 +#define ESTAT_TXABRT 0x02 + +#define ECON1_RXEN 0x04 +#define ECON1_TXRTS 0x08 + +#define ECON2_AUTOINC 0x80 +#define ECON2_PKTDEC 0x40 + +#define EIR_TXIF 0x08 + +#define ERXTX_BANK 0x00 + +#define ERDPTL 0x00 +#define ERDPTH 0x01 +#define EWRPTL 0x02 +#define EWRPTH 0x03 +#define ETXSTL 0x04 +#define ETXSTH 0x05 +#define ETXNDL 0x06 +#define ETXNDH 0x07 +#define ERXSTL 0x08 +#define ERXSTH 0x09 +#define ERXNDL 0x0a +#define ERXNDH 0x0b +#define ERXRDPTL 0x0c +#define ERXRDPTH 0x0d + +#define RX_BUF_START 0x0000 +#define RX_BUF_END 0x0fff + +#define TX_BUF_START 0x1200 + +/* MACONx registers are in bank 2 */ +#define MACONX_BANK 0x02 + +#define MACON1 0x00 +#define MACON3 0x02 +#define MACON4 0x03 +#define MABBIPG 0x04 +#define MAIPGL 0x06 +#define MAIPGH 0x07 +#define MAMXFLL 0x0a +#define MAMXFLH 0x0b + +#define MACON1_TXPAUS 0x08 +#define MACON1_RXPAUS 0x04 +#define MACON1_MARXEN 0x01 + +#define MACON3_PADCFG_FULL 0xe0 +#define MACON3_TXCRCEN 0x10 +#define MACON3_FRMLNEN 0x02 +#define MACON3_FULDPX 0x01 + +#define MAX_MAC_LENGTH 1518 + +#define MAADRX_BANK 0x03 +#define MAADR1 0x04 /* MAADR<47:40> */ +#define MAADR2 0x05 /* MAADR<39:32> */ +#define MAADR3 0x02 /* MAADR<31:24> */ +#define MAADR4 0x03 /* MAADR<23:16> */ +#define MAADR5 0x00 /* MAADR<15:8> */ +#define MAADR6 0x01 /* MAADR<7:0> */ +#define MISTAT 0x0a +#define EREVID 0x12 + +#define EPKTCNT_BANK 0x01 +#define ERXFCON 0x18 +#define EPKTCNT 0x19 + +#define ERXFCON_UCEN 0x80 +#define ERXFCON_ANDOR 0x40 +#define ERXFCON_CRCEN 0x20 +#define ERXFCON_MCEN 0x02 +#define ERXFCON_BCEN 0x01 + +#define REG_BANK_0 0x00 +#define REG_BANK_1 0x01 +#define REG_BANK_2 0x02 +#define REG_BANK_3 0x03 + +// for PHY registers! +#define MICMD 0x12 +#define MIREGADR 0x14 +#define MIWRL 0x16 +#define MIWRH 0x17 +#define MIRDL 0x18 +#define MIRDH 0x19 + +// PHY registers +#define PHCON1 0x00 +#define PHSTAT1 0x01 +#define PHHID1 0x02 +#define PHHID2 0x03 +#define PHCON2 0x10 +#define PHSTAT2 0x11 +#define PHIE 0x12 +#define PHIR 0x13 +#define PHLCON 0x14 + +// The ENC28J60 SPI Interface supports clock speeds up to 20 MHz +static const SPISettings spiSettings(20000000, MSBFIRST, SPI_MODE0); + +ENC28J60::ENC28J60(int8_t cs, SPIClass& spi, int8_t intr): + _bank(ERXTX_BANK), _cs(cs), _spi(spi) +{ + (void)intr; +} + +void +ENC28J60::enc28j60_arch_spi_select(void) +{ + SPI.beginTransaction(spiSettings); + digitalWrite(_cs, LOW); +} + +void +ENC28J60::enc28j60_arch_spi_deselect(void) +{ + digitalWrite(_cs, HIGH); + SPI.endTransaction(); +} + + +/*---------------------------------------------------------------------------*/ +uint8_t +ENC28J60::is_mac_mii_reg(uint8_t reg) +{ + /* MAC or MII register (otherwise, ETH register)? */ + switch (_bank) + { + case MACONX_BANK: + return reg < EIE; + case MAADRX_BANK: + return reg <= MAADR2 || reg == MISTAT; + case ERXTX_BANK: + case EPKTCNT_BANK: + default: + return 0; + } +} +/*---------------------------------------------------------------------------*/ +uint8_t +ENC28J60::readreg(uint8_t reg) +{ + uint8_t r; + enc28j60_arch_spi_select(); + SPI.transfer(0x00 | (reg & 0x1f)); + if (is_mac_mii_reg(reg)) + { + /* MAC and MII registers require that a dummy byte be read first. */ + SPI.transfer(0); + } + r = SPI.transfer(0); + enc28j60_arch_spi_deselect(); + return r; +} +/*---------------------------------------------------------------------------*/ +void +ENC28J60::writereg(uint8_t reg, uint8_t data) +{ + enc28j60_arch_spi_select(); + SPI.transfer(0x40 | (reg & 0x1f)); + SPI.transfer(data); + enc28j60_arch_spi_deselect(); +} +/*---------------------------------------------------------------------------*/ +void +ENC28J60::setregbitfield(uint8_t reg, uint8_t mask) +{ + if (is_mac_mii_reg(reg)) + { + writereg(reg, readreg(reg) | mask); + } + else + { + enc28j60_arch_spi_select(); + SPI.transfer(0x80 | (reg & 0x1f)); + SPI.transfer(mask); + enc28j60_arch_spi_deselect(); + } +} +/*---------------------------------------------------------------------------*/ +void +ENC28J60::clearregbitfield(uint8_t reg, uint8_t mask) +{ + if (is_mac_mii_reg(reg)) + { + writereg(reg, readreg(reg) & ~mask); + } + else + { + enc28j60_arch_spi_select(); + SPI.transfer(0xa0 | (reg & 0x1f)); + SPI.transfer(mask); + enc28j60_arch_spi_deselect(); + } +} +/*---------------------------------------------------------------------------*/ +void +ENC28J60::setregbank(uint8_t new_bank) +{ + writereg(ECON1, (readreg(ECON1) & 0xfc) | (new_bank & 0x03)); + _bank = new_bank; +} +/*---------------------------------------------------------------------------*/ +void +ENC28J60::writedata(const uint8_t *data, int datalen) +{ + int i; + enc28j60_arch_spi_select(); + /* The Write Buffer Memory (WBM) command is 0 1 1 1 1 0 1 0 */ + SPI.transfer(0x7a); + for (i = 0; i < datalen; i++) + { + SPI.transfer(data[i]); + } + enc28j60_arch_spi_deselect(); +} +/*---------------------------------------------------------------------------*/ +void +ENC28J60::writedatabyte(uint8_t byte) +{ + writedata(&byte, 1); +} +/*---------------------------------------------------------------------------*/ +int +ENC28J60::readdata(uint8_t *buf, int len) +{ + int i; + enc28j60_arch_spi_select(); + /* THe Read Buffer Memory (RBM) command is 0 0 1 1 1 0 1 0 */ + SPI.transfer(0x3a); + for (i = 0; i < len; i++) + { + buf[i] = SPI.transfer(0); + } + enc28j60_arch_spi_deselect(); + return i; +} +/*---------------------------------------------------------------------------*/ +uint8_t +ENC28J60::readdatabyte(void) +{ + uint8_t r; + readdata(&r, 1); + return r; +} + +/*---------------------------------------------------------------------------*/ +void +ENC28J60::softreset(void) +{ + enc28j60_arch_spi_select(); + /* The System Command (soft reset) is 1 1 1 1 1 1 1 1 */ + SPI.transfer(0xff); + enc28j60_arch_spi_deselect(); + _bank = ERXTX_BANK; +} + +/*---------------------------------------------------------------------------*/ +//#if DEBUG +uint8_t +ENC28J60::readrev(void) +{ + uint8_t rev; + setregbank(MAADRX_BANK); + rev = readreg(EREVID); + switch (rev) + { + case 2: + return 1; + case 6: + return 7; + default: + return rev; + } +} +//#endif + +/*---------------------------------------------------------------------------*/ + +bool +ENC28J60::reset(void) +{ + PRINTF("enc28j60: resetting chip\n"); + + pinMode(_cs, OUTPUT); + digitalWrite(_cs, HIGH); + SPI.begin(); + + /* + 6.0 INITIALIZATION + + Before the ENC28J60 can be used to transmit and receive packets, + certain device settings must be initialized. Depending on the + application, some configuration options may need to be + changed. Normally, these tasks may be accomplished once after + Reset and do not need to be changed thereafter. + + 6.1 Receive Buffer + + Before receiving any packets, the receive buffer must be + initialized by programming the ERXST and ERXND pointers. All + memory between and including the ERXST and ERXND addresses will be + dedicated to the receive hardware. It is recommended that the + ERXST pointer be programmed with an even address. + + Applications expecting large amounts of data and frequent packet + delivery may wish to allocate most of the memory as the receive + buffer. Applications that may need to save older packets or have + several packets ready for transmission should allocate less + memory. + + When programming the ERXST pointer, the ERXWRPT registers will + automatically be updated with the same values. The address in + ERXWRPT will be used as the starting location when the receive + hardware begins writing received data. For tracking purposes, the + ERXRDPT registers should additionally be programmed with the same + value. To program ERXRDPT, the host controller must write to + ERXRDPTL first, followed by ERXRDPTH. See Section 7.2.4 “Freeing + Receive Buffer Space for more information + + 6.2 Transmission Buffer + + All memory which is not used by the receive buffer is considered + the transmission buffer. Data which is to be transmitted should be + written into any unused space. After a packet is transmitted, + however, the hardware will write a seven-byte status vector into + memory after the last byte in the packet. Therefore, the host + controller should leave at least seven bytes between each packet + and the beginning of the receive buffer. No explicit action is + required to initialize the transmission buffer. + + 6.3 Receive Filters + + The appropriate receive filters should be enabled or disabled by + writing to the ERXFCON register. See Section 8.0 “Receive Filters + for information on how to configure it. + + 6.4 Waiting For OST + + If the initialization procedure is being executed immediately + following a Power-on Reset, the ESTAT.CLKRDY bit should be polled + to make certain that enough time has elapsed before proceeding to + modify the MAC and PHY registers. For more information on the OST, + see Section 2.2 “Oscillator Start-up Timer. + */ + + softreset(); + + /* Workaround for erratum #2. */ + delayMicroseconds(1000); + + /* Wait for OST */ + PRINTF("waiting for ESTAT_CLKRDY\n"); + while ((readreg(ESTAT) & ESTAT_CLKRDY) == 0) {}; + PRINTF("ESTAT_CLKRDY\n"); + + setregbank(ERXTX_BANK); + /* Set up receive buffer */ + writereg(ERXSTL, RX_BUF_START & 0xff); + writereg(ERXSTH, RX_BUF_START >> 8); + writereg(ERXNDL, RX_BUF_END & 0xff); + writereg(ERXNDH, RX_BUF_END >> 8); + writereg(ERDPTL, RX_BUF_START & 0xff); + writereg(ERDPTH, RX_BUF_START >> 8); + writereg(ERXRDPTL, RX_BUF_END & 0xff); + writereg(ERXRDPTH, RX_BUF_END >> 8); + + /* Receive filters */ + setregbank(EPKTCNT_BANK); + writereg(ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_MCEN); + + /* + 6.5 MAC Initialization Settings + + Several of the MAC registers require configuration during + initialization. This only needs to be done once; the order of + programming is unimportant. + + 1. Set the MARXEN bit in MACON1 to enable the MAC to receive + frames. If using full duplex, most applications should also set + TXPAUS and RXPAUS to allow IEEE defined flow control to function. + + 2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3. Most + applications should enable automatic padding to at least 60 bytes + and always append a valid CRC. For convenience, many applications + may wish to set the FRMLNEN bit as well to enable frame length + status reporting. The FULDPX bit should be set if the application + will be connected to a full-duplex configured remote node; + otherwise, it should be left clear. + + 3. Configure the bits in MACON4. For conformance to the IEEE 802.3 + standard, set the DEFER bit. + + 4. Program the MAMXFL registers with the maximum frame length to + be permitted to be received or transmitted. Normal network nodes + are designed to handle packets that are 1518 bytes or less. + + 5. Configure the Back-to-Back Inter-Packet Gap register, + MABBIPG. Most applications will program this register with 15h + when Full-Duplex mode is used and 12h when Half-Duplex mode is + used. + + 6. Configure the Non-Back-to-Back Inter-Packet Gap register low + byte, MAIPGL. Most applications will program this register with + 12h. + + 7. If half duplex is used, the Non-Back-to-Back Inter-Packet Gap + register high byte, MAIPGH, should be programmed. Most + applications will program this register to 0Ch. + + 8. If Half-Duplex mode is used, program the Retransmission and + Collision Window registers, MACLCON1 and MACLCON2. Most + applications will not need to change the default Reset values. If + the network is spread over exceptionally long cables, the default + value of MACLCON2 may need to be increased. + + 9. Program the local MAC address into the MAADR1:MAADR6 registers. + */ + + setregbank(MACONX_BANK); + + /* Turn on reception and IEEE-defined flow control */ + setregbitfield(MACON1, MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS); + + /* Set padding, crc, full duplex */ + setregbitfield(MACON3, MACON3_PADCFG_FULL | MACON3_TXCRCEN | MACON3_FULDPX | + MACON3_FRMLNEN); + + /* Don't modify MACON4 */ + + /* Set maximum frame length in MAMXFL */ + writereg(MAMXFLL, MAX_MAC_LENGTH & 0xff); + writereg(MAMXFLH, MAX_MAC_LENGTH >> 8); + + /* Set back-to-back inter packet gap */ + writereg(MABBIPG, 0x15); + + /* Set non-back-to-back packet gap */ + writereg(MAIPGL, 0x12); + + /* Set MAC address */ + setregbank(MAADRX_BANK); + writereg(MAADR6, _localMac[5]); + writereg(MAADR5, _localMac[4]); + writereg(MAADR4, _localMac[3]); + writereg(MAADR3, _localMac[2]); + writereg(MAADR2, _localMac[1]); + writereg(MAADR1, _localMac[0]); + + /* + 6.6 PHY Initialization Settings + + Depending on the application, bits in three of the PHY module’s + registers may also require configuration. The PHCON1.PDPXMD bit + partially controls the device’s half/full-duplex + configuration. Normally, this bit is initialized correctly by the + external circuitry (see Section 2.6 “LED Configuration). If the + external circuitry is not present or incorrect, however, the host + controller must program the bit properly. Alternatively, for an + externally configurable system, the PDPXMD bit may be read and the + FULDPX bit be programmed to match. + + For proper duplex operation, the PHCON1.PDPXMD bit must also match + the value of the MACON3.FULDPX bit. + + If using half duplex, the host controller may wish to set the + PHCON2.HDLDIS bit to prevent automatic loopback of the data which + is transmitted. The PHY register, PHLCON, controls the outputs of + LEDA and LEDB. If an application requires a LED configuration + other than the default, PHLCON must be altered to match the new + requirements. The settings for LED operation are discussed in + Section 2.6 “LED Configuration. The PHLCON register is shown in + Register 2-2 (page 9). + */ + + /* Don't worry about PHY configuration for now */ + + /* Turn on autoincrement for buffer access */ + setregbitfield(ECON2, ECON2_AUTOINC); + + /* Turn on reception */ + writereg(ECON1, ECON1_RXEN); + + return true; +} +/*---------------------------------------------------------------------------*/ +boolean +ENC28J60::begin(const uint8_t *address) +{ + _localMac = address; + + bool ret = reset(); + uint8_t rev = readrev(); + + PRINTF("ENC28J60 rev. B%d\n", rev); + + return ret && rev != 255; +} + +/*---------------------------------------------------------------------------*/ + +uint16_t +ENC28J60::sendFrame(const uint8_t *data, uint16_t datalen) +{ + uint16_t dataend; + + /* + 1. Appropriately program the ETXST pointer to point to an unused + location in memory. It will point to the per packet control + byte. In the example, it would be programmed to 0120h. It is + recommended that an even address be used for ETXST. + + 2. Use the WBM SPI command to write the per packet control byte, + the destination address, the source MAC address, the + type/length and the data payload. + + 3. Appropriately program the ETXND pointer. It should point to the + last byte in the data payload. In the example, it would be + programmed to 0156h. + + 4. Clear EIR.TXIF, set EIE.TXIE and set EIE.INTIE to enable an + interrupt when done (if desired). + + 5. Start the transmission process by setting + ECON1.TXRTS. + */ + + setregbank(ERXTX_BANK); + /* Set up the transmit buffer pointer */ + writereg(ETXSTL, TX_BUF_START & 0xff); + writereg(ETXSTH, TX_BUF_START >> 8); + writereg(EWRPTL, TX_BUF_START & 0xff); + writereg(EWRPTH, TX_BUF_START >> 8); + + /* Write the transmission control register as the first byte of the + output packet. We write 0x00 to indicate that the default + configuration (the values in MACON3) will be used. */ + writedatabyte(0x00); /* MACON3 */ + + writedata(data, datalen); + + /* Write a pointer to the last data byte. */ + dataend = TX_BUF_START + datalen; + writereg(ETXNDL, dataend & 0xff); + writereg(ETXNDH, dataend >> 8); + + /* Clear EIR.TXIF */ + clearregbitfield(EIR, EIR_TXIF); + + /* Don't care about interrupts for now */ + + /* Send the packet */ + setregbitfield(ECON1, ECON1_TXRTS); + while ((readreg(ECON1) & ECON1_TXRTS) > 0); + +#if DEBUG + if ((readreg(ESTAT) & ESTAT_TXABRT) != 0) + { + uint16_t erdpt; + uint8_t tsv[7]; + erdpt = (readreg(ERDPTH) << 8) | readreg(ERDPTL); + writereg(ERDPTL, (dataend + 1) & 0xff); + writereg(ERDPTH, (dataend + 1) >> 8); + readdata(tsv, sizeof(tsv)); + writereg(ERDPTL, erdpt & 0xff); + writereg(ERDPTH, erdpt >> 8); + PRINTF("enc28j60: tx err: %d: %02x:%02x:%02x:%02x:%02x:%02x\n" + " tsv: %02x%02x%02x%02x%02x%02x%02x\n", datalen, + 0xff & data[0], 0xff & data[1], 0xff & data[2], + 0xff & data[3], 0xff & data[4], 0xff & data[5], + tsv[6], tsv[5], tsv[4], tsv[3], tsv[2], tsv[1], tsv[0]); + } + else + { + PRINTF("enc28j60: tx: %d: %02x:%02x:%02x:%02x:%02x:%02x\n", datalen, + 0xff & data[0], 0xff & data[1], 0xff & data[2], + 0xff & data[3], 0xff & data[4], 0xff & data[5]); + } +#endif + + //sent_packets++; + //PRINTF("enc28j60: sent_packets %d\n", sent_packets); + return datalen; +} + +/*---------------------------------------------------------------------------*/ + +uint16_t +ENC28J60::readFrame(uint8_t *buffer, uint16_t bufsize) +{ + readFrameSize(); + return readFrameData(buffer, bufsize); +} + +uint16_t +ENC28J60::readFrameSize() +{ + uint8_t n; + + uint8_t nxtpkt[2]; + uint8_t status[2]; + uint8_t length[2]; + + setregbank(EPKTCNT_BANK); + n = readreg(EPKTCNT); + + if (n == 0) + { + return 0; + } + + PRINTF("enc28j60: EPKTCNT 0x%02x\n", n); + + setregbank(ERXTX_BANK); + /* Read the next packet pointer */ + nxtpkt[0] = readdatabyte(); + nxtpkt[1] = readdatabyte(); + _next = (nxtpkt[1] << 8) + nxtpkt[0]; + + PRINTF("enc28j60: nxtpkt 0x%02x%02x\n", _nxtpkt[1], _nxtpkt[0]); + + length[0] = readdatabyte(); + length[1] = readdatabyte(); + _len = (length[1] << 8) + length[0]; + + PRINTF("enc28j60: length 0x%02x%02x\n", length[1], length[0]); + + status[0] = readdatabyte(); + status[1] = readdatabyte(); + + /* This statement is just to avoid a compiler warning: */ + (void)status[0]; + PRINTF("enc28j60: status 0x%02x%02x\n", status[1], status[0]); + + return _len; +} + +void +ENC28J60::discardFrame(uint16_t framesize) +{ + (void)framesize; + (void)readFrameData(nullptr, 0); +} + +uint16_t +ENC28J60::readFrameData(uint8_t *buffer, uint16_t framesize) +{ + if (framesize < _len) + { + + buffer = nullptr; + + /* flush rx fifo */ + for (uint16_t i = 0; i < _len; i++) + { + readdatabyte(); + } + } + else + { + readdata(buffer, _len); + } + + /* Read an additional byte at odd lengths, to avoid FIFO corruption */ + if ((_len % 2) != 0) + { + readdatabyte(); + } + + /* Errata #14 */ + if (_next == RX_BUF_START) + { + _next = RX_BUF_END; + } + else + { + _next = _next - 1; + } + writereg(ERXRDPTL, _next & 0xff); + writereg(ERXRDPTH, _next >> 8); + + setregbitfield(ECON2, ECON2_PKTDEC); + + if (!buffer) + { + PRINTF("enc28j60: rx err: flushed %d\n", _len); + return 0; + } + PRINTF("enc28j60: rx: %d: %02x:%02x:%02x:%02x:%02x:%02x\n", _len, + 0xff & buffer[0], 0xff & buffer[1], 0xff & buffer[2], + 0xff & buffer[3], 0xff & buffer[4], 0xff & buffer[5]); + + //received_packets++; + //PRINTF("enc28j60: received_packets %d\n", received_packets); + + return _len; +} + +/*---------------------------------------------------------------------------*/ +uint16_t +ENC28J60::phyRead(uint8_t address) +{ + unsigned int timeout = 15; + + setregbank(REG_BANK_2); + + // set the PHY register address + writereg(MIREGADR, address); + setregbitfield(MICMD, 0x01); // MII Read Enable [MIIRD] + + setregbank(REG_BANK_3); + + // wait until the PHY read completes + while(readreg(MISTAT) & 0x01){ + delay(10); + wdt_reset(); + if(--timeout ==0) return 0; + } + + setregbank(REG_BANK_2); + + clearregbitfield(MICMD, 0x01); // MII Read Disable [MIIRD] + + // read the PHY data + return (readreg(MIRDL) | readreg(MIRDH) << 8); +} + +void +ENC28J60::phyWrite(uint8_t address, uint16_t data) +{ + unsigned int timeout = 15; + + setregbank(REG_BANK_2); + + // set the PHY register address + writereg(MIREGADR, address); + + // write the PHY data + writereg(MIWRL, data & 0xff); + writereg(MIWRH, data >> 8); + + setregbank(REG_BANK_3); + + // wait until the PHY write completes + while(readreg(MISTAT) & 0x01){ + delay(10); + wdt_reset(); + if(--timeout ==0) return; + } +} + +//-------------------------------------------------------- +void +ENC28J60::init(void) +{ + pinMode(_cs, OUTPUT); + digitalWrite(_cs, HIGH); + SPI.begin(); +} + +void +ENC28J60::LEDsConfig(LEDScfg_CONFIG LEDAcfg, LEDScfg_CONFIG LEDBcfg) +{ + uint16_t temp = phyRead(PHLCON); + + if(LEDAcfg) temp &= LEDA_MASK; + if(LEDBcfg) temp &= LEDB_MASK; + + phyWrite(PHLCON, (temp |(uint16_t)(LEDAcfg <<4) |LEDBcfg |0x3006)); +} + +//-------------------------------------------------------- +bool +ENC28J60::resetEther(void) +{ + return reset(); +} + +uint8_t +ENC28J60::hardwareStatus(void) +{ + uint8_t rev; + setregbank(MAADRX_BANK); + rev = readreg(EREVID); /* 0xFF = Hardware Error */ + switch (rev) + { + case 2: + return 1; + case 6: + return 7; + default: + return rev; + } +} + +bool +ENC28J60::linkStatus(void) +{ + return (phyRead(PHSTAT2) & 0x0400); +} diff --git a/lib-patch/enc28j60.h b/lib-patch/enc28j60.h new file mode 100644 index 000000000..efad69bb2 --- /dev/null +++ b/lib-patch/enc28j60.h @@ -0,0 +1,189 @@ +/** + Header file for direct Ethernet frame access to the ENC28J60 controller + @file enc28j60.h +*/ + +/* + Copyright (c) 2012-2013, Thingsquare, http://www.thingsquare.com/. + All rights reserved. + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + OF THE POSSIBILITY OF SUCH DAMAGE. + +*/ + +// original sources: https://github.com/njh/EtherSia/tree/master/src/enc28j60.h + +#ifndef ENC28J60_H +#define ENC28J60_H + +#include + +// PHLCON: PHY module LED control register +// +// R R R R L L L L L L L L R +// E E E E A A A A B B B B L L S E +// S S S S C C C C C C C C F F T S +// E E E E F F F F F F F F R R R E +// R R R R G G G G G G G G Q Q C R +// V V V V 3 2 1 0 3 2 1 0 1 0 H V + +typedef enum { + LED_NO_CHANGE = 0x00, + LED_TRANSMIT = 0x10, + LED_RECEIVE = 0x20, + LED_COLLISION = 0x30, + LED_LINK_STATUS = 0x40, + LED_DUPLEX_STATUS = 0x50, + LED_TRANS_RECV = 0x70, + LED_BLINK_ON = 0x80, + LED_BLINK_OFF = 0x90, + LED_BLINK_FAST = 0xA0, + LED_BLINK_SLOW = 0xB0, + LED_LINK_RXD_STATUS = 0xC0, + LED_LINK_TXD_RXD_STATUS = 0xD0, + LED_DPLX_COLL_STATUS = 0xE0, + + LEDA_MASK = 0xF0F0, + LEDB_MASK = 0xFF00 +} LEDScfg_CONFIG; + +/** + Send and receive Ethernet frames directly using a ENC28J60 controller. +*/ +class ENC28J60 +{ + +public: + /** + Constructor that uses the default hardware SPI pins + @param cs the Arduino Chip Select / Slave Select pin (default 10 on Uno) + */ + ENC28J60(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1); + + /** + Initialise the Ethernet controller + Must be called before sending or receiving Ethernet frames + + @param address the local MAC address for the Ethernet interface + @return Returns true if setting up the Ethernet interface was successful + */ + boolean begin(const uint8_t *address); + + /** + Send an Ethernet frame + @param data a pointer to the data to send + @param datalen the length of the data in the packet + @return the number of bytes transmitted + */ + virtual uint16_t sendFrame(const uint8_t *data, uint16_t datalen); + + /** + Read an Ethernet frame + @param buffer a pointer to a buffer to write the packet to + @param bufsize the available space in the buffer + @return the length of the received packet + or 0 if no packet was received + */ + virtual uint16_t readFrame(uint8_t *buffer, uint16_t bufsize); + + /******/ + void init(void); + void LEDsConfig(LEDScfg_CONFIG LEDAcfg, LEDScfg_CONFIG LEDBcfg); + + bool resetEther(void); + uint8_t hardwareStatus(void); + bool linkStatus(void); + uint8_t readreg(uint8_t reg); + +protected: + + static constexpr bool interruptIsPossible() + { + return false; + } + + /** + Read an Ethernet frame size + @return the length of data do receive + or 0 if no frame was received + */ + uint16_t readFrameSize(); + + /** + discard an Ethernet frame + @param framesize readFrameSize()'s result + */ + void discardFrame(uint16_t framesize); + + /** + Read an Ethernet frame data + readFrameSize() must be called first, + its result must be passed into framesize parameter + @param buffer a pointer to a buffer to write the frame to + @param framesize readFrameSize()'s result + @return the length of the received frame + or 0 if a problem occurred + */ + uint16_t readFrameData(uint8_t *frame, uint16_t framesize); + +private: + + uint8_t is_mac_mii_reg(uint8_t reg); + void writereg(uint8_t reg, uint8_t data); + void setregbitfield(uint8_t reg, uint8_t mask); + void clearregbitfield(uint8_t reg, uint8_t mask); + void setregbank(uint8_t new_bank); + void writedata(const uint8_t *data, int datalen); + void writedatabyte(uint8_t byte); + int readdata(uint8_t *buf, int len); + uint8_t readdatabyte(void); + void softreset(void); + uint8_t readrev(void); + bool reset(void); + + uint16_t phyRead(uint8_t address); + void phyWrite(uint8_t address, uint16_t data); + + void enc28j60_arch_spi_init(void); + uint8_t enc28j60_arch_spi_write(uint8_t data); + uint8_t enc28j60_arch_spi_read(void); + void enc28j60_arch_spi_select(void); + void enc28j60_arch_spi_deselect(void); + + // Previously defined in contiki/core/sys/clock.h + void clock_delay_usec(uint16_t dt); + + uint8_t _bank; + int8_t _cs; + SPIClass& _spi; + + const uint8_t *_localMac; + + /* readFrame*() state */ + uint16_t _next, _len; +}; + +#endif /* ENC28J60_H */ diff --git a/lib-patch/readme.txt b/lib-patch/readme.txt new file mode 100644 index 000000000..e69de29bb diff --git a/main.cpp b/main.cpp index 7a8a0dc82..bae406191 100644 --- a/main.cpp +++ b/main.cpp @@ -916,6 +916,8 @@ void do_loop() // skip if this is the master station if (mas_id == sid + 1) continue; + if(pd.station_qid[sid]==255) continue; // skip if station is not in the queue + q = pd.queue + pd.station_qid[sid]; if (os.bound_to_master(q->sid, mas)) { @@ -2026,8 +2028,9 @@ void check_network() { #define NET_ENC28J60_EIR_RXERIF 0x01 #define NET_ENC28J60_ESTAT_BUFFER 0x40 #define NET_ENC28J60_ECON1_RXEN 0x04 -bool check_enc28j60() -{ + +bool check_enc28j60() { + uint8_t stateEconRxen = eth.readreg((uint8_t) NET_ENC28J60_ECON1) & NET_ENC28J60_ECON1_RXEN; // ESTAT.BUFFER rised on TX or RX error // I think the test of this register is not necessary - EIR.RXERIF state checking may be enough diff --git a/platformio.ini b/platformio.ini index a136569a9..615670892 100644 --- a/platformio.ini +++ b/platformio.ini @@ -17,6 +17,7 @@ include_dir = . [env:d1_mini] platform = espressif8266 +;platform = https://github.com/platformio/platform-espressif8266.git board = d1_mini framework = arduino lib_ldf_mode = deep From a09e04931a0f50da9a1ae33a126aa0373d5141e1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 11 Jun 2023 00:41:15 +0200 Subject: [PATCH 066/281] Changed version to 128 --- defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defines.h b/defines.h index f6a46bcbe..ee79124cc 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 127 // Firmware minor version +#define OS_FW_MINOR 128 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler From d818bd353fdeaf18a27df4cdf93392b23dbb0f45 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 21 Jul 2023 23:41:54 +0200 Subject: [PATCH 067/281] (129) No fallback to wifi if ethernet is connected --- OpenSprinkler.cpp | 2 +- defines.h | 2 +- main.cpp | 6 ++++-- platformio.ini | 5 +++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 73f9426f5..23398b133 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -523,7 +523,7 @@ byte OpenSprinkler::start_ether() { while (!eth.connected()) { DEBUG_PRINT("."); delay(1000); - if(millis()>timeout) return 0; + if(millis()>timeout) break; } DEBUG_PRINTLN(); diff --git a/defines.h b/defines.h index ee79124cc..fb3ab8cbe 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 128 // Firmware minor version +#define OS_FW_MINOR 129 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index bae406191..8cac45920 100644 --- a/main.cpp +++ b/main.cpp @@ -993,7 +993,7 @@ void do_loop() if (os.iopts[IOPT_USE_DHCP]) dhcp_renew(intf); - if (dhcp_timeout > 0 && !check_enc28j60()) { //ENC28J60 REGISTER CHECK!! +/** if (dhcp_timeout > 0 && !check_enc28j60()) { //ENC28J60 REGISTER CHECK!! DEBUG_PRINT(F("Reconnect")); //eth.resetEther(); @@ -1008,7 +1008,7 @@ void do_loop() os.nvdata.reboot_cause = REBOOT_CAUSE_NETWORK_FAIL; os.status.safe_reboot = 1; } - } + }*/ } //else if (os.iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED && os.get_wifi_mode()==WIFI_MODE_STA) { // netif* intf = eagle_lwip_getif(STATION_IF); @@ -2020,6 +2020,7 @@ void check_network() { #endif } +/** #if defined(ESP8266) #define NET_ENC28J60_EIR 0x1C @@ -2044,6 +2045,7 @@ bool check_enc28j60() { return true; } #endif +*/ /** Perform NTP sync */ void perform_ntp_sync() { diff --git a/platformio.ini b/platformio.ini index 615670892..866d13da0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,12 +16,13 @@ src_dir = . include_dir = . [env:d1_mini] -platform = espressif8266 +platform = espressif8266@4.0.1 ;platform = https://github.com/platformio/platform-espressif8266.git board = d1_mini framework = arduino +;lib_ldf_mode = chain lib_ldf_mode = deep -lib_deps = +lib_deps = sui77/rc-switch @ ^2.6.3 https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 From e716a65f499e80e5d230af97df41b3a9041fbba8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 4 Aug 2023 00:38:23 +0200 Subject: [PATCH 068/281] Version 130: Added support for feature flag - needed for standard OpenSprinkler App --- OpenSprinkler.cpp | 11 +- defines.h | 2 +- lib-patch/enc28j60.cpp | 871 --------------------------------------- lib-patch/enc28j60.h | 189 --------- lib-patch/readme.txt | 0 opensprinkler_server.cpp | 22 +- opensprinkler_server.h | 201 ++++----- platformio.ini | 2 +- sensors.cpp | 104 +++++ sensors.h | 18 + 10 files changed, 249 insertions(+), 1171 deletions(-) delete mode 100644 lib-patch/enc28j60.cpp delete mode 100644 lib-patch/enc28j60.h delete mode 100644 lib-patch/readme.txt diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 23398b133..79e477d2a 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -865,9 +865,11 @@ void OpenSprinkler::begin() { nboards = 1; nstations = nboards*8; - // set rf data pin - pinModeExt(PIN_RFTX, OUTPUT); - digitalWriteExt(PIN_RFTX, LOW); + // set rf data pin, unless it is not being used + if(PIN_RFTX != 255) { + pinModeExt(PIN_RFTX, OUTPUT); + digitalWriteExt(PIN_RFTX, LOW); + } #if defined(ARDUINO) // AVR SD and LCD functions @@ -1753,6 +1755,9 @@ void send_rfsignal(ulong code, ulong len) { void OpenSprinkler::switch_rfstation(RFStationData *data, bool turnon) { ulong on, off; uint16_t length = parse_rfstation_code(data, &on, &off); + + if(PIN_RFTX == 255) return; // ignore RF station if RF pin disabled + #if defined(ARDUINO) #if defined(ESP8266) rfswitch.enableTransmit(PIN_RFTX); diff --git a/defines.h b/defines.h index fb3ab8cbe..3ebb5c1ec 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 129 // Firmware minor version +#define OS_FW_MINOR 130 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/lib-patch/enc28j60.cpp b/lib-patch/enc28j60.cpp deleted file mode 100644 index 1154890f9..000000000 --- a/lib-patch/enc28j60.cpp +++ /dev/null @@ -1,871 +0,0 @@ -/* - Copyright (c) 2012-2013, Thingsquare, http://www.thingsquare.com/. - Copyright (c) 2016, Nicholas Humfrey - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - OF THE POSSIBILITY OF SUCH DAMAGE. - -*/ - -// original sources: https://github.com/njh/EtherSia/tree/master/src/enc28j60.cpp - -#include -#include - -#include -#include -#include -#include - -#include "enc28j60.h" - - -void serial_printf(const char *fmt, ...) -{ - char buf[128]; - va_list args; - va_start(args, fmt); - vsnprintf(buf, 128, fmt, args); - va_end(args); - Serial.print(buf); -} - -#define DEBUG 0 -#if DEBUG - #define PRINTF(...) printf(__VA_ARGS__) -#else - #define PRINTF(...) do { (void)0; } while (0) -#endif - -#define EIE 0x1b -#define EIR 0x1c -#define ESTAT 0x1d -#define ECON2 0x1e -#define ECON1 0x1f - -#define ESTAT_CLKRDY 0x01 -#define ESTAT_TXABRT 0x02 - -#define ECON1_RXEN 0x04 -#define ECON1_TXRTS 0x08 - -#define ECON2_AUTOINC 0x80 -#define ECON2_PKTDEC 0x40 - -#define EIR_TXIF 0x08 - -#define ERXTX_BANK 0x00 - -#define ERDPTL 0x00 -#define ERDPTH 0x01 -#define EWRPTL 0x02 -#define EWRPTH 0x03 -#define ETXSTL 0x04 -#define ETXSTH 0x05 -#define ETXNDL 0x06 -#define ETXNDH 0x07 -#define ERXSTL 0x08 -#define ERXSTH 0x09 -#define ERXNDL 0x0a -#define ERXNDH 0x0b -#define ERXRDPTL 0x0c -#define ERXRDPTH 0x0d - -#define RX_BUF_START 0x0000 -#define RX_BUF_END 0x0fff - -#define TX_BUF_START 0x1200 - -/* MACONx registers are in bank 2 */ -#define MACONX_BANK 0x02 - -#define MACON1 0x00 -#define MACON3 0x02 -#define MACON4 0x03 -#define MABBIPG 0x04 -#define MAIPGL 0x06 -#define MAIPGH 0x07 -#define MAMXFLL 0x0a -#define MAMXFLH 0x0b - -#define MACON1_TXPAUS 0x08 -#define MACON1_RXPAUS 0x04 -#define MACON1_MARXEN 0x01 - -#define MACON3_PADCFG_FULL 0xe0 -#define MACON3_TXCRCEN 0x10 -#define MACON3_FRMLNEN 0x02 -#define MACON3_FULDPX 0x01 - -#define MAX_MAC_LENGTH 1518 - -#define MAADRX_BANK 0x03 -#define MAADR1 0x04 /* MAADR<47:40> */ -#define MAADR2 0x05 /* MAADR<39:32> */ -#define MAADR3 0x02 /* MAADR<31:24> */ -#define MAADR4 0x03 /* MAADR<23:16> */ -#define MAADR5 0x00 /* MAADR<15:8> */ -#define MAADR6 0x01 /* MAADR<7:0> */ -#define MISTAT 0x0a -#define EREVID 0x12 - -#define EPKTCNT_BANK 0x01 -#define ERXFCON 0x18 -#define EPKTCNT 0x19 - -#define ERXFCON_UCEN 0x80 -#define ERXFCON_ANDOR 0x40 -#define ERXFCON_CRCEN 0x20 -#define ERXFCON_MCEN 0x02 -#define ERXFCON_BCEN 0x01 - -#define REG_BANK_0 0x00 -#define REG_BANK_1 0x01 -#define REG_BANK_2 0x02 -#define REG_BANK_3 0x03 - -// for PHY registers! -#define MICMD 0x12 -#define MIREGADR 0x14 -#define MIWRL 0x16 -#define MIWRH 0x17 -#define MIRDL 0x18 -#define MIRDH 0x19 - -// PHY registers -#define PHCON1 0x00 -#define PHSTAT1 0x01 -#define PHHID1 0x02 -#define PHHID2 0x03 -#define PHCON2 0x10 -#define PHSTAT2 0x11 -#define PHIE 0x12 -#define PHIR 0x13 -#define PHLCON 0x14 - -// The ENC28J60 SPI Interface supports clock speeds up to 20 MHz -static const SPISettings spiSettings(20000000, MSBFIRST, SPI_MODE0); - -ENC28J60::ENC28J60(int8_t cs, SPIClass& spi, int8_t intr): - _bank(ERXTX_BANK), _cs(cs), _spi(spi) -{ - (void)intr; -} - -void -ENC28J60::enc28j60_arch_spi_select(void) -{ - SPI.beginTransaction(spiSettings); - digitalWrite(_cs, LOW); -} - -void -ENC28J60::enc28j60_arch_spi_deselect(void) -{ - digitalWrite(_cs, HIGH); - SPI.endTransaction(); -} - - -/*---------------------------------------------------------------------------*/ -uint8_t -ENC28J60::is_mac_mii_reg(uint8_t reg) -{ - /* MAC or MII register (otherwise, ETH register)? */ - switch (_bank) - { - case MACONX_BANK: - return reg < EIE; - case MAADRX_BANK: - return reg <= MAADR2 || reg == MISTAT; - case ERXTX_BANK: - case EPKTCNT_BANK: - default: - return 0; - } -} -/*---------------------------------------------------------------------------*/ -uint8_t -ENC28J60::readreg(uint8_t reg) -{ - uint8_t r; - enc28j60_arch_spi_select(); - SPI.transfer(0x00 | (reg & 0x1f)); - if (is_mac_mii_reg(reg)) - { - /* MAC and MII registers require that a dummy byte be read first. */ - SPI.transfer(0); - } - r = SPI.transfer(0); - enc28j60_arch_spi_deselect(); - return r; -} -/*---------------------------------------------------------------------------*/ -void -ENC28J60::writereg(uint8_t reg, uint8_t data) -{ - enc28j60_arch_spi_select(); - SPI.transfer(0x40 | (reg & 0x1f)); - SPI.transfer(data); - enc28j60_arch_spi_deselect(); -} -/*---------------------------------------------------------------------------*/ -void -ENC28J60::setregbitfield(uint8_t reg, uint8_t mask) -{ - if (is_mac_mii_reg(reg)) - { - writereg(reg, readreg(reg) | mask); - } - else - { - enc28j60_arch_spi_select(); - SPI.transfer(0x80 | (reg & 0x1f)); - SPI.transfer(mask); - enc28j60_arch_spi_deselect(); - } -} -/*---------------------------------------------------------------------------*/ -void -ENC28J60::clearregbitfield(uint8_t reg, uint8_t mask) -{ - if (is_mac_mii_reg(reg)) - { - writereg(reg, readreg(reg) & ~mask); - } - else - { - enc28j60_arch_spi_select(); - SPI.transfer(0xa0 | (reg & 0x1f)); - SPI.transfer(mask); - enc28j60_arch_spi_deselect(); - } -} -/*---------------------------------------------------------------------------*/ -void -ENC28J60::setregbank(uint8_t new_bank) -{ - writereg(ECON1, (readreg(ECON1) & 0xfc) | (new_bank & 0x03)); - _bank = new_bank; -} -/*---------------------------------------------------------------------------*/ -void -ENC28J60::writedata(const uint8_t *data, int datalen) -{ - int i; - enc28j60_arch_spi_select(); - /* The Write Buffer Memory (WBM) command is 0 1 1 1 1 0 1 0 */ - SPI.transfer(0x7a); - for (i = 0; i < datalen; i++) - { - SPI.transfer(data[i]); - } - enc28j60_arch_spi_deselect(); -} -/*---------------------------------------------------------------------------*/ -void -ENC28J60::writedatabyte(uint8_t byte) -{ - writedata(&byte, 1); -} -/*---------------------------------------------------------------------------*/ -int -ENC28J60::readdata(uint8_t *buf, int len) -{ - int i; - enc28j60_arch_spi_select(); - /* THe Read Buffer Memory (RBM) command is 0 0 1 1 1 0 1 0 */ - SPI.transfer(0x3a); - for (i = 0; i < len; i++) - { - buf[i] = SPI.transfer(0); - } - enc28j60_arch_spi_deselect(); - return i; -} -/*---------------------------------------------------------------------------*/ -uint8_t -ENC28J60::readdatabyte(void) -{ - uint8_t r; - readdata(&r, 1); - return r; -} - -/*---------------------------------------------------------------------------*/ -void -ENC28J60::softreset(void) -{ - enc28j60_arch_spi_select(); - /* The System Command (soft reset) is 1 1 1 1 1 1 1 1 */ - SPI.transfer(0xff); - enc28j60_arch_spi_deselect(); - _bank = ERXTX_BANK; -} - -/*---------------------------------------------------------------------------*/ -//#if DEBUG -uint8_t -ENC28J60::readrev(void) -{ - uint8_t rev; - setregbank(MAADRX_BANK); - rev = readreg(EREVID); - switch (rev) - { - case 2: - return 1; - case 6: - return 7; - default: - return rev; - } -} -//#endif - -/*---------------------------------------------------------------------------*/ - -bool -ENC28J60::reset(void) -{ - PRINTF("enc28j60: resetting chip\n"); - - pinMode(_cs, OUTPUT); - digitalWrite(_cs, HIGH); - SPI.begin(); - - /* - 6.0 INITIALIZATION - - Before the ENC28J60 can be used to transmit and receive packets, - certain device settings must be initialized. Depending on the - application, some configuration options may need to be - changed. Normally, these tasks may be accomplished once after - Reset and do not need to be changed thereafter. - - 6.1 Receive Buffer - - Before receiving any packets, the receive buffer must be - initialized by programming the ERXST and ERXND pointers. All - memory between and including the ERXST and ERXND addresses will be - dedicated to the receive hardware. It is recommended that the - ERXST pointer be programmed with an even address. - - Applications expecting large amounts of data and frequent packet - delivery may wish to allocate most of the memory as the receive - buffer. Applications that may need to save older packets or have - several packets ready for transmission should allocate less - memory. - - When programming the ERXST pointer, the ERXWRPT registers will - automatically be updated with the same values. The address in - ERXWRPT will be used as the starting location when the receive - hardware begins writing received data. For tracking purposes, the - ERXRDPT registers should additionally be programmed with the same - value. To program ERXRDPT, the host controller must write to - ERXRDPTL first, followed by ERXRDPTH. See Section 7.2.4 “Freeing - Receive Buffer Space for more information - - 6.2 Transmission Buffer - - All memory which is not used by the receive buffer is considered - the transmission buffer. Data which is to be transmitted should be - written into any unused space. After a packet is transmitted, - however, the hardware will write a seven-byte status vector into - memory after the last byte in the packet. Therefore, the host - controller should leave at least seven bytes between each packet - and the beginning of the receive buffer. No explicit action is - required to initialize the transmission buffer. - - 6.3 Receive Filters - - The appropriate receive filters should be enabled or disabled by - writing to the ERXFCON register. See Section 8.0 “Receive Filters - for information on how to configure it. - - 6.4 Waiting For OST - - If the initialization procedure is being executed immediately - following a Power-on Reset, the ESTAT.CLKRDY bit should be polled - to make certain that enough time has elapsed before proceeding to - modify the MAC and PHY registers. For more information on the OST, - see Section 2.2 “Oscillator Start-up Timer. - */ - - softreset(); - - /* Workaround for erratum #2. */ - delayMicroseconds(1000); - - /* Wait for OST */ - PRINTF("waiting for ESTAT_CLKRDY\n"); - while ((readreg(ESTAT) & ESTAT_CLKRDY) == 0) {}; - PRINTF("ESTAT_CLKRDY\n"); - - setregbank(ERXTX_BANK); - /* Set up receive buffer */ - writereg(ERXSTL, RX_BUF_START & 0xff); - writereg(ERXSTH, RX_BUF_START >> 8); - writereg(ERXNDL, RX_BUF_END & 0xff); - writereg(ERXNDH, RX_BUF_END >> 8); - writereg(ERDPTL, RX_BUF_START & 0xff); - writereg(ERDPTH, RX_BUF_START >> 8); - writereg(ERXRDPTL, RX_BUF_END & 0xff); - writereg(ERXRDPTH, RX_BUF_END >> 8); - - /* Receive filters */ - setregbank(EPKTCNT_BANK); - writereg(ERXFCON, ERXFCON_UCEN | ERXFCON_CRCEN | ERXFCON_MCEN); - - /* - 6.5 MAC Initialization Settings - - Several of the MAC registers require configuration during - initialization. This only needs to be done once; the order of - programming is unimportant. - - 1. Set the MARXEN bit in MACON1 to enable the MAC to receive - frames. If using full duplex, most applications should also set - TXPAUS and RXPAUS to allow IEEE defined flow control to function. - - 2. Configure the PADCFG, TXCRCEN and FULDPX bits of MACON3. Most - applications should enable automatic padding to at least 60 bytes - and always append a valid CRC. For convenience, many applications - may wish to set the FRMLNEN bit as well to enable frame length - status reporting. The FULDPX bit should be set if the application - will be connected to a full-duplex configured remote node; - otherwise, it should be left clear. - - 3. Configure the bits in MACON4. For conformance to the IEEE 802.3 - standard, set the DEFER bit. - - 4. Program the MAMXFL registers with the maximum frame length to - be permitted to be received or transmitted. Normal network nodes - are designed to handle packets that are 1518 bytes or less. - - 5. Configure the Back-to-Back Inter-Packet Gap register, - MABBIPG. Most applications will program this register with 15h - when Full-Duplex mode is used and 12h when Half-Duplex mode is - used. - - 6. Configure the Non-Back-to-Back Inter-Packet Gap register low - byte, MAIPGL. Most applications will program this register with - 12h. - - 7. If half duplex is used, the Non-Back-to-Back Inter-Packet Gap - register high byte, MAIPGH, should be programmed. Most - applications will program this register to 0Ch. - - 8. If Half-Duplex mode is used, program the Retransmission and - Collision Window registers, MACLCON1 and MACLCON2. Most - applications will not need to change the default Reset values. If - the network is spread over exceptionally long cables, the default - value of MACLCON2 may need to be increased. - - 9. Program the local MAC address into the MAADR1:MAADR6 registers. - */ - - setregbank(MACONX_BANK); - - /* Turn on reception and IEEE-defined flow control */ - setregbitfield(MACON1, MACON1_MARXEN | MACON1_TXPAUS | MACON1_RXPAUS); - - /* Set padding, crc, full duplex */ - setregbitfield(MACON3, MACON3_PADCFG_FULL | MACON3_TXCRCEN | MACON3_FULDPX | - MACON3_FRMLNEN); - - /* Don't modify MACON4 */ - - /* Set maximum frame length in MAMXFL */ - writereg(MAMXFLL, MAX_MAC_LENGTH & 0xff); - writereg(MAMXFLH, MAX_MAC_LENGTH >> 8); - - /* Set back-to-back inter packet gap */ - writereg(MABBIPG, 0x15); - - /* Set non-back-to-back packet gap */ - writereg(MAIPGL, 0x12); - - /* Set MAC address */ - setregbank(MAADRX_BANK); - writereg(MAADR6, _localMac[5]); - writereg(MAADR5, _localMac[4]); - writereg(MAADR4, _localMac[3]); - writereg(MAADR3, _localMac[2]); - writereg(MAADR2, _localMac[1]); - writereg(MAADR1, _localMac[0]); - - /* - 6.6 PHY Initialization Settings - - Depending on the application, bits in three of the PHY module’s - registers may also require configuration. The PHCON1.PDPXMD bit - partially controls the device’s half/full-duplex - configuration. Normally, this bit is initialized correctly by the - external circuitry (see Section 2.6 “LED Configuration). If the - external circuitry is not present or incorrect, however, the host - controller must program the bit properly. Alternatively, for an - externally configurable system, the PDPXMD bit may be read and the - FULDPX bit be programmed to match. - - For proper duplex operation, the PHCON1.PDPXMD bit must also match - the value of the MACON3.FULDPX bit. - - If using half duplex, the host controller may wish to set the - PHCON2.HDLDIS bit to prevent automatic loopback of the data which - is transmitted. The PHY register, PHLCON, controls the outputs of - LEDA and LEDB. If an application requires a LED configuration - other than the default, PHLCON must be altered to match the new - requirements. The settings for LED operation are discussed in - Section 2.6 “LED Configuration. The PHLCON register is shown in - Register 2-2 (page 9). - */ - - /* Don't worry about PHY configuration for now */ - - /* Turn on autoincrement for buffer access */ - setregbitfield(ECON2, ECON2_AUTOINC); - - /* Turn on reception */ - writereg(ECON1, ECON1_RXEN); - - return true; -} -/*---------------------------------------------------------------------------*/ -boolean -ENC28J60::begin(const uint8_t *address) -{ - _localMac = address; - - bool ret = reset(); - uint8_t rev = readrev(); - - PRINTF("ENC28J60 rev. B%d\n", rev); - - return ret && rev != 255; -} - -/*---------------------------------------------------------------------------*/ - -uint16_t -ENC28J60::sendFrame(const uint8_t *data, uint16_t datalen) -{ - uint16_t dataend; - - /* - 1. Appropriately program the ETXST pointer to point to an unused - location in memory. It will point to the per packet control - byte. In the example, it would be programmed to 0120h. It is - recommended that an even address be used for ETXST. - - 2. Use the WBM SPI command to write the per packet control byte, - the destination address, the source MAC address, the - type/length and the data payload. - - 3. Appropriately program the ETXND pointer. It should point to the - last byte in the data payload. In the example, it would be - programmed to 0156h. - - 4. Clear EIR.TXIF, set EIE.TXIE and set EIE.INTIE to enable an - interrupt when done (if desired). - - 5. Start the transmission process by setting - ECON1.TXRTS. - */ - - setregbank(ERXTX_BANK); - /* Set up the transmit buffer pointer */ - writereg(ETXSTL, TX_BUF_START & 0xff); - writereg(ETXSTH, TX_BUF_START >> 8); - writereg(EWRPTL, TX_BUF_START & 0xff); - writereg(EWRPTH, TX_BUF_START >> 8); - - /* Write the transmission control register as the first byte of the - output packet. We write 0x00 to indicate that the default - configuration (the values in MACON3) will be used. */ - writedatabyte(0x00); /* MACON3 */ - - writedata(data, datalen); - - /* Write a pointer to the last data byte. */ - dataend = TX_BUF_START + datalen; - writereg(ETXNDL, dataend & 0xff); - writereg(ETXNDH, dataend >> 8); - - /* Clear EIR.TXIF */ - clearregbitfield(EIR, EIR_TXIF); - - /* Don't care about interrupts for now */ - - /* Send the packet */ - setregbitfield(ECON1, ECON1_TXRTS); - while ((readreg(ECON1) & ECON1_TXRTS) > 0); - -#if DEBUG - if ((readreg(ESTAT) & ESTAT_TXABRT) != 0) - { - uint16_t erdpt; - uint8_t tsv[7]; - erdpt = (readreg(ERDPTH) << 8) | readreg(ERDPTL); - writereg(ERDPTL, (dataend + 1) & 0xff); - writereg(ERDPTH, (dataend + 1) >> 8); - readdata(tsv, sizeof(tsv)); - writereg(ERDPTL, erdpt & 0xff); - writereg(ERDPTH, erdpt >> 8); - PRINTF("enc28j60: tx err: %d: %02x:%02x:%02x:%02x:%02x:%02x\n" - " tsv: %02x%02x%02x%02x%02x%02x%02x\n", datalen, - 0xff & data[0], 0xff & data[1], 0xff & data[2], - 0xff & data[3], 0xff & data[4], 0xff & data[5], - tsv[6], tsv[5], tsv[4], tsv[3], tsv[2], tsv[1], tsv[0]); - } - else - { - PRINTF("enc28j60: tx: %d: %02x:%02x:%02x:%02x:%02x:%02x\n", datalen, - 0xff & data[0], 0xff & data[1], 0xff & data[2], - 0xff & data[3], 0xff & data[4], 0xff & data[5]); - } -#endif - - //sent_packets++; - //PRINTF("enc28j60: sent_packets %d\n", sent_packets); - return datalen; -} - -/*---------------------------------------------------------------------------*/ - -uint16_t -ENC28J60::readFrame(uint8_t *buffer, uint16_t bufsize) -{ - readFrameSize(); - return readFrameData(buffer, bufsize); -} - -uint16_t -ENC28J60::readFrameSize() -{ - uint8_t n; - - uint8_t nxtpkt[2]; - uint8_t status[2]; - uint8_t length[2]; - - setregbank(EPKTCNT_BANK); - n = readreg(EPKTCNT); - - if (n == 0) - { - return 0; - } - - PRINTF("enc28j60: EPKTCNT 0x%02x\n", n); - - setregbank(ERXTX_BANK); - /* Read the next packet pointer */ - nxtpkt[0] = readdatabyte(); - nxtpkt[1] = readdatabyte(); - _next = (nxtpkt[1] << 8) + nxtpkt[0]; - - PRINTF("enc28j60: nxtpkt 0x%02x%02x\n", _nxtpkt[1], _nxtpkt[0]); - - length[0] = readdatabyte(); - length[1] = readdatabyte(); - _len = (length[1] << 8) + length[0]; - - PRINTF("enc28j60: length 0x%02x%02x\n", length[1], length[0]); - - status[0] = readdatabyte(); - status[1] = readdatabyte(); - - /* This statement is just to avoid a compiler warning: */ - (void)status[0]; - PRINTF("enc28j60: status 0x%02x%02x\n", status[1], status[0]); - - return _len; -} - -void -ENC28J60::discardFrame(uint16_t framesize) -{ - (void)framesize; - (void)readFrameData(nullptr, 0); -} - -uint16_t -ENC28J60::readFrameData(uint8_t *buffer, uint16_t framesize) -{ - if (framesize < _len) - { - - buffer = nullptr; - - /* flush rx fifo */ - for (uint16_t i = 0; i < _len; i++) - { - readdatabyte(); - } - } - else - { - readdata(buffer, _len); - } - - /* Read an additional byte at odd lengths, to avoid FIFO corruption */ - if ((_len % 2) != 0) - { - readdatabyte(); - } - - /* Errata #14 */ - if (_next == RX_BUF_START) - { - _next = RX_BUF_END; - } - else - { - _next = _next - 1; - } - writereg(ERXRDPTL, _next & 0xff); - writereg(ERXRDPTH, _next >> 8); - - setregbitfield(ECON2, ECON2_PKTDEC); - - if (!buffer) - { - PRINTF("enc28j60: rx err: flushed %d\n", _len); - return 0; - } - PRINTF("enc28j60: rx: %d: %02x:%02x:%02x:%02x:%02x:%02x\n", _len, - 0xff & buffer[0], 0xff & buffer[1], 0xff & buffer[2], - 0xff & buffer[3], 0xff & buffer[4], 0xff & buffer[5]); - - //received_packets++; - //PRINTF("enc28j60: received_packets %d\n", received_packets); - - return _len; -} - -/*---------------------------------------------------------------------------*/ -uint16_t -ENC28J60::phyRead(uint8_t address) -{ - unsigned int timeout = 15; - - setregbank(REG_BANK_2); - - // set the PHY register address - writereg(MIREGADR, address); - setregbitfield(MICMD, 0x01); // MII Read Enable [MIIRD] - - setregbank(REG_BANK_3); - - // wait until the PHY read completes - while(readreg(MISTAT) & 0x01){ - delay(10); - wdt_reset(); - if(--timeout ==0) return 0; - } - - setregbank(REG_BANK_2); - - clearregbitfield(MICMD, 0x01); // MII Read Disable [MIIRD] - - // read the PHY data - return (readreg(MIRDL) | readreg(MIRDH) << 8); -} - -void -ENC28J60::phyWrite(uint8_t address, uint16_t data) -{ - unsigned int timeout = 15; - - setregbank(REG_BANK_2); - - // set the PHY register address - writereg(MIREGADR, address); - - // write the PHY data - writereg(MIWRL, data & 0xff); - writereg(MIWRH, data >> 8); - - setregbank(REG_BANK_3); - - // wait until the PHY write completes - while(readreg(MISTAT) & 0x01){ - delay(10); - wdt_reset(); - if(--timeout ==0) return; - } -} - -//-------------------------------------------------------- -void -ENC28J60::init(void) -{ - pinMode(_cs, OUTPUT); - digitalWrite(_cs, HIGH); - SPI.begin(); -} - -void -ENC28J60::LEDsConfig(LEDScfg_CONFIG LEDAcfg, LEDScfg_CONFIG LEDBcfg) -{ - uint16_t temp = phyRead(PHLCON); - - if(LEDAcfg) temp &= LEDA_MASK; - if(LEDBcfg) temp &= LEDB_MASK; - - phyWrite(PHLCON, (temp |(uint16_t)(LEDAcfg <<4) |LEDBcfg |0x3006)); -} - -//-------------------------------------------------------- -bool -ENC28J60::resetEther(void) -{ - return reset(); -} - -uint8_t -ENC28J60::hardwareStatus(void) -{ - uint8_t rev; - setregbank(MAADRX_BANK); - rev = readreg(EREVID); /* 0xFF = Hardware Error */ - switch (rev) - { - case 2: - return 1; - case 6: - return 7; - default: - return rev; - } -} - -bool -ENC28J60::linkStatus(void) -{ - return (phyRead(PHSTAT2) & 0x0400); -} diff --git a/lib-patch/enc28j60.h b/lib-patch/enc28j60.h deleted file mode 100644 index efad69bb2..000000000 --- a/lib-patch/enc28j60.h +++ /dev/null @@ -1,189 +0,0 @@ -/** - Header file for direct Ethernet frame access to the ENC28J60 controller - @file enc28j60.h -*/ - -/* - Copyright (c) 2012-2013, Thingsquare, http://www.thingsquare.com/. - All rights reserved. - - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions - are met: - 1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - 3. Neither the name of the copyright holder nor the names of its - contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) - HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, - STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) - ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED - OF THE POSSIBILITY OF SUCH DAMAGE. - -*/ - -// original sources: https://github.com/njh/EtherSia/tree/master/src/enc28j60.h - -#ifndef ENC28J60_H -#define ENC28J60_H - -#include - -// PHLCON: PHY module LED control register -// -// R R R R L L L L L L L L R -// E E E E A A A A B B B B L L S E -// S S S S C C C C C C C C F F T S -// E E E E F F F F F F F F R R R E -// R R R R G G G G G G G G Q Q C R -// V V V V 3 2 1 0 3 2 1 0 1 0 H V - -typedef enum { - LED_NO_CHANGE = 0x00, - LED_TRANSMIT = 0x10, - LED_RECEIVE = 0x20, - LED_COLLISION = 0x30, - LED_LINK_STATUS = 0x40, - LED_DUPLEX_STATUS = 0x50, - LED_TRANS_RECV = 0x70, - LED_BLINK_ON = 0x80, - LED_BLINK_OFF = 0x90, - LED_BLINK_FAST = 0xA0, - LED_BLINK_SLOW = 0xB0, - LED_LINK_RXD_STATUS = 0xC0, - LED_LINK_TXD_RXD_STATUS = 0xD0, - LED_DPLX_COLL_STATUS = 0xE0, - - LEDA_MASK = 0xF0F0, - LEDB_MASK = 0xFF00 -} LEDScfg_CONFIG; - -/** - Send and receive Ethernet frames directly using a ENC28J60 controller. -*/ -class ENC28J60 -{ - -public: - /** - Constructor that uses the default hardware SPI pins - @param cs the Arduino Chip Select / Slave Select pin (default 10 on Uno) - */ - ENC28J60(int8_t cs = SS, SPIClass& spi = SPI, int8_t intr = -1); - - /** - Initialise the Ethernet controller - Must be called before sending or receiving Ethernet frames - - @param address the local MAC address for the Ethernet interface - @return Returns true if setting up the Ethernet interface was successful - */ - boolean begin(const uint8_t *address); - - /** - Send an Ethernet frame - @param data a pointer to the data to send - @param datalen the length of the data in the packet - @return the number of bytes transmitted - */ - virtual uint16_t sendFrame(const uint8_t *data, uint16_t datalen); - - /** - Read an Ethernet frame - @param buffer a pointer to a buffer to write the packet to - @param bufsize the available space in the buffer - @return the length of the received packet - or 0 if no packet was received - */ - virtual uint16_t readFrame(uint8_t *buffer, uint16_t bufsize); - - /******/ - void init(void); - void LEDsConfig(LEDScfg_CONFIG LEDAcfg, LEDScfg_CONFIG LEDBcfg); - - bool resetEther(void); - uint8_t hardwareStatus(void); - bool linkStatus(void); - uint8_t readreg(uint8_t reg); - -protected: - - static constexpr bool interruptIsPossible() - { - return false; - } - - /** - Read an Ethernet frame size - @return the length of data do receive - or 0 if no frame was received - */ - uint16_t readFrameSize(); - - /** - discard an Ethernet frame - @param framesize readFrameSize()'s result - */ - void discardFrame(uint16_t framesize); - - /** - Read an Ethernet frame data - readFrameSize() must be called first, - its result must be passed into framesize parameter - @param buffer a pointer to a buffer to write the frame to - @param framesize readFrameSize()'s result - @return the length of the received frame - or 0 if a problem occurred - */ - uint16_t readFrameData(uint8_t *frame, uint16_t framesize); - -private: - - uint8_t is_mac_mii_reg(uint8_t reg); - void writereg(uint8_t reg, uint8_t data); - void setregbitfield(uint8_t reg, uint8_t mask); - void clearregbitfield(uint8_t reg, uint8_t mask); - void setregbank(uint8_t new_bank); - void writedata(const uint8_t *data, int datalen); - void writedatabyte(uint8_t byte); - int readdata(uint8_t *buf, int len); - uint8_t readdatabyte(void); - void softreset(void); - uint8_t readrev(void); - bool reset(void); - - uint16_t phyRead(uint8_t address); - void phyWrite(uint8_t address, uint16_t data); - - void enc28j60_arch_spi_init(void); - uint8_t enc28j60_arch_spi_write(uint8_t data); - uint8_t enc28j60_arch_spi_read(void); - void enc28j60_arch_spi_select(void); - void enc28j60_arch_spi_deselect(void); - - // Previously defined in contiki/core/sys/clock.h - void clock_delay_usec(uint16_t dt); - - uint8_t _bank; - int8_t _cs; - SPIClass& _spi; - - const uint8_t *_localMac; - - /* readFrame*() state */ - uint16_t _next, _len; -}; - -#endif /* ENC28J60_H */ diff --git a/lib-patch/readme.txt b/lib-patch/readme.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index d382e4360..e80584ccd 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1021,6 +1021,9 @@ void server_json_options_main() { bfill.emit_p(PSTR(",")); } + //feature flag Analog Sensor API + bfill.emit_p(PSTR(",\"feature\":\"ASB\"")); + bfill.emit_p(PSTR(",\"dexp\":$D,\"mexp\":$D,\"hwt\":$D,"), os.detect_exp(), MAX_EXT_BOARDS, os.hw_type); // print master array byte masid, optidx; @@ -2244,13 +2247,20 @@ void server_sensor_list(OTF_PARAMS_DEF) { print_header(); #endif - int count = sensor_count(); - bfill.emit_p(PSTR("{\"count\":$D,"), count); - bfill.emit_p(PSTR("\"sensors\":[")); - sensorconfig_json(OTF_PARAMS); - bfill.emit_p(PSTR("]")); - bfill.emit_p(PSTR("}")); + uint test = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("test"), true)) + test = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (test) { + bfill.emit_p(PSTR("{\"test\":$D}"), test); + } else { + int count = sensor_count(); + bfill.emit_p(PSTR("{\"count\":$D,"), count); + bfill.emit_p(PSTR("\"sensors\":[")); + sensorconfig_json(OTF_PARAMS); + bfill.emit_p(PSTR("]")); + bfill.emit_p(PSTR("}")); + } handle_return(HTML_OK); } diff --git a/opensprinkler_server.h b/opensprinkler_server.h index bdf383ae5..be8e1276b 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -1,100 +1,101 @@ -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * Server functions - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#ifndef _OPENSPRINKLER_SERVER_H -#define _OPENSPRINKLER_SERVER_H - -#if !defined(ARDUINO) -#include -#endif - -char dec2hexchar(byte dec); - -class BufferFiller { - char *start; //!< Pointer to start of buffer - char *ptr; //!< Pointer to cursor position -public: - BufferFiller () {} - BufferFiller (char *buf) : start (buf), ptr (buf) {} - - 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': - //wtoa(va_arg(ap, uint16_t), (char*) ptr); - itoa(va_arg(ap, int), (char*) ptr, 10); // ray - break; - case 'E': //Double - sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); - break; - case 'L': - //ltoa(va_arg(ap, long), (char*) ptr, 10); - ultoa(va_arg(ap, long), (char*) ptr, 10); // ray - 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); - } - - char* buffer () const { return start; } - unsigned int position () const { return ptr - start; } -}; - - -#endif // _OPENSPRINKLER_SERVER_H +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * Server functions + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _OPENSPRINKLER_SERVER_H +#define _OPENSPRINKLER_SERVER_H + +#if !defined(ARDUINO) +#include +#endif + +char dec2hexchar(byte dec); + +class BufferFiller { + char *start; //!< Pointer to start of buffer + char *ptr; //!< Pointer to cursor position +public: + BufferFiller () {} + BufferFiller (char *buf) : start (buf), ptr (buf) {} + + 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': + //wtoa(va_arg(ap, uint16_t), (char*) ptr); + itoa(va_arg(ap, int), (char*) ptr, 10); // ray + break; + case 'E': //Double + sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); + break; + case 'L': + //ltoa(va_arg(ap, long), (char*) ptr, 10); + //ultoa(va_arg(ap, long), (char*) ptr, 10); // ray + sprintf((char*) ptr, "%lu", va_arg(ap, long)); + 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); + } + + char* buffer () const { return start; } + unsigned int position () const { return ptr - start; } +}; + + +#endif // _OPENSPRINKLER_SERVER_H diff --git a/platformio.ini b/platformio.ini index 866d13da0..5c3c40cef 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,7 +16,7 @@ src_dir = . include_dir = . [env:d1_mini] -platform = espressif8266@4.0.1 +platform = espressif8266@4.2.0 ;platform = https://github.com/platformio/platform-espressif8266.git board = d1_mini framework = arduino diff --git a/sensors.cpp b/sensors.cpp index e6e717251..14c105c3e 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -34,6 +34,9 @@ byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,b //All sensors: static Sensor_t *sensors = NULL; +//Sensor URLS: +static SensorUrl_t * sensorUrls = NULL; + //Program sensor data static ProgSensorAdjust_t *progSensorAdjusts = NULL; @@ -193,6 +196,8 @@ void sensor_load() { sensor->next = NULL; pos += SENSOR_STORE_SIZE; } + + SensorUrl_load(); } /** @@ -1708,3 +1713,102 @@ void GetSensorWeather() { current_weather_ok = false; } } + +void SensorUrl_load() { + sensorUrls = NULL; + if (!file_exists(SENSORURL_FILENAME)) + return; + + ulong pos = 0; + SensorUrl_t *last = NULL; + while (true) { + SensorUrl_t *sensorUrl = new SensorUrl_t; + memset(sensorUrl, 0, sizeof(SensorUrl_t)); + file_read_block (SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE); + sensorUrl->urlstr = (char*)malloc(sensorUrl->length+1); + pos += SENSORURL_STORE_SIZE; + file_read_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, sensorUrl->length); + pos += sensorUrl->length; + + if (!last) sensorUrls = sensorUrl; + else last->next = sensorUrl; + last = sensorUrl; + sensorUrl->next = NULL; + } +} + +void SensorUrl_save() { + if (file_exists(SENSORURL_FILENAME)) + remove_file(SENSORURL_FILENAME); + + ulong pos = 0; + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + file_write_block(SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE); + pos += SENSORURL_STORE_SIZE; + file_write_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, sensorUrl->length); + pos += sensorUrl->length; + + sensorUrl = sensorUrl->next; + } +} + +bool SensorUrl_delete(uint nr) { + SensorUrl_t *sensorUrl = sensorUrls; + SensorUrl_t *last = NULL; + while (sensorUrl) { + if (sensorUrl->nr == nr) { + if (last) + last->next = sensorUrl->next; + else + sensorUrls = sensorUrl->next; + delete sensorUrl->urlstr; + delete sensorUrl; + SensorUrl_save(); + return true; + } + last = sensorUrl; + sensorUrl = sensorUrl->next; + } + return false; +} + +bool SensorUrl_add(uint nr, char *urlstr) { + if (!urlstr || !strlen(urlstr)) { //empty string? delete! + SensorUrl_delete(nr); + return false; + } + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + if (sensorUrl->nr == nr) { //replace existing + delete sensorUrl->urlstr; + sensorUrl->urlstr = (char*) malloc(strlen(urlstr)+1); + strcpy(sensorUrl->urlstr, urlstr); + SensorUrl_save(); + return false; + } + sensorUrl = sensorUrl->next; + } + + //Add new: + sensorUrl = new SensorUrl_t; + memset(sensorUrl, 0, sizeof(SensorUrl_t)); + sensorUrl->nr = nr; + sensorUrl->length = strlen(urlstr); + sensorUrl->urlstr = (char*) malloc(strlen(urlstr)+1); + strcpy(sensorUrl->urlstr, urlstr); + sensorUrl->next = sensorUrls; + sensorUrls = sensorUrl; + SensorUrl_save(); + return true; +} + +char *SensorUrl_get(uint nr) { + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + if (sensorUrl->nr == nr) //replace existing + return sensorUrl->urlstr; + sensorUrl = sensorUrl->next; + } + return NULL; +} diff --git a/sensors.h b/sensors.h index 9f8e61816..cba0400d1 100644 --- a/sensors.h +++ b/sensors.h @@ -48,6 +48,8 @@ #define SENSORLOG_FILENAME_MONTH1 "sensorlogM1.dat" // analog sensor log filename for month average #define SENSORLOG_FILENAME_MONTH2 "sensorlogM2.dat" // analog sensor log filename2 for month average +#define SENSORURL_FILENAME "sensorurl.dat" // long urls filename + //MaxLogSize #define MAX_LOG_SIZE 8000 #define MAX_LOG_SIZE_WEEK 2000 @@ -156,6 +158,16 @@ typedef struct ProgSensorAdjust { } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) +typedef struct SensorUrl { + uint nr; + uint type; //unused, for later + uint length; + char *urlstr; + SensorUrl *next; +} SensorUrl_t; +#define SENSORURL_STORE_SIZE (sizeof(SensorUrl_t)-sizeof(char*)-sizeof(SensorUrl_t*)) + + #define UNIT_NONE 0 #define UNIT_PERCENT 1 #define UNIT_DEGREE 2 @@ -228,6 +240,12 @@ double calc_sensor_watering_by_nr(uint nr); void GetSensorWeather(); +void SensorUrl_load(); +void SensorUrl_save(); +bool SensorUrl_delete(uint nr); +bool SensorUrl_add(uint nr, char *urlstr); +char *SensorUrl_get(uint nr); + #if defined(ESP8266) ulong diskFree(); bool checkDiskFree(); //true: disk space Ok, false: Out of disk space From 622a337f11f5c210fbc36dbff390dbf218eefd4d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 25 Aug 2023 07:54:23 +0200 Subject: [PATCH 069/281] Fixt compile errors for Raspberry Pi --- main.cpp | 3 +++ sensors.cpp | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/main.cpp b/main.cpp index 8cac45920..26a6daa06 100644 --- a/main.cpp +++ b/main.cpp @@ -400,6 +400,9 @@ void do_setup() { os.mqtt.init(); os.status.req_mqtt_restart = true; + + sensor_load(); + prog_adjust_load(); } #endif diff --git a/sensors.cpp b/sensors.cpp index 14c105c3e..3ebcbf867 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -455,6 +455,24 @@ ulong findLogPosition(uint8_t log, ulong after) { return 0; } +#if !defined(ARDUINO) +/** +/* compatibility functions for OSPi: +**/ +#define timeSet 0 +int timeStatus() { + return timeSet; +} + +void dtostrf(float value, int min_width, int precision, char *txt) { + printf(txt, "%*.*f", min_width, precision, value); +} + +void dtostrf(double value, int min_width, int precision, char *txt) { + printf(txt, "%*.*d", min_width, precision, value); +} +#endif + // 1/4 of a day: 6*60*60 #define BLOCKSIZE 64 #define CALCRANGE_WEEK 21600 From fae1bb05029d501f33163adc4cccb449263c9fca Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 25 Aug 2023 08:56:12 +0200 Subject: [PATCH 070/281] Fixt merge bug --- OpenSprinkler.cpp | 2894 +-------------------------------------------- 1 file changed, 1 insertion(+), 2893 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index daa8c0af3..f78254c1c 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -498,2898 +498,6 @@ byte OpenSprinkler::start_network() { #endif } -byte OpenSprinkler::start_ether() { -#if defined(ESP8266) - if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 - - SPI.begin(); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(4000000); - - load_hardware_mac((uint8_t*)tmp_buffer, true); - if (iopts[IOPT_USE_DHCP]==0) { // config static IP before calling eth.begin - IPAddress staticip(iopts+IOPT_STATIC_IP1); - IPAddress gateway(iopts+IOPT_GATEWAY_IP1); - IPAddress dns(iopts+IOPT_DNS_IP1); - IPAddress subn(iopts+IOPT_SUBNET_MASK1); - eth.config(staticip, gateway, subn, dns); - } - eth.setDefault(); - if(!eth.begin((uint8_t*)tmp_buffer)) return 0; - lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); - - ulong timeout = millis()+45000; // 45 seconds time out - while (!eth.connected()) { - DEBUG_PRINT("."); - delay(1000); - if(millis()>timeout) break; - } - - DEBUG_PRINTLN(); - DEBUG_PRINT("eth.ip:"); - DEBUG_PRINTLN(eth.localIP()); - DEBUG_PRINT("eth.dns:"); - DEBUG_PRINTLN(WiFi.dnsIP()); - - if (iopts[IOPT_USE_DHCP]) { - memcpy(iopts+IOPT_STATIC_IP1, &(eth.localIP()[0]), 4); - memcpy(iopts+IOPT_GATEWAY_IP1, &(eth.gatewayIP()[0]),4); - memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); // todo: lwip need dns ip - memcpy(iopts+IOPT_SUBNET_MASK1, &(eth.subnetMask()[0]), 4); - iopts_save(); - } - - return 1; - -#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 true; // todo: lwip currently does not have a way to check link status - else - return (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && state==OS_STATE_CONNECTED); -#else - return (Ethernet.linkStatus()==LinkON); -#endif -} - -/** Reboot controller */ -void OpenSprinkler::reboot_dev(uint8_t cause) { - lcd_print_line_clear_pgm(PSTR("Rebooting..."), 0); - if(cause) { - nvdata.reboot_cause = cause; - nvdata_save(); - } -#if defined(ESP8266) - ESP.restart(); -#else - resetFunc(); -#endif -} - -#else // RPI/BBB/LINUX network init functions - -#include "etherport.h" -#include -#include -#include -#include -#include "utils.h" -#include "opensprinkler_server.h" - -/** Initialize network with the given mac address and http port */ -byte OpenSprinkler::start_network() { - unsigned int port = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; -#if defined(DEMO) -#if defined(HTTP_PORT) - port = HTTP_PORT; -#else - port = 80; -#endif -#endif - if(m_server) { delete m_server; m_server = 0; } - - m_server = new EthernetServer(port); - return m_server->begin(); -} - -bool OpenSprinkler::network_connected(void) { - return true; -} - -// 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(byte* mac, bool wired) { - const char * if_names[] = { "eth0", "eth1", "wlan0", "wlan1" }; - struct ifreq ifr; - int fd; - - // Fallback to asoftware mac if interface not recognised - mac[0] = 0x00; - mac[1] = 0x69; - mac[2] = 0x69; - mac[3] = 0x2D; - mac[4] = 0x31; - mac[5] = iopts[IOPT_DEVICE_ID]; - - if (m_server == NULL) return true; - - if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) return true; - - // Returns the mac address of the first interface if multiple active - for (int i = 0; i < sizeof(if_names)/sizeof(const char *); i++) { - strncpy(ifr.ifr_name, if_names[i], sizeof(ifr.ifr_name)); - if (ioctl(fd, SIOCGIFHWADDR, &ifr) != -1) { - memcpy(mac, ifr.ifr_hwaddr.sa_data, 6); - break; - } - } - close(fd); - return true; -} - -/** Reboot controller */ -void OpenSprinkler::reboot_dev(uint8_t cause) { - nvdata.reboot_cause = cause; - nvdata_save(); -#if defined(DEMO) - // do nothing -#else - sync(); // add sync to prevent file corruption - reboot(RB_AUTOBOOT); -#endif -} - -/** Launch update script */ -void OpenSprinkler::update_dev() { - char cmd[1000]; - sprintf(cmd, "cd %s && ./updater.sh", get_runtime_path()); - system(cmd); -} -#endif // end network init functions - -#if defined(ARDUINO) -/** Initialize LCD */ -void OpenSprinkler::lcd_start() { - -#if defined(ESP8266) - // initialize SSD1306 - lcd.init(); - lcd.begin(); - flash_screen(); -#else - // 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 - - /* check hardware type */ - if(detect_i2c(ACDR_I2CADDR)) hw_type = HW_TYPE_AC; - else if(detect_i2c(DCDR_I2CADDR)) hw_type = HW_TYPE_DC; - else if(detect_i2c(LADR_I2CADDR)) hw_type = HW_TYPE_LATCH; - - /* detect hardware revision type */ - if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists - /* assign revision 0 pins */ - PIN_BUTTON_1 = V0_PIN_BUTTON_1; - PIN_BUTTON_2 = V0_PIN_BUTTON_2; - PIN_BUTTON_3 = V0_PIN_BUTTON_3; - PIN_RFRX = V0_PIN_RFRX; - PIN_RFTX = V0_PIN_RFTX; - PIN_BOOST = V0_PIN_BOOST; - PIN_BOOST_EN = V0_PIN_BOOST_EN; - PIN_SENSOR1 = V0_PIN_SENSOR1; - PIN_SENSOR2 = V0_PIN_SENSOR2; - - // on revision 0, main IOEXP and driver IOEXP are two separate PCF8574 chips - if(hw_type==HW_TYPE_DC) { - drio = new PCF8574(DCDR_I2CADDR); - } else if(hw_type==HW_TYPE_LATCH) { - drio = new PCF8574(LADR_I2CADDR); - } else { - drio = new PCF8574(ACDR_I2CADDR); - } - - mainio = new PCF8574(MAIN_I2CADDR); - mainio->i2c_write(0, 0x0F); // set lower four bits of main PCF8574 (8-ch) to high - - digitalWriteExt(V0_PIN_PWR_TX, 1); // turn on TX power - digitalWriteExt(V0_PIN_PWR_RX, 1); // turn on RX power - pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); - digitalWriteExt(PIN_BOOST, LOW); - digitalWriteExt(PIN_BOOST_EN, LOW); - digitalWriteExt(PIN_LATCH_COM, LOW); - - } else { - - if(hw_type==HW_TYPE_DC) { - drio = new PCA9555(DCDR_I2CADDR); - } else if(hw_type==HW_TYPE_LATCH) { - drio = new PCA9555(LADR_I2CADDR); - } else { - drio = new PCA9555(ACDR_I2CADDR); - } - mainio = drio; - - pinMode(16, INPUT); - if(digitalRead(16)==LOW) { - // revision 1 - hw_rev = 1; - mainio->i2c_write(NXP_CONFIG_REG, V1_IO_CONFIG); - mainio->i2c_write(NXP_OUTPUT_REG, V1_IO_OUTPUT); - - PIN_BUTTON_1 = V1_PIN_BUTTON_1; - PIN_BUTTON_2 = V1_PIN_BUTTON_2; - PIN_BUTTON_3 = V1_PIN_BUTTON_3; - PIN_RFRX = V1_PIN_RFRX; - PIN_RFTX = V1_PIN_RFTX; - PIN_IOEXP_INT = V1_PIN_IOEXP_INT; - PIN_BOOST = V1_PIN_BOOST; - PIN_BOOST_EN = V1_PIN_BOOST_EN; - PIN_LATCH_COM = V1_PIN_LATCH_COM; - PIN_SENSOR1 = V1_PIN_SENSOR1; - PIN_SENSOR2 = V1_PIN_SENSOR2; - } else { - // revision 2 - hw_rev = 2; - mainio->i2c_write(NXP_CONFIG_REG, V2_IO_CONFIG); - mainio->i2c_write(NXP_OUTPUT_REG, V2_IO_OUTPUT); - - PIN_BUTTON_1 = V2_PIN_BUTTON_1; - PIN_BUTTON_2 = V2_PIN_BUTTON_2; - PIN_BUTTON_3 = V2_PIN_BUTTON_3; - PIN_RFTX = V2_PIN_RFTX; - PIN_BOOST = V2_PIN_BOOST; - PIN_BOOST_EN = V2_PIN_BOOST_EN; - PIN_LATCH_COMK = V2_PIN_LATCH_COMK; // os3.2latch uses H-bridge separate cathode and anode design - PIN_LATCH_COMA = V2_PIN_LATCH_COMA; - PIN_SENSOR1 = V2_PIN_SENSOR1; - PIN_SENSOR2 = V2_PIN_SENSOR2; - } - } - - /* detect expanders */ - for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) - 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 - - // Reset all stations - clear_all_station_bits(); - apply_all_station_bits(); - -#if defined(ESP8266) - // OS 3.0 has two independent sensors - pinModeExt(PIN_SENSOR1, INPUT_PULLUP); - pinModeExt(PIN_SENSOR2, INPUT_PULLUP); - -#else - // pull shift register OE low to enable output - 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 - // Static variables are assigned 0 by default - // so only need to initialize non-zero ones - status.enabled = 1; - status.safe_reboot = 0; - - old_status = status; - - nvdata.sunrise_time = 360; // 6:00am default sunrise - nvdata.sunset_time = 1080; // 6:00pm default sunset - nvdata.reboot_cause = REBOOT_CAUSE_POWERON; - - nboards = 1; - nstations = nboards*8; - - // set rf data pin, unless it is not being used - if(PIN_RFTX != 255) { - pinModeExt(PIN_RFTX, OUTPUT); - digitalWriteExt(PIN_RFTX, LOW); - } - -#if defined(ARDUINO) // AVR SD and LCD functions - - #if defined(ESP8266) // OS3.0 specific detections - - 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); - byte 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 - - lcd_start(); - - #if defined(ESP8266) - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); - #else - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_disconnected); - #endif - lcd.createChar(ICON_REMOTEXT, _iconimage_remotext); - lcd.createChar(ICON_RAINDELAY, _iconimage_raindelay); - lcd.createChar(ICON_RAIN, _iconimage_rain); - lcd.createChar(ICON_SOIL, _iconimage_soil); - - #if defined(ESP8266) - - /* create custom characters */ - lcd.createChar(ICON_WIFI_CONNECTED, _iconimage_wifi_connected); - lcd.createChar(ICON_WIFI_DISCONNECTED, _iconimage_wifi_disconnected); - - 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){} - } - - #endif - - // set button pins - // enable internal pullup - pinMode(PIN_BUTTON_1, INPUT_PULLUP); - pinMode(PIN_BUTTON_2, INPUT_PULLUP); - pinMode(PIN_BUTTON_3, INPUT_PULLUP); - - // detect and check RTC type - RTC.detect(); - -#else - DEBUG_PRINTLN(get_runtime_path()); -#endif -} - -#if defined(ESP8266) -/** LATCH boost voltage - * - */ -void OpenSprinkler::latch_boost() { - digitalWriteExt(PIN_BOOST, HIGH); // enable boost converter - delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge - digitalWriteExt(PIN_BOOST, LOW); // disable boost converter -} - -/** Set all zones (for LATCH controller) - * This function sets all zone pins (including COM) to a specified value - */ -void OpenSprinkler::latch_setallzonepins(byte value) { - digitalWriteExt(PIN_LATCH_COM, value); // set latch com pin - // Handle driver board (on main controller) - if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= 0x00FF; // first 8 zones are the lowest 8 bits of main driver board - else reg &= 0xFF00; - drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - // Handle all expansion boards - for(byte i=0;itype==IOEXP_TYPE_9555) { - expanders[i]->i2c_write(NXP_OUTPUT_REG, value?0xFFFF:0x0000); - } - } -} - -void OpenSprinkler::latch_disable_alloutputs_v2() { - digitalWriteExt(PIN_LATCH_COMA, LOW); - digitalWriteExt(PIN_LATCH_COMK, LOW); - - // latch v2 has a pca9555 the lowest 8 bits of which control all h-bridge anode pins - drio->i2c_write(NXP_OUTPUT_REG, drio->i2c_read(NXP_OUTPUT_REG) & 0xFF00); - // latch v2 has a 74hc595 which controls all h-bridge cathode pins - drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, 0x00); - - // todo: handle expander -} - -/** Set one zone (for LATCH controller) - * This function sets one specified zone pin to a specified value - */ -void OpenSprinkler::latch_setzonepin(byte sid, byte value) { - if(sid<8) { // on main controller - if(drio->type==IOEXP_TYPE_9555) { // LATCH contorller only uses PCA9555, no other type - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - } else { // on expander - byte bid=(sid-8)>>4; - uint16_t s=(sid-8)&0x0F; - if(expanders[bid]->type==IOEXP_TYPE_9555) { - uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value - if(value) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); - } - } -} - -void OpenSprinkler::latch_setzoneoutput_v2(byte sid, byte A, byte K) { - if(A==HIGH && K==HIGH) return; // A and K must not be HIGH at the same time - - if(sid<8) { // on main controller - // v2 latch driver has one PCA9555, the lowest 8-bits of which control all anode pins - // and one 74HC595, which controls all cathod pins - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); - if(A) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); - - drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, K ? (1<>3; - byte s=i&0x07; - byte mask=(byte)1<type==IOEXP_TYPE_8574) { - /* revision 0 uses PCF8574 with active low logic, so all bits must be flipped */ - drio->i2c_write(NXP_OUTPUT_REG, ~station_bits[0]); - } else if(drio->type==IOEXP_TYPE_9555) { - /* revision 1 uses PCA9555 with active high logic */ - uint16_t reg = drio->i2c_read(NXP_OUTPUT_REG); // read current output reg value - reg = (reg&0xFF00) | station_bits[0]; // output channels are the low 8-bit - drio->i2c_write(NXP_OUTPUT_REG, reg); // write value to register - } - - // Handle expansion boards - for(int i=0;itype==IOEXP_TYPE_9555) { - expanders[i]->i2c_write(NXP_OUTPUT_REG, data); - } else { - expanders[i]->i2c_write(NXP_OUTPUT_REG, ~data); - } - } - } - - byte bid, s, sbits; -#else - digitalWrite(PIN_SR_LATCH, LOW); - byte bid, s, sbits; - - // Shift out all station bit values - // from the highest bit to the lowest - for(bid=0;bid<=MAX_EXT_BOARDS;bid++) { - if (status.enabled) - sbits = station_bits[MAX_EXT_BOARDS-bid]; - else - sbits = 0; - - for(s=0;s<8;s++) { - digitalWrite(PIN_SR_CLOCK, LOW); - #if defined(OSPI) // if OSPI, use dynamically assigned pin_sr_data - digitalWrite(pin_sr_data, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); - #else - digitalWrite(PIN_SR_DATA, (sbits & ((byte)1<<(7-s))) ? HIGH : LOW ); - #endif - digitalWrite(PIN_SR_CLOCK, HIGH); - } - } - - #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(iopts[IOPT_SPE_AUTO_REFRESH]) { - // handle refresh of RF and remote stations - // we refresh the station that's next in line - static byte next_sid_to_refresh = MAX_NUM_STATIONS>>1; - static byte lastnow = 0; - ulong curr_time = now_tz(); - byte _now = (curr_time & 0xFF); - if (lastnow != _now) { // perform this no more than once per second - lastnow = _now; - next_sid_to_refresh = (next_sid_to_refresh+1) % MAX_NUM_STATIONS; - byte bid=next_sid_to_refresh>>3,s=next_sid_to_refresh&0x07; - if(os.attrib_spe[bid]&(1<>3; - s=next_sid_to_refresh&0x07; - bool on = (station_bits[bid]>>s)&0x01; - uint16_t dur = 0; - if(on) { - byte sqi=pd.station_qid[next_sid_to_refresh]; - RuntimeQueueStruct *q=pd.queue+sqi; - if(sqi<255 && q->st>0 && q->st+q->dur>curr_time) { - dur = q->st+q->dur-curr_time; - } - } - switch_special_station(next_sid_to_refresh, on, dur); - } - } - } -} - -/** Read rain sensor status */ -void OpenSprinkler::detect_binarysensor_status(ulong curr_time) { - // sensor_type: 0 if normally closed, 1 if normally open - if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { - if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 - byte val = digitalReadExt(PIN_SENSOR1); - status.sensor1 = (val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1; - if(status.sensor1) { - if(!sensor1_on_timer) { - // add minimum of 5 seconds on delay - ulong delay_time = (ulong)iopts[IOPT_SENSOR1_ON_DELAY]*60; - sensor1_on_timer = curr_time + (delay_time>5?delay_time:5); - sensor1_off_timer = 0; - } else { - if(curr_time > sensor1_on_timer) { - status.sensor1_active = 1; - } - } - } else { - if(!sensor1_off_timer) { - ulong delay_time = (ulong)iopts[IOPT_SENSOR1_OFF_DELAY]*60; - sensor1_off_timer = curr_time + (delay_time>5?delay_time:5); - sensor1_on_timer = 0; - } else { - if(curr_time > sensor1_off_timer) { - status.sensor1_active = 0; - } - } - } - } - -// 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) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 - byte val = digitalReadExt(PIN_SENSOR2); - status.sensor2 = (val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1; - if(status.sensor2) { - if(!sensor2_on_timer) { - // add minimum of 5 seconds on delay - ulong delay_time = (ulong)iopts[IOPT_SENSOR2_ON_DELAY]*60; - sensor2_on_timer = curr_time + (delay_time>5?delay_time:5); - sensor2_off_timer = 0; - } else { - if(curr_time > sensor2_on_timer) { - status.sensor2_active = 1; - } - } - } else { - if(!sensor2_off_timer) { - ulong delay_time = (ulong)iopts[IOPT_SENSOR2_OFF_DELAY]*60; - sensor2_off_timer = curr_time + (delay_time>5?delay_time:5); - sensor2_on_timer = 0; - } else { - if(curr_time > sensor2_off_timer) { - status.sensor2_active = 0; - } - } - } - } - -#endif -} - -/** Return program switch status */ -byte OpenSprinkler::detect_programswitch_status(ulong curr_time) { - byte ret = 0; - if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { - static byte sensor1_hist = 0; - if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 - status.sensor1 = (digitalReadExt(PIN_SENSOR1) != iopts[IOPT_SENSOR1_OPTION]); // is switch activated? - sensor1_hist = (sensor1_hist<<1) | status.sensor1; - // basic noise filtering: only trigger if sensor matches pattern: - // i.e. two consecutive lows followed by two consecutive highs - if((sensor1_hist&0b1111) == 0b0011) { - ret |= 0x01; - } - } -#if defined(ESP8266) || defined(PIN_SENSOR2) - if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { - static byte sensor2_hist = 0; - if(hw_rev==2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 - status.sensor2 = (digitalReadExt(PIN_SENSOR2) != iopts[IOPT_SENSOR2_OPTION]); // is sensor activated? - sensor2_hist = (sensor2_hist<<1) | status.sensor2; - if((sensor2_hist&0b1111) == 0b0011) { - ret |= 0x02; - } - } -#endif - return ret; -} - -void OpenSprinkler::sensor_resetall() { - sensor1_on_timer = 0; - sensor1_off_timer = 0; - sensor1_active_lasttime = 0; - sensor2_on_timer = 0; - sensor2_off_timer = 0; - sensor2_active_lasttime = 0; - old_status.sensor1_active = status.sensor1_active = 0; - old_status.sensor2_active = status.sensor2_active = 0; -} - -/** Read current sensing value - * OpenSprinkler 2.3 and above have a 0.2 ohm current sensing resistor. - * Therefore the conversion from analog reading to milli-amp is: - * (r/1024)*3.3*1000/0.2 (DC-powered controller) - * AC-powered controller has a built-in precision rectifier to sense - * the peak AC current. Therefore the actual current is discounted by 0.707 - * ESP8266's analog reference voltage is 1.0 instead of 3.3, therefore - * it's further discounted by 1/3.3 - */ -#if defined(ARDUINO) -uint16_t OpenSprinkler::read_current() { - float scale = 1.0f; - if(status.has_curr_sense) { - if (hw_type == HW_TYPE_DC) { - #if defined(ESP8266) - scale = 4.88; - #else - scale = 16.11; - #endif - } else if (hw_type == HW_TYPE_AC) { - #if defined(ESP8266) - scale = 3.45; - #else - scale = 11.39; - #endif - } else { - scale = 0.0; // for other controllers, current is 0 - } - /* do an average */ - const byte K = 8; - uint16_t sum = 0; - for(byte i=0;i=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 -} - -/** Convert hex code to ulong integer */ -static ulong hex2ulong(byte *code, byte len) { - char c; - ulong v = 0; - for(byte i=0;i='0' && c<='9') { - v += (c-'0'); - } else if (c>='A' && c<='F') { - v += 10 + (c-'A'); - } else if (c>='a' && c<='f') { - v += 10 + (c-'a'); - } else { - return 0; - } - } - return v; -} - -/** Parse RF code into on/off/timeing sections */ -uint16_t OpenSprinkler::parse_rfstation_code(RFStationData *data, ulong* on, ulong *off) { - ulong v; - v = hex2ulong(data->on, sizeof(data->on)); - if (!v) return 0; - if (on) *on = v; - v = hex2ulong(data->off, sizeof(data->off)); - if (!v) return 0; - if (off) *off = v; - v = hex2ulong(data->timing, sizeof(data->timing)); - if (!v) return 0; - return v; -} - -/** Get station data */ -void OpenSprinkler::get_station_data(byte sid, StationData* data) { - file_read_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); -} - -/** Set station data */ -/* -void OpenSprinkler::set_station_data(byte sid, StationData* data) { - file_write_block(STATIONS_FILENAME, data, (uint32_t)sid*sizeof(StationData), sizeof(StationData)); -} -*/ - -/** Get station name */ -void OpenSprinkler::get_station_name(byte sid, char tmp[]) { - tmp[STATION_NAME_SIZE]=0; - file_read_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); -} - -/** Set station name */ -void OpenSprinkler::set_station_name(byte sid, char tmp[]) { - // todo: store the right size - tmp[STATION_NAME_SIZE]=0; - char n0[STATION_NAME_SIZE+1]; - get_station_name(sid, n0); - size_t len = strlen(n0); - if(len!=strlen(tmp) || memcmp(n0, tmp, len)!=0) { // only write if the name has changed - file_write_block(STATIONS_FILENAME, tmp, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, name), STATION_NAME_SIZE); - } -} - -/** Get station type */ -byte OpenSprinkler::get_station_type(byte sid) { - return file_read_byte(STATIONS_FILENAME, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type)); -} - -byte OpenSprinkler::is_sequential_station(byte sid) { - return attrib_grp[sid] != PARALLEL_GROUP_ID; -} - -byte OpenSprinkler::is_master_station(byte sid) { - for (byte mas = 0; mas < NUM_MASTER_ZONES; mas++) { - if (get_master_id(mas) && (get_master_id(mas) - 1 == sid)) { - return 1; - } - } - return 0; -} - -byte OpenSprinkler::is_running(byte sid) { - return station_bits[(sid >> 3)] >> (sid & 0x07) & 1; -} - -byte OpenSprinkler::get_master_id(byte mas) { - return masters[mas][MASOPT_SID]; -} - -int16_t OpenSprinkler::get_on_adj(byte mas) { - return water_time_decode_signed(masters[mas][MASOPT_ON_ADJ]); -} - -int16_t OpenSprinkler::get_off_adj(byte mas) { - return water_time_decode_signed(masters[mas][MASOPT_OFF_ADJ]); -} - -byte OpenSprinkler::bound_to_master(byte sid, byte mas) { - byte bid = sid >> 3; - byte s = sid & 0x07; - byte attributes = 0; - - switch (mas) { - case MASTER_1: - attributes= attrib_mas[bid]; - break; - case MASTER_2: - attributes = attrib_mas2[bid]; - break; - default: - break; - } - - return attributes & (1 << s); -} - -byte OpenSprinkler::get_station_gid(byte sid) { - return attrib_grp[sid]; -} - -void OpenSprinkler::set_station_gid(byte sid, byte gid) { - attrib_grp[sid] = gid; -} - -/** Save all station attribs to file (backward compatibility) */ -void OpenSprinkler::attribs_save() { - // re-package attribute bits and save - byte bid, s, sid=0; - StationAttrib at, at0; - memset(&at, 0, sizeof(StationAttrib)); - byte ty = STN_TYPE_STANDARD, ty0; - for(bid=0;bid>s) & 1; - at.igs = (attrib_igs[bid]>>s) & 1; - at.mas2= (attrib_mas2[bid]>>s)& 1; - at.igs2= (attrib_igs2[bid]>>s) & 1; - at.igrd= (attrib_igrd[bid]>>s) & 1; - at.dis = (attrib_dis[bid]>>s) & 1; - at.gid = get_station_gid(sid); - set_station_gid(sid, at.gid); - - // only write if content has changed: this is important for LittleFS as otherwise the overhead is too large - file_read_block(STATIONS_FILENAME, &at0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); - if(memcmp(&at,&at0,sizeof(StationAttrib))!=0) { - file_write_block(STATIONS_FILENAME, &at, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, attrib), sizeof(StationAttrib)); // attribte bits are 1 byte long - } - if(attrib_spe[bid]>>s==0) { - // if station special bit is 0, make sure to write type STANDARD - // only write if content has changed - file_read_block(STATIONS_FILENAME, &ty0, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); - if(ty!=ty0) { - file_write_block(STATIONS_FILENAME, &ty, (uint32_t)sid*sizeof(StationData)+offsetof(StationData, type), 1); // attribte bits are 1 byte long - } - } - } - } -} - -/** Load all station attribs from file (backward compatibility) */ -void OpenSprinkler::attribs_load() { - // load and re-package attributes - byte bid, s, sid=0; - StationAttrib at; - byte ty; - memset(attrib_mas, 0, nboards); - memset(attrib_igs, 0, nboards); - memset(attrib_mas2, 0, nboards); - memset(attrib_igs2, 0, nboards); - memset(attrib_igrd, 0, nboards); - memset(attrib_dis, 0, nboards); - memset(attrib_spe, 0, nboards); - memset(attrib_grp, 0, MAX_NUM_STATIONS); - - for(bid=0;bid>3,s=sid&0x07; - if(!(os.attrib_spe[bid]&(1<sped, value); - break; - - case STN_TYPE_REMOTE: - switch_remotestation((RemoteStationData *)pdata->sped, value, dur); - break; - - case STN_TYPE_GPIO: - switch_gpiostation((GPIOStationData *)pdata->sped, value); - break; - - case STN_TYPE_HTTP: - switch_httpstation((HTTPStationData *)pdata->sped, value); - break; - } - } -} - -/** Set station bit - * This function sets/resets the corresponding station bit variable - * You have to call apply_all_station_bits next to apply the bits - * (which results in physical actions of opening/closing valves). - */ -byte OpenSprinkler::set_station_bit(byte sid, byte value, uint16_t dur) { - byte *data = station_bits+(sid>>3); // pointer to the station byte - byte mask = (byte)1<<(sid&0x07); // mask - if (value) { - if((*data)&mask) return 0; // if bit is already set, return no change - else { - (*data) = (*data) | mask; - engage_booster = true; // if bit is changing from 0 to 1, set engage_booster - switch_special_station(sid, 1, dur); // handle special stations - return 1; - } - } else { - if(!((*data)&mask)) return 0; // if bit is already reset, return no change - else { - (*data) = (*data) & (~mask); - if(hw_type == HW_TYPE_LATCH) { - engage_booster = true; // if LATCH controller, engage booster when bit changes - } - switch_special_station(sid, 0); // handle special stations - return 255; - } - } - return 0; -} - -/** Clear all station bits */ -void OpenSprinkler::clear_all_station_bits() { - byte sid; - for(sid=0;sid<=MAX_NUM_STATIONS;sid++) { - set_station_bit(sid, 0); - } -} - -#if !defined(ARDUINO) -int rf_gpio_fd = -1; -#endif - -/** Transmit one RF signal bit */ -void transmit_rfbit(ulong lenH, ulong lenL) { -#if defined(ARDUINO) - #if defined(ESP8266) - digitalWrite(PIN_RFTX, 1); - delayMicroseconds(lenH); - digitalWrite(PIN_RFTX, 0); - delayMicroseconds(lenL); - #else - PORT_RF |= (1<=0) { - if ((code>>i) & 1) { - transmit_rfbit(len3, len); - } else { - transmit_rfbit(len, len3); - } - i--; - }; - // send sync - transmit_rfbit(len, len31); - } -} - -/** Switch RF station - * This function takes a RF code, - * parses it into signals and timing, - * and sends it out through RF transmitter. - */ -void OpenSprinkler::switch_rfstation(RFStationData *data, bool turnon) { - ulong on, off; - uint16_t length = parse_rfstation_code(data, &on, &off); - - if(PIN_RFTX == 255) return; // ignore RF station if RF pin disabled - -#if defined(ARDUINO) - #if defined(ESP8266) - rfswitch.enableTransmit(PIN_RFTX); - rfswitch.setProtocol(1); - rfswitch.setPulseLength(length); - rfswitch.send(turnon ? on : off, 24); - #else - send_rfsignal(turnon ? on : off, length); - #endif -#else - // pre-open gpio file to minimize overhead - rf_gpio_fd = gpio_fd_open(PIN_RFTX); - send_rfsignal(turnon ? on : off, length); - gpio_fd_close(rf_gpio_fd); - rf_gpio_fd = -1; -#endif - -} - -/** Switch GPIO station - * Special data for GPIO Station is three bytes of ascii decimal (not hex) - * First two bytes are zero padded GPIO pin number. - * Third byte is either 0 or 1 for active low (GND) or high (+5V) relays - */ -void OpenSprinkler::switch_gpiostation(GPIOStationData *data, bool turnon) { - byte gpio = (data->pin[0] - '0') * 10 + (data->pin[1] - '0'); - byte activeState = data->active - '0'; - - pinMode(gpio, OUTPUT); - if (turnon) - digitalWrite(gpio, activeState); - else - digitalWrite(gpio, 1-activeState); -} - -/** Callback function for switching remote station */ -void remote_http_callback(char* buffer) { -/* - DEBUG_PRINTLN(buffer); -*/ -} - -int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { - -#if defined(ARDUINO) - - Client *client; - #if defined(ESP8266) - WiFiClient wifiClient; - client = &wifiClient; - #else - EthernetClient etherClient; - client = ðerClient; - #endif - - #define HTTP_CONNECT_NTRIES 3 - byte tries = 0; - do { - DEBUG_PRINT(server); - DEBUG_PRINT(":"); - DEBUG_PRINT(port); - DEBUG_PRINT("("); - DEBUG_PRINT(tries); - DEBUG_PRINTLN(")"); - - if(client->connect(server, port)==1) break; - tries++; - } while(triesstop(); - return HTTP_RQT_CONNECT_ERR; - } -#else - - EthernetClient etherClient; - EthernetClient *client = ðerClient; - struct hostent *host; - DEBUG_PRINT(server); - DEBUG_PRINT(":"); - DEBUG_PRINTLN(port); - host = gethostbyname(server); - if (!host) { return HTTP_RQT_CONNECT_ERR; } - if(!client->connect((uint8_t*)host->h_addr, port)) { - DEBUG_PRINT(F("failed.")); - client->stop(); - return HTTP_RQT_CONNECT_ERR; - } - -#endif - - uint16_t len = strlen(p); - if(len > ETHER_BUFFER_SIZE) len = ETHER_BUFFER_SIZE; - if(client->connected()) { - client->write((uint8_t *)p, len); - } else { - DEBUG_PRINTLN(F("clint no longer connected")); - } - memset(ether_buffer, 0, ETHER_BUFFER_SIZE); - uint32_t stoptime = millis()+timeout; - - int pos = 0; -#if defined(ARDUINO) - // 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 - while(true) { - int nbytes = client->available(); - if(nbytes>0) { - if(pos+nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE-pos; // cannot read more than buffer size - client->read((uint8_t*)ether_buffer+pos, nbytes); - pos+=nbytes; - } - if(millis()>stoptime) { - DEBUG_PRINTLN(F("host timeout occured")); - //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far - break; - } - if(!client->connected() && !client->available()) { - //DEBUG_PRINTLN(F("host disconnected")); - break; - } - } -#else - while(client->connected()) { - int len=client->read((uint8_t *)ether_buffer+pos, ETHER_BUFFER_SIZE); - if (len<=0) continue; - pos+=len; - if(millis()>stoptime) { - DEBUG_PRINTLN(F("host timeout occured")); - //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far - break; - } - } -#endif - ether_buffer[pos]=0; // properly end buffer with 0 - client->stop(); - if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN; - if(callback) callback(ether_buffer); - return HTTP_RQT_SUCCESS; -} - -int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { - char server[20]; - byte ip[4]; - ip[0] = ip4>>24; - ip[1] = (ip4>>16)&0xff; - ip[2] = (ip4>>8)&0xff; - ip[3] = ip4&0xff; - sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return send_http_request(server, port, p, callback, timeout); -} - -int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { - char * server = strtok(server_with_port, ":"); - char * port = strtok(NULL, ":"); - return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); -} - -/** Switch remote station - * This function takes a remote station code, - * parses it into remote IP, port, station index, - * and makes a HTTP GET request. - * The remote controller is assumed to have the same - * password as the main controller - */ -void OpenSprinkler::switch_remotestation(RemoteStationData *data, bool turnon, uint16_t dur) { - RemoteStationData copy; - memcpy((char*)©, (char*)data, sizeof(RemoteStationData)); - - uint32_t ip4 = hex2ulong(copy.ip, sizeof(copy.ip)); - uint16_t port = (uint16_t)hex2ulong(copy.port, sizeof(copy.port)); - - byte ip[4]; - ip[0] = ip4>>24; - ip[1] = (ip4>>16)&0xff; - ip[2] = (ip4>>8)&0xff; - ip[3] = ip4&0xff; - - // use tmp_buffer starting at a later location - // because remote station data is loaded at the beginning - char *p = tmp_buffer; - BufferFiller bf = p; - // 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 - // if no auto refresh, we will give the maximum allowed duration, and station will be turned off when off command is sent - uint16_t timer = 0; - if(turnon) { - if(dur>0) { - timer = dur; - } else { - timer = iopts[IOPT_SPE_AUTO_REFRESH]?4*MAX_NUM_STATIONS:64800; - } - } - bf.emit_p(PSTR("GET /cm?pw=$O&sid=$D&en=$D&t=$D"), - SOPT_PASSWORD, - (int)hex2ulong(copy.sid, sizeof(copy.sid)), - turnon, timer); - bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), - ip[0],ip[1],ip[2],ip[3]); - - char server[20]; - sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - send_http_request(server, port, p, remote_http_callback); -} - -/** Switch http station - * This function takes an http station code, - * parses it into a server name and two HTTP GET requests. - */ -void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon) { - - HTTPStationData copy; - // make a copy of the HTTP station data and work with it - memcpy((char*)©, (char*)data, sizeof(HTTPStationData)); - char * server = strtok((char *)copy.data, ","); - char * port = strtok(NULL, ","); - char * on_cmd = strtok(NULL, ","); - char * off_cmd = strtok(NULL, ","); - char * cmd = turnon ? on_cmd : off_cmd; - - char *p = tmp_buffer; - BufferFiller bf = p; - - if(cmd==NULL || server==NULL) return; // proceed only if cmd and server are valid - - bf.emit_p(PSTR("GET /$S HTTP/1.0\r\nHOST: $S\r\n\r\n"), cmd, server); - - send_http_request(server, atoi(port), p, remote_http_callback); -} - -/** Prepare factory reset */ -void OpenSprinkler::pre_factory_reset() { - // for ESP8266: wipe out flash - #if defined(ESP8266) - lcd_print_line_clear_pgm(PSTR("Wiping flash.."), 0); - lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); - LittleFS.format(); - #else - // remove 'done' file as an indicator for reset - // todo os2.3 and ospi: delete log files and/or wipe SD card - remove_file(DONE_FILENAME); - #endif -} - -/** Factory reset */ -void OpenSprinkler::factory_reset() { -#if defined(ARDUINO) - lcd_print_line_clear_pgm(PSTR("Factory reset"), 0); - lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); -#else - DEBUG_PRINT("factory reset..."); -#endif - - // 1. reset integer options (by saving default values) - iopts_save(); - // reset string options by first wiping the file clean then write default values - memset(tmp_buffer, 0, MAX_SOPTS_SIZE); - for(int i=0; iname[0]='S'; - pdata->name[3]=0; - pdata->name[4]=0; - StationAttrib at; - memset(&at, 0, sizeof(StationAttrib)); - at.mas=1; - pdata->attrib=at; // mas:1 - pdata->type=STN_TYPE_STANDARD; - pdata->sped[0]='0'; - pdata->sped[1]=0; - for(int i=0; iname[1]='0'+(sid/10); // default station name - pdata->name[2]='0'+(sid%10); - } else { - pdata->name[1]='0'+(sid/100); - pdata->name[2]='0'+((sid%100)/10); - pdata->name[3]='0'+(sid%10); - } - file_write_block(STATIONS_FILENAME, pdata, sizeof(StationData)*i, sizeof(StationData)); - } - - attribs_load(); // load and repackage attrib bits (for backward compatibility) - - // 3. write non-volatile controller status - nvdata.reboot_cause = REBOOT_CAUSE_RESET; - nvdata_save(); - last_reboot_cause = nvdata.reboot_cause; - - // 4. write program data: just need to write a program counter: 0 - file_write_byte(PROG_FILENAME, 0, 0); - - // 5. write 'done' file - file_write_byte(DONE_FILENAME, 0, 1); -} - -#define str(s) #s -#define xstr(s) str(s) - -/** Parse OTC configuration */ -#if defined(ESP8266) -void OpenSprinkler::parse_otc_config() { - char server[MAX_SOPTS_SIZE+1] = {0}; - char token[MAX_SOPTS_SIZE+1] = {0}; - int port = DEFAULT_OTC_PORT; - int en = 0; - - char *config = tmp_buffer; - sopt_load(SOPT_OTC_OPTS, config); - if (*config != 0) { - sscanf(config, "\"en\":%d,\"token\":\"%" xstr(MAX_SOPTS_SIZE) "[^\"]\",\"server\":\"%" xstr(MAX_SOPTS_SIZE) "[^\"]\",\"port\":%d", - &en, token, server, &port); - token[MAX_SOPTS_SIZE] = 0; - server[MAX_SOPTS_SIZE] = 0; - } - otc.en = en; - otc.token = String(token); - otc.server = String(server); - otc.port = 80; -} -#endif - -/** Setup function for options */ -void OpenSprinkler::options_setup() { - - // Check reset conditions: - if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)<220 || // fw version is invalid (<219) - !file_exists(DONE_FILENAME)) { // done file doesn't exist - - factory_reset(); - - } else { - - iopts_load(); - nvdata_load(); - last_reboot_cause = nvdata.reboot_cause; - nvdata.reboot_cause = REBOOT_CAUSE_POWERON; - nvdata_save(); - #if defined(ESP8266) - wifi_ssid = sopt_load(SOPT_STA_SSID); - wifi_pass = sopt_load(SOPT_STA_PASS); - sopt_load(SOPT_STA_BSSID_CHL, tmp_buffer); - if(tmp_buffer[0]!=0) { - char *mac = strchr(tmp_buffer, '@'); - if(mac!=NULL && isValidMAC(tmp_buffer)) { // check if bssid is valid MAC - *mac=0; // terminate MAC string - int chl = atoi(mac+1); - if(chl>=0 && chl<=255) { - str2mac(tmp_buffer, wifi_bssid); - wifi_channel = chl; - } - } - } - parse_otc_config(); - #endif - attribs_load(); - } - -#if defined(ARDUINO) // handle AVR buttons - byte button = button_read(BUTTON_WAIT_NONE); - - switch(button & BUTTON_MASK) { - - case BUTTON_1: - // if BUTTON_1 is pressed during startup, go to 'reset all options' - ui_set_options(IOPT_RESET); - if (iopts[IOPT_RESET]) { - pre_factory_reset(); - reboot_dev(REBOOT_CAUSE_RESET); - } - 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); - lcd_print_line_clear_pgm(PSTR(" B3:proceed"), 1); - do { - button = button_read(BUTTON_WAIT_NONE); - } while(!((button&BUTTON_MASK)==BUTTON_3 && (button&BUTTON_FLAG_DOWN))); - // set test mode parameters - - //iopts[IOPT_WIFI_MODE] = WIFI_MODE_STA; - wifi_testmode = 1; - #if defined(TESTMODE_SSID) - wifi_ssid = TESTMODE_SSID; - wifi_pass = TESTMODE_PASS; - #else - wifi_ssid = "ostest"; - wifi_pass = "opendoor"; - #endif - button = 0; - #endif - - break; - - case BUTTON_3: - // if BUTTON_3 is pressed during startup, enter Setup option mode - lcd_print_line_clear_pgm(PSTR("==Set Options=="), 0); - delay(DISPLAY_MSG_MS); - lcd_print_line_clear_pgm(PSTR("B1/B2:+/-, B3:->"), 0); - lcd_print_line_clear_pgm(PSTR("Hold B3 to save"), 1); - do { - button = button_read(BUTTON_WAIT_NONE); - } while (!(button & BUTTON_FLAG_DOWN)); - lcd.clear(); - ui_set_options(0); - if (iopts[IOPT_RESET]) { - pre_factory_reset(); - reboot_dev(REBOOT_CAUSE_RESET); - } - break; - } - - // turn on LCD backlight and contrast - lcd_set_brightness(); - lcd_set_contrast(); - - if (!button) { - // flash screen - lcd_print_line_clear_pgm(PSTR(" OpenSprinkler"),0); - lcd.setCursor((hw_type==HW_TYPE_LATCH)?2:4, 1); - lcd_print_pgm(PSTR("v")); - byte hwv = iopts[IOPT_HW_VERSION]; - lcd.print((char)('0'+(hwv/10))); - lcd.print('.'); - #if defined(ESP8266) - lcd.print(hw_rev); - #else - lcd.print((char)('0'+(hwv%10))); - #endif - switch(hw_type) { - case HW_TYPE_DC: - lcd_print_pgm(PSTR(" DC")); - break; - case HW_TYPE_LATCH: - lcd_print_pgm(PSTR(" LATCH")); - break; - default: - lcd_print_pgm(PSTR(" AC")); - } - delay(1500); - #if defined(ARDUINO) - lcd.setCursor(2, 1); - lcd_print_pgm(PSTR("FW ")); - lcd.print((char)('0'+(OS_FW_VERSION/100))); - lcd.print('.'); - lcd.print((char)('0'+((OS_FW_VERSION/10)%10))); - lcd.print('.'); - lcd.print((char)('0'+(OS_FW_VERSION%10))); - lcd.print('('); - lcd.print(OS_FW_MINOR); - lcd.print(')'); - delay(1000); - #endif - } -#endif -} - -/** Load non-volatile controller status data from file */ -void OpenSprinkler::nvdata_load() { - file_read_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); - old_status = status; -} - -/** Save non-volatile controller status data */ -void OpenSprinkler::nvdata_save() { - file_write_block(NVCON_FILENAME, &nvdata, 0, sizeof(NVConData)); -} - -void load_wt_monthly(char* wto); - -/** Load integer options from file */ -void OpenSprinkler::iopts_load() { - file_read_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); - nboards = iopts[IOPT_EXT_BOARDS]+1; - nstations = nboards * 8; - status.enabled = iopts[IOPT_DEVICE_ENABLE]; - iopts[IOPT_FW_VERSION] = OS_FW_VERSION; - iopts[IOPT_FW_MINOR] = OS_FW_MINOR; - /* Reject the former default 50.97.210.169 NTP IP address as - * it no longer works, yet is carried on by people's saved - * configs when they upgrade from older versions. - * IOPT_NTP_IP1 = 0 leads to the new good default behavior. */ - if (iopts[IOPT_NTP_IP1] == 50 && iopts[IOPT_NTP_IP2] == 97 && - iopts[IOPT_NTP_IP3] == 210 && iopts[IOPT_NTP_IP4] == 169) { - iopts[IOPT_NTP_IP1] = 0; - iopts[IOPT_NTP_IP2] = 0; - iopts[IOPT_NTP_IP3] = 0; - iopts[IOPT_NTP_IP4] = 0; - } - populate_master(); - sopt_load(SOPT_WEATHER_OPTS, tmp_buffer); - if(iopts[IOPT_USE_WEATHER]==WEATHER_METHOD_MONTHLY) { - load_wt_monthly(tmp_buffer); - } -} - -void OpenSprinkler::populate_master() { - masters[MASTER_1][MASOPT_SID] = iopts[IOPT_MASTER_STATION]; - masters[MASTER_1][MASOPT_ON_ADJ] = iopts[IOPT_MASTER_ON_ADJ]; - masters[MASTER_1][MASOPT_OFF_ADJ] = iopts[IOPT_MASTER_OFF_ADJ]; - - 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]; -} - -/** Save integer options to file */ -void OpenSprinkler::iopts_save() { - file_write_block(IOPTS_FILENAME, iopts, 0, NUM_IOPTS); - nboards = iopts[IOPT_EXT_BOARDS]+1; - nstations = nboards * 8; - status.enabled = iopts[IOPT_DEVICE_ENABLE]; -} - -/** Load a string option from file */ -void OpenSprinkler::sopt_load(byte oid, char *buf) { - file_read_block(SOPTS_FILENAME, buf, MAX_SOPTS_SIZE*oid, MAX_SOPTS_SIZE); - buf[MAX_SOPTS_SIZE]=0; // ensure the string ends properly -} - -/** Load a string option from file, return String */ -String OpenSprinkler::sopt_load(byte oid) { - sopt_load(oid, tmp_buffer); - String str = tmp_buffer; - return str; -} - -/** Save a string option to file */ -bool OpenSprinkler::sopt_save(byte oid, const char *buf) { - // smart save: if value hasn't changed, don't write - if(file_cmp_block(SOPTS_FILENAME, buf, (ulong)MAX_SOPTS_SIZE*oid)==0) return false; - int len = strlen(buf); - if(len>=MAX_SOPTS_SIZE) { - file_write_block(SOPTS_FILENAME, buf, (ulong)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); - } - return true; -} - -// ============================== -// Controller Operation Functions -// ============================== - -/** Enable controller operation */ -void OpenSprinkler::enable() { - status.enabled = 1; - iopts[IOPT_DEVICE_ENABLE] = 1; - iopts_save(); -} - -/** Disable controller operation */ -void OpenSprinkler::disable() { - status.enabled = 0; - iopts[IOPT_DEVICE_ENABLE] = 0; - iopts_save(); -} - -/** Start rain delay */ -void OpenSprinkler::raindelay_start() { - status.rain_delayed = 1; - nvdata_save(); -} - -/** Stop rain delay */ -void OpenSprinkler::raindelay_stop() { - status.rain_delayed = 0; - nvdata.rd_stop_time = 0; - nvdata_save(); -} - -/** LCD and button functions */ -#if defined(ARDUINO) // AVR 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); - } -} - -/** print a program memory string to a given line with clearing */ -#if defined(ESP8266) -void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P str, byte line) { -#else -void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P PROGMEM str, byte line) { -#endif - lcd.setCursor(0, line); - uint8_t c; - int8_t cnt = 0; - while((c=pgm_read_byte(str++))!= '\0') { - lcd.print((char)c); - cnt++; - } - for(; (16-cnt) >= 0; cnt ++) lcd_print_pgm(PSTR(" ")); -} - -void OpenSprinkler::lcd_print_2digit(int v) -{ - lcd.print((int)(v/10)); - lcd.print((int)(v%10)); -} - -/** print time to a given line */ -void OpenSprinkler::lcd_print_time(time_t t) -{ -#if defined(ESP8266) - 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_2digit(month(t)); - lcd_print_pgm(PSTR("-")); - lcd_print_2digit(day(t)); -#if defined(ESP8266) - lcd.display(); - lcd.setAutoDisplay(true); -#endif -} - -/** print ip address */ -void OpenSprinkler::lcd_print_ip(const byte *ip, byte endian) { -#if defined(ESP8266) - lcd.clear(0, 1); -#else - lcd.clear(); -#endif - lcd.setCursor(0, 0); - for (byte i=0; i<4; i++) { - lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); - if(i<3) lcd_print_pgm(PSTR(".")); - } -} - -/** print mac address */ -void OpenSprinkler::lcd_print_mac(const byte *mac) { - lcd.setCursor(0, 0); - for(byte i=0; i<6; i++) { - if(i) lcd_print_pgm(PSTR("-")); - lcd.print((mac[i]>>4), HEX); - lcd.print((mac[i]&0x0F), HEX); - if(i==4) lcd.setCursor(0, 1); - } - if(useEth) { - lcd_print_pgm(PSTR(" (Ether MAC)")); - } else { - lcd_print_pgm(PSTR(" (WiFi MAC)")); - } -} - -/** print station bits */ -void OpenSprinkler::lcd_print_screen(char c) { -#if defined(ESP8266) - 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' - } else { - lcd.print(F("E")); - lcd.print((int)status.display_board); - lcd.print(F(":")); // extension boards are displayed as E1, E2... - } - if (!status.enabled) { - lcd.print(F("-Disabled!-")); - } else { - byte bitvalue = station_bits[status.display_board]; - for (byte s=0; s<8; s++) { - byte sid = (byte)status.display_board<<3; - sid += (s+1); - if (sid == iopts[IOPT_MASTER_STATION]) { - 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 { - lcd.print((bitvalue&1) ? c : '_'); - } - bitvalue >>= 1; - } - } - //lcd.print(F(" ")); - - lcd.setCursor(LCD_CURSOR_REMOTEXT, 1); - lcd.write(iopts[IOPT_REMOTE_EXT_MODE]?ICON_REMOTEXT:' '); - - lcd.setCursor(LCD_CURSOR_RAINDELAY, 1); - lcd.write((status.rain_delayed || status.pause_state)?ICON_RAINDELAY:' '); - - // write sensor 1 icon - lcd.setCursor(LCD_CURSOR_SENSOR1, 1); - switch(iopts[IOPT_SENSOR1_TYPE]) { - case SENSOR_TYPE_RAIN: - lcd.write(status.sensor1_active?ICON_RAIN:(status.sensor1?'R':'r')); - break; - case SENSOR_TYPE_SOIL: - lcd.write(status.sensor1_active?ICON_SOIL:(status.sensor1?'S':'s')); - break; - case SENSOR_TYPE_FLOW: - lcd.write(flowcount_rt>0?'F':'f'); - break; - case SENSOR_TYPE_PSWITCH: - lcd.write(status.sensor1?'P':'p'); - break; - default: - lcd.write(' '); - break; - } - - // write sensor 2 icon - lcd.setCursor(LCD_CURSOR_SENSOR2, 1); - switch(iopts[IOPT_SENSOR2_TYPE]) { - case SENSOR_TYPE_RAIN: - lcd.write(status.sensor2_active?ICON_RAIN:(status.sensor2?'R':'r')); - break; - case SENSOR_TYPE_SOIL: - lcd.write(status.sensor2_active?ICON_SOIL:(status.sensor2?'S':'s')); - break; - // sensor2 cannot be flow sensor - /*case SENSOR_TYPE_FLOW: - lcd.write('F'); - break;*/ - case SENSOR_TYPE_PSWITCH: - lcd.write(status.sensor2?'Q':'q'); - break; - default: - lcd.write(' '); - break; - } - - lcd.setCursor(LCD_CURSOR_NETWORK, 1); -#if defined(ESP8266) - if(useEth) { - lcd.write(eth.connected()?ICON_ETHER_CONNECTED:ICON_ETHER_DISCONNECTED); // todo: need to detect ether status - } - else - lcd.write(WiFi.status()==WL_CONNECTED?ICON_WIFI_CONNECTED:ICON_WIFI_DISCONNECTED); -#else - 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(ESP8266) - - if(useEth || (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP())) { - lcd.setCursor(0, -1); - if(status.rain_delayed) { - lcd.print(F(" ")); - } else if(status.pause_state) { - lcd.print(F("")); - } else if(status.program_busy) { - lcd.print(F(" ")); - } else { - lcd.print(F(" (System Idle) ")); - } - - lcd.setCursor(2, 2); - if(status.program_busy && !status.pause_state) { - //lcd.print(F("Curr: ")); - lcd.print(read_current()); - lcd.print(F(" mA ")); - } else { - lcd.clear(2, 2); - } - } -#endif -#if defined(ESP8266) - lcd.display(); - lcd.setAutoDisplay(true); -#endif -} - -/** print a version number */ -void OpenSprinkler::lcd_print_version(byte v) { - if(v > 99) { - lcd.print(v/100); - lcd.print("."); - } - if(v>9) { - lcd.print((v/10)%10); - lcd.print("."); - } - lcd.print(v%10); -} - -/** print an option value */ -void OpenSprinkler::lcd_print_option(int i) { - // each prompt string takes 16 characters - strncpy_P0(tmp_buffer, iopt_prompts+16*i, 16); - lcd.setCursor(0, 0); - lcd.print(tmp_buffer); - lcd_print_line_clear_pgm(PSTR(""), 1); - lcd.setCursor(0, 1); - int tz; - switch(i) { - case IOPT_HW_VERSION: - lcd.print("v"); - case IOPT_FW_VERSION: - lcd_print_version(iopts[i]); - break; - case IOPT_TIMEZONE: // if this is the time zone option, do some conversion - tz = (int)iopts[i]-48; - if (tz>=0) lcd_print_pgm(PSTR("+")); - else {lcd_print_pgm(PSTR("-")); tz=-tz;} - lcd.print(tz/4); // print integer portion - lcd_print_pgm(PSTR(":")); - tz = (tz%4)*15; - if (tz==0) lcd_print_pgm(PSTR("00")); - else { - lcd.print(tz); // print fractional portion - } - break; - case IOPT_MASTER_ON_ADJ: - case IOPT_MASTER_ON_ADJ_2: - case IOPT_MASTER_OFF_ADJ: - case IOPT_MASTER_OFF_ADJ_2: - case IOPT_STATION_DELAY_TIME: - { - int16_t t=water_time_decode_signed(iopts[i]); - if(t>=0) lcd_print_pgm(PSTR("+")); - lcd.print(t); - } - break; - case IOPT_HTTPPORT_0: - lcd.print((unsigned int)(iopts[i+1]<<8)+iopts[i]); - break; - case IOPT_PULSE_RATE_0: - { - uint16_t fpr = (unsigned int)(iopts[i+1]<<8)+iopts[i]; - lcd.print(fpr/100); - lcd_print_pgm(PSTR(".")); - lcd.print((fpr/10)%10); - lcd.print(fpr%10); - } - break; - case IOPT_LCD_CONTRAST: - lcd_set_contrast(); - lcd.print((int)iopts[i]); - break; - case IOPT_LCD_BACKLIGHT: - lcd_set_brightness(); - lcd.print((int)iopts[i]); - break; - case IOPT_BOOST_TIME: - #if defined(ARDUINO) - if(hw_type==HW_TYPE_AC) { - lcd.print('-'); - } else { - lcd.print((int)iopts[i]*4); - lcd_print_pgm(PSTR(" ms")); - } - #else - lcd.print('-'); - #endif - break; - default: - // if this is a boolean option - if (pgm_read_byte(iopt_max+i)==1) - lcd_print_pgm(iopts[i] ? PSTR("Yes") : PSTR("No")); - else - lcd.print((int)iopts[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) - lcd_print_pgm(PSTR(" sec")); - -} - -/** Button functions */ -/** wait for button */ -byte OpenSprinkler::button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding) { - - int hold_time = 0; - - if (waitmode==BUTTON_WAIT_NONE || (waitmode == BUTTON_WAIT_HOLD && is_holding)) { - if (digitalReadExt(pin_butt) != 0) return BUTTON_NONE; - return butt | (is_holding ? BUTTON_FLAG_HOLD : 0); - } - - while (digitalReadExt(pin_butt) == 0 && - (waitmode == BUTTON_WAIT_RELEASE || (waitmode == BUTTON_WAIT_HOLD && hold_time= BUTTON_HOLD_MS) - butt |= BUTTON_FLAG_HOLD; - return butt; - -} - -/** read button and returns button value 'OR'ed with flag bits */ -byte OpenSprinkler::button_read(byte waitmode) -{ - static byte old = BUTTON_NONE; - byte curr = BUTTON_NONE; - byte is_holding = (old&BUTTON_FLAG_HOLD); - - delay(BUTTON_DELAY_MS); - - if (digitalReadExt(PIN_BUTTON_1) == 0) { - curr = button_read_busy(PIN_BUTTON_1, waitmode, BUTTON_1, is_holding); - } else if (digitalReadExt(PIN_BUTTON_2) == 0) { - curr = button_read_busy(PIN_BUTTON_2, waitmode, BUTTON_2, is_holding); - } else if (digitalReadExt(PIN_BUTTON_3) == 0) { - curr = button_read_busy(PIN_BUTTON_3, waitmode, BUTTON_3, is_holding); - } - - // set flags in return value - byte ret = curr; - if (!(old&BUTTON_MASK) && (curr&BUTTON_MASK)) - ret |= BUTTON_FLAG_DOWN; - if ((old&BUTTON_MASK) && !(curr&BUTTON_MASK)) - ret |= BUTTON_FLAG_UP; - - old = curr; - - return ret; -} - -/** user interface for setting options during startup */ -void OpenSprinkler::ui_set_options(int oid) -{ - boolean finished = false; - byte button; - int i=oid; - - while(!finished) { - button = button_read(BUTTON_WAIT_HOLD); - - switch (button & BUTTON_MASK) { - case BUTTON_1: - if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || - i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || - i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || - i==IOPT_WIFI_MODE) break; // ignore non-editable options - if (pgm_read_byte(iopt_max+i) != iopts[i]) iopts[i] ++; - break; - - case BUTTON_2: - if (i==IOPT_FW_VERSION || i==IOPT_HW_VERSION || i==IOPT_FW_MINOR || - i==IOPT_HTTPPORT_0 || i==IOPT_HTTPPORT_1 || - i==IOPT_PULSE_RATE_0 || i==IOPT_PULSE_RATE_1 || - i==IOPT_WIFI_MODE) break; // ignore non-editable options - if (iopts[i] != 0) iopts[i] --; - break; - - case BUTTON_3: - if (!(button & BUTTON_FLAG_DOWN)) break; - if (button & BUTTON_FLAG_HOLD) { - // long press, save options - iopts_save(); - finished = true; - } - else { - // click, move to the next option - 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 { - i = (i+1) % NUM_IOPTS; - } - if(i==IOPT_SEQUENTIAL_RETIRED) i++; - if(i==IOPT_URS_RETIRED) i++; - if(i==IOPT_RSO_RETIRED) i++; - if (hw_type==HW_TYPE_AC && i==IOPT_BOOST_TIME) i++; // skip boost time for non-DC controller - #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; - } - - if (button != BUTTON_NONE) { - lcd_print_option(i); - } - } - lcd.noBlink(); -} - -/** 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(byte value) { -#if defined(PIN_LCD_BACKLIGHT) - #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - 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(ESP8266) - 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 -} -#endif // end of LCD and button functions - -#if defined(ESP8266) -#include "images.h" -void OpenSprinkler::flash_screen() { - lcd.setCursor(0, -1); - lcd.print(F(" OpenSprinkler")); - lcd.drawXbm(34, 24, WiFi_Logo_width, WiFi_Logo_height, (const byte*) WiFi_Logo_image); - lcd.setCursor(0, 2); - lcd.display(); - delay(1500); - lcd.clear(); - lcd.display(); -} - -void OpenSprinkler::toggle_screen_led() { - static byte status = 0; - status = 1-status; - set_screen_led(!status); -} - -void OpenSprinkler::set_screen_led(byte status) { - lcd.setColor(status ? WHITE : BLACK); - lcd.fillCircle(122, 58, 4); - lcd.display(); - lcd.setColor(WHITE); -} - -void OpenSprinkler::reset_to_ap() { - iopts[IOPT_WIFI_MODE] = WIFI_MODE_AP; - iopts_save(); - reboot_dev(REBOOT_CAUSE_RSTAP); -} - -void OpenSprinkler::config_ip() { - if(iopts[IOPT_USE_DHCP] == 0) { - byte *_ip = iopts+IOPT_STATIC_IP1; - IPAddress dvip(_ip[0], _ip[1], _ip[2], _ip[3]); - if(dvip==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_GATEWAY_IP1; - IPAddress gwip(_ip[0], _ip[1], _ip[2], _ip[3]); - if(gwip==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_SUBNET_MASK1; - IPAddress subn(_ip[0], _ip[1], _ip[2], _ip[3]); - if(subn==(uint32_t)0x00000000) return; - - _ip = iopts+IOPT_DNS_IP1; - IPAddress dnsip(_ip[0], _ip[1], _ip[2], _ip[3]); - - WiFi.config(dvip, gwip, subn, dnsip); - } -} - -void OpenSprinkler::save_wifi_ip() { - // todo: handle wired ethernet - if(iopts[IOPT_USE_DHCP] && WiFi.status() == WL_CONNECTED) { - memcpy(iopts+IOPT_STATIC_IP1, &(WiFi.localIP()[0]), 4); - memcpy(iopts+IOPT_GATEWAY_IP1, &(WiFi.gatewayIP()[0]),4); - memcpy(iopts+IOPT_DNS_IP1, &(WiFi.dnsIP()[0]), 4); - memcpy(iopts+IOPT_SUBNET_MASK1, &(WiFi.subnetMask()[0]), 4); - iopts_save(); - } -} - -void OpenSprinkler::detect_expanders() { - for(byte i=0;i<(MAX_NUM_BOARDS)/2;i++) { - byte address = EXP_I2CADDR_BASE+i; - byte type = IOEXP::detectType(address); - if(expanders[i]!=NULL) delete expanders[i]; - if(type==IOEXP_TYPE_9555) { - expanders[i] = new PCA9555(address); - expanders[i]->i2c_write(NXP_CONFIG_REG, 0); // set all channels to output - } else if(type==IOEXP_TYPE_8575){ - expanders[i] = new PCF8575(address); - } else { - expanders[i] = new IOEXP(address); - } - } -} -#endif -======= -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * OpenSprinkler library - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#include "OpenSprinkler.h" -#include "opensprinkler_server.h" -#include "gpio.h" -#include "testmode.h" -#include "program.h" - -/** Declare static data members */ -OSMqtt OpenSprinkler::mqtt; -NVConData OpenSprinkler::nvdata; -ConStatus OpenSprinkler::status; -ConStatus OpenSprinkler::old_status; - -byte OpenSprinkler::hw_type; -byte OpenSprinkler::hw_rev; -byte OpenSprinkler::nboards; -byte OpenSprinkler::nstations; -byte OpenSprinkler::station_bits[MAX_NUM_BOARDS]; -byte OpenSprinkler::engage_booster; -uint16_t OpenSprinkler::baseline_current; - -ulong OpenSprinkler::sensor1_on_timer; -ulong OpenSprinkler::sensor1_off_timer; -ulong OpenSprinkler::sensor1_active_lasttime; -ulong OpenSprinkler::sensor2_on_timer; -ulong OpenSprinkler::sensor2_off_timer; -ulong OpenSprinkler::sensor2_active_lasttime; -ulong OpenSprinkler::raindelay_on_lasttime; -ulong OpenSprinkler::pause_timer; - -ulong OpenSprinkler::flowcount_log_start; -ulong OpenSprinkler::flowcount_rt; -byte OpenSprinkler::button_timeout; -ulong OpenSprinkler::checkwt_lasttime; -ulong OpenSprinkler::checkwt_success_lasttime; -ulong OpenSprinkler::powerup_lasttime; -uint8_t OpenSprinkler::last_reboot_cause = REBOOT_CAUSE_NONE; -byte OpenSprinkler::weather_update_flag; - -// todo future: the following attribute bytes are for backward compatibility -byte OpenSprinkler::attrib_mas[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igs[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_mas2[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igs2[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; -byte OpenSprinkler::attrib_grp[MAX_NUM_STATIONS]; -byte OpenSprinkler::masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; - -extern char tmp_buffer[]; -extern char ether_buffer[]; -extern ProgramData pd; - -#if defined(ESP8266) - SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); - byte OpenSprinkler::state = OS_STATE_INITIAL; - byte OpenSprinkler::prev_station_bits[MAX_NUM_BOARDS]; - IOEXP* OpenSprinkler::expanders[MAX_NUM_BOARDS/2]; - IOEXP* OpenSprinkler::mainio; // main controller IO expander object - IOEXP* OpenSprinkler::drio; // driver board IO expander object - RCSwitch OpenSprinkler::rfswitch; - OTCConfig OpenSprinkler::otc; - - String OpenSprinkler::wifi_ssid=""; - String OpenSprinkler::wifi_pass=""; - byte OpenSprinkler::wifi_bssid[6]={0}; - byte OpenSprinkler::wifi_channel=255; - byte OpenSprinkler::wifi_testmode = 0; -#elif defined(ARDUINO) - LiquidCrystal OpenSprinkler::lcd; - extern SdFat sd; -#else - #if defined(OSPI) - byte OpenSprinkler::pin_sr_data = PIN_SR_DATA; - #endif - // todo future: LCD define for Linux-based systems -#endif - -/** Option json names (stored in PROGMEM to reduce RAM usage) */ -// IMPORTANT: each json name is strictly 5 characters -// with 0 fillings if less -#define OP_JSON_NAME_STEPSIZE 5 -// for Integer options -const char iopt_json_names[] PROGMEM = - "fwv\0\0" - "tz\0\0\0" - "ntp\0\0" - "dhcp\0" - "ip1\0\0" - "ip2\0\0" - "ip3\0\0" - "ip4\0\0" - "gw1\0\0" - "gw2\0\0" - "gw3\0\0" - "gw4\0\0" - "hp0\0\0" - "hp1\0\0" - "hwv\0\0" - "ext\0\0" - "seq\0\0" - "sdt\0\0" - "mas\0\0" - "mton\0" - "mtof\0" - "urs\0\0" - "rso\0\0" - "wl\0\0\0" - "den\0\0" - "ipas\0" - "devid" - "con\0\0" - "lit\0\0" - "dim\0\0" - "bst\0\0" - "uwt\0\0" - "ntp1\0" - "ntp2\0" - "ntp3\0" - "ntp4\0" - "lg\0\0\0" - "mas2\0" - "mton2" - "mtof2" - "fwm\0\0" - "fpr0\0" - "fpr1\0" - "re\0\0\0" - "dns1\0" - "dns2\0" - "dns3\0" - "dns4\0" - "sar\0\0" - "ife\0\0" - "sn1t\0" - "sn1o\0" - "sn2t\0" - "sn2o\0" - "sn1on" - "sn1of" - "sn2on" - "sn2of" - "subn1" - "subn2" - "subn3" - "subn4" - "wimod" - "reset" - ; - -/** Option prompts (stored in PROGMEM to reduce RAM usage) */ -// Each string is strictly 16 characters -// with SPACE fillings if less -const char iopt_prompts[] PROGMEM = - "Firmware version" - "Time zone (GMT):" - "Enable NTP sync?" - "Enable DHCP? " - "Static.ip1: " - "Static.ip2: " - "Static.ip3: " - "Static.ip4: " - "Gateway.ip1: " - "Gateway.ip2: " - "Gateway.ip3: " - "Gateway.ip4: " - "HTTP Port: " - "----------------" - "Hardware version" - "# of exp. board:" - "----------------" - "Stn. delay (sec)" - "Master 1 (Mas1):" - "Mas1 on adjust:" - "Mas1 off adjust:" - "----------------" - "----------------" - "Watering level: " - "Device enabled? " - "Ignore password?" - "Device ID: " - "LCD contrast: " - "LCD brightness: " - "LCD dimming: " - "DC boost time: " - "Weather algo.: " - "NTP server.ip1: " - "NTP server.ip2: " - "NTP server.ip3: " - "NTP server.ip4: " - "Enable logging? " - "Master 2 (Mas2):" - "Mas2 on adjust:" - "Mas2 off adjust:" - "Firmware minor: " - "Pulse rate: " - "----------------" - "As remote ext.? " - "DNS server.ip1: " - "DNS server.ip2: " - "DNS server.ip3: " - "DNS server.ip4: " - "Special Refresh?" - "IFTTT Enable: " - "Sensor 1 type: " - "Normally open? " - "Sensor 2 type: " - "Normally open? " - "Sn1 on adjust: " - "Sn1 off adjust: " - "Sn2 on adjust: " - "Sn2 off adjust: " - "Subnet mask1: " - "Subnet mask2: " - "Subnet mask3: " - "Subnet mask4: " - "WiFi mode? " - "Factory reset? "; - -// string options do not have prompts - -/** Option maximum values (stored in PROGMEM to reduce RAM usage) */ -const byte iopt_max[] PROGMEM = { - 0, - 108, - 1, - 1, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 0, - MAX_EXT_BOARDS, - 1, - 255, - MAX_NUM_STATIONS, - 255, - 255, - 255, - 1, - 250, - 1, - 1, - 255, - 255, - 255, - 255, - 250, - 255, - 255, - 255, - 255, - 255, - 1, - MAX_NUM_STATIONS, - 255, - 255, - 0, - 255, - 255, - 1, - 255, - 255, - 255, - 255, - 1, - 255, - 255, - 1, - 255, - 1, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 255, - 1 -}; - -// string options do not have maximum values - -/** Integer option values (stored in RAM) */ -byte OpenSprinkler::iopts[] = { - OS_FW_VERSION, // firmware version - 28, // default time zone: GMT-5 - 1, // 0: disable NTP sync, 1: enable NTP sync - 1, // 0: use static ip, 1: use dhcp - 0, // this and next 3 bytes define static ip - 0, - 0, - 0, - 0, // this and next 3 bytes define static gateway ip - 0, - 0, - 0, -#if defined(ARDUINO) // on AVR, the default HTTP port is 80 - 80, // this and next byte define http port number - 0, -#else // on RPI/BBB/LINUX, the default HTTP port is 8080 - 144,// this and next byte define http port number - 31, -#endif - OS_HW_VERSION, - 0, // number of 8-station extension board. 0: no extension boards - 1, // the option 'sequential' is now retired - 120,// station delay time (-10 minutes to 10 minutes). - 0, // index of master station. 0: no master station - 120,// master on time adjusted time (-10 minutes to 10 minutes) - 120,// master off adjusted time (-10 minutes to 10 minutes) - 0, // urs (retired) - 0, // rso (retired) - 100,// water level (default 100%), - 1, // device enable - 0, // 1: ignore password; 0: use password - 0, // device id - 150,// lcd contrast - 100,// lcd backlight - 15, // lcd dimming - 80, // boost time (only valid to DC and LATCH type) - 0, // weather algorithm (0 means not using weather algorithm) - 0, // this and the next three bytes define the ntp server ip - 0, - 0, - 0, - 1, // enable logging: 0: disable; 1: enable. - 0, // index of master2. 0: no master2 station - 120,// master2 on adjusted time - 120,// master2 off adjusted time - OS_FW_MINOR, // firmware minor version - 100,// this and next byte define flow pulse rate (100x) - 0, // default is 1.00 (100) - 0, // set as remote extension - 8, // this and the next three bytes define the custom dns server ip - 8, - 8, - 8, - 0, // special station auto refresh - 0, // ifttt enable bits - 0, // sensor 1 type (see SENSOR_TYPE macro defines) - 1, // sensor 1 option. 0: normally closed; 1: normally open. default 1. - 0, // sensor 2 type - 1, // sensor 2 option. 0: normally closed; 1: normally open. default 1. - 0, // sensor 1 on delay - 0, // sensor 1 off delay - 0, // sensor 2 on delay - 0, // sensor 2 off delay - 255,// subnet mask 1 - 255,// subnet mask 2 - 255,// subnet mask 3 - 0, - WIFI_MODE_AP, // wifi mode - 0 // reset -}; - -/** String option values (stored in RAM) */ -const char *OpenSprinkler::sopts[] = { - DEFAULT_PASSWORD, - DEFAULT_LOCATION, - DEFAULT_JAVASCRIPT_URL, - DEFAULT_WEATHER_URL, - DEFAULT_EMPTY_STRING, // SOPT_WEATHER_OPTS - DEFAULT_EMPTY_STRING, // SOPT_IFTTT_KEY - DEFAULT_EMPTY_STRING, // SOPT_STA_SSID - DEFAULT_EMPTY_STRING, // SOPT_STA_PASS - DEFAULT_EMPTY_STRING, // SOPT_MQTT_OPTS - DEFAULT_EMPTY_STRING, // SOPT_OTC_OPTS - DEFAULT_DEVICE_NAME, - DEFAULT_EMPTY_STRING, // SOPT_STA_BSSID_CHL -}; - -/** Weekday strings (stored in PROGMEM to reduce RAM usage) */ -static const char days_str[] PROGMEM = - "Mon\0" - "Tue\0" - "Wed\0" - "Thu\0" - "Fri\0" - "Sat\0" - "Sun\0"; - -/** Calculate local time (UTC time plus time zone offset) */ -time_t OpenSprinkler::now_tz() { - return now()+(int32_t)3600/4*(int32_t)(iopts[IOPT_TIMEZONE]-48); -} - -#if defined(ARDUINO) - -bool detect_i2c(int addr) { - Wire.beginTransmission(addr); - return (Wire.endTransmission()==0); // successful if received 0 -} - -/** read hardware MAC into tmp_buffer */ -#define MAC_CTRL_ID 0x50 -bool OpenSprinkler::load_hardware_mac(byte* buffer, bool wired) { -#if defined(ESP8266) - WiFi.macAddress((byte*)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(byte 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 */ - -byte 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; - } else { - useEth = false; - } - - if((useEth || get_wifi_mode()==WIFI_MODE_STA) && otc.en>0 && otc.token.length()>=32) { - otf = new OTF::OpenThingsFramework(httpport, otc.server, otc.port, otc.token, false, ether_buffer, ETHER_BUFFER_SIZE); - DEBUG_PRINTLN(F("Started OTF with remote connection")); - } else { - otf = new OTF::OpenThingsFramework(httpport, ether_buffer, ETHER_BUFFER_SIZE); - DEBUG_PRINTLN(F("Started OTF with just local connection")); - } - extern DNSServer *dns; - if(get_wifi_mode() == WIFI_MODE_AP) dns = new DNSServer(); - if(update_server) { delete update_server; update_server = NULL; } - update_server = new ESP8266WebServer(8080); - 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 -} - byte OpenSprinkler::start_ether() { #if defined(ESP8266) if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 @@ -5780,4 +2888,4 @@ void OpenSprinkler::detect_expanders() { } } } -#endif +#endif \ No newline at end of file From 061d9c031fdee293afd314d1178052785c71f73a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 1 Sep 2023 11:50:34 +0200 Subject: [PATCH 071/281] Added analog sensor api support for OSPi with PCF8591 --- build.sh | 3 +- opensprinkler_server.cpp | 24 +++++++++--- sensors.cpp | 84 +++++++++++++++++++++++++++++++++++----- sensors.h | 15 ++++++- 4 files changed, 108 insertions(+), 18 deletions(-) diff --git a/build.sh b/build.sh index a356a4fc9..1903c5273 100755 --- a/build.sh +++ b/build.sh @@ -25,13 +25,14 @@ else apt-get update apt-get install -y libmosquitto-dev apt-get install -y raspi-gpio + apt-get install -y libi2c-dev if ! command -v raspi-gpio &> /dev/null then echo "Command raspi-gpio is required and is not installed" exit 0 fi echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -lpthread -lmosquitto + g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -li2c -lpthread -lmosquitto fi if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index e80584ccd..418a04e88 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -263,7 +263,8 @@ void print_header_download(OTF_PARAMS_DEF, int len=0) { res.writeStatus(200, F("OK")); res.writeHeader(F("Content-Type"), F("text/plain")); res.writeHeader(F("Content-Disposition"), F("attachment; filename=\"log.csv\";")); - if(len>0) + if( + len>0) res.writeHeader(F("Content-Length"), len); res.writeHeader(F("Access-Control-Allow-Origin"), F("*")); res.writeHeader(F("Cache-Control"), F("max-age=0, no-cache, no-store, must-revalidate")); @@ -2643,7 +2644,9 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { const int sensor_types[] = { SENSOR_SMT100_MODBUS_RTU_MOIS, SENSOR_SMT100_MODBUS_RTU_TEMP, -#if defined(ESP8266) + +#if defined(ARDUINO) + #if defined(ESP8266) SENSOR_ANALOG_EXTENSION_BOARD, SENSOR_ANALOG_EXTENSION_BOARD_P, SENSOR_SMT50_MOIS, @@ -2653,8 +2656,13 @@ const int sensor_types[] = { SENSOR_VH400, SENSOR_THERM200, SENSOR_AQUAPLUMB, + #endif +#else + SENSOR_OSPI_ANALOG, + SENSOR_OSPI_ANALOG_P, + SENSOR_OSPI_ANALOG_SMT50_MOIS, + SENSOR_OSPI_ANALOG_SMT50_TEMP, #endif - //SENSOR_OSPI_ANALOG_INPUTS, SENSOR_REMOTE, SENSOR_WEATHER_TEMP_F, SENSOR_WEATHER_TEMP_C, @@ -2672,7 +2680,8 @@ const int sensor_types[] = { const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", -#if defined(ESP8266) +#if defined(ARDUINO) + #if defined(ESP8266) "OpenSprinkler analog extension board 2xADS1x15 x8 - voltage mode 0..4V", "OpenSprinkler analog extension board 2xADS1x15 x8 - 0..3.3V to 0..100%", "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT50 moisture mode", @@ -2683,8 +2692,13 @@ const char* sensor_names[] = { "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix VH400", "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix THERM200", "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix AquaPlumb", + #endif +#else + "OSPi analog input - voltage mode 0..3.3V", + "OSPi analog input - 0.3.3V to 0..100%", + "OSPi analog input - SMT50 moisture mode", + "OSPi analog input - SMT50 temperature mode", #endif - //"OSPi analog input", "Remote sensor of an remote opensprinkler", "Weather data - temperature (°F)", "Weather data - temperature (°C)", diff --git a/sensors.cpp b/sensors.cpp index 3ebcbf867..e903d707a 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -698,6 +698,7 @@ void read_all_sensors() { calc_sensorlogs(); } +#if defined(ARDUINO) #if defined(ESP8266) /** * Read ADS1115 sensors @@ -782,15 +783,65 @@ int read_sensor_adc(Sensor_t *sensor) { return HTTP_RQT_SUCCESS; } #endif - +#else +/** +/* Read the OSPi onboard PCF8591 A2D +**/ int read_sensor_ospi(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_ospi")); if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - //currently not implemented + sensor->flags.data_ok = false; - return HTTP_RQT_SUCCESS; + /** + /* https://medium.com/geekculture/raspberry-pi-c-libraries-for-working-with-i2c-spi-and-uart-4677f401b584 + /* http://www.pibits.net/amp/code/raspberry-pi-and-a-pcf8591-example.php + **/ + int adapter_nr = 1; + char filename[20]; + __u8 addr = sensor->port == 0? 0x48 : sensor->port; + __u8 chn = sensor->id; + + //Open I2C: + snprintf(filename, 19, "/dev/i2c-%d", adapter_nr); + int file = open(filename, O_RDWR); + if (file < 0) return HTTP_RQT_NOT_RECEIVED; + + //Select address: + if (ioctl(file, I2C_SLAVE, addr) < 0) return HTTP_RQT_NOT_RECEIVED; + + //Select channel: + i2c_smbus_write_byte(file, chn); + + //dummy read to start conversion: + i2c_smbus_read_byte(file); + + //read current value: + __s32 res = i2c_smbus_read_byte(file); + + if (res < 0) return HTTP_RQT_NOT_RECEIVED; + + sensor->last_native_data = res; + sensor->flags.data_ok = true; + + //convert values: + switch(sensor->type) { + case SENSOR_OSPI_ANALOG: + sensor->last_data = (double)res * 3.3/255; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_P: + sensor->last_data = (double)res * 100/255; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + sensor->last_data = (double)res * 3.3/255 * 50 / 3; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + sensor->last_data = (((double)res * 3.3/255) - 0.5) * 100; + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; } +#endif bool extract(char *s, char *buf, int maxlen) { s = strstr(s, ":"); @@ -910,7 +961,7 @@ int read_sensor_ip(Sensor_t *sensor) { return HTTP_RQT_CONNECT_ERR; } - uint8_t buffer[10]; +uint8_t buffer[10]; int len = 0; boolean addCrc16 = false; switch(sensor->type) @@ -1055,6 +1106,7 @@ int read_sensor(Sensor_t *sensor) { sensor->last_read = time; return read_sensor_ip(sensor); +#if defined(ARDUINO) #if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: case SENSOR_ANALOG_EXTENSION_BOARD_P: @@ -1068,13 +1120,18 @@ int read_sensor(Sensor_t *sensor) { sensor->last_read = time; return read_sensor_adc(sensor); #endif - - //case SENSOR_OSPI_ANALOG_INPUTS: - // return read_sensor_ospi(sensor); +#else + case SENSOR_OSPI_ANALOG: + case SENSOR_OSPI_ANALOG_P: + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + sensor->last_read = time; + return read_sensor_ospi(sensor); + case SENSOR_REMOTE: sensor->last_read = time; return read_sensor_http(sensor); - +#endif case SENSOR_WEATHER_TEMP_F: case SENSOR_WEATHER_TEMP_C: case SENSOR_WEATHER_HUM: @@ -1573,7 +1630,10 @@ byte getSensorUnitId(int type) { case SENSOR_THERM200: return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; #endif - case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; + case SENSOR_OSPI_ANALOG: return UNIT_VOLT; + case SENSOR_OSPI_ANALOG_P: return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; case SENSOR_WEATHER_TEMP_C: return UNIT_DEGREE; @@ -1606,7 +1666,11 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_THERM200: return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; #endif - case SENSOR_OSPI_ANALOG_INPUTS: return UNIT_VOLT; + case SENSOR_OSPI_ANALOG: return UNIT_VOLT; + case SENSOR_OSPI_ANALOG_P: return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_REMOTE: return sensor->unitid; case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; diff --git a/sensors.h b/sensors.h index cba0400d1..f8880cbe5 100644 --- a/sensors.h +++ b/sensors.h @@ -29,7 +29,11 @@ #include #include #include - + #include +extern "C" { + #include + #include +} #endif #include "defines.h" #include "utils.h" @@ -59,6 +63,7 @@ #define SENSOR_NONE 0 //None or deleted sensor #define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode #define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode +#if defined(ARDUINO) #if defined(ESP8266) #define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board x8 - voltage mode 0..4V #define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% @@ -72,7 +77,13 @@ #define SENSOR_AQUAPLUMB 32 //New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb #endif -#define SENSOR_OSPI_ANALOG_INPUTS 50 //Old OSPi analog input +#else +#define SENSOR_OSPI_ANALOG 50 //Old OSPi analog input - voltage mode 0..3.3V +#define SENSOR_OSPI_ANALOG_P 51 //Old OSPi analog input - percent 0..3.3V to 0...100% +#define SENSOR_OSPI_ANALOG_SMT50_MOIS 52 //Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_OSPI_ANALOG_SMT50_TEMP 53 //Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 +#endif + #define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler #define SENSOR_WEATHER_TEMP_F 101 //Weather service - temperature (Fahrenheit) #define SENSOR_WEATHER_TEMP_C 102 //Weather service - temperature (Celcius) From b85a7c56fe936d621316153bad25c352fcf811f5 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 1 Sep 2023 11:54:23 +0200 Subject: [PATCH 072/281] Small fixes for weather and ping check --- defines.h | 2 +- main.cpp | 9 +++++++-- sensors.cpp | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/defines.h b/defines.h index 3ebb5c1ec..201acde81 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 130 // Firmware minor version +#define OS_FW_MINOR 131 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index 8cac45920..d72208dfe 100644 --- a/main.cpp +++ b/main.cpp @@ -2006,12 +2006,17 @@ void check_network() { return true; }); } - if (useEth && (!eth.connected() || !eth.gatewayIP() || !eth.gatewayIP().isSet())) + if (useEth && (!eth.connected() || !eth.gatewayIP() || !eth.gatewayIP().isSet())) { + os.status.network_fails++; return; - if (!useEth && (!WiFi.isConnected() || !WiFi.gatewayIP() || !WiFi.gatewayIP().isSet() || os.get_wifi_mode()==WIFI_MODE_AP)) + } + if (!useEth && (!WiFi.isConnected() || !WiFi.gatewayIP() || !WiFi.gatewayIP().isSet() || os.get_wifi_mode()==WIFI_MODE_AP)) { + os.status.network_fails++; return; + } if(!pinger->Ping(useEth?eth.gatewayIP() : WiFi.gatewayIP())) { + os.status.network_fails++; #if defined(ENABLE_DEBUG) Serial.println("Error during last ping command."); #endif diff --git a/sensors.cpp b/sensors.cpp index 14c105c3e..84c6b1689 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1635,7 +1635,7 @@ void GetSensorWeather() { // use temp buffer to construct get command BufferFiller bf = tmp_buffer; - bf.emit_p(PSTR("weatherData?loc=$O"), SOPT_LOCATION); + bf.emit_p(PSTR("weatherData?loc=$O&wto=$O"), SOPT_LOCATION, SOPT_WEATHER_OPTS); char *src=tmp_buffer+strlen(tmp_buffer); char *dst=tmp_buffer+TMP_BUFFER_SIZE-12; From cf27aa39e237f4e8e3964a4dbe5db685adb2997c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 1 Sep 2023 11:58:08 +0200 Subject: [PATCH 073/281] Fixed compile errors --- sensors.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 14906d853..f96061d3e 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1618,6 +1618,7 @@ byte getSensorUnitId(int type) { switch(type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; +#if defined(ARDUINO) #if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; @@ -1630,11 +1631,12 @@ byte getSensorUnitId(int type) { case SENSOR_THERM200: return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; #endif +#else case SENSOR_OSPI_ANALOG: return UNIT_VOLT; case SENSOR_OSPI_ANALOG_P: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; - +#endif case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; case SENSOR_WEATHER_TEMP_C: return UNIT_DEGREE; case SENSOR_WEATHER_HUM: return UNIT_HUM_PERCENT; @@ -1654,6 +1656,7 @@ byte getSensorUnitId(Sensor_t *sensor) { switch(sensor->type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; +#if defined(ARDUINO) #if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_LEVEL; @@ -1666,11 +1669,12 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_THERM200: return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; #endif +#else case SENSOR_OSPI_ANALOG: return UNIT_VOLT; case SENSOR_OSPI_ANALOG_P: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; - +#endif case SENSOR_REMOTE: return sensor->unitid; case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; From 4fdf3e568fa30171902d044b5aa5f0236b5f7eea Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 3 Oct 2023 13:46:57 +0200 Subject: [PATCH 074/281] Memory Leak Fix --- OpenSprinkler.h | 1 + defines.h | 2 +- main.cpp | 1 + opensprinkler_server.cpp | 29 ++++++++++++++++------------- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/OpenSprinkler.h b/OpenSprinkler.h index f5d4771ba..d92624f13 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -66,6 +66,7 @@ #if defined(ESP8266) extern ESP8266WebServer *update_server; extern OTF::OpenThingsFramework *otf; + extern bool otf_callbacksInitialised; extern ENC28J60lwIP eth; #else extern EthernetServer *m_server; diff --git a/defines.h b/defines.h index 201acde81..cd4efcc6b 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 131 // Firmware minor version +#define OS_FW_MINOR 132 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index bcd512bd0..77deb8736 100644 --- a/main.cpp +++ b/main.cpp @@ -38,6 +38,7 @@ Pinger *pinger = NULL; ESP8266WebServer *update_server = NULL; OTF::OpenThingsFramework *otf = NULL; + bool otf_callbacksInitialised = false; DNSServer *dns = NULL; ENC28J60lwIP eth(PIN_ETHER_CS); // ENC28J60 lwip for wired Ether bool useEth = false; // tracks whether we are using WiFi or wired Ether connection diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 418a04e88..ab3c3c289 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3091,19 +3091,22 @@ void on_ap_upload() { on_sta_upload(); } void start_server_client() { if(!otf) return; - otf->on("/", server_home); // handle home page - otf->on("/index.html", server_home); - 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); - - // set up all other handlers - char uri[4]; - uri[0]='/'; - uri[3]=0; - for(byte i=0;ion(uri, urls[i]); + if (!otf_callbacksInitialised) { + otf->on("/", server_home); // handle home page + otf->on("/index.html", server_home); + 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); + + // set up all other handlers + char uri[4]; + uri[0]='/'; + uri[3]=0; + for(byte i=0;ion(uri, urls[i]); + } + otf_callbacksInitialised = true; } update_server->begin(); } From 3b4878b8aa38e61b93ef127a9852f0004f71b877 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 8 Oct 2023 16:56:12 +0200 Subject: [PATCH 075/281] iospi: analog sensor api: Added missing close --- sensors.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index f96061d3e..d7fa752f9 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -808,7 +808,10 @@ int read_sensor_ospi(Sensor_t *sensor) { if (file < 0) return HTTP_RQT_NOT_RECEIVED; //Select address: - if (ioctl(file, I2C_SLAVE, addr) < 0) return HTTP_RQT_NOT_RECEIVED; + if (ioctl(file, I2C_SLAVE, addr) < 0) { + close(file); + return HTTP_RQT_NOT_RECEIVED; + } //Select channel: i2c_smbus_write_byte(file, chn); @@ -818,7 +821,8 @@ int read_sensor_ospi(Sensor_t *sensor) { //read current value: __s32 res = i2c_smbus_read_byte(file); - + close(file); + if (res < 0) return HTTP_RQT_NOT_RECEIVED; sensor->last_native_data = res; From eef35c45c5520501cd8ebceb86f7afd9ceeeef4a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 12 Nov 2023 01:31:28 +0100 Subject: [PATCH 076/281] Added user defined sensor with factor and free unit --- defines.h | 2 +- opensprinkler_server.cpp | 71 ++++++++++++++++++++++++++++++++++++++-- sensors.cpp | 56 +++++++++++++++++++++++++++++-- sensors.h | 14 ++++++-- 4 files changed, 134 insertions(+), 9 deletions(-) diff --git a/defines.h b/defines.h index cd4efcc6b..52e7f16cd 100644 --- a/defines.h +++ b/defines.h @@ -36,7 +36,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 132 // Firmware minor version +#define OS_FW_MINOR 133 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index ab3c3c289..bf4418f29 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1984,6 +1984,47 @@ void server_fill_files(OTF_PARAMS_DEF) { } */ +/** + * si + * Sensor config User Defined + * {"nr":1,"fac":100,"div":1,"unit":"bar"} + */ +void server_sensor_config_userdef(OTF_PARAMS_DEF) +{ +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensor_config userder")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); + + int16_t factor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("fac"), true)) + factor = strtol(tmp_buffer, NULL, 0); // factor + + int16_t divider = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("div"), true)) + divider = strtol(tmp_buffer, NULL, 0); // divider + + char userdef_unit_str[8]; + char *userdef_unit; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { + strncpy(userdef_unit_str, tmp_buffer, sizeof(userdef_unit_str)-1); // unit + userdef_unit = userdef_unit_str; + } else { + userdef_unit = NULL; + } + + int ret = sensor_define_userdef(nr, factor, divider, userdef_unit); + ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); +} /** * sc @@ -2043,6 +2084,23 @@ void server_sensor_config(OTF_PARAMS_DEF) handle_return(HTML_DATA_MISSING); uint ri = strtoul(tmp_buffer, NULL, 0); // Read Interval (s) + int16_t factor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("fac"), true)) + factor = strtol(tmp_buffer, NULL, 0); // factor + + int16_t divider = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("div"), true)) + divider = strtol(tmp_buffer, NULL, 0); // divider + + char userdef_unit_str[8]; + char *userdef_unit; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { + strncpy(userdef_unit_str, tmp_buffer, sizeof(userdef_unit_str)-1); // unit + userdef_unit = userdef_unit_str; + } else { + userdef_unit = NULL; + } + uint enable = 1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enable"), true)) enable = strtoul(tmp_buffer, NULL, 0); // 1=enable/0=disable @@ -2056,7 +2114,7 @@ void server_sensor_config(OTF_PARAMS_DEF) show = strtoul(tmp_buffer, NULL, 0); // 1=show enabled/0=show disabled SensorFlags_t flags = {.enable=enable, .log=log, .show=show}; - int ret = sensor_define(nr, name, type, group, ip, port, id, ri, flags); + int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, flags); ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); } @@ -2201,7 +2259,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { if (first) first = false; else bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"fac\":$D,\"div\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L}"), sensor->nr, sensor->type, sensor->group, @@ -2210,6 +2268,8 @@ void sensorconfig_json(OTF_PARAMS_DEF) { sensor->port, sensor->id, sensor->read_interval, + sensor->factor, + sensor->divider, sensor->last_native_data, sensor->last_data, getSensorUnit(sensor), @@ -2656,6 +2716,7 @@ const int sensor_types[] = { SENSOR_VH400, SENSOR_THERM200, SENSOR_AQUAPLUMB, + SENSOR_USERDEF, #endif #else SENSOR_OSPI_ANALOG, @@ -2692,6 +2753,8 @@ const char* sensor_names[] = { "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix VH400", "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix THERM200", "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix AquaPlumb", + + "OpenSprinkler analog extension board 2xADS1x15 x8 - user defined sensor", #endif #else "OSPi analog input - voltage mode 0..3.3V", @@ -2743,7 +2806,7 @@ void server_sensor_types(OTF_PARAMS_DEF) { bfill.emit_p(PSTR(",")); byte unitid = getSensorUnitId(sensor_types[i]); bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), - sensor_types[i], sensor_names[i], sensor_unitNames[unitid], unitid); + sensor_types[i], sensor_names[i], getSensorUnit(unitid), unitid); send_packet(OTF_PARAMS); } bfill.emit_p(PSTR("]}")); @@ -2956,6 +3019,7 @@ const char _url_keys[] PROGMEM = "cu" "ja" "pq" + "si" "sc" "sl" "sg" @@ -3001,6 +3065,7 @@ URLHandler urls[] = { server_change_scripturl,// cu server_json_all, // ja server_pause_queue, // pq + server_sensor_config_userdef, // si server_sensor_config, // sc server_sensor_list, // sl server_sensor_get, // sg diff --git a/sensors.cpp b/sensors.cpp index f96061d3e..5bdfc9057 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -118,7 +118,7 @@ int sensor_delete(uint nr) { * @param port * @param id */ -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, SensorFlags_t flags) { +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char* userdef_unit, SensorFlags_t flags) { if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; @@ -136,6 +136,12 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint sensor->port = port; sensor->id = id; sensor->read_interval = ri; + sensor->factor = factor; + sensor->divider = divider; + if (userdef_unit) + strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); + else + memset(sensor->userdef_unit, 0, sizeof(sensor->userdef_unit)); sensor->flags = flags; sensor_save(); return HTTP_RQT_SUCCESS; @@ -158,6 +164,12 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->port = port; new_sensor->id = id; new_sensor->read_interval = ri; + new_sensor->factor = factor; + new_sensor->divider = divider; + if (userdef_unit) + strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); + else + memset(sensor->userdef_unit, 0, sizeof(sensor->userdef_unit)); new_sensor->flags = flags; if (last) { new_sensor->next = last->next; @@ -170,6 +182,21 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint return HTTP_RQT_SUCCESS; } +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char* userdef_unit) { + Sensor_t *sensor = sensor_by_nr(nr); + if (!sensor) + return HTTP_RQT_NOT_RECEIVED; + + sensor->factor = factor; + sensor->divider = divider; + if (userdef_unit) + strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); + else + memset(sensor->userdef_unit, 0, sizeof(sensor->userdef_unit)); + + return HTTP_RQT_SUCCESS; +} + /** * @brief initial load stored sensor definitions * @@ -771,6 +798,15 @@ int read_sensor_adc(Sensor_t *sensor) { else if (sensor->last_data > 100) sensor->last_data = 100; break; + case SENSOR_USERDEF: //User defined sensor + if (sensor->factor && sensor->divider) + v *= sensor->factor / sensor->divider; + else if (sensor->divider) + v /= sensor->divider; + else if (sensor->factor) + v *= sensor->factor; + sensor->last_data = v; + break; } sensor->flags.data_ok = true; @@ -1117,6 +1153,7 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_VH400: case SENSOR_THERM200: case SENSOR_AQUAPLUMB: + case SENSOR_USERDEF: sensor->last_read = time; return read_sensor_adc(sensor); #endif @@ -1593,11 +1630,24 @@ bool checkDiskFree() { } #endif +const char* getSensorUnit(int unitid) { + if (unitid == UNIT_USERDEF) + return "?"; + if (unitid < 0 || (uint16_t)unitid >= sizeof(sensor_unitNames)) + return sensor_unitNames[0]; + return sensor_unitNames[unitid]; +} + const char* getSensorUnit(Sensor_t *sensor) { if (!sensor) return sensor_unitNames[0]; - return sensor_unitNames[getSensorUnitId(sensor)]; + int unitid = getSensorUnitId(sensor); + if (unitid == UNIT_USERDEF) + return sensor->userdef_unit; + if (unitid < 0 || (uint16_t)unitid >= sizeof(sensor_unitNames)) + return sensor_unitNames[0]; + return sensor_unitNames[unitid]; } boolean sensor_isgroup(Sensor_t *sensor) { @@ -1630,6 +1680,7 @@ byte getSensorUnitId(int type) { case SENSOR_VH400: return UNIT_PERCENT; case SENSOR_THERM200: return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; + case SENSOR_USERDEF: return UNIT_USERDEF; #endif #else case SENSOR_OSPI_ANALOG: return UNIT_VOLT; @@ -1668,6 +1719,7 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_VH400: return UNIT_PERCENT; case SENSOR_THERM200: return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; + case SENSOR_USERDEF: return UNIT_USERDEF; #endif #else case SENSOR_OSPI_ANALOG: return UNIT_VOLT; diff --git a/sensors.h b/sensors.h index f8880cbe5..302c5f1cd 100644 --- a/sensors.h +++ b/sensors.h @@ -76,6 +76,8 @@ extern "C" { #define SENSOR_THERM200 31 //New OpenSprinkler analog extension board x8 - Vegetronix THERM200 #define SENSOR_AQUAPLUMB 32 //New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb +#define SENSOR_USERDEF 49 //New OpenSprinkler analog extension board x8 - User defined sensor + #endif #else #define SENSOR_OSPI_ANALOG 50 //Old OSPi analog input - voltage mode 0..3.3V @@ -122,7 +124,10 @@ typedef struct Sensor { uint32_t last_native_data; // last native sensor data double last_data; // last converted sensor data SensorFlags_t flags; // Flags see obove - byte undef[32]; //for later + int16_t factor; // faktor - for custom sensor + int16_t divider; // divider - for custom sensor + char userdef_unit[8]; // unit - for custom sensor + byte undef[20]; // for later //unstored byte unitid; ulong last_read; //millis @@ -190,10 +195,12 @@ typedef struct SensorUrl { #define UNIT_MPH 8 #define UNIT_KMH 9 #define UNIT_LEVEL 10 +#define UNIT_USERDEF 99 //Unitnames -extern const char* sensor_unitNames[]; +// extern const char* sensor_unitNames[]; +const char* getSensorUnit(int unitid); const char* getSensorUnit(Sensor_t *sensor); byte getSensorUnitId(int type); byte getSensorUnitId(Sensor_t *sensor); @@ -206,7 +213,8 @@ uint16_t CRC16 (byte buf[], int len); //Sensor API functions: int sensor_delete(uint nr); -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, SensorFlags_t flags); +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char* userdef_unit, SensorFlags_t flags); +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char* userdef_unit); void sensor_load(); void sensor_save(); uint sensor_count(); From 1c211cc278c40d9e3cd0ced0d806e7e90091fbd2 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 12 Nov 2023 15:52:05 +0100 Subject: [PATCH 077/281] Update API Doc --- Sensor API.txt | 288 ++++++++++++++++++++++++++----------------------- 1 file changed, 151 insertions(+), 137 deletions(-) diff --git a/Sensor API.txt b/Sensor API.txt index 52e5adeea..5060492d0 100644 --- a/Sensor API.txt +++ b/Sensor API.txt @@ -1,137 +1,151 @@ -Sensor API -This api documentation is part of the opensprinkler - - the ip-address, for example 192.168.0.55 - the MD5 encrypted password, opendoor=a6d82bced638de3def1e9bbb4983225c - - -Create Sensors (sc): -creates, modifies or deletes a sensor. -"nr" for a unique number >= 1 -"type" for the sensor-type, see sf. type=0 deletes the sensor -"group" for group assignment, a nr of another sensor with type=SENSOR_GROUP_YXZ -"name" a name -"ip" for the ip-address, only for network connected sensors. All others use ip=0 -"port" for the ip-port-address, only for network connected sensors. All others use port=0 -"id" sub-id, e.a. modbus address or subid, for ADC Sensors 0..7 -"ri" read interval in seconds -"enable" 0=sensor disabled, 1=sensor enabled -"log" 0=logging disabled, 1=logging enabled - -examples: -http:///sc?pw=&nr=1&type=1&group=0&name=SMT100-Mois&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 -http:///sc?pw=&nr=2&type=2&group=0&name=SMT100-Temp&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 -http:///sc?pw=&nr=3&type=11&group=0&name=SMT50-Mois&ip=0&id=0&ri=60&enable=1&log=1 -http:///sc?pw=&nr=4&type=12&group=0&name=SMT50-Temp&ip=0&id=1&ri=60&enable=1&log=1 - -ip: dec=4261456064 = hex=FE00A8C0 = -FE = 254 -00 = 000 -A8 = 168 -C0 = 192 - = 192.168.000.254 - -For the Truebner SMT100 RS485 Modbus you need a RS485 Modbus RTU over TCP converter. -Set ip/port for the converter, e.a PUSR USR-W610 in transparent modus. - -For the analog ports of the extension board (including SMT50) use id 0..7 - -type: -SENSOR_NONE 0 None or deleted sensor -SENSOR_SMT100_MODBUS_RTU_MOIS 1 Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode -SENSOR_SMT100_MODBUS_RTU_TEMP 2 Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode -SENSOR_ANALOG_EXTENSION_BOARD 10 New OpenSprinkler analog extension board x8 - voltage mode 0..4V -SENSOR_ANALOG_EXTENSION_BOARD_P 11 New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% -SENSOR_SMT50_MOIS 15 New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 -SENSOR_SMT50_TEMP 16 New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 -SENSOR_OSPI_ANALOG_INPUTS 20 Old OSPi analog input -SENSOR_REMOTE 100 Remote sensor of an remote opensprinkler -SENSOR_GROUP_MIN 1000 Sensor group with min value -SENSOR_GROUP_MAX 1001 Sensor group with max value -SENSOR_GROUP_AVG 1002 Sensor group with avg value -SENSOR_GROUP_SUM 1003 Sensor group with sum value - -List Sensors (sl): -lists the current sensors -examples: -http:///sl?pw= - -Get last Sensor values (sg): -returns last read sensor values -examples: -http:///sg?pw= -http:///sg?pw=&nr=1 - -Read sensor now (sr): -executes sensor read and returns the values -examples: -http:///sr?pw= -http:///sr?pw=&nr=1 - -Set sensor address for SMT100 (sa): -Only for SMT100: Set modbus address -Disconnect all other modbus sensors, so that only one sensor is connected. Sets the modbus address for sensor nr to id -examples: -http:///sa?pw=&nr=1&id=1 - -Dump Sensor Log (so): -dumps the sensor log -examples: -http:///so?pw= -http:///so?pw=&start=0&max=100&nr=1&type=1&before=1666915277&after=1666915277 - -Clear Sensor Log (sn): -clears the sensor log -examples: -http:///sn?pw= - -Program adjustments (sb): -defines program adjustments -"nr" adjustment-nr -"type" adjustment-type (0=delete, 1=linear, 2=digital min, 3=digital max) -"sensor" sensor-nr -"prog" program-nr -"factor1", "factor2", "min", "max" formula-values -examples: -http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 - -type: -PROG_NONE 0 //No adjustment (delete) -PROG_LINEAR 1 //formula -PROG_DIGITAL_MIN 2 //1=under or equal min, 0=above -PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below - -formula: - min max factor1 factor2 - 10..90 -> 5..1 factor1 > factor2 - a b c d - (b-sensorData) / (b-a) * (c-d) + d - - 10..90 -> 1..5 factor1 < factor2 - a b c d - (sensorData-a) / (b-a) * (d-c) + c - -min/max is the used range of the sensor (for example min=10 max=80) -factor1/factor2 is the calculated adjustment (for example factor1=2 factor2=0) -So a sensordata of 10 will be a adjustment of factor 2 (200%) or -a sensordata of 80 will be a adjustment of factor 0 (0%) -everything between will be linear scaled in the range of 0..2 - -List Program adjustments (se): -lists the current program adjustments -examples: -http:///se?pw= -&nr=1&prog=1 - -List supported sensor types (sf): -lists supported sensor types -examples: -http:///sf?pw= - -List supported program adjustments (sh): -http:///sh?pw= - -System used and free space (du): -http:///du?pw= - - +Sensor API +This api documentation is part of the opensprinkler + + the ip-address, for example 192.168.0.55 + the MD5 encrypted password, opendoor=a6d82bced638de3def1e9bbb4983225c + + +Create Sensors (sc): +creates, modifies or deletes a sensor. +"nr" for a unique number >= 1 +"type" for the sensor-type, see sf. type=0 deletes the sensor +"group" for group assignment, a nr of another sensor with type=SENSOR_GROUP_YXZ +"name" a name +"ip" for the ip-address, only for network connected sensors. All others use ip=0 +"port" for the ip-port-address, only for network connected sensors. All others use port=0 +"id" sub-id, e.a. modbus address or subid, for ADC Sensors 0..7 +"ri" read interval in seconds +"fac" factor for user defined sensors +"div" divider for user defined sensors +"unit" unit for user defined sensors (unitid=99) +"enable" 0=sensor disabled, 1=sensor enabled +"log" 0=logging disabled, 1=logging enabled +"show" 0=hide 1=show current value on main screen + +examples: +http:///sc?pw=&nr=1&type=1&group=0&name=SMT100-Mois&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 +http:///sc?pw=&nr=2&type=2&group=0&name=SMT100-Temp&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 +http:///sc?pw=&nr=3&type=11&group=0&name=SMT50-Mois&ip=0&id=0&ri=60&enable=1&log=1 +http:///sc?pw=&nr=4&type=12&group=0&name=SMT50-Temp&ip=0&id=1&ri=60&enable=1&log=1 + +ip: dec=4261456064 = hex=FE00A8C0 = +FE = 254 +00 = 000 +A8 = 168 +C0 = 192 + = 192.168.000.254 + +For the Truebner SMT100 RS485 Modbus you need a RS485 Modbus RTU over TCP converter. +Set ip/port for the converter, e.a PUSR USR-W610 in transparent modus. + +For the analog ports of the extension board (including SMT50) use id 0..7 + +type: +SENSOR_NONE 0 None or deleted sensor +SENSOR_SMT100_MODBUS_RTU_MOIS 1 Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode +SENSOR_SMT100_MODBUS_RTU_TEMP 2 Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode +SENSOR_ANALOG_EXTENSION_BOARD 10 New OpenSprinkler analog extension board x8 - voltage mode 0..4V +SENSOR_ANALOG_EXTENSION_BOARD_P 11 New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% +SENSOR_SMT50_MOIS 15 New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 +SENSOR_SMT50_TEMP 16 New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +SENSOR_OSPI_ANALOG_INPUTS 20 Old OSPi analog input +SENSOR_VH400 30 New OpenSprinkler analog extension board x8 - Vegetronix VH400 +SENSOR_THERM200 31 New OpenSprinkler analog extension board x8 - Vegetronix THERM200 +SENSOR_AQUAPLUMB 32 New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb +SENSOR_USERDEF 49 New OpenSprinkler analog extension board x8 - User defined sensor +SENSOR_REMOTE 100 Remote sensor of an remote opensprinkler +SENSOR_GROUP_MIN 1000 Sensor group with min value +SENSOR_GROUP_MAX 1001 Sensor group with max value +SENSOR_GROUP_AVG 1002 Sensor group with avg value +SENSOR_GROUP_SUM 1003 Sensor group with sum value + +List Sensors (sl): +lists the current sensors +examples: +http:///sl?pw= + +Get last Sensor values (sg): +returns last read sensor values +examples: +http:///sg?pw= +http:///sg?pw=&nr=1 + +Read sensor now (sr): +executes sensor read and returns the values +examples: +http:///sr?pw= +http:///sr?pw=&nr=1 + +Set sensor address for SMT100 (sa): +Only for SMT100: Set modbus address +Disconnect all other modbus sensors, so that only one sensor is connected. Sets the modbus address for sensor nr to id +examples: +http:///sa?pw=&nr=1&id=1 + +Dump Sensor Log (so): +dumps the sensor log +examples: +http:///so?pw= +http:///so?pw=&start=0&max=100&nr=1&type=1&before=1666915277&after=1666915277 + +Clear Sensor Log (sn): +clears the sensor log +examples: +http:///sn?pw= + +Program adjustments (sb): +defines program adjustments +"nr" adjustment-nr +"type" adjustment-type (0=delete, 1=linear, 2=digital min, 3=digital max) +"sensor" sensor-nr +"prog" program-nr +"factor1", "factor2", "min", "max" formula-values +examples: +http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 + +type: +PROG_NONE 0 //No adjustment (delete) +PROG_LINEAR 1 //formula +PROG_DIGITAL_MIN 2 //1=under or equal min, 0=above +PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below + +formula: + min max factor1 factor2 + 10..90 -> 5..1 factor1 > factor2 + a b c d + (b-sensorData) / (b-a) * (c-d) + d + + 10..90 -> 1..5 factor1 < factor2 + a b c d + (sensorData-a) / (b-a) * (d-c) + c + +min/max is the used range of the sensor (for example min=10 max=80) +factor1/factor2 is the calculated adjustment (for example factor1=2 factor2=0) +So a sensordata of 10 will be a adjustment of factor 2 (200%) or +a sensordata of 80 will be a adjustment of factor 0 (0%) +everything between will be linear scaled in the range of 0..2 + +List Program adjustments (se): +lists the current program adjustments +examples: +http:///se?pw= +&nr=1&prog=1 + +List supported sensor types (sf): +lists supported sensor types +examples: +http:///sf?pw= + +List supported program adjustments (sh): +http:///sh?pw= + +System used and free space (du): +examples: +http:///du?pw= + +Sensor config User Defined (si): +Updates the user config values of a sensor +examples: +http:///si?pw=&nr=1&fac=100&div=10&unit='bar' + + From 4fe734d1db5fc5c08ec0ca494beaeee9cd6a60fd Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 22 Nov 2023 00:49:28 +0100 Subject: [PATCH 078/281] Bugfix: Add first sensor --- defines.h | 3 ++- opensprinkler_server.cpp | 37 +++++++++++++++++++++++-------------- sensors.cpp | 31 ++++++++++++++++--------------- sensors.h | 4 ++-- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/defines.h b/defines.h index 52e7f16cd..cea65951c 100644 --- a/defines.h +++ b/defines.h @@ -25,6 +25,7 @@ #define _DEFINES_H //#define ENABLE_DEBUG // enable serial debug +//#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -36,7 +37,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 133 // Firmware minor version +#define OS_FW_MINOR 134 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index bf4418f29..f0967077c 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1997,7 +1997,7 @@ void server_sensor_config_userdef(OTF_PARAMS_DEF) char *p = get_buffer; #endif - DEBUG_PRINTLN(F("server_sensor_config userder")); + DEBUG_PRINTLN(F("server_sensor_config userdef")); if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); @@ -2012,13 +2012,13 @@ void server_sensor_config_userdef(OTF_PARAMS_DEF) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("div"), true)) divider = strtol(tmp_buffer, NULL, 0); // divider - char userdef_unit_str[8]; - char *userdef_unit; + char userdef_unit[8]; + memset(userdef_unit, 0, sizeof(userdef_unit)); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { - strncpy(userdef_unit_str, tmp_buffer, sizeof(userdef_unit_str)-1); // unit - userdef_unit = userdef_unit_str; - } else { - userdef_unit = NULL; + urlDecode(tmp_buffer); + strReplace(tmp_buffer, '\"', '\''); + strReplace(tmp_buffer, '\\', '/'); + strncpy(userdef_unit, tmp_buffer, sizeof(userdef_unit)-1); // unit } int ret = sensor_define_userdef(nr, factor, divider, userdef_unit); @@ -2055,6 +2055,8 @@ void server_sensor_config(OTF_PARAMS_DEF) handle_return(HTML_SUCCESS); } + DEBUG_PRINTLN(F("server_sensor_config2")); + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("group"), true)) handle_return(HTML_DATA_MISSING); uint group = strtoul(tmp_buffer, NULL, 0); // Sensor group @@ -2066,7 +2068,7 @@ void server_sensor_config(OTF_PARAMS_DEF) strReplace(tmp_buffer, '\\', '/'); char name[30]; - strncpy(name, tmp_buffer, sizeof(name)-1); // Sensor nr + strncpy(name, tmp_buffer, sizeof(name)-1); // Sensor name if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ip"), true)) handle_return(HTML_DATA_MISSING); @@ -2092,13 +2094,15 @@ void server_sensor_config(OTF_PARAMS_DEF) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("div"), true)) divider = strtol(tmp_buffer, NULL, 0); // divider - char userdef_unit_str[8]; - char *userdef_unit; + DEBUG_PRINTLN(F("server_sensor_config3")); + + char userdef_unit[8]; + memset(userdef_unit, 0, sizeof(userdef_unit)); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { - strncpy(userdef_unit_str, tmp_buffer, sizeof(userdef_unit_str)-1); // unit - userdef_unit = userdef_unit_str; - } else { - userdef_unit = NULL; + urlDecode(tmp_buffer); + strReplace(tmp_buffer, '\"', '\''); + strReplace(tmp_buffer, '\\', '/'); + strncpy(userdef_unit, tmp_buffer, sizeof(userdef_unit)-1); // unit } uint enable = 1; @@ -2113,10 +2117,15 @@ void server_sensor_config(OTF_PARAMS_DEF) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("show"), true)) show = strtoul(tmp_buffer, NULL, 0); // 1=show enabled/0=show disabled + DEBUG_PRINTLN(F("server_sensor_config4")); + SensorFlags_t flags = {.enable=enable, .log=log, .show=show}; int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, flags); ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); + + DEBUG_PRINTLN(F("server_sensor_config5")); + } /** diff --git a/sensors.cpp b/sensors.cpp index da91630bf..82ac2c9c2 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -118,13 +118,15 @@ int sensor_delete(uint nr) { * @param port * @param id */ -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char* userdef_unit, SensorFlags_t flags) { +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char *userdef_unit, SensorFlags_t flags) { if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; if (ri < 10) ri = 10; + DEBUG_PRINTLN(F("server_define")); + Sensor_t *sensor = sensors; Sensor_t *last = NULL; while (sensor) { @@ -138,10 +140,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint sensor->read_interval = ri; sensor->factor = factor; sensor->divider = divider; - if (userdef_unit) - strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); - else - memset(sensor->userdef_unit, 0, sizeof(sensor->userdef_unit)); + strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); sensor->flags = flags; sensor_save(); return HTTP_RQT_SUCCESS; @@ -153,6 +152,9 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint last = sensor; sensor = sensor->next; } + + DEBUG_PRINTLN(F("server_define2")); + //Insert new Sensor_t *new_sensor = new Sensor_t; memset(new_sensor, 0, sizeof(Sensor_t)); @@ -166,10 +168,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->read_interval = ri; new_sensor->factor = factor; new_sensor->divider = divider; - if (userdef_unit) - strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); - else - memset(sensor->userdef_unit, 0, sizeof(sensor->userdef_unit)); + strncpy(new_sensor->userdef_unit, userdef_unit, sizeof(new_sensor->userdef_unit)-1); new_sensor->flags = flags; if (last) { new_sensor->next = last->next; @@ -182,7 +181,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint return HTTP_RQT_SUCCESS; } -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char* userdef_unit) { +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit) { Sensor_t *sensor = sensor_by_nr(nr); if (!sensor) return HTTP_RQT_NOT_RECEIVED; @@ -232,7 +231,7 @@ void sensor_load() { * */ void sensor_save() { - //DEBUG_PRINTLN(F("sensor_save")); + DEBUG_PRINTLN(F("sensor_save")); if (file_exists(SENSOR_FILENAME)) remove_file(SENSOR_FILENAME); @@ -243,6 +242,8 @@ void sensor_save() { sensor = sensor->next; pos += SENSOR_STORE_SIZE; } + + DEBUG_PRINTLN(F("sensor_save2")); } uint sensor_count() { @@ -484,7 +485,7 @@ ulong findLogPosition(uint8_t log, ulong after) { #if !defined(ARDUINO) /** -/* compatibility functions for OSPi: +* compatibility functions for OSPi: **/ #define timeSet 0 int timeStatus() { @@ -821,7 +822,7 @@ int read_sensor_adc(Sensor_t *sensor) { #endif #else /** -/* Read the OSPi onboard PCF8591 A2D +* Read the OSPi onboard PCF8591 A2D **/ int read_sensor_ospi(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_ospi")); @@ -830,8 +831,8 @@ int read_sensor_ospi(Sensor_t *sensor) { sensor->flags.data_ok = false; /** - /* https://medium.com/geekculture/raspberry-pi-c-libraries-for-working-with-i2c-spi-and-uart-4677f401b584 - /* http://www.pibits.net/amp/code/raspberry-pi-and-a-pcf8591-example.php + * https://medium.com/geekculture/raspberry-pi-c-libraries-for-working-with-i2c-spi-and-uart-4677f401b584 + * http://www.pibits.net/amp/code/raspberry-pi-and-a-pcf8591-example.php **/ int adapter_nr = 1; char filename[20]; diff --git a/sensors.h b/sensors.h index 302c5f1cd..3e20ca414 100644 --- a/sensors.h +++ b/sensors.h @@ -213,8 +213,8 @@ uint16_t CRC16 (byte buf[], int len); //Sensor API functions: int sensor_delete(uint nr); -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char* userdef_unit, SensorFlags_t flags); -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char* userdef_unit); +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char *userdef_unit, SensorFlags_t flags); +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit); void sensor_load(); void sensor_save(); uint sensor_count(); From 8f5463fd6797714824a30581d870f96fe16305b3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 30 Jan 2024 09:53:59 +0100 Subject: [PATCH 079/281] - Added better eth detection on startup, fixt fix ip issue (disabled DHCP) - platform update: espressif8266 4.2.1 - Version 135 --- OpenSprinkler.cpp | 31 +++++++++++++++++++++++++++++++ defines.h | 2 +- opensprinkler_server.cpp | 2 +- platformio.ini | 4 ++-- 4 files changed, 35 insertions(+), 4 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index f78254c1c..c17ea4481 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -507,6 +507,37 @@ byte OpenSprinkler::start_ether() { SPI.setDataMode(SPI_MODE0); SPI.setFrequency(4000000); + //TODO 3.3 Network interface + { + /* this is copied from enc28j60.cpp geterevid + * check to see if the hardware revision number if expected + * */ + DEBUG_PRINTLN(F("detect existence of ENC28J60")); + #define MAADRX_BANK 0x03 + #define EREVID 0x12 + #define ECON1 0x1f + + // ==> setregbank(MAADRX_BANK); + pinMode(PIN_ETHER_CS, OUTPUT); + uint8_t r; + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer(0x00 | (ECON1 & 0x1f)); + r = SPI.transfer(0); + digitalWrite(PIN_ETHER_CS, HIGH); + + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer(0x40 | (ECON1 & 0x1f)); + SPI.transfer((r & 0xfc) | (MAADRX_BANK & 0x03)); + digitalWrite(PIN_ETHER_CS, HIGH); + + // ==> r = readreg(EREVID); + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer(0x00 | (EREVID & 0x1f)); + r = SPI.transfer(0); + digitalWrite(PIN_ETHER_CS, HIGH); + if(r==0 || r==255) return 0; // r is expected to be a non-255 revision number + } + load_hardware_mac((uint8_t*)tmp_buffer, true); if (iopts[IOPT_USE_DHCP]==0) { // config static IP before calling eth.begin IPAddress staticip(iopts+IOPT_STATIC_IP1); diff --git a/defines.h b/defines.h index cea65951c..2e193336e 100644 --- a/defines.h +++ b/defines.h @@ -37,7 +37,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 134 // Firmware minor version +#define OS_FW_MINOR 135 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index f0967077c..f37590469 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -227,7 +227,7 @@ void rewind_ether_buffer() { void send_packet(OTF_PARAMS_DEF) { #if defined(ESP8266) - if (bfill.position() > 8192 || !res.willFit(bfill.position())) + if (bfill.position() >= 8192 || !res.willFit(bfill.position())) res.flush(); res.writeBodyChunk((char *)"%s",ether_buffer); #else diff --git a/platformio.ini b/platformio.ini index 5c3c40cef..e293b1e3b 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,8 +15,8 @@ src_dir = . include_dir = . -[env:d1_mini] -platform = espressif8266@4.2.0 +[env:d1_mini]"" +platform = espressif8266@4.2.1 ;platform = https://github.com/platformio/platform-espressif8266.git board = d1_mini framework = arduino From 76356f7ae4e6e85ad93c37dc07bd6078a1fa53d6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 4 Feb 2024 16:53:32 +0100 Subject: [PATCH 080/281] Modus RTU: Implemented read Modbus RTU TCP protocoll --- defines.h | 6 +- main.cpp | 2 +- opensprinkler_server.cpp | 34 +++++-- sensors.cpp | 198 +++++++++++++++++++++++---------------- sensors.h | 7 +- 5 files changed, 153 insertions(+), 94 deletions(-) diff --git a/defines.h b/defines.h index 2e193336e..62e04338e 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug -//#define SERIAL_DEBUG +#define ENABLE_DEBUG // enable serial debug +#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -37,7 +37,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 135 // Firmware minor version +#define OS_FW_MINOR 136 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index 77deb8736..6b922190a 100644 --- a/main.cpp +++ b/main.cpp @@ -34,7 +34,7 @@ #if defined(ESP8266) #include #include - extern "C" struct netif* eagle_lwip_getif (int netif_index); + //extern "C" struct netif* eagle_lwip_getif (int netif_index); Pinger *pinger = NULL; ESP8266WebServer *update_server = NULL; OTF::OpenThingsFramework *otf = NULL; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index f37590469..88b92938a 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -227,8 +227,14 @@ void rewind_ether_buffer() { void send_packet(OTF_PARAMS_DEF) { #if defined(ESP8266) - if (bfill.position() >= 8192 || !res.willFit(bfill.position())) + DEBUG_PRINT(F("bfill position: ")); + DEBUG_PRINTLN(bfill.position()); + if (bfill.position() >= 8192 || !res.willFit(bfill.position())) { + DEBUG_PRINTLN("res.flush"); res.flush(); + } + DEBUG_PRINT("res.writeBodyChunk: "); + DEBUG_PRINTLN(strlen(ether_buffer)); res.writeBodyChunk((char *)"%s",ether_buffer); #else m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); @@ -2020,8 +2026,11 @@ void server_sensor_config_userdef(OTF_PARAMS_DEF) strReplace(tmp_buffer, '\\', '/'); strncpy(userdef_unit, tmp_buffer, sizeof(userdef_unit)-1); // unit } + int16_t offset_mv = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) + offset_mv = strtol(tmp_buffer, NULL, 0); // offset in millivolt - int ret = sensor_define_userdef(nr, factor, divider, userdef_unit); + int ret = sensor_define_userdef(nr, factor, divider, userdef_unit, offset_mv); ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); } @@ -2104,6 +2113,9 @@ void server_sensor_config(OTF_PARAMS_DEF) strReplace(tmp_buffer, '\\', '/'); strncpy(userdef_unit, tmp_buffer, sizeof(userdef_unit)-1); // unit } + int16_t offset_mv = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) + offset_mv = strtol(tmp_buffer, NULL, 0); // offset in millivolt uint enable = 1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enable"), true)) @@ -2120,7 +2132,7 @@ void server_sensor_config(OTF_PARAMS_DEF) DEBUG_PRINTLN(F("server_sensor_config4")); SensorFlags_t flags = {.enable=enable, .log=log, .show=show}; - int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, flags); + int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, offset_mv, flags); ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); @@ -2268,7 +2280,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { if (first) first = false; else bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"fac\":$D,\"div\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"fac\":$D,\"div\":$D,\"offset\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L}"), sensor->nr, sensor->type, sensor->group, @@ -2279,6 +2291,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { sensor->read_interval, sensor->factor, sensor->divider, + sensor->offset_mv, sensor->last_native_data, sensor->last_data, getSensorUnit(sensor), @@ -2446,6 +2459,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { uint sensor_type = 0; + DEBUG_PRINTLN(F("start so")); ulong idx = startAt; while (idx < log_size) { int n = sensorlog_load2(log, idx, BLOCKSIZE, sensorlog); @@ -2501,18 +2515,26 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { } // if available ether buffer is getting small // send out a packet - if (count++ >= maxResults) + if (available_ether_buffer() <=0 ) { + send_packet(OTF_PARAMS); + } + if (count++ >= maxResults) { break; - send_packet(OTF_PARAMS); + } } if (count >= maxResults) break; } + DEBUG_PRINTLN(F("end so")); if (isjson) bfill.emit_p(PSTR("]}")); + else + bfill.emit_p(PSTR("\r\n")); free(sensorlog); + DEBUG_PRINTLN(F("finish so")); + handle_return(HTML_OK); } diff --git a/sensors.cpp b/sensors.cpp index 82ac2c9c2..e656c4227 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -40,6 +40,9 @@ static SensorUrl_t * sensorUrls = NULL; //Program sensor data static ProgSensorAdjust_t *progSensorAdjusts = NULL; +//modbus transaction id +static uint16_t modbusTcpId = 0; + const char* sensor_unitNames[] { "", "%", "°C", "°F", "V", "%", "in", "mm", "mph", "kmh", "%" // 0 1 2 3 4 5 6 7 8 9 10 @@ -118,7 +121,7 @@ int sensor_delete(uint nr) { * @param port * @param id */ -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char *userdef_unit, SensorFlags_t flags) { +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, SensorFlags_t flags) { if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; @@ -140,6 +143,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint sensor->read_interval = ri; sensor->factor = factor; sensor->divider = divider; + sensor->offset_mv = offset_mv; strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); sensor->flags = flags; sensor_save(); @@ -168,6 +172,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->read_interval = ri; new_sensor->factor = factor; new_sensor->divider = divider; + new_sensor->offset_mv = offset_mv; strncpy(new_sensor->userdef_unit, userdef_unit, sizeof(new_sensor->userdef_unit)-1); new_sensor->flags = flags; if (last) { @@ -181,13 +186,14 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint return HTTP_RQT_SUCCESS; } -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit) { +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv) { Sensor_t *sensor = sensor_by_nr(nr); if (!sensor) return HTTP_RQT_NOT_RECEIVED; sensor->factor = factor; sensor->divider = divider; + sensor->offset_mv = offset_mv; if (userdef_unit) strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); else @@ -714,10 +720,13 @@ void read_all_sensors() { while (sensor) { if (time >= sensor->last_read + sensor->read_interval) { - if (read_sensor(sensor) == HTTP_RQT_SUCCESS) { + int result = read_sensor(sensor); + if (result == HTTP_RQT_SUCCESS) { sensorlog_add(LOG_STD, sensor, time); + push_message(sensor); + } else if (result == HTTP_RQT_TIMEOUT) { + sensor->last_read = time - sensor->read_interval + 5; } - push_message(sensor); } sensor = sensor->next; } @@ -751,8 +760,14 @@ int read_sensor_adc(Sensor_t *sensor) { if (!active) return HTTP_RQT_NOT_RECEIVED; - //Read values: - sensor->last_native_data = adc.readADC(id); + //Read values, 3x: + uint64_t avgValue = adc.readADC(id); + delay(100); + avgValue += adc.readADC(id); + delay(100); + avgValue += adc.readADC(id); + + sensor->last_native_data = avgValue / 3; sensor->last_data = adc.toVoltage(sensor->last_native_data); double v = sensor->last_data; @@ -800,8 +815,9 @@ int read_sensor_adc(Sensor_t *sensor) { sensor->last_data = 100; break; case SENSOR_USERDEF: //User defined sensor + v -= (double)sensor->offset_mv/1000; // adjust zero-point offset in millivolt if (sensor->factor && sensor->divider) - v *= sensor->factor / sensor->divider; + v *= (double)sensor->factor / (double)sensor->divider; else if (sensor->divider) v /= sensor->divider; else if (sensor->factor) @@ -990,57 +1006,61 @@ int read_sensor_ip(Sensor_t *sensor) { ip[2] = (byte)((sensor->ip >> 8) &0xFF); ip[3] = (byte)((sensor->ip &0xFF)); #endif - - if(!client->connect(ip, sensor->port)) { - DEBUG_PRINT(F("Cannot connect to ")); - DEBUG_PRINT(ip[0]); DEBUG_PRINT("."); - DEBUG_PRINT(ip[1]); DEBUG_PRINT("."); - DEBUG_PRINT(ip[2]); DEBUG_PRINT("."); - DEBUG_PRINT(ip[3]); DEBUG_PRINT(":"); - DEBUG_PRINTLN(sensor->port); + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + client->setTimeout(200); + if(!client->connect(server, sensor->port)) { + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINT(sensor->port); + DEBUG_PRINT(" "); + DEBUG_PRINTLN(F("failed.")); client->stop(); - return HTTP_RQT_CONNECT_ERR; + return HTTP_RQT_TIMEOUT; } -uint8_t buffer[10]; - int len = 0; - boolean addCrc16 = false; + uint8_t buffer[20]; + + //https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/ + + if (modbusTcpId >= 0xFFFE) + modbusTcpId = 1; + else + modbusTcpId++; + + buffer[0] = (0xFF00&modbusTcpId) >> 8; + buffer[1] = (0x00FF&modbusTcpId); + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + buffer[5] = 6; //len + switch(sensor->type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: - buffer[0] = sensor->id; //Modbus ID - buffer[1] = 0x03; //Read Holding Registers - buffer[2] = 0x00; - buffer[3] = 0x01; //soil moisture is at address 0x0001 - buffer[4] = 0x00; - buffer[5] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) - len = 6; - addCrc16 = true; + buffer[6] = sensor->id; //Modbus ID + buffer[7] = 0x03; //Read Holding Registers + buffer[8] = 0x00; + buffer[9] = 0x01; //soil moisture is at address 0x0001 + buffer[10] = 0x00; + buffer[11] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) break; case SENSOR_SMT100_MODBUS_RTU_TEMP: - buffer[0] = sensor->id; - buffer[1] = 0x03; //Read Holding Registers - buffer[2] = 0x00; - buffer[3] = 0x00; //temperature is at address 0x0000 - buffer[4] = 0x00; - buffer[5] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) - len = 6; - addCrc16 = true; + buffer[6] = sensor->id; + buffer[7] = 0x03; //Read Holding Registers + buffer[8] = 0x00; + buffer[9] = 0x00; //temperature is at address 0x0000 + buffer[10] = 0x00; + buffer[11] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) break; default: client->stop(); return HTTP_RQT_NOT_RECEIVED; } - if (addCrc16) { - uint16_t crc = CRC16(buffer, len); - buffer[len+0] = (0x00FF&crc); - buffer[len+1] = (0xFF00&crc)>>8; - len += 2; - } - client->write(buffer, len); + client->write(buffer, 12); #if defined(ESP8266) client->flush(); #endif @@ -1059,16 +1079,21 @@ uint8_t buffer[10]; client->stop(); DEBUG_PRINT(F("Sensor ")); DEBUG_PRINT(sensor->nr); - DEBUG_PRINT(F(" timeout read!")); + DEBUG_PRINTLN(F(" timeout read!")); return HTTP_RQT_TIMEOUT; } delay(5); } - int n = client->read(buffer, 7); + + int n = client->read(buffer, 11); + if (n < 11) + n += client->read(buffer+n, 11-n); #else - int n = 0; + n = 0; while (true) { - n = client->read(buffer, 7); + n = client->read(buffer, 11); + if (n < 11) + n += client->read(buffer+n, 11-n); if (n > 0) break; if (millis() >= stoptime) { @@ -1084,25 +1109,25 @@ uint8_t buffer[10]; client->stop(); DEBUG_PRINT(F("Sensor ")); DEBUG_PRINT(sensor->nr); - if (n != 7) { + if (n != 11) { DEBUG_PRINT(F(" returned ")); DEBUG_PRINT(n); - DEBUG_PRINT(F(" bytes??")); + DEBUG_PRINTLN(F(" bytes??")); return n == 0 ? HTTP_RQT_EMPTY_RETURN:HTTP_RQT_TIMEOUT; } - if ((buffer[0] != sensor->id && sensor->id != 253)) { //253 is broadcast - DEBUG_PRINT(F(" returned sensor id ")); - DEBUG_PRINT((int)buffer[0]); + if (buffer[0] != (0xFF00&modbusTcpId) >> 8 || buffer[1] != (0x00FF&modbusTcpId)) { + DEBUG_PRINT(F(" returned transaction id ")); + DEBUG_PRINTLN((uint16_t)((buffer[0] << 8) + buffer[1])); return HTTP_RQT_NOT_RECEIVED; } - uint16_t crc = (buffer[6] << 8) | buffer[5]; - if (crc != CRC16(buffer, 5)) { - DEBUG_PRINT(F(" crc error!")); + if ((buffer[6] != sensor->id && sensor->id != 253)) { //253 is broadcast + DEBUG_PRINT(F(" returned sensor id ")); + DEBUG_PRINTLN((int)buffer[0]); return HTTP_RQT_NOT_RECEIVED; } //Valid result: - sensor->last_native_data = (buffer[3] << 8) | buffer[4]; + sensor->last_native_data = (buffer[9] << 8) | buffer[10]; DEBUG_PRINT(F(" native: ")); DEBUG_PRINT(sensor->last_native_data); @@ -1298,18 +1323,6 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { { case SENSOR_SMT100_MODBUS_RTU_MOIS: case SENSOR_SMT100_MODBUS_RTU_TEMP: - uint8_t buffer[10]; - int len = 8; - buffer[0] = sensor->id; - buffer[1] = 0x06; - buffer[2] = 0x00; - buffer[3] = 0x04; - buffer[4] = 0x00; - buffer[5] = new_address; - uint16_t crc = CRC16(buffer, 6); - buffer[6] = (0x00FF&crc); - buffer[7] = (0xFF00&crc)>>8; - len = 8; #if defined(ARDUINO) Client *client; @@ -1340,41 +1353,64 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { ip[2] = (byte)((sensor->ip >> 8) &0xFF); ip[3] = (byte)((sensor->ip &0xFF)); #endif - if(!client->connect(ip, sensor->port)) { + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + client->setTimeout(200); + if(!client->connect(server, sensor->port)) { DEBUG_PRINT(F("Cannot connect to ")); - DEBUG_PRINT(ip[0]); DEBUG_PRINT("."); - DEBUG_PRINT(ip[1]); DEBUG_PRINT("."); - DEBUG_PRINT(ip[2]); DEBUG_PRINT("."); - DEBUG_PRINT(ip[3]); DEBUG_PRINT(":"); + DEBUG_PRINT(server); + DEBUG_PRINT(":"); DEBUG_PRINTLN(sensor->port); client->stop(); return HTTP_RQT_CONNECT_ERR; } - client->write(buffer, len); + uint8_t buffer[20]; + + //https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/ + + if (modbusTcpId >= 0xFFFE) + modbusTcpId = 1; + else + modbusTcpId++; + + buffer[0] = (0xFF00&modbusTcpId) >> 8; + buffer[1] = (0x00FF&modbusTcpId); + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + buffer[5] = 6; //len + buffer[6] = sensor->id; + buffer[7] = 0x06; + buffer[8] = 0x00; + buffer[9] = 0x04; + buffer[10] = 0x00; + buffer[11] = new_address; + + client->write(buffer, 12); #if defined(ESP8266) client->flush(); #endif //Read result: - int n = client->read(buffer, 8); + int n = client->read(buffer, 12); client->stop(); DEBUG_PRINT(F("Sensor ")); DEBUG_PRINT(sensor->nr); - if (n != 8) { + if (n != 12) { DEBUG_PRINT(F(" returned ")); DEBUG_PRINT(n); DEBUG_PRINT(F(" bytes??")); return n == 0 ? HTTP_RQT_EMPTY_RETURN:HTTP_RQT_TIMEOUT; } - if ((buffer[0] != sensor->id && sensor->id != 253)) { //253 is broadcast - DEBUG_PRINT(F(" returned sensor id ")); - DEBUG_PRINT((int)buffer[0]); + if (buffer[0] != (0xFF00&modbusTcpId) >> 8 || buffer[1] != (0x00FF&modbusTcpId)) { + DEBUG_PRINT(F(" returned transaction id ")); + DEBUG_PRINTLN((uint16_t)((buffer[0] << 8) + buffer[1])); return HTTP_RQT_NOT_RECEIVED; } - crc = (buffer[6] | buffer[7] << 8); - if (crc != CRC16(buffer, 6)) { - DEBUG_PRINT(F(" crc error!")); + if ((buffer[6] != sensor->id && sensor->id != 253)) { //253 is broadcast + DEBUG_PRINT(F(" returned sensor id ")); + DEBUG_PRINT((int)buffer[0]); return HTTP_RQT_NOT_RECEIVED; } //Read OK: diff --git a/sensors.h b/sensors.h index 3e20ca414..23cf833ed 100644 --- a/sensors.h +++ b/sensors.h @@ -127,7 +127,8 @@ typedef struct Sensor { int16_t factor; // faktor - for custom sensor int16_t divider; // divider - for custom sensor char userdef_unit[8]; // unit - for custom sensor - byte undef[20]; // for later + int16_t offset_mv; // offset millivolt - for custom sensor + byte undef[18]; // for later //unstored byte unitid; ulong last_read; //millis @@ -213,8 +214,8 @@ uint16_t CRC16 (byte buf[], int len); //Sensor API functions: int sensor_delete(uint nr); -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char *userdef_unit, SensorFlags_t flags); -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit); +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char *userdef_unit, int16_t offsetmv, SensorFlags_t flags); +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv); void sensor_load(); void sensor_save(); uint sensor_count(); From 82c495053ee83e92029b875480e9692f47dd6308 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 6 Feb 2024 00:12:32 +0100 Subject: [PATCH 081/281] Changed defaults --- defines.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/defines.h b/defines.h index 62e04338e..34b1f0426 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug -#define SERIAL_DEBUG +//#define ENABLE_DEBUG // enable serial debug +//#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -37,7 +37,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 136 // Firmware minor version +#define OS_FW_MINOR 137 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -140,13 +140,13 @@ typedef unsigned long ulong; /** Default string option values */ #define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA -#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" +#define DEFAULT_LOCATION "49.484018,8.475593" // Mannheim,Germany +#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinklershop.de/js" #define DEFAULT_WEATHER_URL "weather.opensprinkler.com" #define DEFAULT_IFTTT_URL "maker.ifttt.com" #define DEFAULT_OTC_SERVER "ws.cloud.openthings.io" #define DEFAULT_OTC_PORT 80 -#define DEFAULT_DEVICE_NAME "My OpenSprinkler" +#define DEFAULT_DEVICE_NAME "Mein OpenSprinkler" #define DEFAULT_EMPTY_STRING "" /* Weather Adjustment Methods */ From f9cc278094cd2f820ebda83be2c4ddfb3b539cf5 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Feb 2024 23:47:36 +0100 Subject: [PATCH 082/281] OSPi compatibility for last changes --- sensors.cpp | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index e656c4227..76bdecbef 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -996,7 +996,7 @@ int read_sensor_ip(Sensor_t *sensor) { return HTTP_RQT_CONNECT_ERR; } -#if defined(ESP8266) +#if defined(ARDUINO) IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else @@ -1008,8 +1008,14 @@ int read_sensor_ip(Sensor_t *sensor) { #endif char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); +#if defined(ESP8266) client->setTimeout(200); if(!client->connect(server, sensor->port)) { +#else + struct hostent *host = gethostbyname(server); + if (!host) { return HTTP_RQT_CONNECT_ERR; } + if(!client->connect((uint8_t*)host->h_addr, sensor->port)) { +#endif DEBUG_PRINT(server); DEBUG_PRINT(":"); DEBUG_PRINT(sensor->port); @@ -1089,7 +1095,7 @@ int read_sensor_ip(Sensor_t *sensor) { if (n < 11) n += client->read(buffer+n, 11-n); #else - n = 0; + int n = 0; while (true) { n = client->read(buffer, 11); if (n < 11) @@ -1344,10 +1350,10 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { } #if defined(ESP8266) - IPAddress _ip(sensor->ip); + IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else - byte ip[4]; + byte ip[4]; ip[0] = (byte)((sensor->ip >> 24) &0xFF); ip[1] = (byte)((sensor->ip >> 16) &0xFF); ip[2] = (byte)((sensor->ip >> 8) &0xFF); @@ -1355,8 +1361,14 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { #endif char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - client->setTimeout(200); - if(!client->connect(server, sensor->port)) { +#if defined(ESP8266) + client->setTimeout(200); + if(!client->connect(server, sensor->port)) { +#else + struct hostent *host = gethostbyname(server); + if (!host) { return HTTP_RQT_CONNECT_ERR; } + if(!client->connect((uint8_t*)host->h_addr, sensor->port)) { +#endif DEBUG_PRINT(F("Cannot connect to ")); DEBUG_PRINT(server); DEBUG_PRINT(":"); From 1eaf316885255dd37fa0662bb50a0b1b89458b25 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 8 Feb 2024 00:32:11 +0100 Subject: [PATCH 083/281] WIFI Off when Ethernet is connected, Analog Sensors read 7x and use avg value --- OpenSprinkler.cpp | 1 + sensors.cpp | 16 ++++++++-------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index c17ea4481..341fc5993 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -464,6 +464,7 @@ byte OpenSprinkler::start_network() { if (start_ether()) { useEth = true; + WiFi.mode(WIFI_OFF); } else { useEth = false; } diff --git a/sensors.cpp b/sensors.cpp index e656c4227..097ca5734 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -760,14 +760,14 @@ int read_sensor_adc(Sensor_t *sensor) { if (!active) return HTTP_RQT_NOT_RECEIVED; - //Read values, 3x: - uint64_t avgValue = adc.readADC(id); - delay(100); - avgValue += adc.readADC(id); - delay(100); - avgValue += adc.readADC(id); - - sensor->last_native_data = avgValue / 3; + //Read values multiple times: + uint64_t avgValue = 0; +#define MAX_READ 7 + for (int t = 0; t < MAX_READ; t++) { + if (t > 0) delay(100); + avgValue += adc.readADC(id); + } + sensor->last_native_data = avgValue / MAX_READ; sensor->last_data = adc.toVoltage(sensor->last_native_data); double v = sensor->last_data; From b6acdacc58ee929fef74442e4061d1e90594a575 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 11 Feb 2024 23:18:14 +0100 Subject: [PATCH 084/281] Added offset2 1/100 for user defined sensors, added se&sensor= option --- defines.h | 2 +- opensprinkler_server.cpp | 22 +++++++++++++++++++--- sensors.cpp | 10 +++++++--- sensors.h | 11 +++++++---- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/defines.h b/defines.h index 34b1f0426..acd4b5588 100644 --- a/defines.h +++ b/defines.h @@ -37,7 +37,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 137 // Firmware minor version +#define OS_FW_MINOR 138 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 88b92938a..2b9178420 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2029,8 +2029,11 @@ void server_sensor_config_userdef(OTF_PARAMS_DEF) int16_t offset_mv = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) offset_mv = strtol(tmp_buffer, NULL, 0); // offset in millivolt + int16_t offset2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset2"), true)) + offset2 = strtol(tmp_buffer, NULL, 0); // offset unit value - int ret = sensor_define_userdef(nr, factor, divider, userdef_unit, offset_mv); + int ret = sensor_define_userdef(nr, factor, divider, userdef_unit, offset_mv, offset2); ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); } @@ -2117,6 +2120,10 @@ void server_sensor_config(OTF_PARAMS_DEF) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) offset_mv = strtol(tmp_buffer, NULL, 0); // offset in millivolt + int16_t offset2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset2"), true)) + offset2 = strtol(tmp_buffer, NULL, 0); // offset2 + uint enable = 1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enable"), true)) enable = strtoul(tmp_buffer, NULL, 0); // 1=enable/0=disable @@ -2132,7 +2139,7 @@ void server_sensor_config(OTF_PARAMS_DEF) DEBUG_PRINTLN(F("server_sensor_config4")); SensorFlags_t flags = {.enable=enable, .log=log, .show=show}; - int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, offset_mv, flags); + int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, offset_mv, offset2, flags); ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); @@ -2280,7 +2287,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { if (first) first = false; else bfill.emit_p(PSTR(",")); - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"fac\":$D,\"div\":$D,\"offset\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"fac\":$D,\"div\":$D,\"offset\":$D,\"offset2\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L}"), sensor->nr, sensor->type, sensor->group, @@ -2292,6 +2299,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { sensor->factor, sensor->divider, sensor->offset_mv, + sensor->offset2, sensor->last_native_data, sensor->last_data, getSensorUnit(sensor), @@ -2680,6 +2688,7 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { uint nr = 0; int prog = -1; + uint sensor_nr = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) nr = strtoul(tmp_buffer, NULL, 0); @@ -2687,6 +2696,9 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) prog = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + sensor_nr = strtoul(tmp_buffer, NULL, 0); + #if defined(ESP8266) // 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(). @@ -2705,6 +2717,8 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { continue; if (prog >= 0 && p->prog != (uint)prog) continue; + if (sensor_nr > 0 && p->sensor != sensor_nr) + continue; count++; } @@ -2720,6 +2734,8 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { continue; if (prog >= 0 && p->prog != (uint)prog) continue; + if (sensor_nr > 0 && p->sensor != sensor_nr) + continue; double current = calc_sensor_watering_by_nr(p->nr); diff --git a/sensors.cpp b/sensors.cpp index 450a8326c..4d65abfd7 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -121,7 +121,8 @@ int sensor_delete(uint nr) { * @param port * @param id */ -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, SensorFlags_t flags) { +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, + char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags) { if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; @@ -144,6 +145,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint sensor->factor = factor; sensor->divider = divider; sensor->offset_mv = offset_mv; + sensor->offset2 = offset2; strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); sensor->flags = flags; sensor_save(); @@ -173,6 +175,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->factor = factor; new_sensor->divider = divider; new_sensor->offset_mv = offset_mv; + new_sensor->offset2 = offset2; strncpy(new_sensor->userdef_unit, userdef_unit, sizeof(new_sensor->userdef_unit)-1); new_sensor->flags = flags; if (last) { @@ -186,7 +189,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint return HTTP_RQT_SUCCESS; } -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv) { +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, int16_t offset2) { Sensor_t *sensor = sensor_by_nr(nr); if (!sensor) return HTTP_RQT_NOT_RECEIVED; @@ -194,6 +197,7 @@ int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userde sensor->factor = factor; sensor->divider = divider; sensor->offset_mv = offset_mv; + sensor->offset2 = offset2; if (userdef_unit) strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); else @@ -822,7 +826,7 @@ int read_sensor_adc(Sensor_t *sensor) { v /= sensor->divider; else if (sensor->factor) v *= sensor->factor; - sensor->last_data = v; + sensor->last_data = v + sensor->offset2/100; break; } diff --git a/sensors.h b/sensors.h index 23cf833ed..f51e1ff76 100644 --- a/sensors.h +++ b/sensors.h @@ -127,8 +127,10 @@ typedef struct Sensor { int16_t factor; // faktor - for custom sensor int16_t divider; // divider - for custom sensor char userdef_unit[8]; // unit - for custom sensor - int16_t offset_mv; // offset millivolt - for custom sensor - byte undef[18]; // for later + int16_t offset_mv; // offset millivolt - for custom sensor (before) + int16_t offset2; // offset unit value 1/100 - for custom sensor (after): + // sensorvalue = (read_value-offset_mv/1000) * factor / divider + offset2/100 + byte undef[16]; // for later //unstored byte unitid; ulong last_read; //millis @@ -214,8 +216,9 @@ uint16_t CRC16 (byte buf[], int len); //Sensor API functions: int sensor_delete(uint nr); -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, char *userdef_unit, int16_t offsetmv, SensorFlags_t flags); -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv); +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, + char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags); +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, int16_t offset2); void sensor_load(); void sensor_save(); uint sensor_count(); From db38717120b24fa0cad395ef249ecef80de6136e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 12 Feb 2024 22:24:04 +0100 Subject: [PATCH 085/281] Fix program adjustment "none" changed, because type=0 means deleting, now type none=99 --- sensors.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sensors.h b/sensors.h index f51e1ff76..02b4d3629 100644 --- a/sensors.h +++ b/sensors.h @@ -158,10 +158,11 @@ typedef struct SensorLog { // a b c d // (sensorData-a) / (b-a) * (d-c) + c -#define PROG_NONE 0 //No adjustment (delete) +#define PROG_DELETE 0 //deleted #define PROG_LINEAR 1 //formula see above #define PROG_DIGITAL_MIN 2 //1=under or equal min, 0=above #define PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below +#define PROG_NONE 99 //No adjustment typedef struct ProgSensorAdjust { uint nr; //adjust-nr 1..x From ad4ea1263fe7795c1749530e58ef0f9f8e0d0afc Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 14 Feb 2024 00:45:22 +0100 Subject: [PATCH 086/281] added Min&Max adjustment also existing Min and Max Adjustments uses Factor1+2 --- Sensor API.txt | 10 ++++++---- opensprinkler_server.cpp | 2 ++ sensors.cpp | 10 ++++++++-- sensors.h | 11 ++++++----- 4 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Sensor API.txt b/Sensor API.txt index 5060492d0..ae391a5ff 100644 --- a/Sensor API.txt +++ b/Sensor API.txt @@ -104,10 +104,12 @@ examples: http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 type: -PROG_NONE 0 //No adjustment (delete) -PROG_LINEAR 1 //formula -PROG_DIGITAL_MIN 2 //1=under or equal min, 0=above -PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below +#define PROG_DELETE 0 //deleted +#define PROG_LINEAR 1 //formula see above +#define PROG_DIGITAL_MIN 2 //under or equal min : factor1 else factor2 +#define PROG_DIGITAL_MAX 3 //over or equal max : factor2 else factor1 +#define PROG_DIGITAL_MINMAX 4 //under min or over max : factor1 else factor2 +#define PROG_NONE 99 //No adjustment formula: min max factor1 factor2 diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 2b9178420..33412c392 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2938,6 +2938,7 @@ const int prog_types[] = { PROG_LINEAR, PROG_DIGITAL_MIN, PROG_DIGITAL_MAX, + PROG_DIGITAL_MINMAX, }; const char* prog_names[] = { @@ -2945,6 +2946,7 @@ const char* prog_names[] = { "Linear scaling", "Digital under min", "Digital over max", + "Digital under min or over max", }; /** diff --git a/sensors.cpp b/sensors.cpp index 4d65abfd7..48697fda0 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1462,13 +1462,18 @@ double calc_linear(ProgSensorAdjust_t *p, Sensor_t *sensor) { } double calc_digital_min(ProgSensorAdjust_t *p, Sensor_t *sensor) { - return sensor->last_data <= p->min? 1:0; + return sensor->last_data <= p->min? p->factor1:p->factor2; } double calc_digital_max(ProgSensorAdjust_t *p, Sensor_t *sensor) { - return sensor->last_data >= p->max? 1:0; + return sensor->last_data >= p->max? p->factor2:p->factor1; } +double calc_digital_minmax(ProgSensorAdjust_t *p, Sensor_t *sensor) { + if (sensor->last_data <= p->min) return p->factor1; + if (sensor->last_data >= p->max) return p->factor1; + return p->factor2; +} /** * @brief calculate adjustment * @@ -1490,6 +1495,7 @@ double calc_sensor_watering(uint prog) { case PROG_LINEAR: res = calc_linear(p, sensor); break; case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor); break; case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor); break; + case PROG_DIGITAL_MINMAX: res = calc_digital_minmax(p, sensor); break; default: res = 0; } diff --git a/sensors.h b/sensors.h index 02b4d3629..68f9f1b22 100644 --- a/sensors.h +++ b/sensors.h @@ -158,11 +158,12 @@ typedef struct SensorLog { // a b c d // (sensorData-a) / (b-a) * (d-c) + c -#define PROG_DELETE 0 //deleted -#define PROG_LINEAR 1 //formula see above -#define PROG_DIGITAL_MIN 2 //1=under or equal min, 0=above -#define PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below -#define PROG_NONE 99 //No adjustment +#define PROG_DELETE 0 //deleted +#define PROG_LINEAR 1 //formula see above +#define PROG_DIGITAL_MIN 2 //under or equal min : factor1 else factor2 +#define PROG_DIGITAL_MAX 3 //over or equal max : factor2 else factor1 +#define PROG_DIGITAL_MINMAX 4 //under min or over max : factor1 else factor2 +#define PROG_NONE 99 //No adjustment typedef struct ProgSensorAdjust { uint nr; //adjust-nr 1..x From 7f0c1d756cec3ceb5fa1c27dc80be6f4873776ec Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 17 Feb 2024 01:31:30 +0100 Subject: [PATCH 087/281] analog sensor log: max parameter limits to the last x (instead first x) values --- opensprinkler_server.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 33412c392..df85a36e2 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2464,6 +2464,10 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { } startAt = lastIdx; } + else if (startAt==0 && maxResults > 0 && maxResults != log_size) + { + startAt = log_size-maxResults; + } uint sensor_type = 0; @@ -2526,7 +2530,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (available_ether_buffer() <=0 ) { send_packet(OTF_PARAMS); } - if (count++ >= maxResults) { + if (++count >= maxResults) { break; } } From 696592af38c81780da8ea6b94b2a31f9b7d29a20 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 18 Feb 2024 02:06:39 +0100 Subject: [PATCH 088/281] Fixt analog sensor log download limits (max), changed debugoutput --- defines.h | 2 +- opensprinkler_server.cpp | 116 +++++++++++++++++++++------------------ sensors.cpp | 10 +++- 3 files changed, 70 insertions(+), 58 deletions(-) diff --git a/defines.h b/defines.h index acd4b5588..a089ad1bc 100644 --- a/defines.h +++ b/defines.h @@ -37,7 +37,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 138 // Firmware minor version +#define OS_FW_MINOR 139 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index df85a36e2..d54ca022f 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -227,19 +227,20 @@ void rewind_ether_buffer() { void send_packet(OTF_PARAMS_DEF) { #if defined(ESP8266) - DEBUG_PRINT(F("bfill position: ")); - DEBUG_PRINTLN(bfill.position()); - if (bfill.position() >= 8192 || !res.willFit(bfill.position())) { - DEBUG_PRINTLN("res.flush"); - res.flush(); - } - DEBUG_PRINT("res.writeBodyChunk: "); - DEBUG_PRINTLN(strlen(ether_buffer)); + //DEBUG_PRINT(F("bfill position: ")); + //DEBUG_PRINTLN(bfill.position()); + if (!res.willFit(bfill.position())) { + DEBUG_PRINT(F("flush packet size: ")); + DEBUG_PRINTLN(res.getLength()); + res.flush(); + } + //DEBUG_PRINT("res.writeBodyChunk: "); + //DEBUG_PRINTLN(strlen(ether_buffer)); res.writeBodyChunk((char *)"%s",ether_buffer); #else m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); #endif - + rewind_ether_buffer(); } @@ -2023,7 +2024,7 @@ void server_sensor_config_userdef(OTF_PARAMS_DEF) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { urlDecode(tmp_buffer); strReplace(tmp_buffer, '\"', '\''); - strReplace(tmp_buffer, '\\', '/'); + strReplace(tmp_buffer, '\\', '/'); strncpy(userdef_unit, tmp_buffer, sizeof(userdef_unit)-1); // unit } int16_t offset_mv = 0; @@ -2035,7 +2036,7 @@ void server_sensor_config_userdef(OTF_PARAMS_DEF) int ret = sensor_define_userdef(nr, factor, divider, userdef_unit, offset_mv, offset2); ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; - handle_return(ret); + handle_return(ret); } /** @@ -2077,7 +2078,7 @@ void server_sensor_config(OTF_PARAMS_DEF) handle_return(HTML_DATA_MISSING); urlDecode(tmp_buffer); strReplace(tmp_buffer, '\"', '\''); - strReplace(tmp_buffer, '\\', '/'); + strReplace(tmp_buffer, '\\', '/'); char name[30]; strncpy(name, tmp_buffer, sizeof(name)-1); // Sensor name @@ -2113,7 +2114,7 @@ void server_sensor_config(OTF_PARAMS_DEF) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { urlDecode(tmp_buffer); strReplace(tmp_buffer, '\"', '\''); - strReplace(tmp_buffer, '\\', '/'); + strReplace(tmp_buffer, '\\', '/'); strncpy(userdef_unit, tmp_buffer, sizeof(userdef_unit)-1); // unit } int16_t offset_mv = 0; @@ -2142,7 +2143,7 @@ void server_sensor_config(OTF_PARAMS_DEF) int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, offset_mv, offset2, flags); ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); - + DEBUG_PRINTLN(F("server_sensor_config5")); } @@ -2177,7 +2178,7 @@ void server_set_sensor_address(OTF_PARAMS_DEF) { /** * sg * @brief return one or all last sensor values - * + * */ void server_sensor_get(OTF_PARAMS_DEF) { #if defined(ESP8266) @@ -2213,7 +2214,7 @@ void server_sensor_get(OTF_PARAMS_DEF) { if (first) first = false; else bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"nr\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"last\":$L}"), - sensor->nr, + sensor->nr, sensor->last_native_data, sensor->last_data, getSensorUnit(sensor), @@ -2228,7 +2229,7 @@ void server_sensor_get(OTF_PARAMS_DEF) { /** * sr * @brief read now and return status and last data - * + * */ void server_sensor_readnow(OTF_PARAMS_DEF) { #if defined(ESP8266) @@ -2266,7 +2267,7 @@ void server_sensor_readnow(OTF_PARAMS_DEF) { if (first) first = false; else bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), - sensor->nr, + sensor->nr, status, sensor->last_native_data, sensor->last_data, @@ -2288,7 +2289,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { if (first) first = false; else bfill.emit_p(PSTR(",")); bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"fac\":$D,\"div\":$D,\"offset\":$D,\"offset2\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L}"), - sensor->nr, + sensor->nr, sensor->type, sensor->group, sensor->name, @@ -2316,7 +2317,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { /** * sl * @brief Lists all sensors - * + * */ void server_sensor_list(OTF_PARAMS_DEF) { #if defined(ESP8266) @@ -2358,7 +2359,7 @@ void server_sensor_list(OTF_PARAMS_DEF) { /** * so * @brief output sensorlog - * + * */ void server_sensorlog_list(OTF_PARAMS_DEF) { #if defined(ESP8266) @@ -2372,7 +2373,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { uint8_t log = LOG_STD; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Log type 0=DAY 1=WEEK 2=MONTH - log = strtoul(tmp_buffer, NULL, 0); + log = strtoul(tmp_buffer, NULL, 0); if (log > LOG_MONTH) log = LOG_STD; ulong log_size = sensorlog_size(log); @@ -2381,11 +2382,11 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { ulong startAt = 0; ulong maxResults = log_size; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("start"), true)) // Log start - startAt = strtoul(tmp_buffer, NULL, 0); + startAt = strtoul(tmp_buffer, NULL, 0); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) // Log Lines count maxResults = strtoul(tmp_buffer, NULL, 0); - + //Filters: uint nr = 0; uint type = 0; @@ -2396,10 +2397,10 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { bool shortcsv = false; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr - nr = strtoul(tmp_buffer, NULL, 0); + nr = strtoul(tmp_buffer, NULL, 0); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) // Filter log for sensor-type - type = strtoul(tmp_buffer, NULL, 0); + type = strtoul(tmp_buffer, NULL, 0); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // Filter time after after = strtoul(tmp_buffer, NULL, 0); @@ -2429,7 +2430,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { #endif if (isjson) { - bfill.emit_p(PSTR("{\"logtype\":$D,\"logsize\":$D,\"filesize\":$D,\"log\":["), + bfill.emit_p(PSTR("{\"logtype\":$D,\"logsize\":$D,\"filesize\":$D,\"log\":["), log, log_size, sensorlog_filesize(log)); } else { if (shortcsv) @@ -2446,7 +2447,9 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { //lastHours: find limit for this if (lastHours > 0 && log_size > 0) { after = os.now_tz() - lastHours * 60 * 60; //seconds - DEBUG_PRINTLN(F("lastHours")); + DEBUG_PRINT(F("lastHours=")); + DEBUG_PRINT(lastHours); + DEBUG_PRINTLN(); ulong a = 0; ulong b = log_size-1; @@ -2464,15 +2467,20 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { } startAt = lastIdx; } - else if (startAt==0 && maxResults > 0 && maxResults != log_size) + if (maxResults > 0 && maxResults < log_size) { - startAt = log_size-maxResults; + DEBUG_PRINT(F("max=")); + DEBUG_PRINT(maxResults); + DEBUG_PRINTLN(); + ulong startAt2 = log_size-maxResults; + if (startAt2 > startAt) + startAt = startAt2; } uint sensor_type = 0; DEBUG_PRINTLN(F("start so")); - ulong idx = startAt; + ulong idx = startAt; while (idx < log_size) { int n = sensorlog_load2(log, idx, BLOCKSIZE, sensorlog); if (n <= 0) break; @@ -2495,7 +2503,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (type && sensor_type != type) continue; } - + if (count > 0 && isjson) { bfill.emit_p(PSTR(",")); } @@ -2547,13 +2555,13 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("finish so")); - handle_return(HTML_OK); + handle_return(HTML_OK); } /** - * sn + * sn * @brief Delete/Clear Sensor log - * + * */ void server_sensorlog_clear(OTF_PARAMS_DEF) { #if defined(ESP8266) @@ -2563,7 +2571,7 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { #endif int log = -1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Filter log for sensor-nr - log = atoi(tmp_buffer); + log = atoi(tmp_buffer); DEBUG_PRINTLN(F("server_sensorlog_clear")); @@ -2653,11 +2661,11 @@ void server_sensorprog_config(OTF_PARAMS_DEF) { void progconfig_json(ProgSensorAdjust_t *p, double current) { bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E, \"current\":$E}"), - p->nr, + p->nr, p->type, p->sensor, p->prog, - p->factor1, + p->factor1, p->factor2, p->min, p->max, @@ -2766,7 +2774,7 @@ const int sensor_types[] = { SENSOR_SMT100_ANALOG_TEMP, SENSOR_VH400, SENSOR_THERM200, - SENSOR_AQUAPLUMB, + SENSOR_AQUAPLUMB, SENSOR_USERDEF, #endif #else @@ -2774,7 +2782,7 @@ const int sensor_types[] = { SENSOR_OSPI_ANALOG_P, SENSOR_OSPI_ANALOG_SMT50_MOIS, SENSOR_OSPI_ANALOG_SMT50_TEMP, -#endif +#endif SENSOR_REMOTE, SENSOR_WEATHER_TEMP_F, SENSOR_WEATHER_TEMP_C, @@ -2856,7 +2864,7 @@ void server_sensor_types(OTF_PARAMS_DEF) { if (i > 0) bfill.emit_p(PSTR(",")); byte unitid = getSensorUnitId(sensor_types[i]); - bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), + bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), sensor_types[i], sensor_names[i], getSensorUnit(unitid), unitid); send_packet(OTF_PARAMS); } @@ -2894,13 +2902,13 @@ void server_usage(OTF_PARAMS_DEF) { boolean ok = LittleFS.info(fsinfo); bfill.emit_p(PSTR("{\"status\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D}"), - ok, - fsinfo.totalBytes, - fsinfo.usedBytes, + ok, + fsinfo.totalBytes, + fsinfo.usedBytes, fsinfo.totalBytes-fsinfo.usedBytes, - fsinfo.blockSize, - fsinfo.pageSize, - fsinfo.maxOpenFiles, + fsinfo.blockSize, + fsinfo.pageSize, + fsinfo.maxOpenFiles, fsinfo.maxPathLength); #endif handle_return(HTML_OK); @@ -2919,21 +2927,21 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("server_sensorprog_calc")); //uint nr or uint prog - + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr double adj = calc_sensor_watering_by_nr(nr); bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); - handle_return(HTML_OK); + handle_return(HTML_OK); } - + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) { uint prog = strtoul(tmp_buffer, NULL, 0); // Adjustment nr double adj = calc_sensor_watering(prog); bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); handle_return(HTML_OK); } - + handle_return(HTML_DATA_MISSING); } @@ -2955,7 +2963,7 @@ const char* prog_names[] = { /** * sh - * List supported adjustment types + * List supported adjustment types */ void server_sensorprog_types(OTF_PARAMS_DEF) { #if defined(ESP8266) @@ -2994,7 +3002,7 @@ void server_sensorprog_types(OTF_PARAMS_DEF) { /** * sx * @brief backup sensor configuration - * + * */ void server_sensorconfig_backup(OTF_PARAMS_DEF) { #if defined(ESP8266) @@ -3027,7 +3035,7 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { /** * sy * @brief restore sensor configuration - * + * */ void server_sensorconfig_restore(OTF_PARAMS_DEF) { #if defined(ESP8266) diff --git a/sensors.cpp b/sensors.cpp index 48697fda0..b4ccfeb65 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1170,15 +1170,14 @@ int read_sensor(Sensor_t *sensor) { if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - DEBUG_PRINT(F("Reading sensor ")); - DEBUG_PRINTLN(sensor->name); - ulong time = os.now_tz(); switch(sensor->type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: case SENSOR_SMT100_MODBUS_RTU_TEMP: + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); sensor->last_read = time; return read_sensor_ip(sensor); @@ -1194,6 +1193,8 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_THERM200: case SENSOR_AQUAPLUMB: case SENSOR_USERDEF: + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); sensor->last_read = time; return read_sensor_adc(sensor); #endif @@ -1219,6 +1220,9 @@ int read_sensor(Sensor_t *sensor) { { GetSensorWeather(); if (current_weather_ok) { + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); + sensor->last_read = time; sensor->last_native_data = 0; sensor->flags.data_ok = true; From 1e835660d8daa1ce7c8b5644ef904eea4f97cd36 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 25 Feb 2024 16:32:23 +0100 Subject: [PATCH 089/281] added https station support --- OpenSprinkler.cpp | 198 ++++++++++++++- OpenSprinkler.h | 41 ++- build.sh | 3 +- defines.h | 13 +- defines.h.bak | 535 --------------------------------------- defines.h~ | 502 ------------------------------------ etherport.cpp | 148 +++++++++++ etherport.h | 23 ++ make.lin302m~ | 34 --- make.lin302~ | 36 --- opensprinkler_server.cpp | 2 +- 11 files changed, 404 insertions(+), 1131 deletions(-) delete mode 100644 defines.h.bak delete mode 100644 defines.h~ delete mode 100644 make.lin302m~ delete mode 100644 make.lin302~ diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 341fc5993..b164ae487 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -501,15 +501,44 @@ byte OpenSprinkler::start_network() { byte OpenSprinkler::start_ether() { #if defined(ESP8266) - if(hw_rev<2) return 0; // ethernet capability is only available after hw_rev 2 + 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 SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); SPI.setFrequency(4000000); - //TODO 3.3 Network interface - { + if(eth.isW5500) { + DEBUG_PRINTLN(F("detect existence of W5500")); + /* this is copied from w5500.cpp wizchip_sw_reset + * perform a software reset and see if we get a correct response + * without this, eth.begin will crash if W5500 is not connected + * ideally wizchip_sw_reset should return a value but since it doesn't + * we have to extract it code here + * */ + static const uint8_t AccessModeRead = (0x00 << 2); + static const uint8_t AccessModeWrite = (0x01 << 2); + static const uint8_t BlockSelectCReg = (0x00 << 3); + pinMode(PIN_ETHER_CS, OUTPUT); + // ==> setMR(MR_RST) + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer((0x00 & 0xFF00) >> 8); + SPI.transfer((0x00 & 0x00FF) >> 0); + SPI.transfer(BlockSelectCReg | AccessModeWrite); + SPI.transfer(0x80); + digitalWrite(PIN_ETHER_CS, HIGH); + + // ==> ret = getMR() + uint8_t ret; + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer((0x00 & 0xFF00) >> 8); + SPI.transfer((0x00 & 0x00FF) >> 0); + SPI.transfer(BlockSelectCReg | AccessModeRead); + ret = SPI.transfer(0); + digitalWrite(PIN_ETHER_CS, HIGH); + if(ret!=0) return 0; // ret is expected to be 0 + } else { /* this is copied from enc28j60.cpp geterevid * check to see if the hardware revision number if expected * */ @@ -538,7 +567,7 @@ byte OpenSprinkler::start_ether() { digitalWrite(PIN_ETHER_CS, HIGH); if(r==0 || r==255) return 0; // r is expected to be a non-255 revision number } - + load_hardware_mac((uint8_t*)tmp_buffer, true); if (iopts[IOPT_USE_DHCP]==0) { // config static IP before calling eth.begin IPAddress staticip(iopts+IOPT_STATIC_IP1); @@ -550,7 +579,8 @@ byte OpenSprinkler::start_ether() { eth.setDefault(); if(!eth.begin((uint8_t*)tmp_buffer)) return 0; 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()+30000; // 30 seconds time out while (!eth.connected()) { DEBUG_PRINT("."); @@ -815,8 +845,12 @@ void OpenSprinkler::begin() { PIN_SENSOR1 = V1_PIN_SENSOR1; PIN_SENSOR2 = V1_PIN_SENSOR2; } else { - // revision 2 - hw_rev = 2; + // revision 2 and 3 + if(detect_i2c(EEPROM_I2CADDR)) { // revision 3 has a I2C EEPROM + hw_rev = 3; + } else { + hw_rev = 2; + } mainio->i2c_write(NXP_CONFIG_REG, V2_IO_CONFIG); mainio->i2c_write(NXP_OUTPUT_REG, V2_IO_OUTPUT); @@ -1092,7 +1126,7 @@ void OpenSprinkler::latch_setzoneoutput_v2(byte sid, byte A, byte K) { * */ void OpenSprinkler::latch_open(byte sid) { - if(hw_rev==2) { + if(hw_rev>=2) { DEBUG_PRINTLN(F("latch_open_v2")); latch_disable_alloutputs_v2(); // disable all output pins latch_boost(); // generate boost voltage @@ -1115,7 +1149,7 @@ void OpenSprinkler::latch_open(byte sid) { } void OpenSprinkler::latch_close(byte sid) { - if(hw_rev==2) { + if(hw_rev>=2) { DEBUG_PRINTLN(F("latch_close_v2")); latch_disable_alloutputs_v2(); // disable all output pins latch_boost(); // generate boost voltage @@ -1282,7 +1316,7 @@ void OpenSprinkler::apply_all_station_bits() { void OpenSprinkler::detect_binarysensor_status(ulong curr_time) { // sensor_type: 0 if normally closed, 1 if normally open if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { - if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + if(hw_rev>=2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 byte val = digitalReadExt(PIN_SENSOR1); status.sensor1 = (val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1; if(status.sensor1) { @@ -1312,7 +1346,7 @@ void OpenSprinkler::detect_binarysensor_status(ulong 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) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 + if(hw_rev>=2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 byte val = digitalReadExt(PIN_SENSOR2); status.sensor2 = (val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1; if(status.sensor2) { @@ -1347,7 +1381,7 @@ byte OpenSprinkler::detect_programswitch_status(ulong curr_time) { byte ret = 0; if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_PSWITCH) { static byte sensor1_hist = 0; - if(hw_rev==2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + if(hw_rev>=2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 status.sensor1 = (digitalReadExt(PIN_SENSOR1) != iopts[IOPT_SENSOR1_OPTION]); // is switch activated? sensor1_hist = (sensor1_hist<<1) | status.sensor1; // basic noise filtering: only trigger if sensor matches pattern: @@ -1359,7 +1393,7 @@ byte OpenSprinkler::detect_programswitch_status(ulong curr_time) { #if defined(ESP8266) || defined(PIN_SENSOR2) if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { static byte sensor2_hist = 0; - if(hw_rev==2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 + if(hw_rev>=2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 status.sensor2 = (digitalReadExt(PIN_SENSOR2) != iopts[IOPT_SENSOR2_OPTION]); // is sensor activated? sensor2_hist = (sensor2_hist<<1) | status.sensor2; if((sensor2_hist&0b1111) == 0b0011) { @@ -1691,6 +1725,10 @@ void OpenSprinkler::switch_special_station(byte sid, byte value, uint16_t dur) { case STN_TYPE_HTTP: switch_httpstation((HTTPStationData *)pdata->sped, value); break; + + case STN_TYPE_HTTPS: + switch_https_station((HTTPStationData *)pdata->sped, value); + break; } } } @@ -1933,6 +1971,113 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* return HTTP_RQT_SUCCESS; } +int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { + +#if defined(ARDUINO) + + Client *client; + #if defined(ESP8266) + WiFiClientSecure wifiClient; + client = &wifiClient; + #else + // Choose the analog pin to get semi-random data from for SSL + // Pick a pin that's not connected or attached to a randomish voltage source + const int rand_pin = A5; + + EthernetClient etherClient; + SSLClient *client = new SSLClient(base_client, TAs, (size_t)TAs_NUM, rand_pin); + + #endif + + #define HTTP_CONNECT_NTRIES 3 + byte tries = 0; + do { + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINT(port); + DEBUG_PRINT("("); + DEBUG_PRINT(tries); + DEBUG_PRINTLN(")"); + + if(client->connect(server, port)==1) break; + tries++; + } while(triesstop(); + return HTTP_RQT_CONNECT_ERR; + } +#else + // + + EthernetClientSsl etherClient; + EthernetClientSsl *client = ðerClient; + struct hostent *host; + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINTLN(port); + host = gethostbyname(server); + if (!host) { return HTTP_RQT_CONNECT_ERR; } + if(!client->connect((uint8_t*)host->h_addr, port)) { + DEBUG_PRINT(F("failed.")); + client->stop(); + return HTTP_RQT_CONNECT_ERR; + } + +#endif + + uint16_t len = strlen(p); + if(len > ETHER_BUFFER_SIZE) len = ETHER_BUFFER_SIZE; + if(client->connected()) { + client->write((uint8_t *)p, len); + } else { + DEBUG_PRINTLN(F("client no longer connected")); + } + memset(ether_buffer, 0, ETHER_BUFFER_SIZE); + uint32_t stoptime = millis()+timeout; + + int pos = 0; +#if defined(ARDUINO) + // 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 + while(true) { + int nbytes = client->available(); + if(nbytes>0) { + if(pos+nbytes>ETHER_BUFFER_SIZE) nbytes=ETHER_BUFFER_SIZE-pos; // cannot read more than buffer size + client->read((uint8_t*)ether_buffer+pos, nbytes); + pos+=nbytes; + } + if(millis()>stoptime) { + DEBUG_PRINTLN(F("host timeout occured")); + //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far + break; + } + if(!client->connected() && !client->available()) { + //DEBUG_PRINTLN(F("host disconnected")); + break; + } + } +#else + while(client->connected()) { + int len=client->read((uint8_t *)ether_buffer+pos, ETHER_BUFFER_SIZE); + if (len<=0) continue; + pos+=len; + if(millis()>stoptime) { + DEBUG_PRINTLN(F("host timeout occured")); + //return HTTP_RQT_TIMEOUT; // instead of returning with timeout, we'll work with data received so far + break; + } + } +#endif + ether_buffer[pos]=0; // properly end buffer with 0 + client->stop(); + if(strlen(ether_buffer)==0) return HTTP_RQT_EMPTY_RETURN; + if(callback) callback(ether_buffer); + return HTTP_RQT_SUCCESS; +} + int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { char server[20]; byte ip[4]; @@ -2023,6 +2168,31 @@ void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon) { send_http_request(server, atoi(port), p, remote_http_callback); } +/** Switch https station + * This function takes an https station code, + * parses it into a server name and two HTTPS GET requests. + */ +void OpenSprinkler::switch_https_station(HTTPStationData *data, bool turnon) { + + HTTPStationData copy; + // make a copy of the HTTP station data and work with it + memcpy((char*)©, (char*)data, sizeof(HTTPStationData)); + char * server = strtok((char *)copy.data, ","); + char * port = strtok(NULL, ","); + char * on_cmd = strtok(NULL, ","); + char * off_cmd = strtok(NULL, ","); + char * cmd = turnon ? on_cmd : off_cmd; + + char *p = tmp_buffer; + BufferFiller bf = p; + + if(cmd==NULL || server==NULL) return; // proceed only if cmd and server are valid + + bf.emit_p(PSTR("GET /$S HTTP/1.0\r\nHOST: $S\r\n\r\n"), cmd, server); + + send_https_request(server, atoi(port), p, remote_http_callback); +} + /** Prepare factory reset */ void OpenSprinkler::pre_factory_reset() { // for ESP8266: wipe out flash @@ -2920,4 +3090,4 @@ void OpenSprinkler::detect_expanders() { } } } -#endif \ No newline at end of file +#endif diff --git a/OpenSprinkler.h b/OpenSprinkler.h index d92624f13..d93435528 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -41,10 +41,12 @@ #include #include #include + #include #include #include #include #include + #include #include "SSD1306Display.h" #include "espconnect.h" #else // for AVR @@ -66,8 +68,36 @@ #if defined(ESP8266) extern ESP8266WebServer *update_server; extern OTF::OpenThingsFramework *otf; - extern bool otf_callbacksInitialised; - extern ENC28J60lwIP eth; + extern ENC28J60lwIP enc28j60; + extern Wiznet5500lwIP w5500; + struct lwipEth { + bool isW5500 = false; + inline boolean config(const IPAddress& local_ip, const IPAddress& arg1, const IPAddress& arg2, const IPAddress& arg3 = IPADDR_NONE, const IPAddress& dns2 = IPADDR_NONE) { + return (isW5500)?w5500.config(local_ip, arg1, arg2, arg3, dns2) : enc28j60.config(local_ip, arg1, arg2, arg3, dns2); + } + inline boolean begin(const uint8_t *macAddress = nullptr) { + return (isW5500)?w5500.begin(macAddress):enc28j60.begin(macAddress); + } + inline IPAddress localIP() { + return (isW5500)?w5500.localIP():enc28j60.localIP(); + } + inline IPAddress subnetMask() { + return (isW5500)?w5500.subnetMask():enc28j60.subnetMask(); + } + inline IPAddress gatewayIP() { + return (isW5500)?w5500.gatewayIP():enc28j60.gatewayIP(); + } + inline void setDefault() { + (isW5500)?w5500.setDefault():enc28j60.setDefault(); + } + inline bool connected() { + return (isW5500)?w5500.connected():enc28j60.connected(); + } + inline wl_status_t status() { + return (isW5500)?w5500.status():enc28j60.status(); + } + }; + extern lwipEth eth; #else extern EthernetServer *m_server; #endif @@ -257,6 +287,8 @@ class OpenSprinkler { static void switch_gpiostation(GPIOStationData *data, bool turnon); // switch gpio station static void switch_httpstation(HTTPStationData *data, bool turnon); // switch http station + static void switch_https_station(HTTPStationData *data, bool turnon); // switch https station + // -- options and data storeage static void nvdata_load(); static void nvdata_save(); @@ -295,6 +327,11 @@ class OpenSprinkler { static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + + static int8_t send_https_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + static int8_t send_https_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + static int8_t send_https_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + // -- LCD functions #if defined(ARDUINO) // LCD functions for Arduino #if defined(ESP8266) diff --git a/build.sh b/build.sh index 1903c5273..fccc58ad2 100755 --- a/build.sh +++ b/build.sh @@ -26,13 +26,14 @@ else apt-get install -y libmosquitto-dev apt-get install -y raspi-gpio apt-get install -y libi2c-dev + apt-get install -y libssl-dev if ! command -v raspi-gpio &> /dev/null then echo "Command raspi-gpio is required and is not installed" exit 0 fi echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -li2c -lpthread -lmosquitto + g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -li2c -lpthread -lmosquitto -lcrypto -lssl fi if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then diff --git a/defines.h b/defines.h index a089ad1bc..2e233ba0e 100644 --- a/defines.h +++ b/defines.h @@ -25,7 +25,6 @@ #define _DEFINES_H //#define ENABLE_DEBUG // enable serial debug -//#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -33,11 +32,11 @@ typedef unsigned long ulong; #define TMP_BUFFER_SIZE 255 // scratch buffer size /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 230 // Firmware version: 220 means 2.2.0 +#define OS_FW_VERSION 220 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 139 // Firmware minor version +#define OS_FW_MINOR 3 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -65,6 +64,7 @@ typedef unsigned long ulong; #define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station #define STN_TYPE_GPIO 0x03 // direct GPIO station #define STN_TYPE_HTTP 0x04 // HTTP station +#define STN_TYPE_HTTPS 0x05 // HTTPS station #define STN_TYPE_OTHER 0xFF /** Notification macro defines */ @@ -140,13 +140,13 @@ typedef unsigned long ulong; /** Default string option values */ #define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "49.484018,8.475593" // Mannheim,Germany -#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinklershop.de/js" +#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA +#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" #define DEFAULT_WEATHER_URL "weather.opensprinkler.com" #define DEFAULT_IFTTT_URL "maker.ifttt.com" #define DEFAULT_OTC_SERVER "ws.cloud.openthings.io" #define DEFAULT_OTC_PORT 80 -#define DEFAULT_DEVICE_NAME "Mein OpenSprinkler" +#define DEFAULT_DEVICE_NAME "My OpenSprinkler" #define DEFAULT_EMPTY_STRING "" /* Weather Adjustment Methods */ @@ -333,6 +333,7 @@ enum { #define LADR_I2CADDR 0x23 // latch driver I2C address #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address + #define EEPROM_I2CADDR 0x50 // 24C02 EEPROM I2C address #define PIN_CURR_SENSE A0 #define PIN_FREE_LIST {} // no free GPIO pin at the moment diff --git a/defines.h.bak b/defines.h.bak deleted file mode 100644 index bcdbfc44f..000000000 --- a/defines.h.bak +++ /dev/null @@ -1,535 +0,0 @@ -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * OpenSprinkler macro defines and hardware pin assignments - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#ifndef _DEFINES_H -#define _DEFINES_H - -//#define ENABLE_DEBUG // enable serial debug - -typedef unsigned char byte; -typedef unsigned long ulong; - -#define TMP_BUFFER_SIZE 255 // scratch buffer size - -/** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 230 // Firmware version: 220 means 2.2.0 - // if this number is different from the one stored in non-volatile memory - // a device reset will be automatically triggered - -#define OS_FW_MINOR 1 // Firmware minor version - -/** Hardware version base numbers */ -#define OS_HW_VERSION_BASE 0x00 // OpenSprinkler -#define OSPI_HW_VERSION_BASE 0x40 // OpenSprinkler Pi -#define OSBO_HW_VERSION_BASE 0x80 // OpenSprinkler Beagle -#define SIM_HW_VERSION_BASE 0xC0 // simulation hardware - -/** Hardware type macro defines */ -#define HW_TYPE_AC 0xAC // standard 24VAC for 24VAC solenoids only, with triacs -#define HW_TYPE_DC 0xDC // DC powered, for both DC and 24VAC solenoids, with boost converter and MOSFETs -#define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges -#define HW_TYPE_UNKNOWN 0xFF - -/** Data file names */ -#define IOPTS_FILENAME "iopts.dat" // integer options data file -#define SOPTS_FILENAME "sopts.dat" // string options data file -#define STATIONS_FILENAME "stns.dat" // stations data file -#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 SENSOR_FILENAME "sensor.dat" // analog sensor filename -#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename -#define SENSORLOG_FILENAME "sensorlog.dat" // analog sensor log filename - -/** Station macro defines */ -#define STN_TYPE_STANDARD 0x00 // standard solenoid station -#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station -#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station -#define STN_TYPE_GPIO 0x03 // direct GPIO station -#define STN_TYPE_HTTP 0x04 // HTTP station -#define STN_TYPE_OTHER 0xFF - -/** Notification macro defines */ -#define NOTIFY_PROGRAM_SCHED 0x0001 -#define NOTIFY_SENSOR1 0x0002 -#define NOTIFY_FLOWSENSOR 0x0004 -#define NOTIFY_WEATHER_UPDATE 0x0008 -#define NOTIFY_REBOOT 0x0010 -#define NOTIFY_STATION_OFF 0x0020 -#define NOTIFY_SENSOR2 0x0040 -#define NOTIFY_RAINDELAY 0x0080 -#define NOTIFY_STATION_ON 0x0100 - -/** HTTP request macro defines */ -#define HTTP_RQT_SUCCESS 0 -#define HTTP_RQT_NOT_RECEIVED 1 -#define HTTP_RQT_CONNECT_ERR 2 -#define HTTP_RQT_TIMEOUT 3 -#define HTTP_RQT_EMPTY_RETURN 4 - -/** Sensor macro defines */ -#define SENSOR_TYPE_NONE 0x00 -#define SENSOR_TYPE_RAIN 0x01 // rain sensor -#define SENSOR_TYPE_FLOW 0x02 // flow sensor -#define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor -#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor -#define SENSOR_TYPE_OTHER 0xFF - -#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds - -/** Reboot cause */ -#define REBOOT_CAUSE_NONE 0 -#define REBOOT_CAUSE_RESET 1 -#define REBOOT_CAUSE_BUTTON 2 -#define REBOOT_CAUSE_RSTAP 3 -#define REBOOT_CAUSE_TIMER 4 -#define REBOOT_CAUSE_WEB 5 -#define REBOOT_CAUSE_WIFIDONE 6 -#define REBOOT_CAUSE_FWUPDATE 7 -#define REBOOT_CAUSE_WEATHER_FAIL 8 -#define REBOOT_CAUSE_NETWORK_FAIL 9 -#define REBOOT_CAUSE_NTP 10 -#define REBOOT_CAUSE_PROGRAM 11 -#define REBOOT_CAUSE_POWERON 99 - - -/** WiFi defines */ -#define WIFI_MODE_AP 0xA9 -#define WIFI_MODE_STA 0x2A - -#define OS_STATE_INITIAL 0 -#define OS_STATE_CONNECTING 1 -#define OS_STATE_CONNECTED 2 -#define OS_STATE_TRY_CONNECT 3 -#define OS_STATE_WAIT_REBOOT 4 - -#define LED_FAST_BLINK 100 -#define LED_SLOW_BLINK 500 - -/** Storage / zone expander defines */ -#if defined(ARDUINO) - #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 -#endif - -#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders -#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations -#define STATION_NAME_SIZE 32 // maximum number of characters in each station name -#define MAX_SOPTS_SIZE 160 // maximum string option size - -#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) - -/** Default string option values */ -#define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA -#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" -#define DEFAULT_WEATHER_URL "weather.opensprinkler.com" -#define DEFAULT_IFTTT_URL "maker.ifttt.com" -#define DEFAULT_OTC_SERVER "ws.cloud.openthings.io" -#define DEFAULT_OTC_PORT 80 -#define DEFAULT_DEVICE_NAME "My OpenSprinkler" -#define DEFAULT_EMPTY_STRING "" - -/* Weather Adjustment Methods */ -enum { - WEATHER_METHOD_MANUAL = 0, - WEATHER_METHOD_ZIMMERMAN, - WEATHER_METHOD_AUTORAINDELY, - WEATHER_METHOD_ETO, - WEATHER_METHOD_MONTHLY, - NUM_WEATHER_METHODS -}; - -/* Master */ -enum { - MASTER_1 = 0, - MASTER_2, - NUM_MASTER_ZONES, -}; - -enum { - MASOPT_SID = 0, - MASOPT_ON_ADJ, - MASOPT_OFF_ADJ, - NUM_MASTER_OPTS, -}; - -// Sequential Groups -#define NUM_SEQ_GROUPS 4 -#define PARALLEL_GROUP_ID 255 - -/** Macro define of each option - * Refer to OpenSprinkler.cpp for details on each option - */ -enum { - IOPT_FW_VERSION=0,// read-only (ro) - IOPT_TIMEZONE, - IOPT_USE_NTP, - IOPT_USE_DHCP, - IOPT_STATIC_IP1, - IOPT_STATIC_IP2, - IOPT_STATIC_IP3, - IOPT_STATIC_IP4, - IOPT_GATEWAY_IP1, - IOPT_GATEWAY_IP2, - IOPT_GATEWAY_IP3, - IOPT_GATEWAY_IP4, - IOPT_HTTPPORT_0, - IOPT_HTTPPORT_1, - IOPT_HW_VERSION, //ro - IOPT_EXT_BOARDS, - IOPT_SEQUENTIAL_RETIRED, //ro - IOPT_STATION_DELAY_TIME, - IOPT_MASTER_STATION, - IOPT_MASTER_ON_ADJ, - IOPT_MASTER_OFF_ADJ, - IOPT_URS_RETIRED, // ro - IOPT_RSO_RETIRED, // ro - IOPT_WATER_PERCENTAGE, - IOPT_DEVICE_ENABLE, // editable through jc - IOPT_IGNORE_PASSWORD, - IOPT_DEVICE_ID, - IOPT_LCD_CONTRAST, - IOPT_LCD_BACKLIGHT, - IOPT_LCD_DIMMING, - IOPT_BOOST_TIME, - IOPT_USE_WEATHER, - IOPT_NTP_IP1, - IOPT_NTP_IP2, - IOPT_NTP_IP3, - IOPT_NTP_IP4, - IOPT_ENABLE_LOGGING, - IOPT_MASTER_STATION_2, - IOPT_MASTER_ON_ADJ_2, - IOPT_MASTER_OFF_ADJ_2, - IOPT_FW_MINOR, //ro - IOPT_PULSE_RATE_0, - IOPT_PULSE_RATE_1, - IOPT_REMOTE_EXT_MODE, // editable through jc - IOPT_DNS_IP1, - IOPT_DNS_IP2, - IOPT_DNS_IP3, - IOPT_DNS_IP4, - IOPT_SPE_AUTO_REFRESH, - IOPT_IFTTT_ENABLE, - IOPT_SENSOR1_TYPE, - IOPT_SENSOR1_OPTION, - IOPT_SENSOR2_TYPE, - IOPT_SENSOR2_OPTION, - IOPT_SENSOR1_ON_DELAY, - IOPT_SENSOR1_OFF_DELAY, - IOPT_SENSOR2_ON_DELAY, - IOPT_SENSOR2_OFF_DELAY, - IOPT_SUBNET_MASK1, - IOPT_SUBNET_MASK2, - IOPT_SUBNET_MASK3, - IOPT_SUBNET_MASK4, - IOPT_WIFI_MODE, //ro - IOPT_RESET, //ro - NUM_IOPTS // total number of integer options -}; - -enum { - SOPT_PASSWORD=0, - SOPT_LOCATION, - SOPT_JAVASCRIPTURL, - SOPT_WEATHERURL, - SOPT_WEATHER_OPTS, - SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT - SOPT_STA_SSID, - SOPT_STA_PASS, - SOPT_MQTT_OPTS, - SOPT_OTC_OPTS, - SOPT_DEVICE_NAME, - SOPT_STA_BSSID_CHL, // wifi extra info: bssid and channel - NUM_SOPTS // total number of string options -}; - -/** Log Data Type */ -#define LOGDATA_STATION 0x00 -#define LOGDATA_SENSOR1 0x01 -#define LOGDATA_RAINDELAY 0x02 -#define LOGDATA_WATERLEVEL 0x03 -#define LOGDATA_FLOWSENSE 0x04 -#define LOGDATA_SENSOR2 0x05 -#define LOGDATA_CURRENT 0x80 - -#undef OS_HW_VERSION - -/** Hardware defines */ -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // 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 - -#elif defined(ESP8266) // for ESP8266 - - #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) - #define IOEXP_PIN 0x80 // base for pins on main IO expander - #define MAIN_I2CADDR 0x20 // main IO expander I2C address - #define ACDR_I2CADDR 0x21 // ac driver I2C address - #define DCDR_I2CADDR 0x22 // dc driver I2C address - #define LADR_I2CADDR 0x23 // latch driver I2C address - #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address - #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address - - #define PIN_CURR_SENSE A0 - #define PIN_FREE_LIST {} // no free GPIO pin at the moment - #define ETHER_BUFFER_SIZE 2048 - - #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above - - /* To accommodate different OS30 versions, we use software defines pins */ - extern byte PIN_BUTTON_1; - extern byte PIN_BUTTON_2; - extern byte PIN_BUTTON_3; - extern byte PIN_RFRX; - extern byte PIN_RFTX; - extern byte PIN_BOOST; - extern byte PIN_BOOST_EN; - extern byte PIN_LATCH_COM; - extern byte PIN_LATCH_COMA; - extern byte PIN_LATCH_COMK; - extern byte PIN_SENSOR1; - extern byte PIN_SENSOR2; - extern byte PIN_IOEXP_INT; - - /* Original OS30 pin defines */ - //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask - // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i - #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 - #define V0_PIN_BUTTON_2 0 // button 2 - #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 - #define V0_PIN_RFRX 14 - #define V0_PIN_PWR_RX IOEXP_PIN+0 - #define V0_PIN_RFTX 16 - #define V0_PIN_PWR_TX IOEXP_PIN+2 - #define V0_PIN_BOOST IOEXP_PIN+6 - #define V0_PIN_BOOST_EN IOEXP_PIN+7 - #define V0_PIN_SENSOR1 12 // sensor 1 - #define V0_PIN_SENSOR2 13 // sensor 2 - - /* OS31 pin defines */ - // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V1_IO_CONFIG 0x1F00 // config bits - #define V1_IO_OUTPUT 0x1F00 // output bits - #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 - #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 - #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 - #define V1_PIN_RFRX 14 - #define V1_PIN_RFTX 16 - #define V1_PIN_IOEXP_INT 12 - #define V1_PIN_BOOST IOEXP_PIN+13 - #define V1_PIN_BOOST_EN IOEXP_PIN+14 - #define V1_PIN_LATCH_COM IOEXP_PIN+15 - #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 - #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 - - /* OS32 pin defines */ - // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V2_IO_CONFIG 0x1000 // config bits - #define V2_IO_OUTPUT 0x1E00 // output bits - #define V2_PIN_BUTTON_1 2 // button 1 - #define V2_PIN_BUTTON_2 0 // button 2 - #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 - #define V2_PIN_RFTX 15 - #define V2_PIN_BOOST IOEXP_PIN+13 - #define V2_PIN_BOOST_EN IOEXP_PIN+14 - #define V2_PIN_LATCH_COMA IOEXP_PIN+8 // latch COM+ (anode) - #define V2_PIN_SRLAT IOEXP_PIN+9 // shift register latch - #define V2_PIN_SRCLK IOEXP_PIN+10 // shift register clock - #define V2_PIN_SRDAT IOEXP_PIN+11 // shift register data - #define V2_PIN_LATCH_COMK IOEXP_PIN+15 // latch COM- (cathode) - #define V2_PIN_SENSOR1 3 // sensor 1 - #define V2_PIN_SENSOR2 10 // sensor 2 - -#elif defined(OSPI) // for OSPi - - #define OS_HW_VERSION OSPI_HW_VERSION_BASE - #define PIN_SR_LATCH 22 // shift register latch pin - #define PIN_SR_DATA 27 // shift register data pin - #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) - #define PIN_SR_CLOCK 4 // shift register clock pin - #define PIN_SR_OE 17 // shift register output enable pin - #define PIN_SENSOR1 14 - #define PIN_SENSOR2 23 - #define PIN_RFTX 15 // RF transmitter pin - //#define PIN_BUTTON_1 23 // button 1 - //#define PIN_BUTTON_2 24 // button 2 - //#define PIN_BUTTON_3 25 // button 3 - - #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins - #define ETHER_BUFFER_SIZE 16384 - -#elif defined(OSBO) // for OSBo - - #define OS_HW_VERSION OSBO_HW_VERSION_BASE - // these are gpio pin numbers, refer to - // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp - #define PIN_SR_LATCH 60 // P9_12, shift register latch pin - #define PIN_SR_DATA 30 // P9_11, shift register data pin - #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin - #define PIN_SR_OE 50 // P9_14, shift register output enable pin - #define PIN_SENSOR1 48 - #define PIN_RFTX 51 // RF transmitter pin - - #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} - #define ETHER_BUFFER_SIZE 16384 - -#else // for demo / simulation - // use fake hardware pins - #if defined(DEMO) - #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware - #else - #define OS_HW_VERSION SIM_HW_VERSION_BASE - #endif - #define PIN_SR_LATCH 0 - #define PIN_SR_DATA 0 - #define PIN_SR_CLOCK 0 - #define PIN_SR_OE 0 - #define PIN_SENSOR1 0 - #define PIN_SENSOR2 0 - #define PIN_RFTX 0 - #define PIN_FREE_LIST {} - #define ETHER_BUFFER_SIZE 16384 -#endif - -#if defined(ENABLE_DEBUG) /** Serial debug functions */ - - #if defined(ARDUINO) - #define DEBUG_BEGIN(x) {Serial.begin(x);} - #define DEBUG_PRINT(x) {Serial.print(x);} - #define DEBUG_PRINTLN(x) {Serial.println(x);} - #else - #include - #define DEBUG_BEGIN(x) {} /** Serial debug functions */ - inline void DEBUG_PRINT(int x) {printf("%d", x);} - inline void DEBUG_PRINT(const char*s) {printf("%s", s);} - #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} - #endif - -#else - - #define DEBUG_BEGIN(x) {} - #define DEBUG_PRINT(x) {} - #define DEBUG_PRINTLN(x) {} - -#endif - -/** Re-define avr-specific (e.g. PGM) types to use standard types */ -#if !defined(ARDUINO) - #include - #include - #include - #include - inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} - inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} - #define now() time(0) - #define pgm_read_byte(x) *(x) - #define PSTR(x) x - #define F(x) x - #define strcat_P strcat - #define strcpy_P strcpy - #define sprintf_P sprintf - #include - #define String string - using namespace std; - #define PROGMEM - typedef const char* PGM_P; - typedef unsigned char uint8_t; - typedef short int16_t; - typedef unsigned short uint16_t; - typedef bool boolean; - #define pinModeExt pinMode - #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite -#endif - -/** Other defines */ -// button values -#define BUTTON_1 0x01 -#define BUTTON_2 0x02 -#define BUTTON_3 0x04 - -// button status values -#define BUTTON_NONE 0x00 // no button pressed -#define BUTTON_MASK 0x0F // button status mask -#define BUTTON_FLAG_HOLD 0x80 // long hold flag -#define BUTTON_FLAG_DOWN 0x40 // down flag -#define BUTTON_FLAG_UP 0x20 // up flag - -// button timing values -#define BUTTON_DELAY_MS 1 // short delay (milliseconds) -#define BUTTON_HOLD_MS 1000 // long hold expiration time (milliseconds) - -// button mode values -#define BUTTON_WAIT_NONE 0 // do not wait, return value immediately -#define BUTTON_WAIT_RELEASE 1 // wait until button is release -#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/defines.h~ b/defines.h~ deleted file mode 100644 index fac96058e..000000000 --- a/defines.h~ +++ /dev/null @@ -1,502 +0,0 @@ -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * OpenSprinkler macro defines and hardware pin assignments - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#ifndef _DEFINES_H -#define _DEFINES_H - -#define ENABLE_DEBUG // enable serial debug - -typedef unsigned char byte; -typedef unsigned long ulong; - -#define TMP_BUFFER_SIZE 255 // scratch buffer size - -/** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 220 // Firmware version: 220 means 2.2.0 - // if this number is different from the one stored in non-volatile memory - // a device reset will be automatically triggered - -#define OS_FW_MINOR 109 // Firmware minor version - -/** Hardware version base numbers */ -#define OS_HW_VERSION_BASE 0x00 -#define OSPI_HW_VERSION_BASE 0x40 -#define OSBO_HW_VERSION_BASE 0x80 -#define SIM_HW_VERSION_BASE 0xC0 - -/** Hardware type macro defines */ -#define HW_TYPE_AC 0xAC // standard 24VAC for 24VAC solenoids only, with triacs -#define HW_TYPE_DC 0xDC // DC powered, for both DC and 24VAC solenoids, with boost converter and MOSFETs -#define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges -#define HW_TYPE_UNKNOWN 0xFF - -/** Data file names */ -#define IOPTS_FILENAME "iopts.dat" // integer options data file -#define SOPTS_FILENAME "sopts.dat" // string options data file -#define STATIONS_FILENAME "stns.dat" // stations data file -#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 - -/** Station macro defines */ -#define STN_TYPE_STANDARD 0x00 -#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station -#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station -#define STN_TYPE_GPIO 0x03 // direct GPIO station -#define STN_TYPE_HTTP 0x04 // HTTP station -#define STN_TYPE_OTHER 0xFF - -/** Notification macro defines */ -#define NOTIFY_PROGRAM_SCHED 0x0001 -#define NOTIFY_SENSOR1 0x0002 -#define NOTIFY_FLOWSENSOR 0x0004 -#define NOTIFY_WEATHER_UPDATE 0x0008 -#define NOTIFY_REBOOT 0x0010 -#define NOTIFY_STATION_OFF 0x0020 -#define NOTIFY_SENSOR2 0x0040 -#define NOTIFY_RAINDELAY 0x0080 -#define NOTIFY_STATION_ON 0x0100 - -/** HTTP request macro defines */ -#define HTTP_RQT_SUCCESS 0 -#define HTTP_RQT_NOT_RECEIVED -1 -#define HTTP_RQT_CONNECT_ERR -2 -#define HTTP_RQT_TIMEOUT -3 -#define HTTP_RQT_EMPTY_RETURN -4 -#define HTTP_RQT_DNS_ERROR -5 - -/** Sensor macro defines */ -#define SENSOR_TYPE_NONE 0x00 -#define SENSOR_TYPE_RAIN 0x01 // rain sensor -#define SENSOR_TYPE_FLOW 0x02 // flow sensor -#define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor -#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor -#define SENSOR_TYPE_OTHER 0xFF - -#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds - -/** Reboot cause */ -#define REBOOT_CAUSE_NONE 0 -#define REBOOT_CAUSE_RESET 1 -#define REBOOT_CAUSE_BUTTON 2 -#define REBOOT_CAUSE_RSTAP 3 -#define REBOOT_CAUSE_TIMER 4 -#define REBOOT_CAUSE_WEB 5 -#define REBOOT_CAUSE_WIFIDONE 6 -#define REBOOT_CAUSE_FWUPDATE 7 -#define REBOOT_CAUSE_WEATHER_FAIL 8 -#define REBOOT_CAUSE_NETWORK_FAIL 9 -#define REBOOT_CAUSE_NTP 10 -#define REBOOT_CAUSE_PROGRAM 11 -#define REBOOT_CAUSE_POWERON 99 - -/** Too much current */ -#define MAX_CURRENT 3010 //Max mA - -/** WiFi defines */ -#define WIFI_MODE_AP 0xA9 -#define WIFI_MODE_STA 0x2A - -#define OS_STATE_INITIAL 0 -#define OS_STATE_CONNECTING 1 -#define OS_STATE_CONNECTED 2 -#define OS_STATE_TRY_CONNECT 3 - -#define LED_FAST_BLINK 100 -#define LED_SLOW_BLINK 500 - -/** Storage / zone expander defines */ -#if defined(ARDUINO) - #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 -#endif - -#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders -#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations -#define STATION_NAME_SIZE 32 // maximum number of characters in each station name -#define MAX_SOPTS_SIZE 160 // maximum string option size - -#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) - -/** Default string option values */ -#define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA -#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" -#define DEFAULT_WEATHER_URL "weather.opensprinkler.com" -#define DEFAULT_IFTTT_URL "maker.ifttt.com" -#define DEFAULT_EMPTY_STRING "" - -/** Macro define of each option - * Refer to OpenSprinkler.cpp for details on each option - */ -enum { - IOPT_FW_VERSION=0,// read-only (ro) - IOPT_TIMEZONE, - IOPT_USE_NTP, - IOPT_USE_DHCP, - IOPT_STATIC_IP1, - IOPT_STATIC_IP2, - IOPT_STATIC_IP3, - IOPT_STATIC_IP4, - IOPT_GATEWAY_IP1, - IOPT_GATEWAY_IP2, - IOPT_GATEWAY_IP3, - IOPT_GATEWAY_IP4, - IOPT_HTTPPORT_0, - IOPT_HTTPPORT_1, - IOPT_HW_VERSION, //ro - IOPT_EXT_BOARDS, - IOPT_SEQUENTIAL_RETIRED, //ro - IOPT_STATION_DELAY_TIME, - IOPT_MASTER_STATION, - IOPT_MASTER_ON_ADJ, - IOPT_MASTER_OFF_ADJ, - IOPT_URS_RETIRED, // ro - IOPT_RSO_RETIRED, // ro - IOPT_WATER_PERCENTAGE, - IOPT_DEVICE_ENABLE, // editable through jc - IOPT_IGNORE_PASSWORD, - IOPT_DEVICE_ID, - IOPT_LCD_CONTRAST, - IOPT_LCD_BACKLIGHT, - IOPT_LCD_DIMMING, - IOPT_BOOST_TIME, - IOPT_USE_WEATHER, - IOPT_NTP_IP1, - IOPT_NTP_IP2, - IOPT_NTP_IP3, - IOPT_NTP_IP4, - IOPT_ENABLE_LOGGING, - IOPT_MASTER_STATION_2, - IOPT_MASTER_ON_ADJ_2, - IOPT_MASTER_OFF_ADJ_2, - IOPT_FW_MINOR, //ro - IOPT_PULSE_RATE_0, - IOPT_PULSE_RATE_1, - IOPT_REMOTE_EXT_MODE, // editable through jc - IOPT_DNS_IP1, - IOPT_DNS_IP2, - IOPT_DNS_IP3, - IOPT_DNS_IP4, - IOPT_SPE_AUTO_REFRESH, - IOPT_IFTTT_ENABLE, - IOPT_SENSOR1_TYPE, - IOPT_SENSOR1_OPTION, - IOPT_SENSOR2_TYPE, - IOPT_SENSOR2_OPTION, - IOPT_SENSOR1_ON_DELAY, - IOPT_SENSOR1_OFF_DELAY, - IOPT_SENSOR2_ON_DELAY, - IOPT_SENSOR2_OFF_DELAY, - IOPT_SUBNET_MASK1, - IOPT_SUBNET_MASK2, - IOPT_SUBNET_MASK3, - IOPT_SUBNET_MASK4, - IOPT_WIFI_MODE, //ro - IOPT_RESET, //ro - NUM_IOPTS // total number of integer options -}; - -enum { - SOPT_PASSWORD=0, - SOPT_LOCATION, - SOPT_JAVASCRIPTURL, - SOPT_WEATHERURL, - SOPT_WEATHER_OPTS, - SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT - SOPT_STA_SSID, - SOPT_STA_PASS, - SOPT_MQTT_OPTS, - //SOPT_WEATHER_KEY, - //SOPT_AP_PASS, - NUM_SOPTS // total number of string options -}; - -/** Log Data Type */ -#define LOGDATA_STATION 0x00 -#define LOGDATA_SENSOR1 0x01 -#define LOGDATA_RAINDELAY 0x02 -#define LOGDATA_WATERLEVEL 0x03 -#define LOGDATA_FLOWSENSE 0x04 -#define LOGDATA_SENSOR2 0x05 -#define LOGDATA_CURRENT 0x80 - -#undef OS_HW_VERSION - -/** Hardware defines */ -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // 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 - -#elif defined(ESP8266) // for ESP8266 - - #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) - #define IOEXP_PIN 0x80 // base for pins on main IO expander - #define MAIN_I2CADDR 0x20 // main IO expander I2C address - #define ACDR_I2CADDR 0x21 // ac driver I2C address - #define DCDR_I2CADDR 0x22 // dc driver I2C address - #define LADR_I2CADDR 0x23 // latch driver I2C address - #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address - #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address - - #define PIN_CURR_SENSE A0 - #define PIN_FREE_LIST {} // no free GPIO pin at the moment - #define ETHER_BUFFER_SIZE 2048 - - #define PIN_ETHER_CS 16 // ENC28J60 CS (chip select pin) is 16 on OS 3.2. - - /* To accommodate different OS30 versions, we use software defines pins */ - extern byte PIN_BUTTON_1; - extern byte PIN_BUTTON_2; - extern byte PIN_BUTTON_3; - extern byte PIN_RFRX; - extern byte PIN_RFTX; - extern byte PIN_BOOST; - extern byte PIN_BOOST_EN; - extern byte PIN_LATCH_COM; - extern byte PIN_LATCH_COMA; - extern byte PIN_LATCH_COMK; - extern byte PIN_SENSOR1; - extern byte PIN_SENSOR2; - extern byte PIN_IOEXP_INT; - - /* Original OS30 pin defines */ - //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask - // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i - #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 - #define V0_PIN_BUTTON_2 0 // button 2 - #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 - #define V0_PIN_RFRX 14 - #define V0_PIN_PWR_RX IOEXP_PIN+0 - #define V0_PIN_RFTX 16 - #define V0_PIN_PWR_TX IOEXP_PIN+2 - #define V0_PIN_BOOST IOEXP_PIN+6 - #define V0_PIN_BOOST_EN IOEXP_PIN+7 - #define V0_PIN_SENSOR1 12 // sensor 1 - #define V0_PIN_SENSOR2 13 // sensor 2 - - /* OS30 revision 1 pin defines */ - // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V1_IO_CONFIG 0x1F00 // config bits - #define V1_IO_OUTPUT 0x1F00 // output bits - #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 - #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 - #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 - #define V1_PIN_RFRX 14 - #define V1_PIN_RFTX 16 - #define V1_PIN_IOEXP_INT 12 - #define V1_PIN_BOOST IOEXP_PIN+13 - #define V1_PIN_BOOST_EN IOEXP_PIN+14 - #define V1_PIN_LATCH_COM IOEXP_PIN+15 - #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 - #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 - - /* OS30 revision 2 pin defines */ - // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i - #define V2_IO_CONFIG 0x1000 // config bits - #define V2_IO_OUTPUT 0x1E00 // output bits - #define V2_PIN_BUTTON_1 2 // button 1 - #define V2_PIN_BUTTON_2 0 // button 2 - #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 - #define V2_PIN_RFTX 15 - #define V2_PIN_BOOST IOEXP_PIN+13 - #define V2_PIN_BOOST_EN IOEXP_PIN+14 - #define V2_PIN_LATCH_COMA IOEXP_PIN+8 // latch COM+ (anode) - #define V2_PIN_SRLAT IOEXP_PIN+9 // shift register latch - #define V2_PIN_SRCLK IOEXP_PIN+10 // shift register clock - #define V2_PIN_SRDAT IOEXP_PIN+11 // shift register data - #define V2_PIN_LATCH_COMK IOEXP_PIN+15 // latch COM- (cathode) - #define V2_PIN_SENSOR1 3 // sensor 1 - #define V2_PIN_SENSOR2 10 // sensor 2 - -#elif defined(OSPI) // for OSPi - - #define OS_HW_VERSION OSPI_HW_VERSION_BASE - #define PIN_SR_LATCH 22 // shift register latch pin - #define PIN_SR_DATA 27 // shift register data pin - #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) - #define PIN_SR_CLOCK 4 // shift register clock pin - #define PIN_SR_OE 17 // shift register output enable pin - #define PIN_SENSOR1 14 - #define PIN_SENSOR2 23 - #define PIN_RFTX 15 // RF transmitter pin - //#define PIN_BUTTON_1 23 // button 1 - //#define PIN_BUTTON_2 24 // button 2 - //#define PIN_BUTTON_3 25 // button 3 - - #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins - #define ETHER_BUFFER_SIZE 16384 - -#elif defined(OSBO) // for OSBo - - #define OS_HW_VERSION OSBO_HW_VERSION_BASE - // these are gpio pin numbers, refer to - // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp - #define PIN_SR_LATCH 60 // P9_12, shift register latch pin - #define PIN_SR_DATA 30 // P9_11, shift register data pin - #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin - #define PIN_SR_OE 50 // P9_14, shift register output enable pin - #define PIN_SENSOR1 48 - #define PIN_RFTX 51 // RF transmitter pin - - #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} - #define ETHER_BUFFER_SIZE 16384 - -#else // for demo / simulation - // use fake hardware pins - #if defined(DEMO) - #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware - #else - #define OS_HW_VERSION SIM_HW_VERSION_BASE - #endif - #define PIN_SR_LATCH 0 - #define PIN_SR_DATA 0 - #define PIN_SR_CLOCK 0 - #define PIN_SR_OE 0 - #define PIN_SENSOR1 0 - #define PIN_SENSOR2 0 - #define PIN_RFTX 0 - #define PIN_FREE_LIST {} - #define ETHER_BUFFER_SIZE 16384 -#endif - -#if defined(ENABLE_DEBUG) /** Serial debug functions */ - - #if defined(ARDUINO) - #define DEBUG_BEGIN(x) {Serial.begin(x);} - #define DEBUG_PRINT(x) {Serial.print(x);} - #define DEBUG_PRINTLN(x) {Serial.println(x);} - #else - #include - #define DEBUG_BEGIN(x) {} /** Serial debug functions */ - inline void DEBUG_PRINT(int x) {printf("%d", x);} - inline void DEBUG_PRINT(const char*s) {printf("%s", s);} - #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} - #endif - -#else - - #define DEBUG_BEGIN(x) {} - #define DEBUG_PRINT(x) {} - #define DEBUG_PRINTLN(x) {} - -#endif - -/** Re-define avr-specific (e.g. PGM) types to use standard types */ -#if !defined(ARDUINO) - #include - #include - #include - #include - inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} - inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} - #define now() time(0) - #define pgm_read_byte(x) *(x) - #define PSTR(x) x - #define F(x) x - #define strcat_P strcat - #define strcpy_P strcpy - #define sprintf_P sprintf - #include - #define String string - using namespace std; - #define PROGMEM - typedef const char* PGM_P; - typedef unsigned char uint8_t; - typedef short int16_t; - typedef unsigned short uint16_t; - typedef bool boolean; - #define pinModeExt pinMode - #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite -#endif - -/** Other defines */ -// button values -#define BUTTON_1 0x01 -#define BUTTON_2 0x02 -#define BUTTON_3 0x04 - -// button status values -#define BUTTON_NONE 0x00 // no button pressed -#define BUTTON_MASK 0x0F // button status mask -#define BUTTON_FLAG_HOLD 0x80 // long hold flag -#define BUTTON_FLAG_DOWN 0x40 // down flag -#define BUTTON_FLAG_UP 0x20 // up flag - -// button timing values -#define BUTTON_DELAY_MS 1 // short delay (milliseconds) -#define BUTTON_HOLD_MS 1000 // long hold expiration time (milliseconds) - -// button mode values -#define BUTTON_WAIT_NONE 0 // do not wait, return value immediately -#define BUTTON_WAIT_RELEASE 1 // wait until button is release -#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/etherport.cpp b/etherport.cpp index a2cea4ad5..2862c1e40 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -38,6 +38,13 @@ #include #include "defines.h" +#include +#include +#include +#include +#include +#include + EthernetServer::EthernetServer(uint16_t port) : m_port(port), m_sock(0) { @@ -203,4 +210,145 @@ size_t EthernetClient::write(const uint8_t *buf, size_t size) return ::send(m_sock, buf, size, MSG_NOSIGNAL); } +/** + * SSL Client +*/ + +EthernetClientSsl::EthernetClientSsl() + : m_sock(0), m_connected(false) +{ +} + +EthernetClientSsl::EthernetClientSsl(int sock) + : m_sock(sock), m_connected(true) +{ +} + +EthernetClientSsl::~EthernetClientSsl() +{ + stop(); +} + +static bool sslInit; +//static BIO* certbio; +static SSL_CTX* ctx; + +/** + * https://github.com/angstyloop/c-web/blob/main/openssl-fetch-example.c +*/ +int EthernetClientSsl::connect(uint8_t ip[4], uint16_t port) +{ + if (m_sock) + return 0; + + if (!sslInit) { + OpenSSL_add_all_algorithms(); + ERR_load_BIO_strings(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + //BIO* certbio = BIO_new(BIO_s_file()); + if (SSL_library_init() < 0) + { + DEBUG_PRINTLN("Could not initialize the OpenSSL library.\n"); + return 0; + } + const SSL_METHOD* method = SSLv23_client_method(); + ctx = SSL_CTX_new(method); + if (!ctx) { + DEBUG_PRINTLN("Unable to create SSL context.\n"); + return 0; + } + SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3); + SSL_CTX_set_verify(ctx, SSL_VERIFY_NONE, NULL); //Accept all certs + + sslInit = true; + } + + + // Create a new SSL session. This does not connect the socket. + ssl = SSL_new(ctx); + + struct sockaddr_in sin = {0}; + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + sin.sin_addr.s_addr = *(uint32_t*) (ip); + m_sock = socket(AF_INET, SOCK_STREAM, 0); + if (::connect(m_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) + { + DEBUG_PRINTLN("error connecting to server"); + return 0; + } + SSL_set_fd(ssl, m_sock); + if (SSL_connect(ssl) < 1) { + close(m_sock); + m_sock = 0; + DEBUG_PRINTLN("Error: Could not build an SSL session"); + return 0; + } + m_connected = true; + return 1; +} + +bool EthernetClientSsl::connected() +{ + if (!m_sock || !ssl) + return false; + int error = 0; + socklen_t len = sizeof(error); + int retval = getsockopt(m_sock, SOL_SOCKET, SO_ERROR, &error, &len); + return (retval == 0) && m_connected; +} + +void EthernetClientSsl::stop() +{ + if (m_sock) { + close(m_sock); + m_sock = 0; + } + if (ssl) { + SSL_free(ssl); + ssl = NULL; + } + m_connected = false; +} + +EthernetClientSsl::operator bool() +{ + return m_sock != 0 && ssl != NULL; +} + +// read data from the client into the buffer provided +// This function will block until either data is received OR a timeout happens. +// If an error occurs or a timeout happens, we set the disconnect flag on the socket +// and return 0; +int EthernetClientSsl::read(uint8_t *buf, size_t size) +{ + fd_set sock_set; + FD_ZERO(&sock_set); + FD_SET(m_sock, &sock_set); + struct timeval timeout; + timeout.tv_sec = 3; + timeout.tv_usec = 0; + + //select(m_sock + 1, &sock_set, NULL, NULL, &timeout); + //if (FD_ISSET(m_sock, &sock_set)) + size_t pending = SSL_pending(ssl); + if (pending > 0) + { + if (size > pending) + size = pending; + int retval = SSL_read(ssl, buf, size); + if (retval <= 0) // socket closed + m_connected = false; + return retval; + } + m_connected = false; + return 0; +} + +size_t EthernetClientSsl::write(const uint8_t *buf, size_t size) +{ + return SSL_write(ssl, buf, size); + //return ::send(m_sock, buf, size, MSG_NOSIGNAL); +} #endif diff --git a/etherport.h b/etherport.h index a0788c8be..efcebc2e6 100644 --- a/etherport.h +++ b/etherport.h @@ -32,6 +32,7 @@ #include #include #include +#include #ifdef __APPLE__ #define MSG_NOSIGNAL SO_NOSIGPIPE @@ -60,6 +61,28 @@ class EthernetClient { friend class EthernetServer; }; +class EthernetClientSsl { +public: + EthernetClientSsl(); + EthernetClientSsl(int sock); + ~EthernetClientSsl(); + int connect(uint8_t ip[4], uint16_t port); + bool connected(); + void stop(); + int read(uint8_t *buf, size_t size); + size_t write(const uint8_t *buf, size_t size); + operator bool(); + int GetSocket() + { + return m_sock; + } +private: + int m_sock; + SSL* ssl; + bool m_connected; + friend class EthernetServer; +}; + class EthernetServer { public: EthernetServer(uint16_t port); diff --git a/make.lin302m~ b/make.lin302m~ deleted file mode 100644 index 680556453..000000000 --- a/make.lin302m~ +++ /dev/null @@ -1,34 +0,0 @@ -SKETCH = ./mainArduino.ino -LIBS = . \ - $(ESP_LIBS)/Wire \ - $(ESP_LIBS)/SPI \ - $(ESP_LIBS)/ESP8266WiFi \ - $(ESP_LIBS)/ESP8266WebServer \ - $(ESP_LIBS)/ESP8266mDNS \ - /data/libs/LittleFS \ - /data/libs/lwIP_enc28j60 \ - /data/libs/SSD1306 \ - /data/libs/rc-switch \ - /data/libs/pubsubclient \ - -ESP_ROOT = /data/esp8266_3.0.2/ -ESPCORE_VERSION = 302 -BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) - -UPLOAD_SPEED = 460800 -UPLOAD_VERB = -v -# for OS3.0 revision 1: reset mode is nodemcu -# UPLOAD_RESET = nodemcu -# Uncomment the line below for OS3.0 revision 0: reset mode is ck -# UPLOAD_RESET = ck - -FLASH_DEF = 4M3M -FLASH_MODE = dio -FLASH_SPEED = 80 -F_CPU = 160000000L - -BOARD = generic - -EXCLUDE_DIRS = ./build-1284 - -include ./makeEspArduino.mk diff --git a/make.lin302~ b/make.lin302~ deleted file mode 100644 index 1ba67b7de..000000000 --- a/make.lin302~ +++ /dev/null @@ -1,36 +0,0 @@ -SKETCH = ./mainArduino.ino -LIBS = . \ - $(ESP_LIBS)/Wire \ - $(ESP_LIBS)/SPI \ - $(ESP_LIBS)/ESP8266WiFi \ - $(ESP_LIBS)/ESP8266WebServer \ - $(ESP_LIBS)/ESP8266mDNS \ - $(ESP_LIBS)/LittleFS \ - /data/libs/lwIP_enc28j60 \ - /data/libs/SSD1306 \ - /data/libs/rc-switch \ - /data/libs/pubsubclient \ - /data/libs/ADS1015 \ - -ESP_ROOT = /data/esp8266_3.0.2/ -ESPCORE_VERSION = 302 -BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) - -UPLOAD_SPEED = 460800 -UPLOAD_VERB = -v -# for OS3.0 revision 1: reset mode is nodemcu -# UPLOAD_RESET = nodemcu -# Uncomment the line below for OS3.0 revision 0: reset mode is ck -# UPLOAD_RESET = ck - -FLASH_DEF = 4M3M -FLASH_MODE = dio -FLASH_SPEED = 80 -F_CPU = 160000000L - -BOARD = generic -board_build.filesystem = littlefs - -EXCLUDE_DIRS = ./build-1284 - -include ./makeEspArduino.mk diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index d54ca022f..a6e1d3bb0 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -638,7 +638,7 @@ void server_change_stations(OTF_PARAMS_DEF) { if (!found || activeState > 1) { handle_return(HTML_DATA_OUTOFBOUND); } - } else if (tmp_buffer[0] == STN_TYPE_HTTP) { + } else if (tmp_buffer[0] == STN_TYPE_HTTP || tmp_buffer[0] == STN_TYPE_HTTPS) { #if !defined(ESP8266) urlDecode(tmp_buffer + 1); #endif From 6c751328c24e772e2d9f8272ec1041ec901ae934 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 25 Feb 2024 16:59:45 +0100 Subject: [PATCH 090/281] Updated Version to 140 --- defines.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/defines.h b/defines.h index 2e233ba0e..4161d82a1 100644 --- a/defines.h +++ b/defines.h @@ -25,6 +25,7 @@ #define _DEFINES_H //#define ENABLE_DEBUG // enable serial debug +//#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -32,11 +33,11 @@ typedef unsigned long ulong; #define TMP_BUFFER_SIZE 255 // scratch buffer size /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 220 // Firmware version: 220 means 2.2.0 +#define OS_FW_VERSION 230 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 3 // Firmware minor version +#define OS_FW_MINOR 140 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -140,13 +141,13 @@ typedef unsigned long ulong; /** Default string option values */ #define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA -#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" +#define DEFAULT_LOCATION "49.484018,8.475593" // Mannheim,Germany +#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinklershop.de/js" #define DEFAULT_WEATHER_URL "weather.opensprinkler.com" #define DEFAULT_IFTTT_URL "maker.ifttt.com" #define DEFAULT_OTC_SERVER "ws.cloud.openthings.io" #define DEFAULT_OTC_PORT 80 -#define DEFAULT_DEVICE_NAME "My OpenSprinkler" +#define DEFAULT_DEVICE_NAME "Mein OpenSprinkler" #define DEFAULT_EMPTY_STRING "" /* Weather Adjustment Methods */ From 115c234f6b39b9daf4316a661c772e7962956c4c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 25 Feb 2024 23:01:06 +0100 Subject: [PATCH 091/281] fixt compile error, added calculation-check (min 0, max = 20) for program adjustments --- OpenSprinkler.h | 3 +++ main.cpp | 4 +++- opensprinkler_server.cpp | 3 ++- sensors.cpp | 5 ++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/OpenSprinkler.h b/OpenSprinkler.h index d93435528..4a6e6fca2 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -96,6 +96,9 @@ inline wl_status_t status() { return (isW5500)?w5500.status():enc28j60.status(); } + inline const netif* getNetIf() { + return (isW5500)?w5500.getNetIf():enc28j60.getNetIf(); + } }; extern lwipEth eth; #else diff --git a/main.cpp b/main.cpp index 6b922190a..72b13ffa5 100644 --- a/main.cpp +++ b/main.cpp @@ -40,7 +40,9 @@ OTF::OpenThingsFramework *otf = NULL; bool otf_callbacksInitialised = false; DNSServer *dns = NULL; - ENC28J60lwIP eth(PIN_ETHER_CS); // ENC28J60 lwip for wired Ether + 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 static uint16_t led_blink_ms = LED_FAST_BLINK; #else diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index a6e1d3bb0..5b5c005d5 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -39,7 +39,8 @@ extern ESP8266WebServer *update_server; extern OTF::OpenThingsFramework *otf; - extern ENC28J60lwIP eth; + extern lwipEth eth; + extern bool otf_callbacksInitialised; #define OTF_PARAMS_DEF const OTF::Request &req,OTF::Response &res #define OTF_PARAMS req,res #define FKV_SOURCE req diff --git a/sensors.cpp b/sensors.cpp index b4ccfeb65..0f709a3d3 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1509,7 +1509,10 @@ double calc_sensor_watering(uint prog) { p = p->next; } - + if (result < 0.0) + result = 0.0; + if (result > 20.0) // Factor 20 is a huge value! + result = 20.0; return result; } From 588f6d6e603b4e74cde9166aca67ff9ae2b06083 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 25 Feb 2024 23:41:11 +0100 Subject: [PATCH 092/281] added missing https method --- OpenSprinkler.cpp | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index b164ae487..7e88ea61f 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2089,12 +2089,29 @@ int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, vo return send_http_request(server, port, p, callback, timeout); } +int8_t OpenSprinkler::send_https_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { + char server[20]; + byte ip[4]; + ip[0] = ip4>>24; + ip[1] = (ip4>>16)&0xff; + ip[2] = (ip4>>8)&0xff; + ip[3] = ip4&0xff; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + return send_https_request(server, port, p, callback, timeout); +} + int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { char * server = strtok(server_with_port, ":"); char * port = strtok(NULL, ":"); return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); } +int8_t OpenSprinkler::send_https_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { + char * server = strtok(server_with_port, ":"); + char * port = strtok(NULL, ":"); + return send_https_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); +} + /** Switch remote station * This function takes a remote station code, * parses it into remote IP, port, station index, From 039ecec4622ab74ff1f2e1c164d3b5654bfb5540 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 9 Mar 2024 14:09:27 +0100 Subject: [PATCH 093/281] - Fixed OSPi compile : Added OpenSSL 3 compatibility, build script --- build.sh | 2 +- etherport.cpp | 2 +- opensprinkler_server.cpp | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/build.sh b/build.sh index fccc58ad2..f3dcc0c79 100755 --- a/build.sh +++ b/build.sh @@ -33,7 +33,7 @@ else exit 0 fi echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp -li2c -lpthread -lmosquitto -lcrypto -lssl + g++ -o OpenSprinkler -DOSPI -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -li2c -lpthread -lmosquitto -lcrypto -lssl fi if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then diff --git a/etherport.cpp b/etherport.cpp index 2862c1e40..2fb123c0b 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -243,7 +243,7 @@ int EthernetClientSsl::connect(uint8_t ip[4], uint16_t port) if (!sslInit) { OpenSSL_add_all_algorithms(); - ERR_load_BIO_strings(); + //ERR_load_BIO_strings(); ERR_load_crypto_strings(); SSL_load_error_strings(); //BIO* certbio = BIO_new(BIO_s_file()); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 5b5c005d5..72056ad73 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2449,8 +2449,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (lastHours > 0 && log_size > 0) { after = os.now_tz() - lastHours * 60 * 60; //seconds DEBUG_PRINT(F("lastHours=")); - DEBUG_PRINT(lastHours); - DEBUG_PRINTLN(); + DEBUG_PRINTLN(lastHours); ulong a = 0; ulong b = log_size-1; @@ -2471,8 +2470,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { if (maxResults > 0 && maxResults < log_size) { DEBUG_PRINT(F("max=")); - DEBUG_PRINT(maxResults); - DEBUG_PRINTLN(); + DEBUG_PRINTLN(maxResults); ulong startAt2 = log_size-maxResults; if (startAt2 > startAt) startAt = startAt2; From b93d0821d12f49431702e174dcd3ed4f7c5c90fd Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 12 Mar 2024 01:35:40 +0100 Subject: [PATCH 094/281] MQTT Subscribe sensor for LoRa+NB-IOT Sensors --- defines.h | 6 +- main.cpp | 3 +- mqtt.cpp | 843 ++++++++++++++++++++------------------- mqtt.h | 122 +++--- opensprinkler_server.cpp | 88 +++- sensor_mqtt.cpp | 197 +++++++++ sensor_mqtt.h | 39 ++ sensors.cpp | 74 +++- sensors.h | 31 +- 9 files changed, 902 insertions(+), 501 deletions(-) create mode 100644 sensor_mqtt.cpp create mode 100644 sensor_mqtt.h diff --git a/defines.h b/defines.h index 4161d82a1..0fab28955 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug -//#define SERIAL_DEBUG +#define ENABLE_DEBUG // enable serial debug +#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -37,7 +37,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 140 // Firmware minor version +#define OS_FW_MINOR 150 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index 72b13ffa5..4cd2926ab 100644 --- a/main.cpp +++ b/main.cpp @@ -356,8 +356,7 @@ void do_setup() { os.button_timeout = LCD_BACKLIGHT_TIMEOUT; - sensor_load(); - prog_adjust_load(); + sensor_api_init(); } // Arduino software reset function diff --git a/mqtt.cpp b/mqtt.cpp index 6732e707f..3a5f4c845 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -1,405 +1,438 @@ -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * OpenSprinkler library - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#if defined(ARDUINO) - #include - #if defined(ESP8266) - #include - #else - #include - #endif - #include - - struct PubSubClient *mqtt_client = NULL; - -#else - #include - #include - #include - - struct mosquitto *mqtt_client = NULL; -#endif - -#include "OpenSprinkler.h" -#include "mqtt.h" - -// Debug routines to help identify any blocking of the event loop for an extended period - -#if defined(ENABLE_DEBUG) - #if defined(ARDUINO) - #include "TimeLib.h" - #define DEBUG_PRINTF(msg, ...) {Serial.printf(msg, ##__VA_ARGS__);} - #define DEBUG_TIMESTAMP(msg, ...) {time_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 - #include - #define DEBUG_PRINTF(msg, ...) {printf(msg, ##__VA_ARGS__);} - #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;} -#else - #define DEBUG_PRINTF(msg, ...) {} - #define DEBUG_LOGF(msg, ...) {} - #define DEBUG_DURATION() {} -#endif - -#define str(s) #s -#define xstr(s) str(s) - -extern OpenSprinkler os; -extern char tmp_buffer[]; - -#define MQTT_KEEPALIVE 60 -#define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config -#define MQTT_MAX_HOST_LEN 50 // Note: App is set to max 50 chars for broker name -#define MQTT_MAX_USERNAME_LEN 32 // Note: App is set to max 32 chars for username -#define MQTT_MAX_PASSWORD_LEN 32 // Note: App is set to max 32 chars for password -#define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit -#define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts - -#define MQTT_ROOT_TOPIC "opensprinkler" -#define MQTT_AVAILABILITY_TOPIC MQTT_ROOT_TOPIC "/availability" -#define MQTT_ONLINE_PAYLOAD "online" -#define MQTT_OFFLINE_PAYLOAD "offline" - -#define MQTT_SUCCESS 0 // Returned when function operated successfully -#define MQTT_ERROR 1 // Returned whan function failed - -char OSMqtt::_id[MQTT_MAX_ID_LEN + 1] = {0}; // Id to identify the client to the broker -char OSMqtt::_host[MQTT_MAX_HOST_LEN + 1] = {0}; // IP or host name of the broker -char OSMqtt::_username[MQTT_MAX_USERNAME_LEN + 1] = {0}; // username to connect to the broker -char OSMqtt::_password[MQTT_MAX_PASSWORD_LEN + 1] = {0}; // password to connect to the broker -int OSMqtt::_port = MQTT_DEFAULT_PORT; // Port of the broker (default 1883) -bool OSMqtt::_enabled = false; // Flag indicating whether MQTT is enabled - -// Initialise the client libraries and event handlers. -void OSMqtt::init(void) { - DEBUG_LOGF("MQTT Init\r\n"); - char id[MQTT_MAX_ID_LEN + 1] = {0}; - -#if defined(ARDUINO) - uint8_t mac[6] = {0}; - #if defined(ESP8266) - os.load_hardware_mac(mac, useEth); - #else - os.load_hardware_mac(mac, true); - #endif - snprintf(id, MQTT_MAX_ID_LEN, "OS-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); -#endif - - init(id); -}; - -// Initialise the client libraries and event handlers. -void OSMqtt::init(const char * clientId) { - DEBUG_LOGF("MQTT Init: ClientId %s\r\n", clientId); - - strncpy(_id, clientId, MQTT_MAX_ID_LEN); - _id[MQTT_MAX_ID_LEN] = 0; - _init(); -}; - -// Start the MQTT service and connect to the MQTT broker using the stored configuration. -void OSMqtt::begin(void) { - DEBUG_LOGF("MQTT Begin\r\n"); - char host[MQTT_MAX_HOST_LEN + 1] = {0}; - char username[MQTT_MAX_USERNAME_LEN + 1] = {0}; - char password[MQTT_MAX_PASSWORD_LEN + 1] = {0}; - int port = MQTT_DEFAULT_PORT; - int enabled = 0; - - // JSON configuration settings in the form of {"en":0|1,"host":"server_name|IP address","port":1883,user:"",pass:""} - char *config = tmp_buffer; - os.sopt_load(SOPT_MQTT_OPTS, config); - if (*config != 0) { - sscanf( - config, - "\"en\":%d,\"host\":\"%" xstr(MQTT_MAX_HOST_LEN) "[^\"]\",\"port\":%d,\"user\":\"%" xstr(MQTT_MAX_USERNAME_LEN) "[^\"]\",\"pass\":\"%" xstr(MQTT_MAX_PASSWORD_LEN) "[^\"]\"", - &enabled, host, &port, username, password - ); - } - - begin(host, port, username, password, (bool)enabled); -} - -// Start the MQTT service and connect to the MQTT broker. -void OSMqtt::begin( const char * host, int port, const char * username, const char * password, bool enabled ) { - DEBUG_LOGF("MQTT Begin: Config (%s:%d %s) %s\r\n", host, port, username, enabled ? "Enabled" : "Disabled"); - - strncpy(_host, host, MQTT_MAX_HOST_LEN); - _host[MQTT_MAX_HOST_LEN] = 0; - _port = port; - strncpy(_username, username, MQTT_MAX_USERNAME_LEN); - _username[MQTT_MAX_USERNAME_LEN] = 0; - strncpy(_password, password, MQTT_MAX_PASSWORD_LEN); - _username[MQTT_MAX_PASSWORD_LEN] = 0; - _enabled = enabled; - - if (mqtt_client == NULL || os.status.network_fails > 0) return; - - if (_connected()) { - _disconnect(); - } - - if (_enabled) { - _connect(); - } -} - -// Publish an MQTT message to a specific topic -void OSMqtt::publish(const char *topic, const char *payload) { - DEBUG_LOGF("MQTT Publish: %s %s\r\n", topic, payload); - - if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return; - - if (!_connected()) { - DEBUG_LOGF("MQTT Publish: Not connected\r\n"); - return; - } - - _publish(topic, payload); -} - -// 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; - - if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return; - - // Only attemp to reconnect every MQTT_RECONNECT_DELAY seconds to avoid blocking the main loop - if (!_connected() && (millis() - last_reconnect_attempt >= MQTT_RECONNECT_DELAY * 1000UL)) { - DEBUG_LOGF("MQTT Loop: Reconnecting\r\n"); - _connect(); - last_reconnect_attempt = millis(); - } - - int state = _loop(); - -#if defined(ENABLE_DEBUG) - // Print a diagnostic message whenever the MQTT state changes - bool network = os.network_connected(), mqtt = _connected(); - static bool last_network = 0, last_mqtt = 0; - static int last_state = 999; - - if (last_state != state || last_network != network || last_mqtt != mqtt) { - DEBUG_LOGF("MQTT Loop: Network %s, MQTT %s, State - %s\r\n", - network ? "UP" : "DOWN", - mqtt ? "UP" : "DOWN", - _state_string(state)); - last_state = state; last_network = network; last_mqtt = mqtt; - } -#endif -} - -/**************************** ARDUINO ********************************************/ -#if defined(ARDUINO) - - #if defined(ESP8266) - WiFiClient wifiClient; - #else - EthernetClient ethClient; - #endif - -int OSMqtt::_init(void) { - Client * client = NULL; - - if (mqtt_client) { delete mqtt_client; mqtt_client = 0; } - - #if defined(ESP8266) - client = &wifiClient; - #else - client = ðClient; - #endif - - mqtt_client = new PubSubClient(*client); - mqtt_client->setKeepAlive(MQTT_KEEPALIVE); - - if (mqtt_client == NULL) { - DEBUG_LOGF("MQTT Init: Failed to initialise client\r\n"); - return MQTT_ERROR; - } - - return MQTT_SUCCESS; -} - -int OSMqtt::_connect(void) { - mqtt_client->setServer(_host, _port); - boolean state; - #define MQTT_CONNECT_NTRIES 3 - byte tries = 0; - do { - DEBUG_PRINT(F("mqtt: ")); - DEBUG_PRINTLN(_host); - if (_username[0]) - state = mqtt_client->connect(_id, _username, _password, MQTT_AVAILABILITY_TOPIC, 0, true, MQTT_OFFLINE_PAYLOAD); - else - state = mqtt_client->connect(_id, NULL, NULL, MQTT_AVAILABILITY_TOPIC, 0, true, MQTT_OFFLINE_PAYLOAD); - if(state) break; - tries++; - } while(triesstate()); - return MQTT_ERROR; - } else { - mqtt_client->publish(MQTT_AVAILABILITY_TOPIC, MQTT_ONLINE_PAYLOAD, true); - } - return MQTT_SUCCESS; -} - -int OSMqtt::_disconnect(void) { - mqtt_client->disconnect(); - return MQTT_SUCCESS; -} - -bool OSMqtt::_connected(void) { return mqtt_client->connected(); } - -int OSMqtt::_publish(const char *topic, const char *payload) { - if (!mqtt_client->publish(topic, payload)) { - DEBUG_LOGF("MQTT Publish: Failed (%d)\r\n", mqtt_client->state()); - return MQTT_ERROR; - } - return MQTT_SUCCESS; -} - -int OSMqtt::_loop(void) { - mqtt_client->loop(); - return mqtt_client->state(); -} - -const char * OSMqtt::_state_string(int rc) { - switch (rc) { - case MQTT_CONNECTION_TIMEOUT: return "The server didn't respond within the keepalive time"; - case MQTT_CONNECTION_LOST: return "The network connection was lost"; - case MQTT_CONNECT_FAILED: return "The network connection failed"; - case MQTT_DISCONNECTED: return "The client has cleanly disconnected"; - case MQTT_CONNECTED: return "The client is connected"; - case MQTT_CONNECT_BAD_PROTOCOL: return "The server doesn't support the requested version of MQTT"; - case MQTT_CONNECT_BAD_CLIENT_ID: return "The server rejected the client identifier"; - case MQTT_CONNECT_UNAVAILABLE: return "The server was unavailable to accept the connection"; - case MQTT_CONNECT_BAD_CREDENTIALS: return "The username/password were rejected"; - case MQTT_CONNECT_UNAUTHORIZED: return "The client was not authorized to connect"; - default: return "Unrecognised state"; - } -} -#else - -/************************** RASPBERRY PI / BBB / DEMO ****************************************/ - -static bool _connected = false; - -static void _mqtt_connection_cb(struct mosquitto *mqtt_client, void *obj, int reason) { - DEBUG_LOGF("MQTT Connnection Callback: %s (%d)\r\n", mosquitto_strerror(reason), reason); - - ::_connected = true; - - if (reason == 0) { - int rc = mosquitto_publish(mqtt_client, NULL, MQTT_AVAILABILITY_TOPIC, strlen(MQTT_ONLINE_PAYLOAD), MQTT_ONLINE_PAYLOAD, 0, true); - if (rc != MOSQ_ERR_SUCCESS) { - DEBUG_LOGF("MQTT Publish: Failed (%s)\r\n", mosquitto_strerror(rc)); - } - } -} - -static void _mqtt_disconnection_cb(struct mosquitto *mqtt_client, void *obj, int reason) { - DEBUG_LOGF("MQTT Disconnnection Callback: %s (%d)\r\n", mosquitto_strerror(reason), reason); - - ::_connected = false; -} - -static void _mqtt_log_cb(struct mosquitto *mqtt_client, void *obj, int level, const char *message){ - if (level != MOSQ_LOG_DEBUG ) - DEBUG_LOGF("MQTT Log Callback: %s (%d)\r\n", message, level); -} - -int OSMqtt::_init(void) { - int major, minor, revision; - - mosquitto_lib_init(); - mosquitto_lib_version(&major, &minor, &revision); - DEBUG_LOGF("MQTT Init: Mosquitto Library v%d.%d.%d\r\n", major, minor, revision); - - if (mqtt_client) { mosquitto_destroy(mqtt_client); mqtt_client = NULL; }; - - mqtt_client = mosquitto_new("OS", true, NULL); - if (mqtt_client == NULL) { - DEBUG_PRINTF("MQTT Init: Failed to initialise client\r\n"); - return MQTT_ERROR; - } - - mosquitto_connect_callback_set(mqtt_client, _mqtt_connection_cb); - mosquitto_disconnect_callback_set(mqtt_client, _mqtt_disconnection_cb); - mosquitto_log_callback_set(mqtt_client, _mqtt_log_cb); - mosquitto_will_set(mqtt_client, MQTT_AVAILABILITY_TOPIC, strlen(MQTT_OFFLINE_PAYLOAD), MQTT_OFFLINE_PAYLOAD, 0, true); - - return MQTT_SUCCESS; -} - -int OSMqtt::_connect(void) { - int rc; - if (_username[0]) { - rc = mosquitto_username_pw_set(mqtt_client, _username, _password); - if (rc != MOSQ_ERR_SUCCESS) { - DEBUG_LOGF("MQTT Connect: Connection Failed (%s)\r\n", mosquitto_strerror(rc)); - return MQTT_ERROR; - } - } - rc = mosquitto_connect(mqtt_client, _host, _port, MQTT_KEEPALIVE); - if (rc != MOSQ_ERR_SUCCESS) { - DEBUG_LOGF("MQTT Connect: Connection Failed (%s)\r\n", mosquitto_strerror(rc)); - return MQTT_ERROR; - } - - // Allow 10ms for the Broker's ack to be received. We need this on start-up so that the - // connection is registered before we attempt to send our first NOTIFY_REBOOT notification. - usleep(10000); - - return MQTT_SUCCESS; -} - -int OSMqtt::_disconnect(void) { - int rc = mosquitto_disconnect(mqtt_client); - return rc == MOSQ_ERR_SUCCESS ? MQTT_SUCCESS : MQTT_ERROR; -} - -bool OSMqtt::_connected(void) { return ::_connected; } - -int OSMqtt::_publish(const char *topic, const char *payload) { - int rc = mosquitto_publish(mqtt_client, NULL, topic, strlen(payload), payload, 0, false); - if (rc != MOSQ_ERR_SUCCESS) { - DEBUG_LOGF("MQTT Publish: Failed (%s)\r\n", mosquitto_strerror(rc)); - return MQTT_ERROR; - } - return MQTT_SUCCESS; -} - -int OSMqtt::_loop(void) { - return mosquitto_loop(mqtt_client, 0 , 1); -} - -const char * OSMqtt::_state_string(int error) { - return mosquitto_strerror(error); -} -#endif +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * OpenSprinkler library + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#if defined(ARDUINO) + #include + #if defined(ESP8266) + #include + #else + #include + #endif + #include + + struct PubSubClient *mqtt_client = NULL; + +#else + #include + #include + #include + + struct mosquitto *mqtt_client = NULL; +#endif + +#include "OpenSprinkler.h" +#include "mqtt.h" + +// Debug routines to help identify any blocking of the event loop for an extended period + +#if defined(ENABLE_DEBUG) + #if defined(ARDUINO) + #include "TimeLib.h" + #define DEBUG_PRINTF(msg, ...) {Serial.printf(msg, ##__VA_ARGS__);} + #define DEBUG_TIMESTAMP(msg, ...) {time_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 + #include + #define DEBUG_PRINTF(msg, ...) {printf(msg, ##__VA_ARGS__);} + #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;} +#else + #define DEBUG_PRINTF(msg, ...) {} + #define DEBUG_LOGF(msg, ...) {} + #define DEBUG_DURATION() {} +#endif + +#define str(s) #s +#define xstr(s) str(s) + +extern OpenSprinkler os; +extern char tmp_buffer[]; + +#define MQTT_KEEPALIVE 60 +#define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config +#define MQTT_MAX_HOST_LEN 50 // Note: App is set to max 50 chars for broker name +#define MQTT_MAX_USERNAME_LEN 32 // Note: App is set to max 32 chars for username +#define MQTT_MAX_PASSWORD_LEN 32 // Note: App is set to max 32 chars for password +#define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit +#define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts + +#define MQTT_ROOT_TOPIC "opensprinkler" +#define MQTT_AVAILABILITY_TOPIC MQTT_ROOT_TOPIC "/availability" +#define MQTT_ONLINE_PAYLOAD "online" +#define MQTT_OFFLINE_PAYLOAD "offline" + +#define MQTT_SUCCESS 0 // Returned when function operated successfully +#define MQTT_ERROR 1 // Returned whan function failed + +char OSMqtt::_id[MQTT_MAX_ID_LEN + 1] = {0}; // Id to identify the client to the broker +char OSMqtt::_host[MQTT_MAX_HOST_LEN + 1] = {0}; // IP or host name of the broker +char OSMqtt::_username[MQTT_MAX_USERNAME_LEN + 1] = {0}; // username to connect to the broker +char OSMqtt::_password[MQTT_MAX_PASSWORD_LEN + 1] = {0}; // password to connect to the broker +int OSMqtt::_port = MQTT_DEFAULT_PORT; // Port of the broker (default 1883) +bool OSMqtt::_enabled = false; // Flag indicating whether MQTT is enabled + +// Initialise the client libraries and event handlers. +void OSMqtt::init(void) { + DEBUG_LOGF("MQTT Init\r\n"); + char id[MQTT_MAX_ID_LEN + 1] = {0}; + +#if defined(ARDUINO) + uint8_t mac[6] = {0}; + #if defined(ESP8266) + os.load_hardware_mac(mac, useEth); + #else + os.load_hardware_mac(mac, true); + #endif + snprintf(id, MQTT_MAX_ID_LEN, "OS-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +#endif + + init(id); +}; + +// Initialise the client libraries and event handlers. +void OSMqtt::init(const char * clientId) { + DEBUG_LOGF("MQTT Init: ClientId %s\r\n", clientId); + + strncpy(_id, clientId, MQTT_MAX_ID_LEN); + _id[MQTT_MAX_ID_LEN] = 0; + _init(); +}; + +// Start the MQTT service and connect to the MQTT broker using the stored configuration. +void OSMqtt::begin(void) { + DEBUG_LOGF("MQTT Begin\r\n"); + char host[MQTT_MAX_HOST_LEN + 1] = {0}; + char username[MQTT_MAX_USERNAME_LEN + 1] = {0}; + char password[MQTT_MAX_PASSWORD_LEN + 1] = {0}; + int port = MQTT_DEFAULT_PORT; + int enabled = 0; + + // JSON configuration settings in the form of {"en":0|1,"host":"server_name|IP address","port":1883,user:"",pass:""} + char *config = tmp_buffer; + os.sopt_load(SOPT_MQTT_OPTS, config); + if (*config != 0) { + sscanf( + config, + "\"en\":%d,\"host\":\"%" xstr(MQTT_MAX_HOST_LEN) "[^\"]\",\"port\":%d,\"user\":\"%" xstr(MQTT_MAX_USERNAME_LEN) "[^\"]\",\"pass\":\"%" xstr(MQTT_MAX_PASSWORD_LEN) "[^\"]\"", + &enabled, host, &port, username, password + ); + } + + begin(host, port, username, password, (bool)enabled); +} + +// Start the MQTT service and connect to the MQTT broker. +void OSMqtt::begin( const char * host, int port, const char * username, const char * password, bool enabled ) { + DEBUG_LOGF("MQTT Begin: Config (%s:%d %s) %s\r\n", host, port, username, enabled ? "Enabled" : "Disabled"); + + strncpy(_host, host, MQTT_MAX_HOST_LEN); + _host[MQTT_MAX_HOST_LEN] = 0; + _port = port; + strncpy(_username, username, MQTT_MAX_USERNAME_LEN); + _username[MQTT_MAX_USERNAME_LEN] = 0; + strncpy(_password, password, MQTT_MAX_PASSWORD_LEN); + _username[MQTT_MAX_PASSWORD_LEN] = 0; + _enabled = enabled; + + if (mqtt_client == NULL || os.status.network_fails > 0) return; + + if (_connected()) { + _disconnect(); + } + + if (_enabled) { + _connect(); + } +} + +// Publish an MQTT message to a specific topic +void OSMqtt::publish(const char *topic, const char *payload) { + DEBUG_LOGF("MQTT Publish: %s %s\r\n", topic, payload); + + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return; + + if (!_connected()) { + DEBUG_LOGF("MQTT Publish: Not connected\r\n"); + return; + } + + _publish(topic, payload); +} + +// 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; + + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return; + + // Only attemp to reconnect every MQTT_RECONNECT_DELAY seconds to avoid blocking the main loop + if (!_connected() && (millis() - last_reconnect_attempt >= MQTT_RECONNECT_DELAY * 1000UL)) { + DEBUG_LOGF("MQTT Loop: Reconnecting\r\n"); + _connect(); + last_reconnect_attempt = millis(); + } + + int state = _loop(); + +#if defined(ENABLE_DEBUG) + // Print a diagnostic message whenever the MQTT state changes + bool network = os.network_connected(), mqtt = _connected(); + static bool last_network = 0, last_mqtt = 0; + static int last_state = 999; + + if (last_state != state || last_network != network || last_mqtt != mqtt) { + DEBUG_LOGF("MQTT Loop: Network %s, MQTT %s, State - %s\r\n", + network ? "UP" : "DOWN", + mqtt ? "UP" : "DOWN", + _state_string(state)); + last_state = state; last_network = network; last_mqtt = mqtt; + } +#endif +} + +/**************************** ARDUINO ********************************************/ +#if defined(ARDUINO) + + #if defined(ESP8266) + WiFiClient wifiClient; + #else + EthernetClient ethClient; + #endif + +int OSMqtt::_init(void) { + Client * client = NULL; + + if (mqtt_client) { delete mqtt_client; mqtt_client = 0; } + + #if defined(ESP8266) + client = &wifiClient; + #else + client = ðClient; + #endif + + mqtt_client = new PubSubClient(*client); + mqtt_client->setKeepAlive(MQTT_KEEPALIVE); + mqtt_client->setBufferSize(2048); //Most LORA Pakets are bigger! + + if (mqtt_client == NULL) { + DEBUG_LOGF("MQTT Init: Failed to initialise client\r\n"); + return MQTT_ERROR; + } + + return MQTT_SUCCESS; +} + +int OSMqtt::_connect(void) { + mqtt_client->setServer(_host, _port); + boolean state; + #define MQTT_CONNECT_NTRIES 3 + byte tries = 0; + do { + DEBUG_PRINT(F("mqtt: ")); + DEBUG_PRINTLN(_host); + if (_username[0]) + state = mqtt_client->connect(_id, _username, _password, MQTT_AVAILABILITY_TOPIC, 0, true, MQTT_OFFLINE_PAYLOAD); + else + state = mqtt_client->connect(_id, NULL, NULL, MQTT_AVAILABILITY_TOPIC, 0, true, MQTT_OFFLINE_PAYLOAD); + if(state) break; + tries++; + } while(triesstate()); + return MQTT_ERROR; + } else { + mqtt_client->publish(MQTT_AVAILABILITY_TOPIC, MQTT_ONLINE_PAYLOAD, true); + } + return MQTT_SUCCESS; +} + +int OSMqtt::_disconnect(void) { + mqtt_client->disconnect(); + return MQTT_SUCCESS; +} + +bool OSMqtt::_connected(void) { return mqtt_client->connected(); } + +int OSMqtt::_publish(const char *topic, const char *payload) { + if (!mqtt_client->publish(topic, payload)) { + DEBUG_LOGF("MQTT Publish: Failed (%d)\r\n", mqtt_client->state()); + return MQTT_ERROR; + } + return MQTT_SUCCESS; +} + +int OSMqtt::_loop(void) { + mqtt_client->loop(); + return mqtt_client->state(); +} + +bool OSMqtt::subscribe(const char *topic) { + DEBUG_PRINTLN("subscribe1"); + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0 || !_connected()) return false; + DEBUG_PRINTLN("subscribe2"); + return mqtt_client->subscribe(topic); +} + +bool OSMqtt::unsubscribe(const char *topic) { + DEBUG_PRINTLN("unsubscribe1"); + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0 || !_connected()) return false; + DEBUG_PRINTLN("unsubscribe2"); + return mqtt_client->unsubscribe(topic); +} + +void OSMqtt::setCallback(MQTT_CALLBACK_SIGNATURE) { + mqtt_client->setCallback(callback); +} + +const char * OSMqtt::_state_string(int rc) { + switch (rc) { + case MQTT_CONNECTION_TIMEOUT: return "The server didn't respond within the keepalive time"; + case MQTT_CONNECTION_LOST: return "The network connection was lost"; + case MQTT_CONNECT_FAILED: return "The network connection failed"; + case MQTT_DISCONNECTED: return "The client has cleanly disconnected"; + case MQTT_CONNECTED: return "The client is connected"; + case MQTT_CONNECT_BAD_PROTOCOL: return "The server doesn't support the requested version of MQTT"; + case MQTT_CONNECT_BAD_CLIENT_ID: return "The server rejected the client identifier"; + case MQTT_CONNECT_UNAVAILABLE: return "The server was unavailable to accept the connection"; + case MQTT_CONNECT_BAD_CREDENTIALS: return "The username/password were rejected"; + case MQTT_CONNECT_UNAUTHORIZED: return "The client was not authorized to connect"; + default: return "Unrecognised state"; + } +} +#else + +/************************** RASPBERRY PI / BBB / DEMO ****************************************/ + +static bool _connected = false; + +static void _mqtt_connection_cb(struct mosquitto *mqtt_client, void *obj, int reason) { + DEBUG_LOGF("MQTT Connnection Callback: %s (%d)\r\n", mosquitto_strerror(reason), reason); + + ::_connected = true; + + if (reason == 0) { + int rc = mosquitto_publish(mqtt_client, NULL, MQTT_AVAILABILITY_TOPIC, strlen(MQTT_ONLINE_PAYLOAD), MQTT_ONLINE_PAYLOAD, 0, true); + if (rc != MOSQ_ERR_SUCCESS) { + DEBUG_LOGF("MQTT Publish: Failed (%s)\r\n", mosquitto_strerror(rc)); + } + } +} + +static void _mqtt_disconnection_cb(struct mosquitto *mqtt_client, void *obj, int reason) { + DEBUG_LOGF("MQTT Disconnnection Callback: %s (%d)\r\n", mosquitto_strerror(reason), reason); + + ::_connected = false; +} + +static void _mqtt_log_cb(struct mosquitto *mqtt_client, void *obj, int level, const char *message){ + if (level != MOSQ_LOG_DEBUG ) + DEBUG_LOGF("MQTT Log Callback: %s (%d)\r\n", message, level); +} + +int OSMqtt::_init(void) { + int major, minor, revision; + + mosquitto_lib_init(); + mosquitto_lib_version(&major, &minor, &revision); + DEBUG_LOGF("MQTT Init: Mosquitto Library v%d.%d.%d\r\n", major, minor, revision); + + if (mqtt_client) { mosquitto_destroy(mqtt_client); mqtt_client = NULL; }; + + mqtt_client = mosquitto_new("OS", true, &this); + if (mqtt_client == NULL) { + DEBUG_PRINTF("MQTT Init: Failed to initialise client\r\n"); + return MQTT_ERROR; + } + + mosquitto_connect_callback_set(mqtt_client, _mqtt_connection_cb); + mosquitto_disconnect_callback_set(mqtt_client, _mqtt_disconnection_cb); + mosquitto_log_callback_set(mqtt_client, _mqtt_log_cb); + mosquitto_will_set(mqtt_client, MQTT_AVAILABILITY_TOPIC, strlen(MQTT_OFFLINE_PAYLOAD), MQTT_OFFLINE_PAYLOAD, 0, true); + + return MQTT_SUCCESS; +} + +int OSMqtt::_connect(void) { + int rc; + if (_username[0]) { + rc = mosquitto_username_pw_set(mqtt_client, _username, _password); + if (rc != MOSQ_ERR_SUCCESS) { + DEBUG_LOGF("MQTT Connect: Connection Failed (%s)\r\n", mosquitto_strerror(rc)); + return MQTT_ERROR; + } + } + rc = mosquitto_connect(mqtt_client, _host, _port, MQTT_KEEPALIVE); + if (rc != MOSQ_ERR_SUCCESS) { + DEBUG_LOGF("MQTT Connect: Connection Failed (%s)\r\n", mosquitto_strerror(rc)); + return MQTT_ERROR; + } + + // Allow 10ms for the Broker's ack to be received. We need this on start-up so that the + // connection is registered before we attempt to send our first NOTIFY_REBOOT notification. + usleep(10000); + + return MQTT_SUCCESS; +} + +int OSMqtt::_disconnect(void) { + int rc = mosquitto_disconnect(mqtt_client); + return rc == MOSQ_ERR_SUCCESS ? MQTT_SUCCESS : MQTT_ERROR; +} + +bool OSMqtt::_connected(void) { return ::_connected; } + +int OSMqtt::_publish(const char *topic, const char *payload) { + int rc = mosquitto_publish(mqtt_client, NULL, topic, strlen(payload), payload, 0, false); + if (rc != MOSQ_ERR_SUCCESS) { + DEBUG_LOGF("MQTT Publish: Failed (%s)\r\n", mosquitto_strerror(rc)); + return MQTT_ERROR; + } + return MQTT_SUCCESS; +} + +int OSMqtt::_loop(void) { + return mosquitto_loop(mqtt_client, 0 , 1); +} + +bool OSMqtt::subscribe(const char *topic) { + if (!mqtt_client || !_enabled || os.status.network_fails > 0 || !_connected()) return false; + return mosquitto_subscribe(mqtt_client, NULL, topic, 0); +} + +bool OSMqtt::unsubscribe(const char *topic) { + if (!mqtt_client || !_enabled || os.status.network_fails > 0 || !_connected()) return false; + return mosquitto_unsubscribe(mqtt_client, NULL, topic, 0); +} + +void OSMqtt::setCallback(void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)) { + mosquitto_message_callback_set(mqtt_client, on_message); +} + +const char * OSMqtt::_state_string(int error) { + return mosquitto_strerror(error); +} +#endif diff --git a/mqtt.h b/mqtt.h index 3926d31bf..995f61cef 100644 --- a/mqtt.h +++ b/mqtt.h @@ -1,54 +1,68 @@ -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * OpenSprinkler library header file - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#ifndef _MQTT_H -#define _MQTT_H - -class OSMqtt { -private: - static char _id[]; - static char _host[]; - static int _port; - static char _username[]; - static char _password[]; - static bool _enabled; - - // 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 _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 void begin(const char * host, int port, const char * username, const char * password, bool enable); - static bool enabled(void) { return _enabled; }; - static void publish(const char *topic, const char *payload); - static void loop(void); -}; - -#endif // _MQTT_H +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * OpenSprinkler library header file + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _MQTT_H +#define _MQTT_H +#if defined(ARDUINO) + #include +#else + #include +#endif + +class OSMqtt { +private: + static char _id[]; + static char _host[]; + static int _port; + static char _username[]; + static char _password[]; + static bool _enabled; + + + // 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 _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 void begin(const char * host, int port, const char * username, const char * password, bool enable); + static bool enabled(void) { return _enabled; }; + static void publish(const char *topic, const char *payload); + static void loop(void); + + static bool subscribe(const char *topic); + static bool unsubscribe(const char *topic); +#if defined(ARDUINO) + static void setCallback(MQTT_CALLBACK_SIGNATURE); +#else + static void setCallback(void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)); +#endif +}; + +#endif // _MQTT_H diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 5b5c005d5..7f757ced5 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2040,6 +2040,68 @@ void server_sensor_config_userdef(OTF_PARAMS_DEF) handle_return(ret); } +/** + * sj + * get sensorurl + * {"nr":1,"type":1} + * return { "value": "text"} + */ +void server_sensorurl_get(OTF_PARAMS_DEF) +{ +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + DEBUG_PRINTLN(F("sensorUrl_get")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type + + bfill.emit_p(PSTR("{\"value\":\"$S\"}"), SensorUrl_get(nr, type)); + handle_return(HTML_OK); +} + +/** + * sk + * MQTT and other URL configuration + * type = 0 URL, 1=MQTT Subscription, 2=JSON Filter + * {"nr":1,"type":1,"value":abc} + */ +void server_sensorurl_config(OTF_PARAMS_DEF) +{ +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + DEBUG_PRINTLN(F("serverUrl_config")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type + + char *value = NULL; + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value"), true)) + handle_return(HTML_DATA_MISSING); + value = strdup(tmp_buffer); + + bool ok = SensorUrl_add(nr, type, value); + free(value); + handle_return(ok?HTML_SUCCESS:HTML_DATA_MISSING); +} + /** * sc * Modus RS485 Sensor config @@ -2784,6 +2846,7 @@ const int sensor_types[] = { SENSOR_OSPI_ANALOG_SMT50_MOIS, SENSOR_OSPI_ANALOG_SMT50_TEMP, #endif + SENSOR_MQTT, SENSOR_REMOTE, SENSOR_WEATHER_TEMP_F, SENSOR_WEATHER_TEMP_C, @@ -2822,7 +2885,8 @@ const char* sensor_names[] = { "OSPi analog input - SMT50 moisture mode", "OSPi analog input - SMT50 temperature mode", #endif - "Remote sensor of an remote opensprinkler", + "MQTT subscription", + "Remote opensprinkler sensor", "Weather data - temperature (°F)", "Weather data - temperature (°C)", "Weather data - humidity (%)", @@ -3082,6 +3146,8 @@ const char _url_keys[] PROGMEM = "ja" "pq" "si" + "sj" + "sk" "sc" "sl" "sg" @@ -3128,16 +3194,18 @@ URLHandler urls[] = { server_json_all, // ja server_pause_queue, // pq server_sensor_config_userdef, // si - server_sensor_config, // sc - server_sensor_list, // sl - server_sensor_get, // sg - server_sensor_readnow, // sr + server_sensorurl_get, // sj + server_sensorurl_config, // sk + server_sensor_config, // sc + server_sensor_list, // sl + server_sensor_get, // sg + server_sensor_readnow, // sr server_set_sensor_address, // sa - server_sensorlog_list, // so - server_sensorlog_clear, // sn - server_sensorprog_config, // sb - server_sensorprog_calc, // sd - server_sensorprog_list, // se + server_sensorlog_list, // so + server_sensorlog_clear, // sn + server_sensorprog_config, // sb + server_sensorprog_calc, // sd + server_sensorprog_list, // se server_sensor_types, // sf server_usage, // du server_sensorprog_types, // sh diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp new file mode 100644 index 000000000..d542078ce --- /dev/null +++ b/sensor_mqtt.cpp @@ -0,0 +1,197 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * Utility functions + * 2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#include "sensor_mqtt.h" +#include "sensors.h" +#include "mqtt.h" +#include "OpenSprinkler.h" + +extern OpenSprinkler os; + +/** + * @brief + * + * @param mtopic reported topic opensprinkler/analogsensor/name + * @param topic topic pattern opensprinkler/ or opensprinkler/# or opensprinkler/#/abc/# + * @return true + * @return false + */ +bool mqtt_filter_matches(char* mtopic, char* topic) { + + while (topic && mtopic) { + char ch1 = *mtopic++; + char ch2 = *topic++; + if (ch2 == '+') { //level ok up to "/" + while (mtopic) { + if (ch1 == '/') + break; + ch1 = *mtopic++; + } + } else if (ch2 == '#') { //multilevel + char *p = strpbrk(topic, "#+"); + if (!p) return true; + if (strncmp(topic, mtopic, p-topic)) { + mtopic = mtopic + (p-topic); + topic = p; + } + } else if (ch1 != ch2) + return false; + else if (ch1 == 0 && ch2 == 0) + return true; + else if (ch1 == 0 || ch2 == 0) + return false; + } + return true; +} + + +/** + * @brief mqtt callback + * + */ +#if defined(ARDUINO) +void sensor_mqtt_callback(char* mtopic, byte* payload, unsigned int length) { +#else +void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { + char* mtopic = msg->topic + byte* payload = (byte*)msg->payload; + unsigned int length = msg->payloadlen; +#endif + + DEBUG_PRINTLN("sensor_mqtt_callback1"); + + if (!mtopic || !payload) return; + payload[length] = 0; + + Sensor_t *sensor = getSensors(); + while (sensor) { + if (sensor->type == SENSOR_MQTT) { + char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); + DEBUG_PRINT("mtopic: "); DEBUG_PRINTLN(mtopic); + DEBUG_PRINT("topic: "); DEBUG_PRINTLN(topic); + + if (topic && mqtt_filter_matches(mtopic, topic)) { + DEBUG_PRINTLN("topic match!"); + char *jsonFilter = SensorUrl_get(sensor->nr, SENSORURL_TYPE_JSON); + char *p = (char*)payload; + char *f = jsonFilter; + + DEBUG_PRINT("payload: "); + DEBUG_PRINTLN(p); + + DEBUG_PRINT("jsonfilter: "); + DEBUG_PRINTLN(jsonFilter); + + while (f && p) { + f = strstr(jsonFilter, "|"); + if (f) { + p = strnstr(p, jsonFilter, f-jsonFilter); + jsonFilter = f+1; + } else { + p = strstr(p, jsonFilter); + } + } + if (p) { + char buf[30]; + p = strpbrk(p, "0123456789.-+"); + uint i = 0; + while (p && i < sizeof(buf)) { + char ch = *p++; + if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == '+') { + buf[i++] = ch; + } else break; + } + buf[i] = 0; + DEBUG_PRINT("result: "); + DEBUG_PRINTLN(buf); + sensor->last_data = atof(buf); + sensor->flags.data_ok = true; + sensor->last_read = os.now_tz(); + DEBUG_PRINTLN("sensor_mqtt_callback2"); + + sensorlog_add(LOG_STD, sensor, sensor->last_read); + sensor->mqtt_push = true; + } + } + } + sensor = sensor->next; + } + + //Now push the data out: + sensor = getSensors(); + while (sensor) { + if (sensor->type == SENSOR_MQTT && sensor->mqtt_push) { + sensor->mqtt_push = false; + push_message(sensor); + } + sensor = sensor->next; + } + + DEBUG_PRINTLN("sensor_mqtt_callback3"); +} + +int read_sensor_mqtt(Sensor_t *sensor) { + sensor->flags.data_ok = false; + if (!sensor->mqtt_init && os.mqtt.enabled) { + DEBUG_PRINT("read_sensor_mqtt1: "); + DEBUG_PRINTLN(sensor->name); + char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); + if (topic) { + os.mqtt.setCallback(&sensor_mqtt_callback); + DEBUG_PRINT("subscribe: "); + DEBUG_PRINTLN(topic); + if (!os.mqtt.subscribe(topic)) + DEBUG_PRINTLN("error subscribe!!"); + sensor->mqtt_init = true; + } + } + return HTTP_RQT_NOT_RECEIVED; +} + +void sensor_mqtt_subscribe(uint nr, uint type, char *urlstr) { + Sensor_t* sensor = sensor_by_nr(nr); + if (urlstr && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { + DEBUG_PRINT("sensor_mqtt_subscribe1: "); + DEBUG_PRINTLN(sensor->name); + if (os.mqtt.subscribe(urlstr)) { + os.mqtt.setCallback(&sensor_mqtt_callback); + DEBUG_PRINT("subscribe: "); + DEBUG_PRINTLN(urlstr); + sensor->mqtt_init = true; + } + else DEBUG_PRINTLN("error subscribe!!"); + } +} + +void sensor_mqtt_unsubscribe(uint nr, uint type, char *urlstr) { + Sensor_t* sensor = sensor_by_nr(nr); + if (urlstr && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { + DEBUG_PRINT("sensor_mqtt_unsubscribe1: "); + DEBUG_PRINTLN(sensor->name); + os.mqtt.setCallback(&sensor_mqtt_callback); + DEBUG_PRINT("unsubscribe: "); + DEBUG_PRINTLN(urlstr); + if (!os.mqtt.unsubscribe(urlstr)) + DEBUG_PRINTLN("error subscribe!!"); + sensor->mqtt_init = false; + } +} \ No newline at end of file diff --git a/sensor_mqtt.h b/sensor_mqtt.h new file mode 100644 index 000000000..27093f6bb --- /dev/null +++ b/sensor_mqtt.h @@ -0,0 +1,39 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * sensors header file + * 2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _SENSOR_MQTT_H +#define _SENSOR_MQTT_H + +#include "sensors.h" + +#if defined(ARDUINO) +void sensor_mqtt_callback(char* mtopic, byte* payload, unsigned int length); +#else +void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg); +#endif + +int read_sensor_mqtt(Sensor_t *sensor); + +void sensor_mqtt_subscribe(uint nr, uint type, char *urlstr); +void sensor_mqtt_unsubscribe(uint nr, uint type, char *urlstr); + +#endif // _SENSOR_MQTT_H diff --git a/sensors.cpp b/sensors.cpp index 0f709a3d3..faa1656a7 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -27,7 +27,7 @@ #include "opensprinkler_server.h" #include "sensors.h" #include "weather.h" -#include "mqtt.h" +#include "sensor_mqtt.h" byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); @@ -86,6 +86,20 @@ uint16_t CRC16 (byte buf[], int len) { return crc; } // End: CRC16 +/* + * init sensor api and load data + */ +void sensor_api_init() { + sensor_load(); + prog_adjust_load(); +} + +/* + * get list of all configured sensors + */ +Sensor_t* getSensors() { + return sensors; +} /** * @brief delete a sensor * @@ -1210,6 +1224,10 @@ int read_sensor(Sensor_t *sensor) { sensor->last_read = time; return read_sensor_http(sensor); #endif + case SENSOR_MQTT: + sensor->last_read = time; + return read_sensor_mqtt(sensor); + case SENSOR_WEATHER_TEMP_F: case SENSOR_WEATHER_TEMP_C: case SENSOR_WEATHER_HUM: @@ -1758,6 +1776,7 @@ byte getSensorUnitId(int type) { case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; #endif + case SENSOR_MQTT: return UNIT_USERDEF; case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; case SENSOR_WEATHER_TEMP_C: return UNIT_DEGREE; case SENSOR_WEATHER_HUM: return UNIT_HUM_PERCENT; @@ -1797,6 +1816,7 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; #endif + case SENSOR_MQTT: return UNIT_USERDEF; case SENSOR_REMOTE: return sensor->unitid; case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; @@ -1924,25 +1944,35 @@ void GetSensorWeather() { void SensorUrl_load() { sensorUrls = NULL; + DEBUG_PRINTLN("SensorUrl_load1"); if (!file_exists(SENSORURL_FILENAME)) return; + DEBUG_PRINTLN("SensorUrl_load2"); ulong pos = 0; SensorUrl_t *last = NULL; while (true) { SensorUrl_t *sensorUrl = new SensorUrl_t; memset(sensorUrl, 0, sizeof(SensorUrl_t)); - file_read_block (SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE); + if (file_read_block (SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE) < SENSORURL_STORE_SIZE) + { + free(sensorUrl); + break; + } sensorUrl->urlstr = (char*)malloc(sensorUrl->length+1); pos += SENSORURL_STORE_SIZE; file_read_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, sensorUrl->length); + sensorUrl->urlstr[sensorUrl->length] = 0; pos += sensorUrl->length; + DEBUG_PRINT(sensorUrl->nr); DEBUG_PRINT("/"); DEBUG_PRINT(sensorUrl->type);DEBUG_PRINT(": "); + DEBUG_PRINTLN(sensorUrl->urlstr); if (!last) sensorUrls = sensorUrl; else last->next = sensorUrl; last = sensorUrl; sensorUrl->next = NULL; } + DEBUG_PRINTLN("SensorUrl_load3"); } void SensorUrl_save() { @@ -1961,17 +1991,20 @@ void SensorUrl_save() { } } -bool SensorUrl_delete(uint nr) { +bool SensorUrl_delete(uint nr, uint type) { SensorUrl_t *sensorUrl = sensorUrls; SensorUrl_t *last = NULL; while (sensorUrl) { - if (sensorUrl->nr == nr) { + if (sensorUrl->nr == nr && sensorUrl->type == type) { if (last) last->next = sensorUrl->next; else sensorUrls = sensorUrl->next; - delete sensorUrl->urlstr; - delete sensorUrl; + + sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); + + free(sensorUrl->urlstr); + free(sensorUrl); SensorUrl_save(); return true; } @@ -1981,19 +2014,20 @@ bool SensorUrl_delete(uint nr) { return false; } -bool SensorUrl_add(uint nr, char *urlstr) { +bool SensorUrl_add(uint nr, uint type, char *urlstr) { if (!urlstr || !strlen(urlstr)) { //empty string? delete! - SensorUrl_delete(nr); - return false; + return SensorUrl_delete(nr, type); } SensorUrl_t *sensorUrl = sensorUrls; while (sensorUrl) { - if (sensorUrl->nr == nr) { //replace existing - delete sensorUrl->urlstr; - sensorUrl->urlstr = (char*) malloc(strlen(urlstr)+1); - strcpy(sensorUrl->urlstr, urlstr); + if (sensorUrl->nr == nr && sensorUrl->type == type) { //replace existing + sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); + free(sensorUrl->urlstr); + sensorUrl->length = strlen(urlstr); + sensorUrl->urlstr = strdup(urlstr); SensorUrl_save(); - return false; + sensor_mqtt_subscribe(nr, type, urlstr); + return true; } sensorUrl = sensorUrl->next; } @@ -2002,21 +2036,25 @@ bool SensorUrl_add(uint nr, char *urlstr) { sensorUrl = new SensorUrl_t; memset(sensorUrl, 0, sizeof(SensorUrl_t)); sensorUrl->nr = nr; + sensorUrl->type = type; sensorUrl->length = strlen(urlstr); - sensorUrl->urlstr = (char*) malloc(strlen(urlstr)+1); - strcpy(sensorUrl->urlstr, urlstr); + sensorUrl->urlstr = strdup(urlstr); sensorUrl->next = sensorUrls; sensorUrls = sensorUrl; SensorUrl_save(); + + sensor_mqtt_subscribe(nr, type, urlstr); + return true; } -char *SensorUrl_get(uint nr) { +char *SensorUrl_get(uint nr, uint type) { SensorUrl_t *sensorUrl = sensorUrls; while (sensorUrl) { - if (sensorUrl->nr == nr) //replace existing + if (sensorUrl->nr == nr && sensorUrl->type == type) //replace existing return sensorUrl->urlstr; sensorUrl = sensorUrl->next; } return NULL; } + diff --git a/sensors.h b/sensors.h index 68f9f1b22..19192347c 100644 --- a/sensors.h +++ b/sensors.h @@ -86,6 +86,8 @@ extern "C" { #define SENSOR_OSPI_ANALOG_SMT50_TEMP 53 //Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 #endif +#define SENSOR_MQTT 90 //subscribe to a MQTT server and query a value + #define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler #define SENSOR_WEATHER_TEMP_F 101 //Weather service - temperature (Fahrenheit) #define SENSOR_WEATHER_TEMP_C 102 //Weather service - temperature (Celcius) @@ -105,10 +107,10 @@ extern "C" { #define MIN_DISK_FREE 8192 //8Kb min typedef struct SensorFlags { - uint enable:1; - uint log:1; - uint data_ok:1; - uint show:1; + uint enable:1; // enabled + uint log:1; // log data enabled + uint data_ok:1; // last data is ok + uint show:1; // show on mainpage } SensorFlags_t; //Definition of a sensor @@ -132,6 +134,8 @@ typedef struct Sensor { // sensorvalue = (read_value-offset_mv/1000) * factor / divider + offset2/100 byte undef[16]; // for later //unstored + bool mqtt_init:1; + bool mqtt_push:1; byte unitid; ulong last_read; //millis Sensor *next; @@ -179,16 +183,19 @@ typedef struct ProgSensorAdjust { } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) +#define SENSORURL_TYPE_URL 0 //URL for Host/Path +#define SENSORURL_TYPE_TOPIC 1 //TOPIC for MQTT +#define SENSORURL_TYPE_JSON 2 //JSON Filter for MQTT + typedef struct SensorUrl { uint nr; - uint type; //unused, for later + uint type; //see SENSORURL_TYPE uint length; char *urlstr; SensorUrl *next; } SensorUrl_t; #define SENSORURL_STORE_SIZE (sizeof(SensorUrl_t)-sizeof(char*)-sizeof(SensorUrl_t*)) - #define UNIT_NONE 0 #define UNIT_PERCENT 1 #define UNIT_DEGREE 2 @@ -205,6 +212,9 @@ typedef struct SensorUrl { //Unitnames // extern const char* sensor_unitNames[]; +void sensor_api_init(); + +Sensor_t* getSensors(); const char* getSensorUnit(int unitid); const char* getSensorUnit(Sensor_t *sensor); byte getSensorUnitId(int type); @@ -264,12 +274,15 @@ double calc_sensor_watering(uint prog); double calc_sensor_watering_by_nr(uint nr); void GetSensorWeather(); +//PUSH Message to MQTT and others: +void push_message(Sensor_t *sensor); +//Web URLS Host/Path and MQTT topics: void SensorUrl_load(); void SensorUrl_save(); -bool SensorUrl_delete(uint nr); -bool SensorUrl_add(uint nr, char *urlstr); -char *SensorUrl_get(uint nr); +bool SensorUrl_delete(uint nr, uint type); +bool SensorUrl_add(uint nr, uint type, char *urlstr); +char *SensorUrl_get(uint nr, uint type); #if defined(ESP8266) ulong diskFree(); From e3d3492b2e70f5d0aacf1b5abc342be8f06b90f9 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 13 Mar 2024 00:44:52 +0100 Subject: [PATCH 095/281] added unitid for userdef+mqtt sensors, added sensor list+edit commands for mqtt sensor editing --- Sensor API.txt | 2 + opensprinkler_server.cpp | 82 +++++++++++++++++++++++++++------------- opensprinkler_server.h | 1 + sensor_mqtt.cpp | 8 ++-- sensors.cpp | 12 ++++-- sensors.h | 9 +++-- 6 files changed, 77 insertions(+), 37 deletions(-) diff --git a/Sensor API.txt b/Sensor API.txt index ae391a5ff..15f832e7a 100644 --- a/Sensor API.txt +++ b/Sensor API.txt @@ -21,6 +21,8 @@ creates, modifies or deletes a sensor. "enable" 0=sensor disabled, 1=sensor enabled "log" 0=logging disabled, 1=logging enabled "show" 0=hide 1=show current value on main screen +"offset", "offset2" Offset for user defined sensor +"url", "topic", "filter" mqtt/external sensor (url currently not used) examples: http:///sc?pw=&nr=1&type=1&group=0&name=SMT100-Mois&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index eaad0e4db..cabb48b18 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -601,9 +601,7 @@ void server_change_stations(OTF_PARAMS_DEF) { for(sid=0;sidnr, SENSORURL_TYPE_URL); + char* topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); + char* filter = SensorUrl_get(sensor->nr, SENSORURL_TYPE_FILTER); + + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"fac\":$D,\"div\":$D,\"offset\":$D,\"offset2\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L,\"url\":\"$S\",\"topic\":\"$S\",\"filter\":\"$S\"}"), sensor->nr, sensor->type, sensor->group, @@ -2372,7 +2401,8 @@ void sensorconfig_json(OTF_PARAMS_DEF) { sensor->flags.log, sensor->flags.show, sensor->flags.data_ok, - sensor->last_read); + sensor->last_read, + url?url:"", topic?topic:"", filter?filter:""); send_packet(OTF_PARAMS); } } diff --git a/opensprinkler_server.h b/opensprinkler_server.h index be8e1276b..279df4f76 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -97,5 +97,6 @@ class BufferFiller { unsigned int position () const { return ptr - start; } }; +char* urlDecodeAndUnescape(char *buf); #endif // _OPENSPRINKLER_SERVER_H diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index d542078ce..9293e2d0a 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -91,7 +91,7 @@ void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mos if (topic && mqtt_filter_matches(mtopic, topic)) { DEBUG_PRINTLN("topic match!"); - char *jsonFilter = SensorUrl_get(sensor->nr, SENSORURL_TYPE_JSON); + char *jsonFilter = SensorUrl_get(sensor->nr, SENSORURL_TYPE_FILTER); char *p = (char*)payload; char *f = jsonFilter; @@ -155,7 +155,7 @@ int read_sensor_mqtt(Sensor_t *sensor) { DEBUG_PRINT("read_sensor_mqtt1: "); DEBUG_PRINTLN(sensor->name); char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); - if (topic) { + if (topic && topic[0]) { os.mqtt.setCallback(&sensor_mqtt_callback); DEBUG_PRINT("subscribe: "); DEBUG_PRINTLN(topic); @@ -169,7 +169,7 @@ int read_sensor_mqtt(Sensor_t *sensor) { void sensor_mqtt_subscribe(uint nr, uint type, char *urlstr) { Sensor_t* sensor = sensor_by_nr(nr); - if (urlstr && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { + if (urlstr && urlstr[0] && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { DEBUG_PRINT("sensor_mqtt_subscribe1: "); DEBUG_PRINTLN(sensor->name); if (os.mqtt.subscribe(urlstr)) { @@ -184,7 +184,7 @@ void sensor_mqtt_subscribe(uint nr, uint type, char *urlstr) { void sensor_mqtt_unsubscribe(uint nr, uint type, char *urlstr) { Sensor_t* sensor = sensor_by_nr(nr); - if (urlstr && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { + if (urlstr && urlstr[0] && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { DEBUG_PRINT("sensor_mqtt_unsubscribe1: "); DEBUG_PRINTLN(sensor->name); os.mqtt.setCallback(&sensor_mqtt_callback); diff --git a/sensors.cpp b/sensors.cpp index faa1656a7..d57e623f8 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -136,7 +136,7 @@ int sensor_delete(uint nr) { * @param id */ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, - char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags) { + char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags, int16_t assigned_unitid) { if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; @@ -162,6 +162,8 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint sensor->offset2 = offset2; strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); sensor->flags = flags; + if (assigned_unitid >= 0) + sensor->assigned_unitid = assigned_unitid; sensor_save(); return HTTP_RQT_SUCCESS; } @@ -192,6 +194,9 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint new_sensor->offset2 = offset2; strncpy(new_sensor->userdef_unit, userdef_unit, sizeof(new_sensor->userdef_unit)-1); new_sensor->flags = flags; + if (assigned_unitid >= 0) + new_sensor->assigned_unitid = assigned_unitid; + if (last) { new_sensor->next = last->next; last->next = new_sensor; @@ -203,7 +208,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint return HTTP_RQT_SUCCESS; } -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, int16_t offset2) { +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, int16_t offset2, int16_t assigned_unitid) { Sensor_t *sensor = sensor_by_nr(nr); if (!sensor) return HTTP_RQT_NOT_RECEIVED; @@ -212,6 +217,7 @@ int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userde sensor->divider = divider; sensor->offset_mv = offset_mv; sensor->offset2 = offset2; + sensor->assigned_unitid = assigned_unitid; if (userdef_unit) strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); else @@ -1816,7 +1822,7 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; #endif - case SENSOR_MQTT: return UNIT_USERDEF; + case SENSOR_MQTT: return sensor->assigned_unitid > 0?sensor->assigned_unitid:UNIT_USERDEF; case SENSOR_REMOTE: return sensor->unitid; case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; diff --git a/sensors.h b/sensors.h index 19192347c..e60f3b607 100644 --- a/sensors.h +++ b/sensors.h @@ -132,7 +132,8 @@ typedef struct Sensor { int16_t offset_mv; // offset millivolt - for custom sensor (before) int16_t offset2; // offset unit value 1/100 - for custom sensor (after): // sensorvalue = (read_value-offset_mv/1000) * factor / divider + offset2/100 - byte undef[16]; // for later + byte assigned_unitid; // unitid for userdef and mqtt sensors + byte undef[15]; // for later //unstored bool mqtt_init:1; bool mqtt_push:1; @@ -185,7 +186,7 @@ typedef struct ProgSensorAdjust { #define SENSORURL_TYPE_URL 0 //URL for Host/Path #define SENSORURL_TYPE_TOPIC 1 //TOPIC for MQTT -#define SENSORURL_TYPE_JSON 2 //JSON Filter for MQTT +#define SENSORURL_TYPE_FILTER 2 //JSON Filter for MQTT typedef struct SensorUrl { uint nr; @@ -229,8 +230,8 @@ uint16_t CRC16 (byte buf[], int len); //Sensor API functions: int sensor_delete(uint nr); int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, - char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags); -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, int16_t offset2); + char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags, int16_t assigned_unitid); +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, int16_t offset2, int16_t sensor_define_userdef); void sensor_load(); void sensor_save(); uint sensor_count(); From 2ad7ed878507e45f6888043ab00cf8a5ae0c90f5 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 15 Mar 2024 08:28:33 +0100 Subject: [PATCH 096/281] mqtt resubscribe every intervall --- main.cpp | 20 ++++++++++++-------- mqtt.cpp | 6 ++++++ mqtt.h | 1 + platformio.ini | 2 +- sensor_mqtt.cpp | 21 ++++++++++----------- sensors.cpp | 3 ++- 6 files changed, 32 insertions(+), 21 deletions(-) diff --git a/main.cpp b/main.cpp index 4cd2926ab..48ea7c442 100644 --- a/main.cpp +++ b/main.cpp @@ -1448,9 +1448,12 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { if (!ifttt_enabled && !os.mqtt.enabled()) return; + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + char *dname = strdup(tmp_buffer); + if (ifttt_enabled) { strcpy_P(postval, PSTR("{\"value1\":\"On site [")); - os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval)); + strcat(postval, dname); strcat_P(postval, PSTR("], ")); } @@ -1463,7 +1466,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { case NOTIFY_STATION_ON: if (os.mqtt.enabled()) { - sprintf_P(topic, PSTR("opensprinkler/station/%d"), lval); + sprintf_P(topic, PSTR("%s/station/%d"), dname, lval); sprintf_P(payload, PSTR("{\"state\":1,\"duration\":%d}"), (int)fval); } @@ -1474,7 +1477,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { case NOTIFY_STATION_OFF: if (os.mqtt.enabled()) { - sprintf_P(topic, PSTR("opensprinkler/station/%d"), lval); + sprintf_P(topic, PSTR("%s/station/%d"), dname, lval); if (os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { sprintf_P(payload, PSTR("{\"state\":0,\"duration\":%d,\"flow\":%d.%02d}"), (int)fval, (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); } else { @@ -1511,7 +1514,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { case NOTIFY_SENSOR1: if (os.mqtt.enabled()) { - strcpy_P(topic, PSTR("opensprinkler/sensor1")); + sprintf_P(topic, PSTR("%s/sensor1"), dname); sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval); } if (ifttt_enabled) { @@ -1523,7 +1526,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { case NOTIFY_SENSOR2: if (os.mqtt.enabled()) { - strcpy_P(topic, PSTR("opensprinkler/sensor2")); + sprintf_P(topic, PSTR("%s/sensor2"), dname); sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval); } if (ifttt_enabled) { @@ -1535,7 +1538,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { case NOTIFY_RAINDELAY: if (os.mqtt.enabled()) { - strcpy_P(topic, PSTR("opensprinkler/raindelay")); + sprintf_P(topic, PSTR("%s/raindelay"), dname); sprintf_P(payload, PSTR("{\"state\":%d}"), (int)fval); } if (ifttt_enabled) { @@ -1550,7 +1553,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { volume = (volume<<8)+os.iopts[IOPT_PULSE_RATE_0]; volume = lval*volume; if (os.mqtt.enabled()) { - strcpy_P(topic, PSTR("opensprinkler/sensor/flow")); + sprintf_P(topic, PSTR("%s/sensor/flow"), dname); sprintf_P(payload, PSTR("{\"count\":%u,\"volume\":%d.%02d}"), lval, (int)volume/100, (int)volume%100); } if (ifttt_enabled) { @@ -1578,7 +1581,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { case NOTIFY_REBOOT: if (os.mqtt.enabled()) { - strcpy_P(topic, PSTR("opensprinkler/system")); + sprintf_P(topic, PSTR("%s/system"), dname); strcpy_P(payload, PSTR("{\"state\":\"started\"}")); } if (ifttt_enabled) { @@ -1607,6 +1610,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { } break; } + free(dname); if (os.mqtt.enabled() && strlen(topic) && strlen(payload)) os.mqtt.publish(topic, payload); diff --git a/mqtt.cpp b/mqtt.cpp index 3a5f4c845..27c6f0950 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -290,6 +290,12 @@ int OSMqtt::_loop(void) { return mqtt_client->state(); } +bool OSMqtt::connected(void) { + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0 || !_connected()) + return false; + return mqtt_client->connected(); +} + bool OSMqtt::subscribe(const char *topic) { DEBUG_PRINTLN("subscribe1"); if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0 || !_connected()) return false; diff --git a/mqtt.h b/mqtt.h index 995f61cef..43256e7cd 100644 --- a/mqtt.h +++ b/mqtt.h @@ -56,6 +56,7 @@ class OSMqtt { static void publish(const char *topic, const char *payload); static void loop(void); + static bool connected(); static bool subscribe(const char *topic); static bool unsubscribe(const char *topic); #if defined(ARDUINO) diff --git a/platformio.ini b/platformio.ini index e293b1e3b..e25278c8d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -15,7 +15,7 @@ src_dir = . include_dir = . -[env:d1_mini]"" +[env:d1_mini] platform = espressif8266@4.2.1 ;platform = https://github.com/platformio/platform-espressif8266.git board = d1_mini diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 9293e2d0a..7b8ad277c 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -150,8 +150,10 @@ void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mos } int read_sensor_mqtt(Sensor_t *sensor) { - sensor->flags.data_ok = false; - if (!sensor->mqtt_init && os.mqtt.enabled) { + if (!os.mqtt.enabled() || !os.mqtt.connected()) { + sensor->flags.data_ok = false; + sensor->mqtt_init = false; + } else { DEBUG_PRINT("read_sensor_mqtt1: "); DEBUG_PRINTLN(sensor->name); char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); @@ -159,8 +161,7 @@ int read_sensor_mqtt(Sensor_t *sensor) { os.mqtt.setCallback(&sensor_mqtt_callback); DEBUG_PRINT("subscribe: "); DEBUG_PRINTLN(topic); - if (!os.mqtt.subscribe(topic)) - DEBUG_PRINTLN("error subscribe!!"); + os.mqtt.subscribe(topic); sensor->mqtt_init = true; } } @@ -172,13 +173,11 @@ void sensor_mqtt_subscribe(uint nr, uint type, char *urlstr) { if (urlstr && urlstr[0] && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { DEBUG_PRINT("sensor_mqtt_subscribe1: "); DEBUG_PRINTLN(sensor->name); - if (os.mqtt.subscribe(urlstr)) { - os.mqtt.setCallback(&sensor_mqtt_callback); - DEBUG_PRINT("subscribe: "); - DEBUG_PRINTLN(urlstr); - sensor->mqtt_init = true; - } - else DEBUG_PRINTLN("error subscribe!!"); + DEBUG_PRINT("subscribe: "); + DEBUG_PRINTLN(urlstr); + os.mqtt.subscribe(urlstr); + os.mqtt.setCallback(&sensor_mqtt_callback); + sensor->mqtt_init = true; } } diff --git a/sensors.cpp b/sensors.cpp index d57e623f8..68fe2600d 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -702,7 +702,8 @@ void push_message(Sensor_t *sensor) { char* postval = tmp_buffer; if (os.mqtt.enabled()) { - strcpy_P(topic, PSTR("opensprinkler/analogsensor/")); + os.sopt_load(SOPT_DEVICE_NAME, topic); + strncat_P(topic, PSTR("/analogsensor/"), sizeof(topic)-1); strncat(topic, sensor->name, sizeof(topic)-1); sprintf_P(payload, PSTR("{\"nr\":%u,\"type\":%u,\"data_ok\":%u,\"time\":%u,\"value\":%d.%02d,\"unit\":\"%s\"}"), sensor->nr, sensor->type, sensor->flags.data_ok, sensor->last_read, (int)sensor->last_data, (int)(sensor->last_data*100)%100, getSensorUnit(sensor)); From 4821f6e18b31e3c8d519318cfd45dd4a1feed175 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 17 Mar 2024 15:36:34 +0100 Subject: [PATCH 097/281] Backup+Restore, mqtt resubscribe. Version now 2.3.1(150) --- defines.h | 2 +- opensprinkler_server.cpp | 50 ++++++++++++++++++++-------------------- sensor_mqtt.cpp | 9 ++++---- sensor_mqtt.h | 4 ++-- sensors.cpp | 8 +++---- sensors.h | 8 +++---- 6 files changed, 41 insertions(+), 40 deletions(-) diff --git a/defines.h b/defines.h index 0fab28955..005e21d7f 100644 --- a/defines.h +++ b/defines.h @@ -33,7 +33,7 @@ typedef unsigned long ulong; #define TMP_BUFFER_SIZE 255 // scratch buffer size /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 230 // Firmware version: 220 means 2.2.0 +#define OS_FW_VERSION 231 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index cabb48b18..ff513998b 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3104,6 +3104,16 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { char *p = get_buffer; #endif +#define BACKUP_SENSORS 1 +#define BACKUP_ADJUSTMENTS 2 + + //Backup type: 0=no backup 1=Sensors 2=Adjustments 3=Sensors+Adjustments + int backup = BACKUP_SENSORS|BACKUP_ADJUSTMENTS; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("backup"), true)) { + backup = strtol(tmp_buffer, NULL, 0); + } + + #if defined(ESP8266) // 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(). @@ -3113,33 +3123,25 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { print_header(); #endif - bfill.emit_p(PSTR("\"sensors\":[")); - sensorconfig_json(OTF_PARAMS); - bfill.emit_p(PSTR("],")); - send_packet(OTF_PARAMS); - bfill.emit_p(PSTR("\"progadjust\":[")); - progconfig_json(); - bfill.emit_p(PSTR("]")); + ulong time = os.now_tz(); + bfill.emit_p(PSTR("{\"backup\":$D,\"time\":$L,\"os-version\":$D,\"minor\":$D"), backup, time, OS_FW_VERSION, OS_FW_MINOR); + if (backup & BACKUP_SENSORS) { + bfill.emit_p(PSTR(",\"sensors\":[")); + sensorconfig_json(OTF_PARAMS); + bfill.emit_p(PSTR("]")); + } send_packet(OTF_PARAMS); + if (backup & BACKUP_ADJUSTMENTS) { + bfill.emit_p(PSTR(",\"progadjust\":[")); + progconfig_json(); + bfill.emit_p(PSTR("]")); + } bfill.emit_p(PSTR("}")); + send_packet(OTF_PARAMS); + handle_return(HTML_OK); } -/** - * sy - * @brief restore sensor configuration - * - */ -void server_sensorconfig_restore(OTF_PARAMS_DEF) { -#if defined(ESP8266) - if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif - - - -} typedef void (*URLHandler)(OTF_PARAMS_DEF); @@ -3190,7 +3192,6 @@ const char _url_keys[] PROGMEM = "du" "sh" "sx" - "sy" #if defined(ARDUINO) "db" //"ff" @@ -3238,7 +3239,6 @@ URLHandler urls[] = { server_usage, // du server_sensorprog_types, // sh server_sensorconfig_backup, // sx - server_sensorconfig_restore, // sy #if defined(ARDUINO) server_json_debug, // db //server_fill_files, @@ -3328,7 +3328,7 @@ void start_server_client() { uri[1]=pgm_read_byte(_url_keys+2*i); uri[2]=pgm_read_byte(_url_keys+2*i+1); otf->on(uri, urls[i]); - } + } otf_callbacksInitialised = true; } update_server->begin(); diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 7b8ad277c..757e6afd3 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -82,9 +82,10 @@ void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mos if (!mtopic || !payload) return; payload[length] = 0; + time_t now = os.now_tz(); Sensor_t *sensor = getSensors(); while (sensor) { - if (sensor->type == SENSOR_MQTT) { + if (sensor->type == SENSOR_MQTT && sensor->last_read != now) { char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); DEBUG_PRINT("mtopic: "); DEBUG_PRINTLN(mtopic); DEBUG_PRINT("topic: "); DEBUG_PRINTLN(topic); @@ -125,7 +126,7 @@ void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mos DEBUG_PRINTLN(buf); sensor->last_data = atof(buf); sensor->flags.data_ok = true; - sensor->last_read = os.now_tz(); + sensor->last_read = now; DEBUG_PRINTLN("sensor_mqtt_callback2"); sensorlog_add(LOG_STD, sensor, sensor->last_read); @@ -168,7 +169,7 @@ int read_sensor_mqtt(Sensor_t *sensor) { return HTTP_RQT_NOT_RECEIVED; } -void sensor_mqtt_subscribe(uint nr, uint type, char *urlstr) { +void sensor_mqtt_subscribe(uint nr, uint type, const char *urlstr) { Sensor_t* sensor = sensor_by_nr(nr); if (urlstr && urlstr[0] && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { DEBUG_PRINT("sensor_mqtt_subscribe1: "); @@ -181,7 +182,7 @@ void sensor_mqtt_subscribe(uint nr, uint type, char *urlstr) { } } -void sensor_mqtt_unsubscribe(uint nr, uint type, char *urlstr) { +void sensor_mqtt_unsubscribe(uint nr, uint type, const char *urlstr) { Sensor_t* sensor = sensor_by_nr(nr); if (urlstr && urlstr[0] && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { DEBUG_PRINT("sensor_mqtt_unsubscribe1: "); diff --git a/sensor_mqtt.h b/sensor_mqtt.h index 27093f6bb..303477c4b 100644 --- a/sensor_mqtt.h +++ b/sensor_mqtt.h @@ -33,7 +33,7 @@ void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mos int read_sensor_mqtt(Sensor_t *sensor); -void sensor_mqtt_subscribe(uint nr, uint type, char *urlstr); -void sensor_mqtt_unsubscribe(uint nr, uint type, char *urlstr); +void sensor_mqtt_subscribe(uint nr, uint type, const char *urlstr); +void sensor_mqtt_unsubscribe(uint nr, uint type, const char *urlstr); #endif // _SENSOR_MQTT_H diff --git a/sensors.cpp b/sensors.cpp index 68fe2600d..6199a2fcf 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -135,8 +135,8 @@ int sensor_delete(uint nr) { * @param port * @param id */ -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, - char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags, int16_t assigned_unitid) { +int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags, int16_t assigned_unitid) { if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; @@ -208,7 +208,7 @@ int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint return HTTP_RQT_SUCCESS; } -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, int16_t offset2, int16_t assigned_unitid) { +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, const char *userdef_unit, int16_t offset_mv, int16_t offset2, int16_t assigned_unitid) { Sensor_t *sensor = sensor_by_nr(nr); if (!sensor) return HTTP_RQT_NOT_RECEIVED; @@ -2021,7 +2021,7 @@ bool SensorUrl_delete(uint nr, uint type) { return false; } -bool SensorUrl_add(uint nr, uint type, char *urlstr) { +bool SensorUrl_add(uint nr, uint type, const char *urlstr) { if (!urlstr || !strlen(urlstr)) { //empty string? delete! return SensorUrl_delete(nr, type); } diff --git a/sensors.h b/sensors.h index e60f3b607..f3de78146 100644 --- a/sensors.h +++ b/sensors.h @@ -229,9 +229,9 @@ uint16_t CRC16 (byte buf[], int len); //Sensor API functions: int sensor_delete(uint nr); -int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, - char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags, int16_t assigned_unitid); -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, char *userdef_unit, int16_t offset_mv, int16_t offset2, int16_t sensor_define_userdef); +int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags, int16_t assigned_unitid); +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, const char *userdef_unit, int16_t offset_mv, int16_t offset2, int16_t sensor_define_userdef); void sensor_load(); void sensor_save(); uint sensor_count(); @@ -282,7 +282,7 @@ void push_message(Sensor_t *sensor); void SensorUrl_load(); void SensorUrl_save(); bool SensorUrl_delete(uint nr, uint type); -bool SensorUrl_add(uint nr, uint type, char *urlstr); +bool SensorUrl_add(uint nr, uint type, const char *urlstr); char *SensorUrl_get(uint nr, uint type); #if defined(ESP8266) From 9531378ca5a9c65ec714dd3e55d156df2df28ac9 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 17 Mar 2024 23:23:28 +0100 Subject: [PATCH 098/281] =?UTF-8?q?OSPi=20Sensor=20Update=20f=C3=BCr=20ADS?= =?UTF-8?q?1115=20/=20PCF8591=20(uncomplete)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build2.sh~ | 31 + opensprinkler_server.cpp | 5 + ospi-analog/driver_ads1115.c | 1464 ++++++++++++++++++++++++ ospi-analog/driver_ads1115.h | 691 +++++++++++ ospi-analog/driver_ads1115_interface.c | 138 +++ ospi-analog/driver_ads1115_interface.h | 120 ++ ospi-analog/driver_pcf8591.c | 968 ++++++++++++++++ ospi-analog/driver_pcf8591.h | 478 ++++++++ ospi-analog/driver_pcf8591_interface.c | 136 +++ ospi-analog/driver_pcf8591_interface.h | 118 ++ ospi-analog/iic.c | 365 ++++++ ospi-analog/iic.h | 167 +++ sensor_ospi_ads1115.cpp | 132 +++ sensor_ospi_ads1115.h | 39 + sensor_ospi_pcf8591.cpp | 125 ++ sensor_ospi_pcf8591.h | 39 + 16 files changed, 5016 insertions(+) create mode 100755 build2.sh~ create mode 100644 ospi-analog/driver_ads1115.c create mode 100644 ospi-analog/driver_ads1115.h create mode 100644 ospi-analog/driver_ads1115_interface.c create mode 100644 ospi-analog/driver_ads1115_interface.h create mode 100644 ospi-analog/driver_pcf8591.c create mode 100644 ospi-analog/driver_pcf8591.h create mode 100644 ospi-analog/driver_pcf8591_interface.c create mode 100644 ospi-analog/driver_pcf8591_interface.h create mode 100644 ospi-analog/iic.c create mode 100644 ospi-analog/iic.h create mode 100644 sensor_ospi_ads1115.cpp create mode 100644 sensor_ospi_ads1115.h create mode 100644 sensor_ospi_pcf8591.cpp create mode 100644 sensor_ospi_pcf8591.h diff --git a/build2.sh~ b/build2.sh~ new file mode 100755 index 000000000..030d69a5d --- /dev/null +++ b/build2.sh~ @@ -0,0 +1,31 @@ +#!/bin/bash + +echo "Compiling firmware..." + +ADS1115="" +ADS1115FILES="" +PCF8591="" +PCF8591FILES="" +LIB="" + +i2cdetect -y 1 |grep "48 --" >/dev/null +if [ "$?" -eq 0 ]; then + echo "found ADS1115" + ADS1115="-DADS1115" + ADS1115FILES="./ospi-analog/driver_ads1115*.c ./ospi-analog/iic.c" + LIB="-lgpiod" +fi + +i2cdetect -y 1 |grep "48 49" >/dev/null +if [ "$?" -eq 0 ]; then + echo "found PCF8591" + PCF8591="-DPCF8591" + PCF8591FILES="./ospi-analog/driver_pcf8591*.c ./ospi-analog/iic.c" + LIB="-lgpiod" +fi + +g++ -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 -std=c++14 \ + main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp \ + utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensor*.cpp \ + $ADS1115FILES $PCF8591FILES \ + -li2c -lpthread -lmosquitto -lcrypto -lssl $LIB diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 72056ad73..0bb4351db 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2777,10 +2777,13 @@ const int sensor_types[] = { SENSOR_USERDEF, #endif #else + +#if defined ADS1115||PCF8591 SENSOR_OSPI_ANALOG, SENSOR_OSPI_ANALOG_P, SENSOR_OSPI_ANALOG_SMT50_MOIS, SENSOR_OSPI_ANALOG_SMT50_TEMP, +#endif #endif SENSOR_REMOTE, SENSOR_WEATHER_TEMP_F, @@ -2815,10 +2818,12 @@ const char* sensor_names[] = { "OpenSprinkler analog extension board 2xADS1x15 x8 - user defined sensor", #endif #else +#if defined ADS1115||PCF8591 "OSPi analog input - voltage mode 0..3.3V", "OSPi analog input - 0.3.3V to 0..100%", "OSPi analog input - SMT50 moisture mode", "OSPi analog input - SMT50 temperature mode", +#endif #endif "Remote sensor of an remote opensprinkler", "Weather data - temperature (°F)", diff --git a/ospi-analog/driver_ads1115.c b/ospi-analog/driver_ads1115.c new file mode 100644 index 000000000..c5807d23c --- /dev/null +++ b/ospi-analog/driver_ads1115.c @@ -0,0 +1,1464 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_ads1115.c + * @brief driver ads1115 source file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-13 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/13 2.0 Shifeng Li format the code + *
2020/10/13 1.0 Shifeng Li first upload + *
+ */ + +#include "driver_ads1115.h" + +/** + * @brief chip information definition + */ +#define CHIP_NAME "Texas Instruments ADS1115" /**< chip name */ +#define MANUFACTURER_NAME "Texas Instruments" /**< manufacturer name */ +#define SUPPLY_VOLTAGE_MIN 2.0f /**< chip min supply voltage */ +#define SUPPLY_VOLTAGE_MAX 5.5f /**< chip max supply voltage */ +#define MAX_CURRENT 0.2f /**< chip max current */ +#define TEMPERATURE_MIN -40.0f /**< chip min operating temperature */ +#define TEMPERATURE_MAX 125.0f /**< chip max operating temperature */ +#define DRIVER_VERSION 2000 /**< driver version */ + +/** + * @brief chip register definition + */ +#define ADS1115_REG_CONVERT 0x00 /**< adc result register */ +#define ADS1115_REG_CONFIG 0x01 /**< chip config register */ +#define ADS1115_REG_LOWRESH 0x02 /**< interrupt low threshold register */ +#define ADS1115_REG_HIGHRESH 0x03 /**< interrupt high threshold register */ + +/** + * @brief iic address definition + */ +#define ADS1115_ADDRESS1 (0x48 << 1) /**< iic address 1 */ +#define ADS1115_ADDRESS2 (0x49 << 1) /**< iic address 2 */ +#define ADS1115_ADDRESS3 (0x4A << 1) /**< iic address 3 */ +#define ADS1115_ADDRESS4 (0x4B << 1) /**< iic address 4 */ + +/** + * @brief read multiple bytes + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[out] *data points to a data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +static uint8_t a_ads1115_iic_multiple_read(ads1115_handle_t *handle, uint8_t reg, int16_t *data) +{ + uint8_t buf[2]; + + memset(buf, 0, sizeof(uint8_t) * 2); /* clear the buffer */ + if (handle->iic_read(handle->iic_addr, reg, (uint8_t *)buf, 2) == 0) /* read data */ + { + *data = (uint16_t)(((uint16_t)buf[0] << 8) | buf[1]); /* set data */ + + return 0; /* success return 0 */ + } + else + { + return 1; /* return error */ + } +} + +/** + * @brief write multiple bytes + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[in] data is the sent data + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +static uint8_t a_ads1115_iic_multiple_write(ads1115_handle_t *handle, uint8_t reg, uint16_t data) +{ + uint8_t buf[2]; + + buf[0] = (data >> 8) & 0xFF; /* set MSB */ + buf[1] = data & 0xFF; /* set LSB */ + if (handle->iic_write(handle->iic_addr, reg, (uint8_t *)buf, 2) != 0) /* write data */ + { + return 1; /* return error */ + } + else + { + return 0; /* success return 0 */ + } +} + +/** + * @brief initialize the chip + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 iic initialization failed + * - 2 handle is NULL + * - 3 linked functions is NULL + * @note none + */ +uint8_t ads1115_init(ads1115_handle_t *handle) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->debug_print == NULL) /* check debug_print */ + { + return 3; /* return error */ + } + if (handle->iic_init == NULL) /* check iic_init */ + { + handle->debug_print("ads1115: iic_init is null.\n"); /* iic_init is null */ + + return 3; /* return error */ + } + if (handle->iic_deinit == NULL) /* check iic_deinit */ + { + handle->debug_print("ads1115: iic_deinit is null.\n"); /* iic_deinit is null */ + + return 3; /* return error */ + } + if (handle->iic_read == NULL) /* check iic_read */ + { + handle->debug_print("ads1115: iic_read is null.\n"); /* iic_read is null */ + + return 3; /* return error */ + } + if (handle->iic_write == NULL) /* check iic_write */ + { + handle->debug_print("ads1115: iic_write is null.\n"); /* iic_write is null */ + + return 3; /* return error */ + } + if (handle->delay_ms == NULL) /* check delay_ms */ + { + handle->debug_print("ads1115: delay_ms is null.\n"); /* delay_ms is null */ + + return 3; /* return error */ + } + + if (handle->iic_init() != 0) /* iic init */ + { + handle->debug_print("ads1115: iic init failed.\n"); /* iic init failed */ + + return 1; /* return error */ + } + handle->inited = 1; /* flag inited */ + + return 0; /* success return 0 */ +} + +/** + * @brief close the chip + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 iic deinit failed + * - 2 handle is NULL + * - 3 handle is not initialized + * - 4 power down failed + * @note none + */ +uint8_t ads1115_deinit(ads1115_handle_t *handle) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 4; /* return error */ + } + conf &= ~(0x01 << 8); /* clear bit */ + conf |= 1 << 8; /* set stop continues read */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 4; /* return error */ + } + res = handle->iic_deinit(); /* close iic */ + if (res != 0) /* check the result */ + { + handle->debug_print("ads1115: iic deinit failed.\n"); /* iic deinit failed */ + + return 1; /* return error */ + } + handle->inited = 0; /* flag close */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc channel + * @param[in] *handle points to an ads1115 handle structure + * @param[in] channel is the adc channel + * @return status code + * - 0 success + * - 1 set channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_channel(ads1115_handle_t *handle, ads1115_channel_t channel) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x07 << 12); /* clear channel */ + conf |= (channel & 0x07) << 12; /* set channel */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc channel + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *channel points to a channel buffer + * @return status code + * - 0 success + * - 1 get channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_channel(ads1115_handle_t *handle, ads1115_channel_t *channel) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *channel = (ads1115_channel_t)((conf >> 12) & 0x07); /* get channel */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc range + * @param[in] *handle points to an ads1115 handle structure + * @param[in] range is the adc max voltage range + * @return status code + * - 0 success + * - 1 set range failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_range(ads1115_handle_t *handle, ads1115_range_t range) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x07 << 9); /* clear range */ + conf |= (range & 0x07) << 9; /* set range */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc range + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *range points to a voltage range buffer + * @return status code + * - 0 success + * - 1 get range failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_range(ads1115_handle_t *handle, ads1115_range_t *range) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the alert pin active status + * @param[in] *handle points to an ads1115 handle structure + * @param[in] pin is the alert active status + * @return status code + * - 0 success + * - 1 set alert pin failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_alert_pin(ads1115_handle_t *handle, ads1115_pin_t pin) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(1 << 3); /* clear alert pin */ + conf |= (pin & 0x01) << 3; /* set alert pin */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the alert pin active status + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *pin points to a pin alert active status buffer + * @return status code + * - 0 success + * - 1 get alert pin failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_alert_pin(ads1115_handle_t *handle, ads1115_pin_t *pin) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *pin = (ads1115_pin_t)((conf >> 3) & 0x01); /* get alert pin */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the interrupt compare mode + * @param[in] *handle points to an ads1115 handle structure + * @param[in] compare is the interrupt compare mode + * @return status code + * - 0 success + * - 1 set compare mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare_mode(ads1115_handle_t *handle, ads1115_compare_t compare) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(1 << 4); /* clear compare mode */ + conf |= (compare & 0x01) << 4; /* set compare mode */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the interrupt compare mode + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *compare points to an interrupt compare mode buffer + * @return status code + * - 0 success + * - 1 get compare mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare_mode(ads1115_handle_t *handle, ads1115_compare_t *compare) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *compare = (ads1115_compare_t)((conf >> 4) & 0x01); /* get compare mode */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the sample rate + * @param[in] *handle points to an ads1115 handle structure + * @param[in] rate is the adc sample rate + * @return status code + * - 0 success + * - 1 set rate failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_rate(ads1115_handle_t *handle, ads1115_rate_t rate) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x07 << 5); /* clear rate */ + conf |= (rate & 0x07) << 5; /* set rate */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return */ +} + +/** + * @brief get the sample rate + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *rate points to an adc sample rate buffer + * @return status code + * - 0 success + * - 1 get rate failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_rate(ads1115_handle_t *handle, ads1115_rate_t *rate) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *rate = (ads1115_rate_t)((conf >> 5) & 0x07); /* get rate */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the interrupt comparator queue + * @param[in] *handle points to an ads1115 handle structure + * @param[in] comparator_queue is the interrupt comparator queue + * @return status code + * - 0 success + * - 1 set comparator queue failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_comparator_queue(ads1115_handle_t *handle, ads1115_comparator_queue_t comparator_queue) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x03 << 0); /* clear comparator queue */ + conf |= (comparator_queue & 0x03) << 0; /* set comparator queue */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the interrupt comparator queue + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *comparator_queue points to an interrupt comparator queue + * @return status code + * - 0 success + * - 1 get comparator queue failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_comparator_queue(ads1115_handle_t *handle, ads1115_comparator_queue_t *comparator_queue) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *comparator_queue = (ads1115_comparator_queue_t)((conf >> 0) & 0x03); /* get comparator queue */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the iic address pin + * @param[in] *handle points to an ads1115 handle structure + * @param[in] addr_pin is the chip iic address pin + * @return status code + * - 0 success + * - 1 set addr pin failed + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_set_addr_pin(ads1115_handle_t *handle, ads1115_address_t addr_pin) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + + if (addr_pin == ADS1115_ADDR_GND) /* gnd */ + { + handle->iic_addr = ADS1115_ADDRESS1; /* set address 1 */ + } + else if (addr_pin == ADS1115_ADDR_VCC) /* vcc */ + { + handle->iic_addr = ADS1115_ADDRESS2; /* set address 2 */ + } + else if (addr_pin == ADS1115_ADDR_SDA) /* sda */ + { + handle->iic_addr = ADS1115_ADDRESS3; /* set address 3 */ + } + else if (addr_pin == ADS1115_ADDR_SCL) /* scl */ + { + handle->iic_addr = ADS1115_ADDRESS4; /* set address 4 */ + } + else + { + handle->debug_print("ads1115: addr_pin is invalid.\n"); /* addr_pin is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the iic address pin + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *addr_pin points to a chip iic address pin buffer + * @return status code + * - 0 success + * - 1 get addr pin failed + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_get_addr_pin(ads1115_handle_t *handle, ads1115_address_t *addr_pin) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + + if (handle->iic_addr == ADS1115_ADDRESS1) /* if address 1 */ + { + *addr_pin = ADS1115_ADDR_GND; /* set gnd */ + } + else if (handle->iic_addr == ADS1115_ADDRESS2) /* if address 2 */ + { + *addr_pin = ADS1115_ADDR_VCC; /* set vcc */ + } + else if (handle->iic_addr == ADS1115_ADDRESS3) /* if address 3 */ + { + *addr_pin = ADS1115_ADDR_SDA; /* set sda */ + } + else if (handle->iic_addr == ADS1115_ADDRESS4) /* set address 4 */ + { + *addr_pin = ADS1115_ADDR_SCL; /* set scl */ + } + else + { + handle->debug_print("ads1115: addr_pin is invalid.\n"); /* addr_pin is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief read data from the chip once + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *v points to a converted adc buffer + * @return status code + * - 0 success + * - 1 single read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_single_read(ads1115_handle_t *handle, int16_t *raw, float *v) +{ + uint8_t res; + uint8_t range; + uint16_t conf; + uint32_t timeout = 500; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range conf */ + conf &= ~(1 << 8); /* clear bit */ + conf |= 1 << 8; /* set single read */ + conf |= 1 << 15; /* start single read */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + while (timeout != 0) /* check timeout */ + { + handle->delay_ms(8); /* wait 8 ms */ + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + if ((conf & (1 << 15)) == (1 << 15)) /* check finished */ + { + break; /* break */ + } + timeout--; /* timeout-- */ + } + if (timeout == 0) /* check timeout */ + { + handle->debug_print("ads1115: read timeout.\n"); /* timeout */ + + return 1; /* return error */ + } + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONVERT, raw); /* read data */ + if (res != 0) /* check the result */ + { + handle->debug_print("ads1115: continues read failed.\n"); /* continues read failed */ + + return 1; /* return error */ + } + if (range == ADS1115_RANGE_6P144V) /* if 6.144V */ + { + *v = (float)(*raw) * 6.144f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_4P096V) /* if 4.096V */ + { + *v = (float)(*raw) * 4.096f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_2P048V) /* if 2.048V */ + { + *v = (float)(*raw) * 2.048f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_1P024V) /* if 1.024V */ + { + *v = (float)(*raw) * 1.024f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_0P512V) /* if 0.512V */ + { + *v = (float)(*raw) * 0.512f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_0P256V) /* if 0.256V */ + { + *v = (float)(*raw) * 0.256f / 32768.0f; /* get convert adc */ + } + else + { + handle->debug_print("ads1115: range is invalid.\n"); /* range is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief read data from the chip continuously + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *v points to a converted adc buffer + * @return status code + * - 0 success + * - 1 continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note this function can be used only after run ads1115_start_continuous_read + * and can be stopped by ads1115_stop_continuous_read + */ +uint8_t ads1115_continuous_read(ads1115_handle_t *handle,int16_t *raw, float *v) +{ + uint8_t res; + uint8_t range; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range conf */ + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONVERT, raw); /* read data */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: continuous read failed.\n"); /* continuous read failed */ + + return 1; /* return error */ + } + if (range == ADS1115_RANGE_6P144V) /* if 6.144V */ + { + *v = (float)(*raw) * 6.144f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_4P096V) /* if 4.096V */ + { + *v = (float)(*raw) * 4.096f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_2P048V) /* if 2.048V */ + { + *v = (float)(*raw) * 2.048f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_1P024V) /* if 1.024V */ + { + *v = (float)(*raw) * 1.024f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_0P512V) /* if 0.512V */ + { + *v = (float)(*raw) * 0.512f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_0P256V) /* if 0.256V */ + { + *v = (float)(*raw) * 0.256f / 32768.0f; /* get convert adc */ + } + else + { + handle->debug_print("ads1115: range is invalid.\n"); /* range is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief start the chip reading + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 start continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_start_continuous_read(ads1115_handle_t *handle) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x01 << 8); /* set start continuous read */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief stop the chip reading + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 stop continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_stop_continuous_read(ads1115_handle_t *handle) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x01 << 8); /* clear bit */ + conf |= 1 << 8; /* set stop continues read */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief enable or disable the interrupt compare + * @param[in] *handle points to an ads1115 handle structure + * @param[in] enable is a bool value + * @return status code + * - 0 success + * - 1 set compare failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare(ads1115_handle_t *handle, ads1115_bool_t enable) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x01 << 2); /* clear compare */ + conf |= enable << 2; /* set compare */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the interrupt compare status + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *enable points to a bool value buffer + * @return status code + * - 0 success + * - 1 get compare failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare(ads1115_handle_t *handle, ads1115_bool_t *enable) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *enable = (ads1115_bool_t)((conf >> 2) & 0x01); /* get compare */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the interrupt compare threshold + * @param[in] *handle points to an ads1115 handle structure + * @param[in] high_threshold is the interrupt high threshold + * @param[in] low_threshold is the interrupt low threshold + * @return status code + * - 0 success + * - 1 set compare threshold failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare_threshold(ads1115_handle_t *handle, int16_t high_threshold, int16_t low_threshold) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + if (a_ads1115_iic_multiple_write(handle, ADS1115_REG_HIGHRESH, high_threshold) != 0) /* write high threshold */ + { + handle->debug_print("ads1115: write high threshold failed.\n"); /* write high threshold failed */ + + return 1; /* return error */ + } + if (a_ads1115_iic_multiple_write(handle, ADS1115_REG_LOWRESH, low_threshold) != 0) /* write low threshold */ + { + handle->debug_print("ads1115: write low threshold failed.\n"); /* write low threshold failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the interrupt compare threshold + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *high_threshold points to an interrupt high threshold buffer + * @param[out] *low_threshold points to an interrupt low threshold buffer + * @return status code + * - 0 success + * - 1 get compare threshold failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare_threshold(ads1115_handle_t *handle, int16_t *high_threshold, int16_t *low_threshold) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + if (a_ads1115_iic_multiple_read(handle, ADS1115_REG_HIGHRESH, high_threshold) != 0) /* read high threshold */ + { + handle->debug_print("ads1115: read high threshold failed.\n"); /* read high threshold failed */ + + return 1; /* return error */ + } + if (a_ads1115_iic_multiple_read(handle, ADS1115_REG_LOWRESH, low_threshold) != 0) /* read low threshold */ + { + handle->debug_print("ads1115: read low threshold failed.\n"); /* read low threshold failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief convert a adc value to a register raw data + * @param[in] *handle points to an ads1115 handle structure + * @param[in] s is a converted adc value + * @param[out] *reg points to a register raw buffer + * @return status code + * - 0 success + * - 1 convert to register failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_convert_to_register(ads1115_handle_t *handle, float s, int16_t *reg) +{ + uint8_t res; + uint8_t range; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range conf */ + if (range == ADS1115_RANGE_6P144V) /* if 6.144V */ + { + *reg = (int16_t)(s * 32768.0f / 6.144f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_4P096V) /* if 4.096V */ + { + *reg = (int16_t)(s * 32768.0f / 4.096f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_2P048V) /* if 2.048V */ + { + *reg = (int16_t)(s * 32768.0f / 2.048f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_1P024V) /* if 1.024V */ + { + *reg = (int16_t)(s * 32768.0f / 1.024f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_0P512V) /* if 0.512V */ + { + *reg = (int16_t)(s * 32768.0f / 0.512f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_0P256V) /* if 0.256V */ + { + *reg = (int16_t)(s * 32768.0f / 0.256f); /* convert to reg */ + } + else + { + handle->debug_print("ads1115: range is invalid.\n"); /* range is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief convert a register raw data to a converted adc data + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the register raw data + * @param[out] *s points to a converted adc value buffer + * @return status code + * - 0 success + * - 1 convert to data failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_convert_to_data(ads1115_handle_t *handle, int16_t reg, float *s) +{ + uint8_t res; + uint8_t range; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range conf */ + if (range == ADS1115_RANGE_6P144V) /* if 6.144V */ + { + *s = (float)(reg) * 6.144f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_4P096V) /* if 4.096V */ + { + *s = (float)(reg) * 4.096f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_2P048V) /* if 2.048V */ + { + *s = (float)(reg) * 2.048f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_1P024V) /* if 1.024V */ + { + *s = (float)(reg) * 1.024f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_0P512V) /* if 0.512V */ + { + *s = (float)(reg) * 0.512f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_0P256V) /* if 0.256V */ + { + *s = (float)(reg) * 0.256f / 32768.0f; /* convert to data */ + } + else + { + handle->debug_print("ads1115: range is invalid.\n"); /* range is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief set the chip register + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[in] value is the data written to the register + * @return status code + * - 0 success + * - 1 write failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_reg(ads1115_handle_t *handle, uint8_t reg, int16_t value) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + return a_ads1115_iic_multiple_write(handle, reg, value); /* write reg */ +} + +/** + * @brief get the chip register + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[out] *value points to a read data buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_reg(ads1115_handle_t *handle, uint8_t reg, int16_t *value) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + return a_ads1115_iic_multiple_read(handle, reg, value); /* read reg */ +} + +/** + * @brief get chip's information + * @param[out] *info points to an ads1115 info structure + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_info(ads1115_info_t *info) +{ + if (info == NULL) /* check handle */ + { + return 2; /* return error */ + } + + memset(info, 0, sizeof(ads1115_info_t)); /* initialize ads1115 info structure */ + strncpy(info->chip_name, CHIP_NAME, 32); /* copy chip name */ + strncpy(info->manufacturer_name, MANUFACTURER_NAME, 32); /* copy manufacturer name */ + strncpy(info->interface, "IIC", 8); /* copy interface name */ + info->supply_voltage_min_v = SUPPLY_VOLTAGE_MIN; /* set minimal supply voltage */ + info->supply_voltage_max_v = SUPPLY_VOLTAGE_MAX; /* set maximum supply voltage */ + info->max_current_ma = MAX_CURRENT; /* set maximum current */ + info->temperature_max = TEMPERATURE_MAX; /* set minimal temperature */ + info->temperature_min = TEMPERATURE_MIN; /* set maximum temperature */ + info->driver_version = DRIVER_VERSION; /* set driver version */ + + return 0; /* success return 0 */ +} diff --git a/ospi-analog/driver_ads1115.h b/ospi-analog/driver_ads1115.h new file mode 100644 index 000000000..c1340f61e --- /dev/null +++ b/ospi-analog/driver_ads1115.h @@ -0,0 +1,691 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_ads1115.h + * @brief driver ads1115 header file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-13 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/13 2.0 Shifeng Li format the code + *
2020/10/13 1.0 Shifeng Li first upload + *
+ */ + +#ifndef DRIVER_ADS1115_H +#define DRIVER_ADS1115_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @defgroup ads1115_driver ads1115 driver function + * @brief ads1115 driver modules + * @{ + */ + +/** + * @addtogroup ads1115_base_driver + * @{ + */ + +/** + * @brief ads1115 address enumeration definition + */ +typedef enum +{ + ADS1115_ADDR_GND = 0x00, /**< ADDR pin connected to GND */ + ADS1115_ADDR_VCC = 0x01, /**< ADDR pin connected to VCC */ + ADS1115_ADDR_SDA = 0x02, /**< ADDR pin connected to SDA */ + ADS1115_ADDR_SCL = 0x03, /**< ADDR pin connected to SCL */ +} ads1115_address_t; + +/** + * @brief ads1115 bool enumeration definition + */ +typedef enum +{ + ADS1115_BOOL_FALSE = 0x00, /**< disable function */ + ADS1115_BOOL_TRUE = 0x01, /**< enable function */ +} ads1115_bool_t; + +/** + * @brief ads1115 range enumeration definition + */ +typedef enum +{ + ADS1115_RANGE_6P144V = 0x00, /**< 6.144V range */ + ADS1115_RANGE_4P096V = 0x01, /**< 4.096V range */ + ADS1115_RANGE_2P048V = 0x02, /**< 2.048V range */ + ADS1115_RANGE_1P024V = 0x03, /**< 1.024V range */ + ADS1115_RANGE_0P512V = 0x04, /**< 0.512V range */ + ADS1115_RANGE_0P256V = 0x05, /**< 0.256V range */ +} ads1115_range_t; + +/** + * @brief ads1115 channel rate enumeration definition + */ +typedef enum +{ + ADS1115_RATE_8SPS = 0x00, /**< 8 sample per second */ + ADS1115_RATE_16SPS = 0x01, /**< 16 sample per second */ + ADS1115_RATE_32SPS = 0x02, /**< 32 sample per second */ + ADS1115_RATE_64SPS = 0x03, /**< 64 sample per second */ + ADS1115_RATE_128SPS = 0x04, /**< 128 sample per second */ + ADS1115_RATE_250SPS = 0x05, /**< 250 sample per second */ + ADS1115_RATE_475SPS = 0x06, /**< 475 sample per second */ + ADS1115_RATE_860SPS = 0x07, /**< 860 sample per second */ +} ads1115_rate_t; + +/** + * @brief ads1115 channel enumeration definition + */ +typedef enum +{ + ADS1115_CHANNEL_AIN0_AIN1 = 0x00, /**< AIN0 and AIN1 pins */ + ADS1115_CHANNEL_AIN0_AIN3 = 0x01, /**< AIN0 and AIN3 pins */ + ADS1115_CHANNEL_AIN1_AIN3 = 0x02, /**< AIN1 and AIN3 pins */ + ADS1115_CHANNEL_AIN2_AIN3 = 0x03, /**< AIN2 and AIN3 pins */ + ADS1115_CHANNEL_AIN0_GND = 0x04, /**< AIN0 and GND pins */ + ADS1115_CHANNEL_AIN1_GND = 0x05, /**< AIN1 and GND pins */ + ADS1115_CHANNEL_AIN2_GND = 0x06, /**< AIN2 and GND pins */ + ADS1115_CHANNEL_AIN3_GND = 0x07, /**< AIN3 and GND pins */ +} ads1115_channel_t; + +/** + * @} + */ + +/** + * @addtogroup ads1115_interrupt_driver + * @{ + */ + +/** + * @brief ads1115 pin enumeration definition + */ +typedef enum +{ + ADS1115_PIN_LOW = 0x00, /**< set pin low */ + ADS1115_PIN_HIGH = 0x01, /**< set pin high */ +} ads1115_pin_t; + +/** + * @brief ads1115 compare mode enumeration definition + */ +typedef enum +{ + ADS1115_COMPARE_THRESHOLD = 0x00, /**< threshold compare interrupt mode */ + ADS1115_COMPARE_WINDOW = 0x01, /**< window compare interrupt mode */ +} ads1115_compare_t; + +/** + * @brief ads1115 comparator queue enumeration definition + */ +typedef enum +{ + ADS1115_COMPARATOR_QUEUE_1_CONV = 0x00, /**< comparator queue has 1 conv */ + ADS1115_COMPARATOR_QUEUE_2_CONV = 0x01, /**< comparator queue has 2 conv */ + ADS1115_COMPARATOR_QUEUE_4_CONV = 0x02, /**< comparator queue has 3 conv */ + ADS1115_COMPARATOR_QUEUE_NONE_CONV = 0x03, /**< comparator queue has no conv */ +} ads1115_comparator_queue_t; + +/** + * @} + */ + +/** + * @addtogroup ads1115_base_driver + * @{ + */ + +/** + * @brief ads1115 handle structure definition + */ +typedef struct ads1115_handle_s +{ + uint8_t iic_addr; /**< iic device address */ + uint8_t (*iic_init)(void); /**< point to an iic_init function address */ + uint8_t (*iic_deinit)(void); /**< point to an iic_deinit function address */ + uint8_t (*iic_read)(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); /**< point to an iic_read function address */ + uint8_t (*iic_write)(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); /**< point to an iic_write function address */ + void (*delay_ms)(uint32_t ms); /**< point to a delay_ms function address */ + void (*debug_print)(const char *const fmt, ...); /**< point to a debug_print function address */ + uint8_t inited; /**< inited flag */ +} ads1115_handle_t; + +/** + * @brief ads1115 information structure definition + */ +typedef struct ads1115_info_s +{ + char chip_name[32]; /**< chip name */ + char manufacturer_name[32]; /**< manufacturer name */ + char interface[8]; /**< chip interface name */ + float supply_voltage_min_v; /**< chip min supply voltage */ + float supply_voltage_max_v; /**< chip max supply voltage */ + float max_current_ma; /**< chip max current */ + float temperature_min; /**< chip min operating temperature */ + float temperature_max; /**< chip max operating temperature */ + uint32_t driver_version; /**< driver version */ +} ads1115_info_t; + +/** + * @} + */ + +/** + * @defgroup ads1115_link_driver ads1115 link driver function + * @brief ads1115 link driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief initialize ads1115_handle_t structure + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] STRUCTURE is ads1115_handle_t + * @note none + */ +#define DRIVER_ADS1115_LINK_INIT(HANDLE, STRUCTURE) memset(HANDLE, 0, sizeof(STRUCTURE)) + +/** + * @brief link iic_init function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to an iic_init function address + * @note none + */ +#define DRIVER_ADS1115_LINK_IIC_INIT(HANDLE, FUC) (HANDLE)->iic_init = FUC + +/** + * @brief link iic_deinit function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to an iic_deinit function address + * @note none + */ +#define DRIVER_ADS1115_LINK_IIC_DEINIT(HANDLE, FUC) (HANDLE)->iic_deinit = FUC + +/** + * @brief link iic_read function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to an iic_read function address + * @note none + */ +#define DRIVER_ADS1115_LINK_IIC_READ(HANDLE, FUC) (HANDLE)->iic_read = FUC + +/** + * @brief link iic_write function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to an iic_write function address + * @note none + */ +#define DRIVER_ADS1115_LINK_IIC_WRITE(HANDLE, FUC) (HANDLE)->iic_write = FUC + +/** + * @brief link delay_ms function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to a delay_ms function address + * @note none + */ +#define DRIVER_ADS1115_LINK_DELAY_MS(HANDLE, FUC) (HANDLE)->delay_ms = FUC + +/** + * @brief link debug_print function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to a debug_print function address + * @note none + */ +#define DRIVER_ADS1115_LINK_DEBUG_PRINT(HANDLE, FUC) (HANDLE)->debug_print = FUC + +/** + * @} + */ + +/** + * @defgroup ads1115_base_driver ads1115 base driver function + * @brief ads1115 base driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief get chip's information + * @param[out] *info points to an ads1115 info structure + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_info(ads1115_info_t *info); + +/** + * @brief set the iic address pin + * @param[in] *handle points to an ads1115 handle structure + * @param[in] addr_pin is the chip iic address pin + * @return status code + * - 0 success + * - 1 set addr pin failed + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_set_addr_pin(ads1115_handle_t *handle, ads1115_address_t addr_pin); + +/** + * @brief get the iic address pin + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *addr_pin points to a chip iic address pin buffer + * @return status code + * - 0 success + * - 1 get addr pin failed + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_get_addr_pin(ads1115_handle_t *handle, ads1115_address_t *addr_pin); + +/** + * @brief initialize the chip + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 iic initialization failed + * - 2 handle is NULL + * - 3 linked functions is NULL + * @note none + */ +uint8_t ads1115_init(ads1115_handle_t *handle); + +/** + * @brief close the chip + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 iic deinit failed + * - 2 handle is NULL + * - 3 handle is not initialized + * - 4 power down failed + * @note none + */ +uint8_t ads1115_deinit(ads1115_handle_t *handle); + +/** + * @brief read data from the chip once + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *v points to a converted adc buffer + * @return status code + * - 0 success + * - 1 single read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_single_read(ads1115_handle_t *handle, int16_t *raw, float *v); + +/** + * @brief start the chip reading + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 start continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_start_continuous_read(ads1115_handle_t *handle); + +/** + * @brief stop the chip reading + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 stop continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_stop_continuous_read(ads1115_handle_t *handle); + +/** + * @brief read data from the chip continuously + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *v points to a converted adc buffer + * @return status code + * - 0 success + * - 1 continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note this function can be used only after run ads1115_start_continuous_read + * and can be stopped by ads1115_stop_continuous_read + */ +uint8_t ads1115_continuous_read(ads1115_handle_t *handle,int16_t *raw, float *v); + +/** + * @brief set the adc channel + * @param[in] *handle points to an ads1115 handle structure + * @param[in] channel is the adc channel + * @return status code + * - 0 success + * - 1 set channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_channel(ads1115_handle_t *handle, ads1115_channel_t channel); + +/** + * @brief get the adc channel + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *channel points to a channel buffer + * @return status code + * - 0 success + * - 1 get channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_channel(ads1115_handle_t *handle, ads1115_channel_t *channel); + +/** + * @brief set the adc range + * @param[in] *handle points to an ads1115 handle structure + * @param[in] range is the adc max voltage range + * @return status code + * - 0 success + * - 1 set range failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_range(ads1115_handle_t *handle, ads1115_range_t range); + +/** + * @brief get the adc range + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *range points to a voltage range buffer + * @return status code + * - 0 success + * - 1 get range failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_range(ads1115_handle_t *handle, ads1115_range_t *range); + +/** + * @brief set the sample rate + * @param[in] *handle points to an ads1115 handle structure + * @param[in] rate is the adc sample rate + * @return status code + * - 0 success + * - 1 set rate failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_rate(ads1115_handle_t *handle, ads1115_rate_t rate); + +/** + * @brief get the sample rate + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *rate points to an adc sample rate buffer + * @return status code + * - 0 success + * - 1 get rate failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_rate(ads1115_handle_t *handle, ads1115_rate_t *rate); + +/** + * @} + */ + +/** + * @defgroup ads1115_interrupt_driver ads1115 interrupt driver function + * @brief ads1115 interrupt driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief set the alert pin active status + * @param[in] *handle points to an ads1115 handle structure + * @param[in] pin is the alert active status + * @return status code + * - 0 success + * - 1 set alert pin failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_alert_pin(ads1115_handle_t *handle, ads1115_pin_t pin); + +/** + * @brief get the alert pin active status + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *pin points to a pin alert active status buffer + * @return status code + * - 0 success + * - 1 get alert pin failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_alert_pin(ads1115_handle_t *handle, ads1115_pin_t *pin); + +/** + * @brief set the interrupt compare mode + * @param[in] *handle points to an ads1115 handle structure + * @param[in] compare is the interrupt compare mode + * @return status code + * - 0 success + * - 1 set compare mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare_mode(ads1115_handle_t *handle, ads1115_compare_t compare); + +/** + * @brief get the interrupt compare mode + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *compare points to an interrupt compare mode buffer + * @return status code + * - 0 success + * - 1 get compare mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare_mode(ads1115_handle_t *handle, ads1115_compare_t *compare); + +/** + * @brief set the interrupt comparator queue + * @param[in] *handle points to an ads1115 handle structure + * @param[in] comparator_queue is the interrupt comparator queue + * @return status code + * - 0 success + * - 1 set comparator queue failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_comparator_queue(ads1115_handle_t *handle, ads1115_comparator_queue_t comparator_queue); + +/** + * @brief get the interrupt comparator queue + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *comparator_queue points to an interrupt comparator queue + * @return status code + * - 0 success + * - 1 get comparator queue failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_comparator_queue(ads1115_handle_t *handle, ads1115_comparator_queue_t *comparator_queue); + +/** + * @brief enable or disable the interrupt compare + * @param[in] *handle points to an ads1115 handle structure + * @param[in] enable is a bool value + * @return status code + * - 0 success + * - 1 set compare failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare(ads1115_handle_t *handle, ads1115_bool_t enable); + +/** + * @brief get the interrupt compare status + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *enable points to a bool value buffer + * @return status code + * - 0 success + * - 1 get compare failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare(ads1115_handle_t *handle, ads1115_bool_t *enable); + +/** + * @brief set the interrupt compare threshold + * @param[in] *handle points to an ads1115 handle structure + * @param[in] high_threshold is the interrupt high threshold + * @param[in] low_threshold is the interrupt low threshold + * @return status code + * - 0 success + * - 1 set compare threshold failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare_threshold(ads1115_handle_t *handle, int16_t high_threshold, int16_t low_threshold); + +/** + * @brief get the interrupt compare threshold + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *high_threshold points to an interrupt high threshold buffer + * @param[out] *low_threshold points to an interrupt low threshold buffer + * @return status code + * - 0 success + * - 1 get compare threshold failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare_threshold(ads1115_handle_t *handle, int16_t *high_threshold, int16_t *low_threshold); + +/** + * @brief convert a adc value to a register raw data + * @param[in] *handle points to an ads1115 handle structure + * @param[in] s is a converted adc value + * @param[out] *reg points to a register raw buffer + * @return status code + * - 0 success + * - 1 convert to register failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_convert_to_register(ads1115_handle_t *handle, float s, int16_t *reg); + +/** + * @brief convert a register raw data to a converted adc data + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the register raw data + * @param[out] *s points to a converted adc value buffer + * @return status code + * - 0 success + * - 1 convert to data failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_convert_to_data(ads1115_handle_t *handle, int16_t reg, float *s); + +/** + * @} + */ + +/** + * @defgroup ads1115_extend_driver ads1115 extend driver function + * @brief ads1115 extend driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief set the chip register + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[in] value is the data written to the register + * @return status code + * - 0 success + * - 1 write failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_reg(ads1115_handle_t *handle, uint8_t reg, int16_t value); + +/** + * @brief get the chip register + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[out] *value points to a read data buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_reg(ads1115_handle_t *handle, uint8_t reg, int16_t *value); + +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospi-analog/driver_ads1115_interface.c b/ospi-analog/driver_ads1115_interface.c new file mode 100644 index 000000000..f41b1855a --- /dev/null +++ b/ospi-analog/driver_ads1115_interface.c @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file raspberrypi4b_driver_ads1115_interface.c + * @brief raspberrypi4b driver ads1115 interface source file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-13 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/13 2.0 Shifeng Li format the code + *
2020/10/13 1.0 Shifeng Li first upload + *
+ */ + +#include "driver_ads1115_interface.h" +#include "iic.h" +#include + +/** + * @brief iic device name definition + */ +static const char* IIC_DEVICE_NAME = "/dev/i2c-1"; /**< iic device name */ + +/** + * @brief iic device handle definition + */ +static int gs_fd; /**< iic handle */ + +/** + * @brief interface iic bus init + * @return status code + * - 0 success + * - 1 iic init failed + * @note none + */ +uint8_t ads1115_interface_iic_init(void) +{ + return iic_init(IIC_DEVICE_NAME, &gs_fd); +} + +/** + * @brief interface iic bus deinit + * @return status code + * - 0 success + * - 1 iic deinit failed + * @note none + */ +uint8_t ads1115_interface_iic_deinit(void) +{ + return iic_deinit(gs_fd); +} + +/** + * @brief interface iic bus read + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +uint8_t ads1115_interface_iic_read(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len) +{ + return iic_read(gs_fd, addr, reg, buf, len); +} + +/** + * @brief interface iic bus write + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +uint8_t ads1115_interface_iic_write(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len) +{ + return iic_write(gs_fd, addr, reg, buf, len); +} + +/** + * @brief interface delay ms + * @param[in] ms + * @note none + */ +void ads1115_interface_delay_ms(uint32_t ms) +{ + usleep(ms * 1000); +} + +/** + * @brief interface print format data + * @param[in] fmt is the format data + * @note none + */ +void ads1115_interface_debug_print(const char *const fmt, ...) +{ + /** + char str[256]; + uint16_t len; + va_list args; + + memset((char *)str, 0, sizeof(char) * 256); + va_start(args, fmt); + vsnprintf((char *)str, 255, (char const *)fmt, args); + va_end(args); + + len = strlen((char *)str); + (void)printf((uint8_t *)str, len); + **/ +} diff --git a/ospi-analog/driver_ads1115_interface.h b/ospi-analog/driver_ads1115_interface.h new file mode 100644 index 000000000..f8f788dfc --- /dev/null +++ b/ospi-analog/driver_ads1115_interface.h @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_ads1115_interface.h + * @brief driver ads1115 interface header file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-13 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/13 2.0 Shifeng Li format the code + *
2020/10/13 1.0 Shifeng Li first upload + *
+ */ + +#ifndef DRIVER_ADS1115_INTERFACE_H +#define DRIVER_ADS1115_INTERFACE_H + +#include "driver_ads1115.h" + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @defgroup ads1115_interface_driver ads1115 interface driver function + * @brief ads1115 interface driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief interface iic bus init + * @return status code + * - 0 success + * - 1 iic init failed + * @note none + */ +uint8_t ads1115_interface_iic_init(void); + +/** + * @brief interface iic bus deinit + * @return status code + * - 0 success + * - 1 iic deinit failed + * @note none + */ +uint8_t ads1115_interface_iic_deinit(void); + +/** + * @brief interface iic bus read + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +uint8_t ads1115_interface_iic_read(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief interface iic bus write + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +uint8_t ads1115_interface_iic_write(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief interface delay ms + * @param[in] ms + * @note none + */ +void ads1115_interface_delay_ms(uint32_t ms); + +/** + * @brief interface print format data + * @param[in] fmt is the format data + * @note none + */ +void ads1115_interface_debug_print(const char *const fmt, ...); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospi-analog/driver_pcf8591.c b/ospi-analog/driver_pcf8591.c new file mode 100644 index 000000000..d2848b9c6 --- /dev/null +++ b/ospi-analog/driver_pcf8591.c @@ -0,0 +1,968 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_pcf8591.c + * @brief driver pcf8591 source file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-18 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/18 2.0 Shifeng Li format the code + *
2020/10/17 1.0 Shifeng Li first upload + *
+ */ + +#include "driver_pcf8591.h" + +/** + * @brief chip information definition + */ +#define CHIP_NAME "NXP PCF8591" /**< chip name */ +#define MANUFACTURER_NAME "NXP" /**< manufacturer name */ +#define SUPPLY_VOLTAGE_MIN 2.5f /**< chip min supply voltage */ +#define SUPPLY_VOLTAGE_MAX 6.0f /**< chip max supply voltage */ +#define MAX_CURRENT 50.0f /**< chip max current */ +#define TEMPERATURE_MIN -40.0f /**< chip min operating temperature */ +#define TEMPERATURE_MAX 85.0f /**< chip max operating temperature */ +#define DRIVER_VERSION 2000 /**< driver version */ + +/** + * @brief set the address pin + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] addr_pin is the chip address pins + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_set_addr_pin(pcf8591_handle_t *handle, pcf8591_address_t addr_pin) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + + handle->iic_addr = 0x90; /* set iic addr */ + handle->iic_addr |= addr_pin << 1; /* set iic address */ + + return 0; /* success return 0 */ +} + +/** + * @brief get the address pin + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *addr_pin points to a chip address pins buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_get_addr_pin(pcf8591_handle_t *handle, pcf8591_address_t *addr_pin) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + + *addr_pin = (pcf8591_address_t)((handle->iic_addr & (~0x90)) >> 1); /*get iic address */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc channel + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] channel is the adc channel + * @return status code + * - 0 success + * - 1 set channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_channel(pcf8591_handle_t *handle, pcf8591_channel_t channel) +{ + uint8_t res; + uint8_t prev_conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + prev_conf = handle->conf; /* save conf */ + handle->conf &= ~(3 << 0); /* clear channel bits */ + handle->conf |= channel << 0; /* set channel */ + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)&handle->conf, 1); /* write command */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write command failed */ + handle->conf = prev_conf; /* recover conf */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc channel + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *channel points to an adc channel buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_channel(pcf8591_handle_t *handle, pcf8591_channel_t *channel) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *channel = (pcf8591_channel_t)(handle->conf & (3 << 0)); /* get channel */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] mode is the adc mode + * @return status code + * - 0 success + * - 1 set mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_mode(pcf8591_handle_t *handle, pcf8591_mode_t mode) +{ + uint8_t res; + uint8_t prev_conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + prev_conf = handle->conf; /* save conf */ + handle->conf &= ~(3 << 4); /* clear mode bits */ + handle->conf |= mode << 4; /* set mode */ + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)&handle->conf, 1); /* write command */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write command failed */ + handle->conf = prev_conf; /* recover conf */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *mode points to an adc mode buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_mode(pcf8591_handle_t *handle, pcf8591_mode_t *mode) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *mode = (pcf8591_mode_t)((handle->conf >> 4) & 0x03); /* get mode */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc auto increment read mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] enable is a bool value + * @return status code + * - 0 success + * - 1 set auto increment failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_auto_increment(pcf8591_handle_t *handle, pcf8591_bool_t enable) +{ + uint8_t res; + uint8_t prev_conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + prev_conf = handle->conf; /* save conf */ + handle->conf &= ~(1 << 2); /* clear auto bit */ + handle->conf |= enable << 2; /* set enable */ + if (enable != 0) /* if enable auto increment */ + { + handle->conf |= 0x40; /* set output bit */ + } + else + { + handle->conf &= ~0x40; /* clear output bit */ + } + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)&handle->conf, 1); /* write command */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write command failed */ + handle->conf = prev_conf; /* recover conf */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc auto increment read mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *enable points to a bool value buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_auto_increment(pcf8591_handle_t *handle, pcf8591_bool_t *enable) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *enable = (pcf8591_bool_t)((handle->conf >> 2) & 0x01); /* get auto */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc reference voltage + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] ref_voltage is the adc reference voltage + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_reference_voltage(pcf8591_handle_t *handle, float ref_voltage) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + handle->ref_voltage = ref_voltage; /* set reference voltage */ + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc reference voltage + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *ref_voltage points to an adc reference voltage buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_reference_voltage(pcf8591_handle_t *handle, float *ref_voltage) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *ref_voltage = (float)(handle->ref_voltage); /* get ref voltage */ + + return 0; /* success return 0 */ +} + +/** + * @brief initialize the chip + * @param[in] *handle points to a pcf8591 handle structure + * @return status code + * - 0 success + * - 1 iic initialization failed + * - 2 handle is NULL + * - 3 linked functions is NULL + * @note none + */ +uint8_t pcf8591_init(pcf8591_handle_t *handle) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->debug_print == NULL) /* check debug_print */ + { + return 3; /* return error */ + } + if (handle->iic_init == NULL) /* check iic_init */ + { + handle->debug_print("pcf8591: iic_init is null.\n"); /* iic_init is null */ + + return 3; /* return error */ + } + if (handle->iic_deinit == NULL) /* check iic_deinit */ + { + handle->debug_print("pcf8591: iic_deinit is null.\n"); /* iic_deinit is null */ + + return 3; /* return error */ + } + if (handle->iic_read_cmd == NULL) /* check iic_read_cmd */ + { + handle->debug_print("pcf8591: iic_read_cmd is null.\n"); /* iic_read_cmd is null */ + + return 3; /* return error */ + } + if (handle->iic_write_cmd == NULL) /* check iic_write_cmd */ + { + handle->debug_print("pcf8591: iic_write_cmd is null.\n"); /* iic_write_cmd is null */ + + return 3; /* return error */ + } + if (handle->delay_ms == NULL) /* check delay_ms */ + { + handle->debug_print("pcf8591: delay_ms is null.\n"); /* delay_ms is null */ + + return 3; /* return error */ + } + + if (handle->iic_init() != 0) /* iic init */ + { + handle->debug_print("pcf8591: iic init failed.\n"); /* iic init failed */ + + return 1; /* return error */ + } + handle->inited = 1; /* flag finish initialization */ + + return 0; /* success return 0 */ +} + +/** + * @brief close the chip + * @param[in] *handle points to a pcf8591 handle structure + * @return status code + * - 0 success + * - 1 iic deinit failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_deinit(pcf8591_handle_t *handle) +{ + uint8_t res; + uint8_t buf[2]; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + buf[0] = 0x00; /* set conf */ + buf[1] = 0x00; /* set 0x00 */ + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)buf, 2); /* write data */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write failed */ + + return 1; /* return error */ + } + res = handle->iic_deinit(); /* iic deinit */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: iic deinit failed.\n"); /* iic deinit failed */ + + return 1; /* return error */ + } + handle->inited = 0; /* flag closed */ + + return 0; /* success return 0 */ +} + +/** + * @brief write to the dac + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] data is the dac value + * @return status code + * - 0 success + * - 1 write dac value failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_write(pcf8591_handle_t *handle, uint8_t data) +{ + uint8_t res; + uint8_t buf[2]; + uint8_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + conf = handle->conf | 0x40; /* set output */ + buf[0] = conf; /* set conf */ + buf[1] = data; /* set data */ + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)buf, 2); /* write data */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief convert a dac value to a register raw data + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] dac is a converted dac value + * @param[out] *reg points to a register raw buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_dac_convert_to_register(pcf8591_handle_t *handle, float dac, uint8_t *reg) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *reg = (uint8_t)(dac / handle->ref_voltage * 256.0f); /* convert to register */ + + return 0; /* success return 0 */ +} + +/** + * @brief read data from the chip + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *adc points to a converted adc buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_read(pcf8591_handle_t *handle, int16_t *raw, float *adc) +{ + uint8_t res; + uint8_t mode; + uint8_t channel; + uint8_t u_data; + int8_t s_data; + uint8_t buf[1]; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + if ((handle->conf & (1 << 2)) != 0) /* check mode */ + { + handle->debug_print("pcf8591: can't use this function.\n"); /* channel is invalid */ + + return 1; + } + mode = (handle->conf >> 4) & 0x03; /* check mode */ + channel = handle->conf & 0x03; /* check channel */ + if ((mode == 1) || (mode == 2)) /* if mode 1 mode 2 */ + { + if (channel > 2) /* check channel */ + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + + return 1; /* return error */ + } + } + if (mode == 3) /* if mode 3 */ + { + if (channel > 1) /* check channel */ + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + + return 1; /* return error */ + } + } + res = handle->iic_read_cmd(handle->iic_addr, (uint8_t *)buf, 1); /* read data */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: read command failed.\n"); /* read failed */ + + return 1; /* return error */ + } + switch (mode) /* mode param */ + { + case 0 : /* mode 0 */ + { + if (channel == 0) /* channel 0 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 1) /* channel 1 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 2) /* channel 2 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else /* channel 3 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + + break; /* break */ + } + case 1 : /* mode 1 */ + { + if (channel == 0) /* channel 0 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 1) /* channel 1 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 2) /* channel */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + res = 1; /* failed */ + } + + break; /* break */ + } + case 2 : /* mode 2 */ + { + if (channel == 0) /* channel 0 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 1) /* channel 1 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 2) /* channel 2 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + res = 1; /* failed */ + } + + break; /* break */ + } + case 3 : /* mode 3 */ + { + if (channel == 0) /* channel 0 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 1) /* channel 1 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + res = 1; /* failed */ + } + + break; /* break */ + } + default : + { + handle->debug_print("pcf8591: mode is invalid.\n"); /* mode is invalid */ + res = 1; /* failed */ + + break; /* break */ + } + } + + return res; /* return the result */ +} + +/** + * @brief read the multiple channel data from the chip + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *adc points to a converted adc buffer + * @param[in,out] *len points to an adc length buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note len means max length of raw and adc input buffer and return the read length of raw and adc + */ +uint8_t pcf8591_multiple_read(pcf8591_handle_t *handle, int16_t *raw, float *adc, uint8_t *len) +{ + uint8_t res; + uint8_t mode; + uint8_t i, j; + uint8_t u_data; + int8_t s_data; + uint8_t buf[4]; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + if ((handle->conf & (1 << 2)) == 0) /* check mode */ + { + handle->debug_print("pcf8591: can't use this function.\n"); /* channel is invalid */ + + return 1; /* return error */ + } + mode = (handle->conf >> 4) & 0x03; /* check mode */ + if (mode == 0) /* mode 0 */ + { + i = 4; /* 4 */ + i = i<(*len) ? i : (*len); /* get min length */ + } + else if (mode == 1) /* mode 1 */ + { + i = 3; /* 3 */ + i = i<(*len) ? i : (*len); /* get min length */ + } + else if (mode == 2) /* mode 2 */ + { + i = 3; /* 3 */ + i = i<(*len) ? i : (*len); + } /* get min length */ + else /* mode 3 */ + { + i = 2; /* 2 */ + i = i<(*len) ? i : (*len); /* get min length */ + } + + memset(buf, 0, sizeof(uint8_t) * 4); /* clear the buffer */ + for (j = 0; j < i; j++) + { + res = handle->iic_read_cmd(handle->iic_addr, (uint8_t *)&buf[j], 1); /* read data */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: read command failed.\n"); /* read failed */ + + return 1; /* return error */ + } + handle->delay_ms(5); /* delay 5 ms */ + } + switch (mode) /* mode param */ + { + case 0 : /* mode 0 */ + { + u_data = buf[1]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + u_data = buf[2]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + u_data = buf[3]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + *len = i; /* set length */ + res = 0; /* successful */ + + break; /* break */ + } + case 1 : /* mode 1 */ + { + s_data = (int8_t)(buf[1]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + s_data = (int8_t)(buf[2]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + *len = i; /* set length */ + res = 0; /* successful */ + + break; /* break */ + } + case 2 : /* mode 2 */ + { + u_data = buf[1]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + u_data = buf[2]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + *len = i; /* set length */ + res = 0; /* successful */ + + break; /* break */ + } + case 3 : /* mode 3 */ + { + s_data = (int8_t)(buf[1]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + *len = i; /* set length */ + res = 0; /* successful */ + + break; /* break */ + } + default : + { + handle->debug_print("pcf8591: mode is invalid.\n"); /* mode is invalid */ + res = 1; /* failed */ + + break; /* break */ + } + } + + + return res; /* return the result */ +} + +/** + * @brief set the chip register + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] *buf points to a data buffer. + * @param[in] len is the data buffer + * @return status code + * - 0 success + * - 1 write failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_reg(pcf8591_handle_t *handle, uint8_t *buf, uint16_t len) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + return handle->iic_write_cmd(handle->iic_addr, buf, len); /* write command */ +} + +/** + * @brief get the chip register + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *buf points to a data buffer. + * @param[in] len is the data buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_reg(pcf8591_handle_t *handle, uint8_t *buf, uint16_t len) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + return handle->iic_read_cmd(handle->iic_addr, buf, len); /* read command */ +} + +/** + * @brief get chip's information + * @param[out] *info points to a pcf8591 info structure + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_info(pcf8591_info_t *info) +{ + if (info == NULL) /* check handle */ + { + return 2; /* return error */ + } + + memset(info, 0, sizeof(pcf8591_info_t)); /* initialize pcf8591 info structure */ + strncpy(info->chip_name, CHIP_NAME, 32); /* copy chip name */ + strncpy(info->manufacturer_name, MANUFACTURER_NAME, 32); /* copy manufacturer name */ + strncpy(info->interface, "IIC", 8); /* copy interface name */ + info->supply_voltage_min_v = SUPPLY_VOLTAGE_MIN; /* set minimal supply voltage */ + info->supply_voltage_max_v = SUPPLY_VOLTAGE_MAX; /* set maximum supply voltage */ + info->max_current_ma = MAX_CURRENT; /* set maximum current */ + info->temperature_max = TEMPERATURE_MAX; /* set minimal temperature */ + info->temperature_min = TEMPERATURE_MIN; /* set maximum temperature */ + info->driver_version = DRIVER_VERSION; /* set driver version */ + + return 0; /* success return 0 */ +} diff --git a/ospi-analog/driver_pcf8591.h b/ospi-analog/driver_pcf8591.h new file mode 100644 index 000000000..442a9b62e --- /dev/null +++ b/ospi-analog/driver_pcf8591.h @@ -0,0 +1,478 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_pcf8591.h + * @brief driver pcf8591 header file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-18 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/18 2.0 Shifeng Li format the code + *
2020/10/17 1.0 Shifeng Li first upload + *
+ */ + +#ifndef DRIVER_PCF8591_H +#define DRIVER_PCF8591_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @defgroup pcf8591_driver pcf8591 driver function + * @brief pcf8591 driver modules + * @{ + */ + +/** + * @addtogroup pcf8591_base_driver + * @{ + */ + +/** + * @brief pcf8591 address enumeration definition + */ +typedef enum +{ + PCF8591_ADDRESS_A000 = 0, /**< A2A1A0 000 */ + PCF8591_ADDRESS_A001 = 1, /**< A2A1A0 001 */ + PCF8591_ADDRESS_A010 = 2, /**< A2A1A0 010 */ + PCF8591_ADDRESS_A011 = 3, /**< A2A1A0 011 */ + PCF8591_ADDRESS_A100 = 4, /**< A2A1A0 100 */ + PCF8591_ADDRESS_A101 = 5, /**< A2A1A0 101 */ + PCF8591_ADDRESS_A110 = 6, /**< A2A1A0 110 */ + PCF8591_ADDRESS_A111 = 7, /**< A2A1A0 111 */ +} pcf8591_address_t; + +/** + * @brief pcf8591 bool enumeration definition + */ +typedef enum +{ + PCF8591_BOOL_FALSE = 0x00, /**< disable function */ + PCF8591_BOOL_TRUE = 0x01, /**< enable function */ +} pcf8591_bool_t; + +/** + * @brief pcf8591 channel definition + */ +typedef enum +{ + PCF8591_CHANNEL_0 = 0x00, /**< channel 0 */ + PCF8591_CHANNEL_1 = 0x01, /**< channel 1 */ + PCF8591_CHANNEL_2 = 0x02, /**< channel 2 */ + PCF8591_CHANNEL_3 = 0x03, /**< channel 3 */ +} pcf8591_channel_t; + +/** + * @brief pcf8591 mode definition + */ +typedef enum +{ + PCF8591_MODE_AIN0123_GND = 0x00, /**< AIN0-GND AIN1-GND AIN2-GND AIN3-GND */ + PCF8591_MODE_AIN012_AIN3 = 0x01, /**< AIN0-AIN3 AIN1-AIN3 AIN2-AIN3 */ + PCF8591_MODE_AIN0_GND_AND_AIN1_GND_AND_AIN2_AIN3 = 0x02, /**< AIN0-GND AIN1-GND AIN2-AIN3 */ + PCF8591_MODE_AIN0_AIN1_AND_ANI2_AIN3 = 0x03, /**< AIN0-AIN1 AIN2-AIN3 */ +} pcf8591_mode_t; + +/** + * @brief pcf8591 handle structure definition + */ +typedef struct pcf8591_handle_s +{ + uint8_t iic_addr; /**< iic device address */ + uint8_t (*iic_init)(void); /**< point to an iic_init function address */ + uint8_t (*iic_deinit)(void); /**< point to an iic_deinit function address */ + uint8_t (*iic_read_cmd)(uint8_t addr, uint8_t *buf, uint16_t len); /**< point to an iic_read_cmd function address */ + uint8_t (*iic_write_cmd)(uint8_t addr, uint8_t *buf, uint16_t len); /**< point to an iic_write_cmd function address */ + void (*delay_ms)(uint32_t ms); /**< point to a delay_ms function address */ + void (*debug_print)(const char *const fmt, ...); /**< point to a debug_print function address */ + uint8_t inited; /**< inited flag */ + uint8_t conf; /**< chip conf */ + float ref_voltage; /**< chip reference voltage */ +} pcf8591_handle_t; + +/** + * @brief pcf8591 information structure definition + */ +typedef struct pcf8591_info_s +{ + char chip_name[32]; /**< chip name */ + char manufacturer_name[32]; /**< manufacturer name */ + char interface[8]; /**< chip interface name */ + float supply_voltage_min_v; /**< chip min supply voltage */ + float supply_voltage_max_v; /**< chip max supply voltage */ + float max_current_ma; /**< chip max current */ + float temperature_min; /**< chip min operating temperature */ + float temperature_max; /**< chip max operating temperature */ + uint32_t driver_version; /**< driver version */ +} pcf8591_info_t; + +/** + * @} + */ + +/** + * @defgroup pcf8591_link_driver pcf8591 link driver function + * @brief pcf8591 link driver modules + * @ingroup pcf8591_driver + * @{ + */ + +/** + * @brief initialize pcf8591_handle_t structure + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] STRUCTURE is pcf8591_handle_t + * @note none + */ +#define DRIVER_PCF8591_LINK_INIT(HANDLE, STRUCTURE) memset(HANDLE, 0, sizeof(STRUCTURE)) + +/** + * @brief link iic_init function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to an iic_init function address + * @note none + */ +#define DRIVER_PCF8591_LINK_IIC_INIT(HANDLE, FUC) (HANDLE)->iic_init = FUC + +/** + * @brief link iic_deinit function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to an iic_deinit function address + * @note none + */ +#define DRIVER_PCF8591_LINK_IIC_DEINIT(HANDLE, FUC) (HANDLE)->iic_deinit = FUC + +/** + * @brief link iic_read_cmd function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to an iic_read_cmd function address + * @note none + */ +#define DRIVER_PCF8591_LINK_IIC_READ_COMMAND(HANDLE, FUC) (HANDLE)->iic_read_cmd = FUC + +/** + * @brief link iic_write_cmd function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to an iic_write_cmd function address + * @note none + */ +#define DRIVER_PCF8591_LINK_IIC_WRITE_COMMAND(HANDLE, FUC) (HANDLE)->iic_write_cmd = FUC + +/** + * @brief link delay_ms function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to a delay_ms function address + * @note none + */ +#define DRIVER_PCF8591_LINK_DELAY_MS(HANDLE, FUC) (HANDLE)->delay_ms = FUC + +/** + * @brief link debug_print function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to a debug_print function address + * @note none + */ +#define DRIVER_PCF8591_LINK_DEBUG_PRINT(HANDLE, FUC) (HANDLE)->debug_print = FUC + +/** + * @} + */ + +/** + * @defgroup pcf8591_base_driver pcf8591 base driver function + * @brief pcf8591 base driver modules + * @ingroup pcf8591_driver + * @{ + */ + +/** + * @brief get chip's information + * @param[out] *info points to a pcf8591 info structure + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_info(pcf8591_info_t *info); + +/** + * @brief set the address pin + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] addr_pin is the chip address pins + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_set_addr_pin(pcf8591_handle_t *handle, pcf8591_address_t addr_pin); + +/** + * @brief get the address pin + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *addr_pin points to a chip address pins buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_get_addr_pin(pcf8591_handle_t *handle, pcf8591_address_t *addr_pin); + +/** + * @brief initialize the chip + * @param[in] *handle points to a pcf8591 handle structure + * @return status code + * - 0 success + * - 1 iic initialization failed + * - 2 handle is NULL + * - 3 linked functions is NULL + * @note none + */ +uint8_t pcf8591_init(pcf8591_handle_t *handle); + +/** + * @brief close the chip + * @param[in] *handle points to a pcf8591 handle structure + * @return status code + * - 0 success + * - 1 iic deinit failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_deinit(pcf8591_handle_t *handle); + +/** + * @brief read data from the chip + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *adc points to a converted adc buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_read(pcf8591_handle_t *handle, int16_t *raw, float *adc); + +/** + * @brief read the multiple channel data from the chip + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *adc points to a converted adc buffer + * @param[in,out] *len points to an adc length buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note len means max length of raw and adc input buffer and return the read length of raw and adc + */ +uint8_t pcf8591_multiple_read(pcf8591_handle_t *handle, int16_t *raw, float *adc, uint8_t *len); + +/** + * @brief write to the dac + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] data is the dac value + * @return status code + * - 0 success + * - 1 write dac value failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_write(pcf8591_handle_t *handle, uint8_t data); + +/** + * @brief convert a dac value to a register raw data + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] dac is a converted dac value + * @param[out] *reg points to a register raw buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_dac_convert_to_register(pcf8591_handle_t *handle, float dac, uint8_t *reg); + +/** + * @brief set the adc reference voltage + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] ref_voltage is the adc reference voltage + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_reference_voltage(pcf8591_handle_t *handle, float ref_voltage); + +/** + * @brief get the adc reference voltage + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *ref_voltage points to an adc reference voltage buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_reference_voltage(pcf8591_handle_t *handle, float *ref_voltage); + +/** + * @brief set the adc channel + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] channel is the adc channel + * @return status code + * - 0 success + * - 1 set channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_channel(pcf8591_handle_t *handle, pcf8591_channel_t channel); + +/** + * @brief get the adc channel + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *channel points to an adc channel buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_channel(pcf8591_handle_t *handle, pcf8591_channel_t *channel); + +/** + * @brief set the adc mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] mode is the adc mode + * @return status code + * - 0 success + * - 1 set mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_mode(pcf8591_handle_t *handle, pcf8591_mode_t mode); + +/** + * @brief get the adc mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *mode points to an adc mode buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_mode(pcf8591_handle_t *handle, pcf8591_mode_t *mode); + +/** + * @brief set the adc auto increment read mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] enable is a bool value + * @return status code + * - 0 success + * - 1 set auto increment failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_auto_increment(pcf8591_handle_t *handle, pcf8591_bool_t enable); + +/** + * @brief get the adc auto increment read mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *enable points to a bool value buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_auto_increment(pcf8591_handle_t *handle, pcf8591_bool_t *enable); + +/** + * @} + */ + +/** + * @defgroup pcf8591_extern_driver pcf8591 extern driver function + * @brief pcf8591 extern driver modules + * @ingroup pcf8591_driver + * @{ + */ + +/** + * @brief set the chip register + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] *buf points to a data buffer. + * @param[in] len is the data buffer + * @return status code + * - 0 success + * - 1 write failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_reg(pcf8591_handle_t *handle, uint8_t *buf, uint16_t len); + +/** + * @brief get the chip register + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *buf points to a data buffer. + * @param[in] len is the data buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_reg(pcf8591_handle_t *handle, uint8_t *buf, uint16_t len); + +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospi-analog/driver_pcf8591_interface.c b/ospi-analog/driver_pcf8591_interface.c new file mode 100644 index 000000000..da2861617 --- /dev/null +++ b/ospi-analog/driver_pcf8591_interface.c @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file raspberrypi4b_driver_pcf8591_interface.c + * @brief raspberrypi4b driver pcf8591 interface source file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-18 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/18 2.0 Shifeng Li format the code + *
2020/10/17 1.0 Shifeng Li first upload + *
+ */ + +#include "driver_pcf8591_interface.h" +#include "iic.h" +#include + +/** + * @brief iic device name definition + */ +static const char* IIC_DEVICE_NAME = "/dev/i2c-1"; /**< iic device name */ + +/** + * @brief iic device handle definition + */ +static int gs_fd; /**< iic handle */ + +/** + * @brief interface iic bus init + * @return status code + * - 0 success + * - 1 iic init failed + * @note none + */ +uint8_t pcf8591_interface_iic_init(void) +{ + return iic_init(IIC_DEVICE_NAME, &gs_fd); +} + +/** + * @brief interface iic bus deinit + * @return status code + * - 0 success + * - 1 iic deinit failed + * @note none + */ +uint8_t pcf8591_interface_iic_deinit(void) +{ + return iic_deinit(gs_fd); +} + +/** + * @brief interface iic bus write command + * @param[in] addr is the iic device write address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +uint8_t pcf8591_interface_iic_write_cmd(uint8_t addr, uint8_t *buf, uint16_t len) +{ + return iic_write_cmd(gs_fd, addr, buf, len); +} + +/** + * @brief interface iic bus read command + * @param[in] addr is the iic device write address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +uint8_t pcf8591_interface_iic_read_cmd(uint8_t addr, uint8_t *buf, uint16_t len) +{ + return iic_read_cmd(gs_fd, addr, buf, len); +} + +/** + * @brief interface delay ms + * @param[in] ms + * @note none + */ +void pcf8591_interface_delay_ms(uint32_t ms) +{ + usleep(1000 * ms); +} + +/** + * @brief interface print format data + * @param[in] fmt is the format data + * @note none + */ +void pcf8591_interface_debug_print(const char *const fmt, ...) +{ + /** + char str[256]; + uint16_t len; + va_list args; + + memset((char *)str, 0, sizeof(char) * 256); + va_start(args, fmt); + vsnprintf((char *)str, 255, (char const *)fmt, args); + va_end(args); + + len = strlen((char *)str); + printf((uint8_t *)str, len); + **/ +} diff --git a/ospi-analog/driver_pcf8591_interface.h b/ospi-analog/driver_pcf8591_interface.h new file mode 100644 index 000000000..b1489757b --- /dev/null +++ b/ospi-analog/driver_pcf8591_interface.h @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_pcf8591_interface.h + * @brief driver pcf8591 interface header file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-18 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/18 2.0 Shifeng Li format the code + *
2020/10/17 1.0 Shifeng Li first upload + *
+ */ + +#ifndef DRIVER_PCF8591_INTERFACE_H +#define DRIVER_PCF8591_INTERFACE_H + +#include "driver_pcf8591.h" + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @defgroup pcf8591_interface_driver pcf8591 interface driver function + * @brief pcf8591 interface driver modules + * @ingroup pcf8591_driver + * @{ + */ + +/** + * @brief interface iic bus init + * @return status code + * - 0 success + * - 1 iic init failed + * @note none + */ +uint8_t pcf8591_interface_iic_init(void); + +/** + * @brief interface iic bus deinit + * @return status code + * - 0 success + * - 1 iic deinit failed + * @note none + */ +uint8_t pcf8591_interface_iic_deinit(void); + +/** + * @brief interface iic bus write command + * @param[in] addr is the iic device write address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +uint8_t pcf8591_interface_iic_write_cmd(uint8_t addr, uint8_t *buf, uint16_t len); + +/** + * @brief interface iic bus read command + * @param[in] addr is the iic device write address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +uint8_t pcf8591_interface_iic_read_cmd(uint8_t addr, uint8_t *buf, uint16_t len); + +/** + * @brief interface delay ms + * @param[in] ms + * @note none + */ +void pcf8591_interface_delay_ms(uint32_t ms); + +/** + * @brief interface print format data + * @param[in] fmt is the format data + * @note none + */ +void pcf8591_interface_debug_print(const char *const fmt, ...); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospi-analog/iic.c b/ospi-analog/iic.c new file mode 100644 index 000000000..600a5badd --- /dev/null +++ b/ospi-analog/iic.c @@ -0,0 +1,365 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file iic.c + * @brief iic source file + * @version 1.0.0 + * @author Shifeng Li + * @date 2022-11-11 + * + *

history

+ * + *
Date Version Author Description + *
2022/11/11 1.0 Shifeng Li first upload + *
+ */ + +#include "iic.h" +#include +#include +#include +#include + +/** + * @brief iic bus init + * @param[in] *name points to an iic device name buffer + * @param[out] *fd points to an iic device handle buffer + * @return status code + * - 0 success + * - 1 init failed + * @note none + */ +uint8_t iic_init(const char *name, int *fd) +{ + /* open the device */ + *fd = open(name, O_RDWR); + + /* check the fd */ + if ((*fd) < 0) + { + perror("iic: open failed.\n"); + + return 1; + } + else + { + return 0; + } +} + +/** + * @brief iic bus deinit + * @param[in] fd is the iic handle + * @return status code + * - 0 success + * - 1 deinit failed + * @note none + */ +uint8_t iic_deinit(int fd) +{ + /* close the device */ + if (close(fd) < 0) + { + perror("iic: close failed.\n"); + + return 1; + } + else + { + return 0; + } +} + +/** + * @brief iic bus read command + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read_cmd(int fd, uint8_t addr, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[1]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 1); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = I2C_M_RD; + msgs[0].buf = buf; + msgs[0].len = len; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 1; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: read failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus read + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read(int fd, uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[2]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 2); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + msgs[0].buf = ® + msgs[0].len = 1; + msgs[1].addr = addr >> 1; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = buf; + msgs[1].len = len; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 2; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: read failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus read with 16 bits register address + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[2]; + uint8_t addr_buf[2]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 2); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + addr_buf[0] = (reg >> 8) & 0xFF; + addr_buf[1] = (reg >> 0) & 0xFF; + msgs[0].buf = addr_buf; + msgs[0].len = 2; + msgs[1].addr = addr >> 1; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = buf; + msgs[1].len = len; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 2; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: read failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus write command + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write_cmd(int fd, uint8_t addr, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[1]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 1); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + msgs[0].buf = buf; + msgs[0].len = len; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 1; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: write failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus write + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write(int fd, uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[1]; + uint8_t buf_send[len + 1]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 1); + + /* clear sent buf */ + memset(buf_send, 0, sizeof(uint8_t) * (len + 1)); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + buf_send[0] = reg; + memcpy(&buf_send[1], buf, len); + msgs[0].buf = buf_send; + msgs[0].len = len + 1; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 1; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: write failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus write with 16 bits register address + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[1]; + uint8_t buf_send[len + 2]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 1); + + /* clear sent buf */ + memset(buf_send, 0, sizeof(uint8_t) * (len + 2)); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + buf_send[0] = (reg >> 8) & 0xFF; + buf_send[1] = (reg >> 0) & 0xFF; + memcpy(&buf_send[2], buf, len); + msgs[0].buf = buf_send; + msgs[0].len = len + 2; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 1; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: write failed.\n"); + + return 1; + } + + return 0; +} diff --git a/ospi-analog/iic.h b/ospi-analog/iic.h new file mode 100644 index 000000000..cdbbf1a05 --- /dev/null +++ b/ospi-analog/iic.h @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file iic.h + * @brief iic header file + * @version 1.0.0 + * @author Shifeng Li + * @date 2022-11-11 + * + *

history

+ * + *
Date Version Author Description + *
2022/11/11 1.0 Shifeng Li first upload + *
+ */ + +#ifndef IIC_H +#define IIC_H + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup iic iic function + * @brief iic function modules + * @{ + */ + +/** + * @brief iic bus init + * @param[in] *name points to an iic device name buffer + * @param[out] *fd points to an iic device handle buffer + * @return status code + * - 0 success + * - 1 init failed + * @note none + */ +uint8_t iic_init(const char *name, int *fd); + +/** + * @brief iic bus deinit + * @param[in] fd is the iic handle + * @return status code + * - 0 success + * - 1 deinit failed + * @note none + */ +uint8_t iic_deinit(int fd); + +/** + * @brief iic bus read command + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read_cmd(int fd, uint8_t addr, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus read + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read(int fd, uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus read with 16 bits register address + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus write command + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write_cmd(int fd, uint8_t addr, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus write + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write(int fd, uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus write with 16 bits register address + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, uint16_t len); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/sensor_ospi_ads1115.cpp b/sensor_ospi_ads1115.cpp new file mode 100644 index 000000000..f22c6b74c --- /dev/null +++ b/sensor_ospi_ads1115.cpp @@ -0,0 +1,132 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware Copyright (C) 2015 by + * Ray Wang (ray@opensprinkler.com) + * + * Sensor-API + * 2023/2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifdef ADS1115 + +#include "ospi-analog/driver_ads1115.h" +#include "ospi-analog/driver_ads1115_interface.h" +#include "sensor_ospi_ads1115.h" +#include "sensors.h" + +#define ADS1115_BASIC_DEFAULT_RANGE ADS1115_RANGE_6P144V /**< set range 6.144V */ +#define ADS1115_BASIC_DEFAULT_RATE ADS1115_RATE_128SPS /**< set 128 SPS */ + +#define DEFAULT_RANGE 6.144 + +/** +* Read the OSPi 1.6 onboard ADS1115 A2D +**/ +int read_sensor_ospi(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_ospi")); + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + sensor->flags.data_ok = false; + + static ads1115_handle_t gs_handle; /**< ads1115 handle */ + uint8_t res; + + + DRIVER_ADS1115_LINK_INIT(&gs_handle, ads1115_handle_t); + DRIVER_ADS1115_LINK_IIC_INIT(&gs_handle, ads1115_interface_iic_init); + DRIVER_ADS1115_LINK_IIC_DEINIT(&gs_handle, ads1115_interface_iic_deinit); + DRIVER_ADS1115_LINK_IIC_READ(&gs_handle, ads1115_interface_iic_read); + DRIVER_ADS1115_LINK_IIC_WRITE(&gs_handle, ads1115_interface_iic_write); + DRIVER_ADS1115_LINK_DELAY_MS(&gs_handle, ads1115_interface_delay_ms); + DRIVER_ADS1115_LINK_DEBUG_PRINT(&gs_handle, ads1115_interface_debug_print); + + /* set addr pin */ + res = ads1115_set_addr_pin(&gs_handle, ADS1115_ADDR_GND); + if (res != 0) + return HTTP_RQT_NOT_RECEIVED; + + /* ads1115 init */ + res = ads1115_init(&gs_handle); + if (res != 0) + return HTTP_RQT_NOT_RECEIVED; + + sensor->last_native_data = res; + sensor->flags.data_ok = true; + + /* set channel */ + res = ads1115_set_channel(&gs_handle, ADS1115_CHANNEL_AIN0_AIN1); + if (res != 0) { + (void)ads1115_deinit(&gs_handle); + + return HTTP_RQT_NOT_RECEIVED; + } + + /* set default range */ + res = ads1115_set_range(&gs_handle, ADS1115_BASIC_DEFAULT_RANGE); + if (res != 0) { + (void)ads1115_deinit(&gs_handle); + + return HTTP_RQT_NOT_RECEIVED; + } + + /* set default rate */ + res = ads1115_set_rate(&gs_handle, ADS1115_BASIC_DEFAULT_RATE); + if (res != 0) { + (void)ads1115_deinit(&gs_handle); + + return HTTP_RQT_NOT_RECEIVED; + } + + /* disable compare */ + res = ads1115_set_compare(&gs_handle, ADS1115_BOOL_FALSE); + if (res != 0) { + (void)ads1115_deinit(&gs_handle); + + return HTTP_RQT_NOT_RECEIVED; + } + + int16_t raw; + float v; + res = ads1115_single_read(&gs_handle, &raw, &v); + + ads1115_deinit(&gs_handle); + + if (res != 0) + return HTTP_RQT_NOT_RECEIVED; + + sensor->last_native_data = raw; + + //convert values: + switch(sensor->type) { + case SENSOR_OSPI_ANALOG: + sensor->last_data = (double)v; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_P: + sensor->last_data = (double)v / DEFAULT_RANGE * 3.3 * 100; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + sensor->last_data = (double)v * 50 / 3; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + sensor->last_data = ((double)v - 0.5) * 100; + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} + + + +#endif diff --git a/sensor_ospi_ads1115.h b/sensor_ospi_ads1115.h new file mode 100644 index 000000000..07e4378d2 --- /dev/null +++ b/sensor_ospi_ads1115.h @@ -0,0 +1,39 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * sensors header file - OSPI + * 2023/2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _SENSOR_OSPI_ADS1115_H +#define _SENSOR_OSPI_ADS1115_H + +#if defined ADS1115 + +#include "sensors.h" + +/* + * @brief Read sensor function + * @param[in] Sensor + * @note + */ +int read_sensor_ospi(Sensor_t *sensor); + +#endif // ADS1115 + +#endif // _SENSORS_H diff --git a/sensor_ospi_pcf8591.cpp b/sensor_ospi_pcf8591.cpp new file mode 100644 index 000000000..bbc62c10f --- /dev/null +++ b/sensor_ospi_pcf8591.cpp @@ -0,0 +1,125 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware Copyright (C) 2015 by + * Ray Wang (ray@opensprinkler.com) + * + * Sensor-API + * 2023/2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifdef PCF8591 + +#include "ospi-analog/driver_pcf8591.h" +#include "ospi-analog/driver_pcf8591_interface.h" +#include "sensor_ospi_pcf8591.h" +#include "sensors.h" + +#define DEFAULT_ADDR PCF8591_ADDRESS_A000 +#define DEFAULT_MODE PCF8591_MODE_AIN0123_GND +#define DEFAULT_REF_VOLTAGE 3.3 +/** +* Read the OSPi onboard PCF8591 A2D +**/ +int read_sensor_ospi(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_ospi")); + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + sensor->flags.data_ok = false; + + static pcf8591_handle_t gs_handle; /**< pcf8591 handle */ + uint8_t res; + + DRIVER_PCF8591_LINK_INIT(&gs_handle, pcf8591_handle_t); + DRIVER_PCF8591_LINK_IIC_INIT(&gs_handle, pcf8591_interface_iic_init); + DRIVER_PCF8591_LINK_IIC_DEINIT(&gs_handle, pcf8591_interface_iic_deinit); + DRIVER_PCF8591_LINK_IIC_READ_COMMAND(&gs_handle, pcf8591_interface_iic_read_cmd); + DRIVER_PCF8591_LINK_IIC_WRITE_COMMAND(&gs_handle, pcf8591_interface_iic_write_cmd); + DRIVER_PCF8591_LINK_DELAY_MS(&gs_handle, pcf8591_interface_delay_ms); + DRIVER_PCF8591_LINK_DEBUG_PRINT(&gs_handle, pcf8591_interface_debug_print); + + /* set addr pin */ + res = pcf8591_set_addr_pin(&gs_handle, DEFAULT_ADDR); + if (res != 0) + return HTTP_RQT_NOT_RECEIVED; + + /* pcf8591 init */ + res = pcf8591_init(&gs_handle); + if (res != 0) + return HTTP_RQT_NOT_RECEIVED; + + /* set mode */ + res = pcf8591_set_mode(&gs_handle, DEFAULT_MODE); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + /* disable auto increment */ + res = pcf8591_set_auto_increment(&gs_handle, PCF8591_BOOL_FALSE); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + /* set default reference voltage */ + res = pcf8591_set_reference_voltage(&gs_handle, DEFAULT_REF_VOLTAGE); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + pcf8591_channel_t channel = (pcf8591_channel_t)sensor->id; + res = pcf8591_set_channel(&gs_handle, channel); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + int16_t raw; + float v; + res = pcf8591_read(&gs_handle, &raw, &v); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + pcf8591_deinit(&gs_handle); + + sensor->last_native_data = res; + sensor->flags.data_ok = true; + + //convert values: + switch(sensor->type) { + case SENSOR_OSPI_ANALOG: + sensor->last_data = (double)v; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_P: + sensor->last_data = (double)v / DEFAULT_REF_VOLTAGE * 100; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + sensor->last_data = (double)v * 50 / 3; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + sensor->last_data = ((double)v - 0.5) * 100; + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} + + + + +#endif diff --git a/sensor_ospi_pcf8591.h b/sensor_ospi_pcf8591.h new file mode 100644 index 000000000..954826dff --- /dev/null +++ b/sensor_ospi_pcf8591.h @@ -0,0 +1,39 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * sensors header file - OSPI + * 2023/2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _SENSOR_OSPI_PCF8591_H +#define _SENSOR_OSPI_PCF8591_H + +#if defined PCF8591 + +#include "sensors.h" + +/* + * @brief Read sensor function + * @param[in] Sensor + * @note + */ +int read_sensor_ospi(Sensor_t *sensor); + +#endif // PCF8591 + +#endif // _SENSORS_H From d7cb84f5e0be0ccc93e57fbe44b9b905cf0375ea Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 18 Mar 2024 00:17:19 +0100 Subject: [PATCH 099/281] OSPI ads1115+pcf8591 sensor addons --- build2.sh~ | 8 ++--- defines.h | 1 + main.cpp | 1 + mqtt.cpp | 6 ++-- opensprinkler_server.cpp | 1 - sensor_mqtt.cpp | 27 +++++++++++++-- sensor_mqtt.h | 3 +- sensor_ospi_ads1115.h | 2 +- sensor_ospi_pcf8591.h | 2 +- sensors.cpp | 73 +++++----------------------------------- 10 files changed, 47 insertions(+), 77 deletions(-) diff --git a/build2.sh~ b/build2.sh~ index 030d69a5d..c08e40367 100755 --- a/build2.sh~ +++ b/build2.sh~ @@ -8,24 +8,22 @@ PCF8591="" PCF8591FILES="" LIB="" -i2cdetect -y 1 |grep "48 --" >/dev/null +i2cdetect -y 1 |grep "48 49" >/dev/null if [ "$?" -eq 0 ]; then echo "found ADS1115" ADS1115="-DADS1115" ADS1115FILES="./ospi-analog/driver_ads1115*.c ./ospi-analog/iic.c" - LIB="-lgpiod" fi -i2cdetect -y 1 |grep "48 49" >/dev/null +i2cdetect -y 1 |grep "48 --" >/dev/null if [ "$?" -eq 0 ]; then echo "found PCF8591" PCF8591="-DPCF8591" PCF8591FILES="./ospi-analog/driver_pcf8591*.c ./ospi-analog/iic.c" - LIB="-lgpiod" fi g++ -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 -std=c++14 \ main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp \ utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensor*.cpp \ $ADS1115FILES $PCF8591FILES \ - -li2c -lpthread -lmosquitto -lcrypto -lssl $LIB + -li2c -lpthread -lmosquitto -lcrypto -lssl -v diff --git a/defines.h b/defines.h index 005e21d7f..ed9d454d4 100644 --- a/defines.h +++ b/defines.h @@ -492,6 +492,7 @@ enum { #define PSTR(x) x #define F(x) x #define strcat_P strcat + #define strncat_P strncat #define strcpy_P strcpy #define sprintf_P sprintf #include diff --git a/main.cpp b/main.cpp index 48ea7c442..819d9c99f 100644 --- a/main.cpp +++ b/main.cpp @@ -393,6 +393,7 @@ void do_setup() { os.status.network_fails = 1; } os.status.req_network = 0; + os.powerup_lasttime = os.now_tz(); // because at reboot we don't know if special stations // are in OFF state, here we explicitly turn them off diff --git a/mqtt.cpp b/mqtt.cpp index 27c6f0950..5d5b13072 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -368,7 +368,7 @@ int OSMqtt::_init(void) { if (mqtt_client) { mosquitto_destroy(mqtt_client); mqtt_client = NULL; }; - mqtt_client = mosquitto_new("OS", true, &this); + mqtt_client = mosquitto_new("OS", true, NULL); if (mqtt_client == NULL) { DEBUG_PRINTF("MQTT Init: Failed to initialise client\r\n"); return MQTT_ERROR; @@ -411,6 +411,8 @@ int OSMqtt::_disconnect(void) { bool OSMqtt::_connected(void) { return ::_connected; } +bool OSMqtt::connected(void) { return connected(); } + int OSMqtt::_publish(const char *topic, const char *payload) { int rc = mosquitto_publish(mqtt_client, NULL, topic, strlen(payload), payload, 0, false); if (rc != MOSQ_ERR_SUCCESS) { @@ -431,7 +433,7 @@ bool OSMqtt::subscribe(const char *topic) { bool OSMqtt::unsubscribe(const char *topic) { if (!mqtt_client || !_enabled || os.status.network_fails > 0 || !_connected()) return false; - return mosquitto_unsubscribe(mqtt_client, NULL, topic, 0); + return mosquitto_unsubscribe(mqtt_client, NULL, topic); } void OSMqtt::setCallback(void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)) { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 61f2f734b..34ea13c15 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2917,7 +2917,6 @@ const char* sensor_names[] = { "OSPi analog input - SMT50 moisture mode", "OSPi analog input - SMT50 temperature mode", #endif -#endif #endif "MQTT subscription", "Remote opensprinkler sensor", diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 757e6afd3..3809e7368 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -63,6 +63,29 @@ bool mqtt_filter_matches(char* mtopic, char* topic) { return true; } +#ifndef ARDUINO +#include + +//Compatibility for Raspberry PI: +char *strnstr(const char *haystack, const char *needle, size_t len) +{ + int i; + size_t needle_len; + + if (0 == (needle_len = strnlen(needle, len))) + return (char *)haystack; + + for (i=0; i<=(int)(len-needle_len); i++) + { + if ((haystack[0] == needle[0]) && + (0 == strncmp(haystack, needle, needle_len))) + return (char *)haystack; + + haystack++; + } + return NULL; +} +#endif /** * @brief mqtt callback @@ -71,8 +94,8 @@ bool mqtt_filter_matches(char* mtopic, char* topic) { #if defined(ARDUINO) void sensor_mqtt_callback(char* mtopic, byte* payload, unsigned int length) { #else -void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { - char* mtopic = msg->topic +void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { + char* mtopic = msg->topic; byte* payload = (byte*)msg->payload; unsigned int length = msg->payloadlen; #endif diff --git a/sensor_mqtt.h b/sensor_mqtt.h index 303477c4b..54bbf1efe 100644 --- a/sensor_mqtt.h +++ b/sensor_mqtt.h @@ -28,7 +28,8 @@ #if defined(ARDUINO) void sensor_mqtt_callback(char* mtopic, byte* payload, unsigned int length); #else -void (*sensor_mqtt_callback)(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg); +char *strnstr(const char *haystack, const char *needle, size_t len); +void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg); #endif int read_sensor_mqtt(Sensor_t *sensor); diff --git a/sensor_ospi_ads1115.h b/sensor_ospi_ads1115.h index 07e4378d2..cb3970c1f 100644 --- a/sensor_ospi_ads1115.h +++ b/sensor_ospi_ads1115.h @@ -23,7 +23,7 @@ #ifndef _SENSOR_OSPI_ADS1115_H #define _SENSOR_OSPI_ADS1115_H -#if defined ADS1115 +#ifdef ADS1115 #include "sensors.h" diff --git a/sensor_ospi_pcf8591.h b/sensor_ospi_pcf8591.h index 954826dff..5859bff1e 100644 --- a/sensor_ospi_pcf8591.h +++ b/sensor_ospi_pcf8591.h @@ -23,7 +23,7 @@ #ifndef _SENSOR_OSPI_PCF8591_H #define _SENSOR_OSPI_PCF8591_H -#if defined PCF8591 +#ifdef PCF8591 #include "sensors.h" diff --git a/sensors.cpp b/sensors.cpp index 6199a2fcf..8b0fd94ac 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -28,6 +28,12 @@ #include "sensors.h" #include "weather.h" #include "sensor_mqtt.h" +#ifdef ADS1115 +#include "sensor_ospi_ads1115.h" +#endif +#ifdef PCF8591 +#include "sensor_ospi_pcf8591.h" +#endif byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); @@ -763,7 +769,7 @@ void read_all_sensors() { #if defined(ARDUINO) #if defined(ESP8266) /** - * Read ADS1115 sensors + * Read ESP8296 ADS1115 sensors */ int read_sensor_adc(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_adc")); @@ -861,68 +867,6 @@ int read_sensor_adc(Sensor_t *sensor) { return HTTP_RQT_SUCCESS; } #endif -#else -/** -* Read the OSPi onboard PCF8591 A2D -**/ -int read_sensor_ospi(Sensor_t *sensor) { - DEBUG_PRINTLN(F("read_sensor_ospi")); - if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - - sensor->flags.data_ok = false; - - /** - * https://medium.com/geekculture/raspberry-pi-c-libraries-for-working-with-i2c-spi-and-uart-4677f401b584 - * http://www.pibits.net/amp/code/raspberry-pi-and-a-pcf8591-example.php - **/ - int adapter_nr = 1; - char filename[20]; - __u8 addr = sensor->port == 0? 0x48 : sensor->port; - __u8 chn = sensor->id; - - //Open I2C: - snprintf(filename, 19, "/dev/i2c-%d", adapter_nr); - int file = open(filename, O_RDWR); - if (file < 0) return HTTP_RQT_NOT_RECEIVED; - - //Select address: - if (ioctl(file, I2C_SLAVE, addr) < 0) { - close(file); - return HTTP_RQT_NOT_RECEIVED; - } - - //Select channel: - i2c_smbus_write_byte(file, chn); - - //dummy read to start conversion: - i2c_smbus_read_byte(file); - - //read current value: - __s32 res = i2c_smbus_read_byte(file); - close(file); - - if (res < 0) return HTTP_RQT_NOT_RECEIVED; - - sensor->last_native_data = res; - sensor->flags.data_ok = true; - - //convert values: - switch(sensor->type) { - case SENSOR_OSPI_ANALOG: - sensor->last_data = (double)res * 3.3/255; - return HTTP_RQT_SUCCESS; - case SENSOR_OSPI_ANALOG_P: - sensor->last_data = (double)res * 100/255; - return HTTP_RQT_SUCCESS; - case SENSOR_OSPI_ANALOG_SMT50_MOIS: - sensor->last_data = (double)res * 3.3/255 * 50 / 3; - return HTTP_RQT_SUCCESS; - case SENSOR_OSPI_ANALOG_SMT50_TEMP: - sensor->last_data = (((double)res * 3.3/255) - 0.5) * 100; - return HTTP_RQT_SUCCESS; - } - return HTTP_RQT_NOT_RECEIVED; -} #endif bool extract(char *s, char *buf, int maxlen) { @@ -1220,13 +1164,14 @@ int read_sensor(Sensor_t *sensor) { return read_sensor_adc(sensor); #endif #else +#if defined ADS1115|PCF8591 case SENSOR_OSPI_ANALOG: case SENSOR_OSPI_ANALOG_P: case SENSOR_OSPI_ANALOG_SMT50_MOIS: case SENSOR_OSPI_ANALOG_SMT50_TEMP: sensor->last_read = time; return read_sensor_ospi(sensor); - +#endif case SENSOR_REMOTE: sensor->last_read = time; return read_sensor_http(sensor); From 6b2d0429c4b7d305caf481310f3c2e2325076193 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 18 Mar 2024 00:21:16 +0100 Subject: [PATCH 100/281] raspi/esp defines --- ospi-analog/iic.c | 3 +++ ospi-analog/iic.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/ospi-analog/iic.c b/ospi-analog/iic.c index 600a5badd..b32bbedcb 100644 --- a/ospi-analog/iic.c +++ b/ospi-analog/iic.c @@ -34,6 +34,7 @@ * */ +#ifndef ARDUINO #include "iic.h" #include #include @@ -363,3 +364,5 @@ uint8_t iic_write_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, ui return 0; } + +#endif \ No newline at end of file diff --git a/ospi-analog/iic.h b/ospi-analog/iic.h index cdbbf1a05..98f0939f0 100644 --- a/ospi-analog/iic.h +++ b/ospi-analog/iic.h @@ -37,6 +37,8 @@ #ifndef IIC_H #define IIC_H +#ifndef ARDUINO + #include #include #include @@ -165,3 +167,4 @@ uint8_t iic_write_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, ui #endif #endif +#endif \ No newline at end of file From 887e4f2ed75a12059b70dbe250b423bd01dba283 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 18 Mar 2024 00:34:39 +0100 Subject: [PATCH 101/281] ospi ads1115 cleanup --- sensor_ospi_ads1115.cpp | 43 +++++++++++++++++++++++++---------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/sensor_ospi_ads1115.cpp b/sensor_ospi_ads1115.cpp index f22c6b74c..674ebad61 100644 --- a/sensor_ospi_ads1115.cpp +++ b/sensor_ospi_ads1115.cpp @@ -53,8 +53,23 @@ int read_sensor_ospi(Sensor_t *sensor) { DRIVER_ADS1115_LINK_DELAY_MS(&gs_handle, ads1115_interface_delay_ms); DRIVER_ADS1115_LINK_DEBUG_PRINT(&gs_handle, ads1115_interface_debug_print); + ads1115_address_t addr = ADS1115_ADDR_GND; + ads1115_channel_t channel = ADS1115_CHANNEL_AIN0_GND; + /* set channel */ + switch(sensor->id) { + case 0: channel = ADS1115_CHANNEL_AIN0_GND; break; + case 1: channel = ADS1115_CHANNEL_AIN1_GND; break; + case 2: channel = ADS1115_CHANNEL_AIN2_GND; break; + case 3: channel = ADS1115_CHANNEL_AIN3_GND; break; + case 4: addr = ADS1115_ADDR_VCC; channel = ADS1115_CHANNEL_AIN0_GND; break; + case 5: addr = ADS1115_ADDR_VCC; channel = ADS1115_CHANNEL_AIN1_GND; break; + case 6: addr = ADS1115_ADDR_VCC; channel = ADS1115_CHANNEL_AIN2_GND; break; + case 7: addr = ADS1115_ADDR_VCC; channel = ADS1115_CHANNEL_AIN3_GND; break; + default: return HTTP_RQT_NOT_RECEIVED; + } + /* set addr pin */ - res = ads1115_set_addr_pin(&gs_handle, ADS1115_ADDR_GND); + res = ads1115_set_addr_pin(&gs_handle, addr); if (res != 0) return HTTP_RQT_NOT_RECEIVED; @@ -63,51 +78,45 @@ int read_sensor_ospi(Sensor_t *sensor) { if (res != 0) return HTTP_RQT_NOT_RECEIVED; - sensor->last_native_data = res; - sensor->flags.data_ok = true; - - /* set channel */ - res = ads1115_set_channel(&gs_handle, ADS1115_CHANNEL_AIN0_AIN1); + res = ads1115_set_channel(&gs_handle, channel); if (res != 0) { - (void)ads1115_deinit(&gs_handle); - + ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } /* set default range */ res = ads1115_set_range(&gs_handle, ADS1115_BASIC_DEFAULT_RANGE); if (res != 0) { - (void)ads1115_deinit(&gs_handle); - + ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } /* set default rate */ res = ads1115_set_rate(&gs_handle, ADS1115_BASIC_DEFAULT_RATE); if (res != 0) { - (void)ads1115_deinit(&gs_handle); - + ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } /* disable compare */ res = ads1115_set_compare(&gs_handle, ADS1115_BOOL_FALSE); if (res != 0) { - (void)ads1115_deinit(&gs_handle); - + ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } int16_t raw; float v; res = ads1115_single_read(&gs_handle, &raw, &v); + if (res != 0) { + ads1115_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } ads1115_deinit(&gs_handle); - if (res != 0) - return HTTP_RQT_NOT_RECEIVED; - sensor->last_native_data = raw; + sensor->flags.data_ok = true; //convert values: switch(sensor->type) { From f152d9a4a42bcca14310b648f5836cfc64cbdcd1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 19 Mar 2024 00:30:23 +0100 Subject: [PATCH 102/281] fixt ospi encoding, ip change --- opensprinkler_server.cpp | 17 ++++++++++++++--- sensor_mqtt.cpp | 5 +++-- sensors.cpp | 30 +++++++++++++++--------------- 3 files changed, 32 insertions(+), 20 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 34ea13c15..a7c90936c 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2099,6 +2099,9 @@ void server_sensorurl_config(OTF_PARAMS_DEF) char *value = NULL; if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value"), true)) handle_return(HTML_DATA_MISSING); + DEBUG_PRINTLN(tmp_buffer); + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); value = strdup(tmp_buffer); bool ok = SensorUrl_add(nr, type, value); @@ -2175,7 +2178,10 @@ void server_sensor_config(OTF_PARAMS_DEF) char userdef_unit[8]; memset(userdef_unit, 0, sizeof(userdef_unit)); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { - strncpy(userdef_unit, urlDecodeAndUnescape(tmp_buffer), sizeof(userdef_unit)-1); // unit + DEBUG_PRINTLN(tmp_buffer) + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer) + strncpy(userdef_unit, tmp_buffer, sizeof(userdef_unit)-1); // unit } int16_t assigned_unitid = -1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unitid"), true)) @@ -2206,8 +2212,13 @@ void server_sensor_config(OTF_PARAMS_DEF) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("url"), true)) url = strdup(urlDecodeAndUnescape(tmp_buffer)); char* topic = NULL; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("topic"), true)) - topic = strdup(urlDecodeAndUnescape(tmp_buffer)); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("topic"), true)) { + DEBUG_PRINTLN(tmp_buffer) + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer) + topic = strdup(tmp_buffer); + } + char* filter = NULL; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("filter"), true)) filter = strdup(urlDecodeAndUnescape(tmp_buffer)); diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 3809e7368..51628f831 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -199,7 +199,8 @@ void sensor_mqtt_subscribe(uint nr, uint type, const char *urlstr) { DEBUG_PRINTLN(sensor->name); DEBUG_PRINT("subscribe: "); DEBUG_PRINTLN(urlstr); - os.mqtt.subscribe(urlstr); + if (!os.mqtt.subscribe(urlstr)) + DEBUG_PRINTLN("error subscribe!!"); os.mqtt.setCallback(&sensor_mqtt_callback); sensor->mqtt_init = true; } @@ -214,7 +215,7 @@ void sensor_mqtt_unsubscribe(uint nr, uint type, const char *urlstr) { DEBUG_PRINT("unsubscribe: "); DEBUG_PRINTLN(urlstr); if (!os.mqtt.unsubscribe(urlstr)) - DEBUG_PRINTLN("error subscribe!!"); + DEBUG_PRINTLN("error unsubscribe!!"); sensor->mqtt_init = false; } } \ No newline at end of file diff --git a/sensors.cpp b/sensors.cpp index 8b0fd94ac..c6d20040c 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -893,10 +893,10 @@ int read_sensor_http(Sensor_t *sensor) { byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else byte ip[4]; - ip[0] = (byte)((sensor->ip >> 24) &0xFF); - ip[1] = (byte)((sensor->ip >> 16) &0xFF); - ip[2] = (byte)((sensor->ip >> 8) &0xFF); - ip[3] = (byte)((sensor->ip &0xFF)); + ip[3] = (byte)((sensor->ip >> 24) &0xFF); + ip[2] = (byte)((sensor->ip >> 16) &0xFF); + ip[1] = (byte)((sensor->ip >> 8) &0xFF); + ip[0] = (byte)((sensor->ip &0xFF)); #endif DEBUG_PRINTLN(F("read_sensor_http")); @@ -970,10 +970,10 @@ int read_sensor_ip(Sensor_t *sensor) { byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else byte ip[4]; - ip[0] = (byte)((sensor->ip >> 24) &0xFF); - ip[1] = (byte)((sensor->ip >> 16) &0xFF); - ip[2] = (byte)((sensor->ip >> 8) &0xFF); - ip[3] = (byte)((sensor->ip &0xFF)); + ip[3] = (byte)((sensor->ip >> 24) &0xFF); + ip[2] = (byte)((sensor->ip >> 16) &0xFF); + ip[1] = (byte)((sensor->ip >> 8) &0xFF); + ip[0] = (byte)((sensor->ip &0xFF)); #endif char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); @@ -1331,11 +1331,11 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else - byte ip[4]; - ip[0] = (byte)((sensor->ip >> 24) &0xFF); - ip[1] = (byte)((sensor->ip >> 16) &0xFF); - ip[2] = (byte)((sensor->ip >> 8) &0xFF); - ip[3] = (byte)((sensor->ip &0xFF)); + byte ip[4]; + ip[3] = (byte)((sensor->ip >> 24) &0xFF); + ip[2] = (byte)((sensor->ip >> 16) &0xFF); + ip[1] = (byte)((sensor->ip >> 8) &0xFF); + ip[0] = (byte)((sensor->ip &0xFF)); #endif char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); @@ -1768,8 +1768,8 @@ byte getSensorUnitId(Sensor_t *sensor) { case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; #endif - case SENSOR_MQTT: return sensor->assigned_unitid > 0?sensor->assigned_unitid:UNIT_USERDEF; - case SENSOR_REMOTE: return sensor->unitid; + case SENSOR_MQTT: + case SENSOR_REMOTE: return sensor->assigned_unitid > 0?sensor->assigned_unitid:UNIT_USERDEF; case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; case SENSOR_WEATHER_TEMP_C: return UNIT_DEGREE; From e1c00ef16ee975814a13a627f19d64ff276cffb6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 19 Mar 2024 00:42:56 +0100 Subject: [PATCH 103/281] fix remote sensor --- sensors.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sensors.cpp b/sensors.cpp index c6d20040c..ae6b0a422 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -932,6 +932,12 @@ int read_sensor_http(Sensor_t *sensor) { s = strstr(p, "\"unitid\":"); if (s && extract(s, buf, sizeof(buf))) { sensor->unitid = atoi(buf); + sensor->assigned_unitid = sensor->unitid; + } + s = strstr(p, "\"unit\":"); + if (s && extract(s, buf, sizeof(buf))) { + urlDecodeAndUnescape(buf); + strncpy(sensor->userdef_unit, buf, sizeof(sensor->userdef_unit)-1); } return HTTP_RQT_SUCCESS; From cccfc2632267f0e38fb9917890795f94859a1480 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 20 Mar 2024 00:22:58 +0100 Subject: [PATCH 104/281] added network diagnostik --- opensprinkler_server.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index a7c90936c..8185b157c 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -39,6 +39,8 @@ extern ESP8266WebServer *update_server; extern OTF::OpenThingsFramework *otf; + extern ENC28J60lwIP enc28j60; + extern Wiznet5500lwIP w5500; extern lwipEth eth; extern bool otf_callbacksInitialised; #define OTF_PARAMS_DEF const OTF::Request &req,OTF::Response &res @@ -1938,8 +1940,13 @@ void server_json_debug(OTF_PARAMS_DEF) { (uint16_t)ESP.getFreeHeap()); FSInfo fs_info; LittleFS.info(fs_info); - bfill.emit_p(PSTR(",\"flash\":$D,\"used\":$D,\"rssi\":$D,\"bssid\":\"$S\",\"bssidchl\":\"$O\"}"), - fs_info.totalBytes, fs_info.usedBytes, WiFi.RSSI(), WiFi.BSSIDstr().c_str(), SOPT_STA_BSSID_CHL); + bfill.emit_p(PSTR(",\"flash\":$D,\"used\":$D,"), fs_info.totalBytes, fs_info.usedBytes); + if(useEth) { + bfill.emit_p(PSTR("\"isW5500\":$D}"), eth.isW5500); + } 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 From 1cf933ddc37864788162e33f5d9863834faf1985 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 20 Mar 2024 23:30:44 +0100 Subject: [PATCH 105/281] added new gpio lib. thx to Jason Alonso --- build.sh | 10 ++-- build2.sh~ | 2 +- gpio.cpp | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 140 insertions(+), 8 deletions(-) diff --git a/build.sh b/build.sh index f3dcc0c79..2789bb169 100755 --- a/build.sh +++ b/build.sh @@ -27,13 +27,9 @@ else apt-get install -y raspi-gpio apt-get install -y libi2c-dev apt-get install -y libssl-dev - if ! command -v raspi-gpio &> /dev/null - then - echo "Command raspi-gpio is required and is not installed" - exit 0 - fi - echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSPI -std=c++14 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -li2c -lpthread -lmosquitto -lcrypto -lssl + apt-get install -y libgpiod-dev + + source build2.sh fi if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then diff --git a/build2.sh~ b/build2.sh~ index c08e40367..86ebcea9f 100755 --- a/build2.sh~ +++ b/build2.sh~ @@ -26,4 +26,4 @@ g++ -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 -std=c++14 \ main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp \ utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensor*.cpp \ $ADS1115FILES $PCF8591FILES \ - -li2c -lpthread -lmosquitto -lcrypto -lssl -v + -li2c -lpthread -lmosquitto -lcrypto -lssl diff --git a/gpio.cpp b/gpio.cpp index 2d4825b50..a2c9e5252 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -177,6 +177,8 @@ byte digitalReadExt(byte pin) { #elif defined(OSPI) || defined(OSBO) +#if defined(RASPIGPIO) + #include #include #include @@ -441,6 +443,140 @@ void attachInterrupt(int pin, const char* mode, void (*isr)(void)) { delay(1) ; pthread_mutex_unlock (&pinMutex) ; } +#endif + +#if defined(LIBGPIOD) + +/** + * NEW GPIO Implementation for Raspberry Pi OS 12 (bookworm) + * + * Thanks to Jason Balonso + * https://github.com/jbalonso/ + * + * +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +#define BUFFER_MAX 64 +#define GPIO_MAX 64 + +// GPIO interfaces +const char *gpio_chipname = "gpiochip0"; +const char *gpio_consumer = "opensprinkler"; + +struct gpiod_chip *chip = NULL; +struct gpiod_line* gpio_lines[] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, +}; + +int assert_gpiod_chip() { + if( !chip ) { + chip = gpiod_chip_open_by_name(gpio_chipname); + if( !chip ) { + DEBUG_PRINTLN("failed to open gpio chip"); + return -1; + } else { + DEBUG_PRINTLN("gpio chip opened"); + return 0; + } + } + return 0; +} + +int assert_gpiod_line(int pin) { + if( !gpio_lines[pin] ) { + if( assert_gpiod_chip() ) { return -1; } + gpio_lines[pin] = gpiod_chip_get_line(chip, pin); + if( !gpio_lines[pin] ) { + DEBUG_PRINT("failed to open gpio line "); + DEBUG_PRINTLN(pin); + return -1; + } else { + DEBUG_PRINT("opened gpio line "); + DEBUG_PRINT(pin); + return 0; + } + } + return 0; +} + +/** Set pin mode, in or out */ +void pinMode(int pin, byte mode) { + if( assert_gpiod_line(pin) ) { return; } + switch(mode) { + case INPUT: + gpiod_line_request_input(gpio_lines[pin], gpio_consumer); + break; + case INPUT_PULLUP: + gpiod_line_request_input_flags(gpio_lines[pin], gpio_consumer, GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP); + break; + case OUTPUT: + gpiod_line_request_output(gpio_lines[pin], gpio_consumer, LOW); + break; + default: + DEBUG_PRINTLN("invalid pin direction"); + break; + } + + return; +} + +/** Read digital value */ +byte digitalRead(int pin) { + if( !gpio_lines[pin] ) { + DEBUG_PRINT("tried to read uninitialized pin "); + DEBUG_PRINTLN(pin); + return 0; + } + int val = gpiod_line_get_value(gpio_lines[pin]); + if( val < 0 ) { + DEBUG_PRINT("failed to read value on pin "); + DEBUG_PRINTLN(pin); + return 0; + } + return val; +} + +/** Write digital value */ +void digitalWrite(int pin, byte value) { + if( !gpio_lines[pin] ) { + DEBUG_PRINT("tried to write uninitialized pin "); + DEBUG_PRINTLN(pin); + return; + } + + int res; + res = gpiod_line_set_value(gpio_lines[pin], value); + if( res ) { + DEBUG_PRINT("failed to write value on pin "); + DEBUG_PRINTLN(pin); + } +} + +void attachInterrupt(int pin, const char* mode, void (*isr)(void)) {} +void gpio_write(int fd, byte value) {} +int gpio_fd_open(int pin, int mode) {return 0;} +void gpio_fd_close(int fd) {} + +#endif + #else void pinMode(int pin, byte mode) {} From 77899d3d8287b011a60e49ae0a0cdf561983e6f0 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 00:17:08 +0100 Subject: [PATCH 106/281] analog sensor: repeat read (x7) and calculating avg --- sensor_ospi_ads1115.cpp | 12 ++++++++++++ sensor_ospi_pcf8591.cpp | 14 +++++++++++++- sensors.cpp | 22 +++++++++++++--------- sensors.h | 9 +++++++-- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/sensor_ospi_ads1115.cpp b/sensor_ospi_ads1115.cpp index 674ebad61..5d6139905 100644 --- a/sensor_ospi_ads1115.cpp +++ b/sensor_ospi_ads1115.cpp @@ -115,6 +115,18 @@ int read_sensor_ospi(Sensor_t *sensor) { ads1115_deinit(&gs_handle); + sensor->repeat_native += raw; + sensor->repeat_data += v; + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ) + return HTTP_RQT_NOT_RECEIVED; + + raw = sensor->repeat_native/MAX_SENSOR_REPEAT_READ; + v = sensor->repeat_data/MAX_SENSOR_REPEAT_READ; + + sensor->repeat_native = 0; + sensor->repeat_data = 0; + sensor->repeat_read = 0; + sensor->last_native_data = raw; sensor->flags.data_ok = true; diff --git a/sensor_ospi_pcf8591.cpp b/sensor_ospi_pcf8591.cpp index bbc62c10f..9595ee0c0 100644 --- a/sensor_ospi_pcf8591.cpp +++ b/sensor_ospi_pcf8591.cpp @@ -98,7 +98,19 @@ int read_sensor_ospi(Sensor_t *sensor) { pcf8591_deinit(&gs_handle); - sensor->last_native_data = res; + sensor->repeat_native += raw; + sensor->repeat_data += v; + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ) + return HTTP_RQT_NOT_RECEIVED; + + raw = sensor->repeat_native/MAX_SENSOR_REPEAT_READ; + v = sensor->repeat_data/MAX_SENSOR_REPEAT_READ; + + sensor->repeat_native = 0; + sensor->repeat_data = 0; + sensor->repeat_read = 0; + + sensor->last_native_data = raw; sensor->flags.data_ok = true; //convert values: diff --git a/sensors.cpp b/sensors.cpp index ae6b0a422..4bba85c58 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -750,7 +750,7 @@ void read_all_sensors() { Sensor_t *sensor = sensors; while (sensor) { - if (time >= sensor->last_read + sensor->read_interval) { + if (time >= sensor->last_read + sensor->read_interval || sensor->repeat_read) { int result = read_sensor(sensor); if (result == HTTP_RQT_SUCCESS) { sensorlog_add(LOG_STD, sensor, time); @@ -791,14 +791,18 @@ int read_sensor_adc(Sensor_t *sensor) { if (!active) return HTTP_RQT_NOT_RECEIVED; - //Read values multiple times: - uint64_t avgValue = 0; -#define MAX_READ 7 - for (int t = 0; t < MAX_READ; t++) { - if (t > 0) delay(100); - avgValue += adc.readADC(id); - } - sensor->last_native_data = avgValue / MAX_READ; + + sensor->repeat_native += adc.readADC(id); + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ) + return HTTP_RQT_NOT_RECEIVED; + + uint64_t avgValue = sensor->repeat_native/MAX_SENSOR_REPEAT_READ; + + sensor->repeat_native = 0; + sensor->repeat_data = 0; + sensor->repeat_read = 0; + + sensor->last_native_data = avgValue; sensor->last_data = adc.toVoltage(sensor->last_native_data); double v = sensor->last_data; diff --git a/sensors.h b/sensors.h index f3de78146..b33113500 100644 --- a/sensors.h +++ b/sensors.h @@ -106,6 +106,8 @@ extern "C" { #define MIN_DISK_FREE 8192 //8Kb min +#define MAX_SENSOR_REPEAT_READ 7 //read analog sensors 7x and calculate avg + typedef struct SensorFlags { uint enable:1; // enabled uint log:1; // log data enabled @@ -134,14 +136,17 @@ typedef struct Sensor { // sensorvalue = (read_value-offset_mv/1000) * factor / divider + offset2/100 byte assigned_unitid; // unitid for userdef and mqtt sensors byte undef[15]; // for later - //unstored + //unstored: bool mqtt_init:1; bool mqtt_push:1; byte unitid; + byte repeat_read; + double repeat_data; + uint32_t repeat_native; ulong last_read; //millis Sensor *next; } Sensor_t; -#define SENSOR_STORE_SIZE (sizeof(Sensor_t)-sizeof(Sensor_t*)-sizeof(ulong)-sizeof(byte)) +#define SENSOR_STORE_SIZE (sizeof(Sensor_t)-sizeof(Sensor_t*)-sizeof(ulong)-sizeof(uint32_t)-sizeof(double)-5*sizeof(byte)) //Definition of a log data typedef struct SensorLog { From 5a2f36c6491c37cf1b516c17345afba0ef77fef4 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 00:24:53 +0100 Subject: [PATCH 107/281] repeat read more precision --- sensors.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sensors.h b/sensors.h index b33113500..75a80fadc 100644 --- a/sensors.h +++ b/sensors.h @@ -142,11 +142,11 @@ typedef struct Sensor { byte unitid; byte repeat_read; double repeat_data; - uint32_t repeat_native; + uint64_t repeat_native; ulong last_read; //millis Sensor *next; } Sensor_t; -#define SENSOR_STORE_SIZE (sizeof(Sensor_t)-sizeof(Sensor_t*)-sizeof(ulong)-sizeof(uint32_t)-sizeof(double)-5*sizeof(byte)) +#define SENSOR_STORE_SIZE (sizeof(Sensor_t)-sizeof(Sensor_t*)-sizeof(ulong)-sizeof(uint64_t)-sizeof(double)-5*sizeof(byte)) //Definition of a log data typedef struct SensorLog { From ff171c06a034aafd4102173b1aa5528b2e434b2c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 00:39:38 +0100 Subject: [PATCH 108/281] correction sensor store size --- sensors.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.h b/sensors.h index 75a80fadc..93fba8350 100644 --- a/sensors.h +++ b/sensors.h @@ -146,7 +146,7 @@ typedef struct Sensor { ulong last_read; //millis Sensor *next; } Sensor_t; -#define SENSOR_STORE_SIZE (sizeof(Sensor_t)-sizeof(Sensor_t*)-sizeof(ulong)-sizeof(uint64_t)-sizeof(double)-5*sizeof(byte)) +#define SENSOR_STORE_SIZE 111 //Definition of a log data typedef struct SensorLog { From 308c0c0169bd675a38d79a1fe83054862bbb1137 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 00:40:35 +0100 Subject: [PATCH 109/281] fixt init --- main.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 819d9c99f..95b5b01de 100644 --- a/main.cpp +++ b/main.cpp @@ -404,8 +404,7 @@ void do_setup() { os.mqtt.init(); os.status.req_mqtt_restart = true; - sensor_load(); - prog_adjust_load(); + sensor_api_init(); } #endif From 39297d6a68dd47c14f84c2723f8b3b6f79956511 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 01:34:07 +0100 Subject: [PATCH 110/281] Updated mqtt callback init --- sensor_mqtt.cpp | 26 ++++++++++---------------- sensor_mqtt.h | 4 ++-- sensors.cpp | 1 + 3 files changed, 13 insertions(+), 18 deletions(-) diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 51628f831..e0c91457b 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -27,6 +27,9 @@ extern OpenSprinkler os; +void sensor_mqtt_init() { + os.mqtt.setCallback(sensor_mqtt_callback); +} /** * @brief * @@ -66,21 +69,16 @@ bool mqtt_filter_matches(char* mtopic, char* topic) { #ifndef ARDUINO #include -//Compatibility for Raspberry PI: -char *strnstr(const char *haystack, const char *needle, size_t len) +char *strnlstr(const char *haystack, const char *needle, size_t needle_len, size_t len) { int i; - size_t needle_len; - - if (0 == (needle_len = strnlen(needle, len))) - return (char *)haystack; - for (i=0; i<=(int)(len-needle_len); i++) { + if (haystack[0] == 0) + break; if ((haystack[0] == needle[0]) && - (0 == strncmp(haystack, needle, needle_len))) + (0 == strncmp(haystack, needle, needle_len))) return (char *)haystack; - haystack++; } return NULL; @@ -94,7 +92,8 @@ char *strnstr(const char *haystack, const char *needle, size_t len) #if defined(ARDUINO) void sensor_mqtt_callback(char* mtopic, byte* payload, unsigned int length) { #else -void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { +static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { + DEBUG_PRINTLN("sensor_mqtt_callback0"); char* mtopic = msg->topic; byte* payload = (byte*)msg->payload; unsigned int length = msg->payloadlen; @@ -103,8 +102,6 @@ void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosqui DEBUG_PRINTLN("sensor_mqtt_callback1"); if (!mtopic || !payload) return; - payload[length] = 0; - time_t now = os.now_tz(); Sensor_t *sensor = getSensors(); while (sensor) { @@ -128,7 +125,7 @@ void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosqui while (f && p) { f = strstr(jsonFilter, "|"); if (f) { - p = strnstr(p, jsonFilter, f-jsonFilter); + p = strnlstr(p, jsonFilter, f-jsonFilter, (char*)payload+length-p); jsonFilter = f+1; } else { p = strstr(p, jsonFilter); @@ -182,7 +179,6 @@ int read_sensor_mqtt(Sensor_t *sensor) { DEBUG_PRINTLN(sensor->name); char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); if (topic && topic[0]) { - os.mqtt.setCallback(&sensor_mqtt_callback); DEBUG_PRINT("subscribe: "); DEBUG_PRINTLN(topic); os.mqtt.subscribe(topic); @@ -201,7 +197,6 @@ void sensor_mqtt_subscribe(uint nr, uint type, const char *urlstr) { DEBUG_PRINTLN(urlstr); if (!os.mqtt.subscribe(urlstr)) DEBUG_PRINTLN("error subscribe!!"); - os.mqtt.setCallback(&sensor_mqtt_callback); sensor->mqtt_init = true; } } @@ -211,7 +206,6 @@ void sensor_mqtt_unsubscribe(uint nr, uint type, const char *urlstr) { if (urlstr && urlstr[0] && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { DEBUG_PRINT("sensor_mqtt_unsubscribe1: "); DEBUG_PRINTLN(sensor->name); - os.mqtt.setCallback(&sensor_mqtt_callback); DEBUG_PRINT("unsubscribe: "); DEBUG_PRINTLN(urlstr); if (!os.mqtt.unsubscribe(urlstr)) diff --git a/sensor_mqtt.h b/sensor_mqtt.h index 54bbf1efe..ca2f76966 100644 --- a/sensor_mqtt.h +++ b/sensor_mqtt.h @@ -28,10 +28,10 @@ #if defined(ARDUINO) void sensor_mqtt_callback(char* mtopic, byte* payload, unsigned int length); #else -char *strnstr(const char *haystack, const char *needle, size_t len); -void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg); +static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg); #endif +void sensor_mqtt_init(); int read_sensor_mqtt(Sensor_t *sensor); void sensor_mqtt_subscribe(uint nr, uint type, const char *urlstr); diff --git a/sensors.cpp b/sensors.cpp index 4bba85c58..7d7813a92 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -98,6 +98,7 @@ uint16_t CRC16 (byte buf[], int len) { void sensor_api_init() { sensor_load(); prog_adjust_load(); + sensor_mqtt_init(); } /* From 3c24534297224f786e0e5197ac06cd34814a6905 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 13:26:12 +0100 Subject: [PATCH 111/281] fixt mqtt crash --- .gitignore | 1 + build2.sh~ | 18 ++++++++++++++---- mqtt.cpp | 2 +- sensors.cpp | 2 +- 4 files changed, 17 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index cdc87026e..7308152d3 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build-1284/* .vscode .pio *.bak +build2.sh~ diff --git a/build2.sh~ b/build2.sh~ index 86ebcea9f..9541fe3fb 100755 --- a/build2.sh~ +++ b/build2.sh~ @@ -1,12 +1,22 @@ #!/bin/bash -echo "Compiling firmware..." +echo "Compiling OSPi firmware..." ADS1115="" ADS1115FILES="" PCF8591="" PCF8591FILES="" -LIB="" +USEGPIO="" +GPIOLIB="" +if [ -h "/sys/class/gpio/gpio??" ]; then + echo "using raspi-gpio" + USEGPIO="-DRASPIGPIO" +fi +if [ -h "/sys/class/gpio/gpiochip512" ]; then + echo "using libgpiod" + USEGPIO="-DLIBGPIOD" + GPIOLIB="-lgpiod" +fi i2cdetect -y 1 |grep "48 49" >/dev/null if [ "$?" -eq 0 ]; then @@ -22,8 +32,8 @@ if [ "$?" -eq 0 ]; then PCF8591FILES="./ospi-analog/driver_pcf8591*.c ./ospi-analog/iic.c" fi -g++ -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 -std=c++14 \ +g++ -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 $USEGPIO -std=c++14 \ main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp \ utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensor*.cpp \ $ADS1115FILES $PCF8591FILES \ - -li2c -lpthread -lmosquitto -lcrypto -lssl + -li2c -lpthread -lmosquitto -lcrypto -lssl $GPIOLIB \ No newline at end of file diff --git a/mqtt.cpp b/mqtt.cpp index 5d5b13072..ccd3d968b 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -411,7 +411,7 @@ int OSMqtt::_disconnect(void) { bool OSMqtt::_connected(void) { return ::_connected; } -bool OSMqtt::connected(void) { return connected(); } +bool OSMqtt::connected(void) { return _connected(); } int OSMqtt::_publish(const char *topic, const char *payload) { int rc = mosquitto_publish(mqtt_client, NULL, topic, strlen(payload), payload, 0, false); diff --git a/sensors.cpp b/sensors.cpp index 7d7813a92..3c95c3379 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -703,7 +703,7 @@ void sensor_remote_http_callback(char*) { void push_message(Sensor_t *sensor) { if (!sensor || !sensor->last_read) return; - + static char topic[TMP_BUFFER_SIZE]; static char payload[TMP_BUFFER_SIZE]; char* postval = tmp_buffer; From f6825784617fd48e53b4089c57dc0fba2085841f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 15:20:32 +0100 Subject: [PATCH 112/281] fix build script for ospi --- build2.sh | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100755 build2.sh diff --git a/build2.sh b/build2.sh new file mode 100755 index 000000000..0ffa40ffd --- /dev/null +++ b/build2.sh @@ -0,0 +1,39 @@ +#!/bin/bash + +echo "Compiling OSPi firmware..." + +ADS1115="" +ADS1115FILES="" +PCF8591="" +PCF8591FILES="" +USEGPIO="" +GPIOLIB="" +if [ -h "/sys/class/gpio/gpio??" ]; then + echo "using raspi-gpio" + USEGPIO="-DRASPIGPIO" +fi +if [ -h "/sys/class/gpio/gpiochip512" ]; then + echo "using libgpiod" + USEGPIO="-DLIBGPIOD" + GPIOLIB="-lgpiod" +fi + +i2cdetect -y 1 |grep "48 49" >/dev/null +if [ "$?" -eq 0 ]; then + echo "found ADS1115" + ADS1115="-DADS1115" + ADS1115FILES="./ospi-analog/driver_ads1115*.c ./ospi-analog/iic.c" +fi + +i2cdetect -y 1 |grep "48 --" >/dev/null +if [ "$?" -eq 0 ]; then + echo "found PCF8591" + PCF8591="-DPCF8591" + PCF8591FILES="./ospi-analog/driver_pcf8591*.c ./ospi-analog/iic.c" +fi + +g++ -g -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 $USEGPIO -std=c++14 \ + main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp \ + utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensor*.cpp \ + $ADS1115FILES $PCF8591FILES \ + -li2c -lpthread -lmosquitto -lcrypto -lssl $GPIOLIB \ No newline at end of file From cbcfc9b8428f11d5289961d81256f7e92732c74d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 15:22:11 +0100 Subject: [PATCH 113/281] updated ignore file --- .gitignore | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 7308152d3..f6710345b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,9 @@ logs/** OpenSprinkler wtopts.txt *.dat -*.sh testmode.h build-1284/* .vscode .pio *.bak -build2.sh~ +*.*~ From 3a1955ce2ac5bec07ea5502829fbafd7850e4e4a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 21 Mar 2024 15:31:48 +0100 Subject: [PATCH 114/281] Cleanup+buildscript adjusted --- .gitignore | 4 ++-- build2.sh | 2 +- build2.sh~ | 39 --------------------------------------- 3 files changed, 3 insertions(+), 42 deletions(-) delete mode 100755 build2.sh~ diff --git a/.gitignore b/.gitignore index f6710345b..4beb0c5cd 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,5 @@ testmode.h build-1284/* .vscode .pio -*.bak -*.*~ +.gitignore~ +build2.sh~ diff --git a/build2.sh b/build2.sh index 0ffa40ffd..76bf4deda 100755 --- a/build2.sh +++ b/build2.sh @@ -8,7 +8,7 @@ PCF8591="" PCF8591FILES="" USEGPIO="" GPIOLIB="" -if [ -h "/sys/class/gpio/gpio??" ]; then +if [ -h "/sys/class/gpio/gpio14" ]; then echo "using raspi-gpio" USEGPIO="-DRASPIGPIO" fi diff --git a/build2.sh~ b/build2.sh~ deleted file mode 100755 index 9541fe3fb..000000000 --- a/build2.sh~ +++ /dev/null @@ -1,39 +0,0 @@ -#!/bin/bash - -echo "Compiling OSPi firmware..." - -ADS1115="" -ADS1115FILES="" -PCF8591="" -PCF8591FILES="" -USEGPIO="" -GPIOLIB="" -if [ -h "/sys/class/gpio/gpio??" ]; then - echo "using raspi-gpio" - USEGPIO="-DRASPIGPIO" -fi -if [ -h "/sys/class/gpio/gpiochip512" ]; then - echo "using libgpiod" - USEGPIO="-DLIBGPIOD" - GPIOLIB="-lgpiod" -fi - -i2cdetect -y 1 |grep "48 49" >/dev/null -if [ "$?" -eq 0 ]; then - echo "found ADS1115" - ADS1115="-DADS1115" - ADS1115FILES="./ospi-analog/driver_ads1115*.c ./ospi-analog/iic.c" -fi - -i2cdetect -y 1 |grep "48 --" >/dev/null -if [ "$?" -eq 0 ]; then - echo "found PCF8591" - PCF8591="-DPCF8591" - PCF8591FILES="./ospi-analog/driver_pcf8591*.c ./ospi-analog/iic.c" -fi - -g++ -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 $USEGPIO -std=c++14 \ - main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp \ - utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensor*.cpp \ - $ADS1115FILES $PCF8591FILES \ - -li2c -lpthread -lmosquitto -lcrypto -lssl $GPIOLIB \ No newline at end of file From f2123ca64e316eb5a4184071219eb4ec95da7d1b Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 22 Mar 2024 01:25:51 +0100 Subject: [PATCH 115/281] fixt OSPI mqtt - wrong clientid, added reconnect() --- .gitignore | 1 + mqtt.cpp | 84 ++++++++++++++++++++++++++++++----------- mqtt.h | 1 + sensor_mqtt.cpp | 27 ++++++------- sensor_ospi_ads1115.cpp | 1 - sensor_ospi_pcf8591.cpp | 1 - sensors.cpp | 2 + 7 files changed, 79 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index 4beb0c5cd..8b1bfb85a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ build-1284/* .pio .gitignore~ build2.sh~ +sensor_mqtt.cpp~ diff --git a/mqtt.cpp b/mqtt.cpp index ccd3d968b..0b902b848 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -36,6 +36,8 @@ #include #include #include + #include + #include struct mosquitto *mqtt_client = NULL; #endif @@ -70,6 +72,7 @@ extern OpenSprinkler os; extern char tmp_buffer[]; +static unsigned long last_reconnect_attempt; #define MQTT_KEEPALIVE 60 #define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config @@ -79,8 +82,7 @@ extern char tmp_buffer[]; #define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit #define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts -#define MQTT_ROOT_TOPIC "opensprinkler" -#define MQTT_AVAILABILITY_TOPIC MQTT_ROOT_TOPIC "/availability" +#define MQTT_AVAILABILITY_TOPIC "/availability" #define MQTT_ONLINE_PAYLOAD "online" #define MQTT_OFFLINE_PAYLOAD "offline" @@ -107,10 +109,34 @@ void OSMqtt::init(void) { os.load_hardware_mac(mac, true); #endif snprintf(id, MQTT_MAX_ID_LEN, "OS-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +#else + struct ifaddrs *ifaddr=NULL; + struct ifaddrs *ifa = NULL; + int i = 0; + + if (getifaddrs(&ifaddr) != -1) //OSPi: Generate Client Id from MAC: + { + for ( ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if ( (ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET) ) { + if (strcmp(ifa->ifa_name, "lo") == 0) continue; + struct sockaddr_ll *s = (struct sockaddr_ll*)ifa->ifa_addr; + sprintf(id, "OS-%02x%02x%02x%02x%02x%02x", s->sll_addr[0], s->sll_addr[1], s->sll_addr[2], s->sll_addr[3], s->sll_addr[4], s->sll_addr[5]); + break; + } + } + freeifaddrs(ifaddr); + } #endif init(id); -}; +} + +const char* getOnlineTopic() { + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + strncat(tmp_buffer, MQTT_AVAILABILITY_TOPIC, TMP_BUFFER_SIZE*2); + return tmp_buffer; +} + // Initialise the client libraries and event handlers. void OSMqtt::init(const char * clientId) { @@ -119,7 +145,7 @@ void OSMqtt::init(const char * clientId) { strncpy(_id, clientId, MQTT_MAX_ID_LEN); _id[MQTT_MAX_ID_LEN] = 0; _init(); -}; +} // Start the MQTT service and connect to the MQTT broker using the stored configuration. void OSMqtt::begin(void) { @@ -184,14 +210,16 @@ void OSMqtt::publish(const char *topic, const char *payload) { // 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; if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return; // Only attemp to reconnect every MQTT_RECONNECT_DELAY seconds to avoid blocking the main loop if (!_connected() && (millis() - last_reconnect_attempt >= MQTT_RECONNECT_DELAY * 1000UL)) { DEBUG_LOGF("MQTT Loop: Reconnecting\r\n"); - _connect(); + if (!last_reconnect_attempt) + _connect(); + else + reconnect(); last_reconnect_attempt = millis(); } @@ -253,19 +281,20 @@ int OSMqtt::_connect(void) { do { DEBUG_PRINT(F("mqtt: ")); DEBUG_PRINTLN(_host); - if (_username[0]) - state = mqtt_client->connect(_id, _username, _password, MQTT_AVAILABILITY_TOPIC, 0, true, MQTT_OFFLINE_PAYLOAD); + if (_username[0]) + state = mqtt_client->connect(_id, _username, _password, getOnlineTopic(), 0, true, MQTT_OFFLINE_PAYLOAD); else - state = mqtt_client->connect(_id, NULL, NULL, MQTT_AVAILABILITY_TOPIC, 0, true, MQTT_OFFLINE_PAYLOAD); + state = mqtt_client->connect(_id, NULL, NULL, getOnlineTopic(), 0, true, MQTT_OFFLINE_PAYLOAD); if(state) break; tries++; } while(triesstate()); return MQTT_ERROR; } else { - mqtt_client->publish(MQTT_AVAILABILITY_TOPIC, MQTT_ONLINE_PAYLOAD, true); + mqtt_client->publish(getOnlineTopic(), MQTT_ONLINE_PAYLOAD, true); } return MQTT_SUCCESS; } @@ -297,16 +326,12 @@ bool OSMqtt::connected(void) { } bool OSMqtt::subscribe(const char *topic) { - DEBUG_PRINTLN("subscribe1"); - if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0 || !_connected()) return false; - DEBUG_PRINTLN("subscribe2"); + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return false; return mqtt_client->subscribe(topic); } bool OSMqtt::unsubscribe(const char *topic) { - DEBUG_PRINTLN("unsubscribe1"); - if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0 || !_connected()) return false; - DEBUG_PRINTLN("unsubscribe2"); + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return false; return mqtt_client->unsubscribe(topic); } @@ -329,6 +354,12 @@ const char * OSMqtt::_state_string(int rc) { default: return "Unrecognised state"; } } + +bool OSMqtt::reconnect() { + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return false; + mqtt_client->reconnect(); +} + #else /************************** RASPBERRY PI / BBB / DEMO ****************************************/ @@ -341,9 +372,10 @@ static void _mqtt_connection_cb(struct mosquitto *mqtt_client, void *obj, int re ::_connected = true; if (reason == 0) { - int rc = mosquitto_publish(mqtt_client, NULL, MQTT_AVAILABILITY_TOPIC, strlen(MQTT_ONLINE_PAYLOAD), MQTT_ONLINE_PAYLOAD, 0, true); + const char* topic = getOnlineTopic(); + int rc = mosquitto_publish(mqtt_client, NULL, topic, strlen(MQTT_ONLINE_PAYLOAD), MQTT_ONLINE_PAYLOAD, 0, true); if (rc != MOSQ_ERR_SUCCESS) { - DEBUG_LOGF("MQTT Publish: Failed (%s)\r\n", mosquitto_strerror(rc)); + DEBUG_LOGF("MQTT Publish: Failed (%s)\r\n(%s)\r\n", mosquitto_strerror(rc), topic); } } } @@ -368,7 +400,7 @@ int OSMqtt::_init(void) { if (mqtt_client) { mosquitto_destroy(mqtt_client); mqtt_client = NULL; }; - mqtt_client = mosquitto_new("OS", true, NULL); + mqtt_client = mosquitto_new(_id, true, NULL); if (mqtt_client == NULL) { DEBUG_PRINTF("MQTT Init: Failed to initialise client\r\n"); return MQTT_ERROR; @@ -377,8 +409,8 @@ int OSMqtt::_init(void) { mosquitto_connect_callback_set(mqtt_client, _mqtt_connection_cb); mosquitto_disconnect_callback_set(mqtt_client, _mqtt_disconnection_cb); mosquitto_log_callback_set(mqtt_client, _mqtt_log_cb); - mosquitto_will_set(mqtt_client, MQTT_AVAILABILITY_TOPIC, strlen(MQTT_OFFLINE_PAYLOAD), MQTT_OFFLINE_PAYLOAD, 0, true); - + mosquitto_will_set(mqtt_client, getOnlineTopic(), strlen(MQTT_OFFLINE_PAYLOAD), MQTT_OFFLINE_PAYLOAD, 0, true); + return MQTT_SUCCESS; } @@ -400,10 +432,15 @@ int OSMqtt::_connect(void) { // Allow 10ms for the Broker's ack to be received. We need this on start-up so that the // connection is registered before we attempt to send our first NOTIFY_REBOOT notification. usleep(10000); + last_reconnect_attempt = millis(); return MQTT_SUCCESS; } +bool OSMqtt::reconnect() { + return mosquitto_reconnect(mqtt_client); +} + int OSMqtt::_disconnect(void) { int rc = mosquitto_disconnect(mqtt_client); return rc == MOSQ_ERR_SUCCESS ? MQTT_SUCCESS : MQTT_ERROR; @@ -427,12 +464,12 @@ int OSMqtt::_loop(void) { } bool OSMqtt::subscribe(const char *topic) { - if (!mqtt_client || !_enabled || os.status.network_fails > 0 || !_connected()) return false; + if (!mqtt_client || !_enabled || os.status.network_fails > 0) return false; return mosquitto_subscribe(mqtt_client, NULL, topic, 0); } bool OSMqtt::unsubscribe(const char *topic) { - if (!mqtt_client || !_enabled || os.status.network_fails > 0 || !_connected()) return false; + if (!mqtt_client || !_enabled || os.status.network_fails > 0) return false; return mosquitto_unsubscribe(mqtt_client, NULL, topic); } @@ -443,4 +480,5 @@ void OSMqtt::setCallback(void (*on_message)(struct mosquitto *, void *, const st const char * OSMqtt::_state_string(int error) { return mosquitto_strerror(error); } + #endif diff --git a/mqtt.h b/mqtt.h index 43256e7cd..bb9d79695 100644 --- a/mqtt.h +++ b/mqtt.h @@ -59,6 +59,7 @@ class OSMqtt { static bool connected(); static bool subscribe(const char *topic); static bool unsubscribe(const char *topic); + static bool reconnect(); #if defined(ARDUINO) static void setCallback(MQTT_CALLBACK_SIGNATURE); #else diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index e0c91457b..0d56a3cef 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -34,29 +34,30 @@ void sensor_mqtt_init() { * @brief * * @param mtopic reported topic opensprinkler/analogsensor/name - * @param topic topic pattern opensprinkler/ or opensprinkler/# or opensprinkler/#/abc/# + * @param pattern topic pattern opensprinkler/ or opensprinkler/# or opensprinkler/#/abc/# * @return true * @return false */ -bool mqtt_filter_matches(char* mtopic, char* topic) { +bool mqtt_filter_matches(char* mtopic, char* pattern) { - while (topic && mtopic) { + while (pattern && mtopic) { char ch1 = *mtopic++; - char ch2 = *topic++; + char ch2 = *pattern++; if (ch2 == '+') { //level ok up to "/" - while (mtopic) { + while (mtopic[0]) { if (ch1 == '/') break; ch1 = *mtopic++; } } else if (ch2 == '#') { //multilevel - char *p = strpbrk(topic, "#+"); + char *p = strpbrk(pattern, "#+"); if (!p) return true; - if (strncmp(topic, mtopic, p-topic)) { - mtopic = mtopic + (p-topic); - topic = p; + if (strncmp(pattern, mtopic, p-pattern) ==0) { + mtopic = mtopic + (p-pattern); + pattern = p; } - } else if (ch1 != ch2) + } + if (ch1 != ch2) return false; else if (ch1 == 0 && ch2 == 0) return true; @@ -74,10 +75,10 @@ char *strnlstr(const char *haystack, const char *needle, size_t needle_len, size int i; for (i=0; i<=(int)(len-needle_len); i++) { - if (haystack[0] == 0) - break; + if (haystack[0] == 0) + break; if ((haystack[0] == needle[0]) && - (0 == strncmp(haystack, needle, needle_len))) + (strncmp(haystack, needle, needle_len) == 0)) return (char *)haystack; haystack++; } diff --git a/sensor_ospi_ads1115.cpp b/sensor_ospi_ads1115.cpp index 5d6139905..27d12c484 100644 --- a/sensor_ospi_ads1115.cpp +++ b/sensor_ospi_ads1115.cpp @@ -36,7 +36,6 @@ * Read the OSPi 1.6 onboard ADS1115 A2D **/ int read_sensor_ospi(Sensor_t *sensor) { - DEBUG_PRINTLN(F("read_sensor_ospi")); if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; sensor->flags.data_ok = false; diff --git a/sensor_ospi_pcf8591.cpp b/sensor_ospi_pcf8591.cpp index 9595ee0c0..0a0c7a152 100644 --- a/sensor_ospi_pcf8591.cpp +++ b/sensor_ospi_pcf8591.cpp @@ -34,7 +34,6 @@ * Read the OSPi onboard PCF8591 A2D **/ int read_sensor_ospi(Sensor_t *sensor) { - DEBUG_PRINTLN(F("read_sensor_ospi")); if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; sensor->flags.data_ok = false; diff --git a/sensors.cpp b/sensors.cpp index 3c95c3379..a87e1c145 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -715,6 +715,8 @@ void push_message(Sensor_t *sensor) { sprintf_P(payload, PSTR("{\"nr\":%u,\"type\":%u,\"data_ok\":%u,\"time\":%u,\"value\":%d.%02d,\"unit\":\"%s\"}"), sensor->nr, sensor->type, sensor->flags.data_ok, sensor->last_read, (int)sensor->last_data, (int)(sensor->last_data*100)%100, getSensorUnit(sensor)); + if (!os.mqtt.connected()) + os.mqtt.reconnect(); os.mqtt.publish(topic, payload); } if (os.iopts[IOPT_IFTTT_ENABLE]) { From f50a5a68cc32348479fddd68504a8ebca587e950 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 22 Mar 2024 01:34:48 +0100 Subject: [PATCH 116/281] fixt compatibility --- .gitignore | 1 + mqtt.cpp | 2 +- sensor_mqtt.cpp | 4 ---- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 8b1bfb85a..892a67380 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ build-1284/* .gitignore~ build2.sh~ sensor_mqtt.cpp~ +opensprinkler_server.cpp.bak diff --git a/mqtt.cpp b/mqtt.cpp index 0b902b848..26c3dd532 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -357,7 +357,7 @@ const char * OSMqtt::_state_string(int rc) { bool OSMqtt::reconnect() { if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return false; - mqtt_client->reconnect(); + return _connect(); } #else diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 0d56a3cef..7f7d8acf8 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -67,9 +67,6 @@ bool mqtt_filter_matches(char* mtopic, char* pattern) { return true; } -#ifndef ARDUINO -#include - char *strnlstr(const char *haystack, const char *needle, size_t needle_len, size_t len) { int i; @@ -84,7 +81,6 @@ char *strnlstr(const char *haystack, const char *needle, size_t needle_len, size } return NULL; } -#endif /** * @brief mqtt callback From d210b97f903e163cc0edc181d62bb6a1ab0c7f0f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 26 Mar 2024 11:38:13 +0100 Subject: [PATCH 117/281] api status du with mqtt status --- opensprinkler_server.cpp | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8185b157c..738ed8e69 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3011,13 +3011,15 @@ void server_usage(OTF_PARAMS_DEF) { print_header(); #endif +extern uint32_t ping_ok; + #if defined(ESP8266) struct FSInfo fsinfo; boolean ok = LittleFS.info(fsinfo); - bfill.emit_p(PSTR("{\"status\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D}"), + bfill.emit_p(PSTR("{\"status\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D,\"pingok\":$D,\"mqtt\":$D}"), ok, fsinfo.totalBytes, fsinfo.usedBytes, @@ -3025,7 +3027,12 @@ void server_usage(OTF_PARAMS_DEF) { fsinfo.blockSize, fsinfo.pageSize, fsinfo.maxOpenFiles, - fsinfo.maxPathLength); + fsinfo.maxPathLength, + ping_ok, + os.mqtt.connected()); +#else + bfill.emit_p(PSTR("{\"pingok\":$D,\"mqtt\":$D}"), ping_ok, os.mqtt.connected()); + #endif handle_return(HTML_OK); } From 37991380223389556e7a108ed12fa37a8331e80d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 28 Mar 2024 23:18:21 +0100 Subject: [PATCH 118/281] added ifttt status to diag --- opensprinkler_server.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 738ed8e69..e81eacb71 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3019,7 +3019,7 @@ extern uint32_t ping_ok; boolean ok = LittleFS.info(fsinfo); - bfill.emit_p(PSTR("{\"status\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D,\"pingok\":$D,\"mqtt\":$D}"), + bfill.emit_p(PSTR("{\"status\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D,\"pingok\":$D,\"mqtt\":$D,\"ifttt\":$D}"), ok, fsinfo.totalBytes, fsinfo.usedBytes, @@ -3029,9 +3029,10 @@ extern uint32_t ping_ok; fsinfo.maxOpenFiles, fsinfo.maxPathLength, ping_ok, - os.mqtt.connected()); + os.mqtt.connected(), + os.iopts[IOPT_IFTTT_ENABLE]); #else - bfill.emit_p(PSTR("{\"pingok\":$D,\"mqtt\":$D}"), ping_ok, os.mqtt.connected()); + bfill.emit_p(PSTR("{\"status\":$D,\"pingok\":$D,\"mqtt\":$D,\"ifttt\":$D}"), 1, ping_ok, os.mqtt.connected(), os.iopts[IOPT_IFTTT_ENABLE]); #endif handle_return(HTML_OK); From 8ed63bcfbf63b5ee96c1b1e451041e089b620b6b Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 29 Mar 2024 13:24:00 +0100 Subject: [PATCH 119/281] Fixt typo --- opensprinkler_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index e81eacb71..7ccce6556 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2991,7 +2991,7 @@ void server_sensor_types(OTF_PARAMS_DEF) { /** * du - * List supported sensor types + * system resources status **/ void server_usage(OTF_PARAMS_DEF) { #if defined(ESP8266) From 344b2d86d1649f87f39ff52072078278ff202b71 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 29 Mar 2024 13:43:08 +0100 Subject: [PATCH 120/281] fixt ospi status --- opensprinkler_server.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 7ccce6556..5f2731a39 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3032,7 +3032,7 @@ extern uint32_t ping_ok; os.mqtt.connected(), os.iopts[IOPT_IFTTT_ENABLE]); #else - bfill.emit_p(PSTR("{\"status\":$D,\"pingok\":$D,\"mqtt\":$D,\"ifttt\":$D}"), 1, ping_ok, os.mqtt.connected(), os.iopts[IOPT_IFTTT_ENABLE]); + bfill.emit_p(PSTR("{\"status\":$D,\"mqtt\":$D,\"ifttt\":$D}"), 1, os.mqtt.connected(), os.iopts[IOPT_IFTTT_ENABLE]); #endif handle_return(HTML_OK); From ea13c8e0f43f07c11db8e1de4c10190a5ec9f0cf Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 1 Apr 2024 01:16:20 +0200 Subject: [PATCH 121/281] Program adjustments calulations --- opensprinkler_server.cpp | 68 +++++++++++++++++++++++++++++++++++++++- sensors.cpp | 57 ++++++++++++++++++--------------- sensors.h | 1 + 3 files changed, 100 insertions(+), 26 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 5f2731a39..4d54f0f3d 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3052,6 +3052,15 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("server_sensorprog_calc")); //uint nr or uint prog +#if defined(ESP8266) + // 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 + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr double adj = calc_sensor_watering_by_nr(nr); @@ -3066,7 +3075,64 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { handle_return(HTML_OK); } - handle_return(HTML_DATA_MISSING); + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Adjustment type + if (type == 0) { + handle_return(HTML_DATA_MISSING); + } + + //methods for visual calculation: + ProgSensorAdjust_t p; + memset(&p, 0, sizeof(p)); + p.type = type; + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + handle_return(HTML_DATA_MISSING); + Sensor_t *sensor = sensor_by_nr(strtoul(tmp_buffer, NULL, 0)); // Sensor nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) + handle_return(HTML_DATA_MISSING); + p.factor1 = atof(tmp_buffer); // Factor 1 + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor2"), true)) + handle_return(HTML_DATA_MISSING); + p.factor2 = atof(tmp_buffer); // Factor 2 + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) + handle_return(HTML_DATA_MISSING); + p.min = atof(tmp_buffer); // Min value + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) + handle_return(HTML_DATA_MISSING); + p.max = atof(tmp_buffer); // Max value + + int diff = p.max-p.min; + int minEx = p.min - diff/2; + int maxEx = p.max + diff/2; + + bfill.emit_p(PSTR("{\"adjustment\":{\"min\":$D,\"max\":$D,\"unit\":\"$S\","), minEx, maxEx, getSensorUnit(sensor)); + + int nvalues = max(11, maxEx-minEx+1); + double inVal[nvalues]; + double outVal[nvalues]; + for (int i = 0; i < nvalues; i++) { + inVal[i] = (double)(maxEx - minEx) * (double)i / (nvalues-1) + minEx; + outVal[i] = calc_sensor_watering_int(&p, inVal[i]); + } + + bfill.emit_p(PSTR("\"inval\":[")); + for (int i = 0; i < nvalues; i++) { + if(i > 0) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("$E"), inVal[i]); + } + bfill.emit_p(PSTR("],\"outval\":[")); + for (int i = 0; i < nvalues; i++) { + if(i > 0) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("$E"), outVal[i]); + } + bfill.emit_p(PSTR("]}}")); + + handle_return(HTML_OK); } const int prog_types[] = { diff --git a/sensors.cpp b/sensors.cpp index a87e1c145..7243f8bd3 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1424,7 +1424,7 @@ int set_sensor_address(Sensor_t *sensor, byte new_address) { return HTTP_RQT_NOT_RECEIVED; } -double calc_linear(ProgSensorAdjust_t *p, Sensor_t *sensor) { +double calc_linear(ProgSensorAdjust_t *p, double sensorData) { // min max factor1 factor2 // 10..90 -> 5..1 factor1 > factor2 @@ -1434,31 +1434,33 @@ double calc_linear(ProgSensorAdjust_t *p, Sensor_t *sensor) { // 10..90 -> 1..5 factor1 < factor2 // a b c d // (sensorData-a) / (b-a) * (d-c) + c - - double sensorData = sensor->last_data; + // Limit to min/max: if (sensorData < p->min) sensorData = p->min; if (sensorData > p->max) sensorData = p->max; //Calculate: + double div = (p->max - p->min); + if (abs(div) < 0.00001) return 0; + if (p->factor1 > p->factor2) { // invers scaling factor: - return (p->max - sensorData) / (p->max - p->min) * (p->factor1 - p->factor2) + p->factor2; + return (p->max - sensorData) / div * (p->factor1 - p->factor2) + p->factor2; } else { // upscaling factor: - return (sensorData - p->min) / (p->max - p->min) * (p->factor2 - p->factor1) + p->factor1; + return (sensorData - p->min) / div * (p->factor2 - p->factor1) + p->factor1; } } -double calc_digital_min(ProgSensorAdjust_t *p, Sensor_t *sensor) { - return sensor->last_data <= p->min? p->factor1:p->factor2; +double calc_digital_min(ProgSensorAdjust_t *p, double sensorData) { + return sensorData <= p->min? p->factor1:p->factor2; } -double calc_digital_max(ProgSensorAdjust_t *p, Sensor_t *sensor) { - return sensor->last_data >= p->max? p->factor2:p->factor1; +double calc_digital_max(ProgSensorAdjust_t *p, double sensorData) { + return sensorData >= p->max? p->factor2:p->factor1; } -double calc_digital_minmax(ProgSensorAdjust_t *p, Sensor_t *sensor) { - if (sensor->last_data <= p->min) return p->factor1; - if (sensor->last_data >= p->max) return p->factor1; +double calc_digital_minmax(ProgSensorAdjust_t *p, double sensorData) { + if (sensorData <= p->min) return p->factor1; + if (sensorData >= p->max) return p->factor1; return p->factor2; } /** @@ -1476,16 +1478,7 @@ double calc_sensor_watering(uint prog) { Sensor_t *sensor = sensor_by_nr(p->sensor); if (sensor && sensor->flags.enable && sensor->flags.data_ok) { - double res = 0; - switch(p->type) { - case PROG_NONE: res = 1; break; - case PROG_LINEAR: res = calc_linear(p, sensor); break; - case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor); break; - case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor); break; - case PROG_DIGITAL_MINMAX: res = calc_digital_minmax(p, sensor); break; - default: res = 0; - } - + double res = calc_sensor_watering_int(p, sensor->last_data); result = result * res; } } @@ -1499,6 +1492,20 @@ double calc_sensor_watering(uint prog) { return result; } +double calc_sensor_watering_int(ProgSensorAdjust_t *p, double sensorData) { + double res = 0; + if (!p) return res; + switch(p->type) { + case PROG_NONE: res = 1; break; + case PROG_LINEAR: res = calc_linear(p, sensorData); break; + case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensorData); break; + case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensorData); break; + case PROG_DIGITAL_MINMAX: res = calc_digital_minmax(p, sensorData); break; + default: res = 0; + } + return res; +} + /** * @brief calculate adjustment * @@ -1517,9 +1524,9 @@ double calc_sensor_watering_by_nr(uint nr) { double res = 0; switch(p->type) { case PROG_NONE: res = 1; break; - case PROG_LINEAR: res = calc_linear(p, sensor); break; - case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor); break; - case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor); break; + case PROG_LINEAR: res = calc_linear(p, sensor->last_data); break; + case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor->last_data); break; + case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor->last_data); break; default: res = 0; } diff --git a/sensors.h b/sensors.h index 93fba8350..7d90e0f68 100644 --- a/sensors.h +++ b/sensors.h @@ -278,6 +278,7 @@ ProgSensorAdjust_t *prog_adjust_by_nr(uint nr); ProgSensorAdjust_t *prog_adjust_by_idx(uint idx); double calc_sensor_watering(uint prog); double calc_sensor_watering_by_nr(uint nr); +double calc_sensor_watering_int(ProgSensorAdjust_t *p, double sensorData); void GetSensorWeather(); //PUSH Message to MQTT and others: From e9059e87997d5051effbfa5434f81988871741e8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 1 Apr 2024 01:22:49 +0200 Subject: [PATCH 122/281] Fixt dupIdent for ospi --- opensprinkler_server.cpp | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 4d54f0f3d..611d200b5 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3083,32 +3083,32 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { } //methods for visual calculation: - ProgSensorAdjust_t p; - memset(&p, 0, sizeof(p)); - p.type = type; + ProgSensorAdjust_t progAdj; + memset(&progAdj, 0, sizeof(progAdj)); + progAdj.type = type; if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) handle_return(HTML_DATA_MISSING); Sensor_t *sensor = sensor_by_nr(strtoul(tmp_buffer, NULL, 0)); // Sensor nr if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) handle_return(HTML_DATA_MISSING); - p.factor1 = atof(tmp_buffer); // Factor 1 + progAdj.factor1 = atof(tmp_buffer); // Factor 1 if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor2"), true)) handle_return(HTML_DATA_MISSING); - p.factor2 = atof(tmp_buffer); // Factor 2 + progAdj.factor2 = atof(tmp_buffer); // Factor 2 if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) handle_return(HTML_DATA_MISSING); - p.min = atof(tmp_buffer); // Min value + progAdj.min = atof(tmp_buffer); // Min value if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) handle_return(HTML_DATA_MISSING); - p.max = atof(tmp_buffer); // Max value + progAdj.max = atof(tmp_buffer); // Max value - int diff = p.max-p.min; - int minEx = p.min - diff/2; - int maxEx = p.max + diff/2; + int diff = progAdj.max-progAdj.min; + int minEx = progAdj.min - diff/2; + int maxEx = progAdj.max + diff/2; bfill.emit_p(PSTR("{\"adjustment\":{\"min\":$D,\"max\":$D,\"unit\":\"$S\","), minEx, maxEx, getSensorUnit(sensor)); @@ -3117,7 +3117,7 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { double outVal[nvalues]; for (int i = 0; i < nvalues; i++) { inVal[i] = (double)(maxEx - minEx) * (double)i / (nvalues-1) + minEx; - outVal[i] = calc_sensor_watering_int(&p, inVal[i]); + outVal[i] = calc_sensor_watering_int(&progAdj, inVal[i]); } bfill.emit_p(PSTR("\"inval\":[")); From d91c26397bee79558357d64084467bbb805f2bf4 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 1 Apr 2024 01:54:33 +0200 Subject: [PATCH 123/281] Fix calculating --- opensprinkler_server.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 611d200b5..52c10b600 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3086,10 +3086,14 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { ProgSensorAdjust_t progAdj; memset(&progAdj, 0, sizeof(progAdj)); progAdj.type = type; + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) handle_return(HTML_DATA_MISSING); - Sensor_t *sensor = sensor_by_nr(strtoul(tmp_buffer, NULL, 0)); // Sensor nr - + progAdj.sensor = strtoul(tmp_buffer, NULL, 0); + Sensor_t *sensor = sensor_by_nr(progAdj.sensor); // Sensor nr + if (!sensor) + handle_return(HTML_DATA_MISSING); + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) handle_return(HTML_DATA_MISSING); progAdj.factor1 = atof(tmp_buffer); // Factor 1 From 3c05554daa839b6f04eab71a56885dba23022192 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 1 Apr 2024 02:15:49 +0200 Subject: [PATCH 124/281] Fix analog sensor API sd for OSPi --- opensprinkler_server.cpp | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 52c10b600..9e0fc8967 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2441,6 +2441,10 @@ void server_sensor_list(OTF_PARAMS_DEF) { DEBUG_PRINT(F("server_count: ")); DEBUG_PRINTLN(sensor_count()); + uint test = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("test"), true)) + test = strtoul(tmp_buffer, NULL, 0); // Sensor nr + #if defined(ESP8266) // 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(). @@ -2450,10 +2454,6 @@ void server_sensor_list(OTF_PARAMS_DEF) { print_header(); #endif - uint test = 0; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("test"), true)) - test = strtoul(tmp_buffer, NULL, 0); // Sensor nr - if (test) { bfill.emit_p(PSTR("{\"test\":$D}"), test); } else { @@ -3052,15 +3052,6 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("server_sensorprog_calc")); //uint nr or uint prog -#if defined(ESP8266) - // 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 - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr double adj = calc_sensor_watering_by_nr(nr); @@ -3114,6 +3105,15 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { int minEx = progAdj.min - diff/2; int maxEx = progAdj.max + diff/2; +#if defined(ESP8266) + // 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("{\"adjustment\":{\"min\":$D,\"max\":$D,\"unit\":\"$S\","), minEx, maxEx, getSensorUnit(sensor)); int nvalues = max(11, maxEx-minEx+1); From d93e695eb9b1543b5e9f96551ff5864d9b800c8d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 5 Apr 2024 16:20:02 +0200 Subject: [PATCH 125/281] Version 2.3.1(151) Added Support for 3.2 W5500 with adapter --- OpenSprinkler.cpp | 160 ++++++++++++++++++++++++--------------- defines.h | 6 +- opensprinkler_server.cpp | 6 +- 3 files changed, 105 insertions(+), 67 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 7e88ea61f..8d9639d26 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -499,73 +499,111 @@ byte OpenSprinkler::start_network() { #endif } -byte 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 +bool init_W5500() { + DEBUG_PRINTLN(F("detect existence of W5500")); + /* this is copied from w5500.cpp wizchip_sw_reset + * perform a software reset and see if we get a correct response + * without this, eth.begin will crash if W5500 is not connected + * ideally wizchip_sw_reset should return a value but since it doesn't + * we have to extract it code here + * */ + static const uint8_t AccessModeRead = (0x00 << 2); + static const uint8_t AccessModeWrite = (0x01 << 2); + static const uint8_t BlockSelectCReg = (0x00 << 3); + + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(32000000); + + pinMode(PIN_ETHER_CS, OUTPUT); + // ==> setMR(MR_RST) + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer((0x00 & 0xFF00) >> 8); + SPI.transfer((0x00 & 0x00FF) >> 0); + SPI.transfer(BlockSelectCReg | AccessModeWrite); + SPI.transfer(0x80); + digitalWrite(PIN_ETHER_CS, HIGH); + + // ==> ret = getMR() + uint8_t ret; + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer((0x00 & 0xFF00) >> 8); + SPI.transfer((0x00 & 0x00FF) >> 0); + SPI.transfer(BlockSelectCReg | AccessModeRead); + ret = SPI.transfer(0); + digitalWrite(PIN_ETHER_CS, HIGH); + + if(ret!=0) { // ret is expected to be 0 + SPI.end(); + return false; + } + + eth.isW5500 = true; + DEBUG_PRINTLN(F("found W5500")); + return true; +} + +bool init_ENC28J60() { + /* this is copied from enc28j60.cpp geterevid + * check to see if the hardware revision number if expected + * */ + DEBUG_PRINTLN(F("detect existence of ENC28J60")); + #define MAADRX_BANK 0x03 + #define EREVID 0x12 + #define ECON1 0x1f SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); SPI.setFrequency(4000000); - if(eth.isW5500) { - DEBUG_PRINTLN(F("detect existence of W5500")); - /* this is copied from w5500.cpp wizchip_sw_reset - * perform a software reset and see if we get a correct response - * without this, eth.begin will crash if W5500 is not connected - * ideally wizchip_sw_reset should return a value but since it doesn't - * we have to extract it code here - * */ - static const uint8_t AccessModeRead = (0x00 << 2); - static const uint8_t AccessModeWrite = (0x01 << 2); - static const uint8_t BlockSelectCReg = (0x00 << 3); - pinMode(PIN_ETHER_CS, OUTPUT); - // ==> setMR(MR_RST) - digitalWrite(PIN_ETHER_CS, LOW); - SPI.transfer((0x00 & 0xFF00) >> 8); - SPI.transfer((0x00 & 0x00FF) >> 0); - SPI.transfer(BlockSelectCReg | AccessModeWrite); - SPI.transfer(0x80); - digitalWrite(PIN_ETHER_CS, HIGH); - - // ==> ret = getMR() - uint8_t ret; - digitalWrite(PIN_ETHER_CS, LOW); - SPI.transfer((0x00 & 0xFF00) >> 8); - SPI.transfer((0x00 & 0x00FF) >> 0); - SPI.transfer(BlockSelectCReg | AccessModeRead); - ret = SPI.transfer(0); - digitalWrite(PIN_ETHER_CS, HIGH); - if(ret!=0) return 0; // ret is expected to be 0 + // ==> setregbank(MAADRX_BANK); + pinMode(PIN_ETHER_CS, OUTPUT); + uint8_t r; + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer(0x00 | (ECON1 & 0x1f)); + r = SPI.transfer(0); + DEBUG_PRINT("ECON1=") + DEBUG_PRINTLN(r); + if (r == 2) { + SPI.end(); + return false; + } + digitalWrite(PIN_ETHER_CS, HIGH); + + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer(0x40 | (ECON1 & 0x1f)); + SPI.transfer((r & 0xfc) | (MAADRX_BANK & 0x03)); + digitalWrite(PIN_ETHER_CS, HIGH); + + // ==> r = readreg(EREVID); + digitalWrite(PIN_ETHER_CS, LOW); + SPI.transfer(0x00 | (EREVID & 0x1f)); + r = SPI.transfer(0); + DEBUG_PRINTLN(r); + digitalWrite(PIN_ETHER_CS, HIGH); + if(r==0 || r==255) { // r is expected to be a non-255 revision number + SPI.end(); + return false; + } + + eth.isW5500 = false; + DEBUG_PRINTLN(F("found ENC28J60")); + return true; +} + +byte OpenSprinkler::start_ether() { +#if defined(ESP8266) + if(hw_rev<2) return 0; // ethernet capability is only available when hw_rev>=2 + + // os 3.2 uses enc28j60 and 3.3 uses w5500 + if (hw_rev==2) { + if (!init_ENC28J60() && !init_W5500()) + return 0; } else { - /* this is copied from enc28j60.cpp geterevid - * check to see if the hardware revision number if expected - * */ - DEBUG_PRINTLN(F("detect existence of ENC28J60")); - #define MAADRX_BANK 0x03 - #define EREVID 0x12 - #define ECON1 0x1f - - // ==> setregbank(MAADRX_BANK); - pinMode(PIN_ETHER_CS, OUTPUT); - uint8_t r; - digitalWrite(PIN_ETHER_CS, LOW); - SPI.transfer(0x00 | (ECON1 & 0x1f)); - r = SPI.transfer(0); - digitalWrite(PIN_ETHER_CS, HIGH); - - digitalWrite(PIN_ETHER_CS, LOW); - SPI.transfer(0x40 | (ECON1 & 0x1f)); - SPI.transfer((r & 0xfc) | (MAADRX_BANK & 0x03)); - digitalWrite(PIN_ETHER_CS, HIGH); - - // ==> r = readreg(EREVID); - digitalWrite(PIN_ETHER_CS, LOW); - SPI.transfer(0x00 | (EREVID & 0x1f)); - r = SPI.transfer(0); - digitalWrite(PIN_ETHER_CS, HIGH); - if(r==0 || r==255) return 0; // r is expected to be a non-255 revision number + if (!init_W5500()) + return 0; } load_hardware_mac((uint8_t*)tmp_buffer, true); @@ -581,7 +619,7 @@ byte 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()+30000; // 30 seconds time out + ulong timeout = millis()+45000; // 45 seconds time out while (!eth.connected()) { DEBUG_PRINT("."); delay(1000); diff --git a/defines.h b/defines.h index ed9d454d4..4870c43e6 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug -#define SERIAL_DEBUG +//#define ENABLE_DEBUG // enable serial debug +//#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -37,7 +37,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 150 // Firmware minor version +#define OS_FW_MINOR 151 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 9e0fc8967..05889f512 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2437,9 +2437,9 @@ void server_sensor_list(OTF_PARAMS_DEF) { char *p = get_buffer; #endif - DEBUG_PRINTLN(F("server_sensor_list")); - DEBUG_PRINT(F("server_count: ")); - DEBUG_PRINTLN(sensor_count()); + //DEBUG_PRINTLN(F("server_sensor_list")); + //DEBUG_PRINT(F("server_count: ")); + //DEBUG_PRINTLN(sensor_count()); uint test = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("test"), true)) From 028e5aa4cdc6f92352b2a8d09bbd286b856a07e8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 9 Apr 2024 01:10:31 +0200 Subject: [PATCH 126/281] ADC1115 optimization, log delete per sensor --- opensprinkler_server.cpp | 35 ++++++++++++++++++++---------- sensors.cpp | 47 ++++++++++++++++++++++++++++++---------- sensors.h | 1 + 3 files changed, 60 insertions(+), 23 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 05889f512..6f7520930 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2679,8 +2679,11 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { char *p = get_buffer; #endif int log = -1; + uint nr = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Filter log for sensor-nr log = atoi(tmp_buffer); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr + nr = strtoul(tmp_buffer, NULL, 0); DEBUG_PRINTLN(F("server_sensorlog_clear")); @@ -2693,20 +2696,28 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { print_header(); #endif - ulong log_size = sensorlog_size(LOG_STD); - ulong log_sizeW = sensorlog_size(LOG_WEEK); - ulong log_sizeM = sensorlog_size(LOG_MONTH); + if (nr > 0) { + if (log == -1) { + sensorlog_clear_sensor(nr, LOG_STD); + sensorlog_clear_sensor(nr, LOG_WEEK); + sensorlog_clear_sensor(nr, LOG_MONTH); + } else { + sensorlog_clear_sensor(nr, log); + } + } else { + ulong log_size = sensorlog_size(LOG_STD); + ulong log_sizeW = sensorlog_size(LOG_WEEK); + ulong log_sizeM = sensorlog_size(LOG_MONTH); - if (log == -1) { - sensorlog_clear_all(); - bfill.emit_p(PSTR("{\"deleted\":$L,\"deleted_week\":$L,\"deleted_month\":$L}"), log_size, log_sizeW, log_sizeM); - } - else { - sensorlog_clear(log==LOG_STD, log==LOG_WEEK, log==LOG_MONTH); - bfill.emit_p(PSTR("{\"deleted\":$L}"), log==LOG_STD?log_size:log=LOG_WEEK?log_sizeW:log_sizeM); + if (log == -1) { + sensorlog_clear_all(); + bfill.emit_p(PSTR("{\"deleted\":$L,\"deleted_week\":$L,\"deleted_month\":$L}"), log_size, log_sizeW, log_sizeM); + } + else { + sensorlog_clear(log==LOG_STD, log==LOG_WEEK, log==LOG_MONTH); + bfill.emit_p(PSTR("{\"deleted\":$L}"), log==LOG_STD?log_size:log=LOG_WEEK?log_sizeW:log_sizeM); + } } - - handle_return(HTML_OK); } diff --git a/sensors.cpp b/sensors.cpp index 7243f8bd3..47f756a07 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -450,6 +450,39 @@ void sensorlog_clear(bool std, bool week, bool month) { } } +ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log) { + SensorLog_t sensorlog; + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + ulong size2 = size+file_size(fcur) / SENSORLOG_STORE_SIZE; + const char *f; + ulong idxr = 0; + ulong n = 0; + while (idxr < size2) { + ulong idx = idxr; + if (idx >= size) { + idx -= size; + f = fcur; + } else { + f = flast; + } + + ulong result = file_read_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); + if (result == 0) + break; + if (sensorlog.nr == sensorNr) { + sensorlog.nr = 0; + file_write_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); + n++; + } + idxr++; + } + return n; +} + + SensorLog_t *sensorlog_load(uint8_t log, ulong idx) { SensorLog_t *sensorlog = new SensorLog_t; return sensorlog_load(log, idx, sensorlog); @@ -780,21 +813,13 @@ int read_sensor_adc(Sensor_t *sensor) { if (sensor->id >= 8) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: - int port = sensor->id < 4? 72 : 73; + int port = sensor->id < 4? 0x48 : 0x49; int id = sensor->id % 4; sensor->flags.data_ok = false; ADS1115 adc(port); - bool active = adc.begin(); - if (active) - adc.setGain(1); - DEBUG_PRINT(F("adc sensor found=")); - DEBUG_PRINTLN(active); - - if (!active) - return HTTP_RQT_NOT_RECEIVED; - - + adc.begin(); + adc.reset(); sensor->repeat_native += adc.readADC(id); if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ) return HTTP_RQT_NOT_RECEIVED; diff --git a/sensors.h b/sensors.h index 7d90e0f68..f98bbbcf2 100644 --- a/sensors.h +++ b/sensors.h @@ -258,6 +258,7 @@ bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog); bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time); void sensorlog_clear_all(); void sensorlog_clear(bool std, bool week, bool month); +ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log); SensorLog_t *sensorlog_load(uint8_t log, ulong pos); SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t* sensorlog); int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t* sensorlog); From 182e5e9160c4e15c464240c8ef57a58aca83dcf2 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 10 Apr 2024 23:58:01 +0200 Subject: [PATCH 127/281] Better detection of ENC28J60/W5500 --- OpenSprinkler.cpp | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 8d9639d26..0acc8db32 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -499,7 +499,7 @@ byte OpenSprinkler::start_network() { #endif } -bool init_W5500() { +bool init_W5500(boolean initSPI) { DEBUG_PRINTLN(F("detect existence of W5500")); /* this is copied from w5500.cpp wizchip_sw_reset * perform a software reset and see if we get a correct response @@ -511,12 +511,15 @@ bool init_W5500() { static const uint8_t AccessModeWrite = (0x01 << 2); static const uint8_t BlockSelectCReg = (0x00 << 3); - SPI.begin(); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(32000000); - + if (initSPI) { + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(32000000); + } + pinMode(PIN_ETHER_CS, OUTPUT); + // ==> setMR(MR_RST) digitalWrite(PIN_ETHER_CS, LOW); SPI.transfer((0x00 & 0xFF00) >> 8); @@ -535,7 +538,6 @@ bool init_W5500() { digitalWrite(PIN_ETHER_CS, HIGH); if(ret!=0) { // ret is expected to be 0 - SPI.end(); return false; } @@ -567,7 +569,6 @@ bool init_ENC28J60() { DEBUG_PRINT("ECON1=") DEBUG_PRINTLN(r); if (r == 2) { - SPI.end(); return false; } digitalWrite(PIN_ETHER_CS, HIGH); @@ -584,7 +585,6 @@ bool init_ENC28J60() { DEBUG_PRINTLN(r); digitalWrite(PIN_ETHER_CS, HIGH); if(r==0 || r==255) { // r is expected to be a non-255 revision number - SPI.end(); return false; } @@ -599,10 +599,10 @@ byte OpenSprinkler::start_ether() { // os 3.2 uses enc28j60 and 3.3 uses w5500 if (hw_rev==2) { - if (!init_ENC28J60() && !init_W5500()) + if (!init_ENC28J60() && !init_W5500(false)) return 0; } else { - if (!init_W5500()) + if (!init_W5500(true)) return 0; } @@ -620,9 +620,11 @@ byte OpenSprinkler::start_ether() { lcd_print_line_clear_pgm(eth.isW5500 ? PSTR(" (w5500) ") : PSTR(" (enc28j60) "), 2); ulong timeout = millis()+45000; // 45 seconds time out + while (!eth.connected()) { DEBUG_PRINT("."); delay(1000); + yield(); if(millis()>timeout) return 0; } From c42cbc295c43645a9a5c1501185873e7aeee8b9d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 12 Apr 2024 13:48:13 +0200 Subject: [PATCH 128/281] fixt memory leak, added offline sensor reading --- defines.h | 6 +++--- main.cpp | 3 +-- opensprinkler_server.cpp | 3 ++- sensors.cpp | 36 ++++++++++++++++++++++-------------- sensors.h | 2 +- utils.cpp | 13 +++++++++++++ utils.h | 1 + 7 files changed, 43 insertions(+), 21 deletions(-) diff --git a/defines.h b/defines.h index 4870c43e6..598f6eae2 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug -//#define SERIAL_DEBUG +#define ENABLE_DEBUG // enable serial debug +#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -37,7 +37,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 151 // Firmware minor version +#define OS_FW_MINOR 152 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index 95b5b01de..3e881808e 100644 --- a/main.cpp +++ b/main.cpp @@ -1045,8 +1045,7 @@ void do_loop() } // read analog sensors - if (curr_time && os.network_connected() && os.checkwt_success_lasttime) - read_all_sensors(); + read_all_sensors(curr_time && os.network_connected() && os.checkwt_success_lasttime); static byte reboot_notification = 1; if(reboot_notification) { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 6f7520930..2837ace29 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3030,8 +3030,9 @@ extern uint32_t ping_ok; boolean ok = LittleFS.info(fsinfo); - bfill.emit_p(PSTR("{\"status\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D,\"pingok\":$D,\"mqtt\":$D,\"ifttt\":$D}"), + bfill.emit_p(PSTR("{\"status\":$D,\"freeMemory\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D,\"pingok\":$D,\"mqtt\":$D,\"ifttt\":$D}"), ok, + freeMemory(), fsinfo.totalBytes, fsinfo.usedBytes, fsinfo.totalBytes-fsinfo.usedBytes, diff --git a/sensors.cpp b/sensors.cpp index 47f756a07..0848bb6b5 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -658,12 +658,14 @@ void calc_sensorlogs() if (time >= next_month_calc) { - if (!sensorlog) - sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); - log_size = sensorlog_size(LOG_WEEK); - if (log_size <= 0) + if (log_size <= 0) { + if (sensorlog) + free(sensorlog); return; + } + if (!sensorlog) + sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); DEBUG_PRINTLN(F("calc_sensorlogs MONTH start")); ulong size = sensorlog_size(LOG_MONTH); @@ -742,6 +744,7 @@ void push_message(Sensor_t *sensor) { char* postval = tmp_buffer; if (os.mqtt.enabled()) { + DEBUG_PRINTLN("push mqtt1"); os.sopt_load(SOPT_DEVICE_NAME, topic); strncat_P(topic, PSTR("/analogsensor/"), sizeof(topic)-1); strncat(topic, sensor->name, sizeof(topic)-1); @@ -751,8 +754,10 @@ void push_message(Sensor_t *sensor) { if (!os.mqtt.connected()) os.mqtt.reconnect(); os.mqtt.publish(topic, payload); + DEBUG_PRINTLN("push mqtt2"); } if (os.iopts[IOPT_IFTTT_ENABLE]) { + DEBUG_PRINTLN("push ifttt"); strcpy_P(postval, PSTR("{\"value1\":\"On site [")); os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval)); strcat_P(postval, PSTR("], ")); @@ -772,13 +777,15 @@ void push_message(Sensor_t *sensor) { SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, sensor_remote_http_callback); + DEBUG_PRINTLN("push ifttt2"); } } -void read_all_sensors() { +void read_all_sensors(boolean online) { //DEBUG_PRINTLN(F("read_all_sensors")); - if (!sensors || os.status.network_fails>0 || os.iopts[IOPT_REMOTE_EXT_MODE]) return; - + if (!sensors) + return; + ulong time = os.now_tz(); if (time < os.powerup_lasttime+30) return; //wait 30s before first sensor read @@ -787,18 +794,19 @@ void read_all_sensors() { while (sensor) { if (time >= sensor->last_read + sensor->read_interval || sensor->repeat_read) { - int result = read_sensor(sensor); - if (result == HTTP_RQT_SUCCESS) { - sensorlog_add(LOG_STD, sensor, time); - push_message(sensor); - } else if (result == HTTP_RQT_TIMEOUT) { - sensor->last_read = time - sensor->read_interval + 5; + if (online || sensor->ip == 0) { + int result = read_sensor(sensor); + if (result == HTTP_RQT_SUCCESS) { + sensorlog_add(LOG_STD, sensor, time); + push_message(sensor); + } else if (result == HTTP_RQT_TIMEOUT) { + sensor->last_read = time - sensor->read_interval + 5; + } } } sensor = sensor->next; } sensor_update_groups(); - calc_sensorlogs(); } diff --git a/sensors.h b/sensors.h index f98bbbcf2..88c18cd7f 100644 --- a/sensors.h +++ b/sensors.h @@ -243,7 +243,7 @@ uint sensor_count(); boolean sensor_isgroup(Sensor_t *sensor); void sensor_update_groups(); -void read_all_sensors(); +void read_all_sensors(boolean online); Sensor_t *sensor_by_nr(uint nr); Sensor_t *sensor_by_idx(uint idx); diff --git a/utils.cpp b/utils.cpp index aae48b79d..2f5c41aad 100644 --- a/utils.cpp +++ b/utils.cpp @@ -30,6 +30,7 @@ extern OpenSprinkler os; #if defined(ESP8266) #include #include + #include #else #include #include "SdFat.h" @@ -629,4 +630,16 @@ void str2mac(const char *_str, byte mac[]) { yield(); } } +#endif + +#ifdef ARDUINO + +size_t freeMemory() { + return ESP.getFreeHeap(); +} +#else + +long freeMemory() { + return -1; +} #endif \ No newline at end of file diff --git a/utils.h b/utils.h index 1a51cb3be..99d96dbe8 100644 --- a/utils.h +++ b/utils.h @@ -58,6 +58,7 @@ int16_t water_time_decode_signed(byte i); void urlDecode(char *); void peel_http_header(char*); void strReplace(char *, char c, char r); +size_t freeMemory(); #define date_encode(m,d) ((m<<5)+d) #define MIN_ENCODED_DATE date_encode(1,1) From 38f399c1c3c2674b942b5ba94c04805f0c003f86 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 13 Apr 2024 00:11:00 +0200 Subject: [PATCH 129/281] - Support for max 4x ADS1115 (OSPi) 0x48 / 0x49 / 0x4A / 0x4B - fixt compile error (OSPi) --- sensor_ospi_ads1115.cpp | 16 ++-------------- utils.cpp | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/sensor_ospi_ads1115.cpp b/sensor_ospi_ads1115.cpp index 27d12c484..120e08d3b 100644 --- a/sensor_ospi_ads1115.cpp +++ b/sensor_ospi_ads1115.cpp @@ -52,20 +52,8 @@ int read_sensor_ospi(Sensor_t *sensor) { DRIVER_ADS1115_LINK_DELAY_MS(&gs_handle, ads1115_interface_delay_ms); DRIVER_ADS1115_LINK_DEBUG_PRINT(&gs_handle, ads1115_interface_debug_print); - ads1115_address_t addr = ADS1115_ADDR_GND; - ads1115_channel_t channel = ADS1115_CHANNEL_AIN0_GND; - /* set channel */ - switch(sensor->id) { - case 0: channel = ADS1115_CHANNEL_AIN0_GND; break; - case 1: channel = ADS1115_CHANNEL_AIN1_GND; break; - case 2: channel = ADS1115_CHANNEL_AIN2_GND; break; - case 3: channel = ADS1115_CHANNEL_AIN3_GND; break; - case 4: addr = ADS1115_ADDR_VCC; channel = ADS1115_CHANNEL_AIN0_GND; break; - case 5: addr = ADS1115_ADDR_VCC; channel = ADS1115_CHANNEL_AIN1_GND; break; - case 6: addr = ADS1115_ADDR_VCC; channel = ADS1115_CHANNEL_AIN2_GND; break; - case 7: addr = ADS1115_ADDR_VCC; channel = ADS1115_CHANNEL_AIN3_GND; break; - default: return HTTP_RQT_NOT_RECEIVED; - } + ads1115_address_t addr = (ads1115_address_t)(ADS1115_ADDR_GND + sensor->id / 4); + ads1115_channel_t channel = (ads1115_channel_t)(ADS1115_CHANNEL_AIN0_GND + sensor->id % 4); /* set addr pin */ res = ads1115_set_addr_pin(&gs_handle, addr); diff --git a/utils.cpp b/utils.cpp index 2f5c41aad..085fe4deb 100644 --- a/utils.cpp +++ b/utils.cpp @@ -639,7 +639,7 @@ size_t freeMemory() { } #else -long freeMemory() { +size_t freeMemory() { return -1; } #endif \ No newline at end of file From 3f93d06d2ad4789eef8203ab58bf2e7fdfcb5136 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 14 Apr 2024 01:31:36 +0200 Subject: [PATCH 130/281] Added Support for further ESP-ADS1115 addresses --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 0848bb6b5..0020a6e66 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -821,7 +821,7 @@ int read_sensor_adc(Sensor_t *sensor) { if (sensor->id >= 8) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: - int port = sensor->id < 4? 0x48 : 0x49; + int port = 0x48 + sensor->id / 4; int id = sensor->id % 4; sensor->flags.data_ok = false; From d329602aa2adc4c87d6dcf27be88efc2c0339e80 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 17 Apr 2024 23:26:25 +0200 Subject: [PATCH 131/281] Added Analog Sensorboard Input Filter --- defines.h | 2 +- opensprinkler_server.cpp | 5 ++++- sensor_ospi_ads1115.cpp | 17 ++++++++--------- sensor_ospi_ads1115.h | 2 +- sensor_ospi_pcf8591.cpp | 17 ++++++++--------- sensor_ospi_pcf8591.h | 2 +- sensors.cpp | 29 ++++++++++++----------------- sensors.h | 6 +++--- 8 files changed, 38 insertions(+), 42 deletions(-) diff --git a/defines.h b/defines.h index 598f6eae2..c7e51b6a6 100644 --- a/defines.h +++ b/defines.h @@ -37,7 +37,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 152 // Firmware minor version +#define OS_FW_MINOR 153 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 2837ace29..e7e3ccca4 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2362,13 +2362,16 @@ void server_sensor_readnow(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"datas\":[")); uint count = sensor_count(); bool first = true; + ulong time = os.now_tz(); + for (uint i = 0; i < count; i++) { Sensor_t *sensor = sensor_by_idx(i); if (!sensor || (nr != 0 && nr != sensor->nr)) continue; - int status = read_sensor(sensor); + sensor->last_read = time - sensor->read_interval - 1; + int status = read_sensor(sensor, time); if (first) first = false; else bfill.emit_p(PSTR(",")); diff --git a/sensor_ospi_ads1115.cpp b/sensor_ospi_ads1115.cpp index 120e08d3b..603f7f6f3 100644 --- a/sensor_ospi_ads1115.cpp +++ b/sensor_ospi_ads1115.cpp @@ -35,11 +35,9 @@ /** * Read the OSPi 1.6 onboard ADS1115 A2D **/ -int read_sensor_ospi(Sensor_t *sensor) { +int read_sensor_ospi(Sensor_t *sensor, ulong time) { if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - sensor->flags.data_ok = false; - static ads1115_handle_t gs_handle; /**< ads1115 handle */ uint8_t res; @@ -104,18 +102,19 @@ int read_sensor_ospi(Sensor_t *sensor) { sensor->repeat_native += raw; sensor->repeat_data += v; - if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ) + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && time < sensor->last_read + sensor->read_interval) return HTTP_RQT_NOT_RECEIVED; - raw = sensor->repeat_native/MAX_SENSOR_REPEAT_READ; - v = sensor->repeat_data/MAX_SENSOR_REPEAT_READ; + raw = sensor->repeat_native/sensor->repeat_read; + v = sensor->repeat_data/sensor->repeat_read; - sensor->repeat_native = 0; - sensor->repeat_data = 0; - sensor->repeat_read = 0; + sensor->repeat_native = raw; + sensor->repeat_data = v; + sensor->repeat_read = 1; sensor->last_native_data = raw; sensor->flags.data_ok = true; + sensor->last_read = time; //convert values: switch(sensor->type) { diff --git a/sensor_ospi_ads1115.h b/sensor_ospi_ads1115.h index cb3970c1f..db0fb6c5d 100644 --- a/sensor_ospi_ads1115.h +++ b/sensor_ospi_ads1115.h @@ -32,7 +32,7 @@ * @param[in] Sensor * @note */ -int read_sensor_ospi(Sensor_t *sensor); +int read_sensor_ospi(Sensor_t *sensor, ulong time); #endif // ADS1115 diff --git a/sensor_ospi_pcf8591.cpp b/sensor_ospi_pcf8591.cpp index 0a0c7a152..ecde702ff 100644 --- a/sensor_ospi_pcf8591.cpp +++ b/sensor_ospi_pcf8591.cpp @@ -33,11 +33,9 @@ /** * Read the OSPi onboard PCF8591 A2D **/ -int read_sensor_ospi(Sensor_t *sensor) { +int read_sensor_ospi(Sensor_t *sensor, ulong time) { if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - sensor->flags.data_ok = false; - static pcf8591_handle_t gs_handle; /**< pcf8591 handle */ uint8_t res; @@ -99,18 +97,19 @@ int read_sensor_ospi(Sensor_t *sensor) { sensor->repeat_native += raw; sensor->repeat_data += v; - if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ) + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && time < sensor->last_read + sensor->read_interval) return HTTP_RQT_NOT_RECEIVED; - raw = sensor->repeat_native/MAX_SENSOR_REPEAT_READ; - v = sensor->repeat_data/MAX_SENSOR_REPEAT_READ; + raw = sensor->repeat_native/sensor->repeat_read; + v = sensor->repeat_data/sensor->repeat_read; - sensor->repeat_native = 0; - sensor->repeat_data = 0; - sensor->repeat_read = 0; + sensor->repeat_native = raw; + sensor->repeat_data = v; + sensor->repeat_read = 1; sensor->last_native_data = raw; sensor->flags.data_ok = true; + sensor->last_read = time; //convert values: switch(sensor->type) { diff --git a/sensor_ospi_pcf8591.h b/sensor_ospi_pcf8591.h index 5859bff1e..7527cf7a9 100644 --- a/sensor_ospi_pcf8591.h +++ b/sensor_ospi_pcf8591.h @@ -32,7 +32,7 @@ * @param[in] Sensor * @note */ -int read_sensor_ospi(Sensor_t *sensor); +int read_sensor_ospi(Sensor_t *sensor, ulong time); #endif // PCF8591 diff --git a/sensors.cpp b/sensors.cpp index 0020a6e66..e9d7214f4 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -795,7 +795,7 @@ void read_all_sensors(boolean online) { while (sensor) { if (time >= sensor->last_read + sensor->read_interval || sensor->repeat_read) { if (online || sensor->ip == 0) { - int result = read_sensor(sensor); + int result = read_sensor(sensor, time); if (result == HTTP_RQT_SUCCESS) { sensorlog_add(LOG_STD, sensor, time); push_message(sensor); @@ -815,28 +815,27 @@ void read_all_sensors(boolean online) { /** * Read ESP8296 ADS1115 sensors */ -int read_sensor_adc(Sensor_t *sensor) { +int read_sensor_adc(Sensor_t *sensor, ulong time) { DEBUG_PRINTLN(F("read_sensor_adc")); if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - if (sensor->id >= 8) return HTTP_RQT_NOT_RECEIVED; + if (sensor->id >= 16) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: int port = 0x48 + sensor->id / 4; int id = sensor->id % 4; - sensor->flags.data_ok = false; ADS1115 adc(port); adc.begin(); adc.reset(); sensor->repeat_native += adc.readADC(id); - if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ) + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && time < sensor->last_read + sensor->read_interval) return HTTP_RQT_NOT_RECEIVED; - uint64_t avgValue = sensor->repeat_native/MAX_SENSOR_REPEAT_READ; + uint64_t avgValue = sensor->repeat_native/sensor->repeat_read; - sensor->repeat_native = 0; + sensor->repeat_native = avgValue; sensor->repeat_data = 0; - sensor->repeat_read = 0; + sensor->repeat_read = 1; //read continously sensor->last_native_data = avgValue; sensor->last_data = adc.toVoltage(sensor->last_native_data); @@ -898,6 +897,7 @@ int read_sensor_adc(Sensor_t *sensor) { } sensor->flags.data_ok = true; + sensor->last_read = time; DEBUG_PRINT(F("adc sensor values: ")); DEBUG_PRINT(sensor->last_native_data); @@ -1176,13 +1176,11 @@ int read_sensor_ip(Sensor_t *sensor) { /** * read a sensor */ -int read_sensor(Sensor_t *sensor) { +int read_sensor(Sensor_t *sensor, ulong time) { if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - ulong time = os.now_tz(); - switch(sensor->type) { case SENSOR_SMT100_MODBUS_RTU_MOIS: @@ -1206,8 +1204,7 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_USERDEF: DEBUG_PRINT(F("Reading sensor ")); DEBUG_PRINTLN(sensor->name); - sensor->last_read = time; - return read_sensor_adc(sensor); + return read_sensor_adc(sensor, time); #endif #else #if defined ADS1115|PCF8591 @@ -1215,11 +1212,9 @@ int read_sensor(Sensor_t *sensor) { case SENSOR_OSPI_ANALOG_P: case SENSOR_OSPI_ANALOG_SMT50_MOIS: case SENSOR_OSPI_ANALOG_SMT50_TEMP: - sensor->last_read = time; - return read_sensor_ospi(sensor); + return read_sensor_ospi(sensor, time); #endif case SENSOR_REMOTE: - sensor->last_read = time; return read_sensor_http(sensor); #endif case SENSOR_MQTT: @@ -1306,7 +1301,7 @@ void sensor_update_groups() double value = 0; int n = 0; while (group) { - if (group->nr != nr && group->group == nr && group->flags.enable && group->flags.data_ok) { + if (group->nr != nr && group->group == nr && group->flags.enable) { // && group->flags.data_ok) { switch (sensor->type) { case SENSOR_GROUP_MIN: if (n++ == 0) diff --git a/sensors.h b/sensors.h index 88c18cd7f..a3b875e54 100644 --- a/sensors.h +++ b/sensors.h @@ -106,7 +106,7 @@ extern "C" { #define MIN_DISK_FREE 8192 //8Kb min -#define MAX_SENSOR_REPEAT_READ 7 //read analog sensors 7x and calculate avg +#define MAX_SENSOR_REPEAT_READ 32000 //max reads for calculating avg typedef struct SensorFlags { uint enable:1; // enabled @@ -140,7 +140,7 @@ typedef struct Sensor { bool mqtt_init:1; bool mqtt_push:1; byte unitid; - byte repeat_read; + uint32_t repeat_read; double repeat_data; uint64_t repeat_native; ulong last_read; //millis @@ -248,7 +248,7 @@ void read_all_sensors(boolean online); Sensor_t *sensor_by_nr(uint nr); Sensor_t *sensor_by_idx(uint idx); -int read_sensor(Sensor_t *sensor); //sensor value goes to last_native_data/last_data +int read_sensor(Sensor_t *sensor, ulong time); //sensor value goes to last_native_data/last_data //Sensorlog API functions: #define LOG_STD 0 From 70a8c96ac1d6ce1b6327495ed8e00b9145facee8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 17 Apr 2024 23:49:21 +0200 Subject: [PATCH 132/281] Change last time information for remote sensors --- libwebsockets | 1 + main.cpp | 2 +- sensors.cpp | 12 ++++++++++-- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 160000 libwebsockets diff --git a/libwebsockets b/libwebsockets new file mode 160000 index 000000000..ec76f8178 --- /dev/null +++ b/libwebsockets @@ -0,0 +1 @@ +Subproject commit ec76f8178dde74d9c9f485e0e2ff609a0b8ec1f4 diff --git a/main.cpp b/main.cpp index 3e881808e..f217b7390 100644 --- a/main.cpp +++ b/main.cpp @@ -1045,7 +1045,7 @@ void do_loop() } // read analog sensors - read_all_sensors(curr_time && os.network_connected() && os.checkwt_success_lasttime); + read_all_sensors(curr_time && os.network_connected()); static byte reboot_notification = 1; if(reboot_notification) { diff --git a/sensors.cpp b/sensors.cpp index e9d7214f4..5a429c7a8 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -927,7 +927,7 @@ bool extract(char *s, char *buf, int maxlen) { return true; } -int read_sensor_http(Sensor_t *sensor) { +int read_sensor_http(Sensor_t *sensor, ulong time) { #if defined(ESP8266) IPAddress _ip(sensor->ip); byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; @@ -959,6 +959,8 @@ int read_sensor_http(Sensor_t *sensor) { p = ether_buffer; DEBUG_PRINTLN(p); + sensor->last_read = time; + char buf[20]; char *s = strstr(p, "\"nativedata\":"); if (s && extract(s, buf, sizeof(buf))) { @@ -979,6 +981,12 @@ int read_sensor_http(Sensor_t *sensor) { urlDecodeAndUnescape(buf); strncpy(sensor->userdef_unit, buf, sizeof(sensor->userdef_unit)-1); } + s = strstr(p, "\"last\":"); + if (s && extract(s, buf, sizeof(buf))) { + sensor->last_read = strtoul(buf, NULL, 0); + if (sensor->last_read == 0) + sensor->flags.data_ok = false; + } return HTTP_RQT_SUCCESS; } @@ -1215,7 +1223,7 @@ int read_sensor(Sensor_t *sensor, ulong time) { return read_sensor_ospi(sensor, time); #endif case SENSOR_REMOTE: - return read_sensor_http(sensor); + return read_sensor_http(sensor, time); #endif case SENSOR_MQTT: sensor->last_read = time; From 2371e082086623ed468d3761ae5e7c0500d16068 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 18 Apr 2024 00:08:09 +0200 Subject: [PATCH 133/281] fixt remote sensor --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 5a429c7a8..224c1a6bd 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1221,10 +1221,10 @@ int read_sensor(Sensor_t *sensor, ulong time) { case SENSOR_OSPI_ANALOG_SMT50_MOIS: case SENSOR_OSPI_ANALOG_SMT50_TEMP: return read_sensor_ospi(sensor, time); +#endif #endif case SENSOR_REMOTE: return read_sensor_http(sensor, time); -#endif case SENSOR_MQTT: sensor->last_read = time; return read_sensor_mqtt(sensor); From acc8ef1101d9c2af809681418ede1cf30393a285 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 21 Apr 2024 04:20:33 +0200 Subject: [PATCH 134/281] Check mqtt result value, check remote sensor value, ignore dups --- libwebsockets | 2 +- sensor_mqtt.cpp | 17 +++++++++++------ sensors.cpp | 20 +++++++++++++++----- 3 files changed, 27 insertions(+), 12 deletions(-) diff --git a/libwebsockets b/libwebsockets index ec76f8178..f28a45246 160000 --- a/libwebsockets +++ b/libwebsockets @@ -1 +1 @@ -Subproject commit ec76f8178dde74d9c9f485e0e2ff609a0b8ec1f4 +Subproject commit f28a45246e7ea479718ddba5e80deb355b23f5f3 diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 7f7d8acf8..0540737f2 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -141,13 +141,18 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct buf[i] = 0; DEBUG_PRINT("result: "); DEBUG_PRINTLN(buf); - sensor->last_data = atof(buf); - sensor->flags.data_ok = true; - sensor->last_read = now; - DEBUG_PRINTLN("sensor_mqtt_callback2"); + errno = 0; + char *e; + double value = strtod(buf, &e); + if (*e == 0 && errno == 0 && value != sensor->last_data) { + sensor->last_data = value; + sensor->flags.data_ok = true; + sensor->last_read = now; + DEBUG_PRINTLN("sensor_mqtt_callback2"); - sensorlog_add(LOG_STD, sensor, sensor->last_read); - sensor->mqtt_push = true; + sensorlog_add(LOG_STD, sensor, sensor->last_read); + sensor->mqtt_push = true; + } } } } diff --git a/sensors.cpp b/sensors.cpp index 224c1a6bd..ec2868ec8 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -968,8 +968,15 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { } s = strstr(p, "\"data\":"); if (s && extract(s, buf, sizeof(buf))) { - sensor->last_data = strtod(buf, NULL); - sensor->flags.data_ok = true; + char *e; + errno = 0; + double value = strtod(buf, &e); + if (*e == 0 && errno == 0 && value != sensor->last_data) { + sensor->last_data = value; + sensor->flags.data_ok = true; + } else { + return HTTP_RQT_NOT_RECEIVED; + } } s = strstr(p, "\"unitid\":"); if (s && extract(s, buf, sizeof(buf))) { @@ -983,9 +990,12 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { } s = strstr(p, "\"last\":"); if (s && extract(s, buf, sizeof(buf))) { - sensor->last_read = strtoul(buf, NULL, 0); - if (sensor->last_read == 0) - sensor->flags.data_ok = false; + ulong last = strtoul(buf, NULL, 0); + if (last == 0 || last == sensor->last_read) { + return HTTP_RQT_NOT_RECEIVED; + } else { + sensor->last_read = last; + } } return HTTP_RQT_SUCCESS; From ce0e3ec99310b2cded4fcf90c71d409ba36e1a6a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 21 Apr 2024 04:25:27 +0200 Subject: [PATCH 135/281] added errno for esp (fix compile error) --- sensor_mqtt.cpp | 2 +- sensors.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 0540737f2..9915ff26d 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -19,7 +19,7 @@ * along with this program. If not, see * . */ - +#include #include "sensor_mqtt.h" #include "sensors.h" #include "mqtt.h" diff --git a/sensors.cpp b/sensors.cpp index ec2868ec8..ffa5debd7 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -20,6 +20,7 @@ */ #include +#include #include "defines.h" #include "utils.h" #include "program.h" From b3e8b54d7f6b79515b5ca967cd316f0cc6f23dc8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 21 Apr 2024 04:35:35 +0200 Subject: [PATCH 136/281] mqtt drop dupplicate mqtt values --- sensor_mqtt.cpp | 2 +- sensors.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 9915ff26d..b0ec7d26a 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -144,7 +144,7 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct errno = 0; char *e; double value = strtod(buf, &e); - if (*e == 0 && errno == 0 && value != sensor->last_data) { + if (*e == 0 && errno == 0 && (value != sensor->last_data || !sensor->flags.data_ok || now-sensor->last_read > 6000)) { sensor->last_data = value; sensor->flags.data_ok = true; sensor->last_read = now; diff --git a/sensors.cpp b/sensors.cpp index ffa5debd7..39f7ed942 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -972,7 +972,7 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { char *e; errno = 0; double value = strtod(buf, &e); - if (*e == 0 && errno == 0 && value != sensor->last_data) { + if (*e == 0 && errno == 0 && (value != sensor->last_data || !sensor->flags.data_ok || time-sensor->last_read > 6000)) { sensor->last_data = value; sensor->flags.data_ok = true; } else { From f76358c51281de2eb949dab6d9f1405eda7c174f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 23 Apr 2024 00:10:24 +0200 Subject: [PATCH 137/281] Added log delete over/under value, fix mqtt/http parsing error handling --- opensprinkler_server.cpp | 19 +++++++++++++++---- sensor_mqtt.cpp | 9 ++++----- sensors.cpp | 23 ++++++++++++++--------- sensors.h | 2 +- 4 files changed, 34 insertions(+), 19 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index e7e3ccca4..3306b437d 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2683,10 +2683,19 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { #endif int log = -1; uint nr = 0; + double under = 0; + bool use_under = false; + double over = 0; + bool use_over = false; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Filter log for sensor-nr log = atoi(tmp_buffer); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr nr = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("under"), true)) // values lower than + use_under = sscanf(tmp_buffer, "%lf", &under) == 1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("over"), true)) // values higher than + use_over = sscanf(tmp_buffer, "%lf", &over) == 1; DEBUG_PRINTLN(F("server_sensorlog_clear")); @@ -2700,13 +2709,15 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { #endif if (nr > 0) { + ulong n = 0; if (log == -1) { - sensorlog_clear_sensor(nr, LOG_STD); - sensorlog_clear_sensor(nr, LOG_WEEK); - sensorlog_clear_sensor(nr, LOG_MONTH); + n += sensorlog_clear_sensor(nr, LOG_STD, use_under, under, use_over, over); + n += sensorlog_clear_sensor(nr, LOG_WEEK, use_under, under, use_over, over); + n += sensorlog_clear_sensor(nr, LOG_MONTH, use_under, under, use_over, over); } else { - sensorlog_clear_sensor(nr, log); + n += sensorlog_clear_sensor(nr, log, use_under, under, use_over, over); } + bfill.emit_p(PSTR("{\"deleted\":$L}"), n); } else { ulong log_size = sensorlog_size(LOG_STD); ulong log_sizeW = sensorlog_size(LOG_WEEK); diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index b0ec7d26a..ab788a836 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -19,7 +19,6 @@ * along with this program. If not, see * . */ -#include #include "sensor_mqtt.h" #include "sensors.h" #include "mqtt.h" @@ -141,10 +140,10 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct buf[i] = 0; DEBUG_PRINT("result: "); DEBUG_PRINTLN(buf); - errno = 0; - char *e; - double value = strtod(buf, &e); - if (*e == 0 && errno == 0 && (value != sensor->last_data || !sensor->flags.data_ok || now-sensor->last_read > 6000)) { + + double value = -1; + int ok = sscanf(buf, "%lf", &value); + if (ok && (value != sensor->last_data || !sensor->flags.data_ok || now-sensor->last_read > 6000)) { sensor->last_data = value; sensor->flags.data_ok = true; sensor->last_read = now; diff --git a/sensors.cpp b/sensors.cpp index 39f7ed942..28e1172ca 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -20,7 +20,6 @@ */ #include -#include #include "defines.h" #include "utils.h" #include "program.h" @@ -451,7 +450,7 @@ void sensorlog_clear(bool std, bool week, bool month) { } } -ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log) { +ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, double under, bool use_over, double over) { SensorLog_t sensorlog; checkLogSwitch(log); const char *flast = getlogfile2(log); @@ -474,9 +473,16 @@ ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log) { if (result == 0) break; if (sensorlog.nr == sensorNr) { - sensorlog.nr = 0; - file_write_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); - n++; + boolean found = true; + if (use_under && sensorlog.data > under) + found = false; + if (use_over && sensorlog.data < over) + found = false; + if (found) { + sensorlog.nr = 0; + file_write_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); + n++; + } } idxr++; } @@ -969,10 +975,9 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { } s = strstr(p, "\"data\":"); if (s && extract(s, buf, sizeof(buf))) { - char *e; - errno = 0; - double value = strtod(buf, &e); - if (*e == 0 && errno == 0 && (value != sensor->last_data || !sensor->flags.data_ok || time-sensor->last_read > 6000)) { + double value = -1; + int ok = sscanf(buf, "%lf", &value); + if (ok && (value != sensor->last_data || !sensor->flags.data_ok || time-sensor->last_read > 6000)) { sensor->last_data = value; sensor->flags.data_ok = true; } else { diff --git a/sensors.h b/sensors.h index a3b875e54..0407fc953 100644 --- a/sensors.h +++ b/sensors.h @@ -258,7 +258,7 @@ bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog); bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time); void sensorlog_clear_all(); void sensorlog_clear(bool std, bool week, bool month); -ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log); +ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, double under, bool use_over, double over); SensorLog_t *sensorlog_load(uint8_t log, ulong pos); SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t* sensorlog); int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t* sensorlog); From 2243d42e45bec540a9ef6a983a852ad8b9cea203 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 24 Apr 2024 22:16:38 +0200 Subject: [PATCH 138/281] Mqtt asynchron handling --- defines.h | 2 +- sensor_mqtt.cpp | 22 ++++++++-------------- 2 files changed, 9 insertions(+), 15 deletions(-) diff --git a/defines.h b/defines.h index c7e51b6a6..d602d891f 100644 --- a/defines.h +++ b/defines.h @@ -37,7 +37,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 153 // Firmware minor version +#define OS_FW_MINOR 154 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index ab788a836..56f395176 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -147,27 +147,15 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct sensor->last_data = value; sensor->flags.data_ok = true; sensor->last_read = now; - DEBUG_PRINTLN("sensor_mqtt_callback2"); - - sensorlog_add(LOG_STD, sensor, sensor->last_read); sensor->mqtt_push = true; + sensor->repeat_read = 1; //This will call read_sensor_mqtt + DEBUG_PRINTLN("sensor_mqtt_callback2"); } } } } sensor = sensor->next; } - - //Now push the data out: - sensor = getSensors(); - while (sensor) { - if (sensor->type == SENSOR_MQTT && sensor->mqtt_push) { - sensor->mqtt_push = false; - push_message(sensor); - } - sensor = sensor->next; - } - DEBUG_PRINTLN("sensor_mqtt_callback3"); } @@ -175,7 +163,13 @@ int read_sensor_mqtt(Sensor_t *sensor) { if (!os.mqtt.enabled() || !os.mqtt.connected()) { sensor->flags.data_ok = false; sensor->mqtt_init = false; + } else if (sensor->mqtt_push) { + DEBUG_PRINTLN("read_sensor_mqtt: push data"); + sensor->mqtt_push = false; + sensor->repeat_read = 0; + return HTTP_RQT_SUCCESS; //Adds also data to the log + push data } else { + sensor->repeat_read = 0; DEBUG_PRINT("read_sensor_mqtt1: "); DEBUG_PRINTLN(sensor->name); char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); From 6e73e00685e9dbfb80d0a5fdfb7ed3f7d6e2ec40 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 26 Apr 2024 19:46:01 +0200 Subject: [PATCH 139/281] Mqtt asynchron handling --- sensor_mqtt.cpp | 6 ------ sensors.cpp | 1 + 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 56f395176..0a05e9cfb 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -112,12 +112,6 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct char *p = (char*)payload; char *f = jsonFilter; - DEBUG_PRINT("payload: "); - DEBUG_PRINTLN(p); - - DEBUG_PRINT("jsonfilter: "); - DEBUG_PRINTLN(jsonFilter); - while (f && p) { f = strstr(jsonFilter, "|"); if (f) { diff --git a/sensors.cpp b/sensors.cpp index 28e1172ca..8cc2fc91c 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -256,6 +256,7 @@ void sensor_load() { if (!last) sensors = sensor; else last->next = sensor; last = sensor; + sensor->flags.data_ok = false; sensor->next = NULL; pos += SENSOR_STORE_SIZE; } From b91c606922bdd0246201b93a0e06035949e27dd7 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 26 Apr 2024 20:49:08 +0200 Subject: [PATCH 140/281] Another MQTT fix --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 8cc2fc91c..8cfce98f9 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -802,7 +802,7 @@ void read_all_sensors(boolean online) { while (sensor) { if (time >= sensor->last_read + sensor->read_interval || sensor->repeat_read) { - if (online || sensor->ip == 0) { + if (online || (sensor->ip == 0 && sensor->type != SENSOR_MQTT)/* */) { int result = read_sensor(sensor, time); if (result == HTTP_RQT_SUCCESS) { sensorlog_add(LOG_STD, sensor, time); From 8ea470da995d3b7b5896a6f6f245380e21044fd6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 3 May 2024 08:26:39 +0200 Subject: [PATCH 141/281] added MQTT null value and range (-1000/1000) filter --- sensor_mqtt.cpp | 9 +++++---- sensors.cpp | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 0a05e9cfb..27c9bf909 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -115,15 +115,16 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct while (f && p) { f = strstr(jsonFilter, "|"); if (f) { - p = strnlstr(p, jsonFilter, f-jsonFilter, (char*)payload+length-p); + p = strnlstr(p, jsonFilter, f-jsonFilter, (char*)payload-p+length); jsonFilter = f+1; } else { p = strstr(p, jsonFilter); } } if (p) { + p += strlen(jsonFilter); char buf[30]; - p = strpbrk(p, "0123456789.-+"); + p = strpbrk(p, "0123456789.-+nullNULL"); uint i = 0; while (p && i < sizeof(buf)) { char ch = *p++; @@ -135,9 +136,9 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct DEBUG_PRINT("result: "); DEBUG_PRINTLN(buf); - double value = -1; + double value = -9999; int ok = sscanf(buf, "%lf", &value); - if (ok && (value != sensor->last_data || !sensor->flags.data_ok || now-sensor->last_read > 6000)) { + if (ok && value >= -1000 && value <= 1000 && (value != sensor->last_data || !sensor->flags.data_ok || now-sensor->last_read > 6000)) { sensor->last_data = value; sensor->flags.data_ok = true; sensor->last_read = now; diff --git a/sensors.cpp b/sensors.cpp index 8cfce98f9..c353812fe 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -802,7 +802,7 @@ void read_all_sensors(boolean online) { while (sensor) { if (time >= sensor->last_read + sensor->read_interval || sensor->repeat_read) { - if (online || (sensor->ip == 0 && sensor->type != SENSOR_MQTT)/* */) { + if (online || (sensor->ip == 0 && sensor->type != SENSOR_MQTT)) { int result = read_sensor(sensor, time); if (result == HTTP_RQT_SUCCESS) { sensorlog_add(LOG_STD, sensor, time); From 9f80de8defd8daa11a0554ec7f87d0301b0a759e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 3 May 2024 09:26:12 +0200 Subject: [PATCH 142/281] Saving sensors every 1h for keeping last value --- sensors.cpp | 14 +++++++++++--- sensors.h | 1 + utils.cpp | 11 +++++++++++ utils.h | 1 + 4 files changed, 24 insertions(+), 3 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index c353812fe..c0254a4a8 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -39,6 +39,7 @@ byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,b //All sensors: static Sensor_t *sensors = NULL; +static time_t last_save_time = 0; //Sensor URLS: static SensorUrl_t * sensorUrls = NULL; @@ -262,6 +263,7 @@ void sensor_load() { } SensorUrl_load(); + last_save_time = os.now_tz(); } /** @@ -270,17 +272,21 @@ void sensor_load() { */ void sensor_save() { DEBUG_PRINTLN(F("sensor_save")); - if (file_exists(SENSOR_FILENAME)) - remove_file(SENSOR_FILENAME); + if (file_exists(SENSOR_FILENAME_BAK)) + remove_file(SENSOR_FILENAME_BAK); ulong pos = 0; Sensor_t *sensor = sensors; while (sensor) { - file_write_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); + file_write_block(SENSOR_FILENAME_BAK, sensor, pos, SENSOR_STORE_SIZE); sensor = sensor->next; pos += SENSOR_STORE_SIZE; } + if (file_exists(SENSOR_FILENAME)) + remove_file(SENSOR_FILENAME); + rename_file(SENSOR_FILENAME_BAK, SENSOR_FILENAME); + last_save_time = os.now_tz(); DEBUG_PRINTLN(F("sensor_save2")); } @@ -816,6 +822,8 @@ void read_all_sensors(boolean online) { } sensor_update_groups(); calc_sensorlogs(); + if (time - last_save_time > 3600) //1h + sensor_save(); } #if defined(ARDUINO) diff --git a/sensors.h b/sensors.h index 0407fc953..6178288e5 100644 --- a/sensors.h +++ b/sensors.h @@ -43,6 +43,7 @@ extern "C" { //Files #define SENSOR_FILENAME "sensor.dat" // analog sensor filename +#define SENSOR_FILENAME_BAK "sensor.bak" // analog sensor filename backup #define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename #define SENSORLOG_FILENAME1 "sensorlog.dat" // analog sensor log filename #define SENSORLOG_FILENAME2 "sensorlog2.dat" // analog sensor log filename2 diff --git a/utils.cpp b/utils.cpp index 085fe4deb..e7e68e397 100644 --- a/utils.cpp +++ b/utils.cpp @@ -251,6 +251,17 @@ ulong file_size(const char *fn) { return size; } +bool rename_file(const char *fn_old, const char *fn_new) { +#if defined(ESP8266) + return LittleFS.rename(fn_old, fn_new); +#elif defined(ARDUINO) + SdFile file(fn_old, O_READ); + return file.rename(fn_new); +#else + return rename(fn_old, fn_new) == 0; +#endif +} + // file functions ulong file_read_block(const char *fn, void *dst, ulong pos, ulong len) { ulong result = 0; diff --git a/utils.h b/utils.h index 99d96dbe8..63b184846 100644 --- a/utils.h +++ b/utils.h @@ -38,6 +38,7 @@ //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 rename_file(const char *fn_old, const char *fn_new); bool file_exists(const char *fname); ulong file_size(const char *fn); From ac766c1cdf368757b8bd095d56cfb68510e6bfe3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 9 May 2024 03:11:56 +0200 Subject: [PATCH 143/281] Added OTF-Support for OSPi --- OpenSprinkler.cpp | 38 +- OpenSprinkler.h | 9 + build2.sh | 2 + defines.h | 4 +- etherport.cpp | 125 +++- etherport.h | 17 +- libwebsockets | 1 - main.cpp | 13 +- mqtt.cpp | 8 +- opensprinkler_server.cpp | 251 +++++---- opensprinkler_server.h | 8 +- otfpi/LinkedMap.h | 99 ++++ otfpi/LinuxLocalServer.cpp | 69 +++ otfpi/LinuxLocalServer.h | 41 ++ otfpi/LocalServer.h | 53 ++ otfpi/OpenThingsFramework.cpp | 306 ++++++++++ otfpi/OpenThingsFramework.h | 101 ++++ otfpi/Request.cpp | 302 ++++++++++ otfpi/Request.h | 116 ++++ otfpi/Response.cpp | 74 +++ otfpi/Response.h | 62 ++ otfpi/StringBuilder.cpp | 74 +++ otfpi/StringBuilder.h | 62 ++ otfpi/WebSockets.cpp | 729 ++++++++++++++++++++++++ otfpi/WebSockets.h | 231 ++++++++ otfpi/WebSocketsClient.cpp | 1001 +++++++++++++++++++++++++++++++++ otfpi/WebSocketsClient.h | 173 ++++++ otfpi/WebSocketsVersion.h | 36 ++ otfpi/libb64/AUTHORS | 7 + otfpi/libb64/LICENSE | 29 + otfpi/libb64/cdecode.c | 98 ++++ otfpi/libb64/cdecode_inc.h | 28 + otfpi/libb64/cencode.c | 119 ++++ otfpi/libb64/cencode_inc.h | 31 + otfpi/libsha1/libsha1.c | 202 +++++++ otfpi/libsha1/libsha1.h | 21 + otfpi/wscompat.cpp | 116 ++++ otfpi/wscompat.h | 44 ++ 38 files changed, 4553 insertions(+), 147 deletions(-) delete mode 160000 libwebsockets create mode 100644 otfpi/LinkedMap.h create mode 100644 otfpi/LinuxLocalServer.cpp create mode 100644 otfpi/LinuxLocalServer.h create mode 100644 otfpi/LocalServer.h create mode 100644 otfpi/OpenThingsFramework.cpp create mode 100644 otfpi/OpenThingsFramework.h create mode 100644 otfpi/Request.cpp create mode 100644 otfpi/Request.h create mode 100644 otfpi/Response.cpp create mode 100644 otfpi/Response.h create mode 100644 otfpi/StringBuilder.cpp create mode 100644 otfpi/StringBuilder.h create mode 100644 otfpi/WebSockets.cpp create mode 100644 otfpi/WebSockets.h create mode 100644 otfpi/WebSocketsClient.cpp create mode 100644 otfpi/WebSocketsClient.h create mode 100644 otfpi/WebSocketsVersion.h create mode 100644 otfpi/libb64/AUTHORS create mode 100644 otfpi/libb64/LICENSE create mode 100644 otfpi/libb64/cdecode.c create mode 100644 otfpi/libb64/cdecode_inc.h create mode 100644 otfpi/libb64/cencode.c create mode 100644 otfpi/libb64/cencode_inc.h create mode 100644 otfpi/libsha1/libsha1.c create mode 100644 otfpi/libsha1/libsha1.h create mode 100644 otfpi/wscompat.cpp create mode 100644 otfpi/wscompat.h diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 0acc8db32..0480d2ea6 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -95,6 +95,7 @@ extern ProgramData pd; #else #if defined(OSPI) byte OpenSprinkler::pin_sr_data = PIN_SR_DATA; + OTCConfig OpenSprinkler::otc; #endif // todo future: LCD define for Linux-based systems #endif @@ -706,18 +707,40 @@ void OpenSprinkler::reboot_dev(uint8_t cause) { /** Initialize network with the given mac address and http port */ byte OpenSprinkler::start_network() { - unsigned int port = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; + unsigned int httpport = (unsigned int)(iopts[IOPT_HTTPPORT_1]<<8) + (unsigned int)iopts[IOPT_HTTPPORT_0]; #if defined(DEMO) #if defined(HTTP_PORT) - port = HTTP_PORT; + httpport = HTTP_PORT; #else - port = 80; + httpport = 80; #endif #endif - if(m_server) { delete m_server; m_server = 0; } + #if defined(OSPI) + DEBUG_PRINT("OTF config: "); + DEBUG_PRINTLN(otc.en); + DEBUG_PRINT(otc.server.c_str()); + DEBUG_PRINT(":"); + DEBUG_PRINTLN(otc.port); + DEBUG_PRINT("local port: "); + DEBUG_PRINTLN(httpport); + DEBUG_PRINT("token: "); + DEBUG_PRINTLN(otc.token.c_str()); + if (otc.en>0 && otc.token.length()>=32) { + otf = new OTF::OpenThingsFramework(httpport, otc.server, otc.port, otc.token, false, ether_buffer, ETHER_BUFFER_SIZE); + DEBUG_PRINTLN(F("Started OTF with remote connection")); + } else { + otf = new OTF::OpenThingsFramework(httpport, ether_buffer, ETHER_BUFFER_SIZE); + DEBUG_PRINTLN(F("Started OTF with just local connection")); + } + start_otf(); + DEBUG_PRINTLN(F("Started OTF OSPi Connector")); + return true; + #else + if(m_server) { delete m_server; m_server = 0; } m_server = new EthernetServer(port); return m_server->begin(); + #endif } bool OpenSprinkler::network_connected(void) { @@ -739,7 +762,9 @@ bool OpenSprinkler::load_hardware_mac(byte* mac, bool wired) { mac[4] = 0x31; mac[5] = iopts[IOPT_DEVICE_ID]; + #if !defined(OSPI) if (m_server == NULL) return true; + #endif if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) == 0) return true; @@ -2327,7 +2352,7 @@ void OpenSprinkler::factory_reset() { #define xstr(s) str(s) /** Parse OTC configuration */ -#if defined(ESP8266) +#if defined(ESP8266) || defined(OSPI) void OpenSprinkler::parse_otc_config() { char server[MAX_SOPTS_SIZE+1] = {0}; char token[MAX_SOPTS_SIZE+1] = {0}; @@ -2382,6 +2407,9 @@ void OpenSprinkler::options_setup() { } parse_otc_config(); #endif + #if defined(OSPI) + parse_otc_config(); + #endif attribs_load(); } diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 4a6e6fca2..bd77f733d 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -62,6 +62,10 @@ #include #include #include "etherport.h" + #if defined(OSPI) + #include "OpenThingsFramework.h" + extern OTF::OpenThingsFramework *otf; + #endif #endif // end of headers #if defined(ARDUINO) @@ -414,6 +418,11 @@ class OpenSprinkler { static byte prev_station_bits[]; #endif #endif // LCD functions + +#if defined(OSPI) + static OTCConfig otc; + static void parse_otc_config(); +#endif static byte engage_booster; }; diff --git a/build2.sh b/build2.sh index 76bf4deda..a979db40c 100755 --- a/build2.sh +++ b/build2.sh @@ -33,6 +33,8 @@ if [ "$?" -eq 0 ]; then fi g++ -g -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 $USEGPIO -std=c++14 \ + -I./ -I./otfpi -I./otfpi/libsha1 -I./otfpi/libb64 \ + ./otfpi/*.cpp ./otfpi/libsha1/libsha1.c ./otfpi/libb64/cencode.c \ main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp \ utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensor*.cpp \ $ADS1115FILES $PCF8591FILES \ diff --git a/defines.h b/defines.h index d602d891f..81c509eca 100644 --- a/defines.h +++ b/defines.h @@ -37,7 +37,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 154 // Firmware minor version +#define OS_FW_MINOR 160 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -463,12 +463,14 @@ enum { #define DEBUG_BEGIN(x) {Serial.begin(x);} #define DEBUG_PRINT(x) {Serial.print(x);} #define DEBUG_PRINTLN(x) {Serial.println(x);} + #define DEBUG_PRINTF(msg, ...) {Serial.printf(msg, ##__VA_ARGS__);} #else #include #define DEBUG_BEGIN(x) {} /** Serial debug functions */ inline void DEBUG_PRINT(int x) {printf("%d", x);} inline void DEBUG_PRINT(const char*s) {printf("%s", s);} #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} + #define DEBUG_PRINTF(msg, ...) {printf(msg, ##__VA_ARGS__);} #endif #else diff --git a/etherport.cpp b/etherport.cpp index 2fb123c0b..914a6648b 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ #include #include #include +#include "utils.h" EthernetServer::EthernetServer(uint16_t port) : m_port(port), m_sock(0) @@ -102,24 +104,21 @@ bool EthernetServer::begin() // If it succeeds it will return an EthernetClient. EthernetClient EthernetServer::available() { - fd_set sock_set; - FD_ZERO(&sock_set); - FD_SET(m_sock, &sock_set); - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 50 * 1000; // 50ms - - select(m_sock + 1, &sock_set, NULL, NULL, &timeout); - if (FD_ISSET(m_sock, &sock_set)) + struct pollfd fds; + memset(&fds, 0, sizeof(fds)); + fds.fd = m_sock; + fds.events = POLLIN; + int timeout = 50; + + int rc = poll(&fds, 1, timeout); + if (rc > 0) { int client_sock = 0; - struct sockaddr_in6 cli_addr; - unsigned int clilen = sizeof(cli_addr); - if ((client_sock = accept(m_sock, (struct sockaddr *) &cli_addr, &clilen)) <= 0) - return EthernetClient(0); + if ((client_sock = accept(m_sock, NULL, NULL)) <= 0) + return EthernetClient(); return EthernetClient(client_sock); } - return EthernetClient(0); + return EthernetClient(); } EthernetClient::EthernetClient() @@ -134,7 +133,7 @@ EthernetClient::EthernetClient(int sock) EthernetClient::~EthernetClient() { - stop(); + if (tmpbuf) free(tmpbuf); } int EthernetClient::connect(uint8_t ip[4], uint16_t port) @@ -186,25 +185,97 @@ EthernetClient::operator bool() // and return 0; int EthernetClient::read(uint8_t *buf, size_t size) { - fd_set sock_set; - FD_ZERO(&sock_set); - FD_SET(m_sock, &sock_set); - struct timeval timeout; - timeout.tv_sec = 3; - timeout.tv_usec = 0; + if (tmpbufidx < tmpbufsize) { + size_t tmpsize = tmpbufsize-tmpbufidx; + if (tmpsize > size) + tmpsize = size; + memcpy(buf, &tmpbuf[tmpbufidx], tmpsize); + tmpbufidx += tmpsize; + return tmpsize; + } + + struct pollfd fds; + memset(&fds, 0, sizeof(fds)); + fds.fd = m_sock; + fds.events = POLLIN; + int timeout = 3000; - select(m_sock + 1, &sock_set, NULL, NULL, &timeout); - if (FD_ISSET(m_sock, &sock_set)) + int rc = poll(&fds, 1, timeout); + if (rc > 0) { - int retval = ::read(m_sock, buf, size); - if (retval <= 0) // socket closed + rc = recv(m_sock, buf, size, 0); + if (errno == EWOULDBLOCK) + return 0; + + if (rc <= 0) // socket closed m_connected = false; - return retval; + return rc; } - m_connected = false; + if (rc < 0) + m_connected = false; return 0; } +int EthernetClient::timedRead() { + if (!tmpbuf) tmpbuf = (uint8_t*)malloc(TMPBUF); + if (tmpbufidx < tmpbufsize) + return tmpbuf[tmpbufidx++]; + + tmpbufidx = 0; + tmpbufsize = read(tmpbuf, TMPBUF); + + if (tmpbufsize <= 0) + return -1; + + return tmpbuf[tmpbufidx++]; +} + +size_t EthernetClient::readBytesUntil(char terminator, char *buffer, size_t length) { + size_t n = 0; + int c = timedRead(); + while (c >= 0 && c != terminator && length>0) + { + buffer[n] = (char)c; + length--; + n++; + c = timedRead(); + } + return n; +} + +std::string EthernetClient::readStringUntil(char terminator) { + + String ret; + int c = timedRead(); + while (c >= 0 && c != terminator) + { + ret += (char)c; + c = timedRead(); + } + return ret; +} + +void EthernetClient::flush() { + //for compatibility only +} + +bool EthernetClient::available() { + if (tmpbufidx < tmpbufsize) + return true; + + if (!m_connected) + return false; + + struct pollfd fds; + memset(&fds, 0, sizeof(fds)); + fds.fd = m_sock; + fds.events = POLLIN; + int timeout = 50; + + int rc = poll(&fds, 1, timeout); + return rc > 0; +} + size_t EthernetClient::write(const uint8_t *buf, size_t size) { return ::send(m_sock, buf, size, MSG_NOSIGNAL); diff --git a/etherport.h b/etherport.h index efcebc2e6..bce34866e 100644 --- a/etherport.h +++ b/etherport.h @@ -32,12 +32,15 @@ #include #include #include -#include +#include +#include #ifdef __APPLE__ #define MSG_NOSIGNAL SO_NOSIGPIPE #endif +#define TMPBUF 1024*8 + class EthernetServer; class EthernetClient { @@ -49,14 +52,24 @@ class EthernetClient { bool connected(); void stop(); int read(uint8_t *buf, size_t size); + int readBytes(char *buf, size_t size) { return read((uint8_t*)buf, size); }; + int timedRead(); + size_t readBytesUntil(char terminator, char *buffer, size_t length); + std::string readStringUntil(char value); size_t write(const uint8_t *buf, size_t size); operator bool(); int GetSocket() { return m_sock; } + void flush(); + bool available(); + private: - int m_sock; + uint8_t *tmpbuf = NULL; + int tmpbufsize = 0; + int tmpbufidx = 0; + int m_sock = 0; bool m_connected; friend class EthernetServer; }; diff --git a/libwebsockets b/libwebsockets deleted file mode 160000 index f28a45246..000000000 --- a/libwebsockets +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f28a45246e7ea479718ddba5e80deb355b23f5f3 diff --git a/main.cpp b/main.cpp index f217b7390..007299e28 100644 --- a/main.cpp +++ b/main.cpp @@ -53,8 +53,14 @@ #endif unsigned long getNtpTime(); #else // header and defs for RPI/BBB + #if defined(OSPI) + OTF::OpenThingsFramework *otf = NULL; + bool otf_callbacksInitialised = false; + #elif EthernetServer *m_server = 0; EthernetClient *m_client = 0; + #endif + #endif void reset_all_stations(); @@ -433,7 +439,7 @@ void reboot_in(uint32_t ms) { } } bool check_enc28j60(); -#else +#elif !defined(OSPI) void handle_web_request(char *p); #endif @@ -597,6 +603,9 @@ void do_loop() ui_state_machine(); #else // Process Ethernet packets for RPI/BBB + #if defined(OSPI) + if (otf) otf->loop(); + #elif EthernetClient client = m_server->available(); if (client) { while(true) { @@ -616,6 +625,8 @@ void do_loop() } } } + #endif + #endif // Process Ethernet packets // Start up MQTT when we have a network connection diff --git a/mqtt.cpp b/mqtt.cpp index 26c3dd532..7cab0246f 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -50,11 +50,9 @@ #if defined(ENABLE_DEBUG) #if defined(ARDUINO) #include "TimeLib.h" - #define DEBUG_PRINTF(msg, ...) {Serial.printf(msg, ##__VA_ARGS__);} #define DEBUG_TIMESTAMP(msg, ...) {time_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 #include - #define DEBUG_PRINTF(msg, ...) {printf(msg, ##__VA_ARGS__);} #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__);} @@ -76,9 +74,9 @@ static unsigned long last_reconnect_attempt; #define MQTT_KEEPALIVE 60 #define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config -#define MQTT_MAX_HOST_LEN 50 // Note: App is set to max 50 chars for broker name -#define MQTT_MAX_USERNAME_LEN 32 // Note: App is set to max 32 chars for username -#define MQTT_MAX_PASSWORD_LEN 32 // Note: App is set to max 32 chars for password +#define MQTT_MAX_HOST_LEN 100 // Note: App is set to max 50 chars for broker name +#define MQTT_MAX_USERNAME_LEN 50 // Note: App is set to max 32 chars for username +#define MQTT_MAX_PASSWORD_LEN 100 // Note: App is set to max 32 chars for password #define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit #define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 3306b437d..503cd8356 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -66,10 +66,19 @@ #include "etherport.h" extern EthernetClient *m_client; - #define OTF_PARAMS_DEF - #define OTF_PARAMS - #define FKV_SOURCE p - #define handle_return(x) {return_code=x; return;} + #if defined(OSPI) + extern OTF::OpenThingsFramework *otf; + extern bool otf_callbacksInitialised; + #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.writeBodyChunk((char *)"%s",ether_buffer); else otf_send_result(req,res,x); return;} + #else + #define OTF_PARAMS_DEF + #define OTF_PARAMS + #define FKV_SOURCE p + #define handle_return(x) {return_code=x; return;} + #endif #endif @@ -79,7 +88,7 @@ extern OpenSprinkler os; extern ProgramData pd; extern ulong flow_count; -#if !defined(ESP8266) +#if !defined(OTF_ENABLED) static byte return_code; static char* get_buffer = NULL; #endif @@ -117,7 +126,7 @@ int available_ether_buffer() { #define HTML_UPLOAD_FAILED 0x40 #define HTML_REDIRECT_HOME 0xFF -#if !defined(ESP8266) +#if !defined(OTF_ENABLED) static const char html200OK[] PROGMEM = "HTTP/1.1 200 OK\r\n" ; @@ -149,9 +158,13 @@ static const char htmlReturnHome[] PROGMEM = "\n" ; -#if defined(ESP8266) +#if defined(OTF_ENABLED) byte findKeyVal (const OTF::Request &req,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL) { + #if defined(ESP8266) char* result = key_in_pgm?req.getQueryParameter((const __FlashStringHelper *)key):req.getQueryParameter(key); + #else + char* result = req.getQueryParameter(key); + #endif if(result!=NULL) { strncpy(strbuf, result, maxlen); strbuf[maxlen-1]=0; @@ -228,8 +241,18 @@ void rewind_ether_buffer() { ether_buffer[0] = 0; } +#if defined(OTF_ENABLED) +boolean buffer_available() { + return bfill.position() < RESPONSE_BUFFER_SIZE/2; +} +#else +boolean buffer_available() { + return available_ether_buffer() <=0; +} +#endif + void send_packet(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) //DEBUG_PRINT(F("bfill position: ")); //DEBUG_PRINTLN(bfill.position()); if (!res.willFit(bfill.position())) { @@ -237,8 +260,8 @@ void send_packet(OTF_PARAMS_DEF) { DEBUG_PRINTLN(res.getLength()); res.flush(); } - //DEBUG_PRINT("res.writeBodyChunk: "); - //DEBUG_PRINTLN(strlen(ether_buffer)); + DEBUG_PRINT("res.writeBodyChunk: "); + DEBUG_PRINTLN(strlen(ether_buffer)); res.writeBodyChunk((char *)"%s",ether_buffer); #else m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); @@ -252,7 +275,7 @@ char dec2hexchar(byte dec) { else return 'A'+(dec-10); } -#if defined(ESP8266) +#if defined(OTF_ENABLED) 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")); @@ -268,7 +291,7 @@ void print_header(bool isJson=true) { } #endif -#if defined(ESP8266) +#if defined(OTF_ENABLED) void print_header_download(OTF_PARAMS_DEF, int len=0) { res.writeStatus(200, F("OK")); res.writeHeader(F("Content-Type"), F("text/plain")); @@ -287,11 +310,17 @@ void print_header_download() { } #endif -#if defined(ESP8266) +#if defined(OTF_ENABLED) +#if defined(ESP8266) String two_digits(uint8_t x) { return String(x/10) + (x%10); } +#elif defined(OSPI) +String two_digits(uint8_t x) { + return std::to_string(x/10) + std::to_string(x%10); +} +#endif String toHMS(ulong t) { return two_digits(t/3600)+":"+two_digits((t/60)%60)+":"+two_digits(t%60); @@ -299,7 +328,7 @@ String toHMS(ulong t) { void otf_send_result(OTF_PARAMS_DEF, byte code, const char *item = NULL) { String json = F("{\"result\":"); - json += code; + json += std::to_string(code); if (!item) item = ""; json += F(",\"item\":\""); json += item; @@ -308,7 +337,9 @@ void otf_send_result(OTF_PARAMS_DEF, byte code, const char *item = NULL) { print_header(OTF_PARAMS, true, json.length()); res.writeBodyChunk((char *)"%s",json.c_str()); } +#endif +#if defined(ESP8266) void update_server_send_result(byte code, const char* item = NULL) { String json = F("{\"result\":"); json += code; @@ -406,7 +437,7 @@ void on_ap_try_connect(OTF_PARAMS_DEF) { /** Check and verify password */ -#if defined(ESP8266) +#if defined(OTF_ENABLED) boolean process_password(OTF_PARAMS_DEF, boolean fwv_on_fail=false) #else boolean check_password(char *p) @@ -417,7 +448,7 @@ boolean check_password(char *p) #endif if (os.iopts[IOPT_IGNORE_PASSWORD]) return true; -#if !defined(ESP8266) +#if !defined(OTF_ENABLED) if (m_client && !p) { p = get_buffer; } @@ -487,7 +518,7 @@ 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 ) { + if (!buffer_available() ) { send_packet(OTF_PARAMS); } } @@ -496,7 +527,7 @@ void server_json_stations_main(OTF_PARAMS_DEF) { /** Output stations data */ void server_json_stations(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); @@ -511,7 +542,7 @@ void server_json_stations(OTF_PARAMS_DEF) { /** Output station special attribute */ void server_json_station_special(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); @@ -532,7 +563,7 @@ 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 ) { + if (!buffer_available() ) { send_packet(OTF_PARAMS); } } @@ -540,7 +571,7 @@ void server_json_station_special(OTF_PARAMS_DEF) { handle_return(HTML_OK); } -#if defined(ESP8266) +#if defined(OTF_ENABLED) void server_change_board_attrib(const OTF::Request &req, char header, byte *attrib) #else void server_change_board_attrib(char *p, char header, byte *attrib) @@ -557,7 +588,7 @@ void server_change_board_attrib(char *p, char header, byte *attrib) } } -#if defined(ESP8266) +#if defined(OTF_ENABLED) void server_change_stations_attrib(const OTF::Request &req, char header, byte *attrib) #else void server_change_stations_attrib(char *p, char header, byte *attrib) @@ -591,7 +622,7 @@ void server_change_stations_attrib(char *p, char header, byte *attrib) * g?: sequential group id */ void server_change_stations(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char* p = get_buffer; @@ -640,7 +671,7 @@ void server_change_stations(OTF_PARAMS_DEF) { handle_return(HTML_DATA_OUTOFBOUND); } } else if (tmp_buffer[0] == STN_TYPE_HTTP || tmp_buffer[0] == STN_TYPE_HTTPS) { - #if !defined(ESP8266) + #if !defined(OTF_ENABLED) urlDecode(tmp_buffer + 1); #endif if (strlen(tmp_buffer+1) > sizeof(HTTPStationData)) { @@ -690,7 +721,7 @@ void manual_start_program(byte, byte); * uwt: use weather (i.e. watering percentage) */ void server_manual_program(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -725,7 +756,7 @@ void server_manual_program(OTF_PARAMS_DEF) { * t: station water time */ void server_change_runonce(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) 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; @@ -787,7 +818,7 @@ void server_change_runonce(OTF_PARAMS_DEF) { * pid:program index (-1 will delete all programs) */ void server_delete_program(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -815,7 +846,7 @@ void server_delete_program(OTF_PARAMS_DEF) { * pid: program index (must be 1 or larger, because we can't move up program 0) */ void server_moveup_program(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -849,7 +880,7 @@ void server_moveup_program(OTF_PARAMS_DEF) { */ const char _str_program[] PROGMEM = "Program "; void server_change_program(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -901,12 +932,12 @@ void server_change_program(OTF_PARAMS_DEF) { } } -#if !defined(ESP8266) +#if !defined(OTF_ENABLED) // do a full string decoding if(p) urlDecode(p); #endif -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); char *pv = tmp_buffer+1; #else @@ -1044,7 +1075,7 @@ void server_json_options_main() { /** Output Options */ void server_json_options(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS,true)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); @@ -1089,7 +1120,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { } // push out a packet if available // buffer size is getting small - if (available_ether_buffer() <= 0) { + if (!buffer_available()) { send_packet(OTF_PARAMS); } } @@ -1098,7 +1129,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { /** Output program data */ void server_json_programs(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); @@ -1113,7 +1144,7 @@ void server_json_programs(OTF_PARAMS_DEF) { /** Output script url form */ void server_view_scripturl(OTF_PARAMS_DEF) { rewind_ether_buffer(); -#if defined(ESP8266) +#if defined(OTF_ENABLED) print_header(OTF_PARAMS,false,strlen(ether_buffer)); #else print_header(false); @@ -1161,8 +1192,12 @@ void server_json_controller_main(OTF_PARAMS_DEF) { os.pause_timer, pd.nqueue); -#if defined(ESP8266) +#if defined(OTF_ENABLED) + #if defined(ESP8266) bfill.emit_p(PSTR("\"RSSI\":$D,\"otc\":{$O},\"otcs\":$D,"), (int16_t)WiFi.RSSI(), SOPT_OTC_OPTS, otf->getCloudStatus()); + #elif defined(OSPI) + bfill.emit_p(PSTR("\"RSSI\":$D,\"otc\":{$O},\"otcs\":$D,"), 0, SOPT_OTC_OPTS, otf->getCloudStatus()); + #endif #endif byte mac[6] = {0}; @@ -1205,7 +1240,7 @@ void server_json_controller_main(OTF_PARAMS_DEF) { for(sid=0;sid 0) { - #if defined(ESP8266) + #if defined(OTF_ENABLED) os.status.safe_reboot = 0; reboot_timer = os.now_tz() + 1; handle_return(HTML_SUCCESS); @@ -1367,7 +1402,7 @@ void string_remove_space(char *src) { * jsp: Javascript path */ void server_change_scripturl(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -1389,7 +1424,7 @@ void server_change_scripturl(OTF_PARAMS_DEF) { string_remove_space(tmp_buffer); os.sopt_save(SOPT_WEATHERURL, tmp_buffer); } -#if defined(ESP8266) +#if defined(OTF_ENABLED) rewind_ether_buffer(); print_header(OTF_PARAMS,false,strlen(ether_buffer)); bfill.emit_p(PSTR("$F"), htmlReturnHome); @@ -1410,7 +1445,7 @@ void server_change_scripturl(OTF_PARAMS_DEF) { */ void server_change_options(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -1570,7 +1605,7 @@ void server_change_password(OTF_PARAMS_DEF) { return; #endif -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char* p = get_buffer; @@ -1602,7 +1637,7 @@ void server_json_status_main() { /** Output station status */ void server_json_status(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); @@ -1626,7 +1661,7 @@ void server_json_status(OTF_PARAMS_DEF) * ssta: shift remaining stations */ void server_change_manual(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -1725,7 +1760,7 @@ int file_fgets(File file, char* buf, int maxsize) { */ void server_json_log(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -1760,7 +1795,7 @@ void server_json_log(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, type, 4, PSTR("type"), true)) type_specified = true; -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -1837,7 +1872,7 @@ void server_json_log(OTF_PARAMS_DEF) { 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) { + if (!buffer_available()) { send_packet(OTF_PARAMS); } } @@ -1856,7 +1891,7 @@ void server_json_log(OTF_PARAMS_DEF) { * if day=all: delete all log files) */ void server_delete_log(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -1875,7 +1910,7 @@ void server_delete_log(OTF_PARAMS_DEF) { * dur: duration (in units of seconds) */ void server_pause_queue(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -1893,7 +1928,7 @@ 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(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS,true)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); @@ -1920,7 +1955,7 @@ void server_json_all(OTF_PARAMS_DEF) { #if defined(ARDUINO) -#if !defined(ESP8266) +#if !defined(ESP8266) static int freeHeap () { extern int __heap_start, *__brkval; int v; @@ -1929,7 +1964,7 @@ static int freeHeap () { #endif void server_json_debug(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) rewind_ether_buffer(); print_header(OTF_PARAMS); #else @@ -2008,7 +2043,7 @@ char* urlDecodeAndUnescape(char *buf) { */ void server_sensor_config_userdef(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2058,7 +2093,7 @@ void server_sensor_config_userdef(OTF_PARAMS_DEF) */ void server_sensorurl_get(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2087,7 +2122,7 @@ void server_sensorurl_get(OTF_PARAMS_DEF) */ void server_sensorurl_config(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2123,7 +2158,7 @@ void server_sensorurl_config(OTF_PARAMS_DEF) */ void server_sensor_config(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2260,7 +2295,7 @@ void server_sensor_config(OTF_PARAMS_DEF) * {"nr":1,"id":1} */ void server_set_sensor_address(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2287,7 +2322,7 @@ void server_set_sensor_address(OTF_PARAMS_DEF) { * */ void server_sensor_get(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2299,7 +2334,7 @@ void server_sensor_get(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -2338,7 +2373,7 @@ void server_sensor_get(OTF_PARAMS_DEF) { * */ void server_sensor_readnow(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2350,7 +2385,7 @@ void server_sensor_readnow(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -2434,7 +2469,7 @@ void sensorconfig_json(OTF_PARAMS_DEF) { * */ void server_sensor_list(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2448,7 +2483,7 @@ void server_sensor_list(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("test"), true)) test = strtoul(tmp_buffer, NULL, 0); // Sensor nr -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -2476,7 +2511,7 @@ void server_sensor_list(OTF_PARAMS_DEF) { * */ void server_sensorlog_list(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2534,7 +2569,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { shortcsv = csv == 2; } -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -2647,7 +2682,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { } // if available ether buffer is getting small // send out a packet - if (available_ether_buffer() <=0 ) { + if (!buffer_available() ) { send_packet(OTF_PARAMS); } if (++count >= maxResults) { @@ -2676,7 +2711,7 @@ void server_sensorlog_list(OTF_PARAMS_DEF) { * */ void server_sensorlog_clear(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2699,7 +2734,7 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("server_sensorlog_clear")); -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -2740,7 +2775,7 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { * define a program adjustment */ void server_sensorprog_config(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2824,7 +2859,7 @@ void progconfig_json() { * define a program adjustment */ void server_sensorprog_list(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2845,7 +2880,7 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) sensor_nr = strtoul(tmp_buffer, NULL, 0); -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -2981,7 +3016,7 @@ const char* sensor_names[] = { * List supported sensor types **/ void server_sensor_types(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -2989,7 +3024,7 @@ void server_sensor_types(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("server_sensor_types")); -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -3019,7 +3054,7 @@ void server_sensor_types(OTF_PARAMS_DEF) { * system resources status **/ void server_usage(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -3027,7 +3062,7 @@ void server_usage(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("server_usage")); -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -3069,7 +3104,7 @@ extern uint32_t ping_ok; * Program calc **/ void server_sensorprog_calc(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -3131,7 +3166,7 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { int minEx = progAdj.min - diff/2; int maxEx = progAdj.max + diff/2; -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -3186,7 +3221,7 @@ const char* prog_names[] = { * List supported adjustment types */ void server_sensorprog_types(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -3194,7 +3229,7 @@ void server_sensorprog_types(OTF_PARAMS_DEF) { DEBUG_PRINTLN(F("server_sensorprog_types")); -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -3225,7 +3260,7 @@ void server_sensorprog_types(OTF_PARAMS_DEF) { * */ void server_sensorconfig_backup(OTF_PARAMS_DEF) { -#if defined(ESP8266) +#if defined(OTF_ENABLED) if(!process_password(OTF_PARAMS)) return; #else char *p = get_buffer; @@ -3241,7 +3276,7 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { } -#if defined(ESP8266) +#if defined(OTF_ENABLED) // 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(); @@ -3439,25 +3474,7 @@ void on_sta_upload() { void on_ap_upload() { on_sta_upload(); } void start_server_client() { - if(!otf) return; - - if (!otf_callbacksInitialised) { - otf->on("/", server_home); // handle home page - otf->on("/index.html", server_home); - 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); - - // set up all other handlers - char uri[4]; - uri[0]='/'; - uri[3]=0; - for(byte i=0;ion(uri, urls[i]); - } - otf_callbacksInitialised = true; - } + start_otf(); update_server->begin(); } @@ -3496,7 +3513,33 @@ void start_server_ap() { #endif -#if !defined(ESP8266) +#if defined(OTF_ENABLED) +void start_otf() { + if(!otf) return; + + if (!otf_callbacksInitialised) { + otf->on("/", server_home); // handle home page + otf->on("/index.html", server_home); + #if defined(ESP8266) + 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); + #endif + + // set up all other handlers + char uri[4]; + uri[0]='/'; + uri[3]=0; + for(byte i=0;ion(uri, urls[i]); + } + otf_callbacksInitialised = true; + } +} +#endif + +#if !defined(OTF_ENABLED) // This funtion is only used for non-ESP8266 platforms void handle_web_request(char *p) { rewind_ether_buffer(); diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 279df4f76..03b18bf34 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -28,6 +28,10 @@ #include #endif +#if defined(ESP8266) || defined(OSPI) +#define OTF_ENABLED +#endif + char dec2hexchar(byte dec); class BufferFiller { @@ -98,5 +102,7 @@ class BufferFiller { }; char* urlDecodeAndUnescape(char *buf); - +#if defined(OTF_ENABLED) +void start_otf(); +#endif #endif // _OPENSPRINKLER_SERVER_H diff --git a/otfpi/LinkedMap.h b/otfpi/LinkedMap.h new file mode 100644 index 000000000..9dd862c4b --- /dev/null +++ b/otfpi/LinkedMap.h @@ -0,0 +1,99 @@ +#ifndef OTF_LINKEDMAP_H +#define OTF_LINKEDMAP_H + +#define KEY_MAX_LENGTH 100 + +#include + +namespace OTF { + template + class LinkedMapNode; + + template + class LinkedMap { + friend class OpenThingsFramework; + + friend class Response; + + private: + LinkedMapNode *head = nullptr; + LinkedMapNode *tail = nullptr; + + void _add(LinkedMapNode *node) { + if (head == nullptr) { + head = node; + tail = head; + } else { + tail->next = node; + tail = tail->next; + } + } + + T _find(const char *key) const { + LinkedMapNode *node = head; + while (node != nullptr) { + if (strcmp(node->key, key) == 0) { + return node->value; + } + + node = node->next; + } + + // Indicate the key could not be found. + return nullptr; + } + + public: + ~LinkedMap() { + LinkedMapNode *node = head; + while (node != nullptr) { + LinkedMapNode *next = node->next; + delete node; + node = next; + } + } + + void add(const char *key, T value) { + _add(new LinkedMapNode(key, value)); + } + + T find(const char *key) const { + return _find(key); + } + }; + + template + class LinkedMapNode { + private: + /** Indicates if the key was copied into RAM from flash memory and needs to be freed when the object is destroyed. */ + //bool keyFromFlash = false; + + public: + const char *key = nullptr; + T value; + LinkedMapNode *next = nullptr; + + /*LinkedMapNode(const __FlashStringHelper *key, T value) { + keyFromFlash = true; + char *_key = new char[KEY_MAX_LENGTH]; + strncpy_P(_key, (char *) key, KEY_MAX_LENGTH); + this->key = (const char *) _key; + + this->value = value; + }*/ + + LinkedMapNode(const char *key, T value) { + this->key = key; + this->value = value; + } + + ~LinkedMapNode() { + // Delete the key if it was copied into RAM from flash memory. + //if (keyFromFlash) { + // delete key; + //} + } + }; +}// namespace OTF + +#endif diff --git a/otfpi/LinuxLocalServer.cpp b/otfpi/LinuxLocalServer.cpp new file mode 100644 index 000000000..77df5138e --- /dev/null +++ b/otfpi/LinuxLocalServer.cpp @@ -0,0 +1,69 @@ +#include "LinuxLocalServer.h" + +using namespace OTF; + +LinuxLocalServer::LinuxLocalServer(uint16_t port) : server(port) {} + +LinuxLocalServer::~LinuxLocalServer() { + if (activeClient != nullptr) + activeClient->stop(); + delete activeClient; +} + + +LocalClient *LinuxLocalServer::acceptClient() { + if (activeClient != nullptr) { + activeClient->stop(); + delete activeClient; + } + + EthernetClient client = server.available(); + if (client) { + activeClient = new LinuxLocalClient(client); + } else { + activeClient = nullptr; + } + return activeClient; +} + + +void LinuxLocalServer::begin() { + server.begin(); +} + + +LinuxLocalClient::LinuxLocalClient(EthernetClient client) { + this->client = client; +} + +bool LinuxLocalClient::dataAvailable() { + return client.available(); +} + +size_t LinuxLocalClient::readBytes(char *buffer, size_t length) { + return client.readBytes(buffer, length); +} + +size_t LinuxLocalClient::readBytesUntil(char terminator, char *buffer, size_t length) { + return client.readBytesUntil(terminator, buffer, length); +} + +void LinuxLocalClient::print(const char *data) { + client.write((uint8_t*)data, strlen(data)); +} + +/*int LinuxLocalClient::peek() { + return client.peek(); +}*/ + +void LinuxLocalClient::setTimeout(int timeout) { + //client.setTimeout(timeout); +} + +void LinuxLocalClient::flush() { + client.flush(); +} + +void LinuxLocalClient::stop() { + client.stop(); +} diff --git a/otfpi/LinuxLocalServer.h b/otfpi/LinuxLocalServer.h new file mode 100644 index 000000000..e630a9131 --- /dev/null +++ b/otfpi/LinuxLocalServer.h @@ -0,0 +1,41 @@ +#ifndef OTF_LINUXLOCALSERVER_H +#define OTF_LINUXLOCALSERVER_H + +#include "LocalServer.h" +#include "etherport.h" + +namespace OTF { + class LinuxLocalClient : public LocalClient { + friend class LinuxLocalServer; + + private: + EthernetClient client; + LinuxLocalClient(EthernetClient client); + + public: + bool dataAvailable(); + size_t readBytes(char *buffer, size_t length); + size_t readBytesUntil(char terminator, char *buffer, size_t length); + void print(const char *data); + //int peek(); + void setTimeout(int timeout); + void flush(); + void stop(); + }; + + + class LinuxLocalServer : public LocalServer { + private: + EthernetServer server; + LinuxLocalClient *activeClient = nullptr; + + public: + LinuxLocalServer(uint16_t port); + ~LinuxLocalServer(); + + LocalClient *acceptClient(); + void begin(); + }; +}// namespace OTF + +#endif diff --git a/otfpi/LocalServer.h b/otfpi/LocalServer.h new file mode 100644 index 000000000..aa0621187 --- /dev/null +++ b/otfpi/LocalServer.h @@ -0,0 +1,53 @@ +#ifndef OTF_LOCALSERVER_H +#define OTF_LOCALSERVER_H + +#include + +namespace OTF { + class LocalClient { + public: + /** Returns a boolean indicating if data is currently available from this client. */ + virtual bool dataAvailable() = 0; + + /** + * Reads up to `length` bytes from the request stream into `buffer`. + * @return The number of bytes read. + */ + virtual size_t readBytes(char *buffer, size_t length) = 0; + + /** + * Reads up to `length` bytes from the request stream until `terminator` or the end of stream is reached. + * @return The number of bytes read. + */ + virtual size_t readBytesUntil(char terminator, char *buffer, size_t length) = 0; + + /** Prints a null-terminated string to the response stream. This method may be called multiple times before the stream is closed. */ + virtual void print(const char *data) = 0; + + /** Prints a null-terminated string to the response stream. This method may be called multiple times before the stream is closed. */ + //virtual void print(const __FlashStringHelper *data) = 0; + + /** Returns the next character in the request stream (without advancing the stream), or returns -1 if no character is available. */ + //virtual int peek() = 0; + + /** Sets the maximum number of milliseconds to wait for data to be available when readBytes() is called. */ + virtual void setTimeout(int timeout) = 0; + + virtual void flush() = 0; + virtual void stop() = 0; + }; + + class LocalServer { + public: + /** + * Closes the active client (if one is active) and accepts a new client (if one is available). + * @return The newly accepted client, or `nullptr` if none was available. + */ + virtual LocalClient *acceptClient() = 0; + + /** Starts listening for connections. */ + virtual void begin() = 0; + }; +}// namespace OTF + +#endif diff --git a/otfpi/OpenThingsFramework.cpp b/otfpi/OpenThingsFramework.cpp new file mode 100644 index 000000000..08d4585f6 --- /dev/null +++ b/otfpi/OpenThingsFramework.cpp @@ -0,0 +1,306 @@ +#include "OpenThingsFramework.h" +#include "StringBuilder.h" + +// The timeout for reading and parsing incoming requests. +#define WIFI_CONNECTION_TIMEOUT 1500 +/* How often to try to reconnect to the websocket if the connection is lost. Each reconnect attempt is blocking and has + * a 5 second timeout. + */ +#define WEBSOCKET_RECONNECT_INTERVAL 30000 +#define strncmp_P strncmp +using namespace OTF; + +OpenThingsFramework::OpenThingsFramework(uint16_t webServerPort, char *hdBuffer, int hdBufferSize) : localServer(webServerPort) { + DEBUG_PRINTLN("Instantiating OTF..."); + if(hdBuffer != NULL) { // if header buffer is externally provided, use it directly + headerBuffer = hdBuffer; + headerBufferSize = (hdBufferSize > 0) ? hdBufferSize : HEADERS_BUFFER_SIZE; + } else { // otherwise allocate one + headerBuffer = new char[HEADERS_BUFFER_SIZE]; + headerBufferSize = HEADERS_BUFFER_SIZE; + } + missingPageCallback = defaultMissingPageCallback; + localServer.begin(); +} + +OpenThingsFramework::OpenThingsFramework(uint16_t webServerPort, const String &webSocketHost, uint16_t webSocketPort, + const String &deviceKey, bool useSsl, char *hdBuffer, int hdBufferSize) : OpenThingsFramework(webServerPort, hdBuffer, hdBufferSize) { + setCloudStatus(UNABLE_TO_CONNECT); + DEBUG_PRINTLN(F("Initializing websocket...")); + webSocket = new WebSocketsClient(); + if (useSsl) { + DEBUG_PRINTLN(F("Connecting to websocket with SSL")); + //webSocket->beginSSL(webSocketHost, webSocketPort, "/socket/v1?deviceKey=" + deviceKey); + } else { + DEBUG_PRINTLN(F("Connecting to websocket without SSL")); + webSocket->begin(webSocketHost, webSocketPort, "/socket/v1?deviceKey=" + deviceKey); + } + DEBUG_PRINTLN(F("Initialized websocket")); + + // Wrap the member function in a static function. + webSocket->onEvent([this](WStype_t type, uint8_t *payload, size_t length) -> void { + webSocketCallback(type, payload, length); + }); + + webSocket->setReconnectInterval(WEBSOCKET_RECONNECT_INTERVAL); + // Ping the server every 15 seconds with a timeout of 5 seconds, and treat 1 missed ping as a lost connection. + webSocket->enableHeartbeat(15000, 5000, 1); +} + +char *makeMapKey(StringBuilder *sb, HTTPMethod method, const char *path) { + sb->bprintf(F("%d%s"), method, path); + return sb->toString(); +} + +void OpenThingsFramework::on(const char *path, callback_t callback, HTTPMethod method) { + callbacks.add(makeMapKey(new StringBuilder(KEY_MAX_LENGTH), method, path), callback); +} + +//void OpenThingsFramework::on(const __FlashStringHelper *path, callback_t callback, HTTPMethod method) { +// callbacks.add(makeMapKey(new StringBuilder(KEY_MAX_LENGTH), method, (char *) path), callback); +//} + +void OpenThingsFramework::onMissingPage(callback_t callback) { + missingPageCallback = callback; +} + +void OpenThingsFramework::localServerLoop() { + + static ulong wait_to = 0; // timeout to wait for client data + if (!wait_to) { + localClient = localServer.acceptClient(); + // If a client wasn't available from the server, exit the local server loop. + if (!localClient) { + return; + } + // set a timeout to wait for client data + wait_to = millis()+WIFI_CONNECTION_TIMEOUT; + } + DEBUG_PRINT("wait_to="); + DEBUG_PRINTLN(wait_to); + if (!localClient->dataAvailable()) { + // If data isn't available from the client yet, exit the local server loop and check again next iteration. + // but if we reached timeout, then reset wait_to to 0 and flush localClient so we can accept new client + if(millis()>wait_to) { + wait_to=0; + DEBUG_PRINTLN(F("client wait timeout")); + localClient->flush(); + localClient->stop(); + } + return; + } + + // got new client data, reset wait_to to 0 + wait_to = 0; + + DEBUG_PRINTLN("incoming localServer request"); + + // Update the timeout for each data read to ensure that the total timeout is WIFI_CONNECTION_TIMEOUT. + ulong timeout = millis()+WIFI_CONNECTION_TIMEOUT; + + + char *buffer = headerBuffer; + size_t length = 0; + while (localClient->dataAvailable() && millis() < timeout) { + if (length >= (size_t)headerBufferSize) { + localClient->print(F("HTTP/1.1 413 Request too large\r\n\r\nThe request was too large")); + // Get a new client to indicate that the previous client is no longer needed. + localClient = localServer.acceptClient(); + DEBUG_PRINTLN("incoming localServer request - 1"); + return; + } + + size_t read = localClient->readBytesUntil('\n', &buffer[length], min((int) (headerBufferSize - length - 1), headerBufferSize)); + DEBUG_PRINTF("read=%d", read); + char rc = buffer[length]; + length += read; + buffer[length++] = '\n'; + if(read==1 && rc=='\r') { break; } + } + + buffer[length] = 0; + DEBUG_PRINTF((char *) F("Finished reading data from client. Request line + headers were %d bytes\n"), length); + DEBUG_PRINTF((char *) F("Request: %s\n"), (char *) buffer); + + // Make sure that the headers were fully read into the buffer. + if (strncmp_P(&buffer[length - 4], (char *) F("\r\n\r\n"), 4) != 0) { + DEBUG_PRINTLN(F("The request headers were not fully read into the buffer.")); + localClient->print(F("HTTP/1.1 413 Request too large\r\n\r\nThe request was too large")); + return; + } + + + DEBUG_PRINTLN(F("Parsing request")); + Request request(buffer, length, false); + + char *bodyBuffer = NULL; + // If the request was valid, read the body and add it to the Request object. + if (request.getType() > INVALID) { + char *contentLengthString = request.getHeader(F("content-length")); + // If the header was not specified, the message has no body. + if (contentLengthString != nullptr) { + long contentLength = atol(contentLengthString); + // If the header specifies a length of 0 or could not be parsed, the message has no body. + if (contentLength > 0) { + // Read the body from the client. + bodyBuffer = new char[contentLength]; + size_t bodyLength = 0; + timeout = millis()+WIFI_CONNECTION_TIMEOUT; + while (localClient->dataAvailable() && millis()readBytes(&bodyBuffer[bodyLength], min((int) (contentLength - bodyLength), 1024)); + bodyLength += read; + } + bodyBuffer[bodyLength] = 0; + request.body = bodyBuffer; + request.bodyLength = bodyLength; + } + } + } + + DEBUG_PRINTLN(F("Filling response")); + Response res = Response(localClient); + fillResponse(request, res); + + if(bodyBuffer) delete[] bodyBuffer; + DEBUG_PRINTLN(F("Sending response")); + if (res.isValid()) { + char *responseString = res.toString(); + DEBUG_PRINTF((char *) F("Response message is: %s\n"), responseString); + localClient->print(responseString); + } else { + localClient->print(F("HTTP/1.1 500 OTF error\r\nResponse string could not be built\r\n")); + DEBUG_PRINTLN(F("An error occurred while building the response string.")); + } + localClient->flush(); + localClient->stop(); + + DEBUG_PRINTLN(F("Finished handling request")); + + // Get a new client to indicate that the previous client is no longer needed. + localClient = localServer.acceptClient(); + if (localClient) + wait_to = millis()+WIFI_CONNECTION_TIMEOUT; +} + +void OpenThingsFramework::loop() { + localServerLoop(); + if (webSocket != nullptr) { + webSocket->loop(); + } +} + +void OpenThingsFramework::webSocketCallback(WStype_t type, uint8_t *payload, size_t length) { + switch (type) { + case WStype_DISCONNECTED: + DEBUG_PRINTLN(F("Disconnected from websocket")); + /* A failed attempt to connect will also fire a WStype_DISCONNECTED event, so this check is + * needed to prevent the UNABLE_TO_CONNECT status to be overwritten. */ + if (cloudStatus == CONNECTED) { + setCloudStatus(DISCONNECTED); + } + break; + case WStype_CONNECTED: + DEBUG_PRINTLN(F("Connected to websocket")); + setCloudStatus(CONNECTED); + break; + case WStype_TEXT: { + DEBUG_PRINTF((char *) F("Received a websocket message of length %d\n"), length); + char *message = (char *) payload; + +#define PREFIX_LENGTH 5 +#define ID_LENGTH 4 +// Length of the prefix, request ID, carriage return, and line feed. +#define HEADER_LENGTH PREFIX_LENGTH + ID_LENGTH + 2 + if (strncmp_P(message, (char *) F("FWD: "), PREFIX_LENGTH) == 0) { + DEBUG_PRINTLN(F("Message is a forwarded request.")); + char *requestId = &message[PREFIX_LENGTH]; + // Replace the assumed carriage return with a null character to terminate the ID string. + requestId[ID_LENGTH] = '\0'; + + Request request(&message[HEADER_LENGTH], length - HEADER_LENGTH, true); + Response res = Response(webSocket); + res.bprintf(F("RES: %s\r\n"), requestId); + fillResponse(request, res); + + if (res.isValid()) { + webSocket->sendTXT(res.toString(), res.getLength(), true, false); + } else { + DEBUG_PRINTLN(F("An error occurred building response string")); + StringBuilder builder(100); + builder.bprintf(F("RES: %s\r\n%s"), requestId, + F("HTTP/1.1 500 Internal Error\r\n\r\nAn internal error occurred")); + if (!builder.isValid()) { + DEBUG_PRINTLN(F("Builder is not valid")); + return; + } + } + } else { + DEBUG_PRINTLN(F("Websocket message does not start with the correct prefix.")); + } + break; + } + case WStype_PING: + case WStype_PONG: + // Pings/pongs will be automatically handled by the websockets library. + break; + case WStype_ERROR: + DEBUG(Serial.print(F("Websocket error: "));) + DEBUG_PRINTLN((char *) payload); + break; + default: + DEBUG_PRINTF((char *) F("Received unsupported websocket event of type %d\n"), type); + break; + } +} + +void OpenThingsFramework::fillResponse(const Request &req, Response &res) { + if (req.getType() == INVALID) { + res.writeStatus(400, F("Invalid request")); + res.writeHeader(F("content-type"), F("text/plain")); + res.writeBodyChunk(F("Could not parse request")); + return; + } + + // TODO handle trailing slash in path? + DEBUG_PRINTF((char *) F("Attempting to route request to path '%s'\n"), req.getPath()); + StringBuilder *sb = new StringBuilder(KEY_MAX_LENGTH); + char *key = makeMapKey(sb, req.httpMethod, req.getPath()); + callback_t callback = callbacks.find(key); + + // If there isn't a callback for the specific method, check if there's one for any method. + if (callback == nullptr) { + delete sb; + sb = new StringBuilder(KEY_MAX_LENGTH); + + callback = callbacks.find(makeMapKey(sb, HTTP_ANY, req.getPath())); + } + + delete sb; + + if (callback != nullptr) { + DEBUG_PRINTLN(F("Found callback")); + callback(req, res); + } else { + // Run the missing page callback if none of the registered paths matched. + missingPageCallback(req, res); + } +} + +void OpenThingsFramework::defaultMissingPageCallback(const Request &req, Response &res) { + res.writeStatus(404, F("Not found")); + res.writeHeader(F("content-type"), F("text/plain")); + res.writeBodyChunk(F("The requested page does not exist")); +} + +void OpenThingsFramework::setCloudStatus(CLOUD_STATUS status) { + this->cloudStatus = status; + lastCloudStatusChangeTime = millis(); +} + +CLOUD_STATUS OpenThingsFramework::getCloudStatus() { + return cloudStatus; +} + +unsigned long OpenThingsFramework::getTimeSinceLastCloudStatusChange() { + return millis() - lastCloudStatusChangeTime; +} diff --git a/otfpi/OpenThingsFramework.h b/otfpi/OpenThingsFramework.h new file mode 100644 index 000000000..5b2861a97 --- /dev/null +++ b/otfpi/OpenThingsFramework.h @@ -0,0 +1,101 @@ +#ifndef OTF_OPENTHINGSFRAMEWORK_H +#define OTF_OPENTHINGSFRAMEWORK_H + +#include "Request.h" +#include "Response.h" +#include + +#include "WebSocketsClient.h" +#include "LinuxLocalServer.h" + +#define LOCAL_SERVER_CLASS LinuxLocalServer + +// The size of the buffer to store the incoming request line and headers (does not include body). Larger requests will be discarded. +#define HEADERS_BUFFER_SIZE 1536 + +namespace OTF { + typedef void (*callback_t)(const Request &request, Response &response); + + enum CLOUD_STATUS { + /** Indicates that an OTC token was not specified on initialization. */ + NOT_ENABLED, + /** Indicates that the device was never able to connect to the server. */ + UNABLE_TO_CONNECT, + /** Indicates that the device was previously connected to the server, but got disconnected and has been unable to reconnect. */ + DISCONNECTED, + /** Indicates that the device is currently connected to the server. */ + CONNECTED + }; + + class OpenThingsFramework { + private: + LOCAL_SERVER_CLASS localServer = LOCAL_SERVER_CLASS(80); + LocalClient *localClient = nullptr; + WebSocketsClient *webSocket = nullptr; + LinkedMap callbacks; + callback_t missingPageCallback; + CLOUD_STATUS cloudStatus = NOT_ENABLED; + unsigned long lastCloudStatusChangeTime = millis(); + char *headerBuffer = NULL; + int headerBufferSize = 0; + + void webSocketCallback(WStype_t type, uint8_t *payload, size_t length); + + void fillResponse(const Request &req, Response &res); + void localServerLoop(); + void setCloudStatus(CLOUD_STATUS status); + + static void defaultMissingPageCallback(const Request &req, Response &res); + + public: + /** + * Initializes the library to only listen on a local webserver. + * @param webServerPort The local port to bind the webserver to. + * @param hdBuffer externally provided header buffer (optional) + * @param hdBufferSize size of the externally provided header buffer (optional) + */ + OpenThingsFramework(uint16_t webServerPort, char *hdBuffer = NULL, int hdBufferSize = HEADERS_BUFFER_SIZE); + + /** + * Initializes the library to listen on a local webserver and connect to a remote websocket. + * @param webServerPort The local port to bind the webserver to. + * @param webSocketHost The host of the remote websocket. + * @param webSocketPort The port of the remote websocket. + * @param deviceKey The unique device key that identifies this device. + * @param useSsl Indicates if SSL should be used when connecting to the websocket. + * @param hdBuffer externally provided header buffer (optional) + * @param hdBufferSize size of the externally provided header buffer (optional) + */ + OpenThingsFramework(uint16_t webServerPort, const String &webSocketHost, uint16_t webSocketPort, + const String &deviceKey, bool useSsl, char *hdBuffer = NULL, int hdBufferSize = HEADERS_BUFFER_SIZE); + + /** + * Registers a callback function to run when a request is made to the specified path. The callback function will + * be passed an OpenThingsRequest, and must return an OpenThingsResponse. + * @param path + * @param callback + */ + void on(const char *path, callback_t callback, HTTPMethod method = HTTP_ANY); + + /** + * Registers a callback function to run when a request is made to the specified path. The callback function will + * be passed an OpenThingsRequest, and must return an OpenThingsResponse. + * @param path + * @param callback + */ + //void on(const __FlashStringHelper *path, callback_t callback, HTTPMethod method = HTTP_ANY); + + /** Registers a callback function to run when a request is received but its path does not match a registered callback. */ + void onMissingPage(callback_t callback); + + void loop(); + + /** Returns the current status of the connection to the OpenThings Cloud server. */ + CLOUD_STATUS getCloudStatus(); + + /** Returns the number of milliseconds since there was last a change in the cloud status. */ + unsigned long getTimeSinceLastCloudStatusChange(); + }; +}// namespace OTF + +#endif diff --git a/otfpi/Request.cpp b/otfpi/Request.cpp new file mode 100644 index 000000000..f93a179cf --- /dev/null +++ b/otfpi/Request.cpp @@ -0,0 +1,302 @@ +#include "Request.h" +#include +#include +#include + +using namespace OTF; + +// Find the pointers of substrings within the HTTP request and turns them into null-terminated C strings. +Request::Request(char *str, size_t length, bool cloudRequest) { + this->cloudRequest = cloudRequest; + size_t index = 0; + + // Parse the HTTP method. + DEBUG(Serial.println(F("Parsing HTTP method"));) + for (; index < length; index++) { + if (str[index] == '\0') { + // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. + requestType = INVALID; + return; + } else if (str[index] == ' ') { + // Null terminate the HTTP method. + str[index] = '\0'; + + // Move to the first character in the path. + index++; + + break; + } + } + if (index >= length) { + // Error if the HTTP method extends to the end of the request. + requestType = INVALID; + return; + } + + // Map the string to an enum value. + if (strcmp("GET", &str[0]) == 0) { + this->httpMethod = HTTP_GET; + } else if (strcmp("POST", &str[0]) == 0) { + this->httpMethod = HTTP_POST; + } else if (strcmp("PUT", &str[0]) == 0) { + this->httpMethod = HTTP_PUT; + } else if (strcmp("DELETE", &str[0]) == 0) { + this->httpMethod = HTTP_DELETE; + } else if (strcmp("OPTIONS", &str[0]) == 0) { + this->httpMethod = HTTP_OPTIONS; + } else if (strcmp("PATCH", &str[0]) == 0) { + this->httpMethod = HTTP_PATCH; + } else { + DEBUG(Serial.println(F("Could not match HTTP method"));) + // Error if the method isn't a standard method. + requestType = INVALID; + return; + } + + + char character = str[index]; + // TODO handle cases where target isn't always a root path? (https://tools.ietf.org/html/rfc7230#section-5.3.1) + DEBUG(Serial.println(F("Parsing the request target."));) + // Parse the target. + this->path = &str[index]; + // Skip over the path. + while (true) { + if (++index >= length) { + // Error if the target extends to the end of the request. + requestType = INVALID; + return; + } + + character = str[index]; + if (character == '\0') { + // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. + requestType = INVALID; + return; + } else if (character == '?' || character == '#' || character == ' ') { + // Null terminate the path. + str[index] = '\0'; + break; + } + } + + if (character == '?') { + // Parse the query. + character = parseQuery(str, length, ++index); + + // Exit if an error occurred while parsing the query. + if (character == '\0') { + requestType = INVALID; + return; + } + } + + if (character == '#') { + // Skip over the fragment. + while (index < length && (character = str[++index]) != ' ') { + if (character == '\0') { + // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. + requestType = INVALID; + return; + } + } + } + + DEBUG(Serial.println(F("Finished parsing request target."));) + // Move to the first character in the HTTP version. + index++; + + // Find the HTTP version. + this->httpVersion = &str[index]; + for (; index < length; index++) { + if (str[index] == '\0') { + // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. + requestType = INVALID; + return; + } else if (str[index] == '\r') { + // Replace the carriage return with a null terminator. + str[index] = '\0'; + // Move past the carriage return and assumed line feed. + index += 2; + break; + } + } + + // Parse headers until "\r\n\r\n" is encountered (indicates the end of headers) or the end of the string is reached (caused b"y invalid requests). + while (index < length && str[index] != '\r') { + if (!parseHeader(str, length, index, headers)) { + // Reject the request if an error occurs while parsing headers. + requestType = INVALID; + return; + } + } + + // Move past the 2nd consecutive carriage return and assumed line feed. + index += 2; + + if (index == length) { + /* If the cursor is exactly 1 character after the end of the request, it means there is no body. Point the `body` pointer + * to the last character in the request (since a value of NULL would indicate that the request was invalid), but set + * `bodyLength` to 0 so it never gets read. + */ + body = &str[index - 1]; + requestType = NORMAL; + } else if (index > length) { + // If the cursor is more than 1 character after the end of the request, it means the request was somehow illegally formatted. + requestType = INVALID; + return; + } else { + body = &str[index]; + requestType = NORMAL; + } + + bodyLength = length - index; +} + +char Request::parseQuery(char *str, size_t length, size_t &index) { + DEBUG(Serial.println(F("Starting to parse query."));) + while (index < length) { + char *key = &str[index]; + char *value = nullptr; + + for (; index < length; index++) { + char character = str[index]; + + if (character == '\0') { + // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. + return '\0'; + } + + // If the end of the parameter has been reached, store it in the map. + if (character == ' ' || character == '#' || character == '&') { + // Null terminate the parameter. + str[index] = '\0'; + + // Handle parameters without a value. + if (value == nullptr) { + // Replace the null pointer with a pointer to an empty string to differentiate between empty parameters and unspecified parameters. + value = &str[index]; + } + + decodeQueryParameter(value); + DEBUG(Serial.printf((char *) F("Found query parameter '%s' with value '%s'.\n"), key, value);) + + queryParams.add(key, value); + + if (character == ' ' || character == '#') { + // If the end of the query has been reached, return. + return character; + } else { + // Move to the start of the next parameter key. + index++; + break; + } + } else if (character == '=') { + // Make sure that the character is separating the key and the value (it isn't part of the value). + if (value == nullptr) { + // Null terminate the key. + str[index] = '\0'; + value = &str[index + 1]; + } + } + } + } + + // Error if the query extends to the end of the request. + return '\0'; +} + +bool Request::parseHeader(char *str, size_t length, size_t &index, LinkedMap &headers) { + char *lineStart = &str[index]; + char *colon = nullptr; + char *value = nullptr; + + for (; index < length; index++) { + if (str[index] == '\0') { + // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. + return false; + } else if (str[index] == ':' && colon == nullptr) { + colon = &str[index]; + str[index] = '\0'; + } else if (str[index] == '\r') { + if (colon == nullptr) { + // Error if there is an illegal header line that doesn't contain a colon. + return false; + } + + // Terminate the header value. + str[index] = '\0'; + + // Handle headers without a value. + if (value == nullptr) { + // Replace the null pointer with a pointer to an empty string to differentiate between empty header and unspecified headers. + value = &str[index]; + } + + // Trim trailing whitespace from the header value (https://tools.ietf.org/html/rfc7230#section-3.2.4). + for (size_t i = index; i > 0; i--) { + // Replace whitespace with null terminators so the value string will terminate at the first space after it. + if (isspace(str[index])) { + str[index] = '\0'; + } else { + break; + } + } + + DEBUG(Serial.printf((char *) F("Found header '%s' with value '%s'.\n"), lineStart, value);) + // TODO handle duplicate header fields by concatenating them with a comma. + headers.add(lineStart, value); + + // Move past the carriage return and assumed line feed. + index += 2; + break; + } else if (colon != nullptr && value == nullptr && !isspace(str[index])) { + // Mark the location of the header value after skipping whitespace. + value = &str[index]; + } else if (value == nullptr) { + // Make the header name lowercase. + str[index] = tolower(str[index]); + } + } + return true; +} + +void Request::decodeQueryParameter(char *value) { + unsigned int offset = 0; + unsigned int index = 0; + while (value[index + offset] != '\0') { + DEBUG(Serial.printf((char *) F("Index is %d and offset is %d\n"), index, offset);) + char character = value[index + offset]; + if (character == '+') { + character = ' '; + } else if (character == '%') { + char highDigit = value[index + ++offset]; + char lowDigit = value[index + ++offset]; + if (highDigit == '\0' || lowDigit == '\0') { + // Abort decoding because the query string is illegally formatted. + return; + } + + char hex[3] = {highDigit, lowDigit, '\0'}; + character = strtol(hex, nullptr, 16); + } + + value[index] = character; + + index++; + } + value[index] = '\0'; +} + +char *Request::getPath() const { return path; } + +char *Request::getQueryParameter(const char *key) const { return queryParams.find(key); } + +char *Request::getHeader(const char *key) const { return headers.find(key); } + +char *Request::getBody() const { return body; } + +size_t Request::getBodyLength() const { return bodyLength; } + +RequestType Request::getType() const { return requestType; } + +bool Request::isCloudRequest() const { return cloudRequest; } diff --git a/otfpi/Request.h b/otfpi/Request.h new file mode 100644 index 000000000..dd1ece649 --- /dev/null +++ b/otfpi/Request.h @@ -0,0 +1,116 @@ +#ifndef OTF_REQUEST_H +#define OTF_REQUEST_H + +//#define SERIAL_DEBUG +#ifdef SERIAL_DEBUG +#define DEBUG(x) x +#else +#define DEBUG(x) +#endif + +#include "LinkedMap.h" +#include + +namespace OTF { + + enum HTTPMethod { + HTTP_ANY, + HTTP_GET, + HTTP_POST, + HTTP_PUT, + HTTP_PATCH, + HTTP_DELETE, + HTTP_OPTIONS + }; + + enum RequestType { + INVALID, + NORMAL + }; + + class Request { + friend class OpenThingsFramework; + + private: + enum HTTPMethod httpMethod; + char *httpVersion = nullptr; + char *path = nullptr; + LinkedMap queryParams; + LinkedMap headers; + char *body = nullptr; + size_t bodyLength = 0; + RequestType requestType = INVALID; + bool cloudRequest; + + /** + * Parses an HTTP request. The parser makes some assumptions about the message format that may not hold if the + * message is improperly formatted, so the behavior of this constructor is undefined if it is passed an improperly + * formatted request. + */ + Request(char *str, size_t length, bool cloudRequest); + + /** + * Parses the query of the request. + * @param str The full request string. + * @param length The length of the full request. + * @param index The index of the first character after the '?' character that marks the start of the request. + * When the function terminates successfully, this will be updated to the index of the character that marked the + * end of the query. The character at this index may be changed, but its original value will be returned. + * @return The character that was originally at `index` in the string. Possible values are: + * '\0', indicating an error occurred. + * '#', indicating that the new value of `index` marks the first character in the URI fragment. + * ' ', indicating that the new value of `index` marks the first character in the HTTP version. + */ + char parseQuery(char *str, size_t length, size_t &index); + + /** + * Parses a single header and adds it to `headers`. + * @param str The full request string. + * @param length The length of the full request. + * @param index The index of the first character in the header line. When the function terminates successfully, + * this will be updated to the index of the first character after the CRLF sequence at the end of the header. + * @param headers The set of headers to add the parsed header to. + * @return A boolean indicating if the header could be parsed successfully. + */ + bool parseHeader(char *str, size_t length, size_t &index, LinkedMap &headers); + + /** + * Decodes the specified query string, updating and null-terminating the string in-place. + * @param value a null-terminated query value. + */ + static void decodeQueryParameter(char *value); + + public: + /** Returns the path of the request (not including the query) as a null-terminated string. */ + char *getPath() const; + + /** Returns the decoded value of the specified query parameter as a null-terminated string, or NULL if the parameter was not set in the request. */ + char *getQueryParameter(const char *key) const; + + /** + * Returns the value of the specified header as a null-terminated string, or NULL if the header was not set in the request. + * @param key The lowercase header name. + */ + char *getHeader(const char *key) const; + + /** + * Returns the body of the request. THIS STRING IS NOT NULL TERMINATED, AND IT MAY CONTAIN NULL CHARACTERS. If + * the request could not be parsed (as indicated by the getType() method), this method has undefined behavior. + */ + char *getBody() const; + + /** Returns the length of the request body. */ + size_t getBodyLength() const; + + /** Indicates if this request is a multipart request, a non-multipart request, or an illegally formatted request. */ + RequestType getType() const; + + /** + * Indicates if the request came through the cloud rather than through the local server. If this returns `true`, + * it means that the request has already been authenticated by an OpenThings Cloud token. + */ + + bool isCloudRequest() const; + }; +}// namespace OTF +#endif diff --git a/otfpi/Response.cpp b/otfpi/Response.cpp new file mode 100644 index 000000000..7de2adbc1 --- /dev/null +++ b/otfpi/Response.cpp @@ -0,0 +1,74 @@ +#include "Response.h" + +using namespace OTF; + +void Response::writeStatus(uint16_t statusCode, const String &statusMessage) { + if (responseStatus > CREATED) { + valid = false; + DEBUG_PRINTLN("VALID=FALSE writeStatus1"); + return; + } + responseStatus = STATUS_WRITTEN; + + bprintf(F("HTTP/1.1 %d %s\r\n"), statusCode, statusMessage.c_str()); +} + +void Response::writeStatus(uint16_t statusCode) { + if (responseStatus > CREATED) { + valid = false; + DEBUG_PRINTLN("VALID=FALSE writeStatus2"); + return; + } + responseStatus = STATUS_WRITTEN; + + writeStatus(statusCode, F("No message")); +} + +void Response::writeHeader(const char *name, const char *value) { + if (responseStatus < STATUS_WRITTEN || responseStatus > HEADERS_WRITTEN) { + valid = false; + DEBUG_PRINTLN("VALID=FALSE WRITEHEADER1"); + return; + } + responseStatus = HEADERS_WRITTEN; + + bprintf(F("%s: %s\r\n"), name, value); +} + +void Response::writeHeader(const char *name, int value) { + if (responseStatus < STATUS_WRITTEN || responseStatus > HEADERS_WRITTEN) { + valid = false; + DEBUG_PRINTLN("VALID=FALSE WRITEHEADER2"); + return; + } + responseStatus = HEADERS_WRITTEN; + + bprintf(F("%s: %d\r\n"), name, value); +} + +void Response::writeBodyChunk(const char *format, ...) { + if (responseStatus < STATUS_WRITTEN) { + valid = false; + DEBUG_PRINTLN("VALID=FALSE writeBodyChunk"); + return; + } + if (responseStatus != BODY_WRITTEN) { + append("\r\n"); + responseStatus = BODY_WRITTEN; + } + + va_list args; + va_start(args, format); + bprintf(format, args); + va_end(args); +} + +void Response::flush() { + char *responseString = toString(); + if (localClient) + localClient->print(responseString); + else if (webSocket) + webSocket->sendTXT(responseString, getLength(), false, false); + + reset(); +} diff --git a/otfpi/Response.h b/otfpi/Response.h new file mode 100644 index 000000000..84461fbc5 --- /dev/null +++ b/otfpi/Response.h @@ -0,0 +1,62 @@ +#ifndef OTF_RESPONSE_H +#define OTF_RESPONSE_H + +#include "StringBuilder.h" +#include "LocalServer.h" +#include "WebSocketsClient.h" + +// The maximum possible size of response messages. +#define RESPONSE_BUFFER_SIZE 10*1024 + +namespace OTF { + + class Response : public StringBuilder { + friend class OpenThingsFramework; + + private: + enum ResponseStatus { + CREATED, + STATUS_WRITTEN, + HEADERS_WRITTEN, + BODY_WRITTEN + }; + ResponseStatus responseStatus = CREATED; + LocalClient *localClient = nullptr; + WebSocketsClient *webSocket = nullptr; + + Response(WebSocketsClient *pwebSocket) : StringBuilder(RESPONSE_BUFFER_SIZE) {webSocket = pwebSocket;} + + Response(LocalClient *plocalClient) : StringBuilder(RESPONSE_BUFFER_SIZE) {localClient = plocalClient;} + + public: + static const size_t MAX_RESPONSE_LENGTH = RESPONSE_BUFFER_SIZE; + + /** Writes the status code/message to the response. This must be called before writing the headers or body. */ + void writeStatus(uint16_t statusCode, const String &statusMessage); + + /** Writes the status code/message to the response. This must be called before writing the headers or body. */ + //void writeStatus(uint16_t statusCode, const __FlashStringHelper *const statusMessage); + + /** Writes the status code to the response. This must be called before writing the headers or body. */ + void writeStatus(uint16_t statusCode); + + /** Sets a response header. If called multiple times with the same header name, the header will be specified + * multiple times to create a list. This function must not be called before the status has been written or after + * the body has been written. + */ + void writeHeader(const char *name, const char *value); + + void writeHeader(const char *name, int value); + + /** + * Calls sprintf to write a chunk of data to the response body. This method may only be called after any desired + * headers have been set. + * @param format The format string to pass to sprintf. + * @param ... The format arguments to pass to sprintf. + */ + void writeBodyChunk(const char *format, ...); + + void flush(); + }; +}// namespace OTF +#endif diff --git a/otfpi/StringBuilder.cpp b/otfpi/StringBuilder.cpp new file mode 100644 index 000000000..997d7bf37 --- /dev/null +++ b/otfpi/StringBuilder.cpp @@ -0,0 +1,74 @@ +#include "StringBuilder.h" +#include +#include + +using namespace OTF; + +StringBuilder::StringBuilder(size_t maxLength) { + this->maxLength = maxLength; + buffer = new char[maxLength]; +} + +StringBuilder::~StringBuilder() { + delete buffer; +} + +void StringBuilder::bprintf(const char *format, va_list args) { + // Don't do anything if the buffer already contains invalid data. + if (!valid) { + return; + } + + length += vsnprintf(&buffer[length], maxLength - length, format, args); + + // The builder is invalid if the string fits perfectly in the buffer since there wouldn't be room for the null terminator. + if (length >= maxLength) { + // snprintf will not allow more than the specified number of characters to be written to the buffer, so the length will be the buffer size. + length = maxLength; + valid = false; + } +} + +void StringBuilder::bprintf(const char *const format, ...) { + va_list args; + va_start(args, format); + bprintf(format, args); + va_end(args); +} + +char *StringBuilder::toString() const { + return &buffer[0]; +} + +size_t StringBuilder::getLength() const { + return length; +} + +bool StringBuilder::isValid() { + return valid; +} + +bool StringBuilder::willFit(size_t size) { + return size + length + 1 < maxLength; +} + +void StringBuilder::reset() { + length = 0; + buffer[0] = 0; + valid = true; +} + +void StringBuilder::append(const char *txt, size_t size) { + if (!valid || !willFit(size)) { + valid = false; + return; + } + strncpy(&buffer[length], txt, size); + length += size; + buffer[length] = 0; +} + +void StringBuilder::append(const char *txt) { + append(txt, strlen(txt)); +} + diff --git a/otfpi/StringBuilder.h b/otfpi/StringBuilder.h new file mode 100644 index 000000000..090ec6244 --- /dev/null +++ b/otfpi/StringBuilder.h @@ -0,0 +1,62 @@ +#ifndef OTF_STRINGBUILDER_H +#define OTF_STRINGBUILDER_H + +#include +#include + +namespace OTF { + /** + * Wraps a buffer to build a string with repeated calls to sprintf. If any of calls to sprintf cause an error (such + * as exceeding the size of the internal buffer), the error will be silently swallowed and the StringBuilder will be + * marked as invalid. This means that any error checking can occur after the entire string has been built instead of + * a check being required after each individual call to sprintf. + */ + class StringBuilder { + private: + size_t maxLength; + char *buffer; + size_t length = 0; + + protected: + bool valid = true; + + public: + explicit StringBuilder(size_t maxLength); + + ~StringBuilder(); + /** + * Inserts a string into the buffer at the current position using the same formatting rules as printf. If the operation + * would cause the buffer length to be exceeded or some other error occurs, the StringBuilder will be marked as invalid. + * @param format The format string to pass to sprintf. + * @param ... The format arguments to pass to sprintf. + */ + void bprintf(const char *format, va_list args); + + void bprintf(const char *format, ...); + + void append(const char *txt, size_t size); + + void append(const char *txt); + + /** + * Returns the null-terminated represented string stored in the underlying buffer. + * @return The null-terminated represented string stored in the underlying buffer. + */ + char *toString() const; + + size_t getLength() const; + + /** + * Returns a boolean indicating if the string was built successfully without any errors. If false, the behavior + * of toString() is undefined, and the string it returns (which may or may not be null terminated) should NOT + * be used. + */ + bool isValid(); + + bool willFit(size_t size); + + void reset(); + }; +}// namespace OTF + +#endif diff --git a/otfpi/WebSockets.cpp b/otfpi/WebSockets.cpp new file mode 100644 index 000000000..0b3c79b3c --- /dev/null +++ b/otfpi/WebSockets.cpp @@ -0,0 +1,729 @@ +/** + * @file WebSockets.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" +#include + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param code uint16_t see RFC + * @param reason ptr to the disconnect reason message + * @param reasonLen length of the disconnect reason message + */ +void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code); + if(client->status == WSC_CONNECTED && code) { + if(reason) { + sendFrame(client, WSop_close, (uint8_t *)reason, reasonLen); + } else { + uint8_t buffer[2]; + buffer[0] = ((code >> 8) & 0xFF); + buffer[1] = (code & 0xFF); + sendFrame(client, WSop_close, &buffer[0], 2); + } + } + clientDisconnect(client); +} + +/** + * + * @param buf uint8_t * ptr to the buffer for writing + * @param opcode WSopcode_t + * @param length size_t length of the payload + * @param mask bool add dummy mask to the frame (needed for web browser) + * @param maskkey uint8_t[4] key used for payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + */ +uint8_t WebSockets::createHeader(uint8_t * headerPtr, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin) { + uint8_t headerSize; + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(mask) { + headerSize += 4; + } + + // create header + + // byte 0 + *headerPtr = 0x00; + if(fin) { + *headerPtr |= bit(7); ///< set Fin + } + *headerPtr |= opcode; ///< set opcode + headerPtr++; + + // byte 1 + *headerPtr = 0x00; + if(mask) { + *headerPtr |= bit(7); ///< set mask + } + + if(length < 126) { + *headerPtr |= length; + headerPtr++; + } else if(length < 0xFFFF) { + *headerPtr |= 126; + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } else { + // Normally we never get here (to less memory) + *headerPtr |= 127; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = 0x00; + headerPtr++; + *headerPtr = ((length >> 24) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 16) & 0xFF); + headerPtr++; + *headerPtr = ((length >> 8) & 0xFF); + headerPtr++; + *headerPtr = (length & 0xFF); + headerPtr++; + } + + if(mask) { + *headerPtr = maskKey[0]; + headerPtr++; + *headerPtr = maskKey[1]; + headerPtr++; + *headerPtr = maskKey[2]; + headerPtr++; + *headerPtr = maskKey[3]; + headerPtr++; + } + return headerSize; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param length size_t length of the payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @return true if ok + */ +bool WebSockets::sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length, bool fin) { + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize = createHeader(&buffer[0], opcode, length, client->cIsClient, maskKey, fin); + + if(write(client, &buffer[0], headerSize) != headerSize) { + return false; + } + + return true; +} + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * ptr to the payload + * @param length size_t length of the payload + * @param fin bool can be used to send data in more then one frame (set fin on the last frame) + * @param headerToPayload bool set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!) + * @return true if ok + */ +bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin, bool headerToPayload) { + if(client->tcp && !client->tcp->connected()) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not Connected!?\n", client->num); + return false; + } + + if(client->status != WSC_CONNECTED) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not in WSC_CONNECTED state!?\n", client->num); + return false; + } + + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] ------- send message frame -------\n", client->num); + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] fin: %u opCode: %u mask: %u length: %u headerToPayload: %u\n", client->num, fin, opcode, client->cIsClient, length, headerToPayload); + + if(opcode == WSop_text) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] text: %s\n", client->num, (payload + (headerToPayload ? 14 : 0))); + } + + uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; + uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; + + uint8_t headerSize; + uint8_t * headerPtr; + uint8_t * payloadPtr = payload; + bool useInternBuffer = false; + bool ret = true; + + // calculate header Size + if(length < 126) { + headerSize = 2; + } else if(length < 0xFFFF) { + headerSize = 4; + } else { + headerSize = 10; + } + + if(client->cIsClient) { + headerSize += 4; + } + +#ifdef WEBSOCKETS_USE_BIG_MEM + // only for ESP since AVR has less HEAP + // try to send data in one TCP package (only if some free Heap is there) + if(!headerToPayload && ((length > 0) && (length < 1400))) { //&& (GET_FREE_HEAP > 6000)) { + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] pack to one TCP package...\n", client->num); + uint8_t * dataPtr = (uint8_t *)malloc(length + WEBSOCKETS_MAX_HEADER_SIZE); + if(dataPtr) { + memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length); + headerToPayload = true; + useInternBuffer = true; + payloadPtr = dataPtr; + } + } +#endif + + // set Header Pointer + if(headerToPayload) { + // calculate offset in payload + headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize)); + } else { + headerPtr = &buffer[0]; + } + + if(client->cIsClient && useInternBuffer) { + // if we use a Intern Buffer we can modify the data + // by this fact its possible the do the masking + for(uint8_t x = 0; x < sizeof(maskKey); x++) { + maskKey[x] = random() % 0x100; + } + } + + createHeader(headerPtr, opcode, length, client->cIsClient, maskKey, fin); + + if(client->cIsClient && useInternBuffer) { + uint8_t * dataMaskPtr; + + if(headerToPayload) { + dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); + } else { + dataMaskPtr = payloadPtr; + } + + for(size_t x = 0; x < length; x++) { + dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); + } + } + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + if(headerToPayload) { + // header has be added to payload + // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings + // offset in payload is calculatetd 14 - headerSize + if(write(client, &payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) { + ret = false; + } + } else { + // send header + if(write(client, &buffer[0], headerSize) != headerSize) { + ret = false; + } + + if(payloadPtr && length > 0) { + // send payload + if(write(client, &payloadPtr[0], length) != length) { + ret = false; + } + } + } + + DEBUG_WEBSOCKETS("[WS][%d][sendFrame] sending Frame Done (%luus).\n", client->num, (micros() - start)); + +#ifdef WEBSOCKETS_USE_BIG_MEM + if(useInternBuffer && payloadPtr) { + free(payloadPtr); + } +#endif + + return ret; +} + +/** + * callen when HTTP header is done + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::headerDone(WSclient_t * client) { + client->status = WSC_CONNECTED; + client->cWsRXsize = 0; + DEBUG_WEBSOCKETS("[WS][%d][headerDone] Header Handling Done.\n", client->num); +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->cHttpLine = ""; + handleWebsocket(client); +#endif +} + +/** + * handle the WebSocket stream + * @param client WSclient_t * ptr to the client struct + */ +void WebSockets::handleWebsocket(WSclient_t * client) { + if(client->cWsRXsize == 0) { + handleWebsocketCb(client); + } +} + +/** + * wait for + * @param client + * @param size + */ +bool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) { + if(!client->tcp || !client->tcp->connected()) { + return false; + } + + if(size > WEBSOCKETS_MAX_HEADER_SIZE) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d too big!\n", client->num, size); + return false; + } + + if(client->cWsRXsize >= size) { + return true; + } + + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d cWsRXsize: %d\n", client->num, size, client->cWsRXsize); + readCb(client, &client->cWsHeader[client->cWsRXsize], (size - client->cWsRXsize), std::bind([](WebSockets * server, size_t size, WSclient_t * client, bool ok) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor][readCb] size: %d ok: %d\n", client->num, size, ok); + if(ok) { + client->cWsRXsize = size; + server->handleWebsocketCb(client); + } else { + DEBUG_WEBSOCKETS("[WS][%d][readCb] failed.\n", client->num); + client->cWsRXsize = 0; + // timeout or error + server->clientDisconnect(client, 1002); + } + }, + this, size, std::placeholders::_1, std::placeholders::_2)); + return false; +} + +void WebSockets::handleWebsocketCb(WSclient_t * client) { + if(!client->tcp || !client->tcp->connected()) { + return; + } + + uint8_t * buffer = client->cWsHeader; + + WSMessageHeader_t * header = &client->cWsHeaderDecode; + uint8_t * payload = NULL; + + uint8_t headerLen = 2; + + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + + // split first 2 bytes in the data + header->fin = ((*buffer >> 7) & 0x01); + header->rsv1 = ((*buffer >> 6) & 0x01); + header->rsv2 = ((*buffer >> 5) & 0x01); + header->rsv3 = ((*buffer >> 4) & 0x01); + header->opCode = (WSopcode_t)(*buffer & 0x0F); + buffer++; + + header->mask = ((*buffer >> 7) & 0x01); + header->payloadLen = (WSopcode_t)(*buffer & 0x7F); + buffer++; + + if(header->payloadLen == 126) { + headerLen += 2; + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + header->payloadLen = buffer[0] << 8 | buffer[1]; + buffer += 2; + } else if(header->payloadLen == 127) { + headerLen += 8; + // read 64bit integer as length + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + + if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) { + // really too big! + header->payloadLen = 0xFFFFFFFF; + } else { + header->payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; + } + buffer += 8; + } + + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ------- read massage frame -------\n", client->num); + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, header->fin, header->rsv1, header->rsv2, header->rsv3, header->opCode); + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, header->mask, header->payloadLen); + + if(header->payloadLen > WEBSOCKETS_MAX_DATA_SIZE) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] payload too big! (%u)\n", client->num, header->payloadLen); + clientDisconnect(client, 1009); + return; + } + + if(header->mask) { + headerLen += 4; + if(!handleWebsocketWaitFor(client, headerLen)) { + return; + } + header->maskKey = buffer; + buffer += 4; + } + + if(header->payloadLen > 0) { + // if text data we need one more + payload = (uint8_t *)malloc(header->payloadLen + 1); + + if(!payload) { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, header->payloadLen); + clientDisconnect(client, 1011); + return; + } + readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload)); + } else { + handleWebsocketPayloadCb(client, true, NULL); + } +} + +void WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload) { + WSMessageHeader_t * header = &client->cWsHeaderDecode; + if(ok) { + if(header->payloadLen > 0) { + payload[header->payloadLen] = 0x00; + + if(header->mask) { + // decode XOR + for(size_t i = 0; i < header->payloadLen; i++) { + payload[i] = (payload[i] ^ header->maskKey[i % 4]); + } + } + } + + switch(header->opCode) { + case WSop_text: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] text: %s\n", client->num, payload); + // fallthrough + case WSop_binary: + case WSop_continuation: + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_ping: + // send pong back + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ping received (%s)\n", client->num, payload ? (const char *)payload : ""); + sendFrame(client, WSop_pong, payload, header->payloadLen); + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_pong: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload ? (const char *)payload : ""); + client->pongReceived = true; + messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); + break; + case WSop_close: { +#ifndef NODEBUG_WEBSOCKETS + uint16_t reasonCode = 1000; + if(header->payloadLen >= 2) { + reasonCode = payload[0] << 8 | payload[1]; + } +#endif + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get ask for close. Code: %d\n", client->num, reasonCode); + if(header->payloadLen > 2) { + DEBUG_WEBSOCKETS(" (%s)\n", (payload + 2)); + } else { + DEBUG_WEBSOCKETS("\n"); + } + clientDisconnect(client, 1000); + } break; + default: + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] got unknown opcode: %d\n", client->num, header->opCode); + clientDisconnect(client, 1002); + break; + } + + if(payload) { + free(payload); + } + + // reset input + client->cWsRXsize = 0; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + // register callback for next message + handleWebsocketWaitFor(client, 2); +#endif + + } else { + DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] missing data!\n", client->num); + free(payload); + clientDisconnect(client, 1002); + } +} + +/** + * generate the key for Sec-WebSocket-Accept + * @param clientKey String + * @return String Accept Key + */ +String WebSockets::acceptKey(String & clientKey) { + uint8_t sha1HashBin[20] = { 0 }; +#ifdef ESP8266 + sha1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]); +#elif defined(ESP32) + String data = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + esp_sha(SHA1, (unsigned char *)data.c_str(), data.length(), &sha1HashBin[0]); +#else + clientKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + SHA1_CTX ctx; + SHA1Init(&ctx); + SHA1Update(&ctx, (const unsigned char *)clientKey.c_str(), clientKey.length()); + SHA1Final(&sha1HashBin[0], &ctx); +#endif + + String key = base64_encode(sha1HashBin, 20); + //key.trim(); + trim(key); + + return key; +} + +/** + * base64_encode + * @param data uint8_t * + * @param length size_t + * @return base64 encoded String + */ +String WebSockets::base64_encode(uint8_t * data, size_t length) { + size_t size = ((length * 1.6f) + 1); + size = std::max(size, (size_t)5); // minimum buffer size + char * buffer = (char *)malloc(size); + if(buffer) { + base64_encodestate _state; + base64_init_encodestate(&_state); + int len = base64_encode_block((const char *)&data[0], length, &buffer[0], &_state); + len = base64_encode_blockend((buffer + len), &_state); + + String base64 = String(buffer); + free(buffer); + return base64; + } + return String("-FAIL-"); +} + +/** + * read x byte from tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return true if ok + */ +bool WebSockets::readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb) { +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + if(!client->tcp || !client->tcp->connected()) { + return false; + } + + client->tcp->readBytes(out, n, std::bind([](WSclient_t * client, bool ok, WSreadWaitCb cb) { + if(cb) { + cb(client, ok); + } + }, + client, std::placeholders::_1, cb)); + +#else + unsigned long t = millis(); + ssize_t len; + DEBUG_WEBSOCKETS("[readCb] n: %zu t: %lu\n", n, t); + while(n > 0) { + if(client->tcp == NULL) { + DEBUG_WEBSOCKETS("[readCb] tcp is null!\n"); + if(cb) { + cb(client, false); + } + return false; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[readCb] not connected!\n"); + if(cb) { + cb(client, false); + } + return false; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[readCb] receive TIMEOUT! %lu\n", (millis() - t)); + if(cb) { + cb(client, false); + } + return false; + } + + if(!client->tcp->available()) { + WEBSOCKETS_YIELD_MORE(); + continue; + } + + len = client->tcp->read((uint8_t *)out, n); + if(len > 0) { + t = millis(); + out += len; + n -= len; + // DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } else { + // DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); + } + if(n > 0) { + WEBSOCKETS_YIELD(); + } + } + if(cb) { + cb(client, true); + } + WEBSOCKETS_YIELD(); +#endif + return true; +} + +/** + * write x byte to tcp or get timeout + * @param client WSclient_t * + * @param out uint8_t * data buffer + * @param n size_t byte count + * @return bytes send + */ +size_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) { + if(out == NULL) + return 0; + if(client == NULL) + return 0; + unsigned long t = millis(); + size_t len = 0; + size_t total = 0; + DEBUG_WEBSOCKETS("[write] n: %zu t: %lu\n", n, t); + while(n > 0) { + if(client->tcp == NULL) { + DEBUG_WEBSOCKETS("[write] tcp is null!\n"); + break; + } + + if(!client->tcp->connected()) { + DEBUG_WEBSOCKETS("[write] not connected!\n"); + break; + } + + if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { + DEBUG_WEBSOCKETS("[write] write TIMEOUT! %lu\n", (millis() - t)); + break; + } + + len = client->tcp->write((const uint8_t *)out, n); + if(len) { + t = millis(); + out += len; + n -= len; + total += len; + // DEBUG_WEBSOCKETS("write %d left %d!\n", len, n); + } else { + DEBUG_WEBSOCKETS("WS write %d failed left %d!\n", len, n); + } + if(n > 0) { + WEBSOCKETS_YIELD(); + } + } + WEBSOCKETS_YIELD(); + return total; +} + +size_t WebSockets::write(WSclient_t * client, const char * out) { + if(client == NULL) + return 0; + if(out == NULL) + return 0; + return write(client, (uint8_t *)out, strlen(out)); +} + +/** + * enable ping/pong heartbeat process + * @param client WSclient_t * + * @param pingInterval uint32_t how often ping will be sent + * @param pongTimeout uint32_t millis after which pong should timout if not received + * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect + */ +void WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { + if(client == NULL) + return; + client->pingInterval = pingInterval; + client->pongTimeout = pongTimeout; + client->disconnectTimeoutCount = disconnectTimeoutCount; + client->pongReceived = false; +} + +/** + * handle ping/pong heartbeat timeout process + * @param client WSclient_t * + */ +void WebSockets::handleHBTimeout(WSclient_t * client) { + if(client->pingInterval) { // if heartbeat is enabled + uint32_t pi = millis() - client->lastPing; + + if(client->pongReceived) { + client->pongTimeoutCount = 0; + } else { + if(pi > client->pongTimeout) { // pong not received in time + client->pongTimeoutCount++; + client->lastPing = millis() - client->pingInterval - 500; // force ping on the next run + + DEBUG_WEBSOCKETS("[HBtimeout] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\n", client->lastPing, millis(), pi, client->pongTimeoutCount); + + if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) { + DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); + clientDisconnect(client); + } + } + } + } +} diff --git a/otfpi/WebSockets.h b/otfpi/WebSockets.h new file mode 100644 index 000000000..aaef74512 --- /dev/null +++ b/otfpi/WebSockets.h @@ -0,0 +1,231 @@ +/** + * @file WebSockets.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * 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 WEBSOCKETS_H_ +#define WEBSOCKETS_H_ + +#include +#include +#include +#include "WebSocketsVersion.h" +#include "etherport.h" +#include "wscompat.h" +#include "libsha1.h" +#include "cencode_inc.h" + +#ifndef DEBUG_WEBSOCKETS +#define DEBUG_WEBSOCKETS(msg, ...) {printf(msg, ##__VA_ARGS__);} +//#ifndef NODEBUG_WEBSOCKETS +//#define NODEBUG_WEBSOCKETS +//#endif +#endif + +#define WEBSOCKETS_MAX_DATA_SIZE (15 * 1024) +#define WEBSOCKETS_USE_BIG_MEM +#define GET_FREE_HEAP not for linux +#define WEBSOCKETS_YIELD() sched_yield() +#define WEBSOCKETS_YIELD_MORE() delay(1) + +#ifndef WEBSOCKETS_TCP_TIMEOUT +#define WEBSOCKETS_TCP_TIMEOUT (5000) +#endif + +#define NETWORK_ESP8266_ASYNC (0) +#define NETWORK_ESP8266 (1) +#define NETWORK_W5100 (2) +#define NETWORK_ENC28J60 (3) +#define NETWORK_ESP32 (4) +#define NETWORK_ESP32_ETH (5) +#define NETWORK_RP2040 (6) +#define NETWORK_LINUX (7) + +#define WEBSOCKETS_NETWORK_TYPE NETWORK_LINUX + +// Network Class +#define WEBSOCKETS_NETWORK_CLASS EthernetClient +//#define WEBSOCKETS_NETWORK_SSL_CLASS EthernetClientSsl +#define WEBSOCKETS_NETWORK_SERVER_CLASS EthernetServer + +// max size of the WS Message Header +#define WEBSOCKETS_MAX_HEADER_SIZE (14) + +#ifdef WEBSOCKETS_NETWORK_SSL_CLASS +#define HAS_SSL +#endif + +#define WEBSOCKETS_STRING(var) var + +#define bit(b) (1UL << (b)) // Taken directly from Arduino.h + +typedef enum { + WSC_NOT_CONNECTED, + WSC_HEADER, + WSC_BODY, + WSC_CONNECTED +} WSclientsStatus_t; + +typedef enum { + WStype_ERROR, + WStype_DISCONNECTED, + WStype_CONNECTED, + WStype_TEXT, + WStype_BIN, + WStype_FRAGMENT_TEXT_START, + WStype_FRAGMENT_BIN_START, + WStype_FRAGMENT, + WStype_FRAGMENT_FIN, + WStype_PING, + WStype_PONG, +} WStype_t; + +typedef enum { + WSop_continuation = 0x00, ///< %x0 denotes a continuation frame + WSop_text = 0x01, ///< %x1 denotes a text frame + WSop_binary = 0x02, ///< %x2 denotes a binary frame + ///< %x3-7 are reserved for further non-control frames + WSop_close = 0x08, ///< %x8 denotes a connection close + WSop_ping = 0x09, ///< %x9 denotes a ping + WSop_pong = 0x0A ///< %xA denotes a pong + ///< %xB-F are reserved for further control frames +} WSopcode_t; + +typedef struct { + bool fin; + bool rsv1; + bool rsv2; + bool rsv3; + + WSopcode_t opCode; + bool mask; + + size_t payloadLen; + + uint8_t * maskKey; +} WSMessageHeader_t; + +typedef struct { + void init(uint8_t num, + uint32_t pingInterval, + uint32_t pongTimeout, + uint8_t disconnectTimeoutCount) { + this->num = num; + this->pingInterval = pingInterval; + this->pongTimeout = pongTimeout; + this->disconnectTimeoutCount = disconnectTimeoutCount; + } + + uint8_t num = 0; ///< connection number + + WSclientsStatus_t status = WSC_NOT_CONNECTED; + + WEBSOCKETS_NETWORK_CLASS * tcp = nullptr; + + bool isSocketIO = false; ///< client for socket.io server + +#if defined(HAS_SSL) + bool isSSL = false; ///< run in ssl mode + WEBSOCKETS_NETWORK_SSL_CLASS * ssl; +#endif + + String cUrl; ///< http url + uint16_t cCode = 0; ///< http code + + bool cIsClient = false; ///< will be used for masking + bool cIsUpgrade = false; ///< Connection == Upgrade + bool cIsWebsocket = false; ///< Upgrade == websocket + + String cSessionId; ///< client Set-Cookie (session id) + String cKey; ///< client Sec-WebSocket-Key + String cAccept; ///< client Sec-WebSocket-Accept + String cProtocol; ///< client Sec-WebSocket-Protocol + String cExtensions; ///< client Sec-WebSocket-Extensions + uint16_t cVersion = 0; ///< client Sec-WebSocket-Version + + uint8_t cWsRXsize = 0; ///< State of the RX + uint8_t cWsHeader[WEBSOCKETS_MAX_HEADER_SIZE]; ///< RX WS Message buffer + WSMessageHeader_t cWsHeaderDecode; + + String base64Authorization; ///< Base64 encoded Auth request + String plainAuthorization; ///< Base64 encoded Auth request + + String extraHeaders; + + bool cHttpHeadersValid = false; ///< non-websocket http header validity indicator + size_t cMandatoryHeadersCount; ///< non-websocket mandatory http headers present count + + bool pongReceived = false; + uint32_t pingInterval = 0; // how often ping will be sent, 0 means "heartbeat is not active" + uint32_t lastPing = 0; // millis when last pong has been received + uint32_t pongTimeout = 0; // interval in millis after which pong is considered to timeout + uint8_t disconnectTimeoutCount = 0; // after how many subsequent pong timeouts discconnect will happen, 0 means "do not disconnect" + uint8_t pongTimeoutCount = 0; // current pong timeout count + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + String cHttpLine; ///< HTTP header lines +#endif + +} WSclient_t; + +class WebSockets { + protected: +#ifdef __AVR__ + typedef void (*WSreadWaitCb)(WSclient_t * client, bool ok); +#else + typedef std::function WSreadWaitCb; +#endif + + virtual void clientDisconnect(WSclient_t * client) = 0; + virtual bool clientIsConnected(WSclient_t * client) = 0; + + void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0); + + virtual void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) = 0; + + uint8_t createHeader(uint8_t * buf, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin); + bool sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length = 0, bool fin = true); + bool sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool fin = true, bool headerToPayload = false); + + void headerDone(WSclient_t * client); + + void handleWebsocket(WSclient_t * client); + + bool handleWebsocketWaitFor(WSclient_t * client, size_t size); + void handleWebsocketCb(WSclient_t * client); + void handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload); + + String acceptKey(String & clientKey); + String base64_encode(uint8_t * data, size_t length); + + bool readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb); + virtual size_t write(WSclient_t * client, uint8_t * out, size_t n); + size_t write(WSclient_t * client, const char * out); + + void enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); + void handleHBTimeout(WSclient_t * client); +}; + +#ifndef UNUSED +#define UNUSED(var) (void)(var) +#endif +#endif /* WEBSOCKETS_H_ */ diff --git a/otfpi/WebSocketsClient.cpp b/otfpi/WebSocketsClient.cpp new file mode 100644 index 000000000..ce015904d --- /dev/null +++ b/otfpi/WebSocketsClient.cpp @@ -0,0 +1,1001 @@ +/** + * @file WebSocketsClient.cpp + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include "WebSockets.h" +#include "WebSocketsClient.h" +#include +#include +#include "wscompat.h" + +WebSocketsClient::WebSocketsClient() { + _cbEvent = NULL; + _client.num = 0; + _client.cIsClient = true; + _client.extraHeaders = WEBSOCKETS_STRING("Origin: file://"); + _reconnectInterval = 500; + _port = 0; + _host = ""; +} + +WebSocketsClient::~WebSocketsClient() { + disconnect(); +} + +/** + * calles to init the Websockets server + */ +void WebSocketsClient::begin(const char * host, uint16_t port, const char * url, const char * protocol) { + _host = host; + _port = port; +#if defined(HAS_SSL) + _fingerprint = SSL_FINGERPRINT_NULL; + _CA_cert = NULL; +#endif + + _client.num = 0; + _client.status = WSC_NOT_CONNECTED; + _client.tcp = NULL; +#if defined(HAS_SSL) + _client.isSSL = false; + _client.ssl = NULL; +#endif + _client.cUrl = url; + _client.cCode = 0; + _client.cIsUpgrade = false; + _client.cIsWebsocket = true; + _client.cKey = ""; + _client.cAccept = ""; + _client.cProtocol = protocol; + _client.cExtensions = ""; + _client.cVersion = 0; + _client.base64Authorization = ""; + _client.plainAuthorization = ""; + _client.isSocketIO = false; + + _client.lastPing = 0; + _client.pongReceived = false; + _client.pongTimeoutCount = 0; + + // todo find better seed + srand(millis()); +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + asyncConnect(); +#endif + + _lastConnectionFail = 0; + _lastHeaderSent = 0; + + DEBUG_WEBSOCKETS("[WS-Client] Websocket Version: " WEBSOCKETS_VERSION "\n"); +} + +void WebSocketsClient::begin(String host, uint16_t port, String url, String protocol) { + begin(host.c_str(), port, url.c_str(), protocol.c_str()); +} + +/*void WebSocketsClient::begin(IPAddress host, uint16_t port, const char * url, const char * protocol) { + return begin(host.toString().c_str(), port, url, protocol); +}*/ + +#if defined(HAS_SSL) +#if defined(SSL_AXTLS) +void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const char * fingerprint, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = fingerprint; + _CA_cert = NULL; +} + +void WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint, String protocol) { + beginSSL(host.c_str(), port, url.c_str(), fingerprint.c_str(), protocol.c_str()); +} + +void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; + _CA_cert = CA_cert; +} +#else +void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const uint8_t * fingerprint, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = fingerprint; + _CA_cert = NULL; +} + +void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) { + begin(host, port, url, protocol); + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; + _CA_cert = CA_cert; +} + +void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { + beginSslWithCA(host, port, url, new BearSSL::X509List(CA_cert), protocol); +} + +void WebSocketsClient::setSSLClientCertKey(BearSSL::X509List * clientCert, BearSSL::PrivateKey * clientPrivateKey) { + _client_cert = clientCert; + _client_key = clientPrivateKey; +} + +void WebSocketsClient::setSSLClientCertKey(const char * clientCert, const char * clientPrivateKey) { + setSSLClientCertKey(new BearSSL::X509List(clientCert), new BearSSL::PrivateKey(clientPrivateKey)); +} + +#endif // SSL_AXTLS +#endif // HAS_SSL + +/* +void WebSocketsClient::beginSocketIO(const char * host, uint16_t port, const char * url, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; +} + +void WebSocketsClient::beginSocketIO(String host, uint16_t port, String url, String protocol) { + beginSocketIO(host.c_str(), port, url.c_str(), protocol.c_str()); +} + +#if defined(HAS_SSL) +void WebSocketsClient::beginSocketIOSSL(const char * host, uint16_t port, const char * url, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; +} + +void WebSocketsClient::beginSocketIOSSL(String host, uint16_t port, String url, String protocol) { + beginSocketIOSSL(host.c_str(), port, url.c_str(), protocol.c_str()); +} + +#if defined(SSL_BARESSL) +void WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; + _CA_cert = CA_cert; +} +#endif + +void WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { + begin(host, port, url, protocol); + _client.isSocketIO = true; + _client.isSSL = true; + _fingerprint = SSL_FINGERPRINT_NULL; +#if defined(SSL_BARESSL) + _CA_cert = new BearSSL::X509List(CA_cert); +#else + _CA_cert = CA_cert; +#endif +} + +#endif +*/ +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * called in arduino loop + */ +void WebSocketsClient::loop(void) { + if(_port == 0) { + return; + } + WEBSOCKETS_YIELD(); + if(!clientIsConnected(&_client)) { + // do not flood the server + if((millis() - _lastConnectionFail) < _reconnectInterval) { + return; + } + +#if defined(HAS_SSL) + if(_client.isSSL) { + DEBUG_WEBSOCKETS("[WS-Client] connect wss...\n"); + if(_client.ssl) { + delete _client.ssl; + _client.ssl = NULL; + _client.tcp = NULL; + } + _client.ssl = new WEBSOCKETS_NETWORK_SSL_CLASS(); + _client.tcp = _client.ssl; + if(_CA_cert) { + DEBUG_WEBSOCKETS("[WS-Client] setting CA certificate"); +#if defined(ESP32) + _client.ssl->setCACert(_CA_cert); +#elif defined(ESP8266) && defined(SSL_AXTLS) + _client.ssl->setCACert((const uint8_t *)_CA_cert, strlen(_CA_cert) + 1); +#elif(defined(ESP8266) || defined(ARDUINO_ARCH_RP2040)) && defined(SSL_BARESSL) + _client.ssl->setTrustAnchors(_CA_cert); +#else +#error setCACert not implemented +#endif +#if defined(ESP32) + } else if(!SSL_FINGERPRINT_IS_SET) { + _client.ssl->setInsecure(); +#elif defined(SSL_BARESSL) + } else if(SSL_FINGERPRINT_IS_SET) { + _client.ssl->setFingerprint(_fingerprint); + } else { + _client.ssl->setInsecure(); + } + if(_client_cert && _client_key) { + _client.ssl->setClientRSACert(_client_cert, _client_key); + DEBUG_WEBSOCKETS("[WS-Client] setting client certificate and key"); +#endif + } + } else { + DEBUG_WEBSOCKETS("[WS-Client] connect ws...\n"); + if(_client.tcp) { + delete _client.tcp; + _client.tcp = NULL; + } + _client.tcp = new WEBSOCKETS_NETWORK_CLASS(); + } +#else + _client.tcp = new WEBSOCKETS_NETWORK_CLASS(); +#endif + + if(!_client.tcp) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!"); + return; + } + WEBSOCKETS_YIELD(); +#if defined(ESP32) + if(_client.tcp->connect(_host.c_str(), _port, WEBSOCKETS_TCP_TIMEOUT)) { +#else + struct hostent *host = gethostbyname(_host.c_str()); + if(host && _client.tcp->connect((uint8_t*)host->h_addr, _port)) { +#endif + connectedCb(); + _lastConnectionFail = 0; + } else { + connectFailedCb(); + _lastConnectionFail = millis(); + } + } else { + handleClientData(); + WEBSOCKETS_YIELD(); + if(_client.status == WSC_CONNECTED) { + handleHBPing(); + handleHBTimeout(&_client); + } + } +} +#endif + +/** + * set callback function + * @param cbEvent WebSocketServerEvent + */ +void WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) { + _cbEvent = cbEvent; +} + +bool WebSocketsClient::sendTXT(const char * payload, size_t length, bool fin, bool headerToPayload) { + if(length == 0) { + length = strlen(payload); + } + if(clientIsConnected(&_client)) { + //https://www.oryx-embedded.com/doc/web__socket_8c_source.html + //A fragmented message consists of a single frame with the FIN bit + //clear and an opcode other than 0, followed by zero or more frames + //with the FIN bit clear and the opcode set to 0, and terminated by + //a single frame with the FIN bit set and an opcode of 0 + WSopcode_t opcode = WSop_text; + if (!firstFrag) { + firstFrag = true; + } else { + opcode = WSop_continuation; + } + if (fin) + firstFrag = false; + return sendFrame(&_client, opcode, (uint8_t *)payload, length, fin, headerToPayload); + } + return false; +} + +/** + * send text data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsClient::sendTXT(uint8_t * payload, size_t length, bool headerToPayload) { + if(length == 0) { + length = strlen((const char *)payload); + } + if(clientIsConnected(&_client)) { + return sendFrame(&_client, WSop_text, payload, length, true, headerToPayload); + } + return false; +} + +bool WebSocketsClient::sendTXT(const uint8_t * payload, size_t length) { + return sendTXT((uint8_t *)payload, length); +} + +bool WebSocketsClient::sendTXT(char * payload, size_t length, bool headerToPayload) { + return sendTXT((uint8_t *)payload, length, headerToPayload); +} + +bool WebSocketsClient::sendTXT(const char * payload, size_t length) { + return sendTXT((uint8_t *)payload, length); +} + +bool WebSocketsClient::sendTXT(String & payload) { + return sendTXT((uint8_t *)payload.c_str(), payload.length()); +} + +bool WebSocketsClient::sendTXT(char payload) { + uint8_t buf[WEBSOCKETS_MAX_HEADER_SIZE + 2] = { 0x00 }; + buf[WEBSOCKETS_MAX_HEADER_SIZE] = payload; + return sendTXT(buf, 1, true); +} + +/** + * send binary data to client + * @param num uint8_t client id + * @param payload uint8_t * + * @param length size_t + * @param headerToPayload bool (see sendFrame for more details) + * @return true if ok + */ +bool WebSocketsClient::sendBIN(uint8_t * payload, size_t length, bool headerToPayload) { + if(clientIsConnected(&_client)) { + return sendFrame(&_client, WSop_binary, payload, length, true, headerToPayload); + } + return false; +} + +bool WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) { + return sendBIN((uint8_t *)payload, length); +} + +/** + * sends a WS ping to Server + * @param payload uint8_t * + * @param length size_t + * @return true if ping is send out + */ +bool WebSocketsClient::sendPing(uint8_t * payload, size_t length) { + if(clientIsConnected(&_client)) { + bool sent = sendFrame(&_client, WSop_ping, payload, length); + if(sent) + _client.lastPing = millis(); + return sent; + } + return false; +} + +bool WebSocketsClient::sendPing(String & payload) { + return sendPing((uint8_t *)payload.c_str(), payload.length()); +} + +/** + * disconnect one client + * @param num uint8_t client id + */ +void WebSocketsClient::disconnect(void) { + if(clientIsConnected(&_client)) { + WebSockets::clientDisconnect(&_client, 1000); + } +} + +/** + * set the Authorizatio for the http request + * @param user const char * + * @param password const char * + */ +void WebSocketsClient::setAuthorization(const char * user, const char * password) { + if(user && password) { + String auth = user; + auth += ":"; + auth += password; + _client.base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length()); + } +} + +/** + * set the Authorizatio for the http request + * @param auth const char * base64 + */ +void WebSocketsClient::setAuthorization(const char * auth) { + if(auth) { + //_client.base64Authorization = auth; + _client.plainAuthorization = auth; + } +} + +/** + * set extra headers for the http request; + * separate headers by "\r\n" + * @param extraHeaders const char * extraHeaders + */ +void WebSocketsClient::setExtraHeaders(const char * extraHeaders) { + _client.extraHeaders = extraHeaders; +} + +/** + * set the reconnect Interval + * how long to wait after a connection initiate failed + * @param time in ms + */ +void WebSocketsClient::setReconnectInterval(unsigned long time) { + _reconnectInterval = time; +} + +bool WebSocketsClient::isConnected(void) { + return (_client.status == WSC_CONNECTED); +} + +// ################################################################################# +// ################################################################################# +// ################################################################################# + +/** + * + * @param client WSclient_t * ptr to the client struct + * @param opcode WSopcode_t + * @param payload uint8_t * + * @param length size_t + */ +void WebSocketsClient::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) { + WStype_t type = WStype_ERROR; + + UNUSED(client); + + switch(opcode) { + case WSop_text: + type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START; + break; + case WSop_binary: + type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START; + break; + case WSop_continuation: + type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT; + break; + case WSop_ping: + type = WStype_PING; + break; + case WSop_pong: + type = WStype_PONG; + break; + case WSop_close: + default: + break; + } + + runCbEvent(type, payload, length); +} + +/** + * Disconnect an client + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::clientDisconnect(WSclient_t * client) { + bool event = false; + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) + if(client->isSSL && client->ssl) { + if(client->ssl->connected()) { + client->ssl->flush(); + client->ssl->stop(); + } + event = true; + delete client->ssl; + client->ssl = NULL; + client->tcp = NULL; + } +#endif + + if(client->tcp) { + if(client->tcp->connected()) { +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + client->tcp->flush(); +#endif + client->tcp->stop(); + } + event = true; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->status = WSC_NOT_CONNECTED; +#else + delete client->tcp; +#endif + client->tcp = NULL; + } + + client->cCode = 0; + client->cKey = ""; + client->cAccept = ""; + client->cVersion = 0; + client->cIsUpgrade = false; + client->cIsWebsocket = false; + client->cSessionId = ""; + + client->status = WSC_NOT_CONNECTED; + _lastConnectionFail = millis(); + + DEBUG_WEBSOCKETS("[WS-Client] client disconnected.\n"); + if(event) { + runCbEvent(WStype_DISCONNECTED, NULL, 0); + } +} + +/** + * get client state + * @param client WSclient_t * ptr to the client struct + * @return true = conneted + */ +bool WebSocketsClient::clientIsConnected(WSclient_t * client) { + if(!client->tcp) { + return false; + } + + if(client->tcp->connected()) { + if(client->status != WSC_NOT_CONNECTED) { + return true; + } + } else { + // client lost + if(client->status != WSC_NOT_CONNECTED) { + DEBUG_WEBSOCKETS("[WS-Client] connection lost.\n"); + // do cleanup + clientDisconnect(client); + } + } + + if(client->tcp) { + // do cleanup + clientDisconnect(client); + } + + return false; +} +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) +/** + * Handel incomming data from Client + */ +void WebSocketsClient::handleClientData(void) { + if((_client.status == WSC_HEADER || _client.status == WSC_BODY) && _lastHeaderSent + WEBSOCKETS_TCP_TIMEOUT < millis()) { + DEBUG_WEBSOCKETS("[WS-Client][handleClientData] header response timeout.. disconnecting!\n"); + clientDisconnect(&_client); + WEBSOCKETS_YIELD(); + return; + } + + int len = _client.tcp->available(); + if(len > 0) { + switch(_client.status) { + case WSC_HEADER: { + String headerLine = _client.tcp->readStringUntil('\n'); + handleHeader(&_client, &headerLine); + } break; + case WSC_BODY: { + char buf[256] = { 0 }; + len = _client.tcp->readBytes(&buf[0], sizeof(buf)); + buf[len] = 0; + String bodyLine = buf; + handleHeader(&_client, &bodyLine); + } break; + case WSC_CONNECTED: + WebSockets::handleWebsocket(&_client); + break; + default: + WebSockets::clientDisconnect(&_client, 1002); + break; + } + } + WEBSOCKETS_YIELD(); +} +#endif + +/** + * send the WebSocket header to Server + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::sendHeader(WSclient_t * client) { + static const char * NEW_LINE = "\r\n"; + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header...\n"); + + uint8_t randomKey[16] = { 0 }; + + for(uint8_t i = 0; i < sizeof(randomKey); i++) { + randomKey[i] = random(0xFF); + } + + client->cKey = base64_encode(&randomKey[0], 16); + +#ifndef NODEBUG_WEBSOCKETS + unsigned long start = micros(); +#endif + + String handshake; + bool ws_header = true; + String url = client->cUrl; + + if(client->isSocketIO) { + if(client->cSessionId.length() == 0) { + url += WEBSOCKETS_STRING("&transport=polling"); + ws_header = false; + } else { + url += WEBSOCKETS_STRING("&transport=websocket&sid="); + url += client->cSessionId; + } + } + + handshake = WEBSOCKETS_STRING("GET "); + handshake += url + WEBSOCKETS_STRING( + " HTTP/1.1\r\n" + "Host: "); + handshake += _host + ":" + std::to_string(_port) + NEW_LINE; + + if(ws_header) { + handshake += WEBSOCKETS_STRING( + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Version: 13\r\n" + "Sec-WebSocket-Key: "); + handshake += client->cKey + NEW_LINE; + + if(client->cProtocol.length() > 0) { + handshake += WEBSOCKETS_STRING("Sec-WebSocket-Protocol: "); + handshake += client->cProtocol + NEW_LINE; + } + + if(client->cExtensions.length() > 0) { + handshake += WEBSOCKETS_STRING("Sec-WebSocket-Extensions: "); + handshake += client->cExtensions + NEW_LINE; + } + } else { + handshake += WEBSOCKETS_STRING("Connection: keep-alive\r\n"); + } + + // add extra headers; by default this includes "Origin: file://" + if(client->extraHeaders.length() > 0) { + handshake += client->extraHeaders + NEW_LINE; + } + + handshake += WEBSOCKETS_STRING("User-Agent: arduino-WebSocket-Client\r\n"); + + if(client->base64Authorization.length() > 0) { + handshake += WEBSOCKETS_STRING("Authorization: Basic "); + handshake += client->base64Authorization + NEW_LINE; + } + + if(client->plainAuthorization.length() > 0) { + handshake += WEBSOCKETS_STRING("Authorization: "); + handshake += client->plainAuthorization + NEW_LINE; + } + + handshake += NEW_LINE; + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] handshake %s", (uint8_t *)handshake.c_str()); + write(client, (uint8_t *)handshake.c_str(), handshake.length()); + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine))); +#endif + + DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header... Done (%luus).\n", (micros() - start)); + _lastHeaderSent = millis(); +} + +/** + * handle the WebSocket header reading + * @param client WSclient_t * ptr to the client struct + */ +void WebSocketsClient::handleHeader(WSclient_t * client, String * headerLine) { + trim(*headerLine); + + // this code handels the http body for Socket.IO V3 requests + if(headerLine->length() > 0 && client->isSocketIO && client->status == WSC_BODY && client->cSessionId.length() == 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] socket.io json: %s\n", headerLine->c_str()); + String sid_begin = WEBSOCKETS_STRING("\"sid\":\""); + if(indexOf(headerLine, sid_begin) > -1) { + int start = indexOf(headerLine, sid_begin) + sid_begin.length(); + int end = indexOf(headerLine, "\"", start); + client->cSessionId = substring(headerLine, start, end); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cSessionId: %s\n", client->cSessionId.c_str()); + + // Trigger websocket connection code path + *headerLine = ""; + } + } + + // headle HTTP header + if(headerLine->length() > 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] RX: %s\n", headerLine->c_str()); + + if(startsWith(headerLine, WEBSOCKETS_STRING("HTTP/1."))) { + // "HTTP/1.1 101 Switching Protocols" + client->cCode = stoi(substring(headerLine, 9, indexOf(headerLine, ' ', 9))); + } else if(indexOf(headerLine, ':') >= 0) { + String headerName = substring(headerLine, 0, indexOf(headerLine, ':')); + String headerValue = substring(headerLine, indexOf(headerLine, ':') + 1); + + // remove space in the beginning (RFC2616) + if(headerValue[0] == ' ') { + remove(headerValue, 0, 1); + } + + if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Connection"))) { + if(equalsIgnoreCase(headerValue, WEBSOCKETS_STRING("upgrade"))) { + client->cIsUpgrade = true; + } + } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Upgrade"))) { + if(equalsIgnoreCase(headerValue, WEBSOCKETS_STRING("websocket"))) { + client->cIsWebsocket = true; + } + } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Sec-WebSocket-Accept"))) { + client->cAccept = headerValue; + trim(client->cAccept); // see rfc6455 + } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Sec-WebSocket-Protocol"))) { + client->cProtocol = headerValue; + } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Sec-WebSocket-Extensions"))) { + client->cExtensions = headerValue; + } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Sec-WebSocket-Version"))) { + client->cVersion = stoi(headerValue); + } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Set-Cookie")) && indexOf(headerValue, " io=") > -1) { + if(indexOf(headerValue, ';') > -1) { + client->cSessionId = substring(headerValue, indexOf(headerValue, '=') + 1, indexOf(headerValue, ";")); + } else { + client->cSessionId = substring(headerValue, indexOf(headerValue, '=') + 1); + } + } + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n", headerLine->c_str()); + } + + (*headerLine) = ""; +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine))); +#endif + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header read fin.\n"); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Client settings:\n"); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cURL: %s\n", client->cUrl.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cKey: %s\n", client->cKey.c_str()); + + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Server header:\n"); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cCode: %d\n", client->cCode); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsUpgrade: %d\n", client->cIsUpgrade); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsWebsocket: %d\n", client->cIsWebsocket); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cAccept: %s\n", client->cAccept.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cProtocol: %s\n", client->cProtocol.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cExtensions: %s\n", client->cExtensions.c_str()); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cVersion: %d\n", client->cVersion); + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cSessionId: %s\n", client->cSessionId.c_str()); + + if(client->isSocketIO && client->cSessionId.length() == 0 && clientIsConnected(client)) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] still missing cSessionId try socket.io V3\n"); + client->status = WSC_BODY; + return; + } else { + client->status = WSC_HEADER; + } + + bool ok = (client->cIsUpgrade && client->cIsWebsocket); + + if(ok) { + switch(client->cCode) { + case 101: ///< Switching Protocols + + break; + case 200: + if(client->isSocketIO) { + break; + } + // falls through + case 403: ///< Forbidden + // todo handle login + // falls through + default: ///< Server dont unterstand requrst + ok = false; + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] serverCode is not 101 (%d)\n", client->cCode); + clientDisconnect(client); + _lastConnectionFail = millis(); + break; + } + } + + if(ok) { + if(client->cAccept.length() == 0) { + ok = false; + } else { + // generate Sec-WebSocket-Accept key for check + String sKey = acceptKey(client->cKey); + if(sKey != client->cAccept) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Sec-WebSocket-Accept is wrong\n"); + ok = false; + } + } + } + + if(ok) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection init done.\n"); + headerDone(client); + + runCbEvent(WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length()); +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + } else if(client->isSocketIO) { + if(client->cSessionId.length() > 0) { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] found cSessionId\n"); + if(clientIsConnected(client) && _client.tcp->available()) { + // read not needed data + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] still data in buffer (%d), clean up.\n", _client.tcp->available()); + while(_client.tcp->available() > 0) { + uint8_t buf; + _client.tcp->read(&buf, 1); + } + } + sendHeader(client); + } +#endif + } else { + DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n"); + _lastConnectionFail = millis(); + if(clientIsConnected(client)) { + write(client, "This is a webSocket client!"); + } + clientDisconnect(client); + } + } +} + +void WebSocketsClient::connectedCb() { + DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u.\n", _host.c_str(), _port); + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + _client.tcp->onDisconnect(std::bind([](WebSocketsClient * c, AsyncTCPbuffer * obj, WSclient_t * client) -> bool { + DEBUG_WEBSOCKETS("[WS-Server][%d] Disconnect client\n", client->num); + client->status = WSC_NOT_CONNECTED; + client->tcp = NULL; + + // reconnect + c->asyncConnect(); + + return true; + }, + this, std::placeholders::_1, &_client)); +#endif + + _client.status = WSC_HEADER; + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + // set Timeout for readBytesUntil and readStringUntil + //_client.tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); +#endif + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) + _client.tcp->setNoDelay(true); +#endif + +#if defined(HAS_SSL) +#if defined(SSL_AXTLS) || defined(ESP32) + if(_client.isSSL && SSL_FINGERPRINT_IS_SET) { + if(!_client.ssl->verify(_fingerprint.c_str(), _host.c_str())) { + DEBUG_WEBSOCKETS("[WS-Client] certificate mismatch\n"); + WebSockets::clientDisconnect(&_client, 1000); + return; + } +#else + if(_client.isSSL && SSL_FINGERPRINT_IS_SET) { +#endif + } else if(_client.isSSL && !_CA_cert) { +#if defined(SSL_BARESSL) + _client.ssl->setInsecure(); +#endif + } +#endif + + // send Header to Server + sendHeader(&_client); +} + +void WebSocketsClient::connectFailedCb() { + DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Failed\n", _host.c_str(), _port); +} + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + +void WebSocketsClient::asyncConnect() { + DEBUG_WEBSOCKETS("[WS-Client] asyncConnect...\n"); + + AsyncClient * tcpclient = new AsyncClient(); + + if(!tcpclient) { + DEBUG_WEBSOCKETS("[WS-Client] creating AsyncClient class failed!\n"); + return; + } + + tcpclient->onDisconnect([](void * obj, AsyncClient * c) { + c->free(); + delete c; + }); + + tcpclient->onConnect(std::bind([](WebSocketsClient * ws, AsyncClient * tcp) { + ws->_client.tcp = new AsyncTCPbuffer(tcp); + if(!ws->_client.tcp) { + DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!\n"); + ws->connectFailedCb(); + return; + } + ws->connectedCb(); + }, + this, std::placeholders::_2)); + + tcpclient->onError(std::bind([](WebSocketsClient * ws, AsyncClient * tcp) { + ws->connectFailedCb(); + + // reconnect + ws->asyncConnect(); + }, + this, std::placeholders::_2)); + + if(!tcpclient->connect(_host.c_str(), _port)) { + connectFailedCb(); + delete tcpclient; + } +} + +#endif + +/** + * send heartbeat ping to server in set intervals + */ +void WebSocketsClient::handleHBPing() { + if(_client.pingInterval == 0) + return; + uint32_t pi = millis() - _client.lastPing; + if(pi > _client.pingInterval) { + DEBUG_WEBSOCKETS("[WS-Client] sending HB ping\n"); + if(sendPing()) { + _client.lastPing = millis(); + _client.pongReceived = false; + } else { + DEBUG_WEBSOCKETS("[WS-Client] sending HB ping failed\n"); + WebSockets::clientDisconnect(&_client, 1000); + } + } +} + +/** + * enable ping/pong heartbeat process + * @param pingInterval uint32_t how often ping will be sent + * @param pongTimeout uint32_t millis after which pong should timout if not received + * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect + */ +void WebSocketsClient::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { + WebSockets::enableHeartbeat(&_client, pingInterval, pongTimeout, disconnectTimeoutCount); +} + +/** + * disable ping/pong heartbeat process + */ +void WebSocketsClient::disableHeartbeat() { + _client.pingInterval = 0; +} diff --git a/otfpi/WebSocketsClient.h b/otfpi/WebSocketsClient.h new file mode 100644 index 000000000..c78942cb1 --- /dev/null +++ b/otfpi/WebSocketsClient.h @@ -0,0 +1,173 @@ +/** + * @file WebSocketsClient.h + * @date 20.05.2015 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * 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 WEBSOCKETSCLIENT_H_ +#define WEBSOCKETSCLIENT_H_ + +#include "WebSockets.h" +#include "wscompat.h" + +class WebSocketsClient : protected WebSockets { + public: +#ifdef __AVR__ + typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length); +#else + typedef std::function WebSocketClientEvent; +#endif + + WebSocketsClient(void); + virtual ~WebSocketsClient(void); + + void begin(const char * host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); + void begin(String host, uint16_t port, String url = "/", String protocol = "arduino"); + //void begin(IPAddress host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); + +#if defined(HAS_SSL) +#ifdef SSL_AXTLS + void beginSSL(const char * host, uint16_t port, const char * url = "/", const char * fingerprint = "", const char * protocol = "arduino"); + void beginSSL(String host, uint16_t port, String url = "/", String fingerprint = "", String protocol = "arduino"); +#else + void beginSSL(const char * host, uint16_t port, const char * url = "/", const uint8_t * fingerprint = NULL, const char * protocol = "arduino"); + void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", BearSSL::X509List * CA_cert = NULL, const char * protocol = "arduino"); + void setSSLClientCertKey(BearSSL::X509List * clientCert = NULL, BearSSL::PrivateKey * clientPrivateKey = NULL); + void setSSLClientCertKey(const char * clientCert = NULL, const char * clientPrivateKey = NULL); +#endif + void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", const char * CA_cert = NULL, const char * protocol = "arduino"); +#endif + +/* + void beginSocketIO(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void beginSocketIO(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); + +#if defined(HAS_SSL) + void beginSocketIOSSL(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); + void beginSocketIOSSL(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); + + void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * CA_cert = NULL, const char * protocol = "arduino"); +#if defined(SSL_BARESSL) + void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", BearSSL::X509List * CA_cert = NULL, const char * protocol = "arduino"); +#endif +#endif +*/ +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void loop(void); +#else + // Async interface not need a loop call + void loop(void) __attribute__((deprecated)) {} +#endif + + void onEvent(WebSocketClientEvent cbEvent); + + bool sendTXT(const char* payload, size_t length, bool fin, bool headerToPayload); + bool sendTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(const uint8_t * payload, size_t length = 0); + bool sendTXT(char * payload, size_t length = 0, bool headerToPayload = false); + bool sendTXT(const char * payload, size_t length = 0); + bool sendTXT(String & payload); + bool sendTXT(char payload); + + bool sendBIN(uint8_t * payload, size_t length, bool headerToPayload = false); + bool sendBIN(const uint8_t * payload, size_t length); + + bool sendPing(uint8_t * payload = NULL, size_t length = 0); + bool sendPing(String & payload); + + void disconnect(void); + + void setAuthorization(const char * user, const char * password); + void setAuthorization(const char * auth); + + void setExtraHeaders(const char * extraHeaders = NULL); + + void setReconnectInterval(unsigned long time); + + void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); + void disableHeartbeat(); + + bool isConnected(void); + + protected: + String _host; + uint16_t _port; + bool firstFrag = false; + +#if defined(HAS_SSL) +#ifdef SSL_AXTLS + String _fingerprint; + const char * _CA_cert; +#define SSL_FINGERPRINT_IS_SET (_fingerprint.length()) +#define SSL_FINGERPRINT_NULL "" +#else + const uint8_t * _fingerprint; + BearSSL::X509List * _CA_cert; + BearSSL::X509List * _client_cert; + BearSSL::PrivateKey * _client_key; +#define SSL_FINGERPRINT_IS_SET (_fingerprint != NULL) +#define SSL_FINGERPRINT_NULL NULL +#endif + +#endif + WSclient_t _client; + + WebSocketClientEvent _cbEvent; + + unsigned long _lastConnectionFail; + unsigned long _reconnectInterval; + unsigned long _lastHeaderSent; + + void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin); + + void clientDisconnect(WSclient_t * client); + bool clientIsConnected(WSclient_t * client); + +#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) + void handleClientData(void); +#endif + + void sendHeader(WSclient_t * client); + void handleHeader(WSclient_t * client, String * headerLine); + + void connectedCb(); + void connectFailedCb(); + + void handleHBPing(); // send ping in specified intervals + +#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) + void asyncConnect(); +#endif + + /** + * called for sending a Event to the app + * @param type WStype_t + * @param payload uint8_t * + * @param length size_t + */ + virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) { + if(_cbEvent) { + _cbEvent(type, payload, length); + } + } +}; + +#endif /* WEBSOCKETSCLIENT_H_ */ diff --git a/otfpi/WebSocketsVersion.h b/otfpi/WebSocketsVersion.h new file mode 100644 index 000000000..89dac9607 --- /dev/null +++ b/otfpi/WebSocketsVersion.h @@ -0,0 +1,36 @@ +/** + * @file WebSocketsVersion.h + * @date 01.05.2023 + * @author Markus Sattler + * + * Copyright (c) 2015 Markus Sattler. All rights reserved. + * This file is part of the WebSockets for Arduino. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * 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 WEBSOCKETSVERSION_H_ +#define WEBSOCKETSVERSION_H_ + +#define WEBSOCKETS_VERSION "2.4.1" + +#define WEBSOCKETS_VERSION_MAJOR 2 +#define WEBSOCKETS_VERSION_MINOR 4 +#define WEBSOCKETS_VERSION_PATCH 1 + +#define WEBSOCKETS_VERSION_INT 2004001 + +#endif /* WEBSOCKETSVERSION_H_ */ diff --git a/otfpi/libb64/AUTHORS b/otfpi/libb64/AUTHORS new file mode 100644 index 000000000..af6873756 --- /dev/null +++ b/otfpi/libb64/AUTHORS @@ -0,0 +1,7 @@ +libb64: Base64 Encoding/Decoding Routines +====================================== + +Authors: +------- + +Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com diff --git a/otfpi/libb64/LICENSE b/otfpi/libb64/LICENSE new file mode 100644 index 000000000..a6b56069e --- /dev/null +++ b/otfpi/libb64/LICENSE @@ -0,0 +1,29 @@ +Copyright-Only Dedication (based on United States law) +or Public Domain Certification + +The person or persons who have associated work with this document (the +"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of +his knowledge, the work of authorship identified is in the public domain of the +country from which the work is published, or (b) hereby dedicates whatever +copyright the dedicators holds in the work of authorship identified below (the +"Work") to the public domain. A certifier, moreover, dedicates any copyright +interest he may have in the associated work, and for these purposes, is +described as a "dedicator" below. + +A certifier has taken reasonable steps to verify the copyright status of this +work. Certifier recognizes that his good faith efforts may not shield him from +liability if in fact the work certified is not in the public domain. + +Dedicator makes this dedication for the benefit of the public at large and to +the detriment of the Dedicator's heirs and successors. Dedicator intends this +dedication to be an overt act of relinquishment in perpetuity of all present +and future rights under copyright law, whether vested or contingent, in the +Work. Dedicator understands that such relinquishment of all rights includes +the relinquishment of all rights to enforce (by lawsuit or otherwise) those +copyrights in the Work. + +Dedicator recognizes that, once placed in the public domain, the Work may be +freely reproduced, distributed, transmitted, used, modified, built upon, or +otherwise exploited by anyone for any purpose, commercial or non-commercial, +and in any way, including by methods that have not yet been invented or +conceived. \ No newline at end of file diff --git a/otfpi/libb64/cdecode.c b/otfpi/libb64/cdecode.c new file mode 100644 index 000000000..615068ac2 --- /dev/null +++ b/otfpi/libb64/cdecode.c @@ -0,0 +1,98 @@ +/* +cdecoder.c - c source to a base64 decoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifdef ESP8266 +#include +#endif + +#if defined(ESP32) || defined(ARDUINO_ARCH_RP2040) +#define CORE_HAS_LIBB64 +#endif + +#ifndef CORE_HAS_LIBB64 +#include "cdecode_inc.h" + +int base64_decode_value(char value_in) +{ + static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + static const char decoding_size = sizeof(decoding); + value_in -= 43; + if (value_in < 0 || value_in > decoding_size) return -1; + return decoding[(int)value_in]; +} + +void base64_init_decodestate(base64_decodestate* state_in) +{ + state_in->step = step_a; + state_in->plainchar = 0; +} + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) +{ + const char* codechar = code_in; + char* plainchar = plaintext_out; + char fragment; + + *plainchar = state_in->plainchar; + + switch (state_in->step) + { + while (1) + { + case step_a: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_a; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar = (fragment & 0x03f) << 2; + case step_b: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_b; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x030) >> 4; + *plainchar = (fragment & 0x00f) << 4; + case step_c: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_c; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03c) >> 2; + *plainchar = (fragment & 0x003) << 6; + case step_d: + do { + if (codechar == code_in+length_in) + { + state_in->step = step_d; + state_in->plainchar = *plainchar; + return plainchar - plaintext_out; + } + fragment = (char)base64_decode_value(*codechar++); + } while (fragment < 0); + *plainchar++ |= (fragment & 0x03f); + } + } + /* control should not reach here */ + return plainchar - plaintext_out; +} + +#endif diff --git a/otfpi/libb64/cdecode_inc.h b/otfpi/libb64/cdecode_inc.h new file mode 100644 index 000000000..d0d7f489d --- /dev/null +++ b/otfpi/libb64/cdecode_inc.h @@ -0,0 +1,28 @@ +/* +cdecode.h - c header for a base64 decoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CDECODE_H +#define BASE64_CDECODE_H + +typedef enum +{ + step_a, step_b, step_c, step_d +} base64_decodestep; + +typedef struct +{ + base64_decodestep step; + char plainchar; +} base64_decodestate; + +void base64_init_decodestate(base64_decodestate* state_in); + +int base64_decode_value(char value_in); + +int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); + +#endif /* BASE64_CDECODE_H */ diff --git a/otfpi/libb64/cencode.c b/otfpi/libb64/cencode.c new file mode 100644 index 000000000..cdc0f67bc --- /dev/null +++ b/otfpi/libb64/cencode.c @@ -0,0 +1,119 @@ +/* +cencoder.c - c source to a base64 encoding algorithm implementation + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifdef ESP8266 +#include +#endif + +#if defined(ESP32) || defined(ARDUINO_ARCH_RP2040) +#define CORE_HAS_LIBB64 +#endif + +#ifndef CORE_HAS_LIBB64 +#include "cencode_inc.h" + +const int CHARS_PER_LINE = 72; + +void base64_init_encodestate(base64_encodestate* state_in) +{ + state_in->step = step_A; + state_in->result = 0; + state_in->stepcount = 0; +} + +char base64_encode_value(char value_in) +{ + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + if (value_in > 63) return '='; + return encoding[(int)value_in]; +} + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) +{ + const char* plainchar = plaintext_in; + const char* const plaintextend = plaintext_in + length_in; + char* codechar = code_out; + char result; + char fragment; + + result = state_in->result; + + switch (state_in->step) + { + while (1) + { + case step_A: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_A; + return codechar - code_out; + } + fragment = *plainchar++; + result = (fragment & 0x0fc) >> 2; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x003) << 4; + case step_B: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_B; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0f0) >> 4; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x00f) << 2; + case step_C: + if (plainchar == plaintextend) + { + state_in->result = result; + state_in->step = step_C; + return codechar - code_out; + } + fragment = *plainchar++; + result |= (fragment & 0x0c0) >> 6; + *codechar++ = base64_encode_value(result); + result = (fragment & 0x03f) >> 0; + *codechar++ = base64_encode_value(result); + + ++(state_in->stepcount); + if (state_in->stepcount == CHARS_PER_LINE/4) + { + *codechar++ = '\n'; + state_in->stepcount = 0; + } + } + } + /* control should not reach here */ + return codechar - code_out; +} + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in) +{ + char* codechar = code_out; + + switch (state_in->step) + { + case step_B: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + *codechar++ = '='; + break; + case step_C: + *codechar++ = base64_encode_value(state_in->result); + *codechar++ = '='; + break; + case step_A: + break; + } + *codechar++ = 0x00; + + return codechar - code_out; +} + +#endif diff --git a/otfpi/libb64/cencode_inc.h b/otfpi/libb64/cencode_inc.h new file mode 100644 index 000000000..c1e3464af --- /dev/null +++ b/otfpi/libb64/cencode_inc.h @@ -0,0 +1,31 @@ +/* +cencode.h - c header for a base64 encoding algorithm + +This is part of the libb64 project, and has been placed in the public domain. +For details, see http://sourceforge.net/projects/libb64 +*/ + +#ifndef BASE64_CENCODE_H +#define BASE64_CENCODE_H + +typedef enum +{ + step_A, step_B, step_C +} base64_encodestep; + +typedef struct +{ + base64_encodestep step; + char result; + int stepcount; +} base64_encodestate; + +void base64_init_encodestate(base64_encodestate* state_in); + +char base64_encode_value(char value_in); + +int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); + +int base64_encode_blockend(char* code_out, base64_encodestate* state_in); + +#endif /* BASE64_CENCODE_H */ diff --git a/otfpi/libsha1/libsha1.c b/otfpi/libsha1/libsha1.c new file mode 100644 index 000000000..fcf01c531 --- /dev/null +++ b/otfpi/libsha1/libsha1.c @@ -0,0 +1,202 @@ +/* from valgrind tests */ + +/* ================ sha1.c ================ */ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#if !defined(ESP8266) && !defined(ESP32) + +#define SHA1HANDSOFF + +#include +#include +#include + +#include "libsha1.h" + + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) +{ + uint32_t a, b, c, d, e; + typedef union { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16* block = (const CHAR64LONG16*)buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); + R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); + R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); + R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init(SHA1_CTX* context) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len) +{ + uint32_t i, j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len>>29); + j = (j >> 3) & 63; + if ((j + len) > 63) { + memcpy(&context->buffer[j], data, (i = 64-j)); + SHA1Transform(context->state, context->buffer); + for ( ; i + 63 < len; i += 64) { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final(unsigned char digest[20], SHA1_CTX* context) +{ + unsigned i; + unsigned char finalcount[8]; + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t; + } +#else + for (i = 0; i < 8; i++) { + finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) { + digest[i] = (unsigned char) + ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} +/* ================ end of sha1.c ================ */ + + +#endif diff --git a/otfpi/libsha1/libsha1.h b/otfpi/libsha1/libsha1.h new file mode 100644 index 000000000..40afa61c0 --- /dev/null +++ b/otfpi/libsha1/libsha1.h @@ -0,0 +1,21 @@ +/* ================ sha1.h ================ */ +/* +SHA-1 in C +By Steve Reid +100% Public Domain +*/ + +#if !defined(ESP8266) && !defined(ESP32) + +typedef struct { + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); +void SHA1Init(SHA1_CTX* context); +void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); +void SHA1Final(unsigned char digest[20], SHA1_CTX* context); + +#endif diff --git a/otfpi/wscompat.cpp b/otfpi/wscompat.cpp new file mode 100644 index 000000000..4a6ba3209 --- /dev/null +++ b/otfpi/wscompat.cpp @@ -0,0 +1,116 @@ +#include "wscompat.h" +#include + +std::string& rtrim(std::string& s, const char* t) { + s.erase(s.find_last_not_of(t)+1); + return s; +} + +std::string* rtrim(std::string *s, const char* t) { + s->erase(s->find_last_not_of(t)+1); + return s; +} + +// trim from beginning of string (left) +std::string& ltrim(std::string& s, const char* t) { + s.erase(0, s.find_first_not_of(t)); + return s; +} + +std::string* ltrim(std::string *s, const char* t) { + s->erase(0, s->find_first_not_of(t)); + return s; +} + +// trim from both ends of string (right then left) +std::string& trim(std::string& s, const char* t) { + return ltrim(rtrim(s, t), t); +} + +std::string* trim(std::string *s, const char* t) { + return ltrim(rtrim(s, t), t); +} + +std::string& remove(std::string& s, int index, int count) { + s.erase(index, count); + return s; +} + +std::string* remove(std::string *s, int index, int count) { + s->erase(index, count); + return s; +} + +int indexOf(const std::string& s, const std::string& item, int from) { + return s.find(item, from); +} +int indexOf(const std::string *s, const string& item, int from) { + return s->find(item, from); +} +int indexOf(const std::string& s, char item, int from) { + return s.find(item, from); +} +int indexOf(const std::string *s, char item, int from) { + return s->find(item, from); +} + +std::string substring(const std::string& s, int start) { + return s.substr(start); +} + +std::string substring(const std::string *s, int start) { + return s->substr(start); +} + +std::string substring(const std::string& s, int start, int end) { + return s.substr(start, end-start); +} + +std::string substring(const std::string *s, int start, int end) { + return s->substr(start, end-start); +} + +uint random(uint max) { + return random() % max; +} + +bool startsWith(const std::string& s, const std::string& test, int offset) { + int len = s.length(); + int len2 = test.length(); + if (len < len2) return 0; + if (offset > len - len2) return 0; + return strncmp( s.c_str() + offset, test.c_str(), len2 ) == 0; +} + +bool startsWith(const std::string *s, const std::string& test, int offset) { + int len = s->length(); + int len2 = test.length(); + if (len < len2) return 0; + if (offset > len - len2) return 0; + return strncmp( s->c_str() + offset, test.c_str(), len2 ) == 0; +} + +bool equalsIgnoreCase(const std::string& s1, const std::string& s2) { + if (&s1 == &s2) return 1; + if (s1.length() != s2.length()) return 0; + if (s1.length() == 0) return 1; + const char *p1 = s1.c_str(); + const char *p2 = s2.c_str(); + while (*p1) { + if (tolower(*p1++) != tolower(*p2++)) return 0; + } + return 1; +} + +bool equalsIgnoreCase(const std::string *s1, const std::string *s2) { + if (s1 == s2) return 1; + if (s1->length() != s2->length()) return 0; + if (s1->length() == 0) return 1; + const char *p1 = s1->c_str(); + const char *p2 = s2->c_str(); + while (*p1) { + if (tolower(*p1++) != tolower(*p2++)) return 0; + } + return 1; + +} \ No newline at end of file diff --git a/otfpi/wscompat.h b/otfpi/wscompat.h new file mode 100644 index 000000000..a6c049318 --- /dev/null +++ b/otfpi/wscompat.h @@ -0,0 +1,44 @@ +#ifndef _WS_COMPAT_H_ +#define _WS_COMPAT_H_ + +#include + +/** + * Websocket Compatibility helper functions +*/ + +#define WSTRIM_DEFAULT " \t\n\r\f\v" + +std::string& rtrim(std::string& s, const char* t = WSTRIM_DEFAULT); +std::string* rtrim(std::string *s, const char* t = WSTRIM_DEFAULT); + +// trim from beginning of string (left) +std::string& ltrim(std::string& s, const char* t = WSTRIM_DEFAULT); +std::string* ltrim(std::string *s, const char* t = WSTRIM_DEFAULT); + +// trim from both ends of string (right then left) +std::string& trim(std::string& s, const char* t = WSTRIM_DEFAULT); +std::string* trim(std::string *s, const char* t = WSTRIM_DEFAULT); + +std::string& remove(std::string& s, int index, int count); +std::string* remove(std::string *s, int index, int count); + +int indexOf(const std::string& s, const string& item, int from = 0); +int indexOf(const std::string *s, const string& item, int from = 0); +int indexOf(const std::string& s, const char item, int from = 0); +int indexOf(const std::string *s, const char item, int from = 0); + +std::string substring(const std::string& s, int start); +std::string substring(const std::string *s, int start); +std::string substring(const std::string& s, int start, int end); +std::string substring(const std::string *s, int start, int end); + +uint random(uint max); + +bool startsWith(const std::string& s, const std::string& test, int offset = 0); +bool startsWith(const std::string *s, const std::string& test, int offset = 0); + +bool equalsIgnoreCase(const std::string& s1, const std::string& s2); +bool equalsIgnoreCase(const std::string *s1, const std::string *s2); + +#endif \ No newline at end of file From 05a16df47a12c0a9597f78bb3a9780b1087f26e9 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 9 May 2024 22:49:39 +0200 Subject: [PATCH 144/281] MQTT Host extended to 100 chars, Username 50 chars, Password 100 chars --- mqtt.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mqtt.cpp b/mqtt.cpp index 26c3dd532..cf07c7e28 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -76,9 +76,9 @@ static unsigned long last_reconnect_attempt; #define MQTT_KEEPALIVE 60 #define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config -#define MQTT_MAX_HOST_LEN 50 // Note: App is set to max 50 chars for broker name -#define MQTT_MAX_USERNAME_LEN 32 // Note: App is set to max 32 chars for username -#define MQTT_MAX_PASSWORD_LEN 32 // Note: App is set to max 32 chars for password +#define MQTT_MAX_HOST_LEN 100 // Note: App is set to max 100 chars for broker name +#define MQTT_MAX_USERNAME_LEN 50 // Note: App is set to max 50 chars for username +#define MQTT_MAX_PASSWORD_LEN 100 // Note: App is set to max 100 chars for password #define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit #define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts From e5be085919f7551dd7bc8ac99c52b65ac4a9cd25 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 9 May 2024 23:55:41 +0200 Subject: [PATCH 145/281] Merged OTFPi Framework changes for OTC-OSPi --- defines.h | 4 ++-- mqtt.cpp | 8 ++++---- opensprinkler_server.cpp | 4 ++++ otfpi/LinkedMap.h | 2 ++ otfpi/LinuxLocalServer.cpp | 2 ++ otfpi/LinuxLocalServer.h | 2 ++ otfpi/LocalServer.h | 2 ++ otfpi/OpenThingsFramework.cpp | 2 ++ otfpi/OpenThingsFramework.h | 2 ++ otfpi/Request.cpp | 2 ++ otfpi/Request.h | 2 ++ otfpi/Response.cpp | 2 ++ otfpi/Response.h | 2 ++ otfpi/StringBuilder.cpp | 2 ++ otfpi/StringBuilder.h | 2 ++ otfpi/WebSockets.cpp | 2 ++ otfpi/WebSockets.h | 2 ++ otfpi/WebSocketsClient.cpp | 3 +++ otfpi/WebSocketsClient.h | 2 ++ otfpi/WebSocketsVersion.h | 2 ++ otfpi/wscompat.cpp | 4 +++- otfpi/wscompat.h | 4 +++- sensors.cpp | 7 +++---- 23 files changed, 54 insertions(+), 12 deletions(-) diff --git a/defines.h b/defines.h index 81c509eca..92bb5af40 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug -#define SERIAL_DEBUG +//#define ENABLE_DEBUG // enable serial debug +//#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; diff --git a/mqtt.cpp b/mqtt.cpp index 7cab0246f..c25399857 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -74,9 +74,9 @@ static unsigned long last_reconnect_attempt; #define MQTT_KEEPALIVE 60 #define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config -#define MQTT_MAX_HOST_LEN 100 // Note: App is set to max 50 chars for broker name -#define MQTT_MAX_USERNAME_LEN 50 // Note: App is set to max 32 chars for username -#define MQTT_MAX_PASSWORD_LEN 100 // Note: App is set to max 32 chars for password +#define MQTT_MAX_HOST_LEN 100 // Note: App is set to max 100 chars for broker name +#define MQTT_MAX_USERNAME_LEN 50 // Note: App is set to max 50 chars for username +#define MQTT_MAX_PASSWORD_LEN 100 // Note: App is set to max 100 chars for password #define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit #define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts @@ -178,7 +178,7 @@ void OSMqtt::begin( const char * host, int port, const char * username, const ch strncpy(_username, username, MQTT_MAX_USERNAME_LEN); _username[MQTT_MAX_USERNAME_LEN] = 0; strncpy(_password, password, MQTT_MAX_PASSWORD_LEN); - _username[MQTT_MAX_PASSWORD_LEN] = 0; + _password[MQTT_MAX_PASSWORD_LEN] = 0; _enabled = enabled; if (mqtt_client == NULL || os.status.network_fails > 0) return; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 503cd8356..74dd6e919 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -328,7 +328,11 @@ String toHMS(ulong t) { void otf_send_result(OTF_PARAMS_DEF, byte code, const char *item = NULL) { String json = F("{\"result\":"); + #if defined(OSPI) json += std::to_string(code); + #else + json += code; + #endif if (!item) item = ""; json += F(",\"item\":\""); json += item; diff --git a/otfpi/LinkedMap.h b/otfpi/LinkedMap.h index 9dd862c4b..5cb293799 100644 --- a/otfpi/LinkedMap.h +++ b/otfpi/LinkedMap.h @@ -1,3 +1,4 @@ +#if defined(OSPI) #ifndef OTF_LINKEDMAP_H #define OTF_LINKEDMAP_H @@ -97,3 +98,4 @@ namespace OTF { }// namespace OTF #endif +#endif \ No newline at end of file diff --git a/otfpi/LinuxLocalServer.cpp b/otfpi/LinuxLocalServer.cpp index 77df5138e..7160d6575 100644 --- a/otfpi/LinuxLocalServer.cpp +++ b/otfpi/LinuxLocalServer.cpp @@ -1,3 +1,4 @@ +#if defined(OSPI) #include "LinuxLocalServer.h" using namespace OTF; @@ -67,3 +68,4 @@ void LinuxLocalClient::flush() { void LinuxLocalClient::stop() { client.stop(); } +#endif \ No newline at end of file diff --git a/otfpi/LinuxLocalServer.h b/otfpi/LinuxLocalServer.h index e630a9131..f744e469b 100644 --- a/otfpi/LinuxLocalServer.h +++ b/otfpi/LinuxLocalServer.h @@ -1,3 +1,4 @@ +#if defined(OSPI) #ifndef OTF_LINUXLOCALSERVER_H #define OTF_LINUXLOCALSERVER_H @@ -39,3 +40,4 @@ namespace OTF { }// namespace OTF #endif +#endif \ No newline at end of file diff --git a/otfpi/LocalServer.h b/otfpi/LocalServer.h index aa0621187..f88160969 100644 --- a/otfpi/LocalServer.h +++ b/otfpi/LocalServer.h @@ -1,3 +1,4 @@ +#if defined(OSPI) #ifndef OTF_LOCALSERVER_H #define OTF_LOCALSERVER_H @@ -51,3 +52,4 @@ namespace OTF { }// namespace OTF #endif +#endif \ No newline at end of file diff --git a/otfpi/OpenThingsFramework.cpp b/otfpi/OpenThingsFramework.cpp index 08d4585f6..f7f98ed9e 100644 --- a/otfpi/OpenThingsFramework.cpp +++ b/otfpi/OpenThingsFramework.cpp @@ -1,3 +1,4 @@ +#if defined(OSPI) #include "OpenThingsFramework.h" #include "StringBuilder.h" @@ -304,3 +305,4 @@ CLOUD_STATUS OpenThingsFramework::getCloudStatus() { unsigned long OpenThingsFramework::getTimeSinceLastCloudStatusChange() { return millis() - lastCloudStatusChangeTime; } +#endif \ No newline at end of file diff --git a/otfpi/OpenThingsFramework.h b/otfpi/OpenThingsFramework.h index 5b2861a97..e615cccda 100644 --- a/otfpi/OpenThingsFramework.h +++ b/otfpi/OpenThingsFramework.h @@ -1,3 +1,4 @@ +#if defined(OSPI) #ifndef OTF_OPENTHINGSFRAMEWORK_H #define OTF_OPENTHINGSFRAMEWORK_H @@ -99,3 +100,4 @@ namespace OTF { }// namespace OTF #endif +#endif \ No newline at end of file diff --git a/otfpi/Request.cpp b/otfpi/Request.cpp index f93a179cf..92d188944 100644 --- a/otfpi/Request.cpp +++ b/otfpi/Request.cpp @@ -1,3 +1,4 @@ +#if defined(OSPI) #include "Request.h" #include #include @@ -300,3 +301,4 @@ size_t Request::getBodyLength() const { return bodyLength; } RequestType Request::getType() const { return requestType; } bool Request::isCloudRequest() const { return cloudRequest; } +#endif \ No newline at end of file diff --git a/otfpi/Request.h b/otfpi/Request.h index dd1ece649..c05d66deb 100644 --- a/otfpi/Request.h +++ b/otfpi/Request.h @@ -1,3 +1,4 @@ +#if defined(OSPI) #ifndef OTF_REQUEST_H #define OTF_REQUEST_H @@ -114,3 +115,4 @@ namespace OTF { }; }// namespace OTF #endif +#endif \ No newline at end of file diff --git a/otfpi/Response.cpp b/otfpi/Response.cpp index 7de2adbc1..b4aa37120 100644 --- a/otfpi/Response.cpp +++ b/otfpi/Response.cpp @@ -1,3 +1,4 @@ +#if defined(OSPI) #include "Response.h" using namespace OTF; @@ -72,3 +73,4 @@ void Response::flush() { reset(); } +#endif \ No newline at end of file diff --git a/otfpi/Response.h b/otfpi/Response.h index 84461fbc5..6a73cd8bb 100644 --- a/otfpi/Response.h +++ b/otfpi/Response.h @@ -1,3 +1,4 @@ +#if defined(OSPI) #ifndef OTF_RESPONSE_H #define OTF_RESPONSE_H @@ -60,3 +61,4 @@ namespace OTF { }; }// namespace OTF #endif +#endif \ No newline at end of file diff --git a/otfpi/StringBuilder.cpp b/otfpi/StringBuilder.cpp index 997d7bf37..4f2b7e81e 100644 --- a/otfpi/StringBuilder.cpp +++ b/otfpi/StringBuilder.cpp @@ -1,3 +1,4 @@ +#if defined(OSPI) #include "StringBuilder.h" #include #include @@ -72,3 +73,4 @@ void StringBuilder::append(const char *txt) { append(txt, strlen(txt)); } +#endif \ No newline at end of file diff --git a/otfpi/StringBuilder.h b/otfpi/StringBuilder.h index 090ec6244..2b110e0f4 100644 --- a/otfpi/StringBuilder.h +++ b/otfpi/StringBuilder.h @@ -1,3 +1,4 @@ +#if defined(OSPI) #ifndef OTF_STRINGBUILDER_H #define OTF_STRINGBUILDER_H @@ -60,3 +61,4 @@ namespace OTF { }// namespace OTF #endif +#endif \ No newline at end of file diff --git a/otfpi/WebSockets.cpp b/otfpi/WebSockets.cpp index 0b3c79b3c..f45790a0a 100644 --- a/otfpi/WebSockets.cpp +++ b/otfpi/WebSockets.cpp @@ -1,3 +1,4 @@ +#if defined(OSPI) /** * @file WebSockets.cpp * @date 20.05.2015 @@ -727,3 +728,4 @@ void WebSockets::handleHBTimeout(WSclient_t * client) { } } } +#endif diff --git a/otfpi/WebSockets.h b/otfpi/WebSockets.h index aaef74512..1951a6619 100644 --- a/otfpi/WebSockets.h +++ b/otfpi/WebSockets.h @@ -1,3 +1,4 @@ +#if defined(OSPI) /** * @file WebSockets.h * @date 20.05.2015 @@ -229,3 +230,4 @@ class WebSockets { #define UNUSED(var) (void)(var) #endif #endif /* WEBSOCKETS_H_ */ +#endif \ No newline at end of file diff --git a/otfpi/WebSocketsClient.cpp b/otfpi/WebSocketsClient.cpp index ce015904d..b867dbd45 100644 --- a/otfpi/WebSocketsClient.cpp +++ b/otfpi/WebSocketsClient.cpp @@ -1,3 +1,4 @@ +#if defined(OSPI) /** * @file WebSocketsClient.cpp * @date 20.05.2015 @@ -999,3 +1000,5 @@ void WebSocketsClient::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeo void WebSocketsClient::disableHeartbeat() { _client.pingInterval = 0; } + +#endif \ No newline at end of file diff --git a/otfpi/WebSocketsClient.h b/otfpi/WebSocketsClient.h index c78942cb1..7b48e8780 100644 --- a/otfpi/WebSocketsClient.h +++ b/otfpi/WebSocketsClient.h @@ -1,3 +1,4 @@ +#if defined(OSPI) /** * @file WebSocketsClient.h * @date 20.05.2015 @@ -171,3 +172,4 @@ class WebSocketsClient : protected WebSockets { }; #endif /* WEBSOCKETSCLIENT_H_ */ +#endif \ No newline at end of file diff --git a/otfpi/WebSocketsVersion.h b/otfpi/WebSocketsVersion.h index 89dac9607..46ff46495 100644 --- a/otfpi/WebSocketsVersion.h +++ b/otfpi/WebSocketsVersion.h @@ -1,3 +1,4 @@ +#if defined(OSPI) /** * @file WebSocketsVersion.h * @date 01.05.2023 @@ -34,3 +35,4 @@ #define WEBSOCKETS_VERSION_INT 2004001 #endif /* WEBSOCKETSVERSION_H_ */ +#endif \ No newline at end of file diff --git a/otfpi/wscompat.cpp b/otfpi/wscompat.cpp index 4a6ba3209..c10d31ad0 100644 --- a/otfpi/wscompat.cpp +++ b/otfpi/wscompat.cpp @@ -1,3 +1,4 @@ +#if defined(OSPI) #include "wscompat.h" #include @@ -113,4 +114,5 @@ bool equalsIgnoreCase(const std::string *s1, const std::string *s2) { } return 1; -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/otfpi/wscompat.h b/otfpi/wscompat.h index a6c049318..76b3f0626 100644 --- a/otfpi/wscompat.h +++ b/otfpi/wscompat.h @@ -1,3 +1,4 @@ +#if defined(OSPI) #ifndef _WS_COMPAT_H_ #define _WS_COMPAT_H_ @@ -41,4 +42,5 @@ bool startsWith(const std::string *s, const std::string& test, int offset = 0); bool equalsIgnoreCase(const std::string& s1, const std::string& s2); bool equalsIgnoreCase(const std::string *s1, const std::string *s2); -#endif \ No newline at end of file +#endif +#endif diff --git a/sensors.cpp b/sensors.cpp index c0254a4a8..7c0d8ab74 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -274,18 +274,17 @@ void sensor_save() { DEBUG_PRINTLN(F("sensor_save")); if (file_exists(SENSOR_FILENAME_BAK)) remove_file(SENSOR_FILENAME_BAK); + if (file_exists(SENSOR_FILENAME)) + rename_file(SENSOR_FILENAME, SENSOR_FILENAME_BAK); ulong pos = 0; Sensor_t *sensor = sensors; while (sensor) { - file_write_block(SENSOR_FILENAME_BAK, sensor, pos, SENSOR_STORE_SIZE); + file_write_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); sensor = sensor->next; pos += SENSOR_STORE_SIZE; } - if (file_exists(SENSOR_FILENAME)) - remove_file(SENSOR_FILENAME); - rename_file(SENSOR_FILENAME_BAK, SENSOR_FILENAME); last_save_time = os.now_tz(); DEBUG_PRINTLN(F("sensor_save2")); } From 14bf2456f19a1ad2e8317de4913fe4cef9fb5e07 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 10 May 2024 10:51:10 +0200 Subject: [PATCH 146/281] Fix Compile Error OSPi (DEBUG_PRINTF on deactivated debug) --- defines.h | 1 + 1 file changed, 1 insertion(+) diff --git a/defines.h b/defines.h index 92bb5af40..9dc344763 100644 --- a/defines.h +++ b/defines.h @@ -478,6 +478,7 @@ enum { #define DEBUG_BEGIN(x) {} #define DEBUG_PRINT(x) {} #define DEBUG_PRINTLN(x) {} + #define DEBUG_PRINTF(x) {} #endif From 794227924e5c157dd0329428a1910bdf7be6cecd Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 10 May 2024 10:53:38 +0200 Subject: [PATCH 147/281] Another compile Error fix OSPi --- defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defines.h b/defines.h index 9dc344763..e63e71ffe 100644 --- a/defines.h +++ b/defines.h @@ -478,7 +478,7 @@ enum { #define DEBUG_BEGIN(x) {} #define DEBUG_PRINT(x) {} #define DEBUG_PRINTLN(x) {} - #define DEBUG_PRINTF(x) {} + #define DEBUG_PRINTF(x, ...) {} #endif From 8fca11a7e23635ebd5bcc7feb807d8ffeb15f415 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 10 May 2024 12:04:40 +0200 Subject: [PATCH 148/281] Fixt compile warning (mqtt.cpp / OSPi) --- mqtt.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/mqtt.cpp b/mqtt.cpp index c25399857..fa4bdba46 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -60,7 +60,6 @@ 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;} #else - #define DEBUG_PRINTF(msg, ...) {} #define DEBUG_LOGF(msg, ...) {} #define DEBUG_DURATION() {} #endif From c1d66127d15594f1a5f9c29a75e55ed1373bd1fb Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 12 May 2024 01:07:03 +0200 Subject: [PATCH 149/281] Fixt Buffer overflow --- defines.h | 2 +- opensprinkler_server.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/defines.h b/defines.h index e63e71ffe..ea312b1c5 100644 --- a/defines.h +++ b/defines.h @@ -37,7 +37,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 160 // Firmware minor version +#define OS_FW_MINOR 161 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 74dd6e919..f8f01f821 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -243,11 +243,11 @@ void rewind_ether_buffer() { #if defined(OTF_ENABLED) boolean buffer_available() { - return bfill.position() < RESPONSE_BUFFER_SIZE/2; + return bfill.position() < RESPONSE_BUFFER_SIZE/2 && available_ether_buffer() > 0; } #else boolean buffer_available() { - return available_ether_buffer() <=0; + return available_ether_buffer() > 0; } #endif From 927fb917393f74b07ddf4d283d6913cbdef1c825 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 13 May 2024 12:45:59 +0200 Subject: [PATCH 150/281] Added ADS1115 check --- sensors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 7c0d8ab74..a8bedced0 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -840,8 +840,8 @@ int read_sensor_adc(Sensor_t *sensor, ulong time) { int id = sensor->id % 4; ADS1115 adc(port); - adc.begin(); - adc.reset(); + if (!adc.begin()) + return HTTP_RQT_NOT_RECEIVED; sensor->repeat_native += adc.readADC(id); if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && time < sensor->last_read + sensor->read_interval) return HTTP_RQT_NOT_RECEIVED; From 5fd0387d9f5fa47a40d04fd15202feb437c9848d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 15 May 2024 00:39:17 +0200 Subject: [PATCH 151/281] MQTT password length 100 char --- OpenSprinkler.cpp | 3 ++- defines.h | 7 ++++--- mqtt.cpp | 9 ++++----- opensprinkler_server.cpp | 13 +++++++++---- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 0480d2ea6..877fd213c 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -400,6 +400,7 @@ const char *OpenSprinkler::sopts[] = { DEFAULT_EMPTY_STRING, // SOPT_OTC_OPTS DEFAULT_DEVICE_NAME, DEFAULT_EMPTY_STRING, // SOPT_STA_BSSID_CHL + DEFAULT_EMPTY_STRING, // SOPT_MQTT_OPTS2 }; /** Weekday strings (stored in PROGMEM to reduce RAM usage) */ @@ -443,7 +444,7 @@ bool OpenSprinkler::load_hardware_mac(byte* buffer, bool wired) { 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.res.writebody(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(byte ret=0;ret<6;ret++) { diff --git a/defines.h b/defines.h index ea312b1c5..bc385940e 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug -//#define SERIAL_DEBUG +#define ENABLE_DEBUG // enable serial debug +#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -37,7 +37,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 161 // Firmware minor version +#define OS_FW_MINOR 162 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -262,6 +262,7 @@ enum { SOPT_OTC_OPTS, SOPT_DEVICE_NAME, SOPT_STA_BSSID_CHL, // wifi extra info: bssid and channel + SOPT_MQTT_OPTS2, // MQTT Extra if url+user+pass > 160 NUM_SOPTS // total number of string options }; diff --git a/mqtt.cpp b/mqtt.cpp index fa4bdba46..a58f0488d 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -156,6 +156,8 @@ void OSMqtt::begin(void) { // JSON configuration settings in the form of {"en":0|1,"host":"server_name|IP address","port":1883,user:"",pass:""} char *config = tmp_buffer; os.sopt_load(SOPT_MQTT_OPTS, config); + os.sopt_load(SOPT_MQTT_OPTS2, config+MAX_SOPTS_SIZE); + //DEBUG_PRINTLN(config); if (*config != 0) { sscanf( config, @@ -164,6 +166,8 @@ void OSMqtt::begin(void) { ); } + DEBUG_PRINTF("host: %s port: %d username: %s password: %s ", host, port, username, password); + begin(host, port, username, password, (bool)enabled); } @@ -262,11 +266,6 @@ int OSMqtt::_init(void) { mqtt_client->setKeepAlive(MQTT_KEEPALIVE); mqtt_client->setBufferSize(2048); //Most LORA Pakets are bigger! - if (mqtt_client == NULL) { - DEBUG_LOGF("MQTT Init: Failed to initialise client\r\n"); - return MQTT_ERROR; - } - return MQTT_SUCCESS; } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index f8f01f821..b232d0d51 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -260,8 +260,8 @@ void send_packet(OTF_PARAMS_DEF) { DEBUG_PRINTLN(res.getLength()); res.flush(); } - DEBUG_PRINT("res.writeBodyChunk: "); - DEBUG_PRINTLN(strlen(ether_buffer)); + //DEBUG_PRINT("res.writeBodyChunk: "); + //DEBUG_PRINTLN(strlen(ether_buffer)); res.writeBodyChunk((char *)"%s",ether_buffer); #else m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); @@ -1210,16 +1210,19 @@ void server_json_controller_main(OTF_PARAMS_DEF) { #else os.load_hardware_mac(mac, true); #endif + char mqtt_opt[MAX_SOPTS_SIZE*2]; + os.sopt_load(SOPT_MQTT_OPTS, mqtt_opt); + os.sopt_load(SOPT_MQTT_OPTS2, mqtt_opt+MAX_SOPTS_SIZE); bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","), + bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$S},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","), SOPT_LOCATION, SOPT_JAVASCRIPTURL, SOPT_WEATHERURL, SOPT_WEATHER_OPTS, SOPT_IFTTT_KEY, - SOPT_MQTT_OPTS, + mqtt_opt, strlen(wt_rawData)==0?"{}":wt_rawData, wt_errCode, SOPT_DEVICE_NAME); @@ -1547,10 +1550,12 @@ void server_change_options(OTF_PARAMS_DEF) if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mqtt"), true, &keyfound)) { urlDecode(tmp_buffer); os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); + os.sopt_save(SOPT_MQTT_OPTS2, tmp_buffer+MAX_SOPTS_SIZE); os.status.req_mqtt_restart = true; } else if (keyfound) { tmp_buffer[0]=0; os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); + os.sopt_save(SOPT_MQTT_OPTS2, tmp_buffer+MAX_SOPTS_SIZE); os.status.req_mqtt_restart = true; } From 985df1449b9c77b4749e3b6acc17754f4567dee4 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 22 May 2024 04:50:37 +0200 Subject: [PATCH 152/281] addes read+write timeouts for otfpi --- etherport.cpp | 6 ++++++ otfpi/OpenThingsFramework.cpp | 1 + 2 files changed, 7 insertions(+) diff --git a/etherport.cpp b/etherport.cpp index 914a6648b..7748a9b56 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -150,6 +150,12 @@ int EthernetClient::connect(uint8_t ip[4], uint16_t port) DEBUG_PRINTLN("error connecting to server"); return 0; } + struct timeval timeout; + timeout.tv_sec = 10; + timeout.tv_usec = 0; + setsockopt (m_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt (m_sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + m_connected = true; return 1; } diff --git a/otfpi/OpenThingsFramework.cpp b/otfpi/OpenThingsFramework.cpp index f7f98ed9e..1af46a3eb 100644 --- a/otfpi/OpenThingsFramework.cpp +++ b/otfpi/OpenThingsFramework.cpp @@ -1,6 +1,7 @@ #if defined(OSPI) #include "OpenThingsFramework.h" #include "StringBuilder.h" +#include "defines.h" // The timeout for reading and parsing incoming requests. #define WIFI_CONNECTION_TIMEOUT 1500 From 76875e816ce63127ef81f376fdd4988be0239327 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 3 Jun 2024 23:08:39 +0200 Subject: [PATCH 153/281] Fixt rain delay date --- opensprinkler_server.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 03b18bf34..1c3cb2252 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -64,7 +64,7 @@ class BufferFiller { case 'L': //ltoa(va_arg(ap, long), (char*) ptr, 10); //ultoa(va_arg(ap, long), (char*) ptr, 10); // ray - sprintf((char*) ptr, "%lu", va_arg(ap, long)); + sprintf((char*) ptr, "%" PRIu32, va_arg(ap, long)); break; case 'S': strcpy((char*) ptr, va_arg(ap, const char*)); From 5b547dcdae1b17199ad01250cf7564bbbc181b91 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 3 Jun 2024 23:21:48 +0200 Subject: [PATCH 154/281] Rain delay compile fix for non-OSPI --- opensprinkler_server.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 1c3cb2252..b630ec9e2 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -64,7 +64,11 @@ class BufferFiller { case 'L': //ltoa(va_arg(ap, long), (char*) ptr, 10); //ultoa(va_arg(ap, long), (char*) ptr, 10); // ray - sprintf((char*) ptr, "%" PRIu32, va_arg(ap, long)); + #if defined(OSPI) + sprintf((char*) ptr, "%" PRIu32, va_arg(ap, long)); + #else + sprintf((char*) ptr, "%lu", va_arg(ap, long)); + #endif break; case 'S': strcpy((char*) ptr, va_arg(ap, const char*)); From 30523b7b241607fcfe5330836b804668adaaa850 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 7 Jun 2024 08:05:22 +0200 Subject: [PATCH 155/281] OSPi BugFix memory free/delete --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 892a67380..c007c25c6 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,5 @@ build-1284/* build2.sh~ sensor_mqtt.cpp~ opensprinkler_server.cpp.bak +opensprinkler_server.h~ +sensor.bak From 4daa617d908123eaba943cfa621ef8b418d19069 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 7 Jun 2024 08:05:27 +0200 Subject: [PATCH 156/281] OSPi Bugfix Memory free/delete --- otfpi/OpenThingsFramework.cpp | 2 +- sensors.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/otfpi/OpenThingsFramework.cpp b/otfpi/OpenThingsFramework.cpp index 1af46a3eb..d14e4aea8 100644 --- a/otfpi/OpenThingsFramework.cpp +++ b/otfpi/OpenThingsFramework.cpp @@ -148,7 +148,7 @@ void OpenThingsFramework::localServerLoop() { bodyBuffer = new char[contentLength]; size_t bodyLength = 0; timeout = millis()+WIFI_CONNECTION_TIMEOUT; - while (localClient->dataAvailable() && millis()dataAvailable() && millis() bodyLength) { size_t read = localClient->readBytes(&bodyBuffer[bodyLength], min((int) (contentLength - bodyLength), 1024)); bodyLength += read; } diff --git a/sensors.cpp b/sensors.cpp index a8bedced0..f1c1176e2 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -402,6 +402,7 @@ bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog) { bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { SensorLog_t sensorlog; + memset(&sensorlog, 0, sizeof(SensorLog_t)); sensorlog.nr = sensor->nr; sensorlog.time = time; sensorlog.native_data = sensor->last_native_data; @@ -1988,7 +1989,7 @@ void SensorUrl_load() { memset(sensorUrl, 0, sizeof(SensorUrl_t)); if (file_read_block (SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE) < SENSORURL_STORE_SIZE) { - free(sensorUrl); + delete sensorUrl; break; } sensorUrl->urlstr = (char*)malloc(sensorUrl->length+1); @@ -2036,7 +2037,7 @@ bool SensorUrl_delete(uint nr, uint type) { sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); free(sensorUrl->urlstr); - free(sensorUrl); + delete sensorUrl; SensorUrl_save(); return true; } From e1ce4de4439b2288eabf765f373bc47332fbe7f0 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 13 Jun 2024 00:18:22 +0200 Subject: [PATCH 157/281] try-fix Zone Expander Latch --- OpenSprinkler.cpp | 28 ++++++++++++++++++++-------- OpenSprinkler.h | 2 +- defines.h | 6 +++--- main.cpp | 4 ++-- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 877fd213c..02294a2ad 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1135,7 +1135,7 @@ void OpenSprinkler::latch_setallzonepins(byte value) { } } -void OpenSprinkler::latch_disable_alloutputs_v2() { +void OpenSprinkler::latch_disable_alloutputs_v2(byte expvalue) { digitalWriteExt(PIN_LATCH_COMA, LOW); digitalWriteExt(PIN_LATCH_COMK, LOW); @@ -1144,7 +1144,12 @@ void OpenSprinkler::latch_disable_alloutputs_v2() { // latch v2 has a 74hc595 which controls all h-bridge cathode pins drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, 0x00); - // todo: handle expander + // Handle all expansion boards + for(byte i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, expvalue?0xFFFF:0x0000); + } + } } /** Set one zone (for LATCH controller) @@ -1183,8 +1188,15 @@ void OpenSprinkler::latch_setzoneoutput_v2(byte sid, byte A, byte K) { drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, K ? (1<>4; + uint16_t s=(sid-8)&0x0F; + if(expanders[bid]->type==IOEXP_TYPE_9555) { + uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(A==HIGH && K==LOW) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + } } } @@ -1194,14 +1206,14 @@ void OpenSprinkler::latch_setzoneoutput_v2(byte sid, byte A, byte K) { void OpenSprinkler::latch_open(byte sid) { if(hw_rev>=2) { DEBUG_PRINTLN(F("latch_open_v2")); - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH latch_boost(); // generate boost voltage digitalWriteExt(PIN_LATCH_COMA, HIGH); // enable COM+ latch_setzoneoutput_v2(sid, LOW, HIGH); // enable sid- digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path delay(150); digitalWriteExt(PIN_BOOST_EN, LOW); // disabled output boosted voltage path - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH } else { latch_boost(); // boost voltage latch_setallzonepins(HIGH); // set all switches to HIGH, including COM @@ -1217,14 +1229,14 @@ void OpenSprinkler::latch_open(byte sid) { void OpenSprinkler::latch_close(byte sid) { if(hw_rev>=2) { DEBUG_PRINTLN(F("latch_close_v2")); - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(LOW); // disable all output pins; set expanders all to LOW latch_boost(); // generate boost voltage latch_setzoneoutput_v2(sid, HIGH, LOW); // enable sid+ digitalWriteExt(PIN_LATCH_COMK, HIGH); // enable COM- digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path delay(150); digitalWriteExt(PIN_BOOST_EN, LOW); // disable output boosted voltage path - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH } else { latch_boost(); // boost voltage latch_setallzonepins(LOW); // set all switches to LOW, including COM diff --git a/OpenSprinkler.h b/OpenSprinkler.h index bd77f733d..ee79fe14e 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -412,7 +412,7 @@ class OpenSprinkler { static void latch_close(byte sid); static void latch_setzonepin(byte sid, byte value); static void latch_setallzonepins(byte value); - static void latch_disable_alloutputs_v2(); + static void latch_disable_alloutputs_v2(byte expvalue); static void latch_setzoneoutput_v2(byte sid, byte A, byte K); static void latch_apply_all_station_bits(); static byte prev_station_bits[]; diff --git a/defines.h b/defines.h index bc385940e..ed26ff954 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug -#define SERIAL_DEBUG +//#define ENABLE_DEBUG // enable serial debug +//#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -37,7 +37,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 162 // Firmware minor version +#define OS_FW_MINOR 163 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index 007299e28..3491673ec 100644 --- a/main.cpp +++ b/main.cpp @@ -104,7 +104,7 @@ uint32_t ping_ok = 0; void flow_poll() { #if defined(ESP8266) - if(os.hw_rev == 2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + if(os.hw_rev >= 2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 #endif byte curr_flow_state = digitalReadExt(PIN_SENSOR1); if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { // only record on falling edge @@ -641,7 +641,7 @@ void do_loop() if (curr_time != last_time) { #if defined(ESP8266) - if(os.hw_rev==2) { + if(os.hw_rev>=2) { pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 pinModeExt(PIN_SENSOR2, INPUT_PULLUP); } From 6ed77b2e7f0bea558562e44fff2abfe1363ce0f1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 16 Jun 2024 14:15:18 +0200 Subject: [PATCH 158/281] Updated start script - systemd compatible --- OpenSprinkler.launch | 169 +++++-------------------------------------- build.sh | 16 ++-- 2 files changed, 28 insertions(+), 157 deletions(-) diff --git a/OpenSprinkler.launch b/OpenSprinkler.launch index ad7e17063..31d143619 100755 --- a/OpenSprinkler.launch +++ b/OpenSprinkler.launch @@ -1,150 +1,19 @@ -#! /bin/sh -### BEGIN INIT INFO -# Provides: OpenSprinkler -# Required-Start: $remote_fs $syslog -# Required-Stop: $remote_fs $syslog -# Default-Start: 2 3 4 5 -# Default-Stop: 0 1 6 -# Short-Description: Open Sprinkler Raspberry Pi -# Description: Open Sprinkler Raspberry Pi - Raspberry Pi with -# Open Sprinkler base board from Ray's Hobby -### END INIT INFO - -# -# To auto start on boot execute (once) as root -# -# update-rc.d OpenSprinkler defaults -# -# To stop auto start on boot execute -# -# update-rc.d OpenSprinkler remove -# - -# Author: Denny Fox -# Do NOT "set -e" - -# PATH should only include /usr/* if it runs after the mountnfs.sh script -PATH=/sbin:/usr/sbin:/bin:/usr/bin -NAME=OpenSprinkler -DESC="Open Sprinkler (Unified) Raspberry Pi" -DAEMON=__OpenSprinkler_Path__/OpenSprinkler -DAEMON_ARGS="" -HOMEDIR=__OpenSprinkler_Path__ -PIDFILE=/var/run/$NAME.pid -SCRIPTNAME=/etc/init.d/$NAME -USER=root - -# Exit if the package is not installed -[ -x "$DAEMON" ] || exit 0 - -# Read configuration variable file if it is present -[ -r /etc/default/$NAME ] && . /etc/default/$NAME - -# Load the VERBOSE setting and other rcS variables -. /lib/init/vars.sh - -# Define LSB log_* functions. -# Depend on lsb-base (>= 3.2-14) to ensure that this file is present -# and status_of_proc is working. -. /lib/lsb/init-functions - -# -# Function that starts the daemon/service -# -do_start() -{ - # Return - # 0 if daemon has been started - # 1 if daemon was already running - # 2 if daemon could not be started - start-stop-daemon --start --quiet --chuid $USER --chdir $HOMEDIR --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON --test > /dev/null \ - || return 1 - start-stop-daemon --start --quiet --chuid $USER --chdir $HOMEDIR --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON -- \ - $DAEMON_ARGS \ - || return 2 -} - -# -# Function that stops the daemon/service -# -do_stop() -{ - # Return - # 0 if daemon has been stopped - # 1 if daemon was already stopped - # 2 if daemon could not be stopped - # other if a failure occurred - start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE - RETVAL="$?" - [ "$RETVAL" = 2 ] && return 2 - # Wait for children to finish too if this is a daemon that forks - # and if the daemon is only ever run from this initscript. - # If the above conditions are not satisfied then add some other code - # that waits for the process to drop all resources that could be - # needed by services started subsequently. A last resort is to - # sleep for some time. - start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON - [ "$?" = 2 ] && return 2 - # Many daemons don't delete their pidfiles when they exit. - rm -f $PIDFILE - return "$RETVAL" -} - -# -# Function that sends a SIGHUP to the daemon/service -# -do_reload() { - # - # If the daemon can reload its configuration without - # restarting (for example, when it is sent a SIGHUP), - # then implement that here. - # - start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME - return 0 -} - -case "$1" in - start) - [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" - do_start - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - stop) - [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" - do_stop - case "$?" in - 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; - 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; - esac - ;; - status) - status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? - ;; - restart|force-reload) - log_daemon_msg "Restarting $DESC" "$NAME" - do_stop - case "$?" in - 0|1) - do_start - case "$?" in - 0) log_end_msg 0 ;; - 1) log_end_msg 1 ;; # Old process is still running - *) log_end_msg 1 ;; # Failed to start - esac - ;; - *) - # Failed to stop - log_end_msg 1 - ;; - esac - ;; - *) - echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 - exit 3 - ;; -esac - -: +[Unit] +Description=OpenSprinkler +Documentation=https://github.com/OpenSprinkler +After=network.target + +[Service] +Type=exec +User=root +Group=root +WorkingDirectory=__OpenSprinkler_Path__ +ExecStart=__OpenSprinkler_Path__/OpenSprinkler +StandardOutput=null +#StandardOutput=syslog +#SyslogIdentifier=opensprinkler +StandardError=journal +Restart=always + +[Install] +WantedBy=multi-user.target diff --git a/build.sh b/build.sh index 2789bb169..183fcdb99 100755 --- a/build.sh +++ b/build.sh @@ -49,19 +49,21 @@ if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/O popd > /dev/null # Update binary location in start up script - sed -e 's,\_\_OpenSprinkler\_Path\_\_,'"$DIR"',g' OpenSprinkler.launch > OpenSprinkler.sh + sed -e 's,\_\_OpenSprinkler\_Path\_\_,'"$DIR"',g' OpenSprinkler.launch > OpenSprinkler.service - # Make file executable - chmod +x OpenSprinkler.sh + # remove old start script: + service OpenSprinkler stop 2>/dev/null + rm /etc/init.d/OpenSprinkler.sh 2>/dev/null - # Move start up script to init.d directory - sudo mv OpenSprinkler.sh /etc/init.d/ + # Move start up script to systemd directory + sudo mv OpenSprinkler.service /etc/systemd/system/ # Add to auto-launch on system startup - sudo update-rc.d OpenSprinkler.sh defaults + systemctl daemon-reload + systemctl enable OpenSprinkler.service # Start the deamon now - sudo /etc/init.d/OpenSprinkler.sh start + service OpenSprinkler start fi From 614613de227178e9ed66778fc9f0d38162dfacef Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 16 Jun 2024 19:50:08 +0200 Subject: [PATCH 159/281] startscript for systemd, added timeout handling --- build.sh | 2 +- etherport.cpp | 8 ++++++++ etherport.h | 2 +- otfpi/LinuxLocalServer.cpp | 2 +- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index 183fcdb99..237497a68 100755 --- a/build.sh +++ b/build.sh @@ -32,7 +32,7 @@ else source build2.sh fi -if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then +if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/systemd/system/OpenSprinkler.service ]; then read -p "Do you want to start OpenSprinkler on startup? " -n 1 -r echo diff --git a/etherport.cpp b/etherport.cpp index 7748a9b56..793c5eaba 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -265,6 +265,14 @@ void EthernetClient::flush() { //for compatibility only } +void EthernetClient::setTimeout(int msec) { + struct timeval timeout; + timeout.tv_sec = (msec / 1000); + timeout.tv_usec = (msec % 1000) * 1000; + setsockopt (m_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + setsockopt (m_sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); +} + bool EthernetClient::available() { if (tmpbufidx < tmpbufsize) return true; diff --git a/etherport.h b/etherport.h index bce34866e..a4f921805 100644 --- a/etherport.h +++ b/etherport.h @@ -64,7 +64,7 @@ class EthernetClient { } void flush(); bool available(); - + void setTimeout(int msec); private: uint8_t *tmpbuf = NULL; int tmpbufsize = 0; diff --git a/otfpi/LinuxLocalServer.cpp b/otfpi/LinuxLocalServer.cpp index 7160d6575..7ca1f9035 100644 --- a/otfpi/LinuxLocalServer.cpp +++ b/otfpi/LinuxLocalServer.cpp @@ -58,7 +58,7 @@ void LinuxLocalClient::print(const char *data) { }*/ void LinuxLocalClient::setTimeout(int timeout) { - //client.setTimeout(timeout); + client.setTimeout(timeout); } void LinuxLocalClient::flush() { From bd495e17ff2a81d1dcad90ef83179ec92fecdf0d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 16 Jun 2024 20:15:50 +0200 Subject: [PATCH 160/281] Fixed restart after updater --- updater.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updater.sh b/updater.sh index 6051b5ab9..49fd90039 100755 --- a/updater.sh +++ b/updater.sh @@ -2,4 +2,4 @@ git pull ./build.sh -s ospi -/etc/init.d/OpenSprinkler.sh restart +service OpenSprinkler restart From 6ea9ffd5d6acadb88ca2a8bde377cacddad34a8b Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 18 Jun 2024 23:28:58 +0200 Subject: [PATCH 161/281] changed timeout and error handing for remote sensors --- OpenSprinkler.cpp | 30 ++++++++++++++---------------- OpenSprinkler.h | 12 ++++++------ etherport.cpp | 13 +++++++------ sensors.cpp | 27 ++++++++++++++++++++++----- sensors.h | 3 ++- 5 files changed, 51 insertions(+), 34 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 02294a2ad..377bb6919 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1948,7 +1948,7 @@ void remote_http_callback(char* buffer) { */ } -int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { +int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout, int ntries) { #if defined(ARDUINO) @@ -1961,7 +1961,6 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* client = ðerClient; #endif - #define HTTP_CONNECT_NTRIES 3 byte tries = 0; do { DEBUG_PRINT(server); @@ -1973,9 +1972,9 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* if(client->connect(server, port)==1) break; tries++; - } while(triesstop(); return HTTP_RQT_CONNECT_ERR; @@ -2049,7 +2048,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* return HTTP_RQT_SUCCESS; } -int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { +int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout, int ntries) { #if defined(ARDUINO) @@ -2067,7 +2066,6 @@ int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char #endif - #define HTTP_CONNECT_NTRIES 3 byte tries = 0; do { DEBUG_PRINT(server); @@ -2079,9 +2077,9 @@ int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char if(client->connect(server, port)==1) break; tries++; - } while(triesstop(); return HTTP_RQT_CONNECT_ERR; @@ -2156,7 +2154,7 @@ int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char return HTTP_RQT_SUCCESS; } -int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { +int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout, int ntries) { char server[20]; byte ip[4]; ip[0] = ip4>>24; @@ -2164,10 +2162,10 @@ int8_t OpenSprinkler::send_http_request(uint32_t ip4, uint16_t port, char* p, vo ip[2] = (ip4>>8)&0xff; ip[3] = ip4&0xff; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return send_http_request(server, port, p, callback, timeout); + return send_http_request(server, port, p, callback, timeout, ntries); } -int8_t OpenSprinkler::send_https_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout) { +int8_t OpenSprinkler::send_https_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*), uint16_t timeout, int ntries) { char server[20]; byte ip[4]; ip[0] = ip4>>24; @@ -2175,19 +2173,19 @@ int8_t OpenSprinkler::send_https_request(uint32_t ip4, uint16_t port, char* p, v ip[2] = (ip4>>8)&0xff; ip[3] = ip4&0xff; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - return send_https_request(server, port, p, callback, timeout); + return send_https_request(server, port, p, callback, timeout, ntries); } -int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { +int8_t OpenSprinkler::send_http_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout, int ntries) { char * server = strtok(server_with_port, ":"); char * port = strtok(NULL, ":"); - return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); + return send_http_request(server, (port==NULL)?80:atoi(port), p, callback, timeout, ntries); } -int8_t OpenSprinkler::send_https_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout) { +int8_t OpenSprinkler::send_https_request(char* server_with_port, char* p, void(*callback)(char*), uint16_t timeout, int ntries) { char * server = strtok(server_with_port, ":"); char * port = strtok(NULL, ":"); - return send_https_request(server, (port==NULL)?80:atoi(port), p, callback, timeout); + return send_https_request(server, (port==NULL)?80:atoi(port), p, callback, timeout, ntries); } /** Switch remote station diff --git a/OpenSprinkler.h b/OpenSprinkler.h index ee79fe14e..4c9267e47 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -331,13 +331,13 @@ class OpenSprinkler { static void clear_all_station_bits(); // clear all station bits static void apply_all_station_bits(); // apply all station bits (activate/deactive values) - static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); - static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); - static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000, int ntries=3); + static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000, int ntries=3); + static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000, int ntries=3); - static int8_t send_https_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); - static int8_t send_https_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); - static int8_t send_https_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000); + static int8_t send_https_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000, int ntries=3); + static int8_t send_https_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000, int ntries=3); + static int8_t send_https_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=5000, int ntries=3); // -- LCD functions #if defined(ARDUINO) // LCD functions for Arduino diff --git a/etherport.cpp b/etherport.cpp index 793c5eaba..28411b083 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -145,17 +145,18 @@ int EthernetClient::connect(uint8_t ip[4], uint16_t port) sin.sin_port = htons(port); sin.sin_addr.s_addr = *(uint32_t*) (ip); m_sock = socket(AF_INET, SOCK_STREAM, 0); - if (::connect(m_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) - { - DEBUG_PRINTLN("error connecting to server"); - return 0; - } + struct timeval timeout; - timeout.tv_sec = 10; + timeout.tv_sec = 2; timeout.tv_usec = 0; setsockopt (m_sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); setsockopt (m_sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + if (::connect(m_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) + { + DEBUG_PRINTLN("error connecting to server"); + return 0; + } m_connected = true; return 1; } diff --git a/sensors.cpp b/sensors.cpp index f1c1176e2..036d8de13 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -804,7 +804,10 @@ void read_all_sensors(boolean online) { if (time < os.powerup_lasttime+30) return; //wait 30s before first sensor read - Sensor_t *sensor = sensors; + //When we run out of time, skip some sensors and continue on next loop + static Sensor_t *sensor = NULL; + if (sensor == NULL) + sensor = sensors; while (sensor) { if (time >= sensor->last_read + sensor->read_interval || sensor->repeat_read) { @@ -814,7 +817,20 @@ void read_all_sensors(boolean online) { sensorlog_add(LOG_STD, sensor, time); push_message(sensor); } else if (result == HTTP_RQT_TIMEOUT) { - sensor->last_read = time - sensor->read_interval + 5; + //delay next read on timeout: + sensor->last_read = time + max((uint)60,sensor->read_interval); + sensor->repeat_read = 0; + DEBUG_PRINTF("Delayed1: %s", sensor->name); + } else if (result == HTTP_RQT_CONNECT_ERR) { + //delay next read on error: + sensor->last_read = time + max((uint)60,sensor->read_interval); + sensor->repeat_read = 0; + DEBUG_PRINTF("Delayed2: %s", sensor->name); + } + ulong passed = os.now_tz() - time; + if (passed > MAX_SENSOR_READ_TIME) { + sensor = sensor->next; + break; } } } @@ -970,7 +986,8 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - if (os.send_http_request(server, sensor->port, p) == HTTP_RQT_SUCCESS) { + int res = os.send_http_request(server, sensor->port, p, 2000, 1); //timeout=2000, tries=1 + if (res == HTTP_RQT_SUCCESS) { DEBUG_PRINTLN("Send Ok"); p = ether_buffer; DEBUG_PRINTLN(p); @@ -1015,7 +1032,7 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { return HTTP_RQT_SUCCESS; } - return HTTP_RQT_EMPTY_RETURN; + return res; } /** @@ -1933,7 +1950,7 @@ void GetSensorWeather() { DEBUG_PRINTLN(F("GetSensorWeather")); DEBUG_PRINTLN(ether_buffer); - int ret = os.send_http_request(host, ether_buffer, NULL); + int ret = os.send_http_request(host, ether_buffer, NULL, 2000, 1); //timeout=2000, ntries=1 if(ret == HTTP_RQT_SUCCESS) { last_weather_time = time; DEBUG_PRINTLN(ether_buffer); diff --git a/sensors.h b/sensors.h index 6178288e5..74899c77c 100644 --- a/sensors.h +++ b/sensors.h @@ -107,7 +107,8 @@ extern "C" { #define MIN_DISK_FREE 8192 //8Kb min -#define MAX_SENSOR_REPEAT_READ 32000 //max reads for calculating avg +#define MAX_SENSOR_REPEAT_READ 32000 //max reads for calculating avg +#define MAX_SENSOR_READ_TIME 1 // second for reading sensors typedef struct SensorFlags { uint enable:1; // enabled From fd2c64922c3e2e9f74cd123d0ac81f816d0c7754 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 18 Jun 2024 23:30:25 +0200 Subject: [PATCH 162/281] changed timeout and error handing for remote sensors --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 036d8de13..1fa0dcd01 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -986,7 +986,7 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - int res = os.send_http_request(server, sensor->port, p, 2000, 1); //timeout=2000, tries=1 + int res = os.send_http_request(server, sensor->port, p, NULL, 2000, 1); //timeout=2000, tries=1 if (res == HTTP_RQT_SUCCESS) { DEBUG_PRINTLN("Send Ok"); p = ether_buffer; From 3107029221b2bd66033633f8a584458221eded4d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 20 Jun 2024 22:47:30 +0200 Subject: [PATCH 163/281] Fixt OSPi boot script --- OpenSprinkler.launch | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/OpenSprinkler.launch b/OpenSprinkler.launch index 31d143619..7991b84c2 100755 --- a/OpenSprinkler.launch +++ b/OpenSprinkler.launch @@ -1,7 +1,8 @@ [Unit] Description=OpenSprinkler Documentation=https://github.com/OpenSprinkler -After=network.target +Requires=multi-user.target network.target network-online.target +After=multi-user.target network.target network-online.target [Service] Type=exec @@ -13,7 +14,8 @@ StandardOutput=null #StandardOutput=syslog #SyslogIdentifier=opensprinkler StandardError=journal -Restart=always +Restart=on-failure +RestartSec=5s [Install] WantedBy=multi-user.target From d186c7f69e710c94358b9a79ed7cfc048e2759b3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 20 Jun 2024 23:58:33 +0200 Subject: [PATCH 164/281] Fixed a buffer problem OSPi --- .gitignore | 1 + etherport.cpp | 4 ++++ otfpi/OpenThingsFramework.cpp | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index c007c25c6..d1cbeb309 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ sensor_mqtt.cpp~ opensprinkler_server.cpp.bak opensprinkler_server.h~ sensor.bak +*~ diff --git a/etherport.cpp b/etherport.cpp index 28411b083..56b62ef0b 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -178,6 +178,9 @@ void EthernetClient::stop() close(m_sock); m_sock = 0; m_connected = false; + tmpbufidx = tmpbufsize = 0; + free(tmpbuf); + tmpbuf = NULL; } } @@ -229,6 +232,7 @@ int EthernetClient::timedRead() { return tmpbuf[tmpbufidx++]; tmpbufidx = 0; + tmpbufsize = 0; tmpbufsize = read(tmpbuf, TMPBUF); if (tmpbufsize <= 0) diff --git a/otfpi/OpenThingsFramework.cpp b/otfpi/OpenThingsFramework.cpp index d14e4aea8..b9fdf410e 100644 --- a/otfpi/OpenThingsFramework.cpp +++ b/otfpi/OpenThingsFramework.cpp @@ -127,7 +127,7 @@ void OpenThingsFramework::localServerLoop() { // Make sure that the headers were fully read into the buffer. if (strncmp_P(&buffer[length - 4], (char *) F("\r\n\r\n"), 4) != 0) { DEBUG_PRINTLN(F("The request headers were not fully read into the buffer.")); - localClient->print(F("HTTP/1.1 413 Request too large\r\n\r\nThe request was too large")); + localClient->print(F("HTTP/1.1 413 Request too large\r\n\r\nThe request was incomplete")); return; } From ed7649990b5c7eb33026d07800058d48bf3d0beb Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 21 Jun 2024 23:33:03 +0200 Subject: [PATCH 165/281] fixt OpenThingsFramework incoming local Server requests --- otfpi/OpenThingsFramework.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/otfpi/OpenThingsFramework.cpp b/otfpi/OpenThingsFramework.cpp index b9fdf410e..e2b621818 100644 --- a/otfpi/OpenThingsFramework.cpp +++ b/otfpi/OpenThingsFramework.cpp @@ -106,8 +106,7 @@ void OpenThingsFramework::localServerLoop() { while (localClient->dataAvailable() && millis() < timeout) { if (length >= (size_t)headerBufferSize) { localClient->print(F("HTTP/1.1 413 Request too large\r\n\r\nThe request was too large")); - // Get a new client to indicate that the previous client is no longer needed. - localClient = localServer.acceptClient(); + wait_to = 0; DEBUG_PRINTLN("incoming localServer request - 1"); return; } @@ -125,9 +124,11 @@ void OpenThingsFramework::localServerLoop() { DEBUG_PRINTF((char *) F("Request: %s\n"), (char *) buffer); // Make sure that the headers were fully read into the buffer. - if (strncmp_P(&buffer[length - 4], (char *) F("\r\n\r\n"), 4) != 0) { + if (length < 5 || strncmp_P(&buffer[length - 4], (char *) F("\r\n\r\n"), 4) != 0) { DEBUG_PRINTLN(F("The request headers were not fully read into the buffer.")); localClient->print(F("HTTP/1.1 413 Request too large\r\n\r\nThe request was incomplete")); + wait_to = 0; + DEBUG_PRINTLN("incoming localServer request - 2"); return; } From d5880cadb6d0a55c4d01da5348f40167dafb41b4 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 22 Jun 2024 15:50:01 +0200 Subject: [PATCH 166/281] fixt https --- OpenSprinkler.cpp | 18 ++++++++++++++++++ defines.h | 6 +++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 377bb6919..5ff953c66 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2055,6 +2055,15 @@ int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char Client *client; #if defined(ESP8266) WiFiClientSecure wifiClient; + wifiClient.setInsecure(); + //https://github.com/esp8266/Arduino/blob/master/libraries/ESP8266WiFi/examples/BearSSL_MaxFragmentLength/BearSSL_MaxFragmentLength.ino + bool mfln = wifiClient.probeMaxFragmentLength(server, port, 512); + DEBUG_PRINTF("MFLN supported: %s\n", mfln ? "yes" : "no"); + if (mfln) { + wifiClient.setBufferSizes(512, 512); + } else { + wifiClient.setBufferSizes(2048, 2048); + } client = &wifiClient; #else // Choose the analog pin to get semi-random data from for SSL @@ -2066,6 +2075,11 @@ int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char #endif + if (os.status.req_ntpsync) { + DEBUG_PRINTLN("HTTPS failed: time not synched"); + return HTTP_RQT_CONNECT_ERR; + } + byte tries = 0; do { DEBUG_PRINT(server); @@ -2080,6 +2094,10 @@ int8_t OpenSprinkler::send_https_request(const char* server, uint16_t port, char } while(triesstop(); return HTTP_RQT_CONNECT_ERR; diff --git a/defines.h b/defines.h index ed26ff954..546fcbe99 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug -//#define SERIAL_DEBUG +#define ENABLE_DEBUG // enable serial debug +#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; @@ -37,7 +37,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 163 // Firmware minor version +#define OS_FW_MINOR 164 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler From e6fd105ae00b667d90ea31f3ab9128a8005897b6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 22 Jun 2024 15:50:18 +0200 Subject: [PATCH 167/281] disabled debug --- defines.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/defines.h b/defines.h index 546fcbe99..2cd0c1788 100644 --- a/defines.h +++ b/defines.h @@ -24,8 +24,8 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug -#define SERIAL_DEBUG +//#define ENABLE_DEBUG // enable serial debug +//#define SERIAL_DEBUG typedef unsigned char byte; typedef unsigned long ulong; From d21e478b0191e6eff9518dc12b7314c71c3c3bbd Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 30 Jun 2024 23:46:02 +0200 Subject: [PATCH 168/281] Detecting analog sensor board --- OpenSprinkler.h | 1 + defines.h | 2 +- opensprinkler_server.cpp | 67 ++++++++++++++++++++++++++++++---------- opensprinkler_server.h | 1 + platformio.ini | 1 + sensors.cpp | 3 +- sensors.h | 5 +++ 7 files changed, 62 insertions(+), 18 deletions(-) diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 4c9267e47..0e1b4f5cf 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -109,6 +109,7 @@ extern EthernetServer *m_server; #endif extern bool useEth; + bool detect_i2c(int addr); #else extern EthernetServer *m_server; #endif diff --git a/defines.h b/defines.h index 2cd0c1788..9a5ea2598 100644 --- a/defines.h +++ b/defines.h @@ -37,7 +37,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 164 // Firmware minor version +#define OS_FW_MINOR 165 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index b232d0d51..42c1ce8c5 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2472,6 +2472,33 @@ void sensorconfig_json(OTF_PARAMS_DEF) { } } +//detected Analog Sensor Boards: +#define ASB_BOARD1 0x01 +#define ASB_BOARD2 0x02 +#define OSPI_PCF8591 0x04 +#define OSPI_ADS1115 0x08 + +static byte asb_detected_boards = 0; //bit 1=0x48+0x49 bit 2=0x4A+0x4B + +void detect_asb_board() { + //detect analog sensor board, 0x48+0x49=Board1, 0x4A+0x4B=Board2 +#if defined(ESP8266) + if (detect_i2c(ASB_BOARD_ADDR1a) && detect_i2c(ASB_BOARD_ADDR1b)) asb_detected_boards |= ASB_BOARD1; + if (detect_i2c(ASB_BOARD_ADDR2a) && detect_i2c(ASB_BOARD_ADDR2b)) asb_detected_boards |= ASB_BOARD2; +#endif + +// Old, pre OSPi 1.43 analog inputs: +#if defined(PCF8591) + asb_detected_boards |= OSPI_PCF8591; +#endif + +// New OSPi 1.6 analog inputs: +#if defined(ADS1115) + asb_detected_boards |= OSPI_ADS1115; +#endif +} + + /** * sl * @brief Lists all sensors @@ -2502,10 +2529,12 @@ void server_sensor_list(OTF_PARAMS_DEF) { #endif if (test) { - bfill.emit_p(PSTR("{\"test\":$D}"), test); + bfill.emit_p(PSTR("{\"test\":$D,"), test); + bfill.emit_p(PSTR("\"detected\":$D}"), asb_detected_boards); } else { int count = sensor_count(); bfill.emit_p(PSTR("{\"count\":$D,"), count); + bfill.emit_p(PSTR("\"detected\":$D,"), asb_detected_boards); bfill.emit_p(PSTR("\"sensors\":[")); sensorconfig_json(OTF_PARAMS); bfill.emit_p(PSTR("]")); @@ -2938,7 +2967,7 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { handle_return(HTML_OK); } -const int sensor_types[] = { +static const int sensor_types[] = { SENSOR_SMT100_MODBUS_RTU_MOIS, SENSOR_SMT100_MODBUS_RTU_TEMP, @@ -2979,23 +3008,23 @@ const int sensor_types[] = { SENSOR_GROUP_SUM, }; -const char* sensor_names[] = { +static const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", #if defined(ARDUINO) #if defined(ESP8266) - "OpenSprinkler analog extension board 2xADS1x15 x8 - voltage mode 0..4V", - "OpenSprinkler analog extension board 2xADS1x15 x8 - 0..3.3V to 0..100%", - "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT50 moisture mode", - "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT50 temperature mode", - "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT100-analog moisture mode", - "OpenSprinkler analog extension board 2xADS1x15 x8 - SMT100-analog temperature mode", - - "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix VH400", - "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix THERM200", - "OpenSprinkler analog extension board 2xADS1x15 x8 - Vegetronix AquaPlumb", - - "OpenSprinkler analog extension board 2xADS1x15 x8 - user defined sensor", + "ASB - voltage mode 0..4V", + "ASB - 0..3.3V to 0..100%", + "ASB - SMT50 moisture mode", + "ASB - SMT50 temperature mode", + "ASB - SMT100-analog moisture mode", + "ASB - SMT100-analog temperature mode", + + "ASB - Vegetronix VH400", + "ASB - Vegetronix THERM200", + "ASB - Vegetronix AquaPlumb", + + "ASB - user defined sensor", #endif #else #if defined ADS1115||PCF8591 @@ -3042,10 +3071,16 @@ void server_sensor_types(OTF_PARAMS_DEF) { print_header(); #endif - bfill.emit_p(PSTR("{\"count\":$D,\"sensorTypes\":["), sizeof(sensor_types)/sizeof(int)); + int count = sizeof(sensor_types)/sizeof(int); + boolean use_asb = asb_detected_boards > 0; + if (!use_asb) count -= 10; + + bfill.emit_p(PSTR("{\"count\":$D,\"sensorTypes\":["), count); for (uint i = 0; i < sizeof(sensor_types)/sizeof(int); i++) { + if (!use_asb && i >= SENSOR_ANALOG_EXTENSION_BOARD && i <= SENSOR_USERDEF) + continue; if (i > 0) bfill.emit_p(PSTR(",")); byte unitid = getSensorUnitId(sensor_types[i]); diff --git a/opensprinkler_server.h b/opensprinkler_server.h index b630ec9e2..38c149968 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -33,6 +33,7 @@ #endif char dec2hexchar(byte dec); +void detect_asb_board(); class BufferFiller { char *start; //!< Pointer to start of buffer diff --git a/platformio.ini b/platformio.ini index e25278c8d..7fc5d2223 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,6 +30,7 @@ lib_deps = https://github.com/OpenSprinklerShop/arduinoWebSockets RobTillaart/ADS1X15 https://github.com/bluemurder/esp8266-ping + ;pschatzmann/TinyFTPClient ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - upload_speed = 460800 diff --git a/sensors.cpp b/sensors.cpp index 1fa0dcd01..bf8ffa9fa 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -97,6 +97,7 @@ uint16_t CRC16 (byte buf[], int len) { * init sensor api and load data */ void sensor_api_init() { + detect_asb_board(); sensor_load(); prog_adjust_load(); sensor_mqtt_init(); @@ -853,7 +854,7 @@ int read_sensor_adc(Sensor_t *sensor, ulong time) { if (sensor->id >= 16) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: - int port = 0x48 + sensor->id / 4; + int port = ASB_BOARD_ADDR1a + sensor->id / 4; int id = sensor->id % 4; ADS1115 adc(port); diff --git a/sensors.h b/sensors.h index 74899c77c..d8fe95c6f 100644 --- a/sensors.h +++ b/sensors.h @@ -220,6 +220,11 @@ typedef struct SensorUrl { //Unitnames // extern const char* sensor_unitNames[]; +#define ASB_BOARD_ADDR1a 0x48 +#define ASB_BOARD_ADDR1b 0x49 +#define ASB_BOARD_ADDR2a 0x4A +#define ASB_BOARD_ADDR2b 0x4B + void sensor_api_init(); Sensor_t* getSensors(); From 7cfaa9ee178c5320dec5f7e5f41fe3cf0d34351f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 30 Jun 2024 23:54:33 +0200 Subject: [PATCH 169/281] added OSPi compatibility for sensor detect --- opensprinkler_server.cpp | 13 +++---------- sensors.h | 5 ----- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 42c1ce8c5..c405245a2 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2971,8 +2971,7 @@ static const int sensor_types[] = { SENSOR_SMT100_MODBUS_RTU_MOIS, SENSOR_SMT100_MODBUS_RTU_TEMP, -#if defined(ARDUINO) - #if defined(ESP8266) +#if defined(ESP8266) SENSOR_ANALOG_EXTENSION_BOARD, SENSOR_ANALOG_EXTENSION_BOARD_P, SENSOR_SMT50_MOIS, @@ -2983,15 +2982,12 @@ static const int sensor_types[] = { SENSOR_THERM200, SENSOR_AQUAPLUMB, SENSOR_USERDEF, - #endif -#else - +#endif #if defined ADS1115||PCF8591 SENSOR_OSPI_ANALOG, SENSOR_OSPI_ANALOG_P, SENSOR_OSPI_ANALOG_SMT50_MOIS, SENSOR_OSPI_ANALOG_SMT50_TEMP, -#endif #endif SENSOR_MQTT, SENSOR_REMOTE, @@ -3011,7 +3007,6 @@ static const int sensor_types[] = { static const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", -#if defined(ARDUINO) #if defined(ESP8266) "ASB - voltage mode 0..4V", "ASB - 0..3.3V to 0..100%", @@ -3025,14 +3020,12 @@ static const char* sensor_names[] = { "ASB - Vegetronix AquaPlumb", "ASB - user defined sensor", - #endif -#else +#endif #if defined ADS1115||PCF8591 "OSPi analog input - voltage mode 0..3.3V", "OSPi analog input - 0.3.3V to 0..100%", "OSPi analog input - SMT50 moisture mode", "OSPi analog input - SMT50 temperature mode", -#endif #endif "MQTT subscription", "Remote opensprinkler sensor", diff --git a/sensors.h b/sensors.h index d8fe95c6f..856fb6a45 100644 --- a/sensors.h +++ b/sensors.h @@ -64,8 +64,6 @@ extern "C" { #define SENSOR_NONE 0 //None or deleted sensor #define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode #define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode -#if defined(ARDUINO) -#if defined(ESP8266) #define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board x8 - voltage mode 0..4V #define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% #define SENSOR_SMT50_MOIS 15 //New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 @@ -79,13 +77,10 @@ extern "C" { #define SENSOR_USERDEF 49 //New OpenSprinkler analog extension board x8 - User defined sensor -#endif -#else #define SENSOR_OSPI_ANALOG 50 //Old OSPi analog input - voltage mode 0..3.3V #define SENSOR_OSPI_ANALOG_P 51 //Old OSPi analog input - percent 0..3.3V to 0...100% #define SENSOR_OSPI_ANALOG_SMT50_MOIS 52 //Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 #define SENSOR_OSPI_ANALOG_SMT50_TEMP 53 //Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 -#endif #define SENSOR_MQTT 90 //subscribe to a MQTT server and query a value From d93eca373c86bd2a1b9a38d7d9b1ca09e741d6aa Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 7 Jul 2024 01:33:41 +0200 Subject: [PATCH 170/281] added board detection (see sf+sl command) --- opensprinkler_server.cpp | 42 ++++++++-------------------------------- opensprinkler_server.h | 1 - sensors.cpp | 30 ++++++++++++++++++++++++++++ sensors.h | 10 ++++++++++ 4 files changed, 48 insertions(+), 35 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 42c1ce8c5..724c7eaf5 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2472,33 +2472,6 @@ void sensorconfig_json(OTF_PARAMS_DEF) { } } -//detected Analog Sensor Boards: -#define ASB_BOARD1 0x01 -#define ASB_BOARD2 0x02 -#define OSPI_PCF8591 0x04 -#define OSPI_ADS1115 0x08 - -static byte asb_detected_boards = 0; //bit 1=0x48+0x49 bit 2=0x4A+0x4B - -void detect_asb_board() { - //detect analog sensor board, 0x48+0x49=Board1, 0x4A+0x4B=Board2 -#if defined(ESP8266) - if (detect_i2c(ASB_BOARD_ADDR1a) && detect_i2c(ASB_BOARD_ADDR1b)) asb_detected_boards |= ASB_BOARD1; - if (detect_i2c(ASB_BOARD_ADDR2a) && detect_i2c(ASB_BOARD_ADDR2b)) asb_detected_boards |= ASB_BOARD2; -#endif - -// Old, pre OSPi 1.43 analog inputs: -#if defined(PCF8591) - asb_detected_boards |= OSPI_PCF8591; -#endif - -// New OSPi 1.6 analog inputs: -#if defined(ADS1115) - asb_detected_boards |= OSPI_ADS1115; -#endif -} - - /** * sl * @brief Lists all sensors @@ -2530,11 +2503,11 @@ void server_sensor_list(OTF_PARAMS_DEF) { if (test) { bfill.emit_p(PSTR("{\"test\":$D,"), test); - bfill.emit_p(PSTR("\"detected\":$D}"), asb_detected_boards); + bfill.emit_p(PSTR("\"detected\":$D}"), get_asb_detected_boards()); } else { int count = sensor_count(); bfill.emit_p(PSTR("{\"count\":$D,"), count); - bfill.emit_p(PSTR("\"detected\":$D,"), asb_detected_boards); + bfill.emit_p(PSTR("\"detected\":$D,"), get_asb_detected_boards()); bfill.emit_p(PSTR("\"sensors\":[")); sensorconfig_json(OTF_PARAMS); bfill.emit_p(PSTR("]")); @@ -3072,20 +3045,21 @@ void server_sensor_types(OTF_PARAMS_DEF) { #endif int count = sizeof(sensor_types)/sizeof(int); - boolean use_asb = asb_detected_boards > 0; + boolean use_asb = get_asb_detected_boards() > 0; if (!use_asb) count -= 10; - bfill.emit_p(PSTR("{\"count\":$D,\"sensorTypes\":["), count); + bfill.emit_p(PSTR("{\"count\":$D,\"detected\":$D,\"sensorTypes\":["), count, get_asb_detected_boards()); for (uint i = 0; i < sizeof(sensor_types)/sizeof(int); i++) { - if (!use_asb && i >= SENSOR_ANALOG_EXTENSION_BOARD && i <= SENSOR_USERDEF) + int type = sensor_types[i]; + if (!use_asb && type >= SENSOR_ANALOG_EXTENSION_BOARD && type <= SENSOR_USERDEF) continue; if (i > 0) bfill.emit_p(PSTR(",")); - byte unitid = getSensorUnitId(sensor_types[i]); + byte unitid = getSensorUnitId(type); bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), - sensor_types[i], sensor_names[i], getSensorUnit(unitid), unitid); + type, sensor_names[i], getSensorUnit(unitid), unitid); send_packet(OTF_PARAMS); } bfill.emit_p(PSTR("]}")); diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 38c149968..b630ec9e2 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -33,7 +33,6 @@ #endif char dec2hexchar(byte dec); -void detect_asb_board(); class BufferFiller { char *start; //!< Pointer to start of buffer diff --git a/sensors.cpp b/sensors.cpp index bf8ffa9fa..c6e1efc13 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -41,6 +41,9 @@ byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,b static Sensor_t *sensors = NULL; static time_t last_save_time = 0; +//Boards: +static byte asb_detected_boards = 0; //bit 1=0x48+0x49 bit 2=0x4A+0x4B + //Sensor URLS: static SensorUrl_t * sensorUrls = NULL; @@ -93,6 +96,28 @@ uint16_t CRC16 (byte buf[], int len) { return crc; } // End: CRC16 + +void detect_asb_board() { + //detect analog sensor board, 0x48+0x49=Board1, 0x4A+0x4B=Board2 +#if defined(ESP8266) + if (detect_i2c(ASB_BOARD_ADDR1a) && detect_i2c(ASB_BOARD_ADDR1b)) asb_detected_boards |= ASB_BOARD1; + if (detect_i2c(ASB_BOARD_ADDR2a) && detect_i2c(ASB_BOARD_ADDR2b)) asb_detected_boards |= ASB_BOARD2; +#endif + +// Old, pre OSPi 1.43 analog inputs: +#if defined(PCF8591) + asb_detected_boards |= OSPI_PCF8591; +#endif + +// New OSPi 1.6 analog inputs: +#if defined(ADS1115) + asb_detected_boards |= OSPI_ADS1115; +#endif +} + +byte get_asb_detected_boards() { + return asb_detected_boards; +} /* * init sensor api and load data */ @@ -854,6 +879,11 @@ int read_sensor_adc(Sensor_t *sensor, ulong time) { if (sensor->id >= 16) return HTTP_RQT_NOT_RECEIVED; //Init + Detect: + if (sensor->id < 8 && ((asb_detected_boards & ASB_BOARD1) == 0)) + return HTTP_RQT_NOT_RECEIVED; + if (sensor->id >= 8 && sensor->id < 16 && ((asb_detected_boards & ASB_BOARD2) == 0)) + return HTTP_RQT_NOT_RECEIVED; + int port = ASB_BOARD_ADDR1a + sensor->id / 4; int id = sensor->id % 4; diff --git a/sensors.h b/sensors.h index d8fe95c6f..204d3e1b1 100644 --- a/sensors.h +++ b/sensors.h @@ -110,6 +110,13 @@ extern "C" { #define MAX_SENSOR_REPEAT_READ 32000 //max reads for calculating avg #define MAX_SENSOR_READ_TIME 1 // second for reading sensors +//detected Analog Sensor Boards: +#define ASB_BOARD1 0x01 +#define ASB_BOARD2 0x02 +#define OSPI_PCF8591 0x04 +#define OSPI_ADS1115 0x08 + + typedef struct SensorFlags { uint enable:1; // enabled uint log:1; // log data enabled @@ -226,6 +233,7 @@ typedef struct SensorUrl { #define ASB_BOARD_ADDR2b 0x4B void sensor_api_init(); +byte get_asb_detected_boards(); Sensor_t* getSensors(); const char* getSensorUnit(int unitid); @@ -299,6 +307,8 @@ bool SensorUrl_delete(uint nr, uint type); bool SensorUrl_add(uint nr, uint type, const char *urlstr); char *SensorUrl_get(uint nr, uint type); +void detect_asb_board(); + #if defined(ESP8266) ulong diskFree(); bool checkDiskFree(); //true: disk space Ok, false: Out of disk space From f304350945b0e2331e7c59d4fd7d38864d1e0fdf Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 21 Jul 2024 01:49:05 +0200 Subject: [PATCH 171/281] added rs485 truebner interface support, added SMT100 permittivity mode --- opensprinkler_server.cpp | 12 +- platformio.ini | 1 - sensors.cpp | 3815 ++++++++++++++++++++------------------ sensors.h | 406 ++-- 4 files changed, 2253 insertions(+), 1981 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index ce3f2d36a..466ac4537 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2754,7 +2754,7 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { print_header(); #endif - if (nr > 0) { + if (nr > 0 || use_under || use_over) { ulong n = 0; if (log == -1) { n += sensorlog_clear_sensor(nr, LOG_STD, use_under, under, use_over, over); @@ -2941,8 +2941,9 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { } static const int sensor_types[] = { - SENSOR_SMT100_MODBUS_RTU_MOIS, - SENSOR_SMT100_MODBUS_RTU_TEMP, + SENSOR_SMT100_MOIS, + SENSOR_SMT100_TEMP, + SENSOR_SMT100_PMTY, #if defined(ESP8266) SENSOR_ANALOG_EXTENSION_BOARD, @@ -2978,8 +2979,9 @@ static const int sensor_types[] = { }; static const char* sensor_names[] = { - "Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode", - "Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode", + "Truebner SMT100 RS485 Modbus, moisture mode", + "Truebner SMT100 RS485 Modbus, temperature mode", + "Truebner SMT100 RS485 Modbus, permittivity mode", #if defined(ESP8266) "ASB - voltage mode 0..4V", "ASB - 0..3.3V to 0..100%", diff --git a/platformio.ini b/platformio.ini index 7fc5d2223..e25278c8d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,6 @@ lib_deps = https://github.com/OpenSprinklerShop/arduinoWebSockets RobTillaart/ADS1X15 https://github.com/bluemurder/esp8266-ping - ;pschatzmann/TinyFTPClient ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - upload_speed = 460800 diff --git a/sensors.cpp b/sensors.cpp index c6e1efc13..5a64d07f0 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -19,15 +19,20 @@ * . */ +#include "sensors.h" + #include -#include "defines.h" -#include "utils.h" -#include "program.h" + #include "OpenSprinkler.h" +#ifdef ESP8266 +#include "Wire.h" +#endif +#include "defines.h" #include "opensprinkler_server.h" -#include "sensors.h" -#include "weather.h" +#include "program.h" #include "sensor_mqtt.h" +#include "utils.h" +#include "weather.h" #ifdef ADS1115 #include "sensor_ospi_ads1115.h" #endif @@ -35,42 +40,46 @@ #include "sensor_ospi_pcf8591.h" #endif -byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); +byte findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const char *key, + bool key_in_pgm = false, uint8_t *keyfound = NULL); -//All sensors: +// All sensors: static Sensor_t *sensors = NULL; static time_t last_save_time = 0; -//Boards: -static byte asb_detected_boards = 0; //bit 1=0x48+0x49 bit 2=0x4A+0x4B +// Boards: +static byte asb_detected_boards = 0; // bit 1=0x48+0x49 bit 2=0x4A+0x4B -//Sensor URLS: -static SensorUrl_t * sensorUrls = NULL; +// Sensor URLS: +static SensorUrl_t *sensorUrls = NULL; -//Program sensor data +// Program sensor data static ProgSensorAdjust_t *progSensorAdjusts = NULL; -//modbus transaction id +// modbus transaction id static uint16_t modbusTcpId = 0; - -const char* sensor_unitNames[] { - "", "%", "°C", "°F", "V", "%", "in", "mm", "mph", "kmh", "%" -// 0 1 2 3 4 5 6 7 8 9 10 -// 0=Nothing -// 1=Soil moisture -// 2=degree celsius temperature -// 3=degree fahrenheit temperature -// 4=Volt V -// 5=Humidity % -// 6=Rain inch -// 7=Rain mm -// 8=Wind mph -// 9=Wind kmh -// 10=Level % +static uint i2c_rs485_allocated; + +const char *sensor_unitNames[]{ + "", "%", "°C", "°F", "V", "%", "in", + "mm", "mph", "kmh", "%", "DK" + // 0 1 2 3 4 5 6 7 8 9 10, 11 + // 0=Nothing + // 1=Soil moisture + // 2=degree celsius temperature + // 3=degree fahrenheit temperature + // 4=Volt V + // 5=Humidity % + // 6=Rain inch + // 7=Rain mm + // 8=Wind mph + // 9=Wind kmh + // 10=Level % + // 11=DK }; -byte logFileSwitch[3] = {0,0,0}; //0=use smaller File, 1=LOG1, 2=LOG2 +byte logFileSwitch[3] = {0, 0, 0}; // 0=use smaller File, 1=LOG1, 2=LOG2 -//Weather +// Weather time_t last_weather_time = 0; bool current_weather_ok = false; double current_temp = 0.0; @@ -78,536 +87,539 @@ double current_humidity = 0.0; double current_precip = 0.0; double current_wind = 0.0; -uint16_t CRC16 (byte buf[], int len) { - uint16_t crc = 0xFFFF; - - for (int pos = 0; pos < len; pos++) { - crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc - for (int i = 8; i != 0; i--) { // Loop over each bit - if ((crc & 0x0001) != 0) { // If the LSB is set - crc >>= 1; // Shift right and XOR 0xA001 - crc ^= 0xA001; - } - else // Else LSB is not set - crc >>= 1; // Just shift right - } - } - // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes) - return crc; -} // End: CRC16 - +uint16_t CRC16(byte buf[], int len) { + uint16_t crc = 0xFFFF; + + for (int pos = 0; pos < len; pos++) { + crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } else // Else LSB is not set + crc >>= 1; // Just shift right + } + } + // Note, this number has low and high bytes swapped, so use it accordingly (or + // swap bytes) + return crc; +} // End: CRC16 +/** + * @brief detect connected boards + * + */ void detect_asb_board() { - //detect analog sensor board, 0x48+0x49=Board1, 0x4A+0x4B=Board2 + // detect analog sensor board, 0x48+0x49=Board1, 0x4A+0x4B=Board2 #if defined(ESP8266) - if (detect_i2c(ASB_BOARD_ADDR1a) && detect_i2c(ASB_BOARD_ADDR1b)) asb_detected_boards |= ASB_BOARD1; - if (detect_i2c(ASB_BOARD_ADDR2a) && detect_i2c(ASB_BOARD_ADDR2b)) asb_detected_boards |= ASB_BOARD2; + if (detect_i2c(ASB_BOARD_ADDR1a) && detect_i2c(ASB_BOARD_ADDR1b)) + asb_detected_boards |= ASB_BOARD1; + if (detect_i2c(ASB_BOARD_ADDR2a) && detect_i2c(ASB_BOARD_ADDR2b)) + asb_detected_boards |= ASB_BOARD2; + + if (detect_i2c(RS485_TRUEBNER_ADDR)) asb_detected_boards |= RS485_TRUEBNER; #endif // Old, pre OSPi 1.43 analog inputs: #if defined(PCF8591) - asb_detected_boards |= OSPI_PCF8591; + asb_detected_boards |= OSPI_PCF8591; #endif // New OSPi 1.6 analog inputs: #if defined(ADS1115) - asb_detected_boards |= OSPI_ADS1115; + asb_detected_boards |= OSPI_ADS1115; #endif + DEBUG_PRINT("ASB DETECT="); + DEBUG_PRINTLN(asb_detected_boards); } -byte get_asb_detected_boards() { - return asb_detected_boards; -} +byte get_asb_detected_boards() { return asb_detected_boards; } /* * init sensor api and load data */ void sensor_api_init() { - detect_asb_board(); - sensor_load(); - prog_adjust_load(); - sensor_mqtt_init(); + detect_asb_board(); + sensor_load(); + prog_adjust_load(); + sensor_mqtt_init(); } /* * get list of all configured sensors */ -Sensor_t* getSensors() { - return sensors; -} +Sensor_t *getSensors() { return sensors; } /** * @brief delete a sensor - * - * @param nr + * + * @param nr */ int sensor_delete(uint nr) { - - Sensor_t *sensor = sensors; - Sensor_t *last = NULL; - while (sensor) { - if (sensor->nr == nr) { - if (last == NULL) - sensors = sensor->next; - else - last->next = sensor->next; - delete sensor; - sensor_save(); - return HTTP_RQT_SUCCESS; - } - last = sensor; - sensor = sensor->next; - } - return HTTP_RQT_NOT_RECEIVED; + Sensor_t *sensor = sensors; + Sensor_t *last = NULL; + while (sensor) { + if (sensor->nr == nr) { + if (last == NULL) + sensors = sensor->next; + else + last->next = sensor->next; + delete sensor; + sensor_save(); + return HTTP_RQT_SUCCESS; + } + last = sensor; + sensor = sensor->next; + } + return HTTP_RQT_NOT_RECEIVED; } /** * @brief define or insert a sensor - * + * * @param nr > 0 * @param type > 0 add/modify, 0=delete * @param group group assignment - * @param ip - * @param port - * @param id + * @param ip + * @param port + * @param id */ -int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, - const char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags, int16_t assigned_unitid) { - - if (nr == 0 || type == 0) - return HTTP_RQT_NOT_RECEIVED; - if (ri < 10) - ri = 10; - - DEBUG_PRINTLN(F("server_define")); - - Sensor_t *sensor = sensors; - Sensor_t *last = NULL; - while (sensor) { - if (sensor->nr == nr) { //Modify existing - sensor->type = type; - sensor->group = group; - strncpy(sensor->name, name, sizeof(sensor->name)-1); - sensor->ip = ip; - sensor->port = port; - sensor->id = id; - sensor->read_interval = ri; - sensor->factor = factor; - sensor->divider = divider; - sensor->offset_mv = offset_mv; - sensor->offset2 = offset2; - strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); - sensor->flags = flags; - if (assigned_unitid >= 0) - sensor->assigned_unitid = assigned_unitid; - sensor_save(); - return HTTP_RQT_SUCCESS; - } - - if (sensor->nr > nr) - break; - - last = sensor; - sensor = sensor->next; - } - - DEBUG_PRINTLN(F("server_define2")); - - //Insert new - Sensor_t *new_sensor = new Sensor_t; - memset(new_sensor, 0, sizeof(Sensor_t)); - new_sensor->nr = nr; - new_sensor->type = type; - new_sensor->group = group; - strncpy(new_sensor->name, name, sizeof(new_sensor->name)-1); - new_sensor->ip = ip; - new_sensor->port = port; - new_sensor->id = id; - new_sensor->read_interval = ri; - new_sensor->factor = factor; - new_sensor->divider = divider; - new_sensor->offset_mv = offset_mv; - new_sensor->offset2 = offset2; - strncpy(new_sensor->userdef_unit, userdef_unit, sizeof(new_sensor->userdef_unit)-1); - new_sensor->flags = flags; - if (assigned_unitid >= 0) - new_sensor->assigned_unitid = assigned_unitid; - - if (last) { - new_sensor->next = last->next; - last->next = new_sensor; - } else { - new_sensor->next = sensors; - sensors = new_sensor; - } - sensor_save(); - return HTTP_RQT_SUCCESS; -} - -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, const char *userdef_unit, int16_t offset_mv, int16_t offset2, int16_t assigned_unitid) { - Sensor_t *sensor = sensor_by_nr(nr); - if (!sensor) - return HTTP_RQT_NOT_RECEIVED; - - sensor->factor = factor; - sensor->divider = divider; - sensor->offset_mv = offset_mv; - sensor->offset2 = offset2; - sensor->assigned_unitid = assigned_unitid; - if (userdef_unit) - strncpy(sensor->userdef_unit, userdef_unit, sizeof(sensor->userdef_unit)-1); - else - memset(sensor->userdef_unit, 0, sizeof(sensor->userdef_unit)); - - return HTTP_RQT_SUCCESS; +int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, + uint port, uint id, uint ri, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, int16_t offset2, + SensorFlags_t flags, int16_t assigned_unitid) { + if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; + if (ri < 10) ri = 10; + + DEBUG_PRINTLN(F("server_define")); + + Sensor_t *sensor = sensors; + Sensor_t *last = NULL; + while (sensor) { + if (sensor->nr == nr) { // Modify existing + sensor->type = type; + sensor->group = group; + strncpy(sensor->name, name, sizeof(sensor->name) - 1); + sensor->ip = ip; + sensor->port = port; + sensor->id = id; + sensor->read_interval = ri; + sensor->factor = factor; + sensor->divider = divider; + sensor->offset_mv = offset_mv; + sensor->offset2 = offset2; + strncpy(sensor->userdef_unit, userdef_unit, + sizeof(sensor->userdef_unit) - 1); + sensor->flags = flags; + if (assigned_unitid >= 0) sensor->assigned_unitid = assigned_unitid; + sensor_save(); + return HTTP_RQT_SUCCESS; + } + + if (sensor->nr > nr) break; + + last = sensor; + sensor = sensor->next; + } + + DEBUG_PRINTLN(F("server_define2")); + + // Insert new + Sensor_t *new_sensor = new Sensor_t; + memset(new_sensor, 0, sizeof(Sensor_t)); + new_sensor->nr = nr; + new_sensor->type = type; + new_sensor->group = group; + strncpy(new_sensor->name, name, sizeof(new_sensor->name) - 1); + new_sensor->ip = ip; + new_sensor->port = port; + new_sensor->id = id; + new_sensor->read_interval = ri; + new_sensor->factor = factor; + new_sensor->divider = divider; + new_sensor->offset_mv = offset_mv; + new_sensor->offset2 = offset2; + strncpy(new_sensor->userdef_unit, userdef_unit, + sizeof(new_sensor->userdef_unit) - 1); + new_sensor->flags = flags; + if (assigned_unitid >= 0) new_sensor->assigned_unitid = assigned_unitid; + + if (last) { + new_sensor->next = last->next; + last->next = new_sensor; + } else { + new_sensor->next = sensors; + sensors = new_sensor; + } + sensor_save(); + return HTTP_RQT_SUCCESS; +} + +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, + int16_t offset2, int16_t assigned_unitid) { + Sensor_t *sensor = sensor_by_nr(nr); + if (!sensor) return HTTP_RQT_NOT_RECEIVED; + + sensor->factor = factor; + sensor->divider = divider; + sensor->offset_mv = offset_mv; + sensor->offset2 = offset2; + sensor->assigned_unitid = assigned_unitid; + if (userdef_unit) + strncpy(sensor->userdef_unit, userdef_unit, + sizeof(sensor->userdef_unit) - 1); + else + memset(sensor->userdef_unit, 0, sizeof(sensor->userdef_unit)); + + return HTTP_RQT_SUCCESS; } /** * @brief initial load stored sensor definitions - * + * */ void sensor_load() { - //DEBUG_PRINTLN(F("sensor_load")); - sensors = NULL; - if (!file_exists(SENSOR_FILENAME)) - return; - - ulong pos = 0; - Sensor_t *last = NULL; - while (true) { - Sensor_t *sensor = new Sensor_t; - memset(sensor, 0, sizeof(Sensor_t)); - file_read_block (SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); - if (!sensor->nr || !sensor->type) { - delete sensor; - break; - } - if (!last) sensors = sensor; - else last->next = sensor; - last = sensor; - sensor->flags.data_ok = false; - sensor->next = NULL; - pos += SENSOR_STORE_SIZE; - } - - SensorUrl_load(); - last_save_time = os.now_tz(); + // DEBUG_PRINTLN(F("sensor_load")); + sensors = NULL; + if (!file_exists(SENSOR_FILENAME)) return; + + ulong pos = 0; + Sensor_t *last = NULL; + while (true) { + Sensor_t *sensor = new Sensor_t; + memset(sensor, 0, sizeof(Sensor_t)); + file_read_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); + if (!sensor->nr || !sensor->type) { + delete sensor; + break; + } + if (!last) + sensors = sensor; + else + last->next = sensor; + last = sensor; + sensor->flags.data_ok = false; + sensor->next = NULL; + pos += SENSOR_STORE_SIZE; + } + + SensorUrl_load(); + last_save_time = os.now_tz(); } /** * @brief Store sensor data - * + * */ void sensor_save() { - DEBUG_PRINTLN(F("sensor_save")); - if (file_exists(SENSOR_FILENAME_BAK)) - remove_file(SENSOR_FILENAME_BAK); - if (file_exists(SENSOR_FILENAME)) - rename_file(SENSOR_FILENAME, SENSOR_FILENAME_BAK); - - ulong pos = 0; - Sensor_t *sensor = sensors; - while (sensor) { - file_write_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); - sensor = sensor->next; - pos += SENSOR_STORE_SIZE; - } - - last_save_time = os.now_tz(); - DEBUG_PRINTLN(F("sensor_save2")); + DEBUG_PRINTLN(F("sensor_save")); + if (file_exists(SENSOR_FILENAME_BAK)) remove_file(SENSOR_FILENAME_BAK); + if (file_exists(SENSOR_FILENAME)) + rename_file(SENSOR_FILENAME, SENSOR_FILENAME_BAK); + + ulong pos = 0; + Sensor_t *sensor = sensors; + while (sensor) { + file_write_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); + sensor = sensor->next; + pos += SENSOR_STORE_SIZE; + } + + last_save_time = os.now_tz(); + DEBUG_PRINTLN(F("sensor_save2")); } uint sensor_count() { - //DEBUG_PRINTLN(F("sensor_count")); - Sensor_t *sensor = sensors; - uint count = 0; - while (sensor) { - count++; - sensor = sensor->next; - } - return count; + // DEBUG_PRINTLN(F("sensor_count")); + Sensor_t *sensor = sensors; + uint count = 0; + while (sensor) { + count++; + sensor = sensor->next; + } + return count; } Sensor_t *sensor_by_nr(uint nr) { - //DEBUG_PRINTLN(F("sensor_by_nr")); - Sensor_t *sensor = sensors; - while (sensor) { - if (sensor->nr == nr) - return sensor; - sensor = sensor->next; - } - return NULL; + // DEBUG_PRINTLN(F("sensor_by_nr")); + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->nr == nr) return sensor; + sensor = sensor->next; + } + return NULL; } Sensor_t *sensor_by_idx(uint idx) { - //DEBUG_PRINTLN(F("sensor_by_idx")); - Sensor_t *sensor = sensors; - uint count = 0; - while (sensor) { - if (count == idx) - return sensor; - count++; - sensor = sensor->next; - } - return NULL; + // DEBUG_PRINTLN(F("sensor_by_idx")); + Sensor_t *sensor = sensors; + uint count = 0; + while (sensor) { + if (count == idx) return sensor; + count++; + sensor = sensor->next; + } + return NULL; } // LOGGING METHODS: /** * @brief getlogfile name - * - * @param log - * @return const char* + * + * @param log + * @return const char* */ const char *getlogfile(uint8_t log) { - bool sw = logFileSwitch[log]; - switch (log) { - case 0: return sw?SENSORLOG_FILENAME1:SENSORLOG_FILENAME2; - case 1: return sw?SENSORLOG_FILENAME_WEEK1:SENSORLOG_FILENAME_WEEK2; - case 2: return sw?SENSORLOG_FILENAME_MONTH1:SENSORLOG_FILENAME_MONTH2; - } - return ""; + bool sw = logFileSwitch[log]; + switch (log) { + case 0: + return sw ? SENSORLOG_FILENAME1 : SENSORLOG_FILENAME2; + case 1: + return sw ? SENSORLOG_FILENAME_WEEK1 : SENSORLOG_FILENAME_WEEK2; + case 2: + return sw ? SENSORLOG_FILENAME_MONTH1 : SENSORLOG_FILENAME_MONTH2; + } + return ""; } /** * @brief getlogfile name2 (opposite file) - * - * @param log - * @return const char* + * + * @param log + * @return const char* */ const char *getlogfile2(uint8_t log) { - bool sw = logFileSwitch[log]; - switch (log) { - case 0: return sw?SENSORLOG_FILENAME2:SENSORLOG_FILENAME1; - case 1: return sw?SENSORLOG_FILENAME_WEEK2:SENSORLOG_FILENAME_WEEK1; - case 2: return sw?SENSORLOG_FILENAME_MONTH2:SENSORLOG_FILENAME_MONTH1; - } - return ""; + bool sw = logFileSwitch[log]; + switch (log) { + case 0: + return sw ? SENSORLOG_FILENAME2 : SENSORLOG_FILENAME1; + case 1: + return sw ? SENSORLOG_FILENAME_WEEK2 : SENSORLOG_FILENAME_WEEK1; + case 2: + return sw ? SENSORLOG_FILENAME_MONTH2 : SENSORLOG_FILENAME_MONTH1; + } + return ""; } - void checkLogSwitch(uint8_t log) { - if (logFileSwitch[log] == 0) { // Check file size, use smallest - ulong logFileSize1 = file_size(getlogfile(log)); - ulong logFileSize2 = file_size(getlogfile2(log)); - if (logFileSize1 < logFileSize2) - logFileSwitch[log] = 1; - else - logFileSwitch[log] = 2; - } + if (logFileSwitch[log] == 0) { // Check file size, use smallest + ulong logFileSize1 = file_size(getlogfile(log)); + ulong logFileSize2 = file_size(getlogfile2(log)); + if (logFileSize1 < logFileSize2) + logFileSwitch[log] = 1; + else + logFileSwitch[log] = 2; + } } void checkLogSwitchAfterWrite(uint8_t log) { - ulong size = file_size(getlogfile(log)); - if ((size / SENSORLOG_STORE_SIZE) >= MAX_LOG_SIZE) { // switch logs if max reached - if (logFileSwitch[log] == 1) - logFileSwitch[log] = 2; - else - logFileSwitch[log] = 1; - remove_file(getlogfile(log)); - } + ulong size = file_size(getlogfile(log)); + if ((size / SENSORLOG_STORE_SIZE) >= + MAX_LOG_SIZE) { // switch logs if max reached + if (logFileSwitch[log] == 1) + logFileSwitch[log] = 2; + else + logFileSwitch[log] = 1; + remove_file(getlogfile(log)); + } } bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog) { #if defined(ESP8266) - if (checkDiskFree()) { + if (checkDiskFree()) { #endif - DEBUG_PRINT(F("sensorlog_add ")); - DEBUG_PRINT(log); - checkLogSwitch(log); - file_append_block(getlogfile(log), sensorlog, SENSORLOG_STORE_SIZE); - checkLogSwitchAfterWrite(log); - DEBUG_PRINT(F("=")); - DEBUG_PRINTLN(sensorlog_filesize(log)); - return true; -#if defined(ESP8266) - } - return false; + DEBUG_PRINT(F("sensorlog_add ")); + DEBUG_PRINT(log); + checkLogSwitch(log); + file_append_block(getlogfile(log), sensorlog, SENSORLOG_STORE_SIZE); + checkLogSwitchAfterWrite(log); + DEBUG_PRINT(F("=")); + DEBUG_PRINTLN(sensorlog_filesize(log)); + return true; +#if defined(ESP8266) + } + return false; #endif } bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { - if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { - SensorLog_t sensorlog; - memset(&sensorlog, 0, sizeof(SensorLog_t)); - sensorlog.nr = sensor->nr; - sensorlog.time = time; - sensorlog.native_data = sensor->last_native_data; - sensorlog.data = sensor->last_data; - if (!sensorlog_add(log, &sensorlog)) { - sensor->flags.log = 0; - return false; - } - return true; - } - return false; + if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { + SensorLog_t sensorlog; + memset(&sensorlog, 0, sizeof(SensorLog_t)); + sensorlog.nr = sensor->nr; + sensorlog.time = time; + sensorlog.native_data = sensor->last_native_data; + sensorlog.data = sensor->last_data; + if (!sensorlog_add(log, &sensorlog)) { + sensor->flags.log = 0; + return false; + } + return true; + } + return false; } ulong sensorlog_filesize(uint8_t log) { - //DEBUG_PRINT(F("sensorlog_filesize ")); - checkLogSwitch(log); - ulong size = file_size(getlogfile(log))+file_size(getlogfile2(log)); - //DEBUG_PRINTLN(size); - return size; + // DEBUG_PRINT(F("sensorlog_filesize ")); + checkLogSwitch(log); + ulong size = file_size(getlogfile(log)) + file_size(getlogfile2(log)); + // DEBUG_PRINTLN(size); + return size; } ulong sensorlog_size(uint8_t log) { - //DEBUG_PRINT(F("sensorlog_size ")); - ulong size = sensorlog_filesize(log) / SENSORLOG_STORE_SIZE; - //DEBUG_PRINTLN(size); - return size; + // DEBUG_PRINT(F("sensorlog_size ")); + ulong size = sensorlog_filesize(log) / SENSORLOG_STORE_SIZE; + // DEBUG_PRINTLN(size); + return size; } -void sensorlog_clear_all() { - sensorlog_clear(true, true, true); -} +void sensorlog_clear_all() { sensorlog_clear(true, true, true); } void sensorlog_clear(bool std, bool week, bool month) { - DEBUG_PRINTLN(F("sensorlog_clear ")); - DEBUG_PRINT(std); - DEBUG_PRINT(week); - DEBUG_PRINT(month); - if (std) { - remove_file(SENSORLOG_FILENAME1); - remove_file(SENSORLOG_FILENAME2); - logFileSwitch[LOG_STD] = 1; - } - if (week) { - remove_file(SENSORLOG_FILENAME_WEEK1); - remove_file(SENSORLOG_FILENAME_WEEK2); - logFileSwitch[LOG_WEEK] = 1; - } - if (month) { - remove_file(SENSORLOG_FILENAME_MONTH1); - remove_file(SENSORLOG_FILENAME_MONTH2); - logFileSwitch[LOG_MONTH] = 1; - } -} - -ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, double under, bool use_over, double over) { - SensorLog_t sensorlog; - checkLogSwitch(log); - const char *flast = getlogfile2(log); - const char *fcur = getlogfile(log); - ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; - ulong size2 = size+file_size(fcur) / SENSORLOG_STORE_SIZE; - const char *f; - ulong idxr = 0; - ulong n = 0; - while (idxr < size2) { - ulong idx = idxr; - if (idx >= size) { - idx -= size; - f = fcur; - } else { - f = flast; - } - - ulong result = file_read_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); - if (result == 0) - break; - if (sensorlog.nr == sensorNr) { - boolean found = true; - if (use_under && sensorlog.data > under) - found = false; - if (use_over && sensorlog.data < over) - found = false; - if (found) { - sensorlog.nr = 0; - file_write_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); - n++; - } - } - idxr++; - } - return n; + DEBUG_PRINTLN(F("sensorlog_clear ")); + DEBUG_PRINT(std); + DEBUG_PRINT(week); + DEBUG_PRINT(month); + if (std) { + remove_file(SENSORLOG_FILENAME1); + remove_file(SENSORLOG_FILENAME2); + logFileSwitch[LOG_STD] = 1; + } + if (week) { + remove_file(SENSORLOG_FILENAME_WEEK1); + remove_file(SENSORLOG_FILENAME_WEEK2); + logFileSwitch[LOG_WEEK] = 1; + } + if (month) { + remove_file(SENSORLOG_FILENAME_MONTH1); + remove_file(SENSORLOG_FILENAME_MONTH2); + logFileSwitch[LOG_MONTH] = 1; + } +} + +ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, + double under, bool use_over, double over) { + SensorLog_t sensorlog; + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + ulong size2 = size + file_size(fcur) / SENSORLOG_STORE_SIZE; + const char *f; + ulong idxr = 0; + ulong n = 0; + while (idxr < size2) { + ulong idx = idxr; + if (idx >= size) { + idx -= size; + f = fcur; + } else { + f = flast; + } + + ulong result = file_read_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, + SENSORLOG_STORE_SIZE); + if (result == 0) break; + if (sensorlog.nr > 0 && (sensorlog.nr == sensorNr || sensorNr == 0)) { + boolean found = true; + if (use_under && sensorlog.data > under) found = false; + if (use_over && sensorlog.data < over) found = false; + if (found) { + sensorlog.nr = 0; + file_write_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, + SENSORLOG_STORE_SIZE); + n++; + } + } + idxr++; + } + return n; } - SensorLog_t *sensorlog_load(uint8_t log, ulong idx) { - SensorLog_t *sensorlog = new SensorLog_t; - return sensorlog_load(log, idx, sensorlog); -} - -SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t* sensorlog) { - //DEBUG_PRINTLN(F("sensorlog_load")); - - //Map lower idx to the other log file - checkLogSwitch(log); - const char *flast = getlogfile2(log); - const char *fcur = getlogfile(log); - ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; - const char *f; - if (idx >= size) { - idx -= size; - f = fcur; - } else { - f = flast; - } - - file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); - return sensorlog; -} - -int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t* sensorlog) { - //DEBUG_PRINTLN(F("sensorlog_load")); - - //Map lower idx to the other log file - checkLogSwitch(log); - const char *flast = getlogfile2(log); - const char *fcur = getlogfile(log); - ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; - const char *f; - if (idx >= size) { - idx -= size; - f = fcur; - size = file_size(f) / SENSORLOG_STORE_SIZE; - } else { - f = flast; - } - - if (idx+count > size) - count = size-idx; - if (count <= 0) - return 0; - file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, count * SENSORLOG_STORE_SIZE); - return count; + SensorLog_t *sensorlog = new SensorLog_t; + return sensorlog_load(log, idx, sensorlog); +} + +SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t *sensorlog) { + // DEBUG_PRINTLN(F("sensorlog_load")); + + // Map lower idx to the other log file + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + const char *f; + if (idx >= size) { + idx -= size; + f = fcur; + } else { + f = flast; + } + + file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, + SENSORLOG_STORE_SIZE); + return sensorlog; +} + +int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t *sensorlog) { + // DEBUG_PRINTLN(F("sensorlog_load")); + + // Map lower idx to the other log file + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + const char *f; + if (idx >= size) { + idx -= size; + f = fcur; + size = file_size(f) / SENSORLOG_STORE_SIZE; + } else { + f = flast; + } + + if (idx + count > size) count = size - idx; + if (count <= 0) return 0; + file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, + count * SENSORLOG_STORE_SIZE); + return count; } ulong findLogPosition(uint8_t log, ulong after) { - ulong log_size = sensorlog_size(log); - ulong a = 0; - ulong b = log_size-1; - ulong lastIdx = 0; - SensorLog_t sensorlog; - while (true) { - ulong idx = (b-a)/2+a; - sensorlog_load(log, idx, &sensorlog); - if (sensorlog.time < after) { - a = idx; - } else if (sensorlog.time > after) { - b = idx; - } - if (a >= b || idx == lastIdx) return idx; - lastIdx = idx; - } - return 0; + ulong log_size = sensorlog_size(log); + ulong a = 0; + ulong b = log_size - 1; + ulong lastIdx = 0; + SensorLog_t sensorlog; + while (true) { + ulong idx = (b - a) / 2 + a; + sensorlog_load(log, idx, &sensorlog); + if (sensorlog.time < after) { + a = idx; + } else if (sensorlog.time > after) { + b = idx; + } + if (a >= b || idx == lastIdx) return idx; + lastIdx = idx; + } + return 0; } #if !defined(ARDUINO) /** -* compatibility functions for OSPi: -**/ + * compatibility functions for OSPi: + **/ #define timeSet 0 -int timeStatus() { - return timeSet; -} +int timeStatus() { return timeSet; } void dtostrf(float value, int min_width, int precision, char *txt) { - printf(txt, "%*.*f", min_width, precision, value); + printf(txt, "%*.*f", min_width, precision, value); } void dtostrf(double value, int min_width, int precision, char *txt) { - printf(txt, "%*.*d", min_width, precision, value); + printf(txt, "%*.*d", min_width, precision, value); } #endif @@ -622,1520 +634,1743 @@ static ulong next_month_calc = 0; Calculate week+month Data We store only the average value of 6 hours utc **/ -void calc_sensorlogs() -{ - if (!sensors || timeStatus() != timeSet) - return; - - ulong log_size = sensorlog_size(LOG_STD); - if (log_size == 0) - return; - - SensorLog_t *sensorlog = NULL; - - time_t time = os.now_tz(); - time_t last_day = time; - - if (time >= next_week_calc) { - DEBUG_PRINTLN(F("calc_sensorlogs WEEK start")); - sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); - ulong size = sensorlog_size(LOG_WEEK); - if (size == 0) { - sensorlog_load(LOG_STD, 0, sensorlog); - last_day = sensorlog->time; - } - else - { - sensorlog_load(LOG_WEEK, size - 1, sensorlog); // last record - last_day = sensorlog->time + CALCRANGE_WEEK; // Skip last Range - } - time_t fromdate = (last_day / CALCRANGE_WEEK) * CALCRANGE_WEEK; - time_t todate = fromdate + CALCRANGE_WEEK; - - // 4 blocks per day - - while (todate < time) { - ulong startidx = findLogPosition(LOG_STD, fromdate); - Sensor_t *sensor = sensors; - while (sensor) { - if (sensor->flags.enable && sensor->flags.log) { - ulong idx = startidx; - double data = 0; - ulong n = 0; - bool done = false; - while (!done) { - int sn = sensorlog_load2(LOG_STD, idx, BLOCKSIZE, sensorlog); - if (sn <= 0) break; - for (int i = 0; i < sn; i++) { - idx++; - if (sensorlog[i].time >= todate) { - done = true; - break; - } - if (sensorlog[i].nr == sensor->nr) { - data += sensorlog[i].data; - n++; - } - } - } - if (n > 0) - { - sensorlog->nr = sensor->nr; - sensorlog->time = fromdate; - sensorlog->data = data / (double)n; - sensorlog->native_data = 0; - sensorlog_add(LOG_WEEK, sensorlog); - } - } - sensor = sensor->next; - } - fromdate += CALCRANGE_WEEK; - todate += CALCRANGE_WEEK; - } - next_week_calc = todate; - DEBUG_PRINTLN(F("calc_sensorlogs WEEK end")); - } - - if (time >= next_month_calc) { - - log_size = sensorlog_size(LOG_WEEK); - if (log_size <= 0) { - if (sensorlog) - free(sensorlog); - return; - } - if (!sensorlog) - sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); - - DEBUG_PRINTLN(F("calc_sensorlogs MONTH start")); - ulong size = sensorlog_size(LOG_MONTH); - if (size == 0) - { - sensorlog_load(LOG_WEEK, 0, sensorlog); - last_day = sensorlog->time; - } - else - { - sensorlog_load(LOG_MONTH, size - 1, sensorlog); // last record - last_day = sensorlog->time + CALCRANGE_MONTH; // Skip last Range - } - time_t fromdate = (last_day / CALCRANGE_MONTH) * CALCRANGE_MONTH; - time_t todate = fromdate + CALCRANGE_MONTH; - // 4 blocks per day - - while (todate < time) - { - ulong startidx = findLogPosition(LOG_WEEK, fromdate); - Sensor_t *sensor = sensors; - while (sensor) - { - if (sensor->flags.enable && sensor->flags.log) - { - ulong idx = startidx; - double data = 0; - ulong n = 0; - bool done = false; - while (!done) { - int sn = sensorlog_load2(LOG_WEEK, idx, BLOCKSIZE, sensorlog); - if (sn <= 0) break; - for (int i = 0; i < sn; i++) { - idx++; - if (sensorlog[i].time >= todate) { - done = true; - break; - } - if (sensorlog[i].nr == sensor->nr) { - data += sensorlog[i].data; - n++; - } - } - } - if (n > 0) - { - sensorlog->nr = sensor->nr; - sensorlog->time = fromdate; - sensorlog->data = data / (double)n; - sensorlog->native_data = 0; - sensorlog_add(LOG_MONTH, sensorlog); - } - } - sensor = sensor->next; - } - fromdate += CALCRANGE_MONTH; - todate += CALCRANGE_MONTH; - } - next_month_calc = todate; - DEBUG_PRINTLN(F("calc_sensorlogs MONTH end")); - } - if (sensorlog) - free(sensorlog); -} - -void sensor_remote_http_callback(char*) { -//unused +void calc_sensorlogs() { + if (!sensors || timeStatus() != timeSet) return; + + ulong log_size = sensorlog_size(LOG_STD); + if (log_size == 0) return; + + SensorLog_t *sensorlog = NULL; + + time_t time = os.now_tz(); + time_t last_day = time; + + if (time >= next_week_calc) { + DEBUG_PRINTLN(F("calc_sensorlogs WEEK start")); + sensorlog = (SensorLog_t *)malloc(sizeof(SensorLog_t) * BLOCKSIZE); + ulong size = sensorlog_size(LOG_WEEK); + if (size == 0) { + sensorlog_load(LOG_STD, 0, sensorlog); + last_day = sensorlog->time; + } else { + sensorlog_load(LOG_WEEK, size - 1, sensorlog); // last record + last_day = sensorlog->time + CALCRANGE_WEEK; // Skip last Range + } + time_t fromdate = (last_day / CALCRANGE_WEEK) * CALCRANGE_WEEK; + time_t todate = fromdate + CALCRANGE_WEEK; + + // 4 blocks per day + + while (todate < time) { + ulong startidx = findLogPosition(LOG_STD, fromdate); + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->flags.enable && sensor->flags.log) { + ulong idx = startidx; + double data = 0; + ulong n = 0; + bool done = false; + while (!done) { + int sn = sensorlog_load2(LOG_STD, idx, BLOCKSIZE, sensorlog); + if (sn <= 0) break; + for (int i = 0; i < sn; i++) { + idx++; + if (sensorlog[i].time >= todate) { + done = true; + break; + } + if (sensorlog[i].nr == sensor->nr) { + data += sensorlog[i].data; + n++; + } + } + } + if (n > 0) { + sensorlog->nr = sensor->nr; + sensorlog->time = fromdate; + sensorlog->data = data / (double)n; + sensorlog->native_data = 0; + sensorlog_add(LOG_WEEK, sensorlog); + } + } + sensor = sensor->next; + } + fromdate += CALCRANGE_WEEK; + todate += CALCRANGE_WEEK; + } + next_week_calc = todate; + DEBUG_PRINTLN(F("calc_sensorlogs WEEK end")); + } + + if (time >= next_month_calc) { + log_size = sensorlog_size(LOG_WEEK); + if (log_size <= 0) { + if (sensorlog) free(sensorlog); + return; + } + if (!sensorlog) + sensorlog = (SensorLog_t *)malloc(sizeof(SensorLog_t) * BLOCKSIZE); + + DEBUG_PRINTLN(F("calc_sensorlogs MONTH start")); + ulong size = sensorlog_size(LOG_MONTH); + if (size == 0) { + sensorlog_load(LOG_WEEK, 0, sensorlog); + last_day = sensorlog->time; + } else { + sensorlog_load(LOG_MONTH, size - 1, sensorlog); // last record + last_day = sensorlog->time + CALCRANGE_MONTH; // Skip last Range + } + time_t fromdate = (last_day / CALCRANGE_MONTH) * CALCRANGE_MONTH; + time_t todate = fromdate + CALCRANGE_MONTH; + // 4 blocks per day + + while (todate < time) { + ulong startidx = findLogPosition(LOG_WEEK, fromdate); + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->flags.enable && sensor->flags.log) { + ulong idx = startidx; + double data = 0; + ulong n = 0; + bool done = false; + while (!done) { + int sn = sensorlog_load2(LOG_WEEK, idx, BLOCKSIZE, sensorlog); + if (sn <= 0) break; + for (int i = 0; i < sn; i++) { + idx++; + if (sensorlog[i].time >= todate) { + done = true; + break; + } + if (sensorlog[i].nr == sensor->nr) { + data += sensorlog[i].data; + n++; + } + } + } + if (n > 0) { + sensorlog->nr = sensor->nr; + sensorlog->time = fromdate; + sensorlog->data = data / (double)n; + sensorlog->native_data = 0; + sensorlog_add(LOG_MONTH, sensorlog); + } + } + sensor = sensor->next; + } + fromdate += CALCRANGE_MONTH; + todate += CALCRANGE_MONTH; + } + next_month_calc = todate; + DEBUG_PRINTLN(F("calc_sensorlogs MONTH end")); + } + if (sensorlog) free(sensorlog); +} + +void sensor_remote_http_callback(char *) { + // unused } void push_message(Sensor_t *sensor) { - if (!sensor || !sensor->last_read) - return; - - static char topic[TMP_BUFFER_SIZE]; - static char payload[TMP_BUFFER_SIZE]; - char* postval = tmp_buffer; - - if (os.mqtt.enabled()) { - DEBUG_PRINTLN("push mqtt1"); - os.sopt_load(SOPT_DEVICE_NAME, topic); - strncat_P(topic, PSTR("/analogsensor/"), sizeof(topic)-1); - strncat(topic, sensor->name, sizeof(topic)-1); - sprintf_P(payload, PSTR("{\"nr\":%u,\"type\":%u,\"data_ok\":%u,\"time\":%u,\"value\":%d.%02d,\"unit\":\"%s\"}"), - sensor->nr, sensor->type, sensor->flags.data_ok, sensor->last_read, (int)sensor->last_data, (int)(sensor->last_data*100)%100, getSensorUnit(sensor)); - - if (!os.mqtt.connected()) - os.mqtt.reconnect(); - os.mqtt.publish(topic, payload); - DEBUG_PRINTLN("push mqtt2"); - } - if (os.iopts[IOPT_IFTTT_ENABLE]) { - DEBUG_PRINTLN("push ifttt"); - strcpy_P(postval, PSTR("{\"value1\":\"On site [")); - os.sopt_load(SOPT_DEVICE_NAME, postval+strlen(postval)); - strcat_P(postval, PSTR("], ")); - - strcat_P(postval, PSTR("analogsensor ")); - sprintf_P(postval+strlen(postval), PSTR("nr: %u, type: %u, data_ok: %u, time: %u, value: %d.%02d, unit: %s"), - sensor->nr, sensor->type, sensor->flags.data_ok, sensor->last_read, (int)sensor->last_data, (int)(sensor->last_data*100)%100, getSensorUnit(sensor)); - strcat_P(postval, PSTR("\"}")); - - //char postBuffer[1500]; - BufferFiller bf = ether_buffer; - bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" - "Host: $S\r\n" - "Accept: */*\r\n" - "Content-Length: $D\r\n" - "Content-Type: application/json\r\n\r\n$S"), - SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); - - os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, sensor_remote_http_callback); - DEBUG_PRINTLN("push ifttt2"); - } + if (!sensor || !sensor->last_read) return; + + static char topic[TMP_BUFFER_SIZE]; + static char payload[TMP_BUFFER_SIZE]; + char *postval = tmp_buffer; + + if (os.mqtt.enabled()) { + DEBUG_PRINTLN("push mqtt1"); + os.sopt_load(SOPT_DEVICE_NAME, topic); + strncat_P(topic, PSTR("/analogsensor/"), sizeof(topic) - 1); + strncat(topic, sensor->name, sizeof(topic) - 1); + sprintf_P(payload, + PSTR("{\"nr\":%u,\"type\":%u,\"data_ok\":%u,\"time\":%u," + "\"value\":%d.%02d,\"unit\":\"%s\"}"), + sensor->nr, sensor->type, sensor->flags.data_ok, + sensor->last_read, (int)sensor->last_data, + (int)(sensor->last_data * 100) % 100, getSensorUnit(sensor)); + + if (!os.mqtt.connected()) os.mqtt.reconnect(); + os.mqtt.publish(topic, payload); + DEBUG_PRINTLN("push mqtt2"); + } + if (os.iopts[IOPT_IFTTT_ENABLE]) { + DEBUG_PRINTLN("push ifttt"); + strcpy_P(postval, PSTR("{\"value1\":\"On site [")); + os.sopt_load(SOPT_DEVICE_NAME, postval + strlen(postval)); + strcat_P(postval, PSTR("], ")); + + strcat_P(postval, PSTR("analogsensor ")); + sprintf_P(postval + strlen(postval), + PSTR("nr: %u, type: %u, data_ok: %u, time: %u, value: %d.%02d, " + "unit: %s"), + sensor->nr, sensor->type, sensor->flags.data_ok, + sensor->last_read, (int)sensor->last_data, + (int)(sensor->last_data * 100) % 100, getSensorUnit(sensor)); + strcat_P(postval, PSTR("\"}")); + + // char postBuffer[1500]; + BufferFiller bf = ether_buffer; + bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" + "Host: $S\r\n" + "Accept: */*\r\n" + "Content-Length: $D\r\n" + "Content-Type: application/json\r\n\r\n$S"), + SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); + + os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, + sensor_remote_http_callback); + DEBUG_PRINTLN("push ifttt2"); + } } void read_all_sensors(boolean online) { - //DEBUG_PRINTLN(F("read_all_sensors")); - if (!sensors) - return; - - ulong time = os.now_tz(); - - if (time < os.powerup_lasttime+30) return; //wait 30s before first sensor read - - //When we run out of time, skip some sensors and continue on next loop - static Sensor_t *sensor = NULL; - if (sensor == NULL) - sensor = sensors; - - while (sensor) { - if (time >= sensor->last_read + sensor->read_interval || sensor->repeat_read) { - if (online || (sensor->ip == 0 && sensor->type != SENSOR_MQTT)) { - int result = read_sensor(sensor, time); - if (result == HTTP_RQT_SUCCESS) { - sensorlog_add(LOG_STD, sensor, time); - push_message(sensor); - } else if (result == HTTP_RQT_TIMEOUT) { - //delay next read on timeout: - sensor->last_read = time + max((uint)60,sensor->read_interval); - sensor->repeat_read = 0; - DEBUG_PRINTF("Delayed1: %s", sensor->name); - } else if (result == HTTP_RQT_CONNECT_ERR) { - //delay next read on error: - sensor->last_read = time + max((uint)60,sensor->read_interval); - sensor->repeat_read = 0; - DEBUG_PRINTF("Delayed2: %s", sensor->name); - } - ulong passed = os.now_tz() - time; - if (passed > MAX_SENSOR_READ_TIME) { - sensor = sensor->next; - break; - } - } - } - sensor = sensor->next; - } - sensor_update_groups(); - calc_sensorlogs(); - if (time - last_save_time > 3600) //1h - sensor_save(); + // DEBUG_PRINTLN(F("read_all_sensors")); + if (!sensors) return; + + ulong time = os.now_tz(); + +#ifdef ENABLE_DEBUG + if (time < os.powerup_lasttime + 3) +#else + if (time < os.powerup_lasttime + 30) +#endif + return; // wait 30s before first sensor read + + // When we run out of time, skip some sensors and continue on next loop + static Sensor_t *sensor = NULL; + if (sensor == NULL) sensor = sensors; + + while (sensor) { + if (time >= sensor->last_read + sensor->read_interval || + sensor->repeat_read) { + if (online || (sensor->ip == 0 && sensor->type != SENSOR_MQTT)) { + int result = read_sensor(sensor, time); + if (result == HTTP_RQT_SUCCESS) { + sensorlog_add(LOG_STD, sensor, time); + push_message(sensor); + } else if (result == HTTP_RQT_TIMEOUT) { + // delay next read on timeout: + sensor->last_read = time + max((uint)60, sensor->read_interval); + sensor->repeat_read = 0; + DEBUG_PRINTF("Delayed1: %s", sensor->name); + } else if (result == HTTP_RQT_CONNECT_ERR) { + // delay next read on error: + sensor->last_read = time + max((uint)60, sensor->read_interval); + sensor->repeat_read = 0; + DEBUG_PRINTF("Delayed2: %s", sensor->name); + } + ulong passed = os.now_tz() - time; + if (passed > MAX_SENSOR_READ_TIME) { + sensor = sensor->next; + break; + } + } + } + sensor = sensor->next; + } + sensor_update_groups(); + calc_sensorlogs(); + if (time - last_save_time > 3600) // 1h + sensor_save(); } #if defined(ARDUINO) #if defined(ESP8266) /** * Read ESP8296 ADS1115 sensors -*/ + */ int read_sensor_adc(Sensor_t *sensor, ulong time) { - DEBUG_PRINTLN(F("read_sensor_adc")); - if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - if (sensor->id >= 16) return HTTP_RQT_NOT_RECEIVED; - //Init + Detect: - - if (sensor->id < 8 && ((asb_detected_boards & ASB_BOARD1) == 0)) - return HTTP_RQT_NOT_RECEIVED; - if (sensor->id >= 8 && sensor->id < 16 && ((asb_detected_boards & ASB_BOARD2) == 0)) - return HTTP_RQT_NOT_RECEIVED; - - int port = ASB_BOARD_ADDR1a + sensor->id / 4; - int id = sensor->id % 4; - - ADS1115 adc(port); - if (!adc.begin()) - return HTTP_RQT_NOT_RECEIVED; - sensor->repeat_native += adc.readADC(id); - if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && time < sensor->last_read + sensor->read_interval) - return HTTP_RQT_NOT_RECEIVED; - - uint64_t avgValue = sensor->repeat_native/sensor->repeat_read; - - sensor->repeat_native = avgValue; - sensor->repeat_data = 0; - sensor->repeat_read = 1; //read continously - - sensor->last_native_data = avgValue; - sensor->last_data = adc.toVoltage(sensor->last_native_data); - double v = sensor->last_data; - - switch(sensor->type) { - case SENSOR_SMT50_MOIS: // SMT50 VWC [%] = (U * 50) : 3 - sensor->last_data = (v * 50.0) / 3.0; - break; - case SENSOR_SMT50_TEMP: // SMT50 T [°C] = (U – 0,5) * 100 - sensor->last_data = (v - 0.5) * 100.0; - break; - case SENSOR_ANALOG_EXTENSION_BOARD_P: // 0..3,3V -> 0..100% - sensor->last_data = v * 100.0 / 3.3; - if (sensor->last_data < 0) - sensor->last_data = 0; - else if (sensor->last_data > 100) - sensor->last_data = 100; - break; - case SENSOR_SMT100_ANALOG_MOIS: // 0..3V -> 0..100% - sensor->last_data = v * 100.0 / 3; - break; - case SENSOR_SMT100_ANALOG_TEMP: // 0..3V -> -40°C..60°C - sensor->last_data = v * 100.0 / 3 - 40; - break; - - case SENSOR_VH400: //http://vegetronix.com/Products/VH400/VH400-Piecewise-Curve - if (v <= 1.1) // 0 to 1.1V VWC= 10*V-1 - sensor->last_data = 10 * v - 1; - else if (v < 1.3) // 1.1V to 1.3V VWC= 25*V- 17.5 - sensor->last_data = 25 * v - 17.5; - else if (v < 1.82) // 1.3V to 1.82V VWC= 48.08*V- 47.5 - sensor->last_data = 48.08 * v - 47.5; - else if (v < 2.2) // 1.82V to 2.2V VWC= 26.32*V- 7.89 - sensor->last_data = 26.32 * v - 7.89; - else // 2.2V - 3.0V VWC= 62.5*V - 87.5 - sensor->last_data = 62.5 * v - 87.5; - break; - case SENSOR_THERM200: //http://vegetronix.com/Products/THERM200/ - sensor->last_data = v * 41.67 - 40; - break; - case SENSOR_AQUAPLUMB: //http://vegetronix.com/Products/AquaPlumb/ - sensor->last_data = v * 100.0 / 3.0; // 0..3V -> 0..100% - if (sensor->last_data < 0) - sensor->last_data = 0; - else if (sensor->last_data > 100) - sensor->last_data = 100; - break; - case SENSOR_USERDEF: //User defined sensor - v -= (double)sensor->offset_mv/1000; // adjust zero-point offset in millivolt - if (sensor->factor && sensor->divider) - v *= (double)sensor->factor / (double)sensor->divider; - else if (sensor->divider) - v /= sensor->divider; - else if (sensor->factor) - v *= sensor->factor; - sensor->last_data = v + sensor->offset2/100; - break; - } - - sensor->flags.data_ok = true; - sensor->last_read = time; - - DEBUG_PRINT(F("adc sensor values: ")); - DEBUG_PRINT(sensor->last_native_data); - DEBUG_PRINT(","); - DEBUG_PRINTLN(sensor->last_data); - - return HTTP_RQT_SUCCESS; + DEBUG_PRINTLN(F("read_sensor_adc")); + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + if (sensor->id >= 16) return HTTP_RQT_NOT_RECEIVED; + // Init + Detect: + + if (sensor->id < 8 && ((asb_detected_boards & ASB_BOARD1) == 0)) + return HTTP_RQT_NOT_RECEIVED; + if (sensor->id >= 8 && sensor->id < 16 && + ((asb_detected_boards & ASB_BOARD2) == 0)) + return HTTP_RQT_NOT_RECEIVED; + + int port = ASB_BOARD_ADDR1a + sensor->id / 4; + int id = sensor->id % 4; + + // unsigned long startTime = millis(); + ADS1115 adc(port); + if (!adc.begin()) { + DEBUG_PRINTLN(F("no asb board?!?")); + return HTTP_RQT_NOT_RECEIVED; + } + // unsigned long endTime = millis(); + // DEBUG_PRINTF("t=%lu ms\n", endTime-startTime); + + // startTime = millis(); + sensor->repeat_native += adc.readADC(id); + // endTime = millis(); + // DEBUG_PRINTF("t=%lu ms\n", endTime-startTime); + + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && + time < sensor->last_read + sensor->read_interval) + return HTTP_RQT_NOT_RECEIVED; + + uint64_t avgValue = sensor->repeat_native / sensor->repeat_read; + + sensor->repeat_native = avgValue; + sensor->repeat_data = 0; + sensor->repeat_read = 1; // read continously + + sensor->last_native_data = avgValue; + sensor->last_data = adc.toVoltage(sensor->last_native_data); + double v = sensor->last_data; + + switch (sensor->type) { + case SENSOR_SMT50_MOIS: // SMT50 VWC [%] = (U * 50) : 3 + sensor->last_data = (v * 50.0) / 3.0; + break; + case SENSOR_SMT50_TEMP: // SMT50 T [°C] = (U – 0,5) * 100 + sensor->last_data = (v - 0.5) * 100.0; + break; + case SENSOR_ANALOG_EXTENSION_BOARD_P: // 0..3,3V -> 0..100% + sensor->last_data = v * 100.0 / 3.3; + if (sensor->last_data < 0) + sensor->last_data = 0; + else if (sensor->last_data > 100) + sensor->last_data = 100; + break; + case SENSOR_SMT100_ANALOG_MOIS: // 0..3V -> 0..100% + sensor->last_data = v * 100.0 / 3; + break; + case SENSOR_SMT100_ANALOG_TEMP: // 0..3V -> -40°C..60°C + sensor->last_data = v * 100.0 / 3 - 40; + break; + + case SENSOR_VH400: // http://vegetronix.com/Products/VH400/VH400-Piecewise-Curve + if (v <= 1.1) // 0 to 1.1V VWC= 10*V-1 + sensor->last_data = 10 * v - 1; + else if (v < 1.3) // 1.1V to 1.3V VWC= 25*V- 17.5 + sensor->last_data = 25 * v - 17.5; + else if (v < 1.82) // 1.3V to 1.82V VWC= 48.08*V- 47.5 + sensor->last_data = 48.08 * v - 47.5; + else if (v < 2.2) // 1.82V to 2.2V VWC= 26.32*V- 7.89 + sensor->last_data = 26.32 * v - 7.89; + else // 2.2V - 3.0V VWC= 62.5*V - 87.5 + sensor->last_data = 62.5 * v - 87.5; + break; + case SENSOR_THERM200: // http://vegetronix.com/Products/THERM200/ + sensor->last_data = v * 41.67 - 40; + break; + case SENSOR_AQUAPLUMB: // http://vegetronix.com/Products/AquaPlumb/ + sensor->last_data = v * 100.0 / 3.0; // 0..3V -> 0..100% + if (sensor->last_data < 0) + sensor->last_data = 0; + else if (sensor->last_data > 100) + sensor->last_data = 100; + break; + case SENSOR_USERDEF: // User defined sensor + v -= (double)sensor->offset_mv / + 1000; // adjust zero-point offset in millivolt + if (sensor->factor && sensor->divider) + v *= (double)sensor->factor / (double)sensor->divider; + else if (sensor->divider) + v /= sensor->divider; + else if (sensor->factor) + v *= sensor->factor; + sensor->last_data = v + sensor->offset2 / 100; + break; + } + + sensor->flags.data_ok = true; + sensor->last_read = time; + + DEBUG_PRINT(F("adc sensor values: ")); + DEBUG_PRINT(sensor->last_native_data); + DEBUG_PRINT(","); + DEBUG_PRINTLN(sensor->last_data); + + return HTTP_RQT_SUCCESS; } #endif #endif bool extract(char *s, char *buf, int maxlen) { - s = strstr(s, ":"); - if (!s) return false; - s++; - while (*s == ' ') s++; //skip spaces - char *e = strstr(s, ","); - char *f = strstr(s, "}"); - if (!e && !f) return false; - if (f && f < e) e = f; - int l = e-s; - if (l < 1 || l > maxlen) - return false; - strncpy(buf, s, l); - buf[l] = 0; - //DEBUG_PRINTLN(buf); - return true; + s = strstr(s, ":"); + if (!s) return false; + s++; + while (*s == ' ') s++; // skip spaces + char *e = strstr(s, ","); + char *f = strstr(s, "}"); + if (!e && !f) return false; + if (f && f < e) e = f; + int l = e - s; + if (l < 1 || l > maxlen) return false; + strncpy(buf, s, l); + buf[l] = 0; + // DEBUG_PRINTLN(buf); + return true; } int read_sensor_http(Sensor_t *sensor, ulong time) { #if defined(ESP8266) - IPAddress _ip(sensor->ip); - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + IPAddress _ip(sensor->ip); + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else - byte ip[4]; - ip[3] = (byte)((sensor->ip >> 24) &0xFF); - ip[2] = (byte)((sensor->ip >> 16) &0xFF); - ip[1] = (byte)((sensor->ip >> 8) &0xFF); - ip[0] = (byte)((sensor->ip &0xFF)); + byte ip[4]; + ip[3] = (byte)((sensor->ip >> 24) & 0xFF); + ip[2] = (byte)((sensor->ip >> 16) & 0xFF); + ip[1] = (byte)((sensor->ip >> 8) & 0xFF); + ip[0] = (byte)((sensor->ip & 0xFF)); #endif - DEBUG_PRINTLN(F("read_sensor_http")); - - char *p = tmp_buffer; - BufferFiller bf = p; - - bf.emit_p(PSTR("GET /sg?pw=$O&nr=$D"), - SOPT_PASSWORD, sensor->id); - bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), - ip[0],ip[1],ip[2],ip[3]); - - DEBUG_PRINTLN(p); - - char server[20]; - sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - - int res = os.send_http_request(server, sensor->port, p, NULL, 2000, 1); //timeout=2000, tries=1 - if (res == HTTP_RQT_SUCCESS) { - DEBUG_PRINTLN("Send Ok"); - p = ether_buffer; - DEBUG_PRINTLN(p); - - sensor->last_read = time; - - char buf[20]; - char *s = strstr(p, "\"nativedata\":"); - if (s && extract(s, buf, sizeof(buf))) { - sensor->last_native_data = strtoul(buf, NULL, 0); - } - s = strstr(p, "\"data\":"); - if (s && extract(s, buf, sizeof(buf))) { - double value = -1; - int ok = sscanf(buf, "%lf", &value); - if (ok && (value != sensor->last_data || !sensor->flags.data_ok || time-sensor->last_read > 6000)) { - sensor->last_data = value; - sensor->flags.data_ok = true; - } else { - return HTTP_RQT_NOT_RECEIVED; - } - } - s = strstr(p, "\"unitid\":"); - if (s && extract(s, buf, sizeof(buf))) { - sensor->unitid = atoi(buf); - sensor->assigned_unitid = sensor->unitid; - } - s = strstr(p, "\"unit\":"); - if (s && extract(s, buf, sizeof(buf))) { - urlDecodeAndUnescape(buf); - strncpy(sensor->userdef_unit, buf, sizeof(sensor->userdef_unit)-1); - } - s = strstr(p, "\"last\":"); - if (s && extract(s, buf, sizeof(buf))) { - ulong last = strtoul(buf, NULL, 0); - if (last == 0 || last == sensor->last_read) { - return HTTP_RQT_NOT_RECEIVED; - } else { - sensor->last_read = last; - } - } - - return HTTP_RQT_SUCCESS; - } - return res; + DEBUG_PRINTLN(F("read_sensor_http")); + + char *p = tmp_buffer; + BufferFiller bf = p; + + bf.emit_p(PSTR("GET /sg?pw=$O&nr=$D"), SOPT_PASSWORD, sensor->id); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0], ip[1], ip[2], + ip[3]); + + DEBUG_PRINTLN(p); + + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + int res = os.send_http_request(server, sensor->port, p, NULL, 2000, + 1); // timeout=2000, tries=1 + if (res == HTTP_RQT_SUCCESS) { + DEBUG_PRINTLN("Send Ok"); + p = ether_buffer; + DEBUG_PRINTLN(p); + + sensor->last_read = time; + + char buf[20]; + char *s = strstr(p, "\"nativedata\":"); + if (s && extract(s, buf, sizeof(buf))) { + sensor->last_native_data = strtoul(buf, NULL, 0); + } + s = strstr(p, "\"data\":"); + if (s && extract(s, buf, sizeof(buf))) { + double value = -1; + int ok = sscanf(buf, "%lf", &value); + if (ok && (value != sensor->last_data || !sensor->flags.data_ok || + time - sensor->last_read > 6000)) { + sensor->last_data = value; + sensor->flags.data_ok = true; + } else { + return HTTP_RQT_NOT_RECEIVED; + } + } + s = strstr(p, "\"unitid\":"); + if (s && extract(s, buf, sizeof(buf))) { + sensor->unitid = atoi(buf); + sensor->assigned_unitid = sensor->unitid; + } + s = strstr(p, "\"unit\":"); + if (s && extract(s, buf, sizeof(buf))) { + urlDecodeAndUnescape(buf); + strncpy(sensor->userdef_unit, buf, sizeof(sensor->userdef_unit) - 1); + } + s = strstr(p, "\"last\":"); + if (s && extract(s, buf, sizeof(buf))) { + ulong last = strtoul(buf, NULL, 0); + if (last == 0 || last == sensor->last_read) { + return HTTP_RQT_NOT_RECEIVED; + } else { + sensor->last_read = last; + } + } + + return HTTP_RQT_SUCCESS; + } + return res; } +#if defined(ESP8266) +/** + * @brief Truebner RS485 Interface + * + * @param sensor + * @return int + */ +int read_sensor_i2c(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_i2c")); + if ((asb_detected_boards & RS485_TRUEBNER) == 0) return HTTP_RQT_NOT_RECEIVED; + + if (i2c_rs485_allocated > 0 && i2c_rs485_allocated != sensor->nr) { + sensor->repeat_read = 1000; + DEBUG_PRINT(F("cant' read, allocated by sensor ")); + DEBUG_PRINTLN(i2c_rs485_allocated); + return HTTP_RQT_NOT_RECEIVED; + } + + DEBUG_PRINTLN(F("read_sensor_i2c: check-ok")); + + if (sensor->repeat_read == 0 || sensor->repeat_read == 1000) { + uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 + : sensor->type == SENSOR_SMT100_MOIS ? 0x01 + : 0x02; + Wire.beginTransmission(RS485_TRUEBNER_ADDR); + Wire.write((uint8_t)sensor->id); + Wire.write(type); + if (Wire.endTransmission() == 0) { + DEBUG_PRINTF("read_sensor_i2c: request send: %d - %d\n", sensor->id, + type); + sensor->repeat_read = 1; + i2c_rs485_allocated = sensor->nr; + } + return HTTP_RQT_NOT_RECEIVED; + // delay(500); + } + + if (Wire.requestFrom((uint8_t)RS485_TRUEBNER_ADDR, (size_t)3, true)) { + // read the incoming bytes: + uint8_t addr = Wire.read(); + uint8_t low_byte = Wire.read(); + uint8_t high_byte = Wire.read(); + if (addr == sensor->id) { + // BUG FIX: Double read + if (sensor->repeat_read == 1) { + sensor->repeat_read++; + uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 + : sensor->type == SENSOR_SMT100_MOIS ? 0x01 + : 0x02; + Wire.beginTransmission(RS485_TRUEBNER_ADDR); + Wire.write((uint8_t)sensor->id); + Wire.write(type); + Wire.endTransmission(); + return HTTP_RQT_NOT_RECEIVED; + } + + uint16_t data = (high_byte << 8) | low_byte; + DEBUG_PRINTF("read_sensor_i2c: result: %d - %d (%d %d)\n", sensor->id, + data, low_byte, high_byte); + double value = + sensor->type == SENSOR_SMT100_TEMP + ? (data / 100.0) - 100.0 + : (sensor->type == SENSOR_SMT100_MOIS ? data / 100.0 : data); + sensor->last_native_data = data; + sensor->last_data = value; + DEBUG_PRINTLN(sensor->last_data); + + sensor->flags.data_ok = true; + + sensor->repeat_read = 0; + i2c_rs485_allocated = 0; + return HTTP_RQT_SUCCESS; + } + } + + sensor->repeat_read++; + if (sensor->repeat_read > 4) { // timeout + sensor->repeat_read = 0; + sensor->flags.data_ok = false; + i2c_rs485_allocated = 0; + DEBUG_PRINTLN(F("read_sensor_i2c: timeout")); + } + DEBUG_PRINTLN(F("read_sensor_i2c: exit")); + return HTTP_RQT_NOT_RECEIVED; +} + +#endif + /** * Read ip connected sensors -*/ + */ int read_sensor_ip(Sensor_t *sensor) { #if defined(ARDUINO) - Client *client; - #if defined(ESP8266) - WiFiClient wifiClient; - client = &wifiClient; - #else - EthernetClient etherClient; - client = ðerClient; - #endif + Client *client; +#if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; +#else + EthernetClient etherClient; + client = ðerClient; +#endif #else - EthernetClient etherClient; - EthernetClient *client = ðerClient; + EthernetClient etherClient; + EthernetClient *client = ðerClient; #endif - sensor->flags.data_ok = false; - if (!sensor->ip || !sensor->port) { - sensor->flags.enable = false; - return HTTP_RQT_CONNECT_ERR; - } + sensor->flags.data_ok = false; + if (!sensor->ip || !sensor->port) { + sensor->flags.enable = false; + return HTTP_RQT_CONNECT_ERR; + } #if defined(ARDUINO) - IPAddress _ip(sensor->ip); - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + IPAddress _ip(sensor->ip); + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else - byte ip[4]; - ip[3] = (byte)((sensor->ip >> 24) &0xFF); - ip[2] = (byte)((sensor->ip >> 16) &0xFF); - ip[1] = (byte)((sensor->ip >> 8) &0xFF); - ip[0] = (byte)((sensor->ip &0xFF)); + byte ip[4]; + ip[3] = (byte)((sensor->ip >> 24) & 0xFF); + ip[2] = (byte)((sensor->ip >> 16) & 0xFF); + ip[1] = (byte)((sensor->ip >> 8) & 0xFF); + ip[0] = (byte)((sensor->ip & 0xFF)); #endif - char server[20]; - sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); #if defined(ESP8266) - client->setTimeout(200); - if(!client->connect(server, sensor->port)) { + client->setTimeout(200); + if (!client->connect(server, sensor->port)) { #else - struct hostent *host = gethostbyname(server); - if (!host) { return HTTP_RQT_CONNECT_ERR; } - if(!client->connect((uint8_t*)host->h_addr, sensor->port)) { + struct hostent *host = gethostbyname(server); + if (!host) { + return HTTP_RQT_CONNECT_ERR; + } + if (!client->connect((uint8_t *)host->h_addr, sensor->port)) { #endif - DEBUG_PRINT(server); - DEBUG_PRINT(":"); - DEBUG_PRINT(sensor->port); - DEBUG_PRINT(" "); - DEBUG_PRINTLN(F("failed.")); - client->stop(); - return HTTP_RQT_TIMEOUT; - } - - uint8_t buffer[20]; - - //https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/ - - if (modbusTcpId >= 0xFFFE) - modbusTcpId = 1; - else - modbusTcpId++; - - buffer[0] = (0xFF00&modbusTcpId) >> 8; - buffer[1] = (0x00FF&modbusTcpId); - buffer[2] = 0; - buffer[3] = 0; - buffer[4] = 0; - buffer[5] = 6; //len - - switch(sensor->type) - { - case SENSOR_SMT100_MODBUS_RTU_MOIS: - buffer[6] = sensor->id; //Modbus ID - buffer[7] = 0x03; //Read Holding Registers - buffer[8] = 0x00; - buffer[9] = 0x01; //soil moisture is at address 0x0001 - buffer[10] = 0x00; - buffer[11] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) - break; - - case SENSOR_SMT100_MODBUS_RTU_TEMP: - buffer[6] = sensor->id; - buffer[7] = 0x03; //Read Holding Registers - buffer[8] = 0x00; - buffer[9] = 0x00; //temperature is at address 0x0000 - buffer[10] = 0x00; - buffer[11] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) - break; - - default: - client->stop(); - return HTTP_RQT_NOT_RECEIVED; - } - - client->write(buffer, 12); + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINT(sensor->port); + DEBUG_PRINT(" "); + DEBUG_PRINTLN(F("failed.")); + client->stop(); + return HTTP_RQT_TIMEOUT; + } + + uint8_t buffer[20]; + + // https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/ + + if (modbusTcpId >= 0xFFFE) + modbusTcpId = 1; + else + modbusTcpId++; + + buffer[0] = (0xFF00 & modbusTcpId) >> 8; + buffer[1] = (0x00FF & modbusTcpId); + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + buffer[5] = 6; // len + + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + buffer[6] = sensor->id; // Modbus ID + buffer[7] = 0x03; // Read Holding Registers + buffer[8] = 0x00; + buffer[9] = 0x01; // soil moisture is at address 0x0001 + buffer[10] = 0x00; + buffer[11] = 0x01; // Number of registers to read (soil moisture value is + // one 16-bit register) + break; + + case SENSOR_SMT100_TEMP: + buffer[6] = sensor->id; + buffer[7] = 0x03; // Read Holding Registers + buffer[8] = 0x00; + buffer[9] = 0x00; // temperature is at address 0x0000 + buffer[10] = 0x00; + buffer[11] = 0x01; // Number of registers to read (temperature value is + // one 16-bit register) + break; + + case SENSOR_SMT100_PMTY: + buffer[6] = sensor->id; + buffer[7] = 0x03; // Read Holding Registers + buffer[8] = 0x00; + buffer[9] = 0x02; // permittivity is at address 0x0002 + buffer[10] = 0x00; + buffer[11] = 0x01; // Number of registers to read (permittivity value is + // one 16-bit register) + break; + + default: + client->stop(); + return HTTP_RQT_NOT_RECEIVED; + } + + client->write(buffer, 12); #if defined(ESP8266) - client->flush(); + client->flush(); #endif - //Read result: - switch(sensor->type) - { - case SENSOR_SMT100_MODBUS_RTU_MOIS: - case SENSOR_SMT100_MODBUS_RTU_TEMP: - uint32_t stoptime = millis()+SENSOR_READ_TIMEOUT; + // Read result: + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + case SENSOR_SMT100_TEMP: + case SENSOR_SMT100_PMTY: + // uint32_t stoptime = millis() + SENSOR_READ_TIMEOUT; #if defined(ESP8266) - while (true) { - if (client->available()) - break; - if (millis() >= stoptime) { - client->stop(); - DEBUG_PRINT(F("Sensor ")); - DEBUG_PRINT(sensor->nr); - DEBUG_PRINTLN(F(" timeout read!")); - return HTTP_RQT_TIMEOUT; - } - delay(5); - } - - int n = client->read(buffer, 11); - if (n < 11) - n += client->read(buffer+n, 11-n); + while (true) { + if (client->available()) break; + // if (millis() >= stoptime) + { + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINTLN(F(" timeout read!")); + return HTTP_RQT_TIMEOUT; + } + delay(5); + } + + int n = client->read(buffer, 11); + if (n < 11) n += client->read(buffer + n, 11 - n); #else - int n = 0; - while (true) { - n = client->read(buffer, 11); - if (n < 11) - n += client->read(buffer+n, 11-n); - if (n > 0) - break; - if (millis() >= stoptime) { - client->stop(); - DEBUG_PRINT(F("Sensor ")); - DEBUG_PRINT(sensor->nr); - DEBUG_PRINT(F(" timeout read!")); - return HTTP_RQT_TIMEOUT; - } - delay(5); - } + int n = 0; + while (true) { + n = client->read(buffer, 11); + if (n < 11) n += client->read(buffer + n, 11 - n); + if (n > 0) break; + if (millis() >= stoptime) { + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINT(F(" timeout read!")); + return HTTP_RQT_TIMEOUT; + } + delay(5); + } #endif - client->stop(); - DEBUG_PRINT(F("Sensor ")); - DEBUG_PRINT(sensor->nr); - if (n != 11) { - DEBUG_PRINT(F(" returned ")); - DEBUG_PRINT(n); - DEBUG_PRINTLN(F(" bytes??")); - return n == 0 ? HTTP_RQT_EMPTY_RETURN:HTTP_RQT_TIMEOUT; - } - if (buffer[0] != (0xFF00&modbusTcpId) >> 8 || buffer[1] != (0x00FF&modbusTcpId)) { - DEBUG_PRINT(F(" returned transaction id ")); - DEBUG_PRINTLN((uint16_t)((buffer[0] << 8) + buffer[1])); - return HTTP_RQT_NOT_RECEIVED; - } - if ((buffer[6] != sensor->id && sensor->id != 253)) { //253 is broadcast - DEBUG_PRINT(F(" returned sensor id ")); - DEBUG_PRINTLN((int)buffer[0]); - return HTTP_RQT_NOT_RECEIVED; - } - - //Valid result: - sensor->last_native_data = (buffer[9] << 8) | buffer[10]; - DEBUG_PRINT(F(" native: ")); - DEBUG_PRINT(sensor->last_native_data); - - //Convert to readable value: - switch(sensor->type) - { - case SENSOR_SMT100_MODBUS_RTU_MOIS: //Equation: soil moisture [vol.%]= (16Bit_soil_moisture_value / 100) - sensor->last_data = ((double)sensor->last_native_data / 100); - sensor->flags.data_ok = sensor->last_native_data < 10000; - DEBUG_PRINT(F(" soil moisture %: ")); - break; - case SENSOR_SMT100_MODBUS_RTU_TEMP: //Equation: temperature [°C]= (16Bit_temperature_value / 100)-100 - sensor->last_data = ((double)sensor->last_native_data / 100) - 100; - sensor->flags.data_ok = sensor->last_native_data > 7000; - DEBUG_PRINT(F(" temperature °C: ")); - break; - } - DEBUG_PRINTLN(sensor->last_data); - return sensor->flags.data_ok?HTTP_RQT_SUCCESS:HTTP_RQT_NOT_RECEIVED; - } - - return HTTP_RQT_NOT_RECEIVED; + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + if (n != 11) { + DEBUG_PRINT(F(" returned ")); + DEBUG_PRINT(n); + DEBUG_PRINTLN(F(" bytes??")); + return n == 0 ? HTTP_RQT_EMPTY_RETURN : HTTP_RQT_TIMEOUT; + } + if (buffer[0] != (0xFF00 & modbusTcpId) >> 8 || + buffer[1] != (0x00FF & modbusTcpId)) { + DEBUG_PRINT(F(" returned transaction id ")); + DEBUG_PRINTLN((uint16_t)((buffer[0] << 8) + buffer[1])); + return HTTP_RQT_NOT_RECEIVED; + } + if ((buffer[6] != sensor->id && sensor->id != 253)) { // 253 is broadcast + DEBUG_PRINT(F(" returned sensor id ")); + DEBUG_PRINTLN((int)buffer[0]); + return HTTP_RQT_NOT_RECEIVED; + } + + // Valid result: + sensor->last_native_data = (buffer[9] << 8) | buffer[10]; + DEBUG_PRINT(F(" native: ")); + DEBUG_PRINT(sensor->last_native_data); + + // Convert to readable value: + switch (sensor->type) { + case SENSOR_SMT100_MOIS: // Equation: soil moisture [vol.%]= + // (16Bit_soil_moisture_value / 100) + sensor->last_data = ((double)sensor->last_native_data / 100.0); + sensor->flags.data_ok = sensor->last_native_data < 10000; + DEBUG_PRINT(F(" soil moisture %: ")); + break; + case SENSOR_SMT100_TEMP: // Equation: temperature [°C]= + // (16Bit_temperature_value / 100)-100 + sensor->last_data = + ((double)sensor->last_native_data / 100.0) - 100.0; + sensor->flags.data_ok = sensor->last_native_data > 7000; + DEBUG_PRINT(F(" temperature °C: ")); + break; + case SENSOR_SMT100_PMTY: // permittivity + sensor->last_data = ((double)sensor->last_native_data / 100.0); + sensor->flags.data_ok = true; + DEBUG_PRINT(F(" permittivity DK: ")); + break; + } + DEBUG_PRINTLN(sensor->last_data); + return sensor->flags.data_ok ? HTTP_RQT_SUCCESS : HTTP_RQT_NOT_RECEIVED; + } + + return HTTP_RQT_NOT_RECEIVED; } /** * read a sensor -*/ + */ int read_sensor(Sensor_t *sensor, ulong time) { - - if (!sensor || !sensor->flags.enable) - return HTTP_RQT_NOT_RECEIVED; - - switch(sensor->type) - { - case SENSOR_SMT100_MODBUS_RTU_MOIS: - case SENSOR_SMT100_MODBUS_RTU_TEMP: - DEBUG_PRINT(F("Reading sensor ")); - DEBUG_PRINTLN(sensor->name); - sensor->last_read = time; - return read_sensor_ip(sensor); + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + case SENSOR_SMT100_TEMP: + case SENSOR_SMT100_PMTY: + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); + sensor->last_read = time; + if (sensor->ip) return read_sensor_ip(sensor); +#ifdef ESP8266 + if (sensor->port == 0) // 0 = Truebner RS485 Adapter + return read_sensor_i2c(sensor); +#endif + break; #if defined(ARDUINO) #if defined(ESP8266) - case SENSOR_ANALOG_EXTENSION_BOARD: - case SENSOR_ANALOG_EXTENSION_BOARD_P: - case SENSOR_SMT50_MOIS: //SMT50 VWC [%] = (U * 50) : 3 - case SENSOR_SMT50_TEMP: //SMT50 T [°C] = (U – 0,5) * 100 - case SENSOR_SMT100_ANALOG_MOIS: //SMT100 Analog Moisture - case SENSOR_SMT100_ANALOG_TEMP: //SMT100 Analog Temperature - case SENSOR_VH400: - case SENSOR_THERM200: - case SENSOR_AQUAPLUMB: - case SENSOR_USERDEF: - DEBUG_PRINT(F("Reading sensor ")); - DEBUG_PRINTLN(sensor->name); - return read_sensor_adc(sensor, time); + case SENSOR_ANALOG_EXTENSION_BOARD: + case SENSOR_ANALOG_EXTENSION_BOARD_P: + case SENSOR_SMT50_MOIS: // SMT50 VWC [%] = (U * 50) : 3 + case SENSOR_SMT50_TEMP: // SMT50 T [°C] = (U – 0,5) * 100 + case SENSOR_SMT100_ANALOG_MOIS: // SMT100 Analog Moisture + case SENSOR_SMT100_ANALOG_TEMP: // SMT100 Analog Temperature + case SENSOR_VH400: + case SENSOR_THERM200: + case SENSOR_AQUAPLUMB: + case SENSOR_USERDEF: + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); + return read_sensor_adc(sensor, time); #endif #else -#if defined ADS1115|PCF8591 - case SENSOR_OSPI_ANALOG: - case SENSOR_OSPI_ANALOG_P: - case SENSOR_OSPI_ANALOG_SMT50_MOIS: - case SENSOR_OSPI_ANALOG_SMT50_TEMP: - return read_sensor_ospi(sensor, time); +#if defined ADS1115 | PCF8591 + case SENSOR_OSPI_ANALOG: + case SENSOR_OSPI_ANALOG_P: + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + return read_sensor_ospi(sensor, time); #endif #endif - case SENSOR_REMOTE: - return read_sensor_http(sensor, time); - case SENSOR_MQTT: - sensor->last_read = time; - return read_sensor_mqtt(sensor); - - case SENSOR_WEATHER_TEMP_F: - case SENSOR_WEATHER_TEMP_C: - case SENSOR_WEATHER_HUM: - case SENSOR_WEATHER_PRECIP_IN: - case SENSOR_WEATHER_PRECIP_MM: - case SENSOR_WEATHER_WIND_MPH: - case SENSOR_WEATHER_WIND_KMH: - { - GetSensorWeather(); - if (current_weather_ok) { - DEBUG_PRINT(F("Reading sensor ")); - DEBUG_PRINTLN(sensor->name); - - sensor->last_read = time; - sensor->last_native_data = 0; - sensor->flags.data_ok = true; - - switch(sensor->type) - { - case SENSOR_WEATHER_TEMP_F: { - sensor->last_data = current_temp; - break; - } - case SENSOR_WEATHER_TEMP_C: { - sensor->last_data = (current_temp - 32.0) / 1.8; - break; - } - case SENSOR_WEATHER_HUM: { - sensor->last_data = current_humidity; - break; - } - case SENSOR_WEATHER_PRECIP_IN: { - sensor->last_data = current_precip; - break; - } - case SENSOR_WEATHER_PRECIP_MM: { - sensor->last_data = current_precip * 25.4; - break; - } - case SENSOR_WEATHER_WIND_MPH: { - sensor->last_data = current_wind; - break; - } - case SENSOR_WEATHER_WIND_KMH: { - sensor->last_data = current_wind * 1.609344; - break; - } - } - return HTTP_RQT_SUCCESS; - } - return HTTP_RQT_NOT_RECEIVED; - } - - default: return HTTP_RQT_NOT_RECEIVED; - } + case SENSOR_REMOTE: + return read_sensor_http(sensor, time); + case SENSOR_MQTT: + sensor->last_read = time; + return read_sensor_mqtt(sensor); + + case SENSOR_WEATHER_TEMP_F: + case SENSOR_WEATHER_TEMP_C: + case SENSOR_WEATHER_HUM: + case SENSOR_WEATHER_PRECIP_IN: + case SENSOR_WEATHER_PRECIP_MM: + case SENSOR_WEATHER_WIND_MPH: + case SENSOR_WEATHER_WIND_KMH: { + GetSensorWeather(); + if (current_weather_ok) { + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); + + sensor->last_read = time; + sensor->last_native_data = 0; + sensor->flags.data_ok = true; + + switch (sensor->type) { + case SENSOR_WEATHER_TEMP_F: { + sensor->last_data = current_temp; + break; + } + case SENSOR_WEATHER_TEMP_C: { + sensor->last_data = (current_temp - 32.0) / 1.8; + break; + } + case SENSOR_WEATHER_HUM: { + sensor->last_data = current_humidity; + break; + } + case SENSOR_WEATHER_PRECIP_IN: { + sensor->last_data = current_precip; + break; + } + case SENSOR_WEATHER_PRECIP_MM: { + sensor->last_data = current_precip * 25.4; + break; + } + case SENSOR_WEATHER_WIND_MPH: { + sensor->last_data = current_wind; + break; + } + case SENSOR_WEATHER_WIND_KMH: { + sensor->last_data = current_wind * 1.609344; + break; + } + } + return HTTP_RQT_SUCCESS; + } + } + } + return HTTP_RQT_NOT_RECEIVED; } /** * @brief Update group values - * + * */ -void sensor_update_groups() -{ - Sensor_t *sensor = sensors; - - ulong time = os.now_tz(); - - while (sensor) - { - if (time >= sensor->last_read + sensor->read_interval) { - switch (sensor->type) { - case SENSOR_GROUP_MIN: - case SENSOR_GROUP_MAX: - case SENSOR_GROUP_AVG: - case SENSOR_GROUP_SUM: { - uint nr = sensor->nr; - Sensor_t *group = sensors; - double value = 0; - int n = 0; - while (group) { - if (group->nr != nr && group->group == nr && group->flags.enable) { // && group->flags.data_ok) { - switch (sensor->type) { - case SENSOR_GROUP_MIN: - if (n++ == 0) - value = group->last_data; - else if (group->last_data < value) - value = group->last_data; - break; - case SENSOR_GROUP_MAX: - if (n++ == 0) - value = group->last_data; - else if (group->last_data > value) - value = group->last_data; - break; - case SENSOR_GROUP_AVG: - case SENSOR_GROUP_SUM: - n++; - value += group->last_data; - break; - } - } - group = group->next; - } - if (sensor->type == SENSOR_GROUP_AVG && n > 0) { - value = value / (double)n; - } - sensor->last_data = value; - sensor->last_native_data = 0; - sensor->last_read = time; - sensor->flags.data_ok = n > 0; - sensorlog_add(LOG_STD, sensor, time); - break; - } - } - } - sensor = sensor->next; - } -} - -int set_sensor_address(Sensor_t *sensor, byte new_address) { - if (!sensor) - return HTTP_RQT_NOT_RECEIVED; - - switch(sensor->type) - { - case SENSOR_SMT100_MODBUS_RTU_MOIS: - case SENSOR_SMT100_MODBUS_RTU_TEMP: - +void sensor_update_groups() { + Sensor_t *sensor = sensors; + + ulong time = os.now_tz(); + + while (sensor) { + if (time >= sensor->last_read + sensor->read_interval) { + switch (sensor->type) { + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: { + uint nr = sensor->nr; + Sensor_t *group = sensors; + double value = 0; + int n = 0; + while (group) { + if (group->nr != nr && group->group == nr && + group->flags.enable) { // && group->flags.data_ok) { + switch (sensor->type) { + case SENSOR_GROUP_MIN: + if (n++ == 0) + value = group->last_data; + else if (group->last_data < value) + value = group->last_data; + break; + case SENSOR_GROUP_MAX: + if (n++ == 0) + value = group->last_data; + else if (group->last_data > value) + value = group->last_data; + break; + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + n++; + value += group->last_data; + break; + } + } + group = group->next; + } + if (sensor->type == SENSOR_GROUP_AVG && n > 0) { + value = value / (double)n; + } + sensor->last_data = value; + sensor->last_native_data = 0; + sensor->last_read = time; + sensor->flags.data_ok = n > 0; + sensorlog_add(LOG_STD, sensor, time); + break; + } + } + } + sensor = sensor->next; + } +} + +int set_sensor_address_ip(Sensor_t *sensor, uint8_t new_address) { #if defined(ARDUINO) - Client *client; - #if defined(ESP8266) - WiFiClient wifiClient; - client = &wifiClient; - #else - EthernetClient etherClient; - client = ðerClient; - #endif + Client *client; +#if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; #else - EthernetClient etherClient; - EthernetClient *client = ðerClient; + EthernetClient etherClient; + client = ðerClient; +#endif +#else + EthernetClient etherClient; + EthernetClient *client = ðerClient; #endif - sensor->flags.data_ok = false; - if (!sensor->ip || !sensor->port) { - sensor->flags.enable = false; - return HTTP_RQT_CONNECT_ERR; - } - #if defined(ESP8266) - IPAddress _ip(sensor->ip); - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + IPAddress _ip(sensor->ip); + byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else - byte ip[4]; - ip[3] = (byte)((sensor->ip >> 24) &0xFF); - ip[2] = (byte)((sensor->ip >> 16) &0xFF); - ip[1] = (byte)((sensor->ip >> 8) &0xFF); - ip[0] = (byte)((sensor->ip &0xFF)); -#endif - char server[20]; - sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + byte ip[4]; + ip[3] = (byte)((sensor->ip >> 24) & 0xFF); + ip[2] = (byte)((sensor->ip >> 16) & 0xFF); + ip[1] = (byte)((sensor->ip >> 8) & 0xFF); + ip[0] = (byte)((sensor->ip & 0xFF)); +#endif + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); #if defined(ESP8266) - client->setTimeout(200); - if(!client->connect(server, sensor->port)) { + client->setTimeout(200); + if (!client->connect(server, sensor->port)) { #else - struct hostent *host = gethostbyname(server); - if (!host) { return HTTP_RQT_CONNECT_ERR; } - if(!client->connect((uint8_t*)host->h_addr, sensor->port)) { + struct hostent *host = gethostbyname(server); + if (!host) { + return HTTP_RQT_CONNECT_ERR; + } + if (!client->connect((uint8_t *)host->h_addr, sensor->port)) { #endif - DEBUG_PRINT(F("Cannot connect to ")); - DEBUG_PRINT(server); - DEBUG_PRINT(":"); - DEBUG_PRINTLN(sensor->port); - client->stop(); - return HTTP_RQT_CONNECT_ERR; - } - - uint8_t buffer[20]; - - //https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/ - - if (modbusTcpId >= 0xFFFE) - modbusTcpId = 1; - else - modbusTcpId++; - - buffer[0] = (0xFF00&modbusTcpId) >> 8; - buffer[1] = (0x00FF&modbusTcpId); - buffer[2] = 0; - buffer[3] = 0; - buffer[4] = 0; - buffer[5] = 6; //len - buffer[6] = sensor->id; - buffer[7] = 0x06; - buffer[8] = 0x00; - buffer[9] = 0x04; - buffer[10] = 0x00; - buffer[11] = new_address; - - client->write(buffer, 12); + DEBUG_PRINT(F("Cannot connect to ")); + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINTLN(sensor->port); + client->stop(); + return HTTP_RQT_CONNECT_ERR; + } + + uint8_t buffer[20]; + + // https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/ + + if (modbusTcpId >= 0xFFFE) + modbusTcpId = 1; + else + modbusTcpId++; + + buffer[0] = (0xFF00 & modbusTcpId) >> 8; + buffer[1] = (0x00FF & modbusTcpId); + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + buffer[5] = 6; // len + buffer[6] = sensor->id; + buffer[7] = 0x06; + buffer[8] = 0x00; + buffer[9] = 0x04; + buffer[10] = 0x00; + buffer[11] = new_address; + + client->write(buffer, 12); #if defined(ESP8266) - client->flush(); + client->flush(); +#endif + + // Read result: + int n = client->read(buffer, 12); + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + if (n != 12) { + DEBUG_PRINT(F(" returned ")); + DEBUG_PRINT(n); + DEBUG_PRINT(F(" bytes??")); + return n == 0 ? HTTP_RQT_EMPTY_RETURN : HTTP_RQT_TIMEOUT; + } + if (buffer[0] != (0xFF00 & modbusTcpId) >> 8 || + buffer[1] != (0x00FF & modbusTcpId)) { + DEBUG_PRINT(F(" returned transaction id ")); + DEBUG_PRINTLN((uint16_t)((buffer[0] << 8) + buffer[1])); + return HTTP_RQT_NOT_RECEIVED; + } + if ((buffer[6] != sensor->id && sensor->id != 253)) { // 253 is broadcast + DEBUG_PRINT(F(" returned sensor id ")); + DEBUG_PRINT((int)buffer[0]); + return HTTP_RQT_NOT_RECEIVED; + } + // Read OK: + sensor->id = new_address; + sensor_save(); + return HTTP_RQT_SUCCESS; +} + +#ifdef ESP8266 +/** + * @brief Set the sensor address i2c + * + * @param sensor + * @param new_address + * @return int + */ +int set_sensor_address_i2c(Sensor_t *sensor, uint8_t new_address) { + DEBUG_PRINTLN(F("set_sensor_address_i2c")); + if ((asb_detected_boards & RS485_TRUEBNER) == 0) return HTTP_RQT_NOT_RECEIVED; + + if (i2c_rs485_allocated > 0) { + DEBUG_PRINT(F("sensor currently allocated by ")); + DEBUG_PRINTLN(i2c_rs485_allocated); + return HTTP_RQT_NOT_RECEIVED; + } + + Wire.beginTransmission(RS485_TRUEBNER_ADDR); + Wire.write(254); + Wire.write(new_address); + Wire.endTransmission(); + delay(3000); + Wire.requestFrom((uint8_t)RS485_TRUEBNER_ADDR, (size_t)1, true); + if (Wire.available()) { + delay(10); + uint8_t modbus_address = Wire.read(); + if (modbus_address == new_address) return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} #endif - //Read result: - int n = client->read(buffer, 12); - client->stop(); - DEBUG_PRINT(F("Sensor ")); - DEBUG_PRINT(sensor->nr); - if (n != 12) { - DEBUG_PRINT(F(" returned ")); - DEBUG_PRINT(n); - DEBUG_PRINT(F(" bytes??")); - return n == 0 ? HTTP_RQT_EMPTY_RETURN:HTTP_RQT_TIMEOUT; - } - if (buffer[0] != (0xFF00&modbusTcpId) >> 8 || buffer[1] != (0x00FF&modbusTcpId)) { - DEBUG_PRINT(F(" returned transaction id ")); - DEBUG_PRINTLN((uint16_t)((buffer[0] << 8) + buffer[1])); - return HTTP_RQT_NOT_RECEIVED; - } - if ((buffer[6] != sensor->id && sensor->id != 253)) { //253 is broadcast - DEBUG_PRINT(F(" returned sensor id ")); - DEBUG_PRINT((int)buffer[0]); - return HTTP_RQT_NOT_RECEIVED; - } - //Read OK: - sensor->id = new_address; - sensor_save(); - return HTTP_RQT_SUCCESS; - } - return HTTP_RQT_NOT_RECEIVED; +int set_sensor_address(Sensor_t *sensor, uint8_t new_address) { + if (!sensor) return HTTP_RQT_NOT_RECEIVED; + + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + case SENSOR_SMT100_TEMP: + case SENSOR_SMT100_PMTY: + sensor->flags.data_ok = false; + if (sensor->ip && sensor->port) + return set_sensor_address_ip(sensor, new_address); +#ifdef ESP8266 + if (!sensor->ip && sensor->port == 0) + return set_sensor_address_i2c(sensor, new_address); +#endif + sensor->flags.enable = false; + return HTTP_RQT_NOT_RECEIVED; + } + return HTTP_RQT_CONNECT_ERR; } double calc_linear(ProgSensorAdjust_t *p, double sensorData) { - -// min max factor1 factor2 -// 10..90 -> 5..1 factor1 > factor2 -// a b c d -// (b-sensorData) / (b-a) * (c-d) + d -// -// 10..90 -> 1..5 factor1 < factor2 -// a b c d -// (sensorData-a) / (b-a) * (d-c) + c - - // Limit to min/max: - if (sensorData < p->min) sensorData = p->min; - if (sensorData > p->max) sensorData = p->max; - - //Calculate: - double div = (p->max - p->min); - if (abs(div) < 0.00001) return 0; - - if (p->factor1 > p->factor2) { // invers scaling factor: - return (p->max - sensorData) / div * (p->factor1 - p->factor2) + p->factor2; - } else { // upscaling factor: - return (sensorData - p->min) / div * (p->factor2 - p->factor1) + p->factor1; - } + // min max factor1 factor2 + // 10..90 -> 5..1 factor1 > factor2 + // a b c d + // (b-sensorData) / (b-a) * (c-d) + d + // + // 10..90 -> 1..5 factor1 < factor2 + // a b c d + // (sensorData-a) / (b-a) * (d-c) + c + + // Limit to min/max: + if (sensorData < p->min) sensorData = p->min; + if (sensorData > p->max) sensorData = p->max; + + // Calculate: + double div = (p->max - p->min); + if (abs(div) < 0.00001) return 0; + + if (p->factor1 > p->factor2) { // invers scaling factor: + return (p->max - sensorData) / div * (p->factor1 - p->factor2) + p->factor2; + } else { // upscaling factor: + return (sensorData - p->min) / div * (p->factor2 - p->factor1) + p->factor1; + } } double calc_digital_min(ProgSensorAdjust_t *p, double sensorData) { - return sensorData <= p->min? p->factor1:p->factor2; + return sensorData <= p->min ? p->factor1 : p->factor2; } double calc_digital_max(ProgSensorAdjust_t *p, double sensorData) { - return sensorData >= p->max? p->factor2:p->factor1; + return sensorData >= p->max ? p->factor2 : p->factor1; } double calc_digital_minmax(ProgSensorAdjust_t *p, double sensorData) { - if (sensorData <= p->min) return p->factor1; - if (sensorData >= p->max) return p->factor1; - return p->factor2; + if (sensorData <= p->min) return p->factor1; + if (sensorData >= p->max) return p->factor1; + return p->factor2; } /** * @brief calculate adjustment - * - * @param prog - * @return double + * + * @param prog + * @return double */ double calc_sensor_watering(uint prog) { - double result = 1; - ProgSensorAdjust_t *p = progSensorAdjusts; - - while (p) { - if (p->prog-1 == prog) { - Sensor_t *sensor = sensor_by_nr(p->sensor); - if (sensor && sensor->flags.enable && sensor->flags.data_ok) { - - double res = calc_sensor_watering_int(p, sensor->last_data); - result = result * res; - } - } - - p = p->next; - } - if (result < 0.0) - result = 0.0; - if (result > 20.0) // Factor 20 is a huge value! - result = 20.0; - return result; + double result = 1; + ProgSensorAdjust_t *p = progSensorAdjusts; + + while (p) { + if (p->prog - 1 == prog) { + Sensor_t *sensor = sensor_by_nr(p->sensor); + if (sensor && sensor->flags.enable && sensor->flags.data_ok) { + double res = calc_sensor_watering_int(p, sensor->last_data); + result = result * res; + } + } + + p = p->next; + } + if (result < 0.0) result = 0.0; + if (result > 20.0) // Factor 20 is a huge value! + result = 20.0; + return result; } double calc_sensor_watering_int(ProgSensorAdjust_t *p, double sensorData) { - double res = 0; - if (!p) return res; - switch(p->type) { - case PROG_NONE: res = 1; break; - case PROG_LINEAR: res = calc_linear(p, sensorData); break; - case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensorData); break; - case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensorData); break; - case PROG_DIGITAL_MINMAX: res = calc_digital_minmax(p, sensorData); break; - default: res = 0; - } - return res; + double res = 0; + if (!p) return res; + switch (p->type) { + case PROG_NONE: + res = 1; + break; + case PROG_LINEAR: + res = calc_linear(p, sensorData); + break; + case PROG_DIGITAL_MIN: + res = calc_digital_min(p, sensorData); + break; + case PROG_DIGITAL_MAX: + res = calc_digital_max(p, sensorData); + break; + case PROG_DIGITAL_MINMAX: + res = calc_digital_minmax(p, sensorData); + break; + default: + res = 0; + } + return res; } /** * @brief calculate adjustment - * - * @param nr - * @return double + * + * @param nr + * @return double */ double calc_sensor_watering_by_nr(uint nr) { - double result = 1; - ProgSensorAdjust_t *p = progSensorAdjusts; - - while (p) { - if (p->nr == nr) { - Sensor_t *sensor = sensor_by_nr(p->sensor); - if (sensor && sensor->flags.enable && sensor->flags.data_ok) { - - double res = 0; - switch(p->type) { - case PROG_NONE: res = 1; break; - case PROG_LINEAR: res = calc_linear(p, sensor->last_data); break; - case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor->last_data); break; - case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor->last_data); break; - default: res = 0; - } - - result = result * res; - } - break; - } - - p = p->next; - } - - return result; -} - -int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max) { - ProgSensorAdjust_t *p = progSensorAdjusts; - - ProgSensorAdjust_t *last = NULL; - - while (p) { - if (p->nr == nr) { - p->type = type; - p->sensor = sensor; - p->prog = prog; - p->factor1 = factor1; - p->factor2 = factor2; - p->min = min; - p->max = max; - prog_adjust_save(); - return HTTP_RQT_SUCCESS; - } - - if (p->nr > nr) - break; - - last = p; - p = p->next; - } - - p = new ProgSensorAdjust_t; - p->nr = nr; - p->type = type; - p->sensor = sensor; - p->prog = prog; - p->factor1 = factor1; - p->factor2 = factor2; - p->min = min; - p->max = max; - if (last) { - p->next = last->next; - last->next = p; - } else { - p->next = progSensorAdjusts; - progSensorAdjusts = p; - } - - prog_adjust_save(); - return HTTP_RQT_SUCCESS; + double result = 1; + ProgSensorAdjust_t *p = progSensorAdjusts; + + while (p) { + if (p->nr == nr) { + Sensor_t *sensor = sensor_by_nr(p->sensor); + if (sensor && sensor->flags.enable && sensor->flags.data_ok) { + double res = 0; + switch (p->type) { + case PROG_NONE: + res = 1; + break; + case PROG_LINEAR: + res = calc_linear(p, sensor->last_data); + break; + case PROG_DIGITAL_MIN: + res = calc_digital_min(p, sensor->last_data); + break; + case PROG_DIGITAL_MAX: + res = calc_digital_max(p, sensor->last_data); + break; + default: + res = 0; + } + + result = result * res; + } + break; + } + + p = p->next; + } + + return result; +} + +int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, + double factor1, double factor2, double min, double max) { + ProgSensorAdjust_t *p = progSensorAdjusts; + + ProgSensorAdjust_t *last = NULL; + + while (p) { + if (p->nr == nr) { + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->factor1 = factor1; + p->factor2 = factor2; + p->min = min; + p->max = max; + prog_adjust_save(); + return HTTP_RQT_SUCCESS; + } + + if (p->nr > nr) break; + + last = p; + p = p->next; + } + + p = new ProgSensorAdjust_t; + p->nr = nr; + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->factor1 = factor1; + p->factor2 = factor2; + p->min = min; + p->max = max; + if (last) { + p->next = last->next; + last->next = p; + } else { + p->next = progSensorAdjusts; + progSensorAdjusts = p; + } + + prog_adjust_save(); + return HTTP_RQT_SUCCESS; } int prog_adjust_delete(uint nr) { - ProgSensorAdjust_t *p = progSensorAdjusts; - - ProgSensorAdjust_t *last = NULL; - - while (p) { - if (p->nr == nr) { - if (last) - last->next = p->next; - else - progSensorAdjusts = p->next; - delete p; - prog_adjust_save(); - return HTTP_RQT_SUCCESS; - } - last = p; - p = p->next; - } - return HTTP_RQT_NOT_RECEIVED; + ProgSensorAdjust_t *p = progSensorAdjusts; + + ProgSensorAdjust_t *last = NULL; + + while (p) { + if (p->nr == nr) { + if (last) + last->next = p->next; + else + progSensorAdjusts = p->next; + delete p; + prog_adjust_save(); + return HTTP_RQT_SUCCESS; + } + last = p; + p = p->next; + } + return HTTP_RQT_NOT_RECEIVED; } void prog_adjust_save() { - if (file_exists(PROG_SENSOR_FILENAME)) - remove_file(PROG_SENSOR_FILENAME); + if (file_exists(PROG_SENSOR_FILENAME)) remove_file(PROG_SENSOR_FILENAME); - ulong pos = 0; - ProgSensorAdjust_t *pa = progSensorAdjusts; - while (pa) { - file_write_block(PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); - pa = pa->next; - pos += PROGSENSOR_STORE_SIZE; - } + ulong pos = 0; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + file_write_block(PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); + pa = pa->next; + pos += PROGSENSOR_STORE_SIZE; + } } void prog_adjust_load() { - DEBUG_PRINTLN(F("prog_adjust_load")); - progSensorAdjusts = NULL; - if (!file_exists(PROG_SENSOR_FILENAME)) - return; - - ulong pos = 0; - ProgSensorAdjust_t *last = NULL; - while (true) { - ProgSensorAdjust_t *pa = new ProgSensorAdjust_t; - memset(pa, 0, sizeof(ProgSensorAdjust_t)); - file_read_block (PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); - if (!pa->nr || !pa->type) { - delete pa; - break; - } - if (!last) progSensorAdjusts = pa; - else last->next = pa; - last = pa; - pa->next = NULL; - pos += PROGSENSOR_STORE_SIZE; - } + DEBUG_PRINTLN(F("prog_adjust_load")); + progSensorAdjusts = NULL; + if (!file_exists(PROG_SENSOR_FILENAME)) return; + + ulong pos = 0; + ProgSensorAdjust_t *last = NULL; + while (true) { + ProgSensorAdjust_t *pa = new ProgSensorAdjust_t; + memset(pa, 0, sizeof(ProgSensorAdjust_t)); + file_read_block(PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); + if (!pa->nr || !pa->type) { + delete pa; + break; + } + if (!last) + progSensorAdjusts = pa; + else + last->next = pa; + last = pa; + pa->next = NULL; + pos += PROGSENSOR_STORE_SIZE; + } } uint prog_adjust_count() { - uint count = 0; - ProgSensorAdjust_t *pa = progSensorAdjusts; - while (pa) { - count++; - pa = pa->next; - } - return count; + uint count = 0; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + count++; + pa = pa->next; + } + return count; } ProgSensorAdjust_t *prog_adjust_by_nr(uint nr) { - ProgSensorAdjust_t *pa = progSensorAdjusts; - while (pa) { - if (pa->nr == nr) return pa; - pa = pa->next; - } - return NULL; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + if (pa->nr == nr) return pa; + pa = pa->next; + } + return NULL; } ProgSensorAdjust_t *prog_adjust_by_idx(uint idx) { - ProgSensorAdjust_t *pa = progSensorAdjusts; - uint idxCounter = 0; - while (pa) { - if (idxCounter++ == idx) return pa; - pa = pa->next; - } - return NULL; + ProgSensorAdjust_t *pa = progSensorAdjusts; + uint idxCounter = 0; + while (pa) { + if (idxCounter++ == idx) return pa; + pa = pa->next; + } + return NULL; } #if defined(ESP8266) ulong diskFree() { - struct FSInfo fsinfo; - LittleFS.info(fsinfo); - return fsinfo.totalBytes-fsinfo.usedBytes; + struct FSInfo fsinfo; + LittleFS.info(fsinfo); + return fsinfo.totalBytes - fsinfo.usedBytes; } bool checkDiskFree() { - if (diskFree() < MIN_DISK_FREE) { - DEBUG_PRINT(F("fs has low space!")); - return false; - } - return true; + if (diskFree() < MIN_DISK_FREE) { + DEBUG_PRINT(F("fs has low space!")); + return false; + } + return true; } #endif -const char* getSensorUnit(int unitid) { - if (unitid == UNIT_USERDEF) - return "?"; - if (unitid < 0 || (uint16_t)unitid >= sizeof(sensor_unitNames)) - return sensor_unitNames[0]; - return sensor_unitNames[unitid]; +const char *getSensorUnit(int unitid) { + if (unitid == UNIT_USERDEF) return "?"; + if (unitid < 0 || (uint16_t)unitid >= sizeof(sensor_unitNames)) + return sensor_unitNames[0]; + return sensor_unitNames[unitid]; } -const char* getSensorUnit(Sensor_t *sensor) { - if (!sensor) - return sensor_unitNames[0]; +const char *getSensorUnit(Sensor_t *sensor) { + if (!sensor) return sensor_unitNames[0]; - int unitid = getSensorUnitId(sensor); - if (unitid == UNIT_USERDEF) - return sensor->userdef_unit; - if (unitid < 0 || (uint16_t)unitid >= sizeof(sensor_unitNames)) - return sensor_unitNames[0]; - return sensor_unitNames[unitid]; + int unitid = getSensorUnitId(sensor); + if (unitid == UNIT_USERDEF) return sensor->userdef_unit; + if (unitid < 0 || (uint16_t)unitid >= sizeof(sensor_unitNames)) + return sensor_unitNames[0]; + return sensor_unitNames[unitid]; } boolean sensor_isgroup(Sensor_t *sensor) { - if (!sensor) - return false; + if (!sensor) return false; - switch(sensor->type) { - case SENSOR_GROUP_MIN: - case SENSOR_GROUP_MAX: - case SENSOR_GROUP_AVG: - case SENSOR_GROUP_SUM: return true; + switch (sensor->type) { + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + return true; - default: return false; - } + default: + return false; + } } byte getSensorUnitId(int type) { - switch(type) { - case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; - case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; + switch (type) { + case SENSOR_SMT100_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT100_TEMP: + return UNIT_DEGREE; + case SENSOR_SMT100_PMTY: + return UNIT_DK; #if defined(ARDUINO) #if defined(ESP8266) - case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; - case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_PERCENT; - case SENSOR_SMT50_MOIS: return UNIT_PERCENT; - case SENSOR_SMT50_TEMP: return UNIT_DEGREE; - case SENSOR_SMT100_ANALOG_MOIS: return UNIT_PERCENT; - case SENSOR_SMT100_ANALOG_TEMP: return UNIT_DEGREE; - - case SENSOR_VH400: return UNIT_PERCENT; - case SENSOR_THERM200: return UNIT_DEGREE; - case SENSOR_AQUAPLUMB: return UNIT_PERCENT; - case SENSOR_USERDEF: return UNIT_USERDEF; + case SENSOR_ANALOG_EXTENSION_BOARD: + return UNIT_VOLT; + case SENSOR_ANALOG_EXTENSION_BOARD_P: + return UNIT_PERCENT; + case SENSOR_SMT50_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT50_TEMP: + return UNIT_DEGREE; + case SENSOR_SMT100_ANALOG_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT100_ANALOG_TEMP: + return UNIT_DEGREE; + + case SENSOR_VH400: + return UNIT_PERCENT; + case SENSOR_THERM200: + return UNIT_DEGREE; + case SENSOR_AQUAPLUMB: + return UNIT_PERCENT; + case SENSOR_USERDEF: + return UNIT_USERDEF; #endif #else - case SENSOR_OSPI_ANALOG: return UNIT_VOLT; - case SENSOR_OSPI_ANALOG_P: return UNIT_PERCENT; - case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; - case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_OSPI_ANALOG: + return UNIT_VOLT; + case SENSOR_OSPI_ANALOG_P: + return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + return UNIT_DEGREE; #endif - case SENSOR_MQTT: return UNIT_USERDEF; - case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; - case SENSOR_WEATHER_TEMP_C: return UNIT_DEGREE; - case SENSOR_WEATHER_HUM: return UNIT_HUM_PERCENT; - case SENSOR_WEATHER_PRECIP_IN: return UNIT_INCH; - case SENSOR_WEATHER_PRECIP_MM: return UNIT_MM; - case SENSOR_WEATHER_WIND_MPH: return UNIT_MPH; - case SENSOR_WEATHER_WIND_KMH: return UNIT_KMH; - - default: return UNIT_NONE; - } + case SENSOR_MQTT: + return UNIT_USERDEF; + case SENSOR_WEATHER_TEMP_F: + return UNIT_FAHRENHEIT; + case SENSOR_WEATHER_TEMP_C: + return UNIT_DEGREE; + case SENSOR_WEATHER_HUM: + return UNIT_HUM_PERCENT; + case SENSOR_WEATHER_PRECIP_IN: + return UNIT_INCH; + case SENSOR_WEATHER_PRECIP_MM: + return UNIT_MM; + case SENSOR_WEATHER_WIND_MPH: + return UNIT_MPH; + case SENSOR_WEATHER_WIND_KMH: + return UNIT_KMH; + + default: + return UNIT_NONE; + } } byte getSensorUnitId(Sensor_t *sensor) { - if (!sensor) - return 0; - - switch(sensor->type) { - case SENSOR_SMT100_MODBUS_RTU_MOIS: return UNIT_PERCENT; - case SENSOR_SMT100_MODBUS_RTU_TEMP: return UNIT_DEGREE; -#if defined(ARDUINO) -#if defined(ESP8266) - case SENSOR_ANALOG_EXTENSION_BOARD: return UNIT_VOLT; - case SENSOR_ANALOG_EXTENSION_BOARD_P: return UNIT_LEVEL; - case SENSOR_SMT50_MOIS: return UNIT_PERCENT; - case SENSOR_SMT50_TEMP: return UNIT_DEGREE; - case SENSOR_SMT100_ANALOG_MOIS: return UNIT_PERCENT; - case SENSOR_SMT100_ANALOG_TEMP: return UNIT_DEGREE; - - case SENSOR_VH400: return UNIT_PERCENT; - case SENSOR_THERM200: return UNIT_DEGREE; - case SENSOR_AQUAPLUMB: return UNIT_PERCENT; - case SENSOR_USERDEF: return UNIT_USERDEF; + if (!sensor) return 0; + + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT100_TEMP: + return UNIT_DEGREE; + case SENSOR_SMT100_PMTY: + return UNIT_DK; +#if defined(ARDUINO) +#if defined(ESP8266) + case SENSOR_ANALOG_EXTENSION_BOARD: + return UNIT_VOLT; + case SENSOR_ANALOG_EXTENSION_BOARD_P: + return UNIT_LEVEL; + case SENSOR_SMT50_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT50_TEMP: + return UNIT_DEGREE; + case SENSOR_SMT100_ANALOG_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT100_ANALOG_TEMP: + return UNIT_DEGREE; + + case SENSOR_VH400: + return UNIT_PERCENT; + case SENSOR_THERM200: + return UNIT_DEGREE; + case SENSOR_AQUAPLUMB: + return UNIT_PERCENT; + case SENSOR_USERDEF: + return UNIT_USERDEF; #endif #else - case SENSOR_OSPI_ANALOG: return UNIT_VOLT; - case SENSOR_OSPI_ANALOG_P: return UNIT_PERCENT; - case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; - case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; + case SENSOR_OSPI_ANALOG: + return UNIT_VOLT; + case SENSOR_OSPI_ANALOG_P: + return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + return UNIT_DEGREE; #endif - case SENSOR_MQTT: - case SENSOR_REMOTE: return sensor->assigned_unitid > 0?sensor->assigned_unitid:UNIT_USERDEF; - - case SENSOR_WEATHER_TEMP_F: return UNIT_FAHRENHEIT; - case SENSOR_WEATHER_TEMP_C: return UNIT_DEGREE; - case SENSOR_WEATHER_HUM: return UNIT_HUM_PERCENT; - case SENSOR_WEATHER_PRECIP_IN: return UNIT_INCH; - case SENSOR_WEATHER_PRECIP_MM: return UNIT_MM; - case SENSOR_WEATHER_WIND_MPH: return UNIT_MPH; - case SENSOR_WEATHER_WIND_KMH: return UNIT_KMH; - - case SENSOR_GROUP_MIN: - case SENSOR_GROUP_MAX: - case SENSOR_GROUP_AVG: - case SENSOR_GROUP_SUM: - - for (int i = 0; i < 100; i++) { - Sensor_t *sen = sensors; - while (sen) { - if (sen != sensor && sen->group > 0 && sen->group == sensor->nr) { - if (!sensor_isgroup(sen)) - return getSensorUnitId(sen); - sensor = sen; - break; - } - sen = sen->next; - } - } - - default: return UNIT_NONE; - } + case SENSOR_MQTT: + case SENSOR_REMOTE: + return sensor->assigned_unitid > 0 ? sensor->assigned_unitid + : UNIT_USERDEF; + + case SENSOR_WEATHER_TEMP_F: + return UNIT_FAHRENHEIT; + case SENSOR_WEATHER_TEMP_C: + return UNIT_DEGREE; + case SENSOR_WEATHER_HUM: + return UNIT_HUM_PERCENT; + case SENSOR_WEATHER_PRECIP_IN: + return UNIT_INCH; + case SENSOR_WEATHER_PRECIP_MM: + return UNIT_MM; + case SENSOR_WEATHER_WIND_MPH: + return UNIT_MPH; + case SENSOR_WEATHER_WIND_KMH: + return UNIT_KMH; + + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + + for (int i = 0; i < 100; i++) { + Sensor_t *sen = sensors; + while (sen) { + if (sen != sensor && sen->group > 0 && sen->group == sensor->nr) { + if (!sensor_isgroup(sen)) return getSensorUnitId(sen); + sensor = sen; + break; + } + sen = sen->next; + } + } + + default: + return UNIT_NONE; + } } void GetSensorWeather() { #if defined(ESP8266) - if (!useEth) - if (os.state!=OS_STATE_CONNECTED || WiFi.status()!=WL_CONNECTED) return; + if (!useEth) + if (os.state != OS_STATE_CONNECTED || WiFi.status() != WL_CONNECTED) return; #endif - time_t time = os.now_tz(); - if (last_weather_time == 0) - last_weather_time = time - 59*60; - - if (time < last_weather_time + 60*60) - return; - - // use temp buffer to construct get command - BufferFiller bf = tmp_buffer; - bf.emit_p(PSTR("weatherData?loc=$O&wto=$O"), SOPT_LOCATION, SOPT_WEATHER_OPTS); - - char *src=tmp_buffer+strlen(tmp_buffer); - char *dst=tmp_buffer+TMP_BUFFER_SIZE-12; - - char c; - // url encode. convert SPACE to %20 - // copy reversely from the end because we are potentially expanding - // the string size - while(src!=tmp_buffer) { - c = *src--; - if(c==' ') { - *dst-- = '0'; - *dst-- = '2'; - *dst-- = '%'; - } else { - *dst-- = c; - } - }; - *dst = *src; - - strcpy(ether_buffer, "GET /"); - strcat(ether_buffer, dst); - // because dst is part of tmp_buffer, - // must load weather url AFTER dst is copied to ether_buffer - - // load weather url to tmp_buffer - char *host = tmp_buffer; - os.sopt_load(SOPT_WEATHERURL, host); - - strcat(ether_buffer, " HTTP/1.0\r\nHOST: "); - strcat(ether_buffer, host); - strcat(ether_buffer, "\r\n\r\n"); - - DEBUG_PRINTLN(F("GetSensorWeather")); - DEBUG_PRINTLN(ether_buffer); - - int ret = os.send_http_request(host, ether_buffer, NULL, 2000, 1); //timeout=2000, ntries=1 - if(ret == HTTP_RQT_SUCCESS) { - last_weather_time = time; - DEBUG_PRINTLN(ether_buffer); - - char buf[20]; - char *s = strstr(ether_buffer, "\"temp\":"); - if (s && extract(s, buf, sizeof(buf))) { - current_temp = atof(buf); - } - s = strstr(ether_buffer, "\"humidity\":"); - if (s && extract(s, buf, sizeof(buf))) { - current_humidity = atof(buf); - } - s = strstr(ether_buffer, "\"precip\":"); - if (s && extract(s, buf, sizeof(buf))) { - current_precip = atof(buf); - } - s = strstr(ether_buffer, "\"wind\":"); - if (s && extract(s, buf, sizeof(buf))) { - current_wind = atof(buf); - } - char tmp[10]; - DEBUG_PRINT("temp: "); - dtostrf(current_temp, 2, 2, tmp); - DEBUG_PRINTLN(tmp) - DEBUG_PRINT("humidity: "); - dtostrf(current_humidity, 2, 2, tmp); - DEBUG_PRINTLN(tmp) - DEBUG_PRINT("precip: "); - dtostrf(current_precip, 2, 2, tmp); - DEBUG_PRINTLN(tmp) - DEBUG_PRINT("wind: "); - dtostrf(current_wind, 2, 2, tmp); - DEBUG_PRINTLN(tmp) - - current_weather_ok = true; - } else { - current_weather_ok = false; - } + time_t time = os.now_tz(); + if (last_weather_time == 0) last_weather_time = time - 59 * 60; + + if (time < last_weather_time + 60 * 60) return; + + // use temp buffer to construct get command + BufferFiller bf = tmp_buffer; + bf.emit_p(PSTR("weatherData?loc=$O&wto=$O"), SOPT_LOCATION, + SOPT_WEATHER_OPTS); + + char *src = tmp_buffer + strlen(tmp_buffer); + char *dst = tmp_buffer + TMP_BUFFER_SIZE - 12; + + char c; + // url encode. convert SPACE to %20 + // copy reversely from the end because we are potentially expanding + // the string size + while (src != tmp_buffer) { + c = *src--; + if (c == ' ') { + *dst-- = '0'; + *dst-- = '2'; + *dst-- = '%'; + } else { + *dst-- = c; + } + }; + *dst = *src; + + strcpy(ether_buffer, "GET /"); + strcat(ether_buffer, dst); + // because dst is part of tmp_buffer, + // must load weather url AFTER dst is copied to ether_buffer + + // load weather url to tmp_buffer + char *host = tmp_buffer; + os.sopt_load(SOPT_WEATHERURL, host); + + strcat(ether_buffer, " HTTP/1.0\r\nHOST: "); + strcat(ether_buffer, host); + strcat(ether_buffer, "\r\n\r\n"); + + DEBUG_PRINTLN(F("GetSensorWeather")); + DEBUG_PRINTLN(ether_buffer); + + int ret = os.send_http_request(host, ether_buffer, NULL, 2000, + 1); // timeout=2000, ntries=1 + if (ret == HTTP_RQT_SUCCESS) { + last_weather_time = time; + DEBUG_PRINTLN(ether_buffer); + + char buf[20]; + char *s = strstr(ether_buffer, "\"temp\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_temp = atof(buf); + } + s = strstr(ether_buffer, "\"humidity\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_humidity = atof(buf); + } + s = strstr(ether_buffer, "\"precip\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_precip = atof(buf); + } + s = strstr(ether_buffer, "\"wind\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_wind = atof(buf); + } + char tmp[10]; + DEBUG_PRINT("temp: "); + dtostrf(current_temp, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("humidity: "); + dtostrf(current_humidity, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("precip: "); + dtostrf(current_precip, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("wind: "); + dtostrf(current_wind, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + + current_weather_ok = true; + } else { + current_weather_ok = false; + } } void SensorUrl_load() { - sensorUrls = NULL; - DEBUG_PRINTLN("SensorUrl_load1"); - if (!file_exists(SENSORURL_FILENAME)) - return; - - DEBUG_PRINTLN("SensorUrl_load2"); - ulong pos = 0; - SensorUrl_t *last = NULL; - while (true) { - SensorUrl_t *sensorUrl = new SensorUrl_t; - memset(sensorUrl, 0, sizeof(SensorUrl_t)); - if (file_read_block (SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE) < SENSORURL_STORE_SIZE) - { - delete sensorUrl; - break; - } - sensorUrl->urlstr = (char*)malloc(sensorUrl->length+1); - pos += SENSORURL_STORE_SIZE; - file_read_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, sensorUrl->length); - sensorUrl->urlstr[sensorUrl->length] = 0; - pos += sensorUrl->length; - - DEBUG_PRINT(sensorUrl->nr); DEBUG_PRINT("/"); DEBUG_PRINT(sensorUrl->type);DEBUG_PRINT(": "); - DEBUG_PRINTLN(sensorUrl->urlstr); - if (!last) sensorUrls = sensorUrl; - else last->next = sensorUrl; - last = sensorUrl; - sensorUrl->next = NULL; - } - DEBUG_PRINTLN("SensorUrl_load3"); + sensorUrls = NULL; + DEBUG_PRINTLN("SensorUrl_load1"); + if (!file_exists(SENSORURL_FILENAME)) return; + + DEBUG_PRINTLN("SensorUrl_load2"); + ulong pos = 0; + SensorUrl_t *last = NULL; + while (true) { + SensorUrl_t *sensorUrl = new SensorUrl_t; + memset(sensorUrl, 0, sizeof(SensorUrl_t)); + if (file_read_block(SENSORURL_FILENAME, sensorUrl, pos, + SENSORURL_STORE_SIZE) < SENSORURL_STORE_SIZE) { + delete sensorUrl; + break; + } + sensorUrl->urlstr = (char *)malloc(sensorUrl->length + 1); + pos += SENSORURL_STORE_SIZE; + file_read_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, + sensorUrl->length); + sensorUrl->urlstr[sensorUrl->length] = 0; + pos += sensorUrl->length; + + DEBUG_PRINT(sensorUrl->nr); + DEBUG_PRINT("/"); + DEBUG_PRINT(sensorUrl->type); + DEBUG_PRINT(": "); + DEBUG_PRINTLN(sensorUrl->urlstr); + if (!last) + sensorUrls = sensorUrl; + else + last->next = sensorUrl; + last = sensorUrl; + sensorUrl->next = NULL; + } + DEBUG_PRINTLN("SensorUrl_load3"); } void SensorUrl_save() { - if (file_exists(SENSORURL_FILENAME)) - remove_file(SENSORURL_FILENAME); - - ulong pos = 0; - SensorUrl_t *sensorUrl = sensorUrls; - while (sensorUrl) { - file_write_block(SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE); - pos += SENSORURL_STORE_SIZE; - file_write_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, sensorUrl->length); - pos += sensorUrl->length; + if (file_exists(SENSORURL_FILENAME)) remove_file(SENSORURL_FILENAME); - sensorUrl = sensorUrl->next; - } + ulong pos = 0; + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + file_write_block(SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE); + pos += SENSORURL_STORE_SIZE; + file_write_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, + sensorUrl->length); + pos += sensorUrl->length; + + sensorUrl = sensorUrl->next; + } } bool SensorUrl_delete(uint nr, uint type) { - SensorUrl_t *sensorUrl = sensorUrls; - SensorUrl_t *last = NULL; - while (sensorUrl) { - if (sensorUrl->nr == nr && sensorUrl->type == type) { - if (last) - last->next = sensorUrl->next; - else - sensorUrls = sensorUrl->next; - - sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); - - free(sensorUrl->urlstr); - delete sensorUrl; - SensorUrl_save(); - return true; - } - last = sensorUrl; - sensorUrl = sensorUrl->next; - } - return false; + SensorUrl_t *sensorUrl = sensorUrls; + SensorUrl_t *last = NULL; + while (sensorUrl) { + if (sensorUrl->nr == nr && sensorUrl->type == type) { + if (last) + last->next = sensorUrl->next; + else + sensorUrls = sensorUrl->next; + + sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); + + free(sensorUrl->urlstr); + delete sensorUrl; + SensorUrl_save(); + return true; + } + last = sensorUrl; + sensorUrl = sensorUrl->next; + } + return false; } bool SensorUrl_add(uint nr, uint type, const char *urlstr) { - if (!urlstr || !strlen(urlstr)) { //empty string? delete! - return SensorUrl_delete(nr, type); - } - SensorUrl_t *sensorUrl = sensorUrls; - while (sensorUrl) { - if (sensorUrl->nr == nr && sensorUrl->type == type) { //replace existing - sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); - free(sensorUrl->urlstr); - sensorUrl->length = strlen(urlstr); - sensorUrl->urlstr = strdup(urlstr); - SensorUrl_save(); - sensor_mqtt_subscribe(nr, type, urlstr); - return true; - } - sensorUrl = sensorUrl->next; - } - - //Add new: - sensorUrl = new SensorUrl_t; - memset(sensorUrl, 0, sizeof(SensorUrl_t)); - sensorUrl->nr = nr; - sensorUrl->type = type; - sensorUrl->length = strlen(urlstr); - sensorUrl->urlstr = strdup(urlstr); - sensorUrl->next = sensorUrls; - sensorUrls = sensorUrl; - SensorUrl_save(); - - sensor_mqtt_subscribe(nr, type, urlstr); - - return true; + if (!urlstr || !strlen(urlstr)) { // empty string? delete! + return SensorUrl_delete(nr, type); + } + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + if (sensorUrl->nr == nr && sensorUrl->type == type) { // replace existing + sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); + free(sensorUrl->urlstr); + sensorUrl->length = strlen(urlstr); + sensorUrl->urlstr = strdup(urlstr); + SensorUrl_save(); + sensor_mqtt_subscribe(nr, type, urlstr); + return true; + } + sensorUrl = sensorUrl->next; + } + + // Add new: + sensorUrl = new SensorUrl_t; + memset(sensorUrl, 0, sizeof(SensorUrl_t)); + sensorUrl->nr = nr; + sensorUrl->type = type; + sensorUrl->length = strlen(urlstr); + sensorUrl->urlstr = strdup(urlstr); + sensorUrl->next = sensorUrls; + sensorUrls = sensorUrl; + SensorUrl_save(); + + sensor_mqtt_subscribe(nr, type, urlstr); + + return true; } char *SensorUrl_get(uint nr, uint type) { - SensorUrl_t *sensorUrl = sensorUrls; - while (sensorUrl) { - if (sensorUrl->nr == nr && sensorUrl->type == type) //replace existing - return sensorUrl->urlstr; - sensorUrl = sensorUrl->next; - } - return NULL; + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + if (sensorUrl->nr == nr && sensorUrl->type == type) // replace existing + return sensorUrl->urlstr; + sensorUrl = sensorUrl->next; + } + return NULL; } - diff --git a/sensors.h b/sensors.h index 192c786b7..eab980a75 100644 --- a/sensors.h +++ b/sensors.h @@ -16,23 +16,23 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see - * . + * . */ #ifndef _SENSORS_H #define _SENSORS_H #if defined(ARDUINO) - #include - #include -#else // headers for RPI/BBB - #include - #include - #include - #include +#include +#include +#else // headers for RPI/BBB +#include +#include +#include +#include extern "C" { - #include - #include +#include +#include } #endif #include "defines.h" @@ -41,212 +41,245 @@ extern "C" { #include #endif -//Files -#define SENSOR_FILENAME "sensor.dat" // analog sensor filename -#define SENSOR_FILENAME_BAK "sensor.bak" // analog sensor filename backup -#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename -#define SENSORLOG_FILENAME1 "sensorlog.dat" // analog sensor log filename -#define SENSORLOG_FILENAME2 "sensorlog2.dat" // analog sensor log filename2 - -#define SENSORLOG_FILENAME_WEEK1 "sensorlogW1.dat" // analog sensor log filename for week average -#define SENSORLOG_FILENAME_WEEK2 "sensorlogW2.dat" // analog sensor log filename2 for week average -#define SENSORLOG_FILENAME_MONTH1 "sensorlogM1.dat" // analog sensor log filename for month average -#define SENSORLOG_FILENAME_MONTH2 "sensorlogM2.dat" // analog sensor log filename2 for month average - -#define SENSORURL_FILENAME "sensorurl.dat" // long urls filename - -//MaxLogSize +// Files +#define SENSOR_FILENAME "sensor.dat" // analog sensor filename +#define SENSOR_FILENAME_BAK "sensor.bak" // analog sensor filename backup +#define PROG_SENSOR_FILENAME \ + "progsensor.dat" // sensor to program assign filename +#define SENSORLOG_FILENAME1 "sensorlog.dat" // analog sensor log filename +#define SENSORLOG_FILENAME2 "sensorlog2.dat" // analog sensor log filename2 + +#define SENSORLOG_FILENAME_WEEK1 \ + "sensorlogW1.dat" // analog sensor log filename for week average +#define SENSORLOG_FILENAME_WEEK2 \ + "sensorlogW2.dat" // analog sensor log filename2 for week average +#define SENSORLOG_FILENAME_MONTH1 \ + "sensorlogM1.dat" // analog sensor log filename for month average +#define SENSORLOG_FILENAME_MONTH2 \ + "sensorlogM2.dat" // analog sensor log filename2 for month average + +#define SENSORURL_FILENAME "sensorurl.dat" // long urls filename + +// MaxLogSize #define MAX_LOG_SIZE 8000 #define MAX_LOG_SIZE_WEEK 2000 #define MAX_LOG_SIZE_MONTH 1000 -//Sensor types: -#define SENSOR_NONE 0 //None or deleted sensor -#define SENSOR_SMT100_MODBUS_RTU_MOIS 1 //Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode -#define SENSOR_SMT100_MODBUS_RTU_TEMP 2 //Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode -#define SENSOR_ANALOG_EXTENSION_BOARD 10 //New OpenSprinkler analog extension board x8 - voltage mode 0..4V -#define SENSOR_ANALOG_EXTENSION_BOARD_P 11 //New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% -#define SENSOR_SMT50_MOIS 15 //New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 -#define SENSOR_SMT50_TEMP 16 //New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 -#define SENSOR_SMT100_ANALOG_MOIS 17 //New OpenSprinkler analog extension board x8 - SMT100 VWC [%] = (U * 100) : 3 -#define SENSOR_SMT100_ANALOG_TEMP 18 //New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U * 100) : 3 - 40 - -#define SENSOR_VH400 30 //New OpenSprinkler analog extension board x8 - Vegetronix VH400 -#define SENSOR_THERM200 31 //New OpenSprinkler analog extension board x8 - Vegetronix THERM200 -#define SENSOR_AQUAPLUMB 32 //New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb - -#define SENSOR_USERDEF 49 //New OpenSprinkler analog extension board x8 - User defined sensor - -#define SENSOR_OSPI_ANALOG 50 //Old OSPi analog input - voltage mode 0..3.3V -#define SENSOR_OSPI_ANALOG_P 51 //Old OSPi analog input - percent 0..3.3V to 0...100% -#define SENSOR_OSPI_ANALOG_SMT50_MOIS 52 //Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 -#define SENSOR_OSPI_ANALOG_SMT50_TEMP 53 //Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 - -#define SENSOR_MQTT 90 //subscribe to a MQTT server and query a value - -#define SENSOR_REMOTE 100 //Remote sensor of an remote opensprinkler -#define SENSOR_WEATHER_TEMP_F 101 //Weather service - temperature (Fahrenheit) -#define SENSOR_WEATHER_TEMP_C 102 //Weather service - temperature (Celcius) -#define SENSOR_WEATHER_HUM 103 //Weather service - humidity (%) -#define SENSOR_WEATHER_PRECIP_IN 105 //Weather service - precip (inch) -#define SENSOR_WEATHER_PRECIP_MM 106 //Weather service - precip (mm) -#define SENSOR_WEATHER_WIND_MPH 107 //Weather service - wind (mph) -#define SENSOR_WEATHER_WIND_KMH 108 //Weather service - wind (kmh) - -#define SENSOR_GROUP_MIN 1000 //Sensor group with min value -#define SENSOR_GROUP_MAX 1001 //Sensor group with max value -#define SENSOR_GROUP_AVG 1002 //Sensor group with avg value -#define SENSOR_GROUP_SUM 1003 //Sensor group with sum value - -#define SENSOR_READ_TIMEOUT 3000 //ms - -#define MIN_DISK_FREE 8192 //8Kb min - -#define MAX_SENSOR_REPEAT_READ 32000 //max reads for calculating avg -#define MAX_SENSOR_READ_TIME 1 // second for reading sensors - -//detected Analog Sensor Boards: -#define ASB_BOARD1 0x01 -#define ASB_BOARD2 0x02 +// Sensor types: +#define SENSOR_NONE 0 // None or deleted sensor +#define SENSOR_SMT100_MOIS 1 // Truebner SMT100 RS485, moisture mode +#define SENSOR_SMT100_TEMP 2 // Truebner SMT100 RS485, temperature mode +#define SENSOR_SMT100_PMTY 3 // Truebner SMT100 RS485, permittivity mode +#define SENSOR_ANALOG_EXTENSION_BOARD \ + 10 // New OpenSprinkler analog extension board x8 - voltage mode 0..4V +#define SENSOR_ANALOG_EXTENSION_BOARD_P \ + 11 // New OpenSprinkler analog extension board x8 - percent 0..3.3V to + // 0..100% +#define SENSOR_SMT50_MOIS \ + 15 // New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) + // : 3 +#define SENSOR_SMT50_TEMP \ + 16 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) + // * 100 +#define SENSOR_SMT100_ANALOG_MOIS \ + 17 // New OpenSprinkler analog extension board x8 - SMT100 VWC [%] = (U * + // 100) : 3 +#define SENSOR_SMT100_ANALOG_TEMP \ + 18 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U * 100) + // : 3 - 40 + +#define SENSOR_VH400 \ + 30 // New OpenSprinkler analog extension board x8 - Vegetronix VH400 +#define SENSOR_THERM200 \ + 31 // New OpenSprinkler analog extension board x8 - Vegetronix THERM200 +#define SENSOR_AQUAPLUMB \ + 32 // New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb + +#define SENSOR_USERDEF \ + 49 // New OpenSprinkler analog extension board x8 - User defined sensor + +#define SENSOR_OSPI_ANALOG 50 // Old OSPi analog input - voltage mode 0..3.3V +#define SENSOR_OSPI_ANALOG_P \ + 51 // Old OSPi analog input - percent 0..3.3V to 0...100% +#define SENSOR_OSPI_ANALOG_SMT50_MOIS \ + 52 // Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_OSPI_ANALOG_SMT50_TEMP \ + 53 // Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 + +#define SENSOR_MQTT 90 // subscribe to a MQTT server and query a value + +#define SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler +#define SENSOR_WEATHER_TEMP_F 101 // Weather service - temperature (Fahrenheit) +#define SENSOR_WEATHER_TEMP_C 102 // Weather service - temperature (Celcius) +#define SENSOR_WEATHER_HUM 103 // Weather service - humidity (%) +#define SENSOR_WEATHER_PRECIP_IN 105 // Weather service - precip (inch) +#define SENSOR_WEATHER_PRECIP_MM 106 // Weather service - precip (mm) +#define SENSOR_WEATHER_WIND_MPH 107 // Weather service - wind (mph) +#define SENSOR_WEATHER_WIND_KMH 108 // Weather service - wind (kmh) + +#define SENSOR_GROUP_MIN 1000 // Sensor group with min value +#define SENSOR_GROUP_MAX 1001 // Sensor group with max value +#define SENSOR_GROUP_AVG 1002 // Sensor group with avg value +#define SENSOR_GROUP_SUM 1003 // Sensor group with sum value + +#define SENSOR_READ_TIMEOUT 3000 // ms + +#define MIN_DISK_FREE 8192 // 8Kb min + +#define MAX_SENSOR_REPEAT_READ 32000 // max reads for calculating avg +#define MAX_SENSOR_READ_TIME 1 // second for reading sensors + +// detected Analog Sensor Boards: +#define ASB_BOARD1 0x01 +#define ASB_BOARD2 0x02 #define OSPI_PCF8591 0x04 #define OSPI_ADS1115 0x08 - +#define RS485_TRUEBNER 0x20 typedef struct SensorFlags { - uint enable:1; // enabled - uint log:1; // log data enabled - uint data_ok:1; // last data is ok - uint show:1; // show on mainpage + uint enable : 1; // enabled + uint log : 1; // log data enabled + uint data_ok : 1; // last data is ok + uint show : 1; // show on mainpage } SensorFlags_t; -//Definition of a sensor +// Definition of a sensor typedef struct Sensor { - uint nr; // 1..n sensor-nr, 0=deleted - char name[30]; // name - uint type; // 1..n type definition, 0=deleted - uint group; // group assignment,0=no group - uint32_t ip; // tcp-ip - uint port; // tcp-port / ADC: I2C Address 0x48/0x49 or 0/1 - uint id; // modbus id / ADC: channel - uint read_interval; // seconds - uint32_t last_native_data; // last native sensor data - double last_data; // last converted sensor data - SensorFlags_t flags; // Flags see obove - int16_t factor; // faktor - for custom sensor - int16_t divider; // divider - for custom sensor - char userdef_unit[8]; // unit - for custom sensor - int16_t offset_mv; // offset millivolt - for custom sensor (before) - int16_t offset2; // offset unit value 1/100 - for custom sensor (after): - // sensorvalue = (read_value-offset_mv/1000) * factor / divider + offset2/100 - byte assigned_unitid; // unitid for userdef and mqtt sensors - byte undef[15]; // for later - //unstored: - bool mqtt_init:1; - bool mqtt_push:1; - byte unitid; - uint32_t repeat_read; - double repeat_data; - uint64_t repeat_native; - ulong last_read; //millis - Sensor *next; + uint nr; // 1..n sensor-nr, 0=deleted + char name[30]; // name + uint type; // 1..n type definition, 0=deleted + uint group; // group assignment,0=no group + uint32_t ip; // tcp-ip + uint port; // tcp-port / ADC: I2C Address 0x48/0x49 or 0/1 + uint id; // modbus id / ADC: channel + uint read_interval; // seconds + uint32_t last_native_data; // last native sensor data + double last_data; // last converted sensor data + SensorFlags_t flags; // Flags see obove + int16_t factor; // faktor - for custom sensor + int16_t divider; // divider - for custom sensor + char userdef_unit[8]; // unit - for custom sensor + int16_t offset_mv; // offset millivolt - for custom sensor (before) + int16_t offset2; // offset unit value 1/100 - for custom sensor (after): + // sensorvalue = (read_value-offset_mv/1000) * factor / + // divider + offset2/100 + byte assigned_unitid; // unitid for userdef and mqtt sensors + byte undef[15]; // for later + // unstored: + bool mqtt_init : 1; + bool mqtt_push : 1; + byte unitid; + uint32_t repeat_read; + double repeat_data; + uint64_t repeat_native; + ulong last_read; // millis + Sensor *next; } Sensor_t; #define SENSOR_STORE_SIZE 111 -//Definition of a log data +// Definition of a log data typedef struct SensorLog { - uint nr; //sensor-nr - ulong time; - uint32_t native_data; - double data; + uint nr; // sensor-nr + ulong time; + uint32_t native_data; + double data; } SensorLog_t; #define SENSORLOG_STORE_SIZE (sizeof(SensorLog_t)) -//Sensor to program data -//Adjustment is formula -// min max factor1 factor2 -// 10..90 -> 5..1 factor1 > factor2 -// a b c d -// (b-sensorData) / (b-a) * (c-d) + d +// Sensor to program data +// Adjustment is formula +// min max factor1 factor2 +// 10..90 -> 5..1 factor1 > factor2 +// a b c d +// (b-sensorData) / (b-a) * (c-d) + d // -// 10..90 -> 1..5 factor1 < factor2 -// a b c d -// (sensorData-a) / (b-a) * (d-c) + c +// 10..90 -> 1..5 factor1 < factor2 +// a b c d +// (sensorData-a) / (b-a) * (d-c) + c -#define PROG_DELETE 0 //deleted -#define PROG_LINEAR 1 //formula see above -#define PROG_DIGITAL_MIN 2 //under or equal min : factor1 else factor2 -#define PROG_DIGITAL_MAX 3 //over or equal max : factor2 else factor1 -#define PROG_DIGITAL_MINMAX 4 //under min or over max : factor1 else factor2 -#define PROG_NONE 99 //No adjustment +#define PROG_DELETE 0 // deleted +#define PROG_LINEAR 1 // formula see above +#define PROG_DIGITAL_MIN 2 // under or equal min : factor1 else factor2 +#define PROG_DIGITAL_MAX 3 // over or equal max : factor2 else factor1 +#define PROG_DIGITAL_MINMAX 4 // under min or over max : factor1 else factor2 +#define PROG_NONE 99 // No adjustment typedef struct ProgSensorAdjust { - uint nr; //adjust-nr 1..x - uint type; //PROG_XYZ type=0 -->delete - uint sensor; //sensor-nr - uint prog; //program-nr=pid - double factor1; - double factor2; - double min; - double max; - byte undef[32]; //for later - ProgSensorAdjust *next; + uint nr; // adjust-nr 1..x + uint type; // PROG_XYZ type=0 -->delete + uint sensor; // sensor-nr + uint prog; // program-nr=pid + double factor1; + double factor2; + double min; + double max; + byte undef[32]; // for later + ProgSensorAdjust *next; } ProgSensorAdjust_t; -#define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) +#define PROGSENSOR_STORE_SIZE \ + (sizeof(ProgSensorAdjust_t) - sizeof(ProgSensorAdjust_t *)) -#define SENSORURL_TYPE_URL 0 //URL for Host/Path -#define SENSORURL_TYPE_TOPIC 1 //TOPIC for MQTT -#define SENSORURL_TYPE_FILTER 2 //JSON Filter for MQTT +#define SENSORURL_TYPE_URL 0 // URL for Host/Path +#define SENSORURL_TYPE_TOPIC 1 // TOPIC for MQTT +#define SENSORURL_TYPE_FILTER 2 // JSON Filter for MQTT typedef struct SensorUrl { - uint nr; - uint type; //see SENSORURL_TYPE - uint length; - char *urlstr; - SensorUrl *next; + uint nr; + uint type; // see SENSORURL_TYPE + uint length; + char *urlstr; + SensorUrl *next; } SensorUrl_t; -#define SENSORURL_STORE_SIZE (sizeof(SensorUrl_t)-sizeof(char*)-sizeof(SensorUrl_t*)) - -#define UNIT_NONE 0 -#define UNIT_PERCENT 1 -#define UNIT_DEGREE 2 -#define UNIT_FAHRENHEIT 3 -#define UNIT_VOLT 4 +#define SENSORURL_STORE_SIZE \ + (sizeof(SensorUrl_t) - sizeof(char *) - sizeof(SensorUrl_t *)) + +#define UNIT_NONE 0 +#define UNIT_PERCENT 1 +#define UNIT_DEGREE 2 +#define UNIT_FAHRENHEIT 3 +#define UNIT_VOLT 4 #define UNIT_HUM_PERCENT 5 -#define UNIT_INCH 6 -#define UNIT_MM 7 -#define UNIT_MPH 8 -#define UNIT_KMH 9 -#define UNIT_LEVEL 10 -#define UNIT_USERDEF 99 +#define UNIT_INCH 6 +#define UNIT_MM 7 +#define UNIT_MPH 8 +#define UNIT_KMH 9 +#define UNIT_LEVEL 10 +#define UNIT_DK 11 +#define UNIT_USERDEF 99 -//Unitnames -// extern const char* sensor_unitNames[]; +// Unitnames +// extern const char* sensor_unitNames[]; #define ASB_BOARD_ADDR1a 0x48 #define ASB_BOARD_ADDR1b 0x49 #define ASB_BOARD_ADDR2a 0x4A #define ASB_BOARD_ADDR2b 0x4B +#define RS485_TRUEBNER_ADDR 0x38 void sensor_api_init(); byte get_asb_detected_boards(); -Sensor_t* getSensors(); -const char* getSensorUnit(int unitid); -const char* getSensorUnit(Sensor_t *sensor); +Sensor_t *getSensors(); +const char *getSensorUnit(int unitid); +const char *getSensorUnit(Sensor_t *sensor); byte getSensorUnitId(int type); byte getSensorUnitId(Sensor_t *sensor); extern char ether_buffer[]; extern char tmp_buffer[]; -//Utils: -uint16_t CRC16 (byte buf[], int len); +// Utils: +uint16_t CRC16(byte buf[], int len); -//Sensor API functions: +// Sensor API functions: int sensor_delete(uint nr); -int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, int16_t factor, int16_t divider, - const char *userdef_unit, int16_t offset_mv, int16_t offset2, SensorFlags_t flags, int16_t assigned_unitid); -int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, const char *userdef_unit, int16_t offset_mv, int16_t offset2, int16_t sensor_define_userdef); +int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, + uint port, uint id, uint ri, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, int16_t offset2, + SensorFlags_t flags, int16_t assigned_unitid); +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, + int16_t offset2, int16_t sensor_define_userdef); void sensor_load(); void sensor_save(); uint sensor_count(); @@ -258,29 +291,32 @@ void read_all_sensors(boolean online); Sensor_t *sensor_by_nr(uint nr); Sensor_t *sensor_by_idx(uint idx); -int read_sensor(Sensor_t *sensor, ulong time); //sensor value goes to last_native_data/last_data +int read_sensor(Sensor_t *sensor, + ulong time); // sensor value goes to last_native_data/last_data -//Sensorlog API functions: -#define LOG_STD 0 -#define LOG_WEEK 1 +// Sensorlog API functions: +#define LOG_STD 0 +#define LOG_WEEK 1 #define LOG_MONTH 2 bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog); bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time); void sensorlog_clear_all(); void sensorlog_clear(bool std, bool week, bool month); -ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, double under, bool use_over, double over); +ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, + double under, bool use_over, double over); SensorLog_t *sensorlog_load(uint8_t log, ulong pos); -SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t* sensorlog); -int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t* sensorlog); +SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t *sensorlog); +int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t *sensorlog); ulong sensorlog_filesize(uint8_t log); ulong sensorlog_size(uint8_t log); ulong findLogPosition(uint8_t log, ulong after); -//Set Sensor Address for SMT100: -int set_sensor_address(Sensor_t *sensor, byte new_address); +// Set Sensor Address for SMT100: +int set_sensor_address(Sensor_t *sensor, uint8_t new_address); -//Calc watering adjustment: -int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max); +// Calc watering adjustment: +int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, + double factor1, double factor2, double min, double max); int prog_adjust_delete(uint nr); void prog_adjust_save(); void prog_adjust_load(); @@ -292,10 +328,10 @@ double calc_sensor_watering_by_nr(uint nr); double calc_sensor_watering_int(ProgSensorAdjust_t *p, double sensorData); void GetSensorWeather(); -//PUSH Message to MQTT and others: +// PUSH Message to MQTT and others: void push_message(Sensor_t *sensor); -//Web URLS Host/Path and MQTT topics: +// Web URLS Host/Path and MQTT topics: void SensorUrl_load(); void SensorUrl_save(); bool SensorUrl_delete(uint nr, uint type); @@ -306,7 +342,7 @@ void detect_asb_board(); #if defined(ESP8266) ulong diskFree(); -bool checkDiskFree(); //true: disk space Ok, false: Out of disk space +bool checkDiskFree(); // true: disk space Ok, false: Out of disk space #endif -#endif // _SENSORS_H +#endif // _SENSORS_H From 9596e7d9b9751e8212804c525e99ef27dd70a484 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 21 Jul 2024 01:54:11 +0200 Subject: [PATCH 172/281] Fixt compile error on ospi --- sensors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 5a64d07f0..70bebea78 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1293,11 +1293,11 @@ int read_sensor_ip(Sensor_t *sensor) { case SENSOR_SMT100_MOIS: case SENSOR_SMT100_TEMP: case SENSOR_SMT100_PMTY: - // uint32_t stoptime = millis() + SENSOR_READ_TIMEOUT; + uint32_t stoptime = millis() + SENSOR_READ_TIMEOUT; #if defined(ESP8266) while (true) { if (client->available()) break; - // if (millis() >= stoptime) + if (millis() >= stoptime) { client->stop(); DEBUG_PRINT(F("Sensor ")); From d5a7a436f5dcd4e96be5b3a1cf14985408e6a7d3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 21 Jul 2024 22:56:41 +0200 Subject: [PATCH 173/281] Fixt Log-rotate switching --- sensors.cpp | 36 +++++++++++++++++++++++++++--------- sensors.h | 5 +++++ 2 files changed, 32 insertions(+), 9 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 5a64d07f0..b4d19ef9f 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -77,7 +77,7 @@ const char *sensor_unitNames[]{ // 10=Level % // 11=DK }; -byte logFileSwitch[3] = {0, 0, 0}; // 0=use smaller File, 1=LOG1, 2=LOG2 +uint8_t logFileSwitch[3] = {0, 0, 0}; // 0=use smaller File, 1=LOG1, 2=LOG2 // Weather time_t last_weather_time = 0; @@ -131,6 +131,24 @@ void detect_asb_board() { #endif DEBUG_PRINT("ASB DETECT="); DEBUG_PRINTLN(asb_detected_boards); + + for (int log = 0; log <= 2; log++) { + checkLogSwitch(log); +#if defined(ENABLE_DEBUG) + DEBUG_PRINT("log="); + DEBUG_PRINTLN(log); + const char *f1 = getlogfile(log); + DEBUG_PRINT("logfile1="); + DEBUG_PRINTLN(f1); + DEBUG_PRINT("size1="); + DEBUG_PRINTLN(file_size(f1)); + const char *f2 = getlogfile2(log); + DEBUG_PRINT("logfile2="); + DEBUG_PRINTLN(f2); + DEBUG_PRINT("size2="); + DEBUG_PRINTLN(file_size(f2)); +#endif + } } byte get_asb_detected_boards() { return asb_detected_boards; } @@ -370,14 +388,14 @@ Sensor_t *sensor_by_idx(uint idx) { * @return const char* */ const char *getlogfile(uint8_t log) { - bool sw = logFileSwitch[log]; + uint8_t sw = logFileSwitch[log]; switch (log) { case 0: - return sw ? SENSORLOG_FILENAME1 : SENSORLOG_FILENAME2; + return sw < 2 ? SENSORLOG_FILENAME1 : SENSORLOG_FILENAME2; case 1: - return sw ? SENSORLOG_FILENAME_WEEK1 : SENSORLOG_FILENAME_WEEK2; + return sw < 2 ? SENSORLOG_FILENAME_WEEK1 : SENSORLOG_FILENAME_WEEK2; case 2: - return sw ? SENSORLOG_FILENAME_MONTH1 : SENSORLOG_FILENAME_MONTH2; + return sw < 2 ? SENSORLOG_FILENAME_MONTH1 : SENSORLOG_FILENAME_MONTH2; } return ""; } @@ -389,14 +407,14 @@ const char *getlogfile(uint8_t log) { * @return const char* */ const char *getlogfile2(uint8_t log) { - bool sw = logFileSwitch[log]; + uint8_t sw = logFileSwitch[log]; switch (log) { case 0: - return sw ? SENSORLOG_FILENAME2 : SENSORLOG_FILENAME1; + return sw < 2 ? SENSORLOG_FILENAME2 : SENSORLOG_FILENAME1; case 1: - return sw ? SENSORLOG_FILENAME_WEEK2 : SENSORLOG_FILENAME_WEEK1; + return sw < 2 ? SENSORLOG_FILENAME_WEEK2 : SENSORLOG_FILENAME_WEEK1; case 2: - return sw ? SENSORLOG_FILENAME_MONTH2 : SENSORLOG_FILENAME_MONTH1; + return sw < 2 ? SENSORLOG_FILENAME_MONTH2 : SENSORLOG_FILENAME_MONTH1; } return ""; } diff --git a/sensors.h b/sensors.h index eab980a75..67c428ee7 100644 --- a/sensors.h +++ b/sensors.h @@ -310,6 +310,11 @@ int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t *sensorlog); ulong sensorlog_filesize(uint8_t log); ulong sensorlog_size(uint8_t log); ulong findLogPosition(uint8_t log, ulong after); +const char *getlogfile(uint8_t log); +const char *getlogfile2(uint8_t log); +void checkLogSwitch(uint8_t log); +void checkLogSwitchAfterWrite(uint8_t log); + // Set Sensor Address for SMT100: int set_sensor_address(Sensor_t *sensor, uint8_t new_address); From 0a7517ce03165f70e23ab4b2372857b6b44c50a7 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 22 Jul 2024 23:52:18 +0200 Subject: [PATCH 174/281] completed final truebner rs485 protocol --- sensors.cpp | 28 ++++++++-------------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 7824cf47c..b41e417b0 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1124,10 +1124,11 @@ int read_sensor_i2c(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_i2c: check-ok")); + uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 + : sensor->type == SENSOR_SMT100_MOIS ? 0x01 + : 0x02; + if (sensor->repeat_read == 0 || sensor->repeat_read == 1000) { - uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 - : sensor->type == SENSOR_SMT100_MOIS ? 0x01 - : 0x02; Wire.beginTransmission(RS485_TRUEBNER_ADDR); Wire.write((uint8_t)sensor->id); Wire.write(type); @@ -1141,25 +1142,13 @@ int read_sensor_i2c(Sensor_t *sensor) { // delay(500); } - if (Wire.requestFrom((uint8_t)RS485_TRUEBNER_ADDR, (size_t)3, true)) { + if (Wire.requestFrom((uint8_t)RS485_TRUEBNER_ADDR, (size_t)4, true)) { // read the incoming bytes: uint8_t addr = Wire.read(); + uint8_t reg = Wire.read(); uint8_t low_byte = Wire.read(); uint8_t high_byte = Wire.read(); - if (addr == sensor->id) { - // BUG FIX: Double read - if (sensor->repeat_read == 1) { - sensor->repeat_read++; - uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 - : sensor->type == SENSOR_SMT100_MOIS ? 0x01 - : 0x02; - Wire.beginTransmission(RS485_TRUEBNER_ADDR); - Wire.write((uint8_t)sensor->id); - Wire.write(type); - Wire.endTransmission(); - return HTTP_RQT_NOT_RECEIVED; - } - + if (addr == sensor->id && reg == type) { uint16_t data = (high_byte << 8) | low_byte; DEBUG_PRINTF("read_sensor_i2c: result: %d - %d (%d %d)\n", sensor->id, data, low_byte, high_byte); @@ -1315,8 +1304,7 @@ int read_sensor_ip(Sensor_t *sensor) { #if defined(ESP8266) while (true) { if (client->available()) break; - if (millis() >= stoptime) - { + if (millis() >= stoptime) { client->stop(); DEBUG_PRINT(F("Sensor ")); DEBUG_PRINT(sensor->nr); From 804ea7b989d413b57af7db4f85d512a53bce98ad Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 23 Jul 2024 23:16:14 +0200 Subject: [PATCH 175/281] rs485 minor changes for multiple devices --- sensors.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sensors.cpp b/sensors.cpp index b41e417b0..5a34be2d2 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1119,6 +1119,9 @@ int read_sensor_i2c(Sensor_t *sensor) { sensor->repeat_read = 1000; DEBUG_PRINT(F("cant' read, allocated by sensor ")); DEBUG_PRINTLN(i2c_rs485_allocated); + Sensor_t *t = sensor_by_nr(i2c_rs485_allocated); + if (!t || !t->flags.enable) + i2c_rs485_allocated = 0; //breakout return HTTP_RQT_NOT_RECEIVED; } @@ -1665,6 +1668,9 @@ int set_sensor_address_i2c(Sensor_t *sensor, uint8_t new_address) { if (i2c_rs485_allocated > 0) { DEBUG_PRINT(F("sensor currently allocated by ")); DEBUG_PRINTLN(i2c_rs485_allocated); + Sensor_t *t = sensor_by_nr(i2c_rs485_allocated); + if (!t || !t->flags.enable) + i2c_rs485_allocated = 0; //breakout return HTTP_RQT_NOT_RECEIVED; } From 8fe13ef8d21a5d2e9863f7db542934f758641f5e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 24 Jul 2024 00:03:47 +0200 Subject: [PATCH 176/281] Changed /sd for program adjustment calculation --- opensprinkler_server.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 466ac4537..8300f874a 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3175,8 +3175,12 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { handle_return(HTML_DATA_MISSING); progAdj.max = atof(tmp_buffer); // Max value + byte unitId = getSensorUnitId(sensor); + int diff = progAdj.max-progAdj.min; int minEx = progAdj.min - diff/2; + if (minEx < 0 && (unitId == UNIT_PERCENT || (unitId >= UNIT_VOLT && unitId < UNIT_USERDEF))) + minEx = 0; int maxEx = progAdj.max + diff/2; #if defined(OTF_ENABLED) From 0112509af201fc13c7a1d5b09621f2fcde50d8e2 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 24 Jul 2024 13:25:26 +0200 Subject: [PATCH 177/281] added /du log size for diagnostics --- opensprinkler_server.cpp | 17 +++++++++++++++-- sensors.cpp | 6 +++--- sensors.h | 4 ++-- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8300f874a..8f5184f00 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3092,7 +3092,7 @@ extern uint32_t ping_ok; boolean ok = LittleFS.info(fsinfo); - bfill.emit_p(PSTR("{\"status\":$D,\"freeMemory\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D,\"pingok\":$D,\"mqtt\":$D,\"ifttt\":$D}"), + bfill.emit_p(PSTR("{\"status\":$D,\"freeMemory\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D,\"pingok\":$D,\"mqtt\":$D,\"ifttt\":$D"), ok, freeMemory(), fsinfo.totalBytes, @@ -3105,10 +3105,23 @@ extern uint32_t ping_ok; ping_ok, os.mqtt.connected(), os.iopts[IOPT_IFTTT_ENABLE]); + #else - bfill.emit_p(PSTR("{\"status\":$D,\"mqtt\":$D,\"ifttt\":$D}"), 1, os.mqtt.connected(), os.iopts[IOPT_IFTTT_ENABLE]); + bfill.emit_p(PSTR("{\"status\":$D,\"mqtt\":$D,\"ifttt\":$D"), 1, os.mqtt.connected(), os.iopts[IOPT_IFTTT_ENABLE]); #endif + + bfill.emit_p(PSTR(",\"logfiles\":{\"l01\":$D,\"l02\":$D,\"l11\":$D,\"l12\":$D,\"l21\":$D,\"l22\":$D}"), + file_size(SENSORLOG_FILENAME1) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME2) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME_WEEK1) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME_WEEK2) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME_MONTH1) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME_MONTH2) / sizeof(SensorLog_t)); + + bfill.emit_p(PSTR("}")); + + handle_return(HTML_OK); } diff --git a/sensors.cpp b/sensors.cpp index 5a34be2d2..6d0436338 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -390,11 +390,11 @@ Sensor_t *sensor_by_idx(uint idx) { const char *getlogfile(uint8_t log) { uint8_t sw = logFileSwitch[log]; switch (log) { - case 0: + case LOG_STD: return sw < 2 ? SENSORLOG_FILENAME1 : SENSORLOG_FILENAME2; - case 1: + case LOG_WEEK: return sw < 2 ? SENSORLOG_FILENAME_WEEK1 : SENSORLOG_FILENAME_WEEK2; - case 2: + case LOG_MONTH: return sw < 2 ? SENSORLOG_FILENAME_MONTH1 : SENSORLOG_FILENAME_MONTH2; } return ""; diff --git a/sensors.h b/sensors.h index 67c428ee7..4d260ccd9 100644 --- a/sensors.h +++ b/sensors.h @@ -295,8 +295,8 @@ int read_sensor(Sensor_t *sensor, ulong time); // sensor value goes to last_native_data/last_data // Sensorlog API functions: -#define LOG_STD 0 -#define LOG_WEEK 1 +#define LOG_STD 0 +#define LOG_WEEK 1 #define LOG_MONTH 2 bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog); bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time); From 8f146863676faad77f146c3f3c87e4354b917ec4 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 24 Jul 2024 23:54:07 +0200 Subject: [PATCH 178/281] program calc (/sd): added current and adjustment for current --- opensprinkler_server.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8f5184f00..89729f910 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3205,7 +3205,8 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { print_header(); #endif - bfill.emit_p(PSTR("{\"adjustment\":{\"min\":$D,\"max\":$D,\"unit\":\"$S\","), minEx, maxEx, getSensorUnit(sensor)); + bfill.emit_p(PSTR("{\"adjustment\":{\"min\":$D,\"max\":$D,\"current\":$E,\"adjust\":$E,\"unit\":\"$S\","), minEx, maxEx, + sensor->last_data, calc_sensor_watering_int(&progAdj, sensor->last_data), getSensorUnit(sensor)); int nvalues = max(11, maxEx-minEx+1); double inVal[nvalues]; From 4f8ffae69c7afcbbdd2d3cf8109ae3a9ae96021e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 28 Jul 2024 23:22:40 +0200 Subject: [PATCH 179/281] OSPi: fix nullpointer-free bug --- etherport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etherport.cpp b/etherport.cpp index 56b62ef0b..cdf483e89 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -179,7 +179,7 @@ void EthernetClient::stop() m_sock = 0; m_connected = false; tmpbufidx = tmpbufsize = 0; - free(tmpbuf); + if (tmpbuf) free(tmpbuf); tmpbuf = NULL; } } From eb6f0d0e0d5c4f0082d37eba15dd8ff7f6fb44ee Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 6 Aug 2024 13:23:49 +0200 Subject: [PATCH 180/281] OSPi: flow meter optimization: Ethernet wait time reduced --- etherport.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/etherport.cpp b/etherport.cpp index cdf483e89..a03786d51 100644 --- a/etherport.cpp +++ b/etherport.cpp @@ -100,7 +100,7 @@ bool EthernetServer::begin() } // This function blocks until we get a client connected. -// It will timeout after 50ms and return a blank client. +// It will timeout after 5ms and return a blank client. // If it succeeds it will return an EthernetClient. EthernetClient EthernetServer::available() { @@ -108,7 +108,7 @@ EthernetClient EthernetServer::available() memset(&fds, 0, sizeof(fds)); fds.fd = m_sock; fds.events = POLLIN; - int timeout = 50; + int timeout = 5; int rc = poll(&fds, 1, timeout); if (rc > 0) @@ -289,7 +289,7 @@ bool EthernetClient::available() { memset(&fds, 0, sizeof(fds)); fds.fd = m_sock; fds.events = POLLIN; - int timeout = 50; + int timeout = 5; int rc = poll(&fds, 1, timeout); return rc > 0; From 23f2c0a21414b64e9ccb3a325afc66775bd4a18f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 6 Aug 2024 22:54:52 +0200 Subject: [PATCH 181/281] Added support for multiple truebner rs485 interfaces --- sensors.cpp | 45 ++++++++++++++++++++++++++------------------- sensors.h | 18 ++++++++++++------ 2 files changed, 38 insertions(+), 25 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 6d0436338..b231ba2e9 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -58,7 +58,7 @@ static ProgSensorAdjust_t *progSensorAdjusts = NULL; // modbus transaction id static uint16_t modbusTcpId = 0; -static uint i2c_rs485_allocated; +static uint i2c_rs485_allocated[4]; const char *sensor_unitNames[]{ "", "%", "°C", "°F", "V", "%", "in", @@ -117,7 +117,10 @@ void detect_asb_board() { if (detect_i2c(ASB_BOARD_ADDR2a) && detect_i2c(ASB_BOARD_ADDR2b)) asb_detected_boards |= ASB_BOARD2; - if (detect_i2c(RS485_TRUEBNER_ADDR)) asb_detected_boards |= RS485_TRUEBNER; + if (detect_i2c(RS485_TRUEBNER1_ADDR)) asb_detected_boards |= RS485_TRUEBNER1; + if (detect_i2c(RS485_TRUEBNER2_ADDR)) asb_detected_boards |= RS485_TRUEBNER2; + if (detect_i2c(RS485_TRUEBNER3_ADDR)) asb_detected_boards |= RS485_TRUEBNER3; + if (detect_i2c(RS485_TRUEBNER4_ADDR)) asb_detected_boards |= RS485_TRUEBNER4; #endif // Old, pre OSPi 1.43 analog inputs: @@ -1113,15 +1116,17 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { */ int read_sensor_i2c(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_i2c")); - if ((asb_detected_boards & RS485_TRUEBNER) == 0) return HTTP_RQT_NOT_RECEIVED; + int device = sensor->port % 4; + if ((asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) + return HTTP_RQT_NOT_RECEIVED; - if (i2c_rs485_allocated > 0 && i2c_rs485_allocated != sensor->nr) { + if (i2c_rs485_allocated[device] > 0 && i2c_rs485_allocated[device] != sensor->nr) { sensor->repeat_read = 1000; DEBUG_PRINT(F("cant' read, allocated by sensor ")); - DEBUG_PRINTLN(i2c_rs485_allocated); - Sensor_t *t = sensor_by_nr(i2c_rs485_allocated); + DEBUG_PRINTLN(i2c_rs485_allocated[device]); + Sensor_t *t = sensor_by_nr(i2c_rs485_allocated[device]); if (!t || !t->flags.enable) - i2c_rs485_allocated = 0; //breakout + i2c_rs485_allocated[device] = 0; //breakout return HTTP_RQT_NOT_RECEIVED; } @@ -1132,20 +1137,20 @@ int read_sensor_i2c(Sensor_t *sensor) { : 0x02; if (sensor->repeat_read == 0 || sensor->repeat_read == 1000) { - Wire.beginTransmission(RS485_TRUEBNER_ADDR); + Wire.beginTransmission(RS485_TRUEBNER1_ADDR + device); Wire.write((uint8_t)sensor->id); Wire.write(type); if (Wire.endTransmission() == 0) { DEBUG_PRINTF("read_sensor_i2c: request send: %d - %d\n", sensor->id, type); sensor->repeat_read = 1; - i2c_rs485_allocated = sensor->nr; + i2c_rs485_allocated[device] = sensor->nr; } return HTTP_RQT_NOT_RECEIVED; // delay(500); } - if (Wire.requestFrom((uint8_t)RS485_TRUEBNER_ADDR, (size_t)4, true)) { + if (Wire.requestFrom((uint8_t)(RS485_TRUEBNER1_ADDR + device), (size_t)4, true)) { // read the incoming bytes: uint8_t addr = Wire.read(); uint8_t reg = Wire.read(); @@ -1166,7 +1171,7 @@ int read_sensor_i2c(Sensor_t *sensor) { sensor->flags.data_ok = true; sensor->repeat_read = 0; - i2c_rs485_allocated = 0; + i2c_rs485_allocated[device] = 0; return HTTP_RQT_SUCCESS; } } @@ -1175,7 +1180,7 @@ int read_sensor_i2c(Sensor_t *sensor) { if (sensor->repeat_read > 4) { // timeout sensor->repeat_read = 0; sensor->flags.data_ok = false; - i2c_rs485_allocated = 0; + i2c_rs485_allocated[device] = 0; DEBUG_PRINTLN(F("read_sensor_i2c: timeout")); } DEBUG_PRINTLN(F("read_sensor_i2c: exit")); @@ -1663,23 +1668,25 @@ int set_sensor_address_ip(Sensor_t *sensor, uint8_t new_address) { */ int set_sensor_address_i2c(Sensor_t *sensor, uint8_t new_address) { DEBUG_PRINTLN(F("set_sensor_address_i2c")); - if ((asb_detected_boards & RS485_TRUEBNER) == 0) return HTTP_RQT_NOT_RECEIVED; + int device = sensor->port % 4; + if ((asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) + return HTTP_RQT_NOT_RECEIVED; - if (i2c_rs485_allocated > 0) { + if (i2c_rs485_allocated[device] > 0) { DEBUG_PRINT(F("sensor currently allocated by ")); - DEBUG_PRINTLN(i2c_rs485_allocated); - Sensor_t *t = sensor_by_nr(i2c_rs485_allocated); + DEBUG_PRINTLN(i2c_rs485_allocated[device]); + Sensor_t *t = sensor_by_nr(i2c_rs485_allocated[device]); if (!t || !t->flags.enable) - i2c_rs485_allocated = 0; //breakout + i2c_rs485_allocated[device] = 0; //breakout return HTTP_RQT_NOT_RECEIVED; } - Wire.beginTransmission(RS485_TRUEBNER_ADDR); + Wire.beginTransmission(RS485_TRUEBNER1_ADDR + device); Wire.write(254); Wire.write(new_address); Wire.endTransmission(); delay(3000); - Wire.requestFrom((uint8_t)RS485_TRUEBNER_ADDR, (size_t)1, true); + Wire.requestFrom((uint8_t)(RS485_TRUEBNER1_ADDR + device), (size_t)1, true); if (Wire.available()) { delay(10); uint8_t modbus_address = Wire.read(); diff --git a/sensors.h b/sensors.h index 4d260ccd9..0f9c01ff7 100644 --- a/sensors.h +++ b/sensors.h @@ -130,11 +130,14 @@ extern "C" { #define MAX_SENSOR_READ_TIME 1 // second for reading sensors // detected Analog Sensor Boards: -#define ASB_BOARD1 0x01 -#define ASB_BOARD2 0x02 -#define OSPI_PCF8591 0x04 -#define OSPI_ADS1115 0x08 -#define RS485_TRUEBNER 0x20 +#define ASB_BOARD1 0x0001 +#define ASB_BOARD2 0x0002 +#define OSPI_PCF8591 0x0004 +#define OSPI_ADS1115 0x0008 +#define RS485_TRUEBNER1 0x0020 +#define RS485_TRUEBNER2 0x0040 +#define RS485_TRUEBNER3 0x0080 +#define RS485_TRUEBNER4 0x0100 typedef struct SensorFlags { uint enable : 1; // enabled @@ -254,7 +257,10 @@ typedef struct SensorUrl { #define ASB_BOARD_ADDR1b 0x49 #define ASB_BOARD_ADDR2a 0x4A #define ASB_BOARD_ADDR2b 0x4B -#define RS485_TRUEBNER_ADDR 0x38 +#define RS485_TRUEBNER1_ADDR 0x38 +#define RS485_TRUEBNER2_ADDR 0x39 +#define RS485_TRUEBNER3_ADDR 0x3A +#define RS485_TRUEBNER4_ADDR 0x3B void sensor_api_init(); byte get_asb_detected_boards(); From 52596459da08287a8ecb51a594af61c984165d91 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 18 Aug 2024 01:01:07 +0200 Subject: [PATCH 182/281] Fixt analog Sensor Api for next gen --- defines.h | 3 +-- main.cpp | 2 +- mqtt.cpp | 1 - opensprinkler_server.cpp | 24 ++++++++++++++++++------ sensors.cpp | 15 ++++++--------- 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/defines.h b/defines.h index c9e3bdc52..383fbecba 100644 --- a/defines.h +++ b/defines.h @@ -31,7 +31,7 @@ typedef unsigned long ulong; #define TMP_BUFFER_SIZE 320 // scratch buffer size /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 231 // Firmware version: 220 means 2.2.0 +#define OS_FW_VERSION 232 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered @@ -281,7 +281,6 @@ enum { SOPT_OTC_OPTS, SOPT_DEVICE_NAME, SOPT_STA_BSSID_CHL, // wifi extra info: bssid and channel - SOPT_MQTT_OPTS2, // MQTT Extra if url+user+pass > 160 SOPT_EMAIL_OPTS, NUM_SOPTS // total number of string options }; diff --git a/main.cpp b/main.cpp index cbe498023..a01681e4c 100644 --- a/main.cpp +++ b/main.cpp @@ -107,7 +107,7 @@ uint32_t ping_ok = 0; void flow_poll() { #if defined(ESP8266) - if(os.hw_rev == 2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + if(os.hw_rev >= 2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 #endif unsigned char curr_flow_state = digitalReadExt(PIN_SENSOR1); if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { // only record on falling edge diff --git a/mqtt.cpp b/mqtt.cpp index 918797fad..1a607ef7a 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -369,7 +369,6 @@ void OSMqtt::begin(void) { // JSON configuration settings in the form of {"en":0|1,"host":"server_name|IP address","port":1883,"user:"","pass":"","pubt":"","subt":""} char *config = tmp_buffer + 1; os.sopt_load(SOPT_MQTT_OPTS, config); - os.sopt_load(SOPT_MQTT_OPTS2, config+MAX_SOPTS_SIZE); if(*config != 0) { // Add the wrapping curly braces to the string diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index fc442c52a..3d0b57f80 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1177,9 +1177,8 @@ void server_json_controller_main(OTF_PARAMS_DEF) { #else os.load_hardware_mac(mac, true); #endif - char mqtt_opt[MAX_SOPTS_SIZE*2]; + char mqtt_opt[MAX_SOPTS_SIZE]; os.sopt_load(SOPT_MQTT_OPTS, mqtt_opt); - os.sopt_load(SOPT_MQTT_OPTS2, mqtt_opt+MAX_SOPTS_SIZE); bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -1521,12 +1520,10 @@ void server_change_options(OTF_PARAMS_DEF) if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mqtt"), true, &keyfound)) { urlDecode(tmp_buffer); os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); - os.sopt_save(SOPT_MQTT_OPTS2, tmp_buffer+MAX_SOPTS_SIZE); os.status.req_mqtt_restart = true; } else if (keyfound) { tmp_buffer[0]=0; os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); - os.sopt_save(SOPT_MQTT_OPTS2, tmp_buffer+MAX_SOPTS_SIZE); os.status.req_mqtt_restart = true; } @@ -3409,10 +3406,25 @@ URLHandler urls[] = { server_change_scripturl,// cu server_json_all, // ja server_pause_queue, // pq + server_sensor_config_userdef,//si + server_sensorurl_get,//sj + server_sensorurl_config,//sk + server_sensor_config,//sc + server_sensor_list,//sl + server_sensor_get,//sg + server_sensor_readnow,//sr + server_set_sensor_address,//sa + server_sensorlog_list,//so + server_sensorlog_clear,//sn + server_sensorprog_config,//sb + server_sensorprog_calc,//sd + server_sensorprog_list,//se + server_sensor_types,//sf + server_usage,//du + server_sensorprog_types,//sh + server_sensorconfig_backup,//sx server_json_debug, // db -#if defined(ARDUINO) //server_fill_files, -#endif }; // handle Ethernet request diff --git a/sensors.cpp b/sensors.cpp index dc8ac72ae..0c17ec4de 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -831,7 +831,7 @@ void push_message(Sensor_t *sensor) { strcat_P(postval, PSTR("\"}")); // char postBuffer[1500]; - BufferFiller bf; + BufferFiller bf = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE*2); bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" "Host: $S\r\n" "Accept: */*\r\n" @@ -839,8 +839,7 @@ void push_message(Sensor_t *sensor) { "Content-Type: application/json\r\n\r\n$S"), SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); - os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, - sensor_remote_http_callback); + os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, sensor_remote_http_callback); DEBUG_PRINTLN("push ifttt2"); } } @@ -1045,7 +1044,7 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { DEBUG_PRINTLN(F("read_sensor_http")); char *p = tmp_buffer; - BufferFiller bf; + BufferFiller bf = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE*2); bf.emit_p(PSTR("GET /sg?pw=$O&nr=$D"), SOPT_PASSWORD, sensor->id); bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0], ip[1], ip[2], @@ -1056,8 +1055,7 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); - int res = os.send_http_request(server, sensor->port, p, NULL, 2000, - 1); // timeout=2000, tries=1 + int res = os.send_http_request(server, sensor->port, p, NULL, false, 500); if (res == HTTP_RQT_SUCCESS) { DEBUG_PRINTLN("Send Ok"); p = ether_buffer; @@ -2191,7 +2189,7 @@ void GetSensorWeather() { if (time < last_weather_time + 60 * 60) return; // use temp buffer to construct get command - BufferFiller bf; + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE); bf.emit_p(PSTR("weatherData?loc=$O&wto=$O"), SOPT_LOCATION, SOPT_WEATHER_OPTS); @@ -2230,8 +2228,7 @@ void GetSensorWeather() { DEBUG_PRINTLN(F("GetSensorWeather")); DEBUG_PRINTLN(ether_buffer); - int ret = os.send_http_request(host, ether_buffer, NULL, 2000, - 1); // timeout=2000, ntries=1 + int ret = os.send_http_request(host, ether_buffer, NULL, false, 500); if (ret == HTTP_RQT_SUCCESS) { last_weather_time = time; DEBUG_PRINTLN(ether_buffer); From 2184a02cbf21b1c2db58398be7e540fd02dfc91c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 18 Aug 2024 02:35:51 +0200 Subject: [PATCH 183/281] Updated OSPi for the new OpenThingsFramework --- build.sh | 8 +- build2.sh | 82 +-- defines.h | 2 + mqtt.cpp | 2 +- opensprinkler_server.cpp | 4 +- opensprinkler_server.h | 4 + otfpi/LinkedMap.h | 101 ---- otfpi/LinuxLocalServer.cpp | 71 --- otfpi/LinuxLocalServer.h | 43 -- otfpi/LocalServer.h | 55 -- otfpi/OpenThingsFramework.cpp | 310 ---------- otfpi/OpenThingsFramework.h | 103 ---- otfpi/Request.cpp | 304 ---------- otfpi/Request.h | 118 ---- otfpi/Response.cpp | 76 --- otfpi/Response.h | 64 --- otfpi/StringBuilder.cpp | 76 --- otfpi/StringBuilder.h | 64 --- otfpi/WebSockets.cpp | 731 ------------------------ otfpi/WebSockets.h | 233 -------- otfpi/WebSocketsClient.cpp | 1004 --------------------------------- otfpi/WebSocketsClient.h | 175 ------ otfpi/WebSocketsVersion.h | 38 -- otfpi/libb64/AUTHORS | 7 - otfpi/libb64/LICENSE | 29 - otfpi/libb64/cdecode.c | 98 ---- otfpi/libb64/cdecode_inc.h | 28 - otfpi/libb64/cencode.c | 119 ---- otfpi/libb64/cencode_inc.h | 31 - otfpi/libsha1/libsha1.c | 202 ------- otfpi/libsha1/libsha1.h | 21 - otfpi/wscompat.cpp | 118 ---- otfpi/wscompat.h | 46 -- sensors.cpp | 68 +-- sensors.h | 16 +- 35 files changed, 90 insertions(+), 4361 deletions(-) delete mode 100644 otfpi/LinkedMap.h delete mode 100644 otfpi/LinuxLocalServer.cpp delete mode 100644 otfpi/LinuxLocalServer.h delete mode 100644 otfpi/LocalServer.h delete mode 100644 otfpi/OpenThingsFramework.cpp delete mode 100644 otfpi/OpenThingsFramework.h delete mode 100644 otfpi/Request.cpp delete mode 100644 otfpi/Request.h delete mode 100644 otfpi/Response.cpp delete mode 100644 otfpi/Response.h delete mode 100644 otfpi/StringBuilder.cpp delete mode 100644 otfpi/StringBuilder.h delete mode 100644 otfpi/WebSockets.cpp delete mode 100644 otfpi/WebSockets.h delete mode 100644 otfpi/WebSocketsClient.cpp delete mode 100644 otfpi/WebSocketsClient.h delete mode 100644 otfpi/WebSocketsVersion.h delete mode 100644 otfpi/libb64/AUTHORS delete mode 100644 otfpi/libb64/LICENSE delete mode 100644 otfpi/libb64/cdecode.c delete mode 100644 otfpi/libb64/cdecode_inc.h delete mode 100644 otfpi/libb64/cencode.c delete mode 100644 otfpi/libb64/cencode_inc.h delete mode 100644 otfpi/libsha1/libsha1.c delete mode 100644 otfpi/libsha1/libsha1.h delete mode 100644 otfpi/wscompat.cpp delete mode 100644 otfpi/wscompat.h diff --git a/build.sh b/build.sh index e1d6c297c..a245d0268 100755 --- a/build.sh +++ b/build.sh @@ -46,15 +46,15 @@ elif [ "$1" == "osbo" ]; then else echo "Installing required libraries..." apt-get update - apt-get install -y libmosquitto-dev - apt-get install -y raspi-gpio + apt-get install -y libmosquitto-dev raspi-gpio libi2c-dev libssl-dev libgpiod-dev if ! command -v raspi-gpio &> /dev/null then echo "Command raspi-gpio is required and is not installed" exit 0 fi - echo "Compiling firmware..." - g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensors.cpp -lpthread -lmosquitto + + + source build2.sh fi if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.service ] && [ -f startOpenSprinkler.sh ] && [ ! -f /etc/systemd/system/OpenSprinkler.service ]; then diff --git a/build2.sh b/build2.sh index a979db40c..fa09eb672 100755 --- a/build2.sh +++ b/build2.sh @@ -1,41 +1,45 @@ #!/bin/bash -echo "Compiling OSPi firmware..." - -ADS1115="" -ADS1115FILES="" -PCF8591="" -PCF8591FILES="" -USEGPIO="" -GPIOLIB="" -if [ -h "/sys/class/gpio/gpio14" ]; then - echo "using raspi-gpio" - USEGPIO="-DRASPIGPIO" -fi -if [ -h "/sys/class/gpio/gpiochip512" ]; then - echo "using libgpiod" - USEGPIO="-DLIBGPIOD" - GPIOLIB="-lgpiod" -fi - -i2cdetect -y 1 |grep "48 49" >/dev/null -if [ "$?" -eq 0 ]; then - echo "found ADS1115" - ADS1115="-DADS1115" - ADS1115FILES="./ospi-analog/driver_ads1115*.c ./ospi-analog/iic.c" -fi - -i2cdetect -y 1 |grep "48 --" >/dev/null -if [ "$?" -eq 0 ]; then - echo "found PCF8591" - PCF8591="-DPCF8591" - PCF8591FILES="./ospi-analog/driver_pcf8591*.c ./ospi-analog/iic.c" -fi - -g++ -g -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 $USEGPIO -std=c++14 \ - -I./ -I./otfpi -I./otfpi/libsha1 -I./otfpi/libb64 \ - ./otfpi/*.cpp ./otfpi/libsha1/libsha1.c ./otfpi/libb64/cencode.c \ - main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp \ - utils.cpp weather.cpp gpio.cpp etherport.cpp mqtt.cpp sensor*.cpp \ - $ADS1115FILES $PCF8591FILES \ - -li2c -lpthread -lmosquitto -lcrypto -lssl $GPIOLIB \ No newline at end of file + echo "Compiling OSPi firmware..." + + USEGPIO="" + GPIOLIB="" + + + if [ -h "/sys/class/gpio/gpiochip512" ]; then + echo "using libgpiod" + USEGPIO="-DLIBGPIOD" + GPIOLIB="-lgpiod" + fi + + + ADS1115="" + ADS1115FILES="" + + I2C=$(i2cdetect -y 1) + if [[ "${I2C,,}" == *"48 --"* ]] ;then + echo "found PCF8591" + PCF8591="-DPCF8591" + PCF8591FILES="./ospi-analog/driver_pcf8591*.c ./ospi-analog/iic.c" + fi + + if [[ "${I2C,,}" == *"48 49"* ]] ;then + echo "found ADS1115" + ADS1115="-DADS1115" + ADS1115FILES="./ospi-analog/driver_ads1115*.c ./ospi-analog/iic.c" + fi + + + echo "Compiling firmware..." + 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++17 -include string.h main.cpp \ + OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp \ + mqtt.cpp smtp.c sensor*.cpp \ + $ADS1115FILES $PCF8591FILES \ + -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws \ + -Iexternal/OpenThings-Framework-Firmware-Library/ \ + $otf -li2c -lpthread -lmosquitto -lssl -lcrypto $GPIOLIB + + + diff --git a/defines.h b/defines.h index 383fbecba..c8c31b14a 100644 --- a/defines.h +++ b/defines.h @@ -511,6 +511,8 @@ enum { #include #include #include + inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} + inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} #define pgm_read_byte(x) *(x) #define PSTR(x) x #define F(x) x diff --git a/mqtt.cpp b/mqtt.cpp index 1a607ef7a..360e3a21c 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -677,7 +677,7 @@ static void _mqtt_connection_cb(struct mosquitto *mqtt_client, void *obj, int re if (reason == 0) { int rc = mosquitto_publish(mqtt_client, NULL, avail_topic.c_str(), strlen(MQTT_ONLINE_PAYLOAD), MQTT_ONLINE_PAYLOAD, 0, true); if (rc != MOSQ_ERR_SUCCESS) { - DEBUG_LOGF("MQTT Publish: Failed (%s)\r\n(%s)\r\n", mosquitto_strerror(rc), topic); + DEBUG_LOGF("MQTT Publish: Failed (%s)\r\n", mosquitto_strerror(rc)); } } } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 3d0b57f80..79142b4e6 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3043,7 +3043,7 @@ void server_sensor_types(OTF_PARAMS_DEF) { continue; if (i > 0) bfill.emit_p(PSTR(",")); - byte unitid = getSensorUnitId(type); + unsigned char unitid = getSensorUnitId(type); bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), type, sensor_names[i], getSensorUnit(unitid), unitid); send_packet(OTF_PARAMS); @@ -3179,7 +3179,7 @@ void server_sensorprog_calc(OTF_PARAMS_DEF) { handle_return(HTML_DATA_MISSING); progAdj.max = atof(tmp_buffer); // Max value - byte unitId = getSensorUnitId(sensor); + unsigned char unitId = getSensorUnitId(sensor); int diff = progAdj.max-progAdj.min; int minEx = progAdj.min - diff/2; diff --git a/opensprinkler_server.h b/opensprinkler_server.h index cc8713c36..f730faa19 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -29,6 +29,10 @@ #include #endif +#if defined(ESP8266) || defined(OSPI) +#define OTF_ENABLED +#endif + char dec2hexchar(unsigned char dec); class BufferFiller { diff --git a/otfpi/LinkedMap.h b/otfpi/LinkedMap.h deleted file mode 100644 index 5cb293799..000000000 --- a/otfpi/LinkedMap.h +++ /dev/null @@ -1,101 +0,0 @@ -#if defined(OSPI) -#ifndef OTF_LINKEDMAP_H -#define OTF_LINKEDMAP_H - -#define KEY_MAX_LENGTH 100 - -#include - -namespace OTF { - template - class LinkedMapNode; - - template - class LinkedMap { - friend class OpenThingsFramework; - - friend class Response; - - private: - LinkedMapNode *head = nullptr; - LinkedMapNode *tail = nullptr; - - void _add(LinkedMapNode *node) { - if (head == nullptr) { - head = node; - tail = head; - } else { - tail->next = node; - tail = tail->next; - } - } - - T _find(const char *key) const { - LinkedMapNode *node = head; - while (node != nullptr) { - if (strcmp(node->key, key) == 0) { - return node->value; - } - - node = node->next; - } - - // Indicate the key could not be found. - return nullptr; - } - - public: - ~LinkedMap() { - LinkedMapNode *node = head; - while (node != nullptr) { - LinkedMapNode *next = node->next; - delete node; - node = next; - } - } - - void add(const char *key, T value) { - _add(new LinkedMapNode(key, value)); - } - - T find(const char *key) const { - return _find(key); - } - }; - - template - class LinkedMapNode { - private: - /** Indicates if the key was copied into RAM from flash memory and needs to be freed when the object is destroyed. */ - //bool keyFromFlash = false; - - public: - const char *key = nullptr; - T value; - LinkedMapNode *next = nullptr; - - /*LinkedMapNode(const __FlashStringHelper *key, T value) { - keyFromFlash = true; - char *_key = new char[KEY_MAX_LENGTH]; - strncpy_P(_key, (char *) key, KEY_MAX_LENGTH); - this->key = (const char *) _key; - - this->value = value; - }*/ - - LinkedMapNode(const char *key, T value) { - this->key = key; - this->value = value; - } - - ~LinkedMapNode() { - // Delete the key if it was copied into RAM from flash memory. - //if (keyFromFlash) { - // delete key; - //} - } - }; -}// namespace OTF - -#endif -#endif \ No newline at end of file diff --git a/otfpi/LinuxLocalServer.cpp b/otfpi/LinuxLocalServer.cpp deleted file mode 100644 index 7ca1f9035..000000000 --- a/otfpi/LinuxLocalServer.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#if defined(OSPI) -#include "LinuxLocalServer.h" - -using namespace OTF; - -LinuxLocalServer::LinuxLocalServer(uint16_t port) : server(port) {} - -LinuxLocalServer::~LinuxLocalServer() { - if (activeClient != nullptr) - activeClient->stop(); - delete activeClient; -} - - -LocalClient *LinuxLocalServer::acceptClient() { - if (activeClient != nullptr) { - activeClient->stop(); - delete activeClient; - } - - EthernetClient client = server.available(); - if (client) { - activeClient = new LinuxLocalClient(client); - } else { - activeClient = nullptr; - } - return activeClient; -} - - -void LinuxLocalServer::begin() { - server.begin(); -} - - -LinuxLocalClient::LinuxLocalClient(EthernetClient client) { - this->client = client; -} - -bool LinuxLocalClient::dataAvailable() { - return client.available(); -} - -size_t LinuxLocalClient::readBytes(char *buffer, size_t length) { - return client.readBytes(buffer, length); -} - -size_t LinuxLocalClient::readBytesUntil(char terminator, char *buffer, size_t length) { - return client.readBytesUntil(terminator, buffer, length); -} - -void LinuxLocalClient::print(const char *data) { - client.write((uint8_t*)data, strlen(data)); -} - -/*int LinuxLocalClient::peek() { - return client.peek(); -}*/ - -void LinuxLocalClient::setTimeout(int timeout) { - client.setTimeout(timeout); -} - -void LinuxLocalClient::flush() { - client.flush(); -} - -void LinuxLocalClient::stop() { - client.stop(); -} -#endif \ No newline at end of file diff --git a/otfpi/LinuxLocalServer.h b/otfpi/LinuxLocalServer.h deleted file mode 100644 index f744e469b..000000000 --- a/otfpi/LinuxLocalServer.h +++ /dev/null @@ -1,43 +0,0 @@ -#if defined(OSPI) -#ifndef OTF_LINUXLOCALSERVER_H -#define OTF_LINUXLOCALSERVER_H - -#include "LocalServer.h" -#include "etherport.h" - -namespace OTF { - class LinuxLocalClient : public LocalClient { - friend class LinuxLocalServer; - - private: - EthernetClient client; - LinuxLocalClient(EthernetClient client); - - public: - bool dataAvailable(); - size_t readBytes(char *buffer, size_t length); - size_t readBytesUntil(char terminator, char *buffer, size_t length); - void print(const char *data); - //int peek(); - void setTimeout(int timeout); - void flush(); - void stop(); - }; - - - class LinuxLocalServer : public LocalServer { - private: - EthernetServer server; - LinuxLocalClient *activeClient = nullptr; - - public: - LinuxLocalServer(uint16_t port); - ~LinuxLocalServer(); - - LocalClient *acceptClient(); - void begin(); - }; -}// namespace OTF - -#endif -#endif \ No newline at end of file diff --git a/otfpi/LocalServer.h b/otfpi/LocalServer.h deleted file mode 100644 index f88160969..000000000 --- a/otfpi/LocalServer.h +++ /dev/null @@ -1,55 +0,0 @@ -#if defined(OSPI) -#ifndef OTF_LOCALSERVER_H -#define OTF_LOCALSERVER_H - -#include - -namespace OTF { - class LocalClient { - public: - /** Returns a boolean indicating if data is currently available from this client. */ - virtual bool dataAvailable() = 0; - - /** - * Reads up to `length` bytes from the request stream into `buffer`. - * @return The number of bytes read. - */ - virtual size_t readBytes(char *buffer, size_t length) = 0; - - /** - * Reads up to `length` bytes from the request stream until `terminator` or the end of stream is reached. - * @return The number of bytes read. - */ - virtual size_t readBytesUntil(char terminator, char *buffer, size_t length) = 0; - - /** Prints a null-terminated string to the response stream. This method may be called multiple times before the stream is closed. */ - virtual void print(const char *data) = 0; - - /** Prints a null-terminated string to the response stream. This method may be called multiple times before the stream is closed. */ - //virtual void print(const __FlashStringHelper *data) = 0; - - /** Returns the next character in the request stream (without advancing the stream), or returns -1 if no character is available. */ - //virtual int peek() = 0; - - /** Sets the maximum number of milliseconds to wait for data to be available when readBytes() is called. */ - virtual void setTimeout(int timeout) = 0; - - virtual void flush() = 0; - virtual void stop() = 0; - }; - - class LocalServer { - public: - /** - * Closes the active client (if one is active) and accepts a new client (if one is available). - * @return The newly accepted client, or `nullptr` if none was available. - */ - virtual LocalClient *acceptClient() = 0; - - /** Starts listening for connections. */ - virtual void begin() = 0; - }; -}// namespace OTF - -#endif -#endif \ No newline at end of file diff --git a/otfpi/OpenThingsFramework.cpp b/otfpi/OpenThingsFramework.cpp deleted file mode 100644 index e2b621818..000000000 --- a/otfpi/OpenThingsFramework.cpp +++ /dev/null @@ -1,310 +0,0 @@ -#if defined(OSPI) -#include "OpenThingsFramework.h" -#include "StringBuilder.h" -#include "defines.h" - -// The timeout for reading and parsing incoming requests. -#define WIFI_CONNECTION_TIMEOUT 1500 -/* How often to try to reconnect to the websocket if the connection is lost. Each reconnect attempt is blocking and has - * a 5 second timeout. - */ -#define WEBSOCKET_RECONNECT_INTERVAL 30000 -#define strncmp_P strncmp -using namespace OTF; - -OpenThingsFramework::OpenThingsFramework(uint16_t webServerPort, char *hdBuffer, int hdBufferSize) : localServer(webServerPort) { - DEBUG_PRINTLN("Instantiating OTF..."); - if(hdBuffer != NULL) { // if header buffer is externally provided, use it directly - headerBuffer = hdBuffer; - headerBufferSize = (hdBufferSize > 0) ? hdBufferSize : HEADERS_BUFFER_SIZE; - } else { // otherwise allocate one - headerBuffer = new char[HEADERS_BUFFER_SIZE]; - headerBufferSize = HEADERS_BUFFER_SIZE; - } - missingPageCallback = defaultMissingPageCallback; - localServer.begin(); -} - -OpenThingsFramework::OpenThingsFramework(uint16_t webServerPort, const String &webSocketHost, uint16_t webSocketPort, - const String &deviceKey, bool useSsl, char *hdBuffer, int hdBufferSize) : OpenThingsFramework(webServerPort, hdBuffer, hdBufferSize) { - setCloudStatus(UNABLE_TO_CONNECT); - DEBUG_PRINTLN(F("Initializing websocket...")); - webSocket = new WebSocketsClient(); - if (useSsl) { - DEBUG_PRINTLN(F("Connecting to websocket with SSL")); - //webSocket->beginSSL(webSocketHost, webSocketPort, "/socket/v1?deviceKey=" + deviceKey); - } else { - DEBUG_PRINTLN(F("Connecting to websocket without SSL")); - webSocket->begin(webSocketHost, webSocketPort, "/socket/v1?deviceKey=" + deviceKey); - } - DEBUG_PRINTLN(F("Initialized websocket")); - - // Wrap the member function in a static function. - webSocket->onEvent([this](WStype_t type, uint8_t *payload, size_t length) -> void { - webSocketCallback(type, payload, length); - }); - - webSocket->setReconnectInterval(WEBSOCKET_RECONNECT_INTERVAL); - // Ping the server every 15 seconds with a timeout of 5 seconds, and treat 1 missed ping as a lost connection. - webSocket->enableHeartbeat(15000, 5000, 1); -} - -char *makeMapKey(StringBuilder *sb, HTTPMethod method, const char *path) { - sb->bprintf(F("%d%s"), method, path); - return sb->toString(); -} - -void OpenThingsFramework::on(const char *path, callback_t callback, HTTPMethod method) { - callbacks.add(makeMapKey(new StringBuilder(KEY_MAX_LENGTH), method, path), callback); -} - -//void OpenThingsFramework::on(const __FlashStringHelper *path, callback_t callback, HTTPMethod method) { -// callbacks.add(makeMapKey(new StringBuilder(KEY_MAX_LENGTH), method, (char *) path), callback); -//} - -void OpenThingsFramework::onMissingPage(callback_t callback) { - missingPageCallback = callback; -} - -void OpenThingsFramework::localServerLoop() { - - static ulong wait_to = 0; // timeout to wait for client data - if (!wait_to) { - localClient = localServer.acceptClient(); - // If a client wasn't available from the server, exit the local server loop. - if (!localClient) { - return; - } - // set a timeout to wait for client data - wait_to = millis()+WIFI_CONNECTION_TIMEOUT; - } - DEBUG_PRINT("wait_to="); - DEBUG_PRINTLN(wait_to); - if (!localClient->dataAvailable()) { - // If data isn't available from the client yet, exit the local server loop and check again next iteration. - // but if we reached timeout, then reset wait_to to 0 and flush localClient so we can accept new client - if(millis()>wait_to) { - wait_to=0; - DEBUG_PRINTLN(F("client wait timeout")); - localClient->flush(); - localClient->stop(); - } - return; - } - - // got new client data, reset wait_to to 0 - wait_to = 0; - - DEBUG_PRINTLN("incoming localServer request"); - - // Update the timeout for each data read to ensure that the total timeout is WIFI_CONNECTION_TIMEOUT. - ulong timeout = millis()+WIFI_CONNECTION_TIMEOUT; - - - char *buffer = headerBuffer; - size_t length = 0; - while (localClient->dataAvailable() && millis() < timeout) { - if (length >= (size_t)headerBufferSize) { - localClient->print(F("HTTP/1.1 413 Request too large\r\n\r\nThe request was too large")); - wait_to = 0; - DEBUG_PRINTLN("incoming localServer request - 1"); - return; - } - - size_t read = localClient->readBytesUntil('\n', &buffer[length], min((int) (headerBufferSize - length - 1), headerBufferSize)); - DEBUG_PRINTF("read=%d", read); - char rc = buffer[length]; - length += read; - buffer[length++] = '\n'; - if(read==1 && rc=='\r') { break; } - } - - buffer[length] = 0; - DEBUG_PRINTF((char *) F("Finished reading data from client. Request line + headers were %d bytes\n"), length); - DEBUG_PRINTF((char *) F("Request: %s\n"), (char *) buffer); - - // Make sure that the headers were fully read into the buffer. - if (length < 5 || strncmp_P(&buffer[length - 4], (char *) F("\r\n\r\n"), 4) != 0) { - DEBUG_PRINTLN(F("The request headers were not fully read into the buffer.")); - localClient->print(F("HTTP/1.1 413 Request too large\r\n\r\nThe request was incomplete")); - wait_to = 0; - DEBUG_PRINTLN("incoming localServer request - 2"); - return; - } - - - DEBUG_PRINTLN(F("Parsing request")); - Request request(buffer, length, false); - - char *bodyBuffer = NULL; - // If the request was valid, read the body and add it to the Request object. - if (request.getType() > INVALID) { - char *contentLengthString = request.getHeader(F("content-length")); - // If the header was not specified, the message has no body. - if (contentLengthString != nullptr) { - long contentLength = atol(contentLengthString); - // If the header specifies a length of 0 or could not be parsed, the message has no body. - if (contentLength > 0) { - // Read the body from the client. - bodyBuffer = new char[contentLength]; - size_t bodyLength = 0; - timeout = millis()+WIFI_CONNECTION_TIMEOUT; - while (localClient->dataAvailable() && millis() bodyLength) { - size_t read = localClient->readBytes(&bodyBuffer[bodyLength], min((int) (contentLength - bodyLength), 1024)); - bodyLength += read; - } - bodyBuffer[bodyLength] = 0; - request.body = bodyBuffer; - request.bodyLength = bodyLength; - } - } - } - - DEBUG_PRINTLN(F("Filling response")); - Response res = Response(localClient); - fillResponse(request, res); - - if(bodyBuffer) delete[] bodyBuffer; - DEBUG_PRINTLN(F("Sending response")); - if (res.isValid()) { - char *responseString = res.toString(); - DEBUG_PRINTF((char *) F("Response message is: %s\n"), responseString); - localClient->print(responseString); - } else { - localClient->print(F("HTTP/1.1 500 OTF error\r\nResponse string could not be built\r\n")); - DEBUG_PRINTLN(F("An error occurred while building the response string.")); - } - localClient->flush(); - localClient->stop(); - - DEBUG_PRINTLN(F("Finished handling request")); - - // Get a new client to indicate that the previous client is no longer needed. - localClient = localServer.acceptClient(); - if (localClient) - wait_to = millis()+WIFI_CONNECTION_TIMEOUT; -} - -void OpenThingsFramework::loop() { - localServerLoop(); - if (webSocket != nullptr) { - webSocket->loop(); - } -} - -void OpenThingsFramework::webSocketCallback(WStype_t type, uint8_t *payload, size_t length) { - switch (type) { - case WStype_DISCONNECTED: - DEBUG_PRINTLN(F("Disconnected from websocket")); - /* A failed attempt to connect will also fire a WStype_DISCONNECTED event, so this check is - * needed to prevent the UNABLE_TO_CONNECT status to be overwritten. */ - if (cloudStatus == CONNECTED) { - setCloudStatus(DISCONNECTED); - } - break; - case WStype_CONNECTED: - DEBUG_PRINTLN(F("Connected to websocket")); - setCloudStatus(CONNECTED); - break; - case WStype_TEXT: { - DEBUG_PRINTF((char *) F("Received a websocket message of length %d\n"), length); - char *message = (char *) payload; - -#define PREFIX_LENGTH 5 -#define ID_LENGTH 4 -// Length of the prefix, request ID, carriage return, and line feed. -#define HEADER_LENGTH PREFIX_LENGTH + ID_LENGTH + 2 - if (strncmp_P(message, (char *) F("FWD: "), PREFIX_LENGTH) == 0) { - DEBUG_PRINTLN(F("Message is a forwarded request.")); - char *requestId = &message[PREFIX_LENGTH]; - // Replace the assumed carriage return with a null character to terminate the ID string. - requestId[ID_LENGTH] = '\0'; - - Request request(&message[HEADER_LENGTH], length - HEADER_LENGTH, true); - Response res = Response(webSocket); - res.bprintf(F("RES: %s\r\n"), requestId); - fillResponse(request, res); - - if (res.isValid()) { - webSocket->sendTXT(res.toString(), res.getLength(), true, false); - } else { - DEBUG_PRINTLN(F("An error occurred building response string")); - StringBuilder builder(100); - builder.bprintf(F("RES: %s\r\n%s"), requestId, - F("HTTP/1.1 500 Internal Error\r\n\r\nAn internal error occurred")); - if (!builder.isValid()) { - DEBUG_PRINTLN(F("Builder is not valid")); - return; - } - } - } else { - DEBUG_PRINTLN(F("Websocket message does not start with the correct prefix.")); - } - break; - } - case WStype_PING: - case WStype_PONG: - // Pings/pongs will be automatically handled by the websockets library. - break; - case WStype_ERROR: - DEBUG(Serial.print(F("Websocket error: "));) - DEBUG_PRINTLN((char *) payload); - break; - default: - DEBUG_PRINTF((char *) F("Received unsupported websocket event of type %d\n"), type); - break; - } -} - -void OpenThingsFramework::fillResponse(const Request &req, Response &res) { - if (req.getType() == INVALID) { - res.writeStatus(400, F("Invalid request")); - res.writeHeader(F("content-type"), F("text/plain")); - res.writeBodyChunk(F("Could not parse request")); - return; - } - - // TODO handle trailing slash in path? - DEBUG_PRINTF((char *) F("Attempting to route request to path '%s'\n"), req.getPath()); - StringBuilder *sb = new StringBuilder(KEY_MAX_LENGTH); - char *key = makeMapKey(sb, req.httpMethod, req.getPath()); - callback_t callback = callbacks.find(key); - - // If there isn't a callback for the specific method, check if there's one for any method. - if (callback == nullptr) { - delete sb; - sb = new StringBuilder(KEY_MAX_LENGTH); - - callback = callbacks.find(makeMapKey(sb, HTTP_ANY, req.getPath())); - } - - delete sb; - - if (callback != nullptr) { - DEBUG_PRINTLN(F("Found callback")); - callback(req, res); - } else { - // Run the missing page callback if none of the registered paths matched. - missingPageCallback(req, res); - } -} - -void OpenThingsFramework::defaultMissingPageCallback(const Request &req, Response &res) { - res.writeStatus(404, F("Not found")); - res.writeHeader(F("content-type"), F("text/plain")); - res.writeBodyChunk(F("The requested page does not exist")); -} - -void OpenThingsFramework::setCloudStatus(CLOUD_STATUS status) { - this->cloudStatus = status; - lastCloudStatusChangeTime = millis(); -} - -CLOUD_STATUS OpenThingsFramework::getCloudStatus() { - return cloudStatus; -} - -unsigned long OpenThingsFramework::getTimeSinceLastCloudStatusChange() { - return millis() - lastCloudStatusChangeTime; -} -#endif \ No newline at end of file diff --git a/otfpi/OpenThingsFramework.h b/otfpi/OpenThingsFramework.h deleted file mode 100644 index e615cccda..000000000 --- a/otfpi/OpenThingsFramework.h +++ /dev/null @@ -1,103 +0,0 @@ -#if defined(OSPI) -#ifndef OTF_OPENTHINGSFRAMEWORK_H -#define OTF_OPENTHINGSFRAMEWORK_H - -#include "Request.h" -#include "Response.h" -#include - -#include "WebSocketsClient.h" -#include "LinuxLocalServer.h" - -#define LOCAL_SERVER_CLASS LinuxLocalServer - -// The size of the buffer to store the incoming request line and headers (does not include body). Larger requests will be discarded. -#define HEADERS_BUFFER_SIZE 1536 - -namespace OTF { - typedef void (*callback_t)(const Request &request, Response &response); - - enum CLOUD_STATUS { - /** Indicates that an OTC token was not specified on initialization. */ - NOT_ENABLED, - /** Indicates that the device was never able to connect to the server. */ - UNABLE_TO_CONNECT, - /** Indicates that the device was previously connected to the server, but got disconnected and has been unable to reconnect. */ - DISCONNECTED, - /** Indicates that the device is currently connected to the server. */ - CONNECTED - }; - - class OpenThingsFramework { - private: - LOCAL_SERVER_CLASS localServer = LOCAL_SERVER_CLASS(80); - LocalClient *localClient = nullptr; - WebSocketsClient *webSocket = nullptr; - LinkedMap callbacks; - callback_t missingPageCallback; - CLOUD_STATUS cloudStatus = NOT_ENABLED; - unsigned long lastCloudStatusChangeTime = millis(); - char *headerBuffer = NULL; - int headerBufferSize = 0; - - void webSocketCallback(WStype_t type, uint8_t *payload, size_t length); - - void fillResponse(const Request &req, Response &res); - void localServerLoop(); - void setCloudStatus(CLOUD_STATUS status); - - static void defaultMissingPageCallback(const Request &req, Response &res); - - public: - /** - * Initializes the library to only listen on a local webserver. - * @param webServerPort The local port to bind the webserver to. - * @param hdBuffer externally provided header buffer (optional) - * @param hdBufferSize size of the externally provided header buffer (optional) - */ - OpenThingsFramework(uint16_t webServerPort, char *hdBuffer = NULL, int hdBufferSize = HEADERS_BUFFER_SIZE); - - /** - * Initializes the library to listen on a local webserver and connect to a remote websocket. - * @param webServerPort The local port to bind the webserver to. - * @param webSocketHost The host of the remote websocket. - * @param webSocketPort The port of the remote websocket. - * @param deviceKey The unique device key that identifies this device. - * @param useSsl Indicates if SSL should be used when connecting to the websocket. - * @param hdBuffer externally provided header buffer (optional) - * @param hdBufferSize size of the externally provided header buffer (optional) - */ - OpenThingsFramework(uint16_t webServerPort, const String &webSocketHost, uint16_t webSocketPort, - const String &deviceKey, bool useSsl, char *hdBuffer = NULL, int hdBufferSize = HEADERS_BUFFER_SIZE); - - /** - * Registers a callback function to run when a request is made to the specified path. The callback function will - * be passed an OpenThingsRequest, and must return an OpenThingsResponse. - * @param path - * @param callback - */ - void on(const char *path, callback_t callback, HTTPMethod method = HTTP_ANY); - - /** - * Registers a callback function to run when a request is made to the specified path. The callback function will - * be passed an OpenThingsRequest, and must return an OpenThingsResponse. - * @param path - * @param callback - */ - //void on(const __FlashStringHelper *path, callback_t callback, HTTPMethod method = HTTP_ANY); - - /** Registers a callback function to run when a request is received but its path does not match a registered callback. */ - void onMissingPage(callback_t callback); - - void loop(); - - /** Returns the current status of the connection to the OpenThings Cloud server. */ - CLOUD_STATUS getCloudStatus(); - - /** Returns the number of milliseconds since there was last a change in the cloud status. */ - unsigned long getTimeSinceLastCloudStatusChange(); - }; -}// namespace OTF - -#endif -#endif \ No newline at end of file diff --git a/otfpi/Request.cpp b/otfpi/Request.cpp deleted file mode 100644 index 92d188944..000000000 --- a/otfpi/Request.cpp +++ /dev/null @@ -1,304 +0,0 @@ -#if defined(OSPI) -#include "Request.h" -#include -#include -#include - -using namespace OTF; - -// Find the pointers of substrings within the HTTP request and turns them into null-terminated C strings. -Request::Request(char *str, size_t length, bool cloudRequest) { - this->cloudRequest = cloudRequest; - size_t index = 0; - - // Parse the HTTP method. - DEBUG(Serial.println(F("Parsing HTTP method"));) - for (; index < length; index++) { - if (str[index] == '\0') { - // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. - requestType = INVALID; - return; - } else if (str[index] == ' ') { - // Null terminate the HTTP method. - str[index] = '\0'; - - // Move to the first character in the path. - index++; - - break; - } - } - if (index >= length) { - // Error if the HTTP method extends to the end of the request. - requestType = INVALID; - return; - } - - // Map the string to an enum value. - if (strcmp("GET", &str[0]) == 0) { - this->httpMethod = HTTP_GET; - } else if (strcmp("POST", &str[0]) == 0) { - this->httpMethod = HTTP_POST; - } else if (strcmp("PUT", &str[0]) == 0) { - this->httpMethod = HTTP_PUT; - } else if (strcmp("DELETE", &str[0]) == 0) { - this->httpMethod = HTTP_DELETE; - } else if (strcmp("OPTIONS", &str[0]) == 0) { - this->httpMethod = HTTP_OPTIONS; - } else if (strcmp("PATCH", &str[0]) == 0) { - this->httpMethod = HTTP_PATCH; - } else { - DEBUG(Serial.println(F("Could not match HTTP method"));) - // Error if the method isn't a standard method. - requestType = INVALID; - return; - } - - - char character = str[index]; - // TODO handle cases where target isn't always a root path? (https://tools.ietf.org/html/rfc7230#section-5.3.1) - DEBUG(Serial.println(F("Parsing the request target."));) - // Parse the target. - this->path = &str[index]; - // Skip over the path. - while (true) { - if (++index >= length) { - // Error if the target extends to the end of the request. - requestType = INVALID; - return; - } - - character = str[index]; - if (character == '\0') { - // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. - requestType = INVALID; - return; - } else if (character == '?' || character == '#' || character == ' ') { - // Null terminate the path. - str[index] = '\0'; - break; - } - } - - if (character == '?') { - // Parse the query. - character = parseQuery(str, length, ++index); - - // Exit if an error occurred while parsing the query. - if (character == '\0') { - requestType = INVALID; - return; - } - } - - if (character == '#') { - // Skip over the fragment. - while (index < length && (character = str[++index]) != ' ') { - if (character == '\0') { - // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. - requestType = INVALID; - return; - } - } - } - - DEBUG(Serial.println(F("Finished parsing request target."));) - // Move to the first character in the HTTP version. - index++; - - // Find the HTTP version. - this->httpVersion = &str[index]; - for (; index < length; index++) { - if (str[index] == '\0') { - // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. - requestType = INVALID; - return; - } else if (str[index] == '\r') { - // Replace the carriage return with a null terminator. - str[index] = '\0'; - // Move past the carriage return and assumed line feed. - index += 2; - break; - } - } - - // Parse headers until "\r\n\r\n" is encountered (indicates the end of headers) or the end of the string is reached (caused b"y invalid requests). - while (index < length && str[index] != '\r') { - if (!parseHeader(str, length, index, headers)) { - // Reject the request if an error occurs while parsing headers. - requestType = INVALID; - return; - } - } - - // Move past the 2nd consecutive carriage return and assumed line feed. - index += 2; - - if (index == length) { - /* If the cursor is exactly 1 character after the end of the request, it means there is no body. Point the `body` pointer - * to the last character in the request (since a value of NULL would indicate that the request was invalid), but set - * `bodyLength` to 0 so it never gets read. - */ - body = &str[index - 1]; - requestType = NORMAL; - } else if (index > length) { - // If the cursor is more than 1 character after the end of the request, it means the request was somehow illegally formatted. - requestType = INVALID; - return; - } else { - body = &str[index]; - requestType = NORMAL; - } - - bodyLength = length - index; -} - -char Request::parseQuery(char *str, size_t length, size_t &index) { - DEBUG(Serial.println(F("Starting to parse query."));) - while (index < length) { - char *key = &str[index]; - char *value = nullptr; - - for (; index < length; index++) { - char character = str[index]; - - if (character == '\0') { - // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. - return '\0'; - } - - // If the end of the parameter has been reached, store it in the map. - if (character == ' ' || character == '#' || character == '&') { - // Null terminate the parameter. - str[index] = '\0'; - - // Handle parameters without a value. - if (value == nullptr) { - // Replace the null pointer with a pointer to an empty string to differentiate between empty parameters and unspecified parameters. - value = &str[index]; - } - - decodeQueryParameter(value); - DEBUG(Serial.printf((char *) F("Found query parameter '%s' with value '%s'.\n"), key, value);) - - queryParams.add(key, value); - - if (character == ' ' || character == '#') { - // If the end of the query has been reached, return. - return character; - } else { - // Move to the start of the next parameter key. - index++; - break; - } - } else if (character == '=') { - // Make sure that the character is separating the key and the value (it isn't part of the value). - if (value == nullptr) { - // Null terminate the key. - str[index] = '\0'; - value = &str[index + 1]; - } - } - } - } - - // Error if the query extends to the end of the request. - return '\0'; -} - -bool Request::parseHeader(char *str, size_t length, size_t &index, LinkedMap &headers) { - char *lineStart = &str[index]; - char *colon = nullptr; - char *value = nullptr; - - for (; index < length; index++) { - if (str[index] == '\0') { - // Reject any requests that contain null characters outside of the body to prevent null byte poisoning attacks. - return false; - } else if (str[index] == ':' && colon == nullptr) { - colon = &str[index]; - str[index] = '\0'; - } else if (str[index] == '\r') { - if (colon == nullptr) { - // Error if there is an illegal header line that doesn't contain a colon. - return false; - } - - // Terminate the header value. - str[index] = '\0'; - - // Handle headers without a value. - if (value == nullptr) { - // Replace the null pointer with a pointer to an empty string to differentiate between empty header and unspecified headers. - value = &str[index]; - } - - // Trim trailing whitespace from the header value (https://tools.ietf.org/html/rfc7230#section-3.2.4). - for (size_t i = index; i > 0; i--) { - // Replace whitespace with null terminators so the value string will terminate at the first space after it. - if (isspace(str[index])) { - str[index] = '\0'; - } else { - break; - } - } - - DEBUG(Serial.printf((char *) F("Found header '%s' with value '%s'.\n"), lineStart, value);) - // TODO handle duplicate header fields by concatenating them with a comma. - headers.add(lineStart, value); - - // Move past the carriage return and assumed line feed. - index += 2; - break; - } else if (colon != nullptr && value == nullptr && !isspace(str[index])) { - // Mark the location of the header value after skipping whitespace. - value = &str[index]; - } else if (value == nullptr) { - // Make the header name lowercase. - str[index] = tolower(str[index]); - } - } - return true; -} - -void Request::decodeQueryParameter(char *value) { - unsigned int offset = 0; - unsigned int index = 0; - while (value[index + offset] != '\0') { - DEBUG(Serial.printf((char *) F("Index is %d and offset is %d\n"), index, offset);) - char character = value[index + offset]; - if (character == '+') { - character = ' '; - } else if (character == '%') { - char highDigit = value[index + ++offset]; - char lowDigit = value[index + ++offset]; - if (highDigit == '\0' || lowDigit == '\0') { - // Abort decoding because the query string is illegally formatted. - return; - } - - char hex[3] = {highDigit, lowDigit, '\0'}; - character = strtol(hex, nullptr, 16); - } - - value[index] = character; - - index++; - } - value[index] = '\0'; -} - -char *Request::getPath() const { return path; } - -char *Request::getQueryParameter(const char *key) const { return queryParams.find(key); } - -char *Request::getHeader(const char *key) const { return headers.find(key); } - -char *Request::getBody() const { return body; } - -size_t Request::getBodyLength() const { return bodyLength; } - -RequestType Request::getType() const { return requestType; } - -bool Request::isCloudRequest() const { return cloudRequest; } -#endif \ No newline at end of file diff --git a/otfpi/Request.h b/otfpi/Request.h deleted file mode 100644 index c05d66deb..000000000 --- a/otfpi/Request.h +++ /dev/null @@ -1,118 +0,0 @@ -#if defined(OSPI) -#ifndef OTF_REQUEST_H -#define OTF_REQUEST_H - -//#define SERIAL_DEBUG -#ifdef SERIAL_DEBUG -#define DEBUG(x) x -#else -#define DEBUG(x) -#endif - -#include "LinkedMap.h" -#include - -namespace OTF { - - enum HTTPMethod { - HTTP_ANY, - HTTP_GET, - HTTP_POST, - HTTP_PUT, - HTTP_PATCH, - HTTP_DELETE, - HTTP_OPTIONS - }; - - enum RequestType { - INVALID, - NORMAL - }; - - class Request { - friend class OpenThingsFramework; - - private: - enum HTTPMethod httpMethod; - char *httpVersion = nullptr; - char *path = nullptr; - LinkedMap queryParams; - LinkedMap headers; - char *body = nullptr; - size_t bodyLength = 0; - RequestType requestType = INVALID; - bool cloudRequest; - - /** - * Parses an HTTP request. The parser makes some assumptions about the message format that may not hold if the - * message is improperly formatted, so the behavior of this constructor is undefined if it is passed an improperly - * formatted request. - */ - Request(char *str, size_t length, bool cloudRequest); - - /** - * Parses the query of the request. - * @param str The full request string. - * @param length The length of the full request. - * @param index The index of the first character after the '?' character that marks the start of the request. - * When the function terminates successfully, this will be updated to the index of the character that marked the - * end of the query. The character at this index may be changed, but its original value will be returned. - * @return The character that was originally at `index` in the string. Possible values are: - * '\0', indicating an error occurred. - * '#', indicating that the new value of `index` marks the first character in the URI fragment. - * ' ', indicating that the new value of `index` marks the first character in the HTTP version. - */ - char parseQuery(char *str, size_t length, size_t &index); - - /** - * Parses a single header and adds it to `headers`. - * @param str The full request string. - * @param length The length of the full request. - * @param index The index of the first character in the header line. When the function terminates successfully, - * this will be updated to the index of the first character after the CRLF sequence at the end of the header. - * @param headers The set of headers to add the parsed header to. - * @return A boolean indicating if the header could be parsed successfully. - */ - bool parseHeader(char *str, size_t length, size_t &index, LinkedMap &headers); - - /** - * Decodes the specified query string, updating and null-terminating the string in-place. - * @param value a null-terminated query value. - */ - static void decodeQueryParameter(char *value); - - public: - /** Returns the path of the request (not including the query) as a null-terminated string. */ - char *getPath() const; - - /** Returns the decoded value of the specified query parameter as a null-terminated string, or NULL if the parameter was not set in the request. */ - char *getQueryParameter(const char *key) const; - - /** - * Returns the value of the specified header as a null-terminated string, or NULL if the header was not set in the request. - * @param key The lowercase header name. - */ - char *getHeader(const char *key) const; - - /** - * Returns the body of the request. THIS STRING IS NOT NULL TERMINATED, AND IT MAY CONTAIN NULL CHARACTERS. If - * the request could not be parsed (as indicated by the getType() method), this method has undefined behavior. - */ - char *getBody() const; - - /** Returns the length of the request body. */ - size_t getBodyLength() const; - - /** Indicates if this request is a multipart request, a non-multipart request, or an illegally formatted request. */ - RequestType getType() const; - - /** - * Indicates if the request came through the cloud rather than through the local server. If this returns `true`, - * it means that the request has already been authenticated by an OpenThings Cloud token. - */ - - bool isCloudRequest() const; - }; -}// namespace OTF -#endif -#endif \ No newline at end of file diff --git a/otfpi/Response.cpp b/otfpi/Response.cpp deleted file mode 100644 index b4aa37120..000000000 --- a/otfpi/Response.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#if defined(OSPI) -#include "Response.h" - -using namespace OTF; - -void Response::writeStatus(uint16_t statusCode, const String &statusMessage) { - if (responseStatus > CREATED) { - valid = false; - DEBUG_PRINTLN("VALID=FALSE writeStatus1"); - return; - } - responseStatus = STATUS_WRITTEN; - - bprintf(F("HTTP/1.1 %d %s\r\n"), statusCode, statusMessage.c_str()); -} - -void Response::writeStatus(uint16_t statusCode) { - if (responseStatus > CREATED) { - valid = false; - DEBUG_PRINTLN("VALID=FALSE writeStatus2"); - return; - } - responseStatus = STATUS_WRITTEN; - - writeStatus(statusCode, F("No message")); -} - -void Response::writeHeader(const char *name, const char *value) { - if (responseStatus < STATUS_WRITTEN || responseStatus > HEADERS_WRITTEN) { - valid = false; - DEBUG_PRINTLN("VALID=FALSE WRITEHEADER1"); - return; - } - responseStatus = HEADERS_WRITTEN; - - bprintf(F("%s: %s\r\n"), name, value); -} - -void Response::writeHeader(const char *name, int value) { - if (responseStatus < STATUS_WRITTEN || responseStatus > HEADERS_WRITTEN) { - valid = false; - DEBUG_PRINTLN("VALID=FALSE WRITEHEADER2"); - return; - } - responseStatus = HEADERS_WRITTEN; - - bprintf(F("%s: %d\r\n"), name, value); -} - -void Response::writeBodyChunk(const char *format, ...) { - if (responseStatus < STATUS_WRITTEN) { - valid = false; - DEBUG_PRINTLN("VALID=FALSE writeBodyChunk"); - return; - } - if (responseStatus != BODY_WRITTEN) { - append("\r\n"); - responseStatus = BODY_WRITTEN; - } - - va_list args; - va_start(args, format); - bprintf(format, args); - va_end(args); -} - -void Response::flush() { - char *responseString = toString(); - if (localClient) - localClient->print(responseString); - else if (webSocket) - webSocket->sendTXT(responseString, getLength(), false, false); - - reset(); -} -#endif \ No newline at end of file diff --git a/otfpi/Response.h b/otfpi/Response.h deleted file mode 100644 index 6a73cd8bb..000000000 --- a/otfpi/Response.h +++ /dev/null @@ -1,64 +0,0 @@ -#if defined(OSPI) -#ifndef OTF_RESPONSE_H -#define OTF_RESPONSE_H - -#include "StringBuilder.h" -#include "LocalServer.h" -#include "WebSocketsClient.h" - -// The maximum possible size of response messages. -#define RESPONSE_BUFFER_SIZE 10*1024 - -namespace OTF { - - class Response : public StringBuilder { - friend class OpenThingsFramework; - - private: - enum ResponseStatus { - CREATED, - STATUS_WRITTEN, - HEADERS_WRITTEN, - BODY_WRITTEN - }; - ResponseStatus responseStatus = CREATED; - LocalClient *localClient = nullptr; - WebSocketsClient *webSocket = nullptr; - - Response(WebSocketsClient *pwebSocket) : StringBuilder(RESPONSE_BUFFER_SIZE) {webSocket = pwebSocket;} - - Response(LocalClient *plocalClient) : StringBuilder(RESPONSE_BUFFER_SIZE) {localClient = plocalClient;} - - public: - static const size_t MAX_RESPONSE_LENGTH = RESPONSE_BUFFER_SIZE; - - /** Writes the status code/message to the response. This must be called before writing the headers or body. */ - void writeStatus(uint16_t statusCode, const String &statusMessage); - - /** Writes the status code/message to the response. This must be called before writing the headers or body. */ - //void writeStatus(uint16_t statusCode, const __FlashStringHelper *const statusMessage); - - /** Writes the status code to the response. This must be called before writing the headers or body. */ - void writeStatus(uint16_t statusCode); - - /** Sets a response header. If called multiple times with the same header name, the header will be specified - * multiple times to create a list. This function must not be called before the status has been written or after - * the body has been written. - */ - void writeHeader(const char *name, const char *value); - - void writeHeader(const char *name, int value); - - /** - * Calls sprintf to write a chunk of data to the response body. This method may only be called after any desired - * headers have been set. - * @param format The format string to pass to sprintf. - * @param ... The format arguments to pass to sprintf. - */ - void writeBodyChunk(const char *format, ...); - - void flush(); - }; -}// namespace OTF -#endif -#endif \ No newline at end of file diff --git a/otfpi/StringBuilder.cpp b/otfpi/StringBuilder.cpp deleted file mode 100644 index 4f2b7e81e..000000000 --- a/otfpi/StringBuilder.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#if defined(OSPI) -#include "StringBuilder.h" -#include -#include - -using namespace OTF; - -StringBuilder::StringBuilder(size_t maxLength) { - this->maxLength = maxLength; - buffer = new char[maxLength]; -} - -StringBuilder::~StringBuilder() { - delete buffer; -} - -void StringBuilder::bprintf(const char *format, va_list args) { - // Don't do anything if the buffer already contains invalid data. - if (!valid) { - return; - } - - length += vsnprintf(&buffer[length], maxLength - length, format, args); - - // The builder is invalid if the string fits perfectly in the buffer since there wouldn't be room for the null terminator. - if (length >= maxLength) { - // snprintf will not allow more than the specified number of characters to be written to the buffer, so the length will be the buffer size. - length = maxLength; - valid = false; - } -} - -void StringBuilder::bprintf(const char *const format, ...) { - va_list args; - va_start(args, format); - bprintf(format, args); - va_end(args); -} - -char *StringBuilder::toString() const { - return &buffer[0]; -} - -size_t StringBuilder::getLength() const { - return length; -} - -bool StringBuilder::isValid() { - return valid; -} - -bool StringBuilder::willFit(size_t size) { - return size + length + 1 < maxLength; -} - -void StringBuilder::reset() { - length = 0; - buffer[0] = 0; - valid = true; -} - -void StringBuilder::append(const char *txt, size_t size) { - if (!valid || !willFit(size)) { - valid = false; - return; - } - strncpy(&buffer[length], txt, size); - length += size; - buffer[length] = 0; -} - -void StringBuilder::append(const char *txt) { - append(txt, strlen(txt)); -} - -#endif \ No newline at end of file diff --git a/otfpi/StringBuilder.h b/otfpi/StringBuilder.h deleted file mode 100644 index 2b110e0f4..000000000 --- a/otfpi/StringBuilder.h +++ /dev/null @@ -1,64 +0,0 @@ -#if defined(OSPI) -#ifndef OTF_STRINGBUILDER_H -#define OTF_STRINGBUILDER_H - -#include -#include - -namespace OTF { - /** - * Wraps a buffer to build a string with repeated calls to sprintf. If any of calls to sprintf cause an error (such - * as exceeding the size of the internal buffer), the error will be silently swallowed and the StringBuilder will be - * marked as invalid. This means that any error checking can occur after the entire string has been built instead of - * a check being required after each individual call to sprintf. - */ - class StringBuilder { - private: - size_t maxLength; - char *buffer; - size_t length = 0; - - protected: - bool valid = true; - - public: - explicit StringBuilder(size_t maxLength); - - ~StringBuilder(); - /** - * Inserts a string into the buffer at the current position using the same formatting rules as printf. If the operation - * would cause the buffer length to be exceeded or some other error occurs, the StringBuilder will be marked as invalid. - * @param format The format string to pass to sprintf. - * @param ... The format arguments to pass to sprintf. - */ - void bprintf(const char *format, va_list args); - - void bprintf(const char *format, ...); - - void append(const char *txt, size_t size); - - void append(const char *txt); - - /** - * Returns the null-terminated represented string stored in the underlying buffer. - * @return The null-terminated represented string stored in the underlying buffer. - */ - char *toString() const; - - size_t getLength() const; - - /** - * Returns a boolean indicating if the string was built successfully without any errors. If false, the behavior - * of toString() is undefined, and the string it returns (which may or may not be null terminated) should NOT - * be used. - */ - bool isValid(); - - bool willFit(size_t size); - - void reset(); - }; -}// namespace OTF - -#endif -#endif \ No newline at end of file diff --git a/otfpi/WebSockets.cpp b/otfpi/WebSockets.cpp deleted file mode 100644 index f45790a0a..000000000 --- a/otfpi/WebSockets.cpp +++ /dev/null @@ -1,731 +0,0 @@ -#if defined(OSPI) -/** - * @file WebSockets.cpp - * @date 20.05.2015 - * @author Markus Sattler - * - * Copyright (c) 2015 Markus Sattler. All rights reserved. - * This file is part of the WebSockets for Arduino. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "WebSockets.h" -#include - -/** - * - * @param client WSclient_t * ptr to the client struct - * @param code uint16_t see RFC - * @param reason ptr to the disconnect reason message - * @param reasonLen length of the disconnect reason message - */ -void WebSockets::clientDisconnect(WSclient_t * client, uint16_t code, char * reason, size_t reasonLen) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] clientDisconnect code: %u\n", client->num, code); - if(client->status == WSC_CONNECTED && code) { - if(reason) { - sendFrame(client, WSop_close, (uint8_t *)reason, reasonLen); - } else { - uint8_t buffer[2]; - buffer[0] = ((code >> 8) & 0xFF); - buffer[1] = (code & 0xFF); - sendFrame(client, WSop_close, &buffer[0], 2); - } - } - clientDisconnect(client); -} - -/** - * - * @param buf uint8_t * ptr to the buffer for writing - * @param opcode WSopcode_t - * @param length size_t length of the payload - * @param mask bool add dummy mask to the frame (needed for web browser) - * @param maskkey uint8_t[4] key used for payload - * @param fin bool can be used to send data in more then one frame (set fin on the last frame) - */ -uint8_t WebSockets::createHeader(uint8_t * headerPtr, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin) { - uint8_t headerSize; - // calculate header Size - if(length < 126) { - headerSize = 2; - } else if(length < 0xFFFF) { - headerSize = 4; - } else { - headerSize = 10; - } - - if(mask) { - headerSize += 4; - } - - // create header - - // byte 0 - *headerPtr = 0x00; - if(fin) { - *headerPtr |= bit(7); ///< set Fin - } - *headerPtr |= opcode; ///< set opcode - headerPtr++; - - // byte 1 - *headerPtr = 0x00; - if(mask) { - *headerPtr |= bit(7); ///< set mask - } - - if(length < 126) { - *headerPtr |= length; - headerPtr++; - } else if(length < 0xFFFF) { - *headerPtr |= 126; - headerPtr++; - *headerPtr = ((length >> 8) & 0xFF); - headerPtr++; - *headerPtr = (length & 0xFF); - headerPtr++; - } else { - // Normally we never get here (to less memory) - *headerPtr |= 127; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = 0x00; - headerPtr++; - *headerPtr = ((length >> 24) & 0xFF); - headerPtr++; - *headerPtr = ((length >> 16) & 0xFF); - headerPtr++; - *headerPtr = ((length >> 8) & 0xFF); - headerPtr++; - *headerPtr = (length & 0xFF); - headerPtr++; - } - - if(mask) { - *headerPtr = maskKey[0]; - headerPtr++; - *headerPtr = maskKey[1]; - headerPtr++; - *headerPtr = maskKey[2]; - headerPtr++; - *headerPtr = maskKey[3]; - headerPtr++; - } - return headerSize; -} - -/** - * - * @param client WSclient_t * ptr to the client struct - * @param opcode WSopcode_t - * @param length size_t length of the payload - * @param fin bool can be used to send data in more then one frame (set fin on the last frame) - * @return true if ok - */ -bool WebSockets::sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length, bool fin) { - uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; - uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; - - uint8_t headerSize = createHeader(&buffer[0], opcode, length, client->cIsClient, maskKey, fin); - - if(write(client, &buffer[0], headerSize) != headerSize) { - return false; - } - - return true; -} - -/** - * - * @param client WSclient_t * ptr to the client struct - * @param opcode WSopcode_t - * @param payload uint8_t * ptr to the payload - * @param length size_t length of the payload - * @param fin bool can be used to send data in more then one frame (set fin on the last frame) - * @param headerToPayload bool set true if the payload has reserved 14 Byte at the beginning to dynamically add the Header (payload neet to be in RAM!) - * @return true if ok - */ -bool WebSockets::sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin, bool headerToPayload) { - if(client->tcp && !client->tcp->connected()) { - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not Connected!?\n", client->num); - return false; - } - - if(client->status != WSC_CONNECTED) { - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] not in WSC_CONNECTED state!?\n", client->num); - return false; - } - - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] ------- send message frame -------\n", client->num); - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] fin: %u opCode: %u mask: %u length: %u headerToPayload: %u\n", client->num, fin, opcode, client->cIsClient, length, headerToPayload); - - if(opcode == WSop_text) { - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] text: %s\n", client->num, (payload + (headerToPayload ? 14 : 0))); - } - - uint8_t maskKey[4] = { 0x00, 0x00, 0x00, 0x00 }; - uint8_t buffer[WEBSOCKETS_MAX_HEADER_SIZE] = { 0 }; - - uint8_t headerSize; - uint8_t * headerPtr; - uint8_t * payloadPtr = payload; - bool useInternBuffer = false; - bool ret = true; - - // calculate header Size - if(length < 126) { - headerSize = 2; - } else if(length < 0xFFFF) { - headerSize = 4; - } else { - headerSize = 10; - } - - if(client->cIsClient) { - headerSize += 4; - } - -#ifdef WEBSOCKETS_USE_BIG_MEM - // only for ESP since AVR has less HEAP - // try to send data in one TCP package (only if some free Heap is there) - if(!headerToPayload && ((length > 0) && (length < 1400))) { //&& (GET_FREE_HEAP > 6000)) { - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] pack to one TCP package...\n", client->num); - uint8_t * dataPtr = (uint8_t *)malloc(length + WEBSOCKETS_MAX_HEADER_SIZE); - if(dataPtr) { - memcpy((dataPtr + WEBSOCKETS_MAX_HEADER_SIZE), payload, length); - headerToPayload = true; - useInternBuffer = true; - payloadPtr = dataPtr; - } - } -#endif - - // set Header Pointer - if(headerToPayload) { - // calculate offset in payload - headerPtr = (payloadPtr + (WEBSOCKETS_MAX_HEADER_SIZE - headerSize)); - } else { - headerPtr = &buffer[0]; - } - - if(client->cIsClient && useInternBuffer) { - // if we use a Intern Buffer we can modify the data - // by this fact its possible the do the masking - for(uint8_t x = 0; x < sizeof(maskKey); x++) { - maskKey[x] = random() % 0x100; - } - } - - createHeader(headerPtr, opcode, length, client->cIsClient, maskKey, fin); - - if(client->cIsClient && useInternBuffer) { - uint8_t * dataMaskPtr; - - if(headerToPayload) { - dataMaskPtr = (payloadPtr + WEBSOCKETS_MAX_HEADER_SIZE); - } else { - dataMaskPtr = payloadPtr; - } - - for(size_t x = 0; x < length; x++) { - dataMaskPtr[x] = (dataMaskPtr[x] ^ maskKey[x % 4]); - } - } - -#ifndef NODEBUG_WEBSOCKETS - unsigned long start = micros(); -#endif - - if(headerToPayload) { - // header has be added to payload - // payload is forced to reserved 14 Byte but we may not need all based on the length and mask settings - // offset in payload is calculatetd 14 - headerSize - if(write(client, &payloadPtr[(WEBSOCKETS_MAX_HEADER_SIZE - headerSize)], (length + headerSize)) != (length + headerSize)) { - ret = false; - } - } else { - // send header - if(write(client, &buffer[0], headerSize) != headerSize) { - ret = false; - } - - if(payloadPtr && length > 0) { - // send payload - if(write(client, &payloadPtr[0], length) != length) { - ret = false; - } - } - } - - DEBUG_WEBSOCKETS("[WS][%d][sendFrame] sending Frame Done (%luus).\n", client->num, (micros() - start)); - -#ifdef WEBSOCKETS_USE_BIG_MEM - if(useInternBuffer && payloadPtr) { - free(payloadPtr); - } -#endif - - return ret; -} - -/** - * callen when HTTP header is done - * @param client WSclient_t * ptr to the client struct - */ -void WebSockets::headerDone(WSclient_t * client) { - client->status = WSC_CONNECTED; - client->cWsRXsize = 0; - DEBUG_WEBSOCKETS("[WS][%d][headerDone] Header Handling Done.\n", client->num); -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - client->cHttpLine = ""; - handleWebsocket(client); -#endif -} - -/** - * handle the WebSocket stream - * @param client WSclient_t * ptr to the client struct - */ -void WebSockets::handleWebsocket(WSclient_t * client) { - if(client->cWsRXsize == 0) { - handleWebsocketCb(client); - } -} - -/** - * wait for - * @param client - * @param size - */ -bool WebSockets::handleWebsocketWaitFor(WSclient_t * client, size_t size) { - if(!client->tcp || !client->tcp->connected()) { - return false; - } - - if(size > WEBSOCKETS_MAX_HEADER_SIZE) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d too big!\n", client->num, size); - return false; - } - - if(client->cWsRXsize >= size) { - return true; - } - - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor] size: %d cWsRXsize: %d\n", client->num, size, client->cWsRXsize); - readCb(client, &client->cWsHeader[client->cWsRXsize], (size - client->cWsRXsize), std::bind([](WebSockets * server, size_t size, WSclient_t * client, bool ok) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocketWaitFor][readCb] size: %d ok: %d\n", client->num, size, ok); - if(ok) { - client->cWsRXsize = size; - server->handleWebsocketCb(client); - } else { - DEBUG_WEBSOCKETS("[WS][%d][readCb] failed.\n", client->num); - client->cWsRXsize = 0; - // timeout or error - server->clientDisconnect(client, 1002); - } - }, - this, size, std::placeholders::_1, std::placeholders::_2)); - return false; -} - -void WebSockets::handleWebsocketCb(WSclient_t * client) { - if(!client->tcp || !client->tcp->connected()) { - return; - } - - uint8_t * buffer = client->cWsHeader; - - WSMessageHeader_t * header = &client->cWsHeaderDecode; - uint8_t * payload = NULL; - - uint8_t headerLen = 2; - - if(!handleWebsocketWaitFor(client, headerLen)) { - return; - } - - // split first 2 bytes in the data - header->fin = ((*buffer >> 7) & 0x01); - header->rsv1 = ((*buffer >> 6) & 0x01); - header->rsv2 = ((*buffer >> 5) & 0x01); - header->rsv3 = ((*buffer >> 4) & 0x01); - header->opCode = (WSopcode_t)(*buffer & 0x0F); - buffer++; - - header->mask = ((*buffer >> 7) & 0x01); - header->payloadLen = (WSopcode_t)(*buffer & 0x7F); - buffer++; - - if(header->payloadLen == 126) { - headerLen += 2; - if(!handleWebsocketWaitFor(client, headerLen)) { - return; - } - header->payloadLen = buffer[0] << 8 | buffer[1]; - buffer += 2; - } else if(header->payloadLen == 127) { - headerLen += 8; - // read 64bit integer as length - if(!handleWebsocketWaitFor(client, headerLen)) { - return; - } - - if(buffer[0] != 0 || buffer[1] != 0 || buffer[2] != 0 || buffer[3] != 0) { - // really too big! - header->payloadLen = 0xFFFFFFFF; - } else { - header->payloadLen = buffer[4] << 24 | buffer[5] << 16 | buffer[6] << 8 | buffer[7]; - } - buffer += 8; - } - - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ------- read massage frame -------\n", client->num); - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] fin: %u rsv1: %u rsv2: %u rsv3 %u opCode: %u\n", client->num, header->fin, header->rsv1, header->rsv2, header->rsv3, header->opCode); - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] mask: %u payloadLen: %u\n", client->num, header->mask, header->payloadLen); - - if(header->payloadLen > WEBSOCKETS_MAX_DATA_SIZE) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] payload too big! (%u)\n", client->num, header->payloadLen); - clientDisconnect(client, 1009); - return; - } - - if(header->mask) { - headerLen += 4; - if(!handleWebsocketWaitFor(client, headerLen)) { - return; - } - header->maskKey = buffer; - buffer += 4; - } - - if(header->payloadLen > 0) { - // if text data we need one more - payload = (uint8_t *)malloc(header->payloadLen + 1); - - if(!payload) { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] to less memory to handle payload %d!\n", client->num, header->payloadLen); - clientDisconnect(client, 1011); - return; - } - readCb(client, payload, header->payloadLen, std::bind(&WebSockets::handleWebsocketPayloadCb, this, std::placeholders::_1, std::placeholders::_2, payload)); - } else { - handleWebsocketPayloadCb(client, true, NULL); - } -} - -void WebSockets::handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload) { - WSMessageHeader_t * header = &client->cWsHeaderDecode; - if(ok) { - if(header->payloadLen > 0) { - payload[header->payloadLen] = 0x00; - - if(header->mask) { - // decode XOR - for(size_t i = 0; i < header->payloadLen; i++) { - payload[i] = (payload[i] ^ header->maskKey[i % 4]); - } - } - } - - switch(header->opCode) { - case WSop_text: - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] text: %s\n", client->num, payload); - // fallthrough - case WSop_binary: - case WSop_continuation: - messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); - break; - case WSop_ping: - // send pong back - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] ping received (%s)\n", client->num, payload ? (const char *)payload : ""); - sendFrame(client, WSop_pong, payload, header->payloadLen); - messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); - break; - case WSop_pong: - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get pong (%s)\n", client->num, payload ? (const char *)payload : ""); - client->pongReceived = true; - messageReceived(client, header->opCode, payload, header->payloadLen, header->fin); - break; - case WSop_close: { -#ifndef NODEBUG_WEBSOCKETS - uint16_t reasonCode = 1000; - if(header->payloadLen >= 2) { - reasonCode = payload[0] << 8 | payload[1]; - } -#endif - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] get ask for close. Code: %d\n", client->num, reasonCode); - if(header->payloadLen > 2) { - DEBUG_WEBSOCKETS(" (%s)\n", (payload + 2)); - } else { - DEBUG_WEBSOCKETS("\n"); - } - clientDisconnect(client, 1000); - } break; - default: - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] got unknown opcode: %d\n", client->num, header->opCode); - clientDisconnect(client, 1002); - break; - } - - if(payload) { - free(payload); - } - - // reset input - client->cWsRXsize = 0; -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - // register callback for next message - handleWebsocketWaitFor(client, 2); -#endif - - } else { - DEBUG_WEBSOCKETS("[WS][%d][handleWebsocket] missing data!\n", client->num); - free(payload); - clientDisconnect(client, 1002); - } -} - -/** - * generate the key for Sec-WebSocket-Accept - * @param clientKey String - * @return String Accept Key - */ -String WebSockets::acceptKey(String & clientKey) { - uint8_t sha1HashBin[20] = { 0 }; -#ifdef ESP8266 - sha1(clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", &sha1HashBin[0]); -#elif defined(ESP32) - String data = clientKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - esp_sha(SHA1, (unsigned char *)data.c_str(), data.length(), &sha1HashBin[0]); -#else - clientKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - SHA1_CTX ctx; - SHA1Init(&ctx); - SHA1Update(&ctx, (const unsigned char *)clientKey.c_str(), clientKey.length()); - SHA1Final(&sha1HashBin[0], &ctx); -#endif - - String key = base64_encode(sha1HashBin, 20); - //key.trim(); - trim(key); - - return key; -} - -/** - * base64_encode - * @param data uint8_t * - * @param length size_t - * @return base64 encoded String - */ -String WebSockets::base64_encode(uint8_t * data, size_t length) { - size_t size = ((length * 1.6f) + 1); - size = std::max(size, (size_t)5); // minimum buffer size - char * buffer = (char *)malloc(size); - if(buffer) { - base64_encodestate _state; - base64_init_encodestate(&_state); - int len = base64_encode_block((const char *)&data[0], length, &buffer[0], &_state); - len = base64_encode_blockend((buffer + len), &_state); - - String base64 = String(buffer); - free(buffer); - return base64; - } - return String("-FAIL-"); -} - -/** - * read x byte from tcp or get timeout - * @param client WSclient_t * - * @param out uint8_t * data buffer - * @param n size_t byte count - * @return true if ok - */ -bool WebSockets::readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb) { -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - if(!client->tcp || !client->tcp->connected()) { - return false; - } - - client->tcp->readBytes(out, n, std::bind([](WSclient_t * client, bool ok, WSreadWaitCb cb) { - if(cb) { - cb(client, ok); - } - }, - client, std::placeholders::_1, cb)); - -#else - unsigned long t = millis(); - ssize_t len; - DEBUG_WEBSOCKETS("[readCb] n: %zu t: %lu\n", n, t); - while(n > 0) { - if(client->tcp == NULL) { - DEBUG_WEBSOCKETS("[readCb] tcp is null!\n"); - if(cb) { - cb(client, false); - } - return false; - } - - if(!client->tcp->connected()) { - DEBUG_WEBSOCKETS("[readCb] not connected!\n"); - if(cb) { - cb(client, false); - } - return false; - } - - if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { - DEBUG_WEBSOCKETS("[readCb] receive TIMEOUT! %lu\n", (millis() - t)); - if(cb) { - cb(client, false); - } - return false; - } - - if(!client->tcp->available()) { - WEBSOCKETS_YIELD_MORE(); - continue; - } - - len = client->tcp->read((uint8_t *)out, n); - if(len > 0) { - t = millis(); - out += len; - n -= len; - // DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); - } else { - // DEBUG_WEBSOCKETS("Receive %d left %d!\n", len, n); - } - if(n > 0) { - WEBSOCKETS_YIELD(); - } - } - if(cb) { - cb(client, true); - } - WEBSOCKETS_YIELD(); -#endif - return true; -} - -/** - * write x byte to tcp or get timeout - * @param client WSclient_t * - * @param out uint8_t * data buffer - * @param n size_t byte count - * @return bytes send - */ -size_t WebSockets::write(WSclient_t * client, uint8_t * out, size_t n) { - if(out == NULL) - return 0; - if(client == NULL) - return 0; - unsigned long t = millis(); - size_t len = 0; - size_t total = 0; - DEBUG_WEBSOCKETS("[write] n: %zu t: %lu\n", n, t); - while(n > 0) { - if(client->tcp == NULL) { - DEBUG_WEBSOCKETS("[write] tcp is null!\n"); - break; - } - - if(!client->tcp->connected()) { - DEBUG_WEBSOCKETS("[write] not connected!\n"); - break; - } - - if((millis() - t) > WEBSOCKETS_TCP_TIMEOUT) { - DEBUG_WEBSOCKETS("[write] write TIMEOUT! %lu\n", (millis() - t)); - break; - } - - len = client->tcp->write((const uint8_t *)out, n); - if(len) { - t = millis(); - out += len; - n -= len; - total += len; - // DEBUG_WEBSOCKETS("write %d left %d!\n", len, n); - } else { - DEBUG_WEBSOCKETS("WS write %d failed left %d!\n", len, n); - } - if(n > 0) { - WEBSOCKETS_YIELD(); - } - } - WEBSOCKETS_YIELD(); - return total; -} - -size_t WebSockets::write(WSclient_t * client, const char * out) { - if(client == NULL) - return 0; - if(out == NULL) - return 0; - return write(client, (uint8_t *)out, strlen(out)); -} - -/** - * enable ping/pong heartbeat process - * @param client WSclient_t * - * @param pingInterval uint32_t how often ping will be sent - * @param pongTimeout uint32_t millis after which pong should timout if not received - * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect - */ -void WebSockets::enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { - if(client == NULL) - return; - client->pingInterval = pingInterval; - client->pongTimeout = pongTimeout; - client->disconnectTimeoutCount = disconnectTimeoutCount; - client->pongReceived = false; -} - -/** - * handle ping/pong heartbeat timeout process - * @param client WSclient_t * - */ -void WebSockets::handleHBTimeout(WSclient_t * client) { - if(client->pingInterval) { // if heartbeat is enabled - uint32_t pi = millis() - client->lastPing; - - if(client->pongReceived) { - client->pongTimeoutCount = 0; - } else { - if(pi > client->pongTimeout) { // pong not received in time - client->pongTimeoutCount++; - client->lastPing = millis() - client->pingInterval - 500; // force ping on the next run - - DEBUG_WEBSOCKETS("[HBtimeout] pong TIMEOUT! lp=%d millis=%lu pi=%d count=%d\n", client->lastPing, millis(), pi, client->pongTimeoutCount); - - if(client->disconnectTimeoutCount && client->pongTimeoutCount >= client->disconnectTimeoutCount) { - DEBUG_WEBSOCKETS("[HBtimeout] count=%d, DISCONNECTING\n", client->pongTimeoutCount); - clientDisconnect(client); - } - } - } - } -} -#endif diff --git a/otfpi/WebSockets.h b/otfpi/WebSockets.h deleted file mode 100644 index 1951a6619..000000000 --- a/otfpi/WebSockets.h +++ /dev/null @@ -1,233 +0,0 @@ -#if defined(OSPI) -/** - * @file WebSockets.h - * @date 20.05.2015 - * @author Markus Sattler - * - * Copyright (c) 2015 Markus Sattler. All rights reserved. - * This file is part of the WebSockets for Arduino. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * 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 WEBSOCKETS_H_ -#define WEBSOCKETS_H_ - -#include -#include -#include -#include "WebSocketsVersion.h" -#include "etherport.h" -#include "wscompat.h" -#include "libsha1.h" -#include "cencode_inc.h" - -#ifndef DEBUG_WEBSOCKETS -#define DEBUG_WEBSOCKETS(msg, ...) {printf(msg, ##__VA_ARGS__);} -//#ifndef NODEBUG_WEBSOCKETS -//#define NODEBUG_WEBSOCKETS -//#endif -#endif - -#define WEBSOCKETS_MAX_DATA_SIZE (15 * 1024) -#define WEBSOCKETS_USE_BIG_MEM -#define GET_FREE_HEAP not for linux -#define WEBSOCKETS_YIELD() sched_yield() -#define WEBSOCKETS_YIELD_MORE() delay(1) - -#ifndef WEBSOCKETS_TCP_TIMEOUT -#define WEBSOCKETS_TCP_TIMEOUT (5000) -#endif - -#define NETWORK_ESP8266_ASYNC (0) -#define NETWORK_ESP8266 (1) -#define NETWORK_W5100 (2) -#define NETWORK_ENC28J60 (3) -#define NETWORK_ESP32 (4) -#define NETWORK_ESP32_ETH (5) -#define NETWORK_RP2040 (6) -#define NETWORK_LINUX (7) - -#define WEBSOCKETS_NETWORK_TYPE NETWORK_LINUX - -// Network Class -#define WEBSOCKETS_NETWORK_CLASS EthernetClient -//#define WEBSOCKETS_NETWORK_SSL_CLASS EthernetClientSsl -#define WEBSOCKETS_NETWORK_SERVER_CLASS EthernetServer - -// max size of the WS Message Header -#define WEBSOCKETS_MAX_HEADER_SIZE (14) - -#ifdef WEBSOCKETS_NETWORK_SSL_CLASS -#define HAS_SSL -#endif - -#define WEBSOCKETS_STRING(var) var - -#define bit(b) (1UL << (b)) // Taken directly from Arduino.h - -typedef enum { - WSC_NOT_CONNECTED, - WSC_HEADER, - WSC_BODY, - WSC_CONNECTED -} WSclientsStatus_t; - -typedef enum { - WStype_ERROR, - WStype_DISCONNECTED, - WStype_CONNECTED, - WStype_TEXT, - WStype_BIN, - WStype_FRAGMENT_TEXT_START, - WStype_FRAGMENT_BIN_START, - WStype_FRAGMENT, - WStype_FRAGMENT_FIN, - WStype_PING, - WStype_PONG, -} WStype_t; - -typedef enum { - WSop_continuation = 0x00, ///< %x0 denotes a continuation frame - WSop_text = 0x01, ///< %x1 denotes a text frame - WSop_binary = 0x02, ///< %x2 denotes a binary frame - ///< %x3-7 are reserved for further non-control frames - WSop_close = 0x08, ///< %x8 denotes a connection close - WSop_ping = 0x09, ///< %x9 denotes a ping - WSop_pong = 0x0A ///< %xA denotes a pong - ///< %xB-F are reserved for further control frames -} WSopcode_t; - -typedef struct { - bool fin; - bool rsv1; - bool rsv2; - bool rsv3; - - WSopcode_t opCode; - bool mask; - - size_t payloadLen; - - uint8_t * maskKey; -} WSMessageHeader_t; - -typedef struct { - void init(uint8_t num, - uint32_t pingInterval, - uint32_t pongTimeout, - uint8_t disconnectTimeoutCount) { - this->num = num; - this->pingInterval = pingInterval; - this->pongTimeout = pongTimeout; - this->disconnectTimeoutCount = disconnectTimeoutCount; - } - - uint8_t num = 0; ///< connection number - - WSclientsStatus_t status = WSC_NOT_CONNECTED; - - WEBSOCKETS_NETWORK_CLASS * tcp = nullptr; - - bool isSocketIO = false; ///< client for socket.io server - -#if defined(HAS_SSL) - bool isSSL = false; ///< run in ssl mode - WEBSOCKETS_NETWORK_SSL_CLASS * ssl; -#endif - - String cUrl; ///< http url - uint16_t cCode = 0; ///< http code - - bool cIsClient = false; ///< will be used for masking - bool cIsUpgrade = false; ///< Connection == Upgrade - bool cIsWebsocket = false; ///< Upgrade == websocket - - String cSessionId; ///< client Set-Cookie (session id) - String cKey; ///< client Sec-WebSocket-Key - String cAccept; ///< client Sec-WebSocket-Accept - String cProtocol; ///< client Sec-WebSocket-Protocol - String cExtensions; ///< client Sec-WebSocket-Extensions - uint16_t cVersion = 0; ///< client Sec-WebSocket-Version - - uint8_t cWsRXsize = 0; ///< State of the RX - uint8_t cWsHeader[WEBSOCKETS_MAX_HEADER_SIZE]; ///< RX WS Message buffer - WSMessageHeader_t cWsHeaderDecode; - - String base64Authorization; ///< Base64 encoded Auth request - String plainAuthorization; ///< Base64 encoded Auth request - - String extraHeaders; - - bool cHttpHeadersValid = false; ///< non-websocket http header validity indicator - size_t cMandatoryHeadersCount; ///< non-websocket mandatory http headers present count - - bool pongReceived = false; - uint32_t pingInterval = 0; // how often ping will be sent, 0 means "heartbeat is not active" - uint32_t lastPing = 0; // millis when last pong has been received - uint32_t pongTimeout = 0; // interval in millis after which pong is considered to timeout - uint8_t disconnectTimeoutCount = 0; // after how many subsequent pong timeouts discconnect will happen, 0 means "do not disconnect" - uint8_t pongTimeoutCount = 0; // current pong timeout count - -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - String cHttpLine; ///< HTTP header lines -#endif - -} WSclient_t; - -class WebSockets { - protected: -#ifdef __AVR__ - typedef void (*WSreadWaitCb)(WSclient_t * client, bool ok); -#else - typedef std::function WSreadWaitCb; -#endif - - virtual void clientDisconnect(WSclient_t * client) = 0; - virtual bool clientIsConnected(WSclient_t * client) = 0; - - void clientDisconnect(WSclient_t * client, uint16_t code, char * reason = NULL, size_t reasonLen = 0); - - virtual void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) = 0; - - uint8_t createHeader(uint8_t * buf, WSopcode_t opcode, size_t length, bool mask, uint8_t maskKey[4], bool fin); - bool sendFrameHeader(WSclient_t * client, WSopcode_t opcode, size_t length = 0, bool fin = true); - bool sendFrame(WSclient_t * client, WSopcode_t opcode, uint8_t * payload = NULL, size_t length = 0, bool fin = true, bool headerToPayload = false); - - void headerDone(WSclient_t * client); - - void handleWebsocket(WSclient_t * client); - - bool handleWebsocketWaitFor(WSclient_t * client, size_t size); - void handleWebsocketCb(WSclient_t * client); - void handleWebsocketPayloadCb(WSclient_t * client, bool ok, uint8_t * payload); - - String acceptKey(String & clientKey); - String base64_encode(uint8_t * data, size_t length); - - bool readCb(WSclient_t * client, uint8_t * out, size_t n, WSreadWaitCb cb); - virtual size_t write(WSclient_t * client, uint8_t * out, size_t n); - size_t write(WSclient_t * client, const char * out); - - void enableHeartbeat(WSclient_t * client, uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); - void handleHBTimeout(WSclient_t * client); -}; - -#ifndef UNUSED -#define UNUSED(var) (void)(var) -#endif -#endif /* WEBSOCKETS_H_ */ -#endif \ No newline at end of file diff --git a/otfpi/WebSocketsClient.cpp b/otfpi/WebSocketsClient.cpp deleted file mode 100644 index b867dbd45..000000000 --- a/otfpi/WebSocketsClient.cpp +++ /dev/null @@ -1,1004 +0,0 @@ -#if defined(OSPI) -/** - * @file WebSocketsClient.cpp - * @date 20.05.2015 - * @author Markus Sattler - * - * Copyright (c) 2015 Markus Sattler. All rights reserved. - * This file is part of the WebSockets for Arduino. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -#include "WebSockets.h" -#include "WebSocketsClient.h" -#include -#include -#include "wscompat.h" - -WebSocketsClient::WebSocketsClient() { - _cbEvent = NULL; - _client.num = 0; - _client.cIsClient = true; - _client.extraHeaders = WEBSOCKETS_STRING("Origin: file://"); - _reconnectInterval = 500; - _port = 0; - _host = ""; -} - -WebSocketsClient::~WebSocketsClient() { - disconnect(); -} - -/** - * calles to init the Websockets server - */ -void WebSocketsClient::begin(const char * host, uint16_t port, const char * url, const char * protocol) { - _host = host; - _port = port; -#if defined(HAS_SSL) - _fingerprint = SSL_FINGERPRINT_NULL; - _CA_cert = NULL; -#endif - - _client.num = 0; - _client.status = WSC_NOT_CONNECTED; - _client.tcp = NULL; -#if defined(HAS_SSL) - _client.isSSL = false; - _client.ssl = NULL; -#endif - _client.cUrl = url; - _client.cCode = 0; - _client.cIsUpgrade = false; - _client.cIsWebsocket = true; - _client.cKey = ""; - _client.cAccept = ""; - _client.cProtocol = protocol; - _client.cExtensions = ""; - _client.cVersion = 0; - _client.base64Authorization = ""; - _client.plainAuthorization = ""; - _client.isSocketIO = false; - - _client.lastPing = 0; - _client.pongReceived = false; - _client.pongTimeoutCount = 0; - - // todo find better seed - srand(millis()); -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - asyncConnect(); -#endif - - _lastConnectionFail = 0; - _lastHeaderSent = 0; - - DEBUG_WEBSOCKETS("[WS-Client] Websocket Version: " WEBSOCKETS_VERSION "\n"); -} - -void WebSocketsClient::begin(String host, uint16_t port, String url, String protocol) { - begin(host.c_str(), port, url.c_str(), protocol.c_str()); -} - -/*void WebSocketsClient::begin(IPAddress host, uint16_t port, const char * url, const char * protocol) { - return begin(host.toString().c_str(), port, url, protocol); -}*/ - -#if defined(HAS_SSL) -#if defined(SSL_AXTLS) -void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const char * fingerprint, const char * protocol) { - begin(host, port, url, protocol); - _client.isSSL = true; - _fingerprint = fingerprint; - _CA_cert = NULL; -} - -void WebSocketsClient::beginSSL(String host, uint16_t port, String url, String fingerprint, String protocol) { - beginSSL(host.c_str(), port, url.c_str(), fingerprint.c_str(), protocol.c_str()); -} - -void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { - begin(host, port, url, protocol); - _client.isSSL = true; - _fingerprint = SSL_FINGERPRINT_NULL; - _CA_cert = CA_cert; -} -#else -void WebSocketsClient::beginSSL(const char * host, uint16_t port, const char * url, const uint8_t * fingerprint, const char * protocol) { - begin(host, port, url, protocol); - _client.isSSL = true; - _fingerprint = fingerprint; - _CA_cert = NULL; -} - -void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) { - begin(host, port, url, protocol); - _client.isSSL = true; - _fingerprint = SSL_FINGERPRINT_NULL; - _CA_cert = CA_cert; -} - -void WebSocketsClient::beginSslWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { - beginSslWithCA(host, port, url, new BearSSL::X509List(CA_cert), protocol); -} - -void WebSocketsClient::setSSLClientCertKey(BearSSL::X509List * clientCert, BearSSL::PrivateKey * clientPrivateKey) { - _client_cert = clientCert; - _client_key = clientPrivateKey; -} - -void WebSocketsClient::setSSLClientCertKey(const char * clientCert, const char * clientPrivateKey) { - setSSLClientCertKey(new BearSSL::X509List(clientCert), new BearSSL::PrivateKey(clientPrivateKey)); -} - -#endif // SSL_AXTLS -#endif // HAS_SSL - -/* -void WebSocketsClient::beginSocketIO(const char * host, uint16_t port, const char * url, const char * protocol) { - begin(host, port, url, protocol); - _client.isSocketIO = true; -} - -void WebSocketsClient::beginSocketIO(String host, uint16_t port, String url, String protocol) { - beginSocketIO(host.c_str(), port, url.c_str(), protocol.c_str()); -} - -#if defined(HAS_SSL) -void WebSocketsClient::beginSocketIOSSL(const char * host, uint16_t port, const char * url, const char * protocol) { - begin(host, port, url, protocol); - _client.isSocketIO = true; - _client.isSSL = true; - _fingerprint = SSL_FINGERPRINT_NULL; -} - -void WebSocketsClient::beginSocketIOSSL(String host, uint16_t port, String url, String protocol) { - beginSocketIOSSL(host.c_str(), port, url.c_str(), protocol.c_str()); -} - -#if defined(SSL_BARESSL) -void WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, BearSSL::X509List * CA_cert, const char * protocol) { - begin(host, port, url, protocol); - _client.isSocketIO = true; - _client.isSSL = true; - _fingerprint = SSL_FINGERPRINT_NULL; - _CA_cert = CA_cert; -} -#endif - -void WebSocketsClient::beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url, const char * CA_cert, const char * protocol) { - begin(host, port, url, protocol); - _client.isSocketIO = true; - _client.isSSL = true; - _fingerprint = SSL_FINGERPRINT_NULL; -#if defined(SSL_BARESSL) - _CA_cert = new BearSSL::X509List(CA_cert); -#else - _CA_cert = CA_cert; -#endif -} - -#endif -*/ -#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) -/** - * called in arduino loop - */ -void WebSocketsClient::loop(void) { - if(_port == 0) { - return; - } - WEBSOCKETS_YIELD(); - if(!clientIsConnected(&_client)) { - // do not flood the server - if((millis() - _lastConnectionFail) < _reconnectInterval) { - return; - } - -#if defined(HAS_SSL) - if(_client.isSSL) { - DEBUG_WEBSOCKETS("[WS-Client] connect wss...\n"); - if(_client.ssl) { - delete _client.ssl; - _client.ssl = NULL; - _client.tcp = NULL; - } - _client.ssl = new WEBSOCKETS_NETWORK_SSL_CLASS(); - _client.tcp = _client.ssl; - if(_CA_cert) { - DEBUG_WEBSOCKETS("[WS-Client] setting CA certificate"); -#if defined(ESP32) - _client.ssl->setCACert(_CA_cert); -#elif defined(ESP8266) && defined(SSL_AXTLS) - _client.ssl->setCACert((const uint8_t *)_CA_cert, strlen(_CA_cert) + 1); -#elif(defined(ESP8266) || defined(ARDUINO_ARCH_RP2040)) && defined(SSL_BARESSL) - _client.ssl->setTrustAnchors(_CA_cert); -#else -#error setCACert not implemented -#endif -#if defined(ESP32) - } else if(!SSL_FINGERPRINT_IS_SET) { - _client.ssl->setInsecure(); -#elif defined(SSL_BARESSL) - } else if(SSL_FINGERPRINT_IS_SET) { - _client.ssl->setFingerprint(_fingerprint); - } else { - _client.ssl->setInsecure(); - } - if(_client_cert && _client_key) { - _client.ssl->setClientRSACert(_client_cert, _client_key); - DEBUG_WEBSOCKETS("[WS-Client] setting client certificate and key"); -#endif - } - } else { - DEBUG_WEBSOCKETS("[WS-Client] connect ws...\n"); - if(_client.tcp) { - delete _client.tcp; - _client.tcp = NULL; - } - _client.tcp = new WEBSOCKETS_NETWORK_CLASS(); - } -#else - _client.tcp = new WEBSOCKETS_NETWORK_CLASS(); -#endif - - if(!_client.tcp) { - DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!"); - return; - } - WEBSOCKETS_YIELD(); -#if defined(ESP32) - if(_client.tcp->connect(_host.c_str(), _port, WEBSOCKETS_TCP_TIMEOUT)) { -#else - struct hostent *host = gethostbyname(_host.c_str()); - if(host && _client.tcp->connect((uint8_t*)host->h_addr, _port)) { -#endif - connectedCb(); - _lastConnectionFail = 0; - } else { - connectFailedCb(); - _lastConnectionFail = millis(); - } - } else { - handleClientData(); - WEBSOCKETS_YIELD(); - if(_client.status == WSC_CONNECTED) { - handleHBPing(); - handleHBTimeout(&_client); - } - } -} -#endif - -/** - * set callback function - * @param cbEvent WebSocketServerEvent - */ -void WebSocketsClient::onEvent(WebSocketClientEvent cbEvent) { - _cbEvent = cbEvent; -} - -bool WebSocketsClient::sendTXT(const char * payload, size_t length, bool fin, bool headerToPayload) { - if(length == 0) { - length = strlen(payload); - } - if(clientIsConnected(&_client)) { - //https://www.oryx-embedded.com/doc/web__socket_8c_source.html - //A fragmented message consists of a single frame with the FIN bit - //clear and an opcode other than 0, followed by zero or more frames - //with the FIN bit clear and the opcode set to 0, and terminated by - //a single frame with the FIN bit set and an opcode of 0 - WSopcode_t opcode = WSop_text; - if (!firstFrag) { - firstFrag = true; - } else { - opcode = WSop_continuation; - } - if (fin) - firstFrag = false; - return sendFrame(&_client, opcode, (uint8_t *)payload, length, fin, headerToPayload); - } - return false; -} - -/** - * send text data to client - * @param num uint8_t client id - * @param payload uint8_t * - * @param length size_t - * @param headerToPayload bool (see sendFrame for more details) - * @return true if ok - */ -bool WebSocketsClient::sendTXT(uint8_t * payload, size_t length, bool headerToPayload) { - if(length == 0) { - length = strlen((const char *)payload); - } - if(clientIsConnected(&_client)) { - return sendFrame(&_client, WSop_text, payload, length, true, headerToPayload); - } - return false; -} - -bool WebSocketsClient::sendTXT(const uint8_t * payload, size_t length) { - return sendTXT((uint8_t *)payload, length); -} - -bool WebSocketsClient::sendTXT(char * payload, size_t length, bool headerToPayload) { - return sendTXT((uint8_t *)payload, length, headerToPayload); -} - -bool WebSocketsClient::sendTXT(const char * payload, size_t length) { - return sendTXT((uint8_t *)payload, length); -} - -bool WebSocketsClient::sendTXT(String & payload) { - return sendTXT((uint8_t *)payload.c_str(), payload.length()); -} - -bool WebSocketsClient::sendTXT(char payload) { - uint8_t buf[WEBSOCKETS_MAX_HEADER_SIZE + 2] = { 0x00 }; - buf[WEBSOCKETS_MAX_HEADER_SIZE] = payload; - return sendTXT(buf, 1, true); -} - -/** - * send binary data to client - * @param num uint8_t client id - * @param payload uint8_t * - * @param length size_t - * @param headerToPayload bool (see sendFrame for more details) - * @return true if ok - */ -bool WebSocketsClient::sendBIN(uint8_t * payload, size_t length, bool headerToPayload) { - if(clientIsConnected(&_client)) { - return sendFrame(&_client, WSop_binary, payload, length, true, headerToPayload); - } - return false; -} - -bool WebSocketsClient::sendBIN(const uint8_t * payload, size_t length) { - return sendBIN((uint8_t *)payload, length); -} - -/** - * sends a WS ping to Server - * @param payload uint8_t * - * @param length size_t - * @return true if ping is send out - */ -bool WebSocketsClient::sendPing(uint8_t * payload, size_t length) { - if(clientIsConnected(&_client)) { - bool sent = sendFrame(&_client, WSop_ping, payload, length); - if(sent) - _client.lastPing = millis(); - return sent; - } - return false; -} - -bool WebSocketsClient::sendPing(String & payload) { - return sendPing((uint8_t *)payload.c_str(), payload.length()); -} - -/** - * disconnect one client - * @param num uint8_t client id - */ -void WebSocketsClient::disconnect(void) { - if(clientIsConnected(&_client)) { - WebSockets::clientDisconnect(&_client, 1000); - } -} - -/** - * set the Authorizatio for the http request - * @param user const char * - * @param password const char * - */ -void WebSocketsClient::setAuthorization(const char * user, const char * password) { - if(user && password) { - String auth = user; - auth += ":"; - auth += password; - _client.base64Authorization = base64_encode((uint8_t *)auth.c_str(), auth.length()); - } -} - -/** - * set the Authorizatio for the http request - * @param auth const char * base64 - */ -void WebSocketsClient::setAuthorization(const char * auth) { - if(auth) { - //_client.base64Authorization = auth; - _client.plainAuthorization = auth; - } -} - -/** - * set extra headers for the http request; - * separate headers by "\r\n" - * @param extraHeaders const char * extraHeaders - */ -void WebSocketsClient::setExtraHeaders(const char * extraHeaders) { - _client.extraHeaders = extraHeaders; -} - -/** - * set the reconnect Interval - * how long to wait after a connection initiate failed - * @param time in ms - */ -void WebSocketsClient::setReconnectInterval(unsigned long time) { - _reconnectInterval = time; -} - -bool WebSocketsClient::isConnected(void) { - return (_client.status == WSC_CONNECTED); -} - -// ################################################################################# -// ################################################################################# -// ################################################################################# - -/** - * - * @param client WSclient_t * ptr to the client struct - * @param opcode WSopcode_t - * @param payload uint8_t * - * @param length size_t - */ -void WebSocketsClient::messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin) { - WStype_t type = WStype_ERROR; - - UNUSED(client); - - switch(opcode) { - case WSop_text: - type = fin ? WStype_TEXT : WStype_FRAGMENT_TEXT_START; - break; - case WSop_binary: - type = fin ? WStype_BIN : WStype_FRAGMENT_BIN_START; - break; - case WSop_continuation: - type = fin ? WStype_FRAGMENT_FIN : WStype_FRAGMENT; - break; - case WSop_ping: - type = WStype_PING; - break; - case WSop_pong: - type = WStype_PONG; - break; - case WSop_close: - default: - break; - } - - runCbEvent(type, payload, length); -} - -/** - * Disconnect an client - * @param client WSclient_t * ptr to the client struct - */ -void WebSocketsClient::clientDisconnect(WSclient_t * client) { - bool event = false; - -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) - if(client->isSSL && client->ssl) { - if(client->ssl->connected()) { - client->ssl->flush(); - client->ssl->stop(); - } - event = true; - delete client->ssl; - client->ssl = NULL; - client->tcp = NULL; - } -#endif - - if(client->tcp) { - if(client->tcp->connected()) { -#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) - client->tcp->flush(); -#endif - client->tcp->stop(); - } - event = true; -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - client->status = WSC_NOT_CONNECTED; -#else - delete client->tcp; -#endif - client->tcp = NULL; - } - - client->cCode = 0; - client->cKey = ""; - client->cAccept = ""; - client->cVersion = 0; - client->cIsUpgrade = false; - client->cIsWebsocket = false; - client->cSessionId = ""; - - client->status = WSC_NOT_CONNECTED; - _lastConnectionFail = millis(); - - DEBUG_WEBSOCKETS("[WS-Client] client disconnected.\n"); - if(event) { - runCbEvent(WStype_DISCONNECTED, NULL, 0); - } -} - -/** - * get client state - * @param client WSclient_t * ptr to the client struct - * @return true = conneted - */ -bool WebSocketsClient::clientIsConnected(WSclient_t * client) { - if(!client->tcp) { - return false; - } - - if(client->tcp->connected()) { - if(client->status != WSC_NOT_CONNECTED) { - return true; - } - } else { - // client lost - if(client->status != WSC_NOT_CONNECTED) { - DEBUG_WEBSOCKETS("[WS-Client] connection lost.\n"); - // do cleanup - clientDisconnect(client); - } - } - - if(client->tcp) { - // do cleanup - clientDisconnect(client); - } - - return false; -} -#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) -/** - * Handel incomming data from Client - */ -void WebSocketsClient::handleClientData(void) { - if((_client.status == WSC_HEADER || _client.status == WSC_BODY) && _lastHeaderSent + WEBSOCKETS_TCP_TIMEOUT < millis()) { - DEBUG_WEBSOCKETS("[WS-Client][handleClientData] header response timeout.. disconnecting!\n"); - clientDisconnect(&_client); - WEBSOCKETS_YIELD(); - return; - } - - int len = _client.tcp->available(); - if(len > 0) { - switch(_client.status) { - case WSC_HEADER: { - String headerLine = _client.tcp->readStringUntil('\n'); - handleHeader(&_client, &headerLine); - } break; - case WSC_BODY: { - char buf[256] = { 0 }; - len = _client.tcp->readBytes(&buf[0], sizeof(buf)); - buf[len] = 0; - String bodyLine = buf; - handleHeader(&_client, &bodyLine); - } break; - case WSC_CONNECTED: - WebSockets::handleWebsocket(&_client); - break; - default: - WebSockets::clientDisconnect(&_client, 1002); - break; - } - } - WEBSOCKETS_YIELD(); -} -#endif - -/** - * send the WebSocket header to Server - * @param client WSclient_t * ptr to the client struct - */ -void WebSocketsClient::sendHeader(WSclient_t * client) { - static const char * NEW_LINE = "\r\n"; - - DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header...\n"); - - uint8_t randomKey[16] = { 0 }; - - for(uint8_t i = 0; i < sizeof(randomKey); i++) { - randomKey[i] = random(0xFF); - } - - client->cKey = base64_encode(&randomKey[0], 16); - -#ifndef NODEBUG_WEBSOCKETS - unsigned long start = micros(); -#endif - - String handshake; - bool ws_header = true; - String url = client->cUrl; - - if(client->isSocketIO) { - if(client->cSessionId.length() == 0) { - url += WEBSOCKETS_STRING("&transport=polling"); - ws_header = false; - } else { - url += WEBSOCKETS_STRING("&transport=websocket&sid="); - url += client->cSessionId; - } - } - - handshake = WEBSOCKETS_STRING("GET "); - handshake += url + WEBSOCKETS_STRING( - " HTTP/1.1\r\n" - "Host: "); - handshake += _host + ":" + std::to_string(_port) + NEW_LINE; - - if(ws_header) { - handshake += WEBSOCKETS_STRING( - "Connection: Upgrade\r\n" - "Upgrade: websocket\r\n" - "Sec-WebSocket-Version: 13\r\n" - "Sec-WebSocket-Key: "); - handshake += client->cKey + NEW_LINE; - - if(client->cProtocol.length() > 0) { - handshake += WEBSOCKETS_STRING("Sec-WebSocket-Protocol: "); - handshake += client->cProtocol + NEW_LINE; - } - - if(client->cExtensions.length() > 0) { - handshake += WEBSOCKETS_STRING("Sec-WebSocket-Extensions: "); - handshake += client->cExtensions + NEW_LINE; - } - } else { - handshake += WEBSOCKETS_STRING("Connection: keep-alive\r\n"); - } - - // add extra headers; by default this includes "Origin: file://" - if(client->extraHeaders.length() > 0) { - handshake += client->extraHeaders + NEW_LINE; - } - - handshake += WEBSOCKETS_STRING("User-Agent: arduino-WebSocket-Client\r\n"); - - if(client->base64Authorization.length() > 0) { - handshake += WEBSOCKETS_STRING("Authorization: Basic "); - handshake += client->base64Authorization + NEW_LINE; - } - - if(client->plainAuthorization.length() > 0) { - handshake += WEBSOCKETS_STRING("Authorization: "); - handshake += client->plainAuthorization + NEW_LINE; - } - - handshake += NEW_LINE; - - DEBUG_WEBSOCKETS("[WS-Client][sendHeader] handshake %s", (uint8_t *)handshake.c_str()); - write(client, (uint8_t *)handshake.c_str(), handshake.length()); - -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine))); -#endif - - DEBUG_WEBSOCKETS("[WS-Client][sendHeader] sending header... Done (%luus).\n", (micros() - start)); - _lastHeaderSent = millis(); -} - -/** - * handle the WebSocket header reading - * @param client WSclient_t * ptr to the client struct - */ -void WebSocketsClient::handleHeader(WSclient_t * client, String * headerLine) { - trim(*headerLine); - - // this code handels the http body for Socket.IO V3 requests - if(headerLine->length() > 0 && client->isSocketIO && client->status == WSC_BODY && client->cSessionId.length() == 0) { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] socket.io json: %s\n", headerLine->c_str()); - String sid_begin = WEBSOCKETS_STRING("\"sid\":\""); - if(indexOf(headerLine, sid_begin) > -1) { - int start = indexOf(headerLine, sid_begin) + sid_begin.length(); - int end = indexOf(headerLine, "\"", start); - client->cSessionId = substring(headerLine, start, end); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cSessionId: %s\n", client->cSessionId.c_str()); - - // Trigger websocket connection code path - *headerLine = ""; - } - } - - // headle HTTP header - if(headerLine->length() > 0) { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] RX: %s\n", headerLine->c_str()); - - if(startsWith(headerLine, WEBSOCKETS_STRING("HTTP/1."))) { - // "HTTP/1.1 101 Switching Protocols" - client->cCode = stoi(substring(headerLine, 9, indexOf(headerLine, ' ', 9))); - } else if(indexOf(headerLine, ':') >= 0) { - String headerName = substring(headerLine, 0, indexOf(headerLine, ':')); - String headerValue = substring(headerLine, indexOf(headerLine, ':') + 1); - - // remove space in the beginning (RFC2616) - if(headerValue[0] == ' ') { - remove(headerValue, 0, 1); - } - - if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Connection"))) { - if(equalsIgnoreCase(headerValue, WEBSOCKETS_STRING("upgrade"))) { - client->cIsUpgrade = true; - } - } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Upgrade"))) { - if(equalsIgnoreCase(headerValue, WEBSOCKETS_STRING("websocket"))) { - client->cIsWebsocket = true; - } - } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Sec-WebSocket-Accept"))) { - client->cAccept = headerValue; - trim(client->cAccept); // see rfc6455 - } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Sec-WebSocket-Protocol"))) { - client->cProtocol = headerValue; - } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Sec-WebSocket-Extensions"))) { - client->cExtensions = headerValue; - } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Sec-WebSocket-Version"))) { - client->cVersion = stoi(headerValue); - } else if(equalsIgnoreCase(headerName, WEBSOCKETS_STRING("Set-Cookie")) && indexOf(headerValue, " io=") > -1) { - if(indexOf(headerValue, ';') > -1) { - client->cSessionId = substring(headerValue, indexOf(headerValue, '=') + 1, indexOf(headerValue, ";")); - } else { - client->cSessionId = substring(headerValue, indexOf(headerValue, '=') + 1); - } - } - } else { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header error (%s)\n", headerLine->c_str()); - } - - (*headerLine) = ""; -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - client->tcp->readStringUntil('\n', &(client->cHttpLine), std::bind(&WebSocketsClient::handleHeader, this, client, &(client->cHttpLine))); -#endif - } else { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Header read fin.\n"); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Client settings:\n"); - - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cURL: %s\n", client->cUrl.c_str()); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cKey: %s\n", client->cKey.c_str()); - - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Server header:\n"); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cCode: %d\n", client->cCode); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsUpgrade: %d\n", client->cIsUpgrade); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cIsWebsocket: %d\n", client->cIsWebsocket); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cAccept: %s\n", client->cAccept.c_str()); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cProtocol: %s\n", client->cProtocol.c_str()); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cExtensions: %s\n", client->cExtensions.c_str()); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cVersion: %d\n", client->cVersion); - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] - cSessionId: %s\n", client->cSessionId.c_str()); - - if(client->isSocketIO && client->cSessionId.length() == 0 && clientIsConnected(client)) { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] still missing cSessionId try socket.io V3\n"); - client->status = WSC_BODY; - return; - } else { - client->status = WSC_HEADER; - } - - bool ok = (client->cIsUpgrade && client->cIsWebsocket); - - if(ok) { - switch(client->cCode) { - case 101: ///< Switching Protocols - - break; - case 200: - if(client->isSocketIO) { - break; - } - // falls through - case 403: ///< Forbidden - // todo handle login - // falls through - default: ///< Server dont unterstand requrst - ok = false; - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] serverCode is not 101 (%d)\n", client->cCode); - clientDisconnect(client); - _lastConnectionFail = millis(); - break; - } - } - - if(ok) { - if(client->cAccept.length() == 0) { - ok = false; - } else { - // generate Sec-WebSocket-Accept key for check - String sKey = acceptKey(client->cKey); - if(sKey != client->cAccept) { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Sec-WebSocket-Accept is wrong\n"); - ok = false; - } - } - } - - if(ok) { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] Websocket connection init done.\n"); - headerDone(client); - - runCbEvent(WStype_CONNECTED, (uint8_t *)client->cUrl.c_str(), client->cUrl.length()); -#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) - } else if(client->isSocketIO) { - if(client->cSessionId.length() > 0) { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] found cSessionId\n"); - if(clientIsConnected(client) && _client.tcp->available()) { - // read not needed data - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] still data in buffer (%d), clean up.\n", _client.tcp->available()); - while(_client.tcp->available() > 0) { - uint8_t buf; - _client.tcp->read(&buf, 1); - } - } - sendHeader(client); - } -#endif - } else { - DEBUG_WEBSOCKETS("[WS-Client][handleHeader] no Websocket connection close.\n"); - _lastConnectionFail = millis(); - if(clientIsConnected(client)) { - write(client, "This is a webSocket client!"); - } - clientDisconnect(client); - } - } -} - -void WebSocketsClient::connectedCb() { - DEBUG_WEBSOCKETS("[WS-Client] connected to %s:%u.\n", _host.c_str(), _port); - -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - _client.tcp->onDisconnect(std::bind([](WebSocketsClient * c, AsyncTCPbuffer * obj, WSclient_t * client) -> bool { - DEBUG_WEBSOCKETS("[WS-Server][%d] Disconnect client\n", client->num); - client->status = WSC_NOT_CONNECTED; - client->tcp = NULL; - - // reconnect - c->asyncConnect(); - - return true; - }, - this, std::placeholders::_1, &_client)); -#endif - - _client.status = WSC_HEADER; - -#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) - // set Timeout for readBytesUntil and readStringUntil - //_client.tcp->setTimeout(WEBSOCKETS_TCP_TIMEOUT); -#endif - -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP32) || (WEBSOCKETS_NETWORK_TYPE == NETWORK_RP2040) - _client.tcp->setNoDelay(true); -#endif - -#if defined(HAS_SSL) -#if defined(SSL_AXTLS) || defined(ESP32) - if(_client.isSSL && SSL_FINGERPRINT_IS_SET) { - if(!_client.ssl->verify(_fingerprint.c_str(), _host.c_str())) { - DEBUG_WEBSOCKETS("[WS-Client] certificate mismatch\n"); - WebSockets::clientDisconnect(&_client, 1000); - return; - } -#else - if(_client.isSSL && SSL_FINGERPRINT_IS_SET) { -#endif - } else if(_client.isSSL && !_CA_cert) { -#if defined(SSL_BARESSL) - _client.ssl->setInsecure(); -#endif - } -#endif - - // send Header to Server - sendHeader(&_client); -} - -void WebSocketsClient::connectFailedCb() { - DEBUG_WEBSOCKETS("[WS-Client] connection to %s:%u Failed\n", _host.c_str(), _port); -} - -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - -void WebSocketsClient::asyncConnect() { - DEBUG_WEBSOCKETS("[WS-Client] asyncConnect...\n"); - - AsyncClient * tcpclient = new AsyncClient(); - - if(!tcpclient) { - DEBUG_WEBSOCKETS("[WS-Client] creating AsyncClient class failed!\n"); - return; - } - - tcpclient->onDisconnect([](void * obj, AsyncClient * c) { - c->free(); - delete c; - }); - - tcpclient->onConnect(std::bind([](WebSocketsClient * ws, AsyncClient * tcp) { - ws->_client.tcp = new AsyncTCPbuffer(tcp); - if(!ws->_client.tcp) { - DEBUG_WEBSOCKETS("[WS-Client] creating Network class failed!\n"); - ws->connectFailedCb(); - return; - } - ws->connectedCb(); - }, - this, std::placeholders::_2)); - - tcpclient->onError(std::bind([](WebSocketsClient * ws, AsyncClient * tcp) { - ws->connectFailedCb(); - - // reconnect - ws->asyncConnect(); - }, - this, std::placeholders::_2)); - - if(!tcpclient->connect(_host.c_str(), _port)) { - connectFailedCb(); - delete tcpclient; - } -} - -#endif - -/** - * send heartbeat ping to server in set intervals - */ -void WebSocketsClient::handleHBPing() { - if(_client.pingInterval == 0) - return; - uint32_t pi = millis() - _client.lastPing; - if(pi > _client.pingInterval) { - DEBUG_WEBSOCKETS("[WS-Client] sending HB ping\n"); - if(sendPing()) { - _client.lastPing = millis(); - _client.pongReceived = false; - } else { - DEBUG_WEBSOCKETS("[WS-Client] sending HB ping failed\n"); - WebSockets::clientDisconnect(&_client, 1000); - } - } -} - -/** - * enable ping/pong heartbeat process - * @param pingInterval uint32_t how often ping will be sent - * @param pongTimeout uint32_t millis after which pong should timout if not received - * @param disconnectTimeoutCount uint8_t how many timeouts before disconnect, 0=> do not disconnect - */ -void WebSocketsClient::enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount) { - WebSockets::enableHeartbeat(&_client, pingInterval, pongTimeout, disconnectTimeoutCount); -} - -/** - * disable ping/pong heartbeat process - */ -void WebSocketsClient::disableHeartbeat() { - _client.pingInterval = 0; -} - -#endif \ No newline at end of file diff --git a/otfpi/WebSocketsClient.h b/otfpi/WebSocketsClient.h deleted file mode 100644 index 7b48e8780..000000000 --- a/otfpi/WebSocketsClient.h +++ /dev/null @@ -1,175 +0,0 @@ -#if defined(OSPI) -/** - * @file WebSocketsClient.h - * @date 20.05.2015 - * @author Markus Sattler - * - * Copyright (c) 2015 Markus Sattler. All rights reserved. - * This file is part of the WebSockets for Arduino. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * 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 WEBSOCKETSCLIENT_H_ -#define WEBSOCKETSCLIENT_H_ - -#include "WebSockets.h" -#include "wscompat.h" - -class WebSocketsClient : protected WebSockets { - public: -#ifdef __AVR__ - typedef void (*WebSocketClientEvent)(WStype_t type, uint8_t * payload, size_t length); -#else - typedef std::function WebSocketClientEvent; -#endif - - WebSocketsClient(void); - virtual ~WebSocketsClient(void); - - void begin(const char * host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); - void begin(String host, uint16_t port, String url = "/", String protocol = "arduino"); - //void begin(IPAddress host, uint16_t port, const char * url = "/", const char * protocol = "arduino"); - -#if defined(HAS_SSL) -#ifdef SSL_AXTLS - void beginSSL(const char * host, uint16_t port, const char * url = "/", const char * fingerprint = "", const char * protocol = "arduino"); - void beginSSL(String host, uint16_t port, String url = "/", String fingerprint = "", String protocol = "arduino"); -#else - void beginSSL(const char * host, uint16_t port, const char * url = "/", const uint8_t * fingerprint = NULL, const char * protocol = "arduino"); - void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", BearSSL::X509List * CA_cert = NULL, const char * protocol = "arduino"); - void setSSLClientCertKey(BearSSL::X509List * clientCert = NULL, BearSSL::PrivateKey * clientPrivateKey = NULL); - void setSSLClientCertKey(const char * clientCert = NULL, const char * clientPrivateKey = NULL); -#endif - void beginSslWithCA(const char * host, uint16_t port, const char * url = "/", const char * CA_cert = NULL, const char * protocol = "arduino"); -#endif - -/* - void beginSocketIO(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); - void beginSocketIO(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); - -#if defined(HAS_SSL) - void beginSocketIOSSL(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * protocol = "arduino"); - void beginSocketIOSSL(String host, uint16_t port, String url = "/socket.io/?EIO=3", String protocol = "arduino"); - - void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", const char * CA_cert = NULL, const char * protocol = "arduino"); -#if defined(SSL_BARESSL) - void beginSocketIOSSLWithCA(const char * host, uint16_t port, const char * url = "/socket.io/?EIO=3", BearSSL::X509List * CA_cert = NULL, const char * protocol = "arduino"); -#endif -#endif -*/ -#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) - void loop(void); -#else - // Async interface not need a loop call - void loop(void) __attribute__((deprecated)) {} -#endif - - void onEvent(WebSocketClientEvent cbEvent); - - bool sendTXT(const char* payload, size_t length, bool fin, bool headerToPayload); - bool sendTXT(uint8_t * payload, size_t length = 0, bool headerToPayload = false); - bool sendTXT(const uint8_t * payload, size_t length = 0); - bool sendTXT(char * payload, size_t length = 0, bool headerToPayload = false); - bool sendTXT(const char * payload, size_t length = 0); - bool sendTXT(String & payload); - bool sendTXT(char payload); - - bool sendBIN(uint8_t * payload, size_t length, bool headerToPayload = false); - bool sendBIN(const uint8_t * payload, size_t length); - - bool sendPing(uint8_t * payload = NULL, size_t length = 0); - bool sendPing(String & payload); - - void disconnect(void); - - void setAuthorization(const char * user, const char * password); - void setAuthorization(const char * auth); - - void setExtraHeaders(const char * extraHeaders = NULL); - - void setReconnectInterval(unsigned long time); - - void enableHeartbeat(uint32_t pingInterval, uint32_t pongTimeout, uint8_t disconnectTimeoutCount); - void disableHeartbeat(); - - bool isConnected(void); - - protected: - String _host; - uint16_t _port; - bool firstFrag = false; - -#if defined(HAS_SSL) -#ifdef SSL_AXTLS - String _fingerprint; - const char * _CA_cert; -#define SSL_FINGERPRINT_IS_SET (_fingerprint.length()) -#define SSL_FINGERPRINT_NULL "" -#else - const uint8_t * _fingerprint; - BearSSL::X509List * _CA_cert; - BearSSL::X509List * _client_cert; - BearSSL::PrivateKey * _client_key; -#define SSL_FINGERPRINT_IS_SET (_fingerprint != NULL) -#define SSL_FINGERPRINT_NULL NULL -#endif - -#endif - WSclient_t _client; - - WebSocketClientEvent _cbEvent; - - unsigned long _lastConnectionFail; - unsigned long _reconnectInterval; - unsigned long _lastHeaderSent; - - void messageReceived(WSclient_t * client, WSopcode_t opcode, uint8_t * payload, size_t length, bool fin); - - void clientDisconnect(WSclient_t * client); - bool clientIsConnected(WSclient_t * client); - -#if(WEBSOCKETS_NETWORK_TYPE != NETWORK_ESP8266_ASYNC) - void handleClientData(void); -#endif - - void sendHeader(WSclient_t * client); - void handleHeader(WSclient_t * client, String * headerLine); - - void connectedCb(); - void connectFailedCb(); - - void handleHBPing(); // send ping in specified intervals - -#if(WEBSOCKETS_NETWORK_TYPE == NETWORK_ESP8266_ASYNC) - void asyncConnect(); -#endif - - /** - * called for sending a Event to the app - * @param type WStype_t - * @param payload uint8_t * - * @param length size_t - */ - virtual void runCbEvent(WStype_t type, uint8_t * payload, size_t length) { - if(_cbEvent) { - _cbEvent(type, payload, length); - } - } -}; - -#endif /* WEBSOCKETSCLIENT_H_ */ -#endif \ No newline at end of file diff --git a/otfpi/WebSocketsVersion.h b/otfpi/WebSocketsVersion.h deleted file mode 100644 index 46ff46495..000000000 --- a/otfpi/WebSocketsVersion.h +++ /dev/null @@ -1,38 +0,0 @@ -#if defined(OSPI) -/** - * @file WebSocketsVersion.h - * @date 01.05.2023 - * @author Markus Sattler - * - * Copyright (c) 2015 Markus Sattler. All rights reserved. - * This file is part of the WebSockets for Arduino. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * 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 WEBSOCKETSVERSION_H_ -#define WEBSOCKETSVERSION_H_ - -#define WEBSOCKETS_VERSION "2.4.1" - -#define WEBSOCKETS_VERSION_MAJOR 2 -#define WEBSOCKETS_VERSION_MINOR 4 -#define WEBSOCKETS_VERSION_PATCH 1 - -#define WEBSOCKETS_VERSION_INT 2004001 - -#endif /* WEBSOCKETSVERSION_H_ */ -#endif \ No newline at end of file diff --git a/otfpi/libb64/AUTHORS b/otfpi/libb64/AUTHORS deleted file mode 100644 index af6873756..000000000 --- a/otfpi/libb64/AUTHORS +++ /dev/null @@ -1,7 +0,0 @@ -libb64: Base64 Encoding/Decoding Routines -====================================== - -Authors: -------- - -Chris Venter chris.venter@gmail.com http://rocketpod.blogspot.com diff --git a/otfpi/libb64/LICENSE b/otfpi/libb64/LICENSE deleted file mode 100644 index a6b56069e..000000000 --- a/otfpi/libb64/LICENSE +++ /dev/null @@ -1,29 +0,0 @@ -Copyright-Only Dedication (based on United States law) -or Public Domain Certification - -The person or persons who have associated work with this document (the -"Dedicator" or "Certifier") hereby either (a) certifies that, to the best of -his knowledge, the work of authorship identified is in the public domain of the -country from which the work is published, or (b) hereby dedicates whatever -copyright the dedicators holds in the work of authorship identified below (the -"Work") to the public domain. A certifier, moreover, dedicates any copyright -interest he may have in the associated work, and for these purposes, is -described as a "dedicator" below. - -A certifier has taken reasonable steps to verify the copyright status of this -work. Certifier recognizes that his good faith efforts may not shield him from -liability if in fact the work certified is not in the public domain. - -Dedicator makes this dedication for the benefit of the public at large and to -the detriment of the Dedicator's heirs and successors. Dedicator intends this -dedication to be an overt act of relinquishment in perpetuity of all present -and future rights under copyright law, whether vested or contingent, in the -Work. Dedicator understands that such relinquishment of all rights includes -the relinquishment of all rights to enforce (by lawsuit or otherwise) those -copyrights in the Work. - -Dedicator recognizes that, once placed in the public domain, the Work may be -freely reproduced, distributed, transmitted, used, modified, built upon, or -otherwise exploited by anyone for any purpose, commercial or non-commercial, -and in any way, including by methods that have not yet been invented or -conceived. \ No newline at end of file diff --git a/otfpi/libb64/cdecode.c b/otfpi/libb64/cdecode.c deleted file mode 100644 index 615068ac2..000000000 --- a/otfpi/libb64/cdecode.c +++ /dev/null @@ -1,98 +0,0 @@ -/* -cdecoder.c - c source to a base64 decoding algorithm implementation - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#ifdef ESP8266 -#include -#endif - -#if defined(ESP32) || defined(ARDUINO_ARCH_RP2040) -#define CORE_HAS_LIBB64 -#endif - -#ifndef CORE_HAS_LIBB64 -#include "cdecode_inc.h" - -int base64_decode_value(char value_in) -{ - static const char decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; - static const char decoding_size = sizeof(decoding); - value_in -= 43; - if (value_in < 0 || value_in > decoding_size) return -1; - return decoding[(int)value_in]; -} - -void base64_init_decodestate(base64_decodestate* state_in) -{ - state_in->step = step_a; - state_in->plainchar = 0; -} - -int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in) -{ - const char* codechar = code_in; - char* plainchar = plaintext_out; - char fragment; - - *plainchar = state_in->plainchar; - - switch (state_in->step) - { - while (1) - { - case step_a: - do { - if (codechar == code_in+length_in) - { - state_in->step = step_a; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while (fragment < 0); - *plainchar = (fragment & 0x03f) << 2; - case step_b: - do { - if (codechar == code_in+length_in) - { - state_in->step = step_b; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while (fragment < 0); - *plainchar++ |= (fragment & 0x030) >> 4; - *plainchar = (fragment & 0x00f) << 4; - case step_c: - do { - if (codechar == code_in+length_in) - { - state_in->step = step_c; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while (fragment < 0); - *plainchar++ |= (fragment & 0x03c) >> 2; - *plainchar = (fragment & 0x003) << 6; - case step_d: - do { - if (codechar == code_in+length_in) - { - state_in->step = step_d; - state_in->plainchar = *plainchar; - return plainchar - plaintext_out; - } - fragment = (char)base64_decode_value(*codechar++); - } while (fragment < 0); - *plainchar++ |= (fragment & 0x03f); - } - } - /* control should not reach here */ - return plainchar - plaintext_out; -} - -#endif diff --git a/otfpi/libb64/cdecode_inc.h b/otfpi/libb64/cdecode_inc.h deleted file mode 100644 index d0d7f489d..000000000 --- a/otfpi/libb64/cdecode_inc.h +++ /dev/null @@ -1,28 +0,0 @@ -/* -cdecode.h - c header for a base64 decoding algorithm - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#ifndef BASE64_CDECODE_H -#define BASE64_CDECODE_H - -typedef enum -{ - step_a, step_b, step_c, step_d -} base64_decodestep; - -typedef struct -{ - base64_decodestep step; - char plainchar; -} base64_decodestate; - -void base64_init_decodestate(base64_decodestate* state_in); - -int base64_decode_value(char value_in); - -int base64_decode_block(const char* code_in, const int length_in, char* plaintext_out, base64_decodestate* state_in); - -#endif /* BASE64_CDECODE_H */ diff --git a/otfpi/libb64/cencode.c b/otfpi/libb64/cencode.c deleted file mode 100644 index cdc0f67bc..000000000 --- a/otfpi/libb64/cencode.c +++ /dev/null @@ -1,119 +0,0 @@ -/* -cencoder.c - c source to a base64 encoding algorithm implementation - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#ifdef ESP8266 -#include -#endif - -#if defined(ESP32) || defined(ARDUINO_ARCH_RP2040) -#define CORE_HAS_LIBB64 -#endif - -#ifndef CORE_HAS_LIBB64 -#include "cencode_inc.h" - -const int CHARS_PER_LINE = 72; - -void base64_init_encodestate(base64_encodestate* state_in) -{ - state_in->step = step_A; - state_in->result = 0; - state_in->stepcount = 0; -} - -char base64_encode_value(char value_in) -{ - static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - if (value_in > 63) return '='; - return encoding[(int)value_in]; -} - -int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in) -{ - const char* plainchar = plaintext_in; - const char* const plaintextend = plaintext_in + length_in; - char* codechar = code_out; - char result; - char fragment; - - result = state_in->result; - - switch (state_in->step) - { - while (1) - { - case step_A: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_A; - return codechar - code_out; - } - fragment = *plainchar++; - result = (fragment & 0x0fc) >> 2; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x003) << 4; - case step_B: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_B; - return codechar - code_out; - } - fragment = *plainchar++; - result |= (fragment & 0x0f0) >> 4; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x00f) << 2; - case step_C: - if (plainchar == plaintextend) - { - state_in->result = result; - state_in->step = step_C; - return codechar - code_out; - } - fragment = *plainchar++; - result |= (fragment & 0x0c0) >> 6; - *codechar++ = base64_encode_value(result); - result = (fragment & 0x03f) >> 0; - *codechar++ = base64_encode_value(result); - - ++(state_in->stepcount); - if (state_in->stepcount == CHARS_PER_LINE/4) - { - *codechar++ = '\n'; - state_in->stepcount = 0; - } - } - } - /* control should not reach here */ - return codechar - code_out; -} - -int base64_encode_blockend(char* code_out, base64_encodestate* state_in) -{ - char* codechar = code_out; - - switch (state_in->step) - { - case step_B: - *codechar++ = base64_encode_value(state_in->result); - *codechar++ = '='; - *codechar++ = '='; - break; - case step_C: - *codechar++ = base64_encode_value(state_in->result); - *codechar++ = '='; - break; - case step_A: - break; - } - *codechar++ = 0x00; - - return codechar - code_out; -} - -#endif diff --git a/otfpi/libb64/cencode_inc.h b/otfpi/libb64/cencode_inc.h deleted file mode 100644 index c1e3464af..000000000 --- a/otfpi/libb64/cencode_inc.h +++ /dev/null @@ -1,31 +0,0 @@ -/* -cencode.h - c header for a base64 encoding algorithm - -This is part of the libb64 project, and has been placed in the public domain. -For details, see http://sourceforge.net/projects/libb64 -*/ - -#ifndef BASE64_CENCODE_H -#define BASE64_CENCODE_H - -typedef enum -{ - step_A, step_B, step_C -} base64_encodestep; - -typedef struct -{ - base64_encodestep step; - char result; - int stepcount; -} base64_encodestate; - -void base64_init_encodestate(base64_encodestate* state_in); - -char base64_encode_value(char value_in); - -int base64_encode_block(const char* plaintext_in, int length_in, char* code_out, base64_encodestate* state_in); - -int base64_encode_blockend(char* code_out, base64_encodestate* state_in); - -#endif /* BASE64_CENCODE_H */ diff --git a/otfpi/libsha1/libsha1.c b/otfpi/libsha1/libsha1.c deleted file mode 100644 index fcf01c531..000000000 --- a/otfpi/libsha1/libsha1.c +++ /dev/null @@ -1,202 +0,0 @@ -/* from valgrind tests */ - -/* ================ sha1.c ================ */ -/* -SHA-1 in C -By Steve Reid -100% Public Domain - -Test Vectors (from FIPS PUB 180-1) -"abc" - A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D -"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" - 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 -A million repetitions of "a" - 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F -*/ - -/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ -/* #define SHA1HANDSOFF * Copies data before messing with it. */ - -#if !defined(ESP8266) && !defined(ESP32) - -#define SHA1HANDSOFF - -#include -#include -#include - -#include "libsha1.h" - - -#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) - -/* blk0() and blk() perform the initial expand. */ -/* I got the idea of expanding during the round function from SSLeay */ -#if BYTE_ORDER == LITTLE_ENDIAN -#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ - |(rol(block->l[i],8)&0x00FF00FF)) -#elif BYTE_ORDER == BIG_ENDIAN -#define blk0(i) block->l[i] -#else -#error "Endianness not defined!" -#endif -#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ - ^block->l[(i+2)&15]^block->l[i&15],1)) - -/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ -#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); -#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); -#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); -#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); -#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); - - -/* Hash a single 512-bit block. This is the core of the algorithm. */ - -void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]) -{ - uint32_t a, b, c, d, e; - typedef union { - unsigned char c[64]; - uint32_t l[16]; - } CHAR64LONG16; -#ifdef SHA1HANDSOFF - CHAR64LONG16 block[1]; /* use array to appear as a pointer */ - memcpy(block, buffer, 64); -#else - /* The following had better never be used because it causes the - * pointer-to-const buffer to be cast into a pointer to non-const. - * And the result is written through. I threw a "const" in, hoping - * this will cause a diagnostic. - */ - CHAR64LONG16* block = (const CHAR64LONG16*)buffer; -#endif - /* Copy context->state[] to working vars */ - a = state[0]; - b = state[1]; - c = state[2]; - d = state[3]; - e = state[4]; - /* 4 rounds of 20 operations each. Loop unrolled. */ - R0(a,b,c,d,e, 0); R0(e,a,b,c,d, 1); R0(d,e,a,b,c, 2); R0(c,d,e,a,b, 3); - R0(b,c,d,e,a, 4); R0(a,b,c,d,e, 5); R0(e,a,b,c,d, 6); R0(d,e,a,b,c, 7); - R0(c,d,e,a,b, 8); R0(b,c,d,e,a, 9); R0(a,b,c,d,e,10); R0(e,a,b,c,d,11); - R0(d,e,a,b,c,12); R0(c,d,e,a,b,13); R0(b,c,d,e,a,14); R0(a,b,c,d,e,15); - R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); - R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); - R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); - R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); - R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); - R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); - R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); - R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); - R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); - R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); - R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); - R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); - R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); - R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); - R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); - R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); - /* Add the working vars back into context.state[] */ - state[0] += a; - state[1] += b; - state[2] += c; - state[3] += d; - state[4] += e; - /* Wipe variables */ - a = b = c = d = e = 0; -#ifdef SHA1HANDSOFF - memset(block, '\0', sizeof(block)); -#endif -} - - -/* SHA1Init - Initialize new context */ - -void SHA1Init(SHA1_CTX* context) -{ - /* SHA1 initialization constants */ - context->state[0] = 0x67452301; - context->state[1] = 0xEFCDAB89; - context->state[2] = 0x98BADCFE; - context->state[3] = 0x10325476; - context->state[4] = 0xC3D2E1F0; - context->count[0] = context->count[1] = 0; -} - - -/* Run your data through this. */ - -void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len) -{ - uint32_t i, j; - - j = context->count[0]; - if ((context->count[0] += len << 3) < j) - context->count[1]++; - context->count[1] += (len>>29); - j = (j >> 3) & 63; - if ((j + len) > 63) { - memcpy(&context->buffer[j], data, (i = 64-j)); - SHA1Transform(context->state, context->buffer); - for ( ; i + 63 < len; i += 64) { - SHA1Transform(context->state, &data[i]); - } - j = 0; - } - else i = 0; - memcpy(&context->buffer[j], &data[i], len - i); -} - - -/* Add padding and return the message digest. */ - -void SHA1Final(unsigned char digest[20], SHA1_CTX* context) -{ - unsigned i; - unsigned char finalcount[8]; - unsigned char c; - -#if 0 /* untested "improvement" by DHR */ - /* Convert context->count to a sequence of bytes - * in finalcount. Second element first, but - * big-endian order within element. - * But we do it all backwards. - */ - unsigned char *fcp = &finalcount[8]; - - for (i = 0; i < 2; i++) - { - uint32_t t = context->count[i]; - int j; - - for (j = 0; j < 4; t >>= 8, j++) - *--fcp = (unsigned char) t; - } -#else - for (i = 0; i < 8; i++) { - finalcount[i] = (unsigned char)((context->count[(i >= 4 ? 0 : 1)] - >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ - } -#endif - c = 0200; - SHA1Update(context, &c, 1); - while ((context->count[0] & 504) != 448) { - c = 0000; - SHA1Update(context, &c, 1); - } - SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ - for (i = 0; i < 20; i++) { - digest[i] = (unsigned char) - ((context->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); - } - /* Wipe variables */ - memset(context, '\0', sizeof(*context)); - memset(&finalcount, '\0', sizeof(finalcount)); -} -/* ================ end of sha1.c ================ */ - - -#endif diff --git a/otfpi/libsha1/libsha1.h b/otfpi/libsha1/libsha1.h deleted file mode 100644 index 40afa61c0..000000000 --- a/otfpi/libsha1/libsha1.h +++ /dev/null @@ -1,21 +0,0 @@ -/* ================ sha1.h ================ */ -/* -SHA-1 in C -By Steve Reid -100% Public Domain -*/ - -#if !defined(ESP8266) && !defined(ESP32) - -typedef struct { - uint32_t state[5]; - uint32_t count[2]; - unsigned char buffer[64]; -} SHA1_CTX; - -void SHA1Transform(uint32_t state[5], const unsigned char buffer[64]); -void SHA1Init(SHA1_CTX* context); -void SHA1Update(SHA1_CTX* context, const unsigned char* data, uint32_t len); -void SHA1Final(unsigned char digest[20], SHA1_CTX* context); - -#endif diff --git a/otfpi/wscompat.cpp b/otfpi/wscompat.cpp deleted file mode 100644 index c10d31ad0..000000000 --- a/otfpi/wscompat.cpp +++ /dev/null @@ -1,118 +0,0 @@ -#if defined(OSPI) -#include "wscompat.h" -#include - -std::string& rtrim(std::string& s, const char* t) { - s.erase(s.find_last_not_of(t)+1); - return s; -} - -std::string* rtrim(std::string *s, const char* t) { - s->erase(s->find_last_not_of(t)+1); - return s; -} - -// trim from beginning of string (left) -std::string& ltrim(std::string& s, const char* t) { - s.erase(0, s.find_first_not_of(t)); - return s; -} - -std::string* ltrim(std::string *s, const char* t) { - s->erase(0, s->find_first_not_of(t)); - return s; -} - -// trim from both ends of string (right then left) -std::string& trim(std::string& s, const char* t) { - return ltrim(rtrim(s, t), t); -} - -std::string* trim(std::string *s, const char* t) { - return ltrim(rtrim(s, t), t); -} - -std::string& remove(std::string& s, int index, int count) { - s.erase(index, count); - return s; -} - -std::string* remove(std::string *s, int index, int count) { - s->erase(index, count); - return s; -} - -int indexOf(const std::string& s, const std::string& item, int from) { - return s.find(item, from); -} -int indexOf(const std::string *s, const string& item, int from) { - return s->find(item, from); -} -int indexOf(const std::string& s, char item, int from) { - return s.find(item, from); -} -int indexOf(const std::string *s, char item, int from) { - return s->find(item, from); -} - -std::string substring(const std::string& s, int start) { - return s.substr(start); -} - -std::string substring(const std::string *s, int start) { - return s->substr(start); -} - -std::string substring(const std::string& s, int start, int end) { - return s.substr(start, end-start); -} - -std::string substring(const std::string *s, int start, int end) { - return s->substr(start, end-start); -} - -uint random(uint max) { - return random() % max; -} - -bool startsWith(const std::string& s, const std::string& test, int offset) { - int len = s.length(); - int len2 = test.length(); - if (len < len2) return 0; - if (offset > len - len2) return 0; - return strncmp( s.c_str() + offset, test.c_str(), len2 ) == 0; -} - -bool startsWith(const std::string *s, const std::string& test, int offset) { - int len = s->length(); - int len2 = test.length(); - if (len < len2) return 0; - if (offset > len - len2) return 0; - return strncmp( s->c_str() + offset, test.c_str(), len2 ) == 0; -} - -bool equalsIgnoreCase(const std::string& s1, const std::string& s2) { - if (&s1 == &s2) return 1; - if (s1.length() != s2.length()) return 0; - if (s1.length() == 0) return 1; - const char *p1 = s1.c_str(); - const char *p2 = s2.c_str(); - while (*p1) { - if (tolower(*p1++) != tolower(*p2++)) return 0; - } - return 1; -} - -bool equalsIgnoreCase(const std::string *s1, const std::string *s2) { - if (s1 == s2) return 1; - if (s1->length() != s2->length()) return 0; - if (s1->length() == 0) return 1; - const char *p1 = s1->c_str(); - const char *p2 = s2->c_str(); - while (*p1) { - if (tolower(*p1++) != tolower(*p2++)) return 0; - } - return 1; - -} -#endif \ No newline at end of file diff --git a/otfpi/wscompat.h b/otfpi/wscompat.h deleted file mode 100644 index 76b3f0626..000000000 --- a/otfpi/wscompat.h +++ /dev/null @@ -1,46 +0,0 @@ -#if defined(OSPI) -#ifndef _WS_COMPAT_H_ -#define _WS_COMPAT_H_ - -#include - -/** - * Websocket Compatibility helper functions -*/ - -#define WSTRIM_DEFAULT " \t\n\r\f\v" - -std::string& rtrim(std::string& s, const char* t = WSTRIM_DEFAULT); -std::string* rtrim(std::string *s, const char* t = WSTRIM_DEFAULT); - -// trim from beginning of string (left) -std::string& ltrim(std::string& s, const char* t = WSTRIM_DEFAULT); -std::string* ltrim(std::string *s, const char* t = WSTRIM_DEFAULT); - -// trim from both ends of string (right then left) -std::string& trim(std::string& s, const char* t = WSTRIM_DEFAULT); -std::string* trim(std::string *s, const char* t = WSTRIM_DEFAULT); - -std::string& remove(std::string& s, int index, int count); -std::string* remove(std::string *s, int index, int count); - -int indexOf(const std::string& s, const string& item, int from = 0); -int indexOf(const std::string *s, const string& item, int from = 0); -int indexOf(const std::string& s, const char item, int from = 0); -int indexOf(const std::string *s, const char item, int from = 0); - -std::string substring(const std::string& s, int start); -std::string substring(const std::string *s, int start); -std::string substring(const std::string& s, int start, int end); -std::string substring(const std::string *s, int start, int end); - -uint random(uint max); - -bool startsWith(const std::string& s, const std::string& test, int offset = 0); -bool startsWith(const std::string *s, const std::string& test, int offset = 0); - -bool equalsIgnoreCase(const std::string& s1, const std::string& s2); -bool equalsIgnoreCase(const std::string *s1, const std::string *s2); - -#endif -#endif diff --git a/sensors.cpp b/sensors.cpp index 0c17ec4de..2adbb58af 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -40,7 +40,7 @@ #include "sensor_ospi_pcf8591.h" #endif -byte findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const char *key, +unsigned char findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const char *key, bool key_in_pgm = false, uint8_t *keyfound = NULL); // All sensors: @@ -48,7 +48,7 @@ static Sensor_t *sensors = NULL; static time_t last_save_time = 0; // Boards: -static byte asb_detected_boards = 0; // bit 1=0x48+0x49 bit 2=0x4A+0x4B +static unsigned char asb_detected_boards = 0; // bit 1=0x48+0x49 bit 2=0x4A+0x4B // Sensor URLS: static SensorUrl_t *sensorUrls = NULL; @@ -87,7 +87,7 @@ double current_humidity = 0.0; double current_precip = 0.0; double current_wind = 0.0; -uint16_t CRC16(byte buf[], int len) { +uint16_t CRC16(unsigned char buf[], int len) { uint16_t crc = 0xFFFF; for (int pos = 0; pos < len; pos++) { @@ -154,7 +154,7 @@ void detect_asb_board() { } } -byte get_asb_detected_boards() { return asb_detected_boards; } +unsigned char get_asb_detected_boards() { return asb_detected_boards; } /* * init sensor api and load data */ @@ -804,7 +804,7 @@ void push_message(Sensor_t *sensor) { os.sopt_load(SOPT_DEVICE_NAME, topic); strncat_P(topic, PSTR("/analogsensor/"), sizeof(topic) - 1); strncat(topic, sensor->name, sizeof(topic) - 1); - sprintf_P(payload, + snprintf_P(payload, TMP_BUFFER_SIZE, PSTR("{\"nr\":%u,\"type\":%u,\"data_ok\":%u,\"time\":%u," "\"value\":%d.%02d,\"unit\":\"%s\"}"), sensor->nr, sensor->type, sensor->flags.data_ok, @@ -822,7 +822,7 @@ void push_message(Sensor_t *sensor) { strcat_P(postval, PSTR("], ")); strcat_P(postval, PSTR("analogsensor ")); - sprintf_P(postval + strlen(postval), + snprintf_P(postval + strlen(postval), TMP_BUFFER_SIZE - strlen(postval), PSTR("nr: %u, type: %u, data_ok: %u, time: %u, value: %d.%02d, " "unit: %s"), sensor->nr, sensor->type, sensor->flags.data_ok, @@ -1032,13 +1032,13 @@ bool extract(char *s, char *buf, int maxlen) { int read_sensor_http(Sensor_t *sensor, ulong time) { #if defined(ESP8266) IPAddress _ip(sensor->ip); - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else - byte ip[4]; - ip[3] = (byte)((sensor->ip >> 24) & 0xFF); - ip[2] = (byte)((sensor->ip >> 16) & 0xFF); - ip[1] = (byte)((sensor->ip >> 8) & 0xFF); - ip[0] = (byte)((sensor->ip & 0xFF)); + unsigned char ip[4]; + ip[3] = (unsigned char)((sensor->ip >> 24) & 0xFF); + ip[2] = (unsigned char)((sensor->ip >> 16) & 0xFF); + ip[1] = (unsigned char)((sensor->ip >> 8) & 0xFF); + ip[0] = (unsigned char)((sensor->ip & 0xFF)); #endif DEBUG_PRINTLN(F("read_sensor_http")); @@ -1215,26 +1215,18 @@ int read_sensor_ip(Sensor_t *sensor) { #if defined(ARDUINO) IPAddress _ip(sensor->ip); - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else - byte ip[4]; - ip[3] = (byte)((sensor->ip >> 24) & 0xFF); - ip[2] = (byte)((sensor->ip >> 16) & 0xFF); - ip[1] = (byte)((sensor->ip >> 8) & 0xFF); - ip[0] = (byte)((sensor->ip & 0xFF)); + unsigned char ip[4]; + ip[3] = (unsigned char)((sensor->ip >> 24) & 0xFF); + ip[2] = (unsigned char)((sensor->ip >> 16) & 0xFF); + ip[1] = (unsigned char)((sensor->ip >> 8) & 0xFF); + ip[0] = (unsigned char)((sensor->ip & 0xFF)); #endif char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); -#if defined(ESP8266) client->setTimeout(200); if (!client->connect(server, sensor->port)) { -#else - struct hostent *host = gethostbyname(server); - if (!host) { - return HTTP_RQT_CONNECT_ERR; - } - if (!client->connect((uint8_t *)host->h_addr, sensor->port)) { -#endif DEBUG_PRINT(server); DEBUG_PRINT(":"); DEBUG_PRINT(sensor->port); @@ -1573,26 +1565,18 @@ int set_sensor_address_ip(Sensor_t *sensor, uint8_t new_address) { #endif #if defined(ESP8266) IPAddress _ip(sensor->ip); - byte ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; #else - byte ip[4]; - ip[3] = (byte)((sensor->ip >> 24) & 0xFF); - ip[2] = (byte)((sensor->ip >> 16) & 0xFF); - ip[1] = (byte)((sensor->ip >> 8) & 0xFF); - ip[0] = (byte)((sensor->ip & 0xFF)); + unsigned char ip[4]; + ip[3] = (unsigned char)((sensor->ip >> 24) & 0xFF); + ip[2] = (unsigned char)((sensor->ip >> 16) & 0xFF); + ip[1] = (unsigned char)((sensor->ip >> 8) & 0xFF); + ip[0] = (unsigned char)((sensor->ip & 0xFF)); #endif char server[20]; sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); -#if defined(ESP8266) client->setTimeout(200); if (!client->connect(server, sensor->port)) { -#else - struct hostent *host = gethostbyname(server); - if (!host) { - return HTTP_RQT_CONNECT_ERR; - } - if (!client->connect((uint8_t *)host->h_addr, sensor->port)) { -#endif DEBUG_PRINT(F("Cannot connect to ")); DEBUG_PRINT(server); DEBUG_PRINT(":"); @@ -2028,7 +2012,7 @@ boolean sensor_isgroup(Sensor_t *sensor) { } } -byte getSensorUnitId(int type) { +unsigned char getSensorUnitId(int type) { switch (type) { case SENSOR_SMT100_MOIS: return UNIT_PERCENT; @@ -2092,7 +2076,7 @@ byte getSensorUnitId(int type) { } } -byte getSensorUnitId(Sensor_t *sensor) { +unsigned char getSensorUnitId(Sensor_t *sensor) { if (!sensor) return 0; switch (sensor->type) { diff --git a/sensors.h b/sensors.h index 0f9c01ff7..7e61a93b5 100644 --- a/sensors.h +++ b/sensors.h @@ -166,12 +166,12 @@ typedef struct Sensor { int16_t offset2; // offset unit value 1/100 - for custom sensor (after): // sensorvalue = (read_value-offset_mv/1000) * factor / // divider + offset2/100 - byte assigned_unitid; // unitid for userdef and mqtt sensors - byte undef[15]; // for later + unsigned char assigned_unitid; // unitid for userdef and mqtt sensors + unsigned char undef[15]; // for later // unstored: bool mqtt_init : 1; bool mqtt_push : 1; - byte unitid; + unsigned char unitid; uint32_t repeat_read; double repeat_data; uint64_t repeat_native; @@ -216,7 +216,7 @@ typedef struct ProgSensorAdjust { double factor2; double min; double max; - byte undef[32]; // for later + unsigned char undef[32]; // for later ProgSensorAdjust *next; } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE \ @@ -263,19 +263,19 @@ typedef struct SensorUrl { #define RS485_TRUEBNER4_ADDR 0x3B void sensor_api_init(); -byte get_asb_detected_boards(); +unsigned char get_asb_detected_boards(); Sensor_t *getSensors(); const char *getSensorUnit(int unitid); const char *getSensorUnit(Sensor_t *sensor); -byte getSensorUnitId(int type); -byte getSensorUnitId(Sensor_t *sensor); +unsigned char getSensorUnitId(int type); +unsigned char getSensorUnitId(Sensor_t *sensor); extern char ether_buffer[]; extern char tmp_buffer[]; // Utils: -uint16_t CRC16(byte buf[], int len); +uint16_t CRC16(unsigned char buf[], int len); // Sensor API functions: int sensor_delete(uint nr); From 3a2eb734b8e964dd28620ab584b2cea5f63f0861 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 18 Aug 2024 02:43:23 +0200 Subject: [PATCH 184/281] Renamed internal i2c to rs485 sensor name --- sensors.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 2adbb58af..d69f7afc4 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1112,8 +1112,8 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { * @param sensor * @return int */ -int read_sensor_i2c(Sensor_t *sensor) { - DEBUG_PRINTLN(F("read_sensor_i2c")); +int read_sensor_rs485(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_rs485")); int device = sensor->port % 4; if ((asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) return HTTP_RQT_NOT_RECEIVED; @@ -1128,7 +1128,7 @@ int read_sensor_i2c(Sensor_t *sensor) { return HTTP_RQT_NOT_RECEIVED; } - DEBUG_PRINTLN(F("read_sensor_i2c: check-ok")); + DEBUG_PRINTLN(F("read_sensor_rs485: check-ok")); uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 : sensor->type == SENSOR_SMT100_MOIS ? 0x01 @@ -1139,7 +1139,7 @@ int read_sensor_i2c(Sensor_t *sensor) { Wire.write((uint8_t)sensor->id); Wire.write(type); if (Wire.endTransmission() == 0) { - DEBUG_PRINTF("read_sensor_i2c: request send: %d - %d\n", sensor->id, + DEBUG_PRINTF("read_sensor_rs485: request send: %d - %d\n", sensor->id, type); sensor->repeat_read = 1; i2c_rs485_allocated[device] = sensor->nr; @@ -1156,7 +1156,7 @@ int read_sensor_i2c(Sensor_t *sensor) { uint8_t high_byte = Wire.read(); if (addr == sensor->id && reg == type) { uint16_t data = (high_byte << 8) | low_byte; - DEBUG_PRINTF("read_sensor_i2c: result: %d - %d (%d %d)\n", sensor->id, + DEBUG_PRINTF("read_sensor_rs485: result: %d - %d (%d %d)\n", sensor->id, data, low_byte, high_byte); double value = sensor->type == SENSOR_SMT100_TEMP @@ -1179,9 +1179,9 @@ int read_sensor_i2c(Sensor_t *sensor) { sensor->repeat_read = 0; sensor->flags.data_ok = false; i2c_rs485_allocated[device] = 0; - DEBUG_PRINTLN(F("read_sensor_i2c: timeout")); + DEBUG_PRINTLN(F("read_sensor_rs485: timeout")); } - DEBUG_PRINTLN(F("read_sensor_i2c: exit")); + DEBUG_PRINTLN(F("read_sensor_rs485: exit")); return HTTP_RQT_NOT_RECEIVED; } @@ -1400,7 +1400,7 @@ int read_sensor(Sensor_t *sensor, ulong time) { if (sensor->ip) return read_sensor_ip(sensor); #ifdef ESP8266 if (sensor->port == 0) // 0 = Truebner RS485 Adapter - return read_sensor_i2c(sensor); + return read_sensor_rs485(sensor); #endif break; From 908f6975ef32dfefb1becf5ca7dce49161c3800d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 18 Aug 2024 15:01:46 +0200 Subject: [PATCH 185/281] Fixt remote sensor reading --- build.sh | 6 ++++++ sensors.cpp | 2 +- true | 0 3 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 true diff --git a/build.sh b/build.sh index a245d0268..7a8ee51c7 100755 --- a/build.sh +++ b/build.sh @@ -15,6 +15,12 @@ while getopts ":s:d" opt; do ;; esac done + +if [ ! "$SILENT" <> true ] ;then + echo "This version has a new configuration data structure. Please backup configuration and restore after update!" + read -p "Press ctrl-c to stop now or enter to continue" +fi + echo "Building OpenSprinkler..." #Git update submodules diff --git a/sensors.cpp b/sensors.cpp index d69f7afc4..b2569525e 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1044,7 +1044,7 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { DEBUG_PRINTLN(F("read_sensor_http")); char *p = tmp_buffer; - BufferFiller bf = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE); bf.emit_p(PSTR("GET /sg?pw=$O&nr=$D"), SOPT_PASSWORD, sensor->id); bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0], ip[1], ip[2], diff --git a/true b/true new file mode 100644 index 000000000..e69de29bb From 3970b24b62ffc975925f95a7113b32e223eb5eec Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 18 Aug 2024 15:13:08 +0200 Subject: [PATCH 186/281] Fixt OSPI ADS1115 detection --- build2.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build2.sh b/build2.sh index fa09eb672..889682662 100755 --- a/build2.sh +++ b/build2.sh @@ -33,7 +33,7 @@ echo "Compiling firmware..." 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++17 -include string.h main.cpp \ + g++ -o OpenSprinkler -DOSPI $USEGPIO $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp \ mqtt.cpp smtp.c sensor*.cpp \ $ADS1115FILES $PCF8591FILES \ From ed7d985b7197d34cff1551aa7ba05bae34d9c231 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 18 Aug 2024 15:53:19 +0200 Subject: [PATCH 187/281] fixt new mqtt topic assignment, using mqtt publish options --- sensors.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index b2569525e..4b90ac589 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -801,8 +801,7 @@ void push_message(Sensor_t *sensor) { if (os.mqtt.enabled()) { DEBUG_PRINTLN("push mqtt1"); - os.sopt_load(SOPT_DEVICE_NAME, topic); - strncat_P(topic, PSTR("/analogsensor/"), sizeof(topic) - 1); + strncpy_P(topic, PSTR("analogsensor/"), sizeof(topic) - 1); strncat(topic, sensor->name, sizeof(topic) - 1); snprintf_P(payload, TMP_BUFFER_SIZE, PSTR("{\"nr\":%u,\"type\":%u,\"data_ok\":%u,\"time\":%u," From 7b54cb8625aa5e241b50dad09ab0f6373039eb3c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 20 Aug 2024 00:18:22 +0200 Subject: [PATCH 188/281] Added SOPTS converting --- OpenSprinkler.cpp | 18 +++++++++++++++++- utils.cpp | 2 +- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 0d6d3e751..b9ff78f63 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2346,8 +2346,24 @@ void OpenSprinkler::parse_otc_config() { /** Setup function for options */ void OpenSprinkler::options_setup() { + //Convert old SOPTS + ulong fsize = file_size(SOPTS_FILENAME); + if (fsize >= 160 * 9 && fsize <= 160 * 13) { + DEBUG_PRINTLN("Converting SOPTS..."); + #define SOPTS_TMP "sopts.dat.sav" + rename_file(SOPTS_FILENAME, SOPTS_TMP); + for (ulong pos = 0; pos < NUM_SOPTS; pos++) { + char buffer[MAX_SOPTS_SIZE]; + memset(buffer, 0, sizeof(buffer)); + file_read_block(SOPTS_TMP, buffer, pos*160, 160); + file_write_block(SOPTS_FILENAME, buffer, pos * MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); + } + remove_file(SOPTS_TMP); + DEBUG_PRINTLN("Finished converting SOPTS!"); + } + // Check reset conditions: - if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)!=OS_FW_VERSION || // fw major version has changed + if (//file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)!=OS_FW_VERSION || // fw major version has changed !file_exists(DONE_FILENAME)) { // done file doesn't exist factory_reset(); diff --git a/utils.cpp b/utils.cpp index df54766a9..5837ec05d 100644 --- a/utils.cpp +++ b/utils.cpp @@ -289,7 +289,7 @@ ulong file_read_block(const char *fn, void *dst, ulong pos, ulong len) { File f = LittleFS.open(fn, "r"); if(f) { f.seek(pos, SeekSet); - f.read((unsigned char*)dst, len); + result = f.read((unsigned char*)dst, len); f.close(); } From a1cb3c6e40e42d7ac138319e7f936e14f5169cf0 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 20 Aug 2024 00:18:42 +0200 Subject: [PATCH 189/281] Optimized some debug output --- EMailSender.cpp | 2 +- EMailSenderKey.h | 6 +++--- main.cpp | 23 ++++++++++++++++------- sensors.cpp | 10 +++++----- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/EMailSender.cpp b/EMailSender.cpp index 22b87894a..1d728af27 100644 --- a/EMailSender.cpp +++ b/EMailSender.cpp @@ -432,7 +432,7 @@ EMailSender::Response EMailSender::send(const char* to, EMailMessage &email, Att EMAIL_DEBUG_PRINT(F("ONLY ONE RECIPIENT")); const char* arrEmail[] = {to}; - return send(arrEmail, 1, email, attachments); + return send(arrEmail, 1, 0, 0, email, attachments); } EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, EMailMessage &email, Attachments attachments) { diff --git a/EMailSenderKey.h b/EMailSenderKey.h index 75694b50a..4446b78f4 100644 --- a/EMailSenderKey.h +++ b/EMailSenderKey.h @@ -41,7 +41,7 @@ //#define ENABLE_ATTACHMENTS // Uncomment to enable printing out nice debug messages. -//#define EMAIL_SENDER_DEBUG +#define EMAIL_SENDER_DEBUG // Define where debug output will be printed. #define DEBUG_PRINTER Serial @@ -79,7 +79,7 @@ // #define FORCE_DISABLE_SSL // If you want add a wrapper to emulate SSL over Client like EthernetClient -// #define SSLCLIENT_WRAPPER +//#define SSLCLIENT_WRAPPER // esp8266 microcontrollers configuration #ifndef DEFAULT_EMAIL_NETWORK_TYPE_ESP8266 @@ -136,7 +136,7 @@ * For enc28j60 use EthernetENC available from library manager or * https://github.com/jandrassy/EthernetENC */ - #define ANALOG_PIN A7 + #define ANALOG_PIN -1 #include #include "trust_anchors.h" diff --git a/main.cpp b/main.cpp index a01681e4c..1b4d2c133 100644 --- a/main.cpp +++ b/main.cpp @@ -1438,7 +1438,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, postval); // Test the parsing otherwise parse if (error) { - DEBUG_PRINT(F("mqtt: deserializeJson() failed: ")); + DEBUG_PRINT(F("email: deserializeJson() failed: ")); DEBUG_PRINTLN(error.c_str()); } else { email_en = doc["en"]; @@ -1502,8 +1502,17 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { } } - // todo: add IFTTT and email support for this event as well. - // currently no support due to the number of events exceeds 8 so need to use more than 1 byte + if (ifttt_enabled || email_enabled) { + strcat_P(postval, PSTR("Station [")); + os.get_station_name(lval, postval+strlen(postval)); + strcat_P(postval, PSTR("] open now.")); + + if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); + } + if(email_enabled) { email_message.subject += PSTR("station event"); } + } break; case NOTIFY_STATION_OFF: @@ -1687,10 +1696,10 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { emailSend.setSMTPServer(email_host); // TODO: double check removing strdup emailSend.setSMTPPort(email_port); EMailSender::Response resp = emailSend.send(email_recipient, email_message); - // DEBUG_PRINTLN(F("Sending Status:")); - // DEBUG_PRINTLN(resp.status); - // DEBUG_PRINTLN(resp.code); - // DEBUG_PRINTLN(resp.desc); + DEBUG_PRINTLN(F("Sending Status:")); + DEBUG_PRINTLN(resp.status); + DEBUG_PRINTLN(resp.code); + DEBUG_PRINTLN(resp.desc); } #endif #else diff --git a/sensors.cpp b/sensors.cpp index 4b90ac589..89decc8d8 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -900,7 +900,7 @@ void read_all_sensors(boolean online) { * Read ESP8296 ADS1115 sensors */ int read_sensor_adc(Sensor_t *sensor, ulong time) { - DEBUG_PRINTLN(F("read_sensor_adc")); + //DEBUG_PRINTLN(F("read_sensor_adc")); if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; if (sensor->id >= 16) return HTTP_RQT_NOT_RECEIVED; // Init + Detect: @@ -1393,8 +1393,8 @@ int read_sensor(Sensor_t *sensor, ulong time) { case SENSOR_SMT100_MOIS: case SENSOR_SMT100_TEMP: case SENSOR_SMT100_PMTY: - DEBUG_PRINT(F("Reading sensor ")); - DEBUG_PRINTLN(sensor->name); + //DEBUG_PRINT(F("Reading sensor ")); + //DEBUG_PRINTLN(sensor->name); sensor->last_read = time; if (sensor->ip) return read_sensor_ip(sensor); #ifdef ESP8266 @@ -1415,8 +1415,8 @@ int read_sensor(Sensor_t *sensor, ulong time) { case SENSOR_THERM200: case SENSOR_AQUAPLUMB: case SENSOR_USERDEF: - DEBUG_PRINT(F("Reading sensor ")); - DEBUG_PRINTLN(sensor->name); + //DEBUG_PRINT(F("Reading sensor ")); + //DEBUG_PRINTLN(sensor->name); return read_sensor_adc(sensor, time); #endif #else From ecc9f38b67a1d312adcd77f5d5128c8b1ed8b94d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 21 Aug 2024 23:58:08 +0200 Subject: [PATCH 190/281] Fixt memoryleak and email send --- EMailSender.cpp | 3 ++- EMailSenderKey.h | 2 +- OpenSprinkler.cpp | 6 +++--- defines.h | 2 +- main.cpp | 39 ++++++++++++++++++--------------------- main.h | 3 ++- platformio.ini | 3 +-- sensors.cpp | 42 ++++++++++++++++++++++++++++++++++++++++++ sensors.h | 2 ++ 9 files changed, 72 insertions(+), 30 deletions(-) diff --git a/EMailSender.cpp b/EMailSender.cpp index 1d728af27..3b0843265 100644 --- a/EMailSender.cpp +++ b/EMailSender.cpp @@ -629,7 +629,8 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s // String auth = "AUTH PLAIN "+String(encode64(logPass)); EMAIL_DEBUG_PRINTLN(auth); client.println(auth); - } + free(logPass); + } #if defined(ESP32) else if (this->isCramMD5Login == true) { EMAIL_DEBUG_PRINTLN(F("AUTH CRAM-MD5")); diff --git a/EMailSenderKey.h b/EMailSenderKey.h index 4446b78f4..14209adf5 100644 --- a/EMailSenderKey.h +++ b/EMailSenderKey.h @@ -41,7 +41,7 @@ //#define ENABLE_ATTACHMENTS // Uncomment to enable printing out nice debug messages. -#define EMAIL_SENDER_DEBUG +//#define EMAIL_SENDER_DEBUG // Define where debug output will be printed. #define DEBUG_PRINTER Serial diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index b9ff78f63..c370bd5ec 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2006,9 +2006,9 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* 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) { + 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); diff --git a/defines.h b/defines.h index c8c31b14a..286481abc 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; diff --git a/main.cpp b/main.cpp index 1b4d2c133..599fcf320 100644 --- a/main.cpp +++ b/main.cpp @@ -1692,6 +1692,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { #if defined(ARDUINO) #if defined(ESP8266) if(email_host && email_username && email_password && email_recipient) { // make sure all are valid + free_tmp_memory(); EMailSender emailSend(email_username, email_password); emailSend.setSMTPServer(email_host); // TODO: double check removing strdup emailSend.setSMTPPort(email_port); @@ -1700,6 +1701,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { DEBUG_PRINTLN(resp.status); DEBUG_PRINTLN(resp.code); DEBUG_PRINTLN(resp.desc); + restore_tmp_memory(); } #endif #else @@ -2135,32 +2137,27 @@ static void check_network() { #endif } -/** +void free_tmp_memory() { #if defined(ESP8266) + DEBUG_PRINT(F("freememory start: ")); + DEBUG_PRINTLN(freeMemory()); -#define NET_ENC28J60_EIR 0x1C -#define NET_ENC28J60_ESTAT 0x1D -#define NET_ENC28J60_ECON1 0x1F -#define NET_ENC28J60_EIR_RXERIF 0x01 -#define NET_ENC28J60_ESTAT_BUFFER 0x40 -#define NET_ENC28J60_ECON1_RXEN 0x04 + sensor_save_all(); + sensor_api_free(); -bool check_enc28j60() { - - uint8_t stateEconRxen = eth.readreg((uint8_t) NET_ENC28J60_ECON1) & NET_ENC28J60_ECON1_RXEN; - // ESTAT.BUFFER rised on TX or RX error - // I think the test of this register is not necessary - EIR.RXERIF state checking may be enough - uint8_t stateEstatBuffer = eth.readreg((uint8_t) NET_ENC28J60_ESTAT) & NET_ENC28J60_ESTAT_BUFFER; - // EIR.RXERIF set on RX error - uint8_t stateEirRxerif = eth.readreg((uint8_t) NET_ENC28J60_EIR) & NET_ENC28J60_EIR_RXERIF; - if (!stateEconRxen || (stateEstatBuffer && stateEirRxerif)) { - DEBUG_PRINTLN(F("ENC28J60 FAILED - REBOOT!")) - return false; - } - return true; + DEBUG_PRINT(F("freememory now: ")); + DEBUG_PRINTLN(freeMemory()); +#endif } + +void restore_tmp_memory() { +#if defined(ESP8266) + sensor_api_init(); + + DEBUG_PRINT(F("freememory restore: ")); + DEBUG_PRINTLN(freeMemory()); #endif -*/ +} /** Perform NTP sync */ static void perform_ntp_sync() { diff --git a/main.h b/main.h index b49793b0a..4d2219abb 100644 --- a/main.h +++ b/main.h @@ -35,5 +35,6 @@ void reset_all_stations_immediate(); void delete_log(char *name); void write_log(unsigned char type, time_os_t curr_time); void make_logfile_name(char *name); - +void free_tmp_memory(); +void restore_tmp_memory(); #endif // _MAIN_H diff --git a/platformio.ini b/platformio.ini index 0a9da97ff..20f1cd6cf 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,8 +22,7 @@ lib_deps = sui77/rc-switch @ ^2.6.3 https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 - https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library/archive/refs/heads/master.zip - https://github.com/Links2004/arduinoWebSockets/archive/refs/tags/2.3.5.zip + https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library RobTillaart/ADS1X15 https://github.com/bluemurder/esp8266-ping ; ignore html2raw.cpp source file for firmware compilation (external helper program) diff --git a/sensors.cpp b/sensors.cpp index 89decc8d8..152d9ac56 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -46,6 +46,7 @@ unsigned char findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const c // All sensors: static Sensor_t *sensors = NULL; static time_t last_save_time = 0; +static boolean apiInit = false; // Boards: static unsigned char asb_detected_boards = 0; // bit 1=0x48+0x49 bit 2=0x4A+0x4B @@ -159,12 +160,50 @@ unsigned char get_asb_detected_boards() { return asb_detected_boards; } * init sensor api and load data */ void sensor_api_init() { + apiInit = true; detect_asb_board(); sensor_load(); prog_adjust_load(); sensor_mqtt_init(); } +void sensor_save_all() { + sensor_save(); + prog_adjust_save(); + SensorUrl_save(); +} + +/** + * @brief Unload sensorapi from memory, free everything. Be sure that you have save all before + * + */ +void sensor_api_free() { + apiInit = false; + + os.mqtt.setCallback(NULL); + + while (progSensorAdjusts) { + ProgSensorAdjust_t* next = progSensorAdjusts->next; + free(progSensorAdjusts); + progSensorAdjusts = next; + } + + while (sensorUrls) { + SensorUrl_t* next = sensorUrls->next; + free(sensorUrls); + sensorUrls = next; + } + + while (sensors) { + Sensor_t* next = sensors->next; + free(sensors); + sensors = next; + } + + modbusTcpId = 0; + memset(i2c_rs485_allocated, 0, sizeof(i2c_rs485_allocated)); +} + /* * get list of all configured sensors */ @@ -332,6 +371,7 @@ void sensor_load() { * */ void sensor_save() { + if (!apiInit) return; DEBUG_PRINTLN(F("sensor_save")); if (file_exists(SENSOR_FILENAME_BAK)) remove_file(SENSOR_FILENAME_BAK); if (file_exists(SENSOR_FILENAME)) @@ -1898,6 +1938,7 @@ int prog_adjust_delete(uint nr) { } void prog_adjust_save() { + if (!apiInit) return; if (file_exists(PROG_SENSOR_FILENAME)) remove_file(PROG_SENSOR_FILENAME); ulong pos = 0; @@ -2292,6 +2333,7 @@ void SensorUrl_load() { } void SensorUrl_save() { + if (!apiInit) return; if (file_exists(SENSORURL_FILENAME)) remove_file(SENSORURL_FILENAME); ulong pos = 0; diff --git a/sensors.h b/sensors.h index 7e61a93b5..bb5888b94 100644 --- a/sensors.h +++ b/sensors.h @@ -264,6 +264,8 @@ typedef struct SensorUrl { void sensor_api_init(); unsigned char get_asb_detected_boards(); +void sensor_save_all(); +void sensor_api_free(); Sensor_t *getSensors(); const char *getSensorUnit(int unitid); From d0f800c4f2e2ac154c3bcda1138be5841d66683b Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 23 Aug 2024 00:03:31 +0200 Subject: [PATCH 191/281] Added "Free Memory" and "Free Storage" diagnostic sensors --- EMailSender.cpp | 7 ++++--- OpenSprinkler.cpp | 8 ++++---- defines.h | 2 +- opensprinkler_server.cpp | 8 ++++++++ sensors.cpp | 21 ++++++++++++++++++++- sensors.h | 4 ++++ 6 files changed, 41 insertions(+), 9 deletions(-) diff --git a/EMailSender.cpp b/EMailSender.cpp index 3b0843265..c4b1520be 100644 --- a/EMailSender.cpp +++ b/EMailSender.cpp @@ -476,16 +476,17 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s #ifndef ARDUINO_ESP8266_RELEASE_2_4_2 if (this->isSecure == false){ client.setInsecure(); + /* bool mfln = client.probeMaxFragmentLength(this->smtp_server, this->smtp_port, 512); EMAIL_DEBUG_PRINT("MFLN supported: "); EMAIL_DEBUG_PRINTLN(mfln?"yes":"no"); - if (mfln) { + if (mfln) {*/ client.setBufferSizes(512, 512); - } else { + /*} else { client.setBufferSizes(2048, 2048); - } + }*/ } #endif #elif (EMAIL_NETWORK_TYPE == NETWORK_ESP32) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index c370bd5ec..c56aba3ab 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2006,13 +2006,13 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* if(usessl) { WiFiClientSecure *_c = new WiFiClientSecure(); _c->setInsecure(); - bool mfln = _c->probeMaxFragmentLength(server, port, 512); + /*bool mfln = _c->probeMaxFragmentLength(server, port, 512); DEBUG_PRINTF("MFLN supported: %s\n", mfln ? "yes" : "no"); - if (mfln) { + if (mfln) {*/ _c->setBufferSizes(512, 512); - } else { + /*} else { _c->setBufferSizes(2048, 2048); - } + }*/ client = _c; } else { client = new WiFiClient(); diff --git a/defines.h b/defines.h index 286481abc..c8c31b14a 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 79142b4e6..e5e33d723 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2967,6 +2967,10 @@ static const int sensor_types[] = { SENSOR_GROUP_MAX, SENSOR_GROUP_AVG, SENSOR_GROUP_SUM, +#if defined(ESP8266) + SENSOR_FREE_MEMORY, + SENSOR_FREE_STORE, +#endif }; static const char* sensor_names[] = { @@ -3006,6 +3010,10 @@ static const char* sensor_names[] = { "Sensor group with max value", "Sensor group with avg value", "Sensor group with sum value", +#if defined(ESP8266) + "Free Memory", + "Free Storage", +#endif }; /** diff --git a/sensors.cpp b/sensors.cpp index 152d9ac56..a67b6b8d1 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1458,6 +1458,21 @@ int read_sensor(Sensor_t *sensor, ulong time) { //DEBUG_PRINT(F("Reading sensor ")); //DEBUG_PRINTLN(sensor->name); return read_sensor_adc(sensor, time); + case SENSOR_FREE_MEMORY: + sensor->last_data = freeMemory(); + sensor->last_read = time; + sensor->flags.data_ok = true; + return HTTP_RQT_SUCCESS; + + case SENSOR_FREE_STORE: { + struct FSInfo fsinfo; + boolean ok = LittleFS.info(fsinfo); + if (ok) + sensor->last_data = fsinfo.totalBytes-fsinfo.usedBytes; + sensor->flags.data_ok = ok; + sensor->last_read = time; + return HTTP_RQT_SUCCESS; + } #endif #else #if defined ADS1115 | PCF8591 @@ -2082,6 +2097,8 @@ unsigned char getSensorUnitId(int type) { case SENSOR_AQUAPLUMB: return UNIT_PERCENT; case SENSOR_USERDEF: + case SENSOR_FREE_MEMORY: + case SENSOR_FREE_STORE: return UNIT_USERDEF; #endif #else @@ -2148,6 +2165,8 @@ unsigned char getSensorUnitId(Sensor_t *sensor) { case SENSOR_AQUAPLUMB: return UNIT_PERCENT; case SENSOR_USERDEF: + case SENSOR_FREE_MEMORY: + case SENSOR_FREE_STORE: return UNIT_USERDEF; #endif #else @@ -2196,7 +2215,7 @@ unsigned char getSensorUnitId(Sensor_t *sensor) { sen = sen->next; } } - + default: return UNIT_NONE; } diff --git a/sensors.h b/sensors.h index bb5888b94..095c5126b 100644 --- a/sensors.h +++ b/sensors.h @@ -122,6 +122,10 @@ extern "C" { #define SENSOR_GROUP_AVG 1002 // Sensor group with avg value #define SENSOR_GROUP_SUM 1003 // Sensor group with sum value +//Diagnostic +#define SENSOR_FREE_MEMORY 10000 //Free memory +#define SENSOR_FREE_STORE 10001 //Free storage + #define SENSOR_READ_TIMEOUT 3000 // ms #define MIN_DISK_FREE 8192 // 8Kb min From 328cc4238890216031420d8e578259787b0d24b7 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 25 Aug 2024 22:44:29 +0200 Subject: [PATCH 192/281] Free Memory Sensor - save only changed values --- defines.h | 2 +- main.cpp | 13 ++----------- sensors.cpp | 18 ++++++++++++++---- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/defines.h b/defines.h index c8c31b14a..286481abc 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; diff --git a/main.cpp b/main.cpp index 599fcf320..a6b641516 100644 --- a/main.cpp +++ b/main.cpp @@ -1502,17 +1502,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { } } - if (ifttt_enabled || email_enabled) { - strcat_P(postval, PSTR("Station [")); - os.get_station_name(lval, postval+strlen(postval)); - strcat_P(postval, PSTR("] open now.")); - - if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - size_t len = strlen(postval); - snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); - } - if(email_enabled) { email_message.subject += PSTR("station event"); } - } + // todo: add IFTTT and email support for this event as well. + // currently no support due to the number of events exceeds 8 so need to use more than 1 byte break; case NOTIFY_STATION_OFF: diff --git a/sensors.cpp b/sensors.cpp index a67b6b8d1..45f8f0f18 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1459,16 +1459,26 @@ int read_sensor(Sensor_t *sensor, ulong time) { //DEBUG_PRINTLN(sensor->name); return read_sensor_adc(sensor, time); case SENSOR_FREE_MEMORY: - sensor->last_data = freeMemory(); + { + uint32_t fm = freeMemory(); + if (sensor->last_native_data == fm) + return HTTP_RQT_NOT_RECEIVED; + sensor->last_native_data = fm; + sensor->last_data = fm; sensor->last_read = time; sensor->flags.data_ok = true; return HTTP_RQT_SUCCESS; - + } case SENSOR_FREE_STORE: { struct FSInfo fsinfo; boolean ok = LittleFS.info(fsinfo); - if (ok) - sensor->last_data = fsinfo.totalBytes-fsinfo.usedBytes; + if (ok) { + uint32_t fd = fsinfo.totalBytes-fsinfo.usedBytes; + if (sensor->last_native_data == fd) + return HTTP_RQT_NOT_RECEIVED; + sensor->last_native_data = fd; + sensor->last_data = fd; + } sensor->flags.data_ok = ok; sensor->last_read = time; return HTTP_RQT_SUCCESS; From 6712b2e776249b580fe53cbbfd0dbb707525e2a7 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 29 Aug 2024 00:01:50 +0200 Subject: [PATCH 193/281] api-reinit without sensor detection to avoid blocking --- main.cpp | 38 +++++++++++++++++++++++++++++--------- sensors.cpp | 11 ++++++++--- sensors.h | 2 +- 3 files changed, 38 insertions(+), 13 deletions(-) diff --git a/main.cpp b/main.cpp index a6b641516..ff47c127c 100644 --- a/main.cpp +++ b/main.cpp @@ -365,7 +365,7 @@ void do_setup() { os.button_timeout = LCD_BACKLIGHT_TIMEOUT; - sensor_api_init(); + sensor_api_init(true); } // Arduino software reset function @@ -414,7 +414,7 @@ void do_setup() { os.mqtt.init(); os.status.req_mqtt_restart = true; - sensor_api_init(); + sensor_api_init(true); initalize_otf(); } @@ -1397,7 +1397,8 @@ void manual_start_program(unsigned char pid, unsigned char uwt) { // ====== PUSH NOTIFICATION FUNCTIONS ======= // ========================================== 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]); + int len = strlen(str); + snprintf_P(str+len, str_len-len, PSTR("%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); } #define PUSH_TOPIC_LEN 120 @@ -1524,12 +1525,12 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { }else{ strcat_P(postval, PSTR("] closed. It ran for ")); size_t len = strlen(postval); - snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR(" %d minutes %d seconds."), (int)fval/60, (int)fval%60); + snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR(" %d minutes %d seconds."), (int)fval/60, (int)fval%60); } if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { size_t len = strlen(postval); - snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); + snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); } if(email_enabled) { email_message.subject += PSTR("station event"); } } @@ -1547,7 +1548,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { if(lval=0) { size_t len = strlen(postval); - snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR("water level updated: %d%%."), (int)fval); + snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR("water level updated: %d%%."), (int)fval); } if(email_enabled) { email_message.subject += PSTR("weather update event"); } } @@ -1653,6 +1654,25 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { #else ip2string(postval, TMP_BUFFER_SIZE, &(Ethernet.localIP()[0])); #endif + + //Adding restart reasons: + struct rst_info *rtc_info = system_get_rst_info(); + if (rtc_info) { + int len = strlen(postval); + snprintf_P(postval+len, TMP_BUFFER_SIZE-len, PSTR("
reset reason: %x"), rtc_info->reason); + if (rtc_info->reason == REASON_WDT_RST || + rtc_info->reason == REASON_EXCEPTION_RST || + rtc_info->reason == REASON_SOFT_WDT_RST) { + if (rtc_info->reason == REASON_EXCEPTION_RST) { + len = strlen(postval); + snprintf_P(postval+len, TMP_BUFFER_SIZE-len, PSTR("
Fatal exception: %d"), rtc_info->exccause); + } + len = strlen(postval); + snprintf_P(postval+len, TMP_BUFFER_SIZE-len, PSTR("
epc1=0x%08x, epc2=0x%08x, epc3=0x%08x, excvaddr=0x%08x, depc=0x%08x"), + rtc_info->epc1, rtc_info->epc2, rtc_info->epc3, rtc_info->excvaddr, rtc_info->depc); + } + } + #else strcat_P(postval, PSTR("controller process restarted.")); #endif @@ -2143,7 +2163,7 @@ void free_tmp_memory() { void restore_tmp_memory() { #if defined(ESP8266) - sensor_api_init(); + sensor_api_init(false); DEBUG_PRINT(F("freememory restore: ")); DEBUG_PRINTLN(freeMemory()); diff --git a/sensors.cpp b/sensors.cpp index 45f8f0f18..907db32d7 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -159,9 +159,10 @@ unsigned char get_asb_detected_boards() { return asb_detected_boards; } /* * init sensor api and load data */ -void sensor_api_init() { +void sensor_api_init(boolean detect_boards) { apiInit = true; - detect_asb_board(); + if (detect_boards) + detect_asb_board(); sensor_load(); prog_adjust_load(); sensor_mqtt_init(); @@ -854,7 +855,11 @@ void push_message(Sensor_t *sensor) { os.mqtt.publish(topic, payload); DEBUG_PRINTLN("push mqtt2"); } - if (os.iopts[IOPT_NOTIF_ENABLE]) { + + //ifttt is enabled, when the ifttt key is present! + os.sopt_load(SOPT_IFTTT_KEY, tmp_buffer); + bool ifttt_enabled = strlen(tmp_buffer)!=0; + if (ifttt_enabled) { DEBUG_PRINTLN("push ifttt"); strcpy_P(postval, PSTR("{\"value1\":\"On site [")); os.sopt_load(SOPT_DEVICE_NAME, postval + strlen(postval)); diff --git a/sensors.h b/sensors.h index 095c5126b..1b3cd1d5b 100644 --- a/sensors.h +++ b/sensors.h @@ -266,7 +266,7 @@ typedef struct SensorUrl { #define RS485_TRUEBNER3_ADDR 0x3A #define RS485_TRUEBNER4_ADDR 0x3B -void sensor_api_init(); +void sensor_api_init(boolean detect_boards); unsigned char get_asb_detected_boards(); void sensor_save_all(); void sensor_api_free(); From 0a7c2cb1de1f5adba2c1220f4687a08bdc49d3d8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 30 Aug 2024 23:53:48 +0200 Subject: [PATCH 194/281] Fixt reboot after sendmail --- sensors.cpp | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 907db32d7..40d9b9bff 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -47,6 +47,7 @@ unsigned char findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const c static Sensor_t *sensors = NULL; static time_t last_save_time = 0; static boolean apiInit = false; +static Sensor_t *current_sensor = NULL; // Boards: static unsigned char asb_detected_boards = 0; // bit 1=0x48+0x49 bit 2=0x4A+0x4B @@ -166,12 +167,14 @@ void sensor_api_init(boolean detect_boards) { sensor_load(); prog_adjust_load(); sensor_mqtt_init(); + current_sensor = NULL; } void sensor_save_all() { sensor_save(); prog_adjust_save(); SensorUrl_save(); + current_sensor = NULL; } /** @@ -902,36 +905,35 @@ void read_all_sensors(boolean online) { return; // wait 30s before first sensor read // When we run out of time, skip some sensors and continue on next loop - static Sensor_t *sensor = NULL; - if (sensor == NULL) sensor = sensors; + if (current_sensor == NULL) current_sensor = sensors; - while (sensor) { - if (time >= sensor->last_read + sensor->read_interval || - sensor->repeat_read) { - if (online || (sensor->ip == 0 && sensor->type != SENSOR_MQTT)) { - int result = read_sensor(sensor, time); + while (current_sensor) { + if (time >= current_sensor->last_read + current_sensor->read_interval || + current_sensor->repeat_read) { + if (online || (current_sensor->ip == 0 && current_sensor->type != SENSOR_MQTT)) { + int result = read_sensor(current_sensor, time); if (result == HTTP_RQT_SUCCESS) { - sensorlog_add(LOG_STD, sensor, time); - push_message(sensor); + sensorlog_add(LOG_STD, current_sensor, time); + push_message(current_sensor); } else if (result == HTTP_RQT_TIMEOUT) { // delay next read on timeout: - sensor->last_read = time + max((uint)60, sensor->read_interval); - sensor->repeat_read = 0; + current_sensor->last_read = time + max((uint)60, current_sensor->read_interval); + current_sensor->repeat_read = 0; DEBUG_PRINTF("Delayed1: %s", sensor->name); } else if (result == HTTP_RQT_CONNECT_ERR) { // delay next read on error: - sensor->last_read = time + max((uint)60, sensor->read_interval); - sensor->repeat_read = 0; - DEBUG_PRINTF("Delayed2: %s", sensor->name); + current_sensor->last_read = time + max((uint)60, current_sensor->read_interval); + current_sensor->repeat_read = 0; + DEBUG_PRINTF("Delayed2: %s", current_sensor->name); } ulong passed = os.now_tz() - time; if (passed > MAX_SENSOR_READ_TIME) { - sensor = sensor->next; + current_sensor = current_sensor->next; break; } } } - sensor = sensor->next; + current_sensor = current_sensor->next; } sensor_update_groups(); calc_sensorlogs(); From 8adf649aaf0e4e8f812a1fe9390bd14d4de5e5b1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 31 Aug 2024 00:08:05 +0200 Subject: [PATCH 195/281] added OSPi warning about configuration loss --- build.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index 7a8ee51c7..d947f66d2 100755 --- a/build.sh +++ b/build.sh @@ -16,8 +16,11 @@ while getopts ":s:d" opt; do esac done -if [ ! "$SILENT" <> true ] ;then - echo "This version has a new configuration data structure. Please backup configuration and restore after update!" +if [ !"$SILENT" ]; then + echo "This version has a new configuration data structure." + echo "Please backup configuration and restore after update!" + echo "Otherwise your configuration is lost" + echo "however, if this is a new installation, then you can proceed directly" read -p "Press ctrl-c to stop now or enter to continue" fi From b0c491377eea50bb67b2c5c5882f98fdde690f60 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 31 Aug 2024 08:50:37 +0200 Subject: [PATCH 196/281] bugfix: analog sensor delete could crash --- sensors.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 40d9b9bff..e4afd60c1 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -167,14 +167,12 @@ void sensor_api_init(boolean detect_boards) { sensor_load(); prog_adjust_load(); sensor_mqtt_init(); - current_sensor = NULL; } void sensor_save_all() { sensor_save(); prog_adjust_save(); SensorUrl_save(); - current_sensor = NULL; } /** @@ -344,6 +342,7 @@ int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, void sensor_load() { // DEBUG_PRINTLN(F("sensor_load")); sensors = NULL; + current_sensor = NULL; if (!file_exists(SENSOR_FILENAME)) return; ulong pos = 0; @@ -391,6 +390,7 @@ void sensor_save() { last_save_time = os.now_tz(); DEBUG_PRINTLN(F("sensor_save2")); + current_sensor = NULL; } uint sensor_count() { From 55a0ccc60809282fb888303d1ba9cddb95097423 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 1 Sep 2024 23:30:18 +0200 Subject: [PATCH 197/281] Memleak: sending mail fix / https fix --- OpenSprinkler.cpp | 22 ++++------------------ defines.h | 3 +-- main.cpp | 22 ---------------------- main.h | 2 -- opensprinkler_server.cpp | 22 ++++++++++++++++++++++ opensprinkler_server.h | 3 +++ sensors.cpp | 11 ++++++----- 7 files changed, 36 insertions(+), 49 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index c56aba3ab..9d7ac68d0 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2004,6 +2004,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* Client *client = NULL; #if defined(ESP8266) if(usessl) { + free_tmp_memory(); WiFiClientSecure *_c = new WiFiClientSecure(); _c->setInsecure(); /*bool mfln = _c->probeMaxFragmentLength(server, port, 512); @@ -2013,6 +2014,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* /*} else { _c->setBufferSizes(2048, 2048); }*/ + restore_tmp_memory(); client = _c; } else { client = new WiFiClient(); @@ -2346,25 +2348,9 @@ void OpenSprinkler::parse_otc_config() { /** Setup function for options */ void OpenSprinkler::options_setup() { - //Convert old SOPTS - ulong fsize = file_size(SOPTS_FILENAME); - if (fsize >= 160 * 9 && fsize <= 160 * 13) { - DEBUG_PRINTLN("Converting SOPTS..."); - #define SOPTS_TMP "sopts.dat.sav" - rename_file(SOPTS_FILENAME, SOPTS_TMP); - for (ulong pos = 0; pos < NUM_SOPTS; pos++) { - char buffer[MAX_SOPTS_SIZE]; - memset(buffer, 0, sizeof(buffer)); - file_read_block(SOPTS_TMP, buffer, pos*160, 160); - file_write_block(SOPTS_FILENAME, buffer, pos * MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); - } - remove_file(SOPTS_TMP); - DEBUG_PRINTLN("Finished converting SOPTS!"); - } - // Check reset conditions: - if (//file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)!=OS_FW_VERSION || // fw major version has changed - !file_exists(DONE_FILENAME)) { // done file doesn't exist + if (file_read_byte(IOPTS_FILENAME, IOPT_FW_VERSION)!=OS_FW_VERSION || // fw major version has changed + !file_exists(DONE_FILENAME)) { // done file doesn't exist factory_reset(); diff --git a/defines.h b/defines.h index 286481abc..7c6921303 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; @@ -486,7 +486,6 @@ enum { #define DEBUG_PRINT(x) {Serial.print(x);} #define DEBUG_PRINTLN(x) {Serial.println(x);} #define DEBUG_PRINTF(msg, ...) {Serial.printf(msg, ##__VA_ARGS__);} - #define DEBUG_PRINTF(msg, ...) {Serial.printf(msg, ##__VA_ARGS__);} #else #include #define DEBUG_BEGIN(x) {} /** Serial debug functions */ diff --git a/main.cpp b/main.cpp index ff47c127c..7f5039cc8 100644 --- a/main.cpp +++ b/main.cpp @@ -2148,28 +2148,6 @@ static void check_network() { #endif } -void free_tmp_memory() { -#if defined(ESP8266) - DEBUG_PRINT(F("freememory start: ")); - DEBUG_PRINTLN(freeMemory()); - - sensor_save_all(); - sensor_api_free(); - - DEBUG_PRINT(F("freememory now: ")); - DEBUG_PRINTLN(freeMemory()); -#endif -} - -void restore_tmp_memory() { -#if defined(ESP8266) - sensor_api_init(false); - - DEBUG_PRINT(F("freememory restore: ")); - DEBUG_PRINTLN(freeMemory()); -#endif -} - /** Perform NTP sync */ static void perform_ntp_sync() { #if defined(ARDUINO) diff --git a/main.h b/main.h index 4d2219abb..9118a4db1 100644 --- a/main.h +++ b/main.h @@ -35,6 +35,4 @@ void reset_all_stations_immediate(); void delete_log(char *name); void write_log(unsigned char type, time_os_t curr_time); void make_logfile_name(char *name); -void free_tmp_memory(); -void restore_tmp_memory(); #endif // _MAIN_H diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index e5e33d723..ca98daf85 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3016,6 +3016,28 @@ static const char* sensor_names[] = { #endif }; +void free_tmp_memory() { +#if defined(ESP8266) + DEBUG_PRINT(F("freememory start: ")); + DEBUG_PRINTLN(freeMemory()); + + sensor_save_all(); + sensor_api_free(); + + DEBUG_PRINT(F("freememory now: ")); + DEBUG_PRINTLN(freeMemory()); +#endif +} + +void restore_tmp_memory() { +#if defined(ESP8266) + sensor_api_init(false); + + DEBUG_PRINT(F("freememory restore: ")); + DEBUG_PRINTLN(freeMemory()); +#endif +} + /** * sf * List supported sensor types diff --git a/opensprinkler_server.h b/opensprinkler_server.h index f730faa19..e271a6d72 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -112,6 +112,9 @@ class BufferFiller { } }; +void free_tmp_memory(); +void restore_tmp_memory(); + char* urlDecodeAndUnescape(char *buf); #if defined(OTF_ENABLED) void start_otf(); diff --git a/sensors.cpp b/sensors.cpp index 40d9b9bff..b6d83c9c6 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -188,19 +188,20 @@ void sensor_api_free() { while (progSensorAdjusts) { ProgSensorAdjust_t* next = progSensorAdjusts->next; - free(progSensorAdjusts); + delete progSensorAdjusts; progSensorAdjusts = next; } while (sensorUrls) { SensorUrl_t* next = sensorUrls->next; - free(sensorUrls); + free(sensorUrls->urlstr); + delete sensorUrls; sensorUrls = next; } while (sensors) { Sensor_t* next = sensors->next; - free(sensors); + delete sensors; sensors = next; } @@ -892,8 +893,8 @@ void push_message(Sensor_t *sensor) { } void read_all_sensors(boolean online) { - // DEBUG_PRINTLN(F("read_all_sensors")); if (!sensors) return; + //DEBUG_PRINTLN(F("read_all_sensors")); ulong time = os.now_tz(); @@ -919,7 +920,7 @@ void read_all_sensors(boolean online) { // delay next read on timeout: current_sensor->last_read = time + max((uint)60, current_sensor->read_interval); current_sensor->repeat_read = 0; - DEBUG_PRINTF("Delayed1: %s", sensor->name); + DEBUG_PRINTF("Delayed1: %s", current_sensor->name); } else if (result == HTTP_RQT_CONNECT_ERR) { // delay next read on error: current_sensor->last_read = time + max((uint)60, current_sensor->read_interval); From 7dfe99c7e1964d13bead6ddc17dfdb63ebc610d4 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 1 Sep 2024 23:41:35 +0200 Subject: [PATCH 198/281] Disable Debug --- defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defines.h b/defines.h index 7c6921303..eaabd5c2e 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; From c9964b71e812a87a3f12fd420afbb21f0ec04f07 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 3 Sep 2024 23:54:58 +0200 Subject: [PATCH 199/281] Fixt debug output --- external/OpenThings-Framework-Firmware-Library | 2 +- sensors.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/external/OpenThings-Framework-Firmware-Library b/external/OpenThings-Framework-Firmware-Library index 0f74fa747..26ef0e4e8 160000 --- a/external/OpenThings-Framework-Firmware-Library +++ b/external/OpenThings-Framework-Firmware-Library @@ -1 +1 @@ -Subproject commit 0f74fa747ea98451ab6956be9555227d467b568d +Subproject commit 26ef0e4e85febcca27401f8d56f2a5738808af91 diff --git a/sensors.cpp b/sensors.cpp index 77da7811f..4013c67f0 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2311,6 +2311,7 @@ void GetSensorWeather() { if (s && extract(s, buf, sizeof(buf))) { current_wind = atof(buf); } +#ifdef ENABLE_DEBUG char tmp[10]; DEBUG_PRINT("temp: "); dtostrf(current_temp, 2, 2, tmp); @@ -2324,7 +2325,7 @@ void GetSensorWeather() { DEBUG_PRINT("wind: "); dtostrf(current_wind, 2, 2, tmp); DEBUG_PRINTLN(tmp) - +#endif current_weather_ok = true; } else { current_weather_ok = false; From 5c718864d2ae96b625b3935204c9e71094e3bc37 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 12 Sep 2024 22:51:05 +0200 Subject: [PATCH 200/281] Updated OSPi Build script --- build.sh | 16 ++++++++++------ external/OpenThings-Framework-Firmware-Library | 2 +- platformio.ini | 2 +- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/build.sh b/build.sh index d947f66d2..8d6bc84ac 100755 --- a/build.sh +++ b/build.sh @@ -16,12 +16,16 @@ while getopts ":s:d" opt; do esac done -if [ !"$SILENT" ]; then - echo "This version has a new configuration data structure." - echo "Please backup configuration and restore after update!" - echo "Otherwise your configuration is lost" - echo "however, if this is a new installation, then you can proceed directly" - read -p "Press ctrl-c to stop now or enter to continue" +FILENAME="sopts.dat" +if [[ ! "$SILENT" && -f "$FILENAME" ]]; then + FILESIZE=$(stat -c%s "$FILENAME") + if [[ "$FILESIZE" != "4160" ]]; then + echo "This version has a new configuration data structure." + echo "Please backup configuration and restore after update!" + echo "Otherwise your configuration is lost" + echo "however, if this is a new installation, then you can proceed directly" + read -p "Press ctrl-c to stop now or enter to continue" + fi fi echo "Building OpenSprinkler..." diff --git a/external/OpenThings-Framework-Firmware-Library b/external/OpenThings-Framework-Firmware-Library index 26ef0e4e8..13914114a 160000 --- a/external/OpenThings-Framework-Firmware-Library +++ b/external/OpenThings-Framework-Firmware-Library @@ -1 +1 @@ -Subproject commit 26ef0e4e85febcca27401f8d56f2a5738808af91 +Subproject commit 13914114a31f5087a92eabb82b9c173d36e43a89 diff --git a/platformio.ini b/platformio.ini index 20f1cd6cf..ab42774b9 100644 --- a/platformio.ini +++ b/platformio.ini @@ -22,7 +22,7 @@ lib_deps = sui77/rc-switch @ ^2.6.3 https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 - https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library + https://github.com/OpenSprinklerShop/OpenThings-Framework-Firmware-Library RobTillaart/ADS1X15 https://github.com/bluemurder/esp8266-ping ; ignore html2raw.cpp source file for firmware compilation (external helper program) From 38bb77746ff71e082d882446fb28e7c0effb4837 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 12 Sep 2024 23:46:16 +0200 Subject: [PATCH 201/281] Merge remote-tracking branch 'origin/master' into dev/221_1 --- OpenSprinkler.cpp | 96 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 08aa2c5bc..8e8aec056 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -550,17 +550,7 @@ unsigned char OpenSprinkler::start_network() { #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 - - SPI.begin(); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(4000000); - - if(eth.isW5500) { +bool init_W5500(boolean initSPI) { DEBUG_PRINTLN(F("detect existence of W5500")); /* this is copied from w5500.cpp wizchip_sw_reset * perform a software reset and see if we get a correct response @@ -571,7 +561,16 @@ unsigned char OpenSprinkler::start_ether() { static const uint8_t AccessModeRead = (0x00 << 2); static const uint8_t AccessModeWrite = (0x01 << 2); static const uint8_t BlockSelectCReg = (0x00 << 3); + + if (initSPI) { + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(32000000); + } + pinMode(PIN_ETHER_CS, OUTPUT); + // ==> setMR(MR_RST) digitalWrite(PIN_ETHER_CS, LOW); SPI.transfer((0x00 & 0xFF00) >> 8); @@ -588,8 +587,17 @@ unsigned char OpenSprinkler::start_ether() { SPI.transfer(BlockSelectCReg | AccessModeRead); ret = SPI.transfer(0); digitalWrite(PIN_ETHER_CS, HIGH); - if(ret!=0) return 0; // ret is expected to be 0 - } else { + + if(ret!=0) { // ret is expected to be 0 + return false; + } + + eth.isW5500 = true; + DEBUG_PRINTLN(F("found W5500")); + return true; +} + +bool init_ENC28J60() { /* this is copied from enc28j60.cpp geterevid * check to see if the hardware revision number if expected * */ @@ -598,12 +606,22 @@ unsigned char OpenSprinkler::start_ether() { #define EREVID 0x12 #define ECON1 0x1f + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(4000000); + // ==> setregbank(MAADRX_BANK); pinMode(PIN_ETHER_CS, OUTPUT); uint8_t r; digitalWrite(PIN_ETHER_CS, LOW); SPI.transfer(0x00 | (ECON1 & 0x1f)); r = SPI.transfer(0); + DEBUG_PRINT("ECON1=") + DEBUG_PRINTLN(r); + if (r == 2) { + return false; + } digitalWrite(PIN_ETHER_CS, HIGH); digitalWrite(PIN_ETHER_CS, LOW); @@ -615,8 +633,28 @@ unsigned char OpenSprinkler::start_ether() { digitalWrite(PIN_ETHER_CS, LOW); SPI.transfer(0x00 | (EREVID & 0x1f)); r = SPI.transfer(0); + DEBUG_PRINTLN(r); digitalWrite(PIN_ETHER_CS, HIGH); - if(r==0 || r==255) return 0; // r is expected to be a non-255 revision number + if(r==0 || r==255) { // r is expected to be a non-255 revision number + return false; + } + + eth.isW5500 = false; + DEBUG_PRINTLN(F("found ENC28J60")); + return true; +} + +byte OpenSprinkler::start_ether() { +#if defined(ESP8266) + if(hw_rev<2) return 0; // ethernet capability is only available when hw_rev>=2 + + // os 3.2 uses enc28j60 and 3.3 uses w5500 + if (hw_rev==2) { + if (!init_ENC28J60() && !init_W5500(false)) + return 0; + } else { + if (!init_W5500(true)) + return 0; } load_hardware_mac((uint8_t*)tmp_buffer, true); @@ -1153,7 +1191,7 @@ void OpenSprinkler::latch_setallzonepins(unsigned char value) { } } -void OpenSprinkler::latch_disable_alloutputs_v2() { +void OpenSprinkler::latch_disable_alloutputs_v2(unsigned char expvalue) { digitalWriteExt(PIN_LATCH_COMA, LOW); digitalWriteExt(PIN_LATCH_COMK, LOW); @@ -1162,7 +1200,12 @@ void OpenSprinkler::latch_disable_alloutputs_v2() { // latch v2 has a 74hc595 which controls all h-bridge cathode pins drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, 0x00); - // todo: handle expander + // Handle all expansion boards + for(byte i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, expvalue?0xFFFF:0x0000); + } + } } /** Set one zone (for LATCH controller) @@ -1201,8 +1244,15 @@ void OpenSprinkler::latch_setzoneoutput_v2(unsigned char sid, unsigned char A, u drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, K ? (1<>4; + uint16_t s=(sid-8)&0x0F; + if(expanders[bid]->type==IOEXP_TYPE_9555) { + uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(A==HIGH && K==LOW) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + } } } @@ -1212,7 +1262,7 @@ void OpenSprinkler::latch_setzoneoutput_v2(unsigned char sid, unsigned char A, u void OpenSprinkler::latch_open(unsigned char sid) { if(hw_rev>=2) { DEBUG_PRINTLN(F("latch_open_v2")); - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH DEBUG_PRINTLN(F("boost on voltage: ")); DEBUG_PRINTLN(iopts[IOPT_LATCH_ON_VOLTAGE]); latch_boost(iopts[IOPT_LATCH_ON_VOLTAGE]); // generate boost voltage @@ -1221,7 +1271,7 @@ void OpenSprinkler::latch_open(unsigned char sid) { digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path delay(150); digitalWriteExt(PIN_BOOST_EN, LOW); // disabled output boosted voltage path - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH } else { latch_boost(); // boost voltage latch_setallzonepins(HIGH); // set all switches to HIGH, including COM @@ -1237,7 +1287,7 @@ void OpenSprinkler::latch_open(unsigned char sid) { void OpenSprinkler::latch_close(unsigned char sid) { if(hw_rev>=2) { DEBUG_PRINTLN(F("latch_close_v2")); - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(LOW); // disable all output pins; set expanders all to LOW DEBUG_PRINTLN(F("boost off voltage: ")); DEBUG_PRINTLN(iopts[IOPT_LATCH_OFF_VOLTAGE]); latch_boost(iopts[IOPT_LATCH_OFF_VOLTAGE]); // generate boost voltage @@ -1246,7 +1296,7 @@ void OpenSprinkler::latch_close(unsigned char sid) { digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path delay(150); digitalWriteExt(PIN_BOOST_EN, LOW); // disable output boosted voltage path - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH } else { latch_boost(); // boost voltage latch_setallzonepins(LOW); // set all switches to LOW, including COM @@ -1931,6 +1981,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* Client *client = NULL; #if defined(ESP8266) if(usessl) { + free_tmp_memory(); WiFiClientSecure *_c = new WiFiClientSecure(); _c->setInsecure(); bool mfln = _c->probeMaxFragmentLength(server, port, 512); @@ -1940,6 +1991,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* } else { _c->setBufferSizes(2048, 2048); } + restore_tmp_memory(); client = _c; } else { client = new WiFiClient(); From e702c758966349fccc5f3b9919c9666135fcd74a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 13 Sep 2024 00:19:42 +0200 Subject: [PATCH 202/281] Merge remote-tracking branch 'origin/master' into dev/221_1 --- build.sh | 36 +++++++++++------------------------- build2.sh | 11 ++++++----- main.cpp | 8 +++----- 3 files changed, 20 insertions(+), 35 deletions(-) diff --git a/build.sh b/build.sh index 8a32af34b..17d8b8b49 100755 --- a/build.sh +++ b/build.sh @@ -61,37 +61,23 @@ if [ "$1" == "demo" ]; then apt-get install -y libmosquitto-dev libssl-dev echo "Compiling demo firmware..." - 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 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -elif [ "$1" == "osbo" ]; then - echo "Installing required libraries..." - apt-get install -y libmosquitto-dev libssl-dev - echo "Compiling osbo firmware..." - - ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) - otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - g++ -o OpenSprinkler -DOSBO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto + 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 main.cpp \ + OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp \ + mqtt.cpp smtp.c RCSwitch.cpp \ + -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 apt-get install -y libmosquitto-dev libi2c-dev libssl-dev libgpiod-dev gpiod enable_i2c - USEGPIO="" - GPIOLIB="" - - - if [ -h "/sys/class/gpio/gpiochip512" ]; then - echo "using libgpiod" - USEGPIO="-DLIBGPIOD" - GPIOLIB="-lgpiod" - fi - - echo "Compiling ospi firmware..." - 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 main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto $GPIOLIB + ./build2.sh fi if [ -f /etc/init.d/OpenSprinkler.sh ]; then diff --git a/build2.sh b/build2.sh index 889682662..ad4c895bc 100755 --- a/build2.sh +++ b/build2.sh @@ -34,12 +34,13 @@ ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) g++ -o OpenSprinkler -DOSPI $USEGPIO $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ - OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp \ - mqtt.cpp smtp.c sensor*.cpp \ + OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp \ + smtp.c RCSwitch.cpp sensor*.cpp \ $ADS1115FILES $PCF8591FILES \ - -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws \ + -Iexternal/TinyWebsockets/tiny_websockets_lib/include \ + $ws \ -Iexternal/OpenThings-Framework-Firmware-Library/ \ - $otf -li2c -lpthread -lmosquitto -lssl -lcrypto $GPIOLIB - + $otf \ + -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB diff --git a/main.cpp b/main.cpp index b0cd10a8e..60e195667 100644 --- a/main.cpp +++ b/main.cpp @@ -775,7 +775,9 @@ void do_loop() // check through all programs for(pid=0; pid0) { // Check and update weather if weatherdata is older than 30min: if (os.checkwt_success_lasttime && (!os.checkwt_lasttime || os.now_tz() > os.checkwt_lasttime + 30*60)) { os.checkwt_lasttime = 0; @@ -783,11 +785,7 @@ void do_loop() check_weather(); } break; - } - bool will_delete = false; - unsigned char runcount = prog.check_match(curr_time, &will_delete); - if(runcount>0) { // program match found // check and process special program command if(process_special_program_command(prog.name, curr_time)) continue; From a804d72603faee2bd452844e524a5dc54e05eb65 Mon Sep 17 00:00:00 2001 From: root Date: Fri, 13 Sep 2024 00:37:40 +0200 Subject: [PATCH 203/281] fixt urlencode problem with "%20" commands --- mqtt.cpp | 1 - opensprinkler_server.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/mqtt.cpp b/mqtt.cpp index 360e3a21c..5d4d973f3 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -113,7 +113,6 @@ boolean checkPassword(char* pw) { if (os.iopts[IOPT_IGNORE_PASSWORD]) return true; if(findKeyVal(pw, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pw"), true)){ - urlDecode(tmp_buffer); if (os.password_verify(tmp_buffer)) return true; }else{ DEBUG_LOGF("Device password not found.\r\n"); diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index ca98daf85..67dbf0bd5 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -418,7 +418,6 @@ boolean check_password(char *p) p = get_buffer; } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pw"), true)) { - urlDecode(tmp_buffer); if (os.password_verify(tmp_buffer)) return true; } #else @@ -635,9 +634,6 @@ void server_change_stations(OTF_PARAMS_DEF) { handle_return(HTML_DATA_OUTOFBOUND); } } else if ((tmp_buffer[0] == STN_TYPE_HTTP) || (tmp_buffer[0] == STN_TYPE_HTTPS) || (tmp_buffer[0] == STN_TYPE_REMOTE_OTC)) { - #if !defined(ESP8266) // ESP8266 does automatic decoding - urlDecode(tmp_buffer + 1); - #endif if (strlen(tmp_buffer+1) > sizeof(HTTPStationData)) { handle_return(HTML_DATA_OUTOFBOUND); } @@ -728,7 +724,9 @@ void server_change_runonce(OTF_PARAMS_DEF) { char *p = get_buffer; // decode url first + #if !defined(USE_OTF) if(p) urlDecode(p); + #endif // search for the start of t=[ char *pv; boolean found=false; @@ -2030,7 +2028,9 @@ void server_fill_files(OTF_PARAMS_DEF) { */ char* urlDecodeAndUnescape(char *buf) { + #if !defined(USE_OTF) urlDecode(buf); + #endif strReplace(buf, '\"', '\''); strReplace(buf, '\\', '/'); return buf; From d771353048b2bdf2af654d8daa71cdf8148a6cef Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 15 Sep 2024 23:53:35 +0200 Subject: [PATCH 204/281] Added support for influxdb (api ig/is) --- OpenSprinkler.cpp | 1 + OpenSprinkler.h | 3 +- defines.h | 2 +- influxdb.cpp | 128 +++++++++++++++++++++++++++++++++++++++ influxdb.h | 47 ++++++++++++++ opensprinkler_server.cpp | 104 +++++++++++++++++++++++++++++++ platformio.ini | 1 + sensors.cpp | 39 ++++++++++++ sensors.h | 4 ++ 9 files changed, 327 insertions(+), 2 deletions(-) create mode 100644 influxdb.cpp create mode 100644 influxdb.h diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 9d7ac68d0..52fed2973 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -89,6 +89,7 @@ extern ProgramData pd; unsigned char OpenSprinkler::wifi_bssid[6]={0}; unsigned char OpenSprinkler::wifi_channel=255; unsigned char OpenSprinkler::wifi_testmode = 0; + OSInfluxDB OpenSprinkler::influxdb; #elif defined(ARDUINO) LiquidCrystal OpenSprinkler::lcd; extern SdFat sd; diff --git a/OpenSprinkler.h b/OpenSprinkler.h index d57431900..eb7a89f0f 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -51,6 +51,7 @@ #include "SSD1306Display.h" #include "espconnect.h" #include "EMailSender.h" + #include "influxdb.h" #else // for AVR #include #include @@ -216,6 +217,7 @@ class OpenSprinkler { // data members #if defined(ESP8266) static SSD1306Display lcd; // 128x64 OLED display + static OSInfluxDB influxdb; #elif defined(ARDUINO) static LiquidCrystal lcd; // 16x2 character LCD #else @@ -227,7 +229,6 @@ class OpenSprinkler { #endif static OSMqtt mqtt; - static NVConData nvdata; static ConStatus status; static ConStatus old_status; diff --git a/defines.h b/defines.h index eaabd5c2e..7c6921303 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; diff --git a/influxdb.cpp b/influxdb.cpp new file mode 100644 index 000000000..269f499a4 --- /dev/null +++ b/influxdb.cpp @@ -0,0 +1,128 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2024 by Stefan Schmaltz (info@opensprinklershop.de) + * + * OpenSprinkler library header file + * Sep 2024 @ OpenSprinklerShop.de + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#if defined(ESP8266) +#include "influxdb.h" +#include "utils.h" +#include "defines.h" + +extern char tmp_buffer[TMP_BUFFER_SIZE*2]; + +#define INFLUX_CONFIG_FILE "influx.json" + +void OSInfluxDB::set_influx_config(int enabled, char *url, char *org, char *bucket, char *token) { + ArduinoJson::JsonDocument doc; + doc["enabled"] = enabled; + doc["url"] = url; + doc["org"] = org; + doc["bucket"] = bucket; + doc["token"] = token; + set_influx_config(doc); +} + +void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { + size_t size = ArduinoJson::serializeJson(doc, tmp_buffer); + file_write_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, size+1); + if (client) + { + delete client; + client = NULL; + } + enabled = doc["enabled"]; + initialized = true; +} + +void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { + //Load influx config: + memset(tmp_buffer, 0, TMP_BUFFER_SIZE*2); + if (file_exists(INFLUX_CONFIG_FILE)) + file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); + ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); + if (error || doc.isNull() || doc["enabled"] > 1) { + if (error) { + DEBUG_PRINT(F("email: deserializeJson() failed: ")); + DEBUG_PRINTLN(error.c_str()); + } + doc["enabled"] = 0; + doc["url"] = ""; + doc["org"] = ""; + doc["bucket"] = ""; + doc["token"] = ""; + } + enabled = doc["enabled"]; + initialized = true; +} + +void OSInfluxDB::init() { + ArduinoJson::JsonDocument doc; + get_influx_config(doc); + enabled = doc["enabled"]; + initialized = true; +} + +void OSInfluxDB::write_influx_data(Point &sensor_data) { + if (!initialized) init(); + if (!enabled) + return; + + if (!client) { + if (!file_exists(INFLUX_CONFIG_FILE)) + return; + + //Load influx config: + ArduinoJson::JsonDocument doc; + file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); + ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); + if (error) { + DEBUG_PRINT(F("email: deserializeJson() failed: ")); + DEBUG_PRINTLN(error.c_str()); + return; + } + if (!doc["enabled"]) + return; + + //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); + client = new InfluxDBClient(doc["url"], doc["org"], doc["bucket"], doc["token"], InfluxDbCloud2CACert); + } + + if (client) { + // Print what are we exactly writing + DEBUG_PRINT("Writing: "); + DEBUG_PRINTLN(sensor_data.toLineProtocol()); + + // Write point + if (!client->writePoint(sensor_data)) { + DEBUG_PRINT("InfluxDB write failed: "); + DEBUG_PRINTLN(client->getLastErrorMessage()); + } + } +} + +boolean OSInfluxDB::isEnabled() { + if (!initialized) { + init(); + } + return enabled; +} + +#endif \ No newline at end of file diff --git a/influxdb.h b/influxdb.h new file mode 100644 index 000000000..a7d47582d --- /dev/null +++ b/influxdb.h @@ -0,0 +1,47 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2024 by Stefan Schmaltz (info@opensprinklershop.de) + * + * OpenSprinkler library header file + * Sep 2024 @ OpenSprinklerShop.de + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _INFLUX_H +#define _INFLUX_H +#if defined(ESP8266) //Sorry, currently only for ESP8266! +#include "ArduinoJson.hpp" +#include +#include + +class OSInfluxDB { +private: + InfluxDBClient *client; + boolean enabled; + boolean initialized; + void init(); +public: + ~OSInfluxDB() { if (client) delete client; } + void set_influx_config(int enabled, char *url, char *org, char *bucket, char *token); + void set_influx_config(ArduinoJson::JsonDocument &doc); + void get_influx_config(ArduinoJson::JsonDocument &doc); + void write_influx_data(Point &sensor_data); + boolean isEnabled(); +}; + +#endif +#endif \ No newline at end of file diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 67dbf0bd5..ac22c3e10 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -29,6 +29,7 @@ #include "mqtt.h" #include "main.h" #include "sensors.h" +#include "influxdb.h" // External variables defined in main ion file #if defined(USE_OTF) @@ -3356,7 +3357,102 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { handle_return(HTML_OK); } +#if defined(ESP8266) +/** + * is + * @brief influx set config + * + */ +void server_influx_set(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + int enabled = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enabled"), true)) { + enabled = strtol(tmp_buffer, NULL, 0); + } + + char *url = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("url"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + url = strdup(tmp_buffer); + } + + char *org = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("org"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + org = strdup(tmp_buffer); + } + + char *bucket = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("bucket"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + bucket = strdup(tmp_buffer); + } + + char *token = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("token"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + token = strdup(tmp_buffer); + } + +#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 + os.influxdb.set_influx_config(enabled, url, org, bucket, token); + + handle_return(HTML_OK); +} + + +/** + * ig + * @brief influx get config + * + */ +void server_influx_get(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + +#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 + + ArduinoJson::JsonDocument doc; + os.influxdb.get_influx_config(doc); + int enabled = doc["enabled"]; + const char *url = doc["url"]; + const char *org = doc["org"]; + const char *bucket = doc["bucket"]; + const char *token = doc["token"]; + + bfill.emit_p(PSTR("{\"enabled\":$D,\"url\":\"$S\",\"org\":\"$S\",\"bucket\":\"$S\",\"token\":\"$S\"}"), + enabled, url, org, bucket, token); + send_packet(OTF_PARAMS); + handle_return(HTML_OK); +} +#endif typedef void (*URLHandler)(OTF_PARAMS_DEF); @@ -3407,6 +3503,10 @@ const char _url_keys[] PROGMEM = "sh" "sx" "db" +#if defined(ESP8266) + "is" + "ig" +#endif #if defined(ARDUINO) //"ff" #endif @@ -3454,6 +3554,10 @@ URLHandler urls[] = { server_sensorprog_types,//sh server_sensorconfig_backup,//sx server_json_debug, // db +#if defined(ESP8266) + server_influx_set, + server_influx_get, +#endif //server_fill_files, }; diff --git a/platformio.ini b/platformio.ini index ab42774b9..505252ccb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -25,6 +25,7 @@ lib_deps = https://github.com/OpenSprinklerShop/OpenThings-Framework-Firmware-Library RobTillaart/ADS1X15 https://github.com/bluemurder/esp8266-ping + https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/archive/3.13.2.zip ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - -- upload_speed = 460800 diff --git a/sensors.cpp b/sensors.cpp index 4013c67f0..0cbdca04e 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -33,6 +33,7 @@ #include "sensor_mqtt.h" #include "utils.h" #include "weather.h" +#include "influxdb.h" #ifdef ADS1115 #include "sensor_ospi_ads1115.h" #endif @@ -40,6 +41,7 @@ #include "sensor_ospi_pcf8591.h" #endif + unsigned char findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const char *key, bool key_in_pgm = false, uint8_t *keyfound = NULL); @@ -509,6 +511,7 @@ bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog) { } bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { + if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { SensorLog_t sensorlog; memset(&sensorlog, 0, sizeof(SensorLog_t)); @@ -516,6 +519,12 @@ bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { sensorlog.time = time; sensorlog.native_data = sensor->last_native_data; sensorlog.data = sensor->last_data; + + #ifdef ESP8266 + if (log == LOG_STD) + add_influx_data(sensor); + #endif + if (!sensorlog_add(log, &sensorlog)) { sensor->flags.log = 0; return false; @@ -2453,3 +2462,33 @@ char *SensorUrl_get(uint nr, uint type) { } return NULL; } + +#ifdef ESP8266 + +/** + * @brief Write data to influx db + * + * @param sensor + * @param log + */ +void add_influx_data(Sensor_t *sensor) { + if (!os.influxdb.isEnabled()) + return; + + Point sensor_data("analogsensor"); + sensor_data.clearFields(); + sensor_data.clearTags(); + + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + sensor_data.addTag("devicename", tmp_buffer); + snprintf(tmp_buffer, 10, "%d", sensor->nr); + sensor_data.addTag("nr", tmp_buffer); + sensor_data.addTag("name", sensor->name); + sensor_data.addTag("unit", getSensorUnit(sensor)); + + sensor_data.addField("native_data", sensor->last_native_data); + sensor_data.addField("data", sensor->last_data); + + os.influxdb.write_influx_data(sensor_data); +} +#endif \ No newline at end of file diff --git a/sensors.h b/sensors.h index 1b3cd1d5b..5bbf807b0 100644 --- a/sensors.h +++ b/sensors.h @@ -327,6 +327,10 @@ const char *getlogfile2(uint8_t log); void checkLogSwitch(uint8_t log); void checkLogSwitchAfterWrite(uint8_t log); +//influxdb +#ifdef ESP8266 +void add_influx_data(Sensor_t *sensor); +#endif // Set Sensor Address for SMT100: int set_sensor_address(Sensor_t *sensor, uint8_t new_address); From 5a07cc393b90b4614c25d49e05c7d540478cb820 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 15 Sep 2024 23:53:35 +0200 Subject: [PATCH 205/281] Added support for influxdb (api ig/is) --- .gitmodules | 3 + OpenSprinkler.cpp | 1 + OpenSprinkler.h | 3 +- defines.h | 2 +- main.cpp | 1 + opensprinkler_server.cpp | 105 ++++++++++++++++++++++ osinfluxdb.cpp | 184 +++++++++++++++++++++++++++++++++++++++ osinfluxdb.h | 60 +++++++++++++ platformio.ini | 1 + sensors.cpp | 31 +++++++ sensors.h | 2 + 11 files changed, 391 insertions(+), 2 deletions(-) create mode 100644 osinfluxdb.cpp create mode 100644 osinfluxdb.h diff --git a/.gitmodules b/.gitmodules index 0c7b52e67..3b2f532a6 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 \ No newline at end of file diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 9d7ac68d0..52fed2973 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -89,6 +89,7 @@ extern ProgramData pd; unsigned char OpenSprinkler::wifi_bssid[6]={0}; unsigned char OpenSprinkler::wifi_channel=255; unsigned char OpenSprinkler::wifi_testmode = 0; + OSInfluxDB OpenSprinkler::influxdb; #elif defined(ARDUINO) LiquidCrystal OpenSprinkler::lcd; extern SdFat sd; diff --git a/OpenSprinkler.h b/OpenSprinkler.h index d57431900..f96901e05 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -51,6 +51,7 @@ #include "SSD1306Display.h" #include "espconnect.h" #include "EMailSender.h" + #include "osinfluxdb.h" #else // for AVR #include #include @@ -216,6 +217,7 @@ class OpenSprinkler { // data members #if defined(ESP8266) static SSD1306Display lcd; // 128x64 OLED display + static OSInfluxDB influxdb; #elif defined(ARDUINO) static LiquidCrystal lcd; // 16x2 character LCD #else @@ -227,7 +229,6 @@ class OpenSprinkler { #endif static OSMqtt mqtt; - static NVConData nvdata; static ConStatus status; static ConStatus old_status; diff --git a/defines.h b/defines.h index eaabd5c2e..7c6921303 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; diff --git a/main.cpp b/main.cpp index 7f5039cc8..89f1b6390 100644 --- a/main.cpp +++ b/main.cpp @@ -31,6 +31,7 @@ #include "mqtt.h" #include "sensors.h" #include "main.h" +#include "osinfluxdb.h" #if defined(ARDUINO) #include diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 67dbf0bd5..531ff2c66 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -29,6 +29,7 @@ #include "mqtt.h" #include "main.h" #include "sensors.h" +#include "osinfluxdb.h" // External variables defined in main ion file #if defined(USE_OTF) @@ -3356,7 +3357,107 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { handle_return(HTML_OK); } +/** + * is + * @brief influx set config + * + */ +void server_influx_set(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + int enabled = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enabled"), true)) { + enabled = strtol(tmp_buffer, NULL, 0); + } + + char *url = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("url"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + url = strdup(tmp_buffer); + } + + int port = 8086; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("port"), true)) { + DEBUG_PRINTLN(tmp_buffer); + port = strtol(tmp_buffer, NULL, 0); + } + + char *org = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("org"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + org = strdup(tmp_buffer); + } + + char *bucket = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("bucket"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + bucket = strdup(tmp_buffer); + } + + char *token = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("token"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + token = strdup(tmp_buffer); + } + +#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 + + os.influxdb.set_influx_config(enabled, url, port, org, bucket, token); + + handle_return(HTML_OK); +} + + +/** + * ig + * @brief influx get config + * + */ +void server_influx_get(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif +#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 + + ArduinoJson::JsonDocument doc; + os.influxdb.get_influx_config(doc); + int enabled = doc["enabled"]; + const char *url = doc["url"]; + const uint16_t port = doc["port"]; + const char *org = doc["org"]; + const char *bucket = doc["bucket"]; + const char *token = doc["token"]; + + bfill.emit_p(PSTR("{\"enabled\":$D,\"url\":\"$S\",\"port\":$D,\"org\":\"$S\",\"bucket\":\"$S\",\"token\":\"$S\"}"), + enabled, url, port, org, bucket, token); + send_packet(OTF_PARAMS); + handle_return(HTML_OK); +} typedef void (*URLHandler)(OTF_PARAMS_DEF); @@ -3407,6 +3508,8 @@ const char _url_keys[] PROGMEM = "sh" "sx" "db" + "is" + "ig" #if defined(ARDUINO) //"ff" #endif @@ -3454,6 +3557,8 @@ URLHandler urls[] = { server_sensorprog_types,//sh server_sensorconfig_backup,//sx server_json_debug, // db + server_influx_set, + server_influx_get, //server_fill_files, }; diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp new file mode 100644 index 000000000..da5bfac9e --- /dev/null +++ b/osinfluxdb.cpp @@ -0,0 +1,184 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2024 by Stefan Schmaltz (info@opensprinklershop.de) + * + * OpenSprinkler library header file + * Sep 2024 @ OpenSprinklerShop.de + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#include "osinfluxdb.h" +#include "utils.h" +#include "defines.h" + +extern char tmp_buffer[TMP_BUFFER_SIZE*2]; + +#define INFLUX_CONFIG_FILE "influx.json" + +void OSInfluxDB::set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token) { + ArduinoJson::JsonDocument doc; + doc["enabled"] = enabled; + doc["url"] = url; + doc["port]"] = port; + doc["org"] = org; + doc["bucket"] = bucket; + doc["token"] = token; + set_influx_config(doc); +} + +void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { + size_t size = ArduinoJson::serializeJson(doc, tmp_buffer); + file_write_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, size+1); + if (client) + { + delete client; + client = NULL; + } + enabled = doc["enabled"]; + initialized = true; +} + +void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { + //Load influx config: + memset(tmp_buffer, 0, TMP_BUFFER_SIZE*2); + if (file_exists(INFLUX_CONFIG_FILE)) + file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); + ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); + if (error || doc.isNull() || doc["enabled"] > 1) { + if (error) { + DEBUG_PRINT(F("email: deserializeJson() failed: ")); + DEBUG_PRINTLN(error.c_str()); + } + doc["enabled"] = 0; + doc["url"] = ""; + doc["port"] = 8086; + doc["org"] = ""; + doc["bucket"] = ""; + doc["token"] = ""; + } + enabled = doc["enabled"]; + initialized = true; +} + +void OSInfluxDB::init() { + ArduinoJson::JsonDocument doc; + get_influx_config(doc); + enabled = doc["enabled"]; + initialized = true; +} + +boolean OSInfluxDB::isEnabled() { + if (!initialized) { + init(); + } + return enabled; +} + +#if defined(ESP8266) +void OSInfluxDB::write_influx_data(Point &sensor_data) { + if (!initialized) init(); + if (!enabled) + return; + + if (!client) { + if (!file_exists(INFLUX_CONFIG_FILE)) + return; + + //Load influx config: + ArduinoJson::JsonDocument doc; + file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); + ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); + if (error) { + DEBUG_PRINT(F("email: deserializeJson() failed: ")); + DEBUG_PRINTLN(error.c_str()); + return; + } + if (!doc["enabled"]) + return; + + //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); + String url = doc["url"]; + int port = doc["port"]; + if (port == 0) + port = 8086; + url += ":"+port; + client = new InfluxDBClient(url, doc["org"], doc["bucket"], doc["token"], InfluxDbCloud2CACert); + } + + if (client) { + // Print what are we exactly writing + DEBUG_PRINT("Writing: "); + DEBUG_PRINTLN(sensor_data.toLineProtocol()); + + // Write point + if (!client->writePoint(sensor_data)) { + DEBUG_PRINT("InfluxDB write failed: "); + DEBUG_PRINTLN(client->getLastErrorMessage()); + } + } +} + +#else + +void OSInfluxDB::write_influx_data(Point &sensor_data) { + if (!initialized) init(); + if (!enabled) + return; + + if (!client) { + if (!file_exists(INFLUX_CONFIG_FILE)) + return; + + //Load influx config: + ArduinoJson::JsonDocument doc; + file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); + ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); + if (error) { + DEBUG_PRINT(F("email: deserializeJson() failed: ")); + DEBUG_PRINTLN(error.c_str()); + return; + } + if (!doc["enabled"]) + return; +/* +influxdb_cpp::server_info si("127.0.0.1", 8086, "db", "usr", "pwd"); +influxdb_cpp::builder() + .meas("foo") + .tag("k", "v") + .tag("x", "y") + .field("x", 10) + .field("y", 10.3, 2) + .field("z", 10.3456) + .field("b", !!10) + .timestamp(1512722735522840439) + .post_http(si); +*/ + client = new influxdb_cpp::server_info(doc["url"], doc["port"], doc["bucket"]", "", "", "ms", doc["token"]); + } + + if (client) { + String resp; + sensor_data.timestamp(millis()); + int ret = sensor_data.post_http(client*, &resp); + if (!ret) { + DEBUG_PRINT("InfluxDB write failed: "); + DEBUG_PRINTLN(client->getLastErrorMessage()); + } + } +} + +#endif \ No newline at end of file diff --git a/osinfluxdb.h b/osinfluxdb.h new file mode 100644 index 000000000..d82e6d5ac --- /dev/null +++ b/osinfluxdb.h @@ -0,0 +1,60 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2024 by Stefan Schmaltz (info@opensprinklershop.de) + * + * OpenSprinkler library header file + * Sep 2024 @ OpenSprinklerShop.de + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _OSINFLUX_H +#define _OSINFLUX_H +#include "ArduinoJson.hpp" +#if defined(ESP8266) +#include +#include +#else +#include "influxdb.hpp" + +//compatibility class: +class Point { + influxdb_cpp::builder ib(); + Point(char *m) {ib.meas(m);}; + void addTag(char *tag, char *value) {ib.tag(tag, value);} + void addField(char *field, char *value) {ib.field(field, value);}; +} +#endif + +class OSInfluxDB { +private: + #if defined(ESP8266) + InfluxDBClient *client; + #else + influxdb_cpp::server_info *client; + #endif + boolean enabled; + boolean initialized; + void init(); +public: + ~OSInfluxDB() { if (client) delete client; } + void set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token); + void set_influx_config(ArduinoJson::JsonDocument &doc); + void get_influx_config(ArduinoJson::JsonDocument &doc); + void write_influx_data(Point &sensor_data); + boolean isEnabled(); +}; +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index ab42774b9..505252ccb 100644 --- a/platformio.ini +++ b/platformio.ini @@ -25,6 +25,7 @@ lib_deps = https://github.com/OpenSprinklerShop/OpenThings-Framework-Firmware-Library RobTillaart/ADS1X15 https://github.com/bluemurder/esp8266-ping + https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/archive/3.13.2.zip ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - -- upload_speed = 460800 diff --git a/sensors.cpp b/sensors.cpp index 4013c67f0..4ed060aba 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -33,6 +33,7 @@ #include "sensor_mqtt.h" #include "utils.h" #include "weather.h" +#include "influxdb.h" #ifdef ADS1115 #include "sensor_ospi_ads1115.h" #endif @@ -40,6 +41,7 @@ #include "sensor_ospi_pcf8591.h" #endif + unsigned char findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const char *key, bool key_in_pgm = false, uint8_t *keyfound = NULL); @@ -509,6 +511,7 @@ bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog) { } bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { + if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { SensorLog_t sensorlog; memset(&sensorlog, 0, sizeof(SensorLog_t)); @@ -516,6 +519,10 @@ bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { sensorlog.time = time; sensorlog.native_data = sensor->last_native_data; sensorlog.data = sensor->last_data; + + if (log == LOG_STD) + add_influx_data(sensor); + if (!sensorlog_add(log, &sensorlog)) { sensor->flags.log = 0; return false; @@ -2453,3 +2460,27 @@ char *SensorUrl_get(uint nr, uint type) { } return NULL; } + +/** + * @brief Write data to influx db + * + * @param sensor + * @param log + */ +void add_influx_data(Sensor_t *sensor) { + if (!os.influxdb.isEnabled()) + return; + + Point sensor_data("analogsensor"); + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + sensor_data.addTag("devicename", tmp_buffer); + snprintf(tmp_buffer, 10, "%d", sensor->nr); + sensor_data.addTag("nr", tmp_buffer); + sensor_data.addTag("name", sensor->name); + sensor_data.addTag("unit", getSensorUnit(sensor)); + + sensor_data.addField("native_data", sensor->last_native_data); + sensor_data.addField("data", sensor->last_data); + + os.influxdb.write_influx_data(sensor_data); +} \ No newline at end of file diff --git a/sensors.h b/sensors.h index 1b3cd1d5b..a1b767093 100644 --- a/sensors.h +++ b/sensors.h @@ -327,6 +327,8 @@ const char *getlogfile2(uint8_t log); void checkLogSwitch(uint8_t log); void checkLogSwitchAfterWrite(uint8_t log); +//influxdb +void add_influx_data(Sensor_t *sensor); // Set Sensor Address for SMT100: int set_sensor_address(Sensor_t *sensor, uint8_t new_address); From cc1f60477cb68f1aea70bf733295756a3eff36f1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 19 Sep 2024 00:22:25 +0200 Subject: [PATCH 206/281] added influxdb - support for raspi --- .gitmodules | 2 +- external/influxdb-cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 160000 external/influxdb-cpp diff --git a/.gitmodules b/.gitmodules index 3b2f532a6..a2bf9ffbd 100644 --- a/.gitmodules +++ b/.gitmodules @@ -6,4 +6,4 @@ url = https://github.com/gilmaimon/TinyWebsockets.git [submodule "external/influxdb-cpp"] path = external/influxdb-cpp - url = https://github.com/orca-zhang/influxdb-cpp.git \ No newline at end of file + url = https://github.com/orca-zhang/influxdb-cpp.git 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 aff73789fb4498f0833adebdeeb2defac65f4b5c Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 19 Sep 2024 00:22:27 +0200 Subject: [PATCH 207/281] Added influxdb support for raspi --- OpenSprinkler.cpp | 2 +- OpenSprinkler.h | 4 +- build.sh | 2 + build2.sh | 2 + influxdb.cpp | 128 ---------------------------------------------- influxdb.h | 47 ----------------- osinfluxdb.cpp | 29 +++++------ osinfluxdb.h | 23 ++++----- sensors.cpp | 36 +++++++++++++ 9 files changed, 67 insertions(+), 206 deletions(-) delete mode 100644 influxdb.cpp delete mode 100644 influxdb.h diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 0f56b6d66..025f7dc21 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -71,6 +71,7 @@ unsigned char OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_grp[MAX_NUM_STATIONS]; unsigned char OpenSprinkler::masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; RCSwitch OpenSprinkler::rfswitch; +OSInfluxDB OpenSprinkler::influxdb; extern char tmp_buffer[]; extern char ether_buffer[]; @@ -93,7 +94,6 @@ extern ProgramData pd; unsigned char OpenSprinkler::wifi_bssid[6]={0}; unsigned char OpenSprinkler::wifi_channel=255; unsigned char OpenSprinkler::wifi_testmode = 0; - OSInfluxDB OpenSprinkler::influxdb; #elif defined(ARDUINO) extern SdFat sd; #else diff --git a/OpenSprinkler.h b/OpenSprinkler.h index f4434ec7d..80a76bdbe 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -32,6 +32,7 @@ #include "images.h" #include "mqtt.h" #include "RCSwitch.h" +#include "osinfluxdb.h" #if defined(ARDUINO) // headers for Arduino #include @@ -52,7 +53,6 @@ #include "SSD1306Display.h" #include "espconnect.h" #include "EMailSender.h" - #include "osinfluxdb.h" #else // for AVR #include #include @@ -243,9 +243,9 @@ class OpenSprinkler { public: // data members + static OSInfluxDB influxdb; #if defined(USE_SSD1306) static SSD1306Display lcd; // 128x64 OLED display - static OSInfluxDB influxdb; #elif defined(USE_LCD) static LiquidCrystal lcd; // 16x2 character LCD #endif diff --git a/build.sh b/build.sh index 17d8b8b49..dc12d9e29 100755 --- a/build.sh +++ b/build.sh @@ -63,6 +63,7 @@ if [ "$1" == "demo" ]; then ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) + ifx=$(ls external/influxdb-cpp/*.cpp) g++ -o OpenSprinkler -DDEMO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h main.cpp \ OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp \ mqtt.cpp smtp.c RCSwitch.cpp \ @@ -70,6 +71,7 @@ if [ "$1" == "demo" ]; then $ws \ -Iexternal/OpenThings-Framework-Firmware-Library/ \ $otf \ + $ifx \ -lpthread -lmosquitto -lssl -lcrypto else echo "Installing required libraries..." diff --git a/build2.sh b/build2.sh index ad4c895bc..281e46cd2 100755 --- a/build2.sh +++ b/build2.sh @@ -33,6 +33,7 @@ echo "Compiling firmware..." ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) + ifx=$(ls external/influxdb-cpp/*.hpp) g++ -o OpenSprinkler -DOSPI $USEGPIO $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp \ smtp.c RCSwitch.cpp sensor*.cpp \ @@ -41,6 +42,7 @@ $ws \ -Iexternal/OpenThings-Framework-Firmware-Library/ \ $otf \ + $ifx osinfluxdb.cpp -Iexternal/influxdb-cpp/ \ -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB diff --git a/influxdb.cpp b/influxdb.cpp deleted file mode 100644 index 269f499a4..000000000 --- a/influxdb.cpp +++ /dev/null @@ -1,128 +0,0 @@ -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware - * Copyright (C) 2024 by Stefan Schmaltz (info@opensprinklershop.de) - * - * OpenSprinkler library header file - * Sep 2024 @ OpenSprinklerShop.de - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#if defined(ESP8266) -#include "influxdb.h" -#include "utils.h" -#include "defines.h" - -extern char tmp_buffer[TMP_BUFFER_SIZE*2]; - -#define INFLUX_CONFIG_FILE "influx.json" - -void OSInfluxDB::set_influx_config(int enabled, char *url, char *org, char *bucket, char *token) { - ArduinoJson::JsonDocument doc; - doc["enabled"] = enabled; - doc["url"] = url; - doc["org"] = org; - doc["bucket"] = bucket; - doc["token"] = token; - set_influx_config(doc); -} - -void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { - size_t size = ArduinoJson::serializeJson(doc, tmp_buffer); - file_write_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, size+1); - if (client) - { - delete client; - client = NULL; - } - enabled = doc["enabled"]; - initialized = true; -} - -void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { - //Load influx config: - memset(tmp_buffer, 0, TMP_BUFFER_SIZE*2); - if (file_exists(INFLUX_CONFIG_FILE)) - file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); - ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); - if (error || doc.isNull() || doc["enabled"] > 1) { - if (error) { - DEBUG_PRINT(F("email: deserializeJson() failed: ")); - DEBUG_PRINTLN(error.c_str()); - } - doc["enabled"] = 0; - doc["url"] = ""; - doc["org"] = ""; - doc["bucket"] = ""; - doc["token"] = ""; - } - enabled = doc["enabled"]; - initialized = true; -} - -void OSInfluxDB::init() { - ArduinoJson::JsonDocument doc; - get_influx_config(doc); - enabled = doc["enabled"]; - initialized = true; -} - -void OSInfluxDB::write_influx_data(Point &sensor_data) { - if (!initialized) init(); - if (!enabled) - return; - - if (!client) { - if (!file_exists(INFLUX_CONFIG_FILE)) - return; - - //Load influx config: - ArduinoJson::JsonDocument doc; - file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); - ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); - if (error) { - DEBUG_PRINT(F("email: deserializeJson() failed: ")); - DEBUG_PRINTLN(error.c_str()); - return; - } - if (!doc["enabled"]) - return; - - //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); - client = new InfluxDBClient(doc["url"], doc["org"], doc["bucket"], doc["token"], InfluxDbCloud2CACert); - } - - if (client) { - // Print what are we exactly writing - DEBUG_PRINT("Writing: "); - DEBUG_PRINTLN(sensor_data.toLineProtocol()); - - // Write point - if (!client->writePoint(sensor_data)) { - DEBUG_PRINT("InfluxDB write failed: "); - DEBUG_PRINTLN(client->getLastErrorMessage()); - } - } -} - -boolean OSInfluxDB::isEnabled() { - if (!initialized) { - init(); - } - return enabled; -} - -#endif \ No newline at end of file diff --git a/influxdb.h b/influxdb.h deleted file mode 100644 index a7d47582d..000000000 --- a/influxdb.h +++ /dev/null @@ -1,47 +0,0 @@ -/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware - * Copyright (C) 2024 by Stefan Schmaltz (info@opensprinklershop.de) - * - * OpenSprinkler library header file - * Sep 2024 @ OpenSprinklerShop.de - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#ifndef _INFLUX_H -#define _INFLUX_H -#if defined(ESP8266) //Sorry, currently only for ESP8266! -#include "ArduinoJson.hpp" -#include -#include - -class OSInfluxDB { -private: - InfluxDBClient *client; - boolean enabled; - boolean initialized; - void init(); -public: - ~OSInfluxDB() { if (client) delete client; } - void set_influx_config(int enabled, char *url, char *org, char *bucket, char *token); - void set_influx_config(ArduinoJson::JsonDocument &doc); - void get_influx_config(ArduinoJson::JsonDocument &doc); - void write_influx_data(Point &sensor_data); - boolean isEnabled(); -}; - -#endif -#endif \ No newline at end of file diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index da5bfac9e..5bf56ceb6 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -89,6 +89,10 @@ boolean OSInfluxDB::isEnabled() { } #if defined(ESP8266) +OSInfluxDB::~OSInfluxDB() { + if (client) delete client; +} + void OSInfluxDB::write_influx_data(Point &sensor_data) { if (!initialized) init(); if (!enabled) @@ -133,15 +137,18 @@ void OSInfluxDB::write_influx_data(Point &sensor_data) { } #else +OSInfluxDB::~OSInfluxDB() { + if (client) delete client; +} -void OSInfluxDB::write_influx_data(Point &sensor_data) { +influxdb_cpp::server_info *OSInfluxDB::get_client() { if (!initialized) init(); if (!enabled) - return; + return NULL; if (!client) { if (!file_exists(INFLUX_CONFIG_FILE)) - return; + return NULL; //Load influx config: ArduinoJson::JsonDocument doc; @@ -150,10 +157,10 @@ void OSInfluxDB::write_influx_data(Point &sensor_data) { if (error) { DEBUG_PRINT(F("email: deserializeJson() failed: ")); DEBUG_PRINTLN(error.c_str()); - return; + return NULL; } if (!doc["enabled"]) - return; + return NULL; /* influxdb_cpp::server_info si("127.0.0.1", 8086, "db", "usr", "pwd"); influxdb_cpp::builder() @@ -167,18 +174,10 @@ influxdb_cpp::builder() .timestamp(1512722735522840439) .post_http(si); */ - client = new influxdb_cpp::server_info(doc["url"], doc["port"], doc["bucket"]", "", "", "ms", doc["token"]); - } - if (client) { - String resp; - sensor_data.timestamp(millis()); - int ret = sensor_data.post_http(client*, &resp); - if (!ret) { - DEBUG_PRINT("InfluxDB write failed: "); - DEBUG_PRINTLN(client->getLastErrorMessage()); - } + client = new influxdb_cpp::server_info(doc["url"], doc["port"], doc["bucket"], "", "", "ms", doc["token"]); } + return client; } #endif \ No newline at end of file diff --git a/osinfluxdb.h b/osinfluxdb.h index d82e6d5ac..57ad6afd8 100644 --- a/osinfluxdb.h +++ b/osinfluxdb.h @@ -30,31 +30,28 @@ #else #include "influxdb.hpp" -//compatibility class: -class Point { - influxdb_cpp::builder ib(); - Point(char *m) {ib.meas(m);}; - void addTag(char *tag, char *value) {ib.tag(tag, value);} - void addField(char *field, char *value) {ib.field(field, value);}; -} #endif class OSInfluxDB { private: #if defined(ESP8266) - InfluxDBClient *client; + InfluxDBClient * client; #else - influxdb_cpp::server_info *client; + influxdb_cpp::server_info * client; #endif - boolean enabled; - boolean initialized; + bool enabled; + bool initialized; void init(); public: - ~OSInfluxDB() { if (client) delete client; } + ~OSInfluxDB(); void set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token); void set_influx_config(ArduinoJson::JsonDocument &doc); void get_influx_config(ArduinoJson::JsonDocument &doc); + bool isEnabled(); + #if defined(ESP8266) void write_influx_data(Point &sensor_data); - boolean isEnabled(); + #else + influxdb_cpp::server_info * get_client(); + #endif }; #endif \ No newline at end of file diff --git a/sensors.cpp b/sensors.cpp index 7b971442d..659870a38 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2471,6 +2471,7 @@ void add_influx_data(Sensor_t *sensor) { if (!os.influxdb.isEnabled()) return; + #if defined(ESP8266) Point sensor_data("analogsensor"); os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); sensor_data.addTag("devicename", tmp_buffer); @@ -2483,4 +2484,39 @@ void add_influx_data(Sensor_t *sensor) { sensor_data.addField("data", sensor->last_data); os.influxdb.write_influx_data(sensor_data); + + #else + +/* +influxdb_cpp::server_info si("127.0.0.1", 8086, "db", "usr", "pwd"); +influxdb_cpp::builder() + .meas("foo") + .tag("k", "v") + .tag("x", "y") + .field("x", 10) + .field("y", 10.3, 2) + .field("z", 10.3456) + .field("b", !!10) + .timestamp(1512722735522840439) + .post_http(si); +*/ + influxdb_cpp::server_info * client = os.influxdb.get_client(); + if (client == NULL) + return; + + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + char nr_buf[10]; + snprintf(nr_buf, 10, "%d", sensor->nr); + influxdb_cpp::builder() + .meas("analogsensor") + .tag("devicename", tmp_buffer) + .tag("nr", nr_buf) + .tag("name", sensor->name) + .tag("unit", getSensorUnit(sensor)) + .field("native_data", (long)sensor->last_native_data) + .field("data", sensor->last_data, 2) + .timestamp(millis()) + .post_http(*client); + + #endif } \ No newline at end of file From b363751044953e8af00acd6354862efed0e76254 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 20 Sep 2024 00:59:47 +0200 Subject: [PATCH 208/281] Prepared webservice for APP --- opensprinkler_server.cpp | 30 ++++++++++++++++++++++++++++-- opensprinkler_server.h | 1 + osinfluxdb.cpp | 12 ++++++++++++ osinfluxdb.h | 1 + 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index e52eab95a..6b583137d 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1309,6 +1309,15 @@ void server_json_controller_main(OTF_PARAMS_DEF) { } bfill.emit_p(PSTR("]")); + //influxdb + if(!buffer_available()) { + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR(",\"influxdb\":")); + server_influx_get_main(); + //end influxdb + + bfill.emit_p(PSTR("}")); } @@ -1606,6 +1615,19 @@ void server_change_options(OTF_PARAMS_DEF) os.status.req_mqtt_restart = true; } + //influxdb set + keyfound = 0; + if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("influxdb"), true, &keyfound)) { + #if !defined(USE_OTF) + urlDecode(tmp_buffer); + #endif + os.influxdb.set_influx_config(tmp_buffer); + } else if (keyfound) { + tmp_buffer[0]=0; + os.influxdb.set_influx_config(tmp_buffer); + } + //end influxdb set + keyfound = 0; if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("email"), true, &keyfound)) { #if !defined(USE_OTF) @@ -3539,7 +3561,13 @@ void server_influx_get(OTF_PARAMS_DEF) { #else print_header(); #endif + server_influx_get_main(); + send_packet(OTF_PARAMS); + handle_return(HTML_OK); +} + +void server_influx_get_main() { ArduinoJson::JsonDocument doc; os.influxdb.get_influx_config(doc); int enabled = doc["enabled"]; @@ -3551,8 +3579,6 @@ void server_influx_get(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"enabled\":$D,\"url\":\"$S\",\"port\":$D,\"org\":\"$S\",\"bucket\":\"$S\",\"token\":\"$S\"}"), enabled, url, port, org, bucket, token); - send_packet(OTF_PARAMS); - handle_return(HTML_OK); } typedef void (*URLHandler)(OTF_PARAMS_DEF); diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 5b3c66a56..05075149e 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -112,6 +112,7 @@ class BufferFiller { } }; +void server_influx_get_main(); void free_tmp_memory(); void restore_tmp_memory(); diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index 5bf56ceb6..95e2d9897 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -52,6 +52,18 @@ void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { initialized = true; } +void OSInfluxDB::set_influx_config(const char *data) { + size_t size = strlen(data); + file_write_block(INFLUX_CONFIG_FILE, data, 0, size+1); + if (client) + { + delete client; + client = NULL; + } + enabled = false; + initialized = false; +} + void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { //Load influx config: memset(tmp_buffer, 0, TMP_BUFFER_SIZE*2); diff --git a/osinfluxdb.h b/osinfluxdb.h index 57ad6afd8..e6885a388 100644 --- a/osinfluxdb.h +++ b/osinfluxdb.h @@ -46,6 +46,7 @@ class OSInfluxDB { ~OSInfluxDB(); void set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token); void set_influx_config(ArduinoJson::JsonDocument &doc); + void set_influx_config(const char *json); void get_influx_config(ArduinoJson::JsonDocument &doc); bool isEnabled(); #if defined(ESP8266) From 37066d81550df77405864516af260619fbd68450 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 20 Sep 2024 01:04:42 +0200 Subject: [PATCH 209/281] changed enabled to en --- opensprinkler_server.cpp | 6 +++--- osinfluxdb.cpp | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 6b583137d..c7b89c425 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3488,7 +3488,7 @@ void server_influx_set(OTF_PARAMS_DEF) { #endif int enabled = 0; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enabled"), true)) { + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { enabled = strtol(tmp_buffer, NULL, 0); } @@ -3570,14 +3570,14 @@ void server_influx_get(OTF_PARAMS_DEF) { void server_influx_get_main() { ArduinoJson::JsonDocument doc; os.influxdb.get_influx_config(doc); - int enabled = doc["enabled"]; + int enabled = doc["en"]; const char *url = doc["url"]; const uint16_t port = doc["port"]; const char *org = doc["org"]; const char *bucket = doc["bucket"]; const char *token = doc["token"]; - bfill.emit_p(PSTR("{\"enabled\":$D,\"url\":\"$S\",\"port\":$D,\"org\":\"$S\",\"bucket\":\"$S\",\"token\":\"$S\"}"), + bfill.emit_p(PSTR("{\"en\":$D,\"url\":\"$S\",\"port\":$D,\"org\":\"$S\",\"bucket\":\"$S\",\"token\":\"$S\"}"), enabled, url, port, org, bucket, token); } diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index 95e2d9897..35875db1b 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -31,7 +31,7 @@ extern char tmp_buffer[TMP_BUFFER_SIZE*2]; void OSInfluxDB::set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token) { ArduinoJson::JsonDocument doc; - doc["enabled"] = enabled; + doc["en"] = enabled; doc["url"] = url; doc["port]"] = port; doc["org"] = org; @@ -48,7 +48,7 @@ void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { delete client; client = NULL; } - enabled = doc["enabled"]; + enabled = doc["en"]; initialized = true; } @@ -70,26 +70,26 @@ void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { if (file_exists(INFLUX_CONFIG_FILE)) file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); - if (error || doc.isNull() || doc["enabled"] > 1) { + if (error || doc.isNull() || doc["en"] > 1) { if (error) { DEBUG_PRINT(F("email: deserializeJson() failed: ")); DEBUG_PRINTLN(error.c_str()); } - doc["enabled"] = 0; + doc["en"] = 0; doc["url"] = ""; doc["port"] = 8086; doc["org"] = ""; doc["bucket"] = ""; doc["token"] = ""; } - enabled = doc["enabled"]; + enabled = doc["en"]; initialized = true; } void OSInfluxDB::init() { ArduinoJson::JsonDocument doc; get_influx_config(doc); - enabled = doc["enabled"]; + enabled = doc["en"]; initialized = true; } @@ -123,7 +123,7 @@ void OSInfluxDB::write_influx_data(Point &sensor_data) { DEBUG_PRINTLN(error.c_str()); return; } - if (!doc["enabled"]) + if (!doc["en"]) return; //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); @@ -171,7 +171,7 @@ influxdb_cpp::server_info *OSInfluxDB::get_client() { DEBUG_PRINTLN(error.c_str()); return NULL; } - if (!doc["enabled"]) + if (!doc["en"]) return NULL; /* influxdb_cpp::server_info si("127.0.0.1", 8086, "db", "usr", "pwd"); From 88cae9540bea2516b9f3ae04cd53e38d30a540eb Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 20 Sep 2024 02:46:01 +0200 Subject: [PATCH 210/281] enhancing influx support --- influx.json | 1 + osinfluxdb.cpp | 53 ++++++++++++++++---------------------------------- 2 files changed, 18 insertions(+), 36 deletions(-) create mode 100644 influx.json diff --git a/influx.json b/influx.json new file mode 100644 index 000000000..c0955ed15 --- /dev/null +++ b/influx.json @@ -0,0 +1 @@ +"en":1,"url":"http://192.168.0.50","port":8086,"org":"1f0b8a389ddfb4ab","bucket":"opensprinkler","token":"XS8Z9lJbIImOg27Q0LMycS290ghHDtNeFxGtiDp4wBtOvpx7BwfMCJ92_Ed21vJkBdafc4i1uy0dk6fFr-gKyA==" \ No newline at end of file diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index 35875db1b..85bf9c952 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -42,7 +42,7 @@ void OSInfluxDB::set_influx_config(int enabled, char *url, uint16_t port, char * void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { size_t size = ArduinoJson::serializeJson(doc, tmp_buffer); - file_write_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, size+1); + file_write_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, size); if (client) { delete client; @@ -54,7 +54,7 @@ void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { void OSInfluxDB::set_influx_config(const char *data) { size_t size = strlen(data); - file_write_block(INFLUX_CONFIG_FILE, data, 0, size+1); + file_write_block(INFLUX_CONFIG_FILE, data, 0, size); if (client) { delete client; @@ -65,14 +65,20 @@ void OSInfluxDB::set_influx_config(const char *data) { } void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { - //Load influx config: - memset(tmp_buffer, 0, TMP_BUFFER_SIZE*2); + DEBUG_PRINTLN("Load influx config"); if (file_exists(INFLUX_CONFIG_FILE)) - file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); + { + ulong size = file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); + DEBUG_PRINT(F("influx config size=")); + DEBUG_PRINTLN(size); + tmp_buffer[size] = 0; + DEBUG_PRINT(F("influx config=")); + DEBUG_PRINTLN(tmp_buffer); + } ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); if (error || doc.isNull() || doc["en"] > 1) { if (error) { - DEBUG_PRINT(F("email: deserializeJson() failed: ")); + DEBUG_PRINT(F("influxdb: deserializeJson() failed: ")); DEBUG_PRINTLN(error.c_str()); } doc["en"] = 0; @@ -116,14 +122,8 @@ void OSInfluxDB::write_influx_data(Point &sensor_data) { //Load influx config: ArduinoJson::JsonDocument doc; - file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); - ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); - if (error) { - DEBUG_PRINT(F("email: deserializeJson() failed: ")); - DEBUG_PRINTLN(error.c_str()); - return; - } - if (!doc["en"]) + get_influx_config(doc); + if (doc[en] == 0) return; //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); @@ -142,7 +142,7 @@ void OSInfluxDB::write_influx_data(Point &sensor_data) { // Write point if (!client->writePoint(sensor_data)) { - DEBUG_PRINT("InfluxDB write failed: "); + DEBUG_PRINT("influxdb write failed: "); DEBUG_PRINTLN(client->getLastErrorMessage()); } } @@ -164,28 +164,9 @@ influxdb_cpp::server_info *OSInfluxDB::get_client() { //Load influx config: ArduinoJson::JsonDocument doc; - file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); - ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); - if (error) { - DEBUG_PRINT(F("email: deserializeJson() failed: ")); - DEBUG_PRINTLN(error.c_str()); - return NULL; - } - if (!doc["en"]) + get_influx_config(doc); + if (doc["en"] == 0) return NULL; -/* -influxdb_cpp::server_info si("127.0.0.1", 8086, "db", "usr", "pwd"); -influxdb_cpp::builder() - .meas("foo") - .tag("k", "v") - .tag("x", "y") - .field("x", 10) - .field("y", 10.3, 2) - .field("z", 10.3456) - .field("b", !!10) - .timestamp(1512722735522840439) - .post_http(si); -*/ client = new influxdb_cpp::server_info(doc["url"], doc["port"], doc["bucket"], "", "", "ms", doc["token"]); } From d5b6d22e686626b70a246fe6978216a898070c21 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 20 Sep 2024 02:55:43 +0200 Subject: [PATCH 211/281] fixt file --- influx.json | 1 - 1 file changed, 1 deletion(-) delete mode 100644 influx.json diff --git a/influx.json b/influx.json deleted file mode 100644 index c0955ed15..000000000 --- a/influx.json +++ /dev/null @@ -1 +0,0 @@ -"en":1,"url":"http://192.168.0.50","port":8086,"org":"1f0b8a389ddfb4ab","bucket":"opensprinkler","token":"XS8Z9lJbIImOg27Q0LMycS290ghHDtNeFxGtiDp4wBtOvpx7BwfMCJ92_Ed21vJkBdafc4i1uy0dk6fFr-gKyA==" \ No newline at end of file From 4e89c47510002e3f800be2f16609cd5418c8740f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 20 Sep 2024 02:58:04 +0200 Subject: [PATCH 212/281] fixt gitignore --- .gitignore | 10 +++------- external/influxdb-cpp | 1 + 2 files changed, 4 insertions(+), 7 deletions(-) create mode 160000 external/influxdb-cpp diff --git a/.gitignore b/.gitignore index 828711827..34dd9aeb3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,18 +2,14 @@ logs/** OpenSprinkler wtopts.txt *.dat +*.bak testmode.h build-1284/* .vscode .pio -.gitignore~ -build2.sh~ -sensor_mqtt.cpp~ -opensprinkler_server.cpp.bak -opensprinkler_server.h~ -sensor.bak +influx.json *~ *.o !build.sh !startOpenSprinkler.sh -!updater.sh \ No newline at end of file +!updater.sh 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 092a27fee274d545ef79600483b2f9d80246adb5 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 20 Sep 2024 03:33:27 +0200 Subject: [PATCH 213/281] completed influxdb setup --- osinfluxdb.cpp | 11 ++++------- sensors.cpp | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index 85bf9c952..bb79395d0 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -30,7 +30,7 @@ extern char tmp_buffer[TMP_BUFFER_SIZE*2]; #define INFLUX_CONFIG_FILE "influx.json" void OSInfluxDB::set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token) { - ArduinoJson::JsonDocument doc; + ArduinoJson::JsonDocument doc; doc["en"] = enabled; doc["url"] = url; doc["port]"] = port; @@ -54,7 +54,9 @@ void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { void OSInfluxDB::set_influx_config(const char *data) { size_t size = strlen(data); - file_write_block(INFLUX_CONFIG_FILE, data, 0, size); + file_write_block(INFLUX_CONFIG_FILE, "{", 0, 1); + file_write_block(INFLUX_CONFIG_FILE, data, 1, size); + file_write_block(INFLUX_CONFIG_FILE, "}", size+1, 1); if (client) { delete client; @@ -157,17 +159,12 @@ influxdb_cpp::server_info *OSInfluxDB::get_client() { if (!initialized) init(); if (!enabled) return NULL; - if (!client) { - if (!file_exists(INFLUX_CONFIG_FILE)) - return NULL; - //Load influx config: ArduinoJson::JsonDocument doc; get_influx_config(doc); if (doc["en"] == 0) return NULL; - client = new influxdb_cpp::server_info(doc["url"], doc["port"], doc["bucket"], "", "", "ms", doc["token"]); } return client; diff --git a/sensors.cpp b/sensors.cpp index 659870a38..62fbdee2e 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2501,7 +2501,7 @@ influxdb_cpp::builder() .post_http(si); */ influxdb_cpp::server_info * client = os.influxdb.get_client(); - if (client == NULL) + if (!client) return; os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); From 5dc53fb6cd9172b942248dee4516f22aa37b37a8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 20 Sep 2024 03:46:02 +0200 Subject: [PATCH 214/281] fixt typi --- osinfluxdb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index bb79395d0..28c4ed018 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -125,7 +125,7 @@ void OSInfluxDB::write_influx_data(Point &sensor_data) { //Load influx config: ArduinoJson::JsonDocument doc; get_influx_config(doc); - if (doc[en] == 0) + if (doc["en"] == 0) return; //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); From a58899453f672435c73bb962f2befaef12a579e3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 20 Sep 2024 03:46:15 +0200 Subject: [PATCH 215/281] removed debug output --- osinfluxdb.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index bb79395d0..1f55ee845 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -67,15 +67,15 @@ void OSInfluxDB::set_influx_config(const char *data) { } void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { - DEBUG_PRINTLN("Load influx config"); + //DEBUG_PRINTLN("Load influx config"); if (file_exists(INFLUX_CONFIG_FILE)) { ulong size = file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); - DEBUG_PRINT(F("influx config size=")); - DEBUG_PRINTLN(size); + //DEBUG_PRINT(F("influx config size=")); + //DEBUG_PRINTLN(size); tmp_buffer[size] = 0; - DEBUG_PRINT(F("influx config=")); - DEBUG_PRINTLN(tmp_buffer); + //DEBUG_PRINT(F("influx config=")); + //DEBUG_PRINTLN(tmp_buffer); } ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); if (error || doc.isNull() || doc["en"] > 1) { From a2a637905dfd978af17dc6c92c9ef18625185b71 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 21 Sep 2024 01:35:11 +0200 Subject: [PATCH 216/281] Fixt esp8266 influxdb loading --- main.cpp | 2 ++ osinfluxdb.cpp | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index 4eaffd8e1..a29846747 100644 --- a/main.cpp +++ b/main.cpp @@ -1540,6 +1540,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { } #endif + + // if none if enabled, return here if ((!ifttt_enabled) && (!email_enabled) && (!os.mqtt.enabled())) return; diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index 3a05168c7..c42d3a3bf 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -67,7 +67,8 @@ void OSInfluxDB::set_influx_config(const char *data) { } void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { - //DEBUG_PRINTLN("Load influx config"); + DEBUG_PRINTLN("Load influx config"); + tmp_buffer[0] = 0; if (file_exists(INFLUX_CONFIG_FILE)) { ulong size = file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); @@ -77,6 +78,8 @@ void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { //DEBUG_PRINT(F("influx config=")); //DEBUG_PRINTLN(tmp_buffer); } + if (tmp_buffer[0] != '{') + strcpy(tmp_buffer, "{}"); ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); if (error || doc.isNull() || doc["en"] > 1) { if (error) { @@ -133,7 +136,7 @@ void OSInfluxDB::write_influx_data(Point &sensor_data) { int port = doc["port"]; if (port == 0) port = 8086; - url += ":"+port; + url = url + ":" + port; client = new InfluxDBClient(url, doc["org"], doc["bucket"], doc["token"], InfluxDbCloud2CACert); } From 238dec4688ea2d67b3e746044a6e5c9e29ea85b5 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 21 Sep 2024 01:35:15 +0200 Subject: [PATCH 217/281] Fixt influxdb loading --- main.cpp | 2 -- osinfluxdb.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index a29846747..4eaffd8e1 100644 --- a/main.cpp +++ b/main.cpp @@ -1540,8 +1540,6 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { } #endif - - // if none if enabled, return here if ((!ifttt_enabled) && (!email_enabled) && (!os.mqtt.enabled())) return; diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index c42d3a3bf..f1a545892 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -67,7 +67,7 @@ void OSInfluxDB::set_influx_config(const char *data) { } void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { - DEBUG_PRINTLN("Load influx config"); + //DEBUG_PRINTLN("Load influx config"); tmp_buffer[0] = 0; if (file_exists(INFLUX_CONFIG_FILE)) { From cf8de8956f7d22dfef5d8fbcbb613bae4539a847 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 22 Sep 2024 01:10:17 +0200 Subject: [PATCH 218/281] Added "Flow Alert" notification --- OpenSprinkler.cpp | 6 +- defines.h | 3 +- main.cpp | 129 +++++++++++++++++++++++++++++++++++++-- main.h | 5 ++ opensprinkler_server.cpp | 9 ++- 5 files changed, 141 insertions(+), 11 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 025f7dc21..6c3b57a39 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -177,7 +177,7 @@ const char iopt_json_names[] PROGMEM = "fwire" "laton" "latof" - "resv3" + "ife2\0" "resv4" "resv5" "resv6" @@ -256,7 +256,7 @@ const char iopt_prompts[] PROGMEM = "Force wired? " "Latch On Volt. " "Latch Off Volt. " - "Reserved 3 " + "Notif2 Enable: " "Reserved 4 " "Reserved 5 " "Reserved 6 " @@ -418,7 +418,7 @@ unsigned char OpenSprinkler::iopts[] = { 1, // force wired connection 0, // latch on volt 0, // latch off volt - 0, // reserved 3 + 0, // notif enable bits - part 2 0, // reserved 4 0, // reserved 5 0, // reserved 6 diff --git a/defines.h b/defines.h index faf07f927..190e99836 100644 --- a/defines.h +++ b/defines.h @@ -76,6 +76,7 @@ typedef unsigned long ulong; #define NOTIFY_SENSOR2 0x0040 #define NOTIFY_RAINDELAY 0x0080 #define NOTIFY_STATION_ON 0x0100 +#define NOTIFY_FLOW_ALERT 0x0200 /** HTTP request macro defines */ #define HTTP_RQT_SUCCESS 0 @@ -256,7 +257,7 @@ enum { IOPT_FORCE_WIRED, IOPT_LATCH_ON_VOLTAGE, IOPT_LATCH_OFF_VOLTAGE, - IOPT_RESERVE_3, + IOPT_NOTIF2_ENABLE, // Notification part 2 IOPT_RESERVE_4, IOPT_RESERVE_5, IOPT_RESERVE_6, diff --git a/main.cpp b/main.cpp index 4eaffd8e1..4cec71783 100644 --- a/main.cpp +++ b/main.cpp @@ -76,7 +76,7 @@ #endif #endif -void push_message(int type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); +void push_message(uint16_t type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); void manual_start_program(unsigned char, unsigned char); void remote_http_callback(char*); @@ -1156,6 +1156,8 @@ void check_weather() { void turn_on_station(unsigned char sid, ulong duration) { // RAH implementation of flow sensor flow_start=0; + //Added flow_gallons reset to station turn on. + flow_gallons=0; if (os.set_station_bit(sid, 1, duration)) { push_message(NOTIFY_STATION_ON, sid, duration); @@ -1243,6 +1245,7 @@ void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shif // log station run write_log(LOGDATA_STATION, curr_time); // LOG_TODO push_message(NOTIFY_STATION_OFF, sid, pd.lastrun.duration); + push_message(NOTIFY_FLOW_ALERT, sid, pd.lastrun.duration); } } @@ -1463,6 +1466,20 @@ void manual_start_program(unsigned char pid, unsigned char uwt) { } } +bool is_notif_enabled(uint16_t type) { + uint16_t notif = (uint16_t)os.iopts[IOPT_NOTIF_ENABLE] | ((uint16_t)os.iopts[IOPT_NOTIF2_ENABLE] << 8); + return (notif&type) != 0; +} + +uint16_t get_notif_enabled() { + return (uint16_t)os.iopts[IOPT_NOTIF_ENABLE]|((uint16_t)os.iopts[IOPT_NOTIF2_ENABLE]<<8); +} + +void set_notif_enabled(uint16_t notif) { + os.iopts[IOPT_NOTIF_ENABLE] = notif&0xFF; + os.iopts[IOPT_NOTIF2_ENABLE] = notif >> 8; +} + // ========================================== // ====== PUSH NOTIFICATION FUNCTIONS ======= // ========================================== @@ -1474,7 +1491,7 @@ void ip2string(char* str, size_t str_len, unsigned char ip[4]) { #define PUSH_TOPIC_LEN 120 #define PUSH_PAYLOAD_LEN TMP_BUFFER_SIZE -void push_message(int type, uint32_t lval, float fval, const char* sval) { +void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { static char topic[PUSH_TOPIC_LEN+1]; static char payload[PUSH_PAYLOAD_LEN+1]; char* postval = tmp_buffer+1; // +1 so we can fit a opening { before the loaded config @@ -1482,8 +1499,8 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { // check if ifttt key exists and also if the enable bit is set os.sopt_load(SOPT_IFTTT_KEY, tmp_buffer); - bool ifttt_enabled = ((os.iopts[IOPT_NOTIF_ENABLE]&type)!=0) && (strlen(tmp_buffer)!=0); - + bool ifttt_enabled = is_notif_enabled(type) && (strlen(tmp_buffer)!=0); + #define DEFAULT_EMAIL_PORT 465 // parse email variables @@ -1536,7 +1553,7 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { if(!email_en){ email_enabled = false; }else{ - email_enabled = os.iopts[IOPT_NOTIF_ENABLE]&type; + email_enabled = is_notif_enabled(type); } #endif @@ -1577,6 +1594,108 @@ void push_message(int type, uint32_t lval, float fval, const char* sval) { // currently no support due to the number of events exceeds 8 so need to use more than 1 byte break; + case NOTIFY_FLOW_ALERT:{ + //First determine if a Flow Alert should be sent based on flow amount and setpoint + + //Added variable to track flow alert status + bool flow_alert_flag = false; + + //Added variable for flow_gpm_alert_setpoint and set default value to max + float flow_gpm_alert_setpoint = 999.9f; + + //Added variable for to get flow_pulse_rate_factor and set to 1 as default + float flow_pulse_rate_factor = 1; + + //Added variable for tmp station name + char tmp_station_name[STATION_NAME_SIZE]; + + //Get satation name + os.get_station_name(lval, tmp_station_name); + + //Extract flow_gpm_alert_setpoint from last 5 characters of station name + if (strlen(tmp_station_name) > 5) { + const char *station_name_last_five_chars = tmp_station_name; + station_name_last_five_chars = tmp_station_name + strlen(tmp_station_name) - 5; + + //Convert last five characters to number and check if valid + if (sscanf(station_name_last_five_chars, "%f", &flow_gpm_alert_setpoint) == 1) { + + //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 + //Get Flow Pulse Rate factor and apply to flow_last_gpm when comparing and outputting + flow_pulse_rate_factor = static_cast(os.iopts[IOPT_PULSE_RATE_1]) + static_cast(os.iopts[IOPT_PULSE_RATE_0]) / 100.0; + + // 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*flow_pulse_rate_factor) > flow_gpm_alert_setpoint) { + flow_alert_flag = true; + } + } else { + //Could not convert to a valid number. If a number is not detected as a station name suffix, never send an alert + flow_alert_flag = false; + } + } else { + //Station name was not long enough to include 5 character flow setpoint. + flow_alert_flag = false; + } + + // If flow_alert_flag is true, format the appropriate messages, else don't send alert + if (flow_alert_flag) { + + if (os.mqtt.enabled()) { + //Format mqtt message + snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d/alert/flow"), lval); + snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%d.%02d,\"duration\":%d,\"alert_setpoint\":%d.%02d}"), + (int)(flow_last_gpm*flow_pulse_rate_factor), (int)((flow_last_gpm*flow_pulse_rate_factor) * 100) % 100, + (int)fval, (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint * 100) % 100); + } + + + if (ifttt_enabled || email_enabled) { + //Format ifttt\email message + + // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" + time_os_t curr_time = os.now_tz(); + struct tm *tm_info = localtime((time_t*)&curr_time); + char formatted_time[TMP_BUFFER_SIZE]; + strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%d %I:%M:%S %p", tm_info); + strcat_P(postval, PSTR("
")); + strcat(postval, formatted_time); + + strcat_P(postval, PSTR("
Station: ")); + //Truncate flow setpoint value off station name to shorten ifttt\email message + tmp_station_name[(strlen(tmp_station_name) - 5)] = '\0'; + strcat_P(postval, tmp_station_name); + + if((int)fval == 0){ + strcat_P(postval, PSTR("")); + } else { // master on event does not have duration attached to it + strcat_P(postval, PSTR("
Duration: ")); + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR(" %d minutes %d seconds"), (int)fval/60, (int)fval%60); + } + + strcat_P(postval, PSTR("

FLOW ALERT!")); + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR("
Flow rate: %d.%02d
Flow Alert Setpoint: %d.%02d"), + (int)(flow_last_gpm*flow_pulse_rate_factor), (int)((flow_last_gpm*flow_pulse_rate_factor) * 100) % 100, + (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint * 100) % 100); + + if(email_enabled) { + email_message.subject += PSTR("- FLOW ALERT"); + } + + } + } else { + //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; + email_enabled=false; + } + break; + } + case NOTIFY_STATION_OFF: if (os.mqtt.enabled()) { diff --git a/main.h b/main.h index 35fce4890..f5c93ab35 100644 --- a/main.h +++ b/main.h @@ -35,4 +35,9 @@ void reset_all_stations_immediate(); void delete_log(char *name); void write_log(unsigned char type, time_os_t curr_time); void make_logfile_name(char *name); + +bool is_notif_enabled(uint16_t type); +uint16_t get_notif_enabled(); +void set_notif_enabled(uint16_t notif); + #endif // _MAIN_H diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index c7b89c425..8dda40030 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1541,6 +1541,11 @@ void server_change_options(OTF_PARAMS_DEF) if(oid==IOPT_BOOST_TIME) { v>>=2; } + if (oid==IOPT_NOTIF_ENABLE) { + set_notif_enabled(v); + continue; + } + if (v>=0 && v<=max_value) { os.iopts[oid] = v; } else { @@ -3244,10 +3249,10 @@ extern uint32_t ping_ok; fsinfo.maxPathLength, ping_ok, os.mqtt.connected(), - os.iopts[IOPT_NOTIF_ENABLE]); + get_notif_enabled()); #else - bfill.emit_p(PSTR("{\"status\":$D,\"mqtt\":$D,\"ifttt\":$D"), 1, os.mqtt.connected(), os.iopts[IOPT_NOTIF_ENABLE]); + bfill.emit_p(PSTR("{\"status\":$D,\"mqtt\":$D,\"ifttt\":$D"), 1, os.mqtt.connected(), get_notif_enabled()); #endif From 618722f43136267420ed78224733ccc1170a8898 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 22 Sep 2024 02:12:15 +0200 Subject: [PATCH 219/281] fixt crash on invalid influxdb config --- osinfluxdb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index f1a545892..2a85d1dca 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -81,7 +81,7 @@ void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { if (tmp_buffer[0] != '{') strcpy(tmp_buffer, "{}"); ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); - if (error || doc.isNull() || doc["en"] > 1) { + if (error || doc.isNull() || !doc.containsKey("en")) { if (error) { DEBUG_PRINT(F("influxdb: deserializeJson() failed: ")); DEBUG_PRINTLN(error.c_str()); From 3bbd9e37b1bb6b6e5346d6c9df139d3c17da0e5b Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 23 Sep 2024 00:19:36 +0200 Subject: [PATCH 220/281] Updated build scripts --- build2.sh | 15 ++------------- updater.sh | 5 +++++ 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/build2.sh b/build2.sh index 281e46cd2..403be4d00 100755 --- a/build2.sh +++ b/build2.sh @@ -2,17 +2,6 @@ echo "Compiling OSPi firmware..." - USEGPIO="" - GPIOLIB="" - - - if [ -h "/sys/class/gpio/gpiochip512" ]; then - echo "using libgpiod" - USEGPIO="-DLIBGPIOD" - GPIOLIB="-lgpiod" - fi - - ADS1115="" ADS1115FILES="" @@ -34,7 +23,7 @@ ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) ifx=$(ls external/influxdb-cpp/*.hpp) - g++ -o OpenSprinkler -DOSPI $USEGPIO $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ + g++ -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp \ smtp.c RCSwitch.cpp sensor*.cpp \ $ADS1115FILES $PCF8591FILES \ @@ -43,6 +32,6 @@ -Iexternal/OpenThings-Framework-Firmware-Library/ \ $otf \ $ifx osinfluxdb.cpp -Iexternal/influxdb-cpp/ \ - -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB + -lpthread -lmosquitto -lssl -lcrypto -li2c -lgpiod diff --git a/updater.sh b/updater.sh index eda30f0a4..75cfe3ab1 100755 --- a/updater.sh +++ b/updater.sh @@ -1,5 +1,10 @@ #! /bin/bash +# Get latest release from GitHub api +#latest=$(curl --silent "https://api.github.com/repos/OpenSprinklerShop/OpenSprinkler-Firmware/releases/latest" | +# grep '"tag_name":' | # Get tag line +# sed -E 's/.*"([^"]+)".*/\1/') +#git switch --detach $latest git pull ./build.sh -s ospi systemctl restart OpenSprinkler.service From a5ff2af0c4a838990d99ad61a6478d36ab59f886 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 23 Sep 2024 23:02:37 +0200 Subject: [PATCH 221/281] Fixt Trueber RS485 board with Offset dip position --- sensors.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 62fbdee2e..d72e0df51 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1453,8 +1453,7 @@ int read_sensor(Sensor_t *sensor, ulong time) { sensor->last_read = time; if (sensor->ip) return read_sensor_ip(sensor); #ifdef ESP8266 - if (sensor->port == 0) // 0 = Truebner RS485 Adapter - return read_sensor_rs485(sensor); + return read_sensor_rs485(sensor); #endif break; From 9d79c9a5ad37b6a14799add1402657510379ca07 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 23 Sep 2024 23:25:44 +0200 Subject: [PATCH 222/281] Re-added support for older 5.10 kernel gpio --- build2.sh | 15 ++- gpio.cpp | 270 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+), 2 deletions(-) diff --git a/build2.sh b/build2.sh index 403be4d00..281e46cd2 100755 --- a/build2.sh +++ b/build2.sh @@ -2,6 +2,17 @@ echo "Compiling OSPi firmware..." + USEGPIO="" + GPIOLIB="" + + + if [ -h "/sys/class/gpio/gpiochip512" ]; then + echo "using libgpiod" + USEGPIO="-DLIBGPIOD" + GPIOLIB="-lgpiod" + fi + + ADS1115="" ADS1115FILES="" @@ -23,7 +34,7 @@ ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) ifx=$(ls external/influxdb-cpp/*.hpp) - g++ -o OpenSprinkler -DOSPI $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ + g++ -o OpenSprinkler -DOSPI $USEGPIO $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp \ smtp.c RCSwitch.cpp sensor*.cpp \ $ADS1115FILES $PCF8591FILES \ @@ -32,6 +43,6 @@ -Iexternal/OpenThings-Framework-Firmware-Library/ \ $otf \ $ifx osinfluxdb.cpp -Iexternal/influxdb-cpp/ \ - -lpthread -lmosquitto -lssl -lcrypto -li2c -lgpiod + -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB diff --git a/gpio.cpp b/gpio.cpp index 4c06aeeb7..0edba9c24 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -177,6 +177,275 @@ unsigned char digitalReadExt(unsigned char pin) { #elif defined(OSPI) +#if !defined(LIBGPIOD) // use classic sysfs + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_MAX 64 +#define GPIO_MAX 64 + +// GPIO file descriptors +static int sysFds[GPIO_MAX] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +} ; + +// Interrupt service routine functions +static void (*isrFunctions [GPIO_MAX])(void); + +static volatile int pinPass = -1 ; +static pthread_mutex_t pinMutex ; + +/** Export gpio pin */ +static unsigned char GPIOExport(int pin) { + char buffer[BUFFER_MAX]; + int fd, len; + + fd = open("/sys/class/gpio/export", O_WRONLY); + if (fd < 0) { + DEBUG_PRINTLN("failed to open export for writing"); + return 0; + } + + len = snprintf(buffer, sizeof(buffer), "%d", pin); + write(fd, buffer, len); + close(fd); + return 1; +} + +#if 0 +/** Unexport gpio pin */ +static unsigned char GPIOUnexport(int pin) { + char buffer[BUFFER_MAX]; + int fd, len; + + fd = open("/sys/class/gpio/unexport", O_WRONLY); + if (fd < 0) { + DEBUG_PRINTLN("failed to open unexport for writing"); + return 0; + } + + len = snprintf(buffer, sizeof(buffer), "%d", pin); + write(fd, buffer, len); + close(fd); + return 1; +} +#endif + +/** Set interrupt edge mode */ +static unsigned char GPIOSetEdge(int pin, const char *edge) { + char path[BUFFER_MAX]; + int fd; + + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/edge", pin); + + fd = open(path, O_WRONLY); + if (fd < 0) { + DEBUG_PRINTLN("failed to open gpio edge for writing"); + return 0; + } + write(fd, edge, strlen(edge)+1); + close(fd); + return 1; +} + +/** Set pin mode, in or out */ +void pinMode(int pin, unsigned char mode) { + static const char dir_str[] = "in\0out"; + + char path[BUFFER_MAX]; + int fd; + + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/direction", pin); + + struct stat st; + if(stat(path, &st)) { + if (!GPIOExport(pin)) return; + } + + fd = open(path, O_WRONLY); + if (fd < 0) { + DEBUG_PRINTLN("failed to open gpio direction for writing"); + return; + } + + if (-1 == write(fd, &dir_str[(INPUT==mode)||(INPUT_PULLUP==mode)?0:3], (INPUT==mode)||(INPUT_PULLUP==mode)?2:3)) { + DEBUG_PRINTLN("failed to set direction"); + return; + } + + close(fd); +#if defined(OSPI) + if(mode==INPUT_PULLUP) { + char cmd[BUFFER_MAX]; + //snprintf(cmd, BUFFER_MAX, "gpio -g mode %d up", pin); + snprintf(cmd, BUFFER_MAX, "raspi-gpio set %d pu", pin); + system(cmd); + } +#endif + return; +} + +/** Open file for digital pin */ +int gpio_fd_open(int pin, int mode) { + char path[BUFFER_MAX]; + int fd; + + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); + fd = open(path, mode); + if (fd < 0) { + DEBUG_PRINTLN("failed to open gpio"); + return -1; + } + return fd; +} + +/** Close file */ +void gpio_fd_close(int fd) { + close(fd); +} + +/** Read digital value */ +unsigned char digitalRead(int pin) { + char value_str[3]; + + int fd = gpio_fd_open(pin, O_RDONLY); + if (fd < 0) { + return 0; + } + + if (read(fd, value_str, 3) < 0) { + DEBUG_PRINTLN("failed to read value"); + return 0; + } + + close(fd); + return atoi(value_str); +} + +/** Write digital value given file descriptor */ +void gpio_write(int fd, unsigned char value) { + static const char value_str[] = "01"; + + if (1 != write(fd, &value_str[LOW==value?0:1], 1)) { + DEBUG_PRINT("failed to write value on pin "); + } +} + +/** Write digital value */ +void digitalWrite(int pin, unsigned char value) { + int fd = gpio_fd_open(pin); + if (fd < 0) { + return; + } + gpio_write(fd, value); + close(fd); +} + +static int HiPri (const int pri) { + struct sched_param sched ; + + memset (&sched, 0, sizeof(sched)) ; + + if (pri > sched_get_priority_max (SCHED_RR)) + sched.sched_priority = sched_get_priority_max (SCHED_RR) ; + else + sched.sched_priority = pri ; + + return sched_setscheduler (0, SCHED_RR, &sched) ; +} + +static int waitForInterrupt (int pin, int mS) +{ + int fd, x ; + uint8_t c ; + struct pollfd polls ; + + if((fd=sysFds[pin]) < 0) + return -2; + + polls.fd = fd ; + polls.events = POLLPRI ; // Urgent data! + + x = poll (&polls, 1, mS) ; +// Do a dummy read to clear the interrupt +// A one character read appars to be enough. +// Followed by a seek to reset it. + + (void)read (fd, &c, 1); + lseek (fd, 0, SEEK_SET); + + return x ; +} + +static void *interruptHandler (void *arg) { + int myPin ; + + (void) HiPri (55) ; // Only effective if we run as root + + myPin = pinPass ; + pinPass = -1 ; + + for (;;) + if (waitForInterrupt (myPin, -1) > 0) + isrFunctions[myPin]() ; + + return NULL ; +} + +#include "utils.h" +/** Attach an interrupt function to pin */ +void attachInterrupt(int pin, const char* mode, void (*isr)(void)) { + if((pin<0)||(pin>GPIO_MAX)) { + DEBUG_PRINTLN("pin out of range"); + return; + } + + // set pin to INPUT mode and set interrupt edge mode + pinMode(pin, INPUT); + GPIOSetEdge(pin, mode); + + char path[BUFFER_MAX]; + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); + + // open gpio file + if(sysFds[pin]==-1) { + if((sysFds[pin]=open(path, O_RDWR))<0) { + DEBUG_PRINTLN("failed to open gpio value for reading"); + return; + } + } + + int count, i; + char c; + // clear any pending interrupts + ioctl (sysFds[pin], FIONREAD, &count) ; + for (i=0; i Date: Tue, 24 Sep 2024 00:03:40 +0200 Subject: [PATCH 223/281] Fixt Userdef Sensor: selecting a concrete unit --- sensors.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index d72e0df51..0403f828a 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -275,6 +275,7 @@ int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, sizeof(sensor->userdef_unit) - 1); sensor->flags = flags; if (assigned_unitid >= 0) sensor->assigned_unitid = assigned_unitid; + else sensor->assigned_unitid = 0; sensor_save(); return HTTP_RQT_SUCCESS; } @@ -306,6 +307,7 @@ int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, sizeof(new_sensor->userdef_unit) - 1); new_sensor->flags = flags; if (assigned_unitid >= 0) new_sensor->assigned_unitid = assigned_unitid; + else new_sensor->assigned_unitid = 0; if (last) { new_sensor->next = last->next; @@ -2188,7 +2190,6 @@ unsigned char getSensorUnitId(Sensor_t *sensor) { return UNIT_DEGREE; case SENSOR_AQUAPLUMB: return UNIT_PERCENT; - case SENSOR_USERDEF: case SENSOR_FREE_MEMORY: case SENSOR_FREE_STORE: return UNIT_USERDEF; @@ -2203,6 +2204,7 @@ unsigned char getSensorUnitId(Sensor_t *sensor) { case SENSOR_OSPI_ANALOG_SMT50_TEMP: return UNIT_DEGREE; #endif + case SENSOR_USERDEF: case SENSOR_MQTT: case SENSOR_REMOTE: return sensor->assigned_unitid > 0 ? sensor->assigned_unitid From 4d13090b62f033eb1b4bf58a858e71609c73fdc0 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 24 Sep 2024 00:11:21 +0200 Subject: [PATCH 224/281] Fixt OSPi build script --- build.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.sh b/build.sh index dc12d9e29..d9ac31ef4 100755 --- a/build.sh +++ b/build.sh @@ -46,6 +46,9 @@ fi echo "Building OpenSprinkler..." +/etc/init.d/OpenSprinkler.sh stop 2>/dev/null +service OpenSprinkler stop 2>/dev/null + #Git update submodules if git submodule status | grep --quiet '^-'; then From 838d03b16c47485e9c4ef29469c8d3f6bf7baa5b Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 24 Sep 2024 00:17:26 +0200 Subject: [PATCH 225/281] Another Build script fix OSPi --- build.sh | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.sh b/build.sh index d9ac31ef4..4617caa32 100755 --- a/build.sh +++ b/build.sh @@ -46,8 +46,11 @@ fi echo "Building OpenSprinkler..." -/etc/init.d/OpenSprinkler.sh stop 2>/dev/null -service OpenSprinkler stop 2>/dev/null +if [ -f /etc/init.d/OpenSprinkler.sh ]; then + /etc/init.d/OpenSprinkler.sh stop 2>/dev/null +else + service OpenSprinkler stop 2>/dev/null +fi #Git update submodules From 5b45bf38b3f723090c998136346c50d4360e44f6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 24 Sep 2024 00:25:04 +0200 Subject: [PATCH 226/281] Antoher Build script fix --- build.sh | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/build.sh b/build.sh index 4617caa32..10de83912 100755 --- a/build.sh +++ b/build.sh @@ -47,9 +47,9 @@ fi echo "Building OpenSprinkler..." if [ -f /etc/init.d/OpenSprinkler.sh ]; then - /etc/init.d/OpenSprinkler.sh stop 2>/dev/null + /etc/init.d/OpenSprinkler.sh stop else - service OpenSprinkler stop 2>/dev/null + systemctl stop OpenSprinkler fi #Git update submodules @@ -116,13 +116,12 @@ if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.service ] && [ -f startOpenSprin # Make file executable chmod +x startOpenSprinkler.sh - - # Reload systemd - systemctl daemon-reload - - # Enable and start the service - systemctl enable OpenSprinkler - systemctl start OpenSprinkler fi +# Reload systemd +systemctl daemon-reload + +# Enable and start the service +systemctl enable OpenSprinkler +systemctl start OpenSprinkler echo "Done!" From c9334b6796aae130cfa0be65b11b2d8168674b42 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 25 Sep 2024 00:19:49 +0200 Subject: [PATCH 227/281] fixt crash when not using influxdb --- opensprinkler_server.cpp | 13 ++----------- osinfluxdb.cpp | 19 ++++++++++++------- osinfluxdb.h | 1 + 3 files changed, 15 insertions(+), 18 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8dda40030..595565afd 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3573,17 +3573,8 @@ void server_influx_get(OTF_PARAMS_DEF) { } void server_influx_get_main() { - ArduinoJson::JsonDocument doc; - os.influxdb.get_influx_config(doc); - int enabled = doc["en"]; - const char *url = doc["url"]; - const uint16_t port = doc["port"]; - const char *org = doc["org"]; - const char *bucket = doc["bucket"]; - const char *token = doc["token"]; - - bfill.emit_p(PSTR("{\"en\":$D,\"url\":\"$S\",\"port\":$D,\"org\":\"$S\",\"bucket\":\"$S\",\"token\":\"$S\"}"), - enabled, url, port, org, bucket, token); + os.influxdb.get_influx_config(tmp_buffer); + bfill.emit_p(tmp_buffer); } typedef void (*URLHandler)(OTF_PARAMS_DEF); diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index f1a545892..d2fe9c06c 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -66,20 +66,25 @@ void OSInfluxDB::set_influx_config(const char *data) { initialized = false; } -void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { - //DEBUG_PRINTLN("Load influx config"); - tmp_buffer[0] = 0; +void OSInfluxDB::get_influx_config(char *json { + json[0] = 0; if (file_exists(INFLUX_CONFIG_FILE)) { - ulong size = file_read_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, TMP_BUFFER_SIZE*2); + ulong size = file_read_block(INFLUX_CONFIG_FILE, json, 0, TMP_BUFFER_SIZE*2); //DEBUG_PRINT(F("influx config size=")); //DEBUG_PRINTLN(size); - tmp_buffer[size] = 0; + json[size] = 0; //DEBUG_PRINT(F("influx config=")); //DEBUG_PRINTLN(tmp_buffer); } - if (tmp_buffer[0] != '{') - strcpy(tmp_buffer, "{}"); + if (json[0] != '{') + strcpy(json, "{\"en\":0}"); + +} + +void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { + //DEBUG_PRINTLN("Load influx config"); + get_influx_config(tmp_buffer); ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); if (error || doc.isNull() || doc["en"] > 1) { if (error) { diff --git a/osinfluxdb.h b/osinfluxdb.h index e6885a388..808fe708b 100644 --- a/osinfluxdb.h +++ b/osinfluxdb.h @@ -48,6 +48,7 @@ class OSInfluxDB { void set_influx_config(ArduinoJson::JsonDocument &doc); void set_influx_config(const char *json); void get_influx_config(ArduinoJson::JsonDocument &doc); + void get_influx_config(char *json); bool isEnabled(); #if defined(ESP8266) void write_influx_data(Point &sensor_data); From 1ead927525e5f73495c33fa1a81a8407dc4da683 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 25 Sep 2024 00:20:38 +0200 Subject: [PATCH 228/281] fixt typo --- osinfluxdb.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index d2fe9c06c..90dedec90 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -66,7 +66,7 @@ void OSInfluxDB::set_influx_config(const char *data) { initialized = false; } -void OSInfluxDB::get_influx_config(char *json { +void OSInfluxDB::get_influx_config(char *json) { json[0] = 0; if (file_exists(INFLUX_CONFIG_FILE)) { From c00ef809eeac9e408fbe9b8b2714cb98037139dc Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 5 Oct 2024 00:21:42 +0200 Subject: [PATCH 229/281] added option for deleting sensor log data --- opensprinkler_server.cpp | 16 +++++++++++----- sensors.cpp | 12 ++++++++---- sensors.h | 2 +- 3 files changed, 20 insertions(+), 10 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 595565afd..8ea48e1f2 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2848,6 +2848,8 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { bool use_under = false; double over = 0; bool use_over = false; + ulong before = 0; + ulong after = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Filter log for sensor-nr log = atoi(tmp_buffer); @@ -2857,6 +2859,10 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { use_under = sscanf(tmp_buffer, "%lf", &under) == 1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("over"), true)) // values higher than use_over = sscanf(tmp_buffer, "%lf", &over) == 1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // values higher than + sscanf(tmp_buffer, "%lf", &before); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // values higher than + sscanf(tmp_buffer, "%lf", &after); DEBUG_PRINTLN(F("server_sensorlog_clear")); @@ -2869,14 +2875,14 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { print_header(); #endif - if (nr > 0 || use_under || use_over) { + if (nr > 0 || use_under || use_over || before || after) { ulong n = 0; if (log == -1) { - n += sensorlog_clear_sensor(nr, LOG_STD, use_under, under, use_over, over); - n += sensorlog_clear_sensor(nr, LOG_WEEK, use_under, under, use_over, over); - n += sensorlog_clear_sensor(nr, LOG_MONTH, use_under, under, use_over, over); + n += sensorlog_clear_sensor(nr, LOG_STD, use_under, under, use_over, over, before, after); + n += sensorlog_clear_sensor(nr, LOG_WEEK, use_under, under, use_over, over, before, after); + n += sensorlog_clear_sensor(nr, LOG_MONTH, use_under, under, use_over, over, before, after); } else { - n += sensorlog_clear_sensor(nr, log, use_under, under, use_over, over); + n += sensorlog_clear_sensor(nr, log, use_under, under, use_over, over, before, after); } bfill.emit_p(PSTR("{\"deleted\":$L}"), n); } else { diff --git a/sensors.cpp b/sensors.cpp index 62fbdee2e..813816c1e 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -572,7 +572,7 @@ void sensorlog_clear(bool std, bool week, bool month) { } ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, - double under, bool use_over, double over) { + double under, bool use_over, double over, time_t before, time_t after) { SensorLog_t sensorlog; checkLogSwitch(log); const char *flast = getlogfile2(log); @@ -595,9 +595,13 @@ ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, SENSORLOG_STORE_SIZE); if (result == 0) break; if (sensorlog.nr > 0 && (sensorlog.nr == sensorNr || sensorNr == 0)) { - boolean found = true; - if (use_under && sensorlog.data > under) found = false; - if (use_over && sensorlog.data < over) found = false; + boolean found = false; + if (use_under && sensorlog.data < under) found = true; + if (use_over && sensorlog.data > over) found = true; + if (before && sensorlog.time < before) found = true; + if (after && sensorlog.time > after) found = true; + if (sensorNr > 0 && sensorlog.nr != sensorNr) found = false; + if (sensorNr > 0 && sensorlog.nr == sensorNr && !use_under && !use_over && !before && !after) found = true; if (found) { sensorlog.nr = 0; file_write_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, diff --git a/sensors.h b/sensors.h index a1b767093..834286616 100644 --- a/sensors.h +++ b/sensors.h @@ -315,7 +315,7 @@ bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time); void sensorlog_clear_all(); void sensorlog_clear(bool std, bool week, bool month); ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, - double under, bool use_over, double over); + double under, bool use_over, double over, time_t before, time_t after); SensorLog_t *sensorlog_load(uint8_t log, ulong pos); SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t *sensorlog); int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t *sensorlog); From 97919278e6d49f97cf5e8087468f7e4befaeb7fd Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 5 Oct 2024 01:12:52 +0200 Subject: [PATCH 230/281] Fixt broken influxdb config file --- defines.h | 2 +- osinfluxdb.cpp | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/defines.h b/defines.h index 190e99836..a6181745b 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index 2f1c1444c..36fa47072 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -42,6 +42,7 @@ void OSInfluxDB::set_influx_config(int enabled, char *url, uint16_t port, char * void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { size_t size = ArduinoJson::serializeJson(doc, tmp_buffer); + remove_file(INFLUX_CONFIG_FILE); file_write_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, size); if (client) { @@ -54,6 +55,7 @@ void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { void OSInfluxDB::set_influx_config(const char *data) { size_t size = strlen(data); + remove_file(INFLUX_CONFIG_FILE); file_write_block(INFLUX_CONFIG_FILE, "{", 0, 1); file_write_block(INFLUX_CONFIG_FILE, data, 1, size); file_write_block(INFLUX_CONFIG_FILE, "}", size+1, 1); @@ -77,9 +79,10 @@ void OSInfluxDB::get_influx_config(char *json) { //DEBUG_PRINT(F("influx config=")); //DEBUG_PRINTLN(tmp_buffer); } - if (json[0] != '{') + if (json[0] != '{' || strchr(json, '}') != strrchr(json, '}')) { strcpy(json, "{\"en\":0}"); - + set_influx_config(json); + } } void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { @@ -97,6 +100,7 @@ void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { doc["org"] = ""; doc["bucket"] = ""; doc["token"] = ""; + set_influx_config(doc); } enabled = doc["en"]; initialized = true; From d5baa7d0d03ea9aed944cab517a86813b910cad9 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 6 Oct 2024 01:56:50 +0200 Subject: [PATCH 231/281] added more influxdb : added full set of notifications --- main.cpp | 172 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 164 insertions(+), 8 deletions(-) diff --git a/main.cpp b/main.cpp index 4cec71783..94dc75da5 100644 --- a/main.cpp +++ b/main.cpp @@ -1491,6 +1491,148 @@ void ip2string(char* str, size_t str_len, unsigned char ip[4]) { #define PUSH_TOPIC_LEN 120 #define PUSH_PAYLOAD_LEN TMP_BUFFER_SIZE +#if defined(ESP8266) +void influxdb_send_state(const char *name, int state) { + if (!os.influxdb.isEnabled()) return; + Point data("opensprinkler"); + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + data.addTag("devicename", tmp_buffer); + data.addTag("name", name); + data.addField("state", state); + os.influxdb.write_influx_data(data); +} + +void influxdb_send_station(const char *name, uint32_t station, int state) { + if (!os.influxdb.isEnabled()) return; + Point data("opensprinkler"); + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + data.addTag("devicename", tmp_buffer); + data.addTag("name", name); + data.addField("station", station); + data.addField("state", state); + os.influxdb.write_influx_data(data); +} + +void influxdb_send_program(const char *name, uint32_t nr, float level) { + if (!os.influxdb.isEnabled()) return; + Point data("opensprinkler"); + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + data.addTag("devicename", tmp_buffer); + data.addTag("name", name); + data.addField("program", nr); + data.addField("level", level); + os.influxdb.write_influx_data(data); +} + +void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { + if (!os.influxdb.isEnabled()) return; + Point data("opensprinkler"); + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + data.addTag("devicename", tmp_buffer); + data.addTag("name", name); + data.addField("count", count); + data.addField("volume", volume); + os.influxdb.write_influx_data(data); +} + +void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { + if (!os.influxdb.isEnabled()) return; + Point data("opensprinkler"); + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + data.addTag("devicename", tmp_buffer); + data.addTag("name", name); + data.addField("station", station); + data.addField("flowrate", (double)(f1)+(double)(f2)/100); + data.addField("duration", f3); + data.addField("alert_setpoint", (double)(f4)+(double)(f5)/100); + os.influxdb.write_influx_data(data); +} +#else +void influxdb_send_state(const char *name, int state) { + influxdb_cpp::server_info * client = os.influxdb.get_client(); + if (!client) + return; + + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp_buffer) + .tag("name", name) + .field("state", state) + .timestamp(millis()) + .post_http(*client); +} + +void influxdb_send_station(const char *name, uint32_t station, int state) { + influxdb_cpp::server_info * client = os.influxdb.get_client(); + if (!client) + return; + + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp_buffer) + .tag("name", name) + .field("station", station) + .field("state", state) + .timestamp(millis()) + .post_http(*client); +} + +void influxdb_send_program(const char *name, uint32_t nr, float level) { + influxdb_cpp::server_info * client = os.influxdb.get_client(); + if (!client) + return; + + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp_buffer) + .tag("name", name) + .field("program", nr) + .field("level", level) + .timestamp(millis()) + .post_http(*client); +} + +void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { + influxdb_cpp::server_info * client = os.influxdb.get_client(); + if (!client) + return; + + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp_buffer) + .tag("name", name) + .field("count", count) + .field("volume", volume) + .timestamp(millis()) + .post_http(*client); +} + +void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { + if (!os.influxdb.isEnabled()) return; + influxdb_cpp::server_info * client = os.influxdb.get_client(); + if (!client) + return; + + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp_buffer) + .tag("name", name) + .field("station", station) + .field("flow_rate", (double)(f1)+(double)(f2)/100) + .field("duration", f3) + .field("alert_setpoint", (double)(f4)+(double)(f5)/100) + .timestamp(millis()) + .post_http(*client); +} +#endif + + + void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { static char topic[PUSH_TOPIC_LEN+1]; static char payload[PUSH_PAYLOAD_LEN+1]; @@ -1549,6 +1691,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { #endif bool email_enabled = false; + bool influxdb_enabled = os.influxdb.isEnabled(); #if defined(SUPPORT_EMAIL) if(!email_en){ email_enabled = false; @@ -1558,7 +1701,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { #endif // if none if enabled, return here - if ((!ifttt_enabled) && (!email_enabled) && (!os.mqtt.enabled())) + if ((!ifttt_enabled) && (!email_enabled) && (!os.mqtt.enabled()) && (!influxdb_enabled)) return; if (ifttt_enabled || email_enabled) { @@ -1589,9 +1732,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":1,\"duration\":%d}"), (int)fval); } } - - // todo: add IFTTT and email support for this event as well. - // currently no support due to the number of events exceeds 8 so need to use more than 1 byte + influxdb_send_station("station", lval, 1); break; case NOTIFY_FLOW_ALERT:{ @@ -1642,12 +1783,17 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { // If flow_alert_flag is true, format the appropriate messages, else don't send alert if (flow_alert_flag) { + int f1 = (int)(flow_last_gpm*flow_pulse_rate_factor); + int f2 = (int)((flow_last_gpm*flow_pulse_rate_factor) * 100) % 100; + int f3 = (int)fval; + int f4 = (int)flow_gpm_alert_setpoint; + int f5 = (int)(flow_gpm_alert_setpoint * 100) % 100; + if (os.mqtt.enabled()) { //Format mqtt message snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d/alert/flow"), lval); snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%d.%02d,\"duration\":%d,\"alert_setpoint\":%d.%02d}"), - (int)(flow_last_gpm*flow_pulse_rate_factor), (int)((flow_last_gpm*flow_pulse_rate_factor) * 100) % 100, - (int)fval, (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint * 100) % 100); + f1, f2, f3, f4, f5); } @@ -1678,14 +1824,16 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { strcat_P(postval, PSTR("

FLOW ALERT!")); size_t len = strlen(postval); snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR("
Flow rate: %d.%02d
Flow Alert Setpoint: %d.%02d"), - (int)(flow_last_gpm*flow_pulse_rate_factor), (int)((flow_last_gpm*flow_pulse_rate_factor) * 100) % 100, - (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint * 100) % 100); + f1, f2, f4, f5); if(email_enabled) { email_message.subject += PSTR("- FLOW ALERT"); } } + + influxdb_send_flowalert("flowalert", lval, f1, f2, f3, f4, f5); + } else { //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 @@ -1693,6 +1841,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { ifttt_enabled=false; email_enabled=false; } + break; } @@ -1723,6 +1872,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { } if(email_enabled) { email_message.subject += PSTR("station event"); } } + influxdb_send_station("station", lval, 0); break; case NOTIFY_PROGRAM_SCHED: @@ -1740,6 +1890,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR(" with %d%% water level."), (int)fval); if(email_enabled) { email_message.subject += PSTR("program event"); } } + influxdb_send_program("program sched", lval, fval); break; case NOTIFY_SENSOR1: @@ -1753,6 +1904,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); if(email_enabled) { email_message.subject += PSTR("sensor 1 event"); } } + influxdb_send_state("sensor1", (int)fval); break; case NOTIFY_SENSOR2: @@ -1766,6 +1918,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); if(email_enabled) { email_message.subject += PSTR("sensor 2 event"); } } + influxdb_send_state("sensor2", (int)fval); break; case NOTIFY_RAINDELAY: @@ -1779,6 +1932,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); if(email_enabled) { email_message.subject += PSTR("rain delay event"); } } + influxdb_send_state("raindelay", (int)fval); break; case NOTIFY_FLOWSENSOR: @@ -1795,6 +1949,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR("Flow count: %u, volume: %d.%02d"), lval, (int)volume/100, (int)volume%100); if(email_enabled) { email_message.subject += PSTR("flow sensor event"); } } + influxdb_send_flowsensor("flowsensor", lval, (float)volume/100); break; case NOTIFY_WEATHER_UPDATE: @@ -1818,6 +1973,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { } if(email_enabled) { email_message.subject += PSTR("weather update event"); } } + influxdb_send_state("waterlevel", lval); break; case NOTIFY_REBOOT: From 4d438d6bdab1f0bc78bb064043303aae3165eefe Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 6 Oct 2024 02:07:32 +0200 Subject: [PATCH 232/281] fixt type+typo for ospi --- main.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/main.cpp b/main.cpp index 94dc75da5..1b179862f 100644 --- a/main.cpp +++ b/main.cpp @@ -1549,6 +1549,7 @@ void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, } #else void influxdb_send_state(const char *name, int state) { + if (!os.influxdb.isEnabled()) return; influxdb_cpp::server_info * client = os.influxdb.get_client(); if (!client) return; @@ -1564,6 +1565,7 @@ void influxdb_send_state(const char *name, int state) { } void influxdb_send_station(const char *name, uint32_t station, int state) { + if (!os.influxdb.isEnabled()) return; influxdb_cpp::server_info * client = os.influxdb.get_client(); if (!client) return; @@ -1573,13 +1575,14 @@ void influxdb_send_station(const char *name, uint32_t station, int state) { .meas("opensprinkler") .tag("devicename", tmp_buffer) .tag("name", name) - .field("station", station) + .field("station", (int)station) .field("state", state) .timestamp(millis()) .post_http(*client); } void influxdb_send_program(const char *name, uint32_t nr, float level) { + if (!os.influxdb.isEnabled()) return; influxdb_cpp::server_info * client = os.influxdb.get_client(); if (!client) return; @@ -1589,13 +1592,14 @@ void influxdb_send_program(const char *name, uint32_t nr, float level) { .meas("opensprinkler") .tag("devicename", tmp_buffer) .tag("name", name) - .field("program", nr) + .field("program", (int)nr) .field("level", level) .timestamp(millis()) .post_http(*client); } void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { + if (!os.influxdb.isEnabled()) return; influxdb_cpp::server_info * client = os.influxdb.get_client(); if (!client) return; @@ -1605,14 +1609,14 @@ void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { .meas("opensprinkler") .tag("devicename", tmp_buffer) .tag("name", name) - .field("count", count) + .field("count", (int)count) .field("volume", volume) .timestamp(millis()) .post_http(*client); } void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { - if (!os.influxdb.isEnabled()) return; + if (!os.influxdb.isEnabled()) return; influxdb_cpp::server_info * client = os.influxdb.get_client(); if (!client) return; @@ -1622,8 +1626,8 @@ void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, .meas("opensprinkler") .tag("devicename", tmp_buffer) .tag("name", name) - .field("station", station) - .field("flow_rate", (double)(f1)+(double)(f2)/100) + .field("station", (int)(station)) + .field("flowrate", (double)(f1)+(double)(f2)/100) .field("duration", f3) .field("alert_setpoint", (double)(f4)+(double)(f5)/100) .timestamp(millis()) From 81c165e2703be06d12afe08d3c7784a62fc1e103 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 7 Oct 2024 22:48:43 +0200 Subject: [PATCH 233/281] Updated Sensor API.txt --- Sensor API.txt | 87 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 61 insertions(+), 26 deletions(-) diff --git a/Sensor API.txt b/Sensor API.txt index 15f832e7a..4484658e1 100644 --- a/Sensor API.txt +++ b/Sensor API.txt @@ -37,30 +37,53 @@ A8 = 168 C0 = 192 = 192.168.000.254 -For the Truebner SMT100 RS485 Modbus you need a RS485 Modbus RTU over TCP converter. +For the Truebner SMT100 RS485 Modbus you need the Truebner RS485 Adapter for OpenSprinkler. Set ip/port for the converter, e.a PUSR USR-W610 in transparent modus. For the analog ports of the extension board (including SMT50) use id 0..7 -type: -SENSOR_NONE 0 None or deleted sensor -SENSOR_SMT100_MODBUS_RTU_MOIS 1 Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode -SENSOR_SMT100_MODBUS_RTU_TEMP 2 Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode -SENSOR_ANALOG_EXTENSION_BOARD 10 New OpenSprinkler analog extension board x8 - voltage mode 0..4V -SENSOR_ANALOG_EXTENSION_BOARD_P 11 New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% -SENSOR_SMT50_MOIS 15 New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 -SENSOR_SMT50_TEMP 16 New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 -SENSOR_OSPI_ANALOG_INPUTS 20 Old OSPi analog input -SENSOR_VH400 30 New OpenSprinkler analog extension board x8 - Vegetronix VH400 -SENSOR_THERM200 31 New OpenSprinkler analog extension board x8 - Vegetronix THERM200 -SENSOR_AQUAPLUMB 32 New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb -SENSOR_USERDEF 49 New OpenSprinkler analog extension board x8 - User defined sensor -SENSOR_REMOTE 100 Remote sensor of an remote opensprinkler -SENSOR_GROUP_MIN 1000 Sensor group with min value -SENSOR_GROUP_MAX 1001 Sensor group with max value -SENSOR_GROUP_AVG 1002 Sensor group with avg value -SENSOR_GROUP_SUM 1003 Sensor group with sum value - +// Sensor types: +SENSOR_NONE 0 // None or deleted sensor +SENSOR_SMT100_MOIS 1 // Truebner SMT100 RS485, moisture mode +SENSOR_SMT100_TEMP 2 // Truebner SMT100 RS485, temperature mode +SENSOR_SMT100_PMTY 3 // Truebner SMT100 RS485, permittivity mode +SENSOR_ANALOG_EXTENSION_BOARD 10 // New OpenSprinkler analog extension board x8 - voltage mode 0..4V +SENSOR_ANALOG_EXTENSION_BOARD_P 11 // New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% +SENSOR_SMT50_MOIS 15 // New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 +SENSOR_SMT50_TEMP 16 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +SENSOR_SMT100_ANALOG_MOIS 17 // New OpenSprinkler analog extension board x8 - SMT100 VWC [%] = (U * 100) : 3 +SENSOR_SMT100_ANALOG_TEMP 18 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U * 100) : 3 - 40 + +SENSOR_VH400 30 // New OpenSprinkler analog extension board x8 - Vegetronix VH400 +SENSOR_THERM200 31 // New OpenSprinkler analog extension board x8 - Vegetronix THERM200 +SENSOR_AQUAPLUMB 32 // New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb + +SENSOR_USERDEF 49 // New OpenSprinkler analog extension board x8 - User defined sensor + +SENSOR_OSPI_ANALOG 50 // Old OSPi analog input - voltage mode 0..3.3V +SENSOR_OSPI_ANALOG_P 51 // Old OSPi analog input - percent 0..3.3V to 0...100% +SENSOR_OSPI_ANALOG_SMT50_MOIS 52 // Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 +SENSOR_OSPI_ANALOG_SMT50_TEMP 53 // Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 + +SENSOR_MQTT 90 // subscribe to a MQTT server and query a value + +SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler +SENSOR_WEATHER_TEMP_F 101 // Weather service - temperature (Fahrenheit) +SENSOR_WEATHER_TEMP_C 102 // Weather service - temperature (Celcius) +SENSOR_WEATHER_HUM 103 // Weather service - humidity (%) +SENSOR_WEATHER_PRECIP_IN 105 // Weather service - precip (inch) +SENSOR_WEATHER_PRECIP_MM 106 // Weather service - precip (mm) +SENSOR_WEATHER_WIND_MPH 107 // Weather service - wind (mph) +SENSOR_WEATHER_WIND_KMH 108 // Weather service - wind (kmh) + +SENSOR_GROUP_MIN 1000 // Sensor group with min value +SENSOR_GROUP_MAX 1001 // Sensor group with max value +SENSOR_GROUP_AVG 1002 // Sensor group with avg value +SENSOR_GROUP_SUM 1003 // Sensor group with sum value + +//Diagnostic +SENSOR_FREE_MEMORY 10000 //Free memory +SENSOR_FREE_STORE 10001 //Free storage List Sensors (sl): lists the current sensors examples: @@ -94,6 +117,18 @@ Clear Sensor Log (sn): clears the sensor log examples: http:///sn?pw= +http:///sn?pw=&log=1 + +Parameters: +log=[1|2|3]: 0=Std log, 1=Week log, 2=Month log +nr=n: Sensor-nr +under=v: delete only records with values under v +over=v: delete only records with values over v +before=t: delete only records with timestamp before t +after=t: delete only records with timestamp after t + v is a float value with point as decimal separator + t is a long integer value with an unix timestamp, use this to decode: + https://www.epochconverter.com/ Program adjustments (sb): defines program adjustments @@ -106,12 +141,12 @@ examples: http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 type: -#define PROG_DELETE 0 //deleted -#define PROG_LINEAR 1 //formula see above -#define PROG_DIGITAL_MIN 2 //under or equal min : factor1 else factor2 -#define PROG_DIGITAL_MAX 3 //over or equal max : factor2 else factor1 -#define PROG_DIGITAL_MINMAX 4 //under min or over max : factor1 else factor2 -#define PROG_NONE 99 //No adjustment +PROG_DELETE 0 //deleted +PROG_LINEAR 1 //formula see above +PROG_DIGITAL_MIN 2 //under or equal min : factor1 else factor2 +PROG_DIGITAL_MAX 3 //over or equal max : factor2 else factor1 +PROG_DIGITAL_MINMAX 4 //under min or over max : factor1 else factor2 +PROG_NONE 99 //No adjustment formula: min max factor1 factor2 From 135e9f3497c496ada1a4fbeb6a82762e000f897e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 13 Oct 2024 23:19:53 +0200 Subject: [PATCH 234/281] Added OSPi Support for RS485 Modbus USB Adapter --- build.sh | 2 +- build2.sh | 2 +- sensors.cpp | 201 +++++++++++++++++++++++++++++++++++++++------------- 3 files changed, 152 insertions(+), 53 deletions(-) diff --git a/build.sh b/build.sh index 10de83912..e4e3244e7 100755 --- a/build.sh +++ b/build.sh @@ -82,7 +82,7 @@ if [ "$1" == "demo" ]; then else echo "Installing required libraries..." apt-get update - apt-get install -y libmosquitto-dev libi2c-dev libssl-dev libgpiod-dev gpiod + apt-get install -y libmosquitto-dev libi2c-dev libssl-dev libgpiod-dev gpiod libmodbus-dev enable_i2c ./build2.sh diff --git a/build2.sh b/build2.sh index 281e46cd2..85f1103eb 100755 --- a/build2.sh +++ b/build2.sh @@ -43,6 +43,6 @@ -Iexternal/OpenThings-Framework-Firmware-Library/ \ $otf \ $ifx osinfluxdb.cpp -Iexternal/influxdb-cpp/ \ - -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB + -lpthread -lmosquitto -lssl -lcrypto -li2c -lmodbus $GPIOLIB diff --git a/sensors.cpp b/sensors.cpp index cd26cef35..317c6cd02 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -26,6 +26,13 @@ #include "OpenSprinkler.h" #ifdef ESP8266 #include "Wire.h" +#else +#include +#include +#include +#include +#include +#include #endif #include "defines.h" #include "opensprinkler_server.h" @@ -41,6 +48,7 @@ #include "sensor_ospi_pcf8591.h" #endif +#define MAX_RS485_DEVICES 4 unsigned char findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const char *key, bool key_in_pgm = false, uint8_t *keyfound = NULL); @@ -62,7 +70,10 @@ static ProgSensorAdjust_t *progSensorAdjusts = NULL; // modbus transaction id static uint16_t modbusTcpId = 0; -static uint i2c_rs485_allocated[4]; +static uint i2c_rs485_allocated[MAX_RS485_DEVICES]; +#ifndef ESP8266 +static modbus_t * ttyDevices[MAX_RS485_DEVICES]; +#endif const char *sensor_unitNames[]{ "", "%", "°C", "°F", "V", "%", "in", @@ -169,12 +180,59 @@ void sensor_api_init(boolean detect_boards) { sensor_load(); prog_adjust_load(); sensor_mqtt_init(); +#ifndef ESP8266 + //Read rs485 file. Details see below + std::ifstream file; + file.open("rs485", std::ifstream::in); + if (!file.fail()) { + std::string tty; + int idx = 0; + int n = 0; + DEBUG_PRINTLN(F("Opening USB RS485 Adapters:")); + while (std::getline(file, tty)) { + modbus_t * ctx = modbus_new_rtu(tty.c_str(), 9600, 'E', 8, 1); + DEBUG_PRINT(idx); + DEBUG_PRINT(": "); + DEBUG_PRINTLN(tty.c_str()); + + //unavailable on Raspi? modbus_enable_quirks(ctx, MODBUS_QUIRK_MAX_SLAVE); + modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485); + modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_NONE); // we use auto RTS function by the HAT + modbus_set_response_timeout(ctx, 1, 500000); // 1.5s + if (modbus_connect(ctx) == -1) { + modbus_free(ctx); + } else { + n++; + ttyDevices[idx] = ctx; + #ifdef ENABLE_DEBUG + modbus_set_debug(ctx, TRUE); + DEBUG_PRINTLN(F("DEBUG ENABLED")); + #endif + } + idx++; + if (idx >= MAX_RS485_DEVICES) + break; + } + DEBUG_PRINT(F("Found ")); + DEBUG_PRINT(n); + DEBUG_PRINTLN(F(" RS485 Adapters")); + } +#endif } void sensor_save_all() { sensor_save(); prog_adjust_save(); SensorUrl_save(); +#ifndef ESP8266 + for (int i = 0; i < MAX_RS485_DEVICES; i++) { + if (ttyDevices[i]) { + modbus_close(ttyDevices[i]); + modbus_free(ttyDevices[i]); + } + ttyDevices[i] = NULL; + } +#endif } /** @@ -1174,8 +1232,8 @@ int read_sensor_http(Sensor_t *sensor, ulong time) { */ int read_sensor_rs485(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_rs485")); - int device = sensor->port % 4; - if ((asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) + int device = sensor->port; + if (device >= MAX_RS485_DEVICES || (asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) return HTTP_RQT_NOT_RECEIVED; if (i2c_rs485_allocated[device] > 0 && i2c_rs485_allocated[device] != sensor->nr) { @@ -1244,6 +1302,52 @@ int read_sensor_rs485(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_rs485: exit")); return HTTP_RQT_NOT_RECEIVED; } +#else +/** + * USB RS485 Adapter + * Howto use: Create a file rs485 inside the OpenSprinkler-Firmware directory, enter the USB-TTY connections here. + * For example: + * /dev/ttyUSB0 + * /dev/ttyUSB1 + * You can use multiple rs485 adapters, every line increments the "port" index, starting with 0 for the first line + */ + +/** + * @brief Raspberry PI RS485 Interface + * + * @param sensor + * @return int + */ +int read_sensor_rs485(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_rs485")); + int device = sensor->port; + if (device >= MAX_RS485_DEVICES || !ttyDevices[device]) + return HTTP_RQT_NOT_RECEIVED; + + DEBUG_PRINTLN(F("read_sensor_rs485: check-ok")); + + uint8_t buffer[10]; + uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 + : sensor->type == SENSOR_SMT100_MOIS ? 0x01 + : 0x02; + uint16_t tab_reg[3] = {0}; + modbus_set_slave(ttyDevices[device], sensor->id); + if (modbus_read_registers(ttyDevices[device], type, 2, tab_reg) > 0) { + uint16_t data = tab_reg[0]; + DEBUG_PRINTF("read_sensor_rs485: result: %d - %d\n", sensor->id, data); + double value = + sensor->type == SENSOR_SMT100_TEMP + ? (data / 100.0) - 100.0 + : (sensor->type == SENSOR_SMT100_MOIS ? data / 100.0 : data); + sensor->last_native_data = data; + sensor->last_data = value; + DEBUG_PRINTLN(sensor->last_data); + sensor->flags.data_ok = true; + return HTTP_RQT_SUCCESS; + } + DEBUG_PRINTLN(F("read_sensor_rs485: exit")); + return HTTP_RQT_NOT_RECEIVED; +} #endif @@ -1305,48 +1409,21 @@ int read_sensor_ip(Sensor_t *sensor) { else modbusTcpId++; + uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 + : sensor->type == SENSOR_SMT100_MOIS ? 0x01 + : 0x02; buffer[0] = (0xFF00 & modbusTcpId) >> 8; buffer[1] = (0x00FF & modbusTcpId); buffer[2] = 0; buffer[3] = 0; buffer[4] = 0; buffer[5] = 6; // len - - switch (sensor->type) { - case SENSOR_SMT100_MOIS: - buffer[6] = sensor->id; // Modbus ID - buffer[7] = 0x03; // Read Holding Registers - buffer[8] = 0x00; - buffer[9] = 0x01; // soil moisture is at address 0x0001 - buffer[10] = 0x00; - buffer[11] = 0x01; // Number of registers to read (soil moisture value is - // one 16-bit register) - break; - - case SENSOR_SMT100_TEMP: - buffer[6] = sensor->id; - buffer[7] = 0x03; // Read Holding Registers - buffer[8] = 0x00; - buffer[9] = 0x00; // temperature is at address 0x0000 - buffer[10] = 0x00; - buffer[11] = 0x01; // Number of registers to read (temperature value is - // one 16-bit register) - break; - - case SENSOR_SMT100_PMTY: - buffer[6] = sensor->id; - buffer[7] = 0x03; // Read Holding Registers - buffer[8] = 0x00; - buffer[9] = 0x02; // permittivity is at address 0x0002 - buffer[10] = 0x00; - buffer[11] = 0x01; // Number of registers to read (permittivity value is - // one 16-bit register) - break; - - default: - client->stop(); - return HTTP_RQT_NOT_RECEIVED; - } + buffer[6] = sensor->id; // Modbus ID + buffer[7] = 0x03; // Read Holding Registers + buffer[8] = 0x00; + buffer[9] = type; + buffer[10] = 0x00; + buffer[11] = 0x01; client->write(buffer, 12); #if defined(ESP8266) @@ -1458,9 +1535,7 @@ int read_sensor(Sensor_t *sensor, ulong time) { //DEBUG_PRINTLN(sensor->name); sensor->last_read = time; if (sensor->ip) return read_sensor_ip(sensor); -#ifdef ESP8266 return read_sensor_rs485(sensor); -#endif break; #if defined(ARDUINO) @@ -1732,10 +1807,10 @@ int set_sensor_address_ip(Sensor_t *sensor, uint8_t new_address) { * @param new_address * @return int */ -int set_sensor_address_i2c(Sensor_t *sensor, uint8_t new_address) { - DEBUG_PRINTLN(F("set_sensor_address_i2c")); - int device = sensor->port % 4; - if ((asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) +int set_sensor_address_rs485(Sensor_t *sensor, uint8_t new_address) { + DEBUG_PRINTLN(F("set_sensor_address_rs485")); + int device = sensor->port; + if (device >= MAX_RS485_DEVICES || (asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) return HTTP_RQT_NOT_RECEIVED; if (i2c_rs485_allocated[device] > 0) { @@ -1760,6 +1835,35 @@ int set_sensor_address_i2c(Sensor_t *sensor, uint8_t new_address) { } return HTTP_RQT_NOT_RECEIVED; } +#else +/** + * @brief Raspberry PI RS485 Interface - Set SMT100 Sensor address + * + * @param sensor + * @return int + */ +int set_sensor_address_rs485(Sensor_t *sensor, uint8_t new_address) { + DEBUG_PRINTLN(F("set_sensor_address_rs485")); + int device = sensor->port; + if (device >= MAX_RS485_DEVICES || !ttyDevices[device]) + return HTTP_RQT_NOT_RECEIVED; + + + uint8_t request[10]; + request[0] = 0xFD; //253=Truebner Broadcast + request[1] = 0x06; //Write + request[2] = 0x00; + request[3] = 0x04; //Register address + request[4] = 0x00; + request[5] = new_address; + + if (modbus_send_raw_request(ttyDevices[device], request, 6) > 0) { + modbus_flush(ttyDevices[device]); + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} + #endif int set_sensor_address(Sensor_t *sensor, uint8_t new_address) { @@ -1772,12 +1876,7 @@ int set_sensor_address(Sensor_t *sensor, uint8_t new_address) { sensor->flags.data_ok = false; if (sensor->ip && sensor->port) return set_sensor_address_ip(sensor, new_address); -#ifdef ESP8266 - if (!sensor->ip && sensor->port == 0) - return set_sensor_address_i2c(sensor, new_address); -#endif - sensor->flags.enable = false; - return HTTP_RQT_NOT_RECEIVED; + return set_sensor_address_rs485(sensor, new_address); } return HTTP_RQT_CONNECT_ERR; } From 7a6d113b72c0491ddb6ff1ad0986c66ebdaf839d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 13 Oct 2024 23:21:23 +0200 Subject: [PATCH 235/281] Typo fixes --- defines.h | 2 +- rs485 | 1 + sensors.cpp | 4 +++- sensors.h | 1 + 4 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 rs485 diff --git a/defines.h b/defines.h index a6181745b..190e99836 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; diff --git a/rs485 b/rs485 new file mode 100644 index 000000000..f448d2b46 --- /dev/null +++ b/rs485 @@ -0,0 +1 @@ +/dev/ttyUSB0 diff --git a/sensors.cpp b/sensors.cpp index 317c6cd02..ac0d051bf 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -70,8 +70,9 @@ static ProgSensorAdjust_t *progSensorAdjusts = NULL; // modbus transaction id static uint16_t modbusTcpId = 0; +#ifdef ESP8266 static uint i2c_rs485_allocated[MAX_RS485_DEVICES]; -#ifndef ESP8266 +#else static modbus_t * ttyDevices[MAX_RS485_DEVICES]; #endif @@ -204,6 +205,7 @@ void sensor_api_init(boolean detect_boards) { } else { n++; ttyDevices[idx] = ctx; + asb_detected_boards |= OSPI_USB_RS485; #ifdef ENABLE_DEBUG modbus_set_debug(ctx, TRUE); DEBUG_PRINTLN(F("DEBUG ENABLED")); diff --git a/sensors.h b/sensors.h index 834286616..3502f26bd 100644 --- a/sensors.h +++ b/sensors.h @@ -142,6 +142,7 @@ extern "C" { #define RS485_TRUEBNER2 0x0040 #define RS485_TRUEBNER3 0x0080 #define RS485_TRUEBNER4 0x0100 +#define OSPI_USB_RS485 0x0200 typedef struct SensorFlags { uint enable : 1; // enabled From 02189b48007690f08753da86535cb3b8f033a4e8 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 13 Oct 2024 23:22:41 +0200 Subject: [PATCH 236/281] More typo fixes --- sensors.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sensors.cpp b/sensors.cpp index ac0d051bf..d5bd428d1 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -266,7 +266,9 @@ void sensor_api_free() { } modbusTcpId = 0; + #ifdef ESP8266 memset(i2c_rs485_allocated, 0, sizeof(i2c_rs485_allocated)); + #endif } /* From 283650059786b1ad49076f56100aee3e791cbb04 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 18 Oct 2024 00:36:51 +0200 Subject: [PATCH 237/281] Fixt build script for first setup --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index e4e3244e7..d7a890939 100755 --- a/build.sh +++ b/build.sh @@ -48,7 +48,7 @@ echo "Building OpenSprinkler..." if [ -f /etc/init.d/OpenSprinkler.sh ]; then /etc/init.d/OpenSprinkler.sh stop -else +elif [ -f /etc/systemd/system/OpenSprinkler.service ]; then systemctl stop OpenSprinkler fi From 9f6008430e7b17415e2a9db0663f15ad293685f3 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 18 Oct 2024 13:40:38 +0200 Subject: [PATCH 238/281] fixt email sending when influxdb is active (empty email) --- main.cpp | 53 +++++++++++++++++++++++++++++++---------------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/main.cpp b/main.cpp index 1b179862f..5adb7258b 100644 --- a/main.cpp +++ b/main.cpp @@ -1494,9 +1494,10 @@ void ip2string(char* str, size_t str_len, unsigned char ip[4]) { #if defined(ESP8266) void influxdb_send_state(const char *name, int state) { if (!os.influxdb.isEnabled()) return; + char tmp[TMP_BUFFER_SIZE]; Point data("opensprinkler"); - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); - data.addTag("devicename", tmp_buffer); + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); data.addTag("name", name); data.addField("state", state); os.influxdb.write_influx_data(data); @@ -1505,8 +1506,9 @@ void influxdb_send_state(const char *name, int state) { void influxdb_send_station(const char *name, uint32_t station, int state) { if (!os.influxdb.isEnabled()) return; Point data("opensprinkler"); - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); - data.addTag("devicename", tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); data.addTag("name", name); data.addField("station", station); data.addField("state", state); @@ -1516,8 +1518,9 @@ void influxdb_send_station(const char *name, uint32_t station, int state) { void influxdb_send_program(const char *name, uint32_t nr, float level) { if (!os.influxdb.isEnabled()) return; Point data("opensprinkler"); - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); - data.addTag("devicename", tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); data.addTag("name", name); data.addField("program", nr); data.addField("level", level); @@ -1527,8 +1530,9 @@ void influxdb_send_program(const char *name, uint32_t nr, float level) { void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { if (!os.influxdb.isEnabled()) return; Point data("opensprinkler"); - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); - data.addTag("devicename", tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); data.addTag("name", name); data.addField("count", count); data.addField("volume", volume); @@ -1538,8 +1542,9 @@ void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { if (!os.influxdb.isEnabled()) return; Point data("opensprinkler"); - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); - data.addTag("devicename", tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); data.addTag("name", name); data.addField("station", station); data.addField("flowrate", (double)(f1)+(double)(f2)/100); @@ -1553,11 +1558,11 @@ void influxdb_send_state(const char *name, int state) { influxdb_cpp::server_info * client = os.influxdb.get_client(); if (!client) return; - - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); influxdb_cpp::builder() .meas("opensprinkler") - .tag("devicename", tmp_buffer) + .tag("devicename", tmp) .tag("name", name) .field("state", state) .timestamp(millis()) @@ -1570,10 +1575,11 @@ void influxdb_send_station(const char *name, uint32_t station, int state) { if (!client) return; - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); influxdb_cpp::builder() .meas("opensprinkler") - .tag("devicename", tmp_buffer) + .tag("devicename", tmp) .tag("name", name) .field("station", (int)station) .field("state", state) @@ -1587,10 +1593,11 @@ void influxdb_send_program(const char *name, uint32_t nr, float level) { if (!client) return; - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); influxdb_cpp::builder() .meas("opensprinkler") - .tag("devicename", tmp_buffer) + .tag("devicename", tmp) .tag("name", name) .field("program", (int)nr) .field("level", level) @@ -1604,10 +1611,11 @@ void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { if (!client) return; - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); influxdb_cpp::builder() .meas("opensprinkler") - .tag("devicename", tmp_buffer) + .tag("devicename", tmp) .tag("name", name) .field("count", (int)count) .field("volume", volume) @@ -1621,10 +1629,11 @@ void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, if (!client) return; - os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); influxdb_cpp::builder() .meas("opensprinkler") - .tag("devicename", tmp_buffer) + .tag("devicename", tmp) .tag("name", name) .field("station", (int)(station)) .field("flowrate", (double)(f1)+(double)(f2)/100) @@ -1977,7 +1986,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { } if(email_enabled) { email_message.subject += PSTR("weather update event"); } } - influxdb_send_state("waterlevel", lval); + influxdb_send_state("waterlevel", (int)fval); break; case NOTIFY_REBOOT: From 879a6c71b46713104c7237f82a336bf5e476abd1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 20 Oct 2024 02:28:32 +0200 Subject: [PATCH 239/281] New Feature: Monitoring and control --- defines.h | 6 +- main.cpp | 3 +- opensprinkler_server.cpp | 204 +++++++++++++++++++++++++++++++- opensprinkler_server.h | 3 + rs485 | 2 +- sensors.cpp | 243 ++++++++++++++++++++++++++++++++++++++- sensors.h | 43 ++++++- 7 files changed, 488 insertions(+), 16 deletions(-) diff --git a/defines.h b/defines.h index 190e99836..290c25c11 100644 --- a/defines.h +++ b/defines.h @@ -24,18 +24,18 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; #define TMP_BUFFER_SIZE 320 // scratch buffer size /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 232 // Firmware version: 220 means 2.2.0 +#define OS_FW_VERSION 233 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 167 // Firmware minor version +#define OS_FW_MINOR 168 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index 5adb7258b..aaf595af0 100644 --- a/main.cpp +++ b/main.cpp @@ -1429,7 +1429,8 @@ void reset_all_stations() { */ void manual_start_program(unsigned char pid, unsigned char uwt) { boolean match_found = false; - reset_all_stations_immediate(); + if (uwt != 255) + reset_all_stations_immediate(); ProgramStruct prog; ulong dur; unsigned char sid, bid, s; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8ea48e1f2..18aa7a475 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2902,6 +2902,179 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { handle_return(HTML_OK); } +/** + * mt + * supported monitor types + */ +void server_monitor_types(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_monitor_types")); + +#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("{\"monitortypes\": [")); + bfill.emit_p(PSTR("{\"name\":\"Min\",\"type\":$D},"), MONITOR_MIN); + bfill.emit_p(PSTR("{\"name\":\"Max\",\"type\":$D}]}"), MONITOR_MAX); + handle_return(HTML_OK); +} + +/** + * mc + * define a monitor + */ +void server_monitor_config(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_monitor_config")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + if (nr == 0) + handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Adjustment type + + if (type == 0) { + monitor_delete(nr); + handle_return(HTML_SUCCESS); + } + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + handle_return(HTML_DATA_MISSING); + uint sensor = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + handle_return(HTML_DATA_MISSING); + uint prog = strtoul(tmp_buffer, NULL, 0); // Program nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("zone"), true)) + handle_return(HTML_DATA_MISSING); + uint zone = strtoul(tmp_buffer, NULL, 0); // Zone + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value1"), true)) + handle_return(HTML_DATA_MISSING); + double value1 = atof(tmp_buffer); // Value 1 + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value2"), true)) + handle_return(HTML_DATA_MISSING); + double value2 = atof(tmp_buffer); // Value 2 + + char name[20] = {0}; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) + strncpy(name, tmp_buffer, sizeof(name)); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("maxrun"), true)) + handle_return(HTML_DATA_MISSING); + ulong maxRuntime = strtoul(tmp_buffer, NULL, 0); // Zone + + int ret = monitor_define(nr, type, sensor, prog, zone, value1, value2, name, maxRuntime); + ret = ret >= HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); +} + +void monitorconfig_json(Monitor_t *mon) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"zone\":$D,\"value1\":$E,\"value2\":$E,\"name\":\"$S\",\"maxrun\":$L,\"active\":$D}"), + mon->nr, + mon->type, + mon->sensor, + mon->prog, + mon->zone, + isnan(mon->value1)?0:mon->value1, + isnan(mon->value2)?0:mon->value2, + mon->name, + mon->maxRuntime, + mon->active); +} + +void monitorconfig_json() { + uint count = monitor_count(); + for (uint i = 0; i < count; i++) { + Monitor_t *mon = monitor_by_idx(i); + if (i > 0) bfill.emit_p(PSTR(",")); + monitorconfig_json(mon); + } +} + +/** + * ml + * list monitors + */ +void server_monitor_list(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_monitor_list")); + + uint nr = 0; + int prog = -1; + uint sensor_nr = 0; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + prog = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + sensor_nr = strtoul(tmp_buffer, NULL, 0); + +#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("{\"monitors\": [")); + uint8_t idx = 0; + uint8_t count = monitor_count(); + bool first = true; + + while (idx < count) { + Monitor_t *mon = monitor_by_idx(idx++); + if (nr > 0 && mon->nr != nr) + continue; + if (prog >= 0 && mon->prog != (uint)prog) + continue; + if (sensor_nr > 0 && mon->sensor != sensor_nr) + continue; + + if (!first) + bfill.emit_p(PSTR(",")); + first = false; + monitorconfig_json(mon); + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + + + /** * sb * define a program adjustment @@ -2955,13 +3128,17 @@ void server_sensorprog_config(OTF_PARAMS_DEF) { handle_return(HTML_DATA_MISSING); double max = atof(tmp_buffer); // Max value - int ret = prog_adjust_define(nr, type, sensor, prog, factor1, factor2, min, max); + char name[20] = {0}; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) + strncpy(name, tmp_buffer, sizeof(name)); + + int ret = prog_adjust_define(nr, type, sensor, prog, factor1, factor2, min, max, name); ret = ret >= HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); } void progconfig_json(ProgSensorAdjust_t *p, double current) { - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E, \"current\":$E}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E,\"name\":\"$S\",\"current\":$E}"), p->nr, p->type, p->sensor, @@ -2970,6 +3147,7 @@ void progconfig_json(ProgSensorAdjust_t *p, double current) { p->factor2, p->min, p->max, + (p->name[0] < 0x20 || p->name[0] > 0xEE || p->name[0] == 0x90) ? "" : p->name, current); } @@ -3450,9 +3628,10 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { #define BACKUP_SENSORS 1 #define BACKUP_ADJUSTMENTS 2 +#define BACKUP_MONITORS 4 //Backup type: 0=no backup 1=Sensors 2=Adjustments 3=Sensors+Adjustments - int backup = BACKUP_SENSORS|BACKUP_ADJUSTMENTS; + int backup = BACKUP_SENSORS|BACKUP_ADJUSTMENTS|BACKUP_MONITORS; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("backup"), true)) { backup = strtol(tmp_buffer, NULL, 0); } @@ -3473,12 +3652,19 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { bfill.emit_p(PSTR(",\"sensors\":[")); sensorconfig_json(OTF_PARAMS); bfill.emit_p(PSTR("]")); + send_packet(OTF_PARAMS); } - send_packet(OTF_PARAMS); if (backup & BACKUP_ADJUSTMENTS) { bfill.emit_p(PSTR(",\"progadjust\":[")); progconfig_json(); bfill.emit_p(PSTR("]")); + send_packet(OTF_PARAMS); + } + send_packet(OTF_PARAMS); + if (backup & BACKUP_SENSORS) { + bfill.emit_p(PSTR(",\"monitors\":[")); + monitorconfig_json(); + bfill.emit_p(PSTR("]")); } bfill.emit_p(PSTR("}")); send_packet(OTF_PARAMS); @@ -3634,6 +3820,9 @@ const char _url_keys[] PROGMEM = "db" "is" "ig" + "mc" + "ml" + "mt" #if defined(ARDUINO) //"ff" #endif @@ -3681,8 +3870,11 @@ URLHandler urls[] = { server_sensorprog_types,//sh server_sensorconfig_backup,//sx server_json_debug, // db - server_influx_set, - server_influx_get, + server_influx_set,// is + server_influx_get,// ig + server_monitor_config, // mc + server_monitor_list, // ml + server_monitor_types, // mt //server_fill_files, }; diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 05075149e..51db7bf09 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -27,6 +27,9 @@ #if !defined(ARDUINO) #include #include +#include +#else +#include #endif #if defined(ESP8266) || defined(OSPI) diff --git a/rs485 b/rs485 index f448d2b46..e9065a24a 100644 --- a/rs485 +++ b/rs485 @@ -1 +1 @@ -/dev/ttyUSB0 +/dev/ttyUSB1 diff --git a/sensors.cpp b/sensors.cpp index d5bd428d1..923c4920c 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -68,6 +68,9 @@ static SensorUrl_t *sensorUrls = NULL; // Program sensor data static ProgSensorAdjust_t *progSensorAdjusts = NULL; +// Monitor data +static Monitor_t *monitors = NULL; + // modbus transaction id static uint16_t modbusTcpId = 0; #ifdef ESP8266 @@ -181,6 +184,7 @@ void sensor_api_init(boolean detect_boards) { sensor_load(); prog_adjust_load(); sensor_mqtt_init(); + monitor_load(); #ifndef ESP8266 //Read rs485 file. Details see below std::ifstream file; @@ -226,6 +230,7 @@ void sensor_save_all() { sensor_save(); prog_adjust_save(); SensorUrl_save(); + monitor_save(); #ifndef ESP8266 for (int i = 0; i < MAX_RS485_DEVICES; i++) { if (ttyDevices[i]) { @@ -259,6 +264,12 @@ void sensor_api_free() { sensorUrls = next; } + while (monitors) { + Monitor_t* next = monitors->next; + delete monitors; + monitors = next; + } + while (sensors) { Sensor_t* next = sensors->next; delete sensors; @@ -989,6 +1000,7 @@ void read_all_sensors(boolean online) { if (online || (current_sensor->ip == 0 && current_sensor->type != SENSOR_MQTT)) { int result = read_sensor(current_sensor, time); if (result == HTTP_RQT_SUCCESS) { + check_monitors(); sensorlog_add(LOG_STD, current_sensor, time); push_message(current_sensor); } else if (result == HTTP_RQT_TIMEOUT) { @@ -2019,7 +2031,7 @@ double calc_sensor_watering_by_nr(uint nr) { } int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, - double factor1, double factor2, double min, double max) { + double factor1, double factor2, double min, double max, char * name) { ProgSensorAdjust_t *p = progSensorAdjusts; ProgSensorAdjust_t *last = NULL; @@ -2033,6 +2045,7 @@ int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, p->factor2 = factor2; p->min = min; p->max = max; + strncpy(p->name, name, sizeof(p->name)); prog_adjust_save(); return HTTP_RQT_SUCCESS; } @@ -2052,6 +2065,7 @@ int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, p->factor2 = factor2; p->min = min; p->max = max; + strncpy(p->name, name, sizeof(p->name)); if (last) { p->next = last->next; last->next = p; @@ -2627,4 +2641,231 @@ influxdb_cpp::builder() .post_http(*client); #endif +} + + +//Value Monitoring +void monitor_load() { + DEBUG_PRINTLN(F("monitor_load")); + monitors = NULL; + if (!file_exists(MONITOR_FILENAME)) return; + + ulong pos = 0; + Monitor_t *last = NULL; + while (true) { + Monitor_t *mon = new Monitor_t; + memset(mon, 0, sizeof(Monitor_t)); + file_read_block(MONITOR_FILENAME, mon, pos, MONITOR_STORE_SIZE); + if (!mon->nr || !mon->type) { + delete mon; + break; + } + mon->active = false; + if (!last) + monitors = mon; + else + last->next = mon; + last = mon; + mon->next = NULL; + pos += MONITOR_STORE_SIZE; + } +} + +void monitor_save() { + if (!apiInit) return; + if (file_exists(MONITOR_FILENAME)) remove_file(MONITOR_FILENAME); + + ulong pos = 0; + Monitor_t *mon = monitors; + while (mon) { + file_write_block(MONITOR_FILENAME, mon, pos, MONITOR_STORE_SIZE); + mon = mon->next; + pos += MONITOR_STORE_SIZE; + } +} + +int monitor_count() { + int count = 0; + Monitor_t *mon = monitors; + while (mon) { + mon = mon->next; + count++; + } + return count; +} + +int monitor_delete(uint nr) { + Monitor_t *p = monitors; + + Monitor_t *last = NULL; + + while (p) { + if (p->nr == nr) { + if (last) + last->next = p->next; + else + monitors = p->next; + delete p; + monitor_save(); + return HTTP_RQT_SUCCESS; + } + last = p; + p = p->next; + } + return HTTP_RQT_NOT_RECEIVED; +} + +bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, + double value1, double value2, char * name, ulong maxRuntime) { + Monitor_t *p = monitors; + + Monitor_t *last = NULL; + + while (p) { + if (p->nr == nr) { + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->zone = zone; + p->value1 = value1; + p->value2 = value2; + p->active = false; + p->maxRuntime = maxRuntime; + strncpy(p->name, name, sizeof(p->name)); + monitor_save(); + check_monitors(); + return HTTP_RQT_SUCCESS; + } + + if (p->nr > nr) break; + + last = p; + p = p->next; + } + + p = new Monitor_t; + p->nr = nr; + p->type = type; + p->sensor = sensor; + p->zone = zone; + p->prog = prog; + p->value1 = value1; + p->value2 = value2; + p->active = false; + p->maxRuntime = maxRuntime; + strncpy(p->name, name, sizeof(p->name)); + if (last) { + p->next = last->next; + last->next = p; + } else { + p->next = monitors; + monitors = p; + } + + monitor_save(); + check_monitors(); + return HTTP_RQT_SUCCESS; +} + +Monitor_t *monitor_by_nr(uint nr) { + Monitor_t *mon = monitors; + while (mon) { + if (mon->nr == nr) return mon; + mon = mon->next; + } + return NULL; +} + +Monitor_t *monitor_by_idx(uint idx) { + Monitor_t *mon = monitors; + uint idxCounter = 0; + while (mon) { + if (idxCounter++ == idx) return mon; + mon = mon->next; + } + return NULL; +} + +void manual_start_program(unsigned char, unsigned char); +void schedule_all_stations(time_os_t curr_time); +void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shift=0); + + +void start_monitor_action(Monitor_t * mon) { + mon->time = os.now_tz(); + if (mon->prog > 0) + manual_start_program(mon->prog, 255); + + if (mon->zone > 0) { + uint sid = mon->zone-1; + uint16_t timer=mon->maxRuntime; + RuntimeQueueStruct *q = NULL; + unsigned char sqi = pd.station_qid[sid]; + // check if the station already has a schedule + if (sqi!=0xFF) { // if so, we will overwrite the schedule + q = pd.queue+sqi; + } else { // otherwise create a new queue element + q = pd.enqueue(); + } + // if the queue is not full + if (q) { + q->st = 0; + q->dur = timer; + q->sid = sid; + q->pid = 253; + schedule_all_stations(mon->time); + } + } +} + +void stop_monitor_action(Monitor_t * mon) { + mon->time = os.now_tz(); + if (mon->zone > 0) { + int sid = mon->zone-1; + RuntimeQueueStruct *q = pd.queue + pd.station_qid[sid]; + q->deque_time = mon->time; + turn_off_station(sid, mon->time); + } +} + +void check_monitors() { + Monitor_t *mon = monitors; + while (mon) { + Sensor_t * sensor = sensor_by_nr(mon->sensor); + if (sensor && sensor->flags.data_ok) { + double value = sensor->last_data; + if (!mon->active) { //Check for start monitor actions: + switch(mon->type) { + case MONITOR_MIN: + if (value <= mon->value1) { + mon->active = true; + start_monitor_action(mon); + } + break; + case MONITOR_MAX: + if (value >= mon->value1) { + mon->active = true; + start_monitor_action(mon); + } + break; + } + } else { //mon->active, check for stop monitor actions: + switch(mon->type) { + case MONITOR_MIN: + if (value >= mon->value2) { + mon->active = false; + stop_monitor_action(mon); + } + break; + case MONITOR_MAX: + if (value <= mon->value2) { + mon->active = false; + stop_monitor_action(mon); + } + break; + } + } + } + mon = mon->next; + } } \ No newline at end of file diff --git a/sensors.h b/sensors.h index 3502f26bd..d09530dd1 100644 --- a/sensors.h +++ b/sensors.h @@ -40,14 +40,15 @@ extern "C" { #if defined(ESP8266) #include #endif +#include "program.h" // Files #define SENSOR_FILENAME "sensor.dat" // analog sensor filename #define SENSOR_FILENAME_BAK "sensor.bak" // analog sensor filename backup -#define PROG_SENSOR_FILENAME \ - "progsensor.dat" // sensor to program assign filename +#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename #define SENSORLOG_FILENAME1 "sensorlog.dat" // analog sensor log filename #define SENSORLOG_FILENAME2 "sensorlog2.dat" // analog sensor log filename2 +#define MONITOR_FILENAME "monitors.dat" #define SENSORLOG_FILENAME_WEEK1 \ "sensorlogW1.dat" // analog sensor log filename for week average @@ -221,7 +222,8 @@ typedef struct ProgSensorAdjust { double factor2; double min; double max; - unsigned char undef[32]; // for later + char name[20]; + unsigned char undef[12]; // for later ProgSensorAdjust *next; } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE \ @@ -241,6 +243,27 @@ typedef struct SensorUrl { #define SENSORURL_STORE_SIZE \ (sizeof(SensorUrl_t) - sizeof(char *) - sizeof(SensorUrl_t *)) +#define MONITOR_DELETE 0 +#define MONITOR_MIN 1 +#define MONITOR_MAX 2 + +typedef struct Monitor { + uint nr; + uint type; // MONITOR_TYPES + uint sensor; // sensor-nr + uint prog; // program-nr=pid + uint zone; // Zone + double value1; // MIN/MAX + double value2; // Secondary + boolean active; + ulong time; + char name[20]; + ulong maxRuntime; + unsigned char undef[10]; // for later + Monitor *next; +} Monitor_t; +#define MONITOR_STORE_SIZE (sizeof(Monitor_t) - sizeof(char *) - sizeof(Monitor_t *)) + #define UNIT_NONE 0 #define UNIT_PERCENT 1 #define UNIT_DEGREE 2 @@ -280,6 +303,7 @@ unsigned char getSensorUnitId(Sensor_t *sensor); extern char ether_buffer[]; extern char tmp_buffer[]; +extern ProgramData pd; // Utils: uint16_t CRC16(unsigned char buf[], int len); @@ -336,7 +360,7 @@ int set_sensor_address(Sensor_t *sensor, uint8_t new_address); // Calc watering adjustment: int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, - double factor1, double factor2, double min, double max); + double factor1, double factor2, double min, double max, char * name); int prog_adjust_delete(uint nr); void prog_adjust_save(); void prog_adjust_load(); @@ -360,6 +384,17 @@ char *SensorUrl_get(uint nr, uint type); void detect_asb_board(); +//Value Monitoring +void monitor_load(); +void monitor_save(); +int monitor_count(); +int monitor_delete(uint nr); +bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, + double value1, double value2, char * name, ulong maxRuntime); +Monitor_t * monitor_by_nr(uint nr); +Monitor_t * monitor_by_idx(uint idx); +void check_monitors(); + #if defined(ESP8266) ulong diskFree(); bool checkDiskFree(); // true: disk space Ok, false: Out of disk space From 58fe82d983b039d5be69232f2a9b02ba4c6c5bd9 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 20 Oct 2024 02:45:34 +0200 Subject: [PATCH 240/281] rs485 ospi detect --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 923c4920c..d77146656 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -60,7 +60,7 @@ static boolean apiInit = false; static Sensor_t *current_sensor = NULL; // Boards: -static unsigned char asb_detected_boards = 0; // bit 1=0x48+0x49 bit 2=0x4A+0x4B +static uint16_t asb_detected_boards = 0; // bit 1=0x48+0x49 bit 2=0x4A+0x4B usw // Sensor URLS: static SensorUrl_t *sensorUrls = NULL; From 8c01fd6bb0fcc23030aa7e5baf9b93caf4363271 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 20 Oct 2024 03:03:53 +0200 Subject: [PATCH 241/281] Fix manual changes does not trigger monitor and control --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index d77146656..7d7c66700 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2729,7 +2729,7 @@ bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, p->zone = zone; p->value1 = value1; p->value2 = value2; - p->active = false; + //p->active = false; p->maxRuntime = maxRuntime; strncpy(p->name, name, sizeof(p->name)); monitor_save(); From 9ef92c7f4b9ad986baab209a897ba6e173488c9b Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 20 Oct 2024 13:20:45 +0200 Subject: [PATCH 242/281] fixt detect OSPi Rs485 Adapters --- sensors.cpp | 2 +- sensors.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 7d7c66700..69f1eca1d 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -173,7 +173,7 @@ void detect_asb_board() { } } -unsigned char get_asb_detected_boards() { return asb_detected_boards; } +uint16_t get_asb_detected_boards() { return asb_detected_boards; } /* * init sensor api and load data */ diff --git a/sensors.h b/sensors.h index d09530dd1..3e5c49168 100644 --- a/sensors.h +++ b/sensors.h @@ -291,7 +291,7 @@ typedef struct Monitor { #define RS485_TRUEBNER4_ADDR 0x3B void sensor_api_init(boolean detect_boards); -unsigned char get_asb_detected_boards(); +uint16_t get_asb_detected_boards(); void sensor_save_all(); void sensor_api_free(); From 0b53d4b7325267a72772a040125ae9d17fef86c4 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 23 Oct 2024 00:22:56 +0200 Subject: [PATCH 243/281] added priority and notifications for monitoring and controlling --- defines.h | 7 ++++-- main.cpp | 48 ++++++++++++++++++++++++++++++++++++++++ opensprinkler_server.cpp | 13 +++++++---- sensors.cpp | 23 ++++++++++++++++--- sensors.h | 5 +++-- 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/defines.h b/defines.h index 290c25c11..c40fc4503 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; @@ -35,7 +35,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 168 // Firmware minor version +#define OS_FW_MINOR 169 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -77,6 +77,9 @@ typedef unsigned long ulong; #define NOTIFY_RAINDELAY 0x0080 #define NOTIFY_STATION_ON 0x0100 #define NOTIFY_FLOW_ALERT 0x0200 +#define NOTIFY_MONITOR_LOW 0x0400 +#define NOTIFY_MONITOR_MID 0x0800 +#define NOTIFY_MONITOR_HIGH 0x1000 /** HTTP request macro defines */ #define HTTP_RQT_SUCCESS 0 diff --git a/main.cpp b/main.cpp index aaf595af0..d3044a8f1 100644 --- a/main.cpp +++ b/main.cpp @@ -1553,6 +1553,20 @@ void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, data.addField("alert_setpoint", (double)(f4)+(double)(f5)/100); os.influxdb.write_influx_data(data); } + +void influxdb_send_warning(const char *name, uint32_t level, float value) { + if (!os.influxdb.isEnabled()) return; + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("warning", name); + data.addField("level", (int)level); + data.addField("currentvalue", value); + os.influxdb.write_influx_data(data); +} + + #else void influxdb_send_state(const char *name, int state) { if (!os.influxdb.isEnabled()) return; @@ -1643,6 +1657,24 @@ void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, .timestamp(millis()) .post_http(*client); } + +void influxdb_send_warning(const char *name, uint32_t level, float value) { + if (!os.influxdb.isEnabled()) return; + influxdb_cpp::server_info * client = os.influxdb.get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("warning", name) + .field("level", (int)level) + .field("currentvalue", value) + .timestamp(millis()) + .post_http(*client); +} #endif @@ -2038,6 +2070,22 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { if(email_enabled) { email_message.subject += PSTR("reboot event"); } } break; + + case NOTIFY_MONITOR_LOW: + case NOTIFY_MONITOR_MID: + case NOTIFY_MONITOR_HIGH: + + if (os.mqtt.enabled()) { + strcpy_P(topic, PSTR("monitoring")); + snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"warning\":\"%S\",\"prio\":%u,\"value\":%d.%02d}"), sval, lval, (int)fval, (int)fval*100%100); + } + if (ifttt_enabled || email_enabled) { + snprintf_P(postval, TMP_BUFFER_SIZE, PSTR("monitoring: Warning %S with priority %u current value %d.%02d"), sval, lval, (int)fval, (int)fval*100%100); + if(email_enabled) { email_message.subject += PSTR("Warning"); } + } + influxdb_send_flowsensor(sval, lval, fval); + break; + } if (os.mqtt.enabled() && strlen(topic) && strlen(payload)) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 18aa7a475..c497421b9 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2980,19 +2980,23 @@ void server_monitor_config(OTF_PARAMS_DEF) { char name[20] = {0}; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) - strncpy(name, tmp_buffer, sizeof(name)); + strncpy(name, tmp_buffer, sizeof(name)-1); if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("maxrun"), true)) handle_return(HTML_DATA_MISSING); ulong maxRuntime = strtoul(tmp_buffer, NULL, 0); // Zone - int ret = monitor_define(nr, type, sensor, prog, zone, value1, value2, name, maxRuntime); + uint8_t prio = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prio"), true)) + prio = strtoul(tmp_buffer, NULL, 0); // prio + + int ret = monitor_define(nr, type, sensor, prog, zone, value1, value2, name, maxRuntime, prio); ret = ret >= HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); } void monitorconfig_json(Monitor_t *mon) { - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"zone\":$D,\"value1\":$E,\"value2\":$E,\"name\":\"$S\",\"maxrun\":$L,\"active\":$D}"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"zone\":$D,\"value1\":$E,\"value2\":$E,\"name\":\"$S\",\"maxrun\":$L,\"prio\":$D,\"active\":$D}"), mon->nr, mon->type, mon->sensor, @@ -3002,6 +3006,7 @@ void monitorconfig_json(Monitor_t *mon) { isnan(mon->value2)?0:mon->value2, mon->name, mon->maxRuntime, + mon->prio, mon->active); } @@ -3130,7 +3135,7 @@ void server_sensorprog_config(OTF_PARAMS_DEF) { char name[20] = {0}; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) - strncpy(name, tmp_buffer, sizeof(name)); + strncpy(name, tmp_buffer, sizeof(name)-1); int ret = prog_adjust_define(nr, type, sensor, prog, factor1, factor2, min, max, name); ret = ret >= HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; diff --git a/sensors.cpp b/sensors.cpp index 69f1eca1d..cb4569812 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2660,7 +2660,6 @@ void monitor_load() { delete mon; break; } - mon->active = false; if (!last) monitors = mon; else @@ -2716,7 +2715,7 @@ int monitor_delete(uint nr) { } bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, - double value1, double value2, char * name, ulong maxRuntime) { + double value1, double value2, char * name, ulong maxRuntime, uint8_t prio) { Monitor_t *p = monitors; Monitor_t *last = NULL; @@ -2731,6 +2730,7 @@ bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, p->value2 = value2; //p->active = false; p->maxRuntime = maxRuntime; + p->prio = prio; strncpy(p->name, name, sizeof(p->name)); monitor_save(); check_monitors(); @@ -2753,6 +2753,7 @@ bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, p->value2 = value2; p->active = false; p->maxRuntime = maxRuntime; + p->prio = prio; strncpy(p->name, name, sizeof(p->name)); if (last) { p->next = last->next; @@ -2789,7 +2790,7 @@ Monitor_t *monitor_by_idx(uint idx) { void manual_start_program(unsigned char, unsigned char); void schedule_all_stations(time_os_t curr_time); void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shift=0); - +void push_message(uint16_t type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); void start_monitor_action(Monitor_t * mon) { mon->time = os.now_tz(); @@ -2828,9 +2829,21 @@ void stop_monitor_action(Monitor_t * mon) { } } +void push_message(Monitor_t * mon, float value) { + uint16_t type; + switch(mon->prio) { + case 0: type = NOTIFY_MONITOR_LOW; break; + case 1: type = NOTIFY_MONITOR_MID; break; + case 2: type = NOTIFY_MONITOR_HIGH; break; + default: return; + } + push_message(type, (uint32_t)mon->prio, value, mon->name); +} + void check_monitors() { Monitor_t *mon = monitors; while (mon) { + uint nr = mon->nr; Sensor_t * sensor = sensor_by_nr(mon->sensor); if (sensor && sensor->flags.data_ok) { double value = sensor->last_data; @@ -2840,12 +2853,16 @@ void check_monitors() { if (value <= mon->value1) { mon->active = true; start_monitor_action(mon); + push_message(mon, value); + mon = monitor_by_nr(nr); //restart because if send by mail we unloaded+reloaded the monitors } break; case MONITOR_MAX: if (value >= mon->value1) { mon->active = true; start_monitor_action(mon); + push_message(mon, value); + mon = monitor_by_nr(nr); //restart because if send by mail we unloaded+reloaded the monitors } break; } diff --git a/sensors.h b/sensors.h index 3e5c49168..fd3ae724b 100644 --- a/sensors.h +++ b/sensors.h @@ -259,7 +259,8 @@ typedef struct Monitor { ulong time; char name[20]; ulong maxRuntime; - unsigned char undef[10]; // for later + uint8_t prio; + unsigned char undef[9]; // for later Monitor *next; } Monitor_t; #define MONITOR_STORE_SIZE (sizeof(Monitor_t) - sizeof(char *) - sizeof(Monitor_t *)) @@ -390,7 +391,7 @@ void monitor_save(); int monitor_count(); int monitor_delete(uint nr); bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, - double value1, double value2, char * name, ulong maxRuntime); + double value1, double value2, char * name, ulong maxRuntime, uint8_t prio); Monitor_t * monitor_by_nr(uint nr); Monitor_t * monitor_by_idx(uint idx); void check_monitors(); From 4caf611bbc4a696ca4491b3affa47d1375db0630 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 23 Oct 2024 23:36:55 +0200 Subject: [PATCH 244/281] fixt influxdb + email active at once --- main.cpp | 220 ++++----------------------------------------- osinfluxdb.cpp | 237 ++++++++++++++++++++++++++++++++++++++++++++++++- osinfluxdb.h | 8 ++ sensors.cpp | 8 +- 4 files changed, 269 insertions(+), 204 deletions(-) diff --git a/main.cpp b/main.cpp index d3044a8f1..d1eec04c5 100644 --- a/main.cpp +++ b/main.cpp @@ -1492,194 +1492,14 @@ void ip2string(char* str, size_t str_len, unsigned char ip[4]) { #define PUSH_TOPIC_LEN 120 #define PUSH_PAYLOAD_LEN TMP_BUFFER_SIZE -#if defined(ESP8266) -void influxdb_send_state(const char *name, int state) { - if (!os.influxdb.isEnabled()) return; - char tmp[TMP_BUFFER_SIZE]; - Point data("opensprinkler"); - os.sopt_load(SOPT_DEVICE_NAME, tmp); - data.addTag("devicename", tmp); - data.addTag("name", name); - data.addField("state", state); - os.influxdb.write_influx_data(data); -} - -void influxdb_send_station(const char *name, uint32_t station, int state) { - if (!os.influxdb.isEnabled()) return; - Point data("opensprinkler"); - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - data.addTag("devicename", tmp); - data.addTag("name", name); - data.addField("station", station); - data.addField("state", state); - os.influxdb.write_influx_data(data); -} - -void influxdb_send_program(const char *name, uint32_t nr, float level) { - if (!os.influxdb.isEnabled()) return; - Point data("opensprinkler"); - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - data.addTag("devicename", tmp); - data.addTag("name", name); - data.addField("program", nr); - data.addField("level", level); - os.influxdb.write_influx_data(data); -} - -void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { - if (!os.influxdb.isEnabled()) return; - Point data("opensprinkler"); - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - data.addTag("devicename", tmp); - data.addTag("name", name); - data.addField("count", count); - data.addField("volume", volume); - os.influxdb.write_influx_data(data); -} - -void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { - if (!os.influxdb.isEnabled()) return; - Point data("opensprinkler"); - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - data.addTag("devicename", tmp); - data.addTag("name", name); - data.addField("station", station); - data.addField("flowrate", (double)(f1)+(double)(f2)/100); - data.addField("duration", f3); - data.addField("alert_setpoint", (double)(f4)+(double)(f5)/100); - os.influxdb.write_influx_data(data); -} - -void influxdb_send_warning(const char *name, uint32_t level, float value) { - if (!os.influxdb.isEnabled()) return; - Point data("opensprinkler"); - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - data.addTag("devicename", tmp); - data.addTag("warning", name); - data.addField("level", (int)level); - data.addField("currentvalue", value); - os.influxdb.write_influx_data(data); -} - - -#else -void influxdb_send_state(const char *name, int state) { - if (!os.influxdb.isEnabled()) return; - influxdb_cpp::server_info * client = os.influxdb.get_client(); - if (!client) - return; - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - influxdb_cpp::builder() - .meas("opensprinkler") - .tag("devicename", tmp) - .tag("name", name) - .field("state", state) - .timestamp(millis()) - .post_http(*client); -} - -void influxdb_send_station(const char *name, uint32_t station, int state) { - if (!os.influxdb.isEnabled()) return; - influxdb_cpp::server_info * client = os.influxdb.get_client(); - if (!client) - return; - - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - influxdb_cpp::builder() - .meas("opensprinkler") - .tag("devicename", tmp) - .tag("name", name) - .field("station", (int)station) - .field("state", state) - .timestamp(millis()) - .post_http(*client); -} - -void influxdb_send_program(const char *name, uint32_t nr, float level) { - if (!os.influxdb.isEnabled()) return; - influxdb_cpp::server_info * client = os.influxdb.get_client(); - if (!client) - return; - - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - influxdb_cpp::builder() - .meas("opensprinkler") - .tag("devicename", tmp) - .tag("name", name) - .field("program", (int)nr) - .field("level", level) - .timestamp(millis()) - .post_http(*client); -} - -void influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { - if (!os.influxdb.isEnabled()) return; - influxdb_cpp::server_info * client = os.influxdb.get_client(); - if (!client) - return; - - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - influxdb_cpp::builder() - .meas("opensprinkler") - .tag("devicename", tmp) - .tag("name", name) - .field("count", (int)count) - .field("volume", volume) - .timestamp(millis()) - .post_http(*client); -} - -void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { - if (!os.influxdb.isEnabled()) return; - influxdb_cpp::server_info * client = os.influxdb.get_client(); - if (!client) - return; - - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - influxdb_cpp::builder() - .meas("opensprinkler") - .tag("devicename", tmp) - .tag("name", name) - .field("station", (int)(station)) - .field("flowrate", (double)(f1)+(double)(f2)/100) - .field("duration", f3) - .field("alert_setpoint", (double)(f4)+(double)(f5)/100) - .timestamp(millis()) - .post_http(*client); -} - -void influxdb_send_warning(const char *name, uint32_t level, float value) { - if (!os.influxdb.isEnabled()) return; - influxdb_cpp::server_info * client = os.influxdb.get_client(); - if (!client) - return; - - char tmp[TMP_BUFFER_SIZE]; - os.sopt_load(SOPT_DEVICE_NAME, tmp); - influxdb_cpp::builder() - .meas("opensprinkler") - .tag("devicename", tmp) - .tag("warning", name) - .field("level", (int)level) - .field("currentvalue", value) - .timestamp(millis()) - .post_http(*client); -} -#endif - +void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { + if (!is_notif_enabled(type)) { + DEBUG_PRINT("PUSH INACTIVE: "); + DEBUG_PRINTLN(type); + return; + } -void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { static char topic[PUSH_TOPIC_LEN+1]; static char payload[PUSH_PAYLOAD_LEN+1]; char* postval = tmp_buffer+1; // +1 so we can fit a opening { before the loaded config @@ -1687,7 +1507,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { // check if ifttt key exists and also if the enable bit is set os.sopt_load(SOPT_IFTTT_KEY, tmp_buffer); - bool ifttt_enabled = is_notif_enabled(type) && (strlen(tmp_buffer)!=0); + bool ifttt_enabled = strlen(tmp_buffer)!=0; #define DEFAULT_EMAIL_PORT 465 @@ -1742,13 +1562,16 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { if(!email_en){ email_enabled = false; }else{ - email_enabled = is_notif_enabled(type); + email_enabled = true; } #endif // if none if enabled, return here - if ((!ifttt_enabled) && (!email_enabled) && (!os.mqtt.enabled()) && (!influxdb_enabled)) + if (!ifttt_enabled && !email_enabled && !os.mqtt.enabled()) { + if (influxdb_enabled) + os.influxdb.push_message(type, lval, fval, sval); return; + } if (ifttt_enabled || email_enabled) { strcpy_P(postval, PSTR("{\"value1\":\"On site [")); @@ -1778,7 +1601,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":1,\"duration\":%d}"), (int)fval); } } - influxdb_send_station("station", lval, 1); break; case NOTIFY_FLOW_ALERT:{ @@ -1878,8 +1700,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { } - influxdb_send_flowalert("flowalert", lval, f1, f2, f3, f4, f5); - } else { //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 @@ -1918,7 +1738,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { } if(email_enabled) { email_message.subject += PSTR("station event"); } } - influxdb_send_station("station", lval, 0); break; case NOTIFY_PROGRAM_SCHED: @@ -1936,7 +1755,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR(" with %d%% water level."), (int)fval); if(email_enabled) { email_message.subject += PSTR("program event"); } } - influxdb_send_program("program sched", lval, fval); break; case NOTIFY_SENSOR1: @@ -1950,7 +1768,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); if(email_enabled) { email_message.subject += PSTR("sensor 1 event"); } } - influxdb_send_state("sensor1", (int)fval); break; case NOTIFY_SENSOR2: @@ -1964,7 +1781,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); if(email_enabled) { email_message.subject += PSTR("sensor 2 event"); } } - influxdb_send_state("sensor2", (int)fval); break; case NOTIFY_RAINDELAY: @@ -1978,7 +1794,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { strcat_P(postval, ((int)fval)?PSTR("activated."):PSTR("de-activated.")); if(email_enabled) { email_message.subject += PSTR("rain delay event"); } } - influxdb_send_state("raindelay", (int)fval); break; case NOTIFY_FLOWSENSOR: @@ -1995,7 +1810,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR("Flow count: %u, volume: %d.%02d"), lval, (int)volume/100, (int)volume%100); if(email_enabled) { email_message.subject += PSTR("flow sensor event"); } } - influxdb_send_flowsensor("flowsensor", lval, (float)volume/100); break; case NOTIFY_WEATHER_UPDATE: @@ -2019,7 +1833,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { } if(email_enabled) { email_message.subject += PSTR("weather update event"); } } - influxdb_send_state("waterlevel", (int)fval); break; case NOTIFY_REBOOT: @@ -2077,13 +1890,14 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { if (os.mqtt.enabled()) { strcpy_P(topic, PSTR("monitoring")); - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"warning\":\"%S\",\"prio\":%u,\"value\":%d.%02d}"), sval, lval, (int)fval, (int)fval*100%100); + int len = strlen(postval); + snprintf_P(payload+len, PUSH_PAYLOAD_LEN-len, PSTR("{\"warning\":\"%s\",\"prio\":%u,\"value\":%d.%02d}"), sval, lval, (int)fval, (int)fval*100%100); } if (ifttt_enabled || email_enabled) { - snprintf_P(postval, TMP_BUFFER_SIZE, PSTR("monitoring: Warning %S with priority %u current value %d.%02d"), sval, lval, (int)fval, (int)fval*100%100); + int len = strlen(postval); + snprintf_P(postval+len, TMP_BUFFER_SIZE-len, PSTR("monitoring: Warning %s with priority %u current value %d.%02d"), sval, lval, (int)fval, (int)fval*100%100); if(email_enabled) { email_message.subject += PSTR("Warning"); } } - influxdb_send_flowsensor(sval, lval, fval); break; } @@ -2140,6 +1954,8 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { } #endif } + if (influxdb_enabled) + os.influxdb.push_message(type, lval, fval, sval); } // ================================ diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index 36fa47072..fab8e4278 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -24,8 +24,10 @@ #include "osinfluxdb.h" #include "utils.h" #include "defines.h" +#include "OpenSprinkler.h" extern char tmp_buffer[TMP_BUFFER_SIZE*2]; +extern OpenSprinkler os; #define INFLUX_CONFIG_FILE "influx.json" @@ -182,4 +184,237 @@ influxdb_cpp::server_info *OSInfluxDB::get_client() { return client; } -#endif \ No newline at end of file +#endif + + +#if defined(ESP8266) +void OSInfluxDB::influxdb_send_state(const char *name, int state) { + char tmp[TMP_BUFFER_SIZE]; + Point data("opensprinkler"); + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("state", state); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_station(const char *name, uint32_t station, int state) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("station", station); + data.addField("state", state); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_program(const char *name, uint32_t nr, float level) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("program", nr); + data.addField("level", level); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("count", count); + data.addField("volume", volume); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("station", station); + data.addField("flowrate", (double)(f1)+(double)(f2)/100); + data.addField("duration", f3); + data.addField("alert_setpoint", (double)(f4)+(double)(f5)/100); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_warning(const char *name, uint32_t level, float value) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("warning", name); + data.addField("level", (int)level); + data.addField("currentvalue", value); + write_influx_data(data); +} + +#else + +void OSInfluxDB::influxdb_send_state(const char *name, int state) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("state", state) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_station(const char *name, uint32_t station, int state) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("station", (int)station) + .field("state", state) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_program(const char *name, uint32_t nr, float level) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("program", (int)nr) + .field("level", level) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("count", (int)count) + .field("volume", volume) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("station", (int)(station)) + .field("flowrate", (double)(f1)+(double)(f2)/100) + .field("duration", f3) + .field("alert_setpoint", (double)(f4)+(double)(f5)/100) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_warning(const char *name, uint32_t level, float value) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("warning", name) + .field("level", (int)level) + .field("currentvalue", value) + .timestamp(millis()) + .post_http(*client); +} +#endif + +void OSInfluxDB::push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { + if (!isEnabled()) + return; + + uint32_t volume; + + switch(type) { + case NOTIFY_STATION_ON: + influxdb_send_station("station", lval, 1); + break; + + case NOTIFY_FLOW_ALERT:{ + //influxdb_send_flowalert("flowalert", lval, f1, f2, f3, f4, f5); + break; + } + + case NOTIFY_STATION_OFF: + influxdb_send_station("station", lval, 0); + break; + + case NOTIFY_PROGRAM_SCHED: + influxdb_send_program("program sched", lval, fval); + break; + + case NOTIFY_SENSOR1: + influxdb_send_state("sensor1", (int)fval); + break; + + case NOTIFY_SENSOR2: + influxdb_send_state("sensor2", (int)fval); + break; + + case NOTIFY_RAINDELAY: + influxdb_send_state("raindelay", (int)fval); + break; + + case NOTIFY_FLOWSENSOR: + volume = os.iopts[IOPT_PULSE_RATE_1]; + volume = (volume<<8)+os.iopts[IOPT_PULSE_RATE_0]; + volume = lval*volume; + influxdb_send_flowsensor("flowsensor", lval, (float)volume/100); + break; + + case NOTIFY_WEATHER_UPDATE: + influxdb_send_state("waterlevel", (int)fval); + break; + + case NOTIFY_REBOOT: + break; + + case NOTIFY_MONITOR_LOW: + case NOTIFY_MONITOR_MID: + case NOTIFY_MONITOR_HIGH: + influxdb_send_flowsensor(sval, lval, fval); + break; + + } +} \ No newline at end of file diff --git a/osinfluxdb.h b/osinfluxdb.h index 808fe708b..cfb21cb4c 100644 --- a/osinfluxdb.h +++ b/osinfluxdb.h @@ -42,6 +42,13 @@ class OSInfluxDB { bool enabled; bool initialized; void init(); + void influxdb_send_state(const char *name, int state); + void influxdb_send_station(const char *name, uint32_t station, int state); + void influxdb_send_program(const char *name, uint32_t nr, float level); + void influxdb_send_flowsensor(const char *name, uint32_t count, float volume); + void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5); + void influxdb_send_warning(const char *name, uint32_t level, float value); + public: ~OSInfluxDB(); void set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token); @@ -55,5 +62,6 @@ class OSInfluxDB { #else influxdb_cpp::server_info * get_client(); #endif + void push_message(uint16_t type, uint32_t lval, float fval, const char* sval); }; #endif \ No newline at end of file diff --git a/sensors.cpp b/sensors.cpp index cb4569812..fa2be7b92 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2837,7 +2837,13 @@ void push_message(Monitor_t * mon, float value) { case 2: type = NOTIFY_MONITOR_HIGH; break; default: return; } - push_message(type, (uint32_t)mon->prio, value, mon->name); + char name[20]; + strncpy(name, mon->name, sizeof(name)-1); + DEBUG_PRINT("monitoring: activated "); + DEBUG_PRINT(name); + DEBUG_PRINT(" - "); + DEBUG_PRINTLN(type); + push_message(type, (uint32_t)mon->prio, value, name); } void check_monitors() { From ca44a20d373bc31c498e3a05c914448c2e79ef94 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 23 Oct 2024 23:49:16 +0200 Subject: [PATCH 245/281] fixt warnings via MQTT --- main.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index d1eec04c5..41c69be7f 100644 --- a/main.cpp +++ b/main.cpp @@ -1890,7 +1890,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { if (os.mqtt.enabled()) { strcpy_P(topic, PSTR("monitoring")); - int len = strlen(postval); + int len = strlen(payload); snprintf_P(payload+len, PUSH_PAYLOAD_LEN-len, PSTR("{\"warning\":\"%s\",\"prio\":%u,\"value\":%d.%02d}"), sval, lval, (int)fval, (int)fval*100%100); } if (ifttt_enabled || email_enabled) { @@ -1902,6 +1902,11 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { } + DEBUG_PRINT("topic: "); + DEBUG_PRINTLN(topic); + DEBUG_PRINT("payload: "); + DEBUG_PRINTLN(payload); + if (os.mqtt.enabled() && strlen(topic) && strlen(payload)) os.mqtt.publish(topic, payload); From b862c5d73342f1cf2e5e9e180c2f6340119e60ac Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 23 Oct 2024 23:55:19 +0200 Subject: [PATCH 246/281] Fixt length check of monitor name --- defines.h | 2 +- mainArduino.ino.cpp | 17 +++++++++++++++++ sensors.cpp | 4 ++-- 3 files changed, 20 insertions(+), 3 deletions(-) create mode 100644 mainArduino.ino.cpp diff --git a/defines.h b/defines.h index c40fc4503..e2ed4fee8 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; diff --git a/mainArduino.ino.cpp b/mainArduino.ino.cpp new file mode 100644 index 000000000..633dcb7f0 --- /dev/null +++ b/mainArduino.ino.cpp @@ -0,0 +1,17 @@ +# 1 "C:\\Users\\schlo\\AppData\\Local\\Temp\\tmp56m7eaa7" +#include +# 1 "D:/Projekte/opensprinkler-firmware/mainArduino.ino" +#include "OpenSprinkler.h" + +void do_setup(); +void do_loop(); +void setup(); +void loop(); +#line 6 "D:/Projekte/opensprinkler-firmware/mainArduino.ino" +void setup() { + do_setup(); +} + +void loop() { + do_loop(); +} \ No newline at end of file diff --git a/sensors.cpp b/sensors.cpp index fa2be7b92..d2ff8c36a 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2731,7 +2731,7 @@ bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, //p->active = false; p->maxRuntime = maxRuntime; p->prio = prio; - strncpy(p->name, name, sizeof(p->name)); + strncpy(p->name, name, sizeof(p->name)-1); monitor_save(); check_monitors(); return HTTP_RQT_SUCCESS; @@ -2754,7 +2754,7 @@ bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, p->active = false; p->maxRuntime = maxRuntime; p->prio = prio; - strncpy(p->name, name, sizeof(p->name)); + strncpy(p->name, name, sizeof(p->name)-1); if (last) { p->next = last->next; last->next = p; From 935e65de7f25e81de44e67ac2c091a0dbd6d77de Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 3 Nov 2024 15:55:25 +0100 Subject: [PATCH 247/281] Optimized Flow Alert, calculating avg values. Station now with editor for flow setpoint value --- .gitignore | 1 + OpenSprinkler.cpp | 27 +++++++ OpenSprinkler.h | 10 ++- defines.h | 4 +- main.cpp | 159 ++++++++++++++++----------------------- opensprinkler_server.cpp | 47 +++++++++++- sensors.cpp | 2 +- sensors.h | 8 +- 8 files changed, 152 insertions(+), 106 deletions(-) diff --git a/.gitignore b/.gitignore index 34dd9aeb3..778ac913c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ influx.json !build.sh !startOpenSprinkler.sh !updater.sh +DEADJOE diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 6c3b57a39..37c66c834 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -69,6 +69,8 @@ unsigned char OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_grp[MAX_NUM_STATIONS]; +uint16_t OpenSprinkler::attrib_fas[MAX_NUM_STATIONS]; +uint16_t OpenSprinkler::attrib_favg[MAX_NUM_STATIONS]; unsigned char OpenSprinkler::masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; RCSwitch OpenSprinkler::rfswitch; OSInfluxDB OpenSprinkler::influxdb; @@ -1704,6 +1706,25 @@ unsigned char OpenSprinkler::is_sequential_station(unsigned char sid) { return attrib_grp[sid] != PARALLEL_GROUP_ID; } +uint16_t OpenSprinkler::get_flow_alert_setpoint(unsigned char sid) { + return attrib_fas[sid]; +} + +void OpenSprinkler::set_flow_alert_setpoint(unsigned char sid, uint16_t value) { + attrib_fas[sid] = value; +} + +uint16_t OpenSprinkler::get_flow_avg_value(unsigned char sid) { + return attrib_favg[sid]; +} + +void OpenSprinkler::set_flow_avg_value(unsigned char sid, uint16_t value) { + if (value != attrib_favg[sid]) { + attrib_favg[sid] = value; + file_write_block(STATIONS3_FILENAME, &value, (uint16_t)sid * sizeof(uint16_t), sizeof(uint16_t)); + } +} + unsigned char OpenSprinkler::is_master_station(unsigned char sid) { for (unsigned char mas = 0; mas < NUM_MASTER_ZONES; mas++) { if (get_master_id(mas) && (get_master_id(mas) - 1 == sid)) { @@ -1789,6 +1810,8 @@ void OpenSprinkler::attribs_save() { } } } + file_write_block(STATIONS2_FILENAME, attrib_fas, 0, sizeof(attrib_fas)); + file_write_block(STATIONS3_FILENAME, attrib_favg, 0, sizeof(attrib_favg)); } /** Load all station attribs from file (backward compatibility) */ @@ -1805,6 +1828,10 @@ void OpenSprinkler::attribs_load() { memset(attrib_dis, 0, nboards); memset(attrib_spe, 0, nboards); memset(attrib_grp, 0, MAX_NUM_STATIONS); + memset(attrib_fas, 0, sizeof(attrib_fas)); + memset(attrib_favg, 0, sizeof(attrib_favg)); + file_read_block(STATIONS2_FILENAME, attrib_fas, 0, sizeof(attrib_fas)); + file_read_block(STATIONS3_FILENAME, attrib_favg, 0, sizeof(attrib_favg)); for(bid=0;bid struct NVConData #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files diff --git a/main.cpp b/main.cpp index 41c69be7f..1e7b27e7e 100644 --- a/main.cpp +++ b/main.cpp @@ -1245,7 +1245,30 @@ void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shif // log station run write_log(LOGDATA_STATION, curr_time); // LOG_TODO push_message(NOTIFY_STATION_OFF, sid, pd.lastrun.duration); - push_message(NOTIFY_FLOW_ALERT, sid, pd.lastrun.duration); + + //Flow altert? + if (pd.lastrun.duration > 90) { //check running > 90s + uint16_t flow_alert_setpoint = os.get_flow_alert_setpoint(sid); + //flow_last_gpm is actually collected and stored as pulses per minute, not gallons per minute + //Get Flow Pulse Rate factor and apply to flow_last_gpm when comparing and outputting + float flow_pulse_rate_factor = static_cast(os.iopts[IOPT_PULSE_RATE_1]) + static_cast(os.iopts[IOPT_PULSE_RATE_0]) / 100.0; + + float last_flow = flow_last_gpm * flow_pulse_rate_factor; + uint16_t avg_flow = os.get_flow_avg_value(sid); + uint16_t int_flow = (int)(last_flow * 100); + if (avg_flow > 0) + avg_flow = (avg_flow + int_flow) / 2; + else + avg_flow = int_flow; + os.set_flow_avg_value(sid, int_flow); + // Alert Check - Compare flow_gpm_alert_setpoint with flow_last_gpm and enable flow_alert_flag if flow is above setpoint + if (flow_alert_setpoint) { + float flow_gpm_alert_setpoint = (float)flow_alert_setpoint/100; + if (last_flow > flow_gpm_alert_setpoint) { + push_message(NOTIFY_FLOW_ALERT, sid, pd.lastrun.duration); + } + } + } } } @@ -1604,110 +1627,58 @@ void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { break; case NOTIFY_FLOW_ALERT:{ - //First determine if a Flow Alert should be sent based on flow amount and setpoint + uint16_t flow_alert_setpoint = os.get_flow_alert_setpoint(lval); + float flow_gpm_alert_setpoint = (float)flow_alert_setpoint/100; - //Added variable to track flow alert status - bool flow_alert_flag = false; + //flow_last_gpm is actually collected and stored as pulses per minute, not gallons per minute + //Get Flow Pulse Rate factor and apply to flow_last_gpm when comparing and outputting + float flow_pulse_rate_factor = static_cast(os.iopts[IOPT_PULSE_RATE_1]) + static_cast(os.iopts[IOPT_PULSE_RATE_0]) / 100.0; - //Added variable for flow_gpm_alert_setpoint and set default value to max - float flow_gpm_alert_setpoint = 999.9f; - - //Added variable for to get flow_pulse_rate_factor and set to 1 as default - float flow_pulse_rate_factor = 1; - - //Added variable for tmp station name char tmp_station_name[STATION_NAME_SIZE]; - - //Get satation name os.get_station_name(lval, tmp_station_name); - - //Extract flow_gpm_alert_setpoint from last 5 characters of station name - if (strlen(tmp_station_name) > 5) { - const char *station_name_last_five_chars = tmp_station_name; - station_name_last_five_chars = tmp_station_name + strlen(tmp_station_name) - 5; - - //Convert last five characters to number and check if valid - if (sscanf(station_name_last_five_chars, "%f", &flow_gpm_alert_setpoint) == 1) { - - //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 - //Get Flow Pulse Rate factor and apply to flow_last_gpm when comparing and outputting - flow_pulse_rate_factor = static_cast(os.iopts[IOPT_PULSE_RATE_1]) + static_cast(os.iopts[IOPT_PULSE_RATE_0]) / 100.0; - - // 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*flow_pulse_rate_factor) > flow_gpm_alert_setpoint) { - flow_alert_flag = true; - } - } else { - //Could not convert to a valid number. If a number is not detected as a station name suffix, never send an alert - flow_alert_flag = false; - } - } else { - //Station name was not long enough to include 5 character flow setpoint. - flow_alert_flag = false; + int f1 = (int)(flow_last_gpm*flow_pulse_rate_factor); + int f2 = (int)((flow_last_gpm*flow_pulse_rate_factor) * 100) % 100; + int f3 = (int)fval; + int f4 = (int)flow_gpm_alert_setpoint; + int f5 = (int)(flow_gpm_alert_setpoint * 100) % 100; + if (os.mqtt.enabled()) { + //Format mqtt message + snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d/alert/flow"), lval); + snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%d.%02d,\"duration\":%d,\"alert_setpoint\":%d.%02d}"), + f1, f2, f3, f4, f5); } + if (ifttt_enabled || email_enabled) { + //Format ifttt\email message + // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" + time_os_t curr_time = os.now_tz(); + struct tm *tm_info = localtime((time_t*)&curr_time); + char formatted_time[TMP_BUFFER_SIZE]; + strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%d %I:%M:%S %p", tm_info); + strcat_P(postval, PSTR("
")); + strcat(postval, formatted_time); + + strcat_P(postval, PSTR("
Station: ")); + //Truncate flow setpoint value off station name to shorten ifttt\email message + tmp_station_name[(strlen(tmp_station_name) - 5)] = '\0'; + strcat_P(postval, tmp_station_name); - // If flow_alert_flag is true, format the appropriate messages, else don't send alert - if (flow_alert_flag) { - - int f1 = (int)(flow_last_gpm*flow_pulse_rate_factor); - int f2 = (int)((flow_last_gpm*flow_pulse_rate_factor) * 100) % 100; - int f3 = (int)fval; - int f4 = (int)flow_gpm_alert_setpoint; - int f5 = (int)(flow_gpm_alert_setpoint * 100) % 100; - - if (os.mqtt.enabled()) { - //Format mqtt message - snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d/alert/flow"), lval); - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%d.%02d,\"duration\":%d,\"alert_setpoint\":%d.%02d}"), - f1, f2, f3, f4, f5); - } - - - if (ifttt_enabled || email_enabled) { - //Format ifttt\email message - - // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" - time_os_t curr_time = os.now_tz(); - struct tm *tm_info = localtime((time_t*)&curr_time); - char formatted_time[TMP_BUFFER_SIZE]; - strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%d %I:%M:%S %p", tm_info); - strcat_P(postval, PSTR("
")); - strcat(postval, formatted_time); - - strcat_P(postval, PSTR("
Station: ")); - //Truncate flow setpoint value off station name to shorten ifttt\email message - tmp_station_name[(strlen(tmp_station_name) - 5)] = '\0'; - strcat_P(postval, tmp_station_name); - - if((int)fval == 0){ - strcat_P(postval, PSTR("")); - } else { // master on event does not have duration attached to it - strcat_P(postval, PSTR("
Duration: ")); - size_t len = strlen(postval); - snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR(" %d minutes %d seconds"), (int)fval/60, (int)fval%60); - } - - strcat_P(postval, PSTR("

FLOW ALERT!")); + if((int)fval == 0){ + strcat_P(postval, PSTR("")); + } else { // master on event does not have duration attached to it + strcat_P(postval, PSTR("
Duration: ")); size_t len = strlen(postval); - snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR("
Flow rate: %d.%02d
Flow Alert Setpoint: %d.%02d"), - f1, f2, f4, f5); + snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR(" %d minutes %d seconds"), (int)fval/60, (int)fval%60); + } - if(email_enabled) { - email_message.subject += PSTR("- FLOW ALERT"); - } + strcat_P(postval, PSTR("

FLOW ALERT!")); + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR("
Flow rate: %d.%02d
Flow Alert Setpoint: %d.%02d"), + f1, f2, f4, f5); + if(email_enabled) { + email_message.subject += PSTR("- FLOW ALERT"); } - - } else { - //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; - email_enabled=false; } - break; } diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index c497421b9..2fdc3e74e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -466,6 +466,20 @@ void server_json_stations_attrib(const char* name, unsigned char *attrib) bfill.emit_p(PSTR("],")); } +void server_json_stations_attrib16(const char* name, uint16_t *attrib) +{ + bfill.emit_p(PSTR("\"$F\":["), name); + for(unsigned char bid=0;bidos.nstations) handle_return(HTML_DATA_OUTOFBOUND); if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("st"), true) && - findKeyVal(FKV_SOURCE, tmp_buffer+1, TMP_BUFFER_SIZE-1, PSTR("sd"), true)) { + findKeyVal(FKV_SOURCE, tmp_buffer+1, TMP_BUFFER_SIZE-1, PSTR("sd"), true)) { tmp_buffer[0]-='0'; tmp_buffer[STATION_SPECIAL_DATA_SIZE] = 0; @@ -2978,7 +3017,7 @@ void server_monitor_config(OTF_PARAMS_DEF) { handle_return(HTML_DATA_MISSING); double value2 = atof(tmp_buffer); // Value 2 - char name[20] = {0}; + char name[30] = {0}; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) strncpy(name, tmp_buffer, sizeof(name)-1); @@ -3133,7 +3172,7 @@ void server_sensorprog_config(OTF_PARAMS_DEF) { handle_return(HTML_DATA_MISSING); double max = atof(tmp_buffer); // Max value - char name[20] = {0}; + char name[30] = {0}; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) strncpy(name, tmp_buffer, sizeof(name)-1); @@ -3185,7 +3224,7 @@ void server_sensorprog_list(OTF_PARAMS_DEF) { uint nr = 0; int prog = -1; uint sensor_nr = 0; - + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) nr = strtoul(tmp_buffer, NULL, 0); diff --git a/sensors.cpp b/sensors.cpp index d2ff8c36a..29b0d9213 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2837,7 +2837,7 @@ void push_message(Monitor_t * mon, float value) { case 2: type = NOTIFY_MONITOR_HIGH; break; default: return; } - char name[20]; + char name[30]; strncpy(name, mon->name, sizeof(name)-1); DEBUG_PRINT("monitoring: activated "); DEBUG_PRINT(name); diff --git a/sensors.h b/sensors.h index fd3ae724b..1bfc26ca8 100644 --- a/sensors.h +++ b/sensors.h @@ -222,8 +222,8 @@ typedef struct ProgSensorAdjust { double factor2; double min; double max; - char name[20]; - unsigned char undef[12]; // for later + char name[30]; + unsigned char undef[2]; // for later ProgSensorAdjust *next; } ProgSensorAdjust_t; #define PROGSENSOR_STORE_SIZE \ @@ -257,10 +257,10 @@ typedef struct Monitor { double value2; // Secondary boolean active; ulong time; - char name[20]; + char name[30]; ulong maxRuntime; uint8_t prio; - unsigned char undef[9]; // for later + unsigned char undef[20]; // for later Monitor *next; } Monitor_t; #define MONITOR_STORE_SIZE (sizeof(Monitor_t) - sizeof(char *) - sizeof(Monitor_t *)) From 1d6baae42adcbc7bb6dfe3a564f2bc74a851d327 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 3 Nov 2024 23:28:10 +0100 Subject: [PATCH 248/281] Fix: Do load load invalid monitor data --- mainArduino.ino.cpp | 17 ----------------- sensors.cpp | 3 ++- 2 files changed, 2 insertions(+), 18 deletions(-) delete mode 100644 mainArduino.ino.cpp diff --git a/mainArduino.ino.cpp b/mainArduino.ino.cpp deleted file mode 100644 index 633dcb7f0..000000000 --- a/mainArduino.ino.cpp +++ /dev/null @@ -1,17 +0,0 @@ -# 1 "C:\\Users\\schlo\\AppData\\Local\\Temp\\tmp56m7eaa7" -#include -# 1 "D:/Projekte/opensprinkler-firmware/mainArduino.ino" -#include "OpenSprinkler.h" - -void do_setup(); -void do_loop(); -void setup(); -void loop(); -#line 6 "D:/Projekte/opensprinkler-firmware/mainArduino.ino" -void setup() { - do_setup(); -} - -void loop() { - do_loop(); -} \ No newline at end of file diff --git a/sensors.cpp b/sensors.cpp index 29b0d9213..9f71e1e2f 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2649,7 +2649,8 @@ void monitor_load() { DEBUG_PRINTLN(F("monitor_load")); monitors = NULL; if (!file_exists(MONITOR_FILENAME)) return; - + if (file_size(MONITOR_FILENAME) % MONITOR_STORE_SIZE != 0) return; + ulong pos = 0; Monitor_t *last = NULL; while (true) { From 54a7e1f791ff5f79bfdb0945f93b4a6f2efb9ebe Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 12 Nov 2024 22:20:17 +0100 Subject: [PATCH 249/281] Bug Fix "decade" --- OpenSprinkler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 37c66c834..165c8f566 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1953,7 +1953,7 @@ unsigned char OpenSprinkler::get_station_bit(unsigned char sid) { /** Clear all station bits */ void OpenSprinkler::clear_all_station_bits() { unsigned char sid; - for(sid=0;sid<=MAX_NUM_STATIONS;sid++) { + for(sid=0;sid Date: Fri, 15 Nov 2024 19:14:24 +0100 Subject: [PATCH 250/281] Fix timed program start, fix broken mqtt+influx db --- main.cpp | 2 +- opensprinkler_server.cpp | 11 +++++++++++ osinfluxdb.cpp | 4 ++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 1e7b27e7e..16566c85f 100644 --- a/main.cpp +++ b/main.cpp @@ -785,7 +785,7 @@ void do_loop() os.checkwt_success_lasttime = 0; check_weather(); } - break; + //break; // program match found // check and process special program command diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 2fdc3e74e..d416932c9 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -1283,6 +1283,15 @@ void server_json_controller_main(OTF_PARAMS_DEF) { #endif char mqtt_opt[MAX_SOPTS_SIZE]; os.sopt_load(SOPT_MQTT_OPTS, mqtt_opt); + //DEBUG_PRINTLN(mqtt_opt); + + //Test for invalid mqtt options: + int l = strlen(mqtt_opt); + if (l > 0 && mqtt_opt[l-1] != '"') { //first+last char + mqtt_opt[l] = '"'; + mqtt_opt[l+1] = 0; + } + //DEBUG_PRINTLN(mqtt_opt); bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); @@ -1651,6 +1660,8 @@ void server_change_options(OTF_PARAMS_DEF) #if !defined(USE_OTF) urlDecode(tmp_buffer); #endif + DEBUG_PRINTLN("MQTT:"); + DEBUG_PRINTLN(tmp_buffer); os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); os.status.req_mqtt_restart = true; } else if (keyfound) { diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index fab8e4278..e7690e802 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -80,6 +80,10 @@ void OSInfluxDB::get_influx_config(char *json) { json[size] = 0; //DEBUG_PRINT(F("influx config=")); //DEBUG_PRINTLN(tmp_buffer); + if (size > 10 && (json[size-2] != '"' || json[size-1] != '}')) { + strcpy(json, "{\"en\":0}"); + set_influx_config(json); + } } if (json[0] != '{' || strchr(json, '}') != strrchr(json, '}')) { strcpy(json, "{\"en\":0}"); From 0579bcff090f94326da1e886a680128939158590 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 15 Nov 2024 19:20:44 +0100 Subject: [PATCH 251/281] Version 2.3.3(171) --- defines.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/defines.h b/defines.h index d4a73a332..1e6191f89 100644 --- a/defines.h +++ b/defines.h @@ -35,7 +35,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 170 // Firmware minor version +#define OS_FW_MINOR 171 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler From 7c91d5613261382cadf2c8ea65d43dd44fedad42 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 27 Dec 2024 14:27:54 +0100 Subject: [PATCH 252/281] Monitor and Control added AND, OR, XOR, NOT and (digital SN1/2) Rain-/Soilsensor support --- defines.h | 4 +- opensprinkler_server.cpp | 151 +++++++++++++++++++++++++++++++-------- sensors.cpp | 115 ++++++++++++++++++----------- sensors.h | 43 +++++++++-- 4 files changed, 236 insertions(+), 77 deletions(-) diff --git a/defines.h b/defines.h index 1e6191f89..95e466591 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; @@ -35,7 +35,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 171 // Firmware minor version +#define OS_FW_MINOR 172 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index d416932c9..e0257c9d9 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2976,7 +2976,13 @@ void server_monitor_types(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"monitortypes\": [")); bfill.emit_p(PSTR("{\"name\":\"Min\",\"type\":$D},"), MONITOR_MIN); - bfill.emit_p(PSTR("{\"name\":\"Max\",\"type\":$D}]}"), MONITOR_MAX); + bfill.emit_p(PSTR("{\"name\":\"Max\",\"type\":$D},"), MONITOR_MAX); + bfill.emit_p(PSTR("{\"name\":\"SN 1/2\",\"type\":$D},"), MONITOR_SENSOR12); + bfill.emit_p(PSTR("{\"name\":\"AND\",\"type\":$D},"), MONITOR_AND); + bfill.emit_p(PSTR("{\"name\":\"OR\",\"type\":$D},"), MONITOR_OR); + bfill.emit_p(PSTR("{\"name\":\"XOR\",\"type\":$D},"), MONITOR_XOR); + bfill.emit_p(PSTR("{\"name\":\"NOT\",\"type\":$D}" ), MONITOR_NOT); + bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); } @@ -2995,38 +3001,30 @@ void server_monitor_config(OTF_PARAMS_DEF) { if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) handle_return(HTML_DATA_MISSING); - uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + uint16_t nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr if (nr == 0) handle_return(HTML_DATA_MISSING); if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); - uint type = strtoul(tmp_buffer, NULL, 0); // Adjustment type + uint16_t type = strtoul(tmp_buffer, NULL, 0); // Adjustment type if (type == 0) { monitor_delete(nr); handle_return(HTML_SUCCESS); } - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) - handle_return(HTML_DATA_MISSING); - uint sensor = strtoul(tmp_buffer, NULL, 0); // Sensor nr + uint16_t sensor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + sensor = strtoul(tmp_buffer, NULL, 0); // Sensor nr if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) handle_return(HTML_DATA_MISSING); - uint prog = strtoul(tmp_buffer, NULL, 0); // Program nr + uint16_t prog = strtoul(tmp_buffer, NULL, 0); // Program nr if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("zone"), true)) handle_return(HTML_DATA_MISSING); - uint zone = strtoul(tmp_buffer, NULL, 0); // Zone - - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value1"), true)) - handle_return(HTML_DATA_MISSING); - double value1 = atof(tmp_buffer); // Value 1 - - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value2"), true)) - handle_return(HTML_DATA_MISSING); - double value2 = atof(tmp_buffer); // Value 2 + uint16_t zone = strtoul(tmp_buffer, NULL, 0); // Zone char name[30] = {0}; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) @@ -3040,24 +3038,117 @@ void server_monitor_config(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prio"), true)) prio = strtoul(tmp_buffer, NULL, 0); // prio - int ret = monitor_define(nr, type, sensor, prog, zone, value1, value2, name, maxRuntime, prio); + //type-dependend parameters: + //type = MIN/MAX of sensor value: + double value1 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value1"), true)) + value1 = atof(tmp_buffer); // Value 1 + + double value2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value2"), true)) + value2 = atof(tmp_buffer); // Value 2 + + //type = SENSOR12 + uint16_t sensor12 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor12"), true)) + sensor12 = strtoul(tmp_buffer, NULL, 0); + bool invers = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers"), true)) + invers = strtoul(tmp_buffer, NULL, 0) > 0; + + //type = AND/OR/XOR: + uint16_t monitor1 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor1"), true)) + monitor1 = strtoul(tmp_buffer, NULL, 0); + uint16_t monitor2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor2"), true)) + monitor2 = strtoul(tmp_buffer, NULL, 0); + uint16_t monitor3 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor3"), true)) + monitor3 = strtoul(tmp_buffer, NULL, 0); + uint16_t monitor4 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor4"), true)) + monitor4 = strtoul(tmp_buffer, NULL, 0); + + bool invers1 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers1"), true)) + invers1 = strtoul(tmp_buffer, NULL, 0) > 0; + bool invers2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers2"), true)) + invers2 = strtoul(tmp_buffer, NULL, 0) > 0; + bool invers3 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers3"), true)) + invers3 = strtoul(tmp_buffer, NULL, 0) > 0; + bool invers4 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers4"), true)) + invers4 = strtoul(tmp_buffer, NULL, 0) > 0; + + //type = NOT + uint16_t monitor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor"), true)) + monitor = strtoul(tmp_buffer, NULL, 0); + + Monitor_Union_t m; + switch (type) { + case MONITOR_MIN: + case MONITOR_MAX: + m = (Monitor_Union_t){.minmax = {.value1 = value1, .value2 = value2}}; + break; + case MONITOR_SENSOR12: + m = (Monitor_Union_t){.sensor12 = {.sensor12 = sensor12, .invers = invers}}; + break; + case MONITOR_AND: + case MONITOR_OR: + case MONITOR_XOR: + m = (Monitor_Union_t){.andorxor = {.monitor1 = monitor1, .monitor2 = monitor2, .monitor3 = monitor3, .monitor4 = monitor4, + .invers1 = invers1, .invers2 = invers2, .invers3 = invers3, .invers4 = invers4}}; + break; + case MONITOR_NOT: + m = (Monitor_Union_t){.mnot = {.monitor = monitor}}; + break; + default: handle_return(HTML_DATA_FORMATERROR); + } + int ret = monitor_define(nr, type, sensor, prog, zone, m, name, maxRuntime, prio); ret = ret >= HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; handle_return(ret); } void monitorconfig_json(Monitor_t *mon) { - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"zone\":$D,\"value1\":$E,\"value2\":$E,\"name\":\"$S\",\"maxrun\":$L,\"prio\":$D,\"active\":$D}"), - mon->nr, - mon->type, - mon->sensor, - mon->prog, - mon->zone, - isnan(mon->value1)?0:mon->value1, - isnan(mon->value2)?0:mon->value2, - mon->name, - mon->maxRuntime, - mon->prio, - mon->active); + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"zone\":$D,\"name\":\"$S\",\"maxrun\":$L,\"prio\":$D,\"active\":$D,"), + mon->nr, + mon->type, + mon->sensor, + mon->prog, + mon->zone, + mon->name, + mon->maxRuntime, + mon->prio, + mon->active); + + switch(mon->type) { + case MONITOR_MIN: + case MONITOR_MAX: + bfill.emit_p(PSTR("\"value1\":$E,\"value2\":$E}"), + isnan(mon->m.minmax.value1)?0:mon->m.minmax.value1, + isnan(mon->m.minmax.value2)?0:mon->m.minmax.value2); + break; + case MONITOR_SENSOR12: + bfill.emit_p(PSTR("\"sensor12\":$D,\"invers\":$D}"), + mon->m.sensor12.sensor12, + mon->m.sensor12.invers); + break; + case MONITOR_AND: + case MONITOR_OR: + case MONITOR_XOR: + bfill.emit_p(PSTR("\"monitor1\":$D,\"monitor2\":$D,\"monitor3\":$D,\"monitor4\":$D,\"invers1\":$D,\"invers2\":$D,\"invers3\":$D,\"invers4\":$D}"), + mon->m.andorxor.monitor1, mon->m.andorxor.monitor2, mon->m.andorxor.monitor3, mon->m.andorxor.monitor4, + mon->m.andorxor.invers1, mon->m.andorxor.invers2, mon->m.andorxor.invers3, mon->m.andorxor.invers4); + break; + case MONITOR_NOT: + bfill.emit_p(PSTR("\"monitor\":$D}"), + mon->m.mnot.monitor); + break; + } } void monitorconfig_json() { @@ -3716,7 +3807,7 @@ void server_sensorconfig_backup(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); } send_packet(OTF_PARAMS); - if (backup & BACKUP_SENSORS) { + if (backup & BACKUP_MONITORS) { bfill.emit_p(PSTR(",\"monitors\":[")); monitorconfig_json(); bfill.emit_p(PSTR("]")); diff --git a/sensors.cpp b/sensors.cpp index 9f71e1e2f..4725f2ad3 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2715,8 +2715,7 @@ int monitor_delete(uint nr) { return HTTP_RQT_NOT_RECEIVED; } -bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, - double value1, double value2, char * name, ulong maxRuntime, uint8_t prio) { +bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, const Monitor_Union_t m, char * name, ulong maxRuntime, uint8_t prio) { Monitor_t *p = monitors; Monitor_t *last = NULL; @@ -2727,8 +2726,7 @@ bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, p->sensor = sensor; p->prog = prog; p->zone = zone; - p->value1 = value1; - p->value2 = value2; + p->m = m; //p->active = false; p->maxRuntime = maxRuntime; p->prio = prio; @@ -2748,10 +2746,9 @@ bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, p->nr = nr; p->type = type; p->sensor = sensor; - p->zone = zone; p->prog = prog; - p->value1 = value1; - p->value2 = value2; + p->zone = zone; + p->m = m; p->active = false; p->maxRuntime = maxRuntime; p->prio = prio; @@ -2825,8 +2822,10 @@ void stop_monitor_action(Monitor_t * mon) { if (mon->zone > 0) { int sid = mon->zone-1; RuntimeQueueStruct *q = pd.queue + pd.station_qid[sid]; - q->deque_time = mon->time; - turn_off_station(sid, mon->time); + if (q) { + q->deque_time = mon->time; + turn_off_station(sid, mon->time); + } } } @@ -2847,49 +2846,83 @@ void push_message(Monitor_t * mon, float value) { push_message(type, (uint32_t)mon->prio, value, name); } +bool get_monitor(uint nr, bool inv, bool defaultBool) { + Monitor_t *mon = monitor_by_nr(nr); + if (!mon) return defaultBool; + return inv ? !mon->active : mon->active; +} + void check_monitors() { Monitor_t *mon = monitors; while (mon) { uint nr = mon->nr; - Sensor_t * sensor = sensor_by_nr(mon->sensor); - if (sensor && sensor->flags.data_ok) { - double value = sensor->last_data; - if (!mon->active) { //Check for start monitor actions: - switch(mon->type) { - case MONITOR_MIN: - if (value <= mon->value1) { - mon->active = true; - start_monitor_action(mon); - push_message(mon, value); - mon = monitor_by_nr(nr); //restart because if send by mail we unloaded+reloaded the monitors - } - break; - case MONITOR_MAX: - if (value >= mon->value1) { + + bool wasActive = mon->active; + double value = 0; + + switch(mon->type) { + case MONITOR_MIN: + case MONITOR_MAX: { + Sensor_t * sensor = sensor_by_nr(mon->sensor); + if (sensor && sensor->flags.data_ok) { + value = sensor->last_data; + + if (!mon->active) { + if ((mon->type == MONITOR_MIN && value <= mon->m.minmax.value1) || + (mon->type == MONITOR_MAX && value >= mon->m.minmax.value1)) { mon->active = true; - start_monitor_action(mon); - push_message(mon, value); - mon = monitor_by_nr(nr); //restart because if send by mail we unloaded+reloaded the monitors - } - break; - } - } else { //mon->active, check for stop monitor actions: - switch(mon->type) { - case MONITOR_MIN: - if (value >= mon->value2) { - mon->active = false; - stop_monitor_action(mon); } - break; - case MONITOR_MAX: - if (value <= mon->value2) { + } else { + if ((mon->type == MONITOR_MIN && value >= mon->m.minmax.value2) || + (mon->type == MONITOR_MAX && value <= mon->m.minmax.value2)) { mon->active = false; - stop_monitor_action(mon); } - break; + } } + break; } + + case MONITOR_SENSOR12: + if (mon->m.sensor12.sensor12 == 1) + if (os.iopts[IOPT_SENSOR1_TYPE] == SENSOR_TYPE_RAIN || os.iopts[IOPT_SENSOR1_TYPE] == SENSOR_TYPE_SOIL) + mon->active = mon->m.sensor12.invers? !os.status.sensor1_active : os.status.sensor1_active; + if (mon->m.sensor12.sensor12 == 2) + if (os.iopts[IOPT_SENSOR2_TYPE] == SENSOR_TYPE_RAIN || os.iopts[IOPT_SENSOR2_TYPE] == SENSOR_TYPE_SOIL) + mon->active = mon->m.sensor12.invers? !os.status.sensor2_active : os.status.sensor2_active; + break; + + case MONITOR_AND: + mon->active = get_monitor(mon->m.andorxor.monitor1, mon->m.andorxor.invers1, true) && + get_monitor(mon->m.andorxor.monitor2, mon->m.andorxor.invers2, true) && + get_monitor(mon->m.andorxor.monitor3, mon->m.andorxor.invers3, true) && + get_monitor(mon->m.andorxor.monitor4, mon->m.andorxor.invers4, true); + break; + case MONITOR_OR: + mon->active = get_monitor(mon->m.andorxor.monitor1, mon->m.andorxor.invers1, false) || + get_monitor(mon->m.andorxor.monitor2, mon->m.andorxor.invers2, false) || + get_monitor(mon->m.andorxor.monitor3, mon->m.andorxor.invers3, false) || + get_monitor(mon->m.andorxor.monitor4, mon->m.andorxor.invers4, false); + break; + case MONITOR_XOR: + mon->active = get_monitor(mon->m.andorxor.monitor1, mon->m.andorxor.invers1, false) ^ + get_monitor(mon->m.andorxor.monitor2, mon->m.andorxor.invers2, false) ^ + get_monitor(mon->m.andorxor.monitor3, mon->m.andorxor.invers3, false) ^ + get_monitor(mon->m.andorxor.monitor4, mon->m.andorxor.invers4, false); + break; + case MONITOR_NOT: + mon->active = get_monitor(mon->m.mnot.monitor, true, false); + break; + } + + if (mon->active != wasActive) { + if (mon->active) { + start_monitor_action(mon); + push_message(mon, value); + mon = monitor_by_nr(nr); //restart because if send by mail we unloaded+reloaded the monitors + } else { + stop_monitor_action(mon); } } + mon = mon->next; } } \ No newline at end of file diff --git a/sensors.h b/sensors.h index 1bfc26ca8..2bb8274b5 100644 --- a/sensors.h +++ b/sensors.h @@ -246,6 +246,43 @@ typedef struct SensorUrl { #define MONITOR_DELETE 0 #define MONITOR_MIN 1 #define MONITOR_MAX 2 +#define MONITOR_SENSOR12 3 //Digital OS Sensors +#define MONITOR_AND 10 +#define MONITOR_OR 11 +#define MONITOR_XOR 12 +#define MONITOR_NOT 13 + +typedef struct Monitor_MINMAX { // type = 1+2 + double value1; // MIN/MAX + double value2; // Secondary +} Monitor_MINMAX_t; + +typedef struct Monitor_SENSOR12 { // type = 3 + uint16_t sensor12; + bool invers : 1; +} Monitor_SENSOR12_t; + +typedef struct Monitor_ANDORXOR { // type = 10+11+12 + uint16_t monitor1; + uint16_t monitor2; + uint16_t monitor3; + uint16_t monitor4; + bool invers1 : 1; + bool invers2 : 1; + bool invers3 : 1; + bool invers4 : 1; +} Monitor_ANDORXOR_t; + +typedef struct Monitor_NOT { // type = 13 + uint16_t monitor; +} Monitor_NOT_t; + +typedef union Monitor_Union { + Monitor_MINMAX_t minmax; // type = 1+2 + Monitor_SENSOR12_t sensor12; // type = 3 + Monitor_ANDORXOR_t andorxor; // type = 10+11+12 + Monitor_NOT_t mnot; // type = 13 +} Monitor_Union_t; typedef struct Monitor { uint nr; @@ -253,8 +290,7 @@ typedef struct Monitor { uint sensor; // sensor-nr uint prog; // program-nr=pid uint zone; // Zone - double value1; // MIN/MAX - double value2; // Secondary + Monitor_Union_t m; boolean active; ulong time; char name[30]; @@ -390,8 +426,7 @@ void monitor_load(); void monitor_save(); int monitor_count(); int monitor_delete(uint nr); -bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, - double value1, double value2, char * name, ulong maxRuntime, uint8_t prio); +bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, const Monitor_Union_t m, char * name, ulong maxRuntime, uint8_t prio); Monitor_t * monitor_by_nr(uint nr); Monitor_t * monitor_by_idx(uint idx); void check_monitors(); From c0123118c33da823cb1fcf702c9f560aec45fc13 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 5 Jan 2025 03:12:54 +0100 Subject: [PATCH 253/281] Added Remote Monitor --- opensprinkler_server.cpp | 33 ++++++++++++++++++++---- sensors.cpp | 55 ++++++++++++++++++++++++++++++++++++++++ sensors.h | 8 ++++++ 3 files changed, 91 insertions(+), 5 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index e0257c9d9..81d0f54f3 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2981,7 +2981,8 @@ void server_monitor_types(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"name\":\"AND\",\"type\":$D},"), MONITOR_AND); bfill.emit_p(PSTR("{\"name\":\"OR\",\"type\":$D},"), MONITOR_OR); bfill.emit_p(PSTR("{\"name\":\"XOR\",\"type\":$D},"), MONITOR_XOR); - bfill.emit_p(PSTR("{\"name\":\"NOT\",\"type\":$D}" ), MONITOR_NOT); + bfill.emit_p(PSTR("{\"name\":\"NOT\",\"type\":$D}," ), MONITOR_NOT); + bfill.emit_p(PSTR("{\"name\":\"REMOTE\",\"type\":$D}" ), MONITOR_REMOTE); bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); } @@ -3083,11 +3084,22 @@ void server_monitor_config(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers4"), true)) invers4 = strtoul(tmp_buffer, NULL, 0) > 0; - //type = NOT + //type = NOT uint16_t monitor = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor"), true)) monitor = strtoul(tmp_buffer, NULL, 0); + //type = REMOTE ip + uint16_t rmonitor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rmonitor"), true)) + rmonitor = strtoul(tmp_buffer, NULL, 0); + uint32_t ip = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ip"), true)) + ip = strtoul(tmp_buffer, NULL, 0); + uint16_t port = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("port"), true)) + port = strtoul(tmp_buffer, NULL, 0); + Monitor_Union_t m; switch (type) { case MONITOR_MIN: @@ -3106,6 +3118,9 @@ void server_monitor_config(OTF_PARAMS_DEF) { case MONITOR_NOT: m = (Monitor_Union_t){.mnot = {.monitor = monitor}}; break; + case MONITOR_REMOTE: + m = (Monitor_Union_t){.remote = {.rmonitor = rmonitor, .ip = ip, .port = port}}; + break; default: handle_return(HTML_DATA_FORMATERROR); } int ret = monitor_define(nr, type, sensor, prog, zone, m, name, maxRuntime, prio); @@ -3114,7 +3129,7 @@ void server_monitor_config(OTF_PARAMS_DEF) { } void monitorconfig_json(Monitor_t *mon) { - bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"zone\":$D,\"name\":\"$S\",\"maxrun\":$L,\"prio\":$D,\"active\":$D,"), + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"zone\":$D,\"name\":\"$S\",\"maxrun\":$L,\"prio\":$D,\"active\":$D,\"time\":$L,"), mon->nr, mon->type, mon->sensor, @@ -3123,7 +3138,8 @@ void monitorconfig_json(Monitor_t *mon) { mon->name, mon->maxRuntime, mon->prio, - mon->active); + mon->active, + mon->time); switch(mon->type) { case MONITOR_MIN: @@ -3148,6 +3164,13 @@ void monitorconfig_json(Monitor_t *mon) { bfill.emit_p(PSTR("\"monitor\":$D}"), mon->m.mnot.monitor); break; + case MONITOR_REMOTE: + bfill.emit_p(PSTR("\"rmonitor\":$D,\"ip\":$L,\"port\":$D}"), + mon->m.remote.rmonitor, + mon->m.remote.ip, + mon->m.remote.port); + break; + } } @@ -3432,7 +3455,7 @@ static const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus, temperature mode", "Truebner SMT100 RS485 Modbus, permittivity mode", #if defined(ESP8266) - "ASB - voltage mode 0..4V", + "ASB - voltage mode 0..5V", "ASB - 0..3.3V to 0..100%", "ASB - SMT50 moisture mode", "ASB - SMT50 temperature mode", diff --git a/sensors.cpp b/sensors.cpp index 4725f2ad3..881c8c67b 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -2852,6 +2852,58 @@ bool get_monitor(uint nr, bool inv, bool defaultBool) { return inv ? !mon->active : mon->active; } +bool get_remote_monitor(Monitor_t *mon, bool defaultBool) { +#if defined(ESP8266) + IPAddress _ip(mon->m.remote.ip); + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; +#else + unsigned char ip[4]; + ip[3] = (unsigned char)((mon->m.remote.ip >> 24) & 0xFF); + ip[2] = (unsigned char)((mon->m.remote.ip >> 16) & 0xFF); + ip[1] = (unsigned char)((mon->m.remote.ip >> 8) & 0xFF); + ip[0] = (unsigned char)((mon->m.remote.ip & 0xFF)); +#endif + + DEBUG_PRINTLN(F("read_monitor_http")); + + char *p = tmp_buffer; + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE); + + bf.emit_p(PSTR("GET /ml?pw=$O&nr=$D"), SOPT_PASSWORD, mon->m.remote.rmonitor); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0], ip[1], ip[2], ip[3]); + + DEBUG_PRINTLN(p); + + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + int res = os.send_http_request(server, mon->m.remote.port, p, NULL, false, 500); + if (res == HTTP_RQT_SUCCESS) { + DEBUG_PRINTLN("Send Ok"); + p = ether_buffer; + DEBUG_PRINTLN(p); + + char buf[20]; + char *s = strstr(p, "\"time\":"); + if (s && extract(s, buf, sizeof(buf))) { + ulong time = strtoul(buf, NULL, 0); + if (time == 0 || time == mon->time) { + return defaultBool; + } else { + mon->time = time; + } + } + + s = strstr(p, "\"active\":"); + if (s && extract(s, buf, sizeof(buf))) { + return strtoul(buf, NULL, 0); + } + + return HTTP_RQT_SUCCESS; + } + return defaultBool; +} + void check_monitors() { Monitor_t *mon = monitors; while (mon) { @@ -2911,6 +2963,9 @@ void check_monitors() { case MONITOR_NOT: mon->active = get_monitor(mon->m.mnot.monitor, true, false); break; + case MONITOR_REMOTE: + mon->active = get_remote_monitor(mon, wasActive); + break; } if (mon->active != wasActive) { diff --git a/sensors.h b/sensors.h index 2bb8274b5..49cd45518 100644 --- a/sensors.h +++ b/sensors.h @@ -251,6 +251,7 @@ typedef struct SensorUrl { #define MONITOR_OR 11 #define MONITOR_XOR 12 #define MONITOR_NOT 13 +#define MONITOR_REMOTE 100 typedef struct Monitor_MINMAX { // type = 1+2 double value1; // MIN/MAX @@ -277,11 +278,18 @@ typedef struct Monitor_NOT { // type = 13 uint16_t monitor; } Monitor_NOT_t; +typedef struct Monitor_REMOTE { // type = 100 + uint16_t rmonitor; + uint32_t ip; + uint16_t port; +} Monitor_REMOTE_t; + typedef union Monitor_Union { Monitor_MINMAX_t minmax; // type = 1+2 Monitor_SENSOR12_t sensor12; // type = 3 Monitor_ANDORXOR_t andorxor; // type = 10+11+12 Monitor_NOT_t mnot; // type = 13 + Monitor_REMOTE_t remote; //type = 100 } Monitor_Union_t; typedef struct Monitor { From f758da08c0fe49d66cd52771a7279702e754af8a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Fri, 10 Jan 2025 23:52:36 +0100 Subject: [PATCH 254/281] added Truebner TH100 + Raspberry Pi Temp support --- Sensor API.txt | 4 ++ opensprinkler_server.cpp | 10 ++++ sensors.cpp | 37 +++++++++++++++ sensors.h | 99 +++++++++++++++++----------------------- 4 files changed, 93 insertions(+), 57 deletions(-) diff --git a/Sensor API.txt b/Sensor API.txt index 4484658e1..c26644780 100644 --- a/Sensor API.txt +++ b/Sensor API.txt @@ -47,6 +47,9 @@ SENSOR_NONE 0 // None or deleted sensor SENSOR_SMT100_MOIS 1 // Truebner SMT100 RS485, moisture mode SENSOR_SMT100_TEMP 2 // Truebner SMT100 RS485, temperature mode SENSOR_SMT100_PMTY 3 // Truebner SMT100 RS485, permittivity mode +SENSOR_TH100_MOIS 4 // Truebner TH100 RS485, humidity mode +SENSOR_TH100_TEMP 5 // Truebner TH100 RS485, temperature mode + SENSOR_ANALOG_EXTENSION_BOARD 10 // New OpenSprinkler analog extension board x8 - voltage mode 0..4V SENSOR_ANALOG_EXTENSION_BOARD_P 11 // New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% SENSOR_SMT50_MOIS 15 // New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 @@ -64,6 +67,7 @@ SENSOR_OSPI_ANALOG 50 // Old OSPi analog input - voltage mode 0..3 SENSOR_OSPI_ANALOG_P 51 // Old OSPi analog input - percent 0..3.3V to 0...100% SENSOR_OSPI_ANALOG_SMT50_MOIS 52 // Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 SENSOR_OSPI_ANALOG_SMT50_TEMP 53 // Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 +SENSOR_OSPI_INTERNAL_TEMP 54 // Internal OSPI Temperature SENSOR_MQTT 90 // subscribe to a MQTT server and query a value diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 81d0f54f3..92bcf0290 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -3412,6 +3412,8 @@ static const int sensor_types[] = { SENSOR_SMT100_MOIS, SENSOR_SMT100_TEMP, SENSOR_SMT100_PMTY, + SENSOR_TH100_MOIS, + SENSOR_TH100_TEMP, #if defined(ESP8266) SENSOR_ANALOG_EXTENSION_BOARD, @@ -3430,6 +3432,9 @@ static const int sensor_types[] = { SENSOR_OSPI_ANALOG_P, SENSOR_OSPI_ANALOG_SMT50_MOIS, SENSOR_OSPI_ANALOG_SMT50_TEMP, +#endif +#if defined(OSPI) + SENSOR_OSPI_INTERNAL_TEMP, #endif SENSOR_MQTT, SENSOR_REMOTE, @@ -3454,6 +3459,8 @@ static const char* sensor_names[] = { "Truebner SMT100 RS485 Modbus, moisture mode", "Truebner SMT100 RS485 Modbus, temperature mode", "Truebner SMT100 RS485 Modbus, permittivity mode", + "Truebner TH100 RS485 Modbus, humidity mode", + "Truebner TH100 RS485 Modbus, temperature mode", #if defined(ESP8266) "ASB - voltage mode 0..5V", "ASB - 0..3.3V to 0..100%", @@ -3473,6 +3480,9 @@ static const char* sensor_names[] = { "OSPi analog input - 0.3.3V to 0..100%", "OSPi analog input - SMT50 moisture mode", "OSPi analog input - SMT50 temperature mode", +#endif +#if defined(OSPI) + "Internal Raspbery Pi temperature", #endif "MQTT subscription", "Remote opensprinkler sensor", diff --git a/sensors.cpp b/sensors.cpp index 881c8c67b..e9589d906 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1451,6 +1451,8 @@ int read_sensor_ip(Sensor_t *sensor) { case SENSOR_SMT100_MOIS: case SENSOR_SMT100_TEMP: case SENSOR_SMT100_PMTY: + case SENSOR_TH100_MOIS: + case SENSOR_TH100_TEMP: uint32_t stoptime = millis() + SENSOR_READ_TIMEOUT; #if defined(ESP8266) while (true) { @@ -1511,12 +1513,14 @@ int read_sensor_ip(Sensor_t *sensor) { // Convert to readable value: switch (sensor->type) { + case SENSOR_TH100_MOIS: case SENSOR_SMT100_MOIS: // Equation: soil moisture [vol.%]= // (16Bit_soil_moisture_value / 100) sensor->last_data = ((double)sensor->last_native_data / 100.0); sensor->flags.data_ok = sensor->last_native_data < 10000; DEBUG_PRINT(F(" soil moisture %: ")); break; + case SENSOR_TH100_TEMP: case SENSOR_SMT100_TEMP: // Equation: temperature [°C]= // (16Bit_temperature_value / 100)-100 sensor->last_data = @@ -1537,6 +1541,21 @@ int read_sensor_ip(Sensor_t *sensor) { return HTTP_RQT_NOT_RECEIVED; } +int read_internal_raspi(Sensor_t *sensor, ulong time) { + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + char buf[10]; + if (!file_read_block("/sys/class/thermal/thermal_zone0/temp", buf, 0, 10)) + return HTTP_RQT_NOT_RECEIVED; + + sensor->last_read = time; + sensor->last_native_data = strtol(buf, NULL, 0); + sensor->last_data = (double)sensor->last_native_data / 100; + sensor->flags.data_ok = true; + + return HTTP_RQT_NOT_RECEIVED; +} + /** * read a sensor */ @@ -1547,6 +1566,8 @@ int read_sensor(Sensor_t *sensor, ulong time) { case SENSOR_SMT100_MOIS: case SENSOR_SMT100_TEMP: case SENSOR_SMT100_PMTY: + case SENSOR_TH100_MOIS: + case SENSOR_TH100_TEMP: //DEBUG_PRINT(F("Reading sensor ")); //DEBUG_PRINTLN(sensor->name); sensor->last_read = time; @@ -1603,6 +1624,10 @@ int read_sensor(Sensor_t *sensor, ulong time) { case SENSOR_OSPI_ANALOG_SMT50_TEMP: return read_sensor_ospi(sensor, time); #endif +#if defined(OSPI) + case SENSOR_OSPI_INTERNAL_TEMP: + return read_internal_raspi(sensor, time); +#endif #endif case SENSOR_REMOTE: return read_sensor_http(sensor, time); @@ -1889,6 +1914,8 @@ int set_sensor_address(Sensor_t *sensor, uint8_t new_address) { case SENSOR_SMT100_MOIS: case SENSOR_SMT100_TEMP: case SENSOR_SMT100_PMTY: + case SENSOR_TH100_MOIS: + case SENSOR_TH100_TEMP: sensor->flags.data_ok = false; if (sensor->ip && sensor->port) return set_sensor_address_ip(sensor, new_address); @@ -2222,6 +2249,10 @@ unsigned char getSensorUnitId(int type) { return UNIT_DEGREE; case SENSOR_SMT100_PMTY: return UNIT_DK; + case SENSOR_TH100_MOIS: + return UNIT_HUM_PERCENT; + case SENSOR_TH100_TEMP: + return UNIT_DEGREE; #if defined(ARDUINO) #if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: @@ -2256,6 +2287,7 @@ unsigned char getSensorUnitId(int type) { case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_TEMP: + case SENSOR_OSPI_INTERNAL_TEMP: return UNIT_DEGREE; #endif case SENSOR_MQTT: @@ -2290,6 +2322,10 @@ unsigned char getSensorUnitId(Sensor_t *sensor) { return UNIT_DEGREE; case SENSOR_SMT100_PMTY: return UNIT_DK; + case SENSOR_TH100_MOIS: + return UNIT_HUM_PERCENT; + case SENSOR_TH100_TEMP: + return UNIT_DEGREE; #if defined(ARDUINO) #if defined(ESP8266) case SENSOR_ANALOG_EXTENSION_BOARD: @@ -2323,6 +2359,7 @@ unsigned char getSensorUnitId(Sensor_t *sensor) { case SENSOR_OSPI_ANALOG_SMT50_MOIS: return UNIT_PERCENT; case SENSOR_OSPI_ANALOG_SMT50_TEMP: + case SENSOR_OSPI_INTERNAL_TEMP: return UNIT_DEGREE; #endif case SENSOR_USERDEF: diff --git a/sensors.h b/sensors.h index 49cd45518..8630e7622 100644 --- a/sensors.h +++ b/sensors.h @@ -67,65 +67,50 @@ extern "C" { #define MAX_LOG_SIZE_MONTH 1000 // Sensor types: -#define SENSOR_NONE 0 // None or deleted sensor -#define SENSOR_SMT100_MOIS 1 // Truebner SMT100 RS485, moisture mode -#define SENSOR_SMT100_TEMP 2 // Truebner SMT100 RS485, temperature mode -#define SENSOR_SMT100_PMTY 3 // Truebner SMT100 RS485, permittivity mode -#define SENSOR_ANALOG_EXTENSION_BOARD \ - 10 // New OpenSprinkler analog extension board x8 - voltage mode 0..4V -#define SENSOR_ANALOG_EXTENSION_BOARD_P \ - 11 // New OpenSprinkler analog extension board x8 - percent 0..3.3V to - // 0..100% -#define SENSOR_SMT50_MOIS \ - 15 // New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) - // : 3 -#define SENSOR_SMT50_TEMP \ - 16 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) - // * 100 -#define SENSOR_SMT100_ANALOG_MOIS \ - 17 // New OpenSprinkler analog extension board x8 - SMT100 VWC [%] = (U * - // 100) : 3 -#define SENSOR_SMT100_ANALOG_TEMP \ - 18 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U * 100) - // : 3 - 40 - -#define SENSOR_VH400 \ - 30 // New OpenSprinkler analog extension board x8 - Vegetronix VH400 -#define SENSOR_THERM200 \ - 31 // New OpenSprinkler analog extension board x8 - Vegetronix THERM200 -#define SENSOR_AQUAPLUMB \ - 32 // New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb - -#define SENSOR_USERDEF \ - 49 // New OpenSprinkler analog extension board x8 - User defined sensor - -#define SENSOR_OSPI_ANALOG 50 // Old OSPi analog input - voltage mode 0..3.3V -#define SENSOR_OSPI_ANALOG_P \ - 51 // Old OSPi analog input - percent 0..3.3V to 0...100% -#define SENSOR_OSPI_ANALOG_SMT50_MOIS \ - 52 // Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 -#define SENSOR_OSPI_ANALOG_SMT50_TEMP \ - 53 // Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 - -#define SENSOR_MQTT 90 // subscribe to a MQTT server and query a value - -#define SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler -#define SENSOR_WEATHER_TEMP_F 101 // Weather service - temperature (Fahrenheit) -#define SENSOR_WEATHER_TEMP_C 102 // Weather service - temperature (Celcius) -#define SENSOR_WEATHER_HUM 103 // Weather service - humidity (%) -#define SENSOR_WEATHER_PRECIP_IN 105 // Weather service - precip (inch) -#define SENSOR_WEATHER_PRECIP_MM 106 // Weather service - precip (mm) -#define SENSOR_WEATHER_WIND_MPH 107 // Weather service - wind (mph) -#define SENSOR_WEATHER_WIND_KMH 108 // Weather service - wind (kmh) - -#define SENSOR_GROUP_MIN 1000 // Sensor group with min value -#define SENSOR_GROUP_MAX 1001 // Sensor group with max value -#define SENSOR_GROUP_AVG 1002 // Sensor group with avg value -#define SENSOR_GROUP_SUM 1003 // Sensor group with sum value +#define SENSOR_NONE 0 // None or deleted sensor +#define SENSOR_SMT100_MOIS 1 // Truebner SMT100 RS485, moisture mode +#define SENSOR_SMT100_TEMP 2 // Truebner SMT100 RS485, temperature mode +#define SENSOR_SMT100_PMTY 3 // Truebner SMT100 RS485, permittivity mode +#define SENSOR_TH100_MOIS 4 // Truebner TH100 RS485, humidity mode +#define SENSOR_TH100_TEMP 5 // Truebner TH100 RS485, temperature mode +#define SENSOR_ANALOG_EXTENSION_BOARD 10 // New OpenSprinkler analog extension board x8 - voltage mode 0..4V +#define SENSOR_ANALOG_EXTENSION_BOARD_P 11 // New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% +#define SENSOR_SMT50_MOIS 15 // New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_SMT50_TEMP 16 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +#define SENSOR_SMT100_ANALOG_MOIS 17 // New OpenSprinkler analog extension board x8 - SMT100 VWC [%] = (U * 100) : 3 +#define SENSOR_SMT100_ANALOG_TEMP 18 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U * 100) : 3 - 40 + +#define SENSOR_VH400 30 // New OpenSprinkler analog extension board x8 - Vegetronix VH400 +#define SENSOR_THERM200 31 // New OpenSprinkler analog extension board x8 - Vegetronix THERM200 +#define SENSOR_AQUAPLUMB 32 // New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb + +#define SENSOR_USERDEF 49 // New OpenSprinkler analog extension board x8 - User defined sensor + +#define SENSOR_OSPI_ANALOG 50 // Old OSPi analog input - voltage mode 0..3.3V +#define SENSOR_OSPI_ANALOG_P 51 // Old OSPi analog input - percent 0..3.3V to 0...100% +#define SENSOR_OSPI_ANALOG_SMT50_MOIS 52 // Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_OSPI_ANALOG_SMT50_TEMP 53 // Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 +#define SENSOR_OSPI_INTERNAL_TEMP 54 // Internal OSPI Temperature + +#define SENSOR_MQTT 90 // subscribe to a MQTT server and query a value + +#define SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler +#define SENSOR_WEATHER_TEMP_F 101 // Weather service - temperature (Fahrenheit) +#define SENSOR_WEATHER_TEMP_C 102 // Weather service - temperature (Celcius) +#define SENSOR_WEATHER_HUM 103 // Weather service - humidity (%) +#define SENSOR_WEATHER_PRECIP_IN 105 // Weather service - precip (inch) +#define SENSOR_WEATHER_PRECIP_MM 106 // Weather service - precip (mm) +#define SENSOR_WEATHER_WIND_MPH 107 // Weather service - wind (mph) +#define SENSOR_WEATHER_WIND_KMH 108 // Weather service - wind (kmh) + +#define SENSOR_GROUP_MIN 1000 // Sensor group with min value +#define SENSOR_GROUP_MAX 1001 // Sensor group with max value +#define SENSOR_GROUP_AVG 1002 // Sensor group with avg value +#define SENSOR_GROUP_SUM 1003 // Sensor group with sum value //Diagnostic -#define SENSOR_FREE_MEMORY 10000 //Free memory -#define SENSOR_FREE_STORE 10001 //Free storage +#define SENSOR_FREE_MEMORY 10000 //Free memory +#define SENSOR_FREE_STORE 10001 //Free storage #define SENSOR_READ_TIMEOUT 3000 // ms From afa1e57cecfdae68e5070ad94e3232d3c8de3483 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 11 Jan 2025 00:22:19 +0100 Subject: [PATCH 255/281] fixt Raspi temperature readout --- sensors.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index e9589d906..dc77f3b54 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1541,21 +1541,28 @@ int read_sensor_ip(Sensor_t *sensor) { return HTTP_RQT_NOT_RECEIVED; } +#if defined(OSPI) int read_internal_raspi(Sensor_t *sensor, ulong time) { if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; char buf[10]; - if (!file_read_block("/sys/class/thermal/thermal_zone0/temp", buf, 0, 10)) + size_t res = 0; + FILE *fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r"); + if(fp) { + res = fread(buf, 1, 10, fp); + fclose(fp); + } + if (!res) return HTTP_RQT_NOT_RECEIVED; sensor->last_read = time; sensor->last_native_data = strtol(buf, NULL, 0); - sensor->last_data = (double)sensor->last_native_data / 100; + sensor->last_data = (double)sensor->last_native_data / 1000; sensor->flags.data_ok = true; return HTTP_RQT_NOT_RECEIVED; } - +#endif /** * read a sensor */ From 0056802cd6efb9ea7d6088d002d0a38a20194d0d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 11 Jan 2025 00:35:57 +0100 Subject: [PATCH 256/281] fix log raspi internal temp --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index dc77f3b54..3a0fecb7b 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1560,7 +1560,7 @@ int read_internal_raspi(Sensor_t *sensor, ulong time) { sensor->last_data = (double)sensor->last_native_data / 1000; sensor->flags.data_ok = true; - return HTTP_RQT_NOT_RECEIVED; + return HTTP_RQT_SUCCESS; } #endif /** From 7604b13c010eb4a11621aaef0904e94f4bebfc01 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 11 Jan 2025 13:33:59 +0100 Subject: [PATCH 257/281] Fix TH100 read from rs485 --- sensors.cpp | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index e9589d906..0f0d89f3b 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1264,10 +1264,10 @@ int read_sensor_rs485(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_rs485: check-ok")); - uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 - : sensor->type == SENSOR_SMT100_MOIS ? 0x01 - : 0x02; - + bool isTemp = sensor->type == SENSOR_SMT100_TEMP || sensor->type == SENSOR_TH100_TEMP; + bool isMois = sensor->type == SENSOR_SMT100_MOIS || sensor->type == SENSOR_TH100_MOIS; + uint8_t type = isTemp ? 0x00 : isMois ? 0x01 : 0x02; + if (sensor->repeat_read == 0 || sensor->repeat_read == 1000) { Wire.beginTransmission(RS485_TRUEBNER1_ADDR + device); Wire.write((uint8_t)sensor->id); @@ -1292,10 +1292,7 @@ int read_sensor_rs485(Sensor_t *sensor) { uint16_t data = (high_byte << 8) | low_byte; DEBUG_PRINTF("read_sensor_rs485: result: %d - %d (%d %d)\n", sensor->id, data, low_byte, high_byte); - double value = - sensor->type == SENSOR_SMT100_TEMP - ? (data / 100.0) - 100.0 - : (sensor->type == SENSOR_SMT100_MOIS ? data / 100.0 : data); + double value = isTemp ? (data / 100.0) - 100.0 : (isMois ? data / 100.0 : data); sensor->last_native_data = data; sensor->last_data = value; DEBUG_PRINTLN(sensor->last_data); @@ -1343,18 +1340,17 @@ int read_sensor_rs485(Sensor_t *sensor) { DEBUG_PRINTLN(F("read_sensor_rs485: check-ok")); uint8_t buffer[10]; - uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 - : sensor->type == SENSOR_SMT100_MOIS ? 0x01 - : 0x02; + bool isTemp = sensor->type == SENSOR_SMT100_TEMP || sensor->type == SENSOR_TH100_TEMP; + bool isMois = sensor->type == SENSOR_SMT100_MOIS || sensor->type == SENSOR_TH100_MOIS; + uint8_t type = isTemp ? 0x00 : isMois ? 0x01 : 0x02; + uint16_t tab_reg[3] = {0}; modbus_set_slave(ttyDevices[device], sensor->id); if (modbus_read_registers(ttyDevices[device], type, 2, tab_reg) > 0) { uint16_t data = tab_reg[0]; DEBUG_PRINTF("read_sensor_rs485: result: %d - %d\n", sensor->id, data); double value = - sensor->type == SENSOR_SMT100_TEMP - ? (data / 100.0) - 100.0 - : (sensor->type == SENSOR_SMT100_MOIS ? data / 100.0 : data); + sensor->type == isTemp ? (data / 100.0) - 100.0 : (isMois ? data / 100.0 : data); sensor->last_native_data = data; sensor->last_data = value; DEBUG_PRINTLN(sensor->last_data); @@ -1425,9 +1421,10 @@ int read_sensor_ip(Sensor_t *sensor) { else modbusTcpId++; - uint8_t type = sensor->type == SENSOR_SMT100_TEMP ? 0x00 - : sensor->type == SENSOR_SMT100_MOIS ? 0x01 - : 0x02; + bool isTemp = sensor->type == SENSOR_SMT100_TEMP || sensor->type == SENSOR_TH100_TEMP; + bool isMois = sensor->type == SENSOR_SMT100_MOIS || sensor->type == SENSOR_TH100_MOIS; + uint8_t type = isTemp ? 0x00 : isMois ? 0x01 : 0x02; + buffer[0] = (0xFF00 & modbusTcpId) >> 8; buffer[1] = (0x00FF & modbusTcpId); buffer[2] = 0; From 99a2c1b8b6b09bc422ab39f6b02fd3ab7ecfdea5 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 12 Jan 2025 23:23:31 +0100 Subject: [PATCH 258/281] Fix MQTT empty filter --- mqtt.cpp | 50 +++++++++++++++++++++++++++++++++++++--- mqtt.h | 4 ++-- opensprinkler_server.cpp | 6 +++-- sensor_mqtt.cpp | 7 +++--- sensors.cpp | 13 +++++++---- 5 files changed, 66 insertions(+), 14 deletions(-) diff --git a/mqtt.cpp b/mqtt.cpp index f4bae9514..08c95fe51 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -601,7 +601,7 @@ void subscribe_callback(const char *topic, unsigned char *payload, unsigned int } int OSMqtt::_subscribe(void){ - mqtt_client->setCallback(subscribe_callback); + setCallback(1, subscribe_callback); if (!mqtt_client->subscribe(_sub_topic)) { DEBUG_LOGF("MQTT Subscribe: Failed (%d)\r\n", mqtt_client->state()); return MQTT_ERROR; @@ -630,8 +630,52 @@ bool OSMqtt::unsubscribe(const char *topic) { return mqtt_client->unsubscribe(topic); } -void OSMqtt::setCallback(MQTT_CALLBACK_SIGNATURE) { - mqtt_client->setCallback(callback); +typedef struct KEY_CALLBACK { + int key; + MQTT_CALLBACK_SIGNATURE; +} KEY_CALLBACK_t; +#define MAX_CALLBACKS 2 + +static KEY_CALLBACK_t key_callbacks[MAX_CALLBACKS] = {0}; + +#if defined(ARDUINO) +void key_callback(char* mtopic, byte* payload, unsigned int length) { + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (key_callbacks[i].callback) + key_callbacks[i].callback(mtopic, payload, length); + } +} +#else +static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (key_callbacks[i].callback) + key_callbacks[i].callback(mosq, obj, msg); + } +} +#endif + +void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { + boolean ok = false; + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (callback) { + if (key_callbacks[i].key == 0 || key_callbacks[i].key == key) { + key_callbacks[i].callback = callback; + key_callbacks[i].key = key; + ok = true; + break; + } + } else { + if (key_callbacks[i].key == key) { + key_callbacks[i].callback = callback; + key_callbacks[i].key = 0; + ok = true; + break; + } + } + } + if (!ok) + DEBUG_LOGF("MQTT setCallback: failed!"); + mqtt_client->setCallback(key_callback); } const char * OSMqtt::_state_string(int rc) { diff --git a/mqtt.h b/mqtt.h index f5c68b43c..aa0e7d43b 100644 --- a/mqtt.h +++ b/mqtt.h @@ -66,9 +66,9 @@ class OSMqtt { static bool unsubscribe(const char *topic); static bool reconnect(); #if defined(ARDUINO) - static void setCallback(MQTT_CALLBACK_SIGNATURE); + static void setCallback(int key, MQTT_CALLBACK_SIGNATURE); #else - static void setCallback(void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)); + static void setCallback(int key, void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)); #endif }; diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 92bcf0290..d26696d09 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2910,9 +2910,9 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("over"), true)) // values higher than use_over = sscanf(tmp_buffer, "%lf", &over) == 1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // values higher than - sscanf(tmp_buffer, "%lf", &before); + sscanf(tmp_buffer, "%lu", &before); if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // values higher than - sscanf(tmp_buffer, "%lf", &after); + sscanf(tmp_buffer, "%lu", &after); DEBUG_PRINTLN(F("server_sensorlog_clear")); @@ -2925,6 +2925,7 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { print_header(); #endif + DEBUG_PRINTLN(F("start log cleaning")); if (nr > 0 || use_under || use_over || before || after) { ulong n = 0; if (log == -1) { @@ -2949,6 +2950,7 @@ void server_sensorlog_clear(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"deleted\":$L}"), log==LOG_STD?log_size:log=LOG_WEEK?log_sizeW:log_sizeM); } } + DEBUG_PRINTLN(F("end log cleaning")); handle_return(HTML_OK); } diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index 27c9bf909..c91f408ce 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -27,7 +27,7 @@ extern OpenSprinkler os; void sensor_mqtt_init() { - os.mqtt.setCallback(sensor_mqtt_callback); + os.mqtt.setCallback(2, sensor_mqtt_callback); } /** * @brief @@ -111,8 +111,9 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct char *jsonFilter = SensorUrl_get(sensor->nr, SENSORURL_TYPE_FILTER); char *p = (char*)payload; char *f = jsonFilter; + bool emptyFilter = !jsonFilter||!jsonFilter[0]; - while (f && p) { + while (!emptyFilter && f && p) { f = strstr(jsonFilter, "|"); if (f) { p = strnlstr(p, jsonFilter, f-jsonFilter, (char*)payload-p+length); @@ -122,7 +123,7 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct } } if (p) { - p += strlen(jsonFilter); + p += emptyFilter?0:strlen(jsonFilter); char buf[30]; p = strpbrk(p, "0123456789.-+nullNULL"); uint i = 0; diff --git a/sensors.cpp b/sensors.cpp index 38d429c82..96f93f936 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -249,7 +249,7 @@ void sensor_save_all() { void sensor_api_free() { apiInit = false; - os.mqtt.setCallback(NULL); + os.mqtt.setCallback(2, NULL); while (progSensorAdjusts) { ProgSensorAdjust_t* next = progSensorAdjusts->next; @@ -657,6 +657,8 @@ ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, const char *f; ulong idxr = 0; ulong n = 0; + DEBUG_PRINTLN(F("clearlog1")); + DEBUG_PRINTF("nr: %d log: %d under:%lf over: %lf before: %lld after: %lld size: %ld size2: %ld\n", sensorNr, log, under, over, before, after, size, size2); while (idxr < size2) { ulong idx = idxr; if (idx >= size) { @@ -670,6 +672,7 @@ ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, SENSORLOG_STORE_SIZE); if (result == 0) break; if (sensorlog.nr > 0 && (sensorlog.nr == sensorNr || sensorNr == 0)) { + DEBUG_PRINTF("clearlog2 idx=%ld idx2=%ld\n", idx, idxr); boolean found = false; if (use_under && sensorlog.data < under) found = true; if (use_over && sensorlog.data > over) found = true; @@ -681,11 +684,13 @@ ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, sensorlog.nr = 0; file_write_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, SENSORLOG_STORE_SIZE); + DEBUG_PRINTF("clearlog3 idx=%ld idxr=%ld\n", idx, idxr); n++; } } idxr++; } + DEBUG_PRINTF("clearlog4 n=%ld\n", n); return n; } @@ -939,7 +944,7 @@ void push_message(Sensor_t *sensor) { "\"value\":%d.%02d,\"unit\":\"%s\"}"), sensor->nr, sensor->type, sensor->flags.data_ok, sensor->last_read, (int)sensor->last_data, - (int)(sensor->last_data * 100) % 100, getSensorUnit(sensor)); + abs((int)(sensor->last_data * 100) % 100), getSensorUnit(sensor)); if (!os.mqtt.connected()) os.mqtt.reconnect(); os.mqtt.publish(topic, payload); @@ -961,7 +966,7 @@ void push_message(Sensor_t *sensor) { "unit: %s"), sensor->nr, sensor->type, sensor->flags.data_ok, sensor->last_read, (int)sensor->last_data, - (int)(sensor->last_data * 100) % 100, getSensorUnit(sensor)); + abs((int)(sensor->last_data * 100) % 100), getSensorUnit(sensor)); strcat_P(postval, PSTR("\"}")); // char postBuffer[1500]; @@ -2459,9 +2464,9 @@ void GetSensorWeather() { DEBUG_PRINTLN(F("GetSensorWeather")); DEBUG_PRINTLN(ether_buffer); + last_weather_time = time; int ret = os.send_http_request(host, ether_buffer, NULL, false, 500); if (ret == HTTP_RQT_SUCCESS) { - last_weather_time = time; DEBUG_PRINTLN(ether_buffer); char buf[20]; From 6cc6b0658d50afac1692fbc2fe9686ed5f69d83a Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 12 Jan 2025 23:25:25 +0100 Subject: [PATCH 259/281] Firmware version 173 --- defines.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/defines.h b/defines.h index 95e466591..2f7a90c5d 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; @@ -35,7 +35,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 172 // Firmware minor version +#define OS_FW_MINOR 173 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler From ba680b5c197ca90d8fb7570aa2bb632a170b1ffa Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 12 Jan 2025 23:39:35 +0100 Subject: [PATCH 260/281] Fix MQTT (OSPi) --- mqtt.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 13 deletions(-) diff --git a/mqtt.cpp b/mqtt.cpp index 08c95fe51..36c4d4ae7 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -638,23 +638,14 @@ typedef struct KEY_CALLBACK { static KEY_CALLBACK_t key_callbacks[MAX_CALLBACKS] = {0}; -#if defined(ARDUINO) void key_callback(char* mtopic, byte* payload, unsigned int length) { for (int i = 0; i < MAX_CALLBACKS; i++) { if (key_callbacks[i].callback) key_callbacks[i].callback(mtopic, payload, length); } } -#else -static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { - for (int i = 0; i < MAX_CALLBACKS; i++) { - if (key_callbacks[i].callback) - key_callbacks[i].callback(mosq, obj, msg); - } -} -#endif -void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { +static void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { boolean ok = false; for (int i = 0; i < MAX_CALLBACKS; i++) { if (callback) { @@ -666,7 +657,7 @@ void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { } } else { if (key_callbacks[i].key == key) { - key_callbacks[i].callback = callback; + key_callbacks[i].callback = NULL; key_callbacks[i].key = 0; ok = true; break; @@ -675,6 +666,10 @@ void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { } if (!ok) DEBUG_LOGF("MQTT setCallback: failed!"); +} + +void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { + registerCallback(key, MQTT_CALLBACK_SIGNATURE); mqtt_client->setCallback(key_callback); } @@ -857,8 +852,48 @@ bool OSMqtt::unsubscribe(const char *topic) { return mosquitto_unsubscribe(mqtt_client, NULL, topic); } -void OSMqtt::setCallback(void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)) { - mosquitto_message_callback_set(mqtt_client, on_message); +typedef struct KEY_CALLBACK { + int key; + void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *); +} KEY_CALLBACK_t; +#define MAX_CALLBACKS 2 + +static KEY_CALLBACK_t key_callbacks[MAX_CALLBACKS] = {0}; + +static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (key_callbacks[i].callback) + key_callbacks[i].callback(mosq, obj, msg); + } +} + +static void registerCallback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { + boolean ok = false; + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (callback) { + if (key_callbacks[i].key == 0 || key_callbacks[i].key == key) { + key_callbacks[i].callback = callback; + key_callbacks[i].key = key; + ok = true; + break; + } + } else { + if (key_callbacks[i].key == key) { + key_callbacks[i].callback = NULL; + key_callbacks[i].key = 0; + ok = true; + break; + } + } + } + if (!ok) + DEBUG_LOGF("MQTT setCallback: failed!"); +} + + +void OSMqtt::setCallback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { + registerCallback(key, callback); + mosquitto_message_callback_set(mqtt_client, callback); } const char * OSMqtt::_state_string(int error) { From d84008fba5634891c97979299c1977d4c8b8c5f7 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 12 Jan 2025 23:41:39 +0100 Subject: [PATCH 261/281] Fix Typo --- mqtt.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mqtt.cpp b/mqtt.cpp index 36c4d4ae7..18b974816 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -645,7 +645,7 @@ void key_callback(char* mtopic, byte* payload, unsigned int length) { } } -static void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { +void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { boolean ok = false; for (int i = 0; i < MAX_CALLBACKS; i++) { if (callback) { @@ -669,7 +669,7 @@ static void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { } void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { - registerCallback(key, MQTT_CALLBACK_SIGNATURE); + registerCallback(key, callback); mqtt_client->setCallback(key_callback); } From d47c2d12dce33e31a9806ed7969610e5185a0ea6 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 13 Jan 2025 00:08:51 +0100 Subject: [PATCH 262/281] Fix empty url/topic/filter --- opensprinkler_server.cpp | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index d26696d09..65a800468 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2313,7 +2313,9 @@ void server_sensorurl_config(OTF_PARAMS_DEF) uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type char *value = NULL; - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value"), true)) + uint8_t value_found = 0; + findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value"), true, &value_found); + if (!value_found) handle_return(HTML_DATA_MISSING); DEBUG_PRINTLN(tmp_buffer); urlDecodeAndUnescape(tmp_buffer); @@ -2425,33 +2427,35 @@ void server_sensor_config(OTF_PARAMS_DEF) //mqtt and other: char* url = NULL; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("url"), true)) + uint8_t url_found = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("url"), true, &url_found)) url = strdup(urlDecodeAndUnescape(tmp_buffer)); char* topic = NULL; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("topic"), true)) { + uint8_t topic_found = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("topic"), true, &topic_found)) { DEBUG_PRINTLN(tmp_buffer) urlDecodeAndUnescape(tmp_buffer); DEBUG_PRINTLN(tmp_buffer) topic = strdup(tmp_buffer); } - char* filter = NULL; - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("filter"), true)) + uint8_t filter_found = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("filter"), true, &filter_found)) filter = strdup(urlDecodeAndUnescape(tmp_buffer)); DEBUG_PRINTLN(F("server_sensor_config4")); SensorFlags_t flags = {.enable=enable, .log=log, .show=show}; int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, offset_mv, offset2, flags, assigned_unitid); - if (url) { + if (url_found) { SensorUrl_add(nr, SENSORURL_TYPE_URL, url); free(url); } - if (topic) { + if (topic_found) { SensorUrl_add(nr, SENSORURL_TYPE_TOPIC, topic); free(topic); } - if (filter) { + if (filter_found) { SensorUrl_add(nr, SENSORURL_TYPE_FILTER, filter); free(filter); } From a35960a6e71c249ff7bd41b16e9fa706988a5773 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 27 Jan 2025 13:22:24 +0100 Subject: [PATCH 263/281] Log filter same data / do not write same data --- defines.h | 2 +- sensors.cpp | 24 +++++++++++++++--------- sensors.h | 2 ++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/defines.h b/defines.h index 2f7a90c5d..c2c42f432 100644 --- a/defines.h +++ b/defines.h @@ -35,7 +35,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 173 // Firmware minor version +#define OS_FW_MINOR 174 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/sensors.cpp b/sensors.cpp index 96f93f936..661e84e16 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -588,19 +588,25 @@ bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog) { bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { - SensorLog_t sensorlog; - memset(&sensorlog, 0, sizeof(SensorLog_t)); - sensorlog.nr = sensor->nr; - sensorlog.time = time; - sensorlog.native_data = sensor->last_native_data; - sensorlog.data = sensor->last_data; if (log == LOG_STD) add_influx_data(sensor); - if (!sensorlog_add(log, &sensorlog)) { - sensor->flags.log = 0; - return false; + // Write to log file only if necessary + if (time-sensor->last_logged_time > 86400 || abs(sensor->last_data - sensor->last_logged_data) > 0.00999) { + SensorLog_t sensorlog; + memset(&sensorlog, 0, sizeof(SensorLog_t)); + sensorlog.nr = sensor->nr; + sensorlog.time = time; + sensorlog.native_data = sensor->last_native_data; + sensorlog.data = sensor->last_data; + sensor->last_logged_data = sensor->last_data; + sensor->last_logged_time = time; + + if (!sensorlog_add(log, &sensorlog)) { + sensor->flags.log = 0; + return false; + } } return true; } diff --git a/sensors.h b/sensors.h index 8630e7622..fafb2b2a7 100644 --- a/sensors.h +++ b/sensors.h @@ -167,6 +167,8 @@ typedef struct Sensor { double repeat_data; uint64_t repeat_native; ulong last_read; // millis + double last_logged_data; // last logged sensor data + ulong last_logged_time; // last logged timestamp / Sensor *next; } Sensor_t; #define SENSOR_STORE_SIZE 111 From b59b9ce1fd4ad2a54a2f04301c0665c534ceee97 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 27 Jan 2025 21:48:18 +0100 Subject: [PATCH 264/281] Cleanup unused values --- sensors.h | 2 -- 1 file changed, 2 deletions(-) diff --git a/sensors.h b/sensors.h index 8630e7622..485a249da 100644 --- a/sensors.h +++ b/sensors.h @@ -63,8 +63,6 @@ extern "C" { // MaxLogSize #define MAX_LOG_SIZE 8000 -#define MAX_LOG_SIZE_WEEK 2000 -#define MAX_LOG_SIZE_MONTH 1000 // Sensor types: #define SENSOR_NONE 0 // None or deleted sensor From 0b5b1b179349c4bb4e45117c00ef79205667e96e Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Mon, 27 Jan 2025 23:29:08 +0100 Subject: [PATCH 265/281] Set Digital Sensorport control --- OpenSprinkler.cpp | 4 ++-- OpenSprinkler.h | 2 ++ opensprinkler_server.cpp | 9 +++++++++ sensors.cpp | 12 ++++++++++-- sensors.h | 9 ++++++++- 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 165c8f566..3387b330d 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -1458,7 +1458,7 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { if(hw_rev>=2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 unsigned char val = digitalReadExt(PIN_SENSOR1); - status.sensor1 = (val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1; + status.sensor1 = status.forced_sensor1 || ((val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1); if(status.sensor1) { if(!sensor1_on_timer) { // add minimum of 5 seconds on delay @@ -1488,7 +1488,7 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) { if(hw_rev>=2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 unsigned char val = digitalReadExt(PIN_SENSOR2); - status.sensor2 = (val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1; + status.sensor2 = status.forced_sensor2 || ((val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1); if(status.sensor2) { if(!sensor2_on_timer) { // add minimum of 5 seconds on delay diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 3c5aa39d5..119484204 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -226,6 +226,8 @@ struct ConStatus { unsigned char sensor2_active:1; // sensor2 active bit (when set, sensor2 is activated) unsigned char req_mqtt_restart:1;// request mqtt restart unsigned char pause_state:1; // pause station runs + unsigned char forced_sensor1:1; // forced sensor1 active (from Analog Sensor API) + unsigned char forced_sensor2:1; // forced sensor2 active (from Analog Sensor API) }; /** OTF configuration */ diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 65a800468..02d83dcf7 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -2984,6 +2984,7 @@ void server_monitor_types(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"name\":\"Min\",\"type\":$D},"), MONITOR_MIN); bfill.emit_p(PSTR("{\"name\":\"Max\",\"type\":$D},"), MONITOR_MAX); bfill.emit_p(PSTR("{\"name\":\"SN 1/2\",\"type\":$D},"), MONITOR_SENSOR12); + bfill.emit_p(PSTR("{\"name\":\"SET SN 1/2\",\"type\":$D},"), MONITOR_SET_SENSOR12); bfill.emit_p(PSTR("{\"name\":\"AND\",\"type\":$D},"), MONITOR_AND); bfill.emit_p(PSTR("{\"name\":\"OR\",\"type\":$D},"), MONITOR_OR); bfill.emit_p(PSTR("{\"name\":\"XOR\",\"type\":$D},"), MONITOR_XOR); @@ -3115,6 +3116,9 @@ void server_monitor_config(OTF_PARAMS_DEF) { case MONITOR_SENSOR12: m = (Monitor_Union_t){.sensor12 = {.sensor12 = sensor12, .invers = invers}}; break; + case MONITOR_SET_SENSOR12: + m = (Monitor_Union_t){.set_sensor12 = {.monitor = monitor, .sensor12 = sensor12}}; + break; case MONITOR_AND: case MONITOR_OR: case MONITOR_XOR: @@ -3159,6 +3163,11 @@ void monitorconfig_json(Monitor_t *mon) { mon->m.sensor12.sensor12, mon->m.sensor12.invers); break; + case MONITOR_SET_SENSOR12: + bfill.emit_p(PSTR("\"monitor\":$D,\"sensor12\":$D}"), + mon->m.set_sensor12.monitor, + mon->m.set_sensor12.sensor12); + break; case MONITOR_AND: case MONITOR_OR: case MONITOR_XOR: diff --git a/sensors.cpp b/sensors.cpp index 661e84e16..a3c1f5d99 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -557,8 +557,7 @@ void checkLogSwitch(uint8_t log) { void checkLogSwitchAfterWrite(uint8_t log) { ulong size = file_size(getlogfile(log)); - if ((size / SENSORLOG_STORE_SIZE) >= - MAX_LOG_SIZE) { // switch logs if max reached + if ((size / SENSORLOG_STORE_SIZE) >= MAX_LOG_SIZE) { // switch logs if max reached if (logFileSwitch[log] == 1) logFileSwitch[log] = 2; else @@ -2994,6 +2993,15 @@ void check_monitors() { mon->active = mon->m.sensor12.invers? !os.status.sensor2_active : os.status.sensor2_active; break; + case MONITOR_SET_SENSOR12: + mon->active = get_monitor(mon->m.set_sensor12.monitor, false, false); + if (mon->m.set_sensor12.sensor12 == 1) { + os.status.forced_sensor1 = mon->active; + } + if (mon->m.set_sensor12.sensor12 == 2) { + os.status.forced_sensor2 = mon->active; + } + break; case MONITOR_AND: mon->active = get_monitor(mon->m.andorxor.monitor1, mon->m.andorxor.invers1, true) && get_monitor(mon->m.andorxor.monitor2, mon->m.andorxor.invers2, true) && diff --git a/sensors.h b/sensors.h index c36f07bb7..5793f0f8a 100644 --- a/sensors.h +++ b/sensors.h @@ -231,7 +231,8 @@ typedef struct SensorUrl { #define MONITOR_DELETE 0 #define MONITOR_MIN 1 #define MONITOR_MAX 2 -#define MONITOR_SENSOR12 3 //Digital OS Sensors +#define MONITOR_SENSOR12 3 // Read Digital OS Sensors Rain/Soil Moisture +#define MONITOR_SET_SENSOR12 4 // Write Digital OS Sensors Rain/Soil Moisture #define MONITOR_AND 10 #define MONITOR_OR 11 #define MONITOR_XOR 12 @@ -248,6 +249,11 @@ typedef struct Monitor_SENSOR12 { // type = 3 bool invers : 1; } Monitor_SENSOR12_t; +typedef struct Monitor_SET_SENSOR12 { // type = 4 + uint16_t monitor; + uint16_t sensor12; +} Monitor_SET_SENSOR12_t; + typedef struct Monitor_ANDORXOR { // type = 10+11+12 uint16_t monitor1; uint16_t monitor2; @@ -272,6 +278,7 @@ typedef struct Monitor_REMOTE { // type = 100 typedef union Monitor_Union { Monitor_MINMAX_t minmax; // type = 1+2 Monitor_SENSOR12_t sensor12; // type = 3 + Monitor_SET_SENSOR12_t set_sensor12; // type = 4 Monitor_ANDORXOR_t andorxor; // type = 10+11+12 Monitor_NOT_t mnot; // type = 13 Monitor_REMOTE_t remote; //type = 100 From ce2d9973f69169a88a0a0f18f9b911139216d3e9 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 28 Jan 2025 22:53:36 +0100 Subject: [PATCH 266/281] OSPi bugfix rs485 SMT100 read --- rs485 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rs485 b/rs485 index e9065a24a..f448d2b46 100644 --- a/rs485 +++ b/rs485 @@ -1 +1 @@ -/dev/ttyUSB1 +/dev/ttyUSB0 From 8b7235e29ee4c0026fa5c5b2155eb183d00a552d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 28 Jan 2025 22:55:55 +0100 Subject: [PATCH 267/281] Bugfix OSPi rs485 SMT100 --- sensors.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index a3c1f5d99..a9f6a0825 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -1353,14 +1353,13 @@ int read_sensor_rs485(Sensor_t *sensor) { bool isTemp = sensor->type == SENSOR_SMT100_TEMP || sensor->type == SENSOR_TH100_TEMP; bool isMois = sensor->type == SENSOR_SMT100_MOIS || sensor->type == SENSOR_TH100_MOIS; uint8_t type = isTemp ? 0x00 : isMois ? 0x01 : 0x02; - + uint16_t tab_reg[3] = {0}; modbus_set_slave(ttyDevices[device], sensor->id); if (modbus_read_registers(ttyDevices[device], type, 2, tab_reg) > 0) { uint16_t data = tab_reg[0]; DEBUG_PRINTF("read_sensor_rs485: result: %d - %d\n", sensor->id, data); - double value = - sensor->type == isTemp ? (data / 100.0) - 100.0 : (isMois ? data / 100.0 : data); + double value = isTemp ? (data / 100.0) - 100.0 : (isMois ? data / 100.0 : data); sensor->last_native_data = data; sensor->last_data = value; DEBUG_PRINTLN(sensor->last_data); From b39ccef106b443f7404489abf8de73cdc092596d Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Tue, 28 Jan 2025 23:26:13 +0100 Subject: [PATCH 268/281] build2.sh compile for OS VERSION_ID>10 --- build2.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build2.sh b/build2.sh index 85f1103eb..18d0185b9 100755 --- a/build2.sh +++ b/build2.sh @@ -5,8 +5,8 @@ USEGPIO="" GPIOLIB="" - - if [ -h "/sys/class/gpio/gpiochip512" ]; then + source /etc/os-release + if [[ $VERSION_ID -gt 10 ]]; then echo "using libgpiod" USEGPIO="-DLIBGPIOD" GPIOLIB="-lgpiod" From c908ebaa5ed6a3d32d8a2dcd35935b70e047a404 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 15 Feb 2025 16:53:10 +0100 Subject: [PATCH 269/281] Lumen/Lux added --- sensors.cpp | 16 ++++++++++++---- sensors.h | 4 +++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index a3c1f5d99..08f739671 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -80,9 +80,8 @@ static modbus_t * ttyDevices[MAX_RS485_DEVICES]; #endif const char *sensor_unitNames[]{ - "", "%", "°C", "°F", "V", "%", "in", - "mm", "mph", "kmh", "%", "DK" - // 0 1 2 3 4 5 6 7 8 9 10, 11 + "", "%", "°C", "°F", "V", "%", "in", "mm", "mph", "kmh", "%", "DK", "LM", "LX" + //0 1 2 3 4 5 6 7 8 9 10, 11, 12, 13 // 0=Nothing // 1=Soil moisture // 2=degree celsius temperature @@ -94,7 +93,9 @@ const char *sensor_unitNames[]{ // 8=Wind mph // 9=Wind kmh // 10=Level % - // 11=DK + // 11=DK (Permitivität) + // 12=LM (Lumen) + // 13=LX (LUX) }; uint8_t logFileSwitch[3] = {0, 0, 0}; // 0=use smaller File, 1=LOG1, 2=LOG2 @@ -2848,6 +2849,13 @@ void start_monitor_action(Monitor_t * mon) { if (mon->zone > 0) { uint sid = mon->zone-1; + + // 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)) + return; + uint16_t timer=mon->maxRuntime; RuntimeQueueStruct *q = NULL; unsigned char sqi = pd.station_qid[sid]; diff --git a/sensors.h b/sensors.h index 5793f0f8a..f5ff0a179 100644 --- a/sensors.h +++ b/sensors.h @@ -312,7 +312,9 @@ typedef struct Monitor { #define UNIT_MPH 8 #define UNIT_KMH 9 #define UNIT_LEVEL 10 -#define UNIT_DK 11 +#define UNIT_DK 11 //Permitivität +#define UNIT_LM 12 //Lumen +#define UNIT_LX 13 //Lux #define UNIT_USERDEF 99 // Unitnames From 4cf54b08e827311de82acfda0dee669c0cc516a4 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 23 Feb 2025 01:17:28 +0100 Subject: [PATCH 270/281] fixt FlowPulseRateFactor. Thx to Ray! --- main.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 16566c85f..1ed5bfb5f 100644 --- a/main.cpp +++ b/main.cpp @@ -1251,9 +1251,8 @@ void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shif uint16_t flow_alert_setpoint = os.get_flow_alert_setpoint(sid); //flow_last_gpm is actually collected and stored as pulses per minute, not gallons per minute //Get Flow Pulse Rate factor and apply to flow_last_gpm when comparing and outputting - float flow_pulse_rate_factor = static_cast(os.iopts[IOPT_PULSE_RATE_1]) + static_cast(os.iopts[IOPT_PULSE_RATE_0]) / 100.0; - - float last_flow = flow_last_gpm * flow_pulse_rate_factor; + uint16_t fpr = (unsigned int)(os.iopts[IOPT_PULSE_RATE_1]<<8)+os.iopts[IOPT_PULSE_RATE_0]; + float last_flow = flow_last_gpm * fpr; uint16_t avg_flow = os.get_flow_avg_value(sid); uint16_t int_flow = (int)(last_flow * 100); if (avg_flow > 0) From 0aaa34e35a94f7b3eedeb0ee79c0836f66d9e319 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 26 Mar 2025 00:39:07 +0100 Subject: [PATCH 271/281] 1. Fix ADS1115 too many open files 2. Fix MQTT Subscribe fix --- mqtt.cpp | 19 +++++++++++-------- sensor_ospi_ads1115.cpp | 37 ++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/mqtt.cpp b/mqtt.cpp index 18b974816..9d5735266 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -645,7 +645,7 @@ void key_callback(char* mtopic, byte* payload, unsigned int length) { } } -void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { +void sensor_mqtt_callback(int key, MQTT_CALLBACK_SIGNATURE) { boolean ok = false; for (int i = 0; i < MAX_CALLBACKS; i++) { if (callback) { @@ -669,7 +669,7 @@ void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { } void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { - registerCallback(key, callback); + sensor_mqtt_callback(key, callback); mqtt_client->setCallback(key_callback); } @@ -829,7 +829,7 @@ void subscribe_callback(struct mosquitto *mosq, void *obj, const struct mosquitt } int OSMqtt::_subscribe(void) { - mosquitto_message_callback_set(mqtt_client, subscribe_callback); + setCallback(1, subscribe_callback); int rc = mosquitto_subscribe(mqtt_client, NULL, _sub_topic, 0); if (rc != MOSQ_ERR_SUCCESS) { DEBUG_LOGF("MQTT Subscribe: Failed (%s)\r\n", mosquitto_strerror(rc)); @@ -862,12 +862,15 @@ static KEY_CALLBACK_t key_callbacks[MAX_CALLBACKS] = {0}; static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { for (int i = 0; i < MAX_CALLBACKS; i++) { - if (key_callbacks[i].callback) - key_callbacks[i].callback(mosq, obj, msg); + if (key_callbacks[i].callback) { + DEBUG_PRINT("Callback exec: "); + DEBUG_PRINTLN(key_callbacks[i].key); + key_callbacks[i].callback(mosq, obj, msg); + } } } -static void registerCallback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { +static void sensor_mqtt_callback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { boolean ok = false; for (int i = 0; i < MAX_CALLBACKS; i++) { if (callback) { @@ -892,8 +895,8 @@ static void registerCallback(int key, void (*callback)(struct mosquitto *, void void OSMqtt::setCallback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { - registerCallback(key, callback); - mosquitto_message_callback_set(mqtt_client, callback); + sensor_mqtt_callback(key, callback); + mosquitto_message_callback_set(mqtt_client, sensor_mqtt_callback); } const char * OSMqtt::_state_string(int error) { diff --git a/sensor_ospi_ads1115.cpp b/sensor_ospi_ads1115.cpp index 603f7f6f3..e9af06f83 100644 --- a/sensor_ospi_ads1115.cpp +++ b/sensor_ospi_ads1115.cpp @@ -32,16 +32,11 @@ #define DEFAULT_RANGE 6.144 -/** -* Read the OSPi 1.6 onboard ADS1115 A2D -**/ -int read_sensor_ospi(Sensor_t *sensor, ulong time) { - if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; - - static ads1115_handle_t gs_handle; /**< ads1115 handle */ - uint8_t res; - +static ads1115_handle_t gs_handle; /**< ads1115 handle */ +void init_ads1115() { + if (gs_handle.inited) return; + DRIVER_ADS1115_LINK_INIT(&gs_handle, ads1115_handle_t); DRIVER_ADS1115_LINK_IIC_INIT(&gs_handle, ads1115_interface_iic_init); DRIVER_ADS1115_LINK_IIC_DEINIT(&gs_handle, ads1115_interface_iic_deinit); @@ -49,6 +44,18 @@ int read_sensor_ospi(Sensor_t *sensor, ulong time) { DRIVER_ADS1115_LINK_IIC_WRITE(&gs_handle, ads1115_interface_iic_write); DRIVER_ADS1115_LINK_DELAY_MS(&gs_handle, ads1115_interface_delay_ms); DRIVER_ADS1115_LINK_DEBUG_PRINT(&gs_handle, ads1115_interface_debug_print); + + ads1115_init(&gs_handle); +} +/** +* Read the OSPi 1.6 onboard ADS1115 A2D +**/ +int read_sensor_ospi(Sensor_t *sensor, ulong time) { + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + uint8_t res; + + init_ads1115(); ads1115_address_t addr = (ads1115_address_t)(ADS1115_ADDR_GND + sensor->id / 4); ads1115_channel_t channel = (ads1115_channel_t)(ADS1115_CHANNEL_AIN0_GND + sensor->id % 4); @@ -58,35 +65,26 @@ int read_sensor_ospi(Sensor_t *sensor, ulong time) { if (res != 0) return HTTP_RQT_NOT_RECEIVED; - /* ads1115 init */ - res = ads1115_init(&gs_handle); - if (res != 0) - return HTTP_RQT_NOT_RECEIVED; - res = ads1115_set_channel(&gs_handle, channel); if (res != 0) { - ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } /* set default range */ res = ads1115_set_range(&gs_handle, ADS1115_BASIC_DEFAULT_RANGE); if (res != 0) { - ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } /* set default rate */ res = ads1115_set_rate(&gs_handle, ADS1115_BASIC_DEFAULT_RATE); if (res != 0) { - ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } /* disable compare */ res = ads1115_set_compare(&gs_handle, ADS1115_BOOL_FALSE); if (res != 0) { - ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } @@ -94,12 +92,9 @@ int read_sensor_ospi(Sensor_t *sensor, ulong time) { float v; res = ads1115_single_read(&gs_handle, &raw, &v); if (res != 0) { - ads1115_deinit(&gs_handle); return HTTP_RQT_NOT_RECEIVED; } - ads1115_deinit(&gs_handle); - sensor->repeat_native += raw; sensor->repeat_data += v; if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && time < sensor->last_read + sensor->read_interval) From 9ce272655c9dbebbe568c386487f59ed1d4097f1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 3 Apr 2025 22:37:07 +0200 Subject: [PATCH 272/281] new Version 175, influxdb always active, new monitor-type: Time --- EMailSender.cpp | 1 + OpenSprinkler.cpp | 6 +++--- defines.h | 7 ++++++- main.cpp | 4 ++-- mqtt.cpp | 30 ++++++++++++------------------ opensprinkler_server.cpp | 24 +++++++++++++++++++++--- osinfluxdb.cpp | 2 +- sensors.cpp | 37 +++++++++++++++++++++++++++++++------ sensors.h | 8 ++++++++ weather.cpp | 2 +- 10 files changed, 86 insertions(+), 35 deletions(-) diff --git a/EMailSender.cpp b/EMailSender.cpp index c4b1520be..02971096b 100644 --- a/EMailSender.cpp +++ b/EMailSender.cpp @@ -231,6 +231,7 @@ EMailSender::Response EMailSender::awaitSMTPResponse(EMAIL_NETWORK_CLASS &client EMailSender::Response response; uint32_t ts = millis(); while (!client.available()) { + wdt_reset(); if (millis() > (ts + timeOut)) { response.code = F("1"); response.desc = String(respDesc) + "! " + F("SMTP Response TIMEOUT!"); diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 3387b330d..c30e2c3a3 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -2152,7 +2152,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_SIZE_L); // 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 @@ -2189,7 +2189,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_SIZE_L); // 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 @@ -2228,7 +2228,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_SIZE_L); if(cmd==NULL || server==NULL) return; // proceed only if cmd and server are valid diff --git a/defines.h b/defines.h index c2c42f432..957ea67f1 100644 --- a/defines.h +++ b/defines.h @@ -29,13 +29,14 @@ typedef unsigned long ulong; #define TMP_BUFFER_SIZE 320 // scratch buffer size +#define TMP_BUFFER_SIZE_L TMP_BUFFER_SIZE+100 // scratch buffer size /** Firmware version, hardware version, and maximal values */ #define OS_FW_VERSION 233 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 174 // Firmware minor version +#define OS_FW_MINOR 175 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -342,6 +343,7 @@ enum { #define PIN_CURR_DIGITAL 24 // digital pin index for A7 #define ETHER_BUFFER_SIZE 2048 + #define ETHER_BUFFER_SIZE_L ETHER_BUFFER_SIZE+100 #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset @@ -368,6 +370,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_SIZE_L ETHER_BUFFER_SIZE+100 #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above @@ -455,6 +458,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_SIZE_L ETHER_BUFFER_SIZE+100 #define SDA 0 #define SCL 0 @@ -478,6 +482,7 @@ enum { #define PIN_RFTX 0 #define PIN_FREE_LIST {} #define ETHER_BUFFER_SIZE 16384 + #define ETHER_BUFFER_SIZE_L ETHER_BUFFER_SIZE+100 #endif diff --git a/main.cpp b/main.cpp index 1ed5bfb5f..066f7f239 100644 --- a/main.cpp +++ b/main.cpp @@ -92,8 +92,8 @@ void remote_http_callback(char*); #define CLIENT_READ_TIMEOUT 5 // client read timeout (in seconds) #define DHCP_CHECKLEASE_INTERVAL 3600L // DHCP check lease interval (in 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_L]; // ethernet buffer, make it twice as large to allow overflow +char tmp_buffer[TMP_BUFFER_SIZE_L]; // scratch buffer, make it twice as large to allow overflow // ====== Object defines ====== OpenSprinkler os; // OpenSprinkler object diff --git a/mqtt.cpp b/mqtt.cpp index 18b974816..afd09a89d 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -338,7 +338,7 @@ void OSMqtt::init(void) { const char* getOnlineTopic() { os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); - strncat(tmp_buffer, MQTT_AVAILABILITY_TOPIC, TMP_BUFFER_SIZE*2); + strncat(tmp_buffer, MQTT_AVAILABILITY_TOPIC, TMP_BUFFER_SIZE_L); return tmp_buffer; } @@ -646,26 +646,20 @@ void key_callback(char* mtopic, byte* payload, unsigned int length) { } void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { - boolean ok = false; + int j = -1; for (int i = 0; i < MAX_CALLBACKS; i++) { - if (callback) { - if (key_callbacks[i].key == 0 || key_callbacks[i].key == key) { - key_callbacks[i].callback = callback; - key_callbacks[i].key = key; - ok = true; - break; - } - } else { - if (key_callbacks[i].key == key) { - key_callbacks[i].callback = NULL; - key_callbacks[i].key = 0; - ok = true; - break; - } + if ((callback && key_callbacks[i].key == 0) || key_callbacks[i].key == key) { + if (j < 0 || key > key_callbacks[j].key) + j = i; + break; } } - if (!ok) - DEBUG_LOGF("MQTT setCallback: failed!"); + if (j >= 0) { + key_callbacks[j].callback = callback; + key_callbacks[j].key = key; + return; + } + DEBUG_LOGF("MQTT setCallback: failed!"); } void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 02d83dcf7..c44a64b35 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -208,7 +208,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, ETHER_BUFFER_SIZE_L); ether_buffer[0] = 0; } @@ -1955,7 +1955,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_SIZE_L , "%d", i); make_logfile_name(tmp_buffer); #if defined(ESP8266) @@ -2186,7 +2186,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_SIZE_L , "%d", index); make_logfile_name(tmp_buffer); DEBUG_PRINT(F("creating ")); DEBUG_PRINT(tmp_buffer); @@ -2989,6 +2989,7 @@ void server_monitor_types(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("{\"name\":\"OR\",\"type\":$D},"), MONITOR_OR); bfill.emit_p(PSTR("{\"name\":\"XOR\",\"type\":$D},"), MONITOR_XOR); bfill.emit_p(PSTR("{\"name\":\"NOT\",\"type\":$D}," ), MONITOR_NOT); + bfill.emit_p(PSTR("{\"name\":\"TIME\",\"type\":$D}," ), MONITOR_TIME); bfill.emit_p(PSTR("{\"name\":\"REMOTE\",\"type\":$D}" ), MONITOR_REMOTE); bfill.emit_p(PSTR("]}")); handle_return(HTML_OK); @@ -3096,6 +3097,17 @@ void server_monitor_config(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor"), true)) monitor = strtoul(tmp_buffer, NULL, 0); + //type = TIME + uint16_t time_from = 0000; + uint16_t time_to = 2400; + uint8_t wdays = 0xFF; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("from"), true)) //Format: HHMM + time_from = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("to"), true)) //Format: HHMM + time_to = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wdays"), true)) //0=Monday + wdays = strtoul(tmp_buffer, NULL, 0); + //type = REMOTE ip uint16_t rmonitor = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rmonitor"), true)) @@ -3128,6 +3140,9 @@ void server_monitor_config(OTF_PARAMS_DEF) { case MONITOR_NOT: m = (Monitor_Union_t){.mnot = {.monitor = monitor}}; break; + case MONITOR_TIME: + m = (Monitor_Union_t){.mtime = {.time_from = time_from, .time_to = time_to, .weekdays = wdays}}; + break; case MONITOR_REMOTE: m = (Monitor_Union_t){.remote = {.rmonitor = rmonitor, .ip = ip, .port = port}}; break; @@ -3179,6 +3194,9 @@ void monitorconfig_json(Monitor_t *mon) { bfill.emit_p(PSTR("\"monitor\":$D}"), mon->m.mnot.monitor); break; + case MONITOR_TIME: + bfill.emit_p(PSTR("\"from\":$D,\"to\":$D,\"wdays\":$D}"), mon->m.mtime.time_from, mon->m.mtime.time_to, mon->m.mtime.weekdays); + break; case MONITOR_REMOTE: bfill.emit_p(PSTR("\"rmonitor\":$D,\"ip\":$L,\"port\":$D}"), mon->m.remote.rmonitor, diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp index e7690e802..fa18af158 100644 --- a/osinfluxdb.cpp +++ b/osinfluxdb.cpp @@ -74,7 +74,7 @@ void OSInfluxDB::get_influx_config(char *json) { json[0] = 0; if (file_exists(INFLUX_CONFIG_FILE)) { - ulong size = file_read_block(INFLUX_CONFIG_FILE, json, 0, TMP_BUFFER_SIZE*2); + ulong size = file_read_block(INFLUX_CONFIG_FILE, json, 0, TMP_BUFFER_SIZE_L); //DEBUG_PRINT(F("influx config size=")); //DEBUG_PRINTLN(size); json[size] = 0; diff --git a/sensors.cpp b/sensors.cpp index 67f226381..5fcd67dd9 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -248,8 +248,9 @@ void sensor_save_all() { * */ void sensor_api_free() { + DEBUG_PRINTLN("sensor_api_free1"); apiInit = false; - + current_sensor = NULL; os.mqtt.setCallback(2, NULL); while (progSensorAdjusts) { @@ -258,6 +259,8 @@ void sensor_api_free() { progSensorAdjusts = next; } + DEBUG_PRINTLN("sensor_api_free2"); + while (sensorUrls) { SensorUrl_t* next = sensorUrls->next; free(sensorUrls->urlstr); @@ -265,12 +268,16 @@ void sensor_api_free() { sensorUrls = next; } + DEBUG_PRINTLN("sensor_api_free3"); + while (monitors) { Monitor_t* next = monitors->next; delete monitors; monitors = next; } + DEBUG_PRINTLN("sensor_api_free4"); + while (sensors) { Sensor_t* next = sensors->next; delete sensors; @@ -281,6 +288,7 @@ void sensor_api_free() { #ifdef ESP8266 memset(i2c_rs485_allocated, 0, sizeof(i2c_rs485_allocated)); #endif + DEBUG_PRINTLN("sensor_api_free5"); } /* @@ -589,9 +597,6 @@ bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { - if (log == LOG_STD) - add_influx_data(sensor); - // Write to log file only if necessary if (time-sensor->last_logged_time > 86400 || abs(sensor->last_data - sensor->last_logged_data) > 0.00999) { SensorLog_t sensorlog; @@ -976,7 +981,7 @@ void push_message(Sensor_t *sensor) { strcat_P(postval, PSTR("\"}")); // char postBuffer[1500]; - BufferFiller bf = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE_L); bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" "Host: $S\r\n" "Accept: */*\r\n" @@ -987,6 +992,8 @@ void push_message(Sensor_t *sensor) { os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, sensor_remote_http_callback); DEBUG_PRINTLN("push ifttt2"); } + + add_influx_data(sensor); } void read_all_sensors(boolean online) { @@ -2530,8 +2537,14 @@ void SensorUrl_load() { } sensorUrl->urlstr = (char *)malloc(sensorUrl->length + 1); pos += SENSORURL_STORE_SIZE; - file_read_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, + ulong result = file_read_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, sensorUrl->length); + if (result != sensorUrl->length) { + free(sensorUrl->urlstr); + delete sensorUrl; + break; + } + sensorUrl->urlstr[sensorUrl->length] = 0; pos += sensorUrl->length; @@ -2540,6 +2553,7 @@ void SensorUrl_load() { DEBUG_PRINT(sensorUrl->type); DEBUG_PRINT(": "); DEBUG_PRINTLN(sensorUrl->urlstr); + if (!last) sensorUrls = sensorUrl; else @@ -3030,6 +3044,17 @@ void check_monitors() { case MONITOR_NOT: mon->active = get_monitor(mon->m.mnot.monitor, true, false); break; + case MONITOR_TIME: { + time_os_t timeNow = os.now_tz(); + uint16_t time = hour(timeNow) * 100 + minute(timeNow); //HHMM + uint8_t wday = (weekday(timeNow)+5)%7; //Monday = 0 + mon->active = (mon->m.mtime.weekdays >> wday) & 0x01; + if (mon->m.mtime.time_from > mon->m.mtime.time_to) // FROM > TO ? Over night value + mon->active &= time >= mon->m.mtime.time_from || time <= mon->m.mtime.time_to; + else + mon->active &= time >= mon->m.mtime.time_from && time <= mon->m.mtime.time_to; + break; + } case MONITOR_REMOTE: mon->active = get_remote_monitor(mon, wasActive); break; diff --git a/sensors.h b/sensors.h index f5ff0a179..681f17cff 100644 --- a/sensors.h +++ b/sensors.h @@ -237,6 +237,7 @@ typedef struct SensorUrl { #define MONITOR_OR 11 #define MONITOR_XOR 12 #define MONITOR_NOT 13 +#define MONITOR_TIME 14 #define MONITOR_REMOTE 100 typedef struct Monitor_MINMAX { // type = 1+2 @@ -269,6 +270,12 @@ typedef struct Monitor_NOT { // type = 13 uint16_t monitor; } Monitor_NOT_t; +typedef struct Monitor_TIME { // type = 14 + uint16_t time_from; //Format: HHMM + uint16_t time_to; //Format: HHMM + uint8_t weekdays; //bit 0=monday +} Monitor_TIME_t; + typedef struct Monitor_REMOTE { // type = 100 uint16_t rmonitor; uint32_t ip; @@ -281,6 +288,7 @@ typedef union Monitor_Union { Monitor_SET_SENSOR12_t set_sensor12; // type = 4 Monitor_ANDORXOR_t andorxor; // type = 10+11+12 Monitor_NOT_t mnot; // type = 13 + Monitor_TIME_t mtime; // type = 14 Monitor_REMOTE_t remote; //type = 100 } Monitor_Union_t; diff --git a/weather.cpp b/weather.cpp index ad9bd6ca4..4b9fc408c 100644 --- a/weather.cpp +++ b/weather.cpp @@ -137,7 +137,7 @@ void GetWeather() { if (os.state!=OS_STATE_CONNECTED || WiFi.status()!=WL_CONNECTED) return; #endif // use temp buffer to construct get command - BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE_L); 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 6961792113952c6303d2684517a7c878b68017b1 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Thu, 3 Apr 2025 22:48:56 +0200 Subject: [PATCH 273/281] fixed OSPi error --- sensors.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sensors.cpp b/sensors.cpp index 5fcd67dd9..a72f7a950 100644 --- a/sensors.cpp +++ b/sensors.cpp @@ -3047,7 +3047,13 @@ void check_monitors() { case MONITOR_TIME: { time_os_t timeNow = os.now_tz(); uint16_t time = hour(timeNow) * 100 + minute(timeNow); //HHMM +#if defined(ARDUINO) uint8_t wday = (weekday(timeNow)+5)%7; //Monday = 0 +#else + time_os_t ct = timeNow; + struct tm *ti = gmtime(&ct); + uint8_t wday = (ti->tm_wday+1)%7; +#endif mon->active = (mon->m.mtime.weekdays >> wday) & 0x01; if (mon->m.mtime.time_from > mon->m.mtime.time_to) // FROM > TO ? Over night value mon->active &= time >= mon->m.mtime.time_from || time <= mon->m.mtime.time_to; From 70bbf0e0175da89a37e3a4caa452737b3c4729a5 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 19 Apr 2025 20:36:51 +0200 Subject: [PATCH 274/281] Fixt compile error --- mqtt.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mqtt.cpp b/mqtt.cpp index 5432dce7c..c78dbef40 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -663,7 +663,7 @@ void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { } void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { - sensor_mqtt_callback(key, callback); + registerCallback(key, callback); mqtt_client->setCallback(key_callback); } @@ -859,12 +859,12 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct if (key_callbacks[i].callback) { DEBUG_PRINT("Callback exec: "); DEBUG_PRINTLN(key_callbacks[i].key); - key_callbacks[i].callback(mosq, obj, msg); + key_callbacks[i].callback(mosq, obj, msg); } } } -static void sensor_mqtt_callback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { +static void registerCallback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { boolean ok = false; for (int i = 0; i < MAX_CALLBACKS; i++) { if (callback) { @@ -889,7 +889,7 @@ static void sensor_mqtt_callback(int key, void (*callback)(struct mosquitto *, v void OSMqtt::setCallback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { - sensor_mqtt_callback(key, callback); + registerCallback(key, callback); mosquitto_message_callback_set(mqtt_client, sensor_mqtt_callback); } From 87aa2ffc90c2f94d349ab1f569431a46d2aa739f Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sun, 1 Jun 2025 02:10:11 +0200 Subject: [PATCH 275/281] W5500 + ENC28J60 now running with max speed, fixed mqtt buffer overread, Version 2.3.3(176) --- OpenSprinkler.cpp | 4 ++-- defines.h | 4 ++-- main.cpp | 18 +++++++++++++----- sensor_mqtt.cpp | 4 ++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index c30e2c3a3..d322b700a 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -569,7 +569,7 @@ bool init_W5500(boolean initSPI) { SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(32000000); + SPI.setFrequency(40000000); // 40MHz is the maximum SPI clock for W5500 } pinMode(PIN_ETHER_CS, OUTPUT); @@ -612,7 +612,7 @@ bool init_ENC28J60() { SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(4000000); + SPI.setFrequency(20000000); //ENC28J60 maximum SPI clock is 20MHz // ==> setregbank(MAADRX_BANK); pinMode(PIN_ETHER_CS, OUTPUT); diff --git a/defines.h b/defines.h index 957ea67f1..ec850246d 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -//#define ENABLE_DEBUG // enable serial debug +#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; @@ -36,7 +36,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 175 // Firmware minor version +#define OS_FW_MINOR 176 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler diff --git a/main.cpp b/main.cpp index 066f7f239..2411f9f42 100644 --- a/main.cpp +++ b/main.cpp @@ -2317,10 +2317,6 @@ static void check_network() { // 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; } return true; @@ -2335,7 +2331,19 @@ static void check_network() { return; } - if(!pinger->Ping(useEth?eth.gatewayIP() : WiFi.gatewayIP())) { + boolean ping_ok = false; + switch(os.status.network_fails % 3) { + case 0: + ping_ok = pinger->Ping(useEth?eth.gatewayIP() : WiFi.gatewayIP()); + break; + case 1: + ping_ok = pinger->Ping("google.com"); + break; + case 2: + ping_ok = pinger->Ping("opensprinkler.com"); + break; + } + if(!ping_ok) { os.status.network_fails++; #if defined(ENABLE_DEBUG) Serial.println("Error during last ping command."); diff --git a/sensor_mqtt.cpp b/sensor_mqtt.cpp index c91f408ce..a5f3aa11f 100644 --- a/sensor_mqtt.cpp +++ b/sensor_mqtt.cpp @@ -127,7 +127,7 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct char buf[30]; p = strpbrk(p, "0123456789.-+nullNULL"); uint i = 0; - while (p && i < sizeof(buf)) { + while (p && i < sizeof(buf) && p < (char*)payload+length) { char ch = *p++; if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == '+') { buf[i++] = ch; @@ -139,7 +139,7 @@ static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct double value = -9999; int ok = sscanf(buf, "%lf", &value); - if (ok && value >= -1000 && value <= 1000 && (value != sensor->last_data || !sensor->flags.data_ok || now-sensor->last_read > 6000)) { + if (ok && value >= -10000 && value <= 10000 && (value != sensor->last_data || !sensor->flags.data_ok || now-sensor->last_read > 6000)) { sensor->last_data = value; sensor->flags.data_ok = true; sensor->last_read = now; From 28999be80299ac06ae7f51ad802f4b0c2cbb13a9 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Wed, 4 Jun 2025 00:39:17 +0200 Subject: [PATCH 276/281] W5500 80MHZ --- OpenSprinkler.cpp | 2 +- defines.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index d322b700a..540217df6 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -569,7 +569,7 @@ bool init_W5500(boolean initSPI) { SPI.begin(); SPI.setBitOrder(MSBFIRST); SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(40000000); // 40MHz is the maximum SPI clock for W5500 + SPI.setFrequency(80000000); // 80MHz is the maximum SPI clock for W5500 } pinMode(PIN_ETHER_CS, OUTPUT); diff --git a/defines.h b/defines.h index ec850246d..5b9594512 100644 --- a/defines.h +++ b/defines.h @@ -24,7 +24,7 @@ #ifndef _DEFINES_H #define _DEFINES_H -#define ENABLE_DEBUG // enable serial debug +//#define ENABLE_DEBUG // enable serial debug typedef unsigned long ulong; From f24829e599d06a5ffa23849b6b3b3eb80ab29286 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:07:12 -0400 Subject: [PATCH 277/281] fix build script --- build.sh | 3 +-- build2.sh | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/build.sh b/build.sh index d7a890939..d36264dd0 100755 --- a/build.sh +++ b/build.sh @@ -69,7 +69,6 @@ if [ "$1" == "demo" ]; then ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - ifx=$(ls external/influxdb-cpp/*.cpp) g++ -o OpenSprinkler -DDEMO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h main.cpp \ OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp \ mqtt.cpp smtp.c RCSwitch.cpp \ @@ -77,7 +76,7 @@ if [ "$1" == "demo" ]; then $ws \ -Iexternal/OpenThings-Framework-Firmware-Library/ \ $otf \ - $ifx \ + -Iexternal/influxdb-cpp/ \ -lpthread -lmosquitto -lssl -lcrypto else echo "Installing required libraries..." diff --git a/build2.sh b/build2.sh index 18d0185b9..75294a452 100755 --- a/build2.sh +++ b/build2.sh @@ -33,7 +33,6 @@ echo "Compiling firmware..." ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - ifx=$(ls external/influxdb-cpp/*.hpp) g++ -o OpenSprinkler -DOSPI $USEGPIO $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp \ smtp.c RCSwitch.cpp sensor*.cpp \ @@ -42,7 +41,8 @@ $ws \ -Iexternal/OpenThings-Framework-Firmware-Library/ \ $otf \ - $ifx osinfluxdb.cpp -Iexternal/influxdb-cpp/ \ + -Iexternal/influxdb-cpp/ \ + osinfluxdb.cpp \ -lpthread -lmosquitto -lssl -lcrypto -li2c -lmodbus $GPIOLIB From eb9cc50ce8766c899f676b411a040157fb1356e5 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:11:52 -0400 Subject: [PATCH 278/281] fix influxdb import --- osinfluxdb.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osinfluxdb.h b/osinfluxdb.h index cfb21cb4c..45f05d99f 100644 --- a/osinfluxdb.h +++ b/osinfluxdb.h @@ -28,7 +28,7 @@ #include #include #else -#include "influxdb.hpp" +#include #endif From c3e8bc7821bedcd65ee864a28826f3ba15a01f8a Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:23:28 -0400 Subject: [PATCH 279/281] update makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index ae3964d20..ffaf90d4e 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ CXX=g++ # -std=gnu++17 VERSION=OSPI -CXXFLAGS=-std=gnu++14 -D$(VERSION) -DSMTP_OPENSSL -Wall -include string.h -Iexternal/TinyWebsockets/tiny_websockets_lib/include -Iexternal/OpenThings-Framework-Firmware-Library/ +CXXFLAGS=-std=gnu++14 -D$(VERSION) -DSMTP_OPENSSL -Wall -include string.h -Iexternal/TinyWebsockets/tiny_websockets_lib/include -Iexternal/OpenThings-Framework-Firmware-Library/ -Iexternal/influxdb-cpp/ LD=$(CXX) LIBS=pthread mosquitto ssl crypto i2c LDFLAGS=$(addprefix -l,$(LIBS)) From d75a01c5230ce89322100006c0d0302c53f387cf Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:23:59 -0400 Subject: [PATCH 280/281] fix pinger.h capitalization --- main.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.cpp b/main.cpp index 2411f9f42..7be2e5226 100644 --- a/main.cpp +++ b/main.cpp @@ -41,7 +41,7 @@ #if defined(ARDUINO) #if defined(ESP8266) - #include + #include #include //extern "C" struct netif* eagle_lwip_getif (int netif_index); Pinger *pinger = NULL; From 456018e7d7db2e0c1c276980708252074fa361f6 Mon Sep 17 00:00:00 2001 From: arfrie22 <43021241+arfrie22@users.noreply.github.com> Date: Wed, 25 Jun 2025 12:31:26 -0400 Subject: [PATCH 281/281] update build files --- Makefile | 2 +- build.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index ffaf90d4e..ab3f1bb5c 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LD=$(CXX) LIBS=pthread mosquitto ssl crypto i2c LDFLAGS=$(addprefix -l,$(LIBS)) BINARY=OpenSprinkler -SOURCES=main.cpp OpenSprinkler.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 program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp sensors.cpp osinfluxdb.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 d36264dd0..be1325cef 100755 --- a/build.sh +++ b/build.sh @@ -71,7 +71,7 @@ if [ "$1" == "demo" ]; then otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) g++ -o OpenSprinkler -DDEMO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h main.cpp \ OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp \ - mqtt.cpp smtp.c RCSwitch.cpp \ + mqtt.cpp smtp.c RCSwitch.cpp sensors.cpp osinfluxdb.cpp \ -Iexternal/TinyWebsockets/tiny_websockets_lib/include \ $ws \ -Iexternal/OpenThings-Framework-Firmware-Library/ \