From 3f83dcbdc560c232814302b705a66eabda11de07 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 5 Jul 2015 22:09:07 -0400 Subject: [PATCH 01/39] add support to change weather script url in /su page; avoid sending static ip/gateway ip etc. if device is ospi/osbo/linux; log water level to sd card --- OpenSprinkler.cpp | 24 +++++------------------- OpenSprinkler.h | 22 +++++++++++++--------- defines.h | 42 +++++++++++++++++++++++------------------- main.cpp | 23 ++++++++--------------- server.cpp | 41 +++++++++++++++++++++++++++-------------- weather.cpp | 9 +++++---- 6 files changed, 81 insertions(+), 80 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 2a64a699..8096b276 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -37,8 +37,8 @@ byte OpenSprinkler::masop_bits[MAX_EXT_BOARDS+1]; byte OpenSprinkler::ignrain_bits[MAX_EXT_BOARDS+1]; byte OpenSprinkler::masop2_bits[MAX_EXT_BOARDS+1]; byte OpenSprinkler::stndis_bits[MAX_EXT_BOARDS+1]; -byte OpenSprinkler::rfstn_bits[MAX_EXT_BOARDS+1]; byte OpenSprinkler::stnseq_bits[MAX_EXT_BOARDS+1]; +byte OpenSprinkler::stnspe_bits[MAX_EXT_BOARDS+1]; ulong OpenSprinkler::rainsense_start_time; ulong OpenSprinkler::raindelay_start_time; @@ -48,7 +48,6 @@ ulong OpenSprinkler::checkwt_lasttime; ulong OpenSprinkler::checkwt_success_lasttime; ulong OpenSprinkler::network_lasttime; ulong OpenSprinkler::external_ip; -byte OpenSprinkler::water_percent_avg; char tmp_buffer[TMP_BUFFER_SIZE+1]; // scratch buffer @@ -767,19 +766,6 @@ void transmit_rfbit(ulong lenH, ulong lenL) { #endif } -void OpenSprinkler::update_rfstation_bits() { - byte bid, s, sid; - for(bid=0;bid<(1+MAX_EXT_BOARDS);bid++) { - rfstn_bits[bid] = 0; - for(s=0;s<8;s++) { - sid = (bid<<3) | s; - if(get_station_name_rf(sid, NULL, NULL)) { - rfstn_bits[bid] |= (1< master controller, second byte-> ext. board 1, and so on // station attributes static byte masop_bits[]; // master operation bits. each byte corresponds to a board (8 stations) - static byte ignrain_bits[]; // ignore rain bits. each byte corresponds to a board (8 stations) - static byte masop2_bits[]; // master2 operation bits. each byte corresponds to a board (8 stations) - static byte stndis_bits[]; // station disable bits. each byte corresponds to a board (8 stations) - static byte rfstn_bits[]; // RF station flags. each byte corresponds to a board (8 stations) - static byte stnseq_bits[]; // station sequential bits. each byte corresponds to a board (8 stations) - + static byte ignrain_bits[]; // ignore rain bits. + static byte masop2_bits[]; // master2 operation bits. + static byte stndis_bits[]; // station disable bits. + static byte stnseq_bits[]; // station sequential bits. + static byte stnspe_bits[]; // station special bits. the bit marks if this is a non-standard station. + // variables for time keeping static ulong rainsense_start_time; // time when the most recent rain sensor activation was detected static ulong raindelay_start_time; // time when the most recent rain delay started @@ -116,7 +122,6 @@ class OpenSprinkler { static ulong checkwt_success_lasttime; // time when weather check was successful static ulong network_lasttime; // time when network was checked static ulong external_ip; // external ip address - static byte water_percent_avg; // average water percentage over a day // member functions // -- setup @@ -131,11 +136,10 @@ class OpenSprinkler { static void get_station_name(byte sid, char buf[]); // get station name static void set_station_name(byte sid, char buf[]); // set station name static uint16_t get_station_name_rf(byte sid, ulong *on, ulong *off); // get station name and parse into RF code - static void update_rfstation_bits(); static void send_rfstation_signal(byte sid, bool status); static void station_attrib_bits_save(int addr, byte bits[]); // save station attribute bits to nvm static void station_attrib_bits_load(int addr, byte bits[]); // load station attribute bits from nvm - + // -- options and data storeage static void nvdata_load(); static void nvdata_save(); diff --git a/defines.h b/defines.h index 66819248..1b59a26e 100644 --- a/defines.h +++ b/defines.h @@ -25,11 +25,11 @@ #define _DEFINES_H /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 215 // Firmware version: 215 means 2.1.5 +#define OS_FW_VERSION 216 // Firmware version: 216 means 2.1.6 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 1 // Firmware minor version +#define OS_FW_MINOR 0 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 @@ -46,29 +46,32 @@ #define MAX_NUM_STATIONS ((1+MAX_EXT_BOARDS)*8) // maximum number of stations -#define WEATHER_OPTS_FILENAME "wtopts.txt" // the file where weather options are stored +/** File names */ +#define WEATHER_OPTS_FILENAME "wtopts.txt" // weather options file +#define STATION_ATTR_FILENAME "stns.dat" // station attributes data file +#define MAX_STATION_SPECIAL_DATA 16 /** Non-volatile memory (NVM) defines */ #if defined(ARDUINO) /** 2KB NVM data structure: - * | | | |---STRING PARAMETERS---| |----STATION ATTRIBUTES----- | | - * | UID | PROGRAM_DATA | CON | PWD | LOC | URL | KEY | STATION_NAMES | MAS | IGR | MAS2 | DIS | SEQ | OPTIONS | - * | (8) | (996) | (8) |(32) |(48) |(64) |(32) | (6*8*16)=768 | (6) | (6) | (6) | (6) | (6) | (62) | - * | | | | | | | | | | | | | | | - * 0 8 1004 1012 1044 1092 1156 1188 1956 1962 1968 1974 1980 1986 2048 + * | | | ---STRING PARAMETERS--- | | ----STATION ATTRIBUTES----- | | + * | PROGRAM | CON | PWD | LOC | JURL | WURL | KEY | STN_NAMES | MAS | IGR | MAS2 | DIS | SEQ | SPE | OPTIONS | + * | (996) | (8) |(32) |(48) | (40) | (40) |(32) | (768) | (6) | (6) | (6) | (6) | (6) | (6) | (54) | + * | | | | | | | | | | | | | | | | + * 0 996 1004 1036 1084 1124 1164 1196 1964 1970 1976 1982 1988 1994 2000 2048 */ #define NVM_SIZE 2048 // For AVR, nvm data is stored in EEPROM // ATmega644 has 2KB EEPROM #define STATION_NAME_SIZE 16 // maximum number of characters in each station name - #define MAX_UIDDATA 8 // unique ID, 8 bytes max #define MAX_PROGRAMDATA 996 // program data, 996 bytes max #define MAX_NVCONDATA 8 // non-volatile controller data, 8 bytes max #define MAX_USER_PASSWORD 32 // user password, 32 bytes max #define MAX_LOCATION 48 // location string, 48 bytes max - #define MAX_SCRIPTURL 64 // javascript url, 64 bytes max + #define MAX_JAVASCRIPTURL 40 // javascript url, 40 bytes max + #define MAX_WEATHERURL 40 // weather script url, 40 bytes max #define MAX_WEATHER_KEY 32 // weather api key, 32 bytes max #else // NVM defines for RPI/BBB/LINUX @@ -79,38 +82,39 @@ #define NVM_SIZE 2048 // impose a file size limit: 64KB #define STATION_NAME_SIZE 16 // maximum number of characters in each station name - #define MAX_UIDDATA 8 // unique ID, 8 bytes max #define MAX_PROGRAMDATA 996 // program data, 3984 bytes max #define MAX_NVCONDATA 8 // non-volatile controller data, 8 bytes max #define MAX_USER_PASSWORD 32 // user password, 32 bytes max #define MAX_LOCATION 48 // location string, 48 bytes max - #define MAX_SCRIPTURL 64 // javascript url, 64 bytes max + #define MAX_JAVASCRIPTURL 40 // javascript url, 40 bytes max + #define MAX_WEATHERURL 40 // weather script url, 40 bytes max #define MAX_WEATHER_KEY 32 // weather api key, 32 bytes max #endif // end of NVM defines /** NVM data addresses */ -#define ADDR_NVM_UID 0 -#define ADDR_NVM_PROGRAMS (ADDR_NVM_UID+MAX_UIDDATA) // program starting address +#define ADDR_NVM_PROGRAMS (0) // program starting address #define ADDR_NVM_NVCONDATA (ADDR_NVM_PROGRAMS+MAX_PROGRAMDATA) #define ADDR_NVM_PASSWORD (ADDR_NVM_NVCONDATA+MAX_NVCONDATA) #define ADDR_NVM_LOCATION (ADDR_NVM_PASSWORD+MAX_USER_PASSWORD) -#define ADDR_NVM_SCRIPTURL (ADDR_NVM_LOCATION+MAX_LOCATION) -#define ADDR_NVM_WEATHER_KEY (ADDR_NVM_SCRIPTURL+MAX_SCRIPTURL) +#define ADDR_NVM_JAVASCRIPTURL (ADDR_NVM_LOCATION+MAX_LOCATION) +#define ADDR_NVM_WEATHERURL (ADDR_NVM_JAVASCRIPTURL+MAX_JAVASCRIPTURL) +#define ADDR_NVM_WEATHER_KEY (ADDR_NVM_WEATHERURL+MAX_WEATHERURL) #define ADDR_NVM_STN_NAMES (ADDR_NVM_WEATHER_KEY+MAX_WEATHER_KEY) #define ADDR_NVM_MAS_OP (ADDR_NVM_STN_NAMES+MAX_NUM_STATIONS*STATION_NAME_SIZE) // master op bits #define ADDR_NVM_IGNRAIN (ADDR_NVM_MAS_OP+(MAX_EXT_BOARDS+1)) // ignore rain bits #define ADDR_NVM_MAS_OP_2 (ADDR_NVM_IGNRAIN+(MAX_EXT_BOARDS+1)) // master2 op bits #define ADDR_NVM_STNDISABLE (ADDR_NVM_MAS_OP_2+(MAX_EXT_BOARDS+1))// station disable bits #define ADDR_NVM_STNSEQ (ADDR_NVM_STNDISABLE+(MAX_EXT_BOARDS+1))// station sequential bits -#define ADDR_NVM_OPTIONS (ADDR_NVM_STNSEQ+(MAX_EXT_BOARDS+1)) // options +#define ADDR_NVM_STNSPE (ADDR_NVM_STNSEQ+(MAX_EXT_BOARDS+1)) // station special bits (i.e. non-standard stations) +#define ADDR_NVM_OPTIONS (ADDR_NVM_STNSPE+(MAX_EXT_BOARDS+1)) // options /** Default password, location string, weather key, script urls */ #define DEFAULT_PASSWORD "opendoor" #define DEFAULT_LOCATION "Boston,MA" #define DEFAULT_WEATHER_KEY "" -#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" -#define WEATHER_SCRIPT_HOST "weather.opensprinkler.com" +#define DEFAULT_JAVASCRIPT_URL "http://ui.opensprinkler.com/js" +#define DEFAULT_WEATHER_URL "weather.opensprinkler.com" /** Macro define of each option * Refer to OpenSprinkler.cpp for details on each option diff --git a/main.cpp b/main.cpp index b5dcbd48..f00d901c 100644 --- a/main.cpp +++ b/main.cpp @@ -262,7 +262,6 @@ void process_dynamic_events(ulong curr_time); void check_network(); void check_weather(); void perform_ntp_sync(); -void log_statistics(time_t curr_time); void delete_log(char *name); void analyze_get_url(char *p); @@ -439,10 +438,10 @@ void do_loop() // upon turning on station, process RF // if the station is a RF station - if(os.rfstn_bits[bid]&(1< scheduled_start_time } // if current station is not running }//end_s @@ -574,8 +573,6 @@ void do_loop() // check weather check_weather(); - // calculate statistics - log_statistics(curr_time); } #if !defined(ARDUINO) @@ -598,12 +595,11 @@ void check_weather() { if (!os.checkwt_lasttime || (ntz > os.checkwt_lasttime + CHECK_WEATHER_TIMEOUT)) { os.checkwt_lasttime = ntz; GetWeather(); + write_log(LOGDATA_WATERLEVEL, ntz); // warning: water level may update a few seconds after getweather is called } } void turn_off_station(byte sid, ulong curr_time) { - byte bid = sid>>3; - byte s = sid&0x07; os.set_station_bit(sid, 0); // ignore if we are turning off a station that's not running or scheduled to run @@ -623,10 +619,10 @@ void turn_off_station(byte sid, ulong curr_time) { // upon turning off station, process RF station // if the station is a RF station - if(os.rfstn_bits[bid]&(1<

Script URL:

Default is $S
If local on uSD card, use ./

Password:


"), ADDR_NVM_SCRIPTURL, DEFAULT_JAVASCRIPT_URL); + bfill.emit_p(PSTR("
JavaScript:
Default:$S
Weather:
Default:$S
Password:
"), ADDR_NVM_JAVASCRIPTURL, DEFAULT_JAVASCRIPT_URL, ADDR_NVM_WEATHERURL, DEFAULT_WEATHER_URL); return HTML_OK; } @@ -809,7 +817,7 @@ byte server_home(char *p) // send server variables and javascript packets bfill.emit_p(PSTR("var ver=$D,ipas=$D;\n"), OS_FW_VERSION, os.options[OPTION_IGNORE_PASSWORD].value); - bfill.emit_p(PSTR("\n\n"), ADDR_NVM_SCRIPTURL); + bfill.emit_p(PSTR("\n\n"), ADDR_NVM_JAVASCRIPTURL); return HTML_OK; } @@ -882,11 +890,16 @@ byte server_change_scripturl(char *p) #endif if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("jsp"), true)) { urlDecode(tmp_buffer); - tmp_buffer[MAX_SCRIPTURL]=0; // make sure we don't exceed the maximum size + tmp_buffer[MAX_JAVASCRIPTURL]=0; // make sure we don't exceed the maximum size // trim unwanted space characters string_remove_space(tmp_buffer); - //os.nvm_string_set(ADDR_NVM_SCRIPTURL, tmp_buffer); - nvm_write_block(tmp_buffer, (void *)ADDR_NVM_SCRIPTURL, strlen(tmp_buffer)+1); + nvm_write_block(tmp_buffer, (void *)ADDR_NVM_JAVASCRIPTURL, strlen(tmp_buffer)+1); + } + if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wsp"), true)) { + urlDecode(tmp_buffer); + tmp_buffer[MAX_WEATHERURL]=0; + string_remove_space(tmp_buffer); + nvm_write_block(tmp_buffer, (void *)ADDR_NVM_WEATHERURL, strlen(tmp_buffer)+1); } return HTML_REDIRECT_HOME; } diff --git a/weather.cpp b/weather.cpp index 9e38c26f..656efe2e 100644 --- a/weather.cpp +++ b/weather.cpp @@ -43,7 +43,7 @@ byte findKeyVal (const char *str,char *strbuf, uint8_t maxlen,const char *key,bo // The weather function calls getweather.py on remote server to retrieve weather data // the default script is WEATHER_SCRIPT_HOST/weather?.py -static char website[] PROGMEM = WEATHER_SCRIPT_HOST ; +//static char website[] PROGMEM = DEFAULT_WEATHER_URL ; static void getweather_callback(byte status, uint16_t off, uint16_t len) { #if defined(ARDUINO) @@ -102,7 +102,8 @@ static void getweather_callback(byte status, uint16_t off, uint16_t len) { void GetWeather() { // check if we've already done dns lookup if(ether.hisip[0] == 0) { - ether.dnsLookup(website); + nvm_read_block(tmp_buffer, (void*)ADDR_NVM_WEATHERURL, MAX_WEATHERURL); + ether.dnsLookup(tmp_buffer, true); } //bfill=ether.tcpOffset(); @@ -137,7 +138,7 @@ void GetWeather() { uint16_t _port = ether.hisport; // save current port number ether.hisport = 80; - ether.browseUrl(PSTR("/weather"), dst, website, getweather_callback); + ether.browseUrl(PSTR("/weather"), dst, PSTR("*"), getweather_callback); ether.hisport = _port; } @@ -175,7 +176,7 @@ void GetWeather() { static struct hostent *server = NULL; if (!server) { - strcpy(tmp_buffer, WEATHER_SCRIPT_HOST); + nvm_read_block(tmp_buffer, (void*)ADDR_NVM_WEATHERURL, MAX_WEATHERURL); server = gethostbyname(tmp_buffer); if (!server) { DEBUG_PRINTLN("can't resolve weather server"); From 6532721a3ed8e5066ac40995d1a65f26e3d4cbed Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 24 Jul 2015 12:11:06 -0400 Subject: [PATCH 02/39] preparation for storing station attributes to SD card. add support to detect executable path on Linux --- OpenSprinkler.cpp | 29 +++++++++++++++++++---------- defines.h | 10 ++++++++-- server.cpp | 7 ++++--- utils.cpp | 39 ++++++++++++++++++++++++++++++++------- utils.h | 4 +++- weather.cpp | 6 +++--- 6 files changed, 69 insertions(+), 26 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 8096b276..cceb8695 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -52,9 +52,11 @@ ulong OpenSprinkler::external_ip; char tmp_buffer[TMP_BUFFER_SIZE+1]; // scratch buffer #if defined(ARDUINO) - prog_char wtopts_name[] PROGMEM = WEATHER_OPTS_FILENAME; + prog_char wtopts_filename[] PROGMEM = WEATHER_OPTS_FILENAME; + prog_char stns_filename[] PROGMEM = STATION_ATTR_FILENAME; #else - char wtopts_name[] = WEATHER_OPTS_FILENAME; + char wtopts_filename[] = WEATHER_OPTS_FILENAME; + char stns_filename[] = STATION_ATTR_FILENAME; #endif #if defined(ARDUINO) @@ -536,6 +538,8 @@ void OpenSprinkler::begin() { if (RTC.detect()==0) { status.has_rtc = 1; } +#else + DEBUG_PRINTLN(get_runtime_path()); #endif } @@ -824,19 +828,17 @@ void OpenSprinkler::options_setup() { nvm_write_block(tmp_buffer, (void*)i, nbytes); } - // 1. write program data default parameters - - // 2. write non-volatile controller status + // 1. write non-volatile controller status nvdata_save(); - // 3. write string parameters + // 2. write string parameters nvm_write_block(DEFAULT_PASSWORD, (void*)ADDR_NVM_PASSWORD, strlen(DEFAULT_PASSWORD)+1); nvm_write_block(DEFAULT_LOCATION, (void*)ADDR_NVM_LOCATION, strlen(DEFAULT_LOCATION)+1); nvm_write_block(DEFAULT_JAVASCRIPT_URL, (void*)ADDR_NVM_JAVASCRIPTURL, strlen(DEFAULT_JAVASCRIPT_URL)+1); nvm_write_block(DEFAULT_WEATHER_URL, (void*)ADDR_NVM_WEATHERURL, strlen(DEFAULT_WEATHER_URL)+1); nvm_write_block(DEFAULT_WEATHER_KEY, (void*)ADDR_NVM_WEATHER_KEY, strlen(DEFAULT_WEATHER_KEY)+1); - // 4. reset station names, default Sxx + // 3. reset station names and special attributes, default Sxx tmp_buffer[0]='S'; tmp_buffer[3]=0; for(i=ADDR_NVM_STN_NAMES, sn=1; i\n" ; -extern const char wtopts_name[]; +extern const char wtopts_filename[]; +extern const char stns_filename[]; #if defined(ARDUINO) void print_html_standard_header() { @@ -792,7 +793,7 @@ void server_json_controller_main() { } } - if(read_from_file(wtopts_name, tmp_buffer)) { + if(read_from_file(wtopts_filename, tmp_buffer)) { bfill.emit_p(PSTR(",\"wto\":{$S}"), tmp_buffer); } bfill.emit_p(PSTR("}")); @@ -998,7 +999,7 @@ byte server_change_options(char *p) urlDecode(tmp_buffer); tmp_buffer[TMP_BUFFER_SIZE]=0; // store weather key - write_to_file(wtopts_name, tmp_buffer); + write_to_file(wtopts_filename, tmp_buffer, strlen(tmp_buffer)); weather_change = true; } if (err) { diff --git a/utils.cpp b/utils.cpp index 1c359778..cc632cb4 100644 --- a/utils.cpp +++ b/utils.cpp @@ -31,18 +31,21 @@ extern char tmp_buffer[]; #include "SdFat.h" extern SdFat sd; -void write_to_file(const char *name, const char *data) { +void write_to_file(const char *name, const char *data, int size, int pos, bool trunc) { if (!os.status.has_sd) return; char *fn = tmp_buffer+TMP_BUFFER_SIZE-12; strcpy_P(fn, name); sd.chdir("/"); SdFile file; - int ret = file.open(fn, O_CREAT | O_WRITE | O_TRUNC); + int flag = O_CREAT | O_WRITE; + if (trunc) flag |= O_TRUNC; + int ret = file.open(fn, flag); if(!ret) { return; - } - file.write(data); + } + file.seekSet(pos); + file.write(data, size); file.close(); } @@ -127,13 +130,21 @@ void nvm_write_byte(const byte *p, byte v) { } } -void write_to_file(const char *name, const char *data) { +void write_to_file(const char *name, const char *data, int size, int pos, bool trunc) { FILE *file; - file = fopen(name, "wb"); + if(trunc) { + file = fopen(name, "wb"); + } else { + file = fopen(name, "r+b"); + if(!file) { + file = fopen(name, "wb"); + } + } if (!file) { return; } - fwrite(data, 1, strlen(data), file); + fseek(file, pos, SEEK_SET); + fwrite(data, 1, size, file); fclose(file); } @@ -165,6 +176,20 @@ void remove_file(const char *name) { remove(name); } +char* get_runtime_path() { + static char path[PATH_MAX]; + if(readlink("/proc/self/exe", path, PATH_MAX ) <= 0) { + return NULL; + } + char* path_end = strrchr(path, '/'); + if(path_end == NULL) { + return NULL; + } + path_end++; + *path_end=0; + return path; +} + #if defined(OSPI) unsigned int detect_rpi_rev() { FILE * filp; diff --git a/utils.h b/utils.h index a08f553d..3d1881b9 100644 --- a/utils.h +++ b/utils.h @@ -28,6 +28,7 @@ #else // headers for RPI/BBB #include + #include #endif #include "defines.h" @@ -37,7 +38,7 @@ uint16_t water_time_decode(byte i); ulong water_time_resolve(uint16_t v); byte water_time_encode_signed(int16_t i); int16_t water_time_decode_signed(byte i); -void write_to_file(const char *name, const char *data); +void write_to_file(const char *name, const char *data, int size, int pos=0, bool trunc=true); bool read_from_file(const char *name, char *data, int maxsize=TMP_BUFFER_SIZE); void remove_file(const char *name); #if defined(ARDUINO) @@ -50,6 +51,7 @@ void remove_file(const char *name); void nvm_write_block(const void *src, void *dst, int len); byte nvm_read_byte(const byte *p); void nvm_write_byte(const byte *p, byte v); + char* get_runtime_path(); #if defined(OSPI) unsigned int detect_rpi_rev(); #endif diff --git a/weather.cpp b/weather.cpp index 656efe2e..9477f106 100644 --- a/weather.cpp +++ b/weather.cpp @@ -31,7 +31,7 @@ extern char ether_buffer[]; #endif -extern const char wtopts_name[]; +extern const char wtopts_filename[]; #include "OpenSprinkler.h" #include "utils.h" @@ -108,7 +108,7 @@ void GetWeather() { //bfill=ether.tcpOffset(); char tmp[30]; - read_from_file(wtopts_name, tmp, 30); + read_from_file(wtopts_filename, tmp, 30); BufferFiller bf = (uint8_t*)tmp_buffer; bf.emit_p(PSTR("$D.py?loc=$E&key=$E&fwv=$D&wto=$S"), (int) os.options[OPTION_USE_WEATHER].value, @@ -200,7 +200,7 @@ void GetWeather() { BufferFiller bf = tmp_buffer; char tmp[100]; - read_from_file(wtopts_name, tmp, 100); + read_from_file(wtopts_filename, tmp, 100); bf.emit_p(PSTR("$D.py?loc=$E&key=$E&fwv=$D&wto=$S"), (int) os.options[OPTION_USE_WEATHER].value, ADDR_NVM_LOCATION, From 56ec0c29ed03a3d412cf2ed775e8351b6e5e416c Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 24 Jul 2015 16:58:59 -0400 Subject: [PATCH 03/39] add support to handle POST requests --- main.cpp | 30 ++++------------------- server.cpp | 72 +++++++++++++++++++++++++++++++----------------------- 2 files changed, 46 insertions(+), 56 deletions(-) diff --git a/main.cpp b/main.cpp index f00d901c..6cefabd4 100644 --- a/main.cpp +++ b/main.cpp @@ -52,7 +52,6 @@ EthernetClient *m_client = 0; #define NTP_SYNC_TIMEOUT 86403L // NYP sync timeout, 24 hrs #define RTC_SYNC_INTERVAL 60 // RTC sync interval, 60 secs #define CHECK_NETWORK_TIMEOUT 59 // Network checking timeout, 59 secs -#define STAT_UPDATE_TIMEOUT 900 // Statistics update timeout: 15 mins #define CHECK_WEATHER_TIMEOUT 3601 // Weather check interval: 1 hour #define CHECK_WEATHER_SUCCESS_TIMEOUT 86433L // Weather check success interval: 24 hrs #define LCD_BACKLIGHT_TIMEOUT 15 // LCD backlight timeout: 15 secs @@ -263,14 +262,13 @@ void check_network(); void check_weather(); void perform_ntp_sync(); void delete_log(char *name); -void analyze_get_url(char *p); +void handle_web_request(char *p); /** Main Loop */ void do_loop() { static ulong last_time = 0; static ulong last_minute = 0; - static uint16_t pos; byte bid, sid, s, pid, bitvalue; ProgramStruct prog; @@ -280,9 +278,9 @@ void do_loop() time_t curr_time = os.now_tz(); // ====== Process Ethernet packets ====== #if defined(ARDUINO) // Process Ethernet packets for Arduino - pos=ether.packetLoop(ether.packetReceive()); + uint16_t pos=ether.packetLoop(ether.packetReceive()); if (pos>0) { // packet received - analyze_get_url((char*)Ethernet::buffer+pos); + handle_web_request((char*)Ethernet::buffer+pos); } wdt_reset(); // reset watchdog timer wdt_timeout = 0; @@ -302,7 +300,8 @@ void do_loop() } } else { m_client = &client; - analyze_get_url(ether_buffer); + ether_buffer[len] = 0; // put a zero at the end of the packet + handle_web_request(ether_buffer); m_client = 0; break; } @@ -802,25 +801,6 @@ const char *log_type_names[] = { "wl" }; -/*void log_statistics(time_t curr_time) { - static byte stat_n = 0; - static ulong stat_lasttime = 0; - // update statistics once 15 minutes - if (curr_time > stat_lasttime + STAT_UPDATE_TIMEOUT) { - stat_lasttime = curr_time; - ulong wp_total = os.water_percent_avg; - wp_total = wp_total * stat_n; - wp_total += os.options[OPTION_WATER_PERCENTAGE].value; - stat_n ++; - os.water_percent_avg = byte(wp_total / stat_n); - // writes every 4*24 times (1 day) - if (stat_n == 96) { - write_log(LOGDATA_WATERLEVEL, curr_time); - stat_n = 0; - } - } -}*/ - // write run record to log on SD card void write_log(byte type, ulong curr_time) { if (!os.options[OPTION_ENABLE_LOGGING].value) return; diff --git a/server.cpp b/server.cpp index a5d2c99a..3bf3ebc8 100644 --- a/server.cpp +++ b/server.cpp @@ -57,7 +57,7 @@ void check_weather(time_t curr_time); void perform_ntp_sync(time_t curr_time); void log_statistics(time_t curr_time); void delete_log(char *name); -void analyze_get_url(char *p); +void handle_ether_request(char *p); void reset_all_stations_immediate(); void reset_all_stations(); void make_logfile_name(char *name); @@ -810,7 +810,7 @@ byte server_json_controller(char *p) } /** Output homepage */ -byte server_home(char *p) +byte server_home() { print_html_standard_header(); bfill.emit_p(PSTR("\n\n\n$F\n\n\n"), - OS_FW_VERSION, os.options[OPTION_IGNORE_PASSWORD].value); + OS_FW_VERSION, os.options[OPTION_IGNORE_PASSWORD]); bfill.emit_p(PSTR("\n\n"), ADDR_NVM_JAVASCRIPTURL); return HTML_OK; } @@ -923,11 +928,11 @@ byte server_change_values(char *p) } if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("re"), true)) { - if (tmp_buffer[0]=='1' && !os.options[OPTION_REMOTE_EXT_MODE].value) { - os.options[OPTION_REMOTE_EXT_MODE].value = 1; + if (tmp_buffer[0]=='1' && !os.options[OPTION_REMOTE_EXT_MODE]) { + os.options[OPTION_REMOTE_EXT_MODE] = 1; os.options_save(); - } else if(tmp_buffer[0]=='0' && os.options[OPTION_REMOTE_EXT_MODE].value) { - os.options[OPTION_REMOTE_EXT_MODE].value = 0; + } else if(tmp_buffer[0]=='0' && os.options[OPTION_REMOTE_EXT_MODE]) { + os.options[OPTION_REMOTE_EXT_MODE] = 0; os.options_save(); } } @@ -996,6 +1001,7 @@ byte server_change_options(char *p) // process option values byte err = 0; byte prev_value; + byte max_value; for (byte oid=0; oid>=2; } #endif - if (v>=0 && v<=os.options[oid].max) { - os.options[oid].value = v; + if (v>=0 && v<=max_value) { + os.options[oid] = v; } else { err = 1; } } } - if (os.options[oid].value != prev_value) { // if value has changed + if (os.options[oid] != max_value) { // if value has changed if (oid==OPTION_TIMEZONE || oid==OPTION_USE_NTP) time_change = true; if (oid>=OPTION_NTP_IP1 && oid<=OPTION_NTP_IP4) time_change = true; if (oid>=OPTION_USE_DHCP && oid<=OPTION_HTTPPORT_1) network_change = true; @@ -1060,7 +1067,7 @@ byte server_change_options(char *p) nvm_write_block(tmp_buffer, (void*)ADDR_NVM_WEATHER_KEY, strlen(tmp_buffer)+1); } // if not using NTP and manually setting time - if (!os.options[OPTION_USE_NTP].value && findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ttt"), true)) { + if (!os.options[OPTION_USE_NTP] && findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ttt"), true)) { unsigned long t; t = atol(tmp_buffer); // before chaging time, reset all stations to avoid messing up with timing @@ -1464,7 +1471,7 @@ void handle_web_request(char *p) if(check_password(dat)==false) { print_json_header(); bfill.emit_p(PSTR("\"$F\":$D}"), - os.options[0].json_str, os.options[0].value); + op_json_names+0, os.options[0]); ret = HTML_OK; } else { ret = (urls[i])(dat); @@ -1504,14 +1511,14 @@ void handle_web_request(char *p) #if defined(ARDUINO) -// NTP sync +/** NTP sync request */ unsigned long getNtpTime() { byte ntpip[4] = { - os.options[OPTION_NTP_IP1].value, - os.options[OPTION_NTP_IP2].value, - os.options[OPTION_NTP_IP3].value, - os.options[OPTION_NTP_IP4].value}; + os.options[OPTION_NTP_IP1], + os.options[OPTION_NTP_IP2], + os.options[OPTION_NTP_IP3], + os.options[OPTION_NTP_IP4]}; uint32_t time; byte tick=0; unsigned long expire; diff --git a/utils.cpp b/utils.cpp index 4af61c96..2434b585 100644 --- a/utils.cpp +++ b/utils.cpp @@ -219,6 +219,16 @@ unsigned int detect_rpi_rev() { #endif +// copy n-character string from program memory with ending 0 +void strncpy_P0(char* dest, const char* src, int n) { + byte i; + for(i=0;i=0 && v<=250 && v != os.options[OPTION_WATER_PERCENTAGE].value) { + if (v>=0 && v<=250 && v != os.options[OPTION_WATER_PERCENTAGE]) { // only save if the value has changed - os.options[OPTION_WATER_PERCENTAGE].value = v; + os.options[OPTION_WATER_PERCENTAGE] = v; os.options_save(); } } @@ -90,9 +90,9 @@ static void getweather_callback(byte status, uint16_t off, uint16_t len) { if (findKeyVal(p, tmp_buffer, TMP_BUFFER_SIZE, PSTR("tz"), true)) { v = atoi(tmp_buffer); if (v>=0 && v<= 96) { - if (v != os.options[OPTION_TIMEZONE].value) { + if (v != os.options[OPTION_TIMEZONE]) { // if timezone changed, save change and force ntp sync - os.options[OPTION_TIMEZONE].value = v; + os.options[OPTION_TIMEZONE] = v; os.options_save(); } } @@ -122,10 +122,10 @@ void GetWeather() { read_from_file(wtopts_filename, tmp, 30); BufferFiller bf = (uint8_t*)tmp_buffer; bf.emit_p(PSTR("$D.py?loc=$E&key=$E&fwv=$D&wto=$S"), - (int) os.options[OPTION_USE_WEATHER].value, + (int) os.options[OPTION_USE_WEATHER], ADDR_NVM_LOCATION, ADDR_NVM_WEATHER_KEY, - (int)os.options[OPTION_FW_VERSION].value, + (int)os.options[OPTION_FW_VERSION], tmp); // copy string to tmp_buffer, replacing all spaces with _ char *src=tmp_buffer+strlen(tmp_buffer); @@ -210,10 +210,10 @@ void GetWeather() { char tmp[100]; read_from_file(wtopts_filename, tmp, 100); bf.emit_p(PSTR("$D.py?loc=$E&key=$E&fwv=$D&wto=$S"), - (int) os.options[OPTION_USE_WEATHER].value, + (int) os.options[OPTION_USE_WEATHER], ADDR_NVM_LOCATION, ADDR_NVM_WEATHER_KEY, - (int)os.options[OPTION_FW_VERSION].value, + (int)os.options[OPTION_FW_VERSION], tmp); char *src=tmp_buffer+strlen(tmp_buffer); From db7d04500785254c6ed8f6ab204a32f8732c4cda Mon Sep 17 00:00:00 2001 From: Ray Date: Thu, 20 Aug 2015 00:18:55 -0400 Subject: [PATCH 30/39] add flow count real-time (flcrt) and flow window real-time (flwrt) variables to /jc --- OpenSprinkler.cpp | 3 ++- OpenSprinkler.h | 3 ++- defines.h | 2 ++ main.cpp | 14 ++++++++++++-- server.cpp | 6 +++--- 5 files changed, 21 insertions(+), 7 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 7a93923f..9487adef 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -38,7 +38,8 @@ byte OpenSprinkler::engage_booster; #endif ulong OpenSprinkler::sensor_lasttime; -ulong OpenSprinkler::flowcount_start; +ulong OpenSprinkler::flowcount_log_start; +ulong OpenSprinkler::flowcount_rt; ulong OpenSprinkler::flowcount_time_ms; ulong OpenSprinkler::raindelay_start_time; byte OpenSprinkler::button_timeout; diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 72a179e9..f443b5bf 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -112,7 +112,8 @@ class OpenSprinkler { // variables for time keeping static ulong sensor_lasttime; // time when the last sensor reading is recorded static ulong flowcount_time_ms;// time stamp when new flow sensor click is received (in milliseconds) - static ulong flowcount_start; // starting flow count + static ulong flowcount_rt; // flow count (for computing real-time flow rate) + static ulong flowcount_log_start; // starting flow count (for logging) static ulong raindelay_start_time; // time when the most recent rain delay started static byte button_timeout; // button timeout static ulong checkwt_lasttime; // time when weather was checked diff --git a/defines.h b/defines.h index 26819608..b15e3bad 100644 --- a/defines.h +++ b/defines.h @@ -47,6 +47,8 @@ #define STATION_ATTR_FILENAME "stns.dat" // station attributes data file #define STATION_SPECIAL_DATA_SIZE 23 +#define FLOWCOUNT_RT_WINDOW 300 // flow count window (for computing real-time flow rate), 5 minutes + /** Station type macro defines */ #define STN_TYPE_STANDARD 0x00 #define STN_TYPE_RF 0x01 diff --git a/main.cpp b/main.cpp index 50d4125c..a4ffc98a 100644 --- a/main.cpp +++ b/main.cpp @@ -627,6 +627,15 @@ void do_loop() } #endif + // real-time flow rate + static ulong flowcount_rt_start = 0; + if (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW) { + if (curr_time % FLOWCOUNT_RT_WINDOW == 0) { + os.flowcount_rt = (flow_count > flowcount_rt_start) ? flow_count - flowcount_rt_start: 0; + flowcount_rt_start = flow_count; + } + } + // perform ntp sync if (curr_time % NTP_SYNC_INTERVAL == 0) os.status.req_ntpsync = 1; perform_ntp_sync(); @@ -637,6 +646,7 @@ void do_loop() // check weather check_weather(); + } #if !defined(ARDUINO) @@ -793,7 +803,7 @@ void schedule_all_stations(ulong curr_time) { os.status.program_busy = 1; // set program busy bit // start flow count if(os.options[OPTION_SENSOR_TYPE] == SENSOR_TYPE_FLOW) { // if flow sensor is connected - os.flowcount_start = flow_count; + os.flowcount_log_start = flow_count; os.sensor_lasttime = curr_time; } } @@ -944,7 +954,7 @@ void write_log(byte type, ulong curr_time) { } else { ulong lvalue; if(type==LOGDATA_FLOWSENSE) { - lvalue = (flow_count>os.flowcount_start)?(flow_count-os.flowcount_start):0; + lvalue = (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0; } else { lvalue = 0; } diff --git a/server.cpp b/server.cpp index c357cb87..c1e43841 100644 --- a/server.cpp +++ b/server.cpp @@ -801,7 +801,7 @@ void server_json_controller_main() { byte bid, sid; ulong curr_time = os.now_tz(); //os.nvm_string_get(ADDR_NVM_LOCATION, tmp_buffer); - bfill.emit_p(PSTR("\"devt\":$L,\"nbrd\":$D,\"en\":$D,\"rd\":$D,\"rs\":$D,\"rdst\":$L,\"flt\":$L,\"flc\":$L," + bfill.emit_p(PSTR("\"devt\":$L,\"nbrd\":$D,\"en\":$D,\"rd\":$D,\"rs\":$D,\"rdst\":$L,\"flcrt\":$L,\"flwrt\":$D," "\"loc\":\"$E\",\"wtkey\":\"$E\",\"sunrise\":$D,\"sunset\":$D,\"eip\":$L,\"lwc\":$L,\"lswc\":$L," "\"lrun\":[$D,$D,$D,$L],\"sbits\":["), curr_time, @@ -810,8 +810,8 @@ void server_json_controller_main() { os.status.rain_delayed, os.status.rain_sensed, os.nvdata.rd_stop_time, - (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW)?os.flowcount_time_ms:0, - (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW)?flow_count:0, + (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW)?os.flowcount_rt:0, + FLOWCOUNT_RT_WINDOW, ADDR_NVM_LOCATION, ADDR_NVM_WEATHER_KEY, os.nvdata.sunrise_time, From b3b47a483ca5e3da90a8e4779fe837701ba5fa5c Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 23 Aug 2015 12:40:46 -0400 Subject: [PATCH 31/39] initial implementation of flow sensor for OSPi/OSBo --- OpenSprinkler.cpp | 4 +- defines.h | 1 - gpio.cpp | 194 ++++++++++++++++++++++++++++++++++++++++------ gpio.h | 2 + main.cpp | 30 ++++--- utils.cpp | 76 +++++++++++++++++- utils.h | 8 ++ 7 files changed, 276 insertions(+), 39 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 9487adef..249202a9 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -921,9 +921,9 @@ void transmit_rfbit(ulong lenH, ulong lenL) { delayMicroseconds(lenL); #else digitalWrite(PIN_RF_DATA, 1); - usleep(lenH); + delayMicrosecondsHard(lenH); digitalWrite(PIN_RF_DATA, 0); - usleep(lenL); + delayMicrosecondsHard(lenL); #endif } diff --git a/defines.h b/defines.h index b15e3bad..a95ba8e8 100644 --- a/defines.h +++ b/defines.h @@ -364,7 +364,6 @@ typedef enum { inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} #define now() time(0) - #define delay(x) {} /** Re-define avr-specific (e.g. PGM) types to use standard types */ #define pgm_read_byte(x) *(x) diff --git a/gpio.cpp b/gpio.cpp index c3d5b727..9d6cb78b 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -29,38 +29,92 @@ #include #include +#include #include #include #include #include +#include +#include +#include +#include + +#define BUFFER_MAX 64 +#define GPIO_MAX 64 + +// GPIO file descriptors +static int sysFds[GPIO_MAX] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +} ; + +// Interrupt service routine functions +static void (*isrFunctions [GPIO_MAX])(void); +static volatile int pinPass = -1 ; +static pthread_mutex_t pinMutex ; + +/** Export gpio pin */ static byte GPIOExport(int pin) { -#define BUFFER_MAX 3 char buffer[BUFFER_MAX]; - ssize_t bytes_written; - int fd; + int fd, len; fd = open("/sys/class/gpio/export", O_WRONLY); - if (-1 == fd) { + if (fd < 0) { DEBUG_PRINTLN("failed to open export for writing"); return 0; } - bytes_written = snprintf(buffer, BUFFER_MAX, "%d", pin); - write(fd, buffer, bytes_written); + len = snprintf(buffer, sizeof(buffer), "%d", pin); + write(fd, buffer, len); + close(fd); + return 1; +} + +/** Unexport gpio pin */ +static byte GPIOUnexport(int pin) { + char buffer[BUFFER_MAX]; + int fd, len; + + fd = open("/sys/class/gpio/unexport", O_WRONLY); + if (fd < 0) { + DEBUG_PRINTLN("failed to open unexport for writing"); + return 0; + } + + len = snprintf(buffer, sizeof(buffer), "%d", pin); + write(fd, buffer, len); close(fd); return 1; +} + +/** Set interrupt edge mode */ +static byte GPIOSetEdge(int pin, const char *edge) { + char path[BUFFER_MAX]; + int fd, len; + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/edge", pin); + + fd = open(path, O_WRONLY); + if (fd < 0) { + DEBUG_PRINTLN("failed to open gpio edge for writing"); + return 0; + } + write(fd, edge, strlen(edge)+1); + close(fd); + return 1; } +/** Set pin mode, in or out */ void pinMode(int pin, byte mode) { - static const char s_directions_str[] = "in\0out"; + static const char dir_str[] = "in\0out"; -#define DIRECTION_MAX 35 - char path[DIRECTION_MAX]; + char path[BUFFER_MAX]; int fd; - snprintf(path, DIRECTION_MAX, "/sys/class/gpio/gpio%d/direction", pin); + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/direction", pin); struct stat st; if(stat(path, &st)) { @@ -68,12 +122,12 @@ void pinMode(int pin, byte mode) { } fd = open(path, O_WRONLY); - if (-1 == fd) { + if (fd < 0) { DEBUG_PRINTLN("failed to open gpio direction for writing"); return; } - if (-1 == write(fd, &s_directions_str[INPUT == mode ? 0 : 3], INPUT == mode ? 2 : 3)) { + if (-1 == write(fd, &dir_str[INPUT==mode?0:3], INPUT==mode?2:3)) { DEBUG_PRINTLN("failed to set direction"); return; } @@ -82,42 +136,43 @@ void pinMode(int pin, byte mode) { return; } +/** Read digital value */ byte digitalRead(int pin) { -#define VALUE_MAX 30 - char path[VALUE_MAX]; + char path[BUFFER_MAX]; char value_str[3]; int fd; - snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin); + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); fd = open(path, O_RDONLY); - if (-1 == fd) { + if (fd < 0) { DEBUG_PRINTLN("failed to open gpio value for reading"); return 0; } - if (-1 == read(fd, value_str, 3)) { + if (read(fd, value_str, 3) < 0) { DEBUG_PRINTLN("failed to read value"); return 0; } close(fd); - return(atoi(value_str)); + return atoi(value_str); } +/** Write digital value */ void digitalWrite(int pin, byte value) { - static const char s_values_str[] = "01"; + static const char value_str[] = "01"; - char path[VALUE_MAX]; + char path[BUFFER_MAX]; int fd; - snprintf(path, VALUE_MAX, "/sys/class/gpio/gpio%d/value", pin); + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); fd = open(path, O_WRONLY); - if (-1 == fd) { + if (fd < 0) { DEBUG_PRINTLN("failed to open gpio value for writing"); return; } - if (1 != write(fd, &s_values_str[LOW == value ? 0 : 1], 1)) { + if (1 != write(fd, &value_str[LOW==value?0:1], 1)) { DEBUG_PRINTLN("failed to write value"); DEBUG_PRINTLN(pin); return; @@ -126,10 +181,103 @@ void digitalWrite(int pin, byte value) { close(fd); } +static int HiPri (const int pri) { + struct sched_param sched ; + + memset (&sched, 0, sizeof(sched)) ; + + if (pri > sched_get_priority_max (SCHED_RR)) + sched.sched_priority = sched_get_priority_max (SCHED_RR) ; + else + sched.sched_priority = pri ; + + return sched_setscheduler (0, SCHED_RR, &sched) ; +} + +static int waitForInterrupt (int pin, int mS) +{ + int fd, x ; + uint8_t c ; + struct pollfd polls ; + + if((fd=sysFds[pin]) < 0) + return -2; + + polls.fd = fd ; + polls.events = POLLPRI ; // Urgent data! + + x = poll (&polls, 1, mS) ; +// Do a dummy read to clear the interrupt +// A one character read appars to be enough. +// Followed by a seek to reset it. + + (void)read (fd, &c, 1); + lseek (fd, 0, SEEK_SET); + + return x ; +} + +static void *interruptHandler (void *arg) { + int myPin ; + + (void) HiPri (55) ; // Only effective if we run as root + + myPin = pinPass ; + pinPass = -1 ; + + for (;;) + if (waitForInterrupt (myPin, -1) > 0) + isrFunctions[myPin]() ; + + return NULL ; +} + +/** Attach an interrupt function to pin */ +void attachInterrupt(int pin, const char* mode, void (*isr)(void)) { + if((pin<0)||(pin>GPIO_MAX)) { + DEBUG_PRINTLN("pin out of range"); + return; + } + + // set pin to INPUT mode and set interrupt edge mode + pinMode(pin, INPUT); + GPIOSetEdge(pin, mode); + + char path[BUFFER_MAX]; + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); + int fd; + + // open gpio file + if(sysFds[pin]==-1) { + if((sysFds[pin]=open(path, O_RDWR))<0) { + DEBUG_PRINTLN("failed to open gpio value for reading"); + return; + } + } + + int count, i; + char c; + // clear any pending interrupts + ioctl (sysFds[pin], FIONREAD, &count) ; + for (i=0; i #include "etherport.h" #include "server.h" +#include "gpio.h" char ether_buffer[ETHER_BUFFER_SIZE]; EthernetServer *m_server = 0; EthernetClient *m_client = 0; @@ -62,6 +63,15 @@ BufferFiller bfill; // buffer filler OpenSprinkler os; // OpenSprinkler object ProgramData pd; // ProgramdData object +volatile ulong flow_count = 0; +/** Flow sensor interrupt service routine */ +void flow_isr() { + ulong curr = millis(); + if(curr-os.flowcount_time_ms < 50) return; // debounce threshold: 50ms + flow_count++; + os.flowcount_time_ms = curr; +} + #if defined(ARDUINO) // ====== UI defines ====== static char ui_anim_chars[3] = {'.', 'o', 'O'}; @@ -178,15 +188,6 @@ void ui_state_machine() { } } -volatile ulong flow_count = 0; -/** Flow sensor interrupt service routine */ -void flow_isr() { - ulong curr = millis(); - if(curr-os.flowcount_time_ms < 50) return; // debounce threshold: 50ms - flow_count++; - os.flowcount_time_ms = curr; -} - // ====================== // Setup Function // ====================== @@ -249,13 +250,18 @@ ISR(WDT_vect) } #else -volatile ulong flow_count = 0; void do_setup() { + initialiseEpoch(); // initialize time reference for millis() and micros() os.begin(); // OpenSprinkler init os.options_setup(); // Setup options pd.init(); // ProgramData init + + if (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW) { + attachInterrupt(PIN_FLOWSENSOR, "falling", flow_isr); + } + if (os.start_network()) { // initialize network DEBUG_PRINTLN("network established."); os.status.network_fails = 0; @@ -627,7 +633,7 @@ void do_loop() } #endif - // real-time flow rate + // real-time flow count static ulong flowcount_rt_start = 0; if (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW) { if (curr_time % FLOWCOUNT_RT_WINDOW == 0) { @@ -650,7 +656,7 @@ void do_loop() } #if !defined(ARDUINO) - usleep(1000); // For OSPI/OSBO/LINUX, sleep 1 ms to minimize CPU usage + delay(1); // For OSPI/OSBO/LINUX, sleep 1 ms to minimize CPU usage #endif } diff --git a/utils.cpp b/utils.cpp index 2434b585..488796d0 100644 --- a/utils.cpp +++ b/utils.cpp @@ -80,7 +80,6 @@ void remove_file(const char *name) { } #else // RPI/BBB/LINUX - void nvm_read_block(void *dst, const void *src, int len) { FILE *fp = fopen(NVM_FILENAME, "rb"); if(fp) { @@ -217,6 +216,81 @@ unsigned int detect_rpi_rev() { } #endif +void delay(ulong howLong) +{ + struct timespec sleeper, dummy ; + + sleeper.tv_sec = (time_t)(howLong / 1000) ; + sleeper.tv_nsec = (long)(howLong % 1000) * 1000000 ; + + nanosleep (&sleeper, &dummy) ; +} + +void delayMicrosecondsHard (ulong howLong) +{ + struct timeval tNow, tLong, tEnd ; + + gettimeofday (&tNow, NULL) ; + tLong.tv_sec = howLong / 1000000 ; + tLong.tv_usec = howLong % 1000000 ; + timeradd (&tNow, &tLong, &tEnd) ; + + while (timercmp (&tNow, &tEnd, <)) + gettimeofday (&tNow, NULL) ; +} + +void delayMicroseconds (ulong howLong) +{ + struct timespec sleeper ; + unsigned int uSecs = howLong % 1000000 ; + unsigned int wSecs = howLong / 1000000 ; + + /**/ if (howLong == 0) + return ; + else if (howLong < 100) + delayMicrosecondsHard (howLong) ; + else + { + sleeper.tv_sec = wSecs ; + sleeper.tv_nsec = (long)(uSecs * 1000L) ; + nanosleep (&sleeper, NULL) ; + } +} + +static uint64_t epochMilli, epochMicro ; + +void initialiseEpoch() +{ + struct timeval tv ; + + gettimeofday (&tv, NULL) ; + epochMilli = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; + epochMicro = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)(tv.tv_usec) ; +} + +ulong millis (void) +{ + struct timeval tv ; + uint64_t now ; + + gettimeofday (&tv, NULL) ; + now = (uint64_t)tv.tv_sec * (uint64_t)1000 + (uint64_t)(tv.tv_usec / 1000) ; + + return (uint32_t)(now - epochMilli) ; +} + +ulong micros (void) +{ + struct timeval tv ; + uint64_t now ; + + gettimeofday (&tv, NULL) ; + now = (uint64_t)tv.tv_sec * (uint64_t)1000000 + (uint64_t)tv.tv_usec ; + + return (uint32_t)(now - epochMicro) ; +} + + #endif // copy n-character string from program memory with ending 0 diff --git a/utils.h b/utils.h index 407cf967..8ac8d1e5 100644 --- a/utils.h +++ b/utils.h @@ -29,6 +29,8 @@ #else // headers for RPI/BBB #include #include + #include + #endif #include "defines.h" @@ -53,6 +55,12 @@ void remove_file(const char *name); byte nvm_read_byte(const byte *p); void nvm_write_byte(const byte *p, byte v); char* get_runtime_path(); + void delay(ulong ms); + void delayMicroseconds(ulong us); + void delayMicrosecondsHard(ulong us); + ulong millis(); + ulong micros(); + void initialiseEpoch(); #if defined(OSPI) unsigned int detect_rpi_rev(); #endif From 79ba8c95ce513618c9bbd14283fc1abbba64e600 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 23 Aug 2015 12:54:23 -0400 Subject: [PATCH 32/39] add -lpthread to build script --- build.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.sh b/build.sh index 1599c69e..9b0b8466 100755 --- a/build.sh +++ b/build.sh @@ -11,11 +11,11 @@ done echo "Building OpenSprinkler..." if [ "$1" == "demo" ]; then - g++ -o OpenSprinkler -DDEMO -m32 main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp + g++ -o OpenSprinkler -DDEMO -m32 main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp -lpthread elif [ "$1" == "osbo" ]; then - g++ -o OpenSprinkler -DOSBO main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp + g++ -o OpenSprinkler -DOSBO main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp -lpthread else - g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp + g++ -o OpenSprinkler -DOSPI main.cpp OpenSprinkler.cpp program.cpp server.cpp utils.cpp weather.cpp gpio.cpp etherport.cpp -lpthread fi if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.launch ] && [ ! -f /etc/init.d/OpenSprinkler.sh ]; then From b6a80322a943f7995b9d06c4332b411673be86d7 Mon Sep 17 00:00:00 2001 From: Ray Date: Fri, 28 Aug 2015 02:18:34 -0400 Subject: [PATCH 33/39] fix a bug where the run-time keeping code was not checking if station_qid == 255; change /jc to output real-time flow sensor variables only when sensor type if flow sensor; change flow count window to 30 seconds to make the real-time flow rate updates quicker --- defines.h | 2 +- main.cpp | 1 + server.cpp | 22 +++++++++++++--------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/defines.h b/defines.h index a95ba8e8..4e7782fd 100644 --- a/defines.h +++ b/defines.h @@ -47,7 +47,7 @@ #define STATION_ATTR_FILENAME "stns.dat" // station attributes data file #define STATION_SPECIAL_DATA_SIZE 23 -#define FLOWCOUNT_RT_WINDOW 300 // flow count window (for computing real-time flow rate), 5 minutes +#define FLOWCOUNT_RT_WINDOW 30 // flow count window (for computing real-time flow rate), 30 seconds /** Station type macro defines */ #define STN_TYPE_STANDARD 0x00 diff --git a/main.cpp b/main.cpp index ef9b7ca1..b3295796 100644 --- a/main.cpp +++ b/main.cpp @@ -476,6 +476,7 @@ void do_loop() // skip master station if (os.status.mas == sid+1) continue; if (os.status.mas2== sid+1) continue; + if (pd.station_qid[sid]==255) continue; q = pd.queue + pd.station_qid[sid]; // check if this station is scheduled, either running or waiting to run diff --git a/server.cpp b/server.cpp index c1e43841..50322f7b 100644 --- a/server.cpp +++ b/server.cpp @@ -801,17 +801,15 @@ void server_json_controller_main() { byte bid, sid; ulong curr_time = os.now_tz(); //os.nvm_string_get(ADDR_NVM_LOCATION, tmp_buffer); - bfill.emit_p(PSTR("\"devt\":$L,\"nbrd\":$D,\"en\":$D,\"rd\":$D,\"rs\":$D,\"rdst\":$L,\"flcrt\":$L,\"flwrt\":$D," + bfill.emit_p(PSTR("\"devt\":$L,\"nbrd\":$D,\"en\":$D,\"rd\":$D,\"rs\":$D,\"rdst\":$L," "\"loc\":\"$E\",\"wtkey\":\"$E\",\"sunrise\":$D,\"sunset\":$D,\"eip\":$L,\"lwc\":$L,\"lswc\":$L," - "\"lrun\":[$D,$D,$D,$L],\"sbits\":["), + "\"lrun\":[$D,$D,$D,$L],"), curr_time, os.nboards, os.status.enabled, os.status.rain_delayed, os.status.rain_sensed, os.nvdata.rd_stop_time, - (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW)?os.flowcount_rt:0, - FLOWCOUNT_RT_WINDOW, ADDR_NVM_LOCATION, ADDR_NVM_WEATHER_KEY, os.nvdata.sunrise_time, @@ -823,6 +821,17 @@ void server_json_controller_main() { pd.lastrun.program, pd.lastrun.duration, pd.lastrun.endtime); + +#if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) + if(os.status.has_curr_sense) { + bfill.emit_p(PSTR("\"curr\":$D,"), os.read_current()); + } +#endif + if(os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW) { + bfill.emit_p(PSTR("\"flcrt\":$L,\"flwrt\":$D,"), os.flowcount_rt, FLOWCOUNT_RT_WINDOW); + } + + bfill.emit_p(PSTR("\"sbits\":[")); // print sbits for(bid=0;bid Date: Sat, 29 Aug 2015 01:25:41 -0400 Subject: [PATCH 34/39] fix process_dynamic_events function; optimize detect_exp function; add initial code to calibrate RF timing for RPi/BBB --- OpenSprinkler.cpp | 47 +++++++++++++++++++++++++++++++------------ OpenSprinkler.h | 3 +++ gpio.cpp | 51 ++++++++++++++++++++++++++++------------------- gpio.h | 5 +++++ main.cpp | 25 +++++++++-------------- 5 files changed, 81 insertions(+), 50 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 249202a9..f46056ad 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -730,27 +730,27 @@ int OpenSprinkler::detect_exp() { #if defined(ARDUINO) unsigned int v = analogRead(PIN_EXP_SENSE); // OpenSprinkler uses voltage divider to detect expansion boards - // Master controller has a 1.5K pull-up; + // Master controller has a 1.6K pull-up; // each expansion board (8 stations) has 10K pull-down connected in parallel; // so the exact ADC value for n expansion boards is: - // ADC = 1024 * 10 / (10 + 1.5 * n) + // ADC = 1024 * 10 / (10 + 1.6 * n) // For 0, 1, 2, 3, 4, 5, 6 expansion boards, the ADC values are: - // 1024, 890, 787, 706, 640, 585, 539 + // 1024, 882, 775, 691, 624, 568, 522 // Actual threshold is taken as the midpoint between, to account for errors int n = -1; - if (v > 957) { // 0 + if (v > 953) { // 0 n = 0; - } else if (v > 838) { // 1 + } else if (v > 828) { // 1 n = 1; - } else if (v > 746) { // 2 + } else if (v > 733) { // 2 n = 2; - } else if (v > 673) { // 3 + } else if (v > 657) { // 3 n = 3; - } else if (v > 612) { // 4 + } else if (v > 596) { // 4 n = 4; - } else if (v > 562) { // 5 + } else if (v > 545) { // 5 n = 5; - } else if (v > 520) { // 6 + } else if (v > 502) { // 6 n = 6; } else { // cannot determine } @@ -912,6 +912,21 @@ void OpenSprinkler::clear_all_station_bits() { } } +#if !defined(ARDUINO) +int rf_gpio_fd = -1; +void OpenSprinkler::calibrate_rf_timing() { + char code0[] = "0000000000000000"; // signal with 0 timing + char code1[] = "0000000000000100"; // signal with 0x100 timing + ulong start = micros(); + switch_rfstation((byte*)code0, 0); + DEBUG_PRINTLN(micros()-start); + start = micros(); + switch_rfstation((byte*)code1, 0); + DEBUG_PRINTLN(micros()-start); +} + +#endif + /** Transmit one RF signal bit */ void transmit_rfbit(ulong lenH, ulong lenL) { #if defined(ARDUINO) @@ -920,9 +935,9 @@ void transmit_rfbit(ulong lenH, ulong lenL) { PORT_RF &=~(1<>5); // due to internal call delay, scale time down to 97% + send_rfsignal(turnon ? on : off, length); #else length = (length>>2)+(length>>3); // on RPi and BBB, there is even more overhead, scale to 37.5% -#endif + // pre-open gpio file to minimize overhead + int rf_gpio_fd = gpio_fd_open(PIN_RF_DATA); send_rfsignal(turnon ? on : off, length); + gpio_fd_close(rf_gpio_fd); + rf_gpio_fd = -1; +#endif + } /** Callback function for remote station calls */ diff --git a/OpenSprinkler.h b/OpenSprinkler.h index f443b5bf..603c2556 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -131,6 +131,9 @@ class OpenSprinkler { // -- station names and attributes static void get_station_name(byte sid, char buf[]); // get station name static void set_station_name(byte sid, char buf[]); // set station name +#if !defined(ARDUINO) + static void calibrate_rf_timing(); // for RPi/BBB, we calibrate the RF timing to account for pin switching overhead +#endif static uint16_t parse_rfstation_code(byte *code, ulong *on, ulong *off); // parse rf code into on/off/time sections static void switch_rfstation(byte *code, bool turnon); // switch rf station static void switch_remotestation(byte *code, bool turnon); // switch remote station diff --git a/gpio.cpp b/gpio.cpp index 9d6cb78b..3bcc63b5 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -27,7 +27,6 @@ #elif defined(OSPI) || defined(OSBO) -#include #include #include #include @@ -37,7 +36,6 @@ #include #include #include -#include #define BUFFER_MAX 64 #define GPIO_MAX 64 @@ -136,16 +134,31 @@ void pinMode(int pin, byte mode) { return; } -/** Read digital value */ -byte digitalRead(int pin) { +/** Open file for digital pin */ +int gpio_fd_open(int pin, int mode) { char path[BUFFER_MAX]; - char value_str[3]; int fd; snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); - fd = open(path, O_RDONLY); + fd = open(path, mode); + if (fd < 0) { + DEBUG_PRINTLN("failed to open gpio"); + return -1; + } + return fd; +} + +/** Close file */ +void gpio_fd_close(int fd) { + close(fd); +} + +/** Read digital value */ +byte digitalRead(int pin) { + char value_str[3]; + + int fd = gpio_fd_open(pin, O_RDONLY); if (fd < 0) { - DEBUG_PRINTLN("failed to open gpio value for reading"); return 0; } @@ -158,26 +171,22 @@ byte digitalRead(int pin) { return atoi(value_str); } -/** Write digital value */ -void digitalWrite(int pin, byte value) { +/** Write digital value given file descriptor */ +void gpio_write(int fd, byte value) { static const char value_str[] = "01"; - char path[BUFFER_MAX]; - int fd; - - snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); - fd = open(path, O_WRONLY); - if (fd < 0) { - DEBUG_PRINTLN("failed to open gpio value for writing"); - return; + if (1 != write(fd, &value_str[LOW==value?0:1], 1)) { + DEBUG_PRINT("failed to write value on pin "); } +} - if (1 != write(fd, &value_str[LOW==value?0:1], 1)) { - DEBUG_PRINTLN("failed to write value"); - DEBUG_PRINTLN(pin); +/** Write digital value */ +void digitalWrite(int pin, byte value) { + int fd = gpio_fd_open(pin); + if (fd < 0) { return; } - + gpio_write(fd, value); close(fd); } diff --git a/gpio.h b/gpio.h index d6151c5e..db411fe3 100644 --- a/gpio.h +++ b/gpio.h @@ -27,6 +27,8 @@ #else +#include +#include #define OUTPUT 0 #define INPUT 1 #define HIGH 1 @@ -34,6 +36,9 @@ void pinMode(int pin, byte mode); void digitalWrite(int pin, byte value); +int gpio_fd_open(int pin, int mode = O_WRONLY); +void gpio_fd_close(int fd); +void gpio_write(int fd, byte value); byte digitalRead(int pin); // mode can be any of 'rising', 'falling', 'both' void attachInterrupt(int pin, const char* mode, void (*isr)(void)); diff --git a/main.cpp b/main.cpp index b3295796..7f8053ea 100644 --- a/main.cpp +++ b/main.cpp @@ -724,14 +724,14 @@ void turn_off_station(byte sid, ulong curr_time) { void process_dynamic_events(ulong curr_time) { // check if rain is detected bool rain = false; + bool en = os.status.enabled ? true : false; if (os.status.rain_delayed || (os.status.rain_sensed && os.options[OPTION_SENSOR_TYPE] == SENSOR_TYPE_RAIN)) { rain = true; } - byte sid, s, bid, rbits, sbits; + byte sid, s, bid, qid, rbits; for(bid=0;bid 0) { // if station is currently not running but is waiting to run - - // reset program data variables - pd.scheduled_start_time[sid] = 0; - pd.scheduled_stop_time[sid] = 0; - pd.scheduled_program_index[sid] = 0; - } - }*/ + qid = pd.station_qid[sid]; + if(qid==255) continue; + RuntimeQueueStruct *q = pd.queue + qid; + + if ((q->pid<99) && (!en || (rain && !(rbits&(1< Date: Sat, 29 Aug 2015 01:56:46 -0400 Subject: [PATCH 35/39] turns out no need for calibration: with pre-opening gpio file descriptor, the signal timing is quite good already --- OpenSprinkler.cpp | 15 ++------------- OpenSprinkler.h | 3 --- utils.cpp | 1 - 3 files changed, 2 insertions(+), 17 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index f46056ad..e7d53737 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -914,17 +914,6 @@ void OpenSprinkler::clear_all_station_bits() { #if !defined(ARDUINO) int rf_gpio_fd = -1; -void OpenSprinkler::calibrate_rf_timing() { - char code0[] = "0000000000000000"; // signal with 0 timing - char code1[] = "0000000000000100"; // signal with 0x100 timing - ulong start = micros(); - switch_rfstation((byte*)code0, 0); - DEBUG_PRINTLN(micros()-start); - start = micros(); - switch_rfstation((byte*)code1, 0); - DEBUG_PRINTLN(micros()-start); -} - #endif /** Transmit one RF signal bit */ @@ -974,9 +963,9 @@ void OpenSprinkler::switch_rfstation(byte *code, bool turnon) { length = length - (length>>5); // due to internal call delay, scale time down to 97% send_rfsignal(turnon ? on : off, length); #else - length = (length>>2)+(length>>3); // on RPi and BBB, there is even more overhead, scale to 37.5% + //length = (length>>2)+(length>>3); // on RPi and BBB, there is even more overhead, scale to 37.5% // pre-open gpio file to minimize overhead - int rf_gpio_fd = gpio_fd_open(PIN_RF_DATA); + rf_gpio_fd = gpio_fd_open(PIN_RF_DATA); send_rfsignal(turnon ? on : off, length); gpio_fd_close(rf_gpio_fd); rf_gpio_fd = -1; diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 603c2556..f443b5bf 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -131,9 +131,6 @@ class OpenSprinkler { // -- station names and attributes static void get_station_name(byte sid, char buf[]); // get station name static void set_station_name(byte sid, char buf[]); // set station name -#if !defined(ARDUINO) - static void calibrate_rf_timing(); // for RPi/BBB, we calibrate the RF timing to account for pin switching overhead -#endif static uint16_t parse_rfstation_code(byte *code, ulong *on, ulong *off); // parse rf code into on/off/time sections static void switch_rfstation(byte *code, bool turnon); // switch rf station static void switch_remotestation(byte *code, bool turnon); // switch remote station diff --git a/utils.cpp b/utils.cpp index 488796d0..8bc49d9b 100644 --- a/utils.cpp +++ b/utils.cpp @@ -74,7 +74,6 @@ void remove_file(const char *name) { char *fn = tmp_buffer+TMP_BUFFER_SIZE-12; strcpy_P(fn, name); sd.chdir("/"); - DEBUG_PRINTLN(fn); if (!sd.exists(fn)) return; sd.remove(fn); } From d1a4842b54ef7f2369d945e7d42f51d887e64762 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 1 Sep 2015 02:24:01 -0400 Subject: [PATCH 36/39] fix negative log duration time issue; better sensor initialization to allow changing sensor type on the fly; fix issue with /ja stalling on a large number of stations (missing delay(1) in json_programs_main()), add missing gpio defines for demo --- OpenSprinkler.cpp | 8 ++++++-- gpio.cpp | 3 +++ main.cpp | 12 +++--------- server.cpp | 7 +++++-- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index e7d53737..1ea3fa97 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -458,6 +458,7 @@ void OpenSprinkler::lcd_start() { } #endif +extern void flow_isr(); /** Initialize pins, controller variables, LCD */ void OpenSprinkler::begin() { @@ -492,12 +493,17 @@ void OpenSprinkler::begin() { // Rain sensor port set up pinMode(PIN_RAINSENSOR, INPUT); + // Set up sensors #if defined(ARDUINO) digitalWrite(PIN_RAINSENSOR, HIGH); // enabled internal pullup on rain sensor + attachInterrupt(PIN_FLOWSENSOR_INT, flow_isr, FALLING); #else // OSPI and OSBO use external pullups + attachInterrupt(PIN_FLOWSENSOR, "falling", flow_isr); #endif + + // Default controller status variables // Static variables are assigned 0 by default // so only need to initialize non-zero ones @@ -960,10 +966,8 @@ void OpenSprinkler::switch_rfstation(byte *code, bool turnon) { ulong on, off; uint16_t length = parse_rfstation_code(code, &on, &off); #if defined(ARDUINO) - length = length - (length>>5); // due to internal call delay, scale time down to 97% send_rfsignal(turnon ? on : off, length); #else - //length = (length>>2)+(length>>3); // on RPi and BBB, there is even more overhead, scale to 37.5% // pre-open gpio file to minimize overhead rf_gpio_fd = gpio_fd_open(PIN_RF_DATA); send_rfsignal(turnon ? on : off, length); diff --git a/gpio.cpp b/gpio.cpp index 3bcc63b5..bcab4824 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -288,5 +288,8 @@ void pinMode(int pin, byte mode) {} void digitalWrite(int pin, byte value) {} byte digitalRead(int pin) {return 0;} void attachInterrupt(int pin, const char* mode, void (*isr)(void)) {} +int gpio_fd_open(int pin, int mode) {return 0;} +void gpio_fd_close(int fd) {} +void gpio_write(int fd, byte value) {} #endif diff --git a/main.cpp b/main.cpp index 7f8053ea..ad4047a1 100644 --- a/main.cpp +++ b/main.cpp @@ -66,6 +66,7 @@ ProgramData pd; // ProgramdData object volatile ulong flow_count = 0; /** Flow sensor interrupt service routine */ void flow_isr() { + if(os.options[OPTION_SENSOR_TYPE]!=SENSOR_TYPE_FLOW) return; ulong curr = millis(); if(curr-os.flowcount_time_ms < 50) return; // debounce threshold: 50ms flow_count++; @@ -217,10 +218,6 @@ void do_setup() { /* Enable the WD interrupt (note no reset). */ WDTCSR |= _BV(WDIE); - // Set up flow sensor ISR - if (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW) { - attachInterrupt(PIN_FLOWSENSOR_INT, flow_isr, FALLING); - } if (os.start_network()) { // initialize network os.status.network_fails = 0; } else { @@ -258,10 +255,6 @@ void do_setup() { pd.init(); // ProgramData init - if (os.options[OPTION_SENSOR_TYPE]==SENSOR_TYPE_FLOW) { - attachInterrupt(PIN_FLOWSENSOR, "falling", flow_isr); - } - if (os.start_network()) { // initialize network DEBUG_PRINTLN("network established."); os.status.network_fails = 0; @@ -950,7 +943,8 @@ void write_log(byte type, ulong curr_time) { strcat_P(tmp_buffer, PSTR(",")); itoa(pd.lastrun.station, tmp_buffer+strlen(tmp_buffer), 10); strcat_P(tmp_buffer, PSTR(",")); - itoa(pd.lastrun.duration, tmp_buffer+strlen(tmp_buffer), 10); + // duration is unsigned integer + ultoa((ulong)pd.lastrun.duration, tmp_buffer+strlen(tmp_buffer), 10); } else { ulong lvalue; if(type==LOGDATA_FLOWSENSE) { diff --git a/server.cpp b/server.cpp index 50322f7b..0604a01e 100644 --- a/server.cpp +++ b/server.cpp @@ -780,6 +780,7 @@ void server_json_programs_main() { } } bfill.emit_p(PSTR("]}")); + delay(1); } /** Output program data */ @@ -1041,12 +1042,12 @@ byte server_change_options(char *p) } } } - if (os.options[oid] != max_value) { // if value has changed + if (os.options[oid] != prev_value) { // if value has changed if (oid==OPTION_TIMEZONE || oid==OPTION_USE_NTP) time_change = true; if (oid>=OPTION_NTP_IP1 && oid<=OPTION_NTP_IP4) time_change = true; if (oid>=OPTION_USE_DHCP && oid<=OPTION_HTTPPORT_1) network_change = true; if (oid==OPTION_DEVICE_ID) network_change = true; - if (oid==OPTION_USE_WEATHER) weather_change = true; + if (oid==OPTION_USE_WEATHER) weather_change = true; } } @@ -1106,6 +1107,7 @@ byte server_change_options(char *p) // network related options have changed // this would require a restart to take effect } + return HTML_SUCCESS; } @@ -1380,6 +1382,7 @@ byte server_json_all(char *p) { bfill.emit_p(PSTR(",\"stations\":{")); server_json_stations_main(); bfill.emit_p(PSTR("}")); + delay(1); return HTML_OK; } From e334008011baddc31c2e9d10c357a542cd315b5d Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 13 Sep 2015 15:39:29 -0400 Subject: [PATCH 37/39] modify the current sensing code to support future OS AC controllers that have current sensing ability --- OpenSprinkler.cpp | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 1ea3fa97..dfdfc2f7 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -551,13 +551,13 @@ void OpenSprinkler::begin() { pinMode(PIN_BOOST_EN, OUTPUT); digitalWrite(PIN_BOOST_EN, LOW); - - // detect if current sensing pin is present - pinMode(PIN_CURR_DIGITAL, INPUT); - digitalWrite(PIN_CURR_DIGITAL, HIGH); // enable internal pullup - status.has_curr_sense = digitalRead(PIN_CURR_DIGITAL) ? 0 : 1; - digitalWrite(PIN_CURR_DIGITAL, LOW); } + + // detect if current sensing pin is present + pinMode(PIN_CURR_DIGITAL, INPUT); + digitalWrite(PIN_CURR_DIGITAL, HIGH); // enable internal pullup + status.has_curr_sense = digitalRead(PIN_CURR_DIGITAL) ? 0 : 1; + digitalWrite(PIN_CURR_DIGITAL, LOW); #endif lcd_start(); @@ -716,14 +716,21 @@ void OpenSprinkler::rainsensor_status() { } /** Read current sensing value - * OpenSprinkler DC has a 0.2 ohm current sensing resistor. + * OpenSprinkler has a 0.2 ohm current sensing resistor. * Therefore the conversion from analog reading to milli-amp is: * (r/1024)*3.3*1000/0.2 + * Newer AC controller has a 0.2 ohm curent sensing resistor + * with op-amp to sense the peak current. Therefore the actual + * current is discounted by 0.707 */ #if defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega1284__) uint16_t OpenSprinkler::read_current() { if(status.has_curr_sense) { - return (uint16_t)(analogRead(PIN_CURR_SENSE) * 16.11); + if (hw_type == HW_TYPE_DC) { + return (uint16_t)(analogRead(PIN_CURR_SENSE) * 16.11); + } else { + return (uint16_t)(analogRead(PIN_CURR_SENSE) * 11.39); + } } else { return 0; } From 7cb9528a88ef0ea0a753b1509d1cafdfa6156d56 Mon Sep 17 00:00:00 2001 From: Ray Date: Sun, 13 Sep 2015 18:49:51 -0400 Subject: [PATCH 38/39] fixed a bug that causes write_log and read_from_file functions to corrupt the weather query buffer; optimize PWM frequency for LCD display --- OpenSprinkler.cpp | 8 +++----- main.cpp | 1 - utils.cpp | 7 +++---- weather.cpp | 9 ++++++--- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index dfdfc2f7..5d908474 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -264,7 +264,7 @@ byte OpenSprinkler::options[] = { 1, // device enable 0, // 1: ignore password; 0: use password 0, // device id - 140,// lcd contrast + 150,// lcd contrast 100,// lcd backlight 15, // lcd dimming 80, // boost time (only valid to DC and LATCH type) @@ -442,12 +442,10 @@ void OpenSprinkler::lcd_start() { if (lcd.type() == LCD_STD) { // this is standard 16x2 LCD // set PWM frequency for adjustable LCD backlight and contrast -#if OS_HW_VERSION==(OS_HW_VERSION_BASE+20) // 8MHz +#if OS_HW_VERSION==(OS_HW_VERSION_BASE+20) || OS_HW_VERSION==(OS_HW_VERSION_BASE+21) // 8MHz and 12MHz TCCR1B = 0x01; -#elif OS_HW_VERSION==(OS_HW_VERSION_BASE+21) // 12MHz - TCCR1B = 0x02; // increase division factor for faster clock #else // 16MHz - TCCR1B = 0x03; // increase division factor for faster clock + TCCR1B = 0x02; // increase division factor for faster clock #endif // turn on LCD backlight and contrast lcd_set_brightness(); diff --git a/main.cpp b/main.cpp index ad4047a1..66a8527d 100644 --- a/main.cpp +++ b/main.cpp @@ -673,7 +673,6 @@ void check_weather() { if (!os.checkwt_lasttime || (ntz > os.checkwt_lasttime + CHECK_WEATHER_TIMEOUT)) { os.checkwt_lasttime = ntz; GetWeather(); - write_log(LOGDATA_WATERLEVEL, ntz); } } diff --git a/utils.cpp b/utils.cpp index 8bc49d9b..9ccbdd5d 100644 --- a/utils.cpp +++ b/utils.cpp @@ -24,7 +24,6 @@ #include "utils.h" #include "OpenSprinkler.h" extern OpenSprinkler os; -extern char tmp_buffer[]; #if defined(ARDUINO) // AVR #include @@ -34,7 +33,7 @@ extern SdFat sd; void write_to_file(const char *name, const char *data, int size, int pos, bool trunc) { if (!os.status.has_sd) return; - char *fn = tmp_buffer+TMP_BUFFER_SIZE-12; + char fn[12]; strcpy_P(fn, name); sd.chdir("/"); SdFile file; @@ -52,7 +51,7 @@ void write_to_file(const char *name, const char *data, int size, int pos, bool t bool read_from_file(const char *name, char *data, int maxsize, int pos) { if (!os.status.has_sd) { data[0]=0; return false; } - char *fn = tmp_buffer+TMP_BUFFER_SIZE-12; + char fn[12]; strcpy_P(fn, name); sd.chdir("/"); SdFile file; @@ -71,7 +70,7 @@ bool read_from_file(const char *name, char *data, int maxsize, int pos) { void remove_file(const char *name) { if (!os.status.has_sd) return; - char *fn = tmp_buffer+TMP_BUFFER_SIZE-12; + char fn[12]; strcpy_P(fn, name); sd.chdir("/"); if (!sd.exists(fn)) return; diff --git a/weather.cpp b/weather.cpp index ce98372a..3e460181 100644 --- a/weather.cpp +++ b/weather.cpp @@ -40,6 +40,7 @@ extern const char wtopts_filename[]; extern OpenSprinkler os; // OpenSprinkler object extern char tmp_buffer[]; byte findKeyVal (const char *str,char *strbuf, uint8_t maxlen,const char *key,bool key_in_pgm=false,uint8_t *keyfound=NULL); +void write_log(byte type, ulong curr_time); // The weather function calls getweather.py on remote server to retrieve weather data // the default script is WEATHER_SCRIPT_HOST/weather?.py @@ -50,8 +51,8 @@ static void getweather_callback(byte status, uint16_t off, uint16_t len) { char *p = (char*)Ethernet::buffer + off; #else char *p = ether_buffer; - DEBUG_PRINTLN(p); #endif + DEBUG_PRINTLN(p); /* scan the buffer until the first & symbol */ while(*p && *p!='&') { p++; @@ -109,6 +110,7 @@ static void getweather_callback(byte status, uint16_t off, uint16_t len) { } os.checkwt_success_lasttime = os.now_tz(); + write_log(LOGDATA_WATERLEVEL, os.checkwt_success_lasttime); } #if defined(ARDUINO) // for AVR @@ -129,7 +131,7 @@ void GetWeather() { tmp); // copy string to tmp_buffer, replacing all spaces with _ char *src=tmp_buffer+strlen(tmp_buffer); - char *dst=tmp_buffer+TMP_BUFFER_SIZE-1; + char *dst=tmp_buffer+TMP_BUFFER_SIZE-12; char c; // url encode. convert SPACE to %20 @@ -217,7 +219,7 @@ void GetWeather() { tmp); char *src=tmp_buffer+strlen(tmp_buffer); - char *dst=tmp_buffer+TMP_BUFFER_SIZE-1; + char *dst=tmp_buffer+TMP_BUFFER_SIZE-12; char c; // url encode. convert SPACE to %20 @@ -256,6 +258,7 @@ void GetWeather() { } peel_http_header(); getweather_callback(0, 0, ETHER_BUFFER_SIZE); + break; } client.stop(); } From 560db55d3de406200c0adebe8268166342653730 Mon Sep 17 00:00:00 2001 From: Ray Date: Tue, 15 Sep 2015 01:32:48 -0400 Subject: [PATCH 39/39] use runtime path for all file operations (OSPi/Bo/Linux) --- main.cpp | 12 ++++++------ server.cpp | 2 +- utils.cpp | 50 +++++++++++++++++++++++++++++++------------------- utils.h | 1 + 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/main.cpp b/main.cpp index 66a8527d..189f7ccb 100644 --- a/main.cpp +++ b/main.cpp @@ -921,15 +921,15 @@ void write_log(byte type, ulong curr_time) { } #else // prepare log folder for RPI/BBB struct stat st; - if(stat(LOG_PREFIX, &st)) { - if(mkdir(LOG_PREFIX, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)) { + if(stat(get_filename_fullpath(LOG_PREFIX), &st)) { + if(mkdir(get_filename_fullpath(LOG_PREFIX), S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH)) { return; } } FILE *file; - file = fopen(tmp_buffer, "rb+"); + file = fopen(get_filename_fullpath(tmp_buffer), "rb+"); if(!file) { - file = fopen(tmp_buffer, "wb"); + file = fopen(get_filename_fullpath(tmp_buffer), "wb"); if (!file) return; } fseek(file, 0, SEEK_END); @@ -1009,11 +1009,11 @@ void delete_log(char *name) { #else // delete_log implementation for RPI/BBB if (strncmp(name, "all", 3) == 0) { // delete the log folder - rmdir(LOG_PREFIX); + rmdir(get_filename_fullpath(LOG_PREFIX)); return; } else { make_logfile_name(name); - remove(tmp_buffer); + remove(get_filename_fullpath(tmp_buffer)); } #endif } diff --git a/server.cpp b/server.cpp index 0604a01e..a3551938 100644 --- a/server.cpp +++ b/server.cpp @@ -1289,7 +1289,7 @@ byte server_json_log(char *p) { file.open(tmp_buffer, O_READ); #else // prepare to open log file for RPI/BBB FILE *file; - file = fopen(tmp_buffer, "rb"); + file = fopen(get_filename_fullpath(tmp_buffer), "rb"); if(!file) continue; #endif // prepare to open log file diff --git a/utils.cpp b/utils.cpp index 9ccbdd5d..22786590 100644 --- a/utils.cpp +++ b/utils.cpp @@ -79,7 +79,7 @@ void remove_file(const char *name) { #else // RPI/BBB/LINUX void nvm_read_block(void *dst, const void *src, int len) { - FILE *fp = fopen(NVM_FILENAME, "rb"); + FILE *fp = fopen(get_filename_fullpath(NVM_FILENAME), "rb"); if(fp) { fseek(fp, (unsigned int)src, SEEK_SET); fread(dst, 1, len, fp); @@ -88,9 +88,9 @@ void nvm_read_block(void *dst, const void *src, int len) { } void nvm_write_block(const void *src, void *dst, int len) { - FILE *fp = fopen(NVM_FILENAME, "rb+"); + FILE *fp = fopen(get_filename_fullpath(NVM_FILENAME), "rb+"); if(!fp) { - fp = fopen(NVM_FILENAME, "wb"); + fp = fopen(get_filename_fullpath(NVM_FILENAME), "wb"); } if(fp) { fseek(fp, (unsigned int)dst, SEEK_SET); @@ -102,7 +102,7 @@ void nvm_write_block(const void *src, void *dst, int len) { } byte nvm_read_byte(const byte *p) { - FILE *fp = fopen(NVM_FILENAME, "rb"); + FILE *fp = fopen(get_filename_fullpath(NVM_FILENAME), "rb"); byte v = 0; if(fp) { fseek(fp, (unsigned int)p, SEEK_SET); @@ -115,9 +115,9 @@ byte nvm_read_byte(const byte *p) { } void nvm_write_byte(const byte *p, byte v) { - FILE *fp = fopen(NVM_FILENAME, "rb+"); + FILE *fp = fopen(get_filename_fullpath(NVM_FILENAME), "rb+"); if(!fp) { - fp = fopen(NVM_FILENAME, "wb"); + fp = fopen(get_filename_fullpath(NVM_FILENAME), "wb"); } if(fp) { fseek(fp, (unsigned int)p, SEEK_SET); @@ -131,11 +131,11 @@ void nvm_write_byte(const byte *p, byte v) { void write_to_file(const char *name, const char *data, int size, int pos, bool trunc) { FILE *file; if(trunc) { - file = fopen(name, "wb"); + file = fopen(get_filename_fullpath(name), "wb"); } else { - file = fopen(name, "r+b"); + file = fopen(get_filename_fullpath(name), "r+b"); if(!file) { - file = fopen(name, "wb"); + file = fopen(get_filename_fullpath(name), "wb"); } } @@ -149,7 +149,7 @@ void write_to_file(const char *name, const char *data, int size, int pos, bool t bool read_from_file(const char *name, char *data, int maxsize, int pos) { FILE *file; - file = fopen(name, "rb"); + file = fopen(get_filename_fullpath(name), "rb"); if(!file) { data[0] = 0; return true; @@ -172,23 +172,35 @@ bool read_from_file(const char *name, char *data, int maxsize, int pos) { } void remove_file(const char *name) { - remove(name); + remove(get_filename_fullpath(name)); } char* get_runtime_path() { static char path[PATH_MAX]; - if(readlink("/proc/self/exe", path, PATH_MAX ) <= 0) { - return NULL; - } - char* path_end = strrchr(path, '/'); - if(path_end == NULL) { - return NULL; + static byte query = 1; + + if(query) { + if(readlink("/proc/self/exe", path, PATH_MAX ) <= 0) { + return NULL; + } + char* path_end = strrchr(path, '/'); + if(path_end == NULL) { + return NULL; + } + path_end++; + *path_end=0; + query = 0; } - path_end++; - *path_end=0; return path; } +char* get_filename_fullpath(const char *filename) { + static char fullpath[PATH_MAX]; + strcpy(fullpath, get_runtime_path()); + strcat(fullpath, filename); + return fullpath; +} + #if defined(OSPI) unsigned int detect_rpi_rev() { FILE * filp; diff --git a/utils.h b/utils.h index 8ac8d1e5..74178fcf 100644 --- a/utils.h +++ b/utils.h @@ -55,6 +55,7 @@ void remove_file(const char *name); byte nvm_read_byte(const byte *p); void nvm_write_byte(const byte *p, byte v); char* get_runtime_path(); + char* get_filename_fullpath(const char *filename); void delay(ulong ms); void delayMicroseconds(ulong us); void delayMicrosecondsHard(ulong us);