From ef3161e9dbff4c50eb815af9e0e47dcef4775584 Mon Sep 17 00:00:00 2001 From: Stefan Schmaltz Date: Sat, 13 Jul 2019 01:24:35 +0200 Subject: [PATCH 01/64] 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 637f84bf..b071f7ab 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 4ca85ea2..430a26cd 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 2d8033fe..50669b97 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 b8eda719..b9698915 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 8241f67e..60c6f5bf 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 02/64] RAIN->SOIL --- main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 60c6f5bf..10095f61 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 03/64] 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 b6b51525..b071f7ab 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 01b3c25a..430a26cd 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 be667105..b9698915 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 d4dfd744..60c6f5bf 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 04/64] RAIN->SOIL --- main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.cpp b/main.cpp index 60c6f5bf..10095f61 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 05/64] 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 26c6bea6..6a788273 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 b9cf53fb..7dac2433 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 b071f7ab..fbe7c1ee 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 430a26cd..252330a5 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 dc68cc22..9be398ce 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 50669b97..726f628e 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 b9698915..8d900a1e 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 318ee60b..00000000 --- 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 49c4afef..8afd1bbf 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 318ee60b..1aa649f7 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 00000000..68055645 --- /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 4406e204..ac514067 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 45658d53..1acf2a25 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 b09091e9..c494e603 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 9176e4c3..bd5a4e5c 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 cb18a8a6..23cd670c 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 49599abb..42873900 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 00000000..44aa7fc3 --- /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 00000000..567c7cb3 --- /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 00000000..a739e769 --- /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 00000000..a3d5bae8 --- /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 00000000..7bbcdbea --- /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 00000000..ba0df968 --- /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 00000000..2355e7cc --- /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 00000000..77da724e --- /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 00000000..791f9c1f --- /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 40049c8a..2bed1778 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 f448b8b9..0e5784ca 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 23809cb9..b11e86f8 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 06/64] Update README.txt lwip --- README.txt | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.txt b/README.txt index db30fc39..f15a6ab1 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 07/64] Update README.txt --- README.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.txt b/README.txt index f15a6ab1..136269ae 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 08/64] 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 8d900a1e..6de9e5e4 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 05f43a08..11f84178 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 09/64] - 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 fbe7c1ee..a79fb5c4 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 6de9e5e4..e607de07 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 11f84178..1ab9a105 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 10/64] 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 1ab9a105..1218d6fc 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 11/64] =?UTF-8?q?2.2.0.108=20Beseitigt=20eine=20m=C3=B6gli?= =?UTF-8?q?che=20Bootloop-Situation=20beim=20=C3=84ndern=20der=20Konfigura?= =?UTF-8?q?tion?= 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 a79fb5c4..873c7d43 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 e607de07..33ddf356 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 1218d6fc..249eced4 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 68055645..a12e3967 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 2bed1778..e63f9f0e 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 12/64] =?UTF-8?q?Version=20.109=20mit=20ge=C3=A4nderten=20?= =?UTF-8?q?Build=20Flags=20und=20zus=C3=A4tzlicher=20Wiederholung=20beim?= =?UTF-8?q?=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 873c7d43..74e50461 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 33ddf356..fac96058 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 249eced4..f51a2753 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 a12e3967..74e99206 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 44aa7fc3..ba014ec6 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 13/64] 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 fac96058..b9f6c176 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 f51a2753..6451f983 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 bd5a4e5c..e4f3e238 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 14/64] 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 74e50461..ac37c0f0 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 252330a5..1814fae1 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 b9f6c176..97440ffc 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 6451f983..bb9dd7bc 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 23cd670c..02aa9a93 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 00000000..61143b12 --- /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 00000000..b4f7ac47 --- /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 e63f9f0e..0824e13c 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 0e5784ca..0c6c8052 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 15/64] 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 02aa9a93..5f1d3f51 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 61143b12..a167cd7d 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 b4f7ac47..df585a73 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 16/64] 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 ac37c0f0..ab04e23a 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 97440ffc..25bd2919 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 bb9dd7bc..a17caf19 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 5f1d3f51..66c85bde 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 42873900..feacd868 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 a167cd7d..31295fdd 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 df585a73..ad91c396 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 0824e13c..070b3ae5 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 17/64] 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 31295fdd..909841d8 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 ad91c396..ca57e7fe 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 18/64] 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 a17caf19..93c47134 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 74e99206..1ba67b7d 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 66c85bde..25285024 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 909841d8..5d477101 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 ca57e7fe..6f9b6702 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 19/64] 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 93c47134..4f24c453 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 1ba67b7d..200bb84e 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 25285024..4c7fa7ac 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 5d477101..eb1e430b 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 6f9b6702..c88fd928 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 20/64] 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 ab04e23a..33175b48 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 25bd2919..7a4b30fd 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 00000000..fac96058 --- /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 4f24c453..28504019 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 200bb84e..286afa04 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 00000000..4917f979 --- /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 00000000..68055645 --- /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 00000000..1ba67b7d --- /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 00000000..397456c6 --- /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 4c7fa7ac..4d4c5ed8 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 ba014ec6..220558d8 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 eb1e430b..c98a1a59 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 c88fd928..302d863c 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 21/64] 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 4d4c5ed8..36abad82 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 c98a1a59..a5503006 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 302d863c..fe4e56a5 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 22/64] - 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 00000000..e7b1f702 --- /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 36abad82..1f04d614 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 a5503006..9073bf1f 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 fe4e56a5..3c342787 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 23/64] 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 e7b1f702..ab2bdfdb 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 1f04d614..a57c7696 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 220558d8..e0c78792 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 9073bf1f..4448deb7 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 3c342787..1b48ebce 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 070b3ae5..ee8369e6 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 0c6c8052..03b4b74f 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 24/64] 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 ab2bdfdb..b29591f9 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 a57c7696..9637b0cf 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 25/64] 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 7a4b30fd..1b0b15da 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 9637b0cf..83472af5 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 4448deb7..d329714a 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 1b48ebce..0b56690a 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 26/64] 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 83472af5..5eee93a2 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 feacd868..a7c40125 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 d329714a..0272262f 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 0b56690a..118be034 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 27/64] Fixt sensor timeout issue --- sensors.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 0272262f..a0843a01 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 28/64] 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 6a788273..f3e1f9ed 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 42a150b3..85adc0ea 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 1b9d5577..89ee10ec 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 33175b48..8d02f7a0 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 1814fae1..f5d4771b 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 136269ae..db30fc39 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 b5bd3986..c59866db 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 c8dbed53..bf3faf0f 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 5e1f1566..bbb1a83e 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 9be398ce..8c5df9f5 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 1b0b15da..623170fa 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 c019aefd..279f3b3d 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 a4989041..67f82566 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 2ceca5c4..c6434c13 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 07434bc0..2671a39b 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 8f6fb782..a4eeb2bc 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 29af8c5f..e9f87106 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 28504019..b03dedf2 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 1aa649f7..702f3683 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 286afa04..81501fdf 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 ac514067..26606dbd 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 1acf2a25..47c3673e 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 e4f3e238..6732e707 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 a7c40125..bdf383ae 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 e0c78792..bc6bdcff 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 e7d57319..3930326d 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 a0843a01..c629fdff 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 118be034..f6d4d788 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 ee8369e6..f670f54a 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 03b4b74f..939d89b5 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 b11e86f8..7f2b7f70 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 9e2aa873..29bfa88e 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 29/64] 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 00000000..702f3683 --- /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 b03dedf2..7f922a28 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 30/64] 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 00000000..bcdbfc44 --- /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 c629fdff..0592a643 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 31/64] *.bak to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 84c2ec0b..cdc87026 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 32/64] 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 bcdbfc44..623170fa 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 7f922a28..12694dde 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 bc6bdcff..1f9a8db9 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 33/64] 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 1ed26094..168bfe36 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 0592a643..95a63d7a 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 f6d4d788..9ed3601a 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 34/64] 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 12694dde..79a17085 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 168bfe36..7a5f9e61 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 95a63d7a..6c43988f 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 35/64] 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 8c5df9f5..a356a4fc 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 6c43988f..c8817438 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 36/64] 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 c8817438..a9da5632 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 9ed3601a..41c14427 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 37/64] Fix ip convert OSPi --- sensors.cpp | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index a9da5632..89a8ba70 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 38/64] OSPi compat --- sensors.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 89a8ba70..41c7f391 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 39/64] Tryfix peek for OSPi --- sensors.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 41c7f391..a69cea53 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 40/64] read data OSPi compat --- sensors.cpp | 57 ++++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index a69cea53..32a80983 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 41/64] Typo fix --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index 32a80983..fbb8a980 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 42/64] 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 79a17085..9c202c2e 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 7a5f9e61..89ceceb2 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 43/64] 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 89ceceb2..7d48045d 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 fbb8a980..39bc5a3d 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 41c14427..94c076dd 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 44/64] 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 7d48045d..8a2927ce 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 39bc5a3d..c35816d6 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 94c076dd..2a4de995 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 45/64] 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 8a2927ce..f01f4d25 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 c35816d6..e6e67a06 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 46/64] 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 623170fa..ca4b6a16 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 f01f4d25..d0293d2e 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 1f9a8db9..0c9a08a0 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 e6e67a06..95b92cd3 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 2a4de995..6e3e9ed6 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 47/64] fix remote sensor read --- sensors.cpp | 76 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 19 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index 95b92cd3..dff4323b 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 48/64] fix remote sensor read --- sensors.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sensors.cpp b/sensors.cpp index dff4323b..c9827220 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 49/64] 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 d0293d2e..a97a2524 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 50/64] 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 9c202c2e..d8166ce7 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 c9827220..d05e58f1 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 51/64] 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 d05e58f1..b84c4516 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 52/64] 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 b84c4516..43d70985 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 53/64] 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 43d70985..f52e83eb 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 54/64] 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 a97a2524..f6c6e82f 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 f52e83eb..dcfced65 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 6e3e9ed6..e3987c9f 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 55/64] 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 f6c6e82f..a48ce8c3 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 dcfced65..3226a8e9 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 56/64] 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 a48ce8c3..0945aab8 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 3226a8e9..2e879f9d 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 f670f54a..aae48b79 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 939d89b5..1a51cb3b 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 57/64] 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 2e879f9d..ff5df6d0 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 58/64] Fix week/month calc --- sensors.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index ff5df6d0..eba8680d 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 59/64] 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 0945aab8..2bcb047d 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 60/64] 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 ca4b6a16..1120e0f4 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 279f3b3d..af32da63 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 2bcb047d..d382e436 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 eba8680d..fda071cc 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 e3987c9f..07d4b59a 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 61/64] 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 1120e0f4..0b20f047 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 fda071cc..b6ef0bfe 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 62/64] - added mqtt checks --- sensors.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sensors.cpp b/sensors.cpp index b6ef0bfe..ee656e29 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 63/64] 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 0b20f047..16db9151 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 d8166ce7..0706b7ce 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 64/64] 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 16db9151..e383208e 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 0706b7ce..7a8a0dc8 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 0c9a08a0..a136569a 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 ee656e29..e6e71725 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 07d4b59a..9f8e6181 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[];