diff --git a/.gitignore b/.gitignore index 8348c923..778ac913 100644 --- a/.gitignore +++ b/.gitignore @@ -2,12 +2,15 @@ logs/** OpenSprinkler wtopts.txt *.dat -*.sh +*.bak testmode.h build-1284/* .vscode .pio +influx.json +*~ *.o !build.sh !startOpenSprinkler.sh -!updater.sh \ No newline at end of file +!updater.sh +DEADJOE diff --git a/.gitmodules b/.gitmodules index 0c7b52e6..a2bf9ffb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "external/TinyWebsockets"] path = external/TinyWebsockets url = https://github.com/gilmaimon/TinyWebsockets.git +[submodule "external/influxdb-cpp"] + path = external/influxdb-cpp + url = https://github.com/orca-zhang/influxdb-cpp.git diff --git a/Dockerfile b/Dockerfile index 4c86c872..59473d67 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,7 @@ ARG BUILD_VERSION="OSPI" FROM base AS os-build ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y bash g++ make libmosquittopp-dev libssl-dev libi2c-dev libgpiod-dev libgpiod2 gpiod +RUN apt-get update && apt-get install -y bash g++ make libmosquittopp-dev libssl-dev libi2c-dev RUN rm -rf /var/lib/apt/lists/* COPY . /OpenSprinkler WORKDIR /OpenSprinkler @@ -19,7 +19,7 @@ RUN make VERSION=${BUILD_VERSION} FROM base ENV DEBIAN_FRONTEND=noninteractive -RUN apt-get update && apt-get install -y libstdc++6 libmosquittopp1 libi2c0 libgpiod2 +RUN apt-get update && apt-get install -y libstdc++6 libmosquittopp1 libi2c0 RUN rm -rf /var/lib/apt/lists/* RUN mkdir /OpenSprinkler RUN mkdir -p /data/logs diff --git a/EMailSender.cpp b/EMailSender.cpp index 22b87894..02971096 100644 --- a/EMailSender.cpp +++ b/EMailSender.cpp @@ -231,6 +231,7 @@ EMailSender::Response EMailSender::awaitSMTPResponse(EMAIL_NETWORK_CLASS &client EMailSender::Response response; uint32_t ts = millis(); while (!client.available()) { + wdt_reset(); if (millis() > (ts + timeOut)) { response.code = F("1"); response.desc = String(respDesc) + "! " + F("SMTP Response TIMEOUT!"); @@ -432,7 +433,7 @@ EMailSender::Response EMailSender::send(const char* to, EMailMessage &email, Att EMAIL_DEBUG_PRINT(F("ONLY ONE RECIPIENT")); const char* arrEmail[] = {to}; - return send(arrEmail, 1, email, attachments); + return send(arrEmail, 1, 0, 0, email, attachments); } EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, EMailMessage &email, Attachments attachments) { @@ -476,16 +477,17 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s #ifndef ARDUINO_ESP8266_RELEASE_2_4_2 if (this->isSecure == false){ client.setInsecure(); + /* bool mfln = client.probeMaxFragmentLength(this->smtp_server, this->smtp_port, 512); EMAIL_DEBUG_PRINT("MFLN supported: "); EMAIL_DEBUG_PRINTLN(mfln?"yes":"no"); - if (mfln) { + if (mfln) {*/ client.setBufferSizes(512, 512); - } else { + /*} else { client.setBufferSizes(2048, 2048); - } + }*/ } #endif #elif (EMAIL_NETWORK_TYPE == NETWORK_ESP32) @@ -629,7 +631,8 @@ EMailSender::Response EMailSender::send(const char* to[], byte sizeOfTo, byte s // String auth = "AUTH PLAIN "+String(encode64(logPass)); EMAIL_DEBUG_PRINTLN(auth); client.println(auth); - } + free(logPass); + } #if defined(ESP32) else if (this->isCramMD5Login == true) { EMAIL_DEBUG_PRINTLN(F("AUTH CRAM-MD5")); diff --git a/EMailSenderKey.h b/EMailSenderKey.h index 75694b50..14209adf 100644 --- a/EMailSenderKey.h +++ b/EMailSenderKey.h @@ -79,7 +79,7 @@ // #define FORCE_DISABLE_SSL // If you want add a wrapper to emulate SSL over Client like EthernetClient -// #define SSLCLIENT_WRAPPER +//#define SSLCLIENT_WRAPPER // esp8266 microcontrollers configuration #ifndef DEFAULT_EMAIL_NETWORK_TYPE_ESP8266 @@ -136,7 +136,7 @@ * For enc28j60 use EthernetENC available from library manager or * https://github.com/jandrassy/EthernetENC */ - #define ANALOG_PIN A7 + #define ANALOG_PIN -1 #include #include "trust_anchors.h" diff --git a/Makefile b/Makefile index c11e2ae9..ab3f1bb5 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,12 @@ CXX=g++ # -std=gnu++17 -VERSION?=OSPI -CXXFLAGS=-std=gnu++14 -D$(VERSION) -DSMTP_OPENSSL -Wall -include string.h -include cstdint -Iexternal/TinyWebsockets/tiny_websockets_lib/include -Iexternal/OpenThings-Framework-Firmware-Library/ +VERSION=OSPI +CXXFLAGS=-std=gnu++14 -D$(VERSION) -DSMTP_OPENSSL -Wall -include string.h -Iexternal/TinyWebsockets/tiny_websockets_lib/include -Iexternal/OpenThings-Framework-Firmware-Library/ -Iexternal/influxdb-cpp/ LD=$(CXX) -LIBS=pthread mosquitto ssl crypto i2c gpiod +LIBS=pthread mosquitto ssl crypto i2c LDFLAGS=$(addprefix -l,$(LIBS)) BINARY=OpenSprinkler -SOURCES=main.cpp OpenSprinkler.cpp notifier.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) +SOURCES=main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp smtp.c RCSwitch.cpp sensors.cpp osinfluxdb.cpp $(wildcard external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) $(wildcard external/OpenThings-Framework-Firmware-Library/*.cpp) HEADERS=$(wildcard *.h) $(wildcard *.hpp) OBJECTS=$(addsuffix .o,$(basename $(SOURCES))) diff --git a/OpenSprinkler.cpp b/OpenSprinkler.cpp index 71f85a1c..540217df 100644 --- a/OpenSprinkler.cpp +++ b/OpenSprinkler.cpp @@ -69,9 +69,11 @@ unsigned char OpenSprinkler::attrib_igrd[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_dis[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_spe[MAX_NUM_BOARDS]; unsigned char OpenSprinkler::attrib_grp[MAX_NUM_STATIONS]; +uint16_t OpenSprinkler::attrib_fas[MAX_NUM_STATIONS]; +uint16_t OpenSprinkler::attrib_favg[MAX_NUM_STATIONS]; unsigned char OpenSprinkler::masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; -time_os_t OpenSprinkler::masters_last_on[NUM_MASTER_ZONES]; RCSwitch OpenSprinkler::rfswitch; +OSInfluxDB OpenSprinkler::influxdb; extern char tmp_buffer[]; extern char ether_buffer[]; @@ -256,7 +258,7 @@ const char iopt_prompts[] PROGMEM = "Force wired? " "Latch On Volt. " "Latch Off Volt. " - "Notif 2 Enable " + "Notif2 Enable: " "Reserved 4 " "Reserved 5 " "Reserved 6 " @@ -418,7 +420,7 @@ unsigned char OpenSprinkler::iopts[] = { 1, // force wired connection 0, // latch on volt 0, // latch off volt - 0, // notif enable bits 2 + 0, // notif enable bits - part 2 0, // reserved 4 0, // reserved 5 0, // reserved 6 @@ -551,17 +553,7 @@ unsigned char OpenSprinkler::start_network() { #endif } -unsigned char OpenSprinkler::start_ether() { -#if defined(ESP8266) - if(hw_rev<2) return 0; // ethernet capability is only available when hw_rev>=2 - eth.isW5500 = (hw_rev==2)?false:true; // os 3.2 uses enc28j60 and 3.3 uses w5500 - - SPI.begin(); - SPI.setBitOrder(MSBFIRST); - SPI.setDataMode(SPI_MODE0); - SPI.setFrequency(4000000); - - if(eth.isW5500) { +bool init_W5500(boolean initSPI) { DEBUG_PRINTLN(F("detect existence of W5500")); /* this is copied from w5500.cpp wizchip_sw_reset * perform a software reset and see if we get a correct response @@ -572,7 +564,16 @@ unsigned char OpenSprinkler::start_ether() { static const uint8_t AccessModeRead = (0x00 << 2); static const uint8_t AccessModeWrite = (0x01 << 2); static const uint8_t BlockSelectCReg = (0x00 << 3); + + if (initSPI) { + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(80000000); // 80MHz is the maximum SPI clock for W5500 + } + pinMode(PIN_ETHER_CS, OUTPUT); + // ==> setMR(MR_RST) digitalWrite(PIN_ETHER_CS, LOW); SPI.transfer((0x00 & 0xFF00) >> 8); @@ -589,8 +590,17 @@ unsigned char OpenSprinkler::start_ether() { SPI.transfer(BlockSelectCReg | AccessModeRead); ret = SPI.transfer(0); digitalWrite(PIN_ETHER_CS, HIGH); - if(ret!=0) return 0; // ret is expected to be 0 - } else { + + if(ret!=0) { // ret is expected to be 0 + return false; + } + + eth.isW5500 = true; + DEBUG_PRINTLN(F("found W5500")); + return true; +} + +bool init_ENC28J60() { /* this is copied from enc28j60.cpp geterevid * check to see if the hardware revision number if expected * */ @@ -599,12 +609,22 @@ unsigned char OpenSprinkler::start_ether() { #define EREVID 0x12 #define ECON1 0x1f + SPI.begin(); + SPI.setBitOrder(MSBFIRST); + SPI.setDataMode(SPI_MODE0); + SPI.setFrequency(20000000); //ENC28J60 maximum SPI clock is 20MHz + // ==> setregbank(MAADRX_BANK); pinMode(PIN_ETHER_CS, OUTPUT); uint8_t r; digitalWrite(PIN_ETHER_CS, LOW); SPI.transfer(0x00 | (ECON1 & 0x1f)); r = SPI.transfer(0); + DEBUG_PRINT("ECON1=") + DEBUG_PRINTLN(r); + if (r == 2) { + return false; + } digitalWrite(PIN_ETHER_CS, HIGH); digitalWrite(PIN_ETHER_CS, LOW); @@ -616,8 +636,28 @@ unsigned char OpenSprinkler::start_ether() { digitalWrite(PIN_ETHER_CS, LOW); SPI.transfer(0x00 | (EREVID & 0x1f)); r = SPI.transfer(0); + DEBUG_PRINTLN(r); digitalWrite(PIN_ETHER_CS, HIGH); - if(r==0 || r==255) return 0; // r is expected to be a non-255 revision number + if(r==0 || r==255) { // r is expected to be a non-255 revision number + return false; + } + + eth.isW5500 = false; + DEBUG_PRINTLN(F("found ENC28J60")); + return true; +} + +byte OpenSprinkler::start_ether() { +#if defined(ESP8266) + if(hw_rev<2) return 0; // ethernet capability is only available when hw_rev>=2 + + // os 3.2 uses enc28j60 and 3.3 uses w5500 + if (hw_rev==2) { + if (!init_ENC28J60() && !init_W5500(false)) + return 0; + } else { + if (!init_W5500(true)) + return 0; } load_hardware_mac((uint8_t*)tmp_buffer, true); @@ -737,12 +777,11 @@ unsigned char OpenSprinkler::start_network() { #endif if(otc.en>0 && otc.token.length()>=DEFAULT_OTC_TOKEN_LENGTH) { otf = new OTF::OpenThingsFramework(port, otc.server.c_str(), otc.port, otc.token.c_str(), false, ether_buffer, ETHER_BUFFER_SIZE); - DEBUG_PRINT(F("Started OTF with remote connection. Local port is: ")); + DEBUG_PRINTLN(F("Started OTF with remote connection")); } else { otf = new OTF::OpenThingsFramework(port, ether_buffer, ETHER_BUFFER_SIZE); - DEBUG_PRINT(F("Started OTF with just local connection. Local port is: ")); + DEBUG_PRINTLN(F("Started OTF with just local connection")); } - DEBUG_PRINTLN(port); return 1; } @@ -968,8 +1007,6 @@ pinModeExt(PIN_BUTTON_2, INPUT_PULLUP); pinModeExt(PIN_BUTTON_3, INPUT_PULLUP); #endif - // init masters_last_on array - memset(masters_last_on, 0, sizeof(masters_last_on)); // Reset all stations clear_all_station_bits(); apply_all_station_bits(); @@ -1157,7 +1194,7 @@ void OpenSprinkler::latch_setallzonepins(unsigned char value) { } } -void OpenSprinkler::latch_disable_alloutputs_v2() { +void OpenSprinkler::latch_disable_alloutputs_v2(unsigned char expvalue) { digitalWriteExt(PIN_LATCH_COMA, LOW); digitalWriteExt(PIN_LATCH_COMK, LOW); @@ -1166,7 +1203,12 @@ void OpenSprinkler::latch_disable_alloutputs_v2() { // latch v2 has a 74hc595 which controls all h-bridge cathode pins drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, 0x00); - // todo: handle expander + // Handle all expansion boards + for(byte i=0;itype==IOEXP_TYPE_9555) { + expanders[i]->i2c_write(NXP_OUTPUT_REG, expvalue?0xFFFF:0x0000); + } + } } /** Set one zone (for LATCH controller) @@ -1205,8 +1247,15 @@ void OpenSprinkler::latch_setzoneoutput_v2(unsigned char sid, unsigned char A, u drio->shift_out(V2_PIN_SRLAT, V2_PIN_SRCLK, V2_PIN_SRDAT, K ? (1<>4; + uint16_t s=(sid-8)&0x0F; + if(expanders[bid]->type==IOEXP_TYPE_9555) { + uint16_t reg = expanders[bid]->i2c_read(NXP_OUTPUT_REG); // read current output reg value + if(A==HIGH && K==LOW) reg |= (1<i2c_write(NXP_OUTPUT_REG, reg); + } } } @@ -1216,7 +1265,7 @@ void OpenSprinkler::latch_setzoneoutput_v2(unsigned char sid, unsigned char A, u void OpenSprinkler::latch_open(unsigned char sid) { if(hw_rev>=2) { DEBUG_PRINTLN(F("latch_open_v2")); - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH DEBUG_PRINTLN(F("boost on voltage: ")); DEBUG_PRINTLN(iopts[IOPT_LATCH_ON_VOLTAGE]); latch_boost(iopts[IOPT_LATCH_ON_VOLTAGE]); // generate boost voltage @@ -1225,7 +1274,7 @@ void OpenSprinkler::latch_open(unsigned char sid) { digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path delay(150); digitalWriteExt(PIN_BOOST_EN, LOW); // disabled output boosted voltage path - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH } else { latch_boost(); // boost voltage latch_setallzonepins(HIGH); // set all switches to HIGH, including COM @@ -1241,7 +1290,7 @@ void OpenSprinkler::latch_open(unsigned char sid) { void OpenSprinkler::latch_close(unsigned char sid) { if(hw_rev>=2) { DEBUG_PRINTLN(F("latch_close_v2")); - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(LOW); // disable all output pins; set expanders all to LOW DEBUG_PRINTLN(F("boost off voltage: ")); DEBUG_PRINTLN(iopts[IOPT_LATCH_OFF_VOLTAGE]); latch_boost(iopts[IOPT_LATCH_OFF_VOLTAGE]); // generate boost voltage @@ -1250,7 +1299,7 @@ void OpenSprinkler::latch_close(unsigned char sid) { digitalWriteExt(PIN_BOOST_EN, HIGH); // enable output path delay(150); digitalWriteExt(PIN_BOOST_EN, LOW); // disable output boosted voltage path - latch_disable_alloutputs_v2(); // disable all output pins + latch_disable_alloutputs_v2(HIGH); // disable all output pins; set expanders all to HIGH } else { latch_boost(); // boost voltage latch_setallzonepins(LOW); // set all switches to LOW, including COM @@ -1409,7 +1458,7 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { if(iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_SOIL) { if(hw_rev>=2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 unsigned char val = digitalReadExt(PIN_SENSOR1); - status.sensor1 = (val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1; + status.sensor1 = status.forced_sensor1 || ((val == iopts[IOPT_SENSOR1_OPTION]) ? 0 : 1); if(status.sensor1) { if(!sensor1_on_timer) { // add minimum of 5 seconds on delay @@ -1439,7 +1488,7 @@ void OpenSprinkler::detect_binarysensor_status(time_os_t curr_time) { if(iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_RAIN || iopts[IOPT_SENSOR2_TYPE]==SENSOR_TYPE_SOIL) { if(hw_rev>=2) pinModeExt(PIN_SENSOR2, INPUT_PULLUP); // this seems necessary for OS 3.2 unsigned char val = digitalReadExt(PIN_SENSOR2); - status.sensor2 = (val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1; + status.sensor2 = status.forced_sensor2 || ((val == iopts[IOPT_SENSOR2_OPTION]) ? 0 : 1); if(status.sensor2) { if(!sensor2_on_timer) { // add minimum of 5 seconds on delay @@ -1657,6 +1706,25 @@ unsigned char OpenSprinkler::is_sequential_station(unsigned char sid) { return attrib_grp[sid] != PARALLEL_GROUP_ID; } +uint16_t OpenSprinkler::get_flow_alert_setpoint(unsigned char sid) { + return attrib_fas[sid]; +} + +void OpenSprinkler::set_flow_alert_setpoint(unsigned char sid, uint16_t value) { + attrib_fas[sid] = value; +} + +uint16_t OpenSprinkler::get_flow_avg_value(unsigned char sid) { + return attrib_favg[sid]; +} + +void OpenSprinkler::set_flow_avg_value(unsigned char sid, uint16_t value) { + if (value != attrib_favg[sid]) { + attrib_favg[sid] = value; + file_write_block(STATIONS3_FILENAME, &value, (uint16_t)sid * sizeof(uint16_t), sizeof(uint16_t)); + } +} + unsigned char OpenSprinkler::is_master_station(unsigned char sid) { for (unsigned char mas = 0; mas < NUM_MASTER_ZONES; mas++) { if (get_master_id(mas) && (get_master_id(mas) - 1 == sid)) { @@ -1742,6 +1810,8 @@ void OpenSprinkler::attribs_save() { } } } + file_write_block(STATIONS2_FILENAME, attrib_fas, 0, sizeof(attrib_fas)); + file_write_block(STATIONS3_FILENAME, attrib_favg, 0, sizeof(attrib_favg)); } /** Load all station attribs from file (backward compatibility) */ @@ -1758,6 +1828,10 @@ void OpenSprinkler::attribs_load() { memset(attrib_dis, 0, nboards); memset(attrib_spe, 0, nboards); memset(attrib_grp, 0, MAX_NUM_STATIONS); + memset(attrib_fas, 0, sizeof(attrib_fas)); + memset(attrib_favg, 0, sizeof(attrib_favg)); + file_read_block(STATIONS2_FILENAME, attrib_fas, 0, sizeof(attrib_fas)); + file_read_block(STATIONS3_FILENAME, attrib_favg, 0, sizeof(attrib_favg)); for(bid=0;bidsetInsecure(); bool mfln = _c->probeMaxFragmentLength(server, port, 512); @@ -1944,6 +2019,7 @@ int8_t OpenSprinkler::send_http_request(const char* server, uint16_t port, char* } else { _c->setBufferSizes(2048, 2048); } + restore_tmp_memory(); client = _c; } else { client = new WiFiClient(); @@ -2076,7 +2152,7 @@ void OpenSprinkler::switch_remotestation(RemoteIPStationData *data, bool turnon, ip[3] = ip4&0xff; char *p = tmp_buffer; - BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE_L); // if turning on the zone and duration is defined, give duration as the timer value // otherwise: // if autorefresh is defined, we give a fixed duration each time, and auto refresh will renew it periodically @@ -2113,7 +2189,7 @@ void OpenSprinkler::switch_remotestation(RemoteOTCStationData *data, bool turnon memcpy((char*)©, (char*)data, sizeof(RemoteOTCStationData)); copy.token[sizeof(copy.token)-1] = 0; // ensure the string ends properly char *p = tmp_buffer; - BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE_L); // if turning on the zone and duration is defined, give duration as the timer value // otherwise: // if autorefresh is defined, we give a fixed duration each time, and auto refresh will renew it periodically @@ -2152,7 +2228,7 @@ void OpenSprinkler::switch_httpstation(HTTPStationData *data, bool turnon, bool char * cmd = turnon ? on_cmd : off_cmd; char *p = tmp_buffer; - BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(p, TMP_BUFFER_SIZE_L); if(cmd==NULL || server==NULL) return; // proceed only if cmd and server are valid diff --git a/OpenSprinkler.h b/OpenSprinkler.h index 8010d232..11948420 100644 --- a/OpenSprinkler.h +++ b/OpenSprinkler.h @@ -32,6 +32,7 @@ #include "images.h" #include "mqtt.h" #include "RCSwitch.h" +#include "osinfluxdb.h" #if defined(ARDUINO) // headers for Arduino #include @@ -48,6 +49,8 @@ #include #include #include + #include + #include "SSD1306Display.h" #include "espconnect.h" #include "EMailSender.h" #else // for AVR @@ -64,7 +67,7 @@ #include #include "OpenThingsFramework.h" #include "etherport.h" - #include "rpitime.h" + #include "rpitime.h" #include "smtp.h" #endif // end of headers @@ -113,6 +116,7 @@ // AVR specific #endif extern bool useEth; + bool detect_i2c(int addr); #else // OSPI/Linux specific #endif @@ -222,6 +226,8 @@ struct ConStatus { unsigned char sensor2_active:1; // sensor2 active bit (when set, sensor2 is activated) unsigned char req_mqtt_restart:1;// request mqtt restart unsigned char pause_state:1; // pause station runs + unsigned char forced_sensor1:1; // forced sensor1 active (from Analog Sensor API) + unsigned char forced_sensor2:1; // forced sensor2 active (from Analog Sensor API) }; /** OTF configuration */ @@ -239,6 +245,7 @@ class OpenSprinkler { public: // data members + static OSInfluxDB influxdb; #if defined(USE_SSD1306) static SSD1306Display lcd; // 128x64 OLED display #elif defined(USE_LCD) @@ -250,7 +257,6 @@ class OpenSprinkler { #endif static OSMqtt mqtt; - static NVConData nvdata; static ConStatus status; static ConStatus old_status; @@ -271,8 +277,9 @@ class OpenSprinkler { static unsigned char attrib_dis[]; static unsigned char attrib_spe[]; static unsigned char attrib_grp[]; + static uint16_t attrib_fas[MAX_NUM_STATIONS]; //value*100 flow alert setpoint + static uint16_t attrib_favg[MAX_NUM_STATIONS]; //value*100 flow avg values static unsigned char masters[NUM_MASTER_ZONES][NUM_MASTER_OPTS]; - static time_os_t masters_last_on[NUM_MASTER_ZONES]; // variables for time keeping static time_os_t sensor1_on_timer; // time when sensor1 is detected on last time @@ -309,8 +316,12 @@ class OpenSprinkler { static void set_station_name(unsigned char sid, char buf[]); // set station name static unsigned char get_station_type(unsigned char sid); // get station type static unsigned char is_sequential_station(unsigned char sid); - static unsigned char is_master_station(unsigned char sid); - static unsigned char bound_to_master(unsigned char sid, unsigned char mas); + uint16_t get_flow_alert_setpoint(unsigned char sid); + void set_flow_alert_setpoint(unsigned char sid, uint16_t value); + uint16_t get_flow_avg_value(unsigned char sid); + void set_flow_avg_value(unsigned char sid, uint16_t value); + static unsigned char is_master_station(unsigned char sid); + static unsigned char bound_to_master(unsigned char sid, unsigned char mas); static unsigned char get_master_id(unsigned char mas); static int16_t get_on_adj(unsigned char mas); static int16_t get_off_adj(unsigned char mas); @@ -456,7 +467,7 @@ static void lcd_print_line_clear_pgm(const char *str, unsigned char line); static void latch_close(unsigned char sid); static void latch_setzonepin(unsigned char sid, unsigned char value); static void latch_setallzonepins(unsigned char value); - static void latch_disable_alloutputs_v2(); + static void latch_disable_alloutputs_v2(unsigned char expvalue); static void latch_setzoneoutput_v2(unsigned char sid, unsigned char A, unsigned char K); static void latch_apply_all_station_bits(); static unsigned char prev_station_bits[]; diff --git a/Sensor API.txt b/Sensor API.txt new file mode 100644 index 00000000..c2664478 --- /dev/null +++ b/Sensor API.txt @@ -0,0 +1,194 @@ +Sensor API +This api documentation is part of the opensprinkler + + the ip-address, for example 192.168.0.55 + the MD5 encrypted password, opendoor=a6d82bced638de3def1e9bbb4983225c + + +Create Sensors (sc): +creates, modifies or deletes a sensor. +"nr" for a unique number >= 1 +"type" for the sensor-type, see sf. type=0 deletes the sensor +"group" for group assignment, a nr of another sensor with type=SENSOR_GROUP_YXZ +"name" a name +"ip" for the ip-address, only for network connected sensors. All others use ip=0 +"port" for the ip-port-address, only for network connected sensors. All others use port=0 +"id" sub-id, e.a. modbus address or subid, for ADC Sensors 0..7 +"ri" read interval in seconds +"fac" factor for user defined sensors +"div" divider for user defined sensors +"unit" unit for user defined sensors (unitid=99) +"enable" 0=sensor disabled, 1=sensor enabled +"log" 0=logging disabled, 1=logging enabled +"show" 0=hide 1=show current value on main screen +"offset", "offset2" Offset for user defined sensor +"url", "topic", "filter" mqtt/external sensor (url currently not used) + +examples: +http:///sc?pw=&nr=1&type=1&group=0&name=SMT100-Mois&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 +http:///sc?pw=&nr=2&type=2&group=0&name=SMT100-Temp&ip=4261456064&port=8899&id=253&ri=60&enable=1&log=1 +http:///sc?pw=&nr=3&type=11&group=0&name=SMT50-Mois&ip=0&id=0&ri=60&enable=1&log=1 +http:///sc?pw=&nr=4&type=12&group=0&name=SMT50-Temp&ip=0&id=1&ri=60&enable=1&log=1 + +ip: dec=4261456064 = hex=FE00A8C0 = +FE = 254 +00 = 000 +A8 = 168 +C0 = 192 + = 192.168.000.254 + +For the Truebner SMT100 RS485 Modbus you need the Truebner RS485 Adapter for OpenSprinkler. +Set ip/port for the converter, e.a PUSR USR-W610 in transparent modus. + +For the analog ports of the extension board (including SMT50) use id 0..7 + +// Sensor types: +SENSOR_NONE 0 // None or deleted sensor +SENSOR_SMT100_MOIS 1 // Truebner SMT100 RS485, moisture mode +SENSOR_SMT100_TEMP 2 // Truebner SMT100 RS485, temperature mode +SENSOR_SMT100_PMTY 3 // Truebner SMT100 RS485, permittivity mode +SENSOR_TH100_MOIS 4 // Truebner TH100 RS485, humidity mode +SENSOR_TH100_TEMP 5 // Truebner TH100 RS485, temperature mode + +SENSOR_ANALOG_EXTENSION_BOARD 10 // New OpenSprinkler analog extension board x8 - voltage mode 0..4V +SENSOR_ANALOG_EXTENSION_BOARD_P 11 // New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% +SENSOR_SMT50_MOIS 15 // New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 +SENSOR_SMT50_TEMP 16 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +SENSOR_SMT100_ANALOG_MOIS 17 // New OpenSprinkler analog extension board x8 - SMT100 VWC [%] = (U * 100) : 3 +SENSOR_SMT100_ANALOG_TEMP 18 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U * 100) : 3 - 40 + +SENSOR_VH400 30 // New OpenSprinkler analog extension board x8 - Vegetronix VH400 +SENSOR_THERM200 31 // New OpenSprinkler analog extension board x8 - Vegetronix THERM200 +SENSOR_AQUAPLUMB 32 // New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb + +SENSOR_USERDEF 49 // New OpenSprinkler analog extension board x8 - User defined sensor + +SENSOR_OSPI_ANALOG 50 // Old OSPi analog input - voltage mode 0..3.3V +SENSOR_OSPI_ANALOG_P 51 // Old OSPi analog input - percent 0..3.3V to 0...100% +SENSOR_OSPI_ANALOG_SMT50_MOIS 52 // Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 +SENSOR_OSPI_ANALOG_SMT50_TEMP 53 // Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 +SENSOR_OSPI_INTERNAL_TEMP 54 // Internal OSPI Temperature + +SENSOR_MQTT 90 // subscribe to a MQTT server and query a value + +SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler +SENSOR_WEATHER_TEMP_F 101 // Weather service - temperature (Fahrenheit) +SENSOR_WEATHER_TEMP_C 102 // Weather service - temperature (Celcius) +SENSOR_WEATHER_HUM 103 // Weather service - humidity (%) +SENSOR_WEATHER_PRECIP_IN 105 // Weather service - precip (inch) +SENSOR_WEATHER_PRECIP_MM 106 // Weather service - precip (mm) +SENSOR_WEATHER_WIND_MPH 107 // Weather service - wind (mph) +SENSOR_WEATHER_WIND_KMH 108 // Weather service - wind (kmh) + +SENSOR_GROUP_MIN 1000 // Sensor group with min value +SENSOR_GROUP_MAX 1001 // Sensor group with max value +SENSOR_GROUP_AVG 1002 // Sensor group with avg value +SENSOR_GROUP_SUM 1003 // Sensor group with sum value + +//Diagnostic +SENSOR_FREE_MEMORY 10000 //Free memory +SENSOR_FREE_STORE 10001 //Free storage +List Sensors (sl): +lists the current sensors +examples: +http:///sl?pw= + +Get last Sensor values (sg): +returns last read sensor values +examples: +http:///sg?pw= +http:///sg?pw=&nr=1 + +Read sensor now (sr): +executes sensor read and returns the values +examples: +http:///sr?pw= +http:///sr?pw=&nr=1 + +Set sensor address for SMT100 (sa): +Only for SMT100: Set modbus address +Disconnect all other modbus sensors, so that only one sensor is connected. Sets the modbus address for sensor nr to id +examples: +http:///sa?pw=&nr=1&id=1 + +Dump Sensor Log (so): +dumps the sensor log +examples: +http:///so?pw= +http:///so?pw=&start=0&max=100&nr=1&type=1&before=1666915277&after=1666915277 + +Clear Sensor Log (sn): +clears the sensor log +examples: +http:///sn?pw= +http:///sn?pw=&log=1 + +Parameters: +log=[1|2|3]: 0=Std log, 1=Week log, 2=Month log +nr=n: Sensor-nr +under=v: delete only records with values under v +over=v: delete only records with values over v +before=t: delete only records with timestamp before t +after=t: delete only records with timestamp after t + v is a float value with point as decimal separator + t is a long integer value with an unix timestamp, use this to decode: + https://www.epochconverter.com/ + +Program adjustments (sb): +defines program adjustments +"nr" adjustment-nr +"type" adjustment-type (0=delete, 1=linear, 2=digital min, 3=digital max) +"sensor" sensor-nr +"prog" program-nr +"factor1", "factor2", "min", "max" formula-values +examples: +http:///sb?pw=&nr=1&type=1&sensor=4&prog=1&factor1=0&factor2=2&min=0&max=50 + +type: +PROG_DELETE 0 //deleted +PROG_LINEAR 1 //formula see above +PROG_DIGITAL_MIN 2 //under or equal min : factor1 else factor2 +PROG_DIGITAL_MAX 3 //over or equal max : factor2 else factor1 +PROG_DIGITAL_MINMAX 4 //under min or over max : factor1 else factor2 +PROG_NONE 99 //No adjustment + +formula: + min max factor1 factor2 + 10..90 -> 5..1 factor1 > factor2 + a b c d + (b-sensorData) / (b-a) * (c-d) + d + + 10..90 -> 1..5 factor1 < factor2 + a b c d + (sensorData-a) / (b-a) * (d-c) + c + +min/max is the used range of the sensor (for example min=10 max=80) +factor1/factor2 is the calculated adjustment (for example factor1=2 factor2=0) +So a sensordata of 10 will be a adjustment of factor 2 (200%) or +a sensordata of 80 will be a adjustment of factor 0 (0%) +everything between will be linear scaled in the range of 0..2 + +List Program adjustments (se): +lists the current program adjustments +examples: +http:///se?pw= +&nr=1&prog=1 + +List supported sensor types (sf): +lists supported sensor types +examples: +http:///sf?pw= + +List supported program adjustments (sh): +http:///sh?pw= + +System used and free space (du): +examples: +http:///du?pw= + +Sensor config User Defined (si): +Updates the user config values of a sensor +examples: +http:///si?pw=&nr=1&fac=100&div=10&unit='bar' + + diff --git a/build.sh b/build.sh index 740b6188..be1325ce 100755 --- a/build.sh +++ b/build.sh @@ -11,9 +11,6 @@ function enable_i2c { if [[ $(grep -c '^dtparam=i2c_arm=on$' /boot/config.txt) -ge 1 ]] ; then echo "Setting the i2c clock speed to 400 kHz, you will have to reboot for this to take effect." sudo sed -i -e 's/dtparam=i2c_arm=on$/dtparam=i2c_arm=on,i2c_arm_baudrate=400000/g' /boot/config.txt - elif [[ $(grep -c '^dtparam=i2c_arm=on$' /boot/firmware/config.txt) -ge 1 ]] ; then - echo "Setting the i2c clock speed to 400 kHz, you will have to reboot for this to take effect." - sudo sed -i -e 's/dtparam=i2c_arm=on$/dtparam=i2c_arm=on,i2c_arm_baudrate=400000/g' /boot/firmware/config.txt fi else echo "Can not automatically enable i2c you might have to do this manually" @@ -34,8 +31,27 @@ while getopts ":s:d" opt; do ;; esac done + +FILENAME="sopts.dat" +if [[ ! "$SILENT" && -f "$FILENAME" ]]; then + FILESIZE=$(stat -c%s "$FILENAME") + if [[ "$FILESIZE" != "4160" ]]; then + echo "This version has a new configuration data structure." + echo "Please backup configuration and restore after update!" + echo "Otherwise your configuration is lost" + echo "however, if this is a new installation, then you can proceed directly" + read -p "Press ctrl-c to stop now or enter to continue" + fi +fi + echo "Building OpenSprinkler..." +if [ -f /etc/init.d/OpenSprinkler.sh ]; then + /etc/init.d/OpenSprinkler.sh stop +elif [ -f /etc/systemd/system/OpenSprinkler.service ]; then + systemctl stop OpenSprinkler +fi + #Git update submodules if git submodule status | grep --quiet '^-'; then @@ -51,24 +67,24 @@ if [ "$1" == "demo" ]; then apt-get install -y libmosquitto-dev libssl-dev echo "Compiling demo firmware..." - ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) - otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - g++ -o OpenSprinkler -DDEMO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto + ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) + otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) + g++ -o OpenSprinkler -DDEMO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h main.cpp \ + OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp \ + mqtt.cpp smtp.c RCSwitch.cpp sensors.cpp osinfluxdb.cpp \ + -Iexternal/TinyWebsockets/tiny_websockets_lib/include \ + $ws \ + -Iexternal/OpenThings-Framework-Firmware-Library/ \ + $otf \ + -Iexternal/influxdb-cpp/ \ + -lpthread -lmosquitto -lssl -lcrypto else echo "Installing required libraries..." apt-get update - apt-get install -y libmosquitto-dev libi2c-dev libssl-dev libgpiod-dev gpiod - enable_i2c - - USEGPIO="-DLIBGPIOD" - GPIOLIB="-lgpiod" - - echo "Compiling ospi firmware..." - - ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) - otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) - g++ -o OpenSprinkler -DOSPI $USEGPIO -DSMTP_OPENSSL $DEBUG -std=c++14 -include string.h -include cstdint main.cpp OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp notifier.cpp smtp.c RCSwitch.cpp -Iexternal/TinyWebsockets/tiny_websockets_lib/include $ws -Iexternal/OpenThings-Framework-Firmware-Library/ $otf -lpthread -lmosquitto -lssl -lcrypto -li2c $GPIOLIB + apt-get install -y libmosquitto-dev libi2c-dev libssl-dev libgpiod-dev gpiod libmodbus-dev + enable_i2c + ./build2.sh fi if [ -f /etc/init.d/OpenSprinkler.sh ]; then @@ -99,13 +115,12 @@ if [ ! "$SILENT" = true ] && [ -f OpenSprinkler.service ] && [ -f startOpenSprin # Make file executable chmod +x startOpenSprinkler.sh - - # Reload systemd - systemctl daemon-reload - - # Enable and start the service - systemctl enable OpenSprinkler - systemctl start OpenSprinkler fi +# Reload systemd +systemctl daemon-reload + +# Enable and start the service +systemctl enable OpenSprinkler +systemctl start OpenSprinkler echo "Done!" diff --git a/build2.sh b/build2.sh new file mode 100755 index 00000000..75294a45 --- /dev/null +++ b/build2.sh @@ -0,0 +1,48 @@ +#!/bin/bash + + echo "Compiling OSPi firmware..." + + USEGPIO="" + GPIOLIB="" + + source /etc/os-release + if [[ $VERSION_ID -gt 10 ]]; then + echo "using libgpiod" + USEGPIO="-DLIBGPIOD" + GPIOLIB="-lgpiod" + fi + + + ADS1115="" + ADS1115FILES="" + + I2C=$(i2cdetect -y 1) + if [[ "${I2C,,}" == *"48 --"* ]] ;then + echo "found PCF8591" + PCF8591="-DPCF8591" + PCF8591FILES="./ospi-analog/driver_pcf8591*.c ./ospi-analog/iic.c" + fi + + if [[ "${I2C,,}" == *"48 49"* ]] ;then + echo "found ADS1115" + ADS1115="-DADS1115" + ADS1115FILES="./ospi-analog/driver_ads1115*.c ./ospi-analog/iic.c" + fi + + + echo "Compiling firmware..." + ws=$(ls external/TinyWebsockets/tiny_websockets_lib/src/*.cpp) + otf=$(ls external/OpenThings-Framework-Firmware-Library/*.cpp) + g++ -o OpenSprinkler -DOSPI $USEGPIO $ADS1115 $PCF8591 -DSMTP_OPENSSL $DEBUG -std=c++17 -include string.h main.cpp \ + OpenSprinkler.cpp program.cpp opensprinkler_server.cpp utils.cpp weather.cpp gpio.cpp mqtt.cpp \ + smtp.c RCSwitch.cpp sensor*.cpp \ + $ADS1115FILES $PCF8591FILES \ + -Iexternal/TinyWebsockets/tiny_websockets_lib/include \ + $ws \ + -Iexternal/OpenThings-Framework-Firmware-Library/ \ + $otf \ + -Iexternal/influxdb-cpp/ \ + osinfluxdb.cpp \ + -lpthread -lmosquitto -lssl -lcrypto -li2c -lmodbus $GPIOLIB + + diff --git a/defines.h b/defines.h old mode 100755 new mode 100644 index 6fad1734..5b959451 --- a/defines.h +++ b/defines.h @@ -29,13 +29,14 @@ typedef unsigned long ulong; #define TMP_BUFFER_SIZE 320 // scratch buffer size +#define TMP_BUFFER_SIZE_L TMP_BUFFER_SIZE+100 // scratch buffer size /** Firmware version, hardware version, and maximal values */ -#define OS_FW_VERSION 221 // Firmware version: 221 means 2.2.1 +#define OS_FW_VERSION 233 // Firmware version: 220 means 2.2.0 // if this number is different from the one stored in non-volatile memory // a device reset will be automatically triggered -#define OS_FW_MINOR 1 // Firmware minor version +#define OS_FW_MINOR 176 // Firmware minor version /** Hardware version base numbers */ #define OS_HW_VERSION_BASE 0x00 // OpenSprinkler @@ -52,6 +53,8 @@ typedef unsigned long ulong; #define IOPTS_FILENAME "iopts.dat" // integer options data file #define SOPTS_FILENAME "sopts.dat" // string options data file #define STATIONS_FILENAME "stns.dat" // stations data file +#define STATIONS2_FILENAME "stns2.dat" // stations data file 2 - flow alert values +#define STATIONS3_FILENAME "stns3.dat" // stations data file 3 - flow avg values #define NVCON_FILENAME "nvcon.dat" // non-volatile controller data file, see OpenSprinkler.h --> struct NVConData #define PROG_FILENAME "prog.dat" // program data file #define DONE_FILENAME "done.dat" // used to indicate the completion of all files @@ -77,6 +80,9 @@ typedef unsigned long ulong; #define NOTIFY_RAINDELAY 0x0080 #define NOTIFY_STATION_ON 0x0100 #define NOTIFY_FLOW_ALERT 0x0200 +#define NOTIFY_MONITOR_LOW 0x0400 +#define NOTIFY_MONITOR_MID 0x0800 +#define NOTIFY_MONITOR_HIGH 0x1000 /** HTTP request macro defines */ #define HTTP_RQT_SUCCESS 0 @@ -140,8 +146,8 @@ typedef unsigned long ulong; /** Default string option values */ #define DEFAULT_PASSWORD "a6d82bced638de3def1e9bbb4983225c" // md5 of 'opendoor' -#define DEFAULT_LOCATION "42.36,-71.06" // Boston,MA -#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinkler.com/js" +#define DEFAULT_LOCATION "49.484018,8.475593" // Mannheim,Germany +#define DEFAULT_JAVASCRIPT_URL "https://ui.opensprinklershop.de/js" #define DEFAULT_WEATHER_URL "weather.opensprinkler.com" #define DEFAULT_IFTTT_URL "maker.ifttt.com" #define DEFAULT_OTC_SERVER_DEV "ws.cloud.openthings.io" @@ -257,7 +263,7 @@ enum { IOPT_FORCE_WIRED, IOPT_LATCH_ON_VOLTAGE, IOPT_LATCH_OFF_VOLTAGE, - IOPT_NOTIF2_ENABLE, + IOPT_NOTIF2_ENABLE, // Notification part 2 IOPT_RESERVE_4, IOPT_RESERVE_5, IOPT_RESERVE_6, @@ -337,6 +343,7 @@ enum { #define PIN_CURR_DIGITAL 24 // digital pin index for A7 #define ETHER_BUFFER_SIZE 2048 + #define ETHER_BUFFER_SIZE_L ETHER_BUFFER_SIZE+100 #define wdt_reset() __asm__ __volatile__ ("wdr") // watchdog timer reset @@ -357,11 +364,13 @@ enum { #define EXP_I2CADDR_BASE 0x24 // base of expander I2C address #define LCD_I2CADDR 0x3C // 128x64 OLED display I2C address #define EEPROM_I2CADDR 0x50 // 24C02 EEPROM I2C address + #define EEPROM_I2CADDR 0x50 // 24C02 EEPROM I2C address #define PIN_CURR_SENSE A0 // current sensing pin #define PIN_LATCH_VOLT_SENSE A0 // latch voltage sensing pin #define PIN_FREE_LIST {} // no free GPIO pin at the moment #define ETHER_BUFFER_SIZE 2048 + #define ETHER_BUFFER_SIZE_L ETHER_BUFFER_SIZE+100 #define PIN_ETHER_CS 16 // Ethernet CS (chip select pin) is 16 on OS 3.2 and above @@ -449,6 +458,7 @@ enum { #define PIN_FREE_LIST {5,6,7,8,9,11,12,13,16,19,20,21,23,25,26} // free GPIO pins #define ETHER_BUFFER_SIZE 16384 + #define ETHER_BUFFER_SIZE_L ETHER_BUFFER_SIZE+100 #define SDA 0 #define SCL 0 @@ -472,6 +482,7 @@ enum { #define PIN_RFTX 0 #define PIN_FREE_LIST {} #define ETHER_BUFFER_SIZE 16384 + #define ETHER_BUFFER_SIZE_L ETHER_BUFFER_SIZE+100 #endif @@ -506,6 +517,8 @@ enum { #include #include #include + inline void itoa(int v,char *s,int b) {sprintf(s,"%d",v);} + inline void ultoa(unsigned long v,char *s,int b) {sprintf(s,"%lu",v);} #define pgm_read_byte(x) *(x) #define PSTR(x) x #define F(x) x diff --git a/espconnect.cpp b/espconnect.cpp index c52b874b..5a261b97 100644 --- a/espconnect.cpp +++ b/espconnect.cpp @@ -22,6 +22,7 @@ #include "espconnect.h" String scan_network() { + WiFi.setOutputPower(20.5); WiFi.mode(WIFI_STA); WiFi.disconnect(); unsigned char n = WiFi.scanNetworks(); @@ -51,6 +52,7 @@ String scan_network() { void start_network_ap(const char *ssid, const char *pass) { if(!ssid) return; + WiFi.setOutputPower(20.5); if(pass) WiFi.softAP(ssid, pass); else WiFi.softAP(ssid); WiFi.mode(WIFI_AP_STA); // start in AP_STA mode @@ -59,12 +61,14 @@ void start_network_ap(const char *ssid, const char *pass) { void start_network_sta_with_ap(const char *ssid, const char *pass, int32_t channel, const unsigned char *bssid) { if(!ssid || !pass) return; + WiFi.setOutputPower(20.5); if(WiFi.getMode()!=WIFI_AP_STA) WiFi.mode(WIFI_AP_STA); WiFi.begin(ssid, pass, channel, bssid); } void start_network_sta(const char *ssid, const char *pass, int32_t channel, const unsigned char *bssid) { if(!ssid || !pass) return; + WiFi.setOutputPower(20.5); if(WiFi.getMode()!=WIFI_STA) WiFi.mode(WIFI_STA); WiFi.begin(ssid, pass, channel, bssid); } diff --git a/external/OpenThings-Framework-Firmware-Library b/external/OpenThings-Framework-Firmware-Library index 0f74fa74..13914114 160000 --- a/external/OpenThings-Framework-Firmware-Library +++ b/external/OpenThings-Framework-Firmware-Library @@ -1 +1 @@ -Subproject commit 0f74fa747ea98451ab6956be9555227d467b568d +Subproject commit 13914114a31f5087a92eabb82b9c173d36e43a89 diff --git a/external/influxdb-cpp b/external/influxdb-cpp new file mode 160000 index 00000000..fa4cb492 --- /dev/null +++ b/external/influxdb-cpp @@ -0,0 +1 @@ +Subproject commit fa4cb4927877ef64a4e2aae4ef29daf0ac309614 diff --git a/gpio.cpp b/gpio.cpp index 4c06aeeb..0edba9c2 100644 --- a/gpio.cpp +++ b/gpio.cpp @@ -177,6 +177,275 @@ unsigned char digitalReadExt(unsigned char pin) { #elif defined(OSPI) +#if !defined(LIBGPIOD) // use classic sysfs + +#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 unsigned char GPIOExport(int pin) { + char buffer[BUFFER_MAX]; + int fd, len; + + fd = open("/sys/class/gpio/export", O_WRONLY); + if (fd < 0) { + DEBUG_PRINTLN("failed to open export for writing"); + return 0; + } + + len = snprintf(buffer, sizeof(buffer), "%d", pin); + write(fd, buffer, len); + close(fd); + return 1; +} + +#if 0 +/** Unexport gpio pin */ +static unsigned char 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; +} +#endif + +/** Set interrupt edge mode */ +static unsigned char GPIOSetEdge(int pin, const char *edge) { + char path[BUFFER_MAX]; + int fd; + + 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, unsigned char mode) { + static const char dir_str[] = "in\0out"; + + char path[BUFFER_MAX]; + int fd; + + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/direction", pin); + + struct stat st; + if(stat(path, &st)) { + if (!GPIOExport(pin)) return; + } + + fd = open(path, O_WRONLY); + if (fd < 0) { + DEBUG_PRINTLN("failed to open gpio direction for writing"); + return; + } + + if (-1 == write(fd, &dir_str[(INPUT==mode)||(INPUT_PULLUP==mode)?0:3], (INPUT==mode)||(INPUT_PULLUP==mode)?2:3)) { + DEBUG_PRINTLN("failed to set direction"); + return; + } + + close(fd); +#if defined(OSPI) + if(mode==INPUT_PULLUP) { + char cmd[BUFFER_MAX]; + //snprintf(cmd, BUFFER_MAX, "gpio -g mode %d up", pin); + snprintf(cmd, BUFFER_MAX, "raspi-gpio set %d pu", pin); + system(cmd); + } +#endif + return; +} + +/** Open file for digital pin */ +int gpio_fd_open(int pin, int mode) { + char path[BUFFER_MAX]; + int fd; + + snprintf(path, BUFFER_MAX, "/sys/class/gpio/gpio%d/value", pin); + 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 */ +unsigned char digitalRead(int pin) { + char value_str[3]; + + int fd = gpio_fd_open(pin, O_RDONLY); + if (fd < 0) { + return 0; + } + + if (read(fd, value_str, 3) < 0) { + DEBUG_PRINTLN("failed to read value"); + return 0; + } + + close(fd); + return atoi(value_str); +} + +/** Write digital value given file descriptor */ +void gpio_write(int fd, unsigned char value) { + static const char value_str[] = "01"; + + if (1 != write(fd, &value_str[LOW==value?0:1], 1)) { + DEBUG_PRINT("failed to write value on pin "); + } +} + +/** Write digital value */ +void digitalWrite(int pin, unsigned char value) { + int fd = gpio_fd_open(pin); + if (fd < 0) { + return; + } + gpio_write(fd, 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 ; +} + +#include "utils.h" +/** 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); + + // 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 #endif +#include "ArduinoJson.hpp" + #if defined(ARDUINO) #if defined(ESP8266) + #include + #include + //extern "C" struct netif* eagle_lwip_getif (int netif_index); + Pinger *pinger = NULL; ESP8266WebServer *update_server = NULL; + DNSServer *dns = NULL; + ENC28J60lwIP enc28j60(PIN_ETHER_CS); // ENC28J60 lwip for wired Ether Wiznet5500lwIP w5500(PIN_ETHER_CS); // W5500 lwip for wired Ether lwipEth eth; @@ -67,7 +76,9 @@ #endif #endif +void push_message(uint16_t type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); void manual_start_program(unsigned char, unsigned char); +void remote_http_callback(char*); // Small variations have been added to the timing values below // to minimize conflicting events @@ -81,13 +92,12 @@ void manual_start_program(unsigned char, unsigned char); #define CLIENT_READ_TIMEOUT 5 // client read timeout (in seconds) #define DHCP_CHECKLEASE_INTERVAL 3600L // DHCP check lease interval (in seconds) // Define buffers: need them to be sufficiently large to cover string option reading -char ether_buffer[ETHER_BUFFER_SIZE*2]; // ethernet buffer, make it twice as large to allow overflow -char tmp_buffer[TMP_BUFFER_SIZE*2]; // scratch buffer, make it twice as large to allow overflow +char ether_buffer[ETHER_BUFFER_SIZE_L]; // ethernet buffer, make it twice as large to allow overflow +char tmp_buffer[TMP_BUFFER_SIZE_L]; // scratch buffer, make it twice as large to allow overflow // ====== Object defines ====== OpenSprinkler os; // OpenSprinkler object ProgramData pd; // ProgramdData object -NotifQueue notif; // NotifQueue object /* ====== Robert Hillman (RAH)'s implementation of flow sensor ====== * flow_begin - time when valve turns on @@ -101,10 +111,11 @@ unsigned char prev_flow_state = HIGH; float flow_last_gpm=0; uint32_t reboot_timer = 0; +uint32_t ping_ok = 0; void flow_poll() { #if defined(ESP8266) - if(os.hw_rev>=2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 + if(os.hw_rev >= 2) pinModeExt(PIN_SENSOR1, INPUT_PULLUP); // this seems necessary for OS 3.2 #endif unsigned char curr_flow_state = digitalReadExt(PIN_SENSOR1); if(!(prev_flow_state==HIGH && curr_flow_state==LOW)) { // only record on falling edge @@ -413,6 +424,8 @@ void do_setup() { } os.button_timeout = LCD_BACKLIGHT_TIMEOUT; + + sensor_api_init(true); } // Arduino software reset function @@ -450,6 +463,7 @@ void do_setup() { os.status.network_fails = 1; } os.status.req_network = 0; + os.powerup_lasttime = os.now_tz(); // because at reboot we don't know if special stations // are in OFF state, here we explicitly turn them off @@ -460,6 +474,8 @@ void do_setup() { os.mqtt.init(); os.status.req_mqtt_restart = true; + sensor_api_init(true); + initialize_otf(); } @@ -483,16 +499,16 @@ void reboot_in(uint32_t ms) { reboot_ticker.once_ms(ms, ESP.restart); } } -#else +bool check_enc28j60(); +#elif !defined(OSPI) void handle_web_request(char *p); #endif - /** Main Loop */ void do_loop() { - static ulong flowpoll_timeout=0; // handle flow sensor using polling every 1ms (maximum freq 1/(2*1ms)=500Hz) + static ulong flowpoll_timeout=0; if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { ulong curr = millis(); if(curr!=flowpoll_timeout) { @@ -628,14 +644,6 @@ void do_loop() if(size>0) { if(size>ETHER_BUFFER_SIZE) size=ETHER_BUFFER_SIZE; int len = client.read((uint8_t*) ether_buffer, size); - // Hack: see if we may have another packet, in case there is an overly large packet - // This really should be implemented more gracefully - size = client.available(); - if(size>0) { - // There is still more data to read - if(size+len > ETHER_BUFFER_SIZE) size = ETHER_BUFFER_SIZE - len; // cap read size - len += client.read((uint8_t*) ether_buffer+len, size); - } if(len>0) { m_client = &client; ether_buffer[len] = 0; // properly end the buffer @@ -658,7 +666,7 @@ void do_loop() #else // Process Ethernet packets for RPI/LINUX if(otf) otf->loop(); #if defined(USE_DISPLAY) - ui_state_machine(); + ui_state_machine(); #endif #endif // Process Ethernet packets @@ -705,12 +713,12 @@ void do_loop() if (os.status.rain_delayed) { // rain delay started, record time os.raindelay_on_lasttime = curr_time; - notif.add(NOTIFY_RAINDELAY, LOGDATA_RAINDELAY, 1); + push_message(NOTIFY_RAINDELAY, LOGDATA_RAINDELAY, 1); } else { // rain delay stopped, write log write_log(LOGDATA_RAINDELAY, curr_time); - notif.add(NOTIFY_RAINDELAY, LOGDATA_RAINDELAY, 0); + push_message(NOTIFY_RAINDELAY, LOGDATA_RAINDELAY, 0); } os.old_status.rain_delayed = os.status.rain_delayed; } @@ -722,10 +730,10 @@ void do_loop() // send notification when sensor1 becomes active if(os.status.sensor1_active) { os.sensor1_active_lasttime = curr_time; - notif.add(NOTIFY_SENSOR1, LOGDATA_SENSOR1, 1); + push_message(NOTIFY_SENSOR1, LOGDATA_SENSOR1, 1); } else { write_log(LOGDATA_SENSOR1, curr_time); - notif.add(NOTIFY_SENSOR1, LOGDATA_SENSOR1, 0); + push_message(NOTIFY_SENSOR1, LOGDATA_SENSOR1, 0); } } os.old_status.sensor1_active = os.status.sensor1_active; @@ -734,10 +742,10 @@ void do_loop() // send notification when sensor1 becomes active if(os.status.sensor2_active) { os.sensor2_active_lasttime = curr_time; - notif.add(NOTIFY_SENSOR2, LOGDATA_SENSOR2, 1); + push_message(NOTIFY_SENSOR2, LOGDATA_SENSOR2, 1); } else { write_log(LOGDATA_SENSOR2, curr_time); - notif.add(NOTIFY_SENSOR2, LOGDATA_SENSOR2, 0); + push_message(NOTIFY_SENSOR2, LOGDATA_SENSOR2, 0); } } os.old_status.sensor2_active = os.status.sensor2_active; @@ -771,6 +779,14 @@ void do_loop() bool will_delete = false; unsigned char runcount = prog.check_match(curr_time, &will_delete); if(runcount>0) { + // Check and update weather if weatherdata is older than 30min: + if (os.checkwt_success_lasttime && (!os.checkwt_lasttime || os.now_tz() > os.checkwt_lasttime + 30*60)) { + os.checkwt_lasttime = 0; + os.checkwt_success_lasttime = 0; + check_weather(); + } + //break; + // program match found // check and process special program command if(process_special_program_command(prog.name, curr_time)) continue; @@ -800,6 +816,9 @@ void do_loop() water_time = 0; } + // Analog sensor water time adjustments: + water_time = (ulong)((double)water_time * calc_sensor_watering(pid)); + if (water_time) { // check if water time is still valid // because it may end up being zero after scaling @@ -817,7 +836,7 @@ void do_loop() }// if prog.durations[sid] }// for sid if(match_found) { - notif.add(NOTIFY_PROGRAM_SCHED, pid, prog.use_weather?os.iopts[IOPT_WATER_PERCENTAGE]:100); + push_message(NOTIFY_PROGRAM_SCHED, pid, prog.use_weather?os.iopts[IOPT_WATER_PERCENTAGE]:100); } //delete run-once if on final runtime (stations have already been queued) if(will_delete){ @@ -939,7 +958,7 @@ void do_loop() // log flow sensor reading if flow sensor is used if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { write_log(LOGDATA_FLOWSENSE, curr_time); - notif.add(NOTIFY_FLOWSENSOR, (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0); + push_message(NOTIFY_FLOWSENSOR, (flow_count>os.flowcount_log_start)?(flow_count-os.flowcount_log_start):0); } // in case some options have changed while executing the program @@ -976,7 +995,11 @@ void do_loop() } } } - + + if(os.get_station_bit(mas_id - 1) == 0 && masbit == 1){ // notify master on event + push_message(NOTIFY_STATION_ON, mas_id - 1, 0); + } + os.set_station_bit(mas_id - 1, masbit); } } @@ -992,23 +1015,6 @@ void do_loop() // process dynamic events process_dynamic_events(curr_time); - // handle master on / off notif events - for (unsigned char mas = MASTER_1; mas < NUM_MASTER_ZONES; mas++) { - unsigned char mas_id = os.masters[mas][MASOPT_SID]; - if (mas_id) { // if this master station is defined - time_os_t laston = os.masters_last_on[mas]; - unsigned char masbit = os.get_station_bit(mas_id - 1); - if(!laston && masbit) { // master is about to turn on - notif.add(NOTIFY_STATION_ON, mas_id - 1, 0); - os.masters_last_on[mas] = curr_time; - } - if(laston > 0 && !masbit) { // master is about to turn off - notif.add(NOTIFY_STATION_OFF, mas_id - 1, (curr_time>laston) ? (curr_time-laston) : 0); - os.masters_last_on[mas] = 0; - } - } - } - // activate/deactivate valves os.apply_all_station_bits(); @@ -1063,21 +1069,25 @@ void do_loop() // check weather check_weather(); - // process notifier events - if(os.network_connected()) { - notif.run(); - } - if(os.weather_update_flag & WEATHER_UPDATE_WL) { // at the moment, we only send notification if water level changed // the other changes, such as sunrise, sunset changes are ignored for notification - notif.add(NOTIFY_WEATHER_UPDATE, 0, os.iopts[IOPT_WATER_PERCENTAGE]); + push_message(NOTIFY_WEATHER_UPDATE, 0, os.iopts[IOPT_WATER_PERCENTAGE]); os.weather_update_flag = 0; } + + // read analog sensors + read_all_sensors(curr_time && os.network_connected()); + static unsigned char reboot_notification = 1; if(reboot_notification) { + #if defined(ESP266) + if(useEth || WiFi.status()==WL_CONNECTED) + #endif + { reboot_notification = 0; - notif.add(NOTIFY_REBOOT); + push_message(NOTIFY_REBOOT); + } } } @@ -1112,7 +1122,9 @@ void check_weather() { if (os.status.network_fails>0 || os.iopts[IOPT_REMOTE_EXT_MODE]) return; if (os.status.program_busy) return; +#if defined(ESP8266) if (!os.network_connected()) return; +#endif time_os_t ntz = os.now_tz(); if (os.checkwt_success_lasttime && (ntz > os.checkwt_success_lasttime + CHECK_WEATHER_SUCCESS_TIMEOUT)) { @@ -1148,7 +1160,7 @@ void turn_on_station(unsigned char sid, ulong duration) { flow_gallons=0; if (os.set_station_bit(sid, 1, duration)) { - notif.add(NOTIFY_STATION_ON, sid, duration); + push_message(NOTIFY_STATION_ON, sid, duration); } } @@ -1232,8 +1244,30 @@ void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shif // log station run write_log(LOGDATA_STATION, curr_time); // LOG_TODO - notif.add(NOTIFY_STATION_OFF, sid, pd.lastrun.duration); - notif.add(NOTIFY_FLOW_ALERT, sid, pd.lastrun.duration); + push_message(NOTIFY_STATION_OFF, sid, pd.lastrun.duration); + + //Flow altert? + if (pd.lastrun.duration > 90) { //check running > 90s + uint16_t flow_alert_setpoint = os.get_flow_alert_setpoint(sid); + //flow_last_gpm is actually collected and stored as pulses per minute, not gallons per minute + //Get Flow Pulse Rate factor and apply to flow_last_gpm when comparing and outputting + uint16_t fpr = (unsigned int)(os.iopts[IOPT_PULSE_RATE_1]<<8)+os.iopts[IOPT_PULSE_RATE_0]; + float last_flow = flow_last_gpm * fpr; + uint16_t avg_flow = os.get_flow_avg_value(sid); + uint16_t int_flow = (int)(last_flow * 100); + if (avg_flow > 0) + avg_flow = (avg_flow + int_flow) / 2; + else + avg_flow = int_flow; + os.set_flow_avg_value(sid, int_flow); + // Alert Check - Compare flow_gpm_alert_setpoint with flow_last_gpm and enable flow_alert_flag if flow is above setpoint + if (flow_alert_setpoint) { + float flow_gpm_alert_setpoint = (float)flow_alert_setpoint/100; + if (last_flow > flow_gpm_alert_setpoint) { + push_message(NOTIFY_FLOW_ALERT, sid, pd.lastrun.duration); + } + } + } } } @@ -1417,13 +1451,14 @@ void reset_all_stations() { */ void manual_start_program(unsigned char pid, unsigned char uwt) { boolean match_found = false; - reset_all_stations_immediate(); + if (uwt != 255) + reset_all_stations_immediate(); ProgramStruct prog; ulong dur; unsigned char sid, bid, s; if ((pid>0)&&(pid<255)) { pd.read(pid-1, &prog); - notif.add(NOTIFY_PROGRAM_SCHED, pid-1, uwt?os.iopts[IOPT_WATER_PERCENTAGE]:100, 1); + push_message(NOTIFY_PROGRAM_SCHED, pid-1, uwt?os.iopts[IOPT_WATER_PERCENTAGE]:100, ""); } for(sid=0;sid>3; @@ -1454,6 +1489,449 @@ void manual_start_program(unsigned char pid, unsigned char uwt) { } } +bool is_notif_enabled(uint16_t type) { + uint16_t notif = (uint16_t)os.iopts[IOPT_NOTIF_ENABLE] | ((uint16_t)os.iopts[IOPT_NOTIF2_ENABLE] << 8); + return (notif&type) != 0; +} + +uint16_t get_notif_enabled() { + return (uint16_t)os.iopts[IOPT_NOTIF_ENABLE]|((uint16_t)os.iopts[IOPT_NOTIF2_ENABLE]<<8); +} + +void set_notif_enabled(uint16_t notif) { + os.iopts[IOPT_NOTIF_ENABLE] = notif&0xFF; + os.iopts[IOPT_NOTIF2_ENABLE] = notif >> 8; +} + +// ========================================== +// ====== PUSH NOTIFICATION FUNCTIONS ======= +// ========================================== +void ip2string(char* str, size_t str_len, unsigned char ip[4]) { + int len = strlen(str); + snprintf_P(str+len, str_len-len, PSTR("%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); +} + +#define PUSH_TOPIC_LEN 120 +#define PUSH_PAYLOAD_LEN TMP_BUFFER_SIZE + +void push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { + + if (!is_notif_enabled(type)) { + DEBUG_PRINT("PUSH INACTIVE: "); + DEBUG_PRINTLN(type); + return; + } + + static char topic[PUSH_TOPIC_LEN+1]; + static char payload[PUSH_PAYLOAD_LEN+1]; + char* postval = tmp_buffer+1; // +1 so we can fit a opening { before the loaded config + uint32_t volume; + + // check if ifttt key exists and also if the enable bit is set + os.sopt_load(SOPT_IFTTT_KEY, tmp_buffer); + bool ifttt_enabled = strlen(tmp_buffer)!=0; + +#define DEFAULT_EMAIL_PORT 465 + + // parse email variables + #if defined(SUPPORT_EMAIL) + // define email variables + ArduinoJson::JsonDocument doc; // make sure this has the same scope of email_x variables to prevent use after free + const char *email_host = NULL; + const char *email_username = NULL; + const char *email_password = NULL; + const char *email_recipient = NULL; + int email_port = DEFAULT_EMAIL_PORT; + int email_en = 0; + + os.sopt_load(SOPT_EMAIL_OPTS, postval); + if (*postval != 0) { + // Add the wrapping curly braces to the string + postval = tmp_buffer; + postval[0] = '{'; + int len = strlen(postval); + postval[len] = '}'; + postval[len+1] = 0; + + ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, postval); + // Test the parsing otherwise parse + if (error) { + DEBUG_PRINT(F("email: deserializeJson() failed: ")); + DEBUG_PRINTLN(error.c_str()); + } else { + email_en = doc["en"]; + email_host = doc["host"]; + email_port = doc["port"]; + email_username = doc["user"]; + email_password = doc["pass"]; + email_recipient= doc["recipient"]; + } + } + #endif + + #if defined(ESP8266) + EMailSender::EMailMessage email_message; + #else + struct { + String subject; + String message; + } email_message; + #endif + + bool email_enabled = false; + bool influxdb_enabled = os.influxdb.isEnabled(); +#if defined(SUPPORT_EMAIL) + if(!email_en){ + email_enabled = false; + }else{ + email_enabled = true; + } +#endif + + // if none if enabled, return here + if (!ifttt_enabled && !email_enabled && !os.mqtt.enabled()) { + if (influxdb_enabled) + os.influxdb.push_message(type, lval, fval, sval); + return; + } + + if (ifttt_enabled || email_enabled) { + strcpy_P(postval, PSTR("{\"value1\":\"On site [")); + os.sopt_load(SOPT_DEVICE_NAME, topic, PUSH_TOPIC_LEN); + topic[PUSH_TOPIC_LEN]=0; + strcat(postval+strlen(postval), topic); + strcat_P(postval, PSTR("], ")); + if(email_enabled) { + strcat(topic, " "); + email_message.subject = topic; // prefix the email subject with device name + } + } + + if (os.mqtt.enabled()) { + topic[0] = 0; + payload[0] = 0; + } + + switch(type) { + case NOTIFY_STATION_ON: + + if (os.mqtt.enabled()) { + snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d"), lval); + if((int)fval == 0){ + snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":1}")); // master on event does not have duration attached to it + }else{ + snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":1,\"duration\":%d}"), (int)fval); + } + } + break; + + case NOTIFY_FLOW_ALERT:{ + uint16_t flow_alert_setpoint = os.get_flow_alert_setpoint(lval); + float flow_gpm_alert_setpoint = (float)flow_alert_setpoint/100; + + //flow_last_gpm is actually collected and stored as pulses per minute, not gallons per minute + //Get Flow Pulse Rate factor and apply to flow_last_gpm when comparing and outputting + float flow_pulse_rate_factor = static_cast(os.iopts[IOPT_PULSE_RATE_1]) + static_cast(os.iopts[IOPT_PULSE_RATE_0]) / 100.0; + + char tmp_station_name[STATION_NAME_SIZE]; + os.get_station_name(lval, tmp_station_name); + int f1 = (int)(flow_last_gpm*flow_pulse_rate_factor); + int f2 = (int)((flow_last_gpm*flow_pulse_rate_factor) * 100) % 100; + int f3 = (int)fval; + int f4 = (int)flow_gpm_alert_setpoint; + int f5 = (int)(flow_gpm_alert_setpoint * 100) % 100; + if (os.mqtt.enabled()) { + //Format mqtt message + snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d/alert/flow"), lval); + snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%d.%02d,\"duration\":%d,\"alert_setpoint\":%d.%02d}"), + f1, f2, f3, f4, f5); + } + if (ifttt_enabled || email_enabled) { + //Format ifttt\email message + // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" + time_os_t curr_time = os.now_tz(); + struct tm *tm_info = localtime((time_t*)&curr_time); + char formatted_time[TMP_BUFFER_SIZE]; + strftime(formatted_time, sizeof(formatted_time), "%Y-%m-%d %I:%M:%S %p", tm_info); + strcat_P(postval, PSTR("
")); + strcat(postval, formatted_time); + + strcat_P(postval, PSTR("
Station: ")); + //Truncate flow setpoint value off station name to shorten ifttt\email message + tmp_station_name[(strlen(tmp_station_name) - 5)] = '\0'; + strcat_P(postval, tmp_station_name); + + if((int)fval == 0){ + strcat_P(postval, PSTR("")); + } else { // master on event does not have duration attached to it + strcat_P(postval, PSTR("
Duration: ")); + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR(" %d minutes %d seconds"), (int)fval/60, (int)fval%60); + } + + strcat_P(postval, PSTR("

FLOW ALERT!")); + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE, PSTR("
Flow rate: %d.%02d
Flow Alert Setpoint: %d.%02d"), + f1, f2, f4, f5); + + if(email_enabled) { + email_message.subject += PSTR("- FLOW ALERT"); + } + } + break; + } + + case NOTIFY_STATION_OFF: + + if (os.mqtt.enabled()) { + snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d"), lval); + if (os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":0,\"duration\":%d,\"flow\":%d.%02d}"), (int)fval, (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); + } else { + snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":0,\"duration\":%d}"), (int)fval); + } + } + if (ifttt_enabled || email_enabled) { + strcat_P(postval, PSTR("Station [")); + os.get_station_name(lval, postval+strlen(postval)); + if((int)fval == 0){ + strcat_P(postval, PSTR("] closed.")); + }else{ + strcat_P(postval, PSTR("] closed. It ran for ")); + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR(" %d minutes %d seconds."), (int)fval/60, (int)fval%60); + } + + if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR(" Flow rate: %d.%02d"), (int)flow_last_gpm, (int)(flow_last_gpm*100)%100); + } + if(email_enabled) { email_message.subject += PSTR("station event"); } + } + break; + + case NOTIFY_PROGRAM_SCHED: + + if (ifttt_enabled || email_enabled) { + if (sval) strcat_P(postval, PSTR("manually scheduled ")); + else strcat_P(postval, PSTR("automatically scheduled ")); + strcat_P(postval, PSTR("Program ")); + { + ProgramStruct prog; + pd.read(lval, &prog); + if(lval0) { + strcat_P(postval, PSTR("external IP updated: ")); + unsigned char ip[4] = {(unsigned char)((lval>>24)&0xFF), + (unsigned char)((lval>>16)&0xFF), + (unsigned char)((lval>>8)&0xFF), + (unsigned char)(lval&0xFF)}; + ip2string(postval, TMP_BUFFER_SIZE, ip); + } + if(fval>=0) { + size_t len = strlen(postval); + snprintf_P(postval + len, TMP_BUFFER_SIZE-len, PSTR("water level updated: %d%%."), (int)fval); + } + if(email_enabled) { email_message.subject += PSTR("weather update event"); } + } + break; + + case NOTIFY_REBOOT: + if (os.mqtt.enabled()) { + strcpy_P(topic, PSTR("system")); + strcpy_P(payload, PSTR("{\"state\":\"started\"}")); + } + if (ifttt_enabled || email_enabled) { + #if defined(ARDUINO) + strcat_P(postval, PSTR("rebooted. Device IP: ")); + #if defined(ESP8266) + { + IPAddress _ip; + if (useEth) { + //_ip = Ethernet.localIP(); + _ip = eth.localIP(); + } else { + _ip = WiFi.localIP(); + } + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; + ip2string(postval, TMP_BUFFER_SIZE, ip); + } + #else + ip2string(postval, TMP_BUFFER_SIZE, &(Ethernet.localIP()[0])); + #endif + + //Adding restart reasons: + struct rst_info *rtc_info = system_get_rst_info(); + if (rtc_info) { + int len = strlen(postval); + snprintf_P(postval+len, TMP_BUFFER_SIZE-len, PSTR("
reset reason: %x"), rtc_info->reason); + if (rtc_info->reason == REASON_WDT_RST || + rtc_info->reason == REASON_EXCEPTION_RST || + rtc_info->reason == REASON_SOFT_WDT_RST) { + if (rtc_info->reason == REASON_EXCEPTION_RST) { + len = strlen(postval); + snprintf_P(postval+len, TMP_BUFFER_SIZE-len, PSTR("
Fatal exception: %d"), rtc_info->exccause); + } + len = strlen(postval); + snprintf_P(postval+len, TMP_BUFFER_SIZE-len, PSTR("
epc1=0x%08x, epc2=0x%08x, epc3=0x%08x, excvaddr=0x%08x, depc=0x%08x"), + rtc_info->epc1, rtc_info->epc2, rtc_info->epc3, rtc_info->excvaddr, rtc_info->depc); + } + } + + #else + strcat_P(postval, PSTR("controller process restarted.")); + #endif + if(email_enabled) { email_message.subject += PSTR("reboot event"); } + } + break; + + case NOTIFY_MONITOR_LOW: + case NOTIFY_MONITOR_MID: + case NOTIFY_MONITOR_HIGH: + + if (os.mqtt.enabled()) { + strcpy_P(topic, PSTR("monitoring")); + int len = strlen(payload); + snprintf_P(payload+len, PUSH_PAYLOAD_LEN-len, PSTR("{\"warning\":\"%s\",\"prio\":%u,\"value\":%d.%02d}"), sval, lval, (int)fval, (int)fval*100%100); + } + if (ifttt_enabled || email_enabled) { + int len = strlen(postval); + snprintf_P(postval+len, TMP_BUFFER_SIZE-len, PSTR("monitoring: Warning %s with priority %u current value %d.%02d"), sval, lval, (int)fval, (int)fval*100%100); + if(email_enabled) { email_message.subject += PSTR("Warning"); } + } + break; + + } + + DEBUG_PRINT("topic: "); + DEBUG_PRINTLN(topic); + DEBUG_PRINT("payload: "); + DEBUG_PRINTLN(payload); + + if (os.mqtt.enabled() && strlen(topic) && strlen(payload)) + os.mqtt.publish(topic, payload); + + if (ifttt_enabled) { + strcat_P(postval, PSTR("\"}")); + + BufferFiller bf = BufferFiller(ether_buffer, TMP_BUFFER_SIZE); + bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" + "Host: $S\r\n" + "Accept: */*\r\n" + "Content-Length: $D\r\n" + "Content-Type: application/json\r\n\r\n$S"), + SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); + + os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, remote_http_callback); + } + + if(email_enabled){ + email_message.message = strchr(postval, 'O'); // ad-hoc: remove the value1 part from the ifttt message + #if defined(ARDUINO) + #if defined(ESP8266) + if(email_host && email_username && email_password && email_recipient) { // make sure all are valid + free_tmp_memory(); + EMailSender emailSend(email_username, email_password); + emailSend.setSMTPServer(email_host); + emailSend.setSMTPPort(email_port); + EMailSender::Response resp = emailSend.send(email_recipient, email_message); + DEBUG_PRINTLN(F("Sending Status:")); + DEBUG_PRINTLN(resp.status); + DEBUG_PRINTLN(resp.code); + DEBUG_PRINTLN(resp.desc); + restore_tmp_memory(); + } + #endif + #else + struct smtp *smtp = NULL; + String email_port_str = to_string(email_port); + smtp_status_code rc; + if(email_host && email_username && email_password && email_recipient) { // make sure all are valid + rc = smtp_open(email_host, email_port_str.c_str(), SMTP_SECURITY_TLS, SMTP_NO_CERT_VERIFY, NULL, &smtp); + rc = smtp_auth(smtp, SMTP_AUTH_PLAIN, email_username, email_password); + rc = smtp_address_add(smtp, SMTP_ADDRESS_FROM, email_username, "OpenSprinkler"); + rc = smtp_address_add(smtp, SMTP_ADDRESS_TO, email_recipient, "User"); + rc = smtp_header_add(smtp, "Subject", email_message.subject.c_str()); + rc = smtp_mail(smtp, email_message.message.c_str()); + rc = smtp_close(smtp); + if (rc!=SMTP_STATUS_OK) { + DEBUG_PRINTF("SMTP: Error %s\n", smtp_status_code_errstr(rc)); + } + } + #endif + } + if (influxdb_enabled) + os.influxdb.push_message(type, lval, fval, sval); +} // ================================ // ====== LOGGING FUNCTIONS ======= @@ -1607,7 +2085,8 @@ void write_log(unsigned char type, time_os_t curr_time) { #if defined(ARDUINO) dtostrf(flow_last_gpm,5,2,tmp_buffer+strlen(tmp_buffer)); #else - snprintf(tmp_buffer+strlen(tmp_buffer), TMP_BUFFER_SIZE, "%5.2f", flow_last_gpm); + size_t len = strlen(tmp_buffer); + snprintf(tmp_buffer + len, TMP_BUFFER_SIZE - len, "%5.2f", flow_last_gpm); #endif } strcat_P(tmp_buffer, PSTR("]\r\n")); @@ -1746,8 +2225,131 @@ static void check_network() { os.status.network_fails=0; } } -#else - // nothing to do for other platforms +#endif +#if defined(ESP8266) + if (os.status.program_busy) {return;} + + if (os.status.req_network) { + os.status.req_network = 0; + // change LCD icon to indicate it's checking network + if (!ui_state) { + os.lcd.setCursor(LCD_CURSOR_NETWORK, 1); + os.lcd.write('>'); + } + + if (!pinger) { + pinger = new Pinger(); +#if defined(ENABLE_DEBUG) + pinger->OnReceive([](const PingerResponse& response) { + if (response.ReceivedResponse) { + Serial.printf( + "Reply from %s: bytes=%d time=%ums TTL=%d\r\n", + response.DestIPAddress.toString().c_str(), + response.EchoMessageSize - sizeof(struct icmp_echo_hdr), + response.ResponseTime, + response.TimeToLive); + } else { + Serial.printf("Request timed out.\r\n"); + } + + // Return true to continue the ping sequence. + // If current event returns false, the ping sequence is interrupted. + return true; + }); +#endif + + pinger->OnEnd([](const PingerResponse &response) { +#if defined(ENABLE_DEBUG) + // Evaluate lost packet percentage + float loss = 100; + if(response.TotalReceivedResponses > 0) { + loss = (response.TotalSentRequests - response.TotalReceivedResponses) * 100 / response.TotalSentRequests; + } + + // Print packet trip data + Serial.printf("Ping statistics for %s:\r\n", + response.DestIPAddress.toString().c_str()); + Serial.printf(" Packets: Sent = %u, Received = %u, Lost = %u (%.2f%% loss),\r\n", + response.TotalSentRequests, + response.TotalReceivedResponses, + response.TotalSentRequests - response.TotalReceivedResponses, + loss); + + // Print time information + if(response.TotalReceivedResponses > 0) + { + Serial.printf("Approximate round trip times in milli-seconds:\r\n"); + Serial.printf(" Minimum = %ums, Maximum = %ums, Average = %.2fms\r\n", + response.MinResponseTime, + response.MaxResponseTime, + response.AvgResponseTime); + } + + // Print host data + Serial.printf("Destination host data:\r\n"); + Serial.printf(" IP address: %s\r\n", + response.DestIPAddress.toString().c_str()); + if(response.DestMacAddress != nullptr) { + Serial.printf(" MAC address: " MACSTR "\r\n", + MAC2STR(response.DestMacAddress->addr)); + } + if(response.DestHostname != "") { + Serial.printf(" DNS name: %s\r\n", + response.DestHostname.c_str()); + } +#endif + boolean failed = response.TotalSentRequests > response.TotalReceivedResponses; + + //Idee: If we never received a ping response, then the gateway is blocked. + // So only reboot if we failed 3 times and we never received any ping response. + ping_ok += response.TotalReceivedResponses; + if (!ping_ok) + return true; + + if (failed) { + if(os.status.network_fails<3) os.status.network_fails++; + // clamp it to 6 + //if (os.status.network_fails > 6) os.status.network_fails = 6; + } + else os.status.network_fails=0; + // if failed more than 3 times, restart + if (os.status.network_fails==3) { + // mark for safe restart + os.nvdata.reboot_cause = REBOOT_CAUSE_NETWORK_FAIL; + os.status.safe_reboot = 1; + } + + return true; + }); + } + if (useEth && (!eth.connected() || !eth.gatewayIP() || !eth.gatewayIP().isSet())) { + os.status.network_fails++; + return; + } + if (!useEth && (!WiFi.isConnected() || !WiFi.gatewayIP() || !WiFi.gatewayIP().isSet() || os.get_wifi_mode()==WIFI_MODE_AP)) { + os.status.network_fails++; + return; + } + + boolean ping_ok = false; + switch(os.status.network_fails % 3) { + case 0: + ping_ok = pinger->Ping(useEth?eth.gatewayIP() : WiFi.gatewayIP()); + break; + case 1: + ping_ok = pinger->Ping("google.com"); + break; + case 2: + ping_ok = pinger->Ping("opensprinkler.com"); + break; + } + if(!ping_ok) { + os.status.network_fails++; +#if defined(ENABLE_DEBUG) + Serial.println("Error during last ping command."); +#endif + } + } #endif } @@ -1803,7 +2405,7 @@ int main(int argc, char *argv[]) { } } - do_setup(); + do_setup(); while(true) { do_loop(); diff --git a/main.h b/main.h index 717693b6..f5c93ab3 100644 --- a/main.h +++ b/main.h @@ -36,4 +36,8 @@ void delete_log(char *name); void write_log(unsigned char type, time_os_t curr_time); void make_logfile_name(char *name); +bool is_notif_enabled(uint16_t type); +uint16_t get_notif_enabled(); +void set_notif_enabled(uint16_t notif); + #endif // _MAIN_H diff --git a/make.lin302m b/make.lin302m new file mode 100644 index 00000000..4917f979 --- /dev/null +++ b/make.lin302m @@ -0,0 +1,34 @@ +SKETCH = ./mainArduino.ino +LIBS = . \ + $(ESP_LIBS)/Wire \ + $(ESP_LIBS)/SPI \ + $(ESP_LIBS)/ESP8266WiFi \ + $(ESP_LIBS)/ESP8266WebServer \ + $(ESP_LIBS)/ESP8266mDNS \ + $(ESP_LIBS)/LittleFS \ + $(ESP_LIBS)/lwIP_enc28j60 \ + /data/libs/SSD1306 \ + /data/libs/rc-switch \ + /data/libs/pubsubclient \ + +ESP_ROOT = /data/esp8266_3.0.2-master +ESPCORE_VERSION = 302 +BUILD_ROOT = /data/opensprinkler-firmware/$(MAIN_NAME) + +UPLOAD_SPEED = 460800 +UPLOAD_VERB = -v +# for OS3.0 revision 1: reset mode is nodemcu +# UPLOAD_RESET = nodemcu +# Uncomment the line below for OS3.0 revision 0: reset mode is ck +# UPLOAD_RESET = ck + +FLASH_DEF = 4M3M +FLASH_MODE = dio +FLASH_SPEED = 80 +F_CPU = 160000000L + +BOARD = generic + +EXCLUDE_DIRS = ./build-1284 + +include ./makeEspArduino.mk diff --git a/mqtt.cpp b/mqtt.cpp index ea521622..c78dbef4 100644 --- a/mqtt.cpp +++ b/mqtt.cpp @@ -37,6 +37,8 @@ #include #include #include + #include + #include struct mosquitto *mqtt_client = NULL; #endif @@ -71,15 +73,16 @@ extern OpenSprinkler os; extern ProgramData pd; extern char tmp_buffer[]; +static unsigned long last_reconnect_attempt; #define OS_MQTT_KEEPALIVE 60 -#define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config -#define MQTT_MAX_HOST_LEN 50 // Maximum broker/host name length -#define MQTT_MAX_USERNAME_LEN 50 // Maximum username length -#define MQTT_MAX_PASSWORD_LEN 100 // Maximum password length +#define MQTT_DEFAULT_PORT 1883 // Default port for MQTT. Can be overwritten through App config +#define MQTT_MAX_HOST_LEN 100 // Note: App is set to max 100 chars for broker name +#define MQTT_MAX_USERNAME_LEN 50 // Note: App is set to max 50 chars for username +#define MQTT_MAX_PASSWORD_LEN 100 // Note: App is set to max 100 chars for password #define MQTT_MAX_TOPIC_LEN 24 // Maximum topic length -#define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit -#define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts +#define MQTT_MAX_ID_LEN 16 // MQTT Client Id to uniquely reference this unit +#define MQTT_RECONNECT_DELAY 120 // Minumum of 60 seconds between reconnect attempts #define MQTT_AVAILABILITY_TOPIC "availability" #define MQTT_ONLINE_PAYLOAD "online" @@ -301,18 +304,54 @@ void runOnceProgram(char *message){ // Initialise the client libraries and event handlers. void OSMqtt::init(void) { DEBUG_LOGF("MQTT Init\r\n"); + char id[MQTT_MAX_ID_LEN + 1] = {0}; +#if defined(ARDUINO) uint8_t mac[6] = {0}; #if defined(ESP8266) os.load_hardware_mac(mac, useEth); #else os.load_hardware_mac(mac, true); #endif - snprintf(_id, MQTT_MAX_ID_LEN, "OS-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + snprintf(id, MQTT_MAX_ID_LEN, "OS-%02X%02X%02X%02X%02X%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +#else + struct ifaddrs *ifaddr=NULL; + struct ifaddrs *ifa = NULL; + int i = 0; + + if (getifaddrs(&ifaddr) != -1) //OSPi: Generate Client Id from MAC: + { + for ( ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) { + if ( (ifa->ifa_addr) && (ifa->ifa_addr->sa_family == AF_PACKET) ) { + if (strcmp(ifa->ifa_name, "lo") == 0) continue; + struct sockaddr_ll *s = (struct sockaddr_ll*)ifa->ifa_addr; + sprintf(id, "OS-%02x%02x%02x%02x%02x%02x", s->sll_addr[0], s->sll_addr[1], s->sll_addr[2], s->sll_addr[3], s->sll_addr[4], s->sll_addr[5]); + break; + } + } + freeifaddrs(ifaddr); + } +#endif + + init(id); +} + +const char* getOnlineTopic() { + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + strncat(tmp_buffer, MQTT_AVAILABILITY_TOPIC, TMP_BUFFER_SIZE_L); + return tmp_buffer; +} + + +// Initialise the client libraries and event handlers. +void OSMqtt::init(const char * clientId) { + DEBUG_LOGF("MQTT Init: ClientId %s\r\n", clientId); + + strncpy(_id, clientId, MQTT_MAX_ID_LEN); _id[MQTT_MAX_ID_LEN] = 0; _init(); -}; +} // Start the MQTT service and connect to the MQTT broker using the stored configuration. void OSMqtt::begin(void) { @@ -421,7 +460,6 @@ void OSMqtt::subscribe(void){ // Regularly call the loop function to ensure "keep alive" messages are sent to the broker and to reconnect if needed. void OSMqtt::loop(void) { - static unsigned long last_reconnect_attempt = 0; if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return; @@ -481,6 +519,7 @@ int OSMqtt::_init(void) { mqtt_client = new PubSubClient(*client); mqtt_client->setKeepAlive(OS_MQTT_KEEPALIVE); + mqtt_client->setBufferSize(2048); //Most LORA Pakets are bigger! if (mqtt_client == NULL) { DEBUG_LOGF("MQTT Init: Failed to initialise client\r\n"); @@ -508,6 +547,7 @@ int OSMqtt::_connect(void) { if(state) break; tries++; } while(triesstate()); @@ -561,7 +601,7 @@ void subscribe_callback(const char *topic, unsigned char *payload, unsigned int } int OSMqtt::_subscribe(void){ - mqtt_client->setCallback(subscribe_callback); + setCallback(1, subscribe_callback); if (!mqtt_client->subscribe(_sub_topic)) { DEBUG_LOGF("MQTT Subscribe: Failed (%d)\r\n", mqtt_client->state()); return MQTT_ERROR; @@ -574,6 +614,59 @@ int OSMqtt::_loop(void) { return mqtt_client->state(); } +bool OSMqtt::connected(void) { + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0 || !_connected()) + return false; + return mqtt_client->connected(); +} + +bool OSMqtt::subscribe(const char *topic) { + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return false; + return mqtt_client->subscribe(topic); +} + +bool OSMqtt::unsubscribe(const char *topic) { + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return false; + return mqtt_client->unsubscribe(topic); +} + +typedef struct KEY_CALLBACK { + int key; + MQTT_CALLBACK_SIGNATURE; +} KEY_CALLBACK_t; +#define MAX_CALLBACKS 2 + +static KEY_CALLBACK_t key_callbacks[MAX_CALLBACKS] = {0}; + +void key_callback(char* mtopic, byte* payload, unsigned int length) { + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (key_callbacks[i].callback) + key_callbacks[i].callback(mtopic, payload, length); + } +} + +void registerCallback(int key, MQTT_CALLBACK_SIGNATURE) { + int j = -1; + for (int i = 0; i < MAX_CALLBACKS; i++) { + if ((callback && key_callbacks[i].key == 0) || key_callbacks[i].key == key) { + if (j < 0 || key > key_callbacks[j].key) + j = i; + break; + } + } + if (j >= 0) { + key_callbacks[j].callback = callback; + key_callbacks[j].key = key; + return; + } + DEBUG_LOGF("MQTT setCallback: failed!"); +} + +void OSMqtt::setCallback(int key, MQTT_CALLBACK_SIGNATURE) { + registerCallback(key, callback); + mqtt_client->setCallback(key_callback); +} + const char * OSMqtt::_state_string(int rc) { switch (rc) { case MQTT_CONNECTION_TIMEOUT: return "The server didn't respond within the keepalive time"; @@ -589,6 +682,12 @@ const char * OSMqtt::_state_string(int rc) { default: return "Unrecognised state"; } } + +bool OSMqtt::reconnect() { + if (mqtt_client == NULL || !_enabled || os.status.network_fails > 0) return false; + return _connect(); +} + #else /************************** RASPBERRY PI / Linux ****************************************/ @@ -632,7 +731,7 @@ int OSMqtt::_init(void) { if (mqtt_client) { mosquitto_destroy(mqtt_client); mqtt_client = NULL; }; - mqtt_client = mosquitto_new("OS", true, NULL); + mqtt_client = mosquitto_new(_id, true, NULL); if (mqtt_client == NULL) { DEBUG_PRINTF("MQTT Init: Failed to initialise client\r\n"); return MQTT_ERROR; @@ -668,10 +767,15 @@ int OSMqtt::_connect(void) { // Allow 10ms for the Broker's ack to be received. We need this on start-up so that the // connection is registered before we attempt to send our first NOTIFY_REBOOT notification. usleep(10000); + last_reconnect_attempt = millis(); return MQTT_SUCCESS; } +bool OSMqtt::reconnect() { + return mosquitto_reconnect(mqtt_client); +} + int OSMqtt::_disconnect(void) { int rc = mosquitto_disconnect(mqtt_client); return rc == MOSQ_ERR_SUCCESS ? MQTT_SUCCESS : MQTT_ERROR; @@ -679,6 +783,8 @@ int OSMqtt::_disconnect(void) { bool OSMqtt::_connected(void) { return ::_connected; } +bool OSMqtt::connected(void) { return _connected(); } + int OSMqtt::_publish(const char *topic, const char *payload) { String total_topic(_pub_topic); // concatenate root topic with specific topic total_topic += "/"; @@ -717,7 +823,7 @@ void subscribe_callback(struct mosquitto *mosq, void *obj, const struct mosquitt } int OSMqtt::_subscribe(void) { - mosquitto_message_callback_set(mqtt_client, subscribe_callback); + setCallback(1, subscribe_callback); int rc = mosquitto_subscribe(mqtt_client, NULL, _sub_topic, 0); if (rc != MOSQ_ERR_SUCCESS) { DEBUG_LOGF("MQTT Subscribe: Failed (%s)\r\n", mosquitto_strerror(rc)); @@ -730,7 +836,65 @@ int OSMqtt::_loop(void) { return mosquitto_loop(mqtt_client, 0 , 1); } +bool OSMqtt::subscribe(const char *topic) { + if (!mqtt_client || !_enabled || os.status.network_fails > 0) return false; + return mosquitto_subscribe(mqtt_client, NULL, topic, 0); +} + +bool OSMqtt::unsubscribe(const char *topic) { + if (!mqtt_client || !_enabled || os.status.network_fails > 0) return false; + return mosquitto_unsubscribe(mqtt_client, NULL, topic); +} + +typedef struct KEY_CALLBACK { + int key; + void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *); +} KEY_CALLBACK_t; +#define MAX_CALLBACKS 2 + +static KEY_CALLBACK_t key_callbacks[MAX_CALLBACKS] = {0}; + +static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (key_callbacks[i].callback) { + DEBUG_PRINT("Callback exec: "); + DEBUG_PRINTLN(key_callbacks[i].key); + key_callbacks[i].callback(mosq, obj, msg); + } + } +} + +static void registerCallback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { + boolean ok = false; + for (int i = 0; i < MAX_CALLBACKS; i++) { + if (callback) { + if (key_callbacks[i].key == 0 || key_callbacks[i].key == key) { + key_callbacks[i].callback = callback; + key_callbacks[i].key = key; + ok = true; + break; + } + } else { + if (key_callbacks[i].key == key) { + key_callbacks[i].callback = NULL; + key_callbacks[i].key = 0; + ok = true; + break; + } + } + } + if (!ok) + DEBUG_LOGF("MQTT setCallback: failed!"); +} + + +void OSMqtt::setCallback(int key, void (*callback)(struct mosquitto *, void *, const struct mosquitto_message *)) { + registerCallback(key, callback); + mosquitto_message_callback_set(mqtt_client, sensor_mqtt_callback); +} + const char * OSMqtt::_state_string(int error) { return mosquitto_strerror(error); } + #endif diff --git a/mqtt.h b/mqtt.h index 65c2fd0a..aa0e7d43 100644 --- a/mqtt.h +++ b/mqtt.h @@ -1,60 +1,75 @@ -/* OpenSprinkler Unified Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * OpenSprinkler library header file - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#ifndef _MQTT_H -#define _MQTT_H - -class OSMqtt { -private: - static char _id[]; - static char _host[]; - static int _port; - static char _username[]; - static char _password[]; - static bool _enabled; - static char _pub_topic[]; - static char _sub_topic[]; - static bool _done_subscribed; - - // Following routines are platform specific versions of the public interface - static int _init(void); - static int _connect(void); - static int _disconnect(void); - static bool _connected(void); - static int _publish(const char *topic, const char *payload); - static int _subscribe(void); - static int _loop(void); - static const char * _state_string(int state); - public: - static void init(void); - static void init(const char * id); - static void begin(void); - static bool enabled(void) { return _enabled; }; - static void publish(const char *topic, const char *payload); - static void subscribe(); - static void loop(void); - static char* get_pub_topic() { return _pub_topic; } - static char* get_sub_topic() { return _sub_topic; } -}; - -#endif // _MQTT_H +/* OpenSprinkler Unified Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * OpenSprinkler library header file + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _MQTT_H +#define _MQTT_H +#if defined(ARDUINO) + #include +#else + #include +#endif + +class OSMqtt { +private: + static char _id[]; + static char _host[]; + static int _port; + static char _username[]; + static char _password[]; + static bool _enabled; + static char _pub_topic[]; + static char _sub_topic[]; + static bool _done_subscribed; + + // Following routines are platform specific versions of the public interface + static int _init(void); + static int _connect(void); + static int _disconnect(void); + static bool _connected(void); + static int _publish(const char *topic, const char *payload); + static int _subscribe(void); + static int _loop(void); + static const char * _state_string(int state); + public: + static void init(void); + static void init(const char * id); + static void begin(void); + static bool enabled(void) { return _enabled; }; + static void publish(const char *topic, const char *payload); + static void subscribe(); + static void loop(void); + static char* get_pub_topic() { return _pub_topic; } + static char* get_sub_topic() { return _sub_topic; } + + static bool connected(); + static bool subscribe(const char *topic); + static bool unsubscribe(const char *topic); + static bool reconnect(); +#if defined(ARDUINO) + static void setCallback(int key, MQTT_CALLBACK_SIGNATURE); +#else + static void setCallback(int key, void (*on_message)(struct mosquitto *, void *, const struct mosquitto_message *)); +#endif +}; + +#endif // _MQTT_H diff --git a/notifier.cpp b/notifier.cpp deleted file mode 100644 index 532e524a..00000000 --- a/notifier.cpp +++ /dev/null @@ -1,547 +0,0 @@ -/* OpenSprinkler Unified Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * Notifier data structures and functions - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#include "notifier.h" -#include "program.h" -#include "ArduinoJson.hpp" -#include "opensprinkler_server.h" - -NotifNodeStruct* NotifQueue::head = NULL; -NotifNodeStruct* NotifQueue::tail = NULL; -unsigned char NotifQueue::nqueue = 0; - -extern OpenSprinkler os; -extern ProgramData pd; -extern char tmp_buffer[]; -extern char ether_buffer[]; -extern float flow_last_gpm; -void remote_http_callback(char*); - -bool is_notif_enabled(uint16_t type) { - uint16_t notif = (uint16_t)os.iopts[IOPT_NOTIF_ENABLE] | ((uint16_t)os.iopts[IOPT_NOTIF2_ENABLE] << 8); - return (notif&type) != 0; -} - -uint16_t get_notif_enabled() { - return (uint16_t)os.iopts[IOPT_NOTIF_ENABLE]|((uint16_t)os.iopts[IOPT_NOTIF2_ENABLE]<<8); -} - -void set_notif_enabled(uint16_t notif) { - os.iopts[IOPT_NOTIF_ENABLE] = notif&0xFF; - os.iopts[IOPT_NOTIF2_ENABLE] = notif >> 8; -} - -void ip2string(char* str, size_t str_len, unsigned char ip[4]) { - snprintf_P(str+strlen(str), str_len, PSTR("%d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]); -} - -bool NotifQueue::add(uint16_t t, uint32_t l, float f, uint8_t b) { - if (!is_notif_enabled(t)) { // if not subscribed to this type, return - return false; - } - if(nqueuenext = node; - } - tail = node; - nqueue++; - DEBUG_PRINTF("NotifQueue::add (type %d) [%d]\n", t, nqueue); - return true; - } - DEBUG_PRINTLN(F("NotifQueue::add queue is full!")); - return false; -} - -void NotifQueue::clear() { - while(nqueue!=0) { - NotifNodeStruct* node = head; - head = head->next; - if(head==NULL) { - tail = NULL; - } - delete node; - nqueue--; - } -} - -void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval); - -bool NotifQueue::run(int n) { - if(nqueue == 0) return false; // queue is empty - if(n<=0 || n>nqueue) n=nqueue; - while(nqueue!=0 && n!=0) { - NotifNodeStruct* node = head; - head = head->next; - if(head==NULL) { - tail = NULL; - } - push_message(node->type, node->lval, node->fval, node->bval); - DEBUG_PRINTF("NotifQueue::run (type %d) [%d]\n", node->type, nqueue); - delete node; - nqueue--; - n--; - } - return true; -} - -#define PUSH_TOPIC_LEN 120 -#define PUSH_PAYLOAD_LEN TMP_BUFFER_SIZE - -void push_message(uint16_t type, uint32_t lval, float fval, uint8_t bval) { - if (!is_notif_enabled(type)) { - return; - } - static char topic[PUSH_TOPIC_LEN+1]; - static char payload[PUSH_PAYLOAD_LEN+1]; - char* postval = tmp_buffer+1; // +1 so we can fit a opening { before the loaded config - - // check if ifttt key exists and also if the enable bit is set - os.sopt_load(SOPT_IFTTT_KEY, tmp_buffer); - bool ifttt_enabled = (strlen(tmp_buffer)!=0); - // flow rate - uint32_t flowrate100 = (((uint32_t)os.iopts[IOPT_PULSE_RATE_1])<<8) + os.iopts[IOPT_PULSE_RATE_0]; - -#define DEFAULT_EMAIL_PORT 465 - - // parse email variables - #if defined(SUPPORT_EMAIL) - // define email variables - ArduinoJson::JsonDocument doc; // make sure this has the same scope of email_x variables to prevent use after free - const char *email_host = NULL; - const char *email_username = NULL; - const char *email_password = NULL; - const char *email_recipient = NULL; - int email_port = DEFAULT_EMAIL_PORT; - int email_en = 0; - - os.sopt_load(SOPT_EMAIL_OPTS, postval); - if (*postval != 0) { - // Add the wrapping curly braces to the string - postval = tmp_buffer; - postval[0] = '{'; - int len = strlen(postval); - postval[len] = '}'; - postval[len+1] = 0; - - ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, postval); - // Test the parsing otherwise parse - if (error) { - DEBUG_PRINT(F("mqtt: deserializeJson() failed: ")); - DEBUG_PRINTLN(error.c_str()); - } else { - email_en = doc["en"]; - email_host = doc["host"]; - email_port = doc["port"]; - email_username = doc["user"]; - email_password = doc["pass"]; - email_recipient= doc["recipient"]; - } - } - #endif - - #if defined(ESP8266) - EMailSender::EMailMessage email_message; - #else - struct { - String subject; - String message; - } email_message; - #endif - - bool email_enabled = false; -#if defined(SUPPORT_EMAIL) - if(!email_en){ // todo: this should be simplified - email_enabled = false; - }else{ - email_enabled = true; - } -#endif - - // if none if enabled, return here - if ((!ifttt_enabled) && (!email_enabled) && (!os.mqtt.enabled())) - return; - - if (ifttt_enabled || email_enabled) { - strcpy_P(postval, PSTR("{\"value1\":\"On site [")); - os.sopt_load(SOPT_DEVICE_NAME, topic, PUSH_TOPIC_LEN); - topic[PUSH_TOPIC_LEN]=0; - strcat(postval+strlen(postval), topic); - strcat_P(postval, PSTR("], ")); - if(email_enabled) { - strcat(topic, " "); - email_message.subject = topic; // prefix the email subject with device name - } - } - - if (os.mqtt.enabled()) { - topic[0] = 0; - payload[0] = 0; - } - - switch(type) { - case NOTIFY_STATION_ON: - - if (os.mqtt.enabled()) { - snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d"), lval); - strcat_P(payload, PSTR("{\"state\":1")); - if((int)fval > 0){ - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"duration\":%d"), (int)fval); - } - strcat_P(payload, PSTR("}")); - } - if (ifttt_enabled || email_enabled) { - strcat_P(postval, PSTR("Station [")); - os.get_station_name(lval, postval+strlen(postval)); - strcat_P(postval, PSTR("] just turned on.")); - if((int)fval > 0){ - strcat_P(postval, PSTR(" It's scheduled to run for ")); - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" %d minutes %d seconds."), (int)fval/60, (int)fval%60); - } - if(email_enabled) { email_message.subject += PSTR("station event"); } - } break; - - case NOTIFY_STATION_OFF: - - if (os.mqtt.enabled()) { - snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d"), lval); - strcat_P(payload, PSTR("{\"state\":0")); - if((int)fval > 0) { - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"duration\":%d"), (int)fval); - if (os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"flow\":%d.%02d"), (int)gpm, (int)(gpm*100)%100); - #else - snprintf_P(payload+strlen(payload), PUSH_PAYLOAD_LEN, PSTR(",\"flow\":%.2f"), gpm); - #endif - } - } - strcat_P(payload, PSTR("}")); - } - if (ifttt_enabled || email_enabled) { - strcat_P(postval, PSTR("Station [")); - os.get_station_name(lval, postval+strlen(postval)); - strcat_P(postval, PSTR("] closed.")); - if((int)fval > 0) { - strcat_P(postval, PSTR(" It ran for ")); - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" %d minutes %d seconds."), (int)fval/60, (int)fval%60); - } - - if(os.iopts[IOPT_SENSOR1_TYPE]==SENSOR_TYPE_FLOW) { - float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" Flow rate: %d.%02d"), (int)gpm, (int)(gpm*100)%100); - #else - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" Flow rate: %.2f"), gpm); - #endif - } - if(email_enabled) { email_message.subject += PSTR("station event"); } - } - break; - - case NOTIFY_FLOW_ALERT:{ - //First determine if a Flow Alert should be sent based on flow amount and setpoint - - //Added variable to track flow alert status - bool flow_alert_flag = false; - - //Added variable for flow_gpm_alert_setpoint and set default value to max - float flow_gpm_alert_setpoint = 999.9f; - - //Added variable for tmp station name - char tmp_station_name[STATION_NAME_SIZE]; - - //Get satation name - os.get_station_name(lval, tmp_station_name); - - // only proceed if flow rate is positive, and the station name has at least 5 characters - if (flow_last_gpm > 0 && strlen(tmp_station_name) > 5) { - const char *station_name_last_five_chars = tmp_station_name; - // extract the last 5 characters - station_name_last_five_chars = tmp_station_name + strlen(tmp_station_name) - 5; - // Convert last five characters to number and check if valid - // Had to switch to use strtod because sscanf in AVR doesn't work with float :( - char *endptr; - flow_gpm_alert_setpoint = strtod(station_name_last_five_chars, &endptr); - if (endptr != station_name_last_five_chars) { - //station_name_last_five_chars was successfully converted to a number - //flow_last_gpm is actually collected and stored as pulses per minute, not gallons per minute - // Alert Check - Compare flow_gpm_alert_setpoint with flow_last_gpm and enable flow_alert_flag if flow is above setpoint - if ((flow_last_gpm*flowrate100/100.f) > flow_gpm_alert_setpoint) { - flow_alert_flag = true; - } - } else { - //Could not convert to a valid number. If a number is not detected as a station name suffix, never send an alert - flow_alert_flag = false; - } - } else { - //Station name was not long enough to include 5 character flow setpoint. - flow_alert_flag = false; - } - - // If flow_alert_flag is true, format the appropriate messages, else don't send alert - if (flow_alert_flag == true) { - - if (os.mqtt.enabled()) { - //Format mqtt message - snprintf_P(topic, PUSH_TOPIC_LEN, PSTR("station/%d/alert/flow"), lval); - float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%d.%02d,\"duration\":%d,\"alert_setpoint\":%d.%02d}"), (int)gpm, (int)(gpm*100)%100, - (int)fval, (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint*100)%100); - #else - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"flow_rate\":%.2f,\"duration\":%d,\"alert_setpoint\":%.4f}"), gpm, (int)fval, flow_gpm_alert_setpoint); - #endif - } - - - if (ifttt_enabled || email_enabled) { - //Format ifttt\email message - - // Get and format current local time as "YYYY-MM-DD hh:mm:ss AM/PM" - strcat_P(postval, PSTR("at ")); - time_os_t curr_time = os.now_tz(); - #if defined(ARDUINO) - tmElements_t tm; - breakTime(curr_time, tm); - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), - 1970+tm.Year, tm.Month, tm.Day, tm.Hour, tm.Minute, tm.Second); - #else - struct tm *ti = gmtime(&curr_time); - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%04d-%02d-%02d %02d:%02d:%02d"), - ti->tm_year+1900, ti->tm_mon+1, ti->tm_mday, ti->tm_hour, ti->tm_min, ti->tm_sec); - #endif - - strcat_P(postval, PSTR(", Station [")); - //Truncate flow setpoint value off station name to shorten ifttt\email message - tmp_station_name[(strlen(tmp_station_name) - 5)] = '\0'; - strcat_P(postval, tmp_station_name); - strcat_P(postval, PSTR("]")); - if(fval > 0){ // if there is a valid duration - strcat_P(postval, PSTR(" ran for ")); - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("%d minutes %d seconds."), (int)fval/60, ((int)fval%60)); - } - - strcat_P(postval, PSTR(" FLOW ALERT!")); - float gpm = flow_last_gpm * flowrate100 / 100.f; - #if defined(OS_AVR) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" | Flow rate: %d.%02d > Flow alert setpoint: %d.%02d"), - (int)gpm, (int)(gpm*100)%100, (int)flow_gpm_alert_setpoint, (int)(flow_gpm_alert_setpoint*100)%100); - #else - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR(" | Flow rate: %.2f > Flow alert setpoint: %.4f"), - gpm, flow_gpm_alert_setpoint); - #endif - - if(email_enabled) { email_message.subject += PSTR("- FLOW ALERT"); } - - } - } else { - //Do not send an alert. Flow was not above setpoint or setpoint not valid. - //Must force ifftt_enabled and email_enabled to false to prevent sending - //Can not force os.mqtt.enabled() off, but it will not publish an mqtt message as topic\payload will be empty. - ifttt_enabled=false; - email_enabled=false; - } - break; - } - - case NOTIFY_PROGRAM_SCHED: - - if (ifttt_enabled || email_enabled) { - if (bval) strcat_P(postval, PSTR("manually")); - else strcat_P(postval, PSTR("automatically")); - strcat_P(postval, PSTR(" scheduled Program ")); - { - ProgramStruct prog; - pd.read(lval, &prog); - if(lval0) { - strcat_P(postval, PSTR("external IP updated: ")); - unsigned char ip[4] = {(unsigned char)((lval>>24)&0xFF), - (unsigned char)((lval>>16)&0xFF), - (unsigned char)((lval>>8)&0xFF), - (unsigned char)(lval&0xFF)}; - ip2string(postval, TMP_BUFFER_SIZE, ip); - } - if(fval>=0) { - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("water level updated: %d%%."), (int)fval); - } - if(email_enabled) { email_message.subject += PSTR("weather update event"); } - } - break; - - case NOTIFY_REBOOT: - if (os.mqtt.enabled()) { - strcpy_P(topic, PSTR("system")); - snprintf_P(payload, PUSH_PAYLOAD_LEN, PSTR("{\"state\":\"started\",\"cause\":%d}"), (int)os.last_reboot_cause); - } - if (ifttt_enabled || email_enabled) { - #if defined(ARDUINO) - snprintf_P(postval+strlen(postval), TMP_BUFFER_SIZE, PSTR("rebooted. Cause: %d. Device IP: "), os.last_reboot_cause); - #if defined(ESP8266) - { - IPAddress _ip; - if (useEth) { - //_ip = Ethernet.localIP(); - _ip = eth.localIP(); - } else { - _ip = WiFi.localIP(); - } - unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; - ip2string(postval, TMP_BUFFER_SIZE, ip); - } - #else - ip2string(postval, TMP_BUFFER_SIZE, &(Ethernet.localIP()[0])); - #endif - #else - strcat_P(postval, PSTR("controller process restarted.")); - #endif - if(email_enabled) { email_message.subject += PSTR("reboot event"); } - } - break; - } - - if (os.mqtt.enabled() && strlen(topic) && strlen(payload)) - os.mqtt.publish(topic, payload); - - if (ifttt_enabled) { - strcat_P(postval, PSTR("\"}")); - - BufferFiller bf = BufferFiller(ether_buffer, TMP_BUFFER_SIZE); - bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" - "Host: $S\r\n" - "Accept: */*\r\n" - "Content-Length: $D\r\n" - "Content-Type: application/json\r\n\r\n$S"), - SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); - - os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, remote_http_callback); - } - - if(email_enabled){ - email_message.message = strchr(postval, 'O'); // ad-hoc: remove the value1 part from the ifttt message - #if defined(ARDUINO) - #if defined(ESP8266) - if(email_host && email_username && email_password && email_recipient) { // make sure all are valid - EMailSender emailSend(email_username, email_password); - emailSend.setSMTPServer(email_host); - emailSend.setSMTPPort(email_port); - EMailSender::Response resp = emailSend.send(email_recipient, email_message); - } - #endif - #else - struct smtp *smtp = NULL; - String email_port_str = to_string(email_port); - smtp_status_code rc; - if(email_host && email_username && email_password && email_recipient) { // make sure all are valid - rc = smtp_open(email_host, email_port_str.c_str(), SMTP_SECURITY_TLS, SMTP_NO_CERT_VERIFY, NULL, &smtp); - rc = smtp_auth(smtp, SMTP_AUTH_PLAIN, email_username, email_password); - rc = smtp_address_add(smtp, SMTP_ADDRESS_FROM, email_username, "OpenSprinkler"); - rc = smtp_address_add(smtp, SMTP_ADDRESS_TO, email_recipient, "User"); - rc = smtp_header_add(smtp, "Subject", email_message.subject.c_str()); - rc = smtp_mail(smtp, email_message.message.c_str()); - rc = smtp_close(smtp); - if (rc!=SMTP_STATUS_OK) { - DEBUG_PRINTF("SMTP: Error %s\n", smtp_status_code_errstr(rc)); - } - } - #endif - } -} diff --git a/notifier.h b/notifier.h deleted file mode 100644 index 2572bd84..00000000 --- a/notifier.h +++ /dev/null @@ -1,59 +0,0 @@ -/* OpenSprinkler Unified Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * Notifier data structures and functions header file - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - - -#ifndef _NOTIFIER_H -#define _NOTIFIER_H - -#define NOTIF_QUEUE_MAXSIZE 32 - -#include "OpenSprinkler.h" -#include "types.h" - -/** Notifier Node data structure */ -struct NotifNodeStruct { - uint16_t type; - uint32_t lval; - float fval; - uint8_t bval; - NotifNodeStruct *next; - NotifNodeStruct(uint16_t t, uint32_t l=0, float f=0.f, uint8_t b=0) : type(t), lval(l), fval(f), bval(b), next(NULL) - { } -}; - -/** Notifier Queue data structure */ -class NotifQueue { -public: - // Insert a new notification element - static bool add(uint16_t t, uint32_t l=0, float f=0.f, uint8_t b=0); - // Clear all elements (i.e. empty the queue) - static void clear(); - // Run/Process elements. By default process 1 at a time. If n<=0, process all. - static bool run(int n=1); -protected: - static NotifNodeStruct* head; - static NotifNodeStruct* tail; - static unsigned char nqueue; -}; - -#endif // _NOTIFIER_H \ No newline at end of file diff --git a/opensprinkler-git.code-workspace b/opensprinkler-git.code-workspace new file mode 100644 index 00000000..397456c6 --- /dev/null +++ b/opensprinkler-git.code-workspace @@ -0,0 +1,14 @@ +{ + "folders": [ + { + "path": "." + }, + { + "path": "..\\libs" + }, + { + "path": "..\\esp8266_3.0.2\\libraries" + } + ], + "settings": {} +} \ No newline at end of file diff --git a/opensprinkler_server.cpp b/opensprinkler_server.cpp index 8d073d30..c44a64b3 100644 --- a/opensprinkler_server.cpp +++ b/opensprinkler_server.cpp @@ -28,6 +28,8 @@ #include "weather.h" #include "mqtt.h" #include "main.h" +#include "sensors.h" +#include "osinfluxdb.h" // External variables defined in main ion file #if defined(USE_OTF) @@ -206,16 +208,27 @@ unsigned char findKeyVal (const char *str,char *strbuf, uint16_t maxlen,const ch } void rewind_ether_buffer() { - bfill = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE*2); + bfill = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE_L); ether_buffer[0] = 0; } +#if defined(USE_OTF) +boolean buffer_available() { + return bfill.position() < RESPONSE_BUFFER_SIZE/2 && available_ether_buffer() > 0; +} +#else +boolean buffer_available() { + return available_ether_buffer() > 0; +} +#endif + void send_packet(OTF_PARAMS_DEF) { #if defined(USE_OTF) res.writeBodyData(ether_buffer, strlen(ether_buffer)); #else m_client->write((const uint8_t *)ether_buffer, strlen(ether_buffer)); #endif + rewind_ether_buffer(); } @@ -240,6 +253,24 @@ void print_header(bool isJson=true) { } #endif +#if defined(USE_OTF) +void print_header_download(OTF_PARAMS_DEF, int len=0) { + res.writeStatus(200, F("OK")); + res.writeHeader(F("Content-Type"), F("text/plain")); + res.writeHeader(F("Content-Disposition"), F("attachment; filename=\"log.csv\";")); + if( + len>0) + res.writeHeader(F("Content-Length"), len); + res.writeHeader(F("Access-Control-Allow-Origin"), F("*")); + res.writeHeader(F("Cache-Control"), F("max-age=0, no-cache, no-store, must-revalidate")); + res.writeHeader(F("Connection"), F("close")); +} +#else +void print_header_download() { + bfill.emit_p(PSTR("$F$F$F$F$F\r\n"), html200OK, "Content-Type: text/plain", "Content-Disposition: attachment; filename=\"log.txt\";", htmlAccessControl, htmlNoCache); +} +#endif + #if defined(USE_OTF) #if !defined(ARDUINO) string two_digits(uint8_t x) { @@ -270,6 +301,7 @@ void otf_send_result(OTF_PARAMS_DEF, unsigned char code, const char *item = NULL print_header(OTF_PARAMS, true, json.length()); res.writeBodyChunk((char *)"%s",json.c_str()); } +#endif #if defined(ESP8266) void update_server_send_result(unsigned char code, const char* item = NULL) { @@ -366,8 +398,6 @@ void on_ap_try_connect(OTF_PARAMS_DEF) { } } #endif -#endif - /** Check and verify password */ #if defined(USE_OTF) @@ -436,6 +466,20 @@ void server_json_stations_attrib(const char* name, unsigned char *attrib) bfill.emit_p(PSTR("],")); } +void server_json_stations_attrib16(const char* name, uint16_t *attrib) +{ + bfill.emit_p(PSTR("\"$F\":["), name); + for(unsigned char bid=0;bidtype, data->sped); } - if (available_ether_buffer() <=0 ) { + if (!buffer_available() ) { send_packet(OTF_PARAMS); } } @@ -543,6 +589,26 @@ void server_change_stations_attrib(char *p, char header, unsigned char *attrib) } } +#if defined(USE_OTF) +void server_change_stations_attrib16(const OTF::Request &req, char header, uint16_t *attrib) +#else +void server_change_stations_attrib16(char *p, char header, uint16_t *attrib) +#endif +{ + char tbuf2[6] = {0, 0, 0, 0, 0, 0}; + unsigned char bid, s, sid; + tbuf2[0]=header; + for(bid=0;bidos.nstations) handle_return(HTML_DATA_OUTOFBOUND); if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("st"), true) && - findKeyVal(FKV_SOURCE, tmp_buffer+1, TMP_BUFFER_SIZE-1, PSTR("sd"), true)) { + findKeyVal(FKV_SOURCE, tmp_buffer+1, TMP_BUFFER_SIZE-1, PSTR("sd"), true)) { tmp_buffer[0]-='0'; tmp_buffer[STATION_SPECIAL_DATA_SIZE] = 0; @@ -761,7 +830,7 @@ void server_change_runonce(OTF_PARAMS_DEF) { prog.days[0] = (epoch_t >> 8) & 0b11111111; //one interval past current day in epoch time prog.days[1] = epoch_t & 0b11111111; //one interval past current day in epoch time prog.starttimes[0] = curr_time % 1440; //one interval past current time - strcpy_P(prog.name, PSTR("Run-Once with repeat")); + strcpy_P(prog.name, "Run Once (Repeat)"); //TODO: Change name //if no more repeats, remove interval to flag for deletion if(prog.starttimes[1] == 0){ @@ -1053,8 +1122,8 @@ void server_json_options_main() { } #endif #else - // for Linux-based platforms, we can't adjust contrast or backlight - if(oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT) continue; + // for Linux-based platforms, there is no LCD currently + if(oid==IOPT_LCD_CONTRAST || oid==IOPT_LCD_BACKLIGHT || oid==IOPT_LCD_DIMMING) continue; #endif // each json name takes 5 characters @@ -1064,6 +1133,9 @@ void server_json_options_main() { bfill.emit_p(PSTR(",")); } + //feature flag Analog Sensor API + bfill.emit_p(PSTR(",\"feature\":\"ASB\"")); + bfill.emit_p(PSTR(",\"dexp\":$D,\"mexp\":$D,\"hwt\":$D,"), os.detect_exp(), MAX_EXT_BOARDS, os.hw_type); // print master array unsigned char masid, optidx; @@ -1123,7 +1195,7 @@ void server_json_programs_main(OTF_PARAMS_DEF) { } // push out a packet if available // buffer size is getting small - if (available_ether_buffer() <= 0) { + if (!buffer_available()) { send_packet(OTF_PARAMS); } } @@ -1209,16 +1281,27 @@ void server_json_controller_main(OTF_PARAMS_DEF) { #else os.load_hardware_mac(mac, true); #endif + char mqtt_opt[MAX_SOPTS_SIZE]; + os.sopt_load(SOPT_MQTT_OPTS, mqtt_opt); + //DEBUG_PRINTLN(mqtt_opt); + + //Test for invalid mqtt options: + int l = strlen(mqtt_opt); + if (l > 0 && mqtt_opt[l-1] != '"') { //first+last char + mqtt_opt[l] = '"'; + mqtt_opt[l+1] = 0; + } + //DEBUG_PRINTLN(mqtt_opt); bfill.emit_p(PSTR("\"mac\":\"$X:$X:$X:$X:$X:$X\","), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$O},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","), + bfill.emit_p(PSTR("\"loc\":\"$O\",\"jsp\":\"$O\",\"wsp\":\"$O\",\"wto\":{$O},\"ifkey\":\"$O\",\"mqtt\":{$S},\"wtdata\":$S,\"wterr\":$D,\"dname\":\"$O\","), SOPT_LOCATION, SOPT_JAVASCRIPTURL, SOPT_WEATHERURL, SOPT_WEATHER_OPTS, SOPT_IFTTT_KEY, - SOPT_MQTT_OPTS, + mqtt_opt, strlen(wt_rawData)==0?"{}":wt_rawData, wt_errCode, SOPT_DEVICE_NAME); @@ -1247,7 +1330,7 @@ void server_json_controller_main(OTF_PARAMS_DEF) { for(sid=0;sid>=2; } + if (oid==IOPT_NOTIF_ENABLE) { + set_notif_enabled(v); + continue; + } + if (v>=0 && v<=max_value) { os.iopts[oid] = v; } else { @@ -1563,6 +1660,8 @@ void server_change_options(OTF_PARAMS_DEF) #if !defined(USE_OTF) urlDecode(tmp_buffer); #endif + DEBUG_PRINTLN("MQTT:"); + DEBUG_PRINTLN(tmp_buffer); os.sopt_save(SOPT_MQTT_OPTS, tmp_buffer); os.status.req_mqtt_restart = true; } else if (keyfound) { @@ -1571,6 +1670,19 @@ void server_change_options(OTF_PARAMS_DEF) os.status.req_mqtt_restart = true; } + //influxdb set + keyfound = 0; + if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("influxdb"), true, &keyfound)) { + #if !defined(USE_OTF) + urlDecode(tmp_buffer); + #endif + os.influxdb.set_influx_config(tmp_buffer); + } else if (keyfound) { + tmp_buffer[0]=0; + os.influxdb.set_influx_config(tmp_buffer); + } + //end influxdb set + keyfound = 0; if(findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("email"), true, &keyfound)) { #if !defined(USE_OTF) @@ -1734,12 +1846,12 @@ void server_change_manual(OTF_PARAMS_DEF) { RuntimeQueueStruct *q = NULL; unsigned char sqi = pd.station_qid[sid]; // check if the station already has a schedule - if (sqi!=0xFF) { // if so, do nothing - + if (sqi!=0xFF) { // if so, we will overwrite the schedule + q = pd.queue+sqi; } else { // otherwise create a new queue element q = pd.enqueue(); } - // if the queue is not full (and the station doesn't already have a schedule + // if the queue is not full if (q) { q->st = 0; q->dur = timer; @@ -1843,7 +1955,7 @@ void server_json_log(OTF_PARAMS_DEF) { bool comma = 0; for(unsigned int i=start;i<=end;i++) { - snprintf(tmp_buffer, TMP_BUFFER_SIZE*2 , "%d", i); + snprintf(tmp_buffer, TMP_BUFFER_SIZE_L , "%d", i); make_logfile_name(tmp_buffer); #if defined(ESP8266) @@ -1907,7 +2019,7 @@ void server_json_log(OTF_PARAMS_DEF) { bfill.emit_p(PSTR("$S"), tmp_buffer); // if the available ether buffer size is getting small // push out a packet - if (available_ether_buffer() <= 0) { + if (!buffer_available()) { send_packet(OTF_PARAMS); } } @@ -2074,7 +2186,7 @@ void server_fill_files(OTF_PARAMS_DEF) { ether_buffer[75] = 0; FSInfo fs_info; for(int index=1;index<64;index++) { - snprintf(tmp_buffer, TMP_BUFFER_SIZE*2 , "%d", index); + snprintf(tmp_buffer, TMP_BUFFER_SIZE_L , "%d", index); make_logfile_name(tmp_buffer); DEBUG_PRINT(F("creating ")); DEBUG_PRINT(tmp_buffer); @@ -2089,71 +2201,1893 @@ void server_fill_files(OTF_PARAMS_DEF) { } */ -typedef void (*URLHandler)(OTF_PARAMS_DEF); +char* urlDecodeAndUnescape(char *buf) { + #if !defined(USE_OTF) + urlDecode(buf); + #endif + strReplace(buf, '\"', '\''); + strReplace(buf, '\\', '/'); + return buf; +} -/* Server function urls - * To save RAM space, each GET command keyword is exactly - * 2 characters long, with no ending 0 - * The order must exactly match the order of the - * handler functions below +/** + * si + * Sensor config User Defined + * {"nr":1,"fac":100,"div":1,"unit":"bar"} */ -const char _url_keys[] PROGMEM = - "cv" - "jc" - "dp" - "cp" - "cr" - "mp" - "up" - "jp" - "co" - "jo" - "sp" - "js" - "cm" - "cs" - "jn" - "je" - "jl" - "dl" - "su" - "cu" - "ja" - "pq" - "db" -#if defined(ARDUINO) - //"ff" +void server_sensor_config_userdef(OTF_PARAMS_DEF) +{ +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; #endif - ; -// Server function handlers -URLHandler urls[] = { - server_change_values, // cv - server_json_controller, // jc - server_delete_program, // dp - server_change_program, // cp - server_change_runonce, // cr - server_manual_program, // mp - server_moveup_program, // up - server_json_programs, // jp - server_change_options, // co - server_json_options, // jo - server_change_password, // sp - server_json_status, // js - server_change_manual, // cm - server_change_stations, // cs - server_json_stations, // jn - server_json_station_special,// je - server_json_log, // jl - server_delete_log, // dl - server_view_scripturl, // su - server_change_scripturl,// cu - server_json_all, // ja - server_pause_queue, // pq - server_json_debug, // db -#if defined(ARDUINO) - //server_fill_files, + DEBUG_PRINTLN(F("server_sensor_config userdef")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); + + int16_t factor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("fac"), true)) + factor = strtol(tmp_buffer, NULL, 0); // factor + + int16_t divider = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("div"), true)) + divider = strtol(tmp_buffer, NULL, 0); // divider + + char userdef_unit[8]; + memset(userdef_unit, 0, sizeof(userdef_unit)); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { + strncpy(userdef_unit, urlDecodeAndUnescape(tmp_buffer), sizeof(userdef_unit)-1); // unit + } + int16_t assigned_unitid = -1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unitid"), true)) + assigned_unitid = strtol(tmp_buffer, NULL, 0); // divider + + int16_t offset_mv = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) + offset_mv = strtol(tmp_buffer, NULL, 0); // offset in millivolt + int16_t offset2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset2"), true)) + offset2 = strtol(tmp_buffer, NULL, 0); // offset unit value + + int ret = sensor_define_userdef(nr, factor, divider, userdef_unit, offset_mv, offset2, assigned_unitid); + ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); +} + +/** + * sj + * get sensorurl + * {"nr":1,"type":1} + * return { "value": "text"} + */ +void server_sensorurl_get(OTF_PARAMS_DEF) +{ +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + DEBUG_PRINTLN(F("sensorUrl_get")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type + + char *value = SensorUrl_get(nr, type); + bfill.emit_p(PSTR("{\"value\":\"$S\"}"), value?value:""); + handle_return(HTML_OK); +} + +/** + * sk + * MQTT and other URL configuration + * type = 0 URL, 1=MQTT Subscription, 2=JSON Filter + * {"nr":1,"type":1,"value":abc} + */ +void server_sensorurl_config(OTF_PARAMS_DEF) +{ +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + DEBUG_PRINTLN(F("serverUrl_config")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type + + char *value = NULL; + uint8_t value_found = 0; + findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value"), true, &value_found); + if (!value_found) + handle_return(HTML_DATA_MISSING); + DEBUG_PRINTLN(tmp_buffer); + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + value = strdup(tmp_buffer); + + bool ok = SensorUrl_add(nr, type, value); + free(value); + handle_return(ok?HTML_SUCCESS:HTML_DATA_MISSING); +} + +/** + * sc + * Sensor config + * {"nr":1,"type":1,"group":0,"name":"myname","ip":123456789,"port":3000,"id":1,"ri":1000,"enable":1,"log":1} + */ +void server_sensor_config(OTF_PARAMS_DEF) +{ +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensor_config")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + if (nr == 0) handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Sensor type + + if (type == 0) { + sensor_delete(nr); + handle_return(HTML_SUCCESS); + } + + DEBUG_PRINTLN(F("server_sensor_config2")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("group"), true)) + handle_return(HTML_DATA_MISSING); + uint group = strtoul(tmp_buffer, NULL, 0); // Sensor group + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) + handle_return(HTML_DATA_MISSING); + char name[30]; + strncpy(name, urlDecodeAndUnescape(tmp_buffer), sizeof(name)-1); // Sensor name + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ip"), true)) + handle_return(HTML_DATA_MISSING); + uint32_t ip = strtoul(tmp_buffer, NULL, 0); // Sensor ip + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("port"), true)) + handle_return(HTML_DATA_MISSING); + uint port = strtoul(tmp_buffer, NULL, 0); // Sensor port + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("id"), true)) + handle_return(HTML_DATA_MISSING); + uint id = strtoul(tmp_buffer, NULL, 0); // Sensor modbus id + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ri"), true)) + handle_return(HTML_DATA_MISSING); + uint ri = strtoul(tmp_buffer, NULL, 0); // Read Interval (s) + + int16_t factor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("fac"), true)) + factor = strtol(tmp_buffer, NULL, 0); // factor + + int16_t divider = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("div"), true)) + divider = strtol(tmp_buffer, NULL, 0); // divider + + DEBUG_PRINTLN(F("server_sensor_config3")); + + char userdef_unit[8]; + memset(userdef_unit, 0, sizeof(userdef_unit)); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unit"), true)) { + DEBUG_PRINTLN(tmp_buffer) + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer) + strncpy(userdef_unit, tmp_buffer, sizeof(userdef_unit)-1); // unit + } + int16_t assigned_unitid = -1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("unitid"), true)) + assigned_unitid = strtol(tmp_buffer, NULL, 0); // divider + + int16_t offset_mv = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset"), true)) + offset_mv = strtol(tmp_buffer, NULL, 0); // offset in millivolt + + int16_t offset2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("offset2"), true)) + offset2 = strtol(tmp_buffer, NULL, 0); // offset2 + + uint enable = 1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("enable"), true)) + enable = strtoul(tmp_buffer, NULL, 0); // 1=enable/0=disable + + uint log = 1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) + log = strtoul(tmp_buffer, NULL, 0); // 1=logging enabled/0=logging disabled + + uint show = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("show"), true)) + show = strtoul(tmp_buffer, NULL, 0); // 1=show enabled/0=show disabled + + //mqtt and other: + char* url = NULL; + uint8_t url_found = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("url"), true, &url_found)) + url = strdup(urlDecodeAndUnescape(tmp_buffer)); + char* topic = NULL; + uint8_t topic_found = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("topic"), true, &topic_found)) { + DEBUG_PRINTLN(tmp_buffer) + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer) + topic = strdup(tmp_buffer); + } + char* filter = NULL; + uint8_t filter_found = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("filter"), true, &filter_found)) + filter = strdup(urlDecodeAndUnescape(tmp_buffer)); + + DEBUG_PRINTLN(F("server_sensor_config4")); + + SensorFlags_t flags = {.enable=enable, .log=log, .show=show}; + int ret = sensor_define(nr, name, type, group, ip, port, id, ri, factor, divider, userdef_unit, offset_mv, offset2, flags, assigned_unitid); + if (url_found) { + SensorUrl_add(nr, SENSORURL_TYPE_URL, url); + free(url); + } + if (topic_found) { + SensorUrl_add(nr, SENSORURL_TYPE_TOPIC, topic); + free(topic); + } + if (filter_found) { + SensorUrl_add(nr, SENSORURL_TYPE_FILTER, filter); + free(filter); + } + + ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); + + DEBUG_PRINTLN(F("server_sensor_config5")); + +} + +/** + * sa + * Modus RS485 Sensor set address help function + * {"nr":1,"id":1} + */ +void server_set_sensor_address(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_set_sensor_address")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("id"), true)) + handle_return(HTML_DATA_MISSING); + uint id = strtoul(tmp_buffer, NULL, 0); // Sensor modbus id + + int ret = set_sensor_address(sensor_by_nr(nr), id); + ret = ret == HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); +} + +/** + * sg + * @brief return one or all last sensor values + * + */ +void server_sensor_get(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; #endif + + DEBUG_PRINTLN(F("server_sensor_get")); + + uint nr = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{\"datas\":[")); + uint count = sensor_count(); + bool first = true; + for (uint i = 0; i < count; i++) { + + Sensor_t *sensor = sensor_by_idx(i); + if (!sensor || (nr != 0 && nr != sensor->nr)) + continue; + + if (first) first = false; else bfill.emit_p(PSTR(",")); + + bfill.emit_p(PSTR("{\"nr\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"last\":$L}"), + sensor->nr, + sensor->last_native_data, + sensor->last_data, + getSensorUnit(sensor), + getSensorUnitId(sensor), + sensor->last_read); + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + +/** + * sr + * @brief read now and return status and last data + * + */ +void server_sensor_readnow(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensor_readnow")); + + uint nr = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); // Sensor nr + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{\"datas\":[")); + uint count = sensor_count(); + bool first = true; + ulong time = os.now_tz(); + + for (uint i = 0; i < count; i++) { + + Sensor_t *sensor = sensor_by_idx(i); + if (!sensor || (nr != 0 && nr != sensor->nr)) + continue; + + sensor->last_read = time - sensor->read_interval - 1; + int status = read_sensor(sensor, time); + + if (first) first = false; else bfill.emit_p(PSTR(",")); + + bfill.emit_p(PSTR("{\"nr\":$D,\"status\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), + sensor->nr, + status, + sensor->last_native_data, + sensor->last_data, + getSensorUnit(sensor), + getSensorUnitId(sensor)); + + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + +void sensorconfig_json(OTF_PARAMS_DEF) { + int count = sensor_count(); + bool first = true; + for (int i = 0; i < count; i++) { + Sensor_t *sensor = sensor_by_idx(i); + + if (first) first = false; else bfill.emit_p(PSTR(",")); + + char* url = SensorUrl_get(sensor->nr, SENSORURL_TYPE_URL); + char* topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); + char* filter = SensorUrl_get(sensor->nr, SENSORURL_TYPE_FILTER); + + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"group\":$D,\"name\":\"$S\",\"ip\":$L,\"port\":$D,\"id\":$D,\"ri\":$D,\"fac\":$D,\"div\":$D,\"offset\":$D,\"offset2\":$D,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D,\"enable\":$D,\"log\":$D,\"show\":$D,\"data_ok\":$D,\"last\":$L,\"url\":\"$S\",\"topic\":\"$S\",\"filter\":\"$S\"}"), + sensor->nr, + sensor->type, + sensor->group, + sensor->name, + sensor->ip, + sensor->port, + sensor->id, + sensor->read_interval, + sensor->factor, + sensor->divider, + sensor->offset_mv, + sensor->offset2, + sensor->last_native_data, + sensor->last_data, + getSensorUnit(sensor), + getSensorUnitId(sensor), + sensor->flags.enable, + sensor->flags.log, + sensor->flags.show, + sensor->flags.data_ok, + sensor->last_read, + url?url:"", topic?topic:"", filter?filter:""); + send_packet(OTF_PARAMS); + } +} + +/** + * sl + * @brief Lists all sensors + * + */ +void server_sensor_list(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + //DEBUG_PRINTLN(F("server_sensor_list")); + //DEBUG_PRINT(F("server_count: ")); + //DEBUG_PRINTLN(sensor_count()); + + uint test = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("test"), true)) + test = strtoul(tmp_buffer, NULL, 0); // Sensor nr + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + if (test) { + bfill.emit_p(PSTR("{\"test\":$D,"), test); + bfill.emit_p(PSTR("\"detected\":$D}"), get_asb_detected_boards()); + } else { + int count = sensor_count(); + bfill.emit_p(PSTR("{\"count\":$D,"), count); + bfill.emit_p(PSTR("\"detected\":$D,"), get_asb_detected_boards()); + bfill.emit_p(PSTR("\"sensors\":[")); + sensorconfig_json(OTF_PARAMS); + bfill.emit_p(PSTR("]")); + bfill.emit_p(PSTR("}")); + } + handle_return(HTML_OK); +} + +/** + * so + * @brief output sensorlog + * + */ +void server_sensorlog_list(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorlog_list")); + + uint8_t log = LOG_STD; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Log type 0=DAY 1=WEEK 2=MONTH + log = strtoul(tmp_buffer, NULL, 0); + if (log > LOG_MONTH) + log = LOG_STD; + ulong log_size = sensorlog_size(log); + + //start / max: + ulong startAt = 0; + ulong maxResults = log_size; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("start"), true)) // Log start + startAt = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) // Log Lines count + maxResults = strtoul(tmp_buffer, NULL, 0); + + //Filters: + uint nr = 0; + uint type = 0; + ulong after = 0; + ulong before = 0; + ulong lastHours = 0; + bool isjson = true; + bool shortcsv = false; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr + nr = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) // Filter log for sensor-type + type = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // Filter time after + after = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // Filter time before + before = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lasthours"), true)) // Filter last hours + lastHours = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("lastdays"), true)) // Filter last days + lastHours = strtoul(tmp_buffer, NULL, 0) * 24 + lastHours; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("csv"), true)) { // Filter last days + int csv = atoi(tmp_buffer); + isjson = csv == 0; + shortcsv = csv == 2; + } + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + if (isjson) print_header(OTF_PARAMS); else print_header_download(OTF_PARAMS); +#else + if (isjson) print_header(); else print_header_download(); +#endif + + if (isjson) { + bfill.emit_p(PSTR("{\"logtype\":$D,\"logsize\":$D,\"filesize\":$D,\"log\":["), + log, log_size, sensorlog_filesize(log)); + } else { + if (shortcsv) + bfill.emit_p(PSTR("nr;time;data\r\n")); + else + bfill.emit_p(PSTR("nr;type;time;nativedata;data;unit;unitid\r\n")); + } + + #define BLOCKSIZE 64 + ulong count = 0; + SensorLog_t *sensorlog = (SensorLog_t*)malloc(sizeof(SensorLog_t)*BLOCKSIZE); + Sensor_t *sensor = NULL; + + //lastHours: find limit for this + if (lastHours > 0 && log_size > 0) { + after = os.now_tz() - lastHours * 60 * 60; //seconds + DEBUG_PRINT(F("lastHours=")); + DEBUG_PRINTLN(lastHours); + + ulong a = 0; + ulong b = log_size-1; + ulong lastIdx = 0; + while (true) { + ulong idx = (b-a)/2+a; + sensorlog_load(log, idx, sensorlog); + if (sensorlog->time < after) { + a = idx; + } else if (sensorlog->time > after) { + b = idx; + } + if (a >= b || idx == lastIdx) break; + lastIdx = idx; + } + startAt = lastIdx; + } + if (maxResults > 0 && maxResults < log_size) + { + DEBUG_PRINT(F("max=")); + DEBUG_PRINTLN(maxResults); + ulong startAt2 = log_size-maxResults; + if (startAt2 > startAt) + startAt = startAt2; + } + + uint sensor_type = 0; + + DEBUG_PRINTLN(F("start so")); + ulong idx = startAt; + while (idx < log_size) { + int n = sensorlog_load2(log, idx, BLOCKSIZE, sensorlog); + if (n <= 0) break; + + for (int i = 0; i < n; i++) { + idx++; + if (nr && sensorlog[i].nr != nr) + continue; + + if (after && sensorlog[i].time <= after) + continue; + + if (before && sensorlog[i].time >= before) + continue; + + if (!shortcsv || type) { + if (!sensor || sensor->nr != sensorlog[i].nr) + sensor = sensor_by_nr(sensorlog[i].nr); + sensor_type = sensor?sensor->type:0; + if (type && sensor_type != type) + continue; + } + + if (count > 0 && isjson) { + bfill.emit_p(PSTR(",")); + } + + if (isjson) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"time\":$L,\"nativedata\":$L,\"data\":$E,\"unit\":\"$S\",\"unitid\":$D}"), + sensorlog[i].nr, //sensor-nr + sensor_type, //sensor-type + sensorlog[i].time, //timestamp + sensorlog[i].native_data, //native data + sensorlog[i].data, + getSensorUnit(sensor), + getSensorUnitId(sensor)); + } else { + if (shortcsv) + bfill.emit_p(PSTR("$D;$L;$E\r\n"), + sensorlog[i].nr, //sensor-nr + sensorlog[i].time, //timestamp + sensorlog[i].data); + else + bfill.emit_p(PSTR("$D;$D;$L;$L;$E;$S;$D\r\n"), + sensorlog[i].nr, //sensor-nr + sensor_type, //sensor-type + sensorlog[i].time, //timestamp + sensorlog[i].native_data, //native data + sensorlog[i].data, + getSensorUnit(sensor), + getSensorUnitId(sensor)); + } + // if available ether buffer is getting small + // send out a packet + if (!buffer_available() ) { + send_packet(OTF_PARAMS); + } + if (++count >= maxResults) { + break; + } + } + if (count >= maxResults) + break; + } + DEBUG_PRINTLN(F("end so")); + + if (isjson) + bfill.emit_p(PSTR("]}")); + else + bfill.emit_p(PSTR("\r\n")); + free(sensorlog); + + DEBUG_PRINTLN(F("finish so")); + + handle_return(HTML_OK); +} + +/** + * sn + * @brief Delete/Clear Sensor log + * + */ +void server_sensorlog_clear(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + int log = -1; + uint nr = 0; + double under = 0; + bool use_under = false; + double over = 0; + bool use_over = false; + ulong before = 0; + ulong after = 0; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("log"), true)) // Filter log for sensor-nr + log = atoi(tmp_buffer); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) // Filter log for sensor-nr + nr = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("under"), true)) // values lower than + use_under = sscanf(tmp_buffer, "%lf", &under) == 1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("over"), true)) // values higher than + use_over = sscanf(tmp_buffer, "%lf", &over) == 1; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("before"), true)) // values higher than + sscanf(tmp_buffer, "%lu", &before); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("after"), true)) // values higher than + sscanf(tmp_buffer, "%lu", &after); + + DEBUG_PRINTLN(F("server_sensorlog_clear")); + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + DEBUG_PRINTLN(F("start log cleaning")); + if (nr > 0 || use_under || use_over || before || after) { + ulong n = 0; + if (log == -1) { + n += sensorlog_clear_sensor(nr, LOG_STD, use_under, under, use_over, over, before, after); + n += sensorlog_clear_sensor(nr, LOG_WEEK, use_under, under, use_over, over, before, after); + n += sensorlog_clear_sensor(nr, LOG_MONTH, use_under, under, use_over, over, before, after); + } else { + n += sensorlog_clear_sensor(nr, log, use_under, under, use_over, over, before, after); + } + bfill.emit_p(PSTR("{\"deleted\":$L}"), n); + } else { + ulong log_size = sensorlog_size(LOG_STD); + ulong log_sizeW = sensorlog_size(LOG_WEEK); + ulong log_sizeM = sensorlog_size(LOG_MONTH); + + if (log == -1) { + sensorlog_clear_all(); + bfill.emit_p(PSTR("{\"deleted\":$L,\"deleted_week\":$L,\"deleted_month\":$L}"), log_size, log_sizeW, log_sizeM); + } + else { + sensorlog_clear(log==LOG_STD, log==LOG_WEEK, log==LOG_MONTH); + bfill.emit_p(PSTR("{\"deleted\":$L}"), log==LOG_STD?log_size:log=LOG_WEEK?log_sizeW:log_sizeM); + } + } + DEBUG_PRINTLN(F("end log cleaning")); + handle_return(HTML_OK); +} + +/** + * mt + * supported monitor types + */ +void server_monitor_types(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_monitor_types")); + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{\"monitortypes\": [")); + bfill.emit_p(PSTR("{\"name\":\"Min\",\"type\":$D},"), MONITOR_MIN); + bfill.emit_p(PSTR("{\"name\":\"Max\",\"type\":$D},"), MONITOR_MAX); + bfill.emit_p(PSTR("{\"name\":\"SN 1/2\",\"type\":$D},"), MONITOR_SENSOR12); + bfill.emit_p(PSTR("{\"name\":\"SET SN 1/2\",\"type\":$D},"), MONITOR_SET_SENSOR12); + bfill.emit_p(PSTR("{\"name\":\"AND\",\"type\":$D},"), MONITOR_AND); + bfill.emit_p(PSTR("{\"name\":\"OR\",\"type\":$D},"), MONITOR_OR); + bfill.emit_p(PSTR("{\"name\":\"XOR\",\"type\":$D},"), MONITOR_XOR); + bfill.emit_p(PSTR("{\"name\":\"NOT\",\"type\":$D}," ), MONITOR_NOT); + bfill.emit_p(PSTR("{\"name\":\"TIME\",\"type\":$D}," ), MONITOR_TIME); + bfill.emit_p(PSTR("{\"name\":\"REMOTE\",\"type\":$D}" ), MONITOR_REMOTE); + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + +/** + * mc + * define a monitor + */ +void server_monitor_config(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_monitor_config")); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint16_t nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + if (nr == 0) + handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint16_t type = strtoul(tmp_buffer, NULL, 0); // Adjustment type + + if (type == 0) { + monitor_delete(nr); + handle_return(HTML_SUCCESS); + } + + uint16_t sensor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + sensor = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + handle_return(HTML_DATA_MISSING); + uint16_t prog = strtoul(tmp_buffer, NULL, 0); // Program nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("zone"), true)) + handle_return(HTML_DATA_MISSING); + uint16_t zone = strtoul(tmp_buffer, NULL, 0); // Zone + + char name[30] = {0}; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) + strncpy(name, tmp_buffer, sizeof(name)-1); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("maxrun"), true)) + handle_return(HTML_DATA_MISSING); + ulong maxRuntime = strtoul(tmp_buffer, NULL, 0); // Zone + + uint8_t prio = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prio"), true)) + prio = strtoul(tmp_buffer, NULL, 0); // prio + + //type-dependend parameters: + //type = MIN/MAX of sensor value: + double value1 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value1"), true)) + value1 = atof(tmp_buffer); // Value 1 + + double value2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("value2"), true)) + value2 = atof(tmp_buffer); // Value 2 + + //type = SENSOR12 + uint16_t sensor12 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor12"), true)) + sensor12 = strtoul(tmp_buffer, NULL, 0); + bool invers = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers"), true)) + invers = strtoul(tmp_buffer, NULL, 0) > 0; + + //type = AND/OR/XOR: + uint16_t monitor1 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor1"), true)) + monitor1 = strtoul(tmp_buffer, NULL, 0); + uint16_t monitor2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor2"), true)) + monitor2 = strtoul(tmp_buffer, NULL, 0); + uint16_t monitor3 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor3"), true)) + monitor3 = strtoul(tmp_buffer, NULL, 0); + uint16_t monitor4 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor4"), true)) + monitor4 = strtoul(tmp_buffer, NULL, 0); + + bool invers1 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers1"), true)) + invers1 = strtoul(tmp_buffer, NULL, 0) > 0; + bool invers2 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers2"), true)) + invers2 = strtoul(tmp_buffer, NULL, 0) > 0; + bool invers3 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers3"), true)) + invers3 = strtoul(tmp_buffer, NULL, 0) > 0; + bool invers4 = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("invers4"), true)) + invers4 = strtoul(tmp_buffer, NULL, 0) > 0; + + //type = NOT + uint16_t monitor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("monitor"), true)) + monitor = strtoul(tmp_buffer, NULL, 0); + + //type = TIME + uint16_t time_from = 0000; + uint16_t time_to = 2400; + uint8_t wdays = 0xFF; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("from"), true)) //Format: HHMM + time_from = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("to"), true)) //Format: HHMM + time_to = strtoul(tmp_buffer, NULL, 0); + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("wdays"), true)) //0=Monday + wdays = strtoul(tmp_buffer, NULL, 0); + + //type = REMOTE ip + uint16_t rmonitor = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("rmonitor"), true)) + rmonitor = strtoul(tmp_buffer, NULL, 0); + uint32_t ip = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("ip"), true)) + ip = strtoul(tmp_buffer, NULL, 0); + uint16_t port = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("port"), true)) + port = strtoul(tmp_buffer, NULL, 0); + + Monitor_Union_t m; + switch (type) { + case MONITOR_MIN: + case MONITOR_MAX: + m = (Monitor_Union_t){.minmax = {.value1 = value1, .value2 = value2}}; + break; + case MONITOR_SENSOR12: + m = (Monitor_Union_t){.sensor12 = {.sensor12 = sensor12, .invers = invers}}; + break; + case MONITOR_SET_SENSOR12: + m = (Monitor_Union_t){.set_sensor12 = {.monitor = monitor, .sensor12 = sensor12}}; + break; + case MONITOR_AND: + case MONITOR_OR: + case MONITOR_XOR: + m = (Monitor_Union_t){.andorxor = {.monitor1 = monitor1, .monitor2 = monitor2, .monitor3 = monitor3, .monitor4 = monitor4, + .invers1 = invers1, .invers2 = invers2, .invers3 = invers3, .invers4 = invers4}}; + break; + case MONITOR_NOT: + m = (Monitor_Union_t){.mnot = {.monitor = monitor}}; + break; + case MONITOR_TIME: + m = (Monitor_Union_t){.mtime = {.time_from = time_from, .time_to = time_to, .weekdays = wdays}}; + break; + case MONITOR_REMOTE: + m = (Monitor_Union_t){.remote = {.rmonitor = rmonitor, .ip = ip, .port = port}}; + break; + default: handle_return(HTML_DATA_FORMATERROR); + } + int ret = monitor_define(nr, type, sensor, prog, zone, m, name, maxRuntime, prio); + ret = ret >= HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); +} + +void monitorconfig_json(Monitor_t *mon) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"zone\":$D,\"name\":\"$S\",\"maxrun\":$L,\"prio\":$D,\"active\":$D,\"time\":$L,"), + mon->nr, + mon->type, + mon->sensor, + mon->prog, + mon->zone, + mon->name, + mon->maxRuntime, + mon->prio, + mon->active, + mon->time); + + switch(mon->type) { + case MONITOR_MIN: + case MONITOR_MAX: + bfill.emit_p(PSTR("\"value1\":$E,\"value2\":$E}"), + isnan(mon->m.minmax.value1)?0:mon->m.minmax.value1, + isnan(mon->m.minmax.value2)?0:mon->m.minmax.value2); + break; + case MONITOR_SENSOR12: + bfill.emit_p(PSTR("\"sensor12\":$D,\"invers\":$D}"), + mon->m.sensor12.sensor12, + mon->m.sensor12.invers); + break; + case MONITOR_SET_SENSOR12: + bfill.emit_p(PSTR("\"monitor\":$D,\"sensor12\":$D}"), + mon->m.set_sensor12.monitor, + mon->m.set_sensor12.sensor12); + break; + case MONITOR_AND: + case MONITOR_OR: + case MONITOR_XOR: + bfill.emit_p(PSTR("\"monitor1\":$D,\"monitor2\":$D,\"monitor3\":$D,\"monitor4\":$D,\"invers1\":$D,\"invers2\":$D,\"invers3\":$D,\"invers4\":$D}"), + mon->m.andorxor.monitor1, mon->m.andorxor.monitor2, mon->m.andorxor.monitor3, mon->m.andorxor.monitor4, + mon->m.andorxor.invers1, mon->m.andorxor.invers2, mon->m.andorxor.invers3, mon->m.andorxor.invers4); + break; + case MONITOR_NOT: + bfill.emit_p(PSTR("\"monitor\":$D}"), + mon->m.mnot.monitor); + break; + case MONITOR_TIME: + bfill.emit_p(PSTR("\"from\":$D,\"to\":$D,\"wdays\":$D}"), mon->m.mtime.time_from, mon->m.mtime.time_to, mon->m.mtime.weekdays); + break; + case MONITOR_REMOTE: + bfill.emit_p(PSTR("\"rmonitor\":$D,\"ip\":$L,\"port\":$D}"), + mon->m.remote.rmonitor, + mon->m.remote.ip, + mon->m.remote.port); + break; + + } +} + +void monitorconfig_json() { + uint count = monitor_count(); + for (uint i = 0; i < count; i++) { + Monitor_t *mon = monitor_by_idx(i); + if (i > 0) bfill.emit_p(PSTR(",")); + monitorconfig_json(mon); + } +} + +/** + * ml + * list monitors + */ +void server_monitor_list(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_monitor_list")); + + uint nr = 0; + int prog = -1; + uint sensor_nr = 0; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + prog = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + sensor_nr = strtoul(tmp_buffer, NULL, 0); + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{\"monitors\": [")); + uint8_t idx = 0; + uint8_t count = monitor_count(); + bool first = true; + + while (idx < count) { + Monitor_t *mon = monitor_by_idx(idx++); + if (nr > 0 && mon->nr != nr) + continue; + if (prog >= 0 && mon->prog != (uint)prog) + continue; + if (sensor_nr > 0 && mon->sensor != sensor_nr) + continue; + + if (!first) + bfill.emit_p(PSTR(",")); + first = false; + monitorconfig_json(mon); + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + + + +/** + * sb + * define a program adjustment +*/ +void server_sensorprog_config(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorprog_config")); + //uint nr, uint type, uint sensor, uint prog, double factor1, double factor2, double min, double max + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + handle_return(HTML_DATA_MISSING); + uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + if (nr == 0) + handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Adjustment type + + if (type == 0) { + prog_adjust_delete(nr); + handle_return(HTML_SUCCESS); + } + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + handle_return(HTML_DATA_MISSING); + uint sensor = strtoul(tmp_buffer, NULL, 0); // Sensor nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + handle_return(HTML_DATA_MISSING); + uint prog = strtoul(tmp_buffer, NULL, 0); // Program nr + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) + handle_return(HTML_DATA_MISSING); + double factor1 = atof(tmp_buffer); // Factor 1 + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor2"), true)) + handle_return(HTML_DATA_MISSING); + double factor2 = atof(tmp_buffer); // Factor 2 + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) + handle_return(HTML_DATA_MISSING); + double min = atof(tmp_buffer); // Min value + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) + handle_return(HTML_DATA_MISSING); + double max = atof(tmp_buffer); // Max value + + char name[30] = {0}; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("name"), true)) + strncpy(name, tmp_buffer, sizeof(name)-1); + + int ret = prog_adjust_define(nr, type, sensor, prog, factor1, factor2, min, max, name); + ret = ret >= HTTP_RQT_SUCCESS?HTML_SUCCESS:HTML_DATA_MISSING; + handle_return(ret); +} + +void progconfig_json(ProgSensorAdjust_t *p, double current) { + bfill.emit_p(PSTR("{\"nr\":$D,\"type\":$D,\"sensor\":$D,\"prog\":$D,\"factor1\":$E,\"factor2\":$E,\"min\":$E,\"max\":$E,\"name\":\"$S\",\"current\":$E}"), + p->nr, + p->type, + p->sensor, + p->prog, + p->factor1, + p->factor2, + p->min, + p->max, + (p->name[0] < 0x20 || p->name[0] > 0xEE || p->name[0] == 0x90) ? "" : p->name, + current); +} + +void progconfig_json() { + uint count = prog_adjust_count(); + bool first = true; + for (uint i = 0; i < count; i++) { + ProgSensorAdjust_t *p = prog_adjust_by_idx(i); + double current = calc_sensor_watering_by_nr(p->nr); + + if (first) first = false; else bfill.emit_p(PSTR(",")); + + progconfig_json(p, current); + } +} + +/** + * se + * define a program adjustment +*/ +void server_sensorprog_list(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorprog_list")); + + uint nr = 0; + int prog = -1; + uint sensor_nr = 0; + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) + nr = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) + prog = strtoul(tmp_buffer, NULL, 0); + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + sensor_nr = strtoul(tmp_buffer, NULL, 0); + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + uint n = prog_adjust_count(); + uint idx = 0; + uint count = 0; + while (idx < n) { + ProgSensorAdjust_t *p = prog_adjust_by_idx(idx++); + if (nr > 0 && p->nr != nr) + continue; + if (prog >= 0 && p->prog != (uint)prog) + continue; + if (sensor_nr > 0 && p->sensor != sensor_nr) + continue; + count++; + } + + bfill.emit_p(PSTR("{\"count\": $D,"), count); + + bfill.emit_p(PSTR("\"progAdjust\": [")); + idx = 0; + count = 0; + + while (idx < n) { + ProgSensorAdjust_t *p = prog_adjust_by_idx(idx++); + if (nr > 0 && p->nr != nr) + continue; + if (prog >= 0 && p->prog != (uint)prog) + continue; + if (sensor_nr > 0 && p->sensor != sensor_nr) + continue; + + double current = calc_sensor_watering_by_nr(p->nr); + + if (count++ > 0) + bfill.emit_p(PSTR(",")); + progconfig_json(p, current); + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("]}")); + handle_return(HTML_OK); +} + +static const int sensor_types[] = { + SENSOR_SMT100_MOIS, + SENSOR_SMT100_TEMP, + SENSOR_SMT100_PMTY, + SENSOR_TH100_MOIS, + SENSOR_TH100_TEMP, + +#if defined(ESP8266) + SENSOR_ANALOG_EXTENSION_BOARD, + SENSOR_ANALOG_EXTENSION_BOARD_P, + SENSOR_SMT50_MOIS, + SENSOR_SMT50_TEMP, + SENSOR_SMT100_ANALOG_MOIS, + SENSOR_SMT100_ANALOG_TEMP, + SENSOR_VH400, + SENSOR_THERM200, + SENSOR_AQUAPLUMB, + SENSOR_USERDEF, +#endif +#if defined ADS1115||PCF8591 + SENSOR_OSPI_ANALOG, + SENSOR_OSPI_ANALOG_P, + SENSOR_OSPI_ANALOG_SMT50_MOIS, + SENSOR_OSPI_ANALOG_SMT50_TEMP, +#endif +#if defined(OSPI) + SENSOR_OSPI_INTERNAL_TEMP, +#endif + SENSOR_MQTT, + SENSOR_REMOTE, + SENSOR_WEATHER_TEMP_F, + SENSOR_WEATHER_TEMP_C, + SENSOR_WEATHER_HUM, + SENSOR_WEATHER_PRECIP_IN, + SENSOR_WEATHER_PRECIP_MM, + SENSOR_WEATHER_WIND_MPH, + SENSOR_WEATHER_WIND_KMH, + SENSOR_GROUP_MIN, + SENSOR_GROUP_MAX, + SENSOR_GROUP_AVG, + SENSOR_GROUP_SUM, +#if defined(ESP8266) + SENSOR_FREE_MEMORY, + SENSOR_FREE_STORE, +#endif +}; + +static const char* sensor_names[] = { + "Truebner SMT100 RS485 Modbus, moisture mode", + "Truebner SMT100 RS485 Modbus, temperature mode", + "Truebner SMT100 RS485 Modbus, permittivity mode", + "Truebner TH100 RS485 Modbus, humidity mode", + "Truebner TH100 RS485 Modbus, temperature mode", + #if defined(ESP8266) + "ASB - voltage mode 0..5V", + "ASB - 0..3.3V to 0..100%", + "ASB - SMT50 moisture mode", + "ASB - SMT50 temperature mode", + "ASB - SMT100-analog moisture mode", + "ASB - SMT100-analog temperature mode", + + "ASB - Vegetronix VH400", + "ASB - Vegetronix THERM200", + "ASB - Vegetronix AquaPlumb", + + "ASB - user defined sensor", +#endif +#if defined ADS1115||PCF8591 + "OSPi analog input - voltage mode 0..3.3V", + "OSPi analog input - 0.3.3V to 0..100%", + "OSPi analog input - SMT50 moisture mode", + "OSPi analog input - SMT50 temperature mode", +#endif +#if defined(OSPI) + "Internal Raspbery Pi temperature", +#endif + "MQTT subscription", + "Remote opensprinkler sensor", + "Weather data - temperature (°F)", + "Weather data - temperature (°C)", + "Weather data - humidity (%)", + "Weather data - precip (inch)", + "Weather data - precip (mm)", + "Weather data - wind (mph)", + "Weather data - wind (kmh)", + "Sensor group with min value", + "Sensor group with max value", + "Sensor group with avg value", + "Sensor group with sum value", +#if defined(ESP8266) + "Free Memory", + "Free Storage", +#endif +}; + +void free_tmp_memory() { +#if defined(ESP8266) + DEBUG_PRINT(F("freememory start: ")); + DEBUG_PRINTLN(freeMemory()); + + sensor_save_all(); + sensor_api_free(); + + DEBUG_PRINT(F("freememory now: ")); + DEBUG_PRINTLN(freeMemory()); +#endif +} + +void restore_tmp_memory() { +#if defined(ESP8266) + sensor_api_init(false); + + DEBUG_PRINT(F("freememory restore: ")); + DEBUG_PRINTLN(freeMemory()); +#endif +} + +/** + * sf + * List supported sensor types + **/ +void server_sensor_types(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensor_types")); + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + int count = sizeof(sensor_types)/sizeof(int); + boolean use_asb = get_asb_detected_boards() > 0; + if (!use_asb) count -= 10; + + bfill.emit_p(PSTR("{\"count\":$D,\"detected\":$D,\"sensorTypes\":["), count, get_asb_detected_boards()); + + for (uint i = 0; i < sizeof(sensor_types)/sizeof(int); i++) + { + int type = sensor_types[i]; + if (!use_asb && type >= SENSOR_ANALOG_EXTENSION_BOARD && type <= SENSOR_USERDEF) + continue; + if (i > 0) + bfill.emit_p(PSTR(",")); + unsigned char unitid = getSensorUnitId(type); + bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\",\"unit\":\"$S\",\"unitid\":$D}"), + type, sensor_names[i], getSensorUnit(unitid), unitid); + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("]}")); + + handle_return(HTML_OK); +} + +/** + * du + * system resources status + **/ +void server_usage(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_usage")); + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + +extern uint32_t ping_ok; + +#if defined(ESP8266) + + struct FSInfo fsinfo; + + boolean ok = LittleFS.info(fsinfo); + + bfill.emit_p(PSTR("{\"status\":$D,\"freeMemory\":$D,\"totalBytes\":$D,\"usedBytes\":$D,\"freeBytes\":$D,\"blockSize\":$D,\"pageSize\":$D,\"maxOpenFiles\":$D,\"maxPathLength\":$D,\"pingok\":$D,\"mqtt\":$D,\"ifttt\":$D"), + ok, + freeMemory(), + fsinfo.totalBytes, + fsinfo.usedBytes, + fsinfo.totalBytes-fsinfo.usedBytes, + fsinfo.blockSize, + fsinfo.pageSize, + fsinfo.maxOpenFiles, + fsinfo.maxPathLength, + ping_ok, + os.mqtt.connected(), + get_notif_enabled()); + +#else + bfill.emit_p(PSTR("{\"status\":$D,\"mqtt\":$D,\"ifttt\":$D"), 1, os.mqtt.connected(), get_notif_enabled()); + +#endif + + bfill.emit_p(PSTR(",\"logfiles\":{\"l01\":$D,\"l02\":$D,\"l11\":$D,\"l12\":$D,\"l21\":$D,\"l22\":$D}"), + file_size(SENSORLOG_FILENAME1) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME2) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME_WEEK1) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME_WEEK2) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME_MONTH1) / sizeof(SensorLog_t), + file_size(SENSORLOG_FILENAME_MONTH2) / sizeof(SensorLog_t)); + + bfill.emit_p(PSTR("}")); + + + handle_return(HTML_OK); +} + +/** + * sd + * Program calc + **/ +void server_sensorprog_calc(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorprog_calc")); + //uint nr or uint prog + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("nr"), true)) { + uint nr = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + double adj = calc_sensor_watering_by_nr(nr); + bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); + handle_return(HTML_OK); + } + + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("prog"), true)) { + uint prog = strtoul(tmp_buffer, NULL, 0); // Adjustment nr + double adj = calc_sensor_watering(prog); + bfill.emit_p(PSTR("{\"adjustment\":$E}"), adj); + handle_return(HTML_OK); + } + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("type"), true)) + handle_return(HTML_DATA_MISSING); + uint type = strtoul(tmp_buffer, NULL, 0); // Adjustment type + if (type == 0) { + handle_return(HTML_DATA_MISSING); + } + + //methods for visual calculation: + ProgSensorAdjust_t progAdj; + memset(&progAdj, 0, sizeof(progAdj)); + progAdj.type = type; + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("sensor"), true)) + handle_return(HTML_DATA_MISSING); + progAdj.sensor = strtoul(tmp_buffer, NULL, 0); + Sensor_t *sensor = sensor_by_nr(progAdj.sensor); // Sensor nr + if (!sensor) + handle_return(HTML_DATA_MISSING); + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor1"), true)) + handle_return(HTML_DATA_MISSING); + progAdj.factor1 = atof(tmp_buffer); // Factor 1 + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("factor2"), true)) + handle_return(HTML_DATA_MISSING); + progAdj.factor2 = atof(tmp_buffer); // Factor 2 + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("min"), true)) + handle_return(HTML_DATA_MISSING); + progAdj.min = atof(tmp_buffer); // Min value + + if (!findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("max"), true)) + handle_return(HTML_DATA_MISSING); + progAdj.max = atof(tmp_buffer); // Max value + + unsigned char unitId = getSensorUnitId(sensor); + + int diff = progAdj.max-progAdj.min; + int minEx = progAdj.min - diff/2; + if (minEx < 0 && (unitId == UNIT_PERCENT || (unitId >= UNIT_VOLT && unitId < UNIT_USERDEF))) + minEx = 0; + int maxEx = progAdj.max + diff/2; + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{\"adjustment\":{\"min\":$D,\"max\":$D,\"current\":$E,\"adjust\":$E,\"unit\":\"$S\","), minEx, maxEx, + sensor->last_data, calc_sensor_watering_int(&progAdj, sensor->last_data), getSensorUnit(sensor)); + + int nvalues = max(11, maxEx-minEx+1); + double inVal[nvalues]; + double outVal[nvalues]; + for (int i = 0; i < nvalues; i++) { + inVal[i] = (double)(maxEx - minEx) * (double)i / (nvalues-1) + minEx; + outVal[i] = calc_sensor_watering_int(&progAdj, inVal[i]); + } + + bfill.emit_p(PSTR("\"inval\":[")); + for (int i = 0; i < nvalues; i++) { + if(i > 0) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("$E"), inVal[i]); + } + bfill.emit_p(PSTR("],\"outval\":[")); + for (int i = 0; i < nvalues; i++) { + if(i > 0) bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("$E"), outVal[i]); + } + bfill.emit_p(PSTR("]}}")); + + handle_return(HTML_OK); +} + +const int prog_types[] = { + PROG_NONE, + PROG_LINEAR, + PROG_DIGITAL_MIN, + PROG_DIGITAL_MAX, + PROG_DIGITAL_MINMAX, +}; + +const char* prog_names[] = { + "No Adjustment", + "Linear scaling", + "Digital under min", + "Digital over max", + "Digital under min or over max", +}; + +/** + * sh + * List supported adjustment types + */ +void server_sensorprog_types(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + DEBUG_PRINTLN(F("server_sensorprog_types")); + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + bfill.emit_p(PSTR("{\"count\":$D,\"progTypes\":["), sizeof(prog_types)/sizeof(int)); + + for (uint i = 0; i < sizeof(prog_types)/sizeof(int); i++) + { + + if (i > 0) + bfill.emit_p(PSTR(",")); + bfill.emit_p(PSTR("{\"type\":$D,\"name\":\"$S\"}"), prog_types[i], prog_names[i]); + + send_packet(OTF_PARAMS); + } + bfill.emit_p(PSTR("]}")); + + handle_return(HTML_OK); +} + +/** + * sx + * @brief backup sensor configuration + * + */ +void server_sensorconfig_backup(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + +#define BACKUP_SENSORS 1 +#define BACKUP_ADJUSTMENTS 2 +#define BACKUP_MONITORS 4 + + //Backup type: 0=no backup 1=Sensors 2=Adjustments 3=Sensors+Adjustments + int backup = BACKUP_SENSORS|BACKUP_ADJUSTMENTS|BACKUP_MONITORS; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("backup"), true)) { + backup = strtol(tmp_buffer, NULL, 0); + } + + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + ulong time = os.now_tz(); + bfill.emit_p(PSTR("{\"backup\":$D,\"time\":$L,\"os-version\":$D,\"minor\":$D"), backup, time, OS_FW_VERSION, OS_FW_MINOR); + if (backup & BACKUP_SENSORS) { + bfill.emit_p(PSTR(",\"sensors\":[")); + sensorconfig_json(OTF_PARAMS); + bfill.emit_p(PSTR("]")); + send_packet(OTF_PARAMS); + } + if (backup & BACKUP_ADJUSTMENTS) { + bfill.emit_p(PSTR(",\"progadjust\":[")); + progconfig_json(); + bfill.emit_p(PSTR("]")); + send_packet(OTF_PARAMS); + } + send_packet(OTF_PARAMS); + if (backup & BACKUP_MONITORS) { + bfill.emit_p(PSTR(",\"monitors\":[")); + monitorconfig_json(); + bfill.emit_p(PSTR("]")); + } + bfill.emit_p(PSTR("}")); + send_packet(OTF_PARAMS); + + handle_return(HTML_OK); +} + +/** + * is + * @brief influx set config + * + */ +void server_influx_set(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + + int enabled = 0; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("en"), true)) { + enabled = strtol(tmp_buffer, NULL, 0); + } + + char *url = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("url"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + url = strdup(tmp_buffer); + } + + int port = 8086; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("port"), true)) { + DEBUG_PRINTLN(tmp_buffer); + port = strtol(tmp_buffer, NULL, 0); + } + + char *org = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("org"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + org = strdup(tmp_buffer); + } + + char *bucket = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("bucket"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + bucket = strdup(tmp_buffer); + } + + char *token = NULL; + if (findKeyVal(FKV_SOURCE, tmp_buffer, TMP_BUFFER_SIZE, PSTR("token"), true)) { + urlDecodeAndUnescape(tmp_buffer); + DEBUG_PRINTLN(tmp_buffer); + token = strdup(tmp_buffer); + } + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + + os.influxdb.set_influx_config(enabled, url, port, org, bucket, token); + + handle_return(HTML_OK); +} + + +/** + * ig + * @brief influx get config + * + */ +void server_influx_get(OTF_PARAMS_DEF) { +#if defined(USE_OTF) + if(!process_password(OTF_PARAMS)) return; +#else + char *p = get_buffer; +#endif + +#if defined(USE_OTF) + // as the log data can be large, we will use ESP8266's sendContent function to + // send multiple packets of data, instead of the standard way of using send(). + rewind_ether_buffer(); + print_header(OTF_PARAMS); +#else + print_header(); +#endif + server_influx_get_main(); + + send_packet(OTF_PARAMS); + handle_return(HTML_OK); +} + +void server_influx_get_main() { + os.influxdb.get_influx_config(tmp_buffer); + bfill.emit_p(tmp_buffer); +} + +typedef void (*URLHandler)(OTF_PARAMS_DEF); + +/* Server function urls + * To save RAM space, each GET command keyword is exactly + * 2 characters long, with no ending 0 + * The order must exactly match the order of the + * handler functions below + */ +const char _url_keys[] PROGMEM = + "cv" + "jc" + "dp" + "cp" + "cr" + "mp" + "up" + "jp" + "co" + "jo" + "sp" + "js" + "cm" + "cs" + "jn" + "je" + "jl" + "dl" + "su" + "cu" + "ja" + "pq" + "si" + "sj" + "sk" + "sc" + "sl" + "sg" + "sr" + "sa" + "so" + "sn" + "sb" + "sd" + "se" + "sf" + "du" + "sh" + "sx" + "db" + "is" + "ig" + "mc" + "ml" + "mt" +#if defined(ARDUINO) + //"ff" +#endif + ; + +// Server function handlers +URLHandler urls[] = { + server_change_values, // cv + server_json_controller, // jc + server_delete_program, // dp + server_change_program, // cp + server_change_runonce, // cr + server_manual_program, // mp + server_moveup_program, // up + server_json_programs, // jp + server_change_options, // co + server_json_options, // jo + server_change_password, // sp + server_json_status, // js + server_change_manual, // cm + server_change_stations, // cs + server_json_stations, // jn + server_json_station_special,// je + server_json_log, // jl + server_delete_log, // dl + server_view_scripturl, // su + server_change_scripturl,// cu + server_json_all, // ja + server_pause_queue, // pq + server_sensor_config_userdef,//si + server_sensorurl_get,//sj + server_sensorurl_config,//sk + server_sensor_config,//sc + server_sensor_list,//sl + server_sensor_get,//sg + server_sensor_readnow,//sr + server_set_sensor_address,//sa + server_sensorlog_list,//so + server_sensorlog_clear,//sn + server_sensorprog_config,//sb + server_sensorprog_calc,//sd + server_sensorprog_list,//se + server_sensor_types,//sf + server_usage,//du + server_sensorprog_types,//sh + server_sensorconfig_backup,//sx + server_json_debug, // db + server_influx_set,// is + server_influx_get,// ig + server_monitor_config, // mc + server_monitor_list, // ml + server_monitor_types, // mt + //server_fill_files, }; // handle Ethernet request @@ -2172,9 +4106,7 @@ void on_sta_update(OTF_PARAMS_DEF) { } void on_sta_upload_fin() { - if (os.iopts[IOPT_IGNORE_PASSWORD]) { - // don't check password - } else if(!(update_server->hasArg("pw") && os.password_verify(update_server->arg("pw").c_str()))) { + if(!(update_server->hasArg("pw") && os.password_verify(update_server->arg("pw").c_str()))) { update_server_send_result(HTML_UNAUTHORIZED); Update.end(false); return; diff --git a/opensprinkler_server.h b/opensprinkler_server.h index 1635236c..51db7bf0 100644 --- a/opensprinkler_server.h +++ b/opensprinkler_server.h @@ -1,104 +1,126 @@ -/* OpenSprinkler Unified Firmware - * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) - * - * Server functions - * Feb 2015 @ OpenSprinkler.com - * - * This file is part of the OpenSprinkler library - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see - * . - */ - -#ifndef _OPENSPRINKLER_SERVER_H -#define _OPENSPRINKLER_SERVER_H - -#if !defined(ARDUINO) -#include -#include -#endif - -char dec2hexchar(unsigned char dec); - -class BufferFiller { - char *start; //!< Pointer to start of buffer - char *ptr; //!< Pointer to cursor position - size_t len; -public: - BufferFiller () {} - BufferFiller (char *buf, size_t buffer_len) { - start = buf; - ptr = buf; - len = buffer_len; - } - - char* buffer () const { return start; } - size_t length () const { return len; } - unsigned int position () const { return ptr - start; } - - void emit_p(PGM_P fmt, ...) { - va_list ap; - va_start(ap, fmt); - for (;;) { - char c = pgm_read_byte(fmt++); - if (c == 0) - break; - if (c != '$') { - *ptr++ = c; - continue; - } - c = pgm_read_byte(fmt++); - switch (c) { - case 'D': - // itoa(va_arg(ap, int), (char*) ptr, 10); // ray - snprintf((char*) ptr, len - position(), "%d", va_arg(ap, int)); - break; - case 'L': - // ultoa(va_arg(ap, uint32_t), (char*) ptr, 10); - snprintf((char*) ptr, len - position(), "%lu", (unsigned long) va_arg(ap, uint32_t)); - break; - case 'S': - strcpy((char*) ptr, va_arg(ap, const char*)); - break; - case 'X': { - char d = va_arg(ap, int); - *ptr++ = dec2hexchar((d >> 4) & 0x0F); - *ptr++ = dec2hexchar(d & 0x0F); - } - continue; - case 'F': { - PGM_P s = va_arg(ap, PGM_P); - char d; - while ((d = pgm_read_byte(s++)) != 0) - *ptr++ = d; - continue; - } - case 'O': { - uint16_t oid = va_arg(ap, int); - file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); - } - break; - default: - *ptr++ = c; - continue; - } - ptr += strlen((char*) ptr); - } - *(ptr)=0; - va_end(ap); - } -}; - - -#endif // _OPENSPRINKLER_SERVER_H +/* OpenSprinkler Unified Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * Server functions + * Feb 2015 @ OpenSprinkler.com + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _OPENSPRINKLER_SERVER_H +#define _OPENSPRINKLER_SERVER_H + +#if !defined(ARDUINO) +#include +#include +#include +#else +#include +#endif + +#if defined(ESP8266) || defined(OSPI) +#define OTF_ENABLED +#endif + +char dec2hexchar(unsigned char dec); + +class BufferFiller { + char *start; //!< Pointer to start of buffer + char *ptr; //!< Pointer to cursor position + size_t len; +public: + BufferFiller () {} + BufferFiller (char *buf, size_t buffer_len) { + start = buf; + ptr = buf; + len = buffer_len; + } + + char* buffer () const { return start; } + size_t length () const { return len; } + unsigned int position () const { return ptr - start; } + + void emit_p(PGM_P fmt, ...) { + va_list ap; + va_start(ap, fmt); + for (;;) { + char c = pgm_read_byte(fmt++); + if (c == 0) + break; + if (c != '$') { + *ptr++ = c; + continue; + } + c = pgm_read_byte(fmt++); + switch (c) { + case 'D': + //wtoa(va_arg(ap, uint16_t), (char*) ptr); + itoa(va_arg(ap, int), (char*) ptr, 10); // ray + break; + case 'E': //Double + sprintf((char*) ptr, "%10.6lf", va_arg(ap, double)); + break; + case 'L': + //ltoa(va_arg(ap, long), (char*) ptr, 10); + //ultoa(va_arg(ap, long), (char*) ptr, 10); // ray + #if defined(OSPI) + sprintf((char*) ptr, "%" PRIu32, va_arg(ap, long)); + #else + sprintf((char*) ptr, "%lu", va_arg(ap, long)); + #endif + break; + case 'S': + strcpy((char*) ptr, va_arg(ap, const char*)); + break; + case 'X': { + char d = va_arg(ap, int); + *ptr++ = dec2hexchar((d >> 4) & 0x0F); + *ptr++ = dec2hexchar(d & 0x0F); + } + continue; + case 'F': { + PGM_P s = va_arg(ap, PGM_P); + char d; + while ((d = pgm_read_byte(s++)) != 0) + *ptr++ = d; + continue; + } + case 'O': { + uint16_t oid = va_arg(ap, int); + file_read_block(SOPTS_FILENAME, (char*) ptr, oid*MAX_SOPTS_SIZE, MAX_SOPTS_SIZE); + } + break; + default: + *ptr++ = c; + continue; + } + ptr += strlen((char*) ptr); + } + *(ptr)=0; + va_end(ap); + } +}; + +void server_influx_get_main(); +void free_tmp_memory(); +void restore_tmp_memory(); + +char* urlDecodeAndUnescape(char *buf); +#if defined(OTF_ENABLED) +void start_otf(); +#endif +#endif // _OPENSPRINKLER_SERVER_H diff --git a/osinfluxdb.cpp b/osinfluxdb.cpp new file mode 100644 index 00000000..fa18af15 --- /dev/null +++ b/osinfluxdb.cpp @@ -0,0 +1,424 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2024 by Stefan Schmaltz (info@opensprinklershop.de) + * + * OpenSprinkler library header file + * Sep 2024 @ OpenSprinklerShop.de + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#include "osinfluxdb.h" +#include "utils.h" +#include "defines.h" +#include "OpenSprinkler.h" + +extern char tmp_buffer[TMP_BUFFER_SIZE*2]; +extern OpenSprinkler os; + +#define INFLUX_CONFIG_FILE "influx.json" + +void OSInfluxDB::set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token) { + ArduinoJson::JsonDocument doc; + doc["en"] = enabled; + doc["url"] = url; + doc["port]"] = port; + doc["org"] = org; + doc["bucket"] = bucket; + doc["token"] = token; + set_influx_config(doc); +} + +void OSInfluxDB::set_influx_config(ArduinoJson::JsonDocument &doc) { + size_t size = ArduinoJson::serializeJson(doc, tmp_buffer); + remove_file(INFLUX_CONFIG_FILE); + file_write_block(INFLUX_CONFIG_FILE, tmp_buffer, 0, size); + if (client) + { + delete client; + client = NULL; + } + enabled = doc["en"]; + initialized = true; +} + +void OSInfluxDB::set_influx_config(const char *data) { + size_t size = strlen(data); + remove_file(INFLUX_CONFIG_FILE); + file_write_block(INFLUX_CONFIG_FILE, "{", 0, 1); + file_write_block(INFLUX_CONFIG_FILE, data, 1, size); + file_write_block(INFLUX_CONFIG_FILE, "}", size+1, 1); + if (client) + { + delete client; + client = NULL; + } + enabled = false; + initialized = false; +} + +void OSInfluxDB::get_influx_config(char *json) { + json[0] = 0; + if (file_exists(INFLUX_CONFIG_FILE)) + { + ulong size = file_read_block(INFLUX_CONFIG_FILE, json, 0, TMP_BUFFER_SIZE_L); + //DEBUG_PRINT(F("influx config size=")); + //DEBUG_PRINTLN(size); + json[size] = 0; + //DEBUG_PRINT(F("influx config=")); + //DEBUG_PRINTLN(tmp_buffer); + if (size > 10 && (json[size-2] != '"' || json[size-1] != '}')) { + strcpy(json, "{\"en\":0}"); + set_influx_config(json); + } + } + if (json[0] != '{' || strchr(json, '}') != strrchr(json, '}')) { + strcpy(json, "{\"en\":0}"); + set_influx_config(json); + } +} + +void OSInfluxDB::get_influx_config(ArduinoJson::JsonDocument &doc) { + //DEBUG_PRINTLN("Load influx config"); + get_influx_config(tmp_buffer); + ArduinoJson::DeserializationError error = ArduinoJson::deserializeJson(doc, tmp_buffer); + if (error || doc.isNull() || !doc.containsKey("en")) { + if (error) { + DEBUG_PRINT(F("influxdb: deserializeJson() failed: ")); + DEBUG_PRINTLN(error.c_str()); + } + doc["en"] = 0; + doc["url"] = ""; + doc["port"] = 8086; + doc["org"] = ""; + doc["bucket"] = ""; + doc["token"] = ""; + set_influx_config(doc); + } + enabled = doc["en"]; + initialized = true; +} + +void OSInfluxDB::init() { + ArduinoJson::JsonDocument doc; + get_influx_config(doc); + enabled = doc["en"]; + initialized = true; +} + +boolean OSInfluxDB::isEnabled() { + if (!initialized) { + init(); + } + return enabled; +} + +#if defined(ESP8266) +OSInfluxDB::~OSInfluxDB() { + if (client) delete client; +} + +void OSInfluxDB::write_influx_data(Point &sensor_data) { + if (!initialized) init(); + if (!enabled) + return; + + if (!client) { + if (!file_exists(INFLUX_CONFIG_FILE)) + return; + + //Load influx config: + ArduinoJson::JsonDocument doc; + get_influx_config(doc); + if (doc["en"] == 0) + return; + + //InfluxDBClient client(INFLUXDB_URL, INFLUXDB_ORG, INFLUXDB_BUCKET, INFLUXDB_TOKEN, InfluxDbCloud2CACert); + String url = doc["url"]; + int port = doc["port"]; + if (port == 0) + port = 8086; + url = url + ":" + port; + client = new InfluxDBClient(url, doc["org"], doc["bucket"], doc["token"], InfluxDbCloud2CACert); + } + + if (client) { + // Print what are we exactly writing + DEBUG_PRINT("Writing: "); + DEBUG_PRINTLN(sensor_data.toLineProtocol()); + + // Write point + if (!client->writePoint(sensor_data)) { + DEBUG_PRINT("influxdb write failed: "); + DEBUG_PRINTLN(client->getLastErrorMessage()); + } + } +} + +#else +OSInfluxDB::~OSInfluxDB() { + if (client) delete client; +} + +influxdb_cpp::server_info *OSInfluxDB::get_client() { + if (!initialized) init(); + if (!enabled) + return NULL; + if (!client) { + //Load influx config: + ArduinoJson::JsonDocument doc; + get_influx_config(doc); + if (doc["en"] == 0) + return NULL; + client = new influxdb_cpp::server_info(doc["url"], doc["port"], doc["bucket"], "", "", "ms", doc["token"]); + } + return client; +} + +#endif + + +#if defined(ESP8266) +void OSInfluxDB::influxdb_send_state(const char *name, int state) { + char tmp[TMP_BUFFER_SIZE]; + Point data("opensprinkler"); + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("state", state); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_station(const char *name, uint32_t station, int state) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("station", station); + data.addField("state", state); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_program(const char *name, uint32_t nr, float level) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("program", nr); + data.addField("level", level); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("count", count); + data.addField("volume", volume); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("name", name); + data.addField("station", station); + data.addField("flowrate", (double)(f1)+(double)(f2)/100); + data.addField("duration", f3); + data.addField("alert_setpoint", (double)(f4)+(double)(f5)/100); + write_influx_data(data); +} + +void OSInfluxDB::influxdb_send_warning(const char *name, uint32_t level, float value) { + Point data("opensprinkler"); + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + data.addTag("devicename", tmp); + data.addTag("warning", name); + data.addField("level", (int)level); + data.addField("currentvalue", value); + write_influx_data(data); +} + +#else + +void OSInfluxDB::influxdb_send_state(const char *name, int state) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("state", state) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_station(const char *name, uint32_t station, int state) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("station", (int)station) + .field("state", state) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_program(const char *name, uint32_t nr, float level) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("program", (int)nr) + .field("level", level) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_flowsensor(const char *name, uint32_t count, float volume) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("count", (int)count) + .field("volume", volume) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("name", name) + .field("station", (int)(station)) + .field("flowrate", (double)(f1)+(double)(f2)/100) + .field("duration", f3) + .field("alert_setpoint", (double)(f4)+(double)(f5)/100) + .timestamp(millis()) + .post_http(*client); +} + +void OSInfluxDB::influxdb_send_warning(const char *name, uint32_t level, float value) { + influxdb_cpp::server_info * client = get_client(); + if (!client) + return; + + char tmp[TMP_BUFFER_SIZE]; + os.sopt_load(SOPT_DEVICE_NAME, tmp); + influxdb_cpp::builder() + .meas("opensprinkler") + .tag("devicename", tmp) + .tag("warning", name) + .field("level", (int)level) + .field("currentvalue", value) + .timestamp(millis()) + .post_http(*client); +} +#endif + +void OSInfluxDB::push_message(uint16_t type, uint32_t lval, float fval, const char* sval) { + if (!isEnabled()) + return; + + uint32_t volume; + + switch(type) { + case NOTIFY_STATION_ON: + influxdb_send_station("station", lval, 1); + break; + + case NOTIFY_FLOW_ALERT:{ + //influxdb_send_flowalert("flowalert", lval, f1, f2, f3, f4, f5); + break; + } + + case NOTIFY_STATION_OFF: + influxdb_send_station("station", lval, 0); + break; + + case NOTIFY_PROGRAM_SCHED: + influxdb_send_program("program sched", lval, fval); + break; + + case NOTIFY_SENSOR1: + influxdb_send_state("sensor1", (int)fval); + break; + + case NOTIFY_SENSOR2: + influxdb_send_state("sensor2", (int)fval); + break; + + case NOTIFY_RAINDELAY: + influxdb_send_state("raindelay", (int)fval); + break; + + case NOTIFY_FLOWSENSOR: + volume = os.iopts[IOPT_PULSE_RATE_1]; + volume = (volume<<8)+os.iopts[IOPT_PULSE_RATE_0]; + volume = lval*volume; + influxdb_send_flowsensor("flowsensor", lval, (float)volume/100); + break; + + case NOTIFY_WEATHER_UPDATE: + influxdb_send_state("waterlevel", (int)fval); + break; + + case NOTIFY_REBOOT: + break; + + case NOTIFY_MONITOR_LOW: + case NOTIFY_MONITOR_MID: + case NOTIFY_MONITOR_HIGH: + influxdb_send_flowsensor(sval, lval, fval); + break; + + } +} \ No newline at end of file diff --git a/osinfluxdb.h b/osinfluxdb.h new file mode 100644 index 00000000..45f05d99 --- /dev/null +++ b/osinfluxdb.h @@ -0,0 +1,67 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX/ESP8266) Firmware + * Copyright (C) 2024 by Stefan Schmaltz (info@opensprinklershop.de) + * + * OpenSprinkler library header file + * Sep 2024 @ OpenSprinklerShop.de + * + * This file is part of the OpenSprinkler library + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _OSINFLUX_H +#define _OSINFLUX_H +#include "ArduinoJson.hpp" +#if defined(ESP8266) +#include +#include +#else +#include + +#endif + +class OSInfluxDB { +private: + #if defined(ESP8266) + InfluxDBClient * client; + #else + influxdb_cpp::server_info * client; + #endif + bool enabled; + bool initialized; + void init(); + void influxdb_send_state(const char *name, int state); + void influxdb_send_station(const char *name, uint32_t station, int state); + void influxdb_send_program(const char *name, uint32_t nr, float level); + void influxdb_send_flowsensor(const char *name, uint32_t count, float volume); + void influxdb_send_flowalert(const char *name, uint32_t station, int f1, int f2, int f3, int f4, int f5); + void influxdb_send_warning(const char *name, uint32_t level, float value); + +public: + ~OSInfluxDB(); + void set_influx_config(int enabled, char *url, uint16_t port, char *org, char *bucket, char *token); + void set_influx_config(ArduinoJson::JsonDocument &doc); + void set_influx_config(const char *json); + void get_influx_config(ArduinoJson::JsonDocument &doc); + void get_influx_config(char *json); + bool isEnabled(); + #if defined(ESP8266) + void write_influx_data(Point &sensor_data); + #else + influxdb_cpp::server_info * get_client(); + #endif + void push_message(uint16_t type, uint32_t lval, float fval, const char* sval); +}; +#endif \ No newline at end of file diff --git a/ospi-analog/driver_ads1115.c b/ospi-analog/driver_ads1115.c new file mode 100644 index 00000000..c5807d23 --- /dev/null +++ b/ospi-analog/driver_ads1115.c @@ -0,0 +1,1464 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_ads1115.c + * @brief driver ads1115 source file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-13 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/13 2.0 Shifeng Li format the code + *
2020/10/13 1.0 Shifeng Li first upload + *
+ */ + +#include "driver_ads1115.h" + +/** + * @brief chip information definition + */ +#define CHIP_NAME "Texas Instruments ADS1115" /**< chip name */ +#define MANUFACTURER_NAME "Texas Instruments" /**< manufacturer name */ +#define SUPPLY_VOLTAGE_MIN 2.0f /**< chip min supply voltage */ +#define SUPPLY_VOLTAGE_MAX 5.5f /**< chip max supply voltage */ +#define MAX_CURRENT 0.2f /**< chip max current */ +#define TEMPERATURE_MIN -40.0f /**< chip min operating temperature */ +#define TEMPERATURE_MAX 125.0f /**< chip max operating temperature */ +#define DRIVER_VERSION 2000 /**< driver version */ + +/** + * @brief chip register definition + */ +#define ADS1115_REG_CONVERT 0x00 /**< adc result register */ +#define ADS1115_REG_CONFIG 0x01 /**< chip config register */ +#define ADS1115_REG_LOWRESH 0x02 /**< interrupt low threshold register */ +#define ADS1115_REG_HIGHRESH 0x03 /**< interrupt high threshold register */ + +/** + * @brief iic address definition + */ +#define ADS1115_ADDRESS1 (0x48 << 1) /**< iic address 1 */ +#define ADS1115_ADDRESS2 (0x49 << 1) /**< iic address 2 */ +#define ADS1115_ADDRESS3 (0x4A << 1) /**< iic address 3 */ +#define ADS1115_ADDRESS4 (0x4B << 1) /**< iic address 4 */ + +/** + * @brief read multiple bytes + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[out] *data points to a data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +static uint8_t a_ads1115_iic_multiple_read(ads1115_handle_t *handle, uint8_t reg, int16_t *data) +{ + uint8_t buf[2]; + + memset(buf, 0, sizeof(uint8_t) * 2); /* clear the buffer */ + if (handle->iic_read(handle->iic_addr, reg, (uint8_t *)buf, 2) == 0) /* read data */ + { + *data = (uint16_t)(((uint16_t)buf[0] << 8) | buf[1]); /* set data */ + + return 0; /* success return 0 */ + } + else + { + return 1; /* return error */ + } +} + +/** + * @brief write multiple bytes + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[in] data is the sent data + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +static uint8_t a_ads1115_iic_multiple_write(ads1115_handle_t *handle, uint8_t reg, uint16_t data) +{ + uint8_t buf[2]; + + buf[0] = (data >> 8) & 0xFF; /* set MSB */ + buf[1] = data & 0xFF; /* set LSB */ + if (handle->iic_write(handle->iic_addr, reg, (uint8_t *)buf, 2) != 0) /* write data */ + { + return 1; /* return error */ + } + else + { + return 0; /* success return 0 */ + } +} + +/** + * @brief initialize the chip + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 iic initialization failed + * - 2 handle is NULL + * - 3 linked functions is NULL + * @note none + */ +uint8_t ads1115_init(ads1115_handle_t *handle) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->debug_print == NULL) /* check debug_print */ + { + return 3; /* return error */ + } + if (handle->iic_init == NULL) /* check iic_init */ + { + handle->debug_print("ads1115: iic_init is null.\n"); /* iic_init is null */ + + return 3; /* return error */ + } + if (handle->iic_deinit == NULL) /* check iic_deinit */ + { + handle->debug_print("ads1115: iic_deinit is null.\n"); /* iic_deinit is null */ + + return 3; /* return error */ + } + if (handle->iic_read == NULL) /* check iic_read */ + { + handle->debug_print("ads1115: iic_read is null.\n"); /* iic_read is null */ + + return 3; /* return error */ + } + if (handle->iic_write == NULL) /* check iic_write */ + { + handle->debug_print("ads1115: iic_write is null.\n"); /* iic_write is null */ + + return 3; /* return error */ + } + if (handle->delay_ms == NULL) /* check delay_ms */ + { + handle->debug_print("ads1115: delay_ms is null.\n"); /* delay_ms is null */ + + return 3; /* return error */ + } + + if (handle->iic_init() != 0) /* iic init */ + { + handle->debug_print("ads1115: iic init failed.\n"); /* iic init failed */ + + return 1; /* return error */ + } + handle->inited = 1; /* flag inited */ + + return 0; /* success return 0 */ +} + +/** + * @brief close the chip + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 iic deinit failed + * - 2 handle is NULL + * - 3 handle is not initialized + * - 4 power down failed + * @note none + */ +uint8_t ads1115_deinit(ads1115_handle_t *handle) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 4; /* return error */ + } + conf &= ~(0x01 << 8); /* clear bit */ + conf |= 1 << 8; /* set stop continues read */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 4; /* return error */ + } + res = handle->iic_deinit(); /* close iic */ + if (res != 0) /* check the result */ + { + handle->debug_print("ads1115: iic deinit failed.\n"); /* iic deinit failed */ + + return 1; /* return error */ + } + handle->inited = 0; /* flag close */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc channel + * @param[in] *handle points to an ads1115 handle structure + * @param[in] channel is the adc channel + * @return status code + * - 0 success + * - 1 set channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_channel(ads1115_handle_t *handle, ads1115_channel_t channel) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x07 << 12); /* clear channel */ + conf |= (channel & 0x07) << 12; /* set channel */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc channel + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *channel points to a channel buffer + * @return status code + * - 0 success + * - 1 get channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_channel(ads1115_handle_t *handle, ads1115_channel_t *channel) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *channel = (ads1115_channel_t)((conf >> 12) & 0x07); /* get channel */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc range + * @param[in] *handle points to an ads1115 handle structure + * @param[in] range is the adc max voltage range + * @return status code + * - 0 success + * - 1 set range failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_range(ads1115_handle_t *handle, ads1115_range_t range) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x07 << 9); /* clear range */ + conf |= (range & 0x07) << 9; /* set range */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc range + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *range points to a voltage range buffer + * @return status code + * - 0 success + * - 1 get range failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_range(ads1115_handle_t *handle, ads1115_range_t *range) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the alert pin active status + * @param[in] *handle points to an ads1115 handle structure + * @param[in] pin is the alert active status + * @return status code + * - 0 success + * - 1 set alert pin failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_alert_pin(ads1115_handle_t *handle, ads1115_pin_t pin) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(1 << 3); /* clear alert pin */ + conf |= (pin & 0x01) << 3; /* set alert pin */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the alert pin active status + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *pin points to a pin alert active status buffer + * @return status code + * - 0 success + * - 1 get alert pin failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_alert_pin(ads1115_handle_t *handle, ads1115_pin_t *pin) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *pin = (ads1115_pin_t)((conf >> 3) & 0x01); /* get alert pin */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the interrupt compare mode + * @param[in] *handle points to an ads1115 handle structure + * @param[in] compare is the interrupt compare mode + * @return status code + * - 0 success + * - 1 set compare mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare_mode(ads1115_handle_t *handle, ads1115_compare_t compare) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(1 << 4); /* clear compare mode */ + conf |= (compare & 0x01) << 4; /* set compare mode */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the interrupt compare mode + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *compare points to an interrupt compare mode buffer + * @return status code + * - 0 success + * - 1 get compare mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare_mode(ads1115_handle_t *handle, ads1115_compare_t *compare) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *compare = (ads1115_compare_t)((conf >> 4) & 0x01); /* get compare mode */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the sample rate + * @param[in] *handle points to an ads1115 handle structure + * @param[in] rate is the adc sample rate + * @return status code + * - 0 success + * - 1 set rate failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_rate(ads1115_handle_t *handle, ads1115_rate_t rate) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x07 << 5); /* clear rate */ + conf |= (rate & 0x07) << 5; /* set rate */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return */ +} + +/** + * @brief get the sample rate + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *rate points to an adc sample rate buffer + * @return status code + * - 0 success + * - 1 get rate failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_rate(ads1115_handle_t *handle, ads1115_rate_t *rate) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *rate = (ads1115_rate_t)((conf >> 5) & 0x07); /* get rate */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the interrupt comparator queue + * @param[in] *handle points to an ads1115 handle structure + * @param[in] comparator_queue is the interrupt comparator queue + * @return status code + * - 0 success + * - 1 set comparator queue failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_comparator_queue(ads1115_handle_t *handle, ads1115_comparator_queue_t comparator_queue) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x03 << 0); /* clear comparator queue */ + conf |= (comparator_queue & 0x03) << 0; /* set comparator queue */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the interrupt comparator queue + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *comparator_queue points to an interrupt comparator queue + * @return status code + * - 0 success + * - 1 get comparator queue failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_comparator_queue(ads1115_handle_t *handle, ads1115_comparator_queue_t *comparator_queue) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *comparator_queue = (ads1115_comparator_queue_t)((conf >> 0) & 0x03); /* get comparator queue */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the iic address pin + * @param[in] *handle points to an ads1115 handle structure + * @param[in] addr_pin is the chip iic address pin + * @return status code + * - 0 success + * - 1 set addr pin failed + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_set_addr_pin(ads1115_handle_t *handle, ads1115_address_t addr_pin) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + + if (addr_pin == ADS1115_ADDR_GND) /* gnd */ + { + handle->iic_addr = ADS1115_ADDRESS1; /* set address 1 */ + } + else if (addr_pin == ADS1115_ADDR_VCC) /* vcc */ + { + handle->iic_addr = ADS1115_ADDRESS2; /* set address 2 */ + } + else if (addr_pin == ADS1115_ADDR_SDA) /* sda */ + { + handle->iic_addr = ADS1115_ADDRESS3; /* set address 3 */ + } + else if (addr_pin == ADS1115_ADDR_SCL) /* scl */ + { + handle->iic_addr = ADS1115_ADDRESS4; /* set address 4 */ + } + else + { + handle->debug_print("ads1115: addr_pin is invalid.\n"); /* addr_pin is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the iic address pin + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *addr_pin points to a chip iic address pin buffer + * @return status code + * - 0 success + * - 1 get addr pin failed + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_get_addr_pin(ads1115_handle_t *handle, ads1115_address_t *addr_pin) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + + if (handle->iic_addr == ADS1115_ADDRESS1) /* if address 1 */ + { + *addr_pin = ADS1115_ADDR_GND; /* set gnd */ + } + else if (handle->iic_addr == ADS1115_ADDRESS2) /* if address 2 */ + { + *addr_pin = ADS1115_ADDR_VCC; /* set vcc */ + } + else if (handle->iic_addr == ADS1115_ADDRESS3) /* if address 3 */ + { + *addr_pin = ADS1115_ADDR_SDA; /* set sda */ + } + else if (handle->iic_addr == ADS1115_ADDRESS4) /* set address 4 */ + { + *addr_pin = ADS1115_ADDR_SCL; /* set scl */ + } + else + { + handle->debug_print("ads1115: addr_pin is invalid.\n"); /* addr_pin is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief read data from the chip once + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *v points to a converted adc buffer + * @return status code + * - 0 success + * - 1 single read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_single_read(ads1115_handle_t *handle, int16_t *raw, float *v) +{ + uint8_t res; + uint8_t range; + uint16_t conf; + uint32_t timeout = 500; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range conf */ + conf &= ~(1 << 8); /* clear bit */ + conf |= 1 << 8; /* set single read */ + conf |= 1 << 15; /* start single read */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + while (timeout != 0) /* check timeout */ + { + handle->delay_ms(8); /* wait 8 ms */ + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + if ((conf & (1 << 15)) == (1 << 15)) /* check finished */ + { + break; /* break */ + } + timeout--; /* timeout-- */ + } + if (timeout == 0) /* check timeout */ + { + handle->debug_print("ads1115: read timeout.\n"); /* timeout */ + + return 1; /* return error */ + } + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONVERT, raw); /* read data */ + if (res != 0) /* check the result */ + { + handle->debug_print("ads1115: continues read failed.\n"); /* continues read failed */ + + return 1; /* return error */ + } + if (range == ADS1115_RANGE_6P144V) /* if 6.144V */ + { + *v = (float)(*raw) * 6.144f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_4P096V) /* if 4.096V */ + { + *v = (float)(*raw) * 4.096f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_2P048V) /* if 2.048V */ + { + *v = (float)(*raw) * 2.048f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_1P024V) /* if 1.024V */ + { + *v = (float)(*raw) * 1.024f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_0P512V) /* if 0.512V */ + { + *v = (float)(*raw) * 0.512f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_0P256V) /* if 0.256V */ + { + *v = (float)(*raw) * 0.256f / 32768.0f; /* get convert adc */ + } + else + { + handle->debug_print("ads1115: range is invalid.\n"); /* range is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief read data from the chip continuously + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *v points to a converted adc buffer + * @return status code + * - 0 success + * - 1 continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note this function can be used only after run ads1115_start_continuous_read + * and can be stopped by ads1115_stop_continuous_read + */ +uint8_t ads1115_continuous_read(ads1115_handle_t *handle,int16_t *raw, float *v) +{ + uint8_t res; + uint8_t range; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range conf */ + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONVERT, raw); /* read data */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: continuous read failed.\n"); /* continuous read failed */ + + return 1; /* return error */ + } + if (range == ADS1115_RANGE_6P144V) /* if 6.144V */ + { + *v = (float)(*raw) * 6.144f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_4P096V) /* if 4.096V */ + { + *v = (float)(*raw) * 4.096f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_2P048V) /* if 2.048V */ + { + *v = (float)(*raw) * 2.048f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_1P024V) /* if 1.024V */ + { + *v = (float)(*raw) * 1.024f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_0P512V) /* if 0.512V */ + { + *v = (float)(*raw) * 0.512f / 32768.0f; /* get convert adc */ + } + else if (range == ADS1115_RANGE_0P256V) /* if 0.256V */ + { + *v = (float)(*raw) * 0.256f / 32768.0f; /* get convert adc */ + } + else + { + handle->debug_print("ads1115: range is invalid.\n"); /* range is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief start the chip reading + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 start continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_start_continuous_read(ads1115_handle_t *handle) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x01 << 8); /* set start continuous read */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief stop the chip reading + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 stop continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_stop_continuous_read(ads1115_handle_t *handle) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x01 << 8); /* clear bit */ + conf |= 1 << 8; /* set stop continues read */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief enable or disable the interrupt compare + * @param[in] *handle points to an ads1115 handle structure + * @param[in] enable is a bool value + * @return status code + * - 0 success + * - 1 set compare failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare(ads1115_handle_t *handle, ads1115_bool_t enable) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + conf &= ~(0x01 << 2); /* clear compare */ + conf |= enable << 2; /* set compare */ + res = a_ads1115_iic_multiple_write(handle, ADS1115_REG_CONFIG, conf); /* write config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: write config failed.\n"); /* write config failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the interrupt compare status + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *enable points to a bool value buffer + * @return status code + * - 0 success + * - 1 get compare failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare(ads1115_handle_t *handle, ads1115_bool_t *enable) +{ + uint8_t res; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + *enable = (ads1115_bool_t)((conf >> 2) & 0x01); /* get compare */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the interrupt compare threshold + * @param[in] *handle points to an ads1115 handle structure + * @param[in] high_threshold is the interrupt high threshold + * @param[in] low_threshold is the interrupt low threshold + * @return status code + * - 0 success + * - 1 set compare threshold failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare_threshold(ads1115_handle_t *handle, int16_t high_threshold, int16_t low_threshold) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + if (a_ads1115_iic_multiple_write(handle, ADS1115_REG_HIGHRESH, high_threshold) != 0) /* write high threshold */ + { + handle->debug_print("ads1115: write high threshold failed.\n"); /* write high threshold failed */ + + return 1; /* return error */ + } + if (a_ads1115_iic_multiple_write(handle, ADS1115_REG_LOWRESH, low_threshold) != 0) /* write low threshold */ + { + handle->debug_print("ads1115: write low threshold failed.\n"); /* write low threshold failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the interrupt compare threshold + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *high_threshold points to an interrupt high threshold buffer + * @param[out] *low_threshold points to an interrupt low threshold buffer + * @return status code + * - 0 success + * - 1 get compare threshold failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare_threshold(ads1115_handle_t *handle, int16_t *high_threshold, int16_t *low_threshold) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + if (a_ads1115_iic_multiple_read(handle, ADS1115_REG_HIGHRESH, high_threshold) != 0) /* read high threshold */ + { + handle->debug_print("ads1115: read high threshold failed.\n"); /* read high threshold failed */ + + return 1; /* return error */ + } + if (a_ads1115_iic_multiple_read(handle, ADS1115_REG_LOWRESH, low_threshold) != 0) /* read low threshold */ + { + handle->debug_print("ads1115: read low threshold failed.\n"); /* read low threshold failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief convert a adc value to a register raw data + * @param[in] *handle points to an ads1115 handle structure + * @param[in] s is a converted adc value + * @param[out] *reg points to a register raw buffer + * @return status code + * - 0 success + * - 1 convert to register failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_convert_to_register(ads1115_handle_t *handle, float s, int16_t *reg) +{ + uint8_t res; + uint8_t range; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range conf */ + if (range == ADS1115_RANGE_6P144V) /* if 6.144V */ + { + *reg = (int16_t)(s * 32768.0f / 6.144f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_4P096V) /* if 4.096V */ + { + *reg = (int16_t)(s * 32768.0f / 4.096f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_2P048V) /* if 2.048V */ + { + *reg = (int16_t)(s * 32768.0f / 2.048f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_1P024V) /* if 1.024V */ + { + *reg = (int16_t)(s * 32768.0f / 1.024f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_0P512V) /* if 0.512V */ + { + *reg = (int16_t)(s * 32768.0f / 0.512f); /* convert to reg */ + } + else if (range == ADS1115_RANGE_0P256V) /* if 0.256V */ + { + *reg = (int16_t)(s * 32768.0f / 0.256f); /* convert to reg */ + } + else + { + handle->debug_print("ads1115: range is invalid.\n"); /* range is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief convert a register raw data to a converted adc data + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the register raw data + * @param[out] *s points to a converted adc value buffer + * @return status code + * - 0 success + * - 1 convert to data failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_convert_to_data(ads1115_handle_t *handle, int16_t reg, float *s) +{ + uint8_t res; + uint8_t range; + uint16_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + res = a_ads1115_iic_multiple_read(handle, ADS1115_REG_CONFIG, (int16_t *)&conf); /* read config */ + if (res != 0) /* check error */ + { + handle->debug_print("ads1115: read config failed.\n"); /* read config failed */ + + return 1; /* return error */ + } + range = (ads1115_range_t)((conf >> 9) & 0x07); /* get range conf */ + if (range == ADS1115_RANGE_6P144V) /* if 6.144V */ + { + *s = (float)(reg) * 6.144f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_4P096V) /* if 4.096V */ + { + *s = (float)(reg) * 4.096f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_2P048V) /* if 2.048V */ + { + *s = (float)(reg) * 2.048f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_1P024V) /* if 1.024V */ + { + *s = (float)(reg) * 1.024f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_0P512V) /* if 0.512V */ + { + *s = (float)(reg) * 0.512f / 32768.0f; /* convert to data */ + } + else if (range == ADS1115_RANGE_0P256V) /* if 0.256V */ + { + *s = (float)(reg) * 0.256f / 32768.0f; /* convert to data */ + } + else + { + handle->debug_print("ads1115: range is invalid.\n"); /* range is invalid */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief set the chip register + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[in] value is the data written to the register + * @return status code + * - 0 success + * - 1 write failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_reg(ads1115_handle_t *handle, uint8_t reg, int16_t value) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + return a_ads1115_iic_multiple_write(handle, reg, value); /* write reg */ +} + +/** + * @brief get the chip register + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[out] *value points to a read data buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_reg(ads1115_handle_t *handle, uint8_t reg, int16_t *value) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + return a_ads1115_iic_multiple_read(handle, reg, value); /* read reg */ +} + +/** + * @brief get chip's information + * @param[out] *info points to an ads1115 info structure + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_info(ads1115_info_t *info) +{ + if (info == NULL) /* check handle */ + { + return 2; /* return error */ + } + + memset(info, 0, sizeof(ads1115_info_t)); /* initialize ads1115 info structure */ + strncpy(info->chip_name, CHIP_NAME, 32); /* copy chip name */ + strncpy(info->manufacturer_name, MANUFACTURER_NAME, 32); /* copy manufacturer name */ + strncpy(info->interface, "IIC", 8); /* copy interface name */ + info->supply_voltage_min_v = SUPPLY_VOLTAGE_MIN; /* set minimal supply voltage */ + info->supply_voltage_max_v = SUPPLY_VOLTAGE_MAX; /* set maximum supply voltage */ + info->max_current_ma = MAX_CURRENT; /* set maximum current */ + info->temperature_max = TEMPERATURE_MAX; /* set minimal temperature */ + info->temperature_min = TEMPERATURE_MIN; /* set maximum temperature */ + info->driver_version = DRIVER_VERSION; /* set driver version */ + + return 0; /* success return 0 */ +} diff --git a/ospi-analog/driver_ads1115.h b/ospi-analog/driver_ads1115.h new file mode 100644 index 00000000..c1340f61 --- /dev/null +++ b/ospi-analog/driver_ads1115.h @@ -0,0 +1,691 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_ads1115.h + * @brief driver ads1115 header file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-13 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/13 2.0 Shifeng Li format the code + *
2020/10/13 1.0 Shifeng Li first upload + *
+ */ + +#ifndef DRIVER_ADS1115_H +#define DRIVER_ADS1115_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @defgroup ads1115_driver ads1115 driver function + * @brief ads1115 driver modules + * @{ + */ + +/** + * @addtogroup ads1115_base_driver + * @{ + */ + +/** + * @brief ads1115 address enumeration definition + */ +typedef enum +{ + ADS1115_ADDR_GND = 0x00, /**< ADDR pin connected to GND */ + ADS1115_ADDR_VCC = 0x01, /**< ADDR pin connected to VCC */ + ADS1115_ADDR_SDA = 0x02, /**< ADDR pin connected to SDA */ + ADS1115_ADDR_SCL = 0x03, /**< ADDR pin connected to SCL */ +} ads1115_address_t; + +/** + * @brief ads1115 bool enumeration definition + */ +typedef enum +{ + ADS1115_BOOL_FALSE = 0x00, /**< disable function */ + ADS1115_BOOL_TRUE = 0x01, /**< enable function */ +} ads1115_bool_t; + +/** + * @brief ads1115 range enumeration definition + */ +typedef enum +{ + ADS1115_RANGE_6P144V = 0x00, /**< 6.144V range */ + ADS1115_RANGE_4P096V = 0x01, /**< 4.096V range */ + ADS1115_RANGE_2P048V = 0x02, /**< 2.048V range */ + ADS1115_RANGE_1P024V = 0x03, /**< 1.024V range */ + ADS1115_RANGE_0P512V = 0x04, /**< 0.512V range */ + ADS1115_RANGE_0P256V = 0x05, /**< 0.256V range */ +} ads1115_range_t; + +/** + * @brief ads1115 channel rate enumeration definition + */ +typedef enum +{ + ADS1115_RATE_8SPS = 0x00, /**< 8 sample per second */ + ADS1115_RATE_16SPS = 0x01, /**< 16 sample per second */ + ADS1115_RATE_32SPS = 0x02, /**< 32 sample per second */ + ADS1115_RATE_64SPS = 0x03, /**< 64 sample per second */ + ADS1115_RATE_128SPS = 0x04, /**< 128 sample per second */ + ADS1115_RATE_250SPS = 0x05, /**< 250 sample per second */ + ADS1115_RATE_475SPS = 0x06, /**< 475 sample per second */ + ADS1115_RATE_860SPS = 0x07, /**< 860 sample per second */ +} ads1115_rate_t; + +/** + * @brief ads1115 channel enumeration definition + */ +typedef enum +{ + ADS1115_CHANNEL_AIN0_AIN1 = 0x00, /**< AIN0 and AIN1 pins */ + ADS1115_CHANNEL_AIN0_AIN3 = 0x01, /**< AIN0 and AIN3 pins */ + ADS1115_CHANNEL_AIN1_AIN3 = 0x02, /**< AIN1 and AIN3 pins */ + ADS1115_CHANNEL_AIN2_AIN3 = 0x03, /**< AIN2 and AIN3 pins */ + ADS1115_CHANNEL_AIN0_GND = 0x04, /**< AIN0 and GND pins */ + ADS1115_CHANNEL_AIN1_GND = 0x05, /**< AIN1 and GND pins */ + ADS1115_CHANNEL_AIN2_GND = 0x06, /**< AIN2 and GND pins */ + ADS1115_CHANNEL_AIN3_GND = 0x07, /**< AIN3 and GND pins */ +} ads1115_channel_t; + +/** + * @} + */ + +/** + * @addtogroup ads1115_interrupt_driver + * @{ + */ + +/** + * @brief ads1115 pin enumeration definition + */ +typedef enum +{ + ADS1115_PIN_LOW = 0x00, /**< set pin low */ + ADS1115_PIN_HIGH = 0x01, /**< set pin high */ +} ads1115_pin_t; + +/** + * @brief ads1115 compare mode enumeration definition + */ +typedef enum +{ + ADS1115_COMPARE_THRESHOLD = 0x00, /**< threshold compare interrupt mode */ + ADS1115_COMPARE_WINDOW = 0x01, /**< window compare interrupt mode */ +} ads1115_compare_t; + +/** + * @brief ads1115 comparator queue enumeration definition + */ +typedef enum +{ + ADS1115_COMPARATOR_QUEUE_1_CONV = 0x00, /**< comparator queue has 1 conv */ + ADS1115_COMPARATOR_QUEUE_2_CONV = 0x01, /**< comparator queue has 2 conv */ + ADS1115_COMPARATOR_QUEUE_4_CONV = 0x02, /**< comparator queue has 3 conv */ + ADS1115_COMPARATOR_QUEUE_NONE_CONV = 0x03, /**< comparator queue has no conv */ +} ads1115_comparator_queue_t; + +/** + * @} + */ + +/** + * @addtogroup ads1115_base_driver + * @{ + */ + +/** + * @brief ads1115 handle structure definition + */ +typedef struct ads1115_handle_s +{ + uint8_t iic_addr; /**< iic device address */ + uint8_t (*iic_init)(void); /**< point to an iic_init function address */ + uint8_t (*iic_deinit)(void); /**< point to an iic_deinit function address */ + uint8_t (*iic_read)(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); /**< point to an iic_read function address */ + uint8_t (*iic_write)(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); /**< point to an iic_write function address */ + void (*delay_ms)(uint32_t ms); /**< point to a delay_ms function address */ + void (*debug_print)(const char *const fmt, ...); /**< point to a debug_print function address */ + uint8_t inited; /**< inited flag */ +} ads1115_handle_t; + +/** + * @brief ads1115 information structure definition + */ +typedef struct ads1115_info_s +{ + char chip_name[32]; /**< chip name */ + char manufacturer_name[32]; /**< manufacturer name */ + char interface[8]; /**< chip interface name */ + float supply_voltage_min_v; /**< chip min supply voltage */ + float supply_voltage_max_v; /**< chip max supply voltage */ + float max_current_ma; /**< chip max current */ + float temperature_min; /**< chip min operating temperature */ + float temperature_max; /**< chip max operating temperature */ + uint32_t driver_version; /**< driver version */ +} ads1115_info_t; + +/** + * @} + */ + +/** + * @defgroup ads1115_link_driver ads1115 link driver function + * @brief ads1115 link driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief initialize ads1115_handle_t structure + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] STRUCTURE is ads1115_handle_t + * @note none + */ +#define DRIVER_ADS1115_LINK_INIT(HANDLE, STRUCTURE) memset(HANDLE, 0, sizeof(STRUCTURE)) + +/** + * @brief link iic_init function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to an iic_init function address + * @note none + */ +#define DRIVER_ADS1115_LINK_IIC_INIT(HANDLE, FUC) (HANDLE)->iic_init = FUC + +/** + * @brief link iic_deinit function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to an iic_deinit function address + * @note none + */ +#define DRIVER_ADS1115_LINK_IIC_DEINIT(HANDLE, FUC) (HANDLE)->iic_deinit = FUC + +/** + * @brief link iic_read function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to an iic_read function address + * @note none + */ +#define DRIVER_ADS1115_LINK_IIC_READ(HANDLE, FUC) (HANDLE)->iic_read = FUC + +/** + * @brief link iic_write function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to an iic_write function address + * @note none + */ +#define DRIVER_ADS1115_LINK_IIC_WRITE(HANDLE, FUC) (HANDLE)->iic_write = FUC + +/** + * @brief link delay_ms function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to a delay_ms function address + * @note none + */ +#define DRIVER_ADS1115_LINK_DELAY_MS(HANDLE, FUC) (HANDLE)->delay_ms = FUC + +/** + * @brief link debug_print function + * @param[in] HANDLE points to an ads1115 handle structure + * @param[in] FUC points to a debug_print function address + * @note none + */ +#define DRIVER_ADS1115_LINK_DEBUG_PRINT(HANDLE, FUC) (HANDLE)->debug_print = FUC + +/** + * @} + */ + +/** + * @defgroup ads1115_base_driver ads1115 base driver function + * @brief ads1115 base driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief get chip's information + * @param[out] *info points to an ads1115 info structure + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_info(ads1115_info_t *info); + +/** + * @brief set the iic address pin + * @param[in] *handle points to an ads1115 handle structure + * @param[in] addr_pin is the chip iic address pin + * @return status code + * - 0 success + * - 1 set addr pin failed + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_set_addr_pin(ads1115_handle_t *handle, ads1115_address_t addr_pin); + +/** + * @brief get the iic address pin + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *addr_pin points to a chip iic address pin buffer + * @return status code + * - 0 success + * - 1 get addr pin failed + * - 2 handle is NULL + * @note none + */ +uint8_t ads1115_get_addr_pin(ads1115_handle_t *handle, ads1115_address_t *addr_pin); + +/** + * @brief initialize the chip + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 iic initialization failed + * - 2 handle is NULL + * - 3 linked functions is NULL + * @note none + */ +uint8_t ads1115_init(ads1115_handle_t *handle); + +/** + * @brief close the chip + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 iic deinit failed + * - 2 handle is NULL + * - 3 handle is not initialized + * - 4 power down failed + * @note none + */ +uint8_t ads1115_deinit(ads1115_handle_t *handle); + +/** + * @brief read data from the chip once + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *v points to a converted adc buffer + * @return status code + * - 0 success + * - 1 single read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_single_read(ads1115_handle_t *handle, int16_t *raw, float *v); + +/** + * @brief start the chip reading + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 start continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_start_continuous_read(ads1115_handle_t *handle); + +/** + * @brief stop the chip reading + * @param[in] *handle points to an ads1115 handle structure + * @return status code + * - 0 success + * - 1 stop continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_stop_continuous_read(ads1115_handle_t *handle); + +/** + * @brief read data from the chip continuously + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *v points to a converted adc buffer + * @return status code + * - 0 success + * - 1 continuous read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note this function can be used only after run ads1115_start_continuous_read + * and can be stopped by ads1115_stop_continuous_read + */ +uint8_t ads1115_continuous_read(ads1115_handle_t *handle,int16_t *raw, float *v); + +/** + * @brief set the adc channel + * @param[in] *handle points to an ads1115 handle structure + * @param[in] channel is the adc channel + * @return status code + * - 0 success + * - 1 set channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_channel(ads1115_handle_t *handle, ads1115_channel_t channel); + +/** + * @brief get the adc channel + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *channel points to a channel buffer + * @return status code + * - 0 success + * - 1 get channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_channel(ads1115_handle_t *handle, ads1115_channel_t *channel); + +/** + * @brief set the adc range + * @param[in] *handle points to an ads1115 handle structure + * @param[in] range is the adc max voltage range + * @return status code + * - 0 success + * - 1 set range failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_range(ads1115_handle_t *handle, ads1115_range_t range); + +/** + * @brief get the adc range + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *range points to a voltage range buffer + * @return status code + * - 0 success + * - 1 get range failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_range(ads1115_handle_t *handle, ads1115_range_t *range); + +/** + * @brief set the sample rate + * @param[in] *handle points to an ads1115 handle structure + * @param[in] rate is the adc sample rate + * @return status code + * - 0 success + * - 1 set rate failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_rate(ads1115_handle_t *handle, ads1115_rate_t rate); + +/** + * @brief get the sample rate + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *rate points to an adc sample rate buffer + * @return status code + * - 0 success + * - 1 get rate failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_rate(ads1115_handle_t *handle, ads1115_rate_t *rate); + +/** + * @} + */ + +/** + * @defgroup ads1115_interrupt_driver ads1115 interrupt driver function + * @brief ads1115 interrupt driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief set the alert pin active status + * @param[in] *handle points to an ads1115 handle structure + * @param[in] pin is the alert active status + * @return status code + * - 0 success + * - 1 set alert pin failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_alert_pin(ads1115_handle_t *handle, ads1115_pin_t pin); + +/** + * @brief get the alert pin active status + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *pin points to a pin alert active status buffer + * @return status code + * - 0 success + * - 1 get alert pin failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_alert_pin(ads1115_handle_t *handle, ads1115_pin_t *pin); + +/** + * @brief set the interrupt compare mode + * @param[in] *handle points to an ads1115 handle structure + * @param[in] compare is the interrupt compare mode + * @return status code + * - 0 success + * - 1 set compare mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare_mode(ads1115_handle_t *handle, ads1115_compare_t compare); + +/** + * @brief get the interrupt compare mode + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *compare points to an interrupt compare mode buffer + * @return status code + * - 0 success + * - 1 get compare mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare_mode(ads1115_handle_t *handle, ads1115_compare_t *compare); + +/** + * @brief set the interrupt comparator queue + * @param[in] *handle points to an ads1115 handle structure + * @param[in] comparator_queue is the interrupt comparator queue + * @return status code + * - 0 success + * - 1 set comparator queue failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_comparator_queue(ads1115_handle_t *handle, ads1115_comparator_queue_t comparator_queue); + +/** + * @brief get the interrupt comparator queue + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *comparator_queue points to an interrupt comparator queue + * @return status code + * - 0 success + * - 1 get comparator queue failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_comparator_queue(ads1115_handle_t *handle, ads1115_comparator_queue_t *comparator_queue); + +/** + * @brief enable or disable the interrupt compare + * @param[in] *handle points to an ads1115 handle structure + * @param[in] enable is a bool value + * @return status code + * - 0 success + * - 1 set compare failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare(ads1115_handle_t *handle, ads1115_bool_t enable); + +/** + * @brief get the interrupt compare status + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *enable points to a bool value buffer + * @return status code + * - 0 success + * - 1 get compare failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare(ads1115_handle_t *handle, ads1115_bool_t *enable); + +/** + * @brief set the interrupt compare threshold + * @param[in] *handle points to an ads1115 handle structure + * @param[in] high_threshold is the interrupt high threshold + * @param[in] low_threshold is the interrupt low threshold + * @return status code + * - 0 success + * - 1 set compare threshold failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_compare_threshold(ads1115_handle_t *handle, int16_t high_threshold, int16_t low_threshold); + +/** + * @brief get the interrupt compare threshold + * @param[in] *handle points to an ads1115 handle structure + * @param[out] *high_threshold points to an interrupt high threshold buffer + * @param[out] *low_threshold points to an interrupt low threshold buffer + * @return status code + * - 0 success + * - 1 get compare threshold failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_compare_threshold(ads1115_handle_t *handle, int16_t *high_threshold, int16_t *low_threshold); + +/** + * @brief convert a adc value to a register raw data + * @param[in] *handle points to an ads1115 handle structure + * @param[in] s is a converted adc value + * @param[out] *reg points to a register raw buffer + * @return status code + * - 0 success + * - 1 convert to register failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_convert_to_register(ads1115_handle_t *handle, float s, int16_t *reg); + +/** + * @brief convert a register raw data to a converted adc data + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the register raw data + * @param[out] *s points to a converted adc value buffer + * @return status code + * - 0 success + * - 1 convert to data failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_convert_to_data(ads1115_handle_t *handle, int16_t reg, float *s); + +/** + * @} + */ + +/** + * @defgroup ads1115_extend_driver ads1115 extend driver function + * @brief ads1115 extend driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief set the chip register + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[in] value is the data written to the register + * @return status code + * - 0 success + * - 1 write failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_set_reg(ads1115_handle_t *handle, uint8_t reg, int16_t value); + +/** + * @brief get the chip register + * @param[in] *handle points to an ads1115 handle structure + * @param[in] reg is the iic register address + * @param[out] *value points to a read data buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t ads1115_get_reg(ads1115_handle_t *handle, uint8_t reg, int16_t *value); + +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospi-analog/driver_ads1115_interface.c b/ospi-analog/driver_ads1115_interface.c new file mode 100644 index 00000000..f41b1855 --- /dev/null +++ b/ospi-analog/driver_ads1115_interface.c @@ -0,0 +1,138 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file raspberrypi4b_driver_ads1115_interface.c + * @brief raspberrypi4b driver ads1115 interface source file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-13 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/13 2.0 Shifeng Li format the code + *
2020/10/13 1.0 Shifeng Li first upload + *
+ */ + +#include "driver_ads1115_interface.h" +#include "iic.h" +#include + +/** + * @brief iic device name definition + */ +static const char* IIC_DEVICE_NAME = "/dev/i2c-1"; /**< iic device name */ + +/** + * @brief iic device handle definition + */ +static int gs_fd; /**< iic handle */ + +/** + * @brief interface iic bus init + * @return status code + * - 0 success + * - 1 iic init failed + * @note none + */ +uint8_t ads1115_interface_iic_init(void) +{ + return iic_init(IIC_DEVICE_NAME, &gs_fd); +} + +/** + * @brief interface iic bus deinit + * @return status code + * - 0 success + * - 1 iic deinit failed + * @note none + */ +uint8_t ads1115_interface_iic_deinit(void) +{ + return iic_deinit(gs_fd); +} + +/** + * @brief interface iic bus read + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +uint8_t ads1115_interface_iic_read(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len) +{ + return iic_read(gs_fd, addr, reg, buf, len); +} + +/** + * @brief interface iic bus write + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +uint8_t ads1115_interface_iic_write(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len) +{ + return iic_write(gs_fd, addr, reg, buf, len); +} + +/** + * @brief interface delay ms + * @param[in] ms + * @note none + */ +void ads1115_interface_delay_ms(uint32_t ms) +{ + usleep(ms * 1000); +} + +/** + * @brief interface print format data + * @param[in] fmt is the format data + * @note none + */ +void ads1115_interface_debug_print(const char *const fmt, ...) +{ + /** + char str[256]; + uint16_t len; + va_list args; + + memset((char *)str, 0, sizeof(char) * 256); + va_start(args, fmt); + vsnprintf((char *)str, 255, (char const *)fmt, args); + va_end(args); + + len = strlen((char *)str); + (void)printf((uint8_t *)str, len); + **/ +} diff --git a/ospi-analog/driver_ads1115_interface.h b/ospi-analog/driver_ads1115_interface.h new file mode 100644 index 00000000..f8f788df --- /dev/null +++ b/ospi-analog/driver_ads1115_interface.h @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_ads1115_interface.h + * @brief driver ads1115 interface header file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-13 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/13 2.0 Shifeng Li format the code + *
2020/10/13 1.0 Shifeng Li first upload + *
+ */ + +#ifndef DRIVER_ADS1115_INTERFACE_H +#define DRIVER_ADS1115_INTERFACE_H + +#include "driver_ads1115.h" + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @defgroup ads1115_interface_driver ads1115 interface driver function + * @brief ads1115 interface driver modules + * @ingroup ads1115_driver + * @{ + */ + +/** + * @brief interface iic bus init + * @return status code + * - 0 success + * - 1 iic init failed + * @note none + */ +uint8_t ads1115_interface_iic_init(void); + +/** + * @brief interface iic bus deinit + * @return status code + * - 0 success + * - 1 iic deinit failed + * @note none + */ +uint8_t ads1115_interface_iic_deinit(void); + +/** + * @brief interface iic bus read + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +uint8_t ads1115_interface_iic_read(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief interface iic bus write + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +uint8_t ads1115_interface_iic_write(uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief interface delay ms + * @param[in] ms + * @note none + */ +void ads1115_interface_delay_ms(uint32_t ms); + +/** + * @brief interface print format data + * @param[in] fmt is the format data + * @note none + */ +void ads1115_interface_debug_print(const char *const fmt, ...); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospi-analog/driver_pcf8591.c b/ospi-analog/driver_pcf8591.c new file mode 100644 index 00000000..d2848b9c --- /dev/null +++ b/ospi-analog/driver_pcf8591.c @@ -0,0 +1,968 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_pcf8591.c + * @brief driver pcf8591 source file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-18 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/18 2.0 Shifeng Li format the code + *
2020/10/17 1.0 Shifeng Li first upload + *
+ */ + +#include "driver_pcf8591.h" + +/** + * @brief chip information definition + */ +#define CHIP_NAME "NXP PCF8591" /**< chip name */ +#define MANUFACTURER_NAME "NXP" /**< manufacturer name */ +#define SUPPLY_VOLTAGE_MIN 2.5f /**< chip min supply voltage */ +#define SUPPLY_VOLTAGE_MAX 6.0f /**< chip max supply voltage */ +#define MAX_CURRENT 50.0f /**< chip max current */ +#define TEMPERATURE_MIN -40.0f /**< chip min operating temperature */ +#define TEMPERATURE_MAX 85.0f /**< chip max operating temperature */ +#define DRIVER_VERSION 2000 /**< driver version */ + +/** + * @brief set the address pin + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] addr_pin is the chip address pins + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_set_addr_pin(pcf8591_handle_t *handle, pcf8591_address_t addr_pin) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + + handle->iic_addr = 0x90; /* set iic addr */ + handle->iic_addr |= addr_pin << 1; /* set iic address */ + + return 0; /* success return 0 */ +} + +/** + * @brief get the address pin + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *addr_pin points to a chip address pins buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_get_addr_pin(pcf8591_handle_t *handle, pcf8591_address_t *addr_pin) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + + *addr_pin = (pcf8591_address_t)((handle->iic_addr & (~0x90)) >> 1); /*get iic address */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc channel + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] channel is the adc channel + * @return status code + * - 0 success + * - 1 set channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_channel(pcf8591_handle_t *handle, pcf8591_channel_t channel) +{ + uint8_t res; + uint8_t prev_conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + prev_conf = handle->conf; /* save conf */ + handle->conf &= ~(3 << 0); /* clear channel bits */ + handle->conf |= channel << 0; /* set channel */ + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)&handle->conf, 1); /* write command */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write command failed */ + handle->conf = prev_conf; /* recover conf */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc channel + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *channel points to an adc channel buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_channel(pcf8591_handle_t *handle, pcf8591_channel_t *channel) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *channel = (pcf8591_channel_t)(handle->conf & (3 << 0)); /* get channel */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] mode is the adc mode + * @return status code + * - 0 success + * - 1 set mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_mode(pcf8591_handle_t *handle, pcf8591_mode_t mode) +{ + uint8_t res; + uint8_t prev_conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + prev_conf = handle->conf; /* save conf */ + handle->conf &= ~(3 << 4); /* clear mode bits */ + handle->conf |= mode << 4; /* set mode */ + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)&handle->conf, 1); /* write command */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write command failed */ + handle->conf = prev_conf; /* recover conf */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *mode points to an adc mode buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_mode(pcf8591_handle_t *handle, pcf8591_mode_t *mode) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *mode = (pcf8591_mode_t)((handle->conf >> 4) & 0x03); /* get mode */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc auto increment read mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] enable is a bool value + * @return status code + * - 0 success + * - 1 set auto increment failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_auto_increment(pcf8591_handle_t *handle, pcf8591_bool_t enable) +{ + uint8_t res; + uint8_t prev_conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + prev_conf = handle->conf; /* save conf */ + handle->conf &= ~(1 << 2); /* clear auto bit */ + handle->conf |= enable << 2; /* set enable */ + if (enable != 0) /* if enable auto increment */ + { + handle->conf |= 0x40; /* set output bit */ + } + else + { + handle->conf &= ~0x40; /* clear output bit */ + } + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)&handle->conf, 1); /* write command */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write command failed */ + handle->conf = prev_conf; /* recover conf */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc auto increment read mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *enable points to a bool value buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_auto_increment(pcf8591_handle_t *handle, pcf8591_bool_t *enable) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *enable = (pcf8591_bool_t)((handle->conf >> 2) & 0x01); /* get auto */ + + return 0; /* success return 0 */ +} + +/** + * @brief set the adc reference voltage + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] ref_voltage is the adc reference voltage + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_reference_voltage(pcf8591_handle_t *handle, float ref_voltage) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + handle->ref_voltage = ref_voltage; /* set reference voltage */ + + return 0; /* success return 0 */ +} + +/** + * @brief get the adc reference voltage + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *ref_voltage points to an adc reference voltage buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_reference_voltage(pcf8591_handle_t *handle, float *ref_voltage) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *ref_voltage = (float)(handle->ref_voltage); /* get ref voltage */ + + return 0; /* success return 0 */ +} + +/** + * @brief initialize the chip + * @param[in] *handle points to a pcf8591 handle structure + * @return status code + * - 0 success + * - 1 iic initialization failed + * - 2 handle is NULL + * - 3 linked functions is NULL + * @note none + */ +uint8_t pcf8591_init(pcf8591_handle_t *handle) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->debug_print == NULL) /* check debug_print */ + { + return 3; /* return error */ + } + if (handle->iic_init == NULL) /* check iic_init */ + { + handle->debug_print("pcf8591: iic_init is null.\n"); /* iic_init is null */ + + return 3; /* return error */ + } + if (handle->iic_deinit == NULL) /* check iic_deinit */ + { + handle->debug_print("pcf8591: iic_deinit is null.\n"); /* iic_deinit is null */ + + return 3; /* return error */ + } + if (handle->iic_read_cmd == NULL) /* check iic_read_cmd */ + { + handle->debug_print("pcf8591: iic_read_cmd is null.\n"); /* iic_read_cmd is null */ + + return 3; /* return error */ + } + if (handle->iic_write_cmd == NULL) /* check iic_write_cmd */ + { + handle->debug_print("pcf8591: iic_write_cmd is null.\n"); /* iic_write_cmd is null */ + + return 3; /* return error */ + } + if (handle->delay_ms == NULL) /* check delay_ms */ + { + handle->debug_print("pcf8591: delay_ms is null.\n"); /* delay_ms is null */ + + return 3; /* return error */ + } + + if (handle->iic_init() != 0) /* iic init */ + { + handle->debug_print("pcf8591: iic init failed.\n"); /* iic init failed */ + + return 1; /* return error */ + } + handle->inited = 1; /* flag finish initialization */ + + return 0; /* success return 0 */ +} + +/** + * @brief close the chip + * @param[in] *handle points to a pcf8591 handle structure + * @return status code + * - 0 success + * - 1 iic deinit failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_deinit(pcf8591_handle_t *handle) +{ + uint8_t res; + uint8_t buf[2]; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + buf[0] = 0x00; /* set conf */ + buf[1] = 0x00; /* set 0x00 */ + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)buf, 2); /* write data */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write failed */ + + return 1; /* return error */ + } + res = handle->iic_deinit(); /* iic deinit */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: iic deinit failed.\n"); /* iic deinit failed */ + + return 1; /* return error */ + } + handle->inited = 0; /* flag closed */ + + return 0; /* success return 0 */ +} + +/** + * @brief write to the dac + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] data is the dac value + * @return status code + * - 0 success + * - 1 write dac value failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_write(pcf8591_handle_t *handle, uint8_t data) +{ + uint8_t res; + uint8_t buf[2]; + uint8_t conf; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + conf = handle->conf | 0x40; /* set output */ + buf[0] = conf; /* set conf */ + buf[1] = data; /* set data */ + res = handle->iic_write_cmd(handle->iic_addr, (uint8_t *)buf, 2); /* write data */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: write command failed.\n"); /* write failed */ + + return 1; /* return error */ + } + + return 0; /* success return 0 */ +} + +/** + * @brief convert a dac value to a register raw data + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] dac is a converted dac value + * @param[out] *reg points to a register raw buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_dac_convert_to_register(pcf8591_handle_t *handle, float dac, uint8_t *reg) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + *reg = (uint8_t)(dac / handle->ref_voltage * 256.0f); /* convert to register */ + + return 0; /* success return 0 */ +} + +/** + * @brief read data from the chip + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *adc points to a converted adc buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_read(pcf8591_handle_t *handle, int16_t *raw, float *adc) +{ + uint8_t res; + uint8_t mode; + uint8_t channel; + uint8_t u_data; + int8_t s_data; + uint8_t buf[1]; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + if ((handle->conf & (1 << 2)) != 0) /* check mode */ + { + handle->debug_print("pcf8591: can't use this function.\n"); /* channel is invalid */ + + return 1; + } + mode = (handle->conf >> 4) & 0x03; /* check mode */ + channel = handle->conf & 0x03; /* check channel */ + if ((mode == 1) || (mode == 2)) /* if mode 1 mode 2 */ + { + if (channel > 2) /* check channel */ + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + + return 1; /* return error */ + } + } + if (mode == 3) /* if mode 3 */ + { + if (channel > 1) /* check channel */ + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + + return 1; /* return error */ + } + } + res = handle->iic_read_cmd(handle->iic_addr, (uint8_t *)buf, 1); /* read data */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: read command failed.\n"); /* read failed */ + + return 1; /* return error */ + } + switch (mode) /* mode param */ + { + case 0 : /* mode 0 */ + { + if (channel == 0) /* channel 0 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 1) /* channel 1 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 2) /* channel 2 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else /* channel 3 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + + break; /* break */ + } + case 1 : /* mode 1 */ + { + if (channel == 0) /* channel 0 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 1) /* channel 1 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 2) /* channel */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + res = 1; /* failed */ + } + + break; /* break */ + } + case 2 : /* mode 2 */ + { + if (channel == 0) /* channel 0 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 1) /* channel 1 */ + { + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 2) /* channel 2 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + res = 1; /* failed */ + } + + break; /* break */ + } + case 3 : /* mode 3 */ + { + if (channel == 0) /* channel 0 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else if (channel == 1) /* channel 1 */ + { + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + res = 0; /* successful */ + } + else + { + handle->debug_print("pcf8591: channel is invalid.\n"); /* channel is invalid */ + res = 1; /* failed */ + } + + break; /* break */ + } + default : + { + handle->debug_print("pcf8591: mode is invalid.\n"); /* mode is invalid */ + res = 1; /* failed */ + + break; /* break */ + } + } + + return res; /* return the result */ +} + +/** + * @brief read the multiple channel data from the chip + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *adc points to a converted adc buffer + * @param[in,out] *len points to an adc length buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note len means max length of raw and adc input buffer and return the read length of raw and adc + */ +uint8_t pcf8591_multiple_read(pcf8591_handle_t *handle, int16_t *raw, float *adc, uint8_t *len) +{ + uint8_t res; + uint8_t mode; + uint8_t i, j; + uint8_t u_data; + int8_t s_data; + uint8_t buf[4]; + + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + if ((handle->conf & (1 << 2)) == 0) /* check mode */ + { + handle->debug_print("pcf8591: can't use this function.\n"); /* channel is invalid */ + + return 1; /* return error */ + } + mode = (handle->conf >> 4) & 0x03; /* check mode */ + if (mode == 0) /* mode 0 */ + { + i = 4; /* 4 */ + i = i<(*len) ? i : (*len); /* get min length */ + } + else if (mode == 1) /* mode 1 */ + { + i = 3; /* 3 */ + i = i<(*len) ? i : (*len); /* get min length */ + } + else if (mode == 2) /* mode 2 */ + { + i = 3; /* 3 */ + i = i<(*len) ? i : (*len); + } /* get min length */ + else /* mode 3 */ + { + i = 2; /* 2 */ + i = i<(*len) ? i : (*len); /* get min length */ + } + + memset(buf, 0, sizeof(uint8_t) * 4); /* clear the buffer */ + for (j = 0; j < i; j++) + { + res = handle->iic_read_cmd(handle->iic_addr, (uint8_t *)&buf[j], 1); /* read data */ + if (res != 0) /* check error */ + { + handle->debug_print("pcf8591: read command failed.\n"); /* read failed */ + + return 1; /* return error */ + } + handle->delay_ms(5); /* delay 5 ms */ + } + switch (mode) /* mode param */ + { + case 0 : /* mode 0 */ + { + u_data = buf[1]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + u_data = buf[2]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + u_data = buf[3]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + u_data = buf[0]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + *len = i; /* set length */ + res = 0; /* successful */ + + break; /* break */ + } + case 1 : /* mode 1 */ + { + s_data = (int8_t)(buf[1]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + s_data = (int8_t)(buf[2]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + *len = i; /* set length */ + res = 0; /* successful */ + + break; /* break */ + } + case 2 : /* mode 2 */ + { + u_data = buf[1]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + u_data = buf[2]; /* get data */ + *raw = (int16_t)(u_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + *len = i; /* set length */ + res = 0; /* successful */ + + break; /* break */ + } + case 3 : /* mode 3 */ + { + s_data = (int8_t)(buf[1]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + raw++; /* raw address add */ + adc++; /* adc address add */ + s_data = (int8_t)(buf[0]); /* get data */ + *raw = (int16_t)(s_data); /* get raw */ + *adc = (float)(*raw) / 256.0f * handle->ref_voltage; /* convert to read data */ + *len = i; /* set length */ + res = 0; /* successful */ + + break; /* break */ + } + default : + { + handle->debug_print("pcf8591: mode is invalid.\n"); /* mode is invalid */ + res = 1; /* failed */ + + break; /* break */ + } + } + + + return res; /* return the result */ +} + +/** + * @brief set the chip register + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] *buf points to a data buffer. + * @param[in] len is the data buffer + * @return status code + * - 0 success + * - 1 write failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_reg(pcf8591_handle_t *handle, uint8_t *buf, uint16_t len) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + return handle->iic_write_cmd(handle->iic_addr, buf, len); /* write command */ +} + +/** + * @brief get the chip register + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *buf points to a data buffer. + * @param[in] len is the data buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_reg(pcf8591_handle_t *handle, uint8_t *buf, uint16_t len) +{ + if (handle == NULL) /* check handle */ + { + return 2; /* return error */ + } + if (handle->inited != 1) /* check handle initialization */ + { + return 3; /* return error */ + } + + return handle->iic_read_cmd(handle->iic_addr, buf, len); /* read command */ +} + +/** + * @brief get chip's information + * @param[out] *info points to a pcf8591 info structure + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_info(pcf8591_info_t *info) +{ + if (info == NULL) /* check handle */ + { + return 2; /* return error */ + } + + memset(info, 0, sizeof(pcf8591_info_t)); /* initialize pcf8591 info structure */ + strncpy(info->chip_name, CHIP_NAME, 32); /* copy chip name */ + strncpy(info->manufacturer_name, MANUFACTURER_NAME, 32); /* copy manufacturer name */ + strncpy(info->interface, "IIC", 8); /* copy interface name */ + info->supply_voltage_min_v = SUPPLY_VOLTAGE_MIN; /* set minimal supply voltage */ + info->supply_voltage_max_v = SUPPLY_VOLTAGE_MAX; /* set maximum supply voltage */ + info->max_current_ma = MAX_CURRENT; /* set maximum current */ + info->temperature_max = TEMPERATURE_MAX; /* set minimal temperature */ + info->temperature_min = TEMPERATURE_MIN; /* set maximum temperature */ + info->driver_version = DRIVER_VERSION; /* set driver version */ + + return 0; /* success return 0 */ +} diff --git a/ospi-analog/driver_pcf8591.h b/ospi-analog/driver_pcf8591.h new file mode 100644 index 00000000..442a9b62 --- /dev/null +++ b/ospi-analog/driver_pcf8591.h @@ -0,0 +1,478 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_pcf8591.h + * @brief driver pcf8591 header file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-18 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/18 2.0 Shifeng Li format the code + *
2020/10/17 1.0 Shifeng Li first upload + *
+ */ + +#ifndef DRIVER_PCF8591_H +#define DRIVER_PCF8591_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @defgroup pcf8591_driver pcf8591 driver function + * @brief pcf8591 driver modules + * @{ + */ + +/** + * @addtogroup pcf8591_base_driver + * @{ + */ + +/** + * @brief pcf8591 address enumeration definition + */ +typedef enum +{ + PCF8591_ADDRESS_A000 = 0, /**< A2A1A0 000 */ + PCF8591_ADDRESS_A001 = 1, /**< A2A1A0 001 */ + PCF8591_ADDRESS_A010 = 2, /**< A2A1A0 010 */ + PCF8591_ADDRESS_A011 = 3, /**< A2A1A0 011 */ + PCF8591_ADDRESS_A100 = 4, /**< A2A1A0 100 */ + PCF8591_ADDRESS_A101 = 5, /**< A2A1A0 101 */ + PCF8591_ADDRESS_A110 = 6, /**< A2A1A0 110 */ + PCF8591_ADDRESS_A111 = 7, /**< A2A1A0 111 */ +} pcf8591_address_t; + +/** + * @brief pcf8591 bool enumeration definition + */ +typedef enum +{ + PCF8591_BOOL_FALSE = 0x00, /**< disable function */ + PCF8591_BOOL_TRUE = 0x01, /**< enable function */ +} pcf8591_bool_t; + +/** + * @brief pcf8591 channel definition + */ +typedef enum +{ + PCF8591_CHANNEL_0 = 0x00, /**< channel 0 */ + PCF8591_CHANNEL_1 = 0x01, /**< channel 1 */ + PCF8591_CHANNEL_2 = 0x02, /**< channel 2 */ + PCF8591_CHANNEL_3 = 0x03, /**< channel 3 */ +} pcf8591_channel_t; + +/** + * @brief pcf8591 mode definition + */ +typedef enum +{ + PCF8591_MODE_AIN0123_GND = 0x00, /**< AIN0-GND AIN1-GND AIN2-GND AIN3-GND */ + PCF8591_MODE_AIN012_AIN3 = 0x01, /**< AIN0-AIN3 AIN1-AIN3 AIN2-AIN3 */ + PCF8591_MODE_AIN0_GND_AND_AIN1_GND_AND_AIN2_AIN3 = 0x02, /**< AIN0-GND AIN1-GND AIN2-AIN3 */ + PCF8591_MODE_AIN0_AIN1_AND_ANI2_AIN3 = 0x03, /**< AIN0-AIN1 AIN2-AIN3 */ +} pcf8591_mode_t; + +/** + * @brief pcf8591 handle structure definition + */ +typedef struct pcf8591_handle_s +{ + uint8_t iic_addr; /**< iic device address */ + uint8_t (*iic_init)(void); /**< point to an iic_init function address */ + uint8_t (*iic_deinit)(void); /**< point to an iic_deinit function address */ + uint8_t (*iic_read_cmd)(uint8_t addr, uint8_t *buf, uint16_t len); /**< point to an iic_read_cmd function address */ + uint8_t (*iic_write_cmd)(uint8_t addr, uint8_t *buf, uint16_t len); /**< point to an iic_write_cmd function address */ + void (*delay_ms)(uint32_t ms); /**< point to a delay_ms function address */ + void (*debug_print)(const char *const fmt, ...); /**< point to a debug_print function address */ + uint8_t inited; /**< inited flag */ + uint8_t conf; /**< chip conf */ + float ref_voltage; /**< chip reference voltage */ +} pcf8591_handle_t; + +/** + * @brief pcf8591 information structure definition + */ +typedef struct pcf8591_info_s +{ + char chip_name[32]; /**< chip name */ + char manufacturer_name[32]; /**< manufacturer name */ + char interface[8]; /**< chip interface name */ + float supply_voltage_min_v; /**< chip min supply voltage */ + float supply_voltage_max_v; /**< chip max supply voltage */ + float max_current_ma; /**< chip max current */ + float temperature_min; /**< chip min operating temperature */ + float temperature_max; /**< chip max operating temperature */ + uint32_t driver_version; /**< driver version */ +} pcf8591_info_t; + +/** + * @} + */ + +/** + * @defgroup pcf8591_link_driver pcf8591 link driver function + * @brief pcf8591 link driver modules + * @ingroup pcf8591_driver + * @{ + */ + +/** + * @brief initialize pcf8591_handle_t structure + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] STRUCTURE is pcf8591_handle_t + * @note none + */ +#define DRIVER_PCF8591_LINK_INIT(HANDLE, STRUCTURE) memset(HANDLE, 0, sizeof(STRUCTURE)) + +/** + * @brief link iic_init function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to an iic_init function address + * @note none + */ +#define DRIVER_PCF8591_LINK_IIC_INIT(HANDLE, FUC) (HANDLE)->iic_init = FUC + +/** + * @brief link iic_deinit function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to an iic_deinit function address + * @note none + */ +#define DRIVER_PCF8591_LINK_IIC_DEINIT(HANDLE, FUC) (HANDLE)->iic_deinit = FUC + +/** + * @brief link iic_read_cmd function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to an iic_read_cmd function address + * @note none + */ +#define DRIVER_PCF8591_LINK_IIC_READ_COMMAND(HANDLE, FUC) (HANDLE)->iic_read_cmd = FUC + +/** + * @brief link iic_write_cmd function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to an iic_write_cmd function address + * @note none + */ +#define DRIVER_PCF8591_LINK_IIC_WRITE_COMMAND(HANDLE, FUC) (HANDLE)->iic_write_cmd = FUC + +/** + * @brief link delay_ms function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to a delay_ms function address + * @note none + */ +#define DRIVER_PCF8591_LINK_DELAY_MS(HANDLE, FUC) (HANDLE)->delay_ms = FUC + +/** + * @brief link debug_print function + * @param[in] HANDLE points to a pcf8591 handle structure + * @param[in] FUC points to a debug_print function address + * @note none + */ +#define DRIVER_PCF8591_LINK_DEBUG_PRINT(HANDLE, FUC) (HANDLE)->debug_print = FUC + +/** + * @} + */ + +/** + * @defgroup pcf8591_base_driver pcf8591 base driver function + * @brief pcf8591 base driver modules + * @ingroup pcf8591_driver + * @{ + */ + +/** + * @brief get chip's information + * @param[out] *info points to a pcf8591 info structure + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_info(pcf8591_info_t *info); + +/** + * @brief set the address pin + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] addr_pin is the chip address pins + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_set_addr_pin(pcf8591_handle_t *handle, pcf8591_address_t addr_pin); + +/** + * @brief get the address pin + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *addr_pin points to a chip address pins buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * @note none + */ +uint8_t pcf8591_get_addr_pin(pcf8591_handle_t *handle, pcf8591_address_t *addr_pin); + +/** + * @brief initialize the chip + * @param[in] *handle points to a pcf8591 handle structure + * @return status code + * - 0 success + * - 1 iic initialization failed + * - 2 handle is NULL + * - 3 linked functions is NULL + * @note none + */ +uint8_t pcf8591_init(pcf8591_handle_t *handle); + +/** + * @brief close the chip + * @param[in] *handle points to a pcf8591 handle structure + * @return status code + * - 0 success + * - 1 iic deinit failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_deinit(pcf8591_handle_t *handle); + +/** + * @brief read data from the chip + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *adc points to a converted adc buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_read(pcf8591_handle_t *handle, int16_t *raw, float *adc); + +/** + * @brief read the multiple channel data from the chip + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *raw points to a raw adc buffer + * @param[out] *adc points to a converted adc buffer + * @param[in,out] *len points to an adc length buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note len means max length of raw and adc input buffer and return the read length of raw and adc + */ +uint8_t pcf8591_multiple_read(pcf8591_handle_t *handle, int16_t *raw, float *adc, uint8_t *len); + +/** + * @brief write to the dac + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] data is the dac value + * @return status code + * - 0 success + * - 1 write dac value failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_write(pcf8591_handle_t *handle, uint8_t data); + +/** + * @brief convert a dac value to a register raw data + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] dac is a converted dac value + * @param[out] *reg points to a register raw buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_dac_convert_to_register(pcf8591_handle_t *handle, float dac, uint8_t *reg); + +/** + * @brief set the adc reference voltage + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] ref_voltage is the adc reference voltage + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_reference_voltage(pcf8591_handle_t *handle, float ref_voltage); + +/** + * @brief get the adc reference voltage + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *ref_voltage points to an adc reference voltage buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_reference_voltage(pcf8591_handle_t *handle, float *ref_voltage); + +/** + * @brief set the adc channel + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] channel is the adc channel + * @return status code + * - 0 success + * - 1 set channel failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_channel(pcf8591_handle_t *handle, pcf8591_channel_t channel); + +/** + * @brief get the adc channel + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *channel points to an adc channel buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_channel(pcf8591_handle_t *handle, pcf8591_channel_t *channel); + +/** + * @brief set the adc mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] mode is the adc mode + * @return status code + * - 0 success + * - 1 set mode failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_mode(pcf8591_handle_t *handle, pcf8591_mode_t mode); + +/** + * @brief get the adc mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *mode points to an adc mode buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_mode(pcf8591_handle_t *handle, pcf8591_mode_t *mode); + +/** + * @brief set the adc auto increment read mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] enable is a bool value + * @return status code + * - 0 success + * - 1 set auto increment failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_auto_increment(pcf8591_handle_t *handle, pcf8591_bool_t enable); + +/** + * @brief get the adc auto increment read mode + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *enable points to a bool value buffer + * @return status code + * - 0 success + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_auto_increment(pcf8591_handle_t *handle, pcf8591_bool_t *enable); + +/** + * @} + */ + +/** + * @defgroup pcf8591_extern_driver pcf8591 extern driver function + * @brief pcf8591 extern driver modules + * @ingroup pcf8591_driver + * @{ + */ + +/** + * @brief set the chip register + * @param[in] *handle points to a pcf8591 handle structure + * @param[in] *buf points to a data buffer. + * @param[in] len is the data buffer + * @return status code + * - 0 success + * - 1 write failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_set_reg(pcf8591_handle_t *handle, uint8_t *buf, uint16_t len); + +/** + * @brief get the chip register + * @param[in] *handle points to a pcf8591 handle structure + * @param[out] *buf points to a data buffer. + * @param[in] len is the data buffer + * @return status code + * - 0 success + * - 1 read failed + * - 2 handle is NULL + * - 3 handle is not initialized + * @note none + */ +uint8_t pcf8591_get_reg(pcf8591_handle_t *handle, uint8_t *buf, uint16_t len); + +/** + * @} + */ + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospi-analog/driver_pcf8591_interface.c b/ospi-analog/driver_pcf8591_interface.c new file mode 100644 index 00000000..da286161 --- /dev/null +++ b/ospi-analog/driver_pcf8591_interface.c @@ -0,0 +1,136 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file raspberrypi4b_driver_pcf8591_interface.c + * @brief raspberrypi4b driver pcf8591 interface source file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-18 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/18 2.0 Shifeng Li format the code + *
2020/10/17 1.0 Shifeng Li first upload + *
+ */ + +#include "driver_pcf8591_interface.h" +#include "iic.h" +#include + +/** + * @brief iic device name definition + */ +static const char* IIC_DEVICE_NAME = "/dev/i2c-1"; /**< iic device name */ + +/** + * @brief iic device handle definition + */ +static int gs_fd; /**< iic handle */ + +/** + * @brief interface iic bus init + * @return status code + * - 0 success + * - 1 iic init failed + * @note none + */ +uint8_t pcf8591_interface_iic_init(void) +{ + return iic_init(IIC_DEVICE_NAME, &gs_fd); +} + +/** + * @brief interface iic bus deinit + * @return status code + * - 0 success + * - 1 iic deinit failed + * @note none + */ +uint8_t pcf8591_interface_iic_deinit(void) +{ + return iic_deinit(gs_fd); +} + +/** + * @brief interface iic bus write command + * @param[in] addr is the iic device write address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +uint8_t pcf8591_interface_iic_write_cmd(uint8_t addr, uint8_t *buf, uint16_t len) +{ + return iic_write_cmd(gs_fd, addr, buf, len); +} + +/** + * @brief interface iic bus read command + * @param[in] addr is the iic device write address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +uint8_t pcf8591_interface_iic_read_cmd(uint8_t addr, uint8_t *buf, uint16_t len) +{ + return iic_read_cmd(gs_fd, addr, buf, len); +} + +/** + * @brief interface delay ms + * @param[in] ms + * @note none + */ +void pcf8591_interface_delay_ms(uint32_t ms) +{ + usleep(1000 * ms); +} + +/** + * @brief interface print format data + * @param[in] fmt is the format data + * @note none + */ +void pcf8591_interface_debug_print(const char *const fmt, ...) +{ + /** + char str[256]; + uint16_t len; + va_list args; + + memset((char *)str, 0, sizeof(char) * 256); + va_start(args, fmt); + vsnprintf((char *)str, 255, (char const *)fmt, args); + va_end(args); + + len = strlen((char *)str); + printf((uint8_t *)str, len); + **/ +} diff --git a/ospi-analog/driver_pcf8591_interface.h b/ospi-analog/driver_pcf8591_interface.h new file mode 100644 index 00000000..b1489757 --- /dev/null +++ b/ospi-analog/driver_pcf8591_interface.h @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file driver_pcf8591_interface.h + * @brief driver pcf8591 interface header file + * @version 2.0.0 + * @author Shifeng Li + * @date 2021-02-18 + * + *

history

+ * + *
Date Version Author Description + *
2021/02/18 2.0 Shifeng Li format the code + *
2020/10/17 1.0 Shifeng Li first upload + *
+ */ + +#ifndef DRIVER_PCF8591_INTERFACE_H +#define DRIVER_PCF8591_INTERFACE_H + +#include "driver_pcf8591.h" + +#ifdef __cplusplus +extern "C"{ +#endif + +/** + * @defgroup pcf8591_interface_driver pcf8591 interface driver function + * @brief pcf8591 interface driver modules + * @ingroup pcf8591_driver + * @{ + */ + +/** + * @brief interface iic bus init + * @return status code + * - 0 success + * - 1 iic init failed + * @note none + */ +uint8_t pcf8591_interface_iic_init(void); + +/** + * @brief interface iic bus deinit + * @return status code + * - 0 success + * - 1 iic deinit failed + * @note none + */ +uint8_t pcf8591_interface_iic_deinit(void); + +/** + * @brief interface iic bus write command + * @param[in] addr is the iic device write address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note none + */ +uint8_t pcf8591_interface_iic_write_cmd(uint8_t addr, uint8_t *buf, uint16_t len); + +/** + * @brief interface iic bus read command + * @param[in] addr is the iic device write address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note none + */ +uint8_t pcf8591_interface_iic_read_cmd(uint8_t addr, uint8_t *buf, uint16_t len); + +/** + * @brief interface delay ms + * @param[in] ms + * @note none + */ +void pcf8591_interface_delay_ms(uint32_t ms); + +/** + * @brief interface print format data + * @param[in] fmt is the format data + * @note none + */ +void pcf8591_interface_debug_print(const char *const fmt, ...); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/ospi-analog/iic.c b/ospi-analog/iic.c new file mode 100644 index 00000000..b32bbedc --- /dev/null +++ b/ospi-analog/iic.c @@ -0,0 +1,368 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file iic.c + * @brief iic source file + * @version 1.0.0 + * @author Shifeng Li + * @date 2022-11-11 + * + *

history

+ * + *
Date Version Author Description + *
2022/11/11 1.0 Shifeng Li first upload + *
+ */ + +#ifndef ARDUINO +#include "iic.h" +#include +#include +#include +#include + +/** + * @brief iic bus init + * @param[in] *name points to an iic device name buffer + * @param[out] *fd points to an iic device handle buffer + * @return status code + * - 0 success + * - 1 init failed + * @note none + */ +uint8_t iic_init(const char *name, int *fd) +{ + /* open the device */ + *fd = open(name, O_RDWR); + + /* check the fd */ + if ((*fd) < 0) + { + perror("iic: open failed.\n"); + + return 1; + } + else + { + return 0; + } +} + +/** + * @brief iic bus deinit + * @param[in] fd is the iic handle + * @return status code + * - 0 success + * - 1 deinit failed + * @note none + */ +uint8_t iic_deinit(int fd) +{ + /* close the device */ + if (close(fd) < 0) + { + perror("iic: close failed.\n"); + + return 1; + } + else + { + return 0; + } +} + +/** + * @brief iic bus read command + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read_cmd(int fd, uint8_t addr, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[1]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 1); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = I2C_M_RD; + msgs[0].buf = buf; + msgs[0].len = len; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 1; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: read failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus read + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read(int fd, uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[2]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 2); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + msgs[0].buf = ® + msgs[0].len = 1; + msgs[1].addr = addr >> 1; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = buf; + msgs[1].len = len; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 2; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: read failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus read with 16 bits register address + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[2]; + uint8_t addr_buf[2]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 2); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + addr_buf[0] = (reg >> 8) & 0xFF; + addr_buf[1] = (reg >> 0) & 0xFF; + msgs[0].buf = addr_buf; + msgs[0].len = 2; + msgs[1].addr = addr >> 1; + msgs[1].flags = I2C_M_RD; + msgs[1].buf = buf; + msgs[1].len = len; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 2; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: read failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus write command + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write_cmd(int fd, uint8_t addr, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[1]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 1); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + msgs[0].buf = buf; + msgs[0].len = len; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 1; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: write failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus write + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write(int fd, uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[1]; + uint8_t buf_send[len + 1]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 1); + + /* clear sent buf */ + memset(buf_send, 0, sizeof(uint8_t) * (len + 1)); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + buf_send[0] = reg; + memcpy(&buf_send[1], buf, len); + msgs[0].buf = buf_send; + msgs[0].len = len + 1; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 1; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: write failed.\n"); + + return 1; + } + + return 0; +} + +/** + * @brief iic bus write with 16 bits register address + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, uint16_t len) +{ + struct i2c_rdwr_ioctl_data i2c_rdwr_data; + struct i2c_msg msgs[1]; + uint8_t buf_send[len + 2]; + + /* clear ioctl data */ + memset(&i2c_rdwr_data, 0, sizeof(struct i2c_rdwr_ioctl_data)); + + /* clear msgs data */ + memset(msgs, 0, sizeof(struct i2c_msg) * 1); + + /* clear sent buf */ + memset(buf_send, 0, sizeof(uint8_t) * (len + 2)); + + /* set the param */ + msgs[0].addr = addr >> 1; + msgs[0].flags = 0; + buf_send[0] = (reg >> 8) & 0xFF; + buf_send[1] = (reg >> 0) & 0xFF; + memcpy(&buf_send[2], buf, len); + msgs[0].buf = buf_send; + msgs[0].len = len + 2; + i2c_rdwr_data.msgs = msgs; + i2c_rdwr_data.nmsgs = 1; + + /* transmit */ + if (ioctl(fd, I2C_RDWR, &i2c_rdwr_data) < 0) + { + perror("iic: write failed.\n"); + + return 1; + } + + return 0; +} + +#endif \ No newline at end of file diff --git a/ospi-analog/iic.h b/ospi-analog/iic.h new file mode 100644 index 00000000..98f0939f --- /dev/null +++ b/ospi-analog/iic.h @@ -0,0 +1,170 @@ +/** + * Copyright (c) 2015 - present LibDriver All rights reserved + * + * The MIT License (MIT) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * @file iic.h + * @brief iic header file + * @version 1.0.0 + * @author Shifeng Li + * @date 2022-11-11 + * + *

history

+ * + *
Date Version Author Description + *
2022/11/11 1.0 Shifeng Li first upload + *
+ */ + +#ifndef IIC_H +#define IIC_H + +#ifndef ARDUINO + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup iic iic function + * @brief iic function modules + * @{ + */ + +/** + * @brief iic bus init + * @param[in] *name points to an iic device name buffer + * @param[out] *fd points to an iic device handle buffer + * @return status code + * - 0 success + * - 1 init failed + * @note none + */ +uint8_t iic_init(const char *name, int *fd); + +/** + * @brief iic bus deinit + * @param[in] fd is the iic handle + * @return status code + * - 0 success + * - 1 deinit failed + * @note none + */ +uint8_t iic_deinit(int fd); + +/** + * @brief iic bus read command + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read_cmd(int fd, uint8_t addr, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus read + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read(int fd, uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus read with 16 bits register address + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[out] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 read failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_read_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus write command + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write_cmd(int fd, uint8_t addr, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus write + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write(int fd, uint8_t addr, uint8_t reg, uint8_t *buf, uint16_t len); + +/** + * @brief iic bus write with 16 bits register address + * @param[in] fd is the iic handle + * @param[in] addr is the iic device write address + * @param[in] reg is the iic register address + * @param[in] *buf points to a data buffer + * @param[in] len is the length of the data buffer + * @return status code + * - 0 success + * - 1 write failed + * @note addr = device_address_7bits << 1 + */ +uint8_t iic_write_address16(int fd, uint8_t addr, uint16_t reg, uint8_t *buf, uint16_t len); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif +#endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index cdd227a0..6bc3df90 100644 --- a/platformio.ini +++ b/platformio.ini @@ -16,11 +16,15 @@ include_dir = . platform = espressif8266@4.2.1 board = d1_mini framework = arduino +;lib_ldf_mode = chain lib_ldf_mode = deep lib_deps = https://github.com/ThingPulse/esp8266-oled-ssd1306/archive/4.2.0.zip knolleary/PubSubClient @ ^2.8 - https://github.com/OpenThingsIO/OpenThings-Framework-Firmware-Library @ ^0.2.0 + https://github.com/OpenSprinklerShop/OpenThings-Framework-Firmware-Library + RobTillaart/ADS1X15 + https://github.com/bluemurder/esp8266-ping + https://github.com/tobiasschuerg/InfluxDB-Client-for-Arduino/archive/3.13.2.zip ; ignore html2raw.cpp source file for firmware compilation (external helper program) build_src_filter = +<*> - -- upload_speed = 460800 @@ -41,7 +45,7 @@ lib_ldf_mode = deep lib_deps = https://github.com/UIPEthernet/UIPEthernet/archive/refs/tags/v2.0.12.zip knolleary/PubSubClient @ ^2.8 - https://github.com/greiman/SdFat/archive/refs/tags/1.0.7.zip + greiman/SdFat @ 1.0.7 Wire build_src_filter = +<*> - -- monitor_speed=115200 diff --git a/program.cpp b/program.cpp index 73fff7d1..496bdc61 100644 --- a/program.cpp +++ b/program.cpp @@ -231,14 +231,12 @@ unsigned char ProgramStruct::check_day_match(time_os_t t) { unsigned char weekday_t = weekday(t); // weekday ranges from [0,6] within Sunday being 1 unsigned char day_t = day(t); unsigned char month_t = month(t); - unsigned char year_t = year(t); #else // get current time from RPI/LINUX time_os_t ct = t; struct tm *ti = gmtime(&ct); unsigned char weekday_t = (ti->tm_wday+1)%7; // tm_wday ranges from [0,6] with Sunday being 0 unsigned char day_t = ti->tm_mday; unsigned char month_t = ti->tm_mon+1; // tm_mon ranges from [0,11] - unsigned char year_t = ti->tm_year+1900; // tm_year is years since 1900 #endif // get current time int epoch_t = (t / 86400); @@ -272,14 +270,8 @@ unsigned char ProgramStruct::check_day_match(time_os_t t) { case PROGRAM_TYPE_MONTHLY: if ((days[0]&0b11111) == 0) { - if(month_t == 2){ - if((isLeapYear(year_t) && dt != 29) || (!isLeapYear(year_t) && dt != 28)){ - return 0; - } - }else{ - if(!isLastDayofMonth(month_t, dt)) - return 0; - } + if(!isLastDayofMonth(month_t, dt)) + return 0; } else if (dt != (days[0]&0b11111)){ return 0; } @@ -428,12 +420,11 @@ void ProgramStruct::gen_station_runorder(uint16_t runcount, unsigned char *order case 'I': // descending by index case 'a': // alternating: odd-numbered runs ascending by index, even-numbered runs descending. case 'A': // odd-numbered runs descending by index, even-numbered runs ascending - { - if((anno=='I') || ((anno=='a') && (runcount%2==0)) || ((anno=='A') && (runcount%2==1))) { - // reverse the order - for(i=0;i. + */ +#include "sensor_mqtt.h" +#include "sensors.h" +#include "mqtt.h" +#include "OpenSprinkler.h" + +extern OpenSprinkler os; + +void sensor_mqtt_init() { + os.mqtt.setCallback(2, sensor_mqtt_callback); +} +/** + * @brief + * + * @param mtopic reported topic opensprinkler/analogsensor/name + * @param pattern topic pattern opensprinkler/ or opensprinkler/# or opensprinkler/#/abc/# + * @return true + * @return false + */ +bool mqtt_filter_matches(char* mtopic, char* pattern) { + + while (pattern && mtopic) { + char ch1 = *mtopic++; + char ch2 = *pattern++; + if (ch2 == '+') { //level ok up to "/" + while (mtopic[0]) { + if (ch1 == '/') + break; + ch1 = *mtopic++; + } + } else if (ch2 == '#') { //multilevel + char *p = strpbrk(pattern, "#+"); + if (!p) return true; + if (strncmp(pattern, mtopic, p-pattern) ==0) { + mtopic = mtopic + (p-pattern); + pattern = p; + } + } + if (ch1 != ch2) + return false; + else if (ch1 == 0 && ch2 == 0) + return true; + else if (ch1 == 0 || ch2 == 0) + return false; + } + return true; +} + +char *strnlstr(const char *haystack, const char *needle, size_t needle_len, size_t len) +{ + int i; + for (i=0; i<=(int)(len-needle_len); i++) + { + if (haystack[0] == 0) + break; + if ((haystack[0] == needle[0]) && + (strncmp(haystack, needle, needle_len) == 0)) + return (char *)haystack; + haystack++; + } + return NULL; +} + +/** + * @brief mqtt callback + * + */ +#if defined(ARDUINO) +void sensor_mqtt_callback(char* mtopic, byte* payload, unsigned int length) { +#else +static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg) { + DEBUG_PRINTLN("sensor_mqtt_callback0"); + char* mtopic = msg->topic; + byte* payload = (byte*)msg->payload; + unsigned int length = msg->payloadlen; +#endif + + DEBUG_PRINTLN("sensor_mqtt_callback1"); + + if (!mtopic || !payload) return; + time_t now = os.now_tz(); + Sensor_t *sensor = getSensors(); + while (sensor) { + if (sensor->type == SENSOR_MQTT && sensor->last_read != now) { + char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); + DEBUG_PRINT("mtopic: "); DEBUG_PRINTLN(mtopic); + DEBUG_PRINT("topic: "); DEBUG_PRINTLN(topic); + + if (topic && mqtt_filter_matches(mtopic, topic)) { + DEBUG_PRINTLN("topic match!"); + char *jsonFilter = SensorUrl_get(sensor->nr, SENSORURL_TYPE_FILTER); + char *p = (char*)payload; + char *f = jsonFilter; + bool emptyFilter = !jsonFilter||!jsonFilter[0]; + + while (!emptyFilter && f && p) { + f = strstr(jsonFilter, "|"); + if (f) { + p = strnlstr(p, jsonFilter, f-jsonFilter, (char*)payload-p+length); + jsonFilter = f+1; + } else { + p = strstr(p, jsonFilter); + } + } + if (p) { + p += emptyFilter?0:strlen(jsonFilter); + char buf[30]; + p = strpbrk(p, "0123456789.-+nullNULL"); + uint i = 0; + while (p && i < sizeof(buf) && p < (char*)payload+length) { + char ch = *p++; + if ((ch >= '0' && ch <= '9') || ch == '.' || ch == '-' || ch == '+') { + buf[i++] = ch; + } else break; + } + buf[i] = 0; + DEBUG_PRINT("result: "); + DEBUG_PRINTLN(buf); + + double value = -9999; + int ok = sscanf(buf, "%lf", &value); + if (ok && value >= -10000 && value <= 10000 && (value != sensor->last_data || !sensor->flags.data_ok || now-sensor->last_read > 6000)) { + sensor->last_data = value; + sensor->flags.data_ok = true; + sensor->last_read = now; + sensor->mqtt_push = true; + sensor->repeat_read = 1; //This will call read_sensor_mqtt + DEBUG_PRINTLN("sensor_mqtt_callback2"); + } + } + } + } + sensor = sensor->next; + } + DEBUG_PRINTLN("sensor_mqtt_callback3"); +} + +int read_sensor_mqtt(Sensor_t *sensor) { + if (!os.mqtt.enabled() || !os.mqtt.connected()) { + sensor->flags.data_ok = false; + sensor->mqtt_init = false; + } else if (sensor->mqtt_push) { + DEBUG_PRINTLN("read_sensor_mqtt: push data"); + sensor->mqtt_push = false; + sensor->repeat_read = 0; + return HTTP_RQT_SUCCESS; //Adds also data to the log + push data + } else { + sensor->repeat_read = 0; + DEBUG_PRINT("read_sensor_mqtt1: "); + DEBUG_PRINTLN(sensor->name); + char *topic = SensorUrl_get(sensor->nr, SENSORURL_TYPE_TOPIC); + if (topic && topic[0]) { + DEBUG_PRINT("subscribe: "); + DEBUG_PRINTLN(topic); + os.mqtt.subscribe(topic); + sensor->mqtt_init = true; + } + } + return HTTP_RQT_NOT_RECEIVED; +} + +void sensor_mqtt_subscribe(uint nr, uint type, const char *urlstr) { + Sensor_t* sensor = sensor_by_nr(nr); + if (urlstr && urlstr[0] && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { + DEBUG_PRINT("sensor_mqtt_subscribe1: "); + DEBUG_PRINTLN(sensor->name); + DEBUG_PRINT("subscribe: "); + DEBUG_PRINTLN(urlstr); + if (!os.mqtt.subscribe(urlstr)) + DEBUG_PRINTLN("error subscribe!!"); + sensor->mqtt_init = true; + } +} + +void sensor_mqtt_unsubscribe(uint nr, uint type, const char *urlstr) { + Sensor_t* sensor = sensor_by_nr(nr); + if (urlstr && urlstr[0] && type == SENSORURL_TYPE_TOPIC && sensor && sensor->type == SENSOR_MQTT) { + DEBUG_PRINT("sensor_mqtt_unsubscribe1: "); + DEBUG_PRINTLN(sensor->name); + DEBUG_PRINT("unsubscribe: "); + DEBUG_PRINTLN(urlstr); + if (!os.mqtt.unsubscribe(urlstr)) + DEBUG_PRINTLN("error unsubscribe!!"); + sensor->mqtt_init = false; + } +} \ No newline at end of file diff --git a/sensor_mqtt.h b/sensor_mqtt.h new file mode 100644 index 00000000..ca2f7696 --- /dev/null +++ b/sensor_mqtt.h @@ -0,0 +1,40 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * sensors header file + * 2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _SENSOR_MQTT_H +#define _SENSOR_MQTT_H + +#include "sensors.h" + +#if defined(ARDUINO) +void sensor_mqtt_callback(char* mtopic, byte* payload, unsigned int length); +#else +static void sensor_mqtt_callback(struct mosquitto *mosq, void *obj, const struct mosquitto_message *msg); +#endif + +void sensor_mqtt_init(); +int read_sensor_mqtt(Sensor_t *sensor); + +void sensor_mqtt_subscribe(uint nr, uint type, const char *urlstr); +void sensor_mqtt_unsubscribe(uint nr, uint type, const char *urlstr); + +#endif // _SENSOR_MQTT_H diff --git a/sensor_ospi_ads1115.cpp b/sensor_ospi_ads1115.cpp new file mode 100644 index 00000000..e9af06f8 --- /dev/null +++ b/sensor_ospi_ads1115.cpp @@ -0,0 +1,134 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware Copyright (C) 2015 by + * Ray Wang (ray@opensprinkler.com) + * + * Sensor-API + * 2023/2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifdef ADS1115 + +#include "ospi-analog/driver_ads1115.h" +#include "ospi-analog/driver_ads1115_interface.h" +#include "sensor_ospi_ads1115.h" +#include "sensors.h" + +#define ADS1115_BASIC_DEFAULT_RANGE ADS1115_RANGE_6P144V /**< set range 6.144V */ +#define ADS1115_BASIC_DEFAULT_RATE ADS1115_RATE_128SPS /**< set 128 SPS */ + +#define DEFAULT_RANGE 6.144 + +static ads1115_handle_t gs_handle; /**< ads1115 handle */ + +void init_ads1115() { + if (gs_handle.inited) return; + + DRIVER_ADS1115_LINK_INIT(&gs_handle, ads1115_handle_t); + DRIVER_ADS1115_LINK_IIC_INIT(&gs_handle, ads1115_interface_iic_init); + DRIVER_ADS1115_LINK_IIC_DEINIT(&gs_handle, ads1115_interface_iic_deinit); + DRIVER_ADS1115_LINK_IIC_READ(&gs_handle, ads1115_interface_iic_read); + DRIVER_ADS1115_LINK_IIC_WRITE(&gs_handle, ads1115_interface_iic_write); + DRIVER_ADS1115_LINK_DELAY_MS(&gs_handle, ads1115_interface_delay_ms); + DRIVER_ADS1115_LINK_DEBUG_PRINT(&gs_handle, ads1115_interface_debug_print); + + ads1115_init(&gs_handle); +} +/** +* Read the OSPi 1.6 onboard ADS1115 A2D +**/ +int read_sensor_ospi(Sensor_t *sensor, ulong time) { + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + uint8_t res; + + init_ads1115(); + + ads1115_address_t addr = (ads1115_address_t)(ADS1115_ADDR_GND + sensor->id / 4); + ads1115_channel_t channel = (ads1115_channel_t)(ADS1115_CHANNEL_AIN0_GND + sensor->id % 4); + + /* set addr pin */ + res = ads1115_set_addr_pin(&gs_handle, addr); + if (res != 0) + return HTTP_RQT_NOT_RECEIVED; + + res = ads1115_set_channel(&gs_handle, channel); + if (res != 0) { + return HTTP_RQT_NOT_RECEIVED; + } + + /* set default range */ + res = ads1115_set_range(&gs_handle, ADS1115_BASIC_DEFAULT_RANGE); + if (res != 0) { + return HTTP_RQT_NOT_RECEIVED; + } + + /* set default rate */ + res = ads1115_set_rate(&gs_handle, ADS1115_BASIC_DEFAULT_RATE); + if (res != 0) { + return HTTP_RQT_NOT_RECEIVED; + } + + /* disable compare */ + res = ads1115_set_compare(&gs_handle, ADS1115_BOOL_FALSE); + if (res != 0) { + return HTTP_RQT_NOT_RECEIVED; + } + + int16_t raw; + float v; + res = ads1115_single_read(&gs_handle, &raw, &v); + if (res != 0) { + return HTTP_RQT_NOT_RECEIVED; + } + + sensor->repeat_native += raw; + sensor->repeat_data += v; + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && time < sensor->last_read + sensor->read_interval) + return HTTP_RQT_NOT_RECEIVED; + + raw = sensor->repeat_native/sensor->repeat_read; + v = sensor->repeat_data/sensor->repeat_read; + + sensor->repeat_native = raw; + sensor->repeat_data = v; + sensor->repeat_read = 1; + + sensor->last_native_data = raw; + sensor->flags.data_ok = true; + sensor->last_read = time; + + //convert values: + switch(sensor->type) { + case SENSOR_OSPI_ANALOG: + sensor->last_data = (double)v; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_P: + sensor->last_data = (double)v / DEFAULT_RANGE * 3.3 * 100; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + sensor->last_data = (double)v * 50 / 3; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + sensor->last_data = ((double)v - 0.5) * 100; + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} + + + +#endif diff --git a/sensor_ospi_ads1115.h b/sensor_ospi_ads1115.h new file mode 100644 index 00000000..db0fb6c5 --- /dev/null +++ b/sensor_ospi_ads1115.h @@ -0,0 +1,39 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * sensors header file - OSPI + * 2023/2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _SENSOR_OSPI_ADS1115_H +#define _SENSOR_OSPI_ADS1115_H + +#ifdef ADS1115 + +#include "sensors.h" + +/* + * @brief Read sensor function + * @param[in] Sensor + * @note + */ +int read_sensor_ospi(Sensor_t *sensor, ulong time); + +#endif // ADS1115 + +#endif // _SENSORS_H diff --git a/sensor_ospi_pcf8591.cpp b/sensor_ospi_pcf8591.cpp new file mode 100644 index 00000000..ecde702f --- /dev/null +++ b/sensor_ospi_pcf8591.cpp @@ -0,0 +1,135 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware Copyright (C) 2015 by + * Ray Wang (ray@opensprinkler.com) + * + * Sensor-API + * 2023/2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifdef PCF8591 + +#include "ospi-analog/driver_pcf8591.h" +#include "ospi-analog/driver_pcf8591_interface.h" +#include "sensor_ospi_pcf8591.h" +#include "sensors.h" + +#define DEFAULT_ADDR PCF8591_ADDRESS_A000 +#define DEFAULT_MODE PCF8591_MODE_AIN0123_GND +#define DEFAULT_REF_VOLTAGE 3.3 +/** +* Read the OSPi onboard PCF8591 A2D +**/ +int read_sensor_ospi(Sensor_t *sensor, ulong time) { + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + static pcf8591_handle_t gs_handle; /**< pcf8591 handle */ + uint8_t res; + + DRIVER_PCF8591_LINK_INIT(&gs_handle, pcf8591_handle_t); + DRIVER_PCF8591_LINK_IIC_INIT(&gs_handle, pcf8591_interface_iic_init); + DRIVER_PCF8591_LINK_IIC_DEINIT(&gs_handle, pcf8591_interface_iic_deinit); + DRIVER_PCF8591_LINK_IIC_READ_COMMAND(&gs_handle, pcf8591_interface_iic_read_cmd); + DRIVER_PCF8591_LINK_IIC_WRITE_COMMAND(&gs_handle, pcf8591_interface_iic_write_cmd); + DRIVER_PCF8591_LINK_DELAY_MS(&gs_handle, pcf8591_interface_delay_ms); + DRIVER_PCF8591_LINK_DEBUG_PRINT(&gs_handle, pcf8591_interface_debug_print); + + /* set addr pin */ + res = pcf8591_set_addr_pin(&gs_handle, DEFAULT_ADDR); + if (res != 0) + return HTTP_RQT_NOT_RECEIVED; + + /* pcf8591 init */ + res = pcf8591_init(&gs_handle); + if (res != 0) + return HTTP_RQT_NOT_RECEIVED; + + /* set mode */ + res = pcf8591_set_mode(&gs_handle, DEFAULT_MODE); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + /* disable auto increment */ + res = pcf8591_set_auto_increment(&gs_handle, PCF8591_BOOL_FALSE); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + /* set default reference voltage */ + res = pcf8591_set_reference_voltage(&gs_handle, DEFAULT_REF_VOLTAGE); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + pcf8591_channel_t channel = (pcf8591_channel_t)sensor->id; + res = pcf8591_set_channel(&gs_handle, channel); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + int16_t raw; + float v; + res = pcf8591_read(&gs_handle, &raw, &v); + if (res != 0) { + pcf8591_deinit(&gs_handle); + return HTTP_RQT_NOT_RECEIVED; + } + + pcf8591_deinit(&gs_handle); + + sensor->repeat_native += raw; + sensor->repeat_data += v; + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && time < sensor->last_read + sensor->read_interval) + return HTTP_RQT_NOT_RECEIVED; + + raw = sensor->repeat_native/sensor->repeat_read; + v = sensor->repeat_data/sensor->repeat_read; + + sensor->repeat_native = raw; + sensor->repeat_data = v; + sensor->repeat_read = 1; + + sensor->last_native_data = raw; + sensor->flags.data_ok = true; + sensor->last_read = time; + + //convert values: + switch(sensor->type) { + case SENSOR_OSPI_ANALOG: + sensor->last_data = (double)v; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_P: + sensor->last_data = (double)v / DEFAULT_REF_VOLTAGE * 100; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + sensor->last_data = (double)v * 50 / 3; + return HTTP_RQT_SUCCESS; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + sensor->last_data = ((double)v - 0.5) * 100; + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} + + + + +#endif diff --git a/sensor_ospi_pcf8591.h b/sensor_ospi_pcf8591.h new file mode 100644 index 00000000..7527cf7a --- /dev/null +++ b/sensor_ospi_pcf8591.h @@ -0,0 +1,39 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * sensors header file - OSPI + * 2023/2024 @ OpenSprinklerShop + * Stefan Schmaltz (info@opensprinklershop.de) + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _SENSOR_OSPI_PCF8591_H +#define _SENSOR_OSPI_PCF8591_H + +#ifdef PCF8591 + +#include "sensors.h" + +/* + * @brief Read sensor function + * @param[in] Sensor + * @note + */ +int read_sensor_ospi(Sensor_t *sensor, ulong time); + +#endif // PCF8591 + +#endif // _SENSORS_H diff --git a/sensors.cpp b/sensors.cpp new file mode 100644 index 00000000..a72f7a95 --- /dev/null +++ b/sensors.cpp @@ -0,0 +1,3081 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * Utility functions + * Sep 2022 @ OpenSprinklerShop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#include "sensors.h" + +#include + +#include "OpenSprinkler.h" +#ifdef ESP8266 +#include "Wire.h" +#else +#include +#include +#include +#include +#include +#include +#endif +#include "defines.h" +#include "opensprinkler_server.h" +#include "program.h" +#include "sensor_mqtt.h" +#include "utils.h" +#include "weather.h" +#include "osinfluxdb.h" +#ifdef ADS1115 +#include "sensor_ospi_ads1115.h" +#endif +#ifdef PCF8591 +#include "sensor_ospi_pcf8591.h" +#endif + +#define MAX_RS485_DEVICES 4 + +unsigned char findKeyVal(const char *str, char *strbuf, uint16_t maxlen, const char *key, + bool key_in_pgm = false, uint8_t *keyfound = NULL); + +// All sensors: +static Sensor_t *sensors = NULL; +static time_t last_save_time = 0; +static boolean apiInit = false; +static Sensor_t *current_sensor = NULL; + +// Boards: +static uint16_t asb_detected_boards = 0; // bit 1=0x48+0x49 bit 2=0x4A+0x4B usw + +// Sensor URLS: +static SensorUrl_t *sensorUrls = NULL; + +// Program sensor data +static ProgSensorAdjust_t *progSensorAdjusts = NULL; + +// Monitor data +static Monitor_t *monitors = NULL; + +// modbus transaction id +static uint16_t modbusTcpId = 0; +#ifdef ESP8266 +static uint i2c_rs485_allocated[MAX_RS485_DEVICES]; +#else +static modbus_t * ttyDevices[MAX_RS485_DEVICES]; +#endif + +const char *sensor_unitNames[]{ + "", "%", "°C", "°F", "V", "%", "in", "mm", "mph", "kmh", "%", "DK", "LM", "LX" + //0 1 2 3 4 5 6 7 8 9 10, 11, 12, 13 + // 0=Nothing + // 1=Soil moisture + // 2=degree celsius temperature + // 3=degree fahrenheit temperature + // 4=Volt V + // 5=Humidity % + // 6=Rain inch + // 7=Rain mm + // 8=Wind mph + // 9=Wind kmh + // 10=Level % + // 11=DK (Permitivität) + // 12=LM (Lumen) + // 13=LX (LUX) +}; +uint8_t logFileSwitch[3] = {0, 0, 0}; // 0=use smaller File, 1=LOG1, 2=LOG2 + +// Weather +time_t last_weather_time = 0; +bool current_weather_ok = false; +double current_temp = 0.0; +double current_humidity = 0.0; +double current_precip = 0.0; +double current_wind = 0.0; + +uint16_t CRC16(unsigned char buf[], int len) { + uint16_t crc = 0xFFFF; + + for (int pos = 0; pos < len; pos++) { + crc ^= (uint16_t)buf[pos]; // XOR byte into least sig. byte of crc + for (int i = 8; i != 0; i--) { // Loop over each bit + if ((crc & 0x0001) != 0) { // If the LSB is set + crc >>= 1; // Shift right and XOR 0xA001 + crc ^= 0xA001; + } else // Else LSB is not set + crc >>= 1; // Just shift right + } + } + // Note, this number has low and high bytes swapped, so use it accordingly (or + // swap bytes) + return crc; +} // End: CRC16 + +/** + * @brief detect connected boards + * + */ +void detect_asb_board() { + // detect analog sensor board, 0x48+0x49=Board1, 0x4A+0x4B=Board2 +#if defined(ESP8266) + if (detect_i2c(ASB_BOARD_ADDR1a) && detect_i2c(ASB_BOARD_ADDR1b)) + asb_detected_boards |= ASB_BOARD1; + if (detect_i2c(ASB_BOARD_ADDR2a) && detect_i2c(ASB_BOARD_ADDR2b)) + asb_detected_boards |= ASB_BOARD2; + + if (detect_i2c(RS485_TRUEBNER1_ADDR)) asb_detected_boards |= RS485_TRUEBNER1; + if (detect_i2c(RS485_TRUEBNER2_ADDR)) asb_detected_boards |= RS485_TRUEBNER2; + if (detect_i2c(RS485_TRUEBNER3_ADDR)) asb_detected_boards |= RS485_TRUEBNER3; + if (detect_i2c(RS485_TRUEBNER4_ADDR)) asb_detected_boards |= RS485_TRUEBNER4; +#endif + +// Old, pre OSPi 1.43 analog inputs: +#if defined(PCF8591) + asb_detected_boards |= OSPI_PCF8591; +#endif + +// New OSPi 1.6 analog inputs: +#if defined(ADS1115) + asb_detected_boards |= OSPI_ADS1115; +#endif + DEBUG_PRINT("ASB DETECT="); + DEBUG_PRINTLN(asb_detected_boards); + + for (int log = 0; log <= 2; log++) { + checkLogSwitch(log); +#if defined(ENABLE_DEBUG) + DEBUG_PRINT("log="); + DEBUG_PRINTLN(log); + const char *f1 = getlogfile(log); + DEBUG_PRINT("logfile1="); + DEBUG_PRINTLN(f1); + DEBUG_PRINT("size1="); + DEBUG_PRINTLN(file_size(f1)); + const char *f2 = getlogfile2(log); + DEBUG_PRINT("logfile2="); + DEBUG_PRINTLN(f2); + DEBUG_PRINT("size2="); + DEBUG_PRINTLN(file_size(f2)); +#endif + } +} + +uint16_t get_asb_detected_boards() { return asb_detected_boards; } +/* + * init sensor api and load data + */ +void sensor_api_init(boolean detect_boards) { + apiInit = true; + if (detect_boards) + detect_asb_board(); + sensor_load(); + prog_adjust_load(); + sensor_mqtt_init(); + monitor_load(); +#ifndef ESP8266 + //Read rs485 file. Details see below + std::ifstream file; + file.open("rs485", std::ifstream::in); + if (!file.fail()) { + std::string tty; + int idx = 0; + int n = 0; + DEBUG_PRINTLN(F("Opening USB RS485 Adapters:")); + while (std::getline(file, tty)) { + modbus_t * ctx = modbus_new_rtu(tty.c_str(), 9600, 'E', 8, 1); + DEBUG_PRINT(idx); + DEBUG_PRINT(": "); + DEBUG_PRINTLN(tty.c_str()); + + //unavailable on Raspi? modbus_enable_quirks(ctx, MODBUS_QUIRK_MAX_SLAVE); + modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485); + modbus_rtu_set_rts(ctx, MODBUS_RTU_RTS_NONE); // we use auto RTS function by the HAT + modbus_set_response_timeout(ctx, 1, 500000); // 1.5s + if (modbus_connect(ctx) == -1) { + modbus_free(ctx); + } else { + n++; + ttyDevices[idx] = ctx; + asb_detected_boards |= OSPI_USB_RS485; + #ifdef ENABLE_DEBUG + modbus_set_debug(ctx, TRUE); + DEBUG_PRINTLN(F("DEBUG ENABLED")); + #endif + } + idx++; + if (idx >= MAX_RS485_DEVICES) + break; + } + DEBUG_PRINT(F("Found ")); + DEBUG_PRINT(n); + DEBUG_PRINTLN(F(" RS485 Adapters")); + } +#endif +} + +void sensor_save_all() { + sensor_save(); + prog_adjust_save(); + SensorUrl_save(); + monitor_save(); +#ifndef ESP8266 + for (int i = 0; i < MAX_RS485_DEVICES; i++) { + if (ttyDevices[i]) { + modbus_close(ttyDevices[i]); + modbus_free(ttyDevices[i]); + } + ttyDevices[i] = NULL; + } +#endif +} + +/** + * @brief Unload sensorapi from memory, free everything. Be sure that you have save all before + * + */ +void sensor_api_free() { + DEBUG_PRINTLN("sensor_api_free1"); + apiInit = false; + current_sensor = NULL; + os.mqtt.setCallback(2, NULL); + + while (progSensorAdjusts) { + ProgSensorAdjust_t* next = progSensorAdjusts->next; + delete progSensorAdjusts; + progSensorAdjusts = next; + } + + DEBUG_PRINTLN("sensor_api_free2"); + + while (sensorUrls) { + SensorUrl_t* next = sensorUrls->next; + free(sensorUrls->urlstr); + delete sensorUrls; + sensorUrls = next; + } + + DEBUG_PRINTLN("sensor_api_free3"); + + while (monitors) { + Monitor_t* next = monitors->next; + delete monitors; + monitors = next; + } + + DEBUG_PRINTLN("sensor_api_free4"); + + while (sensors) { + Sensor_t* next = sensors->next; + delete sensors; + sensors = next; + } + + modbusTcpId = 0; + #ifdef ESP8266 + memset(i2c_rs485_allocated, 0, sizeof(i2c_rs485_allocated)); + #endif + DEBUG_PRINTLN("sensor_api_free5"); +} + +/* + * get list of all configured sensors + */ +Sensor_t *getSensors() { return sensors; } +/** + * @brief delete a sensor + * + * @param nr + */ +int sensor_delete(uint nr) { + Sensor_t *sensor = sensors; + Sensor_t *last = NULL; + while (sensor) { + if (sensor->nr == nr) { + if (last == NULL) + sensors = sensor->next; + else + last->next = sensor->next; + delete sensor; + sensor_save(); + return HTTP_RQT_SUCCESS; + } + last = sensor; + sensor = sensor->next; + } + return HTTP_RQT_NOT_RECEIVED; +} + +/** + * @brief define or insert a sensor + * + * @param nr > 0 + * @param type > 0 add/modify, 0=delete + * @param group group assignment + * @param ip + * @param port + * @param id + */ +int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, + uint port, uint id, uint ri, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, int16_t offset2, + SensorFlags_t flags, int16_t assigned_unitid) { + if (nr == 0 || type == 0) return HTTP_RQT_NOT_RECEIVED; + if (ri < 10) ri = 10; + + DEBUG_PRINTLN(F("server_define")); + + Sensor_t *sensor = sensors; + Sensor_t *last = NULL; + while (sensor) { + if (sensor->nr == nr) { // Modify existing + sensor->type = type; + sensor->group = group; + strncpy(sensor->name, name, sizeof(sensor->name) - 1); + sensor->ip = ip; + sensor->port = port; + sensor->id = id; + sensor->read_interval = ri; + sensor->factor = factor; + sensor->divider = divider; + sensor->offset_mv = offset_mv; + sensor->offset2 = offset2; + strncpy(sensor->userdef_unit, userdef_unit, + sizeof(sensor->userdef_unit) - 1); + sensor->flags = flags; + if (assigned_unitid >= 0) sensor->assigned_unitid = assigned_unitid; + else sensor->assigned_unitid = 0; + sensor_save(); + return HTTP_RQT_SUCCESS; + } + + if (sensor->nr > nr) break; + + last = sensor; + sensor = sensor->next; + } + + DEBUG_PRINTLN(F("server_define2")); + + // Insert new + Sensor_t *new_sensor = new Sensor_t; + memset(new_sensor, 0, sizeof(Sensor_t)); + new_sensor->nr = nr; + new_sensor->type = type; + new_sensor->group = group; + strncpy(new_sensor->name, name, sizeof(new_sensor->name) - 1); + new_sensor->ip = ip; + new_sensor->port = port; + new_sensor->id = id; + new_sensor->read_interval = ri; + new_sensor->factor = factor; + new_sensor->divider = divider; + new_sensor->offset_mv = offset_mv; + new_sensor->offset2 = offset2; + strncpy(new_sensor->userdef_unit, userdef_unit, + sizeof(new_sensor->userdef_unit) - 1); + new_sensor->flags = flags; + if (assigned_unitid >= 0) new_sensor->assigned_unitid = assigned_unitid; + else new_sensor->assigned_unitid = 0; + + if (last) { + new_sensor->next = last->next; + last->next = new_sensor; + } else { + new_sensor->next = sensors; + sensors = new_sensor; + } + sensor_save(); + return HTTP_RQT_SUCCESS; +} + +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, + int16_t offset2, int16_t assigned_unitid) { + Sensor_t *sensor = sensor_by_nr(nr); + if (!sensor) return HTTP_RQT_NOT_RECEIVED; + + sensor->factor = factor; + sensor->divider = divider; + sensor->offset_mv = offset_mv; + sensor->offset2 = offset2; + sensor->assigned_unitid = assigned_unitid; + if (userdef_unit) + strncpy(sensor->userdef_unit, userdef_unit, + sizeof(sensor->userdef_unit) - 1); + else + memset(sensor->userdef_unit, 0, sizeof(sensor->userdef_unit)); + + return HTTP_RQT_SUCCESS; +} + +/** + * @brief initial load stored sensor definitions + * + */ +void sensor_load() { + // DEBUG_PRINTLN(F("sensor_load")); + sensors = NULL; + current_sensor = NULL; + if (!file_exists(SENSOR_FILENAME)) return; + + ulong pos = 0; + Sensor_t *last = NULL; + while (true) { + Sensor_t *sensor = new Sensor_t; + memset(sensor, 0, sizeof(Sensor_t)); + file_read_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); + if (!sensor->nr || !sensor->type) { + delete sensor; + break; + } + if (!last) + sensors = sensor; + else + last->next = sensor; + last = sensor; + sensor->flags.data_ok = false; + sensor->next = NULL; + pos += SENSOR_STORE_SIZE; + } + + SensorUrl_load(); + last_save_time = os.now_tz(); +} + +/** + * @brief Store sensor data + * + */ +void sensor_save() { + if (!apiInit) return; + DEBUG_PRINTLN(F("sensor_save")); + if (file_exists(SENSOR_FILENAME_BAK)) remove_file(SENSOR_FILENAME_BAK); + if (file_exists(SENSOR_FILENAME)) + rename_file(SENSOR_FILENAME, SENSOR_FILENAME_BAK); + + ulong pos = 0; + Sensor_t *sensor = sensors; + while (sensor) { + file_write_block(SENSOR_FILENAME, sensor, pos, SENSOR_STORE_SIZE); + sensor = sensor->next; + pos += SENSOR_STORE_SIZE; + } + + last_save_time = os.now_tz(); + DEBUG_PRINTLN(F("sensor_save2")); + current_sensor = NULL; +} + +uint sensor_count() { + // DEBUG_PRINTLN(F("sensor_count")); + Sensor_t *sensor = sensors; + uint count = 0; + while (sensor) { + count++; + sensor = sensor->next; + } + return count; +} + +Sensor_t *sensor_by_nr(uint nr) { + // DEBUG_PRINTLN(F("sensor_by_nr")); + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->nr == nr) return sensor; + sensor = sensor->next; + } + return NULL; +} + +Sensor_t *sensor_by_idx(uint idx) { + // DEBUG_PRINTLN(F("sensor_by_idx")); + Sensor_t *sensor = sensors; + uint count = 0; + while (sensor) { + if (count == idx) return sensor; + count++; + sensor = sensor->next; + } + return NULL; +} + +// LOGGING METHODS: + +/** + * @brief getlogfile name + * + * @param log + * @return const char* + */ +const char *getlogfile(uint8_t log) { + uint8_t sw = logFileSwitch[log]; + switch (log) { + case LOG_STD: + return sw < 2 ? SENSORLOG_FILENAME1 : SENSORLOG_FILENAME2; + case LOG_WEEK: + return sw < 2 ? SENSORLOG_FILENAME_WEEK1 : SENSORLOG_FILENAME_WEEK2; + case LOG_MONTH: + return sw < 2 ? SENSORLOG_FILENAME_MONTH1 : SENSORLOG_FILENAME_MONTH2; + } + return ""; +} + +/** + * @brief getlogfile name2 (opposite file) + * + * @param log + * @return const char* + */ +const char *getlogfile2(uint8_t log) { + uint8_t sw = logFileSwitch[log]; + switch (log) { + case 0: + return sw < 2 ? SENSORLOG_FILENAME2 : SENSORLOG_FILENAME1; + case 1: + return sw < 2 ? SENSORLOG_FILENAME_WEEK2 : SENSORLOG_FILENAME_WEEK1; + case 2: + return sw < 2 ? SENSORLOG_FILENAME_MONTH2 : SENSORLOG_FILENAME_MONTH1; + } + return ""; +} + +void checkLogSwitch(uint8_t log) { + if (logFileSwitch[log] == 0) { // Check file size, use smallest + ulong logFileSize1 = file_size(getlogfile(log)); + ulong logFileSize2 = file_size(getlogfile2(log)); + if (logFileSize1 < logFileSize2) + logFileSwitch[log] = 1; + else + logFileSwitch[log] = 2; + } +} + +void checkLogSwitchAfterWrite(uint8_t log) { + ulong size = file_size(getlogfile(log)); + if ((size / SENSORLOG_STORE_SIZE) >= MAX_LOG_SIZE) { // switch logs if max reached + if (logFileSwitch[log] == 1) + logFileSwitch[log] = 2; + else + logFileSwitch[log] = 1; + remove_file(getlogfile(log)); + } +} + +bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog) { +#if defined(ESP8266) + if (checkDiskFree()) { +#endif + DEBUG_PRINT(F("sensorlog_add ")); + DEBUG_PRINT(log); + checkLogSwitch(log); + file_append_block(getlogfile(log), sensorlog, SENSORLOG_STORE_SIZE); + checkLogSwitchAfterWrite(log); + DEBUG_PRINT(F("=")); + DEBUG_PRINTLN(sensorlog_filesize(log)); + return true; +#if defined(ESP8266) + } + return false; +#endif +} + +bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time) { + + if (sensor->flags.data_ok && sensor->flags.log && time > 1000) { + + // Write to log file only if necessary + if (time-sensor->last_logged_time > 86400 || abs(sensor->last_data - sensor->last_logged_data) > 0.00999) { + SensorLog_t sensorlog; + memset(&sensorlog, 0, sizeof(SensorLog_t)); + sensorlog.nr = sensor->nr; + sensorlog.time = time; + sensorlog.native_data = sensor->last_native_data; + sensorlog.data = sensor->last_data; + sensor->last_logged_data = sensor->last_data; + sensor->last_logged_time = time; + + if (!sensorlog_add(log, &sensorlog)) { + sensor->flags.log = 0; + return false; + } + } + return true; + } + return false; +} + +ulong sensorlog_filesize(uint8_t log) { + // DEBUG_PRINT(F("sensorlog_filesize ")); + checkLogSwitch(log); + ulong size = file_size(getlogfile(log)) + file_size(getlogfile2(log)); + // DEBUG_PRINTLN(size); + return size; +} + +ulong sensorlog_size(uint8_t log) { + // DEBUG_PRINT(F("sensorlog_size ")); + ulong size = sensorlog_filesize(log) / SENSORLOG_STORE_SIZE; + // DEBUG_PRINTLN(size); + return size; +} + +void sensorlog_clear_all() { sensorlog_clear(true, true, true); } + +void sensorlog_clear(bool std, bool week, bool month) { + DEBUG_PRINTLN(F("sensorlog_clear ")); + DEBUG_PRINT(std); + DEBUG_PRINT(week); + DEBUG_PRINT(month); + if (std) { + remove_file(SENSORLOG_FILENAME1); + remove_file(SENSORLOG_FILENAME2); + logFileSwitch[LOG_STD] = 1; + } + if (week) { + remove_file(SENSORLOG_FILENAME_WEEK1); + remove_file(SENSORLOG_FILENAME_WEEK2); + logFileSwitch[LOG_WEEK] = 1; + } + if (month) { + remove_file(SENSORLOG_FILENAME_MONTH1); + remove_file(SENSORLOG_FILENAME_MONTH2); + logFileSwitch[LOG_MONTH] = 1; + } +} + +ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, + double under, bool use_over, double over, time_t before, time_t after) { + SensorLog_t sensorlog; + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + ulong size2 = size + file_size(fcur) / SENSORLOG_STORE_SIZE; + const char *f; + ulong idxr = 0; + ulong n = 0; + DEBUG_PRINTLN(F("clearlog1")); + DEBUG_PRINTF("nr: %d log: %d under:%lf over: %lf before: %lld after: %lld size: %ld size2: %ld\n", sensorNr, log, under, over, before, after, size, size2); + while (idxr < size2) { + ulong idx = idxr; + if (idx >= size) { + idx -= size; + f = fcur; + } else { + f = flast; + } + + ulong result = file_read_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, + SENSORLOG_STORE_SIZE); + if (result == 0) break; + if (sensorlog.nr > 0 && (sensorlog.nr == sensorNr || sensorNr == 0)) { + DEBUG_PRINTF("clearlog2 idx=%ld idx2=%ld\n", idx, idxr); + boolean found = false; + if (use_under && sensorlog.data < under) found = true; + if (use_over && sensorlog.data > over) found = true; + if (before && sensorlog.time < before) found = true; + if (after && sensorlog.time > after) found = true; + if (sensorNr > 0 && sensorlog.nr != sensorNr) found = false; + if (sensorNr > 0 && sensorlog.nr == sensorNr && !use_under && !use_over && !before && !after) found = true; + if (found) { + sensorlog.nr = 0; + file_write_block(f, &sensorlog, idx * SENSORLOG_STORE_SIZE, + SENSORLOG_STORE_SIZE); + DEBUG_PRINTF("clearlog3 idx=%ld idxr=%ld\n", idx, idxr); + n++; + } + } + idxr++; + } + DEBUG_PRINTF("clearlog4 n=%ld\n", n); + return n; +} + +SensorLog_t *sensorlog_load(uint8_t log, ulong idx) { + SensorLog_t *sensorlog = new SensorLog_t; + return sensorlog_load(log, idx, sensorlog); +} + +SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t *sensorlog) { + // DEBUG_PRINTLN(F("sensorlog_load")); + + // Map lower idx to the other log file + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + const char *f; + if (idx >= size) { + idx -= size; + f = fcur; + } else { + f = flast; + } + + file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, + SENSORLOG_STORE_SIZE); + return sensorlog; +} + +int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t *sensorlog) { + // DEBUG_PRINTLN(F("sensorlog_load")); + + // Map lower idx to the other log file + checkLogSwitch(log); + const char *flast = getlogfile2(log); + const char *fcur = getlogfile(log); + ulong size = file_size(flast) / SENSORLOG_STORE_SIZE; + const char *f; + if (idx >= size) { + idx -= size; + f = fcur; + size = file_size(f) / SENSORLOG_STORE_SIZE; + } else { + f = flast; + } + + if (idx + count > size) count = size - idx; + if (count <= 0) return 0; + file_read_block(f, sensorlog, idx * SENSORLOG_STORE_SIZE, + count * SENSORLOG_STORE_SIZE); + return count; +} + +ulong findLogPosition(uint8_t log, ulong after) { + ulong log_size = sensorlog_size(log); + ulong a = 0; + ulong b = log_size - 1; + ulong lastIdx = 0; + SensorLog_t sensorlog; + while (true) { + ulong idx = (b - a) / 2 + a; + sensorlog_load(log, idx, &sensorlog); + if (sensorlog.time < after) { + a = idx; + } else if (sensorlog.time > after) { + b = idx; + } + if (a >= b || idx == lastIdx) return idx; + lastIdx = idx; + } + return 0; +} + +#if !defined(ARDUINO) +/** + * compatibility functions for OSPi: + **/ +#define timeSet 0 +int timeStatus() { return timeSet; } + +void dtostrf(float value, int min_width, int precision, char *txt) { + printf(txt, "%*.*f", min_width, precision, value); +} + +void dtostrf(double value, int min_width, int precision, char *txt) { + printf(txt, "%*.*d", min_width, precision, value); +} +#endif + +// 1/4 of a day: 6*60*60 +#define BLOCKSIZE 64 +#define CALCRANGE_WEEK 21600 +#define CALCRANGE_MONTH 172800 +static ulong next_week_calc = 0; +static ulong next_month_calc = 0; + +/** +Calculate week+month Data +We store only the average value of 6 hours utc +**/ +void calc_sensorlogs() { + if (!sensors || timeStatus() != timeSet) return; + + ulong log_size = sensorlog_size(LOG_STD); + if (log_size == 0) return; + + SensorLog_t *sensorlog = NULL; + + time_t time = os.now_tz(); + time_t last_day = time; + + if (time >= next_week_calc) { + DEBUG_PRINTLN(F("calc_sensorlogs WEEK start")); + sensorlog = (SensorLog_t *)malloc(sizeof(SensorLog_t) * BLOCKSIZE); + ulong size = sensorlog_size(LOG_WEEK); + if (size == 0) { + sensorlog_load(LOG_STD, 0, sensorlog); + last_day = sensorlog->time; + } else { + sensorlog_load(LOG_WEEK, size - 1, sensorlog); // last record + last_day = sensorlog->time + CALCRANGE_WEEK; // Skip last Range + } + time_t fromdate = (last_day / CALCRANGE_WEEK) * CALCRANGE_WEEK; + time_t todate = fromdate + CALCRANGE_WEEK; + + // 4 blocks per day + + while (todate < time) { + ulong startidx = findLogPosition(LOG_STD, fromdate); + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->flags.enable && sensor->flags.log) { + ulong idx = startidx; + double data = 0; + ulong n = 0; + bool done = false; + while (!done) { + int sn = sensorlog_load2(LOG_STD, idx, BLOCKSIZE, sensorlog); + if (sn <= 0) break; + for (int i = 0; i < sn; i++) { + idx++; + if (sensorlog[i].time >= todate) { + done = true; + break; + } + if (sensorlog[i].nr == sensor->nr) { + data += sensorlog[i].data; + n++; + } + } + } + if (n > 0) { + sensorlog->nr = sensor->nr; + sensorlog->time = fromdate; + sensorlog->data = data / (double)n; + sensorlog->native_data = 0; + sensorlog_add(LOG_WEEK, sensorlog); + } + } + sensor = sensor->next; + } + fromdate += CALCRANGE_WEEK; + todate += CALCRANGE_WEEK; + } + next_week_calc = todate; + DEBUG_PRINTLN(F("calc_sensorlogs WEEK end")); + } + + if (time >= next_month_calc) { + log_size = sensorlog_size(LOG_WEEK); + if (log_size <= 0) { + if (sensorlog) free(sensorlog); + return; + } + if (!sensorlog) + sensorlog = (SensorLog_t *)malloc(sizeof(SensorLog_t) * BLOCKSIZE); + + DEBUG_PRINTLN(F("calc_sensorlogs MONTH start")); + ulong size = sensorlog_size(LOG_MONTH); + if (size == 0) { + sensorlog_load(LOG_WEEK, 0, sensorlog); + last_day = sensorlog->time; + } else { + sensorlog_load(LOG_MONTH, size - 1, sensorlog); // last record + last_day = sensorlog->time + CALCRANGE_MONTH; // Skip last Range + } + time_t fromdate = (last_day / CALCRANGE_MONTH) * CALCRANGE_MONTH; + time_t todate = fromdate + CALCRANGE_MONTH; + // 4 blocks per day + + while (todate < time) { + ulong startidx = findLogPosition(LOG_WEEK, fromdate); + Sensor_t *sensor = sensors; + while (sensor) { + if (sensor->flags.enable && sensor->flags.log) { + ulong idx = startidx; + double data = 0; + ulong n = 0; + bool done = false; + while (!done) { + int sn = sensorlog_load2(LOG_WEEK, idx, BLOCKSIZE, sensorlog); + if (sn <= 0) break; + for (int i = 0; i < sn; i++) { + idx++; + if (sensorlog[i].time >= todate) { + done = true; + break; + } + if (sensorlog[i].nr == sensor->nr) { + data += sensorlog[i].data; + n++; + } + } + } + if (n > 0) { + sensorlog->nr = sensor->nr; + sensorlog->time = fromdate; + sensorlog->data = data / (double)n; + sensorlog->native_data = 0; + sensorlog_add(LOG_MONTH, sensorlog); + } + } + sensor = sensor->next; + } + fromdate += CALCRANGE_MONTH; + todate += CALCRANGE_MONTH; + } + next_month_calc = todate; + DEBUG_PRINTLN(F("calc_sensorlogs MONTH end")); + } + if (sensorlog) free(sensorlog); +} + +void sensor_remote_http_callback(char *) { + // unused +} + +void push_message(Sensor_t *sensor) { + if (!sensor || !sensor->last_read) return; + + static char topic[TMP_BUFFER_SIZE]; + static char payload[TMP_BUFFER_SIZE]; + char *postval = tmp_buffer; + + if (os.mqtt.enabled()) { + DEBUG_PRINTLN("push mqtt1"); + strncpy_P(topic, PSTR("analogsensor/"), sizeof(topic) - 1); + strncat(topic, sensor->name, sizeof(topic) - 1); + snprintf_P(payload, TMP_BUFFER_SIZE, + PSTR("{\"nr\":%u,\"type\":%u,\"data_ok\":%u,\"time\":%u," + "\"value\":%d.%02d,\"unit\":\"%s\"}"), + sensor->nr, sensor->type, sensor->flags.data_ok, + sensor->last_read, (int)sensor->last_data, + abs((int)(sensor->last_data * 100) % 100), getSensorUnit(sensor)); + + if (!os.mqtt.connected()) os.mqtt.reconnect(); + os.mqtt.publish(topic, payload); + DEBUG_PRINTLN("push mqtt2"); + } + + //ifttt is enabled, when the ifttt key is present! + os.sopt_load(SOPT_IFTTT_KEY, tmp_buffer); + bool ifttt_enabled = strlen(tmp_buffer)!=0; + if (ifttt_enabled) { + DEBUG_PRINTLN("push ifttt"); + strcpy_P(postval, PSTR("{\"value1\":\"On site [")); + os.sopt_load(SOPT_DEVICE_NAME, postval + strlen(postval)); + strcat_P(postval, PSTR("], ")); + + strcat_P(postval, PSTR("analogsensor ")); + snprintf_P(postval + strlen(postval), TMP_BUFFER_SIZE - strlen(postval), + PSTR("nr: %u, type: %u, data_ok: %u, time: %u, value: %d.%02d, " + "unit: %s"), + sensor->nr, sensor->type, sensor->flags.data_ok, + sensor->last_read, (int)sensor->last_data, + abs((int)(sensor->last_data * 100) % 100), getSensorUnit(sensor)); + strcat_P(postval, PSTR("\"}")); + + // char postBuffer[1500]; + BufferFiller bf = BufferFiller(ether_buffer, ETHER_BUFFER_SIZE_L); + bf.emit_p(PSTR("POST /trigger/sprinkler/with/key/$O HTTP/1.0\r\n" + "Host: $S\r\n" + "Accept: */*\r\n" + "Content-Length: $D\r\n" + "Content-Type: application/json\r\n\r\n$S"), + SOPT_IFTTT_KEY, DEFAULT_IFTTT_URL, strlen(postval), postval); + + os.send_http_request(DEFAULT_IFTTT_URL, 80, ether_buffer, sensor_remote_http_callback); + DEBUG_PRINTLN("push ifttt2"); + } + + add_influx_data(sensor); +} + +void read_all_sensors(boolean online) { + if (!sensors) return; + //DEBUG_PRINTLN(F("read_all_sensors")); + + ulong time = os.now_tz(); + +#ifdef ENABLE_DEBUG + if (time < os.powerup_lasttime + 3) +#else + if (time < os.powerup_lasttime + 30) +#endif + return; // wait 30s before first sensor read + + // When we run out of time, skip some sensors and continue on next loop + if (current_sensor == NULL) current_sensor = sensors; + + while (current_sensor) { + if (time >= current_sensor->last_read + current_sensor->read_interval || + current_sensor->repeat_read) { + if (online || (current_sensor->ip == 0 && current_sensor->type != SENSOR_MQTT)) { + int result = read_sensor(current_sensor, time); + if (result == HTTP_RQT_SUCCESS) { + check_monitors(); + sensorlog_add(LOG_STD, current_sensor, time); + push_message(current_sensor); + } else if (result == HTTP_RQT_TIMEOUT) { + // delay next read on timeout: + current_sensor->last_read = time + max((uint)60, current_sensor->read_interval); + current_sensor->repeat_read = 0; + DEBUG_PRINTF("Delayed1: %s", current_sensor->name); + } else if (result == HTTP_RQT_CONNECT_ERR) { + // delay next read on error: + current_sensor->last_read = time + max((uint)60, current_sensor->read_interval); + current_sensor->repeat_read = 0; + DEBUG_PRINTF("Delayed2: %s", current_sensor->name); + } + ulong passed = os.now_tz() - time; + if (passed > MAX_SENSOR_READ_TIME) { + current_sensor = current_sensor->next; + break; + } + } + } + current_sensor = current_sensor->next; + } + sensor_update_groups(); + calc_sensorlogs(); + if (time - last_save_time > 3600) // 1h + sensor_save(); +} + +#if defined(ARDUINO) +#if defined(ESP8266) +/** + * Read ESP8296 ADS1115 sensors + */ +int read_sensor_adc(Sensor_t *sensor, ulong time) { + //DEBUG_PRINTLN(F("read_sensor_adc")); + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + if (sensor->id >= 16) return HTTP_RQT_NOT_RECEIVED; + // Init + Detect: + + if (sensor->id < 8 && ((asb_detected_boards & ASB_BOARD1) == 0)) + return HTTP_RQT_NOT_RECEIVED; + if (sensor->id >= 8 && sensor->id < 16 && + ((asb_detected_boards & ASB_BOARD2) == 0)) + return HTTP_RQT_NOT_RECEIVED; + + int port = ASB_BOARD_ADDR1a + sensor->id / 4; + int id = sensor->id % 4; + + // unsigned long startTime = millis(); + ADS1115 adc(port); + if (!adc.begin()) { + DEBUG_PRINTLN(F("no asb board?!?")); + return HTTP_RQT_NOT_RECEIVED; + } + // unsigned long endTime = millis(); + // DEBUG_PRINTF("t=%lu ms\n", endTime-startTime); + + // startTime = millis(); + sensor->repeat_native += adc.readADC(id); + // endTime = millis(); + // DEBUG_PRINTF("t=%lu ms\n", endTime-startTime); + + if (++sensor->repeat_read < MAX_SENSOR_REPEAT_READ && + time < sensor->last_read + sensor->read_interval) + return HTTP_RQT_NOT_RECEIVED; + + uint64_t avgValue = sensor->repeat_native / sensor->repeat_read; + + sensor->repeat_native = avgValue; + sensor->repeat_data = 0; + sensor->repeat_read = 1; // read continously + + sensor->last_native_data = avgValue; + sensor->last_data = adc.toVoltage(sensor->last_native_data); + double v = sensor->last_data; + + switch (sensor->type) { + case SENSOR_SMT50_MOIS: // SMT50 VWC [%] = (U * 50) : 3 + sensor->last_data = (v * 50.0) / 3.0; + break; + case SENSOR_SMT50_TEMP: // SMT50 T [°C] = (U – 0,5) * 100 + sensor->last_data = (v - 0.5) * 100.0; + break; + case SENSOR_ANALOG_EXTENSION_BOARD_P: // 0..3,3V -> 0..100% + sensor->last_data = v * 100.0 / 3.3; + if (sensor->last_data < 0) + sensor->last_data = 0; + else if (sensor->last_data > 100) + sensor->last_data = 100; + break; + case SENSOR_SMT100_ANALOG_MOIS: // 0..3V -> 0..100% + sensor->last_data = v * 100.0 / 3; + break; + case SENSOR_SMT100_ANALOG_TEMP: // 0..3V -> -40°C..60°C + sensor->last_data = v * 100.0 / 3 - 40; + break; + + case SENSOR_VH400: // http://vegetronix.com/Products/VH400/VH400-Piecewise-Curve + if (v <= 1.1) // 0 to 1.1V VWC= 10*V-1 + sensor->last_data = 10 * v - 1; + else if (v < 1.3) // 1.1V to 1.3V VWC= 25*V- 17.5 + sensor->last_data = 25 * v - 17.5; + else if (v < 1.82) // 1.3V to 1.82V VWC= 48.08*V- 47.5 + sensor->last_data = 48.08 * v - 47.5; + else if (v < 2.2) // 1.82V to 2.2V VWC= 26.32*V- 7.89 + sensor->last_data = 26.32 * v - 7.89; + else // 2.2V - 3.0V VWC= 62.5*V - 87.5 + sensor->last_data = 62.5 * v - 87.5; + break; + case SENSOR_THERM200: // http://vegetronix.com/Products/THERM200/ + sensor->last_data = v * 41.67 - 40; + break; + case SENSOR_AQUAPLUMB: // http://vegetronix.com/Products/AquaPlumb/ + sensor->last_data = v * 100.0 / 3.0; // 0..3V -> 0..100% + if (sensor->last_data < 0) + sensor->last_data = 0; + else if (sensor->last_data > 100) + sensor->last_data = 100; + break; + case SENSOR_USERDEF: // User defined sensor + v -= (double)sensor->offset_mv / + 1000; // adjust zero-point offset in millivolt + if (sensor->factor && sensor->divider) + v *= (double)sensor->factor / (double)sensor->divider; + else if (sensor->divider) + v /= sensor->divider; + else if (sensor->factor) + v *= sensor->factor; + sensor->last_data = v + sensor->offset2 / 100; + break; + } + + sensor->flags.data_ok = true; + sensor->last_read = time; + + DEBUG_PRINT(F("adc sensor values: ")); + DEBUG_PRINT(sensor->last_native_data); + DEBUG_PRINT(","); + DEBUG_PRINTLN(sensor->last_data); + + return HTTP_RQT_SUCCESS; +} +#endif +#endif + +bool extract(char *s, char *buf, int maxlen) { + s = strstr(s, ":"); + if (!s) return false; + s++; + while (*s == ' ') s++; // skip spaces + char *e = strstr(s, ","); + char *f = strstr(s, "}"); + if (!e && !f) return false; + if (f && f < e) e = f; + int l = e - s; + if (l < 1 || l > maxlen) return false; + strncpy(buf, s, l); + buf[l] = 0; + // DEBUG_PRINTLN(buf); + return true; +} + +int read_sensor_http(Sensor_t *sensor, ulong time) { +#if defined(ESP8266) + IPAddress _ip(sensor->ip); + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; +#else + unsigned char ip[4]; + ip[3] = (unsigned char)((sensor->ip >> 24) & 0xFF); + ip[2] = (unsigned char)((sensor->ip >> 16) & 0xFF); + ip[1] = (unsigned char)((sensor->ip >> 8) & 0xFF); + ip[0] = (unsigned char)((sensor->ip & 0xFF)); +#endif + + DEBUG_PRINTLN(F("read_sensor_http")); + + char *p = tmp_buffer; + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE); + + bf.emit_p(PSTR("GET /sg?pw=$O&nr=$D"), SOPT_PASSWORD, sensor->id); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0], ip[1], ip[2], + ip[3]); + + DEBUG_PRINTLN(p); + + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + int res = os.send_http_request(server, sensor->port, p, NULL, false, 500); + if (res == HTTP_RQT_SUCCESS) { + DEBUG_PRINTLN("Send Ok"); + p = ether_buffer; + DEBUG_PRINTLN(p); + + sensor->last_read = time; + + char buf[20]; + char *s = strstr(p, "\"nativedata\":"); + if (s && extract(s, buf, sizeof(buf))) { + sensor->last_native_data = strtoul(buf, NULL, 0); + } + s = strstr(p, "\"data\":"); + if (s && extract(s, buf, sizeof(buf))) { + double value = -1; + int ok = sscanf(buf, "%lf", &value); + if (ok && (value != sensor->last_data || !sensor->flags.data_ok || + time - sensor->last_read > 6000)) { + sensor->last_data = value; + sensor->flags.data_ok = true; + } else { + return HTTP_RQT_NOT_RECEIVED; + } + } + s = strstr(p, "\"unitid\":"); + if (s && extract(s, buf, sizeof(buf))) { + sensor->unitid = atoi(buf); + sensor->assigned_unitid = sensor->unitid; + } + s = strstr(p, "\"unit\":"); + if (s && extract(s, buf, sizeof(buf))) { + urlDecodeAndUnescape(buf); + strncpy(sensor->userdef_unit, buf, sizeof(sensor->userdef_unit) - 1); + } + s = strstr(p, "\"last\":"); + if (s && extract(s, buf, sizeof(buf))) { + ulong last = strtoul(buf, NULL, 0); + if (last == 0 || last == sensor->last_read) { + return HTTP_RQT_NOT_RECEIVED; + } else { + sensor->last_read = last; + } + } + + return HTTP_RQT_SUCCESS; + } + return res; +} + +#if defined(ESP8266) +/** + * @brief Truebner RS485 Interface + * + * @param sensor + * @return int + */ +int read_sensor_rs485(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_rs485")); + int device = sensor->port; + if (device >= MAX_RS485_DEVICES || (asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) + return HTTP_RQT_NOT_RECEIVED; + + if (i2c_rs485_allocated[device] > 0 && i2c_rs485_allocated[device] != sensor->nr) { + sensor->repeat_read = 1000; + DEBUG_PRINT(F("cant' read, allocated by sensor ")); + DEBUG_PRINTLN(i2c_rs485_allocated[device]); + Sensor_t *t = sensor_by_nr(i2c_rs485_allocated[device]); + if (!t || !t->flags.enable) + i2c_rs485_allocated[device] = 0; //breakout + return HTTP_RQT_NOT_RECEIVED; + } + + DEBUG_PRINTLN(F("read_sensor_rs485: check-ok")); + + bool isTemp = sensor->type == SENSOR_SMT100_TEMP || sensor->type == SENSOR_TH100_TEMP; + bool isMois = sensor->type == SENSOR_SMT100_MOIS || sensor->type == SENSOR_TH100_MOIS; + uint8_t type = isTemp ? 0x00 : isMois ? 0x01 : 0x02; + + if (sensor->repeat_read == 0 || sensor->repeat_read == 1000) { + Wire.beginTransmission(RS485_TRUEBNER1_ADDR + device); + Wire.write((uint8_t)sensor->id); + Wire.write(type); + if (Wire.endTransmission() == 0) { + DEBUG_PRINTF("read_sensor_rs485: request send: %d - %d\n", sensor->id, + type); + sensor->repeat_read = 1; + i2c_rs485_allocated[device] = sensor->nr; + } + return HTTP_RQT_NOT_RECEIVED; + // delay(500); + } + + if (Wire.requestFrom((uint8_t)(RS485_TRUEBNER1_ADDR + device), (size_t)4, true)) { + // read the incoming bytes: + uint8_t addr = Wire.read(); + uint8_t reg = Wire.read(); + uint8_t low_byte = Wire.read(); + uint8_t high_byte = Wire.read(); + if (addr == sensor->id && reg == type) { + uint16_t data = (high_byte << 8) | low_byte; + DEBUG_PRINTF("read_sensor_rs485: result: %d - %d (%d %d)\n", sensor->id, + data, low_byte, high_byte); + double value = isTemp ? (data / 100.0) - 100.0 : (isMois ? data / 100.0 : data); + sensor->last_native_data = data; + sensor->last_data = value; + DEBUG_PRINTLN(sensor->last_data); + + sensor->flags.data_ok = true; + + sensor->repeat_read = 0; + i2c_rs485_allocated[device] = 0; + return HTTP_RQT_SUCCESS; + } + } + + sensor->repeat_read++; + if (sensor->repeat_read > 4) { // timeout + sensor->repeat_read = 0; + sensor->flags.data_ok = false; + i2c_rs485_allocated[device] = 0; + DEBUG_PRINTLN(F("read_sensor_rs485: timeout")); + } + DEBUG_PRINTLN(F("read_sensor_rs485: exit")); + return HTTP_RQT_NOT_RECEIVED; +} +#else +/** + * USB RS485 Adapter + * Howto use: Create a file rs485 inside the OpenSprinkler-Firmware directory, enter the USB-TTY connections here. + * For example: + * /dev/ttyUSB0 + * /dev/ttyUSB1 + * You can use multiple rs485 adapters, every line increments the "port" index, starting with 0 for the first line + */ + +/** + * @brief Raspberry PI RS485 Interface + * + * @param sensor + * @return int + */ +int read_sensor_rs485(Sensor_t *sensor) { + DEBUG_PRINTLN(F("read_sensor_rs485")); + int device = sensor->port; + if (device >= MAX_RS485_DEVICES || !ttyDevices[device]) + return HTTP_RQT_NOT_RECEIVED; + + DEBUG_PRINTLN(F("read_sensor_rs485: check-ok")); + + uint8_t buffer[10]; + bool isTemp = sensor->type == SENSOR_SMT100_TEMP || sensor->type == SENSOR_TH100_TEMP; + bool isMois = sensor->type == SENSOR_SMT100_MOIS || sensor->type == SENSOR_TH100_MOIS; + uint8_t type = isTemp ? 0x00 : isMois ? 0x01 : 0x02; + + uint16_t tab_reg[3] = {0}; + modbus_set_slave(ttyDevices[device], sensor->id); + if (modbus_read_registers(ttyDevices[device], type, 2, tab_reg) > 0) { + uint16_t data = tab_reg[0]; + DEBUG_PRINTF("read_sensor_rs485: result: %d - %d\n", sensor->id, data); + double value = isTemp ? (data / 100.0) - 100.0 : (isMois ? data / 100.0 : data); + sensor->last_native_data = data; + sensor->last_data = value; + DEBUG_PRINTLN(sensor->last_data); + sensor->flags.data_ok = true; + return HTTP_RQT_SUCCESS; + } + DEBUG_PRINTLN(F("read_sensor_rs485: exit")); + return HTTP_RQT_NOT_RECEIVED; +} + +#endif + +/** + * Read ip connected sensors + */ +int read_sensor_ip(Sensor_t *sensor) { +#if defined(ARDUINO) + + Client *client; +#if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; +#else + EthernetClient etherClient; + client = ðerClient; +#endif + +#else + EthernetClient etherClient; + EthernetClient *client = ðerClient; +#endif + + sensor->flags.data_ok = false; + if (!sensor->ip || !sensor->port) { + sensor->flags.enable = false; + return HTTP_RQT_CONNECT_ERR; + } + +#if defined(ARDUINO) + IPAddress _ip(sensor->ip); + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; +#else + unsigned char ip[4]; + ip[3] = (unsigned char)((sensor->ip >> 24) & 0xFF); + ip[2] = (unsigned char)((sensor->ip >> 16) & 0xFF); + ip[1] = (unsigned char)((sensor->ip >> 8) & 0xFF); + ip[0] = (unsigned char)((sensor->ip & 0xFF)); +#endif + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + client->setTimeout(200); + if (!client->connect(server, sensor->port)) { + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINT(sensor->port); + DEBUG_PRINT(" "); + DEBUG_PRINTLN(F("failed.")); + client->stop(); + return HTTP_RQT_TIMEOUT; + } + + uint8_t buffer[20]; + + // https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/ + + if (modbusTcpId >= 0xFFFE) + modbusTcpId = 1; + else + modbusTcpId++; + + bool isTemp = sensor->type == SENSOR_SMT100_TEMP || sensor->type == SENSOR_TH100_TEMP; + bool isMois = sensor->type == SENSOR_SMT100_MOIS || sensor->type == SENSOR_TH100_MOIS; + uint8_t type = isTemp ? 0x00 : isMois ? 0x01 : 0x02; + + buffer[0] = (0xFF00 & modbusTcpId) >> 8; + buffer[1] = (0x00FF & modbusTcpId); + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + buffer[5] = 6; // len + buffer[6] = sensor->id; // Modbus ID + buffer[7] = 0x03; // Read Holding Registers + buffer[8] = 0x00; + buffer[9] = type; + buffer[10] = 0x00; + buffer[11] = 0x01; + + client->write(buffer, 12); +#if defined(ESP8266) + client->flush(); +#endif + + // Read result: + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + case SENSOR_SMT100_TEMP: + case SENSOR_SMT100_PMTY: + case SENSOR_TH100_MOIS: + case SENSOR_TH100_TEMP: + uint32_t stoptime = millis() + SENSOR_READ_TIMEOUT; +#if defined(ESP8266) + while (true) { + if (client->available()) break; + if (millis() >= stoptime) { + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINTLN(F(" timeout read!")); + return HTTP_RQT_TIMEOUT; + } + delay(5); + } + + int n = client->read(buffer, 11); + if (n < 11) n += client->read(buffer + n, 11 - n); +#else + int n = 0; + while (true) { + n = client->read(buffer, 11); + if (n < 11) n += client->read(buffer + n, 11 - n); + if (n > 0) break; + if (millis() >= stoptime) { + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + DEBUG_PRINT(F(" timeout read!")); + return HTTP_RQT_TIMEOUT; + } + delay(5); + } +#endif + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + if (n != 11) { + DEBUG_PRINT(F(" returned ")); + DEBUG_PRINT(n); + DEBUG_PRINTLN(F(" bytes??")); + return n == 0 ? HTTP_RQT_EMPTY_RETURN : HTTP_RQT_TIMEOUT; + } + if (buffer[0] != (0xFF00 & modbusTcpId) >> 8 || + buffer[1] != (0x00FF & modbusTcpId)) { + DEBUG_PRINT(F(" returned transaction id ")); + DEBUG_PRINTLN((uint16_t)((buffer[0] << 8) + buffer[1])); + return HTTP_RQT_NOT_RECEIVED; + } + if ((buffer[6] != sensor->id && sensor->id != 253)) { // 253 is broadcast + DEBUG_PRINT(F(" returned sensor id ")); + DEBUG_PRINTLN((int)buffer[0]); + return HTTP_RQT_NOT_RECEIVED; + } + + // Valid result: + sensor->last_native_data = (buffer[9] << 8) | buffer[10]; + DEBUG_PRINT(F(" native: ")); + DEBUG_PRINT(sensor->last_native_data); + + // Convert to readable value: + switch (sensor->type) { + case SENSOR_TH100_MOIS: + case SENSOR_SMT100_MOIS: // Equation: soil moisture [vol.%]= + // (16Bit_soil_moisture_value / 100) + sensor->last_data = ((double)sensor->last_native_data / 100.0); + sensor->flags.data_ok = sensor->last_native_data < 10000; + DEBUG_PRINT(F(" soil moisture %: ")); + break; + case SENSOR_TH100_TEMP: + case SENSOR_SMT100_TEMP: // Equation: temperature [°C]= + // (16Bit_temperature_value / 100)-100 + sensor->last_data = + ((double)sensor->last_native_data / 100.0) - 100.0; + sensor->flags.data_ok = sensor->last_native_data > 7000; + DEBUG_PRINT(F(" temperature °C: ")); + break; + case SENSOR_SMT100_PMTY: // permittivity + sensor->last_data = ((double)sensor->last_native_data / 100.0); + sensor->flags.data_ok = true; + DEBUG_PRINT(F(" permittivity DK: ")); + break; + } + DEBUG_PRINTLN(sensor->last_data); + return sensor->flags.data_ok ? HTTP_RQT_SUCCESS : HTTP_RQT_NOT_RECEIVED; + } + + return HTTP_RQT_NOT_RECEIVED; +} + +#if defined(OSPI) +int read_internal_raspi(Sensor_t *sensor, ulong time) { + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + char buf[10]; + size_t res = 0; + FILE *fp = fopen("/sys/class/thermal/thermal_zone0/temp", "r"); + if(fp) { + res = fread(buf, 1, 10, fp); + fclose(fp); + } + if (!res) + return HTTP_RQT_NOT_RECEIVED; + + sensor->last_read = time; + sensor->last_native_data = strtol(buf, NULL, 0); + sensor->last_data = (double)sensor->last_native_data / 1000; + sensor->flags.data_ok = true; + + return HTTP_RQT_SUCCESS; +} +#endif +/** + * read a sensor + */ +int read_sensor(Sensor_t *sensor, ulong time) { + if (!sensor || !sensor->flags.enable) return HTTP_RQT_NOT_RECEIVED; + + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + case SENSOR_SMT100_TEMP: + case SENSOR_SMT100_PMTY: + case SENSOR_TH100_MOIS: + case SENSOR_TH100_TEMP: + //DEBUG_PRINT(F("Reading sensor ")); + //DEBUG_PRINTLN(sensor->name); + sensor->last_read = time; + if (sensor->ip) return read_sensor_ip(sensor); + return read_sensor_rs485(sensor); + break; + +#if defined(ARDUINO) +#if defined(ESP8266) + case SENSOR_ANALOG_EXTENSION_BOARD: + case SENSOR_ANALOG_EXTENSION_BOARD_P: + case SENSOR_SMT50_MOIS: // SMT50 VWC [%] = (U * 50) : 3 + case SENSOR_SMT50_TEMP: // SMT50 T [°C] = (U – 0,5) * 100 + case SENSOR_SMT100_ANALOG_MOIS: // SMT100 Analog Moisture + case SENSOR_SMT100_ANALOG_TEMP: // SMT100 Analog Temperature + case SENSOR_VH400: + case SENSOR_THERM200: + case SENSOR_AQUAPLUMB: + case SENSOR_USERDEF: + //DEBUG_PRINT(F("Reading sensor ")); + //DEBUG_PRINTLN(sensor->name); + return read_sensor_adc(sensor, time); + case SENSOR_FREE_MEMORY: + { + uint32_t fm = freeMemory(); + if (sensor->last_native_data == fm) + return HTTP_RQT_NOT_RECEIVED; + sensor->last_native_data = fm; + sensor->last_data = fm; + sensor->last_read = time; + sensor->flags.data_ok = true; + return HTTP_RQT_SUCCESS; + } + case SENSOR_FREE_STORE: { + struct FSInfo fsinfo; + boolean ok = LittleFS.info(fsinfo); + if (ok) { + uint32_t fd = fsinfo.totalBytes-fsinfo.usedBytes; + if (sensor->last_native_data == fd) + return HTTP_RQT_NOT_RECEIVED; + sensor->last_native_data = fd; + sensor->last_data = fd; + } + sensor->flags.data_ok = ok; + sensor->last_read = time; + return HTTP_RQT_SUCCESS; + } +#endif +#else +#if defined ADS1115 | PCF8591 + case SENSOR_OSPI_ANALOG: + case SENSOR_OSPI_ANALOG_P: + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + return read_sensor_ospi(sensor, time); +#endif +#if defined(OSPI) + case SENSOR_OSPI_INTERNAL_TEMP: + return read_internal_raspi(sensor, time); +#endif +#endif + case SENSOR_REMOTE: + return read_sensor_http(sensor, time); + case SENSOR_MQTT: + sensor->last_read = time; + return read_sensor_mqtt(sensor); + + case SENSOR_WEATHER_TEMP_F: + case SENSOR_WEATHER_TEMP_C: + case SENSOR_WEATHER_HUM: + case SENSOR_WEATHER_PRECIP_IN: + case SENSOR_WEATHER_PRECIP_MM: + case SENSOR_WEATHER_WIND_MPH: + case SENSOR_WEATHER_WIND_KMH: { + GetSensorWeather(); + if (current_weather_ok) { + DEBUG_PRINT(F("Reading sensor ")); + DEBUG_PRINTLN(sensor->name); + + sensor->last_read = time; + sensor->last_native_data = 0; + sensor->flags.data_ok = true; + + switch (sensor->type) { + case SENSOR_WEATHER_TEMP_F: { + sensor->last_data = current_temp; + break; + } + case SENSOR_WEATHER_TEMP_C: { + sensor->last_data = (current_temp - 32.0) / 1.8; + break; + } + case SENSOR_WEATHER_HUM: { + sensor->last_data = current_humidity; + break; + } + case SENSOR_WEATHER_PRECIP_IN: { + sensor->last_data = current_precip; + break; + } + case SENSOR_WEATHER_PRECIP_MM: { + sensor->last_data = current_precip * 25.4; + break; + } + case SENSOR_WEATHER_WIND_MPH: { + sensor->last_data = current_wind; + break; + } + case SENSOR_WEATHER_WIND_KMH: { + sensor->last_data = current_wind * 1.609344; + break; + } + } + return HTTP_RQT_SUCCESS; + } + } + } + return HTTP_RQT_NOT_RECEIVED; +} + +/** + * @brief Update group values + * + */ +void sensor_update_groups() { + Sensor_t *sensor = sensors; + + ulong time = os.now_tz(); + + while (sensor) { + if (time >= sensor->last_read + sensor->read_interval) { + switch (sensor->type) { + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: { + uint nr = sensor->nr; + Sensor_t *group = sensors; + double value = 0; + int n = 0; + while (group) { + if (group->nr != nr && group->group == nr && + group->flags.enable) { // && group->flags.data_ok) { + switch (sensor->type) { + case SENSOR_GROUP_MIN: + if (n++ == 0) + value = group->last_data; + else if (group->last_data < value) + value = group->last_data; + break; + case SENSOR_GROUP_MAX: + if (n++ == 0) + value = group->last_data; + else if (group->last_data > value) + value = group->last_data; + break; + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + n++; + value += group->last_data; + break; + } + } + group = group->next; + } + if (sensor->type == SENSOR_GROUP_AVG && n > 0) { + value = value / (double)n; + } + sensor->last_data = value; + sensor->last_native_data = 0; + sensor->last_read = time; + sensor->flags.data_ok = n > 0; + sensorlog_add(LOG_STD, sensor, time); + break; + } + } + } + sensor = sensor->next; + } +} + +int set_sensor_address_ip(Sensor_t *sensor, uint8_t new_address) { +#if defined(ARDUINO) + Client *client; +#if defined(ESP8266) + WiFiClient wifiClient; + client = &wifiClient; +#else + EthernetClient etherClient; + client = ðerClient; +#endif +#else + EthernetClient etherClient; + EthernetClient *client = ðerClient; +#endif +#if defined(ESP8266) + IPAddress _ip(sensor->ip); + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; +#else + unsigned char ip[4]; + ip[3] = (unsigned char)((sensor->ip >> 24) & 0xFF); + ip[2] = (unsigned char)((sensor->ip >> 16) & 0xFF); + ip[1] = (unsigned char)((sensor->ip >> 8) & 0xFF); + ip[0] = (unsigned char)((sensor->ip & 0xFF)); +#endif + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + client->setTimeout(200); + if (!client->connect(server, sensor->port)) { + DEBUG_PRINT(F("Cannot connect to ")); + DEBUG_PRINT(server); + DEBUG_PRINT(":"); + DEBUG_PRINTLN(sensor->port); + client->stop(); + return HTTP_RQT_CONNECT_ERR; + } + + uint8_t buffer[20]; + + // https://ipc2u.com/articles/knowledge-base/detailed-description-of-the-modbus-tcp-protocol-with-command-examples/ + + if (modbusTcpId >= 0xFFFE) + modbusTcpId = 1; + else + modbusTcpId++; + + buffer[0] = (0xFF00 & modbusTcpId) >> 8; + buffer[1] = (0x00FF & modbusTcpId); + buffer[2] = 0; + buffer[3] = 0; + buffer[4] = 0; + buffer[5] = 6; // len + buffer[6] = sensor->id; + buffer[7] = 0x06; + buffer[8] = 0x00; + buffer[9] = 0x04; + buffer[10] = 0x00; + buffer[11] = new_address; + + client->write(buffer, 12); +#if defined(ESP8266) + client->flush(); +#endif + + // Read result: + int n = client->read(buffer, 12); + client->stop(); + DEBUG_PRINT(F("Sensor ")); + DEBUG_PRINT(sensor->nr); + if (n != 12) { + DEBUG_PRINT(F(" returned ")); + DEBUG_PRINT(n); + DEBUG_PRINT(F(" bytes??")); + return n == 0 ? HTTP_RQT_EMPTY_RETURN : HTTP_RQT_TIMEOUT; + } + if (buffer[0] != (0xFF00 & modbusTcpId) >> 8 || + buffer[1] != (0x00FF & modbusTcpId)) { + DEBUG_PRINT(F(" returned transaction id ")); + DEBUG_PRINTLN((uint16_t)((buffer[0] << 8) + buffer[1])); + return HTTP_RQT_NOT_RECEIVED; + } + if ((buffer[6] != sensor->id && sensor->id != 253)) { // 253 is broadcast + DEBUG_PRINT(F(" returned sensor id ")); + DEBUG_PRINT((int)buffer[0]); + return HTTP_RQT_NOT_RECEIVED; + } + // Read OK: + sensor->id = new_address; + sensor_save(); + return HTTP_RQT_SUCCESS; +} + +#ifdef ESP8266 +/** + * @brief Set the sensor address i2c + * + * @param sensor + * @param new_address + * @return int + */ +int set_sensor_address_rs485(Sensor_t *sensor, uint8_t new_address) { + DEBUG_PRINTLN(F("set_sensor_address_rs485")); + int device = sensor->port; + if (device >= MAX_RS485_DEVICES || (asb_detected_boards & (RS485_TRUEBNER1 << device)) == 0) + return HTTP_RQT_NOT_RECEIVED; + + if (i2c_rs485_allocated[device] > 0) { + DEBUG_PRINT(F("sensor currently allocated by ")); + DEBUG_PRINTLN(i2c_rs485_allocated[device]); + Sensor_t *t = sensor_by_nr(i2c_rs485_allocated[device]); + if (!t || !t->flags.enable) + i2c_rs485_allocated[device] = 0; //breakout + return HTTP_RQT_NOT_RECEIVED; + } + + Wire.beginTransmission(RS485_TRUEBNER1_ADDR + device); + Wire.write(254); + Wire.write(new_address); + Wire.endTransmission(); + delay(3000); + Wire.requestFrom((uint8_t)(RS485_TRUEBNER1_ADDR + device), (size_t)1, true); + if (Wire.available()) { + delay(10); + uint8_t modbus_address = Wire.read(); + if (modbus_address == new_address) return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} +#else +/** + * @brief Raspberry PI RS485 Interface - Set SMT100 Sensor address + * + * @param sensor + * @return int + */ +int set_sensor_address_rs485(Sensor_t *sensor, uint8_t new_address) { + DEBUG_PRINTLN(F("set_sensor_address_rs485")); + int device = sensor->port; + if (device >= MAX_RS485_DEVICES || !ttyDevices[device]) + return HTTP_RQT_NOT_RECEIVED; + + + uint8_t request[10]; + request[0] = 0xFD; //253=Truebner Broadcast + request[1] = 0x06; //Write + request[2] = 0x00; + request[3] = 0x04; //Register address + request[4] = 0x00; + request[5] = new_address; + + if (modbus_send_raw_request(ttyDevices[device], request, 6) > 0) { + modbus_flush(ttyDevices[device]); + return HTTP_RQT_SUCCESS; + } + return HTTP_RQT_NOT_RECEIVED; +} + +#endif + +int set_sensor_address(Sensor_t *sensor, uint8_t new_address) { + if (!sensor) return HTTP_RQT_NOT_RECEIVED; + + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + case SENSOR_SMT100_TEMP: + case SENSOR_SMT100_PMTY: + case SENSOR_TH100_MOIS: + case SENSOR_TH100_TEMP: + sensor->flags.data_ok = false; + if (sensor->ip && sensor->port) + return set_sensor_address_ip(sensor, new_address); + return set_sensor_address_rs485(sensor, new_address); + } + return HTTP_RQT_CONNECT_ERR; +} + +double calc_linear(ProgSensorAdjust_t *p, double sensorData) { + // min max factor1 factor2 + // 10..90 -> 5..1 factor1 > factor2 + // a b c d + // (b-sensorData) / (b-a) * (c-d) + d + // + // 10..90 -> 1..5 factor1 < factor2 + // a b c d + // (sensorData-a) / (b-a) * (d-c) + c + + // Limit to min/max: + if (sensorData < p->min) sensorData = p->min; + if (sensorData > p->max) sensorData = p->max; + + // Calculate: + double div = (p->max - p->min); + if (abs(div) < 0.00001) return 0; + + if (p->factor1 > p->factor2) { // invers scaling factor: + return (p->max - sensorData) / div * (p->factor1 - p->factor2) + p->factor2; + } else { // upscaling factor: + return (sensorData - p->min) / div * (p->factor2 - p->factor1) + p->factor1; + } +} + +double calc_digital_min(ProgSensorAdjust_t *p, double sensorData) { + return sensorData <= p->min ? p->factor1 : p->factor2; +} + +double calc_digital_max(ProgSensorAdjust_t *p, double sensorData) { + return sensorData >= p->max ? p->factor2 : p->factor1; +} + +double calc_digital_minmax(ProgSensorAdjust_t *p, double sensorData) { + if (sensorData <= p->min) return p->factor1; + if (sensorData >= p->max) return p->factor1; + return p->factor2; +} +/** + * @brief calculate adjustment + * + * @param prog + * @return double + */ +double calc_sensor_watering(uint prog) { + double result = 1; + ProgSensorAdjust_t *p = progSensorAdjusts; + + while (p) { + if (p->prog - 1 == prog) { + Sensor_t *sensor = sensor_by_nr(p->sensor); + if (sensor && sensor->flags.enable && sensor->flags.data_ok) { + double res = calc_sensor_watering_int(p, sensor->last_data); + result = result * res; + } + } + + p = p->next; + } + if (result < 0.0) result = 0.0; + if (result > 20.0) // Factor 20 is a huge value! + result = 20.0; + return result; +} + +double calc_sensor_watering_int(ProgSensorAdjust_t *p, double sensorData) { + double res = 0; + if (!p) return res; + switch (p->type) { + case PROG_NONE: + res = 1; + break; + case PROG_LINEAR: + res = calc_linear(p, sensorData); + break; + case PROG_DIGITAL_MIN: + res = calc_digital_min(p, sensorData); + break; + case PROG_DIGITAL_MAX: + res = calc_digital_max(p, sensorData); + break; + case PROG_DIGITAL_MINMAX: + res = calc_digital_minmax(p, sensorData); + break; + default: + res = 0; + } + return res; +} + +/** + * @brief calculate adjustment + * + * @param nr + * @return double + */ +double calc_sensor_watering_by_nr(uint nr) { + double result = 1; + ProgSensorAdjust_t *p = progSensorAdjusts; + + while (p) { + if (p->nr == nr) { + Sensor_t *sensor = sensor_by_nr(p->sensor); + if (sensor && sensor->flags.enable && sensor->flags.data_ok) { + double res = 0; + switch (p->type) { + case PROG_NONE: + res = 1; + break; + case PROG_LINEAR: + res = calc_linear(p, sensor->last_data); + break; + case PROG_DIGITAL_MIN: + res = calc_digital_min(p, sensor->last_data); + break; + case PROG_DIGITAL_MAX: + res = calc_digital_max(p, sensor->last_data); + break; + default: + res = 0; + } + + result = result * res; + } + break; + } + + p = p->next; + } + + return result; +} + +int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, + double factor1, double factor2, double min, double max, char * name) { + ProgSensorAdjust_t *p = progSensorAdjusts; + + ProgSensorAdjust_t *last = NULL; + + while (p) { + if (p->nr == nr) { + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->factor1 = factor1; + p->factor2 = factor2; + p->min = min; + p->max = max; + strncpy(p->name, name, sizeof(p->name)); + prog_adjust_save(); + return HTTP_RQT_SUCCESS; + } + + if (p->nr > nr) break; + + last = p; + p = p->next; + } + + p = new ProgSensorAdjust_t; + p->nr = nr; + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->factor1 = factor1; + p->factor2 = factor2; + p->min = min; + p->max = max; + strncpy(p->name, name, sizeof(p->name)); + if (last) { + p->next = last->next; + last->next = p; + } else { + p->next = progSensorAdjusts; + progSensorAdjusts = p; + } + + prog_adjust_save(); + return HTTP_RQT_SUCCESS; +} + +int prog_adjust_delete(uint nr) { + ProgSensorAdjust_t *p = progSensorAdjusts; + + ProgSensorAdjust_t *last = NULL; + + while (p) { + if (p->nr == nr) { + if (last) + last->next = p->next; + else + progSensorAdjusts = p->next; + delete p; + prog_adjust_save(); + return HTTP_RQT_SUCCESS; + } + last = p; + p = p->next; + } + return HTTP_RQT_NOT_RECEIVED; +} + +void prog_adjust_save() { + if (!apiInit) return; + if (file_exists(PROG_SENSOR_FILENAME)) remove_file(PROG_SENSOR_FILENAME); + + ulong pos = 0; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + file_write_block(PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); + pa = pa->next; + pos += PROGSENSOR_STORE_SIZE; + } +} + +void prog_adjust_load() { + DEBUG_PRINTLN(F("prog_adjust_load")); + progSensorAdjusts = NULL; + if (!file_exists(PROG_SENSOR_FILENAME)) return; + + ulong pos = 0; + ProgSensorAdjust_t *last = NULL; + while (true) { + ProgSensorAdjust_t *pa = new ProgSensorAdjust_t; + memset(pa, 0, sizeof(ProgSensorAdjust_t)); + file_read_block(PROG_SENSOR_FILENAME, pa, pos, PROGSENSOR_STORE_SIZE); + if (!pa->nr || !pa->type) { + delete pa; + break; + } + if (!last) + progSensorAdjusts = pa; + else + last->next = pa; + last = pa; + pa->next = NULL; + pos += PROGSENSOR_STORE_SIZE; + } +} + +uint prog_adjust_count() { + uint count = 0; + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + count++; + pa = pa->next; + } + return count; +} + +ProgSensorAdjust_t *prog_adjust_by_nr(uint nr) { + ProgSensorAdjust_t *pa = progSensorAdjusts; + while (pa) { + if (pa->nr == nr) return pa; + pa = pa->next; + } + return NULL; +} + +ProgSensorAdjust_t *prog_adjust_by_idx(uint idx) { + ProgSensorAdjust_t *pa = progSensorAdjusts; + uint idxCounter = 0; + while (pa) { + if (idxCounter++ == idx) return pa; + pa = pa->next; + } + return NULL; +} + +#if defined(ESP8266) +ulong diskFree() { + struct FSInfo fsinfo; + LittleFS.info(fsinfo); + return fsinfo.totalBytes - fsinfo.usedBytes; +} + +bool checkDiskFree() { + if (diskFree() < MIN_DISK_FREE) { + DEBUG_PRINT(F("fs has low space!")); + return false; + } + return true; +} +#endif + +const char *getSensorUnit(int unitid) { + if (unitid == UNIT_USERDEF) return "?"; + if (unitid < 0 || (uint16_t)unitid >= sizeof(sensor_unitNames)) + return sensor_unitNames[0]; + return sensor_unitNames[unitid]; +} + +const char *getSensorUnit(Sensor_t *sensor) { + if (!sensor) return sensor_unitNames[0]; + + int unitid = getSensorUnitId(sensor); + if (unitid == UNIT_USERDEF) return sensor->userdef_unit; + if (unitid < 0 || (uint16_t)unitid >= sizeof(sensor_unitNames)) + return sensor_unitNames[0]; + return sensor_unitNames[unitid]; +} + +boolean sensor_isgroup(Sensor_t *sensor) { + if (!sensor) return false; + + switch (sensor->type) { + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + return true; + + default: + return false; + } +} + +unsigned char getSensorUnitId(int type) { + switch (type) { + case SENSOR_SMT100_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT100_TEMP: + return UNIT_DEGREE; + case SENSOR_SMT100_PMTY: + return UNIT_DK; + case SENSOR_TH100_MOIS: + return UNIT_HUM_PERCENT; + case SENSOR_TH100_TEMP: + return UNIT_DEGREE; +#if defined(ARDUINO) +#if defined(ESP8266) + case SENSOR_ANALOG_EXTENSION_BOARD: + return UNIT_VOLT; + case SENSOR_ANALOG_EXTENSION_BOARD_P: + return UNIT_PERCENT; + case SENSOR_SMT50_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT50_TEMP: + return UNIT_DEGREE; + case SENSOR_SMT100_ANALOG_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT100_ANALOG_TEMP: + return UNIT_DEGREE; + + case SENSOR_VH400: + return UNIT_PERCENT; + case SENSOR_THERM200: + return UNIT_DEGREE; + case SENSOR_AQUAPLUMB: + return UNIT_PERCENT; + case SENSOR_USERDEF: + case SENSOR_FREE_MEMORY: + case SENSOR_FREE_STORE: + return UNIT_USERDEF; +#endif +#else + case SENSOR_OSPI_ANALOG: + return UNIT_VOLT; + case SENSOR_OSPI_ANALOG_P: + return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + case SENSOR_OSPI_INTERNAL_TEMP: + return UNIT_DEGREE; +#endif + case SENSOR_MQTT: + return UNIT_USERDEF; + case SENSOR_WEATHER_TEMP_F: + return UNIT_FAHRENHEIT; + case SENSOR_WEATHER_TEMP_C: + return UNIT_DEGREE; + case SENSOR_WEATHER_HUM: + return UNIT_HUM_PERCENT; + case SENSOR_WEATHER_PRECIP_IN: + return UNIT_INCH; + case SENSOR_WEATHER_PRECIP_MM: + return UNIT_MM; + case SENSOR_WEATHER_WIND_MPH: + return UNIT_MPH; + case SENSOR_WEATHER_WIND_KMH: + return UNIT_KMH; + + default: + return UNIT_NONE; + } +} + +unsigned char getSensorUnitId(Sensor_t *sensor) { + if (!sensor) return 0; + + switch (sensor->type) { + case SENSOR_SMT100_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT100_TEMP: + return UNIT_DEGREE; + case SENSOR_SMT100_PMTY: + return UNIT_DK; + case SENSOR_TH100_MOIS: + return UNIT_HUM_PERCENT; + case SENSOR_TH100_TEMP: + return UNIT_DEGREE; +#if defined(ARDUINO) +#if defined(ESP8266) + case SENSOR_ANALOG_EXTENSION_BOARD: + return UNIT_VOLT; + case SENSOR_ANALOG_EXTENSION_BOARD_P: + return UNIT_LEVEL; + case SENSOR_SMT50_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT50_TEMP: + return UNIT_DEGREE; + case SENSOR_SMT100_ANALOG_MOIS: + return UNIT_PERCENT; + case SENSOR_SMT100_ANALOG_TEMP: + return UNIT_DEGREE; + + case SENSOR_VH400: + return UNIT_PERCENT; + case SENSOR_THERM200: + return UNIT_DEGREE; + case SENSOR_AQUAPLUMB: + return UNIT_PERCENT; + case SENSOR_FREE_MEMORY: + case SENSOR_FREE_STORE: + return UNIT_USERDEF; +#endif +#else + case SENSOR_OSPI_ANALOG: + return UNIT_VOLT; + case SENSOR_OSPI_ANALOG_P: + return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_MOIS: + return UNIT_PERCENT; + case SENSOR_OSPI_ANALOG_SMT50_TEMP: + case SENSOR_OSPI_INTERNAL_TEMP: + return UNIT_DEGREE; +#endif + case SENSOR_USERDEF: + case SENSOR_MQTT: + case SENSOR_REMOTE: + return sensor->assigned_unitid > 0 ? sensor->assigned_unitid + : UNIT_USERDEF; + + case SENSOR_WEATHER_TEMP_F: + return UNIT_FAHRENHEIT; + case SENSOR_WEATHER_TEMP_C: + return UNIT_DEGREE; + case SENSOR_WEATHER_HUM: + return UNIT_HUM_PERCENT; + case SENSOR_WEATHER_PRECIP_IN: + return UNIT_INCH; + case SENSOR_WEATHER_PRECIP_MM: + return UNIT_MM; + case SENSOR_WEATHER_WIND_MPH: + return UNIT_MPH; + case SENSOR_WEATHER_WIND_KMH: + return UNIT_KMH; + + case SENSOR_GROUP_MIN: + case SENSOR_GROUP_MAX: + case SENSOR_GROUP_AVG: + case SENSOR_GROUP_SUM: + + for (int i = 0; i < 100; i++) { + Sensor_t *sen = sensors; + while (sen) { + if (sen != sensor && sen->group > 0 && sen->group == sensor->nr) { + if (!sensor_isgroup(sen)) return getSensorUnitId(sen); + sensor = sen; + break; + } + sen = sen->next; + } + } + + default: + return UNIT_NONE; + } +} + +void GetSensorWeather() { +#if defined(ESP8266) + if (!useEth) + if (os.state != OS_STATE_CONNECTED || WiFi.status() != WL_CONNECTED) return; +#endif + time_t time = os.now_tz(); + if (last_weather_time == 0) last_weather_time = time - 59 * 60; + + if (time < last_weather_time + 60 * 60) return; + + // use temp buffer to construct get command + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE); + bf.emit_p(PSTR("weatherData?loc=$O&wto=$O"), SOPT_LOCATION, + SOPT_WEATHER_OPTS); + + char *src = tmp_buffer + strlen(tmp_buffer); + char *dst = tmp_buffer + TMP_BUFFER_SIZE - 12; + + char c; + // url encode. convert SPACE to %20 + // copy reversely from the end because we are potentially expanding + // the string size + while (src != tmp_buffer) { + c = *src--; + if (c == ' ') { + *dst-- = '0'; + *dst-- = '2'; + *dst-- = '%'; + } else { + *dst-- = c; + } + }; + *dst = *src; + + strcpy(ether_buffer, "GET /"); + strcat(ether_buffer, dst); + // because dst is part of tmp_buffer, + // must load weather url AFTER dst is copied to ether_buffer + + // load weather url to tmp_buffer + char *host = tmp_buffer; + os.sopt_load(SOPT_WEATHERURL, host); + + strcat(ether_buffer, " HTTP/1.0\r\nHOST: "); + strcat(ether_buffer, host); + strcat(ether_buffer, "\r\n\r\n"); + + DEBUG_PRINTLN(F("GetSensorWeather")); + DEBUG_PRINTLN(ether_buffer); + + last_weather_time = time; + int ret = os.send_http_request(host, ether_buffer, NULL, false, 500); + if (ret == HTTP_RQT_SUCCESS) { + DEBUG_PRINTLN(ether_buffer); + + char buf[20]; + char *s = strstr(ether_buffer, "\"temp\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_temp = atof(buf); + } + s = strstr(ether_buffer, "\"humidity\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_humidity = atof(buf); + } + s = strstr(ether_buffer, "\"precip\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_precip = atof(buf); + } + s = strstr(ether_buffer, "\"wind\":"); + if (s && extract(s, buf, sizeof(buf))) { + current_wind = atof(buf); + } +#ifdef ENABLE_DEBUG + char tmp[10]; + DEBUG_PRINT("temp: "); + dtostrf(current_temp, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("humidity: "); + dtostrf(current_humidity, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("precip: "); + dtostrf(current_precip, 2, 2, tmp); + DEBUG_PRINTLN(tmp) + DEBUG_PRINT("wind: "); + dtostrf(current_wind, 2, 2, tmp); + DEBUG_PRINTLN(tmp) +#endif + current_weather_ok = true; + } else { + current_weather_ok = false; + } +} + +void SensorUrl_load() { + sensorUrls = NULL; + DEBUG_PRINTLN("SensorUrl_load1"); + if (!file_exists(SENSORURL_FILENAME)) return; + + DEBUG_PRINTLN("SensorUrl_load2"); + ulong pos = 0; + SensorUrl_t *last = NULL; + while (true) { + SensorUrl_t *sensorUrl = new SensorUrl_t; + memset(sensorUrl, 0, sizeof(SensorUrl_t)); + if (file_read_block(SENSORURL_FILENAME, sensorUrl, pos, + SENSORURL_STORE_SIZE) < SENSORURL_STORE_SIZE) { + delete sensorUrl; + break; + } + sensorUrl->urlstr = (char *)malloc(sensorUrl->length + 1); + pos += SENSORURL_STORE_SIZE; + ulong result = file_read_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, + sensorUrl->length); + if (result != sensorUrl->length) { + free(sensorUrl->urlstr); + delete sensorUrl; + break; + } + + sensorUrl->urlstr[sensorUrl->length] = 0; + pos += sensorUrl->length; + + DEBUG_PRINT(sensorUrl->nr); + DEBUG_PRINT("/"); + DEBUG_PRINT(sensorUrl->type); + DEBUG_PRINT(": "); + DEBUG_PRINTLN(sensorUrl->urlstr); + + if (!last) + sensorUrls = sensorUrl; + else + last->next = sensorUrl; + last = sensorUrl; + sensorUrl->next = NULL; + } + DEBUG_PRINTLN("SensorUrl_load3"); +} + +void SensorUrl_save() { + if (!apiInit) return; + if (file_exists(SENSORURL_FILENAME)) remove_file(SENSORURL_FILENAME); + + ulong pos = 0; + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + file_write_block(SENSORURL_FILENAME, sensorUrl, pos, SENSORURL_STORE_SIZE); + pos += SENSORURL_STORE_SIZE; + file_write_block(SENSORURL_FILENAME, sensorUrl->urlstr, pos, + sensorUrl->length); + pos += sensorUrl->length; + + sensorUrl = sensorUrl->next; + } +} + +bool SensorUrl_delete(uint nr, uint type) { + SensorUrl_t *sensorUrl = sensorUrls; + SensorUrl_t *last = NULL; + while (sensorUrl) { + if (sensorUrl->nr == nr && sensorUrl->type == type) { + if (last) + last->next = sensorUrl->next; + else + sensorUrls = sensorUrl->next; + + sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); + + free(sensorUrl->urlstr); + delete sensorUrl; + SensorUrl_save(); + return true; + } + last = sensorUrl; + sensorUrl = sensorUrl->next; + } + return false; +} + +bool SensorUrl_add(uint nr, uint type, const char *urlstr) { + if (!urlstr || !strlen(urlstr)) { // empty string? delete! + return SensorUrl_delete(nr, type); + } + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + if (sensorUrl->nr == nr && sensorUrl->type == type) { // replace existing + sensor_mqtt_unsubscribe(nr, type, sensorUrl->urlstr); + free(sensorUrl->urlstr); + sensorUrl->length = strlen(urlstr); + sensorUrl->urlstr = strdup(urlstr); + SensorUrl_save(); + sensor_mqtt_subscribe(nr, type, urlstr); + return true; + } + sensorUrl = sensorUrl->next; + } + + // Add new: + sensorUrl = new SensorUrl_t; + memset(sensorUrl, 0, sizeof(SensorUrl_t)); + sensorUrl->nr = nr; + sensorUrl->type = type; + sensorUrl->length = strlen(urlstr); + sensorUrl->urlstr = strdup(urlstr); + sensorUrl->next = sensorUrls; + sensorUrls = sensorUrl; + SensorUrl_save(); + + sensor_mqtt_subscribe(nr, type, urlstr); + + return true; +} + +char *SensorUrl_get(uint nr, uint type) { + SensorUrl_t *sensorUrl = sensorUrls; + while (sensorUrl) { + if (sensorUrl->nr == nr && sensorUrl->type == type) // replace existing + return sensorUrl->urlstr; + sensorUrl = sensorUrl->next; + } + return NULL; +} + +/** + * @brief Write data to influx db + * + * @param sensor + * @param log + */ +void add_influx_data(Sensor_t *sensor) { + if (!os.influxdb.isEnabled()) + return; + + #if defined(ESP8266) + Point sensor_data("analogsensor"); + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + sensor_data.addTag("devicename", tmp_buffer); + snprintf(tmp_buffer, 10, "%d", sensor->nr); + sensor_data.addTag("nr", tmp_buffer); + sensor_data.addTag("name", sensor->name); + sensor_data.addTag("unit", getSensorUnit(sensor)); + + sensor_data.addField("native_data", sensor->last_native_data); + sensor_data.addField("data", sensor->last_data); + + os.influxdb.write_influx_data(sensor_data); + + #else + +/* +influxdb_cpp::server_info si("127.0.0.1", 8086, "db", "usr", "pwd"); +influxdb_cpp::builder() + .meas("foo") + .tag("k", "v") + .tag("x", "y") + .field("x", 10) + .field("y", 10.3, 2) + .field("z", 10.3456) + .field("b", !!10) + .timestamp(1512722735522840439) + .post_http(si); +*/ + influxdb_cpp::server_info * client = os.influxdb.get_client(); + if (!client) + return; + + os.sopt_load(SOPT_DEVICE_NAME, tmp_buffer); + char nr_buf[10]; + snprintf(nr_buf, 10, "%d", sensor->nr); + influxdb_cpp::builder() + .meas("analogsensor") + .tag("devicename", tmp_buffer) + .tag("nr", nr_buf) + .tag("name", sensor->name) + .tag("unit", getSensorUnit(sensor)) + .field("native_data", (long)sensor->last_native_data) + .field("data", sensor->last_data, 2) + .timestamp(millis()) + .post_http(*client); + + #endif +} + + +//Value Monitoring +void monitor_load() { + DEBUG_PRINTLN(F("monitor_load")); + monitors = NULL; + if (!file_exists(MONITOR_FILENAME)) return; + if (file_size(MONITOR_FILENAME) % MONITOR_STORE_SIZE != 0) return; + + ulong pos = 0; + Monitor_t *last = NULL; + while (true) { + Monitor_t *mon = new Monitor_t; + memset(mon, 0, sizeof(Monitor_t)); + file_read_block(MONITOR_FILENAME, mon, pos, MONITOR_STORE_SIZE); + if (!mon->nr || !mon->type) { + delete mon; + break; + } + if (!last) + monitors = mon; + else + last->next = mon; + last = mon; + mon->next = NULL; + pos += MONITOR_STORE_SIZE; + } +} + +void monitor_save() { + if (!apiInit) return; + if (file_exists(MONITOR_FILENAME)) remove_file(MONITOR_FILENAME); + + ulong pos = 0; + Monitor_t *mon = monitors; + while (mon) { + file_write_block(MONITOR_FILENAME, mon, pos, MONITOR_STORE_SIZE); + mon = mon->next; + pos += MONITOR_STORE_SIZE; + } +} + +int monitor_count() { + int count = 0; + Monitor_t *mon = monitors; + while (mon) { + mon = mon->next; + count++; + } + return count; +} + +int monitor_delete(uint nr) { + Monitor_t *p = monitors; + + Monitor_t *last = NULL; + + while (p) { + if (p->nr == nr) { + if (last) + last->next = p->next; + else + monitors = p->next; + delete p; + monitor_save(); + return HTTP_RQT_SUCCESS; + } + last = p; + p = p->next; + } + return HTTP_RQT_NOT_RECEIVED; +} + +bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, const Monitor_Union_t m, char * name, ulong maxRuntime, uint8_t prio) { + Monitor_t *p = monitors; + + Monitor_t *last = NULL; + + while (p) { + if (p->nr == nr) { + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->zone = zone; + p->m = m; + //p->active = false; + p->maxRuntime = maxRuntime; + p->prio = prio; + strncpy(p->name, name, sizeof(p->name)-1); + monitor_save(); + check_monitors(); + return HTTP_RQT_SUCCESS; + } + + if (p->nr > nr) break; + + last = p; + p = p->next; + } + + p = new Monitor_t; + p->nr = nr; + p->type = type; + p->sensor = sensor; + p->prog = prog; + p->zone = zone; + p->m = m; + p->active = false; + p->maxRuntime = maxRuntime; + p->prio = prio; + strncpy(p->name, name, sizeof(p->name)-1); + if (last) { + p->next = last->next; + last->next = p; + } else { + p->next = monitors; + monitors = p; + } + + monitor_save(); + check_monitors(); + return HTTP_RQT_SUCCESS; +} + +Monitor_t *monitor_by_nr(uint nr) { + Monitor_t *mon = monitors; + while (mon) { + if (mon->nr == nr) return mon; + mon = mon->next; + } + return NULL; +} + +Monitor_t *monitor_by_idx(uint idx) { + Monitor_t *mon = monitors; + uint idxCounter = 0; + while (mon) { + if (idxCounter++ == idx) return mon; + mon = mon->next; + } + return NULL; +} + +void manual_start_program(unsigned char, unsigned char); +void schedule_all_stations(time_os_t curr_time); +void turn_off_station(unsigned char sid, time_os_t curr_time, unsigned char shift=0); +void push_message(uint16_t type, uint32_t lval=0, float fval=0.f, const char* sval=NULL); + +void start_monitor_action(Monitor_t * mon) { + mon->time = os.now_tz(); + if (mon->prog > 0) + manual_start_program(mon->prog, 255); + + if (mon->zone > 0) { + uint sid = mon->zone-1; + + // schedule manual station + // skip if the station is a master station + // (because master cannot be scheduled independently) + if ((os.status.mas==sid+1) || (os.status.mas2==sid+1)) + return; + + uint16_t timer=mon->maxRuntime; + RuntimeQueueStruct *q = NULL; + unsigned char sqi = pd.station_qid[sid]; + // check if the station already has a schedule + if (sqi!=0xFF) { // if so, we will overwrite the schedule + q = pd.queue+sqi; + } else { // otherwise create a new queue element + q = pd.enqueue(); + } + // if the queue is not full + if (q) { + q->st = 0; + q->dur = timer; + q->sid = sid; + q->pid = 253; + schedule_all_stations(mon->time); + } + } +} + +void stop_monitor_action(Monitor_t * mon) { + mon->time = os.now_tz(); + if (mon->zone > 0) { + int sid = mon->zone-1; + RuntimeQueueStruct *q = pd.queue + pd.station_qid[sid]; + if (q) { + q->deque_time = mon->time; + turn_off_station(sid, mon->time); + } + } +} + +void push_message(Monitor_t * mon, float value) { + uint16_t type; + switch(mon->prio) { + case 0: type = NOTIFY_MONITOR_LOW; break; + case 1: type = NOTIFY_MONITOR_MID; break; + case 2: type = NOTIFY_MONITOR_HIGH; break; + default: return; + } + char name[30]; + strncpy(name, mon->name, sizeof(name)-1); + DEBUG_PRINT("monitoring: activated "); + DEBUG_PRINT(name); + DEBUG_PRINT(" - "); + DEBUG_PRINTLN(type); + push_message(type, (uint32_t)mon->prio, value, name); +} + +bool get_monitor(uint nr, bool inv, bool defaultBool) { + Monitor_t *mon = monitor_by_nr(nr); + if (!mon) return defaultBool; + return inv ? !mon->active : mon->active; +} + +bool get_remote_monitor(Monitor_t *mon, bool defaultBool) { +#if defined(ESP8266) + IPAddress _ip(mon->m.remote.ip); + unsigned char ip[4] = {_ip[0], _ip[1], _ip[2], _ip[3]}; +#else + unsigned char ip[4]; + ip[3] = (unsigned char)((mon->m.remote.ip >> 24) & 0xFF); + ip[2] = (unsigned char)((mon->m.remote.ip >> 16) & 0xFF); + ip[1] = (unsigned char)((mon->m.remote.ip >> 8) & 0xFF); + ip[0] = (unsigned char)((mon->m.remote.ip & 0xFF)); +#endif + + DEBUG_PRINTLN(F("read_monitor_http")); + + char *p = tmp_buffer; + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE); + + bf.emit_p(PSTR("GET /ml?pw=$O&nr=$D"), SOPT_PASSWORD, mon->m.remote.rmonitor); + bf.emit_p(PSTR(" HTTP/1.0\r\nHOST: $D.$D.$D.$D\r\n\r\n"), ip[0], ip[1], ip[2], ip[3]); + + DEBUG_PRINTLN(p); + + char server[20]; + sprintf(server, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); + + int res = os.send_http_request(server, mon->m.remote.port, p, NULL, false, 500); + if (res == HTTP_RQT_SUCCESS) { + DEBUG_PRINTLN("Send Ok"); + p = ether_buffer; + DEBUG_PRINTLN(p); + + char buf[20]; + char *s = strstr(p, "\"time\":"); + if (s && extract(s, buf, sizeof(buf))) { + ulong time = strtoul(buf, NULL, 0); + if (time == 0 || time == mon->time) { + return defaultBool; + } else { + mon->time = time; + } + } + + s = strstr(p, "\"active\":"); + if (s && extract(s, buf, sizeof(buf))) { + return strtoul(buf, NULL, 0); + } + + return HTTP_RQT_SUCCESS; + } + return defaultBool; +} + +void check_monitors() { + Monitor_t *mon = monitors; + while (mon) { + uint nr = mon->nr; + + bool wasActive = mon->active; + double value = 0; + + switch(mon->type) { + case MONITOR_MIN: + case MONITOR_MAX: { + Sensor_t * sensor = sensor_by_nr(mon->sensor); + if (sensor && sensor->flags.data_ok) { + value = sensor->last_data; + + if (!mon->active) { + if ((mon->type == MONITOR_MIN && value <= mon->m.minmax.value1) || + (mon->type == MONITOR_MAX && value >= mon->m.minmax.value1)) { + mon->active = true; + } + } else { + if ((mon->type == MONITOR_MIN && value >= mon->m.minmax.value2) || + (mon->type == MONITOR_MAX && value <= mon->m.minmax.value2)) { + mon->active = false; + } + } + } + break; } + + case MONITOR_SENSOR12: + if (mon->m.sensor12.sensor12 == 1) + if (os.iopts[IOPT_SENSOR1_TYPE] == SENSOR_TYPE_RAIN || os.iopts[IOPT_SENSOR1_TYPE] == SENSOR_TYPE_SOIL) + mon->active = mon->m.sensor12.invers? !os.status.sensor1_active : os.status.sensor1_active; + if (mon->m.sensor12.sensor12 == 2) + if (os.iopts[IOPT_SENSOR2_TYPE] == SENSOR_TYPE_RAIN || os.iopts[IOPT_SENSOR2_TYPE] == SENSOR_TYPE_SOIL) + mon->active = mon->m.sensor12.invers? !os.status.sensor2_active : os.status.sensor2_active; + break; + + case MONITOR_SET_SENSOR12: + mon->active = get_monitor(mon->m.set_sensor12.monitor, false, false); + if (mon->m.set_sensor12.sensor12 == 1) { + os.status.forced_sensor1 = mon->active; + } + if (mon->m.set_sensor12.sensor12 == 2) { + os.status.forced_sensor2 = mon->active; + } + break; + case MONITOR_AND: + mon->active = get_monitor(mon->m.andorxor.monitor1, mon->m.andorxor.invers1, true) && + get_monitor(mon->m.andorxor.monitor2, mon->m.andorxor.invers2, true) && + get_monitor(mon->m.andorxor.monitor3, mon->m.andorxor.invers3, true) && + get_monitor(mon->m.andorxor.monitor4, mon->m.andorxor.invers4, true); + break; + case MONITOR_OR: + mon->active = get_monitor(mon->m.andorxor.monitor1, mon->m.andorxor.invers1, false) || + get_monitor(mon->m.andorxor.monitor2, mon->m.andorxor.invers2, false) || + get_monitor(mon->m.andorxor.monitor3, mon->m.andorxor.invers3, false) || + get_monitor(mon->m.andorxor.monitor4, mon->m.andorxor.invers4, false); + break; + case MONITOR_XOR: + mon->active = get_monitor(mon->m.andorxor.monitor1, mon->m.andorxor.invers1, false) ^ + get_monitor(mon->m.andorxor.monitor2, mon->m.andorxor.invers2, false) ^ + get_monitor(mon->m.andorxor.monitor3, mon->m.andorxor.invers3, false) ^ + get_monitor(mon->m.andorxor.monitor4, mon->m.andorxor.invers4, false); + break; + case MONITOR_NOT: + mon->active = get_monitor(mon->m.mnot.monitor, true, false); + break; + case MONITOR_TIME: { + time_os_t timeNow = os.now_tz(); + uint16_t time = hour(timeNow) * 100 + minute(timeNow); //HHMM +#if defined(ARDUINO) + uint8_t wday = (weekday(timeNow)+5)%7; //Monday = 0 +#else + time_os_t ct = timeNow; + struct tm *ti = gmtime(&ct); + uint8_t wday = (ti->tm_wday+1)%7; +#endif + mon->active = (mon->m.mtime.weekdays >> wday) & 0x01; + if (mon->m.mtime.time_from > mon->m.mtime.time_to) // FROM > TO ? Over night value + mon->active &= time >= mon->m.mtime.time_from || time <= mon->m.mtime.time_to; + else + mon->active &= time >= mon->m.mtime.time_from && time <= mon->m.mtime.time_to; + break; + } + case MONITOR_REMOTE: + mon->active = get_remote_monitor(mon, wasActive); + break; + } + + if (mon->active != wasActive) { + if (mon->active) { + start_monitor_action(mon); + push_message(mon, value); + mon = monitor_by_nr(nr); //restart because if send by mail we unloaded+reloaded the monitors + } else { + stop_monitor_action(mon); + } + } + + mon = mon->next; + } +} \ No newline at end of file diff --git a/sensors.h b/sensors.h new file mode 100644 index 00000000..681f17cf --- /dev/null +++ b/sensors.h @@ -0,0 +1,449 @@ +/* OpenSprinkler Unified (AVR/RPI/BBB/LINUX) Firmware + * Copyright (C) 2015 by Ray Wang (ray@opensprinkler.com) + * + * sensors header file + * Sep 2022 @ OpenSprinklerShop + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see + * . + */ + +#ifndef _SENSORS_H +#define _SENSORS_H + +#if defined(ARDUINO) +#include +#include +#else // headers for RPI/BBB +#include +#include +#include +#include +extern "C" { +#include +#include +} +#endif +#include "defines.h" +#include "utils.h" +#if defined(ESP8266) +#include +#endif +#include "program.h" + +// Files +#define SENSOR_FILENAME "sensor.dat" // analog sensor filename +#define SENSOR_FILENAME_BAK "sensor.bak" // analog sensor filename backup +#define PROG_SENSOR_FILENAME "progsensor.dat" // sensor to program assign filename +#define SENSORLOG_FILENAME1 "sensorlog.dat" // analog sensor log filename +#define SENSORLOG_FILENAME2 "sensorlog2.dat" // analog sensor log filename2 +#define MONITOR_FILENAME "monitors.dat" + +#define SENSORLOG_FILENAME_WEEK1 \ + "sensorlogW1.dat" // analog sensor log filename for week average +#define SENSORLOG_FILENAME_WEEK2 \ + "sensorlogW2.dat" // analog sensor log filename2 for week average +#define SENSORLOG_FILENAME_MONTH1 \ + "sensorlogM1.dat" // analog sensor log filename for month average +#define SENSORLOG_FILENAME_MONTH2 \ + "sensorlogM2.dat" // analog sensor log filename2 for month average + +#define SENSORURL_FILENAME "sensorurl.dat" // long urls filename + +// MaxLogSize +#define MAX_LOG_SIZE 8000 + +// Sensor types: +#define SENSOR_NONE 0 // None or deleted sensor +#define SENSOR_SMT100_MOIS 1 // Truebner SMT100 RS485, moisture mode +#define SENSOR_SMT100_TEMP 2 // Truebner SMT100 RS485, temperature mode +#define SENSOR_SMT100_PMTY 3 // Truebner SMT100 RS485, permittivity mode +#define SENSOR_TH100_MOIS 4 // Truebner TH100 RS485, humidity mode +#define SENSOR_TH100_TEMP 5 // Truebner TH100 RS485, temperature mode +#define SENSOR_ANALOG_EXTENSION_BOARD 10 // New OpenSprinkler analog extension board x8 - voltage mode 0..4V +#define SENSOR_ANALOG_EXTENSION_BOARD_P 11 // New OpenSprinkler analog extension board x8 - percent 0..3.3V to 0..100% +#define SENSOR_SMT50_MOIS 15 // New OpenSprinkler analog extension board x8 - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_SMT50_TEMP 16 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U – 0,5) * 100 +#define SENSOR_SMT100_ANALOG_MOIS 17 // New OpenSprinkler analog extension board x8 - SMT100 VWC [%] = (U * 100) : 3 +#define SENSOR_SMT100_ANALOG_TEMP 18 // New OpenSprinkler analog extension board x8 - SMT50 T [°C] = (U * 100) : 3 - 40 + +#define SENSOR_VH400 30 // New OpenSprinkler analog extension board x8 - Vegetronix VH400 +#define SENSOR_THERM200 31 // New OpenSprinkler analog extension board x8 - Vegetronix THERM200 +#define SENSOR_AQUAPLUMB 32 // New OpenSprinkler analog extension board x8 - Vegetronix Aquaplumb + +#define SENSOR_USERDEF 49 // New OpenSprinkler analog extension board x8 - User defined sensor + +#define SENSOR_OSPI_ANALOG 50 // Old OSPi analog input - voltage mode 0..3.3V +#define SENSOR_OSPI_ANALOG_P 51 // Old OSPi analog input - percent 0..3.3V to 0...100% +#define SENSOR_OSPI_ANALOG_SMT50_MOIS 52 // Old OSPi analog input - SMT50 VWC [%] = (U * 50) : 3 +#define SENSOR_OSPI_ANALOG_SMT50_TEMP 53 // Old OSPi analog input - SMT50 T [°C] = (U – 0,5) * 100 +#define SENSOR_OSPI_INTERNAL_TEMP 54 // Internal OSPI Temperature + +#define SENSOR_MQTT 90 // subscribe to a MQTT server and query a value + +#define SENSOR_REMOTE 100 // Remote sensor of an remote opensprinkler +#define SENSOR_WEATHER_TEMP_F 101 // Weather service - temperature (Fahrenheit) +#define SENSOR_WEATHER_TEMP_C 102 // Weather service - temperature (Celcius) +#define SENSOR_WEATHER_HUM 103 // Weather service - humidity (%) +#define SENSOR_WEATHER_PRECIP_IN 105 // Weather service - precip (inch) +#define SENSOR_WEATHER_PRECIP_MM 106 // Weather service - precip (mm) +#define SENSOR_WEATHER_WIND_MPH 107 // Weather service - wind (mph) +#define SENSOR_WEATHER_WIND_KMH 108 // Weather service - wind (kmh) + +#define SENSOR_GROUP_MIN 1000 // Sensor group with min value +#define SENSOR_GROUP_MAX 1001 // Sensor group with max value +#define SENSOR_GROUP_AVG 1002 // Sensor group with avg value +#define SENSOR_GROUP_SUM 1003 // Sensor group with sum value + +//Diagnostic +#define SENSOR_FREE_MEMORY 10000 //Free memory +#define SENSOR_FREE_STORE 10001 //Free storage + +#define SENSOR_READ_TIMEOUT 3000 // ms + +#define MIN_DISK_FREE 8192 // 8Kb min + +#define MAX_SENSOR_REPEAT_READ 32000 // max reads for calculating avg +#define MAX_SENSOR_READ_TIME 1 // second for reading sensors + +// detected Analog Sensor Boards: +#define ASB_BOARD1 0x0001 +#define ASB_BOARD2 0x0002 +#define OSPI_PCF8591 0x0004 +#define OSPI_ADS1115 0x0008 +#define RS485_TRUEBNER1 0x0020 +#define RS485_TRUEBNER2 0x0040 +#define RS485_TRUEBNER3 0x0080 +#define RS485_TRUEBNER4 0x0100 +#define OSPI_USB_RS485 0x0200 + +typedef struct SensorFlags { + uint enable : 1; // enabled + uint log : 1; // log data enabled + uint data_ok : 1; // last data is ok + uint show : 1; // show on mainpage +} SensorFlags_t; + +// Definition of a sensor +typedef struct Sensor { + uint nr; // 1..n sensor-nr, 0=deleted + char name[30]; // name + uint type; // 1..n type definition, 0=deleted + uint group; // group assignment,0=no group + uint32_t ip; // tcp-ip + uint port; // tcp-port / ADC: I2C Address 0x48/0x49 or 0/1 + uint id; // modbus id / ADC: channel + uint read_interval; // seconds + uint32_t last_native_data; // last native sensor data + double last_data; // last converted sensor data + SensorFlags_t flags; // Flags see obove + int16_t factor; // faktor - for custom sensor + int16_t divider; // divider - for custom sensor + char userdef_unit[8]; // unit - for custom sensor + int16_t offset_mv; // offset millivolt - for custom sensor (before) + int16_t offset2; // offset unit value 1/100 - for custom sensor (after): + // sensorvalue = (read_value-offset_mv/1000) * factor / + // divider + offset2/100 + unsigned char assigned_unitid; // unitid for userdef and mqtt sensors + unsigned char undef[15]; // for later + // unstored: + bool mqtt_init : 1; + bool mqtt_push : 1; + unsigned char unitid; + uint32_t repeat_read; + double repeat_data; + uint64_t repeat_native; + ulong last_read; // millis + double last_logged_data; // last logged sensor data + ulong last_logged_time; // last logged timestamp / + Sensor *next; +} Sensor_t; +#define SENSOR_STORE_SIZE 111 + +// Definition of a log data +typedef struct SensorLog { + uint nr; // sensor-nr + ulong time; + uint32_t native_data; + double data; +} SensorLog_t; +#define SENSORLOG_STORE_SIZE (sizeof(SensorLog_t)) + +// Sensor to program data +// Adjustment is formula +// min max factor1 factor2 +// 10..90 -> 5..1 factor1 > factor2 +// a b c d +// (b-sensorData) / (b-a) * (c-d) + d +// +// 10..90 -> 1..5 factor1 < factor2 +// a b c d +// (sensorData-a) / (b-a) * (d-c) + c + +#define PROG_DELETE 0 // deleted +#define PROG_LINEAR 1 // formula see above +#define PROG_DIGITAL_MIN 2 // under or equal min : factor1 else factor2 +#define PROG_DIGITAL_MAX 3 // over or equal max : factor2 else factor1 +#define PROG_DIGITAL_MINMAX 4 // under min or over max : factor1 else factor2 +#define PROG_NONE 99 // No adjustment + +typedef struct ProgSensorAdjust { + uint nr; // adjust-nr 1..x + uint type; // PROG_XYZ type=0 -->delete + uint sensor; // sensor-nr + uint prog; // program-nr=pid + double factor1; + double factor2; + double min; + double max; + char name[30]; + unsigned char undef[2]; // for later + ProgSensorAdjust *next; +} ProgSensorAdjust_t; +#define PROGSENSOR_STORE_SIZE \ + (sizeof(ProgSensorAdjust_t) - sizeof(ProgSensorAdjust_t *)) + +#define SENSORURL_TYPE_URL 0 // URL for Host/Path +#define SENSORURL_TYPE_TOPIC 1 // TOPIC for MQTT +#define SENSORURL_TYPE_FILTER 2 // JSON Filter for MQTT + +typedef struct SensorUrl { + uint nr; + uint type; // see SENSORURL_TYPE + uint length; + char *urlstr; + SensorUrl *next; +} SensorUrl_t; +#define SENSORURL_STORE_SIZE \ + (sizeof(SensorUrl_t) - sizeof(char *) - sizeof(SensorUrl_t *)) + +#define MONITOR_DELETE 0 +#define MONITOR_MIN 1 +#define MONITOR_MAX 2 +#define MONITOR_SENSOR12 3 // Read Digital OS Sensors Rain/Soil Moisture +#define MONITOR_SET_SENSOR12 4 // Write Digital OS Sensors Rain/Soil Moisture +#define MONITOR_AND 10 +#define MONITOR_OR 11 +#define MONITOR_XOR 12 +#define MONITOR_NOT 13 +#define MONITOR_TIME 14 +#define MONITOR_REMOTE 100 + +typedef struct Monitor_MINMAX { // type = 1+2 + double value1; // MIN/MAX + double value2; // Secondary +} Monitor_MINMAX_t; + +typedef struct Monitor_SENSOR12 { // type = 3 + uint16_t sensor12; + bool invers : 1; +} Monitor_SENSOR12_t; + +typedef struct Monitor_SET_SENSOR12 { // type = 4 + uint16_t monitor; + uint16_t sensor12; +} Monitor_SET_SENSOR12_t; + +typedef struct Monitor_ANDORXOR { // type = 10+11+12 + uint16_t monitor1; + uint16_t monitor2; + uint16_t monitor3; + uint16_t monitor4; + bool invers1 : 1; + bool invers2 : 1; + bool invers3 : 1; + bool invers4 : 1; +} Monitor_ANDORXOR_t; + +typedef struct Monitor_NOT { // type = 13 + uint16_t monitor; +} Monitor_NOT_t; + +typedef struct Monitor_TIME { // type = 14 + uint16_t time_from; //Format: HHMM + uint16_t time_to; //Format: HHMM + uint8_t weekdays; //bit 0=monday +} Monitor_TIME_t; + +typedef struct Monitor_REMOTE { // type = 100 + uint16_t rmonitor; + uint32_t ip; + uint16_t port; +} Monitor_REMOTE_t; + +typedef union Monitor_Union { + Monitor_MINMAX_t minmax; // type = 1+2 + Monitor_SENSOR12_t sensor12; // type = 3 + Monitor_SET_SENSOR12_t set_sensor12; // type = 4 + Monitor_ANDORXOR_t andorxor; // type = 10+11+12 + Monitor_NOT_t mnot; // type = 13 + Monitor_TIME_t mtime; // type = 14 + Monitor_REMOTE_t remote; //type = 100 +} Monitor_Union_t; + +typedef struct Monitor { + uint nr; + uint type; // MONITOR_TYPES + uint sensor; // sensor-nr + uint prog; // program-nr=pid + uint zone; // Zone + Monitor_Union_t m; + boolean active; + ulong time; + char name[30]; + ulong maxRuntime; + uint8_t prio; + unsigned char undef[20]; // for later + Monitor *next; +} Monitor_t; +#define MONITOR_STORE_SIZE (sizeof(Monitor_t) - sizeof(char *) - sizeof(Monitor_t *)) + +#define UNIT_NONE 0 +#define UNIT_PERCENT 1 +#define UNIT_DEGREE 2 +#define UNIT_FAHRENHEIT 3 +#define UNIT_VOLT 4 +#define UNIT_HUM_PERCENT 5 +#define UNIT_INCH 6 +#define UNIT_MM 7 +#define UNIT_MPH 8 +#define UNIT_KMH 9 +#define UNIT_LEVEL 10 +#define UNIT_DK 11 //Permitivität +#define UNIT_LM 12 //Lumen +#define UNIT_LX 13 //Lux +#define UNIT_USERDEF 99 + +// Unitnames +// extern const char* sensor_unitNames[]; + +#define ASB_BOARD_ADDR1a 0x48 +#define ASB_BOARD_ADDR1b 0x49 +#define ASB_BOARD_ADDR2a 0x4A +#define ASB_BOARD_ADDR2b 0x4B +#define RS485_TRUEBNER1_ADDR 0x38 +#define RS485_TRUEBNER2_ADDR 0x39 +#define RS485_TRUEBNER3_ADDR 0x3A +#define RS485_TRUEBNER4_ADDR 0x3B + +void sensor_api_init(boolean detect_boards); +uint16_t get_asb_detected_boards(); +void sensor_save_all(); +void sensor_api_free(); + +Sensor_t *getSensors(); +const char *getSensorUnit(int unitid); +const char *getSensorUnit(Sensor_t *sensor); +unsigned char getSensorUnitId(int type); +unsigned char getSensorUnitId(Sensor_t *sensor); + +extern char ether_buffer[]; +extern char tmp_buffer[]; +extern ProgramData pd; + +// Utils: +uint16_t CRC16(unsigned char buf[], int len); + +// Sensor API functions: +int sensor_delete(uint nr); +int sensor_define(uint nr, const char *name, uint type, uint group, uint32_t ip, + uint port, uint id, uint ri, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, int16_t offset2, + SensorFlags_t flags, int16_t assigned_unitid); +int sensor_define_userdef(uint nr, int16_t factor, int16_t divider, + const char *userdef_unit, int16_t offset_mv, + int16_t offset2, int16_t sensor_define_userdef); +void sensor_load(); +void sensor_save(); +uint sensor_count(); +boolean sensor_isgroup(Sensor_t *sensor); +void sensor_update_groups(); + +void read_all_sensors(boolean online); + +Sensor_t *sensor_by_nr(uint nr); +Sensor_t *sensor_by_idx(uint idx); + +int read_sensor(Sensor_t *sensor, + ulong time); // sensor value goes to last_native_data/last_data + +// Sensorlog API functions: +#define LOG_STD 0 +#define LOG_WEEK 1 +#define LOG_MONTH 2 +bool sensorlog_add(uint8_t log, SensorLog_t *sensorlog); +bool sensorlog_add(uint8_t log, Sensor_t *sensor, ulong time); +void sensorlog_clear_all(); +void sensorlog_clear(bool std, bool week, bool month); +ulong sensorlog_clear_sensor(uint sensorNr, uint8_t log, bool use_under, + double under, bool use_over, double over, time_t before, time_t after); +SensorLog_t *sensorlog_load(uint8_t log, ulong pos); +SensorLog_t *sensorlog_load(uint8_t log, ulong idx, SensorLog_t *sensorlog); +int sensorlog_load2(uint8_t log, ulong idx, int count, SensorLog_t *sensorlog); +ulong sensorlog_filesize(uint8_t log); +ulong sensorlog_size(uint8_t log); +ulong findLogPosition(uint8_t log, ulong after); +const char *getlogfile(uint8_t log); +const char *getlogfile2(uint8_t log); +void checkLogSwitch(uint8_t log); +void checkLogSwitchAfterWrite(uint8_t log); + +//influxdb +void add_influx_data(Sensor_t *sensor); + +// Set Sensor Address for SMT100: +int set_sensor_address(Sensor_t *sensor, uint8_t new_address); + +// Calc watering adjustment: +int prog_adjust_define(uint nr, uint type, uint sensor, uint prog, + double factor1, double factor2, double min, double max, char * name); +int prog_adjust_delete(uint nr); +void prog_adjust_save(); +void prog_adjust_load(); +uint prog_adjust_count(); +ProgSensorAdjust_t *prog_adjust_by_nr(uint nr); +ProgSensorAdjust_t *prog_adjust_by_idx(uint idx); +double calc_sensor_watering(uint prog); +double calc_sensor_watering_by_nr(uint nr); +double calc_sensor_watering_int(ProgSensorAdjust_t *p, double sensorData); + +void GetSensorWeather(); +// PUSH Message to MQTT and others: +void push_message(Sensor_t *sensor); + +// Web URLS Host/Path and MQTT topics: +void SensorUrl_load(); +void SensorUrl_save(); +bool SensorUrl_delete(uint nr, uint type); +bool SensorUrl_add(uint nr, uint type, const char *urlstr); +char *SensorUrl_get(uint nr, uint type); + +void detect_asb_board(); + +//Value Monitoring +void monitor_load(); +void monitor_save(); +int monitor_count(); +int monitor_delete(uint nr); +bool monitor_define(uint nr, uint type, uint sensor, uint prog, uint zone, const Monitor_Union_t m, char * name, ulong maxRuntime, uint8_t prio); +Monitor_t * monitor_by_nr(uint nr); +Monitor_t * monitor_by_idx(uint idx); +void check_monitors(); + +#if defined(ESP8266) +ulong diskFree(); +bool checkDiskFree(); // true: disk space Ok, false: Out of disk space +#endif + +#endif // _SENSORS_H diff --git a/true b/true new file mode 100644 index 00000000..e69de29b diff --git a/updater.sh b/updater.sh index eda30f0a..75cfe3ab 100755 --- a/updater.sh +++ b/updater.sh @@ -1,5 +1,10 @@ #! /bin/bash +# Get latest release from GitHub api +#latest=$(curl --silent "https://api.github.com/repos/OpenSprinklerShop/OpenSprinkler-Firmware/releases/latest" | +# grep '"tag_name":' | # Get tag line +# sed -E 's/.*"([^"]+)".*/\1/') +#git switch --detach $latest git pull ./build.sh -s ospi systemctl restart OpenSprinkler.service diff --git a/utils.cpp b/utils.cpp index bfcc3121..e64e08f6 100644 --- a/utils.cpp +++ b/utils.cpp @@ -31,6 +31,7 @@ extern OpenSprinkler os; #if defined(ESP8266) #include #include + #include #else #include #include "SdFat.h" @@ -312,7 +313,13 @@ void remove_file(const char *fn) { bool file_exists(const char *fn) { #if defined(ESP8266) - return LittleFS.exists(fn); + //return LittleFS.exists(fn); + File f = LittleFS.open(fn, "r"); + if (f) { + f.close(); + return true; + } + return false; #elif defined(ARDUINO) @@ -330,14 +337,60 @@ bool file_exists(const char *fn) { } // file functions -void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { +ulong file_size(const char *fn) { + ulong size = 0; +#if defined(ESP8266) + + // do not use File.readBytes or readBytesUntil because it's very slow + File f = LittleFS.open(fn, "r"); + if(f) { + size = f.size(); + f.close(); + } + +#elif defined(ARDUINO) + + sd.chdir("/"); + SdFile file; + if(file.open(fn, O_READ)) { + size = file.size(); + file.close(); + } + +#else + + FILE *fp = fopen(get_filename_fullpath(fn), "rb"); + if(fp) { + fseek(fp, 0, SEEK_END); + size = ftell(fp); + fclose(fp); + } + +#endif + return size; +} + +bool rename_file(const char *fn_old, const char *fn_new) { +#if defined(ESP8266) + return LittleFS.rename(fn_old, fn_new); +#elif defined(ARDUINO) + SdFile file(fn_old, O_READ); + return file.rename(fn_new); +#else + return rename(fn_old, fn_new) == 0; +#endif +} + +// file functions +ulong file_read_block(const char *fn, void *dst, ulong pos, ulong len) { + ulong result = 0; #if defined(ESP8266) // do not use File.read_byte or read_byteUntil because it's very slow File f = LittleFS.open(fn, "r"); if(f) { f.seek(pos, SeekSet); - f.read((unsigned char*)dst, len); + result = f.read((unsigned char*)dst, len); f.close(); } @@ -347,7 +400,7 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { SdFile file; if(file.open(fn, O_READ)) { file.seekSet(pos); - file.read(dst, len); + result = file.read(dst, len); file.close(); } @@ -356,11 +409,12 @@ void file_read_block(const char *fn, void *dst, ulong pos, ulong len) { FILE *fp = fopen(get_filename_fullpath(fn), "rb"); if(fp) { fseek(fp, pos, SEEK_SET); - fread(dst, 1, len, fp); + result = fread(dst, 1, len, fp); fclose(fp); } #endif + return result; } void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { @@ -400,6 +454,43 @@ void file_write_block(const char *fn, const void *src, ulong pos, ulong len) { } +void file_append_block(const char *fn, const void *src, ulong len) { +#if defined(ESP8266) + + File f = LittleFS.open(fn, "r+"); + if(!f) f = LittleFS.open(fn, "w"); + if(f) { + f.seek(0, SeekEnd); + f.write((byte*)src, len); + f.close(); + } + +#elif defined(ARDUINO) + + sd.chdir("/"); + SdFile file; + int ret = file.open(fn, O_CREAT | O_RDWR); + if(!ret) return; + file.seekEnd(0); + file.write(src, len); + file.close(); + +#else + + FILE *fp = fopen(get_filename_fullpath(fn), "rb+"); + if(!fp) { + fp = fopen(get_filename_fullpath(fn), "wb+"); + } + if(fp) { + fseek(fp, 0, SEEK_END); //this fails silently without the above change + fwrite(src, 1, len, fp); + fclose(fp); + } + +#endif + +} + void file_copy_block(const char *fn, ulong from, ulong to, ulong len, void *tmp) { // assume tmp buffer is provided and is larger than len // todo future: if tmp buffer is not provided, do unsigned char-to-unsigned char copy @@ -612,7 +703,7 @@ void strReplaceQuoteBackslash(char *buf) { static const unsigned char month_days[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; bool isLastDayofMonth(unsigned char month, unsigned char day) { - return day == month_days[month-1]; + return day == month_days[month]; } bool isValidDate(unsigned char m, unsigned char d) { @@ -630,10 +721,6 @@ bool isValidDate(uint16_t date) { return isValidDate(month, day); } -bool isLeapYear(uint16_t y){ // Accepts 4 digit year and returns if leap year - return (y%400==0) || ((y%4==0) && (y%100!=0)); -} - #if defined(ESP8266) unsigned char hex2dec(const char *hex) { return strtol(hex, NULL, 16); @@ -682,4 +769,16 @@ void str2mac(const char *_str, unsigned char mac[]) { yield(); } } +#endif + +#ifdef ARDUINO + +size_t freeMemory() { + return ESP.getFreeHeap(); +} +#else + +size_t freeMemory() { + return -1; +} #endif \ No newline at end of file diff --git a/utils.h b/utils.h index 14140aa7..157ac147 100644 --- a/utils.h +++ b/utils.h @@ -41,10 +41,14 @@ //remove unused functions: void write_to_file(const char *fname, const char *data, ulong size, ulong pos=0, bool trunc=true); //remove unused functions: void read_from_file(const char *fname, char *data, ulong maxsize=TMP_BUFFER_SIZE, int pos=0); void remove_file(const char *fname); +bool rename_file(const char *fn_old, const char *fn_new); bool file_exists(const char *fname); -void file_read_block (const char *fname, void *dst, ulong pos, ulong len); +ulong file_size(const char *fn); + +ulong file_read_block (const char *fname, void *dst, ulong pos, ulong len); void file_write_block(const char *fname, const void *src, ulong pos, ulong len); +void file_append_block(const char *fname, const void *src, ulong len); void file_copy_block (const char *fname, ulong from, ulong to, ulong len, void *tmp=0); unsigned char file_read_byte (const char *fname, ulong pos); void file_write_byte(const char *fname, ulong pos, unsigned char v); @@ -59,13 +63,13 @@ void urlDecode(char *); void strReplaceQuoteBackslash(char *); void peel_http_header(char*); void strReplace(char *, char c, char r); +size_t freeMemory(); #define date_encode(m,d) ((m<<5)+d) #define MIN_ENCODED_DATE date_encode(1,1) #define MAX_ENCODED_DATE date_encode(12, 31) bool isLastDayofMonth(unsigned char month, unsigned char day); bool isValidDate(uint16_t date); -bool isLeapYear(uint16_t year); // whether a 4 digit year is a leap year #if defined(ESP8266) unsigned char hex2dec(const char *hex); bool isHex(char c); diff --git a/weather.cpp b/weather.cpp index a36d7eab..4b9fc408 100644 --- a/weather.cpp +++ b/weather.cpp @@ -132,9 +132,12 @@ static void getweather_callback_with_peel_header(char* buffer) { } void GetWeather() { - if(!os.network_connected()) return; +#if defined(ESP8266) + if (!useEth) + if (os.state!=OS_STATE_CONNECTED || WiFi.status()!=WL_CONNECTED) return; +#endif // use temp buffer to construct get command - BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE*2); + BufferFiller bf = BufferFiller(tmp_buffer, TMP_BUFFER_SIZE_L); int method = os.iopts[IOPT_USE_WEATHER]; // use manual adjustment call for monthly adjustment -- a bit ugly, but does not involve weather server changes if(method==WEATHER_METHOD_MONTHLY) method=WEATHER_METHOD_MANUAL;