diff --git a/.gitignore b/.gitignore index 84c2ec0b3..cdc87026e 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ testmode.h build-1284/* .vscode .pio +*.bak diff --git a/Sensor API.txt b/Sensor API.txt new file mode 100644 index 000000000..52e5adeea --- /dev/null +++ b/Sensor API.txt @@ -0,0 +1,137 @@ +Sensor API +This api documentation is part of the opensprinkler + + the ip-address, for example 192.168.0.55 + the MD5 encrypted password, opendoor=a6d82bced638de3def1e9bbb4983225c + + +Create Sensors (sc): +creates, modifies or deletes a sensor. +"nr" for a unique number >= 1 +"type" for the sensor-type, see sf. type=0 deletes the sensor +"group" for group assignment, a nr of another sensor with type=SENSOR_GROUP_YXZ +"name" a name +"ip" for the ip-address, only for network connected sensors. All others use ip=0 +"port" for the ip-port-address, only for network connected sensors. All others use port=0 +"id" sub-id, e.a. modbus address or subid, for ADC Sensors 0..7 +"ri" read interval in seconds +"enable" 0=sensor disabled, 1=sensor enabled +"log" 0=logging disabled, 1=logging enabled + +examples: +http:///sc?pw=&nr=1&type=1&group=0&name=SMT100-Mois&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 +http:///sc?pw=&nr=2&type=2&group=0&name=SMT100-Temp&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 +http:///sc?pw=&nr=3&type=11&group=0&name=SMT50-Mois&ip=0&id=0&ri=60&enable=1&log=1 +http:///sc?pw=&nr=4&type=12&group=0&name=SMT50-Temp&ip=0&id=1&ri=60&enable=1&log=1 + +ip: dec=4261456064 = hex=FE00A8C0 = +FE = 254 +00 = 000 +A8 = 168 +C0 = 192 + = 192.168.000.254 + +For the Truebner SMT100 RS485 Modbus you need a RS485 Modbus RTU over TCP converter. +Set ip/port for the converter, e.a PUSR USR-W610 in transparent modus. + +For the analog ports of the extension board (including SMT50) use id 0..7 + +type: +SENSOR_NONE 0 None or deleted sensor +SENSOR_SMT100_MODBUS_RTU_MOIS 1 Truebner SMT100 RS485 Modbus RTU over TCP, moisture mode +SENSOR_SMT100_MODBUS_RTU_TEMP 2 Truebner SMT100 RS485 Modbus RTU over TCP, temperature mode +SENSOR_ANALOG_EXTENSION_BOARD 10 New OpenSprinkler analog extension board x8 - voltage mode 0..4V +SENSOR_ANALOG_EXTENSION_BOARD_P 11 New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% +SENSOR_SMT50_MOIS 15 New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 +SENSOR_SMT50_TEMP 16 New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +SENSOR_OSPI_ANALOG_INPUTS 20 Old OSPi analog input +SENSOR_REMOTE 100 Remote sensor of an remote opensprinkler +SENSOR_GROUP_MIN 1000 Sensor group with min value +SENSOR_GROUP_MAX 1001 Sensor group with max value +SENSOR_GROUP_AVG 1002 Sensor group with avg value +SENSOR_GROUP_SUM 1003 Sensor group with sum value + +List Sensors (sl): +lists the current sensors +examples: +http:///sl?pw= + +Get last Sensor values (sg): +returns last read sensor values +examples: +http:///sg?pw= +http:///sg?pw=&nr=1 + +Read sensor now (sr): +executes sensor read and returns the values +examples: +http:///sr?pw= +http:///sr?pw=&nr=1 + +Set sensor address for SMT100 (sa): +Only for SMT100: Set modbus address +Disconnect all other modbus sensors, so that only one sensor is connected. Sets the modbus address for sensor nr to id +examples: +http:///sa?pw=&nr=1&id=1 + +Dump Sensor Log (so): +dumps the sensor log +examples: +http:///so?pw= +http:///so?pw=&start=0&max=100&nr=1&type=1&before=1666915277&after=1666915277 + +Clear Sensor Log (sn): +clears the sensor log +examples: +http:///sn?pw= + +Program adjustments (sb): +defines program adjustments +"nr" adjustment-nr +"type" adjustment-type (0=delete, 1=linear, 2=digital min, 3=digital max) +"sensor" sensor-nr +"prog" program-nr +"factor1", "factor2", "min", "max" formula-values +examples: +http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 + +type: +PROG_NONE 0 //No adjustment (delete) +PROG_LINEAR 1 //formula +PROG_DIGITAL_MIN 2 //1=under or equal min, 0=above +PROG_DIGITAL_MAX 3 //1=over or equal max, 0=below + +formula: + min max factor1 factor2 + 10..90 -> 5..1 factor1 > factor2 + a b c d + (b-sensorData) / (b-a) * (c-d) + d + + 10..90 -> 1..5 factor1 < factor2 + a b c d + (sensorData-a) / (b-a) * (d-c) + c + +min/max is the used range of the sensor (for example min=10 max=80) +factor1/factor2 is the calculated adjustment (for example factor1=2 factor2=0) +So a sensordata of 10 will be a adjustment of factor 2 (200%) or +a sensordata of 80 will be a adjustment of factor 0 (0%) +everything between will be linear scaled in the range of 0..2 + +List Program adjustments (se): +lists the current program adjustments +examples: +http:///se?pw= +&nr=1&prog=1 + +List supported sensor types (sf): +lists supported sensor types +examples: +http:///sf?pw= + +List supported program adjustments (sh): +http:///sh?pw= + +System used and free space (du): +http:///du?pw= + + diff --git a/defines.h b/defines.h old mode 100755 new mode 100644 index cb9758cf2..623170fab --- 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; @@ -32,7 +32,7 @@ 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 @@ -57,6 +57,9 @@ typedef unsigned long ulong; #define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files +#define 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/defines.h.bak b/defines.h.bak new file mode 100644 index 000000000..bcdbfc44f --- /dev/null +++ b/defines.h.bak @@ -0,0 +1,535 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * OpenSprinkler macro defines and hardware pin assignments + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _DEFINES_H +#define _DEFINES_H + +//#define ENABLE_DEBUG // enable serial debug + +typedef unsigned char byte; +typedef unsigned long ulong; + +#define TMP_BUFFER_SIZE 255 // scratch buffer size + +/** Firmware version, hardware version, and maximal values */ +#define OS_FW_VERSION 230 // Firmware version: 220 means 2.2.0 + // if this number is different from the one stored in non-volatile memory + // a device reset will be automatically triggered + +#define OS_FW_MINOR 1 // Firmware minor version + +/** Hardware version base numbers */ +#define OS_HW_VERSION_BASE 0x00 // OpenSprinkler +#define OSPI_HW_VERSION_BASE 0x40 // OpenSprinkler Pi +#define OSBO_HW_VERSION_BASE 0x80 // OpenSprinkler Beagle +#define SIM_HW_VERSION_BASE 0xC0 // simulation hardware + +/** Hardware type macro defines */ +#define HW_TYPE_AC 0xAC // standard 24VAC for 24VAC solenoids only, with triacs +#define HW_TYPE_DC 0xDC // DC powered, for both DC and 24VAC solenoids, with boost converter and MOSFETs +#define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges +#define HW_TYPE_UNKNOWN 0xFF + +/** Data file names */ +#define IOPTS_FILENAME "iopts.dat" // integer options data file +#define SOPTS_FILENAME "sopts.dat" // string options data file +#define STATIONS_FILENAME "stns.dat" // stations data file +#define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData +#define PROG_FILENAME "prog.dat" // program data file +#define DONE_FILENAME "done.dat" // used to indicate the completion of all files +#define SENSOR_FILENAME "sensor.dat" // analog sensor filename +#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename +#define SENSORLOG_FILENAME "sensorlog.dat" // analog sensor log filename + +/** Station macro defines */ +#define STN_TYPE_STANDARD 0x00 // standard solenoid station +#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station +#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station +#define STN_TYPE_GPIO 0x03 // direct GPIO station +#define STN_TYPE_HTTP 0x04 // HTTP station +#define STN_TYPE_OTHER 0xFF + +/** Notification macro defines */ +#define NOTIFY_PROGRAM_SCHED 0x0001 +#define NOTIFY_SENSOR1 0x0002 +#define NOTIFY_FLOWSENSOR 0x0004 +#define NOTIFY_WEATHER_UPDATE 0x0008 +#define NOTIFY_REBOOT 0x0010 +#define NOTIFY_STATION_OFF 0x0020 +#define NOTIFY_SENSOR2 0x0040 +#define NOTIFY_RAINDELAY 0x0080 +#define NOTIFY_STATION_ON 0x0100 + +/** HTTP request macro defines */ +#define HTTP_RQT_SUCCESS 0 +#define HTTP_RQT_NOT_RECEIVED 1 +#define HTTP_RQT_CONNECT_ERR 2 +#define HTTP_RQT_TIMEOUT 3 +#define HTTP_RQT_EMPTY_RETURN 4 + +/** Sensor macro defines */ +#define SENSOR_TYPE_NONE 0x00 +#define SENSOR_TYPE_RAIN 0x01 // rain sensor +#define SENSOR_TYPE_FLOW 0x02 // flow sensor +#define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor +#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor +#define SENSOR_TYPE_OTHER 0xFF + +#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds + +/** Reboot cause */ +#define REBOOT_CAUSE_NONE 0 +#define REBOOT_CAUSE_RESET 1 +#define REBOOT_CAUSE_BUTTON 2 +#define REBOOT_CAUSE_RSTAP 3 +#define REBOOT_CAUSE_TIMER 4 +#define REBOOT_CAUSE_WEB 5 +#define REBOOT_CAUSE_WIFIDONE 6 +#define REBOOT_CAUSE_FWUPDATE 7 +#define REBOOT_CAUSE_WEATHER_FAIL 8 +#define REBOOT_CAUSE_NETWORK_FAIL 9 +#define REBOOT_CAUSE_NTP 10 +#define REBOOT_CAUSE_PROGRAM 11 +#define REBOOT_CAUSE_POWERON 99 + + +/** WiFi defines */ +#define WIFI_MODE_AP 0xA9 +#define WIFI_MODE_STA 0x2A + +#define OS_STATE_INITIAL 0 +#define OS_STATE_CONNECTING 1 +#define OS_STATE_CONNECTED 2 +#define OS_STATE_TRY_CONNECT 3 +#define OS_STATE_WAIT_REBOOT 4 + +#define LED_FAST_BLINK 100 +#define LED_SLOW_BLINK 500 + +/** Storage / zone expander defines */ +#if defined(ARDUINO) + #define MAX_EXT_BOARDS 8 // maximum number of 8-zone expanders (each 16-zone expander counts as 2) +#else + #define MAX_EXT_BOARDS 24 // allow more zones for linux-based firmwares +#endif + +#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders +#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations +#define STATION_NAME_SIZE 32 // maximum number of characters in each station name +#define MAX_SOPTS_SIZE 160 // maximum string option size + +#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) + +/** Default string option values */ +#define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' +#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA +#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" +#define DEFAULT_WEATHER_URL "weather.opensprinkler.com" +#define DEFAULT_IFTTT_URL "maker.ifttt.com" +#define DEFAULT_OTC_SERVER "ws.cloud.openthings.io" +#define DEFAULT_OTC_PORT 80 +#define DEFAULT_DEVICE_NAME "My OpenSprinkler" +#define DEFAULT_EMPTY_STRING "" + +/* Weather Adjustment Methods */ +enum { + WEATHER_METHOD_MANUAL = 0, + WEATHER_METHOD_ZIMMERMAN, + WEATHER_METHOD_AUTORAINDELY, + WEATHER_METHOD_ETO, + WEATHER_METHOD_MONTHLY, + NUM_WEATHER_METHODS +}; + +/* Master */ +enum { + MASTER_1 = 0, + MASTER_2, + NUM_MASTER_ZONES, +}; + +enum { + MASOPT_SID = 0, + MASOPT_ON_ADJ, + MASOPT_OFF_ADJ, + NUM_MASTER_OPTS, +}; + +// Sequential Groups +#define NUM_SEQ_GROUPS 4 +#define PARALLEL_GROUP_ID 255 + +/** Macro define of each option + * Refer to OpenSprinkler.cpp for details on each option + */ +enum { + IOPT_FW_VERSION=0,// read-only (ro) + IOPT_TIMEZONE, + IOPT_USE_NTP, + IOPT_USE_DHCP, + IOPT_STATIC_IP1, + IOPT_STATIC_IP2, + IOPT_STATIC_IP3, + IOPT_STATIC_IP4, + IOPT_GATEWAY_IP1, + IOPT_GATEWAY_IP2, + IOPT_GATEWAY_IP3, + IOPT_GATEWAY_IP4, + IOPT_HTTPPORT_0, + IOPT_HTTPPORT_1, + IOPT_HW_VERSION, //ro + IOPT_EXT_BOARDS, + IOPT_SEQUENTIAL_RETIRED, //ro + IOPT_STATION_DELAY_TIME, + IOPT_MASTER_STATION, + IOPT_MASTER_ON_ADJ, + IOPT_MASTER_OFF_ADJ, + IOPT_URS_RETIRED, // ro + IOPT_RSO_RETIRED, // ro + IOPT_WATER_PERCENTAGE, + IOPT_DEVICE_ENABLE, // editable through jc + IOPT_IGNORE_PASSWORD, + IOPT_DEVICE_ID, + IOPT_LCD_CONTRAST, + IOPT_LCD_BACKLIGHT, + IOPT_LCD_DIMMING, + IOPT_BOOST_TIME, + IOPT_USE_WEATHER, + IOPT_NTP_IP1, + IOPT_NTP_IP2, + IOPT_NTP_IP3, + IOPT_NTP_IP4, + IOPT_ENABLE_LOGGING, + IOPT_MASTER_STATION_2, + IOPT_MASTER_ON_ADJ_2, + IOPT_MASTER_OFF_ADJ_2, + IOPT_FW_MINOR, //ro + IOPT_PULSE_RATE_0, + IOPT_PULSE_RATE_1, + IOPT_REMOTE_EXT_MODE, // editable through jc + IOPT_DNS_IP1, + IOPT_DNS_IP2, + IOPT_DNS_IP3, + IOPT_DNS_IP4, + IOPT_SPE_AUTO_REFRESH, + IOPT_IFTTT_ENABLE, + IOPT_SENSOR1_TYPE, + IOPT_SENSOR1_OPTION, + IOPT_SENSOR2_TYPE, + IOPT_SENSOR2_OPTION, + IOPT_SENSOR1_ON_DELAY, + IOPT_SENSOR1_OFF_DELAY, + IOPT_SENSOR2_ON_DELAY, + IOPT_SENSOR2_OFF_DELAY, + IOPT_SUBNET_MASK1, + IOPT_SUBNET_MASK2, + IOPT_SUBNET_MASK3, + IOPT_SUBNET_MASK4, + IOPT_WIFI_MODE, //ro + IOPT_RESET, //ro + NUM_IOPTS // total number of integer options +}; + +enum { + SOPT_PASSWORD=0, + SOPT_LOCATION, + SOPT_JAVASCRIPTURL, + SOPT_WEATHERURL, + SOPT_WEATHER_OPTS, + SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT + SOPT_STA_SSID, + SOPT_STA_PASS, + SOPT_MQTT_OPTS, + SOPT_OTC_OPTS, + SOPT_DEVICE_NAME, + SOPT_STA_BSSID_CHL, // wifi extra info: bssid and channel + NUM_SOPTS // total number of string options +}; + +/** Log Data Type */ +#define LOGDATA_STATION 0x00 +#define LOGDATA_SENSOR1 0x01 +#define LOGDATA_RAINDELAY 0x02 +#define LOGDATA_WATERLEVEL 0x03 +#define LOGDATA_FLOWSENSE 0x04 +#define LOGDATA_SENSOR2 0x05 +#define LOGDATA_CURRENT 0x80 + +#undef OS_HW_VERSION + +/** Hardware defines */ +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // for OS 2.3 + + #define OS_HW_VERSION (OS_HW_VERSION_BASE+23) + #define PIN_FREE_LIST {2,10,12,13,14,15,18,19} // Free GPIO pins + + // hardware pins + #define PIN_BUTTON_1 31 // button 1 + #define PIN_BUTTON_2 30 // button 2 + #define PIN_BUTTON_3 29 // button 3 + #define PIN_RFTX 28 // RF data pin + #define PORT_RF PORTA + #define PINX_RF PINA3 + #define PIN_SR_LATCH 3 // shift register latch pin + #define PIN_SR_DATA 21 // shift register data pin + #define PIN_SR_CLOCK 22 // shift register clock pin + #define PIN_SR_OE 1 // shift register output enable pin + + // regular 16x2 LCD pin defines + #define PIN_LCD_RS 19 // LCD rs pin + #define PIN_LCD_EN 18 // LCD enable pin + #define PIN_LCD_D4 20 // LCD d4 pin + #define PIN_LCD_D5 21 // LCD d5 pin + #define PIN_LCD_D6 22 // LCD d6 pin + #define PIN_LCD_D7 23 // LCD d7 pin + #define PIN_LCD_BACKLIGHT 12 // LCD backlight pin + #define PIN_LCD_CONTRAST 13 // LCD contrast pin + + // DC controller pin defines + #define PIN_BOOST 20 // booster pin + #define PIN_BOOST_EN 23 // boost voltage enable pin + + #define PIN_ETHER_CS 4 // Ethernet controller chip select pin + #define PIN_SENSOR1 11 // + #define PIN_SD_CS 0 // SD card chip select pin + #define PIN_FLOWSENSOR_INT 1 // flow sensor interrupt pin (INT1) + #define PIN_EXP_SENSE 4 // expansion board sensing pin (A4) + #define PIN_CURR_SENSE 7 // current sensing pin (A7) + #define PIN_CURR_DIGITAL 24 // digital pin index for A7 + + #define ETHER_BUFFER_SIZE 2048 + + #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset + + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite + +#elif defined(ESP8266) // for ESP8266 + + #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) + #define IOEXP_PIN 0x80 // base for pins on main IO expander + #define MAIN_I2CADDR 0x20 // main IO expander I2C address + #define ACDR_I2CADDR 0x21 // ac driver I2C address + #define DCDR_I2CADDR 0x22 // dc driver I2C address + #define LADR_I2CADDR 0x23 // latch driver I2C address + #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address + #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address + + #define PIN_CURR_SENSE A0 + #define PIN_FREE_LIST {} // no free GPIO pin at the moment + #define ETHER_BUFFER_SIZE 2048 + + #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above + + /* To accommodate different OS30 versions, we use software defines pins */ + extern byte PIN_BUTTON_1; + extern byte PIN_BUTTON_2; + extern byte PIN_BUTTON_3; + extern byte PIN_RFRX; + extern byte PIN_RFTX; + extern byte PIN_BOOST; + extern byte PIN_BOOST_EN; + extern byte PIN_LATCH_COM; + extern byte PIN_LATCH_COMA; + extern byte PIN_LATCH_COMK; + extern byte PIN_SENSOR1; + extern byte PIN_SENSOR2; + extern byte PIN_IOEXP_INT; + + /* Original OS30 pin defines */ + //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask + // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i + #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 + #define V0_PIN_BUTTON_2 0 // button 2 + #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 + #define V0_PIN_RFRX 14 + #define V0_PIN_PWR_RX IOEXP_PIN+0 + #define V0_PIN_RFTX 16 + #define V0_PIN_PWR_TX IOEXP_PIN+2 + #define V0_PIN_BOOST IOEXP_PIN+6 + #define V0_PIN_BOOST_EN IOEXP_PIN+7 + #define V0_PIN_SENSOR1 12 // sensor 1 + #define V0_PIN_SENSOR2 13 // sensor 2 + + /* OS31 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V1_IO_CONFIG 0x1F00 // config bits + #define V1_IO_OUTPUT 0x1F00 // output bits + #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 + #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 + #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V1_PIN_RFRX 14 + #define V1_PIN_RFTX 16 + #define V1_PIN_IOEXP_INT 12 + #define V1_PIN_BOOST IOEXP_PIN+13 + #define V1_PIN_BOOST_EN IOEXP_PIN+14 + #define V1_PIN_LATCH_COM IOEXP_PIN+15 + #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 + #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 + + /* OS32 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V2_IO_CONFIG 0x1000 // config bits + #define V2_IO_OUTPUT 0x1E00 // output bits + #define V2_PIN_BUTTON_1 2 // button 1 + #define V2_PIN_BUTTON_2 0 // button 2 + #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V2_PIN_RFTX 15 + #define V2_PIN_BOOST IOEXP_PIN+13 + #define V2_PIN_BOOST_EN IOEXP_PIN+14 + #define V2_PIN_LATCH_COMA IOEXP_PIN+8 // latch COM+ (anode) + #define V2_PIN_SRLAT IOEXP_PIN+9 // shift register latch + #define V2_PIN_SRCLK IOEXP_PIN+10 // shift register clock + #define V2_PIN_SRDAT IOEXP_PIN+11 // shift register data + #define V2_PIN_LATCH_COMK IOEXP_PIN+15 // latch COM- (cathode) + #define V2_PIN_SENSOR1 3 // sensor 1 + #define V2_PIN_SENSOR2 10 // sensor 2 + +#elif defined(OSPI) // for OSPi + + #define OS_HW_VERSION OSPI_HW_VERSION_BASE + #define PIN_SR_LATCH 22 // shift register latch pin + #define PIN_SR_DATA 27 // shift register data pin + #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) + #define PIN_SR_CLOCK 4 // shift register clock pin + #define PIN_SR_OE 17 // shift register output enable pin + #define PIN_SENSOR1 14 + #define PIN_SENSOR2 23 + #define PIN_RFTX 15 // RF transmitter pin + //#define PIN_BUTTON_1 23 // button 1 + //#define PIN_BUTTON_2 24 // button 2 + //#define PIN_BUTTON_3 25 // button 3 + + #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins + #define ETHER_BUFFER_SIZE 16384 + +#elif defined(OSBO) // for OSBo + + #define OS_HW_VERSION OSBO_HW_VERSION_BASE + // these are gpio pin numbers, refer to + // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp + #define PIN_SR_LATCH 60 // P9_12, shift register latch pin + #define PIN_SR_DATA 30 // P9_11, shift register data pin + #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin + #define PIN_SR_OE 50 // P9_14, shift register output enable pin + #define PIN_SENSOR1 48 + #define PIN_RFTX 51 // RF transmitter pin + + #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} + #define ETHER_BUFFER_SIZE 16384 + +#else // for demo / simulation + // use fake hardware pins + #if defined(DEMO) + #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware + #else + #define OS_HW_VERSION SIM_HW_VERSION_BASE + #endif + #define PIN_SR_LATCH 0 + #define PIN_SR_DATA 0 + #define PIN_SR_CLOCK 0 + #define PIN_SR_OE 0 + #define PIN_SENSOR1 0 + #define PIN_SENSOR2 0 + #define PIN_RFTX 0 + #define PIN_FREE_LIST {} + #define ETHER_BUFFER_SIZE 16384 +#endif + +#if defined(ENABLE_DEBUG) /** Serial debug functions */ + + #if defined(ARDUINO) + #define DEBUG_BEGIN(x) {Serial.begin(x);} + #define DEBUG_PRINT(x) {Serial.print(x);} + #define DEBUG_PRINTLN(x) {Serial.println(x);} + #else + #include + #define DEBUG_BEGIN(x) {} /** Serial debug functions */ + inline void DEBUG_PRINT(int x) {printf("%d", x);} + inline void DEBUG_PRINT(const char*s) {printf("%s", s);} + #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} + #endif + +#else + + #define DEBUG_BEGIN(x) {} + #define DEBUG_PRINT(x) {} + #define DEBUG_PRINTLN(x) {} + +#endif + +/** Re-define avr-specific (e.g. PGM) types to use standard types */ +#if !defined(ARDUINO) + #include + #include + #include + #include + inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} + inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} + #define now() time(0) + #define pgm_read_byte(x) *(x) + #define PSTR(x) x + #define F(x) x + #define strcat_P strcat + #define strcpy_P strcpy + #define sprintf_P sprintf + #include + #define String string + using namespace std; + #define PROGMEM + typedef const char* PGM_P; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef bool boolean; + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite +#endif + +/** Other defines */ +// button values +#define BUTTON_1 0x01 +#define BUTTON_2 0x02 +#define BUTTON_3 0x04 + +// button status values +#define BUTTON_NONE 0x00 // no button pressed +#define BUTTON_MASK 0x0F // button status mask +#define BUTTON_FLAG_HOLD 0x80 // long hold flag +#define BUTTON_FLAG_DOWN 0x40 // down flag +#define BUTTON_FLAG_UP 0x20 // up flag + +// button timing values +#define BUTTON_DELAY_MS 1 // short delay (milliseconds) +#define BUTTON_HOLD_MS 1000 // long hold expiration time (milliseconds) + +// button mode values +#define BUTTON_WAIT_NONE 0 // do not wait, return value immediately +#define BUTTON_WAIT_RELEASE 1 // wait until button is release +#define BUTTON_WAIT_HOLD 2 // wait until button hold time expires + +#define DISPLAY_MSG_MS 2000 // message display time (milliseconds) + +#endif // _DEFINES_H diff --git a/defines.h~ b/defines.h~ new file mode 100644 index 000000000..fac96058e --- /dev/null +++ b/defines.h~ @@ -0,0 +1,502 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * OpenSprinkler macro defines and hardware pin assignments + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _DEFINES_H +#define _DEFINES_H + +#define ENABLE_DEBUG // enable serial debug + +typedef unsigned char byte; +typedef unsigned long ulong; + +#define TMP_BUFFER_SIZE 255 // scratch buffer size + +/** Firmware version, hardware version, and maximal values */ +#define OS_FW_VERSION 220 // Firmware version: 220 means 2.2.0 + // if this number is different from the one stored in non-volatile memory + // a device reset will be automatically triggered + +#define OS_FW_MINOR 109 // Firmware minor version + +/** Hardware version base numbers */ +#define OS_HW_VERSION_BASE 0x00 +#define OSPI_HW_VERSION_BASE 0x40 +#define OSBO_HW_VERSION_BASE 0x80 +#define SIM_HW_VERSION_BASE 0xC0 + +/** Hardware type macro defines */ +#define HW_TYPE_AC 0xAC // standard 24VAC for 24VAC solenoids only, with triacs +#define HW_TYPE_DC 0xDC // DC powered, for both DC and 24VAC solenoids, with boost converter and MOSFETs +#define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges +#define HW_TYPE_UNKNOWN 0xFF + +/** Data file names */ +#define IOPTS_FILENAME "iopts.dat" // integer options data file +#define SOPTS_FILENAME "sopts.dat" // string options data file +#define STATIONS_FILENAME "stns.dat" // stations data file +#define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData +#define PROG_FILENAME "prog.dat" // program data file +#define DONE_FILENAME "done.dat" // used to indicate the completion of all files + +/** Station macro defines */ +#define STN_TYPE_STANDARD 0x00 +#define STN_TYPE_RF 0x01 // Radio Frequency (RF) station +#define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station +#define STN_TYPE_GPIO 0x03 // direct GPIO station +#define STN_TYPE_HTTP 0x04 // HTTP station +#define STN_TYPE_OTHER 0xFF + +/** Notification macro defines */ +#define NOTIFY_PROGRAM_SCHED 0x0001 +#define NOTIFY_SENSOR1 0x0002 +#define NOTIFY_FLOWSENSOR 0x0004 +#define NOTIFY_WEATHER_UPDATE 0x0008 +#define NOTIFY_REBOOT 0x0010 +#define NOTIFY_STATION_OFF 0x0020 +#define NOTIFY_SENSOR2 0x0040 +#define NOTIFY_RAINDELAY 0x0080 +#define NOTIFY_STATION_ON 0x0100 + +/** HTTP request macro defines */ +#define HTTP_RQT_SUCCESS 0 +#define HTTP_RQT_NOT_RECEIVED -1 +#define HTTP_RQT_CONNECT_ERR -2 +#define HTTP_RQT_TIMEOUT -3 +#define HTTP_RQT_EMPTY_RETURN -4 +#define HTTP_RQT_DNS_ERROR -5 + +/** Sensor macro defines */ +#define SENSOR_TYPE_NONE 0x00 +#define SENSOR_TYPE_RAIN 0x01 // rain sensor +#define SENSOR_TYPE_FLOW 0x02 // flow sensor +#define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor +#define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor +#define SENSOR_TYPE_OTHER 0xFF + +#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds + +/** Reboot cause */ +#define REBOOT_CAUSE_NONE 0 +#define REBOOT_CAUSE_RESET 1 +#define REBOOT_CAUSE_BUTTON 2 +#define REBOOT_CAUSE_RSTAP 3 +#define REBOOT_CAUSE_TIMER 4 +#define REBOOT_CAUSE_WEB 5 +#define REBOOT_CAUSE_WIFIDONE 6 +#define REBOOT_CAUSE_FWUPDATE 7 +#define REBOOT_CAUSE_WEATHER_FAIL 8 +#define REBOOT_CAUSE_NETWORK_FAIL 9 +#define REBOOT_CAUSE_NTP 10 +#define REBOOT_CAUSE_PROGRAM 11 +#define REBOOT_CAUSE_POWERON 99 + +/** Too much current */ +#define MAX_CURRENT 3010 //Max mA + +/** WiFi defines */ +#define WIFI_MODE_AP 0xA9 +#define WIFI_MODE_STA 0x2A + +#define OS_STATE_INITIAL 0 +#define OS_STATE_CONNECTING 1 +#define OS_STATE_CONNECTED 2 +#define OS_STATE_TRY_CONNECT 3 + +#define LED_FAST_BLINK 100 +#define LED_SLOW_BLINK 500 + +/** Storage / zone expander defines */ +#if defined(ARDUINO) + #define MAX_EXT_BOARDS 8 // maximum number of 8-zone expanders (each 16-zone expander counts as 2) +#else + #define MAX_EXT_BOARDS 24 // allow more zones for linux-based firmwares +#endif + +#define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders +#define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations +#define STATION_NAME_SIZE 32 // maximum number of characters in each station name +#define MAX_SOPTS_SIZE 160 // maximum string option size + +#define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) + +/** Default string option values */ +#define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' +#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA +#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" +#define DEFAULT_WEATHER_URL "weather.opensprinkler.com" +#define DEFAULT_IFTTT_URL "maker.ifttt.com" +#define DEFAULT_EMPTY_STRING "" + +/** Macro define of each option + * Refer to OpenSprinkler.cpp for details on each option + */ +enum { + IOPT_FW_VERSION=0,// read-only (ro) + IOPT_TIMEZONE, + IOPT_USE_NTP, + IOPT_USE_DHCP, + IOPT_STATIC_IP1, + IOPT_STATIC_IP2, + IOPT_STATIC_IP3, + IOPT_STATIC_IP4, + IOPT_GATEWAY_IP1, + IOPT_GATEWAY_IP2, + IOPT_GATEWAY_IP3, + IOPT_GATEWAY_IP4, + IOPT_HTTPPORT_0, + IOPT_HTTPPORT_1, + IOPT_HW_VERSION, //ro + IOPT_EXT_BOARDS, + IOPT_SEQUENTIAL_RETIRED, //ro + IOPT_STATION_DELAY_TIME, + IOPT_MASTER_STATION, + IOPT_MASTER_ON_ADJ, + IOPT_MASTER_OFF_ADJ, + IOPT_URS_RETIRED, // ro + IOPT_RSO_RETIRED, // ro + IOPT_WATER_PERCENTAGE, + IOPT_DEVICE_ENABLE, // editable through jc + IOPT_IGNORE_PASSWORD, + IOPT_DEVICE_ID, + IOPT_LCD_CONTRAST, + IOPT_LCD_BACKLIGHT, + IOPT_LCD_DIMMING, + IOPT_BOOST_TIME, + IOPT_USE_WEATHER, + IOPT_NTP_IP1, + IOPT_NTP_IP2, + IOPT_NTP_IP3, + IOPT_NTP_IP4, + IOPT_ENABLE_LOGGING, + IOPT_MASTER_STATION_2, + IOPT_MASTER_ON_ADJ_2, + IOPT_MASTER_OFF_ADJ_2, + IOPT_FW_MINOR, //ro + IOPT_PULSE_RATE_0, + IOPT_PULSE_RATE_1, + IOPT_REMOTE_EXT_MODE, // editable through jc + IOPT_DNS_IP1, + IOPT_DNS_IP2, + IOPT_DNS_IP3, + IOPT_DNS_IP4, + IOPT_SPE_AUTO_REFRESH, + IOPT_IFTTT_ENABLE, + IOPT_SENSOR1_TYPE, + IOPT_SENSOR1_OPTION, + IOPT_SENSOR2_TYPE, + IOPT_SENSOR2_OPTION, + IOPT_SENSOR1_ON_DELAY, + IOPT_SENSOR1_OFF_DELAY, + IOPT_SENSOR2_ON_DELAY, + IOPT_SENSOR2_OFF_DELAY, + IOPT_SUBNET_MASK1, + IOPT_SUBNET_MASK2, + IOPT_SUBNET_MASK3, + IOPT_SUBNET_MASK4, + IOPT_WIFI_MODE, //ro + IOPT_RESET, //ro + NUM_IOPTS // total number of integer options +}; + +enum { + SOPT_PASSWORD=0, + SOPT_LOCATION, + SOPT_JAVASCRIPTURL, + SOPT_WEATHERURL, + SOPT_WEATHER_OPTS, + SOPT_IFTTT_KEY, // todo: make this IFTTT config just like MQTT + SOPT_STA_SSID, + SOPT_STA_PASS, + SOPT_MQTT_OPTS, + //SOPT_WEATHER_KEY, + //SOPT_AP_PASS, + NUM_SOPTS // total number of string options +}; + +/** Log Data Type */ +#define LOGDATA_STATION 0x00 +#define LOGDATA_SENSOR1 0x01 +#define LOGDATA_RAINDELAY 0x02 +#define LOGDATA_WATERLEVEL 0x03 +#define LOGDATA_FLOWSENSE 0x04 +#define LOGDATA_SENSOR2 0x05 +#define LOGDATA_CURRENT 0x80 + +#undef OS_HW_VERSION + +/** Hardware defines */ +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // for OS 2.3 + + #define OS_HW_VERSION (OS_HW_VERSION_BASE+23) + #define PIN_FREE_LIST {2,10,12,13,14,15,18,19} // Free GPIO pins + + // hardware pins + #define PIN_BUTTON_1 31 // button 1 + #define PIN_BUTTON_2 30 // button 2 + #define PIN_BUTTON_3 29 // button 3 + #define PIN_RFTX 28 // RF data pin + #define PORT_RF PORTA + #define PINX_RF PINA3 + #define PIN_SR_LATCH 3 // shift register latch pin + #define PIN_SR_DATA 21 // shift register data pin + #define PIN_SR_CLOCK 22 // shift register clock pin + #define PIN_SR_OE 1 // shift register output enable pin + + // regular 16x2 LCD pin defines + #define PIN_LCD_RS 19 // LCD rs pin + #define PIN_LCD_EN 18 // LCD enable pin + #define PIN_LCD_D4 20 // LCD d4 pin + #define PIN_LCD_D5 21 // LCD d5 pin + #define PIN_LCD_D6 22 // LCD d6 pin + #define PIN_LCD_D7 23 // LCD d7 pin + #define PIN_LCD_BACKLIGHT 12 // LCD backlight pin + #define PIN_LCD_CONTRAST 13 // LCD contrast pin + + // DC controller pin defines + #define PIN_BOOST 20 // booster pin + #define PIN_BOOST_EN 23 // boost voltage enable pin + + #define PIN_ETHER_CS 4 // Ethernet controller chip select pin + #define PIN_SENSOR1 11 // + #define PIN_SD_CS 0 // SD card chip select pin + #define PIN_FLOWSENSOR_INT 1 // flow sensor interrupt pin (INT1) + #define PIN_EXP_SENSE 4 // expansion board sensing pin (A4) + #define PIN_CURR_SENSE 7 // current sensing pin (A7) + #define PIN_CURR_DIGITAL 24 // digital pin index for A7 + + #define ETHER_BUFFER_SIZE 2048 + + #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset + + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite + +#elif defined(ESP8266) // for ESP8266 + + #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) + #define IOEXP_PIN 0x80 // base for pins on main IO expander + #define MAIN_I2CADDR 0x20 // main IO expander I2C address + #define ACDR_I2CADDR 0x21 // ac driver I2C address + #define DCDR_I2CADDR 0x22 // dc driver I2C address + #define LADR_I2CADDR 0x23 // latch driver I2C address + #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address + #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address + + #define PIN_CURR_SENSE A0 + #define PIN_FREE_LIST {} // no free GPIO pin at the moment + #define ETHER_BUFFER_SIZE 2048 + + #define PIN_ETHER_CS 16 // ENC28J60 CS (chip select pin) is 16 on OS 3.2. + + /* To accommodate different OS30 versions, we use software defines pins */ + extern byte PIN_BUTTON_1; + extern byte PIN_BUTTON_2; + extern byte PIN_BUTTON_3; + extern byte PIN_RFRX; + extern byte PIN_RFTX; + extern byte PIN_BOOST; + extern byte PIN_BOOST_EN; + extern byte PIN_LATCH_COM; + extern byte PIN_LATCH_COMA; + extern byte PIN_LATCH_COMK; + extern byte PIN_SENSOR1; + extern byte PIN_SENSOR2; + extern byte PIN_IOEXP_INT; + + /* Original OS30 pin defines */ + //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask + // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i + #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 + #define V0_PIN_BUTTON_2 0 // button 2 + #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 + #define V0_PIN_RFRX 14 + #define V0_PIN_PWR_RX IOEXP_PIN+0 + #define V0_PIN_RFTX 16 + #define V0_PIN_PWR_TX IOEXP_PIN+2 + #define V0_PIN_BOOST IOEXP_PIN+6 + #define V0_PIN_BOOST_EN IOEXP_PIN+7 + #define V0_PIN_SENSOR1 12 // sensor 1 + #define V0_PIN_SENSOR2 13 // sensor 2 + + /* OS30 revision 1 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V1_IO_CONFIG 0x1F00 // config bits + #define V1_IO_OUTPUT 0x1F00 // output bits + #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 + #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 + #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V1_PIN_RFRX 14 + #define V1_PIN_RFTX 16 + #define V1_PIN_IOEXP_INT 12 + #define V1_PIN_BOOST IOEXP_PIN+13 + #define V1_PIN_BOOST_EN IOEXP_PIN+14 + #define V1_PIN_LATCH_COM IOEXP_PIN+15 + #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 + #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 + + /* OS30 revision 2 pin defines */ + // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i + #define V2_IO_CONFIG 0x1000 // config bits + #define V2_IO_OUTPUT 0x1E00 // output bits + #define V2_PIN_BUTTON_1 2 // button 1 + #define V2_PIN_BUTTON_2 0 // button 2 + #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 + #define V2_PIN_RFTX 15 + #define V2_PIN_BOOST IOEXP_PIN+13 + #define V2_PIN_BOOST_EN IOEXP_PIN+14 + #define V2_PIN_LATCH_COMA IOEXP_PIN+8 // latch COM+ (anode) + #define V2_PIN_SRLAT IOEXP_PIN+9 // shift register latch + #define V2_PIN_SRCLK IOEXP_PIN+10 // shift register clock + #define V2_PIN_SRDAT IOEXP_PIN+11 // shift register data + #define V2_PIN_LATCH_COMK IOEXP_PIN+15 // latch COM- (cathode) + #define V2_PIN_SENSOR1 3 // sensor 1 + #define V2_PIN_SENSOR2 10 // sensor 2 + +#elif defined(OSPI) // for OSPi + + #define OS_HW_VERSION OSPI_HW_VERSION_BASE + #define PIN_SR_LATCH 22 // shift register latch pin + #define PIN_SR_DATA 27 // shift register data pin + #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) + #define PIN_SR_CLOCK 4 // shift register clock pin + #define PIN_SR_OE 17 // shift register output enable pin + #define PIN_SENSOR1 14 + #define PIN_SENSOR2 23 + #define PIN_RFTX 15 // RF transmitter pin + //#define PIN_BUTTON_1 23 // button 1 + //#define PIN_BUTTON_2 24 // button 2 + //#define PIN_BUTTON_3 25 // button 3 + + #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins + #define ETHER_BUFFER_SIZE 16384 + +#elif defined(OSBO) // for OSBo + + #define OS_HW_VERSION OSBO_HW_VERSION_BASE + // these are gpio pin numbers, refer to + // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp + #define PIN_SR_LATCH 60 // P9_12, shift register latch pin + #define PIN_SR_DATA 30 // P9_11, shift register data pin + #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin + #define PIN_SR_OE 50 // P9_14, shift register output enable pin + #define PIN_SENSOR1 48 + #define PIN_RFTX 51 // RF transmitter pin + + #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} + #define ETHER_BUFFER_SIZE 16384 + +#else // for demo / simulation + // use fake hardware pins + #if defined(DEMO) + #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware + #else + #define OS_HW_VERSION SIM_HW_VERSION_BASE + #endif + #define PIN_SR_LATCH 0 + #define PIN_SR_DATA 0 + #define PIN_SR_CLOCK 0 + #define PIN_SR_OE 0 + #define PIN_SENSOR1 0 + #define PIN_SENSOR2 0 + #define PIN_RFTX 0 + #define PIN_FREE_LIST {} + #define ETHER_BUFFER_SIZE 16384 +#endif + +#if defined(ENABLE_DEBUG) /** Serial debug functions */ + + #if defined(ARDUINO) + #define DEBUG_BEGIN(x) {Serial.begin(x);} + #define DEBUG_PRINT(x) {Serial.print(x);} + #define DEBUG_PRINTLN(x) {Serial.println(x);} + #else + #include + #define DEBUG_BEGIN(x) {} /** Serial debug functions */ + inline void DEBUG_PRINT(int x) {printf("%d", x);} + inline void DEBUG_PRINT(const char*s) {printf("%s", s);} + #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} + #endif + +#else + + #define DEBUG_BEGIN(x) {} + #define DEBUG_PRINT(x) {} + #define DEBUG_PRINTLN(x) {} + +#endif + +/** Re-define avr-specific (e.g. PGM) types to use standard types */ +#if !defined(ARDUINO) + #include + #include + #include + #include + inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} + inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} + #define now() time(0) + #define pgm_read_byte(x) *(x) + #define PSTR(x) x + #define F(x) x + #define strcat_P strcat + #define strcpy_P strcpy + #define sprintf_P sprintf + #include + #define String string + using namespace std; + #define PROGMEM + typedef const char* PGM_P; + typedef unsigned char uint8_t; + typedef short int16_t; + typedef unsigned short uint16_t; + typedef bool boolean; + #define pinModeExt pinMode + #define digitalReadExt digitalRead + #define digitalWriteExt digitalWrite +#endif + +/** Other defines */ +// button values +#define BUTTON_1 0x01 +#define BUTTON_2 0x02 +#define BUTTON_3 0x04 + +// button status values +#define BUTTON_NONE 0x00 // no button pressed +#define BUTTON_MASK 0x0F // button status mask +#define BUTTON_FLAG_HOLD 0x80 // long hold flag +#define BUTTON_FLAG_DOWN 0x40 // down flag +#define BUTTON_FLAG_UP 0x20 // up flag + +// button timing values +#define BUTTON_DELAY_MS 1 // short delay (milliseconds) +#define BUTTON_HOLD_MS 1000 // long hold expiration time (milliseconds) + +// button mode values +#define BUTTON_WAIT_NONE 0 // do not wait, return value immediately +#define BUTTON_WAIT_RELEASE 1 // wait until button is release +#define BUTTON_WAIT_HOLD 2 // wait until button hold time expires + +#define DISPLAY_MSG_MS 2000 // message display time (milliseconds) + +#endif // _DEFINES_H diff --git a/main.cpp b/main.cpp index 32086901b..12694dde3 100644 --- a/main.cpp +++ b/main.cpp @@ -28,9 +28,14 @@ #include "weather.h" #include "opensprinkler_server.h" #include "mqtt.h" +#include "sensors.h" #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; @@ -346,6 +351,9 @@ void do_setup() { } os.button_timeout = LCD_BACKLIGHT_TIMEOUT; + + sensor_load(); + prog_adjust_load(); } // Arduino software reset function @@ -418,6 +426,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 @@ -708,6 +717,16 @@ void do_loop() // check through all programs 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 @@ -734,6 +753,9 @@ void do_loop() 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 // because it may end up being zero after scaling @@ -922,6 +944,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) { os.lcd_print_screen(ui_anim_chars[(unsigned long)curr_time%3]); } @@ -959,6 +984,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 @@ -1842,9 +1901,136 @@ 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 +#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() { diff --git a/make.lin302m b/make.lin302m new file mode 100644 index 000000000..4917f9790 --- /dev/null +++ b/make.lin302m @@ -0,0 +1,34 @@ +SKETCH = ./mainArduino.ino +LIBS = . \ + $(ESP_LIBS)/Wire \ + $(ESP_LIBS)/SPI \ + $(ESP_LIBS)/ESP8266WiFi \ + $(ESP_LIBS)/ESP8266WebServer \ + $(ESP_LIBS)/ESP8266mDNS \ + $(ESP_LIBS)/LittleFS \ + $(ESP_LIBS)/lwIP_enc28j60 \ + /data/libs/SSD1306 \ + /data/libs/rc-switch \ + /data/libs/pubsubclient \ + +ESP_ROOT = /data/esp8266_3.0.2-master +ESPCORE_VERSION = 302 +BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) + +UPLOAD_SPEED = 460800 +UPLOAD_VERB = -v +# for OS3.0 revision 1: reset mode is nodemcu +# UPLOAD_RESET = nodemcu +# Uncomment the line below for OS3.0 revision 0: reset mode is ck +# UPLOAD_RESET = ck + +FLASH_DEF = 4M3M +FLASH_MODE = dio +FLASH_SPEED = 80 +F_CPU = 160000000L + +BOARD = generic + +EXCLUDE_DIRS = ./build-1284 + +include ./makeEspArduino.mk diff --git a/make.lin302m~ b/make.lin302m~ new file mode 100644 index 000000000..680556453 --- /dev/null +++ b/make.lin302m~ @@ -0,0 +1,34 @@ +SKETCH = ./mainArduino.ino +LIBS = . \ + $(ESP_LIBS)/Wire \ + $(ESP_LIBS)/SPI \ + $(ESP_LIBS)/ESP8266WiFi \ + $(ESP_LIBS)/ESP8266WebServer \ + $(ESP_LIBS)/ESP8266mDNS \ + /data/libs/LittleFS \ + /data/libs/lwIP_enc28j60 \ + /data/libs/SSD1306 \ + /data/libs/rc-switch \ + /data/libs/pubsubclient \ + +ESP_ROOT = /data/esp8266_3.0.2/ +ESPCORE_VERSION = 302 +BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) + +UPLOAD_SPEED = 460800 +UPLOAD_VERB = -v +# for OS3.0 revision 1: reset mode is nodemcu +# UPLOAD_RESET = nodemcu +# Uncomment the line below for OS3.0 revision 0: reset mode is ck +# UPLOAD_RESET = ck + +FLASH_DEF = 4M3M +FLASH_MODE = dio +FLASH_SPEED = 80 +F_CPU = 160000000L + +BOARD = generic + +EXCLUDE_DIRS = ./build-1284 + +include ./makeEspArduino.mk diff --git a/make.lin302~ b/make.lin302~ new file mode 100644 index 000000000..1ba67b7de --- /dev/null +++ b/make.lin302~ @@ -0,0 +1,36 @@ +SKETCH = ./mainArduino.ino +LIBS = . \ + $(ESP_LIBS)/Wire \ + $(ESP_LIBS)/SPI \ + $(ESP_LIBS)/ESP8266WiFi \ + $(ESP_LIBS)/ESP8266WebServer \ + $(ESP_LIBS)/ESP8266mDNS \ + $(ESP_LIBS)/LittleFS \ + /data/libs/lwIP_enc28j60 \ + /data/libs/SSD1306 \ + /data/libs/rc-switch \ + /data/libs/pubsubclient \ + /data/libs/ADS1015 \ + +ESP_ROOT = /data/esp8266_3.0.2/ +ESPCORE_VERSION = 302 +BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) + +UPLOAD_SPEED = 460800 +UPLOAD_VERB = -v +# for OS3.0 revision 1: reset mode is nodemcu +# UPLOAD_RESET = nodemcu +# Uncomment the line below for OS3.0 revision 0: reset mode is ck +# UPLOAD_RESET = ck + +FLASH_DEF = 4M3M +FLASH_MODE = dio +FLASH_SPEED = 80 +F_CPU = 160000000L + +BOARD = generic +board_build.filesystem = littlefs + +EXCLUDE_DIRS = ./build-1284 + +include ./makeEspArduino.mk diff --git a/opensprinkler-git.code-workspace b/opensprinkler-git.code-workspace new file mode 100644 index 000000000..397456c6a --- /dev/null +++ b/opensprinkler-git.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "..\\libs" + }, + { + "path": "..\\esp8266_3.0.2\\libraries" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 3494a2894..1ed26094a 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) @@ -1958,6 +1959,822 @@ void server_fill_files(OTF_PARAMS_DEF) { } */ + +/** + * 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(OTF_PARAMS_DEF) +{ +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensor_config")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type + + if (type == 0) { + sensor_delete(nr); + handle_return(HTML_SUCCESS); + } + + 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(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(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(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(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(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(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(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) + log = strtoul(tmp_buffer, NULL, 0); // 1=logging enabled/0=logging disabled + + 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 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); +} + +/** + * sa + * Modus RS485 Sensor set address help function + * {"nr":1,"id":1} + */ +void server_set_sensor_address(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_set_sensor_address")); + + 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(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 ret = set_sensor_address(sensor_by_nr(nr), id); + ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); +} + +/** + * sg + * @brief return one or all last sensor values + * + */ +void server_sensor_get(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensor_get")); + + uint nr = 0; + 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_header(OTF_PARAMS); +#else + 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 || (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, + sensor->last_data, + 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); + } + } + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + +/** + * sr + * @brief read now and return status and last data + * + */ +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(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_header(OTF_PARAMS); +#else + 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 || (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, + sensor->last_native_data, + sensor->last_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); + } + } + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + +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, + 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 available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } +} + +/** + * sl + * @brief Lists all sensors + * + */ +void server_sensor_list(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensor_list")); + DEBUG_PRINT(F("server_count: ")); + 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_header(OTF_PARAMS); +#else + print_header(); +#endif + + int count = sensor_count(); + bfill.emit_p(PSTR("{\"count\":$D,"), count); + bfill.emit_p(PSTR("\"sensors\":[")); + sensorconfig_json(OTF_PARAMS); + bfill.emit_p(PSTR("]")); + bfill.emit_p(PSTR("}")); + + handle_return(HTML_OK); +} + +/** + * so + * @brief output sensorlog + * + */ +void server_sensorlog_list(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + ulong log_size = sensorlog_size(); + + DEBUG_PRINTLN(F("server_sensorlog_list")); + + //start / max: + ulong startAt = 0; + ulong maxResults = log_size; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("start"), true)) // Log start + startAt = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) // Log Lines count + maxResults = strtoul(tmp_buffer, NULL, 0); + + //Filters: + uint nr = 0; + uint type = 0; + ulong after = 0; + ulong before = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr + nr = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) // Filter log for sensor-type + type = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // Filter time after + after = strtoul(tmp_buffer, NULL, 0); + + 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_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{\"logsize\":$D,\"filesize\":$D,\"log\":["), + log_size, sensorlog_filesize()); + + 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,\"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 available ether buffer is getting small + // send out a packet + if(available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + if (++count >= maxResults) + break; + } + bfill.emit_p(PSTR("]}")); + + handle_return(HTML_OK); +} + +/** + * sn + * @brief Delete/Clear Sensor log + * + */ +void server_sensorlog_clear(OTF_PARAMS_DEF) { +#if defined(ESP8266) + 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_header(OTF_PARAMS); +#else + print_header(); +#endif + + ulong log_size = sensorlog_size(); + + sensorlog_clear_all(); + + bfill.emit_p(PSTR("{\"deleted\":$L}"), log_size); + + handle_return(HTML_OK); +} + +/** + * sb + * define a program adjustment +*/ +void server_sensorprog_config(OTF_PARAMS_DEF) { +#if defined(ESP8266) + 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(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + if (nr == 0) + handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Adjustment type + + if (type == 0) { + prog_adjust_delete(nr); + handle_return(HTML_SUCCESS); + } + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + handle_return(HTML_DATA_MISSING); + uint sensor = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + handle_return(HTML_DATA_MISSING); + uint prog = strtoul(tmp_buffer, NULL, 0); // Program nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) + handle_return(HTML_DATA_MISSING); + double factor1 = atof(tmp_buffer); // Factor 1 + + 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(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) + handle_return(HTML_DATA_MISSING); + double min = atof(tmp_buffer); // Min value + + 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 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) { + 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, + p->prog, + p->factor1, + p->factor2, + p->min, + p->max, + 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); + } +} + +/** + * se + * define a program adjustment +*/ +void server_sensorprog_list(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorprog_list")); + + uint nr = 0; + int prog = -1; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + prog = strtoul(tmp_buffer, NULL, 0); + +#if 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 + + 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 != (uint)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 != (uint)prog) + continue; + + double current = calc_sensor_watering_by_nr(p->nr); + + 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); + } + } + 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_ANALOG_EXTENSION_BOARD_P, + 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 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", + "Sensor group with max value", + "Sensor group with avg value", + "Sensor group with sum value", +}; + +/** + * sf + * List supported sensor types + **/ +void server_sensor_types(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + 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_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{\"count\":$D,\"sensorTypes\":["), sizeof(sensor_types)/sizeof(int)); + + for (uint i = 0; i < sizeof(sensor_types)/sizeof(int); i++) + { + if (i > 0) + bfill.emit_p(PSTR(",")); + byte unitid = getSensorUnitId(sensor_types[i]); + bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), + 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); + } + } + bfill.emit_p(PSTR("]}")); + + handle_return(HTML_OK); +} + +/** + * du + * List supported sensor types + **/ +void server_usage(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + 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_header(OTF_PARAMS); +#else + print_header(); +#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 + **/ +void server_sensorprog_calc(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorprog_calc")); + //uint nr or uint prog + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { + uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + double adj = calc_sensor_watering_by_nr(nr); + bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); + handle_return(HTML_OK); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) { + uint prog = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + double adj = calc_sensor_watering(prog); + bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); + handle_return(HTML_OK); + } + + handle_return(HTML_DATA_MISSING); +} + +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(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + 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_header(OTF_PARAMS); +#else + print_header(); +#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(OTF_PARAMS); + } + } + bfill.emit_p(PSTR("]}")); + + handle_return(HTML_OK); +} + +/** + * sx + * @brief backup sensor configuration + * + */ +void server_sensorconfig_backup(OTF_PARAMS_DEF) { +#if defined(ESP8266) + 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 + + bfill.emit_p(PSTR("\"sensors\":[")); + sensorconfig_json(OTF_PARAMS); + bfill.emit_p(PSTR("],")); + send_packet(OTF_PARAMS); + bfill.emit_p(PSTR("\"progadjust\":[")); + progconfig_json(); + bfill.emit_p(PSTR("]")); + send_packet(OTF_PARAMS); + bfill.emit_p(PSTR("}")); + handle_return(HTML_OK); +} + +/** + * sy + * @brief restore sensor configuration + * + */ +void server_sensorconfig_restore(OTF_PARAMS_DEF) { +#if defined(ESP8266) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + + +} + + typedef void (*URLHandler)(OTF_PARAMS_DEF); /* Server function urls @@ -1989,6 +2806,21 @@ const char _url_keys[] PROGMEM = "cu" "ja" "pq" + "sc" + "sl" + "sg" + "sr" + "sa" + "so" + "sn" + "sb" + "sd" + "se" + "sf" + "du" + "sh" + "sx" + "sy" #if defined(ARDUINO) "db" //"ff" @@ -2019,6 +2851,21 @@ URLHandler urls[] = { server_change_scripturl,// cu server_json_all, // ja server_pause_queue, // pq + server_sensor_config, // sc + server_sensor_list, // sl + server_sensor_get, // sg + server_sensor_readnow, // sr + server_set_sensor_address, // sa + server_sensorlog_list, // so + server_sensorlog_clear, // sn + server_sensorprog_config, // sb + server_sensorprog_calc, // sd + server_sensorprog_list, // se + server_sensor_types, // sf + server_usage, // du + server_sensorprog_types, // sh + server_sensorconfig_backup, // sx + server_sensorconfig_restore, // sy #if defined(ARDUINO) server_json_debug, // db //server_fill_files, diff --git a/opensprinkler_server.h b/opensprinkler_server.h index b40d2ab5b..bdf383ae5 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, "%10.6lf", va_arg(ap, double)); + break; case 'L': //ltoa(va_arg(ap, long), (char*) ptr, 10); ultoa(va_arg(ap, long), (char*) ptr, 10); // ray diff --git a/platformio.ini b/platformio.ini index 48dca934f..1f9a8db9e 100644 --- a/platformio.ini +++ b/platformio.ini @@ -26,6 +26,8 @@ lib_deps = 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 + https://github.com/bluemurder/esp8266-ping ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - upload_speed = 460800 diff --git a/sensors.cpp b/sensors.cpp new file mode 100644 index 000000000..0592a6438 --- /dev/null +++ b/sensors.cpp @@ -0,0 +1,1020 @@ +/* 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 "program.h" +#include "OpenSprinkler.h" +#include "OpenSprinkler_server.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; + + for (int pos = 0; pos < len; pos++) { + crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } + else // Else LSB is not set + crc >>= 1; // Just shift right + } + } + // Note, this number has low and high bytes swapped, so use it accordingly (or swap bytes) + return crc; +} // End: CRC16 + +/** + * @brief delete a sensor + * + * @param nr + */ +int sensor_delete(uint nr) { + + Sensor_t *sensor = sensors; + Sensor_t *last = NULL; + while (sensor) { + if (sensor->nr == nr) { + if (last == NULL) + sensors = sensor->next; + else + last->next = sensor->next; + delete sensor; + sensor_save(); + return HTTP_RQT_SUCCESS; + } + last = sensor; + sensor = sensor->next; + } + return HTTP_RQT_NOT_RECEIVED; +} + +/** + * @brief define or insert a sensor + * + * @param nr > 0 + * @param type > 0 add/modify, 0=delete + * @param group group assignment + * @param ip + * @param port + * @param id + */ +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; + if (ri < 10) + ri = 10; + + Sensor_t *sensor = sensors; + Sensor_t *last = NULL; + 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; + sensor->id = id; + sensor->read_interval = ri; + sensor->flags = flags; + sensor_save(); + return HTTP_RQT_SUCCESS; + } + + 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->nr = nr; + new_sensor->type = type; + new_sensor->group = group; + strlcpy(new_sensor->name, name, sizeof(new_sensor->name)-1); + new_sensor->ip = ip; + new_sensor->port = port; + new_sensor->id = id; + new_sensor->read_interval = ri; + new_sensor->flags = flags; + 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; +} + +/** + * @brief initial load stored sensor definitions + * + */ +void sensor_load() { + DEBUG_PRINTLN(F("sensor_load")); + sensors = NULL; + if (!file_exists(SENSOR_FILENAME)) + return; + + ulong pos = 0; + Sensor_t *last = NULL; + while (true) { + Sensor_t *sensor = new Sensor_t; + memset(sensor, 0, sizeof(Sensor_t)); + file_read_block (SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); + if (!sensor->nr || !sensor->type) { + delete sensor; + break; + } + if (!last) sensors = sensor; + else last->next = sensor; + last = sensor; + sensor->next = NULL; + pos += SENSOR_STORE_SIZE; + } +} + +/** + * @brief Store sensor data + * + */ +void sensor_save() { + DEBUG_PRINTLN(F("sensor_save")); + if (!sensors && 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() { + DEBUG_PRINTLN(F("sensor_count")); + Sensor_t *sensor = sensors; + uint count = 0; + while (sensor) { + count++; + sensor = sensor->next; + } + return count; +} + +Sensor_t *sensor_by_nr(uint nr) { + DEBUG_PRINTLN(F("sensor_by_nr")); + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->nr == nr) + return sensor; + sensor = sensor->next; + } + return NULL; +} + +Sensor_t *sensor_by_idx(uint idx) { + DEBUG_PRINTLN(F("sensor_by_idx")); + Sensor_t *sensor = sensors; + uint count = 0; + while (sensor) { + if (count == idx) + return sensor; + count++; + sensor = sensor->next; + } + return NULL; +} + +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; +} + +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; + if (!sensorlog_add(&sensorlog)) { + sensor->flags.log = 0; + return false; + } + return true; + } + return false; +} + +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 ")); + 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); +} + +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) { + 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(); + 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(); +} + +/** + * Read ADS1015 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: + + int port = sensor->id < 4? 72 : 73; + int id = sensor->id % 4; + + ADS1015 adc(port); + bool active = adc.begin(); + if (active) + adc.setGain(1); + DEBUG_PRINT(F("adc sensor found=")); + DEBUG_PRINTLN(active); + + if (!active) + return HTTP_RQT_NOT_RECEIVED; + + //Read values: + sensor->last_native_data = adc.readADC(id); + sensor->last_data = adc.toVoltage(sensor->last_native_data); + + 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; + + DEBUG_PRINT(F("adc sensor values: ")); + DEBUG_PRINT(sensor->last_native_data); + 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->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + //currently not implemented + + 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 /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]); + + 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->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; +} + +/** + * Read ip connected sensors +*/ +int read_sensor_ip(Sensor_t *sensor) { +#if defined(ARDUINO) + + Client *client; + #if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; + #else + EthernetClient etherClient; + client = ðerClient; + #endif + +#else + EthernetClient etherClient; + EthernetClient *client = ðerClient; +#endif + + sensor->flags.data_ok = false; + if (!sensor->ip || !sensor->port) { + sensor->flags.enable = false; + return HTTP_RQT_CONNECT_ERR; + } + + 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[10]; + int len = 0; + boolean addCrc16 = false; + switch(sensor->type) + { + case SENSOR_SMT100_MODBUS_RTU_MOIS: + buffer[0] = sensor->id; //Modbus ID + buffer[1] = 0x03; //Read Holding Registers + buffer[2] = 0x00; + buffer[3] = 0x01; //soil moisture is at address 0x0001 + buffer[4] = 0x00; + buffer[5] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) + len = 6; + addCrc16 = true; + break; + + case SENSOR_SMT100_MODBUS_RTU_TEMP: + buffer[0] = sensor->id; + buffer[1] = 0x03; //Read Holding Registers + buffer[2] = 0x00; + buffer[3] = 0x00; //temperature is at address 0x0000 + buffer[4] = 0x00; + buffer[5] = 0x01; //Number of registers to read (soil moisture value is one 16-bit register) + len = 6; + addCrc16 = true; + break; + + default: + client->stop(); + return HTTP_RQT_NOT_RECEIVED; + } + if (addCrc16) { + uint16_t crc = CRC16(buffer, len); + buffer[len+0] = (0x00FF&crc); + buffer[len+1] = (0xFF00&crc)>>8; + len += 2; + } + + client->write(buffer, len); + client->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); + } + + //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[6] << 8) | buffer[5]; + 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 = ((double)sensor->last_native_data / 100); + 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->flags.data_ok = true; + DEBUG_PRINT(F(" temperature °C: ")); + break; + } + DEBUG_PRINTLN(sensor->last_data); + return HTTP_RQT_SUCCESS; + } + + return HTTP_RQT_NOT_RECEIVED; +} + +/** + * read a sensor +*/ +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); + + 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: + 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); + + //case SENSOR_OSPI_ANALOG_INPUTS: + // return read_sensor_ospi(sensor); + case SENSOR_REMOTE: + return read_sensor_http(sensor); + + default: return HTTP_RQT_NOT_RECEIVED; + } +} + +/** + * @brief Update group values + * + */ +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; + } + } + 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 = os.now_tz(); + sensor->flags.data_ok = n>0; + break; + } + } + + sensor = sensor->next; + } +} + +int set_sensor_address(Sensor_t *sensor, byte new_address) { + if (!sensor) + return HTTP_RQT_NOT_RECEIVED; + + switch(sensor->type) + { + case SENSOR_SMT100_MODBUS_RTU_MOIS: + case SENSOR_SMT100_MODBUS_RTU_TEMP: + 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 + 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("."); + 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; +} + +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->flags.enable && sensor->flags.data_ok) { + + double res = 0; + switch(p->type) { + case PROG_NONE: res = 1; break; + case PROG_LINEAR: res = calc_linear(p, sensor); break; + case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor); break; + case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor); break; + default: res = 0; + } + + result = result * res; + } + } + + p = p->next; + } + + 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->flags.enable && sensor->flags.data_ok) { + + double res = 0; + switch(p->type) { + case PROG_NONE: res = 1; break; + case PROG_LINEAR: res = calc_linear(p, sensor); break; + case PROG_DIGITAL_MIN: res = calc_digital_min(p, sensor); break; + case PROG_DIGITAL_MAX: res = calc_digital_max(p, sensor); break; + default: res = 0; + } + + result = result * res; + } + break; + } + + p = p->next; + } + + return result; +} + +int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max) { + ProgSensorAdjust_t *p = progSensorAdjusts; + + ProgSensorAdjust_t *last = NULL; + + while (p) { + if (p->nr == nr) { + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->factor1 = factor1; + p->factor2 = factor2; + p->min = min; + p->max = max; + prog_adjust_save(); + return HTTP_RQT_SUCCESS; + } + + if (p->nr > nr) + break; + + last = p; + p = p->next; + } + + p = new ProgSensorAdjust_t; + p->nr = nr; + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->factor1 = factor1; + p->factor2 = factor2; + p->min = min; + p->max = max; + if (last) { + p->next = last->next; + last->next = p; + } else { + p->next = progSensorAdjusts; + progSensorAdjusts = p; + } + + prog_adjust_save(); + return HTTP_RQT_SUCCESS; +} + +int prog_adjust_delete(uint nr) { + ProgSensorAdjust_t *p = progSensorAdjusts; + + ProgSensorAdjust_t *last = NULL; + + while (p) { + if (p->nr == nr) { + if (last) + last->next = p->next; + else + progSensorAdjusts = p->next; + delete p; + prog_adjust_save(); + return HTTP_RQT_SUCCESS; + } + last = p; + p = p->next; + } + return HTTP_RQT_NOT_RECEIVED; +} + +void prog_adjust_save() { + if (!progSensorAdjusts && file_exists(PROG_SENSOR_FILENAME)) + remove_file(PROG_SENSOR_FILENAME); + + ulong pos = 0; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + file_write_block(PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); + pa = pa->next; + pos += PROGSENSOR_STORE_SIZE; + } +} + +void prog_adjust_load() { + DEBUG_PRINTLN(F("prog_adjust_load")); + progSensorAdjusts = NULL; + if (!file_exists(PROG_SENSOR_FILENAME)) + return; + + ulong pos = 0; + ProgSensorAdjust_t *last = NULL; + while (true) { + ProgSensorAdjust_t *pa = new ProgSensorAdjust_t; + memset(pa, 0, sizeof(ProgSensorAdjust_t)); + file_read_block (PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); + if (!pa->nr || !pa->type) { + delete pa; + break; + } + if (!last) progSensorAdjusts = pa; + else last->next = pa; + last = pa; + pa->next = NULL; + pos += PROGSENSOR_STORE_SIZE; + } +} + +uint prog_adjust_count() { + uint count = 0; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + count++; + pa = pa->next; + } + return count; +} + +ProgSensorAdjust_t *prog_adjust_by_nr(uint nr) { + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + if (pa->nr == nr) return pa; + pa = pa->next; + } + return NULL; +} + +ProgSensorAdjust_t *prog_adjust_by_idx(uint idx) { + ProgSensorAdjust_t *pa = progSensorAdjusts; + uint idxCounter = 0; + while (pa) { + if (idxCounter++ == idx) return pa; + pa = pa->next; + } + return NULL; +} + +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_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; + } +} + +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_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: + 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 new file mode 100644 index 000000000..f6d4d7887 --- /dev/null +++ b/sensors.h @@ -0,0 +1,189 @@ +/* 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 + #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_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 + +#define SENSOR_GROUP_MIN 1000 //Sensor group with min value +#define SENSOR_GROUP_MAX 1001 //Sensor group with max value +#define SENSOR_GROUP_AVG 1002 //Sensor group with avg value +#define SENSOR_GROUP_SUM 1003 //Sensor group with sum value + +#define SENSOR_READ_TIMEOUT 3000 //ms + +#define MIN_DISK_FREE 8192 //8Kb min + +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 + char name[30]; // name + uint type; // 1..n type definition, 0=deleted + uint group; // group assignment,0=no group + uint32_t ip; // tcp-ip + uint port; // tcp-port / ADC: I2C Address 0x48/0x49 or 0/1 + uint id; // modbus id / ADC: channel + uint read_interval; // seconds + uint32_t last_native_data; // last native sensor data + double last_data; // last converted sensor data + SensorFlags_t flags; // Flags see obove + 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)-sizeof(byte)) + +//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)) + +//Sensor to program data +//Adjustment is formula +// min max factor1 factor2 +// 10..90 -> 5..1 factor1 > factor2 +// a b c d +// (b-sensorData) / (b-a) * (c-d) + d +// +// 10..90 -> 1..5 factor1 < factor2 +// a b c d +// (sensorData-a) / (b-a) * (d-c) + c + +#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; //adjust-nr 1..x + uint type; //PROG_XYZ type=0 -->delete + uint sensor; //sensor-nr + uint prog; //program-nr=pid + double factor1; + double factor2; + double min; + double max; + byte undef[32]; //for later + ProgSensorAdjust *next; +} ProgSensorAdjust_t; +#define PROGSENSOR_STORE_SIZE (sizeof(ProgSensorAdjust_t)-sizeof(ProgSensorAdjust_t*)) + +#define 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[]; + +//Utils: +uint16_t CRC16 (byte buf[], int len); + +//Sensor API functions: +int sensor_delete(uint nr); +int sensor_define(uint nr, char *name, uint type, uint group, uint32_t ip, uint port, uint id, uint ri, SensorFlags_t flags); +void sensor_load(); +void sensor_save(); +uint sensor_count(); +boolean sensor_isgroup(Sensor_t *sensor); +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: +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); +ulong sensorlog_filesize(); +ulong sensorlog_size(); + +//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(); +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); + +ulong diskFree(); +bool checkDiskFree(); //true: disk space Ok, false: Out of disk space + +#endif // _SENSORS_H diff --git a/utils.cpp b/utils.cpp index 9d4a938e6..f670f54a2 100644 --- a/utils.cpp +++ b/utils.cpp @@ -193,7 +193,13 @@ 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) @@ -210,6 +216,40 @@ bool file_exists(const char *fn) { #endif } +// 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; +} + // file functions void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { #if defined(ESP8266) @@ -281,6 +321,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 bd9681220..939d89b5b 100644 --- a/utils.h +++ b/utils.h @@ -40,8 +40,11 @@ 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_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);