├── .gitignore ├── updater.sh ├── make.os23 ├── defines.cpp ├── examples └── mainArduino │ └── mainArduino.ino ├── make.lin32 ├── I2CRTC.h ├── mainArduino.ino ├── weather.h ├── espconnect.h ├── testmode.h ├── html ├── ap_update.html ├── sta_update.html ├── html2raw.cpp └── ap_home.html ├── etherport.h ├── SSD1306Display.h ├── espconnect.cpp ├── server.h ├── NTPClient.h ├── utils.h ├── README.txt ├── esp32.h ├── Common.mk ├── LiquidCrystal.h ├── OpenSprinkler.launch ├── program.h ├── gpio.h ├── etherport.cpp ├── I2CRTC.cpp ├── images.h ├── weather.cpp ├── NTPClient.cpp ├── TimeLib.h ├── htmls.h ├── TimeLib.cpp ├── program.cpp ├── LiquidCrystal.cpp ├── gpio.cpp ├── OpenSprinkler.h ├── utils.cpp └── defines.h /.gitignore: -------------------------------------------------------------------------------- 1 | logs/** 2 | OpenSprinkler 3 | wtopts.txt 4 | *.dat 5 | *.sh 6 | testmode.h 7 | build-1284/* 8 | -------------------------------------------------------------------------------- /updater.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | git pull 4 | ./build.sh -s ospi 5 | /etc/init.d/OpenSprinkler.sh restart 6 | -------------------------------------------------------------------------------- /make.os23: -------------------------------------------------------------------------------- 1 | OFLAG = -Os 2 | ARDMK_DIR = . 3 | ARDUINO_DIR = $(HOME)/arduino-1.8.5 4 | ALTERNATE_CORE_PATH = $(HOME)/.arduino15/packages/MightyCore/hardware/avr/2.0.1 5 | BOARD_TAG = 1284 6 | MCU = atmega1284p 7 | VARIANT = sanguino 8 | F_CPU = 16000000L 9 | ARDUINO_LIBS = UIPEthernet Wire SdFat SPI 10 | MONITOR_PORT = /dev/ttyUSB0 11 | MONITOR_BAUDRATE = 115200 12 | include ./Arduino.mk 13 | -------------------------------------------------------------------------------- /defines.cpp: -------------------------------------------------------------------------------- 1 | #if defined(ESP8266) || defined(ESP32) 2 | 3 | #include "defines.h" 4 | 5 | byte PIN_BUTTON_1 = 255; 6 | byte PIN_BUTTON_2 = 255; 7 | byte PIN_BUTTON_3 = 255; 8 | byte PIN_RFRX = 255; 9 | byte PIN_RFTX = 255; 10 | byte PIN_BOOST = 255; 11 | byte PIN_BOOST_EN = 255; 12 | byte PIN_LATCH_COM = 255; 13 | byte PIN_SENSOR1 = 255; 14 | byte PIN_SENSOR2 = 255; 15 | byte PIN_IOEXP_INT = 255; 16 | 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /examples/mainArduino/mainArduino.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #if defined(ESP8266) 4 | struct tcp_pcb; 5 | extern struct tcp_pcb* tcp_tw_pcbs; 6 | extern "C" void tcp_abort (struct tcp_pcb* pcb); 7 | void tcpCleanup() { // losing bytes work around 8 | while(tcp_tw_pcbs) { tcp_abort(tcp_tw_pcbs); } 9 | } 10 | #else 11 | #include 12 | #endif 13 | 14 | #include "OpenSprinkler.h" 15 | 16 | extern OpenSprinkler os; 17 | 18 | void do_setup(); 19 | void do_loop(); 20 | 21 | void setup() { 22 | do_setup(); 23 | } 24 | 25 | void loop() { 26 | do_loop(); 27 | #if defined(ESP8266) 28 | tcpCleanup(); 29 | #endif 30 | } 31 | -------------------------------------------------------------------------------- /make.lin32: -------------------------------------------------------------------------------- 1 | SKETCH = ./mainArduino.ino 2 | LIBS = . \ 3 | $(ESP_LIBS)/Wire \ 4 | $(ESP_LIBS)/SPI \ 5 | $(ESP_LIBS)/ESP8266WiFi \ 6 | $(ESP_LIBS)/ESP8266WebServer \ 7 | $(ESP_LIBS)/ESP8266mDNS \ 8 | ~/Arduino/libraries/SSD1306 \ 9 | ~/Arduino/libraries/rc-switch \ 10 | ~/Arduino/libraries/UIPEthernet \ 11 | 12 | ESP_ROOT = $(HOME)/esp8266_2.5.2/ 13 | ESPCORE_VERSION = 252 14 | BUILD_ROOT = /tmp/$(MAIN_NAME) 15 | 16 | UPLOAD_SPEED = 460800 17 | UPLOAD_VERB = -v 18 | # for OS3.0 revision 1: reset mode is nodemcu 19 | UPLOAD_RESET = nodemcu 20 | # Uncomment the line below for OS3.0 revision 0: reset mode is ck 21 | # UPLOAD_RESET = ck 22 | 23 | FLASH_DEF = 4M3M 24 | FLASH_MODE = dio 25 | FLASH_SPEED = 80 26 | F_CPU = 160000000L 27 | 28 | BOARD = generic 29 | 30 | EXCLUDE_DIRS = ./build-1284 31 | 32 | include ./makeEspArduino.mk 33 | -------------------------------------------------------------------------------- /I2CRTC.h: -------------------------------------------------------------------------------- 1 | /* 2 | * I2CRTC.h - library for common I2C RTCs 3 | * This library is intended to be uses with Arduino Time.h library functions 4 | */ 5 | 6 | 7 | #ifndef I2CRTC_h 8 | #define I2CRTC_h 9 | 10 | #define DS1307_ADDR 0x68 11 | #define MCP7940_ADDR 0x6F 12 | #define PCF8563_ADDR 0x51 13 | 14 | #include "TimeLib.h" 15 | 16 | // library interface description 17 | class I2CRTC 18 | { 19 | // user-accessible "public" interface 20 | public: 21 | I2CRTC(); 22 | static time_t get(); 23 | static void set(time_t t); 24 | static void read(tmElements_t &tm); 25 | static void write(tmElements_t &tm); 26 | static bool detect(); 27 | 28 | private: 29 | static uint8_t dec2bcd(uint8_t num); 30 | static uint8_t bcd2dec(uint8_t num); 31 | static uint8_t addr; 32 | }; 33 | 34 | extern I2CRTC RTC; 35 | 36 | #endif 37 | 38 | -------------------------------------------------------------------------------- /mainArduino.ino: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | #if defined(ESP8266) || defined(ESP32) 5 | struct tcp_pcb; 6 | extern struct tcp_pcb* tcp_tw_pcbs; 7 | extern "C" void tcp_abort (struct tcp_pcb* pcb); 8 | void tcpCleanup() { // losing bytes work around 9 | while(tcp_tw_pcbs) { tcp_abort(tcp_tw_pcbs); } 10 | } 11 | #else 12 | #include 13 | #endif 14 | 15 | #include "OpenSprinkler.h" 16 | 17 | 18 | 19 | extern OpenSprinkler os; 20 | 21 | void do_setup(); 22 | void do_loop(); 23 | 24 | void setup() { 25 | 26 | #if defined(ESP32) 27 | /* Seting internal station pins to prevent unstable behavior on startup */ 28 | int i; 29 | unsigned int pin_list[] = ON_BOARD_GPIN_LIST; 30 | for( i=0; i<8; i++ ){ 31 | if(pin_list[i] !=255){ 32 | pinMode(pin_list[i], OUTPUT); 33 | digitalWrite(pin_list[i], ~STATION_LOGIC); 34 | } 35 | } 36 | 37 | #endif 38 | 39 | 40 | do_setup(); 41 | } 42 | 43 | void loop() { 44 | do_loop(); 45 | #if defined(ESP8266) 46 | tcpCleanup(); 47 | #endif 48 | } 49 | -------------------------------------------------------------------------------- /weather.h: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * Weather functions header file 5 | * Feb 2015 @ OpenSprinkler.com 6 | * 7 | * This file is part of the OpenSprinkler library 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see 21 | * . 22 | */ 23 | 24 | 25 | #ifndef _WEATHER_H 26 | #define _WEATHER_H 27 | 28 | #define WEATHER_UPDATE_SUNRISE 0x01 29 | #define WEATHER_UPDATE_SUNSET 0x02 30 | #define WEATHER_UPDATE_EIP 0x04 31 | #define WEATHER_UPDATE_WL 0x08 32 | #define WEATHER_UPDATE_TZ 0x10 33 | #define WEATHER_UPDATE_RD 0x20 34 | 35 | void GetWeather(); 36 | 37 | extern char wt_rawData[]; 38 | extern int wt_errCode; 39 | #endif // _WEATHER_H 40 | -------------------------------------------------------------------------------- /espconnect.h: -------------------------------------------------------------------------------- 1 | /* ESPConnect header file 2 | * December 2016 @ opensprinkler.com 3 | * 4 | * This file is part of the OpenSprinkler library 5 | * 6 | * This program is free software: you can redistribute it and/or modify 7 | * it under the terms of the GNU General Public License as published by 8 | * the Free Software Foundation, either version 3 of the License, or 9 | * (at your option) any later version. 10 | * 11 | * This program is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | * GNU General Public License for more details. 15 | * 16 | * You should have received a copy of the GNU General Public License 17 | * along with this program. If not, see 18 | * . 19 | */ 20 | 21 | #if defined(ESP8266) || defined(ESP32) 22 | 23 | #ifndef _ESP_CONNECT_H 24 | #define _ESP_CONNECT_H 25 | 26 | #if defined(ESP8266) 27 | #include 28 | #include 29 | #elif defined(ESP32) 30 | #include 31 | #include 32 | #include 33 | #include 34 | #endif 35 | #include 36 | #include "time.h" 37 | #include "defines.h" 38 | #include "htmls.h" 39 | 40 | String scan_network(); 41 | void start_network_ap(const char *ssid, const char *pass); 42 | void start_network_sta(const char *ssid, const char *pass); 43 | void start_network_sta_with_ap(const char *ssid, const char *pass); 44 | #endif 45 | 46 | #endif 47 | -------------------------------------------------------------------------------- /testmode.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _TESTMODE_H 3 | #define _TESTMODE_H 4 | 5 | #if defined(ESP32) 6 | void SPIFFS_list_dir(); 7 | void scan_i2C(); 8 | 9 | void SPIFFS_list_dir() { 10 | 11 | 12 | if (!SPIFFS.begin(true)) { 13 | DEBUG_PRINTLN("An Error has occurred while mounting SPIFFS"); 14 | return; 15 | } 16 | 17 | File root = SPIFFS.open("/"); 18 | 19 | File file = root.openNextFile(); 20 | 21 | while(file){ 22 | 23 | DEBUG_PRINT("FILE: "); 24 | DEBUG_PRINTLN(file.name()); 25 | 26 | file = root.openNextFile(); 27 | } 28 | } 29 | 30 | void scan_i2c() { 31 | 32 | byte error, address; 33 | int nDevices; 34 | 35 | DEBUG_PRINTLN("Scanning i2c for devices..."); 36 | 37 | nDevices = 0; 38 | for(address = 1; address < 127; address++ ) 39 | { 40 | 41 | Wire.beginTransmission(address); 42 | error = Wire.endTransmission(); 43 | 44 | if (error == 0) 45 | { 46 | DEBUG_PRINT("I2C device found at address 0x"); 47 | if (address<16) 48 | DEBUG_PRINT("0"); 49 | DEBUG_PRINTX(address); 50 | DEBUG_PRINTLN(" !"); 51 | 52 | nDevices++; 53 | } 54 | else if (error==4) 55 | { 56 | DEBUG_PRINT("Unknow error at address 0x"); 57 | if (address<16) 58 | DEBUG_PRINT("0"); 59 | DEBUG_PRINTX(address); 60 | } 61 | } 62 | 63 | if (nDevices == 0) DEBUG_PRINTLN("No I2C devices found"); 64 | 65 | } 66 | 67 | #endif //ESP32 68 | #endif // _TESTMODE_H 69 | -------------------------------------------------------------------------------- /html/ap_update.html: -------------------------------------------------------------------------------- 1 | 2 | OpenSprinkler Firmware Update 3 | 4 | 5 | 6 |
7 |

OpenSprinkler AP-mode Firmware Update

8 |
9 |
10 | 11 | 12 | 13 | 14 |
Device password:
15 |
18 |
19 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /etherport.h: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * Linux Ethernet functions header file 5 | * This file is based on Richard Zimmerman's sprinklers_pi program 6 | * Copyright (c) 2013 Richard Zimmerman 7 | * 8 | * This file is part of the OpenSprinkler library 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see 22 | * . 23 | */ 24 | 25 | #ifndef _ETHERPORT_H_ 26 | #define _ETHERPORT_H_ 27 | 28 | #if defined(ARDUINO) 29 | 30 | #else // headers for RPI/BBB 31 | 32 | #include 33 | #include 34 | #include 35 | 36 | #ifdef __APPLE__ 37 | #define MSG_NOSIGNAL SO_NOSIGPIPE 38 | #endif 39 | 40 | class EthernetServer; 41 | 42 | class EthernetClient { 43 | public: 44 | EthernetClient(); 45 | EthernetClient(int sock); 46 | ~EthernetClient(); 47 | int connect(uint8_t ip[4], uint16_t port); 48 | bool connected(); 49 | void stop(); 50 | int read(uint8_t *buf, size_t size); 51 | size_t write(const uint8_t *buf, size_t size); 52 | operator bool(); 53 | int GetSocket() 54 | { 55 | return m_sock; 56 | } 57 | private: 58 | int m_sock; 59 | bool m_connected; 60 | friend class EthernetServer; 61 | }; 62 | 63 | class EthernetServer { 64 | public: 65 | EthernetServer(uint16_t port); 66 | ~EthernetServer(); 67 | 68 | bool begin(); 69 | EthernetClient available(); 70 | private: 71 | uint16_t m_port; 72 | int m_sock; 73 | }; 74 | #endif 75 | 76 | #endif /* _ETHERPORT_H_ */ 77 | -------------------------------------------------------------------------------- /SSD1306Display.h: -------------------------------------------------------------------------------- 1 | #ifndef SSD1306_DISPLAY_H 2 | #define SSD1306_DISPLAY_H 3 | 4 | #if defined(ESP8266) || defined(ESP32) 5 | 6 | #include 7 | #include "font.h" 8 | #include "images.h" 9 | 10 | #define LCD_STD 0 // Standard LCD 11 | #define LCD_I2C 1 12 | 13 | class SSD1306Display : public SSD1306{ 14 | public: 15 | SSD1306Display(uint8_t _addr, uint8_t _sda, uint8_t _scl) : SSD1306(_addr, _sda, _scl) { 16 | cx = 0; 17 | cy = 0; 18 | for(byte i=0;i=0&&idx. 19 | */ 20 | #if defined(ESP8266) || defined(ESP32) 21 | 22 | #include "espconnect.h" 23 | #include "esp32.h" 24 | 25 | const char html_ap_redirect[] PROGMEM = "

WiFi config saved. Now switching to station mode.

"; 26 | 27 | String scan_network() { 28 | WiFi.mode(WIFI_STA); 29 | WiFi.disconnect(); 30 | byte n = WiFi.scanNetworks(); 31 | String wirelessinfo; 32 | if (n>32) n = 32; // limit to 32 ssids max 33 | //Maintain old format of wireless network JSON for mobile app compat 34 | wirelessinfo = "{\"ssids\":["; 35 | for(int i=0;i. 22 | */ 23 | 24 | #ifndef _SERVER_H 25 | #define _SERVER_H 26 | 27 | #if !defined(ARDUINO) 28 | #include 29 | #endif 30 | 31 | class BufferFiller { 32 | char *start; //!< Pointer to start of buffer 33 | char *ptr; //!< Pointer to cursor position 34 | public: 35 | BufferFiller () {} 36 | BufferFiller (char *buf) : start (buf), ptr (buf) {} 37 | 38 | void emit_p(PGM_P fmt, ...) { 39 | va_list ap; 40 | va_start(ap, fmt); 41 | for (;;) { 42 | char c = pgm_read_byte(fmt++); 43 | if (c == 0) 44 | break; 45 | if (c != '$') { 46 | *ptr++ = c; 47 | continue; 48 | } 49 | c = pgm_read_byte(fmt++); 50 | switch (c) { 51 | case 'D': 52 | //wtoa(va_arg(ap, uint16_t), (char*) ptr); 53 | itoa(va_arg(ap, int), (char*) ptr, 10); // ray 54 | break; 55 | case 'L': 56 | //ltoa(va_arg(ap, long), (char*) ptr, 10); 57 | ultoa(va_arg(ap, long), (char*) ptr, 10); // ray 58 | break; 59 | case 'S': 60 | strcpy((char*) ptr, va_arg(ap, const char*)); 61 | break; 62 | case 'F': { 63 | PGM_P s = va_arg(ap, PGM_P); 64 | char d; 65 | while ((d = pgm_read_byte(s++)) != 0) 66 | *ptr++ = d; 67 | continue; 68 | } 69 | case 'O': { 70 | uint16_t oid = va_arg(ap, int); 71 | file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); 72 | } 73 | break; 74 | default: 75 | *ptr++ = c; 76 | continue; 77 | } 78 | ptr += strlen((char*) ptr); 79 | } 80 | *(ptr)=0; 81 | va_end(ap); 82 | } 83 | 84 | char* buffer () const { return start; } 85 | unsigned int position () const { return ptr - start; } 86 | }; 87 | 88 | 89 | #endif // _SERVER_H 90 | -------------------------------------------------------------------------------- /NTPClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Arduino.h" 4 | 5 | #include 6 | 7 | #define SEVENZYYEARS 2208988800UL 8 | #define NTP_PACKET_SIZE 48 9 | #define NTP_DEFAULT_LOCAL_PORT 1337 10 | 11 | class NTPClient { 12 | private: 13 | UDP* _udp; 14 | bool _udpSetup = false; 15 | 16 | const char* _poolServerName = "pool.ntp.org"; // Default time server 17 | int _port = NTP_DEFAULT_LOCAL_PORT; 18 | long _timeOffset = 0; 19 | 20 | unsigned long _updateInterval = 60000; // In ms 21 | 22 | unsigned long _currentEpoc = 0; // In s 23 | unsigned long _lastUpdate = 0; // In ms 24 | 25 | byte _packetBuffer[NTP_PACKET_SIZE]; 26 | 27 | void sendNTPPacket(); 28 | 29 | public: 30 | NTPClient(UDP& udp); 31 | NTPClient(UDP& udp, long timeOffset); 32 | NTPClient(UDP& udp, const char* poolServerName); 33 | NTPClient(UDP& udp, const char* poolServerName, long timeOffset); 34 | NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval); 35 | 36 | /** 37 | * Set time server name 38 | * 39 | * @param poolServerName 40 | */ 41 | void setPoolServerName(const char* poolServerName); 42 | 43 | /** 44 | * Starts the underlying UDP client with the default local port 45 | */ 46 | void begin(); 47 | 48 | /** 49 | * Starts the underlying UDP client with the specified local port 50 | */ 51 | void begin(int port); 52 | 53 | /** 54 | * This should be called in the main loop of your application. By default an update from the NTP Server is only 55 | * made every 60 seconds. This can be configured in the NTPClient constructor. 56 | * 57 | * @return true on success, false on failure 58 | */ 59 | bool update(); 60 | 61 | /** 62 | * This will force the update from the NTP Server. 63 | * 64 | * @return true on success, false on failure 65 | */ 66 | bool forceUpdate(); 67 | 68 | int getDay() const; 69 | int getHours() const; 70 | int getMinutes() const; 71 | int getSeconds() const; 72 | 73 | /** 74 | * Changes the time offset. Useful for changing timezones dynamically 75 | */ 76 | void setTimeOffset(int timeOffset); 77 | 78 | /** 79 | * Set the update interval to another frequency. E.g. useful when the 80 | * timeOffset should not be set in the constructor 81 | */ 82 | void setUpdateInterval(unsigned long updateInterval); 83 | 84 | /** 85 | * @return time formatted like `hh:mm:ss` 86 | */ 87 | String getFormattedTime() const; 88 | 89 | /** 90 | * @return time in seconds since Jan. 1, 1970 91 | */ 92 | unsigned long getEpochTime() const; 93 | 94 | /** 95 | * Stops the underlying UDP client 96 | */ 97 | void end(); 98 | }; 99 | -------------------------------------------------------------------------------- /utils.h: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * Utility functions header file 5 | * Feb 2015 @ OpenSprinkler.com 6 | * 7 | * This file is part of the OpenSprinkler library 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see 21 | * . 22 | */ 23 | 24 | #ifndef _UTILS_H 25 | #define _UTILS_H 26 | 27 | #if defined(ARDUINO) 28 | #include 29 | #else // headers for RPI/BBB 30 | #include 31 | #include 32 | #include 33 | 34 | #endif 35 | #include "defines.h" 36 | 37 | // File reading/writing functions 38 | void write_to_file(const char *fname, const char *data, ulong size, ulong pos=0, bool trunc=true); 39 | void read_from_file(const char *fname, char *data, ulong maxsize=TMP_BUFFER_SIZE, int pos=0); 40 | void remove_file(const char *fname); 41 | bool file_exists(const char *fname); 42 | 43 | void file_read_block (const char *fname, void *dst, ulong pos, ulong len); 44 | void file_write_block(const char *fname, const void *src, ulong pos, ulong len); 45 | void file_copy_block (const char *fname, ulong from, ulong to, ulong len, void *tmp=0); 46 | byte file_read_byte (const char *fname, ulong pos); 47 | void file_write_byte(const char *fname, ulong pos, byte v); 48 | byte file_cmp_block(const char *fname, const char *buf, ulong pos); 49 | 50 | // misc. string and time converstion functions 51 | void strncpy_P0(char* dest, const char* src, int n); 52 | ulong water_time_resolve(uint16_t v); 53 | byte water_time_encode_signed(int16_t i); 54 | int16_t water_time_decode_signed(byte i); 55 | void urlDecode(char *); 56 | void peel_http_header(char*); 57 | 58 | #if defined(ARDUINO) 59 | 60 | #else // Arduino compatible functions for RPI/BBB 61 | char* get_runtime_path(); 62 | char* get_filename_fullpath(const char *filename); 63 | void delay(ulong ms); 64 | void delayMicroseconds(ulong us); 65 | void delayMicrosecondsHard(ulong us); 66 | ulong millis(); 67 | ulong micros(); 68 | void initialiseEpoch(); 69 | #if defined(OSPI) 70 | unsigned int detect_rpi_rev(); 71 | #endif 72 | 73 | #endif 74 | 75 | #endif // _UTILS_H 76 | -------------------------------------------------------------------------------- /html/sta_update.html: -------------------------------------------------------------------------------- 1 | 2 | OpenSprinkler Firmware Update 3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 |

OpenSprinkler Firmware Update

11 |
12 |
13 | 14 | 15 | 16 | 17 |
Device password:
18 | Submit 19 |
20 |
21 |
22 |

© OpenSprinkler (www.opensprinkler.com)

23 |
24 |
25 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /html/html2raw.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #define LIST_FNAME "list.txt" 6 | #define H_FNAME "../htmls.h" 7 | 8 | void file_error(const char* name) { 9 | printf("Can't open file %s\n", name); 10 | } 11 | 12 | void html2raw(const char*, const char*, FILE*); 13 | 14 | int main(int argc, char* argv[]) 15 | { 16 | printf("--------------------------------------\n"); 17 | printf("Convert all .html files in this folder\n"); 18 | printf("to C++ raw strings and save them in the\n"); 19 | printf("parent folder as htmls.h\n"); 20 | printf("-----------------------------------------\n"); 21 | 22 | char command[100]; 23 | sprintf(command, "ls *.html > %s", LIST_FNAME); 24 | system(command); 25 | FILE *lp = fopen(LIST_FNAME, "rb"); 26 | if(!lp) {file_error(LIST_FNAME); return 0;} 27 | 28 | FILE *hp = fopen(H_FNAME, "wb"); 29 | if(!hp) {file_error(H_FNAME); return 0;} 30 | 31 | char hfname[100]; 32 | char hsname[100]; 33 | int nfiles = 0; 34 | while(!feof(lp)) { 35 | hfname[0]=0; 36 | fgets(hfname, sizeof(hfname), lp); 37 | char *ext = strstr(hfname, ".html"); 38 | if(!ext) break; 39 | *ext=0; 40 | strcpy(hsname, hfname); 41 | strcat(hfname, ".html"); 42 | strcat(hsname, "_html"); 43 | printf("%s", hfname); 44 | printf("\n"); 45 | html2raw(hfname, hsname, hp); 46 | nfiles++; 47 | } 48 | printf("%d files processed.\n", nfiles); 49 | fclose(hp); 50 | fclose(lp); 51 | } 52 | 53 | char in[10000]; 54 | char out[10000]; 55 | 56 | void html2raw(const char *hfname, const char *hsname, FILE *hp) { 57 | FILE *fp = fopen(hfname, "rb"); 58 | if(!fp) { file_error(hfname); return; } 59 | 60 | int size; 61 | char c; 62 | int i; 63 | fprintf(hp, "const char %s[] PROGMEM = R\"(", hsname); 64 | while(!feof(fp)) { 65 | in[0]=0; 66 | fgets(in, sizeof(in), fp); 67 | size = strlen(in); 68 | if(size==0) break; 69 | c = in[size-1]; 70 | if(c=='\r' || c=='\n') size--; 71 | c = in[size-1]; 72 | if(c=='\r' || c=='\n') size--; 73 | char *outp = out; 74 | bool isEmpty = true; 75 | for(i=0;i struct NVConData 11 | #define PROG_FILENAME "/prog.dat" // program data file 12 | #define DONE_FILENAME "/done.dat" // used to indicate the completion of all files 13 | 14 | 15 | #define MDNS_NAME "opensprinkler" // mDNS name for OS controler 16 | #define OS_HW_VERSION (OS_HW_VERSION_BASE+40) 17 | 18 | //#define RFTX // uncoment when planning to use RX controler 19 | //#define ETHPORT // uncoment when palnning to use wired etherner 20 | 21 | #define SDA_PIN 5 // I2C pin definition 22 | #define SCL_PIN 4 23 | #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address 24 | 25 | #define IOEXP_PIN 0x80 // base for pins on main IO expander 26 | 27 | /* ESP32 port support only AC mode as DC and Latch need dedicated HW 28 | * Dont need this to declere and saerch for Main IO controler and 29 | * ac, dc or latch drivers. However you may use DC nad Latch when deciated HW builded - not tested 30 | */ 31 | #define MAIN_I2CADDR 0x20 // main IO expander I2C address 32 | #define ACDR_I2CADDR 0x21 // ac driver I2C address 33 | #define DCDR_I2CADDR 0x22 // dc driver I2C address 34 | #define LADR_I2CADDR 0x23 // latch driver I2C address 35 | 36 | #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address 37 | 38 | 39 | #define ETHER_BUFFER_SIZE 8192 40 | 41 | /* To accommodate different OS30 versions, we use software defines pins */ 42 | extern byte PIN_BUTTON_1; 43 | extern byte PIN_BUTTON_2; 44 | extern byte PIN_BUTTON_3; 45 | extern byte PIN_RFRX; 46 | extern byte PIN_RFTX; 47 | extern byte PIN_BOOST; 48 | extern byte PIN_BOOST_EN; 49 | extern byte PIN_LATCH_COM; 50 | extern byte PIN_SENSOR1; 51 | extern byte PIN_SENSOR2; 52 | extern byte PIN_IOEXP_INT; 53 | 54 | 55 | #define E0_PIN_BUTTON_1 25 // button 1 56 | #define E0_PIN_BUTTON_2 0 // button 2 57 | #define E0_PIN_BUTTON_3 26 // button 3 58 | #define E0_PIN_RFRX 255 59 | #define E0_PIN_RFTX 255 60 | #define E0_PIN_BOOST 255// special HW needed 61 | #define E0_PIN_BOOST_EN 255// special HW needed 62 | #define E0_PIN_LATCH_COM 255// not needed for ESP32 63 | #define E0_PIN_SENSOR1 36 // sensor 1 64 | #define E0_PIN_SENSOR2 2 // sensor 2 65 | #define E0_PIN_IOEXP_INT 255// not needed for ESP32 66 | 67 | #define PIN_ETHER_CS 255 // ENC28J60 CS (chip select pin) is 16 on OS 3.2. 68 | 69 | #define ON_BOARD_GPIN_LIST {12,13,14,15,16,255,255,255} // ESP32 on board gpins to be usead as sections, 255 - pin not defined 70 | #define PIN_FREE_LIST {} // no free GPIO pin at the moment 71 | 72 | #define PIN_CURR_SENSE 39 73 | 74 | #define STATION_LOGIC 0 // GPIO logic ex. for relays conneted to grand 0 meens ON 75 | 76 | 77 | #endif 78 | #endif //_ESP32_H 79 | -------------------------------------------------------------------------------- /Common.mk: -------------------------------------------------------------------------------- 1 | # Useful functions 2 | # Returns the first argument (typically a directory), if the file or directory 3 | # named by concatenating the first and optionally second argument 4 | # (directory and optional filename) exists 5 | dir_if_exists = $(if $(wildcard $(1)$(2)),$(1)) 6 | 7 | # Run a shell script if it exists. Stops make on error. 8 | runscript_if_exists = \ 9 | $(if $(wildcard $(1)), \ 10 | $(if $(findstring 0, \ 11 | $(lastword $(shell $(abspath $(wildcard $(1))); echo $$?))), \ 12 | $(info Info: $(1) success), \ 13 | $(error ERROR: $(1) failed))) 14 | 15 | # For message printing: pad the right side of the first argument with spaces to 16 | # the number of bytes indicated by the second argument. 17 | space_pad_to = $(shell echo $(1) " " | head -c$(2)) 18 | 19 | # Call with some text, and a prefix tag if desired (like [AUTODETECTED]), 20 | show_config_info = $(call arduino_output,- $(call space_pad_to,$(2),20) $(1)) 21 | 22 | # Call with the name of the variable, a prefix tag if desired (like [AUTODETECTED]), 23 | # and an explanation if desired (like (found in $$PATH) 24 | show_config_variable = $(call show_config_info,$(1) = $($(1)) $(3),$(2)) 25 | 26 | # Just a nice simple visual separator 27 | show_separator = $(call arduino_output,-------------------------) 28 | 29 | $(call show_separator) 30 | $(call arduino_output,Arduino.mk Configuration:) 31 | 32 | ######################################################################## 33 | # 34 | # Detect OS 35 | ifeq ($(OS),Windows_NT) 36 | CURRENT_OS = WINDOWS 37 | else 38 | UNAME_S := $(shell uname -s) 39 | ifeq ($(UNAME_S),Linux) 40 | CURRENT_OS = LINUX 41 | endif 42 | ifeq ($(UNAME_S),Darwin) 43 | CURRENT_OS = MAC 44 | endif 45 | endif 46 | $(call show_config_variable,CURRENT_OS,[AUTODETECTED]) 47 | 48 | ######################################################################## 49 | # 50 | # Travis-CI 51 | ifneq ($(TEST),) 52 | DEPENDENCIES_DIR = /var/tmp/Arduino-Makefile-testing-dependencies 53 | 54 | DEPENDENCIES_MPIDE_DIR = $(DEPENDENCIES_DIR)/mpide-0023-linux64-20130817-test 55 | ifeq ($(MPIDE_DIR),) 56 | MPIDE_DIR = $(DEPENDENCIES_MPIDE_DIR) 57 | endif 58 | 59 | DEPENDENCIES_ARDUINO_DIR = $(DEPENDENCIES_DIR)/arduino-1.0.6 60 | ifeq ($(ARDUINO_DIR),) 61 | ARDUINO_DIR = $(DEPENDENCIES_ARDUINO_DIR) 62 | endif 63 | endif 64 | 65 | ######################################################################## 66 | # Arduino Directory 67 | 68 | ifndef ARDUINO_DIR 69 | AUTO_ARDUINO_DIR := $(firstword \ 70 | $(call dir_if_exists,/usr/share/arduino) \ 71 | $(call dir_if_exists,/Applications/Arduino.app/Contents/Resources/Java) \ 72 | $(call dir_if_exists,/Applications/Arduino.app/Contents/Java) ) 73 | ifdef AUTO_ARDUINO_DIR 74 | ARDUINO_DIR = $(AUTO_ARDUINO_DIR) 75 | $(call show_config_variable,ARDUINO_DIR,[AUTODETECTED]) 76 | else 77 | echo $(error "ARDUINO_DIR is not defined") 78 | endif 79 | else 80 | $(call show_config_variable,ARDUINO_DIR,[USER]) 81 | endif 82 | 83 | ifeq ($(CURRENT_OS),WINDOWS) 84 | ifneq ($(shell echo $(ARDUINO_DIR) | egrep '^(/|[a-zA-Z]:\\)'),) 85 | echo $(error On Windows, ARDUINO_DIR must be a relative path) 86 | endif 87 | endif 88 | -------------------------------------------------------------------------------- /LiquidCrystal.h: -------------------------------------------------------------------------------- 1 | #ifndef LIQUID_CRYSTAL_DUAL_H 2 | #define LIQUID_CRYSTAL_DUAL_H 3 | 4 | #if defined(ARDUINO) && (!defined(ESP8266) && !defined(ESP32)) 5 | 6 | #include 7 | #include 8 | 9 | // commands 10 | #define LCD_CLEARDISPLAY 0x01 11 | #define LCD_RETURNHOME 0x02 12 | #define LCD_ENTRYMODESET 0x04 13 | #define LCD_DISPLAYCONTROL 0x08 14 | #define LCD_CURSORSHIFT 0x10 15 | #define LCD_FUNCTIONSET 0x20 16 | #define LCD_SETCGRAMADDR 0x40 17 | #define LCD_SETDDRAMADDR 0x80 18 | 19 | // flags for display entry mode 20 | #define LCD_ENTRYRIGHT 0x00 21 | #define LCD_ENTRYLEFT 0x02 22 | #define LCD_ENTRYSHIFTINCREMENT 0x01 23 | #define LCD_ENTRYSHIFTDECREMENT 0x00 24 | 25 | // flags for display on/off control 26 | #define LCD_DISPLAYON 0x04 27 | #define LCD_DISPLAYOFF 0x00 28 | #define LCD_CURSORON 0x02 29 | #define LCD_CURSOROFF 0x00 30 | #define LCD_BLINKON 0x01 31 | #define LCD_BLINKOFF 0x00 32 | 33 | // flags for display/cursor shift 34 | #define LCD_DISPLAYMOVE 0x08 35 | #define LCD_CURSORMOVE 0x00 36 | #define LCD_MOVERIGHT 0x04 37 | #define LCD_MOVELEFT 0x00 38 | 39 | // flags for function set 40 | #define LCD_8BITMODE 0x10 41 | #define LCD_4BITMODE 0x00 42 | #define LCD_2LINE 0x08 43 | #define LCD_1LINE 0x00 44 | #define LCD_5x10DOTS 0x04 45 | #define LCD_5x8DOTS 0x00 46 | 47 | // flags for backlight control 48 | #define LCD_BACKLIGHT 0x08 49 | #define LCD_NOBACKLIGHT 0x00 50 | 51 | #define En B00000100 // Enable bit 52 | #define Rw B00000010 // Read/Write bit 53 | #define Rs B00000001 // Register select bit 54 | 55 | #define LCD_STD 0 // Standard LCD 56 | #define LCD_I2C 1 // I2C LCD 57 | #define LCD_I2C_ADDR1 0x27 // type using PCF8574, at address 0x27 58 | #define LCD_I2C_ADDR2 0x3F // type using PCF8574A, at address 0x3F 59 | 60 | class LiquidCrystal : public Print { 61 | public: 62 | LiquidCrystal() {} 63 | void init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable, 64 | uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, 65 | uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7); 66 | 67 | void begin(); 68 | 69 | void clear(); 70 | void clear(int start, int end) { clear(); } 71 | void home(); 72 | 73 | void noDisplay(); 74 | void display(); 75 | void noBlink(); 76 | void blink(); 77 | void noCursor(); 78 | void cursor(); 79 | void scrollDisplayLeft(); 80 | void scrollDisplayRight(); 81 | void leftToRight(); 82 | void rightToLeft(); 83 | void autoscroll(); 84 | void noAutoscroll(); 85 | 86 | //void createChar(uint8_t, uint8_t[]); 87 | void createChar(uint8_t, PGM_P ptr); 88 | void setCursor(uint8_t, uint8_t); 89 | virtual size_t write(uint8_t); 90 | void command(uint8_t); 91 | 92 | inline uint8_t type() { return _type; } 93 | void noBacklight(); 94 | void backlight(); 95 | 96 | using Print::write; 97 | private: 98 | void send(uint8_t, uint8_t); 99 | void write4bits(uint8_t); 100 | void pulseEnable(); 101 | 102 | void expanderWrite(uint8_t); 103 | void pulseEnable(uint8_t); 104 | uint8_t _addr; 105 | uint8_t _cols; 106 | uint8_t _rows; 107 | uint8_t _charsize; 108 | uint8_t _backlightval; 109 | 110 | uint8_t _type; // LCD type. 0: standard; 1: I2C 111 | uint8_t _rs_pin; // LOW: command. HIGH: character. 112 | uint8_t _rw_pin; // LOW: write to LCD. HIGH: read from LCD. 113 | uint8_t _enable_pin; // activated by a HIGH pulse. 114 | uint8_t _data_pins[8]; 115 | 116 | uint8_t _displayfunction; 117 | uint8_t _displaycontrol; 118 | uint8_t _displaymode; 119 | 120 | uint8_t _initialized; 121 | 122 | uint8_t _numlines,_currline; 123 | }; 124 | 125 | #endif 126 | 127 | #endif // LIQUID_CRYSTAL_DUAL_H 128 | -------------------------------------------------------------------------------- /html/ap_home.html: -------------------------------------------------------------------------------- 1 | 2 | OpenSprinkler WiFi Config 3 | 4 | 5 | 6 | 9 | OpenSprinkler WiFi Config

10 | 11 | 12 | 13 |
Detected SSIDsStrengthPower Level
(Scanning...)
14 |

15 | 16 | 17 | 18 | 19 | 20 | 21 |
(Your WiFi SSID)
(Your WiFi Password)

22 | 73 | 74 | -------------------------------------------------------------------------------- /OpenSprinkler.launch: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: OpenSprinkler 4 | # Required-Start: $remote_fs $syslog 5 | # Required-Stop: $remote_fs $syslog 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: Open Sprinkler Raspberry Pi 9 | # Description: Open Sprinkler Raspberry Pi - Raspberry Pi with 10 | # Open Sprinkler base board from Ray's Hobby 11 | ### END INIT INFO 12 | 13 | # 14 | # To auto start on boot execute (once) as root 15 | # 16 | # update-rc.d OpenSprinkler defaults 17 | # 18 | # To stop auto start on boot execute 19 | # 20 | # update-rc.d OpenSprinkler remove 21 | # 22 | 23 | # Author: Denny Fox 24 | # Do NOT "set -e" 25 | 26 | # PATH should only include /usr/* if it runs after the mountnfs.sh script 27 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 28 | NAME=OpenSprinkler 29 | DESC="Open Sprinkler (Unified) Raspberry Pi" 30 | DAEMON=__OpenSprinkler_Path__/OpenSprinkler 31 | DAEMON_ARGS="" 32 | HOMEDIR=__OpenSprinkler_Path__ 33 | PIDFILE=/var/run/$NAME.pid 34 | SCRIPTNAME=/etc/init.d/$NAME 35 | USER=root 36 | 37 | # Exit if the package is not installed 38 | [ -x "$DAEMON" ] || exit 0 39 | 40 | # Read configuration variable file if it is present 41 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 42 | 43 | # Load the VERBOSE setting and other rcS variables 44 | . /lib/init/vars.sh 45 | 46 | # Define LSB log_* functions. 47 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present 48 | # and status_of_proc is working. 49 | . /lib/lsb/init-functions 50 | 51 | # 52 | # Function that starts the daemon/service 53 | # 54 | do_start() 55 | { 56 | # Return 57 | # 0 if daemon has been started 58 | # 1 if daemon was already running 59 | # 2 if daemon could not be started 60 | start-stop-daemon --start --quiet --chuid $USER --chdir $HOMEDIR --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON --test > /dev/null \ 61 | || return 1 62 | start-stop-daemon --start --quiet --chuid $USER --chdir $HOMEDIR --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON -- \ 63 | $DAEMON_ARGS \ 64 | || return 2 65 | } 66 | 67 | # 68 | # Function that stops the daemon/service 69 | # 70 | do_stop() 71 | { 72 | # Return 73 | # 0 if daemon has been stopped 74 | # 1 if daemon was already stopped 75 | # 2 if daemon could not be stopped 76 | # other if a failure occurred 77 | start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE 78 | RETVAL="$?" 79 | [ "$RETVAL" = 2 ] && return 2 80 | # Wait for children to finish too if this is a daemon that forks 81 | # and if the daemon is only ever run from this initscript. 82 | # If the above conditions are not satisfied then add some other code 83 | # that waits for the process to drop all resources that could be 84 | # needed by services started subsequently. A last resort is to 85 | # sleep for some time. 86 | start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON 87 | [ "$?" = 2 ] && return 2 88 | # Many daemons don't delete their pidfiles when they exit. 89 | rm -f $PIDFILE 90 | return "$RETVAL" 91 | } 92 | 93 | # 94 | # Function that sends a SIGHUP to the daemon/service 95 | # 96 | do_reload() { 97 | # 98 | # If the daemon can reload its configuration without 99 | # restarting (for example, when it is sent a SIGHUP), 100 | # then implement that here. 101 | # 102 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME 103 | return 0 104 | } 105 | 106 | case "$1" in 107 | start) 108 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" 109 | do_start 110 | case "$?" in 111 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 112 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 113 | esac 114 | ;; 115 | stop) 116 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 117 | do_stop 118 | case "$?" in 119 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 120 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 121 | esac 122 | ;; 123 | status) 124 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? 125 | ;; 126 | restart|force-reload) 127 | log_daemon_msg "Restarting $DESC" "$NAME" 128 | do_stop 129 | case "$?" in 130 | 0|1) 131 | do_start 132 | case "$?" in 133 | 0) log_end_msg 0 ;; 134 | 1) log_end_msg 1 ;; # Old process is still running 135 | *) log_end_msg 1 ;; # Failed to start 136 | esac 137 | ;; 138 | *) 139 | # Failed to stop 140 | log_end_msg 1 141 | ;; 142 | esac 143 | ;; 144 | *) 145 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 146 | exit 3 147 | ;; 148 | esac 149 | 150 | : 151 | -------------------------------------------------------------------------------- /program.h: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * Program data structures and functions header file 5 | * Feb 2015 @ OpenSprinkler.com 6 | * 7 | * This file is part of the OpenSprinkler library 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see 21 | * . 22 | */ 23 | 24 | 25 | #ifndef _PROGRAM_H 26 | #define _PROGRAM_H 27 | 28 | #define MAX_NUM_PROGRAMS 40 // maximum number of programs 29 | #define MAX_NUM_STARTTIMES 4 30 | #define PROGRAM_NAME_SIZE 32 31 | #define RUNTIME_QUEUE_SIZE MAX_NUM_STATIONS 32 | #define PROGRAMSTRUCT_SIZE sizeof(ProgramStruct) 33 | #include "OpenSprinkler.h" 34 | 35 | /** Log data structure */ 36 | struct LogStruct { 37 | byte station; 38 | byte program; 39 | uint16_t duration; 40 | uint32_t endtime; 41 | }; 42 | 43 | #define PROGRAM_TYPE_WEEKLY 0 44 | #define PROGRAM_TYPE_BIWEEKLY 1 45 | #define PROGRAM_TYPE_MONTHLY 2 46 | #define PROGRAM_TYPE_INTERVAL 3 47 | 48 | #define STARTTIME_SUNRISE_BIT 14 49 | #define STARTTIME_SUNSET_BIT 13 50 | #define STARTTIME_SIGN_BIT 12 51 | 52 | #define PROGRAMSTRUCT_EN_BIT 0 53 | #define PROGRAMSTRUCT_UWT_BIT 1 54 | 55 | /** Program data structure */ 56 | class ProgramStruct { 57 | public: 58 | byte enabled :1; // HIGH means the program is enabled 59 | 60 | // weather data 61 | byte use_weather: 1; 62 | 63 | // odd/even restriction: 64 | // 0->none, 1->odd day (except 31st and Feb 29th) 65 | // 2->even day, 3->N/A 66 | byte oddeven :2; 67 | 68 | // schedule type: 69 | // 0: weekly, 1->biweekly, 2->monthly, 3->interval 70 | byte type :2; 71 | 72 | // starttime type: 73 | // 0: repeating (give start time, repeat every, number of repeats) 74 | // 1: fixed start time (give arbitrary start times up to MAX_NUM_STARTTIMEs) 75 | byte starttime_type: 1; 76 | 77 | // misc. data 78 | byte dummy1: 1; 79 | 80 | // weekly: days[0][0..6] correspond to Monday till Sunday 81 | // bi-weekly:days[0][0..6] and [1][0..6] store two weeks 82 | // monthly: days[0][0..5] stores the day of the month (32 means last day of month) 83 | // interval: days[0] stores the interval (0 to 255), days[1] stores the starting day remainder (0 to 254) 84 | byte days[2]; 85 | 86 | // When the program is a fixed start time type: 87 | // up to MAX_NUM_STARTTIMES fixed start times 88 | // When the program is a repeating type: 89 | // starttimes[0]: start time 90 | // starttimes[1]: repeat count 91 | // starttimes[2]: repeat every 92 | // Start time structure: 93 | // bit 15 : not used, reserved 94 | // if bit 14 == 1 : sunrise time +/- offset (by lowest 12 bits) 95 | // or bit 13 == 1 : sunset time +/- offset (by lowest 12 bits) 96 | // bit 12 : sign, 0 is positive, 1 is negative 97 | // bit 11 : not used, reserved 98 | // else: standard start time (value between 0 to 1440, by bits 0 to 10) 99 | int16_t starttimes[MAX_NUM_STARTTIMES]; 100 | 101 | uint16_t durations[MAX_NUM_STATIONS]; // duration / water time of each station 102 | 103 | char name[PROGRAM_NAME_SIZE]; 104 | 105 | byte check_match(time_t t); 106 | int16_t starttime_decode(int16_t t); 107 | 108 | protected: 109 | 110 | byte check_day_match(time_t t); 111 | 112 | }; 113 | 114 | extern OpenSprinkler os; 115 | 116 | class RuntimeQueueStruct { 117 | public: 118 | ulong st; // start time 119 | uint16_t dur; // water time 120 | byte sid; 121 | byte pid; 122 | }; 123 | 124 | class ProgramData { 125 | public: 126 | static RuntimeQueueStruct queue[]; 127 | static byte nqueue; // number of queue elements 128 | static byte station_qid[]; // this array stores the queue element index for each scheduled station 129 | static byte nprograms; // number of programs 130 | static LogStruct lastrun; 131 | static ulong last_seq_stop_time; // the last stop time of a sequential station 132 | 133 | static void reset_runtime(); 134 | static RuntimeQueueStruct* enqueue(); // this returns a pointer to the next available slot in the queue 135 | static void dequeue(byte qid); // this removes an element from the queue 136 | 137 | static void init(); 138 | static void eraseall(); 139 | static void read(byte pid, ProgramStruct *buf); 140 | static byte add(ProgramStruct *buf); 141 | static byte modify(byte pid, ProgramStruct *buf); 142 | static byte set_flagbit(byte pid, byte bid, byte value); 143 | static void moveup(byte pid); 144 | static byte del(byte pid); 145 | static void drem_to_relative(byte days[2]); // absolute to relative reminder conversion 146 | static void drem_to_absolute(byte days[2]); 147 | private: 148 | static void load_count(); 149 | static void save_count(); 150 | }; 151 | 152 | #endif // _PROGRAM_H 153 | -------------------------------------------------------------------------------- /gpio.h: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * GPIO header file 5 | * Feb 2015 @ OpenSprinkler.com 6 | * 7 | * This file is part of the OpenSprinkler library 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see 21 | * . 22 | */ 23 | 24 | #ifndef GPIO_H 25 | #define GPIO_H 26 | 27 | #if defined(ARDUINO) 28 | 29 | #if defined(ESP8266) || defined(ESP32) 30 | 31 | #include "Arduino.h" 32 | 33 | #include "esp32.h" 34 | 35 | 36 | #if defined(ARDUINO) 37 | #define DEBUG_BEGIN(x) {Serial.begin(x);} 38 | #define DEBUG_PRINT(x) {Serial.print(x);} 39 | #define DEBUG_PRINTLN(x) {Serial.println(x);} 40 | #else 41 | #include 42 | #define DEBUG_BEGIN(x) {} /** Serial debug functions */ 43 | inline void DEBUG_PRINT(int x) {printf("%d", x);} 44 | inline void DEBUG_PRINT(const char*s) {printf("%s", s);} 45 | #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} 46 | #endif 47 | 48 | // PCA9555 register defines 49 | #define NXP_INPUT_REG 0 50 | #define NXP_OUTPUT_REG 2 51 | #define NXP_INVERT_REG 4 52 | #define NXP_CONFIG_REG 6 53 | 54 | #define IOEXP_TYPE_8574 0 55 | #define IOEXP_TYPE_8575 1 56 | #define IOEXP_TYPE_9555 2 57 | #if defined(ESP32) 58 | #define IOEXP_TYPE_BUILD_IN_GPIO 3 59 | #endif 60 | #define IOEXP_TYPE_UNKNOWN 254 61 | #define IOEXP_TYPE_NONEXIST 255 62 | 63 | class IOEXP { 64 | public: 65 | IOEXP(uint8_t addr=255) { address = addr; type = IOEXP_TYPE_NONEXIST; } 66 | 67 | virtual void pinMode(uint8_t pin, uint8_t IOMode) { } 68 | virtual uint16_t i2c_read(uint8_t reg) { return 0xFFFF; } 69 | virtual void i2c_write(uint8_t reg, uint16_t v) { } 70 | virtual void i2c_write(uint16_t v) { } 71 | virtual void set_pins_output_mode() { } 72 | 73 | 74 | void digitalWrite(uint16_t v) { 75 | i2c_write(NXP_OUTPUT_REG, v); 76 | } 77 | 78 | uint16_t digitalRead() { 79 | return i2c_read(NXP_INPUT_REG); 80 | } 81 | 82 | uint8_t digitalRead(uint8_t pin) { 83 | return (digitalRead() & (1< 0) values |= (1< 159 | #include 160 | #include 161 | 162 | #include "defines.h" 163 | #define OUTPUT 0 164 | #define INPUT 1 165 | #define INPUT_PULLUP 1 166 | #define HIGH 1 167 | #define LOW 0 168 | 169 | void pinMode(int pin, byte mode); 170 | void digitalWrite(int pin, byte value); 171 | int gpio_fd_open(int pin, int mode = O_WRONLY); 172 | void gpio_fd_close(int fd); 173 | void gpio_write(int fd, byte value); 174 | byte digitalRead(int pin); 175 | // mode can be any of 'rising', 'falling', 'both' 176 | void attachInterrupt(int pin, const char* mode, void (*isr)(void)); 177 | 178 | #endif 179 | 180 | #endif // GPIO_H 181 | -------------------------------------------------------------------------------- /etherport.cpp: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * Linux Ethernet functions 5 | * This file is based on Richard Zimmerman's sprinklers_pi program 6 | * Copyright (c) 2013 Richard Zimmerman 7 | * 8 | * This file is part of the OpenSprinkler library 9 | * 10 | * This program is free software: you can redistribute it and/or modify 11 | * it under the terms of the GNU General Public License as published by 12 | * the Free Software Foundation, either version 3 of the License, or 13 | * (at your option) any later version. 14 | * 15 | * This program is distributed in the hope that it will be useful, 16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18 | * GNU General Public License for more details. 19 | * 20 | * You should have received a copy of the GNU General Public License 21 | * along with this program. If not, see 22 | * . 23 | */ 24 | 25 | #if defined(ARDUINO) 26 | 27 | #else 28 | 29 | #include "etherport.h" 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include "defines.h" 40 | 41 | EthernetServer::EthernetServer(uint16_t port) 42 | : m_port(port), m_sock(0) 43 | { 44 | } 45 | 46 | EthernetServer::~EthernetServer() 47 | { 48 | close(m_sock); 49 | } 50 | 51 | bool EthernetServer::begin() 52 | { 53 | struct sockaddr_in6 sin = {0}; 54 | sin.sin6_family = AF_INET6; 55 | sin.sin6_port = htons(m_port); 56 | sin.sin6_addr = in6addr_any; 57 | 58 | if ((m_sock = socket(PF_INET6, SOCK_STREAM, 0)) < 0) 59 | { 60 | DEBUG_PRINTLN("can't create shell listen socket"); 61 | return false; 62 | } 63 | int on = 1; 64 | if (setsockopt(m_sock, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) 65 | { 66 | DEBUG_PRINTLN("can't setsockopt SO_REUSEADDR"); 67 | return false; 68 | } 69 | int off = 0; 70 | if (setsockopt(m_sock, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off)) < 0) 71 | { 72 | DEBUG_PRINTLN("can't setsockopt IPV6_V6ONLY"); 73 | return false; 74 | } 75 | if (bind(m_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) 76 | { 77 | DEBUG_PRINTLN("shell bind error"); 78 | return false; 79 | } 80 | if (ioctl(m_sock, FIONBIO, (char*) &on) < 0) 81 | { 82 | DEBUG_PRINTLN("setting nonblock failed"); 83 | return false; 84 | } 85 | if (listen(m_sock, 2) < 0) 86 | { 87 | DEBUG_PRINTLN("shell listen error"); 88 | return false; 89 | } 90 | return true; 91 | } 92 | 93 | // This function blocks until we get a client connected. 94 | // It will timeout after 50ms and return a blank client. 95 | // If it succeeds it will return an EthernetClient. 96 | EthernetClient EthernetServer::available() 97 | { 98 | fd_set sock_set; 99 | FD_ZERO(&sock_set); 100 | FD_SET(m_sock, &sock_set); 101 | struct timeval timeout; 102 | timeout.tv_sec = 0; 103 | timeout.tv_usec = 50 * 1000; // 50ms 104 | 105 | select(m_sock + 1, &sock_set, NULL, NULL, &timeout); 106 | if (FD_ISSET(m_sock, &sock_set)) 107 | { 108 | int client_sock = 0; 109 | struct sockaddr_in6 cli_addr; 110 | unsigned int clilen = sizeof(cli_addr); 111 | if ((client_sock = accept(m_sock, (struct sockaddr *) &cli_addr, &clilen)) <= 0) 112 | return EthernetClient(0); 113 | return EthernetClient(client_sock); 114 | } 115 | return EthernetClient(0); 116 | } 117 | 118 | EthernetClient::EthernetClient() 119 | : m_sock(0), m_connected(false) 120 | { 121 | } 122 | 123 | EthernetClient::EthernetClient(int sock) 124 | : m_sock(sock), m_connected(true) 125 | { 126 | } 127 | 128 | EthernetClient::~EthernetClient() 129 | { 130 | stop(); 131 | } 132 | 133 | int EthernetClient::connect(uint8_t ip[4], uint16_t port) 134 | { 135 | if (m_sock) 136 | return 0; 137 | struct sockaddr_in sin = {0}; 138 | sin.sin_family = AF_INET; 139 | sin.sin_port = htons(port); 140 | sin.sin_addr.s_addr = *(uint32_t*) (ip); 141 | m_sock = socket(AF_INET, SOCK_STREAM, 0); 142 | if (::connect(m_sock, (struct sockaddr *) &sin, sizeof(sin)) < 0) 143 | { 144 | DEBUG_PRINTLN("error connecting to server"); 145 | return 0; 146 | } 147 | m_connected = true; 148 | return 1; 149 | } 150 | 151 | bool EthernetClient::connected() 152 | { 153 | if (!m_sock) 154 | return false; 155 | int error = 0; 156 | socklen_t len = sizeof(error); 157 | int retval = getsockopt(m_sock, SOL_SOCKET, SO_ERROR, &error, &len); 158 | return (retval == 0) && m_connected; 159 | } 160 | 161 | void EthernetClient::stop() 162 | { 163 | if (m_sock) 164 | { 165 | close(m_sock); 166 | m_sock = 0; 167 | m_connected = false; 168 | } 169 | } 170 | 171 | EthernetClient::operator bool() 172 | { 173 | return m_sock != 0; 174 | } 175 | 176 | // read data from the client into the buffer provided 177 | // This function will block until either data is received OR a timeout happens. 178 | // If an error occurs or a timeout happens, we set the disconnect flag on the socket 179 | // and return 0; 180 | int EthernetClient::read(uint8_t *buf, size_t size) 181 | { 182 | fd_set sock_set; 183 | FD_ZERO(&sock_set); 184 | FD_SET(m_sock, &sock_set); 185 | struct timeval timeout; 186 | timeout.tv_sec = 3; 187 | timeout.tv_usec = 0; 188 | 189 | select(m_sock + 1, &sock_set, NULL, NULL, &timeout); 190 | if (FD_ISSET(m_sock, &sock_set)) 191 | { 192 | int retval = ::read(m_sock, buf, size); 193 | if (retval <= 0) // socket closed 194 | m_connected = false; 195 | return retval; 196 | } 197 | m_connected = false; 198 | return 0; 199 | } 200 | 201 | size_t EthernetClient::write(const uint8_t *buf, size_t size) 202 | { 203 | return ::send(m_sock, buf, size, MSG_NOSIGNAL); 204 | } 205 | 206 | #endif 207 | -------------------------------------------------------------------------------- /I2CRTC.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * I2CRTC.cpp - library for I2C RTC 3 | 4 | Copyright (c) Michael Margolis 2009 5 | This library is intended to be uses with Arduino Time.h library functions 6 | 7 | The library is free software; you can redistribute it and/or 8 | modify it under the terms of the GNU Lesser General Public 9 | License as published by the Free Software Foundation; either 10 | version 2.1 of the License, or (at your option) any later version. 11 | 12 | This library is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 | Lesser General Public License for more details. 16 | 17 | You should have received a copy of the GNU Lesser General Public 18 | License along with this library; if not, write to the Free Software 19 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 20 | 21 | 30 Dec 2009 - Initial release 22 | 5 Sep 2011 updated for Arduino 1.0 23 | 24 | 23 Dec 2013 -- modified by Ray Wang (Rayshobby LLC) to add support for MCP7940 25 | */ 26 | 27 | 28 | #if defined(ARDUINO) 29 | 30 | #include "I2CRTC.h" 31 | #include 32 | #if defined(ESP32) 33 | #include "defines.h" 34 | #include "OpenSprinkler.h" 35 | #endif 36 | static uint8_t _addrs[] = {DS1307_ADDR, MCP7940_ADDR, PCF8563_ADDR}; 37 | 38 | uint8_t I2CRTC::addr = 0; 39 | 40 | I2CRTC::I2CRTC() 41 | { 42 | #if defined(ESP32) 43 | if(!Wire.begin(SDA_PIN,SCL_PIN)) DEBUG_PRINT("Error initiating I2CRTC"); 44 | #else 45 | Wire.begin(); // init I2C 46 | #endif 47 | } 48 | 49 | bool I2CRTC::detect() 50 | { 51 | addr = 0; 52 | for(uint8_t i = 0;i < sizeof(_addrs);i ++) { 53 | Wire.beginTransmission(_addrs[i]); 54 | Wire.write((uint8_t)0x00); 55 | if(Wire.endTransmission()==0) { // if chip detected successfully 56 | addr = _addrs[i]; 57 | return true; 58 | } 59 | } 60 | return false; 61 | } 62 | 63 | // PUBLIC FUNCTIONS 64 | time_t I2CRTC::get() // Aquire data from buffer and convert to time_t 65 | { 66 | tmElements_t tm; 67 | read(tm); 68 | return(makeTime(tm)); 69 | } 70 | 71 | void I2CRTC::set(time_t t) 72 | { 73 | tmElements_t tm; 74 | breakTime(t, tm); 75 | //tm.Second |= 0x80; // stop the clock ray: removed this step 76 | //write(tm); 77 | //tm.Second &= 0x7f; // start the clock ray: moved to write function 78 | write(tm); 79 | } 80 | 81 | // Aquire data from the RTC chip in BCD format 82 | void I2CRTC::read( tmElements_t &tm) 83 | { 84 | if(!addr) return; 85 | Wire.beginTransmission(addr); 86 | 87 | if(addr == PCF8563_ADDR) { 88 | Wire.write((uint8_t)0x02); 89 | Wire.endTransmission(); 90 | Wire.requestFrom(addr, (uint8_t)7); 91 | tm.Second = bcd2dec(Wire.read() & 0x7f); 92 | tm.Minute = bcd2dec(Wire.read() & 0x7f); 93 | tm.Hour = bcd2dec(Wire.read() & 0x3f); // mask assumes 24hr clock 94 | tm.Day = bcd2dec(Wire.read() & 0x3f); 95 | tm.Wday = bcd2dec(Wire.read() & 0x07); 96 | tm.Month = bcd2dec(Wire.read() & 0x1f); // fix bug for MCP7940 97 | tm.Year = (bcd2dec(Wire.read())); 98 | } else { 99 | Wire.write((uint8_t)0x00); 100 | Wire.endTransmission(); 101 | Wire.requestFrom(addr, (uint8_t)7); 102 | tm.Second = bcd2dec(Wire.read() & 0x7f); 103 | tm.Minute = bcd2dec(Wire.read() & 0x7f); 104 | tm.Hour = bcd2dec(Wire.read() & 0x3f); // mask assumes 24hr clock 105 | tm.Wday = bcd2dec(Wire.read() & 0x07); 106 | tm.Day = bcd2dec(Wire.read() & 0x3f); 107 | tm.Month = bcd2dec(Wire.read() & 0x1f); // fix bug for MCP7940 108 | tm.Year = y2kYearToTm((bcd2dec(Wire.read()))); 109 | } 110 | } 111 | 112 | void I2CRTC::write(tmElements_t &tm) 113 | { 114 | if(!addr) return; 115 | switch(addr) { 116 | case DS1307_ADDR: 117 | Wire.beginTransmission(addr); 118 | Wire.write((uint8_t)0x00); // reset register pointer 119 | Wire.write(dec2bcd(tm.Second) & 0x7f); // ray: start clock by setting CH bit low 120 | Wire.write(dec2bcd(tm.Minute)); 121 | Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format 122 | Wire.write(dec2bcd(tm.Wday)); 123 | Wire.write(dec2bcd(tm.Day)); 124 | Wire.write(dec2bcd(tm.Month)); 125 | Wire.write(dec2bcd(tmYearToY2k(tm.Year))); 126 | Wire.endTransmission(); 127 | break; 128 | case MCP7940_ADDR: 129 | Wire.beginTransmission(addr); 130 | Wire.write((uint8_t)0x00); // reset register pointer 131 | Wire.write(dec2bcd(tm.Second) | 0x80); // ray: start clock by setting ST bit high 132 | Wire.write(dec2bcd(tm.Minute)); 133 | Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format 134 | Wire.write(dec2bcd(tm.Wday) | 0x08); // ray: turn on battery backup by setting VBATEN bit high 135 | Wire.write(dec2bcd(tm.Day)); 136 | Wire.write(dec2bcd(tm.Month)); 137 | Wire.write(dec2bcd(tmYearToY2k(tm.Year))); 138 | Wire.endTransmission(); 139 | break; 140 | case PCF8563_ADDR: 141 | Wire.beginTransmission(addr); 142 | Wire.write((uint8_t)0x02); // reset register pointer 143 | Wire.write(dec2bcd(tm.Second) & 0x7f); 144 | Wire.write(dec2bcd(tm.Minute)); 145 | Wire.write(dec2bcd(tm.Hour)); // sets 24 hour format 146 | Wire.write(dec2bcd(tm.Day)); 147 | Wire.write(dec2bcd(tm.Wday)); 148 | Wire.write(dec2bcd(tm.Month)); 149 | Wire.write(dec2bcd(tm.Year)); 150 | Wire.endTransmission(); 151 | break; 152 | } 153 | } 154 | 155 | // Convert Decimal to Binary Coded Decimal (BCD) 156 | uint8_t I2CRTC::dec2bcd(uint8_t num) 157 | { 158 | return ((num/10 * 16) + (num % 10)); 159 | } 160 | 161 | // Convert Binary Coded Decimal (BCD) to Decimal 162 | uint8_t I2CRTC::bcd2dec(uint8_t num) 163 | { 164 | return ((num/16 * 10) + (num % 16)); 165 | } 166 | 167 | I2CRTC RTC = I2CRTC(); // create an instance for the user 168 | 169 | #endif 170 | -------------------------------------------------------------------------------- /images.h: -------------------------------------------------------------------------------- 1 | #ifndef IMAGES_H 2 | #define IMAGES_H 3 | 4 | enum { 5 | ICON_CONNECTED = 0, 6 | ICON_DISCONNECTED, 7 | ICON_REMOTEXT, 8 | ICON_RAINDELAY, 9 | ICON_RAIN, 10 | ICON_SOIL, 11 | ICON_ETHER_CONNECTED, 12 | ICON_ETHER_DISCONNECTED, 13 | NUM_CUSTOM_ICONS 14 | }; 15 | 16 | #if defined(ESP8266) || defined(PIN_SENSOR2) || defined(ESP32) 17 | enum { 18 | LCD_CURSOR_REMOTEXT = 11, 19 | LCD_CURSOR_RAINDELAY,// 12 20 | LCD_CURSOR_SENSOR1, // 13 21 | LCD_CURSOR_SENSOR2, // 14 22 | LCD_CURSOR_NETWORK // 15 23 | }; 24 | #else 25 | enum { 26 | LCD_CURSOR_SENSOR2 = 11, 27 | LCD_CURSOR_REMOTEXT, // 12 28 | LCD_CURSOR_RAINDELAY,// 13 29 | LCD_CURSOR_SENSOR1, // 14 30 | LCD_CURSOR_NETWORK // 15 31 | }; 32 | #endif 33 | 34 | 35 | #if defined(ESP8266) || defined(ESP32) 36 | 37 | #define WiFi_Logo_width 60 38 | #define WiFi_Logo_height 36 39 | const char WiFi_Logo_image[] PROGMEM = { 40 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 41 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 42 | 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 43 | 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 44 | 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 45 | 0xFF, 0x03, 0x00, 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 46 | 0x00, 0xFF, 0xFF, 0xFF, 0x07, 0xC0, 0x83, 0x01, 0x80, 0xFF, 0xFF, 0xFF, 47 | 0x01, 0x00, 0x07, 0x00, 0xC0, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x0C, 0x00, 48 | 0xC0, 0xFF, 0xFF, 0x7C, 0x00, 0x60, 0x0C, 0x00, 0xC0, 0x31, 0x46, 0x7C, 49 | 0xFC, 0x77, 0x08, 0x00, 0xE0, 0x23, 0xC6, 0x3C, 0xFC, 0x67, 0x18, 0x00, 50 | 0xE0, 0x23, 0xE4, 0x3F, 0x1C, 0x00, 0x18, 0x00, 0xE0, 0x23, 0x60, 0x3C, 51 | 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x03, 0x60, 0x3C, 0x1C, 0x70, 0x18, 0x00, 52 | 0xE0, 0x07, 0x60, 0x3C, 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 53 | 0xFC, 0x73, 0x18, 0x00, 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 54 | 0xE0, 0x87, 0x70, 0x3C, 0x1C, 0x70, 0x18, 0x00, 0xE0, 0x8F, 0x71, 0x3C, 55 | 0x1C, 0x70, 0x18, 0x00, 0xC0, 0xFF, 0xFF, 0x3F, 0x00, 0x00, 0x08, 0x00, 56 | 0xC0, 0xFF, 0xFF, 0x1F, 0x00, 0x00, 0x0C, 0x00, 0x80, 0xFF, 0xFF, 0x1F, 57 | 0x00, 0x00, 0x06, 0x00, 0x80, 0xFF, 0xFF, 0x0F, 0x00, 0x00, 0x07, 0x00, 58 | 0x00, 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xF8, 0xFF, 0xFF, 59 | 0xFF, 0x7F, 0x00, 0x00, 0x00, 0x00, 0xFE, 0xFF, 0xFF, 0x01, 0x00, 0x00, 60 | 0x00, 0x00, 0xFC, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF8, 0xFF, 61 | 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0xFF, 0x1F, 0x00, 0x00, 0x00, 62 | 0x00, 0x00, 0x80, 0xFF, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC, 63 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 64 | }; 65 | 66 | const char _iconimage_connected[] PROGMEM = { 67 | 0x00, 0x00, 0x00, 0x00, 68 | 0x00, 0x00, 0x80, 0x80, 69 | 0xA0, 0xA0, 0xA8, 0xA8, 70 | 0xAA, 0xAA, 0x00, 0x00, 71 | }; 72 | 73 | const char _iconimage_disconnected[] PROGMEM = { 74 | 0x00, 0x00, 0x00, 0x11, 75 | 0x0A, 0x04, 0x8A, 0x91, 76 | 0xA0, 0xA0, 0xA8, 0xA8, 77 | 0xAA, 0xAA, 0x00, 0x00, 78 | }; 79 | 80 | const char _iconimage_remotext[] PROGMEM = { 81 | 0x00, 0x00, 0x00, 0x00, 82 | 0x00, 0x00, 0x00, 0x41, 83 | 0x62, 0x54, 0x48, 0x44, 84 | 0x22, 0x1F, 0x00, 0x00, 85 | }; 86 | 87 | const char _iconimage_raindelay[] PROGMEM = { 88 | 0x00, 0x00, 0x00, 0x1C, 89 | 0x22, 0x49, 0x49, 0x49, 90 | 0x59, 0x41, 0x41, 0x41, 91 | 0x22, 0x1C, 0x00, 0x00, 92 | }; 93 | 94 | const char _iconimage_rain[] PROGMEM = { 95 | 0x00, 0x00, 0x00, 0x00, 96 | 0x18, 0x24, 0x3E, 0x00, 97 | 0x2A, 0x2A, 0x00, 0x2A, 98 | 0x2A, 0x00, 0x00, 0x00, 99 | }; 100 | 101 | const char _iconimage_soil[] PROGMEM = { 102 | 0x00, 0x00, 0x10, 103 | 0x10, 0x28, 0x28, 0x44, 104 | 0x44, 0x8A, 0x8A, 0x92, 105 | 0x44, 0x38, 0x00, 0xC6, 0x38, 106 | }; 107 | 108 | const char _iconimage_ether_connected[] PROGMEM = { 109 | 0x00, 0x00, 0x00, 0x38, 110 | 0x28, 0x38, 0x10, 0x10, 111 | 0xFE, 0x44, 0x44, 0xEE, 112 | 0xAA, 0xEE, 0x00, 0x00, 113 | }; 114 | 115 | const char _iconimage_ether_disconnected[] PROGMEM = { 116 | 0x00, 0x00, 0x11, 0x0A, 117 | 0x04, 0xEA, 0xB1, 0xE0, 118 | 0x40, 0xFE, 0x44, 0xEE, 119 | 0xAA, 0xEE, 0x00, 0x00, 120 | }; 121 | 122 | 123 | /* 124 | 125 | const char _iconimage_flow[] PROGMEM = { 126 | 0x00, 0x00, 0x0F, 0x0F, 127 | 0x03, 0x0F, 0x0F, 0x03, 128 | 0x1B, 0x18, 0x18, 0x18, 129 | 0x78, 0x78, 0x00, 0x00, 130 | }; 131 | 132 | const char _iconimage_pswitch[] PROGMEM = { 133 | 0x00, 0x00, 0x1E, 0x12, 134 | 0x12, 0x12, 0x1E, 0x02, 135 | 0x22, 0x32, 0x22, 0x20, 136 | 0x20, 0x70, 0x00, 0x00, 137 | }; 138 | 139 | */ 140 | 141 | #elif defined(ARDUINO) 142 | 143 | const char _iconimage_connected[] PROGMEM = { 144 | B00000, 145 | B00000, 146 | B00000, 147 | B00001, 148 | B00001, 149 | B00101, 150 | B00101, 151 | B10101 152 | }; 153 | 154 | const char _iconimage_disconnected[] PROGMEM = { 155 | B00000, 156 | B10100, 157 | B01000, 158 | B10101, 159 | B00001, 160 | B00101, 161 | B00101, 162 | B10101 163 | }; 164 | 165 | const char _iconimage_remotext[] PROGMEM = { 166 | B00000, 167 | B00000, 168 | B00000, 169 | B10001, 170 | B01011, 171 | B00101, 172 | B01001, 173 | B11110 174 | }; 175 | 176 | const char _iconimage_raindelay[] PROGMEM = { 177 | B00000, 178 | B01110, 179 | B10101, 180 | B10101, 181 | B10111, 182 | B10001, 183 | B10001, 184 | B01110 185 | }; 186 | 187 | const char _iconimage_rain[] PROGMEM = { 188 | B00000, 189 | B00000, 190 | B00110, 191 | B01001, 192 | B11111, 193 | B00000, 194 | B10101, 195 | B10101 196 | }; 197 | 198 | const char _iconimage_soil[] PROGMEM = { 199 | B00100, 200 | B00100, 201 | B01010, 202 | B01010, 203 | B10001, 204 | B10001, 205 | B10001, 206 | B01110 207 | }; 208 | 209 | /* 210 | const char _iconimage_flow[] PROGMEM = { 211 | B00000, 212 | B00000, 213 | B00000, 214 | B11010, 215 | B10010, 216 | B11010, 217 | B10011, 218 | B00000 219 | }; 220 | 221 | const char _iconimage_pswitch[] PROGMEM = { 222 | B00000, 223 | B11000, 224 | B10100, 225 | B11000, 226 | B10010, 227 | B10110, 228 | B00010, 229 | B00111 230 | }; 231 | 232 | // todo 233 | 234 | */ 235 | 236 | 237 | #else 238 | 239 | #endif 240 | 241 | #endif 242 | -------------------------------------------------------------------------------- /weather.cpp: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * Weather functions 5 | * Feb 2015 @ OpenSprinkler.com 6 | * 7 | * This file is part of the OpenSprinkler library 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see 21 | * . 22 | */ 23 | 24 | #include "OpenSprinkler.h" 25 | #include "utils.h" 26 | #include "server.h" 27 | #include "weather.h" 28 | 29 | extern OpenSprinkler os; // OpenSprinkler object 30 | extern char tmp_buffer[]; 31 | extern char ether_buffer[]; 32 | char wt_rawData[TMP_BUFFER_SIZE]; 33 | int wt_errCode = HTTP_RQT_NOT_RECEIVED; 34 | 35 | byte findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); 36 | void write_log(byte type, ulong curr_time); 37 | 38 | // The weather function calls getweather.py on remote server to retrieve weather data 39 | // the default script is WEATHER_SCRIPT_HOST/weather?.py 40 | //static char website[] PROGMEM = DEFAULT_WEATHER_URL ; 41 | 42 | static void getweather_callback(char* buffer) { 43 | char *p = buffer; 44 | 45 | /* scan the buffer until the first & symbol */ 46 | while(*p && *p!='&') { 47 | p++; 48 | } 49 | if (*p != '&') return; 50 | int v; 51 | bool save_nvdata = false; 52 | 53 | // first check errCode, only update lswc timestamp if errCode is 0 54 | if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("errCode"), true)) { 55 | wt_errCode = atoi(tmp_buffer); 56 | if(wt_errCode==0) os.checkwt_success_lasttime = os.now_tz(); 57 | } 58 | 59 | // then only parse scale if errCode is 0 60 | if (wt_errCode==0 && findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("scale"), true)) { 61 | v = atoi(tmp_buffer); 62 | if (v>=0 && v<=250 && v != os.iopts[IOPT_WATER_PERCENTAGE]) { 63 | // only save if the value has changed 64 | os.iopts[IOPT_WATER_PERCENTAGE] = v; 65 | os.iopts_save(); 66 | os.weather_update_flag |= WEATHER_UPDATE_WL; 67 | } 68 | } 69 | 70 | if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sunrise"), true)) { 71 | v = atoi(tmp_buffer); 72 | if (v>=0 && v<=1440 && v != os.nvdata.sunrise_time) { 73 | os.nvdata.sunrise_time = v; 74 | save_nvdata = true; 75 | os.weather_update_flag |= WEATHER_UPDATE_SUNRISE; 76 | } 77 | } 78 | 79 | if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sunset"), true)) { 80 | v = atoi(tmp_buffer); 81 | if (v>=0 && v<=1440 && v != os.nvdata.sunset_time) { 82 | os.nvdata.sunset_time = v; 83 | save_nvdata = true; 84 | os.weather_update_flag |= WEATHER_UPDATE_SUNSET; 85 | } 86 | } 87 | 88 | if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("eip"), true)) { 89 | uint32_t l = atol(tmp_buffer); 90 | if(l != os.nvdata.external_ip) { 91 | os.nvdata.external_ip = atol(tmp_buffer); 92 | save_nvdata = true; 93 | os.weather_update_flag |= WEATHER_UPDATE_EIP; 94 | } 95 | } 96 | 97 | if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("tz"), true)) { 98 | v = atoi(tmp_buffer); 99 | if (v>=0 && v<= 108) { 100 | if (v != os.iopts[IOPT_TIMEZONE]) { 101 | // if timezone changed, save change and force ntp sync 102 | os.iopts[IOPT_TIMEZONE] = v; 103 | os.iopts_save(); 104 | os.weather_update_flag |= WEATHER_UPDATE_TZ; 105 | } 106 | } 107 | } 108 | 109 | if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rd"), true)) { 110 | v = atoi(tmp_buffer); 111 | if (v>0) { 112 | os.nvdata.rd_stop_time = os.now_tz() + (unsigned long) v * 3600; 113 | os.raindelay_start(); 114 | } else if (v==0) { 115 | os.raindelay_stop(); 116 | } 117 | } 118 | 119 | if (findKeyVal(p, wt_rawData, TMP_BUFFER_SIZE, PSTR("rawData"), true)) { 120 | wt_rawData[TMP_BUFFER_SIZE-1]=0; // make sure the buffer ends properly 121 | } 122 | 123 | if(save_nvdata) os.nvdata_save(); 124 | write_log(LOGDATA_WATERLEVEL, os.checkwt_success_lasttime); 125 | } 126 | 127 | static void getweather_callback_with_peel_header(char* buffer) { 128 | peel_http_header(buffer); 129 | getweather_callback(buffer); 130 | } 131 | 132 | void GetWeather() { 133 | #if defined(ESP8266) || defined(ESP32) 134 | if(!m_server) { 135 | if (os.state!=OS_STATE_CONNECTED || WiFi.status()!=WL_CONNECTED) return; 136 | } 137 | #endif 138 | // use temp buffer to construct get command 139 | BufferFiller bf = tmp_buffer; 140 | bf.emit_p(PSTR("$D?loc=$O&wto=$O&fwv=$D"), 141 | (int) os.iopts[IOPT_USE_WEATHER], 142 | SOPT_LOCATION, 143 | SOPT_WEATHER_OPTS, 144 | (int)os.iopts[IOPT_FW_VERSION]); 145 | 146 | char *src=tmp_buffer+strlen(tmp_buffer); 147 | char *dst=tmp_buffer+TMP_BUFFER_SIZE-12; 148 | 149 | char c; 150 | // url encode. convert SPACE to %20 151 | // copy reversely from the end because we are potentially expanding 152 | // the string size 153 | while(src!=tmp_buffer) { 154 | c = *src--; 155 | if(c==' ') { 156 | *dst-- = '0'; 157 | *dst-- = '2'; 158 | *dst-- = '%'; 159 | } else { 160 | *dst-- = c; 161 | } 162 | }; 163 | *dst = *src; 164 | 165 | strcpy(ether_buffer, "GET /"); 166 | strcat(ether_buffer, dst); 167 | // because dst is part of tmp_buffer, 168 | // must load weather url AFTER dst is copied to ether_buffer 169 | 170 | // load weather url to tmp_buffer 171 | char *host = tmp_buffer; 172 | os.sopt_load(SOPT_WEATHERURL, host); 173 | 174 | strcat(ether_buffer, " HTTP/1.0\r\nHOST: "); 175 | strcat(ether_buffer, host); 176 | strcat(ether_buffer, "\r\n\r\n"); 177 | 178 | wt_errCode = HTTP_RQT_NOT_RECEIVED; 179 | int ret = os.send_http_request(host, ether_buffer, getweather_callback_with_peel_header); 180 | if(ret!=HTTP_RQT_SUCCESS) { 181 | if(wt_errCode < 0) wt_errCode = ret; 182 | // if wt_errCode > 0, the call is successful but weather script may return error 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /NTPClient.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * The MIT License (MIT) 3 | * Copyright (c) 2015 by Fabrice Weinberg 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * The above copyright notice and this permission notice shall be included in all 12 | * copies or substantial portions of the Software. 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | * SOFTWARE. 20 | */ 21 | 22 | #include "NTPClient.h" 23 | 24 | NTPClient::NTPClient(UDP& udp) { 25 | this->_udp = &udp; 26 | } 27 | 28 | NTPClient::NTPClient(UDP& udp, long timeOffset) { 29 | this->_udp = &udp; 30 | this->_timeOffset = timeOffset; 31 | } 32 | 33 | NTPClient::NTPClient(UDP& udp, const char* poolServerName) { 34 | this->_udp = &udp; 35 | this->_poolServerName = poolServerName; 36 | } 37 | 38 | NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset) { 39 | this->_udp = &udp; 40 | this->_timeOffset = timeOffset; 41 | this->_poolServerName = poolServerName; 42 | } 43 | 44 | NTPClient::NTPClient(UDP& udp, const char* poolServerName, long timeOffset, unsigned long updateInterval) { 45 | this->_udp = &udp; 46 | this->_timeOffset = timeOffset; 47 | this->_poolServerName = poolServerName; 48 | this->_updateInterval = updateInterval; 49 | } 50 | 51 | void NTPClient::begin() { 52 | this->begin(NTP_DEFAULT_LOCAL_PORT); 53 | } 54 | 55 | void NTPClient::begin(int port) { 56 | this->_port = port; 57 | 58 | this->_udp->begin(this->_port); 59 | 60 | this->_udpSetup = true; 61 | } 62 | 63 | bool NTPClient::forceUpdate() { 64 | #ifdef DEBUG_NTPClient 65 | Serial.println("Update from NTP Server"); 66 | #endif 67 | 68 | this->sendNTPPacket(); 69 | 70 | // Wait till data is there or timeout... 71 | byte timeout = 0; 72 | int cb = 0; 73 | do { 74 | delay ( 10 ); 75 | cb = this->_udp->parsePacket(); 76 | if (timeout > 100) return false; // timeout after 1000 ms 77 | timeout++; 78 | } while (cb == 0); 79 | 80 | this->_lastUpdate = millis() - (10 * (timeout + 1)); // Account for delay in reading the time 81 | 82 | this->_udp->read(this->_packetBuffer, NTP_PACKET_SIZE); 83 | 84 | unsigned long highWord = word(this->_packetBuffer[40], this->_packetBuffer[41]); 85 | unsigned long lowWord = word(this->_packetBuffer[42], this->_packetBuffer[43]); 86 | // combine the four bytes (two words) into a long integer 87 | // this is NTP time (seconds since Jan 1 1900): 88 | unsigned long secsSince1900 = highWord << 16 | lowWord; 89 | 90 | this->_currentEpoc = secsSince1900 - SEVENZYYEARS; 91 | 92 | return true; 93 | } 94 | 95 | bool NTPClient::update() { 96 | if ((millis() - this->_lastUpdate >= this->_updateInterval) // Update after _updateInterval 97 | || this->_lastUpdate == 0) { // Update if there was no update yet. 98 | if (!this->_udpSetup) this->begin(); // setup the UDP client if needed 99 | return this->forceUpdate(); 100 | } 101 | return true; 102 | } 103 | 104 | unsigned long NTPClient::getEpochTime() const { 105 | return this->_timeOffset + // User offset 106 | this->_currentEpoc + // Epoc returned by the NTP server 107 | ((millis() - this->_lastUpdate) / 1000); // Time since last update 108 | } 109 | 110 | int NTPClient::getDay() const { 111 | return (((this->getEpochTime() / 86400L) + 4 ) % 7); //0 is Sunday 112 | } 113 | int NTPClient::getHours() const { 114 | return ((this->getEpochTime() % 86400L) / 3600); 115 | } 116 | int NTPClient::getMinutes() const { 117 | return ((this->getEpochTime() % 3600) / 60); 118 | } 119 | int NTPClient::getSeconds() const { 120 | return (this->getEpochTime() % 60); 121 | } 122 | 123 | String NTPClient::getFormattedTime() const { 124 | unsigned long rawTime = this->getEpochTime(); 125 | unsigned long hours = (rawTime % 86400L) / 3600; 126 | String hoursStr = hours < 10 ? "0" + String(hours) : String(hours); 127 | 128 | unsigned long minutes = (rawTime % 3600) / 60; 129 | String minuteStr = minutes < 10 ? "0" + String(minutes) : String(minutes); 130 | 131 | unsigned long seconds = rawTime % 60; 132 | String secondStr = seconds < 10 ? "0" + String(seconds) : String(seconds); 133 | 134 | return hoursStr + ":" + minuteStr + ":" + secondStr; 135 | } 136 | 137 | void NTPClient::end() { 138 | this->_udp->stop(); 139 | 140 | this->_udpSetup = false; 141 | } 142 | 143 | void NTPClient::setTimeOffset(int timeOffset) { 144 | this->_timeOffset = timeOffset; 145 | } 146 | 147 | void NTPClient::setUpdateInterval(unsigned long updateInterval) { 148 | this->_updateInterval = updateInterval; 149 | } 150 | 151 | void NTPClient::setPoolServerName(const char* poolServerName) { 152 | this->_poolServerName = poolServerName; 153 | } 154 | 155 | void NTPClient::sendNTPPacket() { 156 | // set all bytes in the buffer to 0 157 | memset(this->_packetBuffer, 0, NTP_PACKET_SIZE); 158 | // Initialize values needed to form NTP request 159 | // (see URL above for details on the packets) 160 | this->_packetBuffer[0] = 0b11100011; // LI, Version, Mode 161 | this->_packetBuffer[1] = 0; // Stratum, or type of clock 162 | this->_packetBuffer[2] = 6; // Polling Interval 163 | this->_packetBuffer[3] = 0xEC; // Peer Clock Precision 164 | // 8 bytes of zero for Root Delay & Root Dispersion 165 | this->_packetBuffer[12] = 49; 166 | this->_packetBuffer[13] = 0x4E; 167 | this->_packetBuffer[14] = 49; 168 | this->_packetBuffer[15] = 52; 169 | 170 | // all NTP fields have been given values, now 171 | // you can send a packet requesting a timestamp: 172 | this->_udp->beginPacket(this->_poolServerName, 123); //NTP requests are to port 123 173 | this->_udp->write(this->_packetBuffer, NTP_PACKET_SIZE); 174 | this->_udp->endPacket(); 175 | } 176 | -------------------------------------------------------------------------------- /TimeLib.h: -------------------------------------------------------------------------------- 1 | /* 2 | time.h - low level time and date functions 3 | */ 4 | 5 | /* 6 | July 3 2011 - fixed elapsedSecsThisWeek macro (thanks Vincent Valdy for this) 7 | - fixed daysToTime_t macro (thanks maniacbug) 8 | */ 9 | 10 | #ifndef _Time_h 11 | #ifdef __cplusplus 12 | #define _Time_h 13 | 14 | #include 15 | #ifndef __AVR__ 16 | #include // for __time_t_defined, but avr libc lacks sys/types.h 17 | #endif 18 | 19 | 20 | #if !defined(__time_t_defined) // avoid conflict with newlib or other posix libc 21 | typedef unsigned long time_t; 22 | #endif 23 | 24 | 25 | // This ugly hack allows us to define C++ overloaded functions, when included 26 | // from within an extern "C", as newlib's sys/stat.h does. Actually it is 27 | // intended to include "time.h" from the C library (on ARM, but AVR does not 28 | // have that file at all). On Mac and Windows, the compiler will find this 29 | // "Time.h" instead of the C library "time.h", so we may cause other weird 30 | // and unpredictable effects by conflicting with the C library header "time.h", 31 | // but at least this hack lets us define C++ functions as intended. Hopefully 32 | // nothing too terrible will result from overriding the C library header?! 33 | extern "C++" { 34 | typedef enum {timeNotSet, timeNeedsSync, timeSet 35 | } timeStatus_t ; 36 | 37 | typedef enum { 38 | dowInvalid, dowSunday, dowMonday, dowTuesday, dowWednesday, dowThursday, dowFriday, dowSaturday 39 | } timeDayOfWeek_t; 40 | 41 | typedef enum { 42 | tmSecond, tmMinute, tmHour, tmWday, tmDay,tmMonth, tmYear, tmNbrFields 43 | } tmByteFields; 44 | 45 | typedef struct { 46 | uint8_t Second; 47 | uint8_t Minute; 48 | uint8_t Hour; 49 | uint8_t Wday; // day of week, sunday is day 1 50 | uint8_t Day; 51 | uint8_t Month; 52 | uint8_t Year; // offset from 1970; 53 | } tmElements_t, TimeElements, *tmElementsPtr_t; 54 | 55 | //convenience macros to convert to and from tm years 56 | #define tmYearToCalendar(Y) ((Y) + 1970) // full four digit year 57 | #define CalendarYrToTm(Y) ((Y) - 1970) 58 | #define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000 59 | #define y2kYearToTm(Y) ((Y) + 30) 60 | 61 | typedef time_t(*getExternalTime)(); 62 | //typedef void (*setExternalTime)(const time_t); // not used in this version 63 | 64 | 65 | /*==============================================================================*/ 66 | /* Useful Constants */ 67 | #define SECS_PER_MIN (60UL) 68 | #define SECS_PER_HOUR (3600UL) 69 | #define SECS_PER_DAY (SECS_PER_HOUR * 24UL) 70 | #define DAYS_PER_WEEK (7UL) 71 | #define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK) 72 | #define SECS_PER_YEAR (SECS_PER_WEEK * 52UL) 73 | #define SECS_YR_2000 (946684800UL) // the time at the start of y2k 74 | 75 | /* Useful Macros for getting elapsed time */ 76 | #define numberOfSeconds(_time_) (_time_ % SECS_PER_MIN) 77 | #define numberOfMinutes(_time_) ((_time_ / SECS_PER_MIN) % SECS_PER_MIN) 78 | #define numberOfHours(_time_) (( _time_% SECS_PER_DAY) / SECS_PER_HOUR) 79 | #define dayOfWeek(_time_) ((( _time_ / SECS_PER_DAY + 4) % DAYS_PER_WEEK)+1) // 1 = Sunday 80 | #define elapsedDays(_time_) ( _time_ / SECS_PER_DAY) // this is number of days since Jan 1 1970 81 | #define elapsedSecsToday(_time_) (_time_ % SECS_PER_DAY) // the number of seconds since last midnight 82 | // The following macros are used in calculating alarms and assume the clock is set to a date later than Jan 1 1971 83 | // Always set the correct time before settting alarms 84 | #define previousMidnight(_time_) (( _time_ / SECS_PER_DAY) * SECS_PER_DAY) // time at the start of the given day 85 | #define nextMidnight(_time_) ( previousMidnight(_time_) + SECS_PER_DAY ) // time at the end of the given day 86 | #define elapsedSecsThisWeek(_time_) (elapsedSecsToday(_time_) + ((dayOfWeek(_time_)-1) * SECS_PER_DAY) ) // note that week starts on day 1 87 | #define previousSunday(_time_) (_time_ - elapsedSecsThisWeek(_time_)) // time at the start of the week for the given time 88 | #define nextSunday(_time_) ( previousSunday(_time_)+SECS_PER_WEEK) // time at the end of the week for the given time 89 | 90 | 91 | /* Useful Macros for converting elapsed time to a time_t */ 92 | #define minutesToTime_t ((M)) ( (M) * SECS_PER_MIN) 93 | #define hoursToTime_t ((H)) ( (H) * SECS_PER_HOUR) 94 | #define daysToTime_t ((D)) ( (D) * SECS_PER_DAY) // fixed on Jul 22 2011 95 | #define weeksToTime_t ((W)) ( (W) * SECS_PER_WEEK) 96 | 97 | /*============================================================================*/ 98 | /* time and date functions */ 99 | int hour(); // the hour now 100 | int hour(time_t t); // the hour for the given time 101 | int hourFormat12(); // the hour now in 12 hour format 102 | int hourFormat12(time_t t); // the hour for the given time in 12 hour format 103 | uint8_t isAM(); // returns true if time now is AM 104 | uint8_t isAM(time_t t); // returns true the given time is AM 105 | uint8_t isPM(); // returns true if time now is PM 106 | uint8_t isPM(time_t t); // returns true the given time is PM 107 | int minute(); // the minute now 108 | int minute(time_t t); // the minute for the given time 109 | int second(); // the second now 110 | int second(time_t t); // the second for the given time 111 | int day(); // the day now 112 | int day(time_t t); // the day for the given time 113 | int weekday(); // the weekday now (Sunday is day 1) 114 | int weekday(time_t t); // the weekday for the given time 115 | int month(); // the month now (Jan is month 1) 116 | int month(time_t t); // the month for the given time 117 | int year(); // the full four digit year: (2009, 2010 etc) 118 | int year(time_t t); // the year for the given time 119 | 120 | time_t now(); // return the current time as seconds since Jan 1 1970 121 | void setTime(time_t t); 122 | void setTime(int hr,int min,int sec,int day, int month, int yr); 123 | void adjustTime(long adjustment); 124 | 125 | /* date strings */ 126 | #define dt_MAX_STRING_LEN 9 // length of longest date string (excluding terminating null) 127 | char* monthStr(uint8_t month); 128 | char* dayStr(uint8_t day); 129 | char* monthShortStr(uint8_t month); 130 | char* dayShortStr(uint8_t day); 131 | 132 | /* time sync functions */ 133 | timeStatus_t timeStatus(); // indicates if time has been set and recently synchronized 134 | void setSyncProvider( getExternalTime getTimeFunction); // identify the external time provider 135 | void setSyncInterval(time_t interval); // set the number of seconds between re-sync 136 | 137 | /* low level functions to convert to and from system time */ 138 | void breakTime(time_t time, tmElements_t &tm); // break time_t into elements 139 | time_t makeTime(tmElements_t &tm); // convert time elements into time_t 140 | 141 | } // extern "C++" 142 | #endif // __cplusplus 143 | #endif /* _Time_h */ 144 | 145 | -------------------------------------------------------------------------------- /htmls.h: -------------------------------------------------------------------------------- 1 | const char ap_home_html[] PROGMEM = R"( 2 | OpenSprinkler WiFi Config 3 | 4 | 5 | 6 | 9 | OpenSprinkler WiFi Config

10 | 11 | 12 | 13 |
Detected SSIDsStrengthPower Level
(Scanning...)
14 |

15 | 16 | 17 | 18 | 19 | 20 | 21 |
(Your WiFi SSID)
(Your WiFi Password)

22 | 73 | 74 | )"; 75 | const char ap_update_html[] PROGMEM = R"( 76 | OpenSprinkler Firmware Update 77 | 78 | 79 | 80 |
81 |

OpenSprinkler AP-mode Firmware Update

82 |
83 |
84 | 85 | 86 | 87 | 88 |
Device password:
89 |
92 |
93 | 130 | 131 | )"; 132 | const char sta_update_html[] PROGMEM = R"( 133 | OpenSprinkler Firmware Update 134 | 135 | 136 | 137 | 138 | 139 | 140 |
141 |

OpenSprinkler Firmware Update

142 |
143 |
144 | 145 | 146 | 147 | 148 |
Device password:
149 | Submit 150 |
151 |
152 |
153 |

© OpenSprinkler (www.opensprinkler.com)

154 |
155 |
156 | 192 | 193 | 194 | )"; 195 | -------------------------------------------------------------------------------- /TimeLib.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | time.c - low level time and date functions 3 | Copyright (c) Michael Margolis 2009-2014 4 | 5 | This library is free software; you can redistribute it and/or 6 | modify it under the terms of the GNU Lesser General Public 7 | License as published by the Free Software Foundation; either 8 | version 2.1 of the License, or (at your option) any later version. 9 | 10 | This library is distributed in the hope that it will be useful, 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 | Lesser General Public License for more details. 14 | 15 | You should have received a copy of the GNU Lesser General Public 16 | License along with this library; if not, write to the Free Software 17 | Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 18 | 19 | 1.0 6 Jan 2010 - initial release 20 | 1.1 12 Feb 2010 - fixed leap year calculation error 21 | 1.2 1 Nov 2010 - fixed setTime bug (thanks to Korman for this) 22 | 1.3 24 Mar 2012 - many edits by Paul Stoffregen: fixed timeStatus() to update 23 | status, updated examples for Arduino 1.0, fixed ARM 24 | compatibility issues, added TimeArduinoDue and TimeTeensy3 25 | examples, add error checking and messages to RTC examples, 26 | add examples to DS1307RTC library. 27 | 1.4 5 Sep 2014 - compatibility with Arduino 1.5.7 28 | */ 29 | 30 | #if ARDUINO >= 100 31 | #include 32 | #else 33 | #include 34 | #endif 35 | 36 | #include "TimeLib.h" 37 | 38 | static tmElements_t tm; // a cache of time elements 39 | static time_t cacheTime; // the time the cache was updated 40 | static uint32_t syncInterval = 300; // time sync will be attempted after this many seconds 41 | 42 | void refreshCache(time_t t) { 43 | if (t != cacheTime) { 44 | breakTime(t, tm); 45 | cacheTime = t; 46 | } 47 | } 48 | 49 | int hour() { // the hour now 50 | return hour(now()); 51 | } 52 | 53 | int hour(time_t t) { // the hour for the given time 54 | refreshCache(t); 55 | return tm.Hour; 56 | } 57 | 58 | int hourFormat12() { // the hour now in 12 hour format 59 | return hourFormat12(now()); 60 | } 61 | 62 | int hourFormat12(time_t t) { // the hour for the given time in 12 hour format 63 | refreshCache(t); 64 | if( tm.Hour == 0 ) 65 | return 12; // 12 midnight 66 | else if( tm.Hour > 12) 67 | return tm.Hour - 12 ; 68 | else 69 | return tm.Hour ; 70 | } 71 | 72 | uint8_t isAM() { // returns true if time now is AM 73 | return !isPM(now()); 74 | } 75 | 76 | uint8_t isAM(time_t t) { // returns true if given time is AM 77 | return !isPM(t); 78 | } 79 | 80 | uint8_t isPM() { // returns true if PM 81 | return isPM(now()); 82 | } 83 | 84 | uint8_t isPM(time_t t) { // returns true if PM 85 | return (hour(t) >= 12); 86 | } 87 | 88 | int minute() { 89 | return minute(now()); 90 | } 91 | 92 | int minute(time_t t) { // the minute for the given time 93 | refreshCache(t); 94 | return tm.Minute; 95 | } 96 | 97 | int second() { 98 | return second(now()); 99 | } 100 | 101 | int second(time_t t) { // the second for the given time 102 | refreshCache(t); 103 | return tm.Second; 104 | } 105 | 106 | int day(){ 107 | return(day(now())); 108 | } 109 | 110 | int day(time_t t) { // the day for the given time (0-6) 111 | refreshCache(t); 112 | return tm.Day; 113 | } 114 | 115 | int weekday() { // Sunday is day 1 116 | return weekday(now()); 117 | } 118 | 119 | int weekday(time_t t) { 120 | refreshCache(t); 121 | return tm.Wday; 122 | } 123 | 124 | int month(){ 125 | return month(now()); 126 | } 127 | 128 | int month(time_t t) { // the month for the given time 129 | refreshCache(t); 130 | return tm.Month; 131 | } 132 | 133 | int year() { // as in Processing, the full four digit year: (2009, 2010 etc) 134 | return year(now()); 135 | } 136 | 137 | int year(time_t t) { // the year for the given time 138 | refreshCache(t); 139 | return tmYearToCalendar(tm.Year); 140 | } 141 | 142 | /*============================================================================*/ 143 | /* functions to convert to and from system time */ 144 | /* These are for interfacing with time serivces and are not normally needed in a sketch */ 145 | 146 | // leap year calulator expects year argument as years offset from 1970 147 | #define LEAP_YEAR(Y) ( ((1970+Y)>0) && !((1970+Y)%4) && ( ((1970+Y)%100) || !((1970+Y)%400) ) ) 148 | 149 | static const uint8_t monthDays[]={31,28,31,30,31,30,31,31,30,31,30,31}; // API starts months from 1, this array starts from 0 150 | 151 | void breakTime(time_t timeInput, tmElements_t &tm){ 152 | // break the given time_t into time components 153 | // this is a more compact version of the C library localtime function 154 | // note that year is offset from 1970 !!! 155 | 156 | uint8_t year; 157 | uint8_t month, monthLength; 158 | uint32_t time; 159 | unsigned long days; 160 | 161 | time = (uint32_t)timeInput; 162 | tm.Second = time % 60; 163 | time /= 60; // now it is minutes 164 | tm.Minute = time % 60; 165 | time /= 60; // now it is hours 166 | tm.Hour = time % 24; 167 | time /= 24; // now it is days 168 | tm.Wday = ((time + 4) % 7) + 1; // Sunday is day 1 169 | 170 | year = 0; 171 | days = 0; 172 | while((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { 173 | year++; 174 | } 175 | tm.Year = year; // year is offset from 1970 176 | 177 | days -= LEAP_YEAR(year) ? 366 : 365; 178 | time -= days; // now it is days in this year, starting at 0 179 | 180 | days=0; 181 | month=0; 182 | monthLength=0; 183 | for (month=0; month<12; month++) { 184 | if (month==1) { // february 185 | if (LEAP_YEAR(year)) { 186 | monthLength=29; 187 | } else { 188 | monthLength=28; 189 | } 190 | } else { 191 | monthLength = monthDays[month]; 192 | } 193 | 194 | if (time >= monthLength) { 195 | time -= monthLength; 196 | } else { 197 | break; 198 | } 199 | } 200 | tm.Month = month + 1; // jan is month 1 201 | tm.Day = time + 1; // day of month 202 | } 203 | 204 | time_t makeTime(tmElements_t &tm){ 205 | // assemble time elements into time_t 206 | // note year argument is offset from 1970 (see macros in time.h to convert to other formats) 207 | // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 208 | 209 | int i; 210 | uint32_t seconds; 211 | 212 | // seconds from 1970 till 1 jan 00:00:00 of the given year 213 | seconds= tm.Year*(SECS_PER_DAY * 365); 214 | for (i = 0; i < tm.Year; i++) { 215 | if (LEAP_YEAR(i)) { 216 | seconds += SECS_PER_DAY; // add extra days for leap years 217 | } 218 | } 219 | 220 | // add days for this year, months start from 1 221 | for (i = 1; i < tm.Month; i++) { 222 | if ( (i == 2) && LEAP_YEAR(tm.Year)) { 223 | seconds += SECS_PER_DAY * 29; 224 | } else { 225 | seconds += SECS_PER_DAY * monthDays[i-1]; //monthDay array starts from 0 226 | } 227 | } 228 | seconds+= (tm.Day-1) * SECS_PER_DAY; 229 | seconds+= tm.Hour * SECS_PER_HOUR; 230 | seconds+= tm.Minute * SECS_PER_MIN; 231 | seconds+= tm.Second; 232 | return (time_t)seconds; 233 | } 234 | /*=====================================================*/ 235 | /* Low level system time functions */ 236 | 237 | static uint32_t sysTime = 0; 238 | static uint32_t prevMillis = 0; 239 | static uint32_t nextSyncTime = 0; 240 | static timeStatus_t Status = timeNotSet; 241 | 242 | getExternalTime getTimePtr; // pointer to external sync function 243 | //setExternalTime setTimePtr; // not used in this version 244 | 245 | #ifdef TIME_DRIFT_INFO // define this to get drift data 246 | time_t sysUnsyncedTime = 0; // the time sysTime unadjusted by sync 247 | #endif 248 | 249 | 250 | time_t now() { 251 | // calculate number of seconds passed since last call to now() 252 | while (millis() - prevMillis >= 1000) { 253 | // millis() and prevMillis are both unsigned ints thus the subtraction will always be the absolute value of the difference 254 | sysTime++; 255 | prevMillis += 1000; 256 | #ifdef TIME_DRIFT_INFO 257 | sysUnsyncedTime++; // this can be compared to the synced time to measure long term drift 258 | #endif 259 | } 260 | if (nextSyncTime <= sysTime) { 261 | if (getTimePtr != 0) { 262 | time_t t = getTimePtr(); 263 | if (t != 0) { 264 | setTime(t); 265 | } else { 266 | nextSyncTime = sysTime + syncInterval; 267 | Status = (Status == timeNotSet) ? timeNotSet : timeNeedsSync; 268 | } 269 | } 270 | } 271 | return (time_t)sysTime; 272 | } 273 | 274 | void setTime(time_t t) { 275 | #ifdef TIME_DRIFT_INFO 276 | if(sysUnsyncedTime == 0) 277 | sysUnsyncedTime = t; // store the time of the first call to set a valid Time 278 | #endif 279 | 280 | sysTime = (uint32_t)t; 281 | nextSyncTime = (uint32_t)t + syncInterval; 282 | Status = timeSet; 283 | prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) 284 | } 285 | 286 | void setTime(int hr,int min,int sec,int dy, int mnth, int yr){ 287 | // year can be given as full four digit year or two digts (2010 or 10 for 2010); 288 | //it is converted to years since 1970 289 | if( yr > 99) 290 | yr = yr - 1970; 291 | else 292 | yr += 30; 293 | tm.Year = yr; 294 | tm.Month = mnth; 295 | tm.Day = dy; 296 | tm.Hour = hr; 297 | tm.Minute = min; 298 | tm.Second = sec; 299 | setTime(makeTime(tm)); 300 | } 301 | 302 | void adjustTime(long adjustment) { 303 | sysTime += adjustment; 304 | } 305 | 306 | // indicates if time has been set and recently synchronized 307 | timeStatus_t timeStatus() { 308 | now(); // required to actually update the status 309 | return Status; 310 | } 311 | 312 | void setSyncProvider( getExternalTime getTimeFunction){ 313 | getTimePtr = getTimeFunction; 314 | nextSyncTime = sysTime; 315 | now(); // this will sync the clock 316 | } 317 | 318 | void setSyncInterval(time_t interval){ // set the number of seconds between re-sync 319 | syncInterval = (uint32_t)interval; 320 | nextSyncTime = sysTime + syncInterval; 321 | } 322 | -------------------------------------------------------------------------------- /program.cpp: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * Program data structures and functions 5 | * Feb 2015 @ OpenSprinkler.com 6 | * 7 | * This file is part of the OpenSprinkler library 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see 21 | * . 22 | */ 23 | 24 | #include 25 | #include "program.h" 26 | 27 | #if !defined(SECS_PER_DAY) 28 | #define SECS_PER_MIN (60UL) 29 | #define SECS_PER_HOUR (3600UL) 30 | #define SECS_PER_DAY (SECS_PER_HOUR * 24UL) 31 | #endif 32 | 33 | // Declare static data members 34 | byte ProgramData::nprograms = 0; 35 | byte ProgramData::nqueue = 0; 36 | RuntimeQueueStruct ProgramData::queue[RUNTIME_QUEUE_SIZE]; 37 | byte ProgramData::station_qid[MAX_NUM_STATIONS]; 38 | LogStruct ProgramData::lastrun; 39 | ulong ProgramData::last_seq_stop_time; 40 | extern char tmp_buffer[]; 41 | 42 | void ProgramData::init() { 43 | reset_runtime(); 44 | load_count(); 45 | } 46 | 47 | void ProgramData::reset_runtime() { 48 | memset(station_qid, 0xFF, MAX_NUM_STATIONS); // reset station qid to 0xFF 49 | nqueue = 0; 50 | last_seq_stop_time = 0; 51 | } 52 | 53 | /** Insert a new element to the queue 54 | * This function returns pointer to the next available element in the queue 55 | * and returns NULL if the queue is full 56 | */ 57 | RuntimeQueueStruct* ProgramData::enqueue() { 58 | if (nqueue < RUNTIME_QUEUE_SIZE) { 59 | nqueue ++; 60 | return queue + (nqueue-1); 61 | } else { 62 | return NULL; 63 | } 64 | } 65 | 66 | /** Remove an element from the queue 67 | * This function copies the last element of 68 | * the queue to overwrite the requested 69 | * element, therefore removing the requested element. 70 | */ 71 | // this removes an element from the queue 72 | void ProgramData::dequeue(byte qid) { 73 | if (qid>=nqueue) return; 74 | if (qid= nprograms) return; 101 | // first byte is program counter, so 1+ 102 | file_read_block(PROG_FILENAME, buf, 1+(ulong)pid*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); 103 | } 104 | 105 | /** Add a program */ 106 | byte ProgramData::add(ProgramStruct *buf) { 107 | if (nprograms >= MAX_NUM_PROGRAMS) return 0; 108 | file_write_block(PROG_FILENAME, buf, 1+(ulong)nprograms*PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE); 109 | nprograms ++; 110 | save_count(); 111 | return 1; 112 | } 113 | 114 | /** Move a program up (i.e. swap a program with the one above it) */ 115 | void ProgramData::moveup(byte pid) { 116 | if(pid >= nprograms || pid == 0) return; 117 | // swap program pid-1 and pid 118 | ulong pos = 1+(ulong)(pid-1)*PROGRAMSTRUCT_SIZE; 119 | ulong next = pos+PROGRAMSTRUCT_SIZE; 120 | char buf2[PROGRAMSTRUCT_SIZE]; 121 | file_read_block(PROG_FILENAME, tmp_buffer, pos, PROGRAMSTRUCT_SIZE); 122 | file_read_block(PROG_FILENAME, buf2, next, PROGRAMSTRUCT_SIZE); 123 | file_write_block(PROG_FILENAME, tmp_buffer, next, PROGRAMSTRUCT_SIZE); 124 | file_write_block(PROG_FILENAME, buf2, pos, PROGRAMSTRUCT_SIZE); 125 | } 126 | 127 | /** Modify a program */ 128 | byte ProgramData::modify(byte pid, ProgramStruct *buf) { 129 | if (pid >= nprograms) return 0; 130 | ulong pos = 1+(ulong)pid*PROGRAMSTRUCT_SIZE; 131 | file_write_block(PROG_FILENAME, buf, pos, PROGRAMSTRUCT_SIZE); 132 | return 1; 133 | } 134 | 135 | /** Delete program(s) */ 136 | byte ProgramData::del(byte pid) { 137 | if (pid >= nprograms) return 0; 138 | if (nprograms == 0) return 0; 139 | ulong pos = 1+(ulong)(pid+1)*PROGRAMSTRUCT_SIZE; 140 | // erase by copying backward 141 | for (; pos < 1+(ulong)nprograms*PROGRAMSTRUCT_SIZE; pos+=PROGRAMSTRUCT_SIZE) { 142 | file_copy_block(PROG_FILENAME, pos, pos-PROGRAMSTRUCT_SIZE, PROGRAMSTRUCT_SIZE, tmp_buffer); 143 | } 144 | nprograms --; 145 | save_count(); 146 | return 1; 147 | } 148 | 149 | // set the enable bit 150 | byte ProgramData::set_flagbit(byte pid, byte bid, byte value) { 151 | if (pid >= nprograms) return 0; 152 | byte flag = file_read_byte(PROG_FILENAME, 1+(ulong)pid*PROGRAMSTRUCT_SIZE); 153 | if(value) flag|=(1<>15)&1) return -1; 162 | int16_t offset = t&0x7ff; 163 | if((t>>STARTTIME_SIGN_BIT)&1) offset = -offset; 164 | if((t>>STARTTIME_SUNRISE_BIT)&1) { // sunrise time 165 | t = os.nvdata.sunrise_time + offset; 166 | if (t<0) t=0; // clamp it to 0 if less than 0 167 | } else if((t>>STARTTIME_SUNSET_BIT)&1) { 168 | t = os.nvdata.sunset_time + offset; 169 | if (t>=1440) t=1439; // clamp it to 1440 if larger than 1440 170 | } 171 | return t; 172 | } 173 | 174 | /** Check if a given time matches the program's start day */ 175 | byte ProgramStruct::check_day_match(time_t t) { 176 | 177 | #if defined(ARDUINO) // get current time from Arduino 178 | byte weekday_t = weekday(t); // weekday ranges from [0,6] within Sunday being 1 179 | byte day_t = day(t); 180 | byte month_t = month(t); 181 | #else // get current time from RPI/BBB 182 | time_t ct = t; 183 | struct tm *ti = gmtime(&ct); 184 | byte weekday_t = (ti->tm_wday+1)%7; // tm_wday ranges from [0,6] with Sunday being 0 185 | byte day_t = ti->tm_mday; 186 | byte month_t = ti->tm_mon+1; // tm_mon ranges from [0,11] 187 | #endif // get current time 188 | 189 | byte wd = (weekday_t+5)%7; 190 | byte dt = day_t; 191 | 192 | // check day match 193 | switch(type) { 194 | case PROGRAM_TYPE_WEEKLY: 195 | // weekday match 196 | if (!(days[0] & (1< start && interval) { 260 | // check if we are on any interval match 261 | int16_t c = (current_minute - start) / interval; 262 | if ((c * interval == (current_minute - start)) && c <= repeat) { 263 | return 1; 264 | } 265 | } 266 | } 267 | } 268 | // to proceed, program has to be repeating type, and interval and repeat must be non-zero 269 | if (starttime_type || !interval) return 0; 270 | 271 | // next, assume program started the previous day and ran over night 272 | if (check_day_match(t-86400L)) { 273 | // t-86400L matches the program's start day 274 | int16_t c = (current_minute - start + 1440) / interval; 275 | if ((c * interval == (current_minute - start + 1440)) && c <= repeat) { 276 | return 1; 277 | } 278 | } 279 | return 0; 280 | } 281 | 282 | // convert absolute remainder (reference time 1970 01-01) to relative remainder (reference time today) 283 | // absolute remainder is stored in flash, relative remainder is presented to web 284 | void ProgramData::drem_to_relative(byte days[2]) { 285 | byte rem_abs=days[0]; 286 | byte inv=days[1]; 287 | // todo future: use now_tz()? 288 | days[0] = (byte)((rem_abs + inv - (os.now_tz()/SECS_PER_DAY) % inv) % inv); 289 | } 290 | 291 | // relative remainder -> absolute remainder 292 | void ProgramData::drem_to_absolute(byte days[2]) { 293 | byte rem_rel=days[0]; 294 | byte inv=days[1]; 295 | // todo future: use now_tz()? 296 | days[0] = (byte)(((os.now_tz()/SECS_PER_DAY) + rem_rel) % inv); 297 | } 298 | 299 | 300 | 301 | -------------------------------------------------------------------------------- /LiquidCrystal.cpp: -------------------------------------------------------------------------------- 1 | #if defined(ARDUINO) && (!defined(ESP8266) && !defined(ESP32)) 2 | 3 | #include "LiquidCrystal.h" 4 | #include 5 | #include 6 | #include 7 | 8 | // When the display powers up, it is configured as follows: 9 | // 10 | // 1. Display clear 11 | // 2. Function set: 12 | // DL = 1; 8-bit interface data 13 | // N = 0; 1-line display 14 | // F = 0; 5x8 dot character font 15 | // 3. Display on/off control: 16 | // D = 0; Display off 17 | // C = 0; Cursor off 18 | // B = 0; Blinking off 19 | // 4. Entry mode set: 20 | // I/D = 1; Increment by 1 21 | // S = 0; No shift 22 | // 23 | // Note, however, that resetting the Arduino doesn't reset the LCD, so we 24 | // can't assume that its in that state when a sketch starts (and the 25 | // LiquidCrystal constructor is called). 26 | 27 | void LiquidCrystal::begin() { 28 | if (_type == LCD_I2C) { 29 | _displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; 30 | 31 | // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! 32 | // according to datasheet, we need at least 40ms after power rises above 2.7V 33 | // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 34 | delay(50); 35 | 36 | // Now we pull both RS and R/W low to begin commands 37 | expanderWrite(_backlightval); // reset expanderand turn backlight off (Bit 8 =1) 38 | delay(1000); 39 | 40 | //put the LCD into 4 bit mode 41 | // this is according to the hitachi HD44780 datasheet 42 | // figure 24, pg 46 43 | 44 | // we start in 8bit mode, try to set 4 bit mode 45 | write4bits(0x03 << 4); 46 | delayMicroseconds(4500); // wait min 4.1ms 47 | 48 | // second try 49 | write4bits(0x03 << 4); 50 | delayMicroseconds(4500); // wait min 4.1ms 51 | 52 | // third go! 53 | write4bits(0x03 << 4); 54 | delayMicroseconds(150); 55 | 56 | // finally, set to 4-bit interface 57 | write4bits(0x02 << 4); 58 | 59 | // set # lines, font size, etc. 60 | command(LCD_FUNCTIONSET | _displayfunction); 61 | 62 | // turn the display on with no cursor or blinking default 63 | _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; 64 | display(); 65 | 66 | // clear it off 67 | clear(); 68 | 69 | // Initialize to default text direction (for roman languages) 70 | _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; 71 | 72 | // set the entry mode 73 | command(LCD_ENTRYMODESET | _displaymode); 74 | 75 | home(); 76 | } 77 | 78 | if (_type == LCD_STD) { 79 | _displayfunction |= LCD_2LINE; 80 | _numlines = 2; 81 | _currline = 0; 82 | 83 | // SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION! 84 | // according to datasheet, we need at least 40ms after power rises above 2.7V 85 | // before sending commands. Arduino can turn on way befer 4.5V so we'll wait 50 86 | delayMicroseconds(50000); 87 | // Now we pull both RS and R/W low to begin commands 88 | digitalWrite(_rs_pin, LOW); 89 | digitalWrite(_enable_pin, LOW); 90 | if (_rw_pin != 255) { 91 | digitalWrite(_rw_pin, LOW); 92 | } 93 | 94 | //put the LCD into 4 bit or 8 bit mode 95 | if (! (_displayfunction & LCD_8BITMODE)) { 96 | // this is according to the hitachi HD44780 datasheet 97 | // figure 24, pg 46 98 | 99 | // we start in 8bit mode, try to set 4 bit mode 100 | write4bits(0x03); 101 | delayMicroseconds(4500); // wait min 4.1ms 102 | 103 | // second try 104 | write4bits(0x03); 105 | delayMicroseconds(4500); // wait min 4.1ms 106 | 107 | // third go! 108 | write4bits(0x03); 109 | delayMicroseconds(150); 110 | 111 | // finally, set to 4-bit interface 112 | write4bits(0x02); 113 | } else { 114 | // this is according to the hitachi HD44780 datasheet 115 | // page 45 figure 23 116 | 117 | // Send function set command sequence 118 | command(LCD_FUNCTIONSET | _displayfunction); 119 | delayMicroseconds(4500); // wait more than 4.1ms 120 | 121 | // second try 122 | command(LCD_FUNCTIONSET | _displayfunction); 123 | delayMicroseconds(150); 124 | 125 | // third go 126 | command(LCD_FUNCTIONSET | _displayfunction); 127 | } 128 | 129 | // finally, set # lines, font size, etc. 130 | command(LCD_FUNCTIONSET | _displayfunction); 131 | 132 | // turn the display on with no cursor or blinking default 133 | _displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF; 134 | display(); 135 | 136 | // clear it off 137 | clear(); 138 | 139 | // Initialize to default text direction (for romance languages) 140 | _displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT; 141 | // set the entry mode 142 | command(LCD_ENTRYMODESET | _displaymode); 143 | } 144 | } 145 | 146 | void LiquidCrystal::init(uint8_t fourbitmode, uint8_t rs, uint8_t rw, uint8_t enable, 147 | uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3, 148 | uint8_t d4, uint8_t d5, uint8_t d6, uint8_t d7) 149 | { 150 | _rs_pin = rs; 151 | _rw_pin = rw; 152 | _enable_pin = enable; 153 | 154 | _data_pins[0] = d0; 155 | _data_pins[1] = d1; 156 | _data_pins[2] = d2; 157 | _data_pins[3] = d3; 158 | _data_pins[4] = d4; 159 | _data_pins[5] = d5; 160 | _data_pins[6] = d6; 161 | _data_pins[7] = d7; 162 | 163 | Wire.begin(); 164 | _type = LCD_STD; 165 | 166 | // detect I2C and assign _type variable accordingly 167 | Wire.beginTransmission(LCD_I2C_ADDR1); // check type 1 168 | //Wire.write(0x00); 169 | uint8_t ret1 = Wire.endTransmission(); 170 | Wire.beginTransmission(LCD_I2C_ADDR2); // check type 2 171 | //Wire.write(0x00); 172 | uint8_t ret2 = Wire.endTransmission(); 173 | 174 | if (!ret1 || !ret2) _type = LCD_I2C; 175 | if (_type == LCD_I2C) { 176 | if(!ret1) _addr = LCD_I2C_ADDR1; 177 | else _addr = LCD_I2C_ADDR2; 178 | _cols = 16; 179 | _rows = 2; 180 | _charsize = LCD_5x8DOTS; 181 | _backlightval = LCD_BACKLIGHT; 182 | } 183 | 184 | if (_type == LCD_STD) { 185 | pinMode(_rs_pin, OUTPUT); 186 | // we can save 1 pin by not using RW. Indicate by passing 255 instead of pin# 187 | if (_rw_pin != 255) { 188 | pinMode(_rw_pin, OUTPUT); 189 | } 190 | pinMode(_enable_pin, OUTPUT); 191 | 192 | } 193 | _displayfunction = LCD_4BITMODE | LCD_2LINE | LCD_5x8DOTS; 194 | 195 | } 196 | 197 | /********** high level commands, for the user! */ 198 | void LiquidCrystal::clear() 199 | { 200 | command(LCD_CLEARDISPLAY);// clear display, set cursor position to zero 201 | delayMicroseconds(2000); // this command takes a long time! 202 | } 203 | 204 | void LiquidCrystal::home() 205 | { 206 | command(LCD_RETURNHOME); // set cursor position to zero 207 | delayMicroseconds(2000); // this command takes a long time! 208 | } 209 | 210 | void LiquidCrystal::setCursor(uint8_t col, uint8_t row) 211 | { 212 | int row_offsets[] = { 0x00, 0x40, 0x14, 0x54 }; 213 | if (_type == LCD_I2C) { 214 | if (row > _rows) { 215 | row = _rows-1; // we count rows starting w/0 216 | } 217 | } 218 | if (_type == LCD_STD) { 219 | if (row >= _numlines) { 220 | row = _numlines-1; 221 | } 222 | } 223 | command(LCD_SETDDRAMADDR | (col + row_offsets[row])); 224 | } 225 | 226 | // Turn the display on/off (quickly) 227 | void LiquidCrystal::noDisplay() { 228 | _displaycontrol &= ~LCD_DISPLAYON; 229 | command(LCD_DISPLAYCONTROL | _displaycontrol); 230 | } 231 | void LiquidCrystal::display() { 232 | _displaycontrol |= LCD_DISPLAYON; 233 | command(LCD_DISPLAYCONTROL | _displaycontrol); 234 | } 235 | 236 | // Turns the underline cursor on/off 237 | void LiquidCrystal::noCursor() { 238 | _displaycontrol &= ~LCD_CURSORON; 239 | command(LCD_DISPLAYCONTROL | _displaycontrol); 240 | } 241 | void LiquidCrystal::cursor() { 242 | _displaycontrol |= LCD_CURSORON; 243 | command(LCD_DISPLAYCONTROL | _displaycontrol); 244 | } 245 | 246 | // Turn on and off the blinking cursor 247 | void LiquidCrystal::noBlink() { 248 | _displaycontrol &= ~LCD_BLINKON; 249 | command(LCD_DISPLAYCONTROL | _displaycontrol); 250 | } 251 | void LiquidCrystal::blink() { 252 | _displaycontrol |= LCD_BLINKON; 253 | command(LCD_DISPLAYCONTROL | _displaycontrol); 254 | } 255 | 256 | // These commands scroll the display without changing the RAM 257 | void LiquidCrystal::scrollDisplayLeft(void) { 258 | command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT); 259 | } 260 | void LiquidCrystal::scrollDisplayRight(void) { 261 | command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT); 262 | } 263 | 264 | // This is for text that flows Left to Right 265 | void LiquidCrystal::leftToRight(void) { 266 | _displaymode |= LCD_ENTRYLEFT; 267 | command(LCD_ENTRYMODESET | _displaymode); 268 | } 269 | 270 | // This is for text that flows Right to Left 271 | void LiquidCrystal::rightToLeft(void) { 272 | _displaymode &= ~LCD_ENTRYLEFT; 273 | command(LCD_ENTRYMODESET | _displaymode); 274 | } 275 | 276 | // This will 'right justify' text from the cursor 277 | void LiquidCrystal::autoscroll(void) { 278 | _displaymode |= LCD_ENTRYSHIFTINCREMENT; 279 | command(LCD_ENTRYMODESET | _displaymode); 280 | } 281 | 282 | // This will 'left justify' text from the cursor 283 | void LiquidCrystal::noAutoscroll(void) { 284 | _displaymode &= ~LCD_ENTRYSHIFTINCREMENT; 285 | command(LCD_ENTRYMODESET | _displaymode); 286 | } 287 | 288 | // Allows us to fill the first 8 CGRAM locations 289 | // with custom characters 290 | //void LiquidCrystal::createChar(uint8_t location, uint8_t charmap[]) { 291 | void LiquidCrystal::createChar(uint8_t location, PGM_P ptr) { 292 | location &= 0x7; // we only have 8 locations 0-7 293 | command(LCD_SETCGRAMADDR | (location << 3)); 294 | for (int i=0; i<8; i++) { 295 | //write(charmap[i]); 296 | write(pgm_read_byte(ptr++)); 297 | } 298 | } 299 | 300 | // Turn the (optional) backlight off/on 301 | void LiquidCrystal::noBacklight(void) { 302 | _backlightval=LCD_NOBACKLIGHT; 303 | expanderWrite(0); 304 | } 305 | 306 | void LiquidCrystal::backlight(void) { 307 | _backlightval=LCD_BACKLIGHT; 308 | expanderWrite(0); 309 | } 310 | 311 | /*********** mid level commands, for sending data/cmds */ 312 | 313 | inline void LiquidCrystal::command(uint8_t value) { 314 | send(value, 0); 315 | } 316 | 317 | inline size_t LiquidCrystal::write(uint8_t value) { 318 | send(value, Rs); 319 | return 1; // assume sucess 320 | } 321 | 322 | /************ low level data pushing commands **********/ 323 | 324 | // write either command or data 325 | void LiquidCrystal::send(uint8_t value, uint8_t mode) { 326 | if (_type == LCD_I2C) { 327 | uint8_t highnib=value&0xf0; 328 | uint8_t lownib=(value<<4)&0xf0; 329 | write4bits((highnib)|mode); 330 | write4bits((lownib)|mode); 331 | } 332 | if (_type == LCD_STD) { 333 | digitalWrite(_rs_pin, mode); 334 | 335 | // if there is a RW pin indicated, set it low to Write 336 | if (_rw_pin != 255) { 337 | digitalWrite(_rw_pin, LOW); 338 | } 339 | 340 | write4bits(value>>4); 341 | write4bits(value); 342 | } 343 | } 344 | 345 | void LiquidCrystal::write4bits(uint8_t value) { 346 | if (_type == LCD_I2C) { 347 | expanderWrite(value); 348 | pulseEnable(value); 349 | } 350 | if (_type == LCD_STD) { 351 | for (int i = 0; i < 4; i++) { 352 | pinMode(_data_pins[i], OUTPUT); 353 | digitalWrite(_data_pins[i], (value >> i) & 0x01); 354 | } 355 | 356 | pulseEnable(); 357 | } 358 | } 359 | 360 | void LiquidCrystal::expanderWrite(uint8_t _data){ 361 | Wire.beginTransmission(_addr); 362 | Wire.write((int)(_data) | _backlightval); 363 | Wire.endTransmission(); 364 | } 365 | 366 | void LiquidCrystal::pulseEnable(uint8_t _data){ 367 | expanderWrite(_data | En); // En high 368 | delayMicroseconds(1); // enable pulse must be >450ns 369 | 370 | expanderWrite(_data & ~En); // En low 371 | delayMicroseconds(50); // commands need > 37us to settle 372 | } 373 | 374 | void LiquidCrystal::pulseEnable(void) { 375 | digitalWrite(_enable_pin, LOW); 376 | delayMicroseconds(1); 377 | digitalWrite(_enable_pin, HIGH); 378 | delayMicroseconds(1); // enable pulse must be >450ns 379 | digitalWrite(_enable_pin, LOW); 380 | delayMicroseconds(100); // commands need > 37us to settle 381 | } 382 | 383 | #endif 384 | -------------------------------------------------------------------------------- /gpio.cpp: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2014 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * GPIO functions 5 | * Feb 2015 @ OpenSprinkler.com 6 | * 7 | * This file is part of the OpenSprinkler library 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see 21 | * . 22 | */ 23 | 24 | #include "gpio.h" 25 | 26 | #if defined(ARDUINO) 27 | 28 | #if defined(ESP8266) || defined(ESP32) 29 | 30 | #include 31 | #include "defines.h" 32 | 33 | byte IOEXP::detectType(uint8_t address) { 34 | Wire.beginTransmission(address); 35 | if(Wire.endTransmission()!=0) return IOEXP_TYPE_NONEXIST; // this I2C address does not exist 36 | 37 | Wire.beginTransmission(address); 38 | Wire.write(NXP_INVERT_REG); // ask for polarity register 39 | Wire.endTransmission(); 40 | 41 | if(Wire.requestFrom(address, (uint8_t)2) != 2) return IOEXP_TYPE_UNKNOWN; 42 | uint8_t low = Wire.read(); 43 | uint8_t high = Wire.read(); 44 | if(low==0x00 && high==0x00) { 45 | return IOEXP_TYPE_9555; // PCA9555 has polarity register which inits to 0 46 | } 47 | return IOEXP_TYPE_8575; 48 | } 49 | 50 | void PCA9555::pinMode(uint8_t pin, uint8_t IOMode) { 51 | uint16_t config = i2c_read(NXP_CONFIG_REG); 52 | if(IOMode == OUTPUT) { 53 | config &= ~(1 << pin); // config bit set to 0 for output pin 54 | } else { 55 | config |= (1 << pin); // config bit set to 1 for input pin 56 | } 57 | i2c_write(NXP_CONFIG_REG, config); 58 | } 59 | 60 | uint16_t PCA9555::i2c_read(uint8_t reg) { 61 | if(address==255) return 0xFFFF; 62 | Wire.beginTransmission(address); 63 | Wire.write(reg); 64 | Wire.endTransmission(); 65 | if(Wire.requestFrom(address, (uint8_t)2) != 2) {DEBUG_PRINTLN("PCA9555 GPIO error"); return 0xFFFF;} 66 | uint16_t data0 = Wire.read(); 67 | uint16_t data1 = Wire.read(); 68 | return data0+(data1<<8); 69 | } 70 | 71 | void PCA9555::i2c_write(uint8_t reg, uint16_t v){ 72 | if(address==255) return; 73 | Wire.beginTransmission(address); 74 | Wire.write(reg); 75 | Wire.write(v&0xff); 76 | Wire.write(v>>8); 77 | Wire.endTransmission(); 78 | } 79 | 80 | uint16_t PCF8575::i2c_read(uint8_t reg) { 81 | if(address==255) return 0xFFFF; 82 | Wire.beginTransmission(address); 83 | if(Wire.requestFrom(address, (uint8_t)2) != 2) {DEBUG_PRINTLN("PCF8575 GPIO error"); return 0xFFFF;} 84 | uint16_t data0 = Wire.read(); 85 | uint16_t data1 = Wire.read(); 86 | Wire.endTransmission(); 87 | return data0+(data1<<8); 88 | } 89 | 90 | void PCF8575::i2c_write(uint8_t reg, uint16_t v) { 91 | if(address==255) return; 92 | Wire.beginTransmission(address); 93 | // todo: handle inputmask (not necessary unless if using any pin as input) 94 | Wire.write(v&0xff); 95 | Wire.write(v>>8); 96 | Wire.endTransmission(); 97 | } 98 | 99 | uint16_t PCF8574::i2c_read(uint8_t reg) { 100 | if(address==255) return 0xFFFF; 101 | Wire.beginTransmission(address); 102 | if(Wire.requestFrom(address, (uint8_t)1) != 1) {DEBUG_PRINTLN("PCF8574 GPIO error"); return 0xFFFF;} 103 | uint16_t data = Wire.read(); 104 | DEBUG_PRINT("PCF8574 address read request: "); 105 | DEBUG_PRINT(address); 106 | DEBUG_PRINT(" Data: "); 107 | DEBUG_PRINTLN(data); 108 | Wire.endTransmission(); 109 | return data; 110 | } 111 | 112 | void PCF8574::i2c_write(uint8_t reg, uint16_t v) { 113 | if(address==255) return; 114 | Wire.beginTransmission(address); 115 | Wire.write((uint8_t)(v&0xFF) | inputmask); 116 | Wire.endTransmission(); 117 | } 118 | 119 | #if defined(ESP32) 120 | void BUILD_IN_GPIO::set_pins_output_mode (){ 121 | int i; 122 | for (i=0; i<8; i++) 123 | if ( on_board_gpin_list[i] != 255){ 124 | pinModeExt( on_board_gpin_list[i], OUTPUT); 125 | } 126 | } 127 | 128 | 129 | void BUILD_IN_GPIO::i2c_write(uint16_t v){ 130 | v = (uint8_t)(v&0xFF) | inputmask; 131 | int i; 132 | for (i=0; i<8; i++) 133 | if ( on_board_gpin_list[i] != 255){ 134 | digitalWriteExt( on_board_gpin_list[i], ((v)>>(i)) & 1); 135 | } 136 | } 137 | 138 | 139 | 140 | #endif 141 | 142 | 143 | #include "OpenSprinkler.h" 144 | 145 | extern OpenSprinkler os; 146 | 147 | void pinModeExt(byte pin, byte mode) { 148 | if(pin==255) return; 149 | if(pin>=IOEXP_PIN) { 150 | os.mainio->pinMode(pin-IOEXP_PIN, mode); 151 | } else { 152 | pinMode(pin, mode); 153 | } 154 | } 155 | 156 | void digitalWriteExt(byte pin, byte value) { 157 | if(pin==255) return; 158 | if(pin>=IOEXP_PIN) { 159 | 160 | os.mainio->digitalWrite(pin-IOEXP_PIN, value); 161 | /* 162 | // a pin on IO expander 163 | byte data=pcf_read(MAIN_I2CADDR); 164 | if(value) data|=(1<<(pin-IOEXP_PIN)); 165 | else data&=~(1<<(pin-IOEXP_PIN)); 166 | data |= MAIN_INPUTMASK; // make sure to enforce 1 for input pins 167 | pcf_write(MAIN_I2CADDR, data);*/ 168 | } else { 169 | digitalWrite(pin, value); 170 | } 171 | } 172 | 173 | byte digitalReadExt(byte pin) { 174 | if(pin==255) return HIGH; 175 | if(pin>=IOEXP_PIN) { 176 | return os.mainio->digitalRead(pin-IOEXP_PIN); 177 | // a pin on IO expander 178 | //return pcf_read(MAIN_I2CADDR)&(1<<(pin-IOEXP_PIN)); 179 | } else { 180 | return digitalRead(pin); 181 | } 182 | } 183 | #endif 184 | 185 | #elif defined(OSPI) || defined(OSBO) 186 | 187 | #include 188 | #include 189 | #include 190 | #include 191 | #include 192 | #include 193 | #include 194 | #include 195 | #include 196 | 197 | #define BUFFER_MAX 64 198 | #define GPIO_MAX 64 199 | 200 | // GPIO file descriptors 201 | static int sysFds[GPIO_MAX] = { 202 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 203 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 204 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 205 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 206 | } ; 207 | 208 | // Interrupt service routine functions 209 | static void (*isrFunctions [GPIO_MAX])(void); 210 | 211 | static volatile int pinPass = -1 ; 212 | static pthread_mutex_t pinMutex ; 213 | 214 | /** Export gpio pin */ 215 | static byte GPIOExport(int pin) { 216 | char buffer[BUFFER_MAX]; 217 | int fd, len; 218 | 219 | fd = open("/sys/class/gpio/export", O_WRONLY); 220 | if (fd < 0) { 221 | DEBUG_PRINTLN("failed to open export for writing"); 222 | return 0; 223 | } 224 | 225 | len = snprintf(buffer, sizeof(buffer), "%d", pin); 226 | write(fd, buffer, len); 227 | close(fd); 228 | return 1; 229 | } 230 | 231 | /** Unexport gpio pin */ 232 | static byte GPIOUnexport(int pin) { 233 | char buffer[BUFFER_MAX]; 234 | int fd, len; 235 | 236 | fd = open("/sys/class/gpio/unexport", O_WRONLY); 237 | if (fd < 0) { 238 | DEBUG_PRINTLN("failed to open unexport for writing"); 239 | return 0; 240 | } 241 | 242 | len = snprintf(buffer, sizeof(buffer), "%d", pin); 243 | write(fd, buffer, len); 244 | close(fd); 245 | return 1; 246 | } 247 | 248 | /** Set interrupt edge mode */ 249 | static byte GPIOSetEdge(int pin, const char *edge) { 250 | char path[BUFFER_MAX]; 251 | int fd, len; 252 | 253 | snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/edge", pin); 254 | 255 | fd = open(path, O_WRONLY); 256 | if (fd < 0) { 257 | DEBUG_PRINTLN("failed to open gpio edge for writing"); 258 | return 0; 259 | } 260 | write(fd, edge, strlen(edge)+1); 261 | close(fd); 262 | return 1; 263 | } 264 | 265 | /** Set pin mode, in or out */ 266 | void pinMode(int pin, byte mode) { 267 | static const char dir_str[] = "in\0out"; 268 | 269 | char path[BUFFER_MAX]; 270 | int fd; 271 | 272 | snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/direction", pin); 273 | 274 | struct stat st; 275 | if(stat(path, &st)) { 276 | if (!GPIOExport(pin)) return; 277 | } 278 | 279 | fd = open(path, O_WRONLY); 280 | if (fd < 0) { 281 | DEBUG_PRINTLN("failed to open gpio direction for writing"); 282 | return; 283 | } 284 | 285 | if (-1 == write(fd, &dir_str[INPUT==mode?0:3], INPUT==mode?2:3)) { 286 | DEBUG_PRINTLN("failed to set direction"); 287 | return; 288 | } 289 | 290 | close(fd); 291 | return; 292 | } 293 | 294 | /** Open file for digital pin */ 295 | int gpio_fd_open(int pin, int mode) { 296 | char path[BUFFER_MAX]; 297 | int fd; 298 | 299 | snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); 300 | fd = open(path, mode); 301 | if (fd < 0) { 302 | DEBUG_PRINTLN("failed to open gpio"); 303 | return -1; 304 | } 305 | return fd; 306 | } 307 | 308 | /** Close file */ 309 | void gpio_fd_close(int fd) { 310 | close(fd); 311 | } 312 | 313 | /** Read digital value */ 314 | byte digitalRead(int pin) { 315 | char value_str[3]; 316 | 317 | int fd = gpio_fd_open(pin, O_RDONLY); 318 | if (fd < 0) { 319 | return 0; 320 | } 321 | 322 | if (read(fd, value_str, 3) < 0) { 323 | DEBUG_PRINTLN("failed to read value"); 324 | return 0; 325 | } 326 | 327 | close(fd); 328 | return atoi(value_str); 329 | } 330 | 331 | /** Write digital value given file descriptor */ 332 | void gpio_write(int fd, byte value) { 333 | static const char value_str[] = "01"; 334 | 335 | if (1 != write(fd, &value_str[LOW==value?0:1], 1)) { 336 | DEBUG_PRINT("failed to write value on pin "); 337 | } 338 | } 339 | 340 | /** Write digital value */ 341 | void digitalWrite(int pin, byte value) { 342 | int fd = gpio_fd_open(pin); 343 | if (fd < 0) { 344 | return; 345 | } 346 | gpio_write(fd, value); 347 | close(fd); 348 | } 349 | 350 | static int HiPri (const int pri) { 351 | struct sched_param sched ; 352 | 353 | memset (&sched, 0, sizeof(sched)) ; 354 | 355 | if (pri > sched_get_priority_max (SCHED_RR)) 356 | sched.sched_priority = sched_get_priority_max (SCHED_RR) ; 357 | else 358 | sched.sched_priority = pri ; 359 | 360 | return sched_setscheduler (0, SCHED_RR, &sched) ; 361 | } 362 | 363 | static int waitForInterrupt (int pin, int mS) 364 | { 365 | int fd, x ; 366 | uint8_t c ; 367 | struct pollfd polls ; 368 | 369 | if((fd=sysFds[pin]) < 0) 370 | return -2; 371 | 372 | polls.fd = fd ; 373 | polls.events = POLLPRI ; // Urgent data! 374 | 375 | x = poll (&polls, 1, mS) ; 376 | // Do a dummy read to clear the interrupt 377 | // A one character read appars to be enough. 378 | // Followed by a seek to reset it. 379 | 380 | (void)read (fd, &c, 1); 381 | lseek (fd, 0, SEEK_SET); 382 | 383 | return x ; 384 | } 385 | 386 | static void *interruptHandler (void *arg) { 387 | int myPin ; 388 | 389 | (void) HiPri (55) ; // Only effective if we run as root 390 | 391 | myPin = pinPass ; 392 | pinPass = -1 ; 393 | 394 | for (;;) 395 | if (waitForInterrupt (myPin, -1) > 0) 396 | isrFunctions[myPin]() ; 397 | 398 | return NULL ; 399 | } 400 | 401 | #include "utils.h" 402 | /** Attach an interrupt function to pin */ 403 | void attachInterrupt(int pin, const char* mode, void (*isr)(void)) { 404 | if((pin<0)||(pin>GPIO_MAX)) { 405 | DEBUG_PRINTLN("pin out of range"); 406 | return; 407 | } 408 | 409 | // set pin to INPUT mode and set interrupt edge mode 410 | pinMode(pin, INPUT); 411 | GPIOSetEdge(pin, mode); 412 | 413 | char path[BUFFER_MAX]; 414 | snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); 415 | int fd; 416 | 417 | // open gpio file 418 | if(sysFds[pin]==-1) { 419 | if((sysFds[pin]=open(path, O_RDWR))<0) { 420 | DEBUG_PRINTLN("failed to open gpio value for reading"); 421 | return; 422 | } 423 | } 424 | 425 | int count, i; 426 | char c; 427 | // clear any pending interrupts 428 | ioctl (sysFds[pin], FIONREAD, &count) ; 429 | for (i=0; i. 22 | */ 23 | 24 | 25 | #ifndef _OPENSPRINKLER_H 26 | #define _OPENSPRINKLER_H 27 | 28 | #include "defines.h" 29 | #include "utils.h" 30 | #include "gpio.h" 31 | #include "images.h" 32 | 33 | #if defined(ARDUINO) // headers for ESP8266 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include "I2CRTC.h" 39 | 40 | #if defined(ESP8266) || defined(ESP32) 41 | #include 42 | #include 43 | #include "SSD1306Display.h" 44 | #include "espconnect.h" 45 | #else 46 | #include 47 | #include "LiquidCrystal.h" 48 | #endif 49 | #if defined(ESP32) 50 | #include 51 | #endif 52 | #else // headers for RPI/BBB/LINUX 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include "etherport.h" 59 | #endif // end of headers 60 | 61 | /** Non-volatile data structure */ 62 | struct NVConData { 63 | uint16_t sunrise_time; // sunrise time (in minutes) 64 | uint16_t sunset_time; // sunset time (in minutes) 65 | uint32_t rd_stop_time; // rain delay stop time 66 | uint32_t external_ip; // external ip 67 | uint8_t reboot_cause; // reboot cause 68 | }; 69 | 70 | struct StationAttrib { // station attributes 71 | byte mas:1; 72 | byte igs:1; // ignore sensor 1 73 | byte mas2:1; 74 | byte dis:1; 75 | byte seq:1; 76 | byte igs2:1;// ignore sensor 2 77 | byte igrd:1;// ignore rain delay 78 | byte unused:1; 79 | 80 | byte gid:4; // group id: reserved for the future 81 | byte dummy:4; 82 | byte reserved[2]; // reserved bytes for the future 83 | }; // total is 4 bytes so far 84 | 85 | /** Station data structure */ 86 | struct StationData { 87 | char name[STATION_NAME_SIZE]; 88 | StationAttrib attrib; 89 | byte type; // station type 90 | byte sped[STATION_SPECIAL_DATA_SIZE]; // special station data 91 | }; 92 | 93 | /** RF station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ 94 | struct RFStationData { 95 | byte on[6]; 96 | byte off[6]; 97 | byte timing[4]; 98 | }; 99 | 100 | /** Remote station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ 101 | struct RemoteStationData { 102 | byte ip[8]; 103 | byte port[4]; 104 | byte sid[2]; 105 | }; 106 | 107 | /** GPIO station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ 108 | struct GPIOStationData { 109 | byte pin[2]; 110 | byte active; 111 | }; 112 | 113 | /** HTTP station data structures - Must fit in STATION_SPECIAL_DATA_SIZE */ 114 | struct HTTPStationData { 115 | byte data[STATION_SPECIAL_DATA_SIZE]; 116 | }; 117 | 118 | /** Volatile controller status bits */ 119 | struct ConStatus { 120 | byte enabled:1; // operation enable (when set, controller operation is enabled) 121 | byte rain_delayed:1; // rain delay bit (when set, rain delay is applied) 122 | byte sensor1:1; // sensor1 status bit (when set, sensor1 on is detected) 123 | byte program_busy:1; // HIGH means a program is being executed currently 124 | byte has_curr_sense:1; // HIGH means the controller has a current sensing pin 125 | byte safe_reboot:1; // HIGH means a safe reboot has been marked 126 | byte req_ntpsync:1; // request ntpsync 127 | byte req_network:1; // request check network 128 | byte display_board:5; // the board that is being displayed onto the lcd 129 | byte network_fails:3; // number of network fails 130 | byte mas:8; // master station index 131 | byte mas2:8; // master2 station index 132 | byte sensor2:1; // sensor2 status bit (when set, sensor2 on is detected) 133 | byte sensor1_active:1; // sensor1 active bit (when set, sensor1 is activated) 134 | byte sensor2_active:1; // sensor2 active bit (when set, sensor2 is activated) 135 | }; 136 | 137 | extern const char iopt_json_names[]; 138 | extern const uint8_t iopt_max[]; 139 | 140 | class OpenSprinkler { 141 | public: 142 | 143 | // data members 144 | #if defined(ESP8266) || defined(ESP32) 145 | static SSD1306Display lcd; // 128x64 OLED display 146 | #elif defined(ARDUINO) 147 | static LiquidCrystal lcd; // 16x2 character LCD 148 | #else 149 | // todo: LCD define for RPI/BBB 150 | #endif 151 | 152 | #if defined(OSPI) 153 | static byte pin_sr_data; // RPi shift register data pin 154 | // to handle RPi rev. 1 155 | #endif 156 | 157 | static NVConData nvdata; 158 | static ConStatus status; 159 | static ConStatus old_status; 160 | static byte nboards, nstations; 161 | static byte hw_type; // hardware type 162 | static byte hw_rev; // hardware minor 163 | 164 | static byte iopts[]; // integer options 165 | static const char*sopts[]; // string options 166 | static byte station_bits[]; // station activation bits. each byte corresponds to a board (8 stations) 167 | // first byte-> master controller, second byte-> ext. board 1, and so on 168 | // todo future: the following attribute bytes are for backward compatibility 169 | static byte attrib_mas[]; 170 | static byte attrib_igs[]; 171 | static byte attrib_mas2[]; 172 | static byte attrib_igs2[]; 173 | static byte attrib_igrd[]; 174 | static byte attrib_dis[]; 175 | static byte attrib_seq[]; 176 | static byte attrib_spe[]; 177 | 178 | // variables for time keeping 179 | static ulong sensor1_on_timer; // time when sensor1 is detected on last time 180 | static ulong sensor1_off_timer; // time when sensor1 is detected off last time 181 | static ulong sensor1_active_lasttime; // most recent time sensor1 is activated 182 | static ulong sensor2_on_timer; // time when sensor2 is detected on last time 183 | static ulong sensor2_off_timer; // time when sensor2 is detected off last time 184 | static ulong sensor2_active_lasttime; // most recent time sensor1 is activated 185 | static ulong raindelay_on_lasttime; // time when the most recent rain delay started 186 | static ulong flowcount_rt; // flow count (for computing real-time flow rate) 187 | static ulong flowcount_log_start; // starting flow count (for logging) 188 | 189 | static byte button_timeout; // button timeout 190 | static ulong checkwt_lasttime; // time when weather was checked 191 | static ulong checkwt_success_lasttime; // time when weather check was successful 192 | static ulong powerup_lasttime; // time when controller is powered up most recently 193 | static uint8_t last_reboot_cause; // last reboot cause 194 | static byte weather_update_flag; 195 | // member functions 196 | // -- setup 197 | static void update_dev(); // update software for Linux instances 198 | static void reboot_dev(uint8_t); // reboot the microcontroller 199 | static void begin(); // initialization, must call this function before calling other functions 200 | static byte start_network(); // initialize network with the given mac and port 201 | static byte start_ether(); // initialize ethernet with the given mac and port 202 | #if defined(ARDUINO) 203 | static bool load_hardware_mac(byte* buffer, bool wired=false); // read hardware mac address 204 | #endif 205 | static time_t now_tz(); 206 | // -- station names and attributes 207 | static void get_station_data(byte sid, StationData* data); // get station data 208 | static void set_station_data(byte sid, StationData* data); // set station data 209 | static void get_station_name(byte sid, char buf[]); // get station name 210 | static void set_station_name(byte sid, char buf[]); // set station name 211 | static byte get_station_type(byte sid); // get station type 212 | //static StationAttrib get_station_attrib(byte sid); // get station attribute 213 | static void attribs_save(); // repackage attrib bits and save (backward compatibility) 214 | static void attribs_load(); // load and repackage attrib bits (backward compatibility) 215 | static uint16_t parse_rfstation_code(RFStationData *data, ulong *on, ulong *off); // parse rf code into on/off/time sections 216 | static void switch_rfstation(RFStationData *data, bool turnon); // switch rf station 217 | static void switch_remotestation(RemoteStationData *data, bool turnon); // switch remote station 218 | static void switch_gpiostation(GPIOStationData *data, bool turnon); // switch gpio station 219 | static void switch_httpstation(HTTPStationData *data, bool turnon); // switch http station 220 | 221 | // -- options and data storeage 222 | static void nvdata_load(); 223 | static void nvdata_save(); 224 | 225 | static void options_setup(); 226 | static void iopts_load(); 227 | static void iopts_save(); 228 | static bool sopt_save(byte oid, const char *buf); 229 | static void sopt_load(byte oid, char *buf); 230 | static String sopt_load(byte oid); 231 | 232 | static byte password_verify(char *pw); // verify password 233 | 234 | // -- controller operation 235 | static void enable(); // enable controller operation 236 | static void disable(); // disable controller operation, all stations will be closed immediately 237 | static void raindelay_start(); // start raindelay 238 | static void raindelay_stop(); // stop rain delay 239 | static void detect_binarysensor_status(ulong);// update binary (rain, soil) sensor status 240 | static byte detect_programswitch_status(ulong); // get program switch status 241 | static void sensor_resetall(); 242 | 243 | static uint16_t read_current(); // read current sensing value 244 | static uint16_t baseline_current; // resting state current 245 | 246 | static int detect_exp(); // detect the number of expansion boards 247 | static byte weekday_today(); // returns index of today's weekday (Monday is 0) 248 | 249 | static byte set_station_bit(byte sid, byte value); // set station bit of one station (sid->station index, value->0/1) 250 | static void switch_special_station(byte sid, byte value); // swtich special station 251 | static void clear_all_station_bits(); // clear all station bits 252 | static void apply_all_station_bits(); // apply all station bits (activate/deactive values) 253 | 254 | static int8_t send_http_request(uint32_t ip4, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); 255 | static int8_t send_http_request(const char* server, uint16_t port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); 256 | static int8_t send_http_request(char* server_with_port, char* p, void(*callback)(char*)=NULL, uint16_t timeout=3000); 257 | // -- LCD functions 258 | #if defined(ARDUINO) // LCD functions for Arduino 259 | #if defined(ESP8266) || defined(ESP32) 260 | static void lcd_print_pgm(PGM_P str); // ESP8266 does not allow PGM_P followed by PROGMEM 261 | static void lcd_print_line_clear_pgm(PGM_P str, byte line); 262 | #else 263 | static void lcd_print_pgm(PGM_P PROGMEM str); // print a program memory string 264 | static void lcd_print_line_clear_pgm(PGM_P PROGMEM str, byte line); 265 | #endif 266 | static void lcd_print_time(time_t t); // print current time 267 | static void lcd_print_ip(const byte *ip, byte endian); // print ip 268 | static void lcd_print_mac(const byte *mac); // print mac 269 | static void lcd_print_station(byte line, char c); // print station bits of the board selected by display_board 270 | static void lcd_print_version(byte v); // print version number 271 | 272 | // -- UI and buttons 273 | static byte button_read(byte waitmode); // Read button value. options for 'waitmodes' are: 274 | // BUTTON_WAIT_NONE, BUTTON_WAIT_RELEASE, BUTTON_WAIT_HOLD 275 | // return values are 'OR'ed with flags 276 | // check defines.h for details 277 | 278 | // -- UI functions -- 279 | static void ui_set_options(int oid); // ui for setting options (oid-> starting option index) 280 | static void lcd_set_brightness(byte value=1); 281 | static void lcd_set_contrast(); 282 | 283 | #if defined(ESP8266) || defined(ESP32) 284 | static IOEXP *mainio, *drio; 285 | static IOEXP *expanders[]; 286 | static RCSwitch rfswitch; 287 | static void detect_expanders(); 288 | static void flash_screen(); 289 | static void toggle_screen_led(); 290 | static void set_screen_led(byte status); 291 | static byte get_wifi_mode() {return wifi_testmode ? WIFI_M_STA : iopts[IOPT_WIFI_MODE];} 292 | static byte wifi_testmode; 293 | static String wifi_ssid, wifi_pass; 294 | static void config_ip(); 295 | static void save_wifi_ip(); 296 | static void reset_to_ap(); 297 | static byte state; 298 | #endif 299 | 300 | private: 301 | static void lcd_print_option(int i); // print an option to the lcd 302 | static void lcd_print_2digit(int v); // print a integer in 2 digits 303 | static void lcd_start(); 304 | static byte button_read_busy(byte pin_butt, byte waitmode, byte butt, byte is_holding); 305 | 306 | #if defined(ESP8266) || defined(ESP32) 307 | static void latch_boost(); 308 | static void latch_open(byte sid); 309 | static void latch_close(byte sid); 310 | static void latch_setzonepin(byte sid, byte value); 311 | static void latch_setallzonepins(byte value); 312 | static void latch_apply_all_station_bits(); 313 | static byte prev_station_bits[]; 314 | #endif 315 | #endif // LCD functions 316 | static byte engage_booster; 317 | }; 318 | 319 | // todo 320 | #if defined(ARDUINO) 321 | extern EthernetServer *m_server; 322 | extern EthernetClient *m_client; 323 | extern EthernetUDP *Udp; 324 | #if defined(ESP8266) 325 | extern ESP8266WebServer *wifi_server; 326 | #elif defined(ESP32) 327 | extern WebServer *wifi_server; 328 | #endif 329 | #else 330 | extern EthernetServer *m_server; 331 | #endif 332 | 333 | #endif // _OPENSPRINKLER_H 334 | -------------------------------------------------------------------------------- /utils.cpp: -------------------------------------------------------------------------------- 1 | /* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware 2 | * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) 3 | * 4 | * Utility functions 5 | * Feb 2015 @ OpenSprinkler.com 6 | * 7 | * This file is part of the OpenSprinkler library 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU General Public License 20 | * along with this program. If not, see 21 | * . 22 | */ 23 | 24 | #include "utils.h" 25 | #include "OpenSprinkler.h" 26 | 27 | extern OpenSprinkler os; 28 | 29 | #if defined(ARDUINO) // Arduino 30 | 31 | #if defined(ESP8266) || defined(ESP32) 32 | #include "FS.h" 33 | #else 34 | #include 35 | #include "SdFat.h" 36 | extern SdFat sd; 37 | #endif 38 | 39 | #else // RPI/BBB 40 | 41 | char* get_runtime_path() { 42 | static char path[PATH_MAX]; 43 | static byte query = 1; 44 | 45 | #ifdef __APPLE__ 46 | strcpy(path, "./"); 47 | return path; 48 | #endif 49 | 50 | if(query) { 51 | if(readlink("/proc/self/exe", path, PATH_MAX ) <= 0) { 52 | return NULL; 53 | } 54 | char* path_end = strrchr(path, '/'); 55 | if(path_end == NULL) { 56 | return NULL; 57 | } 58 | path_end++; 59 | *path_end=0; 60 | query = 0; 61 | } 62 | return path; 63 | } 64 | 65 | char* get_filename_fullpath(const char *filename) { 66 | static char fullpath[PATH_MAX]; 67 | strcpy(fullpath, get_runtime_path()); 68 | strcat(fullpath, filename); 69 | return fullpath; 70 | } 71 | 72 | void delay(ulong howLong) 73 | { 74 | struct timespec sleeper, dummy ; 75 | 76 | sleeper.tv_sec = (time_t)(howLong / 1000) ; 77 | sleeper.tv_nsec = (long)(howLong % 1000) * 1000000 ; 78 | 79 | nanosleep (&sleeper, &dummy) ; 80 | } 81 | 82 | void delayMicrosecondsHard (ulong howLong) 83 | { 84 | struct timeval tNow, tLong, tEnd ; 85 | 86 | gettimeofday (&tNow, NULL) ; 87 | tLong.tv_sec = howLong / 1000000 ; 88 | tLong.tv_usec = howLong % 1000000 ; 89 | timeradd (&tNow, &tLong, &tEnd) ; 90 | 91 | while (timercmp (&tNow, &tEnd, <)) 92 | gettimeofday (&tNow, NULL) ; 93 | } 94 | 95 | void delayMicroseconds (ulong howLong) 96 | { 97 | struct timespec sleeper ; 98 | unsigned int uSecs = howLong % 1000000 ; 99 | unsigned int wSecs = howLong / 1000000 ; 100 | 101 | /**/ if (howLong == 0) 102 | return ; 103 | else if (howLong < 100) 104 | delayMicrosecondsHard (howLong) ; 105 | else 106 | { 107 | sleeper.tv_sec = wSecs ; 108 | sleeper.tv_nsec = (long)(uSecs * 1000L) ; 109 | nanosleep (&sleeper, NULL) ; 110 | } 111 | } 112 | 113 | static uint64_t epochMilli, epochMicro ; 114 | 115 | void initialiseEpoch() 116 | { 117 | struct timeval tv ; 118 | 119 | gettimeofday (&tv, NULL) ; 120 | epochMilli = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; 121 | epochMicro = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)(tv.tv_usec) ; 122 | } 123 | 124 | ulong millis (void) 125 | { 126 | struct timeval tv ; 127 | uint64_t now ; 128 | 129 | gettimeofday (&tv, NULL) ; 130 | now = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; 131 | 132 | return (ulong)(now - epochMilli) ; 133 | } 134 | 135 | ulong micros (void) 136 | { 137 | struct timeval tv ; 138 | uint64_t now ; 139 | 140 | gettimeofday (&tv, NULL) ; 141 | now = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)tv.tv_usec ; 142 | 143 | return (ulong)(now - epochMicro) ; 144 | } 145 | 146 | #if defined(OSPI) 147 | unsigned int detect_rpi_rev() { 148 | FILE * filp; 149 | unsigned int rev; 150 | char buf[512]; 151 | char term; 152 | 153 | rev = 0; 154 | filp = fopen ("/proc/cpuinfo", "r"); 155 | 156 | if (filp != NULL) { 157 | while (fgets(buf, sizeof(buf), filp) != NULL) { 158 | if (!strncasecmp("revision\t", buf, 9)) { 159 | if (sscanf(buf+strlen(buf)-5, "%x%c", &rev, &term) == 2) { 160 | if (term == '\n') break; 161 | rev = 0; 162 | } 163 | } 164 | } 165 | fclose(filp); 166 | } 167 | return rev; 168 | } 169 | #endif 170 | 171 | #endif 172 | 173 | void write_to_file(const char *fn, const char *data, ulong size, ulong pos, bool trunc) { 174 | 175 | #if defined(ESP8266) 176 | 177 | File f; 178 | if(trunc) { 179 | f = SPIFFS.open(fn, "w"); 180 | } else { 181 | f = SPIFFS.open(fn, "r+"); 182 | if(!f) f = SPIFFS.open(fn, "w"); 183 | } 184 | if(!f) return; 185 | if(pos) f.seek(pos, SeekSet); 186 | if(size==0) { 187 | f.write((byte*)" ", 1); // hack to circumvent SPIFFS bug involving writing empty file 188 | } else { 189 | f.write((byte*)data, size); 190 | } 191 | f.close(); 192 | 193 | #elif defined(ESP32) 194 | 195 | DEBUG_PRINT("write_to_file() "); DEBUG_PRINTLN(fn); 196 | File f; 197 | if(trunc) { 198 | f = SPIFFS.open(fn, "w"); 199 | } else { 200 | f = SPIFFS.open(fn, "r+"); 201 | if(!f) f = SPIFFS.open(fn, "w"); 202 | } 203 | if(!f) return; 204 | f.seek(0, SeekSet); 205 | if(pos) f.seek(pos, SeekSet); 206 | if(size==0) { 207 | f.write((byte*)" ", 1); // hack to circumvent SPIFFS bug involving writing empty file 208 | } else { 209 | f.write((byte*)data, size); 210 | } 211 | f.close(); 212 | 213 | 214 | #elif defined(ARDUINO) 215 | 216 | sd.chdir("/"); 217 | SdFile file; 218 | int flag = O_CREAT | O_RDWR; 219 | if(trunc) flag |= O_TRUNC; 220 | int ret = file.open(fn, flag); 221 | if(!ret) return; 222 | file.seekSet(pos); 223 | file.write(data, size); 224 | file.close(); 225 | 226 | #else 227 | 228 | FILE *file; 229 | if(trunc) { 230 | file = fopen(get_filename_fullpath(fn), "wb"); 231 | } else { 232 | file = fopen(get_filename_fullpath(fn), "r+b"); 233 | if(!file) file = fopen(get_filename_fullpath(fn), "wb"); 234 | } 235 | if(!file) return; 236 | fseek(file, pos, SEEK_SET); 237 | fwrite(data, 1, size, file); 238 | fclose(file); 239 | 240 | #endif 241 | } 242 | 243 | void read_from_file(const char *fn, char *data, ulong maxsize, ulong pos) { 244 | #if defined(ESP8266) 245 | 246 | File f = SPIFFS.open(fn, "r"); 247 | if(!f) { 248 | data[0]=0; 249 | return; // return with empty string 250 | } 251 | if(pos) f.seek(pos, SeekSet); 252 | int len = f.read((byte*)data, maxsize); 253 | if(len>0) data[len]=0; 254 | if(len==1 && data[0]==' ') data[0] = 0; // hack to circumvent SPIFFS bug involving writing empty file 255 | data[maxsize-1]=0; 256 | f.close(); 257 | return; 258 | 259 | #elif defined(ESP32) 260 | 261 | DEBUG_PRINT("read_from_file() "); DEBUG_PRINTLN(fn); 262 | File f = SPIFFS.open(fn, "r"); 263 | if(!f) { 264 | data[0]=0; 265 | return; // return with empty string 266 | } 267 | f.seek(0, SeekSet); 268 | if(pos) f.seek(pos, SeekSet); 269 | int len = f.read((byte*)data, maxsize); 270 | if(len>0) data[len]=0; 271 | if(len==1 && data[0]==' ') data[0] = 0; // hack to circumvent SPIFFS bug involving writing empty file 272 | data[maxsize-1]=0; 273 | f.close(); 274 | return; 275 | 276 | #elif defined(ARDUINO) 277 | 278 | sd.chdir("/"); 279 | SdFile file; 280 | int ret = file.open(fn, O_READ); 281 | if(!ret) { 282 | data[0]=0; 283 | return; // return with empty string 284 | } 285 | file.seekSet(pos); 286 | ret = file.fgets(data, maxsize); 287 | data[maxsize-1]=0; 288 | file.close(); 289 | return; 290 | 291 | #else 292 | 293 | FILE *file; 294 | file = fopen(get_filename_fullpath(fn), "rb"); 295 | if(!file) { 296 | data[0] = 0; 297 | return; 298 | } 299 | 300 | int res; 301 | fseek(file, pos, SEEK_SET); 302 | if(fgets(data, maxsize, file)) { 303 | res = strlen(data); 304 | } else { 305 | res = 0; 306 | } 307 | if (res <= 0) { 308 | data[0] = 0; 309 | } 310 | 311 | data[maxsize-1]=0; 312 | fclose(file); 313 | return; 314 | 315 | #endif 316 | } 317 | 318 | void remove_file(const char *fn) { 319 | #if defined(ESP8266) || defined(ESP32) 320 | 321 | if(!SPIFFS.exists(fn)) return; 322 | SPIFFS.remove(fn); 323 | 324 | #elif defined(ARDUINO) 325 | 326 | sd.chdir("/"); 327 | if (!sd.exists(fn)) return; 328 | sd.remove(fn); 329 | 330 | #else 331 | 332 | remove(get_filename_fullpath(fn)); 333 | 334 | #endif 335 | } 336 | 337 | bool file_exists(const char *fn) { 338 | #if defined(ESP8266) || defined(ESP32) 339 | 340 | return SPIFFS.exists(fn); 341 | 342 | #elif defined(ARDUINO) 343 | 344 | sd.chdir("/"); 345 | return sd.exists(fn); 346 | 347 | #else 348 | 349 | FILE *file; 350 | file = fopen(get_filename_fullpath(fn), "rb"); 351 | if(file) {fclose(file); return true;} 352 | else {return false;} 353 | 354 | #endif 355 | } 356 | 357 | // file functions 358 | void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { 359 | #if defined(ESP8266) 360 | 361 | // do not use File.readBytes or readBytesUntil because it's very slow 362 | File f = SPIFFS.open(fn, "r"); 363 | if(f) { 364 | f.seek(pos, SeekSet); 365 | f.read((byte*)dst, len); 366 | f.close(); 367 | } 368 | #elif defined(ESP32) 369 | 370 | File f = SPIFFS.open(fn, "r"); 371 | if(f) { 372 | f.seek(0, SeekSet); 373 | f.seek(pos, SeekSet); 374 | f.read((byte*)dst, len); 375 | f.close(); 376 | } 377 | 378 | #elif defined(ARDUINO) 379 | 380 | sd.chdir("/"); 381 | SdFile file; 382 | if(file.open(fn, O_READ)) { 383 | file.seekSet(pos); 384 | file.read(dst, len); 385 | file.close(); 386 | } 387 | 388 | #else 389 | 390 | FILE *fp = fopen(get_filename_fullpath(fn), "rb"); 391 | if(fp) { 392 | fseek(fp, pos, SEEK_SET); 393 | fread(dst, 1, len, fp); 394 | fclose(fp); 395 | } 396 | 397 | #endif 398 | } 399 | 400 | void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { 401 | #if defined(ESP8266) 402 | 403 | 404 | File f = SPIFFS.open(fn, "r+"); 405 | if(!f) f = SPIFFS.open(fn, "w"); 406 | if(f) { 407 | f.seek(pos, SeekSet); 408 | f.write((byte*)src, len); 409 | f.close(); 410 | } 411 | 412 | #elif defined(ESP32) 413 | 414 | // DEBUG_PRINT("file_write_block() "); DEBUG_PRINTLN(fn); 415 | File f; 416 | 417 | if(SPIFFS.exists(fn)) 418 | f = SPIFFS.open(fn, "r+"); 419 | else 420 | f = SPIFFS.open(fn, "w"); 421 | 422 | if(f) { 423 | f.seek(0,SeekSet); 424 | f.seek(pos, SeekSet); 425 | f.write((byte*)src, len); 426 | f.close(); 427 | } 428 | 429 | #elif defined(ARDUINO) 430 | 431 | sd.chdir("/"); 432 | SdFile file; 433 | int ret = file.open(fn, O_CREAT | O_RDWR); 434 | if(!ret) return; 435 | file.seekSet(pos); 436 | file.write(src, len); 437 | file.close(); 438 | 439 | #else 440 | 441 | FILE *fp = fopen(get_filename_fullpath(fn), "rb+"); 442 | if(!fp) { 443 | fp = fopen(get_filename_fullpath(fn), "wb+"); 444 | } 445 | if(fp) { 446 | fseek(fp, pos, SEEK_SET); //this fails silently without the above change 447 | fwrite(src, 1, len, fp); 448 | fclose(fp); 449 | } 450 | 451 | #endif 452 | 453 | } 454 | 455 | void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) { 456 | // assume tmp buffer is provided and is larger than len 457 | // todo future: if tmp buffer is not provided, do byte-to-byte copy 458 | if(tmp==NULL) { return; } 459 | #if defined(ESP8266) 460 | 461 | File f = SPIFFS.open(fn, "r+"); 462 | if(!f) return; 463 | f.seek(from, SeekSet); 464 | f.read((byte*)tmp, len); 465 | f.seek(to, SeekSet); 466 | f.write((byte*)tmp, len); 467 | f.close(); 468 | 469 | #elif defined(ESP32) 470 | 471 | File f = SPIFFS.open(fn, "r+"); 472 | if(!f) return; 473 | f.seek(0,SeekSet); 474 | f.seek(from, SeekSet); 475 | f.read((byte*)tmp, len); 476 | f.seek(to, SeekSet); 477 | f.write((byte*)tmp, len); 478 | f.close(); 479 | 480 | #elif defined(ARDUINO) 481 | 482 | sd.chdir("/"); 483 | SdFile file; 484 | int ret = file.open(fn, O_RDWR); 485 | if(!ret) return; 486 | file.seekSet(from); 487 | file.read(tmp, len); 488 | file.seekSet(to); 489 | file.write(tmp, len); 490 | file.close(); 491 | 492 | #else 493 | 494 | FILE *fp = fopen(get_filename_fullpath(fn), "rb+"); 495 | if(!fp) return; 496 | fseek(fp, from, SEEK_SET); 497 | fread(tmp, 1, len, fp); 498 | fseek(fp, to, SEEK_SET); 499 | fwrite(tmp, 1, len, fp); 500 | fclose(fp); 501 | 502 | #endif 503 | 504 | } 505 | 506 | // compare a block of content 507 | byte file_cmp_block(const char *fn, const char *buf, ulong pos) { 508 | char c; 509 | #if defined(ESP8266) 510 | 511 | File f = SPIFFS.open(fn, "r"); 512 | if(f) { 513 | f.seek(pos, SeekSet); 514 | char c = f.read(); 515 | while(*buf && (c==*buf)) { 516 | buf++; 517 | c=f.read(); 518 | } 519 | f.close(); 520 | return (*buf==c)?0:1; 521 | } 522 | 523 | #elif defined(ESP32) 524 | 525 | File f = SPIFFS.open(fn, "r"); 526 | if(f) { 527 | f.seek(0, SeekSet); 528 | f.seek(pos, SeekSet); 529 | char c = f.read(); 530 | while(*buf && (c==*buf)) { 531 | buf++; 532 | c=f.read(); 533 | } 534 | f.close(); 535 | return (*buf==c)?0:1; 536 | } 537 | 538 | #elif defined(ARDUINO) 539 | 540 | sd.chdir("/"); 541 | SdFile file; 542 | if(file.open(fn, O_READ)) { 543 | file.seekSet(pos); 544 | char c = file.read(); 545 | while(*buf && (c==*buf)) { 546 | buf++; 547 | c=file.read(); 548 | } 549 | file.close(); 550 | return (*buf==c)?0:1; 551 | } 552 | 553 | #else 554 | 555 | FILE *fp = fopen(get_filename_fullpath(fn), "rb"); 556 | if(fp) { 557 | fseek(fp, pos, SEEK_SET); 558 | char c = fgetc(fp); 559 | while(*buf && (c==*buf)) { 560 | buf++; 561 | c=fgetc(fp); 562 | } 563 | fclose(fp); 564 | return (*buf==c)?0:1; 565 | } 566 | 567 | #endif 568 | return 1; 569 | } 570 | 571 | byte file_read_byte(const char *fn, ulong pos) { 572 | 573 | // DEBUG_PRINT("file_read_byte() "); DEBUG_PRINTLN(fn); 574 | byte v = 0; 575 | file_read_block(fn, &v, pos, 1); 576 | return v; 577 | } 578 | 579 | void file_write_byte(const char *fn, ulong pos, byte v) { 580 | DEBUG_PRINT("file_write_byte() "); DEBUG_PRINTLN(fn); 581 | file_write_block(fn, &v, pos, 1); 582 | } 583 | 584 | // copy n-character string from program memory with ending 0 585 | void strncpy_P0(char* dest, const char* src, int n) { 586 | byte i; 587 | for(i=0;i600)?600:i; 613 | i=(i<-600)?-600:i; 614 | return (i+600)/5; 615 | } 616 | 617 | // decode a 8-bit unsigned byte (0 to 240) 618 | // to a 16-bit signed water time (-600 to 600) 619 | int16_t water_time_decode_signed(byte i) { 620 | i=(i>240)?240:i; 621 | return ((int16_t)i-120)*5; 622 | } 623 | 624 | 625 | /** Convert a single hex digit character to its integer value */ 626 | static unsigned char h2int(char c) { 627 | if (c >= '0' && c <='9'){ 628 | return((unsigned char)c - '0'); 629 | } 630 | if (c >= 'a' && c <='f'){ 631 | return((unsigned char)c - 'a' + 10); 632 | } 633 | if (c >= 'A' && c <='F'){ 634 | return((unsigned char)c - 'A' + 10); 635 | } 636 | return(0); 637 | } 638 | 639 | /** Decode a url string e.g "hello%20joe" or "hello+joe" becomes "hello joe" */ 640 | void urlDecode (char *urlbuf) { 641 | if(!urlbuf) return; 642 | char c; 643 | char *dst = urlbuf; 644 | while ((c = *urlbuf) != 0) { 645 | if (c == '+') c = ' '; 646 | if (c == '%') { 647 | c = *++urlbuf; 648 | c = (h2int(c) << 4) | h2int(*++urlbuf); 649 | } 650 | *dst++ = c; 651 | urlbuf++; 652 | } 653 | *dst = '\0'; 654 | } 655 | 656 | void peel_http_header(char* buffer) { // remove the HTTP header 657 | uint16_t i=0; 658 | bool eol=true; 659 | while(i. 22 | */ 23 | 24 | #ifndef _DEFINES_H 25 | #define _DEFINES_H 26 | 27 | #define ENABLE_DEBUG // enable serial debug 28 | //#define DEMO 29 | 30 | typedef unsigned char byte; 31 | typedef unsigned long ulong; 32 | 33 | #define TMP_BUFFER_SIZE 255 // scratch buffer size 34 | 35 | /** Firmware version, hardware version, and maximal values */ 36 | #define OS_FW_VERSION 219 // Firmware version: 219 means 2.1.9 37 | // if this number is different from the one stored in non-volatile memory 38 | // a device reset will be automatically triggered 39 | 40 | #define OS_FW_MINOR 3 // Firmware minor version 41 | 42 | /** Hardware version base numbers */ 43 | #define OS_HW_VERSION_BASE 0x00 44 | #define OSPI_HW_VERSION_BASE 0x40 45 | #define OSBO_HW_VERSION_BASE 0x80 46 | #define SIM_HW_VERSION_BASE 0xC0 47 | 48 | /** Hardware type macro defines */ 49 | #define HW_TYPE_AC 0xAC // standard 24VAC for 24VAC solenoids only, with triacs 50 | #define HW_TYPE_DC 0xDC // DC powered, for both DC and 24VAC solenoids, with boost converter and MOSFETs 51 | #define HW_TYPE_LATCH 0x1A // DC powered, for DC latching solenoids only, with boost converter and H-bridges 52 | #define HW_TYPE_UNKNOWN 0xFF 53 | 54 | /** Data file names */ 55 | #define IOPTS_FILENAME "iopts.dat" // integer options data file 56 | #define SOPTS_FILENAME "sopts.dat" // string options data file 57 | #define STATIONS_FILENAME "stns.dat" // stations data file 58 | #define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData 59 | #define PROG_FILENAME "prog.dat" // program data file 60 | #define DONE_FILENAME "done.dat" // used to indicate the completion of all files 61 | 62 | /** Station macro defines */ 63 | #define STN_TYPE_STANDARD 0x00 64 | #define STN_TYPE_RF 0x01 // Radio Frequency (RF) station 65 | #define STN_TYPE_REMOTE 0x02 // Remote OpenSprinkler station 66 | #define STN_TYPE_GPIO 0x03 // direct GPIO station 67 | #define STN_TYPE_HTTP 0x04 // HTTP station 68 | #define STN_TYPE_OTHER 0xFF 69 | 70 | /** IFTTT macro defines */ 71 | #define IFTTT_PROGRAM_SCHED 0x01 72 | #define IFTTT_SENSOR1 0x02 73 | #define IFTTT_FLOWSENSOR 0x04 74 | #define IFTTT_WEATHER_UPDATE 0x08 75 | #define IFTTT_REBOOT 0x10 76 | #define IFTTT_STATION_RUN 0x20 77 | #define IFTTT_SENSOR2 0x40 78 | #define IFTTT_RAINDELAY 0x80 79 | 80 | /** HTTP request macro defines */ 81 | #define HTTP_RQT_SUCCESS 0 82 | #define HTTP_RQT_NOT_RECEIVED -1 83 | #define HTTP_RQT_CONNECT_ERR -2 84 | #define HTTP_RQT_TIMEOUT -3 85 | #define HTTP_RQT_EMPTY_RETURN -4 86 | 87 | /** Sensor macro defines */ 88 | #define SENSOR_TYPE_NONE 0x00 89 | #define SENSOR_TYPE_RAIN 0x01 // rain sensor 90 | #define SENSOR_TYPE_FLOW 0x02 // flow sensor 91 | #define SENSOR_TYPE_SOIL 0x03 // soil moisture sensor 92 | #define SENSOR_TYPE_PSWITCH 0xF0 // program switch sensor 93 | #define SENSOR_TYPE_OTHER 0xFF 94 | 95 | #define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds 96 | 97 | /** Reboot cause */ 98 | #define REBOOT_CAUSE_NONE 0 99 | #define REBOOT_CAUSE_RESET 1 100 | #define REBOOT_CAUSE_BUTTON 2 101 | #define REBOOT_CAUSE_RSTAP 3 102 | #define REBOOT_CAUSE_TIMER 4 103 | #define REBOOT_CAUSE_WEB 5 104 | #define REBOOT_CAUSE_WIFIDONE 6 105 | #define REBOOT_CAUSE_FWUPDATE 7 106 | #define REBOOT_CAUSE_WEATHER_FAIL 8 107 | #define REBOOT_CAUSE_NETWORK_FAIL 9 108 | #define REBOOT_CAUSE_NTP 10 109 | #define REBOOT_CAUSE_POWERON 99 110 | 111 | 112 | /** WiFi defines */ 113 | #define WIFI_M_AP 0xA9 114 | #define WIFI_M_STA 0x2A 115 | 116 | 117 | #define OS_STATE_INITIAL 0 118 | #define OS_STATE_CONNECTING 1 119 | #define OS_STATE_CONNECTED 2 120 | #define OS_STATE_TRY_CONNECT 3 121 | 122 | #define LED_FAST_BLINK 100 123 | #define LED_SLOW_BLINK 500 124 | 125 | /** Storage / zone expander defines */ 126 | #if defined(ARDUINO) 127 | #define MAX_EXT_BOARDS 8 // maximum number of 8-zone expanders (each 16-zone expander counts as 2) 128 | #else 129 | #define MAX_EXT_BOARDS 24 // allow more zones for linux-based firmwares 130 | #endif 131 | 132 | #define MAX_NUM_BOARDS (1+MAX_EXT_BOARDS) // maximum number of 8-zone boards including expanders 133 | #define MAX_NUM_STATIONS (MAX_NUM_BOARDS*8) // maximum number of stations 134 | #define STATION_NAME_SIZE 32 // maximum number of characters in each station name 135 | #define MAX_SOPTS_SIZE 160 // maximum string option size 136 | 137 | #define STATION_SPECIAL_DATA_SIZE (TMP_BUFFER_SIZE - STATION_NAME_SIZE - 12) 138 | 139 | /** Default string option values */ 140 | #define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' 141 | #define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA 142 | #define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" 143 | #define DEFAULT_WEATHER_URL "weather.opensprinkler.com" 144 | #define DEFAULT_IFTTT_URL "maker.ifttt.com" 145 | #define DEFAULT_EMPTY_STRING "" 146 | 147 | /** Macro define of each option 148 | * Refer to OpenSprinkler.cpp for details on each option 149 | */ 150 | enum { 151 | IOPT_FW_VERSION=0,// read-only (ro) 152 | IOPT_TIMEZONE, 153 | IOPT_USE_NTP, 154 | IOPT_USE_DHCP, 155 | IOPT_STATIC_IP1, 156 | IOPT_STATIC_IP2, 157 | IOPT_STATIC_IP3, 158 | IOPT_STATIC_IP4, 159 | IOPT_GATEWAY_IP1, 160 | IOPT_GATEWAY_IP2, 161 | IOPT_GATEWAY_IP3, 162 | IOPT_GATEWAY_IP4, 163 | IOPT_HTTPPORT_0, 164 | IOPT_HTTPPORT_1, 165 | IOPT_HW_VERSION, //ro 166 | IOPT_EXT_BOARDS, 167 | IOPT_SEQUENTIAL_RETIRED, //ro 168 | IOPT_STATION_DELAY_TIME, 169 | IOPT_MASTER_STATION, 170 | IOPT_MASTER_ON_ADJ, 171 | IOPT_MASTER_OFF_ADJ, 172 | IOPT_URS_RETIRED, // ro 173 | IOPT_RSO_RETIRED, // ro 174 | IOPT_WATER_PERCENTAGE, 175 | IOPT_DEVICE_ENABLE, // editable through jc 176 | IOPT_IGNORE_PASSWORD, 177 | IOPT_DEVICE_ID, 178 | IOPT_LCD_CONTRAST, 179 | IOPT_LCD_BACKLIGHT, 180 | IOPT_LCD_DIMMING, 181 | IOPT_BOOST_TIME, 182 | IOPT_USE_WEATHER, 183 | IOPT_NTP_IP1, 184 | IOPT_NTP_IP2, 185 | IOPT_NTP_IP3, 186 | IOPT_NTP_IP4, 187 | IOPT_ENABLE_LOGGING, 188 | IOPT_MASTER_STATION_2, 189 | IOPT_MASTER_ON_ADJ_2, 190 | IOPT_MASTER_OFF_ADJ_2, 191 | IOPT_FW_MINOR, //ro 192 | IOPT_PULSE_RATE_0, 193 | IOPT_PULSE_RATE_1, 194 | IOPT_REMOTE_EXT_MODE, // editable through jc 195 | IOPT_DNS_IP1, 196 | IOPT_DNS_IP2, 197 | IOPT_DNS_IP3, 198 | IOPT_DNS_IP4, 199 | IOPT_SPE_AUTO_REFRESH, 200 | IOPT_IFTTT_ENABLE, 201 | IOPT_SENSOR1_TYPE, 202 | IOPT_SENSOR1_OPTION, 203 | IOPT_SENSOR2_TYPE, 204 | IOPT_SENSOR2_OPTION, 205 | IOPT_SENSOR1_ON_DELAY, 206 | IOPT_SENSOR1_OFF_DELAY, 207 | IOPT_SENSOR2_ON_DELAY, 208 | IOPT_SENSOR2_OFF_DELAY, 209 | IOPT_SUBNET_MASK1, 210 | IOPT_SUBNET_MASK2, 211 | IOPT_SUBNET_MASK3, 212 | IOPT_SUBNET_MASK4, 213 | IOPT_WIFI_MODE, //ro 214 | IOPT_RESET, //ro 215 | NUM_IOPTS // total number of integer options 216 | }; 217 | 218 | enum { 219 | SOPT_PASSWORD=0, 220 | SOPT_LOCATION, 221 | SOPT_JAVASCRIPTURL, 222 | SOPT_WEATHERURL, 223 | SOPT_WEATHER_OPTS, 224 | SOPT_IFTTT_KEY, 225 | SOPT_STA_SSID, 226 | SOPT_STA_PASS, 227 | //SOPT_WEATHER_KEY, 228 | //SOPT_AP_PASS, 229 | //SOPT_MQTT_IP, 230 | NUM_SOPTS // total number of string options 231 | }; 232 | 233 | /** Log Data Type */ 234 | #define LOGDATA_STATION 0x00 235 | #define LOGDATA_SENSOR1 0x01 236 | #define LOGDATA_RAINDELAY 0x02 237 | #define LOGDATA_WATERLEVEL 0x03 238 | #define LOGDATA_FLOWSENSE 0x04 239 | #define LOGDATA_SENSOR2 0x05 240 | #define LOGDATA_CURRENT 0x80 241 | 242 | #undef OS_HW_VERSION 243 | 244 | /** Hardware defines */ 245 | #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) // for OS 2.3 246 | 247 | #define OS_HW_VERSION (OS_HW_VERSION_BASE+23) 248 | #define PIN_FREE_LIST {2,10,12,13,14,15,18,19} // Free GPIO pins 249 | 250 | // hardware pins 251 | #define PIN_BUTTON_1 31 // button 1 252 | #define PIN_BUTTON_2 30 // button 2 253 | #define PIN_BUTTON_3 29 // button 3 254 | #define PIN_RFTX 28 // RF data pin 255 | #define PORT_RF PORTA 256 | #define PINX_RF PINA3 257 | #define PIN_SR_LATCH 3 // shift register latch pin 258 | #define PIN_SR_DATA 21 // shift register data pin 259 | #define PIN_SR_CLOCK 22 // shift register clock pin 260 | #define PIN_SR_OE 1 // shift register output enable pin 261 | 262 | // regular 16x2 LCD pin defines 263 | #define PIN_LCD_RS 19 // LCD rs pin 264 | #define PIN_LCD_EN 18 // LCD enable pin 265 | #define PIN_LCD_D4 20 // LCD d4 pin 266 | #define PIN_LCD_D5 21 // LCD d5 pin 267 | #define PIN_LCD_D6 22 // LCD d6 pin 268 | #define PIN_LCD_D7 23 // LCD d7 pin 269 | #define PIN_LCD_BACKLIGHT 12 // LCD backlight pin 270 | #define PIN_LCD_CONTRAST 13 // LCD contrast pin 271 | 272 | // DC controller pin defines 273 | #define PIN_BOOST 20 // booster pin 274 | #define PIN_BOOST_EN 23 // boost voltage enable pin 275 | 276 | #define PIN_ETHER_CS 4 // Ethernet controller chip select pin 277 | #define PIN_SENSOR1 11 // 278 | #define PIN_SD_CS 0 // SD card chip select pin 279 | #define PIN_FLOWSENSOR_INT 1 // flow sensor interrupt pin (INT1) 280 | #define PIN_EXP_SENSE 4 // expansion board sensing pin (A4) 281 | #define PIN_CURR_SENSE 7 // current sensing pin (A7) 282 | #define PIN_CURR_DIGITAL 24 // digital pin index for A7 283 | 284 | #define ETHER_BUFFER_SIZE 8192 285 | 286 | #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset 287 | 288 | #define pinModeExt pinMode 289 | #define digitalReadExt digitalRead 290 | #define digitalWriteExt digitalWrite 291 | 292 | #elif defined(ESP8266) // for ESP8266 293 | 294 | #define OS_HW_VERSION (OS_HW_VERSION_BASE+30) 295 | #define IOEXP_PIN 0x80 // base for pins on main IO expander 296 | #define MAIN_I2CADDR 0x20 // main IO expander I2C address 297 | #define ACDR_I2CADDR 0x21 // ac driver I2C address 298 | #define DCDR_I2CADDR 0x22 // dc driver I2C address 299 | #define LADR_I2CADDR 0x23 // latch driver I2C address 300 | #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address 301 | #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address 302 | 303 | #define PIN_CURR_SENSE A0 304 | #define PIN_FREE_LIST {} // no free GPIO pin at the moment 305 | #define ETHER_BUFFER_SIZE 8192 306 | 307 | #define PIN_ETHER_CS 16 // ENC28J60 CS (chip select pin) is 16 on OS 3.2. 308 | 309 | /* To accommodate different OS30 versions, we use software defines pins */ 310 | extern byte PIN_BUTTON_1; 311 | extern byte PIN_BUTTON_2; 312 | extern byte PIN_BUTTON_3; 313 | extern byte PIN_RFRX; 314 | extern byte PIN_RFTX; 315 | extern byte PIN_BOOST; 316 | extern byte PIN_BOOST_EN; 317 | extern byte PIN_LATCH_COM; 318 | extern byte PIN_SENSOR1; 319 | extern byte PIN_SENSOR2; 320 | extern byte PIN_IOEXP_INT; 321 | 322 | /* Original OS30 pin defines */ 323 | //#define V0_MAIN_INPUTMASK 0b00001010 // main input pin mask 324 | // pins on main PCF8574 IO expander have pin numbers IOEXP_PIN+i 325 | 326 | #define V0_PIN_BUTTON_1 IOEXP_PIN+1 // button 1 327 | #define V0_PIN_BUTTON_2 0 // button 2 328 | #define V0_PIN_BUTTON_3 IOEXP_PIN+3 // button 3 329 | #define V0_PIN_RFRX 14 330 | #define V0_PIN_PWR_RX IOEXP_PIN+0 331 | #define V0_PIN_RFTX 16 332 | #define V0_PIN_PWR_TX IOEXP_PIN+2 333 | #define V0_PIN_BOOST IOEXP_PIN+6 334 | #define V0_PIN_BOOST_EN IOEXP_PIN+7 335 | #define V0_PIN_SENSOR1 12 // sensor 1 336 | #define V0_PIN_SENSOR2 13 // sensor 2 337 | 338 | /* OS30 revision 1 pin defines */ 339 | // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i 340 | #define V1_IO_CONFIG 0x1F00 // config bits 341 | #define V1_IO_OUTPUT 0x1F00 // output bits 342 | #define V1_PIN_BUTTON_1 IOEXP_PIN+10 // button 1 343 | #define V1_PIN_BUTTON_2 IOEXP_PIN+11 // button 2 344 | #define V1_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 345 | #define V1_PIN_RFRX 14 346 | #define V1_PIN_RFTX 16 347 | #define V1_PIN_IOEXP_INT 12 348 | #define V1_PIN_BOOST IOEXP_PIN+13 349 | #define V1_PIN_BOOST_EN IOEXP_PIN+14 350 | #define V1_PIN_LATCH_COM IOEXP_PIN+15 351 | #define V1_PIN_SENSOR1 IOEXP_PIN+8 // sensor 1 352 | #define V1_PIN_SENSOR2 IOEXP_PIN+9 // sensor 2 353 | 354 | /* OS30 revision 2 pin defines */ 355 | // pins on PCA9555A IO expander have pin numbers IOEXP_PIN+i 356 | #define V2_IO_CONFIG 0x1F00 // config bits 357 | #define V2_IO_OUTPUT 0x1F00 // output bits 358 | #define V2_PIN_BUTTON_1 2 // button 1 359 | #define V2_PIN_BUTTON_2 0 // button 2 360 | #define V2_PIN_BUTTON_3 IOEXP_PIN+12 // button 3 361 | #define V2_PIN_RFTX 15 362 | #define V2_PIN_BOOST IOEXP_PIN+13 363 | #define V2_PIN_BOOST_EN IOEXP_PIN+14 364 | #define V2_PIN_LATCH_COM IOEXP_PIN+15 365 | #define V2_PIN_SENSOR1 3 // sensor 1 366 | #define V2_PIN_SENSOR2 10 // sensor 2 367 | 368 | #elif defined(ESP32) 369 | 370 | #include "esp32.h" 371 | 372 | #elif defined(OSPI) // for OSPi 373 | 374 | #define OS_HW_VERSION OSPI_HW_VERSION_BASE 375 | #define PIN_SR_LATCH 22 // shift register latch pin 376 | #define PIN_SR_DATA 27 // shift register data pin 377 | #define PIN_SR_DATA_ALT 21 // shift register data pin (alternative, for RPi 1 rev. 1 boards) 378 | #define PIN_SR_CLOCK 4 // shift register clock pin 379 | #define PIN_SR_OE 17 // shift register output enable pin 380 | #define PIN_SENSOR1 14 381 | #define PIN_SENSOR2 23 382 | #define PIN_RFTX 15 // RF transmitter pin 383 | #define PIN_BUTTON_1 23 // button 1 384 | #define PIN_BUTTON_2 24 // button 2 385 | #define PIN_BUTTON_3 25 // button 3 386 | 387 | #define PIN_FREE_LIST {5,6,7,8,9,10,11,12,13,16,18,19,20,21,23,24,25,26} // free GPIO pins 388 | #define ETHER_BUFFER_SIZE 16384 389 | 390 | #elif defined(OSBO) // for OSBo 391 | 392 | #define OS_HW_VERSION OSBO_HW_VERSION_BASE 393 | // these are gpio pin numbers, refer to 394 | // https://github.com/mkaczanowski/BeagleBoneBlack-GPIO/blob/master/GPIO/GPIOConst.cpp 395 | #define PIN_SR_LATCH 60 // P9_12, shift register latch pin 396 | #define PIN_SR_DATA 30 // P9_11, shift register data pin 397 | #define PIN_SR_CLOCK 31 // P9_13, shift register clock pin 398 | #define PIN_SR_OE 50 // P9_14, shift register output enable pin 399 | #define PIN_SENSOR1 48 400 | #define PIN_RFTX 51 // RF transmitter pin 401 | 402 | #define PIN_FREE_LIST {38,39,34,35,45,44,26,47,27,65,63,62,37,36,33,32,61,86,88,87,89,76,77,74,72,73,70,71} 403 | #define ETHER_BUFFER_SIZE 16384 404 | 405 | #else // for demo / simulation 406 | // use fake hardware pins 407 | #if defined(DEMO) 408 | #define OS_HW_VERSION 255 // assign hardware number 255 to DEMO firmware 409 | #else 410 | #define OS_HW_VERSION SIM_HW_VERSION_BASE 411 | #endif 412 | #define PIN_SR_LATCH 0 413 | #define PIN_SR_DATA 0 414 | #define PIN_SR_CLOCK 0 415 | #define PIN_SR_OE 0 416 | #define PIN_SENSOR1 0 417 | #define PIN_SENSOR2 0 418 | #define PIN_RFTX 0 419 | #define PIN_FREE_LIST {} 420 | #define ETHER_BUFFER_SIZE 16384 421 | #endif 422 | 423 | #if defined(ENABLE_DEBUG) /** Serial debug functions */ 424 | 425 | #if defined(ARDUINO) 426 | #define DEBUG_BEGIN(x) {Serial.begin(x);} 427 | #define DEBUG_PRINT(x) {Serial.print(x);} 428 | #define DEBUG_PRINTLN(x) {Serial.println(x);} 429 | #define DEBUG_PRINTX(x) {Serial.print(F("0x")); Serial.print(x, HEX); } 430 | #else 431 | #include 432 | #define DEBUG_BEGIN(x) {} /** Serial debug functions */ 433 | inline void DEBUG_PRINT(int x) {printf("%d", x);} 434 | inline void DEBUG_PRINT(const char*s) {printf("%s", s);} 435 | #define DEBUG_PRINTLN(x) {DEBUG_PRINT(x);printf("\n");} 436 | #endif 437 | 438 | #else 439 | 440 | #define DEBUG_BEGIN(x) {} 441 | #define DEBUG_PRINT(x) {} 442 | #define DEBUG_PRINTLN(x) {} 443 | 444 | #endif 445 | 446 | /** Re-define avr-specific (e.g. PGM) types to use standard types */ 447 | #if !defined(ARDUINO) 448 | #include 449 | #include 450 | #include 451 | #include 452 | inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} 453 | inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} 454 | #define now() time(0) 455 | #define pgm_read_byte(x) *(x) 456 | #define PSTR(x) x 457 | #define F(x) x 458 | #define strcat_P strcat 459 | #define strcpy_P strcpy 460 | #include 461 | #define String string 462 | using namespace std; 463 | #define PROGMEM 464 | typedef const char* PGM_P; 465 | typedef unsigned char uint8_t; 466 | typedef short int16_t; 467 | typedef unsigned short uint16_t; 468 | typedef bool boolean; 469 | #define pinModeExt pinMode 470 | #define digitalReadExt digitalRead 471 | #define digitalWriteExt digitalWrite 472 | #endif 473 | 474 | /** Other defines */ 475 | // button values 476 | #define BUTTON_1 0x01 477 | #define BUTTON_2 0x02 478 | #define BUTTON_3 0x04 479 | 480 | // button status values 481 | #define BUTTON_NONE 0x00 // no button pressed 482 | #define BUTTON_MASK 0x0F // button status mask 483 | #define BUTTON_FLAG_HOLD 0x80 // long hold flag 484 | #define BUTTON_FLAG_DOWN 0x40 // down flag 485 | #define BUTTON_FLAG_UP 0x20 // up flag 486 | 487 | // button timing values 488 | #define BUTTON_DELAY_MS 1 // short delay (milliseconds) 489 | #define BUTTON_HOLD_MS 1000 // long hold expiration time (milliseconds) 490 | 491 | // button mode values 492 | #define BUTTON_WAIT_NONE 0 // do not wait, return value immediately 493 | #define BUTTON_WAIT_RELEASE 1 // wait until button is release 494 | #define BUTTON_WAIT_HOLD 2 // wait until button hold time expires 495 | 496 | #define DISPLAY_MSG_MS 2000 // message display time (milliseconds) 497 | 498 | 499 | #endif // _DEFINES_H 500 | --------------------------------------------------------------------------------