diff --git a/.gitmodules b/.gitmodules index 0c7b52e6..a2bf9ffb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "external/TinyWebsockets"] path = external/TinyWebsockets url = https://github.com/gilmaimon/TinyWebsockets.git +[submodule "external/influxdb-cpp"] + path = external/influxdb-cpp + url = https://github.com/orca-zhang/influxdb-cpp.git diff --git a/I2CRTC.cpp b/I2CRTC.cpp index 6832a558..d1d76dc3 100644 --- a/I2CRTC.cpp +++ b/I2CRTC.cpp @@ -24,9 +24,6 @@ 23 Dec 2013 -- modified by Ray Wang (Rayshobby LLC) to add support for MCP7940 */ - -#if defined(ARDUINO) - #include "I2CRTC.h" #include @@ -159,5 +156,3 @@ uint8_t I2CRTC::bcd2dec(uint8_t num) } I2CRTC RTC = I2CRTC(); // create an instance for the user - -#endif diff --git a/LiquidCrystal.cpp b/LiquidCrystal.cpp deleted file mode 100644 index 88050bd3..00000000 --- a/LiquidCrystal.cpp +++ /dev/null @@ -1,383 +0,0 @@ -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - -#include "LiquidCrystal.h" -#include -#include -#include - -// When the display powers up, it is configured as follows: -// -// 1. Display clear -// 2. Function set: -// DL = 1; 8-bit interface data -// N = 0; 1-line display -// F = 0; 5x8 dot character font -// 3. Display on/off control: -// D = 0; Display off -// C = 0; Cursor off -// B = 0; Blinking off -// 4. Entry mode set: -// I/D = 1; Increment by 1 -// S = 0; No shift -// -// Note, however, that resetting the Arduino doesn't reset the LCD, so we -// can't assume that its in that state when a sketch starts (and the -// LiquidCrystal constructor is called) - -void LiquidCrystal::begin() { - if (_type == LCD_I2C) { - _displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; - - // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! - // according to datasheet, we need at least 40ms after power rises above 2.7V - // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 - delay(50); - - // Now we pull both RS and R/W low to begin commands - expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1) - delay(1000); - - //put the LCD into 4 bit mode - // this is according to the hitachi HD44780 datasheet - // figure 24, pg 46 - - // we start in 8bit mode, try to set 4 bit mode - write4bits(0x03 << 4); - delayMicroseconds(4500); // wait min 4.1ms - - // second try - write4bits(0x03 << 4); - delayMicroseconds(4500); // wait min 4.1ms - - // third go! - write4bits(0x03 << 4); - delayMicroseconds(150); - - // finally, set to 4-bit interface - write4bits(0x02 << 4); - - // set # lines, font size, etc. - command(LCD_FUNCTIONSET | _displayfunction); - - // turn the display on with no cursor or blinking default - _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; - display(); - - // clear it off - clear(); - - // Initialize to default text direction (for roman languages) - _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; - - // set the entry mode - command(LCD_ENTRYMODESET | _displaymode); - - home(); - } - - if (_type == LCD_STD) { - _displayfunction |= LCD_2LINE; - _numlines = 2; - _currline = 0; - - // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! - // according to datasheet, we need at least 40ms after power rises above 2.7V - // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 - delayMicroseconds(50000); - // Now we pull both RS and R/W low to begin commands - digitalWrite(_rs_pin, LOW); - digitalWrite(_enable_pin, LOW); - if (_rw_pin != 255) { - digitalWrite(_rw_pin, LOW); - } - - //put the LCD into 4 bit or 8 bit mode - if (! (_displayfunction & LCD_8BITMODE)) { - // this is according to the hitachi HD44780 datasheet - // figure 24, pg 46 - - // we start in 8bit mode, try to set 4 bit mode - write4bits(0x03); - delayMicroseconds(4500); // wait min 4.1ms - - // second try - write4bits(0x03); - delayMicroseconds(4500); // wait min 4.1ms - - // third go! - write4bits(0x03); - delayMicroseconds(150); - - // finally, set to 4-bit interface - write4bits(0x02); - } else { - // this is according to the hitachi HD44780 datasheet - // page 45 figure 23 - - // Send function set command sequence - command(LCD_FUNCTIONSET | _displayfunction); - delayMicroseconds(4500); // wait more than 4.1ms - - // second try - command(LCD_FUNCTIONSET | _displayfunction); - delayMicroseconds(150); - - // third go - command(LCD_FUNCTIONSET | _displayfunction); - } - - // finally, set # lines, font size, etc. - command(LCD_FUNCTIONSET | _displayfunction); - - // turn the display on with no cursor or blinking default - _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; - display(); - - // clear it off - clear(); - - // Initialize to default text direction (for romance languages) - _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; - // set the entry mode - command(LCD_ENTRYMODESET | _displaymode); - } -} - -void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable, - uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, - uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) -{ - _rs_pin = rs; - _rw_pin = rw; - _enable_pin = enable; - - _data_pins[0] = d0; - _data_pins[1] = d1; - _data_pins[2] = d2; - _data_pins[3] = d3; - _data_pins[4] = d4; - _data_pins[5] = d5; - _data_pins[6] = d6; - _data_pins[7] = d7; - - Wire.begin(); - _type = LCD_STD; - - // detect I2C and assign _type variable accordingly - Wire.beginTransmission(LCD_I2C_ADDR1); // check type 1 - //Wire.write(0x00); - uint8_t ret1 = Wire.endTransmission(); - Wire.beginTransmission(LCD_I2C_ADDR2); // check type 2 - //Wire.write(0x00); - uint8_t ret2 = Wire.endTransmission(); - - if (!ret1 || !ret2) _type = LCD_I2C; - if (_type == LCD_I2C) { - if(!ret1) _addr = LCD_I2C_ADDR1; - else _addr = LCD_I2C_ADDR2; - _cols = 16; - _rows = 2; - _charsize = LCD_5x8DOTS; - _backlightval = LCD_BACKLIGHT; - } - - if (_type == LCD_STD) { - pinMode(_rs_pin, OUTPUT); - // we can save 1 pin by not using RW. Indicate by passing 255 instead of pin# - if (_rw_pin != 255) { - pinMode(_rw_pin, OUTPUT); - } - pinMode(_enable_pin, OUTPUT); - - } - _displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; - -} - -/********** high level commands, for the user! */ -void LiquidCrystal::clear() -{ - command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero - delayMicroseconds(2000); // this command takes a long time! -} - -void LiquidCrystal::home() -{ - command(LCD_RETURNHOME); // set cursor position to zero - delayMicroseconds(2000); // this command takes a long time! -} - -void LiquidCrystal::setCursor(uint8_t col, uint8_t row) -{ - int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 }; - if (_type == LCD_I2C) { - if (row > _rows) { - row = _rows-1; // we count rows starting w/0 - } - } - if (_type == LCD_STD) { - if (row >= _numlines) { - row = _numlines-1; - } - } - command(LCD_SETDDRAMADDR | (col + row_offsets[row])); -} - -// Turn the display on/off (quickly) -void LiquidCrystal::noDisplay() { - _displaycontrol &= ~LCD_DISPLAYON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} -void LiquidCrystal::display() { - _displaycontrol |= LCD_DISPLAYON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} - -// Turns the underline cursor on/off -void LiquidCrystal::noCursor() { - _displaycontrol &= ~LCD_CURSORON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} -void LiquidCrystal::cursor() { - _displaycontrol |= LCD_CURSORON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} - -// Turn on and off the blinking cursor -void LiquidCrystal::noBlink() { - _displaycontrol &= ~LCD_BLINKON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} -void LiquidCrystal::blink() { - _displaycontrol |= LCD_BLINKON; - command(LCD_DISPLAYCONTROL | _displaycontrol); -} - -// These commands scroll the display without changing the RAM -void LiquidCrystal::scrollDisplayLeft(void) { - command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT); -} -void LiquidCrystal::scrollDisplayRight(void) { - command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT); -} - -// This is for text that flows Left to Right -void LiquidCrystal::leftToRight(void) { - _displaymode |= LCD_ENTRYLEFT; - command(LCD_ENTRYMODESET | _displaymode); -} - -// This is for text that flows Right to Left -void LiquidCrystal::rightToLeft(void) { - _displaymode &= ~LCD_ENTRYLEFT; - command(LCD_ENTRYMODESET | _displaymode); -} - -// This will 'right justify' text from the cursor -void LiquidCrystal::autoscroll(void) { - _displaymode |= LCD_ENTRYSHIFTINCREMENT; - command(LCD_ENTRYMODESET | _displaymode); -} - -// This will 'left justify' text from the cursor -void LiquidCrystal::noAutoscroll(void) { - _displaymode &= ~LCD_ENTRYSHIFTINCREMENT; - command(LCD_ENTRYMODESET | _displaymode); -} - -// Allows us to fill the first 8 CGRAM locations -// with custom characters -//void LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) { -void LiquidCrystal::createChar(uint8_t location, PGM_P ptr) { - location &= 0x7; // we only have 8 locations 0-7 - command(LCD_SETCGRAMADDR | (location << 3)); - for (int i=0; i<8; i++) { - //write(charmap[i]); - write(pgm_read_byte(ptr++)); - } -} - -// Turn the (optional) backlight off/on -void LiquidCrystal::noBacklight(void) { - _backlightval=LCD_NOBACKLIGHT; - expanderWrite(0); -} - -void LiquidCrystal::backlight(void) { - _backlightval=LCD_BACKLIGHT; - expanderWrite(0); -} - -/*********** mid level commands, for sending data/cmds */ - -inline void LiquidCrystal::command(uint8_t value) { - send(value, 0); -} - -inline size_t LiquidCrystal::write(uint8_t value) { - send(value, Rs); - return 1; // assume sucess -} - -/************ low level data pushing commands **********/ - -// write either command or data -void LiquidCrystal::send(uint8_t value, uint8_t mode) { - if (_type == LCD_I2C) { - uint8_t highnib=value&0xf0; - uint8_t lownib=(value<<4)&0xf0; - write4bits((highnib)|mode); - write4bits((lownib)|mode); - } - if (_type == LCD_STD) { - digitalWrite(_rs_pin, mode); - - // if there is a RW pin indicated, set it low to Write - if (_rw_pin != 255) { - digitalWrite(_rw_pin, LOW); - } - - write4bits(value>>4); - write4bits(value); - } -} - -void LiquidCrystal::write4bits(uint8_t value) { - if (_type == LCD_I2C) { - expanderWrite(value); - pulseEnable(value); - } - if (_type == LCD_STD) { - for (int i = 0; i < 4; i++) { - pinMode(_data_pins[i], OUTPUT); - digitalWrite(_data_pins[i], (value >> i) & 0x01); - } - - pulseEnable(); - } -} - -void LiquidCrystal::expanderWrite(uint8_t _data){ - Wire.beginTransmission(_addr); - Wire.write((int)(_data) | _backlightval); - Wire.endTransmission(); -} - -void LiquidCrystal::pulseEnable(uint8_t _data){ - expanderWrite(_data | En); // En high - delayMicroseconds(1); // enable pulse must be >450ns - - expanderWrite(_data & ~En); // En low - delayMicroseconds(50); // commands need > 37us to settle -} - -void LiquidCrystal::pulseEnable(void) { - digitalWrite(_enable_pin, LOW); - delayMicroseconds(1); - digitalWrite(_enable_pin, HIGH); - delayMicroseconds(1); // enable pulse must be >450ns - digitalWrite(_enable_pin, LOW); - delayMicroseconds(100); // commands need > 37us to settle -} - -#endif diff --git a/LiquidCrystal.h b/LiquidCrystal.h deleted file mode 100644 index ab964da7..00000000 --- a/LiquidCrystal.h +++ /dev/null @@ -1,127 +0,0 @@ -#ifndef LIQUID_CRYSTAL_DUAL_H -#define LIQUID_CRYSTAL_DUAL_H - -#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) - -#include -#include - -// commands -#define LCD_CLEARDISPLAY 0x01 -#define LCD_RETURNHOME 0x02 -#define LCD_ENTRYMODESET 0x04 -#define LCD_DISPLAYCONTROL 0x08 -#define LCD_CURSORSHIFT 0x10 -#define LCD_FUNCTIONSET 0x20 -#define LCD_SETCGRAMADDR 0x40 -#define LCD_SETDDRAMADDR 0x80 - -// flags for display entry mode -#define LCD_ENTRYRIGHT 0x00 -#define LCD_ENTRYLEFT 0x02 -#define LCD_ENTRYSHIFTINCREMENT 0x01 -#define LCD_ENTRYSHIFTDECREMENT 0x00 - -// flags for display on/off control -#define LCD_DISPLAYON 0x04 -#define LCD_DISPLAYOFF 0x00 -#define LCD_CURSORON 0x02 -#define LCD_CURSOROFF 0x00 -#define LCD_BLINKON 0x01 -#define LCD_BLINKOFF 0x00 - -// flags for display/cursor shift -#define LCD_DISPLAYMOVE 0x08 -#define LCD_CURSORMOVE 0x00 -#define LCD_MOVERIGHT 0x04 -#define LCD_MOVELEFT 0x00 - -// flags for function set -#define LCD_8BITMODE 0x10 -#define LCD_4BITMODE 0x00 -#define LCD_2LINE 0x08 -#define LCD_1LINE 0x00 -#define LCD_5x10DOTS 0x04 -#define LCD_5x8DOTS 0x00 - -// flags for backlight control -#define LCD_BACKLIGHT 0x08 -#define LCD_NOBACKLIGHT 0x00 - -#define En B00000100 // Enable bit -#define Rw B00000010 // Read/Write bit -#define Rs B00000001 // Register select bit - -#define LCD_STD 0 // Standard LCD -#define LCD_I2C 1 // I2C LCD -#define LCD_I2C_ADDR1 0x27 // type using PCF8574, at address 0x27 -#define LCD_I2C_ADDR2 0x3F // type using PCF8574A, at address 0x3F - -class LiquidCrystal : public Print { -public: - LiquidCrystal() {} - void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable, - uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, - uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); - - void begin(); - - void clear(); - void clear(int start, int end) { clear(); } - void home(); - - void noDisplay(); - void display(); - void noBlink(); - void blink(); - void noCursor(); - void cursor(); - void scrollDisplayLeft(); - void scrollDisplayRight(); - void leftToRight(); - void rightToLeft(); - void autoscroll(); - void noAutoscroll(); - - //void createChar(uint8_t, uint8_t[]); - void createChar(uint8_t, PGM_P ptr); - void setCursor(uint8_t, uint8_t); - virtual size_t write(uint8_t); - void command(uint8_t); - - inline uint8_t type() { return _type; } - void noBacklight(); - void backlight(); - - using Print::write; -private: - void send(uint8_t, uint8_t); - void write4bits(uint8_t); - void pulseEnable(); - - void expanderWrite(uint8_t); - void pulseEnable(uint8_t); - uint8_t _addr; - uint8_t _cols; - uint8_t _rows; - uint8_t _charsize; - uint8_t _backlightval; - - uint8_t _type; // LCD type. 0: standard; 1: I2C - uint8_t _rs_pin; // LOW: command. HIGH: character. - uint8_t _rw_pin; // LOW: write to LCD. HIGH: read from LCD. - uint8_t _enable_pin; // activated by a HIGH pulse. - uint8_t _data_pins[8]; - - uint8_t _displayfunction; - uint8_t _displaycontrol; - uint8_t _displaymode; - - uint8_t _initialized; - - uint8_t _numlines,_currline; -}; - -#endif - -#endif // LIQUID_CRYSTAL_DUAL_H diff --git a/Makefile b/Makefile index b1318456..5413e9c0 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ LD=$(CXX) LIBS=pthread mosquitto ssl crypto i2c lgpio LDFLAGS=$(addprefix -l,$(LIBS)) BINARY=OpenSprinkler -SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) +SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp i2cd.cpp sensor.cpp ads1115.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) HEADERS=$(wildcard *.h) $(wildcard *.hpp) OBJECTS=$(addsuffix .o,$(basename $(SOURCES))) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 206ce5a5..e498ffde 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -29,6 +29,10 @@ #include "ArduinoJson.hpp" /** Declare static data members */ +#if defined(USE_SENSORS) +sensor_memory_t OpenSprinkler::sensors[64] = {0}; +uint16_t OpenSprinkler::sensor_file_no = 0; +#endif OSMqtt OpenSprinkler::mqtt; NVConData OpenSprinkler::nvdata; ConStatus OpenSprinkler::status; @@ -79,10 +83,12 @@ extern ProgramData pd; extern const char* user_agent_string; extern unsigned char curr_alert_sid; -#if defined(USE_SSD1306) - SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); -#elif defined(USE_LCD) - LiquidCrystal OpenSprinkler::lcd; +#if defined(USE_DISPLAY) +SSD1306Display OpenSprinkler::lcd(0x3c, SDA, SCL); +#endif + +#if defined(USE_ADS1115) + ADS1115 *OpenSprinkler::ads1115_devices[4] = {nullptr}; #endif #if defined(ESP8266) @@ -98,17 +104,13 @@ extern unsigned char curr_alert_sid; unsigned char OpenSprinkler::wifi_testmode = 0; CH224 OpenSprinkler::usbpd; uint8_t OpenSprinkler::actual_pd_voltage = 0; -#elif defined(ARDUINO) - extern SdFat sd; #else #if defined(OSPI) unsigned char OpenSprinkler::pin_sr_data = PIN_SR_DATA; #endif #endif -#if defined(USE_OTF) - OTCConfig OpenSprinkler::otc; -#endif +OTCConfig OpenSprinkler::otc; /** Option json names (stored in PROGMEM to reduce RAM usage) */ // IMPORTANT: each json name is strictly 5 characters @@ -364,7 +366,7 @@ unsigned char OpenSprinkler::iopts[] = { 0, 0, 0, -#if defined(ARDUINO) // on AVR, the default HTTP port is 80 +#if defined(ESP8266) // on Arduino, the default HTTP port is 80 80, // this and next byte define http port number 0, #else // on RPI/LINUX, the default HTTP port is 8080 @@ -474,7 +476,7 @@ static const char months_str[] PROGMEM = "Nov\0" "Dec\0"; -#if !defined(ARDUINO) +#if !defined(ESP8266) static inline uint32_t now() { time_t rawtime; time(&rawtime); @@ -486,7 +488,7 @@ time_os_t OpenSprinkler::now_tz() { return now()+(int32_t)3600/4*(int32_t)(iopts[IOPT_TIMEZONE]-48); } -#if defined(ARDUINO) +#if defined(ESP8266) bool detect_i2c(int addr) { Wire.beginTransmission(addr); @@ -494,44 +496,19 @@ bool detect_i2c(int addr) { } /** read hardware MAC into tmp_buffer */ -#define MAC_CTRL_ID 0x50 bool OpenSprinkler::load_hardware_mac(unsigned char* buffer, bool wired) { -#if defined(ESP8266) WiFi.macAddress((unsigned char*)buffer); // if requesting wired Ethernet MAC, flip the last byte to create a modified MAC if(wired) buffer[5] = ~buffer[5]; return true; -#else - // initialize the buffer by assigning software mac - buffer[0] = 0x00; - buffer[1] = 0x69; - buffer[2] = 0x69; - buffer[3] = 0x2D; - buffer[4] = 0x31; - buffer[5] = iopts[IOPT_DEVICE_ID]; - if (detect_i2c(MAC_CTRL_ID)==false) return false; - - Wire.beginTransmission(MAC_CTRL_ID); - Wire.write(0xFA); // The address of the register we want - Wire.endTransmission(); // Send the data - if(Wire.requestFrom(MAC_CTRL_ID, 6) != 6) return false; // if not enough data, return false - for(unsigned char ret=0;ret<6;ret++) { - buffer[ret] = Wire.read(); - } - return true; -#endif } -void(* resetFunc) (void) = 0; // AVR software reset function - /** Initialize network with the given mac address and http port */ unsigned char OpenSprinkler::start_network() { lcd_print_line_clear_pgm(PSTR("Starting..."), 1); uint16_t httpport = (uint16_t)(iopts[IOPT_HTTPPORT_1]<<8) + (uint16_t)iopts[IOPT_HTTPPORT_0]; -#if defined(ESP8266) - if (start_ether()) { useEth = true; WiFi.mode(WIFI_OFF); @@ -553,24 +530,9 @@ unsigned char OpenSprinkler::start_network() { DEBUG_PRINT(F("Started update server")); return 1; -#else - - if (start_ether()) { - if(m_server) { delete m_server; m_server = NULL; } - m_server = new EthernetServer(httpport); - m_server->begin(); - useEth = true; - return 1; - } else { - useEth = false; - return 0; - } - -#endif } unsigned char OpenSprinkler::start_ether() { -#if defined(ESP8266) if(hw_rev<2) return 0; // ethernet capability is only available when hw_rev>=2 eth.isW5500 = (hw_rev==2)?false:true; // os 3.2 uses enc28j60 and 3.3 uses w5500 @@ -675,42 +637,13 @@ unsigned char OpenSprinkler::start_ether() { // if wired connection has failed at this point, return depending on whether the user wants to force wired return (iopts[IOPT_FORCE_WIRED] ? 1 : 0); } - -#else - Ethernet.init(PIN_ETHER_CS); // make sure to call this before any Ethernet calls - if(Ethernet.hardwareStatus()==EthernetNoHardware) return 0; - load_hardware_mac((uint8_t*)tmp_buffer, true); - - lcd_print_line_clear_pgm(PSTR("Start wired link"), 1); - - if (iopts[IOPT_USE_DHCP]) { - if(!Ethernet.begin((uint8_t*)tmp_buffer)) return 0; - memcpy(iopts+IOPT_STATIC_IP1, &(Ethernet.localIP()[0]), 4); - memcpy(iopts+IOPT_GATEWAY_IP1, &(Ethernet.gatewayIP()[0]),4); - memcpy(iopts+IOPT_DNS_IP1, &(Ethernet.dnsServerIP()[0]), 4); - memcpy(iopts+IOPT_SUBNET_MASK1, &(Ethernet.subnetMask()[0]), 4); - iopts_save(); - } else { - IPAddress staticip(iopts+IOPT_STATIC_IP1); - IPAddress gateway(iopts+IOPT_GATEWAY_IP1); - IPAddress dns(iopts+IOPT_DNS_IP1); - IPAddress subn(iopts+IOPT_SUBNET_MASK1); - Ethernet.begin((uint8_t*)tmp_buffer, staticip, dns, gateway, subn); - } - - return 1; -#endif } bool OpenSprinkler::network_connected(void) { -#if defined (ESP8266) if(useEth) return eth.connected(); else return (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && state==OS_STATE_CONNECTED); -#else - return (Ethernet.linkStatus()==LinkON); -#endif } /** Reboot controller */ @@ -720,11 +653,7 @@ void OpenSprinkler::reboot_dev(uint8_t cause) { nvdata.reboot_cause = cause; nvdata_save(); } -#if defined(ESP8266) ESP.restart(); -#else - resetFunc(); -#endif } #else // RPI/LINUX network init functions @@ -763,6 +692,12 @@ bool OpenSprinkler::network_connected(void) { return true; } +#if defined(OSPI) +bool detect_i2c(int addr) { + Bus.detect(addr); +} +#endif + // Return mac of first recognised interface and fallback to software mac // Note: on OSPi, operating system handles interface allocation so 'wired' ignored bool OpenSprinkler::load_hardware_mac(unsigned char* mac, bool wired) { @@ -812,32 +747,13 @@ void OpenSprinkler::update_dev() { } #endif // end network init functions -#if defined(USE_DISPLAY) /** Initialize LCD */ +#if defined(USE_DISPLAY) void OpenSprinkler::lcd_start() { - -#if defined(USE_SSD1306) // initialize SSD1306 lcd.init(); lcd.begin(); flash_screen(); -#elif defined(USE_LCD) - // initialize 16x2 character LCD - // turn on lcd - lcd.init(1, PIN_LCD_RS, 255, PIN_LCD_EN, PIN_LCD_D4, PIN_LCD_D5, PIN_LCD_D6, PIN_LCD_D7, 0,0,0,0); - lcd.begin(); - - if (lcd.type() == LCD_STD) { - // this is standard 16x2 LCD - // set PWM frequency for adjustable LCD backlight and contrast - TCCR1B = 0x02; // increase division factor for faster clock - // turn on LCD backlight and contrast - lcd_set_brightness(); - lcd_set_contrast(); - } else { - // for I2C LCD, we don't need to do anything - } -#endif } #endif @@ -845,16 +761,11 @@ void OpenSprinkler::lcd_start() { /** Initialize pins, controller variables, LCD */ void OpenSprinkler::begin() { - -#if defined(ARDUINO) - Wire.begin(); // init I2C -#endif - hw_type = HW_TYPE_UNKNOWN; hw_rev = 0; -#if defined(ESP8266) // ESP8266 specific initializations - +#if defined(ESP8266) + Wire.begin(); // init I2C /* detect hardware revision type */ if(detect_i2c(MAIN_I2CADDR)) { // check if main PCF8574 exists /* assign revision 0 pins */ @@ -988,35 +899,13 @@ void OpenSprinkler::begin() { expanders[i] = NULL; detect_expanders(); -#else - - // shift register setup - pinMode(PIN_SR_OE, OUTPUT); - // pull shift register OE high to disable output - digitalWrite(PIN_SR_OE, HIGH); - pinMode(PIN_SR_LATCH, OUTPUT); - digitalWrite(PIN_SR_LATCH, HIGH); - - pinMode(PIN_SR_CLOCK, OUTPUT); - - #if defined(OSPI) - pin_sr_data = PIN_SR_DATA; - // detect RPi revision - unsigned int rev = detect_rpi_rev(); - if (rev==0x0002 || rev==0x0003) - pin_sr_data = PIN_SR_DATA_ALT; - // if this is revision 1, use PIN_SR_DATA_ALT - pinMode(pin_sr_data, OUTPUT); - #else - pinMode(PIN_SR_DATA, OUTPUT); - #endif - #endif #if defined(OSPI) -pinModeExt(PIN_BUTTON_1, INPUT_PULLUP); -pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); -pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); + Bus.begin(); // init I2C for OSPI + pinModeExt(PIN_BUTTON_1, INPUT_PULLUP); + pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); + pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); #endif // init masters_last_on array @@ -1035,9 +924,7 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); digitalWrite(PIN_SR_OE, LOW); // Rain sensor port set up pinMode(PIN_SENSOR1, INPUT_PULLUP); - #if defined(PIN_SENSOR2) pinMode(PIN_SENSOR2, INPUT_PULLUP); - #endif #endif // Default controller status variables @@ -1061,94 +948,36 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); digitalWriteExt(PIN_RFTX, LOW); } -#if defined(ARDUINO) // AVR SD and LCD functions - - #if defined(ESP8266) // OS3.0 specific detections - +#if defined(ESP8266) status.has_curr_sense = 1; // OS3.0 has current sensing capacility // measure baseline current baseline_current = 80; - - #else // OS 2.3 specific detections - - // detect hardware type - if (detect_i2c(MAC_CTRL_ID)) { - Wire.beginTransmission(MAC_CTRL_ID); - Wire.write(0x00); - Wire.endTransmission(); - Wire.requestFrom(MAC_CTRL_ID, 1); - unsigned char ret = Wire.read(); - if (ret == HW_TYPE_AC || ret == HW_TYPE_DC || ret == HW_TYPE_LATCH) { - hw_type = ret; - } else { - hw_type = HW_TYPE_AC; // if type not supported, make it AC - } - } - - if (hw_type == HW_TYPE_DC) { - pinMode(PIN_BOOST, OUTPUT); - digitalWrite(PIN_BOOST, LOW); - - pinMode(PIN_BOOST_EN, OUTPUT); - digitalWrite(PIN_BOOST_EN, LOW); - } - - // detect if current sensing pin is present - pinMode(PIN_CURR_DIGITAL, INPUT); - digitalWrite(PIN_CURR_DIGITAL, HIGH); // enable internal pullup - status.has_curr_sense = digitalRead(PIN_CURR_DIGITAL) ? 0 : 1; - digitalWrite(PIN_CURR_DIGITAL, LOW); - baseline_current = 0; - - #endif #endif + #if defined(USE_DISPLAY) lcd_start(); - - #if defined(USE_SSD1306) - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); - lcd.createChar(ICON_WIFI_CONNECTED, _iconimage_wifi_connected); - lcd.createChar(ICON_WIFI_DISCONNECTED, _iconimage_wifi_disconnected); - #elif defined(USE_LCD) - lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_connected); - lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_disconnected); - #endif - + lcd.createChar(ICON_ETHER_CONNECTED, _iconimage_ether_connected); + lcd.createChar(ICON_ETHER_DISCONNECTED, _iconimage_ether_disconnected); + lcd.createChar(ICON_WIFI_CONNECTED, _iconimage_wifi_connected); + lcd.createChar(ICON_WIFI_DISCONNECTED, _iconimage_wifi_disconnected); lcd.createChar(ICON_REMOTEXT, _iconimage_remotext); lcd.createChar(ICON_RAINDELAY, _iconimage_raindelay); lcd.createChar(ICON_RAIN, _iconimage_rain); lcd.createChar(ICON_SOIL, _iconimage_soil); #endif -#if defined(ARDUINO) - #if defined(ESP8266) - lcd.setCursor(0,0); - lcd.print(F("Init file system")); - lcd.setCursor(0,1); - if(!LittleFS.begin()) { - // !!! flash init failed, stall as we cannot proceed - lcd.setCursor(0, 0); - lcd_print_pgm(PSTR("Error Code: 0x2D")); - delay(5000); - } - - state = OS_STATE_INITIAL; - - #else - - // set sd cs pin high to release SD - pinMode(PIN_SD_CS, OUTPUT); - digitalWrite(PIN_SD_CS, HIGH); - - if(!sd.begin(PIN_SD_CS, SPI_HALF_SPEED)) { - // !!! sd card not detected, stall as we cannot proceed - lcd.setCursor(0, 0); - lcd_print_pgm(PSTR("Error Code: 0x2D")); - while(1){} - } +#if defined(ESP8266) + lcd.setCursor(0,0); + lcd.print(F("Init file system")); + lcd.setCursor(0,1); + if(!LittleFS.begin()) { + // !!! flash init failed, stall as we cannot proceed + lcd.setCursor(0, 0); + lcd_print_pgm(PSTR("Error Code: 0x2D")); + delay(5000); + } - #endif + state = OS_STATE_INITIAL; // set button pins // enable internal pullup @@ -1162,6 +991,41 @@ pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); #else //DEBUG_PRINTLN(get_runtime_path()); #endif + +#if defined(USE_ADS1115) + for (size_t i = 0; i < 4; i++) { + uint8_t address = 0x48 + i; + if (detect_i2c(address)) { + ads1115_devices[i] = new ADS1115(address); + } + } +#endif + +#if defined(USE_SENSORS) + lcd.clear(); + lcd.setCursor(0,0); + lcd.print(F("Init sensors")); + + os_file_type file; + uint16_t next = 0; + size_t f; + for (f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + file = open_sensor_log(f, FileOpenMode::Read); + if (file) { + file_read(file, &next, sizeof(next)); + file_close(file); + + if (next < SENSOR_LOG_PER_FILE) break; + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + } + } + if (f == SENSOR_LOG_FILE_COUNT) f -= 1; + sensor_file_no = f; + + os.load_sensors(); +#endif } #if defined(ESP8266) @@ -1424,23 +1288,6 @@ void OpenSprinkler::apply_all_station_bits(void (*post_activation_callback)()) { } } - #if defined(ARDUINO) - if((hw_type==HW_TYPE_DC) && engage_booster) { - // for DC controller: boost voltage - digitalWrite(PIN_BOOST_EN, LOW); // disable output path - digitalWrite(PIN_BOOST, HIGH); // enable boost converter - delay((int)iopts[IOPT_BOOST_TIME]<<2); // wait for booster to charge - digitalWrite(PIN_BOOST, LOW); // disable boost converter - - digitalWrite(PIN_BOOST_EN, HIGH); // enable output path - digitalWrite(PIN_SR_LATCH, HIGH); - engage_booster = 0; - } else { - digitalWrite(PIN_SR_LATCH, HIGH); - } - #else - digitalWrite(PIN_SR_LATCH, HIGH); - #endif #endif // If a post activation callback function is defined, call it here @@ -1506,8 +1353,6 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { } } -// ESP8266 is guaranteed to have sensor 2 -#if defined(ESP8266) || defined(PIN_SENSOR2) if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) { if(hw_rev>=2) pinMode(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 unsigned char val = digitalReadExt(PIN_SENSOR2); @@ -1535,8 +1380,6 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { } } } - -#endif } /** Return program switch status */ @@ -1553,7 +1396,7 @@ unsigned char OpenSprinkler::detect_programswitch_status(time_os_t curr_time) { ret |= 0x01; } } -#if defined(ESP8266) || defined(PIN_SENSOR2) + if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_PSWITCH) { static unsigned char sensor2_hist = 0; if(hw_rev>=2) pinMode(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 @@ -1563,7 +1406,7 @@ unsigned char OpenSprinkler::detect_programswitch_status(time_os_t curr_time) { ret |= 0x02; } } -#endif + return ret; } @@ -1587,7 +1430,7 @@ void OpenSprinkler::sensor_resetall() { * ESP8266's analog reference voltage is 1.0 instead of 3.3, therefore * it's further discounted by 1/3.3 */ -#if defined(ARDUINO) +#if defined(ESP8266) uint16_t OpenSprinkler::read_current(bool use_ema) { static uint16_t ema = 0; // exponential moving average static float scale = -1; @@ -1615,27 +1458,15 @@ uint16_t OpenSprinkler::read_current(bool use_ema) { #endif /** Read the number of 8-station expansion boards */ -// AVR has capability to detect number of expansion boards +// Arduino has capability to detect number of expansion boards int OpenSprinkler::detect_exp() { -#if defined(ARDUINO) - #if defined(ESP8266) +#if defined(ESP8266) // detect the highest expansion board index int n; for(n=4;n>=0;n--) { if(detect_i2c(EXP_I2CADDR_BASE+n)) break; } return (n+1)*2; - #else - // OpenSprinkler uses voltage divider to detect expansion boards - // Master controller has a 1.6K pull-up; - // each expansion board (8 stations) has 2x 4.7K pull-down connected in parallel; - // so the exact ADC value for n expansion boards is: - // ADC = 1024 * 9.4 / (10 + 9.4 * n) - // Reverse this fomular we have: - // n = (1024 * 9.4 / ADC - 9.4) / 1.6 - int n = (int)((1024 * 9.4 / analogRead(PIN_EXP_SENSE) - 9.4) / 1.6 + 0.33); - return n; - #endif #else return -1; #endif @@ -1869,7 +1700,7 @@ unsigned char OpenSprinkler::password_verify(const char *pw) { /** Index of today's weekday (Monday is 0) */ unsigned char OpenSprinkler::weekday_today() { //return ((unsigned char)weekday()+5)%7; // Time::weekday() assumes Sunday is 1 -#if defined(ARDUINO) +#if defined(ESP8266) ulong wd = now_tz() / 86400L; return (wd+3) % 7; // Jan 1, 1970 is a Thursday #else @@ -2011,27 +1842,23 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* DEBUG_PRINTLN("server:port is invalid!"); return HTTP_RQT_CONNECT_ERR; } -#if defined(ARDUINO) +#if defined(ESP8266) Client *client = NULL; - #if defined(ESP8266) - if(usessl) { - WiFiClientSecure *_c = new WiFiClientSecure(); - _c->setInsecure(); - bool mfln = _c->probeMaxFragmentLength(server, port, 512); - DEBUG_PRINTF("MFLN supported: %s\n", mfln ? "yes" : "no"); - if (mfln) { - _c->setBufferSizes(512, 512); - } else { - _c->setBufferSizes(2048, 2048); - } - client = _c; + if(usessl) { + WiFiClientSecure *_c = new WiFiClientSecure(); + _c->setInsecure(); + bool mfln = _c->probeMaxFragmentLength(server, port, 512); + DEBUG_PRINTF("MFLN supported: %s\n", mfln ? "yes" : "no"); + if (mfln) { + _c->setBufferSizes(512, 512); } else { - client = new WiFiClient(); + _c->setBufferSizes(2048, 2048); } - #else - client = new EthernetClient(); - #endif + client = _c; + } else { + client = new WiFiClient(); + } #define HTTP_CONNECT_NTRIES 3 unsigned char tries = 0; @@ -2084,7 +1911,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* uint32_t stoptime = millis()+timeout; int pos = 0; -#if defined(ARDUINO) +#if defined(ESP8266) // with ESP8266 core 3.0.2, client->connected() is not always true even if there is more data // so this loop is going to take longer than it should be // todo: can consider using HTTPClient for ESP8266 @@ -2262,7 +2089,7 @@ void OpenSprinkler::pre_factory_reset() { /** Factory reset */ void OpenSprinkler::factory_reset() { -#if defined(ARDUINO) +#if defined(ESP8266) lcd_print_line_clear_pgm(PSTR("Factory reset"), 0); lcd_print_line_clear_pgm(PSTR("Please Wait..."), 1); #else @@ -2315,12 +2142,70 @@ void OpenSprinkler::factory_reset() { // 4. write program data: just need to write a program counter: 0 file_write_byte(PROG_FILENAME, 0, 0); + #if defined(USE_SENSORS) + // Initalize the senor file + memset(tmp_buffer, 0, TMP_BUFFER_SIZE); + + remove_file(SENSORS_FILENAME); + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + for (size_t i = 0; i < MAX_SENSORS; i++) { + file_write(file, tmp_buffer, sizeof(uint32_t)); + file_write(file, tmp_buffer, TMP_BUFFER_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } + + uint16_t next = SENSOR_LOG_PER_FILE; + for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + { + char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; + sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; + memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", f); + remove_file(sensor_log_name_buf); + } + file = open_sensor_log(f, FileOpenMode::WriteTruncate); + if (file) { + file_write(file, &next, sizeof(next)); + for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + } + } + + remove_file(SENADJ_FILENAME); + file = file_open(SENADJ_FILENAME, FileOpenMode::WriteTruncate); + if (file) { + sensor_adjustment_point_t point = sensor_adjustment_point_t {0.0, 0.0}; + SensorAdjustment adj = SensorAdjustment(0, 0, 0, &point); + + uint32_t size = adj.serialize(tmp_buffer); + for (size_t i = 0; i < MAX_NUM_PROGRAMS; i++) { + file_write(file, tmp_buffer, size); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } + #endif + // 5. write 'done' file file_write_byte(DONE_FILENAME, 0, 1); } /** Parse OTC configuration */ -#if defined(USE_OTF) void OpenSprinkler::parse_otc_config() { ArduinoJson::JsonDocument doc; // make sure this has the same scope as server and token const char *server = NULL; @@ -2357,7 +2242,6 @@ void OpenSprinkler::parse_otc_config() { otc.server = server ? String(server) : ""; otc.port = port; } -#endif /** Setup function for options */ void OpenSprinkler::options_setup() { @@ -2391,14 +2275,12 @@ void OpenSprinkler::options_setup() { } } #endif - #if defined(USE_OTF) parse_otc_config(); - #endif attribs_load(); } -#if defined(ARDUINO) // handle AVR buttons +#if defined(ESP8266) // handle buttons unsigned char button = button_read(BUTTON_WAIT_NONE); switch(button & BUTTON_MASK) { @@ -2413,7 +2295,6 @@ void OpenSprinkler::options_setup() { break; case BUTTON_2: - #if defined(ESP8266) // if BUTTON_2 is pressed during startup, go to Test OS mode // only available for OS 3.0 lcd_print_line_clear_pgm(PSTR("===Test Mode==="), 0); @@ -2433,8 +2314,6 @@ void OpenSprinkler::options_setup() { wifi_pass = "opendoor"; #endif button = 0; - #endif - break; case BUTTON_3: @@ -2483,7 +2362,7 @@ void OpenSprinkler::options_setup() { lcd_print_pgm(PSTR(" AC")); } delay(1500); - #if defined(ARDUINO) + #if defined(ESP8266) lcd.setCursor(2, 1); lcd_print_pgm(PSTR("FW ")); lcd.print((char)('0'+(OS_FW_VERSION/100))); @@ -2616,15 +2495,223 @@ void OpenSprinkler::raindelay_stop() { nvdata_save(); } +#if defined(USE_SENSORS) +/** Sensor functions */ +Sensor *OpenSprinkler::parse_sensor(os_file_type file) { + uint32_t len = 0; + file_read(file, &len, sizeof(len)); + + if (len == 0 || len > TMP_BUFFER_SIZE) return nullptr; + + file_read(file, tmp_buffer, len); + file_seek(file, TMP_BUFFER_SIZE - len, FileSeekMode::Current); + + if ((uint8_t)(tmp_buffer[0]) >= (uint8_t)SensorType::MAX_VALUE) { + return nullptr; + } + + SensorType sensor_type = static_cast(*tmp_buffer); + + switch (sensor_type) { + case SensorType::Ensemble: + return new EnsembleSensor(os.sensors, (char*)tmp_buffer); + case SensorType::ADS1115: + return new ADS1115Sensor(os.ads1115_devices, (char*)tmp_buffer); + case SensorType::Weather: + return new WeatherSensor(os.get_sensor_weather_data, (char*)tmp_buffer); + default: + return nullptr; + }; +} + +Sensor *OpenSprinkler::get_sensor(uint8_t index) { + ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + Sensor *result = parse_sensor(file); + file_close(file); + return result; + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + return nullptr; + } +} + +os_file_type OpenSprinkler::open_sensor_log(uint16_t file_no, FileOpenMode mode) { + char sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 3]; + sensor_log_name_buf[sizeof(SENSORS_LOG_FILENAME) + 2] = 0; + memcpy(sensor_log_name_buf, SENSORS_LOG_FILENAME, sizeof(SENSORS_LOG_FILENAME)); + snprintf(sensor_log_name_buf + sizeof(SENSORS_LOG_FILENAME) - 1, 4, "%03u", file_no); + + return file_open(sensor_log_name_buf, mode); +} + +void OpenSprinkler::load_sensors() { + Sensor *sensor; + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::Read); + if (file) { + for (size_t i = 0; i < MAX_SENSORS; i++) { + if ((sensor = parse_sensor(file))) { + sensors[i].interval = sensor->interval; + sensors[i].flags = sensor->flags; + sensors[i].next_update = 0; + sensors[i].value = sensor->get_initial_value(); + delete sensor; + } + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } +} + +void OpenSprinkler::write_sensor(Sensor *sensor, uint8_t index) { + ulong pos = (TMP_BUFFER_SIZE + sizeof(uint32_t)) * index; + uint32_t len = 0; + + os_file_type file = file_open(SENSORS_FILENAME, FileOpenMode::ReadWrite); + if (file) { + if (sensor) { + len = sensor->serialize(tmp_buffer); + } + + file_seek(file, pos, FileSeekMode::Current); + file_write(file, &len, sizeof(len)); + if (sensor) { + file_write(file, tmp_buffer, len); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_FILENAME); + } +} + +void OpenSprinkler::log_sensor(uint8_t sid, float value) { + os_file_type file = open_sensor_log(sensor_file_no, FileOpenMode::ReadWrite); + if (file) { + uint16_t next = 0; + file_read(file, &next, sizeof(next)); + if (next == SENSOR_LOG_PER_FILE) next -= 1; + + time_os_t timestamp = now(); + tmp_buffer[0] = 1; + tmp_buffer[1] = sid; + char *ptr = tmp_buffer + 2; + memcpy(ptr, ×tamp, sizeof(timestamp)); + ptr += sizeof(timestamp); + memcpy(ptr, &value, sizeof(value)); + + uint32_t pos = (next * SENSOR_LOG_ITEM_SIZE); + + + if (next > 0) { + next -= 1; + } else { + next = SENSOR_LOG_PER_FILE; + if (sensor_file_no > 0) { + sensor_file_no -= 1; + } else { + sensor_file_no = SENSOR_LOG_FILE_COUNT - 1; + } + } + + file_seek(file, 0); + file_write(file, &next, sizeof(next)); + + file_seek(file, pos, FileSeekMode::Current); + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENSORS_LOG_FILENAME); + } +} + +void OpenSprinkler::poll_sensors() { + for (uint8_t i = 0; i < MAX_SENSORS; i++) { + if (sensors[i].interval && sensors[i].flags & (1 << SENSOR_FLAG_ENABLE)) { + if ((long)(millis() - sensors[i].next_update) > 0) { + Sensor *sensor = get_sensor(i); + if (sensor) { + sensors[i].value = sensor->get_new_value(); + delete sensor; + sensors[i].next_update = millis() + (sensors[i].interval * 1000 * 60); + if (sensors[i].flags & (1 << SENSOR_FLAG_LOG)) { + os.log_sensor(i, sensors[i].value); + } + } + } + } + } +} + +SensorAdjustment *OpenSprinkler::get_sensor_adjust(uint8_t index) { + if (index > MAX_NUM_PROGRAMS) return nullptr; + ulong pos = SENSOR_ADJUSTMENT_SIZE * index; + + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + file_read(file, tmp_buffer, SENSOR_ADJUSTMENT_SIZE); + + SensorAdjustment *result = new SensorAdjustment(tmp_buffer); + file_close(file); + + if (result->sid == 255) { + delete result; + return nullptr; + } else { + return result; + } + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + return nullptr; + } +} + +void OpenSprinkler::write_sensor_adjust(SensorAdjustment *adj, uint8_t index) { + ulong pos = SENSOR_ADJUSTMENT_SIZE * index; + + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::ReadWrite); + if (file) { + file_seek(file, pos, FileSeekMode::Current); + + if (adj) { + ulong len = adj->serialize(tmp_buffer); + file_write(file, tmp_buffer, len); + } else { + tmp_buffer[0] = 0; + file_write(file, tmp_buffer, 1); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } +} + +double OpenSprinkler::get_sensor_weather_data(WeatherAction action) { + return NAN; // TODO make function for WeatherSensor +} +#endif + /** LCD and button functions */ #if defined(USE_DISPLAY) -#if defined(ARDUINO) // AVR LCD and button functions +#if defined(ESP8266) // Arduino LCD and button functions /** print a program memory string */ -#if defined(ESP8266) void OpenSprinkler::lcd_print_pgm(PGM_P str) { -#else -void OpenSprinkler::lcd_print_pgm(PGM_P PROGMEM str) { -#endif uint8_t c; while((c=pgm_read_byte(str++))!= '\0') { lcd.print((char)c); @@ -2632,11 +2719,7 @@ void OpenSprinkler::lcd_print_pgm(PGM_P PROGMEM str) { } /** print a program memory string to a given line with clearing */ -#if defined(ESP8266) void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P str, unsigned char line) { -#else -void OpenSprinkler::lcd_print_line_clear_pgm(PGM_P PROGMEM str, unsigned char line) { -#endif lcd.setCursor(0, line); uint8_t c; int8_t cnt = 0; @@ -2678,42 +2761,26 @@ void OpenSprinkler::lcd_print_2digit(int v) /** print time to a given line */ void OpenSprinkler::lcd_print_time(time_os_t t) { -#if defined(USE_SSD1306) lcd.setAutoDisplay(false); -#endif lcd.setCursor(0, 0); lcd_print_2digit(hour(t)); - lcd_print_pgm(PSTR(":")); - lcd_print_2digit(minute(t)); - lcd_print_pgm(PSTR(" ")); - // each weekday string has 3 characters + ending 0 lcd_print_pgm(days_str+4*weekday_today()); - lcd_print_pgm(PSTR(" ")); - lcd_print_pgm(months_str+4*(month(t)-1)); - lcd_print_pgm(PSTR("-")); - lcd_print_2digit(day(t)); -#if defined(USE_SSD1306) lcd.display(); lcd.setAutoDisplay(true); -#endif } /** print ip address */ void OpenSprinkler::lcd_print_ip(const unsigned char *ip, unsigned char endian) { -#if defined(USE_SSD1306) lcd.clear(0, 1); lcd.setAutoDisplay(false); -#elif defined(USE_LCD) - lcd.clear(); -#endif lcd.setCursor(0, 0); for (unsigned char i=0; i<4; i++) { lcd.print(endian ? (int)ip[3-i] : (int)ip[i]); @@ -2722,18 +2789,13 @@ void OpenSprinkler::lcd_print_ip(const unsigned char *ip, unsigned char endian) lcd_print_pgm(PSTR(".")); } } - - #if defined(USE_SSD1306) - lcd.display(); - lcd.setAutoDisplay(true); - #endif + lcd.display(); + lcd.setAutoDisplay(true); } /** print mac address */ void OpenSprinkler::lcd_print_mac(const unsigned char *mac) { - #if defined(USE_SSD1306) - lcd.setAutoDisplay(false); // reduce screen drawing time by turning off display() when drawing individual characters - #endif + lcd.setAutoDisplay(false); // reduce screen drawing time by turning off display() when drawing individual characters lcd.setCursor(0, 0); for(unsigned char i=0; i<6; i++) { if(i) { @@ -2744,7 +2806,7 @@ void OpenSprinkler::lcd_print_mac(const unsigned char *mac) { lcd.print((mac[i]&0x0F), HEX); if(i==4) lcd.setCursor(0, 1); } - #if defined(ARDUINO) + #if defined(ESP8266) if(useEth) { lcd_print_pgm(PSTR(" (Ether MAC)")); } else { @@ -2754,17 +2816,13 @@ void OpenSprinkler::lcd_print_mac(const unsigned char *mac) { lcd_print_pgm(PSTR(" (MAC)")); #endif - #if defined(USE_SSD1306) - lcd.display(); - lcd.setAutoDisplay(true); - #endif + lcd.display(); + lcd.setAutoDisplay(true); } /** print station bits */ void OpenSprinkler::lcd_print_screen(char c) { -#if defined(USE_SSD1306) lcd.setAutoDisplay(false); // reduce screen drawing time by turning off display() when drawing individual characters -#endif lcd.setCursor(0, 1); if (status.display_board == 0) { lcd.print(F("MC:")); // Master controller is display as 'MC' @@ -2851,7 +2909,6 @@ void OpenSprinkler::lcd_print_screen(char c) { lcd.write(status.network_fails>2?ICON_ETHER_DISCONNECTED:ICON_ETHER_CONNECTED); // if network failure detection is more than 2, display disconnect icon #endif -#if defined(USE_SSD1306) #if defined(ESP8266) if(useEth || (get_wifi_mode()==WIFI_MODE_STA && WiFi.status()==WL_CONNECTED && WiFi.localIP())) { #else @@ -2883,11 +2940,9 @@ void OpenSprinkler::lcd_print_screen(char c) { lcd.clear(2, 2); } } - - lcd.display(); lcd.setAutoDisplay(true); -#endif + } /** print a version number */ @@ -2962,7 +3017,7 @@ void OpenSprinkler::lcd_print_option(int i) { lcd.print((int)iopts[i]); break; case IOPT_BOOST_TIME: - #if defined(ARDUINO) + #if defined(ESP8266) if(hw_type==HW_TYPE_AC) { lcd.print('-'); } else { @@ -2975,7 +3030,7 @@ void OpenSprinkler::lcd_print_option(int i) { break; case IOPT_I_MIN_THRESHOLD: case IOPT_I_MAX_LIMIT: - #if defined(ARDUINO) + #if defined(ESP8266) lcd.print((int)iopts[i]*10); lcd_print_pgm(PSTR(" mA")); #else @@ -2984,7 +3039,7 @@ void OpenSprinkler::lcd_print_option(int i) { break; case IOPT_LATCH_ON_VOLTAGE: case IOPT_LATCH_OFF_VOLTAGE: - #if defined(ARDUINO) + #if defined(ESP8266) if(hw_type==HW_TYPE_LATCH) { lcd.print((int)iopts[i]); lcd.print('V'); @@ -3075,7 +3130,7 @@ unsigned char OpenSprinkler::button_read(unsigned char waitmode) return ret; } -#if defined(ARDUINO) +#if defined(ESP8266) /** user interface for setting options during startup */ void OpenSprinkler::ui_set_options(int oid) @@ -3127,11 +3182,7 @@ void OpenSprinkler::ui_set_options(int oid) if (hw_type==HW_TYPE_AC && i==IOPT_BOOST_TIME) i++; // skip boost time for non-DC controller if (i==IOPT_LATCH_ON_VOLTAGE && hw_type!=HW_TYPE_LATCH) i+= 2; // skip latch voltage defs for non-latch controllers if (i==IOPT_TARGET_PD_VOLTAGE && !(hw_rev==4 && hw_type==HW_TYPE_DC)) i++; // skip target pd voltage if not 3.4 or not DC type - #if defined(ESP8266) else if (lcd.type()==LCD_I2C && i==IOPT_LCD_CONTRAST) i+=3; - #else - else if (lcd.type()==LCD_I2C && i==IOPT_LCD_CONTRAST) i+=2; - #endif // string options are not editable } break; @@ -3143,56 +3194,22 @@ void OpenSprinkler::ui_set_options(int oid) } lcd.noBlink(); } - -#else #endif // end of LCD and button functions /** Set LCD contrast (using PWM) */ void OpenSprinkler::lcd_set_contrast() { -#ifdef PIN_LCD_CONTRAST - // set contrast is only valid for standard LCD - if (lcd.type()==LCD_STD) { - pinMode(PIN_LCD_CONTRAST, OUTPUT); - analogWrite(PIN_LCD_CONTRAST, iopts[IOPT_LCD_CONTRAST]); - } -#endif } /** Set LCD brightness (using PWM) */ void OpenSprinkler::lcd_set_brightness(unsigned char value) { -#if defined(PIN_LCD_BACKLIGHT) - #if defined(OS_AVR) - if (lcd.type()==LCD_I2C) { - if (value) lcd.backlight(); - else { - // turn off LCD backlight - // only if dimming value is set to 0 - if(iopts[IOPT_LCD_DIMMING]==0) lcd.noBacklight(); - else lcd.backlight(); - } - } - #endif - if (lcd.type()==LCD_STD) { - pinMode(PIN_LCD_BACKLIGHT, OUTPUT); - if (value) { - analogWrite(PIN_LCD_BACKLIGHT, 255-iopts[IOPT_LCD_BACKLIGHT]); - } else { - analogWrite(PIN_LCD_BACKLIGHT, 255-iopts[IOPT_LCD_DIMMING]); - } - } - -#elif defined(USE_SSD1306) if (value) {lcd.displayOn();lcd.setBrightness(255); } else { if(iopts[IOPT_LCD_DIMMING]==0) lcd.displayOff(); else { lcd.displayOn();lcd.setBrightness(iopts[IOPT_LCD_DIMMING]); } } -#endif } - - -#if defined(USE_SSD1306) +#if defined(USE_DISPLAY) #include "images.h" void OpenSprinkler::flash_screen() { lcd.setCursor(0, -1); diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 15bac6d3..94092635 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -21,9 +21,7 @@ * . */ - -#ifndef _OPENSPRINKLER_H -#define _OPENSPRINKLER_H +#pragma once #include "types.h" #include "defines.h" @@ -32,30 +30,25 @@ #include "images.h" #include "mqtt.h" #include "RCSwitch.h" +#include -#if defined(ARDUINO) // headers for Arduino +#if defined(ESP8266) // headers for Arduino #include #include #include #include #include "I2CRTC.h" - #if defined(ESP8266) // for ESP8266 - #include - #include - #include - #include - #include - #include - #include - #include "espconnect.h" - #include "EMailSender.h" - #include "ch224.h" - #else // for AVR - #include - #include - #include "LiquidCrystal.h" - #endif + #include + #include + #include + #include + #include + #include + #include + #include "espconnect.h" + #include "EMailSender.h" + #include "ch224.h" #else // headers for RPI/LINUX #include @@ -69,16 +62,19 @@ #include "smtp.h" #endif // end of headers -#if defined(USE_LCD) - #include "LiquidCrystal.h" +#if defined(USE_DISPLAY) + #include "SSD1306Display.h" #endif -#if defined(USE_SSD1306) - #include "SSD1306Display.h" +#if defined(USE_SENSORS) +#include "sensor.h" #endif -#if defined(ARDUINO) - #if defined(ESP8266) +#if defined(USE_ADS1115) + #include "ads1115.h" +#endif + +#if defined(ESP8266) extern ESP8266WebServer *update_server; extern ENC28J60lwIP enc28j60; extern Wiznet5500lwIP w5500; @@ -110,20 +106,12 @@ } }; extern lwipEth eth; - #else - // AVR specific - #endif extern bool useEth; #else // OSPI/Linux specific #endif -#if defined(USE_OTF) - extern OTF::OpenThingsFramework *otf; -#else - extern EthernetServer *m_server; - extern bool useEth; -#endif +extern OTF::OpenThingsFramework *otf; /** Non-volatile data structure */ struct NVConData { @@ -241,10 +229,17 @@ class OpenSprinkler { public: // data members -#if defined(USE_SSD1306) +#if defined(USE_DISPLAY) static SSD1306Display lcd; // 128x64 OLED display -#elif defined(USE_LCD) - static LiquidCrystal lcd; // 16x2 character LCD +#endif + +#if defined(USE_ADS1115) + static ADS1115 *ads1115_devices[4]; +#endif + +#if defined(USE_SENSORS) + static sensor_memory_t sensors[MAX_SENSORS]; + static uint16_t sensor_file_no; #endif #if defined(OSPI) @@ -372,9 +367,22 @@ class OpenSprinkler { static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, bool usessl=false, uint16_t timeout=5000); static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, bool usessl=false, uint16_t timeout=5000); - #if defined(USE_OTF) static OTCConfig otc; - #endif + + // -- Sensor functions + #if defined(USE_SENSORS) + static os_file_type open_sensor_log(uint16_t file_no, FileOpenMode mode); + static void load_sensors(); + static Sensor *parse_sensor(os_file_type file); + static Sensor *get_sensor(uint8_t index); + static void write_sensor(Sensor *sensor, uint8_t index); + void log_sensor(uint8_t sid, float value); + static void poll_sensors(); + static SensorAdjustment *get_sensor_adjust(uint8_t index); + static void write_sensor_adjust(SensorAdjustment *adj, uint8_t index); + + static double get_sensor_weather_data(WeatherAction action); + #endif // -- LCD functions #if defined(USE_DISPLAY) @@ -385,12 +393,9 @@ class OpenSprinkler { static void lcd_print_version(unsigned char v); // print version number static void lcd_set_brightness(unsigned char value=1); static void lcd_set_contrast(); - - #if defined(USE_SSD1306) static void flash_screen(); static void toggle_screen_led(); static void set_screen_led(unsigned char status); - #endif static String time2str(uint32_t t) { uint16_t h = hour(t); @@ -417,16 +422,10 @@ class OpenSprinkler { static void ui_set_options(int oid); // ui for setting options (oid-> starting option index) #endif -#if defined(ARDUINO) // LCD functions for Arduino - #if defined(ESP8266) +#if defined(ESP8266) // LCD functions for Arduino static void lcd_print_pgm(PGM_P str); // ESP8266 does not allow PGM_P followed by PROGMEM static void lcd_print_line_clear_pgm(PGM_P str, unsigned char line); - #else - static void lcd_print_pgm(PGM_P PROGMEM str); // print a program memory string - static void lcd_print_line_clear_pgm(PGM_P PROGMEM str, unsigned char line); - #endif - #if defined(ESP8266) static IOEXP *mainio, *drio; static IOEXP *expanders[]; static CH224 usbpd; @@ -442,11 +441,10 @@ class OpenSprinkler { static void reset_to_ap(); static unsigned char state; static void setup_pd_voltage(); - #endif #else -static void lcd_print_pgm(const char *str); -static void lcd_print_line_clear_pgm(const char *str, unsigned char line); + static void lcd_print_pgm(const char *str); + static void lcd_print_line_clear_pgm(const char *str, unsigned char line); #endif // LCD functions for Arduino private: @@ -471,9 +469,6 @@ static void lcd_print_line_clear_pgm(const char *str, unsigned char line); static unsigned char engage_booster; static RCSwitch rfswitch; - #if defined(USE_OTF) static void parse_otc_config(); - #endif }; -#endif // _OPENSPRINKLER_H diff --git a/RCSwitch.cpp b/RCSwitch.cpp index 2f4ae7f6..d821f17a 100644 --- a/RCSwitch.cpp +++ b/RCSwitch.cpp @@ -32,7 +32,7 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ -#if defined(ARDUINO) +#if defined(ESP8266) #include #endif diff --git a/SSD1306Display.h b/SSD1306Display.h index f97683a5..6438f857 100644 --- a/SSD1306Display.h +++ b/SSD1306Display.h @@ -1,5 +1,4 @@ -#ifndef SSD1306_DISPLAY_H -#define SSD1306_DISPLAY_H +#pragma once #if defined(ESP8266) @@ -13,81 +12,81 @@ class SSD1306Display : public SSD1306 { public: - SSD1306Display(uint8_t _addr, uint8_t _sda, uint8_t _scl) - : SSD1306(_addr, _sda, _scl) { - cx = 0; - cy = 0; - for (unsigned char i = 0; i < NUM_CUSTOM_ICONS; i++) - custom_chars[i] = NULL; - } - void begin() { - Wire.setClock(400000L); // lower clock to 400kHz - flipScreenVertically(); - setFont(Monospaced_plain_13); - fontWidth = 8; - fontHeight = 16; - } - void clear() { SSD1306::clear(); } - void clear(int start, int end) { - setColor(BLACK); - fillRect(0, (start + 1) * fontHeight, 128, (end - start + 1) * fontHeight); - setColor(WHITE); - } - - uint8_t type() { return LCD_I2C; } - void noBlink() { /*no support*/ } - void blink() { /*no support*/ } - void setCursor(uint8_t col, int8_t row) { - /* assume 4 lines, the middle two lines - are row 0 and 1 */ - cy = (row + 1) * fontHeight; - cx = col * fontWidth; - } - void noBacklight() { /*no support*/ } - void backlight() { /*no support*/ } - size_t write(uint8_t c) { - setColor(BLACK); - fillRect(cx, cy, fontWidth, fontHeight); - setColor(WHITE); - - if (c < NUM_CUSTOM_ICONS && custom_chars[c] != NULL) { - drawXbm(cx, cy, fontWidth, fontHeight, - (const unsigned char *)custom_chars[c]); - } else { - drawString(cx, cy, String((char)c)); - } - cx += fontWidth; - if (auto_display) - display(); // todo: not very efficient - return 1; - } - size_t write(const char *s) { - uint8_t nc = strlen(s); - setColor(BLACK); - fillRect(cx, cy, fontWidth * nc, fontHeight); - setColor(WHITE); - drawString(cx, cy, String(s)); - cx += fontWidth * nc; - if (auto_display) - display(); // todo: not very efficient - return nc; - } - void createChar(unsigned char idx, PGM_P ptr) { - if (idx >= 0 && idx < NUM_CUSTOM_ICONS) - custom_chars[idx] = ptr; - } - - void createChar(unsigned char idx, const unsigned char *ptr) { - createChar(idx, (const char *)ptr); - } - - void setAutoDisplay(bool v) { auto_display = v; } + SSD1306Display(uint8_t _addr, uint8_t _sda, uint8_t _scl) + : SSD1306(_addr, _sda, _scl) { + cx = 0; + cy = 0; + for (unsigned char i = 0; i < NUM_CUSTOM_ICONS; i++) + custom_chars[i] = NULL; + } + void begin() { + Wire.setClock(400000L); // lower clock to 400kHz + flipScreenVertically(); + setFont(Monospaced_plain_13); + fontWidth = 8; + fontHeight = 16; + } + void clear() { SSD1306::clear(); } + void clear(int start, int end) { + setColor(BLACK); + fillRect(0, (start + 1) * fontHeight, 128, (end - start + 1) * fontHeight); + setColor(WHITE); + } + + uint8_t type() { return LCD_I2C; } + void noBlink() { /*no support*/ } + void blink() { /*no support*/ } + void setCursor(uint8_t col, int8_t row) { + /* assume 4 lines, the middle two lines + are row 0 and 1 */ + cy = (row + 1) * fontHeight; + cx = col * fontWidth; + } + void noBacklight() { /*no support*/ } + void backlight() { /*no support*/ } + size_t write(uint8_t c) { + setColor(BLACK); + fillRect(cx, cy, fontWidth, fontHeight); + setColor(WHITE); + + if (c < NUM_CUSTOM_ICONS && custom_chars[c] != NULL) { + drawXbm(cx, cy, fontWidth, fontHeight, + (const unsigned char *)custom_chars[c]); + } else { + drawString(cx, cy, String((char)c)); + } + cx += fontWidth; + if (auto_display) + display(); // todo: not very efficient + return 1; + } + size_t write(const char *s) { + uint8_t nc = strlen(s); + setColor(BLACK); + fillRect(cx, cy, fontWidth * nc, fontHeight); + setColor(WHITE); + drawString(cx, cy, String(s)); + cx += fontWidth * nc; + if (auto_display) + display(); // todo: not very efficient + return nc; + } + void createChar(unsigned char idx, PGM_P ptr) { + if (idx >= 0 && idx < NUM_CUSTOM_ICONS) + custom_chars[idx] = ptr; + } + + void createChar(unsigned char idx, const unsigned char *ptr) { + createChar(idx, (const char *)ptr); + } + + void setAutoDisplay(bool v) { auto_display = v; } private: - bool auto_display = true; - uint8_t cx, cy; - uint8_t fontWidth, fontHeight; - PGM_P custom_chars[NUM_CUSTOM_ICONS]; + bool auto_display = true; + uint8_t cx, cy; + uint8_t fontWidth, fontHeight; + PGM_P custom_chars[NUM_CUSTOM_ICONS]; }; #else @@ -171,387 +170,379 @@ class SSD1306Display : public SSD1306 { class SSD1306Display { public: - SSD1306Display(uint8_t addr, uint8_t _sda, uint8_t _scl) { - cx = 0; - cy = 0; - _addr = addr; - for (uint8_t i = 0; i < NUM_CUSTOM_ICONS; i++) - custom_chars[i] = 0; - - clear_buffer(); - - height = 64; - width = 128; - - i2c = I2CDevice(); - } - - ~SSD1306Display() { - displayOff(); - close(file); - } - - void init() {} // Dummy function to match ESP8266 - - int begin() { - i2c.begin(_addr); - - setFont(Monospaced_plain_13); - fontWidth = 8; - fontHeight = 16; - - i2c.begin_transaction(SSD1306_COMMAND_ADDRESS); - ssd1306_command(SSD1306_DISPLAY_OFF); - ssd1306_command(SSD1306_SET_DISPLAY_CLOCK_DIV_RATIO); - ssd1306_command(0x80); - ssd1306_command(SSD1306_SET_MULTIPLEX_RATIO); - ssd1306_command(height - 1); - ssd1306_command(SSD1306_SET_DISPLAY_OFFSET); - ssd1306_command(0x00); - ssd1306_command(SSD1306_SET_START_LINE); - ssd1306_command(SSD1306_CHARGE_PUMP); - ssd1306_command(0x14); - ssd1306_command(SSD1306_MEMORY_ADDR_MODE); - ssd1306_command(0x00); - ssd1306_command(SSD1306_SET_SEGMENT_REMAP | 0x01); - ssd1306_command(SSD1306_COM_SCAN_DIR_DEC); - - switch (height) { - case 64: - ssd1306_command(SSD1306_SET_COM_PINS); - ssd1306_command(0x12); - ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); - ssd1306_command(0xCF); - break; - case 32: - ssd1306_command(SSD1306_SET_COM_PINS); - ssd1306_command(0x02); - ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); - ssd1306_command(0x8F); - break; - case 16: // NOTE: not tested, lacking part. - ssd1306_command(SSD1306_SET_COM_PINS); - ssd1306_command(0x2); - ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); - ssd1306_command(0xAF); - break; - } - - ssd1306_command(SSD1306_SET_PRECHARGE_PERIOD); - ssd1306_command(0xF1); - - ssd1306_command(SSD1306_SET_VCOM_DESELECT); - ssd1306_command(0x40); - - ssd1306_command(SSD1306_DISPLAY_ALL_ON_RESUME); - ssd1306_command(SSD1306_NORMAL_DISPLAY); - ssd1306_command(SSD1306_DEACTIVATE_SCROLL); - ssd1306_command(SSD1306_DISPLAY_ON); - - i2c.end_transaction(); - - return 0; - } - - void setFont(const uint8_t *f) { font = (uint8_t *)f; } - - void display() { - i2c.begin_transaction(SSD1306_COMMAND_ADDRESS); - ssd1306_command(SSD1306_SET_PAGE_ADDR); - ssd1306_command(0x00); // Page start address (0 = reset) - switch (height) { - case 64: - ssd1306_command(7); - break; - case 32: - ssd1306_command(3); - break; - case 16: - ssd1306_command(1); - break; - } - - ssd1306_command(SSD1306_SET_COLUMN_ADDR); - ssd1306_command(0x00); // Column start address (0 = reset) + SSD1306Display(uint8_t addr, uint8_t _sda, uint8_t _scl) : i2c(Bus, addr) { + cx = 0; + cy = 0; + for (uint8_t i = 0; i < NUM_CUSTOM_ICONS; i++) + custom_chars[i] = 0; + + clear_buffer(); + + height = 64; + width = 128; + } + + ~SSD1306Display() { + displayOff(); + close(file); + } + + void init() {} // Dummy function to match ESP8266 + + int begin() { + setFont(Monospaced_plain_13); + fontWidth = 8; + fontHeight = 16; + + i2c.begin_transaction(SSD1306_COMMAND_ADDRESS); + ssd1306_command(SSD1306_DISPLAY_OFF); + ssd1306_command(SSD1306_SET_DISPLAY_CLOCK_DIV_RATIO); + ssd1306_command(0x80); + ssd1306_command(SSD1306_SET_MULTIPLEX_RATIO); + ssd1306_command(height - 1); + ssd1306_command(SSD1306_SET_DISPLAY_OFFSET); + ssd1306_command(0x00); + ssd1306_command(SSD1306_SET_START_LINE); + ssd1306_command(SSD1306_CHARGE_PUMP); + ssd1306_command(0x14); + ssd1306_command(SSD1306_MEMORY_ADDR_MODE); + ssd1306_command(0x00); + ssd1306_command(SSD1306_SET_SEGMENT_REMAP | 0x01); + ssd1306_command(SSD1306_COM_SCAN_DIR_DEC); + + switch (height) { + case 64: + ssd1306_command(SSD1306_SET_COM_PINS); + ssd1306_command(0x12); + ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); + ssd1306_command(0xCF); + break; + case 32: + ssd1306_command(SSD1306_SET_COM_PINS); + ssd1306_command(0x02); + ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); + ssd1306_command(0x8F); + break; + case 16: // NOTE: not tested, lacking part. + ssd1306_command(SSD1306_SET_COM_PINS); + ssd1306_command(0x2); + ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); + ssd1306_command(0xAF); + break; + } + + ssd1306_command(SSD1306_SET_PRECHARGE_PERIOD); + ssd1306_command(0xF1); + + ssd1306_command(SSD1306_SET_VCOM_DESELECT); + ssd1306_command(0x40); + + ssd1306_command(SSD1306_DISPLAY_ALL_ON_RESUME); + ssd1306_command(SSD1306_NORMAL_DISPLAY); + ssd1306_command(SSD1306_DEACTIVATE_SCROLL); + ssd1306_command(SSD1306_DISPLAY_ON); + + i2c.end_transaction(); + + return 0; + } + + void setFont(const uint8_t *f) { font = (uint8_t *)f; } + + void display() { + i2c.begin_transaction(SSD1306_COMMAND_ADDRESS); + ssd1306_command(SSD1306_SET_PAGE_ADDR); + ssd1306_command(0x00); // Page start address (0 = reset) + switch (height) { + case 64: + ssd1306_command(7); + break; + case 32: + ssd1306_command(3); + break; + case 16: + ssd1306_command(1); + break; + } + + ssd1306_command(SSD1306_SET_COLUMN_ADDR); + ssd1306_command(0x00); // Column start address (0 = reset) ssd1306_command(width - 1); // Column end address (127 = reset) - i2c.end_transaction(); + i2c.end_transaction(); - i2c.begin_transaction(SSD1306_DATA_CONTINUE_ADDRESS); + i2c.begin_transaction(SSD1306_DATA_CONTINUE_ADDRESS); - int b; - for (b = 0; b < 1024; b++) { - ssd1306_data(frame[b]); - } + int b; + for (b = 0; b < 1024; b++) { + ssd1306_data(frame[b]); + } - i2c.end_transaction(); - } + i2c.end_transaction(); + } - void clear() { - clear_buffer(); - display(); - } + void clear() { + clear_buffer(); + display(); + } - void setBrightness(uint8_t brightness) { - ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); - ssd1306_command(brightness); - } + void setBrightness(uint8_t brightness) { + ssd1306_command(SSD1306_SET_CONTRAST_CONTROL); + ssd1306_command(brightness); + } - void displayOn() { ssd1306_command(SSD1306_DISPLAY_ON); } + void displayOn() { ssd1306_command(SSD1306_DISPLAY_ON); } - void displayOff() { ssd1306_command(SSD1306_DISPLAY_OFF); } + void displayOff() { ssd1306_command(SSD1306_DISPLAY_OFF); } - void setColor(uint8_t color) { this->color = color; } + void setColor(uint8_t color) { this->color = color; } - void drawPixel(uint8_t x, uint8_t y) { - if (x >= 128 || y >= 64) - return; + void drawPixel(uint8_t x, uint8_t y) { + if (x >= 128 || y >= 64) + return; - if (color == WHITE) { - frame[x + (y / 8) * 128] |= 1 << (y % 8); - } else { - frame[x + (y / 8) * 128] &= ~(1 << (y % 8)); - } - } + if (color == WHITE) { + frame[x + (y / 8) * 128] |= 1 << (y % 8); + } else { + frame[x + (y / 8) * 128] &= ~(1 << (y % 8)); + } + } - void fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h) { - for (int _x = x; _x < x + w; _x++) { - for (int _y = y; _y < y + h; _y++) { - drawPixel(_x, _y); - } - } - } + void fillRect(uint8_t x, uint8_t y, uint8_t w, uint8_t h) { + for (int _x = x; _x < x + w; _x++) { + for (int _y = y; _y < y + h; _y++) { + drawPixel(_x, _y); + } + } + } - void clear(int start, int end) { - setColor(BLACK); - fillRect(0, (start + 1) * fontHeight, 128, (end - start + 1) * fontHeight); - setColor(WHITE); - } + void clear(int start, int end) { + setColor(BLACK); + fillRect(0, (start + 1) * fontHeight, 128, (end - start + 1) * fontHeight); + setColor(WHITE); + } - void print(const char *s) { write(s); } + void print(const char *s) { write(s); } - void print(char s) { write(s); } + void print(char s) { write(s); } - void print(int i) { - char buf[100]; - snprintf(buf, 100, "%d", i); - print((const char *)buf); - } + void print(int i) { + char buf[100]; + snprintf(buf, 100, "%d", i); + print((const char *)buf); + } - void print(unsigned int i) { - char buf[100]; - snprintf(buf, 100, "%u", i); - print((const char *)buf); - } - void print(float f) { - char buf[100]; - snprintf(buf, 100, "%f", f); - print((const char *)buf); - } + void print(unsigned int i) { + char buf[100]; + snprintf(buf, 100, "%u", i); + print((const char *)buf); + } + void print(float f) { + char buf[100]; + snprintf(buf, 100, "%f", f); + print((const char *)buf); + } #define DEC 10 #define HEX 16 #define OCT 8 #define BIN 2 - void print(int i, int base) { - char buf[100]; - switch (base) { - case DEC: - snprintf(buf, 100, "%d", i); - break; - case HEX: - snprintf(buf, 100, "%x", i); - break; - case OCT: - snprintf(buf, 100, "%o", i); - break; - case BIN: - snprintf(buf, 100, "%b", i); - break; - default: - snprintf(buf, 100, "%d", i); - break; - } - print((const char *)buf); - } - - uint8_t type() { return LCD_I2C; } - void noBlink() { /*no support*/ } - void blink() { /*no support*/ } - void setCursor(uint8_t col, int8_t row) { - /* assume 4 lines, the middle two lines - are row 0 and 1 */ - cy = (row + 1) * fontHeight; - cx = col * fontWidth; - } - void noBacklight() { /*no support*/ } - void backlight() { /*no support*/ } - void drawXbm(int x, int y, int w, int h, const char *xbm) { - int xbmWidth = (w + 7) / 8; - uint8_t data = 0; - - for (int i = 0; i < h; i++) { - for (int j = 0; j < w; j++) { - if (j & 7) { - data >>= 1; - } else { - data = xbm[(i * xbmWidth) + (j / 8)]; - } - - if (data & 0x01) { - drawPixel(x + j, y + i); - } - } - } - } - - void drawXbm(int x, int y, int w, int h, const uint8_t *data) { - drawXbm(x, y, w, h, (const char *)data); - } - - void fillCircle(int x0, int y0, int r) { - for (int y = -r; y <= r; y++) { - for (int x = -r; x <= r; x++) { - if (x * x + y * y <= r * r) { - drawPixel(x0 + x, y0 + y); - } - } - } - } - - void drawChar(int x, int y, char c) { - uint8_t textHeight = font[HEIGHT_POS]; - uint8_t firstChar = font[FIRST_CHAR_POS]; - uint8_t numChars = font[CHAR_NUM_POS]; - uint16_t sizeOfJumpTable = numChars * JUMPTABLE_BYTES; - - if (c < firstChar || c >= firstChar + numChars) - return; - - // 4 Bytes per char code - uint8_t charCode = c - firstChar; - - uint8_t msbJumpToChar = - font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES]; // MSB \ JumpAddress - uint8_t lsbJumpToChar = font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES + - JUMPTABLE_LSB]; // LSB / - uint8_t charByteSize = font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES + - JUMPTABLE_SIZE]; // Size - uint8_t currentCharWidth = - font[JUMPTABLE_START + (c - firstChar) * JUMPTABLE_BYTES + - JUMPTABLE_WIDTH]; // Width - - // Test if the char is drawable - if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { - // Get the position of the char data - uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + - ((msbJumpToChar << 8) + lsbJumpToChar); - int _y = y; - int _x = x; - - setColor(WHITE); - - for (int b = 0; b < charByteSize; b++) { - for (int i = 0; i < 8; i++) { - if (font[charDataPosition + b] & (1 << i)) { - drawPixel(_x, _y); - } - - _y++; - if (_y >= y + textHeight) { - _y = y; - _x++; - break; - } - } - } - } - } - - void drawString(int x, int y, const char *text) { - int _x = x; - int _y = y; - - while (*text) { - if (*text == '\n') { - _x = x; - _y += fontHeight; - } else { - drawChar(_x, _y, *text); - _x += fontWidth; - } - - text++; - } - } - - size_t write(uint8_t c) { - setColor(BLACK); - fillRect(cx, cy, fontWidth, fontHeight); - setColor(WHITE); - char cc[2] = {(char)c, 0}; - - if (c < NUM_CUSTOM_ICONS && custom_chars[c] != 0) { - drawXbm(cx, cy, fontWidth, fontHeight, (const char *)custom_chars[c]); - } else { - drawString(cx, cy, cc); - } - cx += fontWidth; - if (auto_display) - display(); // todo: not very efficient - return 1; - } - - uint8_t write(const char *s) { - uint8_t nc = strlen(s); - bool temp_auto_display = auto_display; - auto_display = false; - setColor(BLACK); - fillRect(cx, cy, fontWidth * nc, fontHeight); - setColor(WHITE); - drawString(cx, cy, s); - auto_display = temp_auto_display; - cx += fontWidth * nc; - if (auto_display) - display(); // todo: not very efficient - return nc; - } - - void createChar(uint8_t idx, const char *ptr) { - if (idx >= 0 && idx < NUM_CUSTOM_ICONS) - custom_chars[idx] = ptr; - } - - void createChar(unsigned char idx, const unsigned char *ptr) { - createChar(idx, (const char *)ptr); - } - - void setAutoDisplay(bool v) { auto_display = v; } + void print(int i, int base) { + char buf[100]; + switch (base) { + case DEC: + snprintf(buf, 100, "%d", i); + break; + case HEX: + snprintf(buf, 100, "%x", i); + break; + case OCT: + snprintf(buf, 100, "%o", i); + break; + case BIN: + snprintf(buf, 100, "%b", i); + break; + default: + snprintf(buf, 100, "%d", i); + break; + } + print((const char *)buf); + } + + uint8_t type() { return LCD_I2C; } + void noBlink() { /*no support*/ } + void blink() { /*no support*/ } + void setCursor(uint8_t col, int8_t row) { + /* assume 4 lines, the middle two lines + are row 0 and 1 */ + cy = (row + 1) * fontHeight; + cx = col * fontWidth; + } + void noBacklight() { /*no support*/ } + void backlight() { /*no support*/ } + void drawXbm(int x, int y, int w, int h, const char *xbm) { + int xbmWidth = (w + 7) / 8; + uint8_t data = 0; + + for (int i = 0; i < h; i++) { + for (int j = 0; j < w; j++) { + if (j & 7) { + data >>= 1; + } else { + data = xbm[(i * xbmWidth) + (j / 8)]; + } + + if (data & 0x01) { + drawPixel(x + j, y + i); + } + } + } + } + + void drawXbm(int x, int y, int w, int h, const uint8_t *data) { + drawXbm(x, y, w, h, (const char *)data); + } + + void fillCircle(int x0, int y0, int r) { + for (int y = -r; y <= r; y++) { + for (int x = -r; x <= r; x++) { + if (x * x + y * y <= r * r) { + drawPixel(x0 + x, y0 + y); + } + } + } + } + + void drawChar(int x, int y, char c) { + uint8_t textHeight = font[HEIGHT_POS]; + uint8_t firstChar = font[FIRST_CHAR_POS]; + uint8_t numChars = font[CHAR_NUM_POS]; + uint16_t sizeOfJumpTable = numChars * JUMPTABLE_BYTES; + + if (c < firstChar || c >= firstChar + numChars) + return; + + // 4 Bytes per char code + uint8_t charCode = c - firstChar; + + uint8_t msbJumpToChar = + font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES]; // MSB \ JumpAddress + uint8_t lsbJumpToChar = font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES + + JUMPTABLE_LSB]; // LSB / + uint8_t charByteSize = font[JUMPTABLE_START + charCode * JUMPTABLE_BYTES + + JUMPTABLE_SIZE]; // Size + uint8_t currentCharWidth = + font[JUMPTABLE_START + (c - firstChar) * JUMPTABLE_BYTES + + JUMPTABLE_WIDTH]; // Width + + // Test if the char is drawable + if (!(msbJumpToChar == 255 && lsbJumpToChar == 255)) { + // Get the position of the char data + uint16_t charDataPosition = JUMPTABLE_START + sizeOfJumpTable + + ((msbJumpToChar << 8) + lsbJumpToChar); + int _y = y; + int _x = x; + + setColor(WHITE); + + for (int b = 0; b < charByteSize; b++) { + for (int i = 0; i < 8; i++) { + if (font[charDataPosition + b] & (1 << i)) { + drawPixel(_x, _y); + } + + _y++; + if (_y >= y + textHeight) { + _y = y; + _x++; + break; + } + } + } + } + } + + void drawString(int x, int y, const char *text) { + int _x = x; + int _y = y; + + while (*text) { + if (*text == '\n') { + _x = x; + _y += fontHeight; + } else { + drawChar(_x, _y, *text); + _x += fontWidth; + } + + text++; + } + } + + size_t write(uint8_t c) { + setColor(BLACK); + fillRect(cx, cy, fontWidth, fontHeight); + setColor(WHITE); + char cc[2] = {(char)c, 0}; + + if (c < NUM_CUSTOM_ICONS && custom_chars[c] != 0) { + drawXbm(cx, cy, fontWidth, fontHeight, (const char *)custom_chars[c]); + } else { + drawString(cx, cy, cc); + } + cx += fontWidth; + if (auto_display) + display(); // todo: not very efficient + return 1; + } + + uint8_t write(const char *s) { + uint8_t nc = strlen(s); + bool temp_auto_display = auto_display; + auto_display = false; + setColor(BLACK); + fillRect(cx, cy, fontWidth * nc, fontHeight); + setColor(WHITE); + drawString(cx, cy, s); + auto_display = temp_auto_display; + cx += fontWidth * nc; + if (auto_display) + display(); // todo: not very efficient + return nc; + } + + void createChar(uint8_t idx, const char *ptr) { + if (idx >= 0 && idx < NUM_CUSTOM_ICONS) + custom_chars[idx] = ptr; + } + + void createChar(unsigned char idx, const unsigned char *ptr) { + createChar(idx, (const char *)ptr); + } + + void setAutoDisplay(bool v) { auto_display = v; } private: - int file = -1; - bool auto_display = false; - uint8_t cx, cy = 0; - uint8_t fontWidth, fontHeight; - const char *custom_chars[NUM_CUSTOM_ICONS]; - uint8_t frame[1024]; - int i2cd; - bool color; - uint8_t *font; + int file = -1; + bool auto_display = false; + uint8_t cx, cy = 0; + uint8_t fontWidth, fontHeight; + const char *custom_chars[NUM_CUSTOM_ICONS]; + uint8_t frame[1024]; + int i2cd; + bool color; + uint8_t *font; - I2CDevice i2c; - unsigned char _addr; + I2CDevice i2c; - unsigned char height; - unsigned char width; + unsigned char height; + unsigned char width; - void clear_buffer() { memset(frame, 0x00, sizeof(frame)); } + void clear_buffer() { memset(frame, 0x00, sizeof(frame)); } - int ssd1306_command(unsigned char command) { return i2c.send(0x00, command); } + int ssd1306_command(unsigned char command) { return i2c.send(0x00, command); } - int ssd1306_data(unsigned char value) { return i2c.send(0x40, value); } + int ssd1306_data(unsigned char value) { return i2c.send(0x40, value); } }; #endif - -#endif // SSD1306_DISPLAY_H diff --git a/ads1115.cpp b/ads1115.cpp new file mode 100644 index 00000000..2b99d2bb --- /dev/null +++ b/ads1115.cpp @@ -0,0 +1,128 @@ +#include "ads1115.h" + +#if defined(USE_SENSORS) + +#if defined(ESP8266) +ADS1115::ADS1115(uint8_t address, TwoWire& wire) : _address(address), _wire(&wire) {} +ADS1115::ADS1115(uint8_t address) : ADS1115(address, Wire) {} + +bool ADS1115::begin() { + if ((this->_address < 0x48) || (this->_address > 0x4B)) { + return false; + } + this->_wire->beginTransmission(_address); + return (this->_wire->endTransmission() == 0); +} + + +void ADS1115::_write_register(uint8_t reg, uint16_t value) { + this->_wire->beginTransmission(this->_address); + this->_wire->write(reg); + this->_wire->write((uint8_t)(value >> 8)); + this->_wire->write((uint8_t)(value & 0xFF)); + this->_wire->endTransmission(); +} + +uint16_t ADS1115::_read_register(uint8_t reg) { + this->_wire->beginTransmission(this->_address); + this->_wire->write(reg); + if (!this->_wire->endTransmission(false)) { + if (this->_wire->requestFrom((int)_address, (int)2) == 2) { + uint16_t val = ((uint16_t)this->_wire->read()) << 8; + val += (uint16_t)this->_wire->read(); + return val; + } + } + + return 0; +} + +#else // OSPI +ADS1115::ADS1115(uint8_t address, I2CBus& bus) : _address(address), _i2c(bus, address) {} +ADS1115::ADS1115(uint8_t address) : ADS1115(address, Bus) {} + +bool ADS1115::begin() { + if ((this->_address < 0x48) || (this->_address > 0x4B)) { + return false; + } + if (this->_i2c.detect() < 0) { + return false; + } + return true; +} + +void ADS1115::_write_register(uint8_t reg, uint16_t value) { + this->_i2c.send_word(reg, this->swap_reg(value)); +} + +uint16_t ADS1115::_read_register(uint8_t reg) { + return this->swap_reg((uint16_t)(this->_i2c.read_word(reg) & 0xFFFF)); +} +#endif + +int16_t ADS1115::get_pin_value(uint8_t pin) { + this->request_pin(pin); + ulong start = millis(); + while (this->is_busy()) { + // if ((millis() - start) > 11) { + if ((millis() - start) > 18) { + return 0; + } + +#if defined(ESP8266) + yield(); +#else + delay(1); +#endif + } + + return this->get_value(); +} + +void ADS1115::request_pin(uint8_t pin) { + uint16_t config = 0x8000 | ((4 + ((uint16_t)pin)) << 12) | 0x0100 | (4 << 5); + this->_write_register(0x01, config); +} + +ADS1115Sensor::ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, ADS1115** sensors, uint8_t sensor_index, uint8_t pin) : +Sensor(interval, min, max, scale, offset, name, unit, flags), +sensor_index(sensor_index), +pin(pin), +sensors(sensors) {} + +void ADS1115Sensor::emit_extra_json(BufferFiller *bfill) { + bfill->emit_p(PSTR("{\"pin\":$D}"), ((this->sensor_index << 2) + this->pin + 1)); +} + +void ADS1115Sensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"name\":\"ADS1115 Sensor\",\"args\":[{\"name\":\"Pin Number\",\"arg\":\"pin\",\"type\":\"int::[1,16]\",\"default\":\"1\",\"extra\":[]}]}")); +} + +double ADS1115Sensor::get_initial_value() { + return 0.0; +} + +double ADS1115Sensor::_get_raw_value() { + if (this->sensors[sensor_index] == nullptr) { + return 0.0; + } + else { + return ((double)this->sensors[sensor_index]->get_pin_value(this->pin)) * ADS1115_SCALE_FACTOR; + } +} + +uint32_t ADS1115Sensor::_serialize_internal(char *buf) { + uint32_t i = 0; + buf[i++] = static_cast(this->sensor_index); + buf[i++] = static_cast(this->pin); + return i; +} + +ADS1115Sensor::ADS1115Sensor(ADS1115 **sensors, char *buf) { + uint32_t i = Sensor::_deserialize(buf); + this->sensor_index = static_cast(buf[i++]); + this->pin = static_cast(buf[i++]); + this->sensors = sensors; +} + +#endif \ No newline at end of file diff --git a/ads1115.h b/ads1115.h new file mode 100644 index 00000000..f474d58a --- /dev/null +++ b/ads1115.h @@ -0,0 +1,80 @@ +#pragma once + +#include "defines.h" + +#if defined(USE_SENSORS) + +#include +#include "sensor.h" + +#define ADS1115_SCALE_FACTOR (6144.0 / 32768.0) + +#if defined(ESP8266) +#include +#include +#else +#include "i2cd.h" +#endif + +class ADS1115 { +public: +#if defined(ESP8266) + ADS1115(uint8_t address, TwoWire& wire); +#else + ADS1115(uint8_t address, I2CBus& bus); +#endif + ADS1115(uint8_t address); + int16_t get_pin_value(uint8_t pin); + bool begin(); + + int16_t get_value() { + return (int16_t) this->_read_register(0x00); + } + + void request_pin(uint8_t pin); + + bool is_busy() { + return (this->_read_register(0x01) & 0x8000) == 0; + } + + private: + uint8_t _address; +#if defined(ESP8266) + TwoWire *_wire; +#else + I2CDevice _i2c; + uint16_t swap_reg(uint16_t val) { + return (val << 8) | (val >> 8); + } +#endif + + void _write_register(uint8_t reg, uint16_t value); + uint16_t _read_register(uint8_t reg); +}; + + +class ADS1115Sensor : public Sensor { + public: + ADS1115Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, ADS1115 **sensors, uint8_t sensor_index, uint8_t pin); + ADS1115Sensor(ADS1115 **sensors, char *buf); + + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); + + SensorType get_sensor_type() { + return SensorType::ADS1115; + } + + uint8_t sensor_index; + uint8_t pin; + + double get_initial_value(); + + private: + double _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + ADS1115 **sensors; +}; + +#endif \ No newline at end of file diff --git a/bfiller.h b/bfiller.h new file mode 100644 index 00000000..57774feb --- /dev/null +++ b/bfiller.h @@ -0,0 +1,83 @@ +#pragma once + +#include "utils.h" + +#if defined(ESP8266) +#include +#else +#include +#include +#include +#endif + +class BufferFiller { + char *start; //!< Pointer to start of buffer + char *ptr; //!< Pointer to cursor position + size_t len; +public: + BufferFiller () {} + BufferFiller (char *buf, size_t buffer_len) { + start = buf; + ptr = buf; + len = buffer_len; + } + + char* buffer () const { return start; } + size_t length () const { return len; } + unsigned int position () const { return ptr - start; } + + void emit_p(PGM_P fmt, ...) { + va_list ap; + va_start(ap, fmt); + for (;;) { + char c = pgm_read_byte(fmt++); + if (c == 0) + break; + if (c != '$') { + *ptr++ = c; + continue; + } + c = pgm_read_byte(fmt++); + switch (c) { + case 'D': + // itoa(va_arg(ap, int), (char*) ptr, 10); // ray + snprintf((char*) ptr, len - position(), "%d", va_arg(ap, int)); + break; + case 'E': //Double + sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); + break; + case 'L': + // ultoa(va_arg(ap, uint32_t), (char*) ptr, 10); + snprintf((char*) ptr, len - position(), "%lu", (unsigned long) va_arg(ap, uint32_t)); + break; + case 'S': + strcpy((char*) ptr, va_arg(ap, const char*)); + break; + case 'X': { + char d = va_arg(ap, int); + *ptr++ = dec2hexchar((d >> 4) & 0x0F); + *ptr++ = dec2hexchar(d & 0x0F); + } + continue; + case 'F': { + PGM_P s = va_arg(ap, PGM_P); + char d; + while ((d = pgm_read_byte(s++)) != 0) + *ptr++ = d; + continue; + } + case 'O': { + uint16_t oid = va_arg(ap, int); + file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); + } + break; + default: + *ptr++ = c; + continue; + } + ptr += strlen((char*) ptr); + } + *(ptr)=0; + va_end(ap); + } +}; diff --git a/ch224.h b/ch224.h index 9dba85bb..d741a233 100644 --- a/ch224.h +++ b/ch224.h @@ -1,7 +1,5 @@ #pragma once -#if defined(ESP8266) - #include #include @@ -232,5 +230,3 @@ class CH224 { uint8_t _pps_apdo_count; uint8_t _avs_apdo_count; }; - -#endif \ No newline at end of file diff --git a/defines.h b/defines.h index e92b4eab..d97d4e24 100755 --- a/defines.h +++ b/defines.h @@ -20,13 +20,11 @@ * along with this program. If not, see * . */ +#pragma once -#ifndef _DEFINES_H -#define _DEFINES_H +#define ENABLE_DEBUG // enable serial debug -//#define ENABLE_DEBUG // enable serial debug - -typedef unsigned long ulong; +typedef unsigned long ulong; // TODO: remove and replace by uint32_t for cross-platform consistency #define TMP_BUFFER_SIZE 320 // scratch buffer size @@ -35,7 +33,7 @@ typedef unsigned long ulong; // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 4 // Firmware minor version +#define OS_FW_MINOR 5 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -55,6 +53,9 @@ typedef unsigned long ulong; #define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files +#define SENSORS_FILENAME "sens.dat" // sensor data file +#define SENSORS_LOG_FILENAME "senslog.dat" // name base for all sensor files +#define SENADJ_FILENAME "senadj.dat" // sensor adjustment data for programs file /** Station macro defines */ #define STN_TYPE_STANDARD 0x00 // standard solenoid station @@ -139,7 +140,7 @@ enum { #define LED_SLOW_BLINK 500 /** Storage / zone expander defines */ -#if defined(ARDUINO) +#if defined(ESP8266) #define MAX_EXT_BOARDS 8 // maximum number of 8-zone expanders (each 16-zone expander counts as 2) #else #define MAX_EXT_BOARDS 24 // allow more zones for linux-based firmwares @@ -150,6 +151,12 @@ enum { #define STATION_NAME_SIZE 32 // maximum number of characters in each station name #define MAX_SOPTS_SIZE 320 // maximum string option size +#define MAX_SENSORS 64 +#define SENSOR_LOG_PER_FILE 1024 +#define SENSOR_LOG_FILE_COUNT 32 +#define MAX_SENSOR_LOG_COUNT (SENSOR_LOG_PER_FILE * SENSOR_LOG_FILE_COUNT) +#define SENSOR_LOG_ITEM_SIZE (sizeof(time_os_t) + sizeof(float) + 1 + 1) + #define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) /** Default string option values */ @@ -172,14 +179,6 @@ enum { #define DEFAULT_LATCH_BOOST_VOLTAGE 9 // default latch boost voltage in volt #define DEFAULT_TARGET_PD_VOLTAGE 75 // default target voltage (unit: 100mV, so 75 means 7500mV ot 7.5V) -#if (defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__)) - #define OS_AVR -#else // all non-AVR platforms support OTF, EMAIL and HTTPS - #define USE_OTF - #define SUPPORT_EMAIL - #define SUPPORT_HTTPS -#endif - /* Weather Adjustment Methods */ enum { WEATHER_METHOD_MANUAL = 0, @@ -317,56 +316,7 @@ enum { #undef OS_HW_VERSION /** Hardware defines */ -#if defined(OS_AVR) // for OS 2.3 - - #define OS_HW_VERSION (OS_HW_VERSION_BASE+23) - #define PIN_FREE_LIST {2,10,12,13,14,15,18,19} // Free GPIO pins - - // hardware pins - #define PIN_BUTTON_1 31 // button 1 - #define PIN_BUTTON_2 30 // button 2 - #define PIN_BUTTON_3 29 // button 3 - #define PIN_RFTX 28 // RF data pin - #define PORT_RF PORTA - #define PINX_RF PINA3 - #define PIN_SR_LATCH 3 // shift register latch pin - #define PIN_SR_DATA 21 // shift register data pin - #define PIN_SR_CLOCK 22 // shift register clock pin - #define PIN_SR_OE 1 // shift register output enable pin - - // regular 16x2 LCD pin defines - #define PIN_LCD_RS 19 // LCD rs pin - #define PIN_LCD_EN 18 // LCD enable pin - #define PIN_LCD_D4 20 // LCD d4 pin - #define PIN_LCD_D5 21 // LCD d5 pin - #define PIN_LCD_D6 22 // LCD d6 pin - #define PIN_LCD_D7 23 // LCD d7 pin - #define PIN_LCD_BACKLIGHT 12 // LCD backlight pin - #define PIN_LCD_CONTRAST 13 // LCD contrast pin - - // DC controller pin defines - #define PIN_BOOST 20 // booster pin - #define PIN_BOOST_EN 23 // boost voltage enable pin - - #define PIN_ETHER_CS 4 // Ethernet controller chip select pin - #define PIN_SENSOR1 11 // - #define PIN_SD_CS 0 // SD card chip select pin - #define PIN_FLOWSENSOR_INT 1 // flow sensor interrupt pin (INT1) - #define PIN_EXP_SENSE 4 // expansion board sensing pin (A4) - #define PIN_CURR_SENSE 7 // current sensing pin (A7) - #define PIN_CURR_DIGITAL 24 // digital pin index for A7 - - #define ETHER_BUFFER_SIZE 2048 - - #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset - - #define pinModeExt pinMode - #define digitalReadExt digitalRead - #define digitalWriteExt digitalWrite - - #define USE_DISPLAY - #define USE_LCD -#elif defined(ESP8266) // for ESP8266 +#if defined(ESP8266) // for ESP8266 #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) #define IOEXP_PIN 0x80 // base for pins on main IO expander @@ -453,8 +403,9 @@ enum { #define V2_PIN_BOOST_SEL IOEXP_PIN+8 #define USE_DISPLAY - #define USE_SSD1306 - + #define USE_ADS1115 + #define USE_SENSORS + #elif defined(OSPI) // for OSPi #define OS_HW_VERSION OSPI_HW_VERSION_BASE @@ -477,7 +428,8 @@ enum { #define SCL 0 #define USE_DISPLAY - #define USE_SSD1306 + #define USE_ADS1115 + #define USE_SENSORS #else // for demo / simulation // use fake hardware pins @@ -500,7 +452,7 @@ enum { #if defined(ENABLE_DEBUG) /** Serial debug functions */ - #if defined(ARDUINO) + #if defined(ESP8266) #define DEBUG_BEGIN(x) {Serial.begin(x);} #define DEBUG_PRINT(x) {Serial.print(x);} #define DEBUG_PRINTLN(x) {Serial.println(x);} @@ -516,7 +468,7 @@ enum { #else - #if defined(ARDUINO) + #if defined(ESP8266) // work-around for PIN_SENSOR1 on OS3.2 and above #define DEBUG_BEGIN(x) {Serial.begin(115200); Serial.end();} #else @@ -528,8 +480,8 @@ enum { #endif -/** Re-define avr-specific (e.g. PGM) types to use standard types */ -#if !defined(ARDUINO) +/** Re-define arduino-specific (e.g. PGM) types to use standard types */ +#if !defined(ESP8266) #include #include #include @@ -579,5 +531,3 @@ enum { #define BUTTON_WAIT_HOLD 2 // wait until button hold time expires #define DISPLAY_MSG_MS 2000 // message display time (milliseconds) - -#endif // _DEFINES_H diff --git a/docs/docs/index.md b/docs/docs/index.md index f3fdf367..a270b733 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -72,6 +72,6 @@ The firmware compilation instructions below are for OpenSprinkler **v3.x and v2. 1. In VS Code, click `File -> Open Folder` and select the `OpenSprinkler-Firmware` folder. 2. PlatformIO will recognize the `platformio.ini` file in that folder, which contains all the libraries and settings needed to compile the firmware. 3. Click the **PlatformIO: Build** button (with the checkmark icon ✓) in the blue status bar at the bottom of the screen to build the firmware. -4. The default build environment is for OpenSprinkler v3.x (`env:os3x_esp8266`). To build for OpenSprinkler v2.3, switch to `env:os23_atmega1284p`. +4. The default build environment is for OpenSprinkler v3.x (`env:os3x_esp8266`).
diff --git a/espconnect.h b/espconnect.h index edf595c1..e67613d7 100644 --- a/espconnect.h +++ b/espconnect.h @@ -17,11 +17,7 @@ * along with this program. If not, see * . */ - -#if defined(ESP8266) - -#ifndef _ESP_CONNECT_H -#define _ESP_CONNECT_H +#pragma once #include #include @@ -34,6 +30,3 @@ String scan_network(); void start_network_ap(const char *ssid, const char *pass); void start_network_sta(const char *ssid, const char *pass, int32_t channel=0, const unsigned char *mac=NULL); void start_network_sta_with_ap(const char *ssid, const char *pass, int32_t channel=0, const unsigned char *mac=NULL); -#endif - -#endif diff --git a/external/influxdb-cpp b/external/influxdb-cpp new file mode 160000 index 00000000..75a4ec9b --- /dev/null +++ b/external/influxdb-cpp @@ -0,0 +1 @@ +Subproject commit 75a4ec9bb216e4d4a073c363e5ba10f95900e3ec diff --git a/font.h b/font.h index dc1ad7f1..0a2b3732 100644 --- a/font.h +++ b/font.h @@ -1,7 +1,6 @@ // Created by http://oleddisplay.squix.ch/ Consider a donation // In case of problems make sure that you are using the font file with the correct version! -#ifndef FONT_H -#define FONT_H +#pragma once const unsigned char Monospaced_plain_13[] PROGMEM = { 0x08, // Width: 8 @@ -459,5 +458,3 @@ const unsigned char Monospaced_plain_13[] PROGMEM = { 0x00,0x00,0x00,0xF8,0xFF,0x00,0x80,0x18,0x00,0x40,0x10,0x00,0x40,0x10,0x00,0xC0,0x18,0x00,0x80,0x0F, // 254 0x00,0x00,0x00,0xC0,0x80,0x00,0x08,0x87,0x00,0x00,0xFC,0x00,0x00,0x38,0x00,0x08,0x07,0x00,0xC0,0x01 // 255 }; - -#endif \ No newline at end of file diff --git a/gpio.cpp b/gpio.cpp index 0b0df751..8656d24b 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -25,8 +25,6 @@ #if defined(ARDUINO) -#if defined(ESP8266) - #include #include "defines.h" @@ -173,7 +171,6 @@ unsigned char digitalReadExt(unsigned char pin) { return digitalRead(pin); } } -#endif #elif defined(OSPI) diff --git a/gpio.h b/gpio.h index d9b028b8..1710b10b 100644 --- a/gpio.h +++ b/gpio.h @@ -21,13 +21,10 @@ * . */ -#ifndef GPIO_H -#define GPIO_H +#pragma once #if defined(ARDUINO) -#if defined(ESP8266) - #include "Arduino.h" // PCA9555 register defines @@ -45,6 +42,7 @@ class IOEXP { public: IOEXP(uint8_t addr=255) { address = addr; type = IOEXP_TYPE_NONEXIST; } + virtual ~IOEXP() {} virtual void pinMode(uint8_t pin, uint8_t IOMode) { } virtual uint16_t i2c_read(uint8_t reg) { return 0xFFFF; } @@ -113,8 +111,6 @@ void pinModeExt(unsigned char pin, unsigned char mode); void digitalWriteExt(unsigned char pin, unsigned char value); unsigned char digitalReadExt(unsigned char pin); -#endif // ESP8266 - #else #include @@ -145,4 +141,3 @@ void attachInterrupt(int pin, const char* mode, void (*isr)(void)); #endif -#endif // GPIO_H diff --git a/i2cd.cpp b/i2cd.cpp new file mode 100644 index 00000000..3bc67916 --- /dev/null +++ b/i2cd.cpp @@ -0,0 +1,5 @@ +#if defined(OSPI) +#include "i2cd.h" + +I2CBus Bus; +#endif \ No newline at end of file diff --git a/i2cd.h b/i2cd.h index 53d46f9a..e4d9d6b0 100644 --- a/i2cd.h +++ b/i2cd.h @@ -1,5 +1,6 @@ -#ifndef I2CD_H -#define I2CD_H +#pragma once + +#if defined(OSPI) #include #include @@ -7,92 +8,177 @@ extern "C" { #include #include +} #include #include "utils.h" -} + +class I2CBus { +public: + I2CBus() {} + + int begin(const char *bus) { + _file = open(bus, O_RDWR); + if (_file < 0) { + return _file; + } + + return 0; + } + + int begin() { return begin(getDefaultBus()); } + + int send(unsigned char addr, unsigned char reg, unsigned char data) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_write_byte_data(_file, reg, data); + } + + int send_transaction(unsigned char addr, unsigned char transaction_id, unsigned char transaction_buffer_length, unsigned char *transaction_buffer) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_write_i2c_block_data( + _file, transaction_id, transaction_buffer_length, transaction_buffer); + } + + int read(unsigned char addr, unsigned char reg, unsigned char length, unsigned char *values) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_read_i2c_block_data(_file, reg, length, values); + } + + int send_word(unsigned char addr, unsigned char reg, unsigned short data) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_write_word_data(_file, reg, data); + } + + int read_word(unsigned char addr, unsigned char reg) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + return i2c_smbus_read_word_data(_file, reg); + } + + int detect(unsigned char addr) { + int res = ioctl(_file, I2C_SLAVE, addr); + if (res < 0) return -1; + + res = i2c_smbus_read_byte(_file); + if (res < 0) { + return res; + } else { + return 0; + } + } + +private: + int _file = -1; + + const char *getDefaultBus() { + switch (get_board_type()) { + case BoardType::RaspberryPi_bcm2712: + case BoardType::RaspberryPi_bcm2711: + case BoardType::RaspberryPi_bcm2837: + case BoardType::RaspberryPi_bcm2836: + case BoardType::RaspberryPi_bcm2835: + return "/dev/i2c-1"; + case BoardType::Unknown: + case BoardType::RaspberryPi_Unknown: + default: + return "/dev/i2c-0"; + } + } +}; class I2CDevice { public: - I2CDevice() {} - - int begin(const char *bus, unsigned char addr) { - _file = open(bus, O_RDWR); - if (_file < 0) { - return _file; - } - - return ioctl(_file, I2C_SLAVE, addr); - } - - int begin(unsigned char addr) { return begin(getDefaultBus(), addr); } - - int begin_transaction(unsigned char id) { - if (transaction) { - return -1; - } else { - transaction_id = id; - transaction = true; - memset(transaction_buffer, 0x00, sizeof(transaction_buffer)); - transaction_buffer_length = 0; - return 0; - } - } - - int end_transaction() { - if (transaction) { - transaction = false; - return send_transaction(); - } else { - return -1; - } - } - - int send(unsigned char reg, unsigned char data) { - if (transaction) { - if (reg != transaction_id) { - return -1; - } - - int res = 0; - if (transaction_buffer_length >= sizeof(transaction_buffer)) { - res = send_transaction(); - transaction_buffer_length = 0; - } - - transaction_buffer[transaction_buffer_length] = data; - transaction_buffer_length++; - return res; - } else { - return i2c_smbus_write_byte_data(_file, reg, data); - } - } + I2CDevice(I2CBus &bus, unsigned char addr) : _addr(addr), _bus(&bus) {} + + bool detect() { + return _bus->detect(_addr); + } + + int begin_transaction(unsigned char id) { + if (transaction) { + return -1; + } else { + transaction_id = id; + transaction = true; + memset(transaction_buffer, 0x00, sizeof(transaction_buffer)); + transaction_buffer_length = 0; + return 0; + } + } + + int end_transaction() { + if (transaction) { + transaction = false; + return send_transaction(); + } else { + return -1; + } + } + + int send(unsigned char reg, unsigned char data) { + if (transaction) { + if (reg != transaction_id) { + return -1; + } + + int res = 0; + if (transaction_buffer_length >= sizeof(transaction_buffer)) { + res = send_transaction(); + transaction_buffer_length = 0; + } + + transaction_buffer[transaction_buffer_length] = data; + transaction_buffer_length++; + return res; + } else { + return _bus->send(_addr, reg, data); + } + } + + int read(unsigned char reg, unsigned char length, unsigned char *values) { + return _bus->read(_addr, reg, length, values); + } + + int send_word(unsigned char reg, unsigned short data) { + return _bus->send_word(_addr, reg, data); + } + + int read_word(unsigned char reg) { + return _bus->read_word(_addr, reg); + } private: - int _file = -1; - bool transaction = false; - unsigned char transaction_id = 0; - unsigned char transaction_buffer[32]; - unsigned char transaction_buffer_length = 0; - - const char *getDefaultBus() { - switch (get_board_type()) { - case BoardType::RaspberryPi_bcm2712: - case BoardType::RaspberryPi_bcm2711: - case BoardType::RaspberryPi_bcm2837: - case BoardType::RaspberryPi_bcm2836: - case BoardType::RaspberryPi_bcm2835: - return "/dev/i2c-1"; - case BoardType::Unknown: - case BoardType::RaspberryPi_Unknown: - default: - return "/dev/i2c-0"; - } - } - - int send_transaction() { - return i2c_smbus_write_i2c_block_data( - _file, transaction_id, transaction_buffer_length, transaction_buffer); - } + I2CBus *_bus; + unsigned char _addr; + + bool transaction = false; + unsigned char transaction_id = 0; + unsigned char transaction_buffer[32]; + unsigned char transaction_buffer_length = 0; + + const char *getDefaultBus() { + switch (get_board_type()) { + case BoardType::RaspberryPi_bcm2712: + case BoardType::RaspberryPi_bcm2711: + case BoardType::RaspberryPi_bcm2837: + case BoardType::RaspberryPi_bcm2836: + case BoardType::RaspberryPi_bcm2835: + return "/dev/i2c-1"; + case BoardType::Unknown: + case BoardType::RaspberryPi_Unknown: + default: + return "/dev/i2c-0"; + } + } + + int send_transaction() { + return _bus->send_transaction(_addr, transaction_id, transaction_buffer_length, transaction_buffer); + } }; -#endif // I2CD_H \ No newline at end of file +extern I2CBus Bus; + +#endif // I2CD_H diff --git a/images.h b/images.h index adaf517e..02c3ed25 100644 --- a/images.h +++ b/images.h @@ -1,5 +1,4 @@ -#ifndef IMAGES_H -#define IMAGES_H +#pragma once enum { ICON_ETHER_CONNECTED = 0, @@ -13,7 +12,6 @@ enum { NUM_CUSTOM_ICONS }; -#if defined(ESP8266) || defined(PIN_SENSOR2) enum { LCD_CURSOR_REMOTEXT = 11, LCD_CURSOR_RAINDELAY,// 12 @@ -21,22 +19,8 @@ enum { LCD_CURSOR_SENSOR2, // 14 LCD_CURSOR_NETWORK // 15 }; -#else -enum { - LCD_CURSOR_SENSOR2 = 11, - LCD_CURSOR_REMOTEXT, // 12 - LCD_CURSOR_RAINDELAY,// 13 - LCD_CURSOR_SENSOR1, // 14 - LCD_CURSOR_NETWORK // 15 -}; -#endif - -#if defined(USE_SSD1306) - -#if not defined(ARDUINO) -#define PROGMEM -#endif +#if defined(USE_DISPLAY) #define WiFi_Logo_width 60 #define WiFi_Logo_height 36 @@ -141,102 +125,4 @@ const unsigned char _iconimage_pswitch[] PROGMEM = { */ -#elif defined(USE_LCD) - -const char _iconimage_connected[] PROGMEM = { - B00000, - B00000, - B00000, - B00001, - B00001, - B00101, - B00101, - B10101 -}; - -const char _iconimage_disconnected[] PROGMEM = { - B00000, - B10100, - B01000, - B10101, - B00001, - B00101, - B00101, - B10101 -}; - -const char _iconimage_remotext[] PROGMEM = { - B00000, - B00000, - B00000, - B10001, - B01011, - B00101, - B01001, - B11110 -}; - -const char _iconimage_raindelay[] PROGMEM = { - B00000, - B01110, - B10101, - B10101, - B10111, - B10001, - B10001, - B01110 -}; - -const char _iconimage_rain[] PROGMEM = { - B00000, - B00000, - B00110, - B01001, - B11111, - B00000, - B10101, - B10101 -}; - -const char _iconimage_soil[] PROGMEM = { - B00100, - B00100, - B01010, - B01010, - B10001, - B10001, - B10001, - B01110 -}; - -/* -const unsigned char _iconimage_flow[] PROGMEM = { - B00000, - B00000, - B00000, - B11010, - B10010, - B11010, - B10011, - B00000 -}; - -const unsigned char _iconimage_pswitch[] PROGMEM = { - B00000, - B11000, - B10100, - B11000, - B10010, - B10110, - B00010, - B00111 -}; - -// todo - -*/ - #endif - - -#endif \ No newline at end of file diff --git a/main.cpp b/main.cpp index bcc87495..a6b6852e 100644 --- a/main.cpp +++ b/main.cpp @@ -32,39 +32,25 @@ #include "main.h" #include "notifier.h" -#if defined(ARDUINO) -#include -#endif - -#if defined(ARDUINO) - #if defined(ESP8266) - ESP8266WebServer *update_server = NULL; - DNSServer *dns = NULL; - ENC28J60lwIP enc28j60(PIN_ETHER_CS); // ENC28J60 lwip for wired Ether - Wiznet5500lwIP w5500(PIN_ETHER_CS); // W5500 lwip for wired Ether - lwipEth eth; - bool useEth = false; // tracks whether we are using WiFi or wired Ether connection - #else - EthernetServer *m_server = NULL; - EthernetClient *m_client = NULL; - SdFat sd; // SD card object - bool useEth = true; - #endif +#if defined(ESP8266) + #include + ESP8266WebServer *update_server = NULL; + DNSServer *dns = NULL; + ENC28J60lwIP enc28j60(PIN_ETHER_CS); // ENC28J60 lwip for wired Ether + Wiznet5500lwIP w5500(PIN_ETHER_CS); // W5500 lwip for wired Ether + lwipEth eth; + bool useEth = false; // tracks whether we are using WiFi or wired Ether connection unsigned long getNtpTime(); #else // header and defs for RPI/Linux bool useEth = false; #endif -#if defined(USE_OTF) - OTF::OpenThingsFramework *otf = NULL; -#endif +OTF::OpenThingsFramework *otf = NULL; -#if defined(USE_SSD1306) - #if defined(ESP8266) - static uint16_t led_blink_ms = LED_FAST_BLINK; - #else - static uint16_t led_blink_ms = 0; - #endif +#if defined(ESP8266) +static uint16_t led_blink_ms = LED_FAST_BLINK; +#else +static uint16_t led_blink_ms = 0; #endif #define STRINGIFY(x) #x @@ -213,7 +199,6 @@ void ui_state_machine() { if(millis() - last_usm <= UI_STATE_MACHINE_INTERVAL) { return; } last_usm = millis(); -#if defined(USE_SSD1306) // process screen led static ulong led_toggle_prev = 0; if(led_blink_ms) { @@ -223,7 +208,6 @@ void ui_state_machine() { led_toggle_prev = tm; } } -#endif if (!os.button_timeout) { os.lcd_set_brightness(0); @@ -249,86 +233,68 @@ void ui_state_machine() { if(!ui_confirm(PSTR("Start 2s test?"))) {ui_state = UI_STATE_DEFAULT; break;} manual_start_program(255, 0, QUEUE_OPTION_REPLACE); } else if (digitalReadExt(PIN_BUTTON_2)==0) { // if B2 is pressed while holding B1, display gateway IP - #if defined(USE_SSD1306) - os.lcd.setAutoDisplay(false); - #endif + os.lcd.setAutoDisplay(false); os.lcd.clear(0, 1); os.lcd.setCursor(0, 0); - #if defined(ARDUINO) #if defined(ESP8266) - if (useEth) { os.lcd.print(eth.gatewayIP()); } - else { os.lcd.print(WiFi.gatewayIP()); } - #else - { os.lcd.print(Ethernet.gatewayIP()); } - #endif + if (useEth) { os.lcd.print(eth.gatewayIP()); } + else { os.lcd.print(WiFi.gatewayIP()); } #else - route_t route = get_route(); - char str[INET_ADDRSTRLEN]; + route_t route = get_route(); + char str[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &(route.gateway), str, INET_ADDRSTRLEN); - os.lcd.print(str); + inet_ntop(AF_INET, &(route.gateway), str, INET_ADDRSTRLEN); + os.lcd.print(str); #endif os.lcd.setCursor(0, 1); os.lcd_print_pgm(PSTR("(gwip)")); ui_state = UI_STATE_DISP_IP; - #if defined(USE_SSD1306) - os.lcd.display(); - os.lcd.setAutoDisplay(true); - #endif + os.lcd.display(); + os.lcd.setAutoDisplay(true); } else { // if no other button is clicked, stop all zones if(!ui_confirm(PSTR("Stop all zones?"))) {ui_state = UI_STATE_DEFAULT; break;} reset_all_stations(); } } else { // clicking B1: display device IP and port - #if defined(USE_SSD1306) - os.lcd.setAutoDisplay(false); - #endif + os.lcd.setAutoDisplay(false); os.lcd.clear(0, 1); os.lcd.setCursor(0, 0); - #if defined(ARDUINO) #if defined(ESP8266) - if (useEth) { os.lcd.print(eth.localIP()); } - else { os.lcd.print(WiFi.localIP()); } - #else - { os.lcd.print(Ethernet.localIP()); } - #endif + if (useEth) { os.lcd.print(eth.localIP()); } + else { os.lcd.print(WiFi.localIP()); } #else - route_t route = get_route(); - char str[INET_ADDRSTRLEN]; - in_addr_t ip = get_ip_address(route.iface); + route_t route = get_route(); + char str[INET_ADDRSTRLEN]; + in_addr_t ip = get_ip_address(route.iface); - inet_ntop(AF_INET, &ip, str, INET_ADDRSTRLEN); - os.lcd.print(str); + inet_ntop(AF_INET, &ip, str, INET_ADDRSTRLEN); + os.lcd.print(str); #endif os.lcd.setCursor(0, 1); os.lcd_print_pgm(PSTR(":")); uint16_t httpport = (uint16_t)(os.iopts[IOPT_HTTPPORT_1]<<8) + (uint16_t)os.iopts[IOPT_HTTPPORT_0]; os.lcd.print(httpport); os.lcd_print_pgm(PSTR(" (ip:port)")); - #if defined(USE_OTF) - os.lcd.setCursor(0, 2); - os.lcd_print_pgm(PSTR("OTC:")); - switch(otf->getCloudStatus()) { - case OTF::NOT_ENABLED: - os.lcd_print_pgm(PSTR(" not enabled")); - break; - case OTF::UNABLE_TO_CONNECT: - os.lcd_print_pgm(PSTR("connecting..")); - break; - case OTF::DISCONNECTED: - os.lcd_print_pgm(PSTR("disconnected")); - break; - case OTF::CONNECTED: - os.lcd_print_pgm(PSTR(" Connected")); - break; - } - #endif + os.lcd.setCursor(0, 2); + os.lcd_print_pgm(PSTR("OTC:")); + switch(otf->getCloudStatus()) { + case OTF::NOT_ENABLED: + os.lcd_print_pgm(PSTR(" not enabled")); + break; + case OTF::UNABLE_TO_CONNECT: + os.lcd_print_pgm(PSTR("connecting..")); + break; + case OTF::DISCONNECTED: + os.lcd_print_pgm(PSTR("disconnected")); + break; + case OTF::CONNECTED: + os.lcd_print_pgm(PSTR(" Connected")); + break; + } ui_state = UI_STATE_DISP_IP; - #if defined(USE_SSD1306) - os.lcd.display(); - os.lcd.setAutoDisplay(true); - #endif + os.lcd.display(); + os.lcd.setAutoDisplay(true); } break; case BUTTON_2: @@ -415,15 +381,11 @@ void ui_state_machine() { // ====================== // Setup Function // ====================== -#if defined(ARDUINO) +#if defined(ESP8266) void do_setup() { /* Clear WDT reset flag. */ -#if defined(ESP8266) WiFi.persistent(false); led_blink_ms = LED_FAST_BLINK; -#else - MCUSR &= ~(1< 15) { - // reset after 120 seconds of timeout - sysReset(); - } -} -#endif - #else void initialize_otf(); @@ -550,7 +484,7 @@ void handle_web_request(char *p); ulong currpoll_timeout = 0; void overcurrent_monitor() { -#if defined(ARDUINO) +#if defined(ESP8266) // If a zone is turning on, do immediate overcurrent monitoring here for ~50ms if (curr_alert_sid) { int16_t imax = os.get_imax(); @@ -590,7 +524,11 @@ void do_loop() } } -#if defined(ARDUINO) +#if defined(USE_SENSORS) + os.poll_sensors(); +#endif + +#if defined(ESP8266) { ulong tn = millis(); if((long)(tn-currpoll_timeout) > 0) { // overflow proof timeout @@ -619,8 +557,7 @@ void do_loop() time_os_t curr_time = os.now_tz(); // ====== Process Ethernet packets ====== -#if defined(ARDUINO) // Process Ethernet packets for Arduino - #if defined(ESP8266) +#if defined(ESP8266) // Process Ethernet packets for Arduino static ulong connecting_timeout; switch(os.state) { case OS_STATE_INITIAL: @@ -717,45 +654,6 @@ void do_loop() break; } - #else // AVR - - static unsigned long dhcp_timeout = 0; - if(curr_time > dhcp_timeout) { - Ethernet.maintain(); - dhcp_timeout = curr_time + DHCP_CHECKLEASE_INTERVAL; - } - EthernetClient client = m_server->available(); - if (client) { - ulong cli_timeout = now() + CLIENT_READ_TIMEOUT; - size_t size = 0; - while(client.connected() && now() < cli_timeout) { - size = client.available(); // wait till we have client data available - if(size>0) break; - } - if(size>0) { - size_t len = 0; - while (client.available() && now()0) { - m_client = &client; - ether_buffer[len] = 0; // properly end the buffer - handle_web_request(ether_buffer); - m_client = NULL; - } - } - client.stop(); - } - - wdt_reset(); // reset watchdog timer - wdt_timeout = 0; - - #endif - ui_state_machine(); #else // Process Ethernet packets for RPI/LINUX @@ -871,6 +769,17 @@ void do_loop() // check through all programs for(pid=0; pidget_adjustment_factor(os.sensors); + delete adj; + } + #else + double adjustment = 1.0; + #endif bool will_delete = false; unsigned char runcount = prog.check_match(curr_time, &will_delete); if(runcount>0) { @@ -909,8 +818,9 @@ void do_loop() if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) continue; + ulong dur = (ulong)((double)prog.durations[sid] * adjustment); // if station has non-zero water time and the station is not disabled - if (prog.durations[sid] && !(os.attrib_dis[bid]&(1< os.checkwt_lasttime + CHECK_WEATHER_TIMEOUT)) { os.checkwt_lasttime = ntz; - #if defined(ARDUINO) + #if defined(USE_DISPLAY) if (!ui_state) { os.lcd_print_line_clear_pgm(PSTR("Check Weather..."),1); } @@ -1337,7 +1247,7 @@ void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shif if (!station_bit) { return; } } //else { return; } - #if defined(ARDUINO) + #if defined(ESP8266) int16_t current = (int16_t)os.read_current(true); // use ema value int16_t imin = os.get_imin(); // if current is less than imin threshold and hardware type is AC or DC @@ -1685,6 +1595,7 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo boolean match_found = false; ProgramStruct prog; ulong dur; + double adjustment = 1.0; unsigned char sid, bid, s; unsigned char ns = os.nstations; unsigned char order[ns]; @@ -1696,6 +1607,13 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo unsigned char wl = 100; if ((pid>0)&&(pid<255)) { pd.read(pid-1, &prog); + #if defined(USE_SENSORS) + SensorAdjustment *adj = os.get_sensor_adjust(pid-1); + if (adj) { + adjustment = adj->get_adjustment_factor(os.sensors); + delete adj; + } + #endif if(uwt) wl = os.iopts[IOPT_WATER_PERCENTAGE]; notif.add(NOTIFY_PROGRAM_SCHED, pid-1, wl, 1); // get station ordering from program name @@ -1710,9 +1628,13 @@ void manual_start_program(unsigned char pid, unsigned char uwt, unsigned char qo if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) continue; dur = 60; - if(pid==255) dur=2; - else if(pid>0) + if(pid==255) { + dur=2; + } else if (pid>0) { dur = water_time_resolve(prog.durations[sid]); + dur = (ulong)((double)dur * adjustment); + } + dur = dur * wl / 100; if(dur>0 && !(os.attrib_dis[bid]&(1<rmRfStar(); - } - } else { - // delete a single log file - make_logfile_name(name); - if (!sd.exists(tmp_buffer)) return; - sd.remove(tmp_buffer); - } - #endif - #else // delete_log implementation for RPI/LINUX if (strncmp(name, "all", 3) == 0) { // delete the log folder @@ -1979,57 +1855,13 @@ void delete_log(char *name) { * If not, it re-initializes Ethernet controller. */ static void check_network() { -#if defined(OS_AVR) - // do not perform network checking if the controller has just started, or if a program is running - if (os.status.program_busy) {return;} - - // check network condition periodically - if (os.status.req_network) { - os.status.req_network = 0; - // change LCD icon to indicate it's checking network - if (!ui_state) { - os.lcd.setCursor(LCD_CURSOR_NETWORK, 1); - os.lcd.write('>'); - } - - - boolean failed = false; - // todo: ping gateway ip - /*ether.clientIcmpRequest(ether.gwip); - ulong start = millis(); - // wait at most PING_TIMEOUT milliseconds for ping result - do { - ether.packetLoop(ether.packetReceive()); - if (ether.packetLoopIcmpCheckReply(ether.gwip)) { - failed = false; - break; - } - } while((long)(millis() - start) < PING_TIMEOUT);*/ - if (failed) { - if(os.status.network_fails<3) os.status.network_fails++; - // clamp it to 6 - //if (os.status.network_fails > 6) os.status.network_fails = 6; - } - else os.status.network_fails=0; - // if failed more than 3 times, restart - if (os.status.network_fails==3) { - // mark for safe restart - os.nvdata.reboot_cause = REBOOT_CAUSE_NETWORK_FAIL; - os.status.safe_reboot = 1; - } else if (os.status.network_fails>2) { - // if failed more than twice, try to reconnect - if (os.start_network()) - os.status.network_fails=0; - } - } -#else + // TODO: // nothing to do for other platforms -#endif } /** Perform NTP sync */ static void perform_ntp_sync() { -#if defined(ARDUINO) +#if defined(ESP8266) // do not perform ntp if this option is disabled, or if a program is currently running if (!os.iopts[IOPT_USE_NTP] || os.status.program_busy) return; // do not perform ntp if network is not connected @@ -2061,7 +1893,7 @@ static void perform_ntp_sync() { #endif } -#if !defined(ARDUINO) // main function for RPI/LINUX +#if !defined(ESP8266) // main function for RPI/LINUX int main(int argc, char *argv[]) { // Disable buffering to work with systemctl journal setvbuf(stdout, NULL, _IOLBF, 0); diff --git a/main.h b/main.h index a7ef7ebe..9a9bd168 100644 --- a/main.h +++ b/main.h @@ -24,8 +24,7 @@ */ -#ifndef _MAIN_H -#define _MAIN_H 1 +#pragma once void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shift=0); void turn_off_running_station_immediate(unsigned char sid, time_os_t curr_time, unsigned char shift=0); @@ -35,6 +34,4 @@ void reset_all_stations(bool running_ones_only=false); void reset_all_stations_immediate(bool running_ones_only=false); void delete_log(char *name); void write_log(unsigned char type, time_os_t curr_time); -void make_logfile_name(char *name); - -#endif // _MAIN_H +void make_logfile_name(char *name); \ No newline at end of file diff --git a/mqtt.cpp b/mqtt.cpp index b8a649ef..d088cc37 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -21,13 +21,9 @@ * . */ -#if defined(ARDUINO) +#if defined(ESP8266) #include - #if defined(ESP8266) - #include - #else - #include - #endif + #include #define MQTT_SOCKET_TIMEOUT 5 #include @@ -52,7 +48,7 @@ // Debug routines to help identify any blocking of the event loop for an extended period #if defined(ENABLE_DEBUG) - #if defined(ARDUINO) + #if defined(ESP8266) #include "TimeLib.h" #define DEBUG_TIMESTAMP(msg, ...) {time_os_t t = os.now_tz(); Serial.printf("%02d-%02d-%02d %02d:%02d:%02d - ", year(t), month(t), day(t), hour(t), minute(t), second(t));} #else @@ -480,14 +476,9 @@ void OSMqtt::loop(void) { #endif } -/**************************** ARDUINO ********************************************/ -#if defined(ARDUINO) - - #if defined(ESP8266) - WiFiClient wifiClient; - #else - EthernetClient ethClient; - #endif +/**************************** ESP8266 ********************************************/ +#if defined(ESP8266) +WiFiClient wifiClient; int OSMqtt::_init(void) { Client * client = NULL; diff --git a/mqtt.h b/mqtt.h index 65c2fd0a..d39cdd38 100644 --- a/mqtt.h +++ b/mqtt.h @@ -21,8 +21,7 @@ * . */ -#ifndef _MQTT_H -#define _MQTT_H +#pragma once class OSMqtt { private: @@ -57,4 +56,3 @@ class OSMqtt { static char* get_sub_topic() { return _sub_topic; } }; -#endif // _MQTT_H diff --git a/notifier.cpp b/notifier.cpp index 7fefbb75..08b096b6 100644 --- a/notifier.cpp +++ b/notifier.cpp @@ -130,8 +130,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { #define DEFAULT_EMAIL_PORT 465 // parse email variables - #if defined(SUPPORT_EMAIL) - // define email variables ArduinoJson::JsonDocument doc; // make sure this has the same scope of email_x variables to prevent use after free const char *email_host = NULL; const char *email_username = NULL; @@ -163,8 +161,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { email_recipient= doc["recipient"]; } } - #endif - + #if defined(ESP8266) EMailSender::EMailMessage email_message; #else @@ -175,13 +172,11 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { #endif bool email_enabled = false; -#if defined(SUPPORT_EMAIL) if(!email_en){ // todo: this should be simplified email_enabled = false; }else{ email_enabled = true; } -#endif // if none if enabled, return here if ((!ifttt_enabled) && (!email_enabled) && (!os.mqtt.enabled())) @@ -236,11 +231,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"duration\":%d"), (int)fval); if (os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"flow\":%d.%02d"), (int)gpm, (int)(gpm*100)%100); - #else snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"flow\":%.2f"), gpm); - #endif } } strcat_P(payload, PSTR("}")); @@ -256,11 +247,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" Flow rate: %d.%02d"), (int)gpm, (int)(gpm*100)%100); - #else snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" Flow rate: %.2f"), gpm); - #endif } if(email_enabled) { email_message.subject += PSTR("station event"); } } @@ -313,12 +300,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { //Format mqtt message snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d/alert/flow"), lval); float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%d.%02d,\"duration\":%d,\"alert_setpoint\":%d.%02d}"), (int)gpm, (int)(gpm*100)%100, - (int)fval, (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint*100)%100); - #else snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%.2f,\"duration\":%d,\"alert_setpoint\":%.4f}"), gpm, (int)fval, flow_gpm_alert_setpoint); - #endif } @@ -328,7 +310,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" strcat_P(postval, PSTR("at ")); time_os_t curr_time = os.now_tz(); - #if defined(ARDUINO) + #if defined(ESP8266) tmElements_t tm; breakTime(curr_time, tm); snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), @@ -351,13 +333,8 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { strcat_P(postval, PSTR(" FLOW ALERT!")); float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" | Flow rate: %d.%02d > Flow alert setpoint: %d.%02d"), - (int)gpm, (int)(gpm*100)%100, (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint*100)%100); - #else snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" | Flow rate: %.2f > Flow alert setpoint: %.4f"), gpm, flow_gpm_alert_setpoint); - #endif if(email_enabled) { email_message.subject += PSTR("- FLOW ALERT"); } @@ -451,18 +428,10 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { float vol = lval*flowrate100/100.f; if (os.mqtt.enabled()) { strcpy_P(topic, PSTR("sensor/flow")); - #if defined(OS_AVR) - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"count\":%d,\"volume\":%d.%02d}"), (int)lval, (int)vol, (int)(vol*100)%100); - #else snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"count\":%d,\"volume\":%.2f}"), (int)lval, vol); - #endif } if (ifttt_enabled || email_enabled) { - #if defined(OS_AVR) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("Flow count: %d, volume: %d.%02d"), (int)lval, (int)vol, (int)(vol*100)%100); - #else snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("Flow count: %d, volume: %.2f"), (int)lval, vol); - #endif if(email_enabled) { email_message.subject += PSTR("flow sensor event"); } } } @@ -496,7 +465,7 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" strcat_P(postval, PSTR("at ")); time_os_t curr_time = os.now_tz(); - #if defined(ARDUINO) + #if defined(ESP8266) tmElements_t tm; breakTime(curr_time, tm); snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), @@ -565,9 +534,8 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":\"started\",\"cause\":%d}"), (int)os.last_reboot_cause); } if (ifttt_enabled || email_enabled) { - #if defined(ARDUINO) + #if defined(ESP8266) snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("rebooted. Cause: %d. Device IP: "), os.last_reboot_cause); - #if defined(ESP8266) { IPAddress _ip; if (useEth) { @@ -579,9 +547,6 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; ip2string(postval, TMP_BUFFER_SIZE, ip); } - #else - ip2string(postval, TMP_BUFFER_SIZE, &(Ethernet.localIP()[0])); - #endif #else strcat_P(postval, PSTR("controller process restarted.")); #endif @@ -610,15 +575,13 @@ void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { if(email_enabled){ email_message.message = strchr(postval, 'O'); // ad-hoc: remove the value1 part from the ifttt message - #if defined(ARDUINO) - #if defined(ESP8266) - if(email_host && email_username && email_password && email_recipient) { // make sure all are valid - EMailSender emailSend(email_username, email_password); - emailSend.setSMTPServer(email_host); - emailSend.setSMTPPort(email_port); - EMailSender::Response resp = emailSend.send(email_recipient, email_message); - } - #endif + #if defined(ESP8266) + if(email_host && email_username && email_password && email_recipient) { // make sure all are valid + EMailSender emailSend(email_username, email_password); + emailSend.setSMTPServer(email_host); + emailSend.setSMTPPort(email_port); + EMailSender::Response resp = emailSend.send(email_recipient, email_message); + } #else struct smtp *smtp = NULL; String email_port_str = to_string(email_port); diff --git a/notifier.h b/notifier.h index 2572bd84..ca1f3e8c 100644 --- a/notifier.h +++ b/notifier.h @@ -21,9 +21,7 @@ * . */ - -#ifndef _NOTIFIER_H -#define _NOTIFIER_H +#pragma once #define NOTIF_QUEUE_MAXSIZE 32 @@ -55,5 +53,3 @@ class NotifQueue { static NotifNodeStruct* tail; static unsigned char nqueue; }; - -#endif // _NOTIFIER_H \ No newline at end of file diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 0314337e..8df20d0e 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -21,42 +21,30 @@ * . */ +#include "opensprinkler_server.h" #include "types.h" #include "OpenSprinkler.h" #include "program.h" -#include "opensprinkler_server.h" +#include "bfiller.h" #include "weather.h" #include "mqtt.h" #include "main.h" // External variables defined in main ion file -#if defined(USE_OTF) - extern OTF::OpenThingsFramework *otf; - #define OTF_PARAMS_DEF const OTF::Request &req,OTF::Response &res - #define OTF_PARAMS req,res - #define FKV_SOURCE req - #define handle_return(x) {if(x==HTML_OK) res.writeBodyData(ether_buffer, strlen(ether_buffer)); else otf_send_result(req,res,x); return;} -#else - extern EthernetClient *m_client; - #define OTF_PARAMS_DEF - #define OTF_PARAMS - #define FKV_SOURCE p - #define handle_return(x) {return_code=x; return;} -#endif +extern OTF::OpenThingsFramework *otf; +#define OTF_PARAMS_DEF const OTF::Request &req,OTF::Response &res +#define OTF_PARAMS req,res +#define FKV_SOURCE req +#define handle_return(x) {if(x==HTML_OK) res.writeBodyData(ether_buffer, strlen(ether_buffer)); else otf_send_result(req,res,x); return;} -#if defined(ARDUINO) - #if defined(ESP8266) - #include - #include - #include "espconnect.h" - extern ESP8266WebServer *update_server; - extern ENC28J60lwIP enc28j60; - extern Wiznet5500lwIP w5500; - extern lwipEth eth; - #else - #include "SdFat.h" - extern SdFat sd; - #endif +#if defined(ESP8266) + #include + #include + #include "espconnect.h" + extern ESP8266WebServer *update_server; + extern ENC28J60lwIP enc28j60; + extern Wiznet5500lwIP w5500; + extern lwipEth eth; #else #include #include @@ -69,11 +57,6 @@ extern OpenSprinkler os; extern ProgramData pd; extern ulong flow_count; -#if !defined(USE_OTF) -static unsigned char return_code; -static char* get_buffer = NULL; -#endif - BufferFiller bfill; /* Check available space (number of bytes) in the Ethernet buffer */ @@ -93,32 +76,9 @@ int available_ether_buffer() { #define HTML_PAGE_NOT_FOUND 0x20 #define HTML_NOT_PERMITTED 0x30 #define HTML_UPLOAD_FAILED 0x40 +#define HTML_INTERNAL_ERROR 0x50 #define HTML_REDIRECT_HOME 0xFF -#if !defined(USE_OTF) -static const char html200OK[] PROGMEM = - "HTTP/1.1 200 OK\r\n" -; - -static const char htmlNoCache[] PROGMEM = - "Cache-Control: max-age=0, no-cache, no-store, must-revalidate\r\n" -; - -static const char htmlContentJSON[] PROGMEM = - "Content-Type: application/json\r\n" - "Connection: close\r\n" -; - -static const char htmlContentHTML[] PROGMEM = - "Content-Type: text/html\r\n" - "Connection: close\r\n" -; - -static const char htmlAccessControl[] PROGMEM = - "Access-Control-Allow-Origin: *\r\n" -; -#endif - static const char htmlMobileHeader[] PROGMEM = "" ; @@ -127,9 +87,8 @@ static const char htmlReturnHome[] PROGMEM = "\n" ; -#if defined(USE_OTF) unsigned char findKeyVal (const OTF::Request &req,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL) { -#if defined(ARDUINO) +#if defined(ESP8266) char* result = key_in_pgm ? req.getQueryParameter((const __FlashStringHelper *)key) : req.getQueryParameter(key); #else char* result = req.getQueryParameter(key); @@ -144,7 +103,7 @@ unsigned char findKeyVal (const OTF::Request &req,char *strbuf, uint16_t maxlen, } return 0; } -#endif + unsigned char findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL) { uint8_t found=0; uint16_t i=0; @@ -211,20 +170,10 @@ void rewind_ether_buffer() { } void send_packet(OTF_PARAMS_DEF) { -#if defined(USE_OTF) res.writeBodyData(ether_buffer, strlen(ether_buffer)); -#else - m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); -#endif rewind_ether_buffer(); } -char dec2hexchar(unsigned char dec) { - if(dec<10) return '0'+dec; - else return 'A'+(dec-10); -} - -#if defined(USE_OTF) void print_header(OTF_PARAMS_DEF, bool isJson=true, int len=0) { res.writeStatus(200, F("OK")); res.writeHeader(F("Content-Type"), isJson?F("application/json"):F("text/html")); @@ -244,14 +193,8 @@ void print_header_compressed_html(OTF_PARAMS_DEF, int len) { res.writeHeader(F("Content-Encoding"), F("gzip")); res.writeHeader(F("Connection"), F("close")); } -#else -void print_header(bool isJson=true) { - bfill.emit_p(PSTR("$F$F$F$F\r\n"), html200OK, isJson?htmlContentJSON:htmlContentHTML, htmlAccessControl, htmlNoCache); -} -#endif -#if defined(USE_OTF) -#if !defined(ARDUINO) +#if !defined(ESP8266) string two_digits(uint8_t x) { return std::to_string(x); } @@ -267,7 +210,7 @@ String toHMS(ulong t) { void otf_send_result(OTF_PARAMS_DEF, unsigned char code, const char *item = NULL) { String json = F("{\"result\":"); -#if defined(ARDUINO) +#if defined(ESP8266) json += code; #else json += std::to_string(code); @@ -377,32 +320,18 @@ void on_ap_try_connect(OTF_PARAMS_DEF) { } } #endif -#endif /** Check and verify password */ -#if defined(USE_OTF) boolean check_password(char *p) { return true; } -boolean process_password(OTF_PARAMS_DEF, boolean fwv_on_fail=false) -#else -boolean check_password(char *p) -#endif -{ +boolean process_password(OTF_PARAMS_DEF, boolean fwv_on_fail=false) { #if defined(DEMO) return true; #endif if (os.iopts[IOPT_IGNORE_PASSWORD]) return true; -#if !defined(USE_OTF) - if (m_client && !p) { - p = get_buffer; - } - if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pw"), true)) { - if (os.password_verify(tmp_buffer)) return true; - } -#else /*if(req.isCloudRequest()){ // password is not required if this is coming from cloud connection return true; }*/ @@ -418,7 +347,6 @@ boolean check_password(char *p) } else { otf_send_result(OTF_PARAMS, HTML_UNAUTHORIZED); } -#endif return false; } @@ -473,13 +401,9 @@ void server_json_stations_main(OTF_PARAMS_DEF) { /** Output stations data */ void server_json_stations(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("{")); server_json_stations_main(OTF_PARAMS); @@ -488,13 +412,9 @@ void server_json_stations(OTF_PARAMS_DEF) { /** Output station special attribute */ void server_json_station_special(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif unsigned char sid; unsigned char comma=0; @@ -517,12 +437,7 @@ void server_json_station_special(OTF_PARAMS_DEF) { handle_return(HTML_OK); } -#if defined(USE_OTF) -void server_change_board_attrib(const OTF::Request &req, char header, unsigned char *attrib) -#else -void server_change_board_attrib(char *p, char header, unsigned char *attrib) -#endif -{ +void server_change_board_attrib(const OTF::Request &req, char header, unsigned char *attrib) { char tbuf2[6] = {0}; unsigned char bid; tbuf2[0]=header; @@ -534,12 +449,7 @@ void server_change_board_attrib(char *p, char header, unsigned char *attrib) } } -#if defined(USE_OTF) -void server_change_stations_attrib(const OTF::Request &req, char header, unsigned char *attrib) -#else -void server_change_stations_attrib(char *p, char header, unsigned char *attrib) -#endif -{ +void server_change_stations_attrib(const OTF::Request &req, char header, unsigned char *attrib) { char tbuf2[6] = {0}; unsigned char bid, s, sid; tbuf2[0]=header; @@ -568,11 +478,7 @@ void server_change_stations_attrib(char *p, char header, unsigned char *attrib) * g?: sequential group id */ void server_change_stations(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char* p = get_buffer; -#endif unsigned char sid; char tbuf2[5] = {'s', 0, 0, 0, 0}; @@ -580,9 +486,6 @@ void server_change_stations(OTF_PARAMS_DEF) { for(sid=0;sid sizeof(HTTPStationData)) { handle_return(HTML_DATA_OUTOFBOUND); } @@ -670,11 +570,7 @@ void manual_start_program(unsigned char, unsigned char, unsigned char); * qo: queue option (0: append; 1: insert at front; 2: replace (default) ) */ void server_manual_program(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pid"), true)) handle_return(HTML_DATA_MISSING); @@ -715,27 +611,9 @@ void server_manual_program(OTF_PARAMS_DEF) { * anno?: annotation for station ordering (refer to program name annotation) */ void server_change_runonce(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "t", false)) handle_return(HTML_DATA_MISSING); char *pv = tmp_buffer+1; -#else - char *p = get_buffer; - - // decode url first - if(p) urlDecode(p); - // search for the start of t=[ - char *pv; - boolean found=false; - for(pv=p;(*pv)!=0 && pvflags; + sid = adj->sid; + point_count = adj->point_count; + for (size_t i = 0; i <= point_count; i++) { + points[i] = adj->points[i]; + } + delete adj; + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_flags"), true)) { + flags=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } -#if defined(USE_OTF) - if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); - char *pv = tmp_buffer+1; -#else - // parse ad-hoc v=[... - // search for the start of v=[ - char *pv; - boolean found=false; - - for(pv=p;(*pv)!=0 && pv= MAX_SENSORS) sid = 255; } - if(!found) handle_return(HTML_DATA_MISSING); - pv+=3; -#endif + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("adj_points"), true)) { + unsigned long i = 0; + double x, y; + const char *ptr = tmp_buffer; + int result; + double last_x = -std::numeric_limits::infinity();; + + while (*ptr != '\0') { + if (i >= SENSOR_ADJUSTMENT_POINTS) handle_return(HTML_DATA_FORMATERROR); + + result = sscanf(ptr, "%lf,%lf;", &x, &y); + + if (result != 2 || x <= last_x) { + handle_return(HTML_DATA_FORMATERROR); + } + + points[i++] = sensor_adjustment_point_t {x, y}; + + last_x = x; + + while (*(ptr++) != ';') {} + } + + point_count = i; + } + + adj = new SensorAdjustment(flags, sid, point_count, points); + os.write_sensor_adjust(adj, pid); + delete adj; + #endif + + if(!findKeyVal(FKV_SOURCE,tmp_buffer,TMP_BUFFER_SIZE, "v",false)) handle_return(HTML_DATA_MISSING); + char *pv = tmp_buffer+1; // parse headers *(char*)(&prog) = parse_listdata(&pv); @@ -1044,7 +951,7 @@ void server_change_program(OTF_PARAMS_DEF) { void server_json_options_main() { unsigned char oid; for(oid=0;oid=IOPT_STATIC_IP1 && oid<=IOPT_STATIC_IP4) || (oid>=IOPT_GATEWAY_IP1 && oid<=IOPT_GATEWAY_IP4) || @@ -1054,12 +961,6 @@ void server_json_options_main() { continue; #endif - #if !(defined(ESP8266) || defined(PIN_SENSOR2)) - // only OS 3.x or controllers that have PIN_SENSOR2 defined support sensor 2 options - if (oid==IOPT_SENSOR2_TYPE || oid==IOPT_SENSOR2_OPTION || oid==IOPT_SENSOR2_ON_DELAY || oid==IOPT_SENSOR2_OFF_DELAY) - continue; - #endif - int32_t v=os.iopts[oid]; if (oid==IOPT_MASTER_OFF_ADJ || oid==IOPT_MASTER_OFF_ADJ_2 || oid==IOPT_MASTER_ON_ADJ || oid==IOPT_MASTER_ON_ADJ_2 || @@ -1067,7 +968,7 @@ void server_json_options_main() { v=water_time_decode_signed(v); } - #if defined(ARDUINO) + #if defined(ESP8266) if (oid==IOPT_BOOST_TIME) { if (os.hw_type==HW_TYPE_AC || os.hw_type==HW_TYPE_UNKNOWN) continue; else v<<=2; @@ -1095,22 +996,9 @@ void server_json_options_main() { } #endif - if (oid==IOPT_SEQUENTIAL_RETIRED || oid==IOPT_URS_RETIRED || oid==IOPT_RSO_RETIRED || oid==IOPT_RESERVE_7 || oid==IOPT_RESERVE_8) continue; - -#if defined(ARDUINO) - #if defined(ESP8266) - // for SSD1306, we can't adjust contrast or backlight - if(oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT) continue; - #else - if (os.lcd.type() == LCD_I2C) { - // for I2C type LCD, we can't adjust contrast or backlight - if(oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT) continue; - } - #endif -#else - // for Linux-based platforms, we can't adjust contrast or backlight - if(oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT) continue; -#endif + if (oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT || oid==IOPT_SEQUENTIAL_RETIRED || oid==IOPT_URS_RETIRED || + oid==IOPT_RSO_RETIRED || oid==IOPT_RESERVE_7 || oid==IOPT_RESERVE_8) + continue; // each json name takes 5 characters strncpy_P0(tmp_buffer, iopt_json_names+oid*5, 5); @@ -1134,13 +1022,9 @@ void server_json_options_main() { /** Output Options */ void server_json_options(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS,true)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("{")); server_json_options_main(); handle_return(HTML_OK); @@ -1183,18 +1067,49 @@ void server_json_programs_main(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); } } + + #if defined(USE_SENSORS) + bfill.emit_p(PSTR("],\"adj\":[")); + uint8_t adj_count = 0; + + SensorAdjustment *adj; + os_file_type file = file_open(SENADJ_FILENAME, FileOpenMode::Read); + if (file) { + for (size_t i = 0; i < pd.nprograms; i++) { + if ((adj = os.get_sensor_adjust(i))) { + if (adj_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"pid\":$D,\"flags\":$D,\"sid\":$D,\"point_count\":$D,\"splits\":["), i, adj->flags, adj->sid, adj->point_count); + for (int j = 0; j < adj->point_count; j++) { + if (j) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"x\":$E,\"y\":$E}"), adj->points[j].x, adj->points[j].y); + } + bfill.emit_p(PSTR("]}")); + adj_count += 1; + delete adj; + + + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open file: "); + DEBUG_PRINTLN(SENADJ_FILENAME); + } + #endif bfill.emit_p(PSTR("]}")); } /** Output program data */ void server_json_programs(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("{")); server_json_programs_main(OTF_PARAMS); handle_return(HTML_OK); @@ -1203,11 +1118,7 @@ void server_json_programs(OTF_PARAMS_DEF) { /** Output script url form */ void server_view_scripturl(OTF_PARAMS_DEF) { rewind_ether_buffer(); -#if defined(USE_OTF) print_header(OTF_PARAMS,false,strlen(ether_buffer)); -#else - print_header(false); -#endif //bfill.emit_p(PSTR("
JavaScript:
Default:$S
Weather:
Default:$S
Password:
"), bfill.emit_p(PSTR(R"(
@@ -1257,12 +1168,10 @@ void server_json_controller_main(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("\"apdv\":$D,"), os.actual_pd_voltage); #endif -#if defined(USE_OTF) bfill.emit_p(PSTR("\"otc\":{$O},\"otcs\":$D,"), SOPT_OTC_OPTS, otf->getCloudStatus()); -#endif unsigned char mac[6] = {0}; -#if defined(ARDUINO) +#if defined(ESP8266) os.load_hardware_mac(mac, useEth); #else os.load_hardware_mac(mac, true); @@ -1282,9 +1191,7 @@ void server_json_controller_main(OTF_PARAMS_DEF) { wt_restricted, SOPT_DEVICE_NAME); -#if defined(SUPPORT_EMAIL) bfill.emit_p(PSTR("\"email\":{$O},"), SOPT_EMAIL_OPTS); -#endif bfill.emit_p(PSTR("\"wls\":[")); if (md_N == 0) { @@ -1295,7 +1202,7 @@ void server_json_controller_main(OTF_PARAMS_DEF) { bfill.emit_p((idx == md_N-1) ? PSTR("],") : PSTR(",")); } -#if defined(ARDUINO) +#if defined(ESP8266) uint16_t current = os.read_current(true); if((!os.status.program_busy) && (current$F"), @@ -1394,12 +1293,8 @@ void server_home(OTF_PARAMS_DEF) */ void server_change_values(OTF_PARAMS_DEF) { -#if defined(USE_OTF) extern uint32_t reboot_timer; if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rsn"), true) && atoi(tmp_buffer) > 0) { reset_all_stations(); } @@ -1412,24 +1307,16 @@ void server_change_values(OTF_PARAMS_DEF) os.status.overcurrent_sid = 0; // clear overcurrent status } - #if !defined(ARDUINO) + #if !defined(ESP8266) if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("update"), true) && atoi(tmp_buffer) > 0) { os.update_dev(); } #endif if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rbt"), true) && atoi(tmp_buffer) > 0) { - #if defined(USE_OTF) - os.status.safe_reboot = 0; - reboot_timer = os.now_tz() + 1; - handle_return(HTML_SUCCESS); - #else - print_header(false); - //bfill.emit_p(PSTR("Rebooting...")); - send_packet(); - m_client->stop(); - os.reboot_dev(REBOOT_CAUSE_WEB); - #endif + os.status.safe_reboot = 0; + reboot_timer = os.now_tz() + 1; + handle_return(HTML_SUCCESS); } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { @@ -1485,40 +1372,26 @@ void string_remove_space(char *src) { * jsp: Javascript path */ void server_change_scripturl(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif #if defined(DEMO) handle_return(HTML_REDIRECT_HOME); #endif if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("jsp"), true)) { tmp_buffer[TMP_BUFFER_SIZE-1]=0; // make sure we don't exceed the maximum size - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif // trim unwanted space characters string_remove_space(tmp_buffer); os.sopt_save(SOPT_JAVASCRIPTURL, tmp_buffer); } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wsp"), true)) { tmp_buffer[TMP_BUFFER_SIZE-1]=0; - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif string_remove_space(tmp_buffer); os.sopt_save(SOPT_WEATHERURL, tmp_buffer); } -#if defined(USE_OTF) rewind_ether_buffer(); print_header(OTF_PARAMS,false,strlen(ether_buffer)); bfill.emit_p(PSTR("$F"), htmlReturnHome); handle_return(HTML_OK); -#else - handle_return(HTML_REDIRECT_HOME); -#endif } /** @@ -1532,12 +1405,7 @@ void server_change_scripturl(OTF_PARAMS_DEF) { */ void server_change_options(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif - // temporarily save some old options values bool time_change = false; bool weather_change = false; @@ -1598,9 +1466,6 @@ void server_change_options(OTF_PARAMS_DEF) } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("loc"), true)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif strReplaceQuoteBackslash(tmp_buffer); if (os.sopt_save(SOPT_LOCATION, tmp_buffer)) { // if location string has changed weather_change = true; @@ -1608,9 +1473,6 @@ void server_change_options(OTF_PARAMS_DEF) } uint8_t keyfound = 0; if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wto"), true)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif if (os.sopt_save(SOPT_WEATHER_OPTS, tmp_buffer)) { os.sopt_load(SOPT_WEATHER_OPTS, tmp_buffer+1); // make room for the leading '{' parse_wto(tmp_buffer); // parse wto @@ -1621,9 +1483,6 @@ void server_change_options(OTF_PARAMS_DEF) keyfound = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ifkey"), true, &keyfound)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif os.sopt_save(SOPT_IFTTT_KEY, tmp_buffer); } else if (keyfound) { tmp_buffer[0]=0; @@ -1632,9 +1491,6 @@ void server_change_options(OTF_PARAMS_DEF) keyfound = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("otc"), true, &keyfound)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif os.sopt_save(SOPT_OTC_OPTS, tmp_buffer); } else if (keyfound) { tmp_buffer[0]=0; @@ -1643,9 +1499,6 @@ void server_change_options(OTF_PARAMS_DEF) keyfound = 0; if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("mqtt"), true, &keyfound)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); os.status.req_mqtt_restart = true; } else if (keyfound) { @@ -1656,9 +1509,6 @@ void server_change_options(OTF_PARAMS_DEF) keyfound = 0; if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("email"), true, &keyfound)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif os.sopt_save(SOPT_EMAIL_OPTS, tmp_buffer); } else if (keyfound) { tmp_buffer[0]=0; @@ -1666,22 +1516,19 @@ void server_change_options(OTF_PARAMS_DEF) } if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("dname"), true)) { - #if !defined(USE_OTF) - urlDecode(tmp_buffer); - #endif strReplaceQuoteBackslash(tmp_buffer); os.sopt_save(SOPT_DEVICE_NAME, tmp_buffer); } // if not using NTP and manually setting time if (!os.iopts[IOPT_USE_NTP] && findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ttt"), true)) { -#if defined(ARDUINO) +#if defined(ESP8266) unsigned long t; t = strtoul(tmp_buffer, NULL, 0); #endif // before chaging time, reset all stations to avoid messing up with timing reset_all_stations_immediate(); -#if defined(ARDUINO) +#if defined(ESP8266) setTime(t); RTC.set(t); #endif @@ -1731,11 +1578,6 @@ void server_change_password(OTF_PARAMS_DEF) { return; #endif -#if defined(USE_OTF) - if(!process_password(OTF_PARAMS)) return; -#else - char* p = get_buffer; -#endif if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("npw"), true)) { const int pwBufferSize = TMP_BUFFER_SIZE/2; char *tbuf2 = tmp_buffer + pwBufferSize; // use the second half of tmp_buffer @@ -1763,13 +1605,9 @@ void server_json_status_main() { /** Output station status */ void server_json_status(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("{")); server_json_status_main(); @@ -1788,11 +1626,7 @@ void server_json_status(OTF_PARAMS_DEF) * qo: queuing option (0: append after others; 1: run now and pause others) */ void server_change_manual(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif int sid=-1; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { @@ -1896,12 +1730,7 @@ int file_fgets(File file, char* buf, int maxsize) { * if unspecified, output all records */ void server_json_log(OTF_PARAMS_DEF) { - -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif unsigned int start, end; @@ -1932,14 +1761,10 @@ void server_json_log(OTF_PARAMS_DEF) { if (findKeyVal(FKV_SOURCE, type, 4, PSTR("type"), true)) type_specified = true; -#if defined(USE_OTF) // as the log data can be large, we will use ESP8266's sendContent function to // send multiple packets of data, instead of the standard way of using send(). rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif bfill.emit_p(PSTR("[")); @@ -1951,10 +1776,6 @@ void server_json_log(OTF_PARAMS_DEF) { #if defined(ESP8266) File file = LittleFS.open(tmp_buffer, "r"); if(!file) continue; -#elif defined(ARDUINO) - if (!sd.exists(tmp_buffer)) continue; - SdFile file; - file.open(tmp_buffer, O_READ); #else // prepare to open log file for Linux FILE *file = fopen(get_filename_fullpath(tmp_buffer), "rb"); if(!file) continue; @@ -1969,12 +1790,6 @@ void server_json_log(OTF_PARAMS_DEF) { break; } tmp_buffer[result]=0; - #elif defined(ARDUINO) - result = file.fgets(tmp_buffer, TMP_BUFFER_SIZE); - if (result <= 0) { - file.close(); - break; - } #else if(fgets(tmp_buffer, TMP_BUFFER_SIZE, file)) { result = strlen(tmp_buffer); @@ -2028,12 +1843,7 @@ void server_json_log(OTF_PARAMS_DEF) { * if day=all: delete all log files) */ void server_delete_log(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif - if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("day"), true)) handle_return(HTML_DATA_MISSING); @@ -2048,11 +1858,7 @@ void server_delete_log(OTF_PARAMS_DEF) { * repl: replace (in units of seconds) (New UI allows for replace, extend, and pause using this) */ void server_pause_queue(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS)) return; -#else - char *p = get_buffer; -#endif ulong duration = 0; if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("repl"), true)) { @@ -2077,15 +1883,629 @@ void server_pause_queue(OTF_PARAMS_DEF) { handle_return(HTML_SUCCESS); } +#if defined(USE_SENSORS) +void server_json_sensors_main(OTF_PARAMS_DEF) { + bfill.emit_p(PSTR("\"sn\":[")); + uint8_t sensor_count = 0; + + Sensor *sensor; + for (size_t i = 0; i < MAX_SENSORS; i++) { + if (os.sensors[i].interval && (sensor = os.get_sensor(i))) { + if (sensor_count) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"sid\":$D,\"name\":\"$S\",\"unit\":$D,\"flags\":$D,\"interval\":$L,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E,\"value\":$E,\"type\":$D,\"extra\":"), i, sensor->name, static_cast(sensor->unit), sensor->flags, sensor->interval, sensor->max, sensor->min, sensor->scale, sensor->offset, os.sensors[i].value, static_cast(sensor->get_sensor_type())); + sensor->emit_extra_json(&bfill); + bfill.emit_p(PSTR("}")); + sensor_count += 1; + delete sensor; + + + // push out a packet if available + // buffer size is getting small + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + } + } + + + bfill.emit_p(PSTR("],\"count\":$D}"), sensor_count); +} + +/** Sensor status */ +void server_json_sensors(OTF_PARAMS_DEF) +{ + if(!process_password(OTF_PARAMS)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); + + bfill.emit_p(PSTR("{")); + server_json_sensors_main(OTF_PARAMS); + handle_return(HTML_OK); +} + +void server_change_sensor(OTF_PARAMS_DEF) { + if(!process_password(OTF_PARAMS)) return; + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) handle_return(HTML_DATA_MISSING); + + char *end; + long sid = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + + if (sid < -1 || sid >= MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + + if (sid == -1 ) { + while (++sid < MAX_SENSORS) { + if (!os.sensors[sid].interval) { + break; + } + } + + if (sid == MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + } + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) handle_return(HTML_DATA_MISSING); + + ulong type_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (type_raw >= (ulong)SensorType::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + + SensorType sensor_type = static_cast(type_raw); + + Sensor *sensor = nullptr; + double min = 0; + double max = 100; + double scale = 1; + double offset = 0; + ulong interval = 1000; + SensorUnit unit = SensorUnit::None; + uint32_t flags = 0; + + char name[SENSOR_NAME_LEN]; + snprintf(name, SENSOR_NAME_LEN, "Sensor: %d", (int)sid); + + SensorType original_sensor_type = SensorType::MAX_VALUE; + if (os.sensors[sid].interval) { + if ((sensor = os.get_sensor(sid))) { + original_sensor_type = sensor->get_sensor_type(); + strncpy(name, sensor->name, SENSOR_NAME_LEN); + min = sensor->min; + max = sensor->max; + scale = sensor->scale; + offset = sensor->offset; + interval = sensor->interval; + unit = sensor->unit; + flags = sensor->flags; + delete sensor; + } + } + + // parse sensor name + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) { + strReplaceQuoteBackslash(tmp_buffer); + strncpy(name, tmp_buffer, SENSOR_NAME_LEN); + } + + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) { + min=strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) { + max=strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { + scale=strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) { + offset=strtod(tmp_buffer, &end); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("interval"), true)) { + interval=strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + if (interval < 1) handle_return(HTML_DATA_OUTOFBOUND); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { + ulong unit_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (unit_raw >= (ulong)SensorUnit::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + unit = static_cast(unit_raw); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("flags"), true)) { + flags = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + } + + Sensor *result_sensor; + switch (sensor_type) { + case SensorType::Ensemble: { + uint8_t children_count = 0; + ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + children[i].sensor_id = 255; + } + + EnsembleAction action = EnsembleAction::Min; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + EnsembleSensor* e = static_cast(sensor); + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + children[i] = e->children[i]; + } + + action = e->action; + delete sensor; + } + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("children"), true)) { + unsigned int i = 0; + int d; + double d1, d2, d3, d4; + const char *ptr = tmp_buffer; + int result; + + while (*ptr != '\0') { + if (i >= ENSEMBLE_SENSOR_CHILDREN_COUNT) handle_return(HTML_DATA_FORMATERROR); + + result = sscanf(ptr, "%d,%lf,%lf,%lf,%lf;", &d, &d1, &d2, &d3, &d4); + + if (result != 5) { + handle_return(HTML_DATA_FORMATERROR); + } + + if (d >= MAX_SENSORS || d < -1) handle_return(HTML_DATA_FORMATERROR); + if (d == -1) d = sid; + + children[i++] = ensemble_children_t {(uint8_t)d, d1, d2, d3, d4}; + + while (*(ptr++) != ';') {} + } + + children_count = i; + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { + ulong action_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (action_raw >= (ulong)EnsembleAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + action = static_cast(action_raw); + } + + result_sensor = new EnsembleSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.sensors, children, children_count, action); + break; + } + case SensorType::ADS1115: { + ulong sensor_index = 0; + ulong sensor_pin = 0; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + ADS1115Sensor* e = static_cast(sensor); + sensor_index = e->sensor_index; + sensor_pin = e->pin; + delete sensor; + } + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("pin"), true)) { + ulong raw_sensor_pin = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (raw_sensor_pin == 0 || raw_sensor_pin > 16) handle_return(HTML_DATA_OUTOFBOUND); + raw_sensor_pin -= 1; + sensor_index = raw_sensor_pin >> 2; + sensor_pin = raw_sensor_pin & 0b11; + } + + result_sensor = new ADS1115Sensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.ads1115_devices, sensor_index, sensor_pin); + break; + } + case SensorType::Weather: { + WeatherAction action = WeatherAction::MAX_VALUE; + + if (sensor_type == original_sensor_type) { + if ((sensor = os.get_sensor(sid))) { + WeatherSensor* e = static_cast(sensor); + action = e->action; + delete sensor; + } + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("action"), true)) { + ulong action_raw = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (action_raw >= (ulong)WeatherAction::MAX_VALUE) handle_return(HTML_DATA_OUTOFBOUND); + action = static_cast(action_raw); + } + + result_sensor = new WeatherSensor(interval, min, max, scale, offset, (const char*)&name, unit, flags, os.get_sensor_weather_data, action); + + break; + } + default: { + handle_return(HTML_DATA_OUTOFBOUND) + break; + } + } + + os.sensors[sid].interval = interval; + os.sensors[sid].flags = flags; + os.sensors[sid].next_update = 0; + os.sensors[sid].value = result_sensor->get_initial_value(); + os.write_sensor(result_sensor, sid); + + delete result_sensor; + + handle_return(HTML_SUCCESS); +} + +/** + * Delete a sensor + * Command: /dsn?pw=xxx&sid=xxx + * + * pw: password + * sid:staiton index (-1 will delete all programs) + */ +void server_delete_sensor(OTF_PARAMS_DEF) { + if(!process_password(OTF_PARAMS)) return; + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) + handle_return(HTML_DATA_MISSING); + + int sid=atoi(tmp_buffer); + if (sid == -1) { + uint8_t i; + for (i=0;i 0) { + buf[index++] = (num%10) + '0'; + num /= 10; + } + + return index; + } else { + buf[0] = '0'; + return 1; + } +} + +void server_log_sensor(OTF_PARAMS_DEF) { + if(!process_password(OTF_PARAMS)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); + + ulong start_time = millis(); + + ulong count = 0; + ulong i; + + char *end; + ulong max_count = 100; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("count"), true)) { + max_count = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (max_count > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); + } + + uint16_t file_no = os.sensor_file_no; + uint16_t next; + + os_file_type file = os.open_sensor_log(file_no, FileOpenMode::Read); + if (file) { + file_read(file, &next, sizeof(next)); + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(file_no); + handle_return(HTML_INTERNAL_ERROR); + } + + if (next == SENSOR_LOG_PER_FILE) { + next = 0; + file_no = (file_no + 1) % SENSOR_LOG_FILE_COUNT; + } else { + next += 1; + } + + ulong cursor = (file_no * SENSOR_LOG_PER_FILE) + next; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("cursor"), true)) { + cursor = strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (cursor > MAX_SENSOR_LOG_COUNT) handle_return(HTML_DATA_OUTOFBOUND); + next = cursor % SENSOR_LOG_PER_FILE; + file_no = (cursor - next) / SENSOR_LOG_PER_FILE; + } + + using std::numeric_limits; + time_os_t before = std::numeric_limits::max(); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) { + before = (time_os_t)strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (before == 0) handle_return(HTML_DATA_OUTOFBOUND); + } + + time_os_t after = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) { + after = (time_os_t)strtoul(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (after <= before) handle_return(HTML_DATA_OUTOFBOUND); + } + + long target_sid = -1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + target_sid = strtol(tmp_buffer, &end, 10); + if (*end != '\0') handle_return(HTML_DATA_FORMATERROR); + if (target_sid >= MAX_SENSORS || target_sid < -1) handle_return(HTML_DATA_OUTOFBOUND); + } + + // Clear out buffer + memset(tmp_buffer, 0, SENSOR_LOG_ITEM_SIZE); + + file = os.open_sensor_log(file_no, FileOpenMode::Read); + if (file) { + file_seek(file, sizeof(next) + (next * SENSOR_LOG_ITEM_SIZE), FileSeekMode::Current); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(file_no); + handle_return(HTML_INTERNAL_ERROR); + } + + send_packet(OTF_PARAMS); + + char print_buf[22] = "00,00000000,00000000\n"; + + for (i=0;i MAX_SENSORS) continue; + buf_ptr += 1; + + if (target_sid > -1 && sid != target_sid) continue; + + time_os_t timestamp; + memcpy(×tamp, buf_ptr, sizeof(timestamp)); + buf_ptr += sizeof(timestamp); + uint32_t value; + memcpy(&value, buf_ptr, sizeof(value)); + buf_ptr += sizeof(value); + + if (timestamp > before || timestamp < after) continue; + + print_buf[0] = dec2hexchar((sid >> 4) & 0xF); + print_buf[1] = dec2hexchar(sid & 0xF); + + print_buf[3] = dec2hexchar((timestamp >> 28) & 0xF); + print_buf[4] = dec2hexchar((timestamp >> 24) & 0xF); + print_buf[5] = dec2hexchar((timestamp >> 20) & 0xF); + print_buf[6] = dec2hexchar((timestamp >> 16) & 0xF); + print_buf[7] = dec2hexchar((timestamp >> 12) & 0xF); + print_buf[8] = dec2hexchar((timestamp >> 8) & 0xF); + print_buf[9] = dec2hexchar((timestamp >> 4) & 0xF); + print_buf[10] = dec2hexchar(timestamp & 0xF); + + print_buf[12] = dec2hexchar((value >> 28) & 0xF); + print_buf[13] = dec2hexchar((value >> 24) & 0xF); + print_buf[14] = dec2hexchar((value >> 20) & 0xF); + print_buf[15] = dec2hexchar((value >> 16) & 0xF); + print_buf[16] = dec2hexchar((value >> 12) & 0xF); + print_buf[17] = dec2hexchar((value >> 8) & 0xF); + print_buf[18] = dec2hexchar((value >> 4) & 0xF); + print_buf[19] = dec2hexchar(value & 0xF); + res.write(print_buf, 21); + count += 1; + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(file_no); + break; + } + } + + if (file) file_close(file); + + handle_return(HTML_OK); +} + + +// TODO: delete sensor log delete +void server_clear_sensor_log(OTF_PARAMS_DEF) { + if(!process_password(OTF_PARAMS)) return; + os_file_type file; + + int sid = -1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sid"), true)) { + sid = atoi(tmp_buffer); + if (sid<-1 || sid>=MAX_SENSORS) handle_return(HTML_DATA_OUTOFBOUND); + } else { + handle_return(HTML_DATA_MISSING); + } + + tmp_buffer[0] = 0; + tmp_buffer[1] = 255; + + uint16_t next = SENSOR_LOG_PER_FILE; + for (uint16_t f = 0; f < SENSOR_LOG_FILE_COUNT; f++) { + file = os.open_sensor_log(f, FileOpenMode::ReadWrite); + if (file) { + file_write(file, &next, sizeof(next)); + for (size_t i = 0; i < SENSOR_LOG_PER_FILE; i++) { + file_write(file, tmp_buffer, SENSOR_LOG_ITEM_SIZE); + } + + file_close(file); + } else { + DEBUG_PRINT("Failed to open sensor log file: "); + DEBUG_PRINTLN(f); + handle_return(HTML_INTERNAL_ERROR); + } + } + + + handle_return(HTML_SUCCESS); +} + +template +void bfill_enum_values(const char *name) { + static_assert(std::is_enum_v, "T must be an enum type"); + + bool needs_comma = false; + + bfill.emit_p(PSTR("\"$S\":["), name); + + for (size_t i = 0; i < static_cast(T::MAX_VALUE); ++i) { + if (needs_comma) { + bfill.emit_p(PSTR(",")); + needs_comma = false; + } + + const char* str = enum_string(static_cast(i)); + if (str) { + bfill.emit_p(PSTR("\"$S\""), str); + needs_comma = true; + } + } + + bfill.emit_p(PSTR("]")); +} + +void server_json_sensor_description_main(OTF_PARAMS_DEF) { + bfill.emit_p(PSTR("\"sensor\":[")); + for (uint8_t i = 0; i < static_cast(SensorType::MAX_VALUE); i++) { + if (i) bfill.emit_p(PSTR(",")); + switch (static_cast(i)) { + case SensorType::Ensemble: + EnsembleSensor::emit_description_json(&bfill); + break; + case SensorType::ADS1115: + ADS1115Sensor::emit_description_json(&bfill); + break; + case SensorType::Weather: + WeatherSensor::emit_description_json(&bfill); + break; + case SensorType::MAX_VALUE: + break; + } + } + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + + bfill.emit_p(PSTR("],\"units\":[")); + for (uint8_t i = 0; i < static_cast(SensorUnit::MAX_VALUE)-1; i++) { + if (i) bfill.emit_p(PSTR(",")); + SensorUnit unit = static_cast(i); + bfill.emit_p(PSTR("{\"name\":\"$S\",\"short\":\"$S\",\"group\":$D,\"index\":$D,\"value\":$D}"), get_sensor_unit_name(unit), get_sensor_unit_short(unit), get_sensor_unit_group(unit), get_sensor_unit_index(unit), i); + } + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + + bfill.emit_p(PSTR("],\"enums\":{")); + bfill_enum_values(PSTR("SensorUnitGroup")); + bfill.emit_p(PSTR(",")); + bfill_enum_values(PSTR("EnsembleAction")); + bfill.emit_p(PSTR(",")); + bfill_enum_values(PSTR("WeatherAction")); + bfill.emit_p(PSTR("}")); + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + + bfill.emit_p(PSTR(",\"base\":[{\"name\":\"Sensor Information\",\"args\":[{\"name\":\"Name\",\"arg\":\"name\",\"type\":\"string::[1,32]\",\"default\":\"\",\"extra\":[]},{\"name\":\"Update Interval\",\"arg\":\"interval\",\"type\":\"int::[1,any]\",\"default\":\"5\",\"extra\":[]},{\"name\":\"Unit\",\"arg\":\"unit\",\"type\":\"unit\",\"extra\":[]}]},{\"name\":\"Sensor Scaling\",\"args\":[{\"name\":\"Linear Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"1\",\"extra\":[]},{\"name\":\"Value Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"0\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"100\",\"extra\":[]}]},{\"name\":\"Sensor Type\",\"args\":[{\"name\":\"Sensor Type\",\"arg\":\"type\",\"type\":\"type\",\"default\":\"0\",\"extra\":[]}]}]")); + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + + static_assert(SENSOR_FLAG_COUNT == 2); // If this fails make sure that the json is updated and the count is updated here + bfill.emit_p(PSTR(",\"flags\":[[\"Enable Sensor\",\"true\"],[\"Enable Logging\",\"true\"]]")); + + if (available_ether_buffer() <= 0) { + send_packet(OTF_PARAMS); + } + + bfill.emit_p(PSTR("}")); +} + +void server_json_sen_desc(OTF_PARAMS_DEF) +{ + if(!process_password(OTF_PARAMS)) return; + rewind_ether_buffer(); + print_header(OTF_PARAMS); + + bfill.emit_p(PSTR("{")); + server_json_sensor_description_main(OTF_PARAMS); + handle_return(HTML_OK); +} +#endif + /** Output all JSON data, including jc, jp, jo, js, jn */ void server_json_all(OTF_PARAMS_DEF) { -#if defined(USE_OTF) if(!process_password(OTF_PARAMS,true)) return; rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif + bfill.emit_p(PSTR("{\"settings\":{")); server_json_controller_main(OTF_PARAMS); send_packet(OTF_PARAMS); @@ -2100,19 +2520,18 @@ void server_json_all(OTF_PARAMS_DEF) { send_packet(OTF_PARAMS); bfill.emit_p(PSTR(",\"stations\":{")); server_json_stations_main(OTF_PARAMS); + #if defined(USE_SENSORS) + bfill.emit_p(PSTR(",\"sensors\":{")); + server_json_sensors_main(OTF_PARAMS); + bfill.emit_p(PSTR(",\"sensor_desc\":{")); + server_json_sensor_description_main(OTF_PARAMS); + #endif bfill.emit_p(PSTR("}")); handle_return(HTML_OK); } -#if defined(ARDUINO) +#if defined(ESP8266) -#if defined(OS_AVR) -static int freeHeap () { - extern int __heap_start, *__brkval; - int v; - return (int) &v - (__brkval == 0 ? (int) &__heap_start : (int) __brkval); -} -#endif #else #include static unsigned long freeHeap() { @@ -2127,12 +2546,9 @@ static unsigned long freeHeap() { #endif void server_json_debug(OTF_PARAMS_DEF) { -#if defined(USE_OTF) rewind_ether_buffer(); print_header(OTF_PARAMS); -#else - print_header(); -#endif + bfill.emit_p(PSTR("{\"date\":\"$S\",\"time\":\"$S\",\"heap\":$L"), __DATE__, __TIME__, #if defined(ESP8266) ESP.getFreeHeap()); @@ -2198,34 +2614,40 @@ typedef void (*URLHandler)(OTF_PARAMS_DEF); * The order must exactly match the order of the * handler functions below */ -const char _url_keys[] PROGMEM = - "cv" - "jc" - "dp" - "cp" - "cr" - "mp" - "up" - "jp" - "co" - "jo" - "sp" - "js" - "cm" - "cs" - "jn" - "je" - "jl" - "dl" - "su" - "cu" - "ja" - "pq" - "db" -#if defined(ARDUINO) - //"ff" -#endif - ; + +const char *uris[] PROGMEM = { + "cv", + "jc", + "dp", + "cp", + "cr", + "mp", + "up", + "jp", + "co", + "jo", + "sp", + "js", + "cm", + "cs", + "jn", + "je", + "jl", + "dl", + "su", + "cu", + "ja", + "pq", + "db", + #if defined(USE_SENSORS) + "jsn", + "csn", + "dsn", + "lsn", + "csl", + "jsd", + #endif +}; // Server function handlers URLHandler urls[] = { @@ -2252,9 +2674,14 @@ URLHandler urls[] = { server_json_all, // ja server_pause_queue, // pq server_json_debug, // db -#if defined(ARDUINO) - //server_fill_files, -#endif + #if defined(USE_SENSORS) + server_json_sensors, // jsn + server_change_sensor, // csn + server_delete_sensor, // dsn + server_log_sensor, // lsn + server_clear_sensor_log, // csl + server_json_sen_desc, // jsd + #endif }; // handle Ethernet request @@ -2337,14 +2764,14 @@ void start_server_client() { update_server->on("/update", HTTP_POST, on_firmware_upload_fin, on_firmware_upload); update_server->on("/update", HTTP_OPTIONS, on_update_options); + char uri_buf[10] = {0}; + uri_buf[0] = '/'; + // set up all other handlers - char uri[4]; - uri[0]='/'; - uri[3]=0; for(unsigned char i=0;ion(uri, urls[i]); + strncpy_P(uri_buf+1, uris[i], 9); + uri_buf[9] = 0; + otf->on(uri_buf, urls[i]); } callback_initialized = true; } @@ -2368,15 +2795,15 @@ void start_server_ap() { otf->onMissingPage(on_ap_home); update_server->begin(); - // set up all other handlers - char uri[4]; - uri[0]='/'; - uri[3]=0; - for(unsigned char i=0;ion(uri, urls[i]); - } + char uri_buf[10] = {0}; + uri_buf[0] = '/'; + + // set up all other handlers + for(unsigned char i=0;ion(uri_buf, urls[i]); + } os.lcd.setCursor(0, -1); os.lcd.print(F("OSAP:")); @@ -2387,7 +2814,7 @@ void start_server_ap() { #endif -#if defined(USE_OTF) && !defined(ARDUINO) +#if !defined(ESP8266) void initialize_otf() { if(!otf) return; static bool callback_initialized = false; @@ -2396,109 +2823,23 @@ void initialize_otf() { otf->on("/", server_home); // handle home page otf->on("/index.html", server_home); + char uri_buf[10] = {0}; + uri_buf[0] = '/'; + // set up all other handlers - char uri[4]; - uri[0]='/'; - uri[3]=0; for(unsigned char i=0;ion(uri, urls[i]); + strncpy(uri_buf+1, uris[i], 9); + uri_buf[9] = 0; + otf->on(uri_buf, urls[i]); } callback_initialized = true; } } #endif -#if !defined(USE_OTF) -// This funtion is only used for non-OTF platforms -void handle_web_request(char *p) { - rewind_ether_buffer(); - - // assume this is a GET request - // GET /xx?xxxx - char *com = p+5; - char *dat = com+3; - - if(com[0]==' ') { - server_home(); // home page handler - send_packet(); - m_client->stop(); - } else { - // server funtion handlers - unsigned char i; - for(i=0;istop(); - return; - } - switch(ret) { - case HTML_OK: - break; - case HTML_REDIRECT_HOME: - print_header(false); - bfill.emit_p(PSTR("$F"), htmlReturnHome); - break; - default: - print_header(); - bfill.emit_p(PSTR("{\"result\":$D}"), ret); - } - break; - } - } - - if(i==sizeof(urls)/sizeof(URLHandler)) { - // no server funtion found - print_header(); - bfill.emit_p(PSTR("{\"result\":$D}"), HTML_PAGE_NOT_FOUND); - } - send_packet(); - m_client->stop(); - } -} -#endif - -#if defined(ARDUINO) +#if defined(ESP8266) #define NTP_NTRIES 10 /** NTP sync request */ -#if defined(ESP8266) // due to lwip not supporting UDP, we have to use configTime and time() functions // othewise, using UDP is much faster for NTP sync ulong getNtpTime() { @@ -2533,104 +2874,4 @@ ulong getNtpTime() { } return gt; } -#else // AVR -ulong getNtpTime() { - - // only proceed if we are connected - if(!os.network_connected()) return 0; - - uint16_t port = (uint16_t)(os.iopts[IOPT_HTTPPORT_1]<<8) + (uint16_t)os.iopts[IOPT_HTTPPORT_0]; - port = (port==8000) ? 8888:8000; // use a different port than http port - EthernetUDP udp; - - #define NTP_PACKET_SIZE 48 - #define NTP_PORT 123 - #define N_PUBLIC_SERVERS 5 - - static const char* public_ntp_servers[] = { - "time.google.com", - "time.nist.gov", - "time.windows.com", - "time.cloudflare.com", - "pool.ntp.org" }; - static uint8_t sidx = 0; - - static unsigned char packetBuffer[NTP_PACKET_SIZE]; - unsigned char ntpip[4] = { - os.iopts[IOPT_NTP_IP1], - os.iopts[IOPT_NTP_IP2], - os.iopts[IOPT_NTP_IP3], - os.iopts[IOPT_NTP_IP4]}; - unsigned char tries=0; - ulong startt = millis(); - while(tries1577836800UL) { - udp.stop(); - DEBUG_PRINT(F("took ")); - DEBUG_PRINT(millis()-startt); - DEBUG_PRINTLN(F("ms")); - return gt; - } - } - } - tries++; - udp.stop(); - sidx=(sidx+1)%N_PUBLIC_SERVERS; - } - if(tries==NTP_NTRIES) {DEBUG_PRINTLN(F("NTP failed!!"));} - udp.stop(); - return 0; -} -#endif #endif diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 1635236c..930abe41 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -21,84 +21,13 @@ * . */ -#ifndef _OPENSPRINKLER_SERVER_H -#define _OPENSPRINKLER_SERVER_H +#pragma once -#if !defined(ARDUINO) +#include "types.h" +#include +#include "bfiller.h" + +#if !defined(ESP8266) #include #include #endif - -char dec2hexchar(unsigned char dec); - -class BufferFiller { - char *start; //!< Pointer to start of buffer - char *ptr; //!< Pointer to cursor position - size_t len; -public: - BufferFiller () {} - BufferFiller (char *buf, size_t buffer_len) { - start = buf; - ptr = buf; - len = buffer_len; - } - - char* buffer () const { return start; } - size_t length () const { return len; } - unsigned int position () const { return ptr - start; } - - void emit_p(PGM_P fmt, ...) { - va_list ap; - va_start(ap, fmt); - for (;;) { - char c = pgm_read_byte(fmt++); - if (c == 0) - break; - if (c != '$') { - *ptr++ = c; - continue; - } - c = pgm_read_byte(fmt++); - switch (c) { - case 'D': - // itoa(va_arg(ap, int), (char*) ptr, 10); // ray - snprintf((char*) ptr, len - position(), "%d", va_arg(ap, int)); - break; - case 'L': - // ultoa(va_arg(ap, uint32_t), (char*) ptr, 10); - snprintf((char*) ptr, len - position(), "%lu", (unsigned long) va_arg(ap, uint32_t)); - break; - case 'S': - strcpy((char*) ptr, va_arg(ap, const char*)); - break; - case 'X': { - char d = va_arg(ap, int); - *ptr++ = dec2hexchar((d >> 4) & 0x0F); - *ptr++ = dec2hexchar(d & 0x0F); - } - continue; - case 'F': { - PGM_P s = va_arg(ap, PGM_P); - char d; - while ((d = pgm_read_byte(s++)) != 0) - *ptr++ = d; - continue; - } - case 'O': { - uint16_t oid = va_arg(ap, int); - file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); - } - break; - default: - *ptr++ = c; - continue; - } - ptr += strlen((char*) ptr); - } - *(ptr)=0; - va_end(ap); - } -}; - - -#endif // _OPENSPRINKLER_SERVER_H diff --git a/platformio.ini b/platformio.ini index a14fcd48..014839b0 100644 --- a/platformio.ini +++ b/platformio.ini @@ -18,10 +18,10 @@ board = d1_mini framework = arduino extra_scripts = pre:run_prebuild.py lib_ldf_mode = deep -lib_deps = - https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip - knolleary/PubSubClient @ ^2.8 - https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.2.0 +lib_deps = + https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip + knolleary/PubSubClient @ ^2.8 + https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.2.0 ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - -- upload_speed = 460800 @@ -33,23 +33,6 @@ board_build.f_flash = 80000000L build_flags = -DARP_TABLE_SIZE=40 ;build_flags = -DENABLE_DEBUG -[env:os23_atmega1284p] -platform = atmelavr -board = ATmega1284P -board_build.f_cpu = 16000000L -board_build.variant = sanguino -framework = arduino -lib_ldf_mode = deep -lib_deps = - https://github.com/UIPEthernet/UIPEthernet/archive/refs/tags/v2.0.12.zip - knolleary/PubSubClient @ ^2.8 - https://github.com/greiman/SdFat/archive/refs/tags/1.0.7.zip - Wire -build_src_filter = +<*> - -- -monitor_speed=115200 -upload_protocol = arduino -upload_speed = 115200 - ; The following env is for syntax highlighting only, ; it is NOT for building the firmware for Linux. ; To build the firmware for Linux, please follow: diff --git a/program.cpp b/program.cpp index fce3e1d7..89e2584e 100644 --- a/program.cpp +++ b/program.cpp @@ -227,7 +227,7 @@ int16_t ProgramStruct::starttime_decode(int16_t t) { /** Check if a given time matches the program's start day */ unsigned char ProgramStruct::check_day_match(time_os_t t) { -#if defined(ARDUINO) // get current time from Arduino +#if defined(ESP8266) // get current time from Arduino unsigned char weekday_t = weekday(t); // weekday ranges from [0,6] within Sunday being 1 unsigned char day_t = day(t); unsigned char month_t = month(t); diff --git a/program.h b/program.h index 1da1d6a5..ac899be6 100644 --- a/program.h +++ b/program.h @@ -21,9 +21,7 @@ * . */ - -#ifndef _PROGRAM_H -#define _PROGRAM_H +#pragma once #define MAX_NUM_PROGRAMS 40 // maximum number of programs #define MAX_NUM_STARTTIMES 4 @@ -157,5 +155,3 @@ class ProgramData { static void load_count(); static void save_count(); }; - -#endif // _PROGRAM_H diff --git a/rpitime.h b/rpitime.h index 49ff07e8..ae617894 100644 --- a/rpitime.h +++ b/rpitime.h @@ -1,5 +1,5 @@ -#ifndef RPI_TIME_H -#define RPI_TIME_H +#pragma once +#if !defined(ARDUINO) #include diff --git a/sensor.cpp b/sensor.cpp new file mode 100644 index 00000000..cdf69f43 --- /dev/null +++ b/sensor.cpp @@ -0,0 +1,630 @@ +#include "sensor.h" +#include "OpenSprinkler.h" + +const char *enum_string(SensorUnitGroup group) { + switch (group) { + case SensorUnitGroup::None: + return PSTR("No Group"); + case SensorUnitGroup::Temperature: + return PSTR("Temperature"); + case SensorUnitGroup::Length: + return PSTR("Length"); + case SensorUnitGroup::Volume: + return PSTR("Volume"); + case SensorUnitGroup::Light: + return PSTR("Light"); + case SensorUnitGroup::Energy: + return PSTR("Energy"); + case SensorUnitGroup::Velocity: + return PSTR("Velocity"); + case SensorUnitGroup::Pressure: + return PSTR("Pressure"); + case SensorUnitGroup::Flow: + return PSTR("Flow"); + case SensorUnitGroup::MAX_VALUE: + return nullptr; + } + + return nullptr; +} + +const char *enum_string(EnsembleAction action) { + switch (action) { + case EnsembleAction::Min: return PSTR("Min"); + case EnsembleAction::Max: return PSTR("Max"); + case EnsembleAction::Average: return PSTR("Average"); + case EnsembleAction::Sum: return PSTR("Sum"); + case EnsembleAction::Product: return PSTR("Product"); + case EnsembleAction::MAX_VALUE: return nullptr; + } + + return nullptr; +} + +const char *enum_string(WeatherAction action) { + switch (action) { + case WeatherAction::MAX_VALUE: return nullptr; + } + + return nullptr; +} + +const char* get_sensor_unit_name(SensorUnit unit) { + switch (unit) { + case SensorUnit::None: + return PSTR("None"); + case SensorUnit::Celsius: + return PSTR("Celsius"); + case SensorUnit::Fahrenheit: + return PSTR("Fahrenheit"); + case SensorUnit::Kelvin: + return PSTR("Kelvin"); + case SensorUnit::Milimeter: + return PSTR("Milimeter"); + case SensorUnit::Centieter: + return PSTR("Centieter"); + case SensorUnit::Meter: + return PSTR("Meter"); + case SensorUnit::Kilometer: + return PSTR("Kilometer"); + case SensorUnit::Inch: + return PSTR("Inch"); + case SensorUnit::Foot: + return PSTR("Foot"); + case SensorUnit::Mile: + return PSTR("Mile"); + case SensorUnit::Lux: + return PSTR("Lux"); + case SensorUnit::Lumen: + return PSTR("Lumen"); + case SensorUnit::Milivolt: + return PSTR("Milivolt"); + case SensorUnit::Volt: + return PSTR("Volt"); + case SensorUnit::Miliampere: + return PSTR("Miliampere"); + case SensorUnit::Ampere: + return PSTR("Ampere"); + case SensorUnit::Percent: + return PSTR("Percent"); + case SensorUnit::MilesPerHour: + return PSTR("Miles Per Hour"); + case SensorUnit::KilometersPerHour: + return PSTR("Kilometers Per Hour"); + case SensorUnit::MetersPerSecond: + return PSTR("Meters Per Second"); + case SensorUnit::DialetricConstant: + return PSTR("Dialetric Constant"); + case SensorUnit::PartsPerMillion: + return PSTR("Parts Per Million"); + case SensorUnit::Ohm: + return PSTR("Ohm"); + case SensorUnit::Miliohm: + return PSTR("Miliohm"); + case SensorUnit::Kiloohm: + return PSTR("Kiloohm"); + case SensorUnit::Bar: + return PSTR("Bar"); + case SensorUnit::Kilopascal: + return PSTR("Kilopascal"); + case SensorUnit::Pascal: + return PSTR("Pascal"); + case SensorUnit::Torr: + return PSTR("Torr"); + case SensorUnit::LitersPerSecond: + return PSTR("Liters Per Second"); + case SensorUnit::GallonsPerSecond: + return PSTR("Gallons"); + case SensorUnit::MAX_VALUE: + return nullptr; + } + + return nullptr; +} + +const char* get_sensor_unit_short(SensorUnit unit) { + switch (unit) { + case SensorUnit::None: + return PSTR(""); + case SensorUnit::Celsius: + return PSTR("°C"); + case SensorUnit::Fahrenheit: + return PSTR("°F"); + case SensorUnit::Kelvin: + return PSTR("K"); + case SensorUnit::Milimeter: + return PSTR("mm"); + case SensorUnit::Centieter: + return PSTR("cm"); + case SensorUnit::Meter: + return PSTR("m"); + case SensorUnit::Kilometer: + return PSTR("km"); + case SensorUnit::Inch: + return PSTR("in"); + case SensorUnit::Foot: + return PSTR("ft"); + case SensorUnit::Mile: + return PSTR("mi"); + case SensorUnit::Lux: + return PSTR("lx"); + case SensorUnit::Lumen: + return PSTR("lm"); + case SensorUnit::Milivolt: + return PSTR("mV"); + case SensorUnit::Volt: + return PSTR("V"); + case SensorUnit::Miliampere: + return PSTR("mA"); + case SensorUnit::Ampere: + return PSTR("A"); + case SensorUnit::Percent: + return PSTR("%"); + case SensorUnit::MilesPerHour: + return PSTR("mph"); + case SensorUnit::KilometersPerHour: + return PSTR("km/h"); + case SensorUnit::MetersPerSecond: + return PSTR("xxx"); + case SensorUnit::DialetricConstant: + return PSTR("xxx"); + case SensorUnit::PartsPerMillion: + return PSTR("ppm"); + case SensorUnit::Ohm: + return PSTR("Ω"); + case SensorUnit::Miliohm: + return PSTR("mΩ"); + case SensorUnit::Kiloohm: + return PSTR("kΩ"); + case SensorUnit::Bar: + return PSTR("bar"); + case SensorUnit::Kilopascal: + return PSTR("kPa"); + case SensorUnit::Pascal: + return PSTR("Pa"); + case SensorUnit::Torr: + return PSTR("torr"); + case SensorUnit::LitersPerSecond: + return PSTR("L/s"); + case SensorUnit::GallonsPerSecond: + return PSTR("gal/s"); + case SensorUnit::MAX_VALUE: + return nullptr; + } + + return nullptr; +} + +const SensorUnitGroup get_sensor_unit_group(SensorUnit unit) { + switch (unit) { + case SensorUnit::None: + return SensorUnitGroup::None; + case SensorUnit::Celsius: + return SensorUnitGroup::Temperature; + case SensorUnit::Fahrenheit: + return SensorUnitGroup::Temperature; + case SensorUnit::Kelvin: + return SensorUnitGroup::Temperature; + case SensorUnit::Milimeter: + return SensorUnitGroup::Length; + case SensorUnit::Centieter: + return SensorUnitGroup::Length; + case SensorUnit::Meter: + return SensorUnitGroup::Length; + case SensorUnit::Kilometer: + return SensorUnitGroup::Length; + case SensorUnit::Inch: + return SensorUnitGroup::Length; + case SensorUnit::Foot: + return SensorUnitGroup::Length; + case SensorUnit::Mile: + return SensorUnitGroup::Length; + case SensorUnit::Lux: + return SensorUnitGroup::Light; + case SensorUnit::Lumen: + return SensorUnitGroup::Light; + case SensorUnit::Milivolt: + return SensorUnitGroup::Energy; + case SensorUnit::Volt: + return SensorUnitGroup::Energy; + case SensorUnit::Miliampere: + return SensorUnitGroup::Energy; + case SensorUnit::Ampere: + return SensorUnitGroup::Energy; + case SensorUnit::Percent: + return SensorUnitGroup::None; + case SensorUnit::MilesPerHour: + return SensorUnitGroup::Velocity; + case SensorUnit::KilometersPerHour: + return SensorUnitGroup::Velocity; + case SensorUnit::MetersPerSecond: + return SensorUnitGroup::Velocity; + case SensorUnit::DialetricConstant: + return SensorUnitGroup::Energy; + case SensorUnit::PartsPerMillion: + return SensorUnitGroup::None; + case SensorUnit::Ohm: + return SensorUnitGroup::Energy; + case SensorUnit::Miliohm: + return SensorUnitGroup::Energy; + case SensorUnit::Kiloohm: + return SensorUnitGroup::Energy; + case SensorUnit::Bar: + return SensorUnitGroup::Pressure; + case SensorUnit::Kilopascal: + return SensorUnitGroup::Pressure; + case SensorUnit::Pascal: + return SensorUnitGroup::Pressure; + case SensorUnit::Torr: + return SensorUnitGroup::Pressure; + case SensorUnit::LitersPerSecond: + return SensorUnitGroup::Flow; + case SensorUnit::GallonsPerSecond: + return SensorUnitGroup::Flow; + case SensorUnit::MAX_VALUE: + return SensorUnitGroup::MAX_VALUE; + } + + return SensorUnitGroup::MAX_VALUE; +} + +const ulong get_sensor_unit_index(SensorUnit unit) { + switch (unit) { + case SensorUnit::None: + return 0; + case SensorUnit::Celsius: + return 0; + case SensorUnit::Fahrenheit: + return 0; + case SensorUnit::Kelvin: + return 0; + case SensorUnit::Milimeter: + return 0; + case SensorUnit::Centieter: + return 0; + case SensorUnit::Meter: + return 0; + case SensorUnit::Kilometer: + return 0; + case SensorUnit::Inch: + return 0; + case SensorUnit::Foot: + return 0; + case SensorUnit::Mile: + return 0; + case SensorUnit::Lux: + return 0; + case SensorUnit::Lumen: + return 0; + case SensorUnit::Milivolt: + return 0; + case SensorUnit::Volt: + return 0; + case SensorUnit::Miliampere: + return 0; + case SensorUnit::Ampere: + return 0; + case SensorUnit::Percent: + return 0; + case SensorUnit::MilesPerHour: + return 0; + case SensorUnit::KilometersPerHour: + return 0; + case SensorUnit::MetersPerSecond: + return 0; + case SensorUnit::DialetricConstant: + return 0; + case SensorUnit::PartsPerMillion: + return 0; + case SensorUnit::Ohm: + return 0; + case SensorUnit::Miliohm: + return 0; + case SensorUnit::Kiloohm: + return 0; + case SensorUnit::Bar: + return 0; + case SensorUnit::Kilopascal: + return 0; + case SensorUnit::Pascal: + return 0; + case SensorUnit::Torr: + return 0; + case SensorUnit::LitersPerSecond: + return 0; + case SensorUnit::GallonsPerSecond: + return 0; + case SensorUnit::MAX_VALUE: + return 0; + } + + return 0; +} + +Sensor::Sensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags) : + interval(interval), min(min), max(max), scale(scale), offset(offset), unit(unit), flags(flags) { + strncpy(this->name, name, SENSOR_NAME_LEN); + this->name[SENSOR_NAME_LEN - 1] = 0; +} + +Sensor::Sensor() {} + +double Sensor::get_new_value() { + double value = this->_get_raw_value(); + value = (value * this->scale) + this->offset; + if (value < this->min) value = this->min; + if (value > this->max) value = this->max; + + return value; +} + +template +uint32_t write_buf(char* buf, T val) { + std::memcpy(buf, &val, sizeof(val)); + return sizeof(val); +} + +template +T read_buf(char* buf, uint32_t* i) { + T val; + std::memcpy(&val, buf + (*i), sizeof(T)); + *i += sizeof(T); + return val; +} + + +uint32_t Sensor::serialize(char* buf) { + uint32_t i = 0; + + buf[i++] = static_cast(this->get_sensor_type()); + memcpy(buf + i, this->name, SENSOR_NAME_LEN); + i += SENSOR_NAME_LEN; + buf[i++] = static_cast(this->unit); + i += write_buf(buf + i, this->interval); + i += write_buf(buf + i, this->flags); + i += write_buf(buf + i, this->scale); + i += write_buf(buf + i, this->offset); + i += write_buf(buf + i, this->min); + i += write_buf(buf + i, this->max); + + i += this->_serialize_internal(buf + i); + return i; +} + +uint32_t Sensor::_deserialize(char* buf) { + uint32_t i = 1; // Skip sensor type + + memcpy(this->name, buf + i, SENSOR_NAME_LEN); + i += SENSOR_NAME_LEN; + this->unit = static_cast(buf[i++]); + this->interval = read_buf(buf, &i); + this->flags = read_buf(buf, &i); + this->scale = read_buf(buf, &i); + this->offset = read_buf(buf, &i); + this->min = read_buf(buf, &i); + this->max = read_buf(buf, &i); + + return i; +} + +EnsembleSensor::EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, sensor_memory_t* sensors, ensemble_children_t* children, uint8_t children_count, EnsembleAction action) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + action(action), + sensors(sensors) { + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + if (i < children_count) { + this->children[i] = children[i]; + } + else { + this->children[i] = ensemble_children_t{ sensor_id: 255, min : 0.0, max : 0.0, scale : 0.0, offset : 0.0 }; + } + } +} + +void EnsembleSensor::emit_extra_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"action\":$D,\"children\":["), this->action); + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + if (i) bfill->emit_p(PSTR(",")); + ensemble_children_t* child = &this->children[i]; + bfill->emit_p(PSTR("{\"sid\":$D,\"max\":$E,\"min\":$E,\"scale\":$E,\"offset\":$E}"), child->sensor_id, child->max, child->min, child->scale, child->offset); + } + bfill->emit_p(PSTR("]}")); +} + +void EnsembleSensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"name\":\"Ensemble Sensor\",\"args\":[{\"name\":\"Argument Sensors\",\"arg\":\"children\",\"type\":\"array::4\",\"extra\":[{\"name\":\"Sensor ID\",\"arg\":\"sid\",\"type\":\"sensor\",\"default\":\"\",\"extra\":[]},{\"name\":\"Minimum Value\",\"arg\":\"min\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Maximum Value\",\"arg\":\"max\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Scale\",\"arg\":\"scale\",\"type\":\"double\",\"default\":\"\",\"extra\":[]},{\"name\":\"Offset\",\"arg\":\"offset\",\"type\":\"double\",\"default\":\"\",\"extra\":[]}]},{\"name\":\"Ensemble Action\",\"arg\":\"action\",\"type\":\"enum::EnsembleAction\",\"default\":\"0\",\"extra\":[]}]}")); +} + +double EnsembleSensor::get_initial_value() { + switch (this->action) { + case EnsembleAction::Min: + return this->max; + break; + case EnsembleAction::Max: + return this->min; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + return 0; + break; + case EnsembleAction::Product: + return 1; + break; + default: + // Unreachable + return 0.0; + } +} + +double EnsembleSensor::_get_raw_value() { + double inital = this->get_initial_value(); + uint8_t count = 0; + + for (size_t i = 0; i < ENSEMBLE_SENSOR_CHILDREN_COUNT; i++) { + uint8_t sensor = this->children[i].sensor_id; + if (sensor < MAX_SENSORS && sensors[sensor].interval) { + double value = sensors[sensor].value; + value = (value * this->children[i].scale) + this->children[i].offset; + if (value < this->children[i].min) value = this->children[i].min; + if (value > this->children[i].max) value = this->children[i].max; + + switch (this->action) { + case EnsembleAction::Min: + if (value < inital) inital = value; + break; + case EnsembleAction::Max: + if (value > inital) inital = value; + break; + case EnsembleAction::Average: + case EnsembleAction::Sum: + inital += value; + break; + case EnsembleAction::Product: + inital *= value; + break; + default: + // Unreachable + return 0.0; + } + + count += 1; + } + } + + if (count == 0) { + return 0.0; + } + else if (this->action == EnsembleAction::Average) { + return inital / (double)count; + } + else { + return inital; + } +} + +uint32_t EnsembleSensor::_serialize_internal(char* buf) { + uint32_t i = 0; + for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { + i += write_buf(buf + i, this->children[j].sensor_id); + i += write_buf(buf + i, this->children[j].min); + i += write_buf(buf + i, this->children[j].max); + i += write_buf(buf + i, this->children[j].scale); + i += write_buf(buf + i, this->children[j].offset); + } + + buf[i++] = static_cast(this->action); + return i; +} + +EnsembleSensor::EnsembleSensor(sensor_memory_t* sensors, char* buf) { + uint32_t i = Sensor::_deserialize(buf); + for (size_t j = 0; j < ENSEMBLE_SENSOR_CHILDREN_COUNT; j++) { + this->children[j].sensor_id = read_buf(buf, &i); + this->children[j].min = read_buf(buf, &i); + this->children[j].max = read_buf(buf, &i); + this->children[j].scale = read_buf(buf, &i); + this->children[j].offset = read_buf(buf, &i); + } + + this->action = static_cast(buf[i++]); + this->sensors = sensors; +} + + +WeatherSensor::WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char* name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action) : + Sensor(interval, min, max, scale, offset, name, unit, flags), + action(action), + weather_getter(weather_getter) { +} + +void WeatherSensor::emit_extra_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"action\":$D}"), this->action); +} + +void WeatherSensor::emit_description_json(BufferFiller* bfill) { + bfill->emit_p(PSTR("{\"name\":\"Weather Sensor\",\"args\":[{\"name\":\"Weather Information\",\"arg\":\"action\",\"type\":\"enum::WeatherAction\",\"default\":\"0\",\"extra\":[]}]}")); +} + +double WeatherSensor::get_initial_value() { + return 0.0; +} + +double WeatherSensor::_get_raw_value() { + return this->weather_getter(this->action); +} + +uint32_t WeatherSensor::_serialize_internal(char* buf) { + uint32_t i = 0; + buf[i++] = static_cast(this->action); + return i; +} + +WeatherSensor::WeatherSensor(WeatherGetter weather_getter, char* buf) { + uint32_t i = Sensor::_deserialize(buf); + this->action = static_cast(buf[i++]); +} + +SensorAdjustment::SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t* points) { + this->flags = flags; + this->sid = sid; + if (point_count > SENSOR_ADJUSTMENT_POINTS) point_count = SENSOR_ADJUSTMENT_POINTS; + this->point_count = point_count; + for (size_t i = 0; i < point_count; i++) { + this->points[i] = points[i]; + } + +} + +SensorAdjustment::SensorAdjustment(char* buf) { + uint32_t i = 0; + this->flags = buf[i++]; + this->sid = buf[i++]; + this->point_count = buf[i++]; + + for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { + this->points[j].x = read_buf(buf, &i); + this->points[j].y = read_buf(buf, &i); + } +} + +double SensorAdjustment::get_adjustment_factor(sensor_memory_t* sensors) { + if (this->flags & (1 << SENADJ_FLAG_ENABLE) && this->sid < MAX_SENSORS && sensors[this->sid].interval) { + double value = sensors[this->sid].value; + if (value <= this->points[0].x) return this->points[0].y; + if (value >= this->points[this->point_count - 1].x) return this->points[this->point_count - 1].y; + + uint8_t i; + + for (i = 0; i < this->point_count - 1; i++) { + if (value >= this->points[i].x) { + break; + } + } + + sensor_adjustment_point_t left = this->points[i]; + sensor_adjustment_point_t right = this->points[i + 1]; + if (right.x == left.x) return left.y; + + value = (value - left.x) / (right.x - left.x) * (right.y - left.y) + left.y; + + if (value < 0) return 0; + return value; + } + else { + return 1.0; + } +} + +uint32_t SensorAdjustment::serialize(char* buf) { + uint32_t i = 0; + buf[i++] = this->flags; + buf[i++] = this->sid; + buf[i++] = this->point_count; + + for (size_t j = 0; j < SENSOR_ADJUSTMENT_POINTS; j++) { + i += write_buf(buf + i, this->points[j].x); + i += write_buf(buf + i, this->points[j].y); + } + + return i; +} \ No newline at end of file diff --git a/sensor.h b/sensor.h new file mode 100644 index 00000000..45a7359b --- /dev/null +++ b/sensor.h @@ -0,0 +1,226 @@ +#ifndef SENSOR_H +#define SENSOR_H + +#include +#if defined(ARDUINO) +#include +#else +#include "utils.h" +#endif +#include "defines.h" +#include "bfiller.h" + +#define SENSOR_NAME_LEN 33 +#define SENSOR_CUSTOM_UNIT_LEN 9 + +typedef struct { + ulong interval; + uint32_t flags; + ulong next_update; + double value; +} sensor_memory_t; + +enum class SensorType { + Ensemble, + ADS1115, + Weather, + MAX_VALUE, +}; + +enum class SensorUnitGroup { + None, + Temperature, + Length, + Volume, + Light, + Energy, + Velocity, + Pressure, + Flow, + MAX_VALUE, +}; + +enum class SensorUnit { + None, + Celsius, + Fahrenheit, + Kelvin, + Milimeter, + Centieter, + Meter, + Kilometer, + Inch, + Foot, + Mile, + Lux, + Lumen, + Milivolt, + Volt, + Miliampere, + Ampere, + Percent, + MilesPerHour, + KilometersPerHour, + MetersPerSecond, + DialetricConstant, + PartsPerMillion, + Ohm, + Miliohm, + Kiloohm, + Bar, + Kilopascal, + Pascal, + Torr, + LitersPerSecond, + GallonsPerSecond, + MAX_VALUE, +}; + +typedef enum { + SENSOR_FLAG_ENABLE = 0, + SENSOR_FLAG_LOG, + SENSOR_FLAG_COUNT +} sensor_flags; + +class Sensor { +public: + Sensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags); + Sensor(); + virtual ~Sensor() {} + + double get_new_value(); + uint32_t serialize(char *buf); + + void virtual emit_extra_json(BufferFiller *bfill) = 0; + + unsigned long interval = 1; + double min = 0.0; + double max = 0.0; + double scale = 0.0; + double offset = 0.0; + char name[SENSOR_NAME_LEN] = {0}; + SensorUnit unit = SensorUnit::None; + + uint32_t flags = 0; + + SensorType virtual get_sensor_type() = 0; + double virtual get_initial_value() = 0; + + private: + double virtual _get_raw_value() = 0; + protected: + uint32_t _deserialize(char *buf); + uint32_t virtual _serialize_internal(char *buf) = 0; +}; + +enum class EnsembleAction { + Min, + Max, + Average, + Sum, + Product, + MAX_VALUE, +}; + +typedef Sensor* (*SensorGetter)(uint8_t); + +typedef struct { + uint8_t sensor_id; + double min; + double max; + double scale; + double offset; +} ensemble_children_t; + +#define ENSEMBLE_SENSOR_CHILDREN_COUNT 4 + +class EnsembleSensor : public Sensor { + public: + EnsembleSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, sensor_memory_t *sensors, ensemble_children_t *children, uint8_t children_count, EnsembleAction action); + EnsembleSensor(sensor_memory_t *sensors, char *buf); + + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); + + SensorType get_sensor_type() { + return SensorType::Ensemble; + } + + ensemble_children_t children[ENSEMBLE_SENSOR_CHILDREN_COUNT]; + EnsembleAction action; + + double get_initial_value(); + + private: + double _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + sensor_memory_t *sensors; +}; + +enum class WeatherAction { + MAX_VALUE, +}; + +typedef double (*WeatherGetter)(WeatherAction); + +class WeatherSensor : public Sensor { + public: + WeatherSensor(unsigned long interval, double min, double max, double scale, double offset, const char *name, SensorUnit unit, uint32_t flags, WeatherGetter weather_getter, WeatherAction action); + WeatherSensor(WeatherGetter weather_getter, char *buf); + + void emit_extra_json(BufferFiller *bfill); + static void emit_description_json(BufferFiller *bfill); + + SensorType get_sensor_type() { + return SensorType::Weather; + } + + WeatherAction action; + + double get_initial_value(); + + private: + double _get_raw_value(); + uint32_t _serialize_internal(char *buf); + + WeatherGetter weather_getter; +}; + +typedef struct { + double x; + double y; +} sensor_adjustment_point_t; + +#define SENSOR_ADJUSTMENT_POINTS 8 + +typedef enum { + SENADJ_FLAG_ENABLE = 0, +} senadj_flags; + +class SensorAdjustment { +public: + SensorAdjustment(uint8_t flags, uint8_t sid, uint8_t point_count, sensor_adjustment_point_t *points); + SensorAdjustment(char *buf); + + double get_adjustment_factor(sensor_memory_t *sensors); + uint32_t serialize(char *buf); + + uint8_t flags; + uint8_t sid; + uint8_t point_count; + sensor_adjustment_point_t points[SENSOR_ADJUSTMENT_POINTS]; +}; + +#define SENSOR_ADJUSTMENT_SIZE (3 + (SENSOR_ADJUSTMENT_POINTS * sizeof(sensor_adjustment_point_t))) + +const char *enum_string(SensorUnitGroup group); +const char *enum_string(EnsembleAction action); +const char *enum_string(WeatherAction action); + +const char* get_sensor_unit_name(SensorUnit unit); +const char* get_sensor_unit_short(SensorUnit unit); +const SensorUnitGroup get_sensor_unit_group(SensorUnit unit); +const ulong get_sensor_unit_index(SensorUnit unit); + +#endif //SENSOR_H \ No newline at end of file diff --git a/types.h b/types.h index d03aee03..e4d7831c 100644 --- a/types.h +++ b/types.h @@ -1,12 +1,10 @@ -#ifndef TYPES_H -#define TYPES_H +#pragma once + #include -#if defined(ARDUINO) +#if defined(ESP8266) typedef unsigned long time_os_t; #else #include typedef time_t time_os_t; #endif - -#endif \ No newline at end of file diff --git a/utils.cpp b/utils.cpp index 18de656b..23cf94a4 100644 --- a/utils.cpp +++ b/utils.cpp @@ -26,16 +26,9 @@ #include "OpenSprinkler.h" extern OpenSprinkler os; -#if defined(ARDUINO) // Arduino - - #if defined(ESP8266) - #include - #include - #else - #include - #include "SdFat.h" - extern SdFat sd; - #endif +#if defined(ESP8266) // Arduino + #include + #include #else // RPI/LINUX @@ -296,12 +289,6 @@ void remove_file(const char *fn) { if(!LittleFS.exists(fn)) return; LittleFS.remove(fn); -#elif defined(ARDUINO) - - sd.chdir("/"); - if (!sd.exists(fn)) return; - sd.remove(fn); - #else remove(get_filename_fullpath(fn)); @@ -314,11 +301,6 @@ bool file_exists(const char *fn) { return LittleFS.exists(fn); -#elif defined(ARDUINO) - - sd.chdir("/"); - return sd.exists(fn); - #else FILE *file; @@ -329,6 +311,104 @@ bool file_exists(const char *fn) { #endif } +os_file_type file_open(const char *fn, FileOpenMode mode) { + #if defined(ESP8266) + switch (mode) { + default: + case FileOpenMode::Read: + return LittleFS.open(fn, "r"); + case FileOpenMode::ReadWrite: + if (!LittleFS.exists(fn)) { + File f = LittleFS.open(fn, "w"); + if (!f) return f; + f.close(); + } + return LittleFS.open(fn, "r+"); + case FileOpenMode::WriteTruncate: + return LittleFS.open(fn, "w"); + case FileOpenMode::ReadWriteTruncate: + return LittleFS.open(fn, "w+"); + case FileOpenMode::Append: + return LittleFS.open(fn, "a"); + case FileOpenMode::ReadAppend: + return LittleFS.open(fn, "a+"); + } + #else + char *full_file = get_filename_fullpath(fn); + switch (mode) { + default: + case FileOpenMode::Read: + return fopen(full_file, "rb"); + case FileOpenMode::ReadWrite: { + int fd = open(full_file, O_RDWR | O_CREAT, 0644); + if (fd == -1) return nullptr; + return fdopen(fd, "rb+"); + } + case FileOpenMode::WriteTruncate: + return fopen(full_file, "wb"); + case FileOpenMode::ReadWriteTruncate: + return fopen(full_file, "wb+"); + case FileOpenMode::Append: + return fopen(full_file, "ab"); + case FileOpenMode::ReadAppend: + return fopen(full_file, "ab+"); + } + + #endif +} + +void file_close(os_file_type f) { + #if defined(ESP8266) + f.close(); + #else + fclose(f); + #endif +} + +bool file_seek(os_file_type f, uint32_t position, FileSeekMode mode) { + #if defined(ESP8266) + switch (mode) { + case FileSeekMode::Set: + return f.seek(position, fs::SeekMode::SeekSet); + case FileSeekMode::Current: + return f.seek(position, fs::SeekMode::SeekCur); + case FileSeekMode::End: + return f.seek(position, fs::SeekMode::SeekEnd); + } + #else + switch (mode) { + case FileSeekMode::Set: + return fseek(f, position, SEEK_SET); + case FileSeekMode::Current: + return fseek(f, position, SEEK_CUR); + case FileSeekMode::End: + return fseek(f, position, SEEK_END); + } + #endif + + return false; +} + +bool file_seek(os_file_type f, uint32_t position) { + return file_seek(f, position, FileSeekMode::Set); +} + +int file_read(os_file_type f, void *target, uint32_t len) { + #if defined(ESP8266) + return f.read((uint8_t*)target, len); + #else + return fread(target, 1, len, f); + #endif +} + +int file_write(os_file_type f, const void *source, uint32_t len) { + #if defined(ESP8266) + return f.write((const uint8_t*)source, len); + #else + return fwrite(source, 1, len, f); + #endif +} + // file functions void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { #if defined(ESP8266) @@ -341,16 +421,6 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { f.close(); } -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - if(file.open(fn, O_READ)) { - file.seekSet(pos); - file.read(dst, len); - file.close(); - } - #else FILE *fp = fopen(get_filename_fullpath(fn), "rb"); @@ -374,16 +444,6 @@ void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { f.close(); } -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - int ret = file.open(fn, O_CREAT | O_RDWR); - if(!ret) return; - file.seekSet(pos); - file.write(src, len); - file.close(); - #else FILE *fp = fopen(get_filename_fullpath(fn), "rb+"); @@ -414,18 +474,6 @@ void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) f.write((unsigned char*)tmp, len); f.close(); -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - int ret = file.open(fn, O_RDWR); - if(!ret) return; - file.seekSet(from); - file.read(tmp, len); - file.seekSet(to); - file.write(tmp, len); - file.close(); - #else FILE *fp = fopen(get_filename_fullpath(fn), "rb+"); @@ -456,21 +504,6 @@ unsigned char file_cmp_block(const char *fn, const char *buf, ulong pos) { return (*buf==c)?0:1; } -#elif defined(ARDUINO) - - sd.chdir("/"); - SdFile file; - if(file.open(fn, O_READ)) { - file.seekSet(pos); - char c = file.read(); - while(*buf && (c==*buf)) { - buf++; - c=file.read(); - } - file.close(); - return (*buf==c)?0:1; - } - #else FILE *fp = fopen(get_filename_fullpath(fn), "rb"); @@ -718,4 +751,9 @@ void str2mac(const char *_str, unsigned char mac[]) { yield(); } } -#endif \ No newline at end of file +#endif + +char dec2hexchar(unsigned char dec) { + if(dec<10) return '0'+dec; + else return 'A'+(dec-10); +} \ No newline at end of file diff --git a/utils.h b/utils.h index 24790bed..c3104f02 100644 --- a/utils.h +++ b/utils.h @@ -21,11 +21,12 @@ * . */ -#ifndef _UTILS_H -#define _UTILS_H + #pragma once -#if defined(ARDUINO) +#if defined(ESP8266) #include + #include + #include #else // headers for RPI/LINUX #include #include @@ -37,12 +38,42 @@ #endif #include "defines.h" + +#if defined(ESP8266) +typedef File os_file_type; +#else +typedef FILE* os_file_type; +#endif + +enum class FileOpenMode { + Read, + ReadWrite, + WriteTruncate, + ReadWriteTruncate, + Append, + ReadAppend, +}; + +enum class FileSeekMode { + Set, + Current, + End +}; + + // File reading/writing functions //remove unused functions: void write_to_file(const char *fname, const char *data, ulong size, ulong pos=0, bool trunc=true); //remove unused functions: void read_from_file(const char *fname, char *data, ulong maxsize=TMP_BUFFER_SIZE, int pos=0); void remove_file(const char *fname); bool file_exists(const char *fname); +os_file_type file_open(const char *fn, FileOpenMode mode); +void file_close(os_file_type f); +bool file_seek(os_file_type f, uint32_t position, FileSeekMode mode); +bool file_seek(os_file_type f, uint32_t position); +int file_read(os_file_type f, void *target, uint32_t len); +int file_write(os_file_type f, const void *source, uint32_t len); + void file_read_block (const char *fname, void *dst, ulong pos, ulong len); void file_write_block(const char *fname, const void *src, ulong pos, ulong len); void file_copy_block (const char *fname, ulong from, ulong to, ulong len, void *tmp=0); @@ -74,7 +105,7 @@ bool isValidMAC(const char *_mac); void str2mac(const char *_str, unsigned char mac[]); #endif -#if defined(ARDUINO) +#if defined(ESP8266) #else // Arduino compatible functions for RPI/LINUX const char* get_data_dir(); @@ -113,4 +144,4 @@ void str2mac(const char *_str, unsigned char mac[]); BoardType get_board_type(); #endif -#endif // _UTILS_H +char dec2hexchar(unsigned char dec); \ No newline at end of file diff --git a/weather.cpp b/weather.cpp index 628b40de..fd5c58a6 100644 --- a/weather.cpp +++ b/weather.cpp @@ -186,13 +186,6 @@ void GetWeather() { // Parse protocol and extract host/port char *host_start = host; -#if defined(OS_AVR) - if (strncmp_P(host, PSTR("http://"), 7) == 0) { - host_start = host + 7; - } else if (strncmp_P(host, PSTR("https://"), 8) == 0) { // note that avr does not support https - host_start = host + 8; - } -#else bool use_ssl = true; // default to https int port = 443; // default to https port @@ -214,8 +207,6 @@ void GetWeather() { port = atoi(colon + 1); } -#endif - strcat(ether_buffer, " HTTP/1.0\r\nHOST: "); strcat(ether_buffer, host_start); strcat(ether_buffer, "\r\nUser-Agent: "); @@ -224,11 +215,7 @@ void GetWeather() { wt_errCode = HTTP_RQT_NOT_RECEIVED; DEBUG_PRINT(ether_buffer); -#if defined(OS_AVR) - int ret = os.send_http_request(host_start, ether_buffer, getweather_callback_with_peel_header); -#else int ret = os.send_http_request(host_start, port, ether_buffer, getweather_callback_with_peel_header, use_ssl); -#endif if(ret!=HTTP_RQT_SUCCESS) { if(wt_errCode < 0) wt_errCode = ret; // if wt_errCode > 0, the call is successful but weather script may return error @@ -270,7 +257,7 @@ void parse_wto(char* wto) { void apply_monthly_adjustment(time_os_t curr_time) { // ====== Check monthly water percentage ====== if(os.iopts[IOPT_USE_WEATHER]==WEATHER_METHOD_MONTHLY) { -#if defined(ARDUINO) +#if defined(ESP8266) unsigned char m = month(curr_time)-1; #else time_os_t ct = curr_time; diff --git a/weather.h b/weather.h index 1ac7a745..a8ad3621 100644 --- a/weather.h +++ b/weather.h @@ -21,9 +21,7 @@ * */ - -#ifndef _WEATHER_H -#define _WEATHER_H +#pragma once #define WEATHER_UPDATE_SUNRISE 0x01 #define WEATHER_UPDATE_SUNSET 0x02 @@ -45,4 +43,3 @@ extern unsigned char wt_monthly[]; extern unsigned char wt_restricted; void parse_wto(char* wto); void apply_monthly_adjustment(time_os_t curr_time); -#endif // _WEATHER_H
UI Source: