diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100755 index 00000000..0f13bcf7 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,33 @@ +name: Build binary + +on: [push, pull_request] + +jobs: + build: + runs-on: windows-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup Arduino CLI + uses: arduino/setup-arduino-cli@v1 + + - name: Install platform + run: arduino-cli core install esp8266:esp8266 --additional-urls https://arduino.esp8266.com/stable/package_esp8266com_index.json -v + + - name: Install dependencies + run: arduino-cli lib install ringbuffer pubsubclient doubleresetdetect arduinojson dallastemperature onewire WebSockets + + - name: Compile Sketch + run: cd HeishaMon && arduino-cli compile --output-dir . --fqbn=esp8266:esp8266:d1_mini:xtal=160,vt=flash,ssl=basic,mmu=3216,non32xfer=safe,eesz=4M2M,ip=lm2f,dbg=Disabled,lvl=None____,wipe=none,baud=921600 --vid-pid=1A86_7523 --warnings=none --verbose HeishaMon.ino + + - name: Add MD5 checksum + run: cd HeishaMon && MD5=`md5sum HeishaMon.ino.bin | cut -d\ -f1` && mv HeishaMon.ino.bin HeishaMon-alpha-$MD5.bin + shell: bash + + - name: Upload artifacts + uses: actions/upload-artifact@v2 + with: + name: HeishaMon.ino.bin + path: HeishaMon/HeishaMon-*.bin diff --git a/.gitmodules b/.gitmodules index 6f67595f..c077caf3 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "Integrations/Domoticz/HeishamonMQTT"] path = Integrations/Domoticz/HeishamonMQTT url = https://github.com/MarFanNL/HeishamonMQTT +[submodule "Integrations/NodeRed"] + path = Integrations/NodeRed + url = https://github.com/edterbak/NodeRed_Heishamon_control diff --git a/HeatPumpType.md b/HeatPumpType.md index d8e9b88e..16dea466 100644 --- a/HeatPumpType.md +++ b/HeatPumpType.md @@ -21,10 +21,12 @@ Assuming that bytes from #129 to #138 are unique for each model of Aquarea heat |14 | E2 CF 0B 82 05 12 D0 0C 91 05 | WH-SQC09H3E8 | WH-UQ09HE8 | KIT-WQC09H3E8 | 9 | 3ph | T-CAP - Super Quiet | |15 | E2 CF 0C 55 14 12 D0 0B 15 08 | WH-SDC09H3E5 | WH-UD09HE5 | KIT-WC09H3E5 | 9 | 1 ph | HP | |16 | E2 CF 0C 43 00 12 D0 0B 15 08 | WH-ADC0309H3E5 | WH-UD09HE5 | KIT-ADC09HE5 | 9 | 1 ph | HP - All-In-One | -|17 | 62 D2 0B 45 54 32 D2 0C 45 55 | WH-ADC0309J3E5 | WH-UD05JE5 | KIT-ADC-05JE5 | 5 | 1ph | HP - All-In-One | +|17 | 62 D2 0B 45 54 32 D2 0C 45 55 | WH-ADC0309J3E5 | WH-UD05JE5 | KIT-ADC05JE5 | 5 | 1ph | HP - All-In-One | |18 | 62 D2 0B 43 54 42 D2 0C 46 55 | WH-SDC0709J3E5 | WH-UD07JE5 | KIT-WC07J3E5 | 7 | 1 ph | HP | |19 | E2 CF 0C 54 14 12 D0 0B 14 08 | WH-SDC07H3E5-1 | WH-UD07HE5-1 | KIT-WC07H3E5-1 | 7 | 1 ph | HP | |20 | C2 D3 0B 34 65 B2 D3 0B 95 65 | Monoblock | WH-MDC07J3E5 | Monoblock | 7 | 1ph | HP | +|21 | C2 D3 0B 35 65 B2 D3 0B 96 65 | Monoblock | WH-MDC09J3E5 | Monoblock | 9 | 1ph | HP | +|22 | 62 D2 0B 41 54 32 D2 0C 45 55 | WH-SDC0305J3E5 | WH-UD05JE5 | KIT-WC05J3E5 | 5 | 1ph | HP | All bytes are used for Heat Pump model identification in the code. diff --git a/HeishaMon normal case.stl b/HeishaMon normal case.stl new file mode 100644 index 00000000..8fa7e238 Binary files /dev/null and b/HeishaMon normal case.stl differ diff --git a/HeishaMon opentherm case.stl b/HeishaMon opentherm case.stl new file mode 100644 index 00000000..062e955b Binary files /dev/null and b/HeishaMon opentherm case.stl differ diff --git a/HeishaMon v3 case.stl b/HeishaMon v3 case.stl deleted file mode 100644 index b3279500..00000000 Binary files a/HeishaMon v3 case.stl and /dev/null differ diff --git a/HeishaMon v3.1 case.stl b/HeishaMon v3.1 case.stl deleted file mode 100644 index 11449b93..00000000 Binary files a/HeishaMon v3.1 case.stl and /dev/null differ diff --git a/HeishaMon/HeishaMon.ino b/HeishaMon/HeishaMon.ino old mode 100644 new mode 100755 index 531dcf76..2dbd58a3 --- a/HeishaMon/HeishaMon.ino +++ b/HeishaMon/HeishaMon.ino @@ -1,18 +1,25 @@ +#define LWIP_INTERNAL + #include #include #include #include #include -#include -#include #include #include #include +#include "lwip/apps/sntp.h" +#include "src/common/timerqueue.h" +#include "src/common/stricmp.h" +#include "src/common/log.h" +#include "src/rules/rules.h" + #include "webfunctions.h" #include "decode.h" #include "commands.h" +#include "rules.h" DNSServer dnsServer; @@ -32,10 +39,6 @@ const byte DNS_PORT = 53; #define SERIALTIMEOUT 2000 // wait until all 203 bytes are read, must not be too long to avoid blocking the code -ESP8266WebServer httpServer(80); -WebSocketsServer webSocket = WebSocketsServer(81); -ESP8266HTTPUpdateServer httpUpdater; - settingsStruct heishamonSettings; bool sending = false; // mutex for sending data @@ -44,10 +47,11 @@ bool mqttcallbackinprogress = false; // mutex for processing mqtt callback #define MQTTRECONNECTTIMER 30000 //it takes 30 secs for each mqtt server reconnect attempt unsigned long lastMqttReconnectAttempt = 0; -#define WIFIRETRYTIMER 10000 // switch between hotspot and configured SSID each 10 secs if SSID is lost +#define WIFIRETRYTIMER 15000 // switch between hotspot and configured SSID each 10 secs if SSID is lost unsigned long lastWifiRetryTimer = 0; unsigned long lastRunTime = 0; +unsigned long lastOptionalPCBRunTime = 0; unsigned long sendCommandReadTime = 0; //set to millis value during send, allow to wait millis for answer unsigned long goodreads = 0; @@ -58,15 +62,19 @@ unsigned long tooshortread = 0; unsigned long toolongread = 0; unsigned long timeoutread = 0; float readpercentage = 0; +static int uploadpercentage = 0; // instead of passing array pointers between functions we just define this in the global scope #define MAXDATASIZE 255 -char data[MAXDATASIZE]; -byte data_length = 0; +char data[MAXDATASIZE] = { '\0' }; +byte data_length = 0; -// store actual data in an String array -String actData[NUMBER_OF_TOPICS]; -String actOptData[NUMBER_OF_OPT_TOPICS]; +// store actual data +String openTherm[2]; +char actData[DATASIZE] = { '\0' }; +#define OPTDATASIZE 20 +char actOptData[OPTDATASIZE] = { '\0' }; +String RESTmsg = ""; // log message to sprintf to char log_msg[256]; @@ -98,54 +106,59 @@ PubSubClient mqtt_client(mqtt_wifi_client); bool firstConnectSinceBoot = true; //if this is true there is no first connection made yet +struct timerqueue_t **timerqueue = NULL; +int timerqueue_size = 0; + /* - * check_wifi will process wifi reconnecting managing - */ + check_wifi will process wifi reconnecting managing +*/ void check_wifi() { - if ((WiFi.status() != WL_CONNECTED) || (!WiFi.localIP())) { + if ((WiFi.status() != WL_CONNECTED) && (WiFi.localIP())) { + // special case where it seems that we are not connect but we do have working IP (causing the -1% wifi signal), do a reset. + log_message((char *)"Weird case, WiFi seems disconnected but is not. Resetting WiFi!"); + setupWifi(&heishamonSettings); + } else if ((WiFi.status() != WL_CONNECTED) || (!WiFi.localIP())) { /* - * if we are not connected to an AP - * we must be in softAP so respond to DNS - */ + if we are not connected to an AP + we must be in softAP so respond to DNS + */ dnsServer.processNextRequest(); /* we need to stop reconnecting to a configured wifi network if there is a hotspot user connected - * also, do not disconnect if wifi network scan is active - */ + also, do not disconnect if wifi network scan is active + */ if ((heishamonSettings.wifi_ssid[0] != '\0') && (WiFi.status() != WL_DISCONNECTED) && (WiFi.scanComplete() != -1) && (WiFi.softAPgetStationNum() > 0)) { - log_message((char *)"WiFi lost, but softAP station connecting, so stop trying to connect to configured ssid..."); + log_message(F("WiFi lost, but softAP station connecting, so stop trying to connect to configured ssid...")); WiFi.disconnect(true); } /* only start this routine if timeout on - * reconnecting to AP and SSID is set - */ + reconnecting to AP and SSID is set + */ if ((heishamonSettings.wifi_ssid[0] != '\0') && ((unsigned long)(millis() - lastWifiRetryTimer) > WIFIRETRYTIMER ) ) { lastWifiRetryTimer = millis(); if (WiFi.softAPSSID() == "") { - log_message((char *)"WiFi lost, starting setup hotspot..."); - WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); - WiFi.softAP("HeishaMon-Setup"); + log_message(F("WiFi lost, starting setup hotspot...")); + WiFi.softAP((char*)"HeishaMon-Setup"); } if ((WiFi.status() == WL_DISCONNECTED) && (WiFi.softAPgetStationNum() == 0 )) { - log_message((char *)"Retrying configured WiFi, ..."); + log_message(F("Retrying configured WiFi, ...")); if (heishamonSettings.wifi_password[0] == '\0') { WiFi.begin(heishamonSettings.wifi_ssid); } else { WiFi.begin(heishamonSettings.wifi_ssid, heishamonSettings.wifi_password); } } else { - log_message((char *)"Reconnecting to WiFi failed. Waiting a few seconds before trying again."); + log_message(F("Reconnecting to WiFi failed. Waiting a few seconds before trying again.")); WiFi.disconnect(true); } } } else { //WiFi connected if (WiFi.softAPSSID() != "") { - log_message((char *)"WiFi (re)connected, shutting down hotspot..."); + log_message(F("WiFi (re)connected, shutting down hotspot...")); WiFi.softAPdisconnect(true); MDNS.notifyAPChange(); - experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs(5000); //necessary for some users with bad wifi routers } if (firstConnectSinceBoot) { // this should start only when softap is down or else it will not work properly so run after the routine to disable softap @@ -154,15 +167,18 @@ void check_wifi() setupOTA(); MDNS.begin(heishamonSettings.wifi_hostname); MDNS.addService("http", "tcp", 80); + experimental::ESP8266WiFiGratuitous::stationKeepAliveSetIntervalMs(5000); //necessary for some users with bad wifi routers if (heishamonSettings.wifi_ssid[0] == '\0') { - log_message((char *)"WiFi connected without SSID and password in settings. Must come from persistent memory. Storing in settings."); + log_message(F("WiFi connected without SSID and password in settings. Must come from persistent memory. Storing in settings.")); WiFi.SSID().toCharArray(heishamonSettings.wifi_ssid, 40); WiFi.psk().toCharArray(heishamonSettings.wifi_password, 40); DynamicJsonDocument jsonDoc(1024); settingsToJson(jsonDoc, &heishamonSettings); //stores current settings in a json document saveJsonToConfig(jsonDoc); //save to config file } + + ntpReload(&heishamonSettings); } /* @@ -179,15 +195,16 @@ void check_wifi() void mqtt_reconnect() { unsigned long now = millis(); - if ((unsigned long)(now - lastMqttReconnectAttempt) > MQTTRECONNECTTIMER) { //only try reconnect each MQTTRECONNECTTIMER seconds or on boot when lastMqttReconnectAttempt is still 0 + if ((lastMqttReconnectAttempt == 0) || ((unsigned long)(now - lastMqttReconnectAttempt) > MQTTRECONNECTTIMER)) { //only try reconnect each MQTTRECONNECTTIMER seconds or on boot when lastMqttReconnectAttempt is still 0 lastMqttReconnectAttempt = now; - log_message((char*)"Reconnecting to mqtt server ..."); + log_message(F("Reconnecting to mqtt server ...")); char topic[256]; sprintf(topic, "%s/%s", heishamonSettings.mqtt_topic_base, mqtt_willtopic); if (mqtt_client.connect(heishamonSettings.wifi_hostname, heishamonSettings.mqtt_username, heishamonSettings.mqtt_password, topic, 1, true, "Offline")) { mqttReconnects++; + mqtt_client.subscribe("panasonic_heat_pump/opentherm/#"); sprintf(topic, "%s/%s/#", heishamonSettings.mqtt_topic_base, mqtt_topic_commands); mqtt_client.subscribe(topic); sprintf(topic, "%s/%s", heishamonSettings.mqtt_topic_base, mqtt_send_raw_value_topic); @@ -203,35 +220,58 @@ void mqtt_reconnect() sprintf_P(mqtt_topic, PSTR("%s/%s/WatthourTotal/2"), heishamonSettings.mqtt_topic_base, mqtt_topic_s0); mqtt_client.subscribe(mqtt_topic); } + if (mqttReconnects == 1) { //only resend all data on first connect to mqtt so a data bomb like and bad mqtt server will not cause a reconnect bomb everytime + if (heishamonSettings.use_1wire) resetlastalldatatime_dallas(); //resend all 1wire values to mqtt + resetlastalldatatime(); //resend all heatpump values to mqtt + } } + } +} +void log_message(const __FlashStringHelper *msg) { + PGM_P p = (PGM_P)msg; + int len = strlen_P((const char *)p); + char *str = (char *)MALLOC(len+1); + if(str == NULL) { + OUT_OF_MEMORY + } + strcpy_P(str, p); + log_message(str); - } + FREE(str); } void log_message(char* string) { + time_t rawtime; + rawtime = time(NULL); + struct tm *timeinfo = localtime(&rawtime); + char timestring[32]; + strftime(timestring, 32, "%c", timeinfo); + size_t len = strlen(string) + strlen(timestring) + 20; //+20 long enough to contain millis() + char* log_line = (char *) malloc(len); + snprintf(log_line, len, "%s (%lu): %s", timestring, millis(), string); + if (heishamonSettings.logSerial1) { - Serial1.print(millis()); - Serial1.print(": "); - Serial1.println(string); + Serial1.println(log_line); } if (heishamonSettings.logMqtt && mqtt_client.connected()) { char log_topic[256]; sprintf(log_topic, "%s/%s", heishamonSettings.mqtt_topic_base, mqtt_logtopic); - if (!mqtt_client.publish(log_topic, string)) { - Serial1.print(millis()); - Serial1.print(F(": ")); - Serial1.println(F("MQTT publish log message failed!")); + if (!mqtt_client.publish(log_topic, log_line)) { + if (heishamonSettings.logSerial1) { + Serial1.print(millis()); + Serial1.print(F(": ")); + Serial1.println(F("MQTT publish log message failed!")); + } mqtt_client.disconnect(); } } - if (webSocket.connectedClients() > 0) { - webSocket.broadcastTXT(string, strlen(string)); - } + websocket_write_all(log_line, strlen(log_line)); + free(log_line); } void logHex(char *hex, byte hex_len) { @@ -242,7 +282,7 @@ void logHex(char *hex, byte hex_len) { for (int j = 0; ((j < LOGHEXBYTESPERLINE) && ((i + j) < hex_len)); j++) { sprintf(&buffer[3 * j], "%02X ", hex[i + j]); } - sprintf(log_msg, "data: %s", buffer ); log_message(log_msg); + sprintf_P(log_msg, PSTR("data: %s"), buffer ); log_message(log_msg); } } @@ -265,24 +305,26 @@ bool isValidReceiveChecksum() { bool readSerial() { - if (data_length == 0 ) totalreads++; //this is the start of a new read - - while ((Serial.available()) && (data_length < MAXDATASIZE)) { - data[data_length] = Serial.read(); //read available data and place it after the last received data - data_length++; + int len = 0; + while ((Serial.available()) && (len < MAXDATASIZE)) { + data[data_length+len] = Serial.read(); //read available data and place it after the last received data + len++; if (data[0] != 113) { //wrong header received! - log_message((char*)"Received bad header. Ignoring this data!"); - if (heishamonSettings.logHexdump) logHex(data, data_length); + log_message(F("Received bad header. Ignoring this data!")); + if (heishamonSettings.logHexdump) logHex(data, len); badheaderread++; data_length = 0; return false; //return so this while loop does not loop forever if there happens to be a continous invalid data stream } } + if ((len > 0) && (data_length == 0 )) totalreads++; //this is the start of a new read + data_length += len; + if (data_length > 1) { //should have received length part of header now if ((data_length > (data[1] + 3)) || (data_length >= MAXDATASIZE) ) { - log_message((char*)"Received more data than header suggests! Ignoring this as this is bad data."); + log_message(F("Received more data than header suggests! Ignoring this as this is bad data.")); if (heishamonSettings.logHexdump) logHex(data, data_length); data_length = 0; toolongread++; @@ -290,31 +332,38 @@ bool readSerial() } if (data_length == (data[1] + 3)) { //we received all data (data[1] is header length field) - sprintf(log_msg, "Received %d bytes data", data_length); log_message(log_msg); + sprintf_P(log_msg, PSTR("Received %d bytes data"), data_length); log_message(log_msg); sending = false; //we received an answer after our last command so from now on we can start a new send request again if (heishamonSettings.logHexdump) logHex(data, data_length); if (! isValidReceiveChecksum() ) { - log_message((char*)"Checksum received false!"); + log_message(F("Checksum received false!")); data_length = 0; //for next attempt badcrcread++; return false; } - log_message((char*)"Checksum and header received ok!"); + log_message(F("Checksum and header received ok!")); goodreads++; - if (data_length == 203) { //for now only return true for this datagram because we can not decode the shorter datagram yet - data_length = 0; + if (data_length == DATASIZE) { //decode the normal data decode_heatpump_data(data, actData, mqtt_client, log_message, heishamonSettings.mqtt_topic_base, heishamonSettings.updateAllTime); + memcpy(actData, data, DATASIZE); + { + char mqtt_topic[256]; + sprintf(mqtt_topic, "%s/raw/data", heishamonSettings.mqtt_topic_base); + mqtt_client.publish(mqtt_topic, (const uint8_t *)actData, DATASIZE, false); //do not retain this raw data + } + data_length = 0; return true; } - else if (data_length == 20 ) { //optional pcb acknowledge answer - log_message((char*)"Received optional PCB ack answer. Decoding this in OPT topics."); - data_length = 0; + else if (data_length == OPTDATASIZE ) { //optional pcb acknowledge answer + log_message(F("Received optional PCB ack answer. Decoding this in OPT topics.")); decode_optional_heatpump_data(data, actOptData, mqtt_client, log_message, heishamonSettings.mqtt_topic_base, heishamonSettings.updateAllTime); + memcpy(actOptData, data, OPTDATASIZE); + data_length = 0; return true; } else { - log_message((char*)"Received a shorter datagram. Can't decode this yet."); + log_message(F("Received a shorter datagram. Can't decode this yet.")); data_length = 0; return false; } @@ -334,7 +383,7 @@ void popCommandBuffer() { void pushCommandBuffer(byte* command, int length) { if (cmdnrel + 1 > MAXCOMMANDSINBUFFER) { - log_message((char *)"Too much commands already in buffer. Ignoring this commands.\n"); + log_message(F("Too much commands already in buffer. Ignoring this commands.\n")); return; } cmdbuffer[cmdend].length = length; @@ -345,11 +394,11 @@ void pushCommandBuffer(byte* command, int length) { bool send_command(byte* command, int length) { if ( heishamonSettings.listenonly ) { - log_message((char*)"Not sending this command. Heishamon in listen only mode!"); + log_message(F("Not sending this command. Heishamon in listen only mode!")); return false; } if ( sending ) { - log_message((char*)"Already sending data. Buffering this send request"); + log_message(F("Already sending data. Buffering this send request")); pushCommandBuffer(command, length); return false; } @@ -369,7 +418,7 @@ bool send_command(byte* command, int length) { // Callback function that is called when a message has been pushed to one of your topics. void mqtt_callback(char* topic, byte* payload, unsigned int length) { if (mqttcallbackinprogress) { - log_message((char*)"Already processing another mqtt callback. Ignoring this one"); + log_message(F("Already processing another mqtt callback. Ignoring this one")); } else { mqttcallbackinprogress = true; //simple semaphore to make sure we don't have two callbacks at the same time @@ -388,6 +437,7 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) { sprintf_P(log_msg, PSTR("sending raw value")); log_message(log_msg); send_command(rawcommand, length); + free(rawcommand); } else if (strncmp(topic_command, mqtt_topic_s0, 2) == 0) // this is a s0 topic, check for watthour topic and restore it { char* topic_s0_watthour_port = topic_command + 17; //strip the first 17 "s0/WatthourTotal/" from the topic to get the s0 port @@ -398,12 +448,25 @@ void mqtt_callback(char* topic, byte* payload, unsigned int length) { char mqtt_topic[256]; sprintf(mqtt_topic, "%s", topic); if (mqtt_client.unsubscribe(mqtt_topic)) { - log_message((char*)"Unsubscribed from S0 watthour restore topic"); + log_message(F("Unsubscribed from S0 watthour restore topic")); } } else if (strncmp(topic_command, mqtt_topic_commands, 8) == 0) // check for optional pcb commands { char* topic_sendcommand = topic_command + 9; //strip the first 9 "commands/" from the topic to get what we need send_heatpump_command(topic_sendcommand, msg, send_command, log_message, heishamonSettings.optionalPCB); + } else if (stricmp((char const *)topic, "panasonic_heat_pump/opentherm/Temperature") == 0) { + char cpy[length + 1]; + memset(&cpy, 0, length + 1); + strncpy(cpy, (char *)payload, length); + openTherm[0] = cpy; + rules_event_cb("temperature"); + } else if (stricmp((char const *)topic, "panasonic_heat_pump/opentherm/Setpoint") == 0) { + char cpy[length + 1]; + memset(&cpy, 0, length + 1); + strncpy(cpy, (char *)payload, length); + openTherm[1] = cpy; + + rules_event_cb("setpoint"); } mqttcallbackinprogress = false; } @@ -432,83 +495,383 @@ void setupOTA() { ArduinoOTA.begin(); } -void setupHttp() { - httpUpdater.setup(&httpServer, heishamonSettings.update_path, heishamonSettings.update_username, heishamonSettings.ota_password); - httpServer.on("/", [] { - handleRoot(&httpServer, readpercentage, mqttReconnects, &heishamonSettings); - }); - httpServer.on("/command", [] { - handleREST(&httpServer, heishamonSettings.optionalPCB); - }); - httpServer.on("/tablerefresh", [] { - handleTableRefresh(&httpServer, actData); - }); - httpServer.on("/json", [] { - handleJsonOutput(&httpServer, actData); - }); - httpServer.on("/factoryreset", [] { - handleFactoryReset(&httpServer); - }); - httpServer.on("/reboot", [] { - handleReboot(&httpServer); - }); - httpServer.on("/debug", [] { - handleDebug(&httpServer, data, 203); - }); - httpServer.on("/settings", [] { - if (handleSettings(&httpServer, &heishamonSettings)) { - // reload some settings during runtime - setupConditionals(); - } - }); - httpServer.on("/wifiscan", [] { - handleWifiScan(&httpServer); - }); +int8_t webserver_cb(struct webserver_t *client, void *dat) { + switch (client->step) { + case WEBSERVER_CLIENT_REQUEST_METHOD: { + if (strcmp_P((char *)dat, PSTR("POST")) == 0) { + client->route = 110; + } + return 0; + } break; + case WEBSERVER_CLIENT_REQUEST_URI: { + if (strcmp_P((char *)dat, PSTR("/")) == 0) { + client->route = 1; + } else if (strcmp_P((char *)dat, PSTR("/tablerefresh")) == 0) { + client->route = 10; + } else if (strcmp_P((char *)dat, PSTR("/json")) == 0) { + client->route = 20; + } else if (strcmp_P((char *)dat, PSTR("/reboot")) == 0) { + client->route = 30; + } else if (strcmp_P((char *)dat, PSTR("/debug")) == 0) { + client->route = 40; + log_message(F("Debug URL requested")); + } else if (strcmp_P((char *)dat, PSTR("/wifiscan")) == 0) { + client->route = 50; + } else if (strcmp_P((char *)dat, PSTR("/togglelog")) == 0) { + client->route = 1; + log_message(F("Toggled mqtt log flag")); + heishamonSettings.logMqtt ^= true; + } else if (strcmp_P((char *)dat, PSTR("/togglehexdump")) == 0) { + client->route = 1; + log_message(F("Toggled hexdump log flag")); + heishamonSettings.logHexdump ^= true; + } else if (strcmp_P((char *)dat, PSTR("/hotspot-detect.html")) == 0 || + strcmp_P((char *)dat, PSTR("/fwlink")) == 0 || + strcmp_P((char *)dat, PSTR("/generate_204")) == 0 || + strcmp_P((char *)dat, PSTR("/gen_204")) == 0 || + strcmp_P((char *)dat, PSTR("/popup")) == 0) { + client->route = 80; + } else if (strcmp_P((char *)dat, PSTR("/factoryreset")) == 0) { + client->route = 90; + } else if (strcmp_P((char *)dat, PSTR("/command")) == 0) { + if((client->userdata = malloc(1)) == NULL) { + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + } + ((char *)client->userdata)[0] = 0; + client->route = 100; + } else if (client->route == 110) { + // Only accept settings POST requests + if (strcmp_P((char *)dat, PSTR("/savesettings")) == 0) { + client->route = 110; + } else if (strcmp_P((char *)dat, PSTR("/saverules")) == 0) { + client->route = 170; + + if (LittleFS.begin()) { + LittleFS.remove("/rules.new"); + client->userdata = new File(LittleFS.open("/rules.new", "a+")); + } + } else if (strcmp_P((char *)dat, PSTR("/firmware")) == 0) { + if (!Update.isRunning()) { + Update.runAsync(true); + if (!Update.begin((ESP.getFreeSketchSpace() - 0x1000) & 0xFFFFF000)) { + Update.printError(Serial1); + return -1; + } else { + client->route = 150; + } + } else { + Serial1.println(PSTR("New firmware update client, while previous isn't finished yet! Assume broken connection, abort!")); + Update.end(); + return -1; + } + } else { + return -1; + } + } else if (strcmp_P((char *)dat, PSTR("/settings")) == 0) { + client->route = 120; + } else if (strcmp_P((char *)dat, PSTR("/getsettings")) == 0) { + client->route = 130; + } else if (strcmp_P((char *)dat, PSTR("/firmware")) == 0) { + client->route = 140; + } else if (strcmp_P((char *)dat, PSTR("/rules")) == 0) { + client->route = 160; + } else { + client->route = 0; + } - httpServer.on("/smartcontrol", [] { - handleSmartcontrol(&httpServer, &heishamonSettings, actData); - }); - httpServer.on("/togglelog", [] { - log_message((char*)"Toggled mqtt log flag"); - heishamonSettings.logMqtt ^= true; - httpServer.sendHeader("Location", String("/"), true); - httpServer.send ( 302, "text/plain", ""); - httpServer.client().stop(); - }); - httpServer.on("/togglehexdump", [] { - log_message((char*)"Toggled hexdump log flag"); - heishamonSettings.logHexdump ^= true; - httpServer.sendHeader("Location", String("/"), true); - httpServer.send ( 302, "text/plain", ""); - httpServer.client().stop(); - }); - httpServer.onNotFound([]() { - httpServer.sendHeader("Location", String("/"), true); - httpServer.send(302, "text/plain", ""); - httpServer.client().stop(); - }); - /* - Captive portal url's - for now, the android one sometimes gets the heishamon in a wait loop during wifi reconfig - - httpServer.on("/generate_204", [] { - handleSettings(&httpServer, &heishamonSettings); - }); */ - httpServer.on("/hotspot-detect.html", [] { - handleSettings(&httpServer, &heishamonSettings); - }); - httpServer.on("/fwlink", [] { - handleSettings(&httpServer, &heishamonSettings); - }); - httpServer.on("/popup", [] { - handleSettings(&httpServer, &heishamonSettings); - }); + return 0; + } break; + case WEBSERVER_CLIENT_ARGS: { + struct arguments_t *args = (struct arguments_t *)dat; + switch (client->route) { + case 10: { + if (strcmp_P((char *)args->name, PSTR("1wire")) == 0) { + client->route = 11; + } else if (strcmp_P((char *)args->name, PSTR("s0")) == 0) { + client->route = 12; + } + } break; + case 100: { + unsigned char cmd[256] = { 0 }; + char cpy[args->len + 1]; + char log_msg[256] = { 0 }; + unsigned int len = 0; + + memset(&cpy, 0, args->len + 1); + snprintf((char *)&cpy, args->len + 1, "%.*s", args->len, args->value); + + for (uint8_t x = 0; x < sizeof(commands) / sizeof(commands[0]); x++) { + cmdStruct tmp; + memcpy_P(&tmp, &commands[x], sizeof(tmp)); + if (strcmp((char *)args->name, tmp.name) == 0) { + len = tmp.func(cpy, cmd, log_msg); + if ((client->userdata = realloc(client->userdata, strlen((char *)client->userdata) + strlen(log_msg) + 2)) == NULL) { + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + } + strcat((char *)client->userdata, log_msg); + strcat((char *)client->userdata, "\n"); + log_message(log_msg); + send_command(cmd, len); + } + } + + memset(&cmd, 256, 0); + memset(&log_msg, 256, 0); + + if (heishamonSettings.optionalPCB) { + //optional commands + for (uint8_t x = 0; x < sizeof(optionalCommands) / sizeof(optionalCommands[0]); x++) { + optCmdStruct tmp; + memcpy_P(&tmp, &optionalCommands[x], sizeof(tmp)); + if (strcmp((char *)args->name, tmp.name) == 0) { + len = tmp.func(cpy, log_msg); + if ((client->userdata = realloc(client->userdata, strlen((char *)client->userdata) + strlen(log_msg) + 2)) == NULL) { + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + } + strcat((char *)client->userdata, log_msg); + strcat((char *)client->userdata, "\n"); + log_message(log_msg); + } + } + } + + if (stricmp((char const *)args->name, "temperature") == 0) { + char cpy[args->len + 1]; + strcpy(cpy, (char *)args->value); + openTherm[0] = cpy; + rules_event_cb("temperature"); + } else if (stricmp((char const *)args->name, "setpoint") == 0) { + char cpy[args->len + 1]; + strcpy(cpy, (char *)args->value); + openTherm[1] = cpy; + rules_event_cb("setpoint"); + } + } break; + case 110: { + return cacheSettings(client, args); + } break; + case 150: { + if (Update.isRunning() && (!Update.hasError())) { + if ((strcmp((char *)args->name, "md5") == 0) && (args->len > 0)) { + char md5[args->len + 1]; + memset(&md5, 0, args->len + 1); + snprintf((char *)&md5, args->len + 1, "%.*s", args->len, args->value); + sprintf_P(log_msg, PSTR("Firmware MD5 expected: %s"), md5); + log_message(log_msg); + if (!Update.setMD5(md5)) { + log_message(F("Failed to set expected update file MD5!")); + Update.end(false); + } + } else if (strcmp((char *)args->name, "firmware") == 0) { + if (Update.write((uint8_t *)args->value, args->len) != args->len) { + Update.printError(Serial1); + Update.end(false); + } else { + if (uploadpercentage != (unsigned int)(((float)client->readlen / (float)client->totallen) * 20)) { + uploadpercentage = (unsigned int)(((float)client->readlen / (float)client->totallen) * 20); + sprintf_P(log_msg, PSTR("Uploading new firmware: %d%%"), uploadpercentage * 5); + log_message(log_msg); + } + } + } + } else { + log_message((char*)"New firmware POST data but update not running anymore!"); + } + } break; + case 170: { + File *f = (File *)client->userdata; + if (!f || !*f) { + client->route = 160; + } else { + f->write(args->value, args->len); + } + } break; + } + } break; + case WEBSERVER_CLIENT_HEADER: { + struct arguments_t *args = (struct arguments_t *)dat; + return 0; + } break; + case WEBSERVER_CLIENT_WRITE: { + switch (client->route) { + case 0: { + if(client->content == 0) { + webserver_send(client, 404, (char *)"text/plain", 13); + webserver_send_content_P(client, PSTR("404 Not found"), 13); + } + return 0; + } break; + case 1: { + return handleRoot(client, readpercentage, mqttReconnects, &heishamonSettings); + } break; + case 10: + case 11: + case 12: { + return handleTableRefresh(client, actData); + } break; + case 20: { + return handleJsonOutput(client, actData); + } break; + case 30: { + return handleReboot(client); + } break; + case 40: { + return handleDebug(client, (char *)data, 203); + } break; + case 50: { + return handleWifiScan(client); + } break; + case 80: { + return handleSettings(client); + } break; + case 90: { + return handleFactoryReset(client); + } break; + case 100: { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/plain", 0); + char *RESTmsg = (char *)client->userdata; + webserver_send_content(client, (char *)RESTmsg, strlen(RESTmsg)); + free(RESTmsg); + client->userdata = NULL; + } + return 0; + } break; + case 110: { + int ret = saveSettings(client, &heishamonSettings); + switch (client->route) { + case 111: { + return settingsNewPassword(client, &heishamonSettings); + } break; + case 112: { + return settingsReconnectWifi(client, &heishamonSettings); + } break; + case 113: { + webserver_send(client, 301, (char *)"text/plain", 0); + } break; + } + return 0; + } break; + case 111: { + return settingsNewPassword(client, &heishamonSettings); + } break; + case 112: { + return settingsReconnectWifi(client, &heishamonSettings); + } break; + case 120: { + return handleSettings(client); + } break; + case 130: { + return getSettings(client, &heishamonSettings); + } break; + case 140: { + return showFirmware(client); + } break; + case 150: { + log_message((char*)"In /firmware client write part"); + if (Update.isRunning()) { + if (Update.end(true)) { + log_message((char*)"Firmware update success"); + timerqueue_insert(2, 0, -2); // Start reboot sequence + return showFirmwareSuccess(client); + } else { + Update.printError(Serial1); + return showFirmwareFail(client); + } + } + return 0; + } break; + case 160: { + return showRules(client); + } break; + case 170: { + File *f = (File *)client->userdata; + if (f) { + if (*f) { + f->close(); + } + delete f; + } + client->userdata = NULL; + timerqueue_insert(0, 1, -4); + webserver_send(client, 301, (char *)"text/plain", 0); + } break; + default: { + webserver_send(client, 301, (char *)"text/plain", 0); + } break; + } + return -1; + } break; + case WEBSERVER_CLIENT_CREATE_HEADER: { + struct header_t *header = (struct header_t *)dat; + switch (client->route) { + case 113: { + header->ptr += sprintf_P((char *)header->buffer, PSTR("Location: /settings")); + return -1; + } break; + case 60: + case 70: { + header->ptr += sprintf_P((char *)header->buffer, PSTR("Location: /")); + return -1; + } break; + case 170: { + header->ptr += sprintf_P((char *)header->buffer, PSTR("Location: /rules")); + return -1; + } break; + default: { + if(client->route != 0) { + header->ptr += sprintf_P((char *)header->buffer, PSTR("Access-Control-Allow-Origin: *")); + } + } break; + } + return 0; + } break; + case WEBSERVER_CLIENT_CLOSE: { + switch (client->route) { + case 100: { + if (client->userdata != NULL) { + free(client->userdata); + } + } break; + case 110: { + struct websettings_t *tmp = NULL; + while (client->userdata) { + tmp = (struct websettings_t *)client->userdata; + client->userdata = ((struct websettings_t *)(client->userdata))->next; + free(tmp); + } + } break; + case 160: + case 170: { + if (client->userdata != NULL) { + File *f = (File *)client->userdata; + if (f) { + if (*f) { + f->close(); + } + delete f; + } + } + } break; + } + client->userdata = NULL; + } break; + default: { + return 0; + } break; + } - httpServer.begin(); + return 0; +} - webSocket.begin(); - webSocket.onEvent(webSocketEvent); - webSocket.enableHeartbeat(3000, 3000, 2); +void setupHttp() { + webserver_start(80, &webserver_cb, 0); } void doubleResetDetect() { @@ -565,7 +928,7 @@ void switchSerial() { setupGPIO(heishamonSettings.gpioSettings); //switch extra GPIOs to configured mode - //enable gpio15 after boot using gpio5 (D1) + //enable gpio15 after boot using gpio5 (D1) which enables the level shifter for the tx to panasonic pinMode(5, OUTPUT); digitalWrite(5, HIGH); } @@ -581,32 +944,66 @@ void setupConditionals() { //load optional PCB data from flash if (heishamonSettings.optionalPCB) { if (loadOptionalPCB(optionalPCBQuery, OPTIONALPCBQUERYSIZE)) { - log_message((char*)"Succesfully loaded optional PCB data from saved flash!"); + log_message(F("Succesfully loaded optional PCB data from saved flash!")); } else { - log_message((char*)"Failed to load optional PCB data from flash!"); + log_message(F("Failed to load optional PCB data from flash!")); } delay(1500); //need 1.5 sec delay before sending first datagram send_optionalpcb_query(); //send one datagram already at start + lastOptionalPCBRunTime = millis(); } //these two after optional pcb because it needs to send a datagram fast after boot - if (heishamonSettings.use_1wire) initDallasSensors(log_message, heishamonSettings.updataAllDallasTime, heishamonSettings.waitDallasTime); + if (heishamonSettings.use_1wire) initDallasSensors(log_message, heishamonSettings.updataAllDallasTime, heishamonSettings.waitDallasTime, heishamonSettings.dallasResolution); if (heishamonSettings.use_s0) initS0Sensors(heishamonSettings.s0Settings); } + +void timer_cb(int nr) { + if(nr > 0) { + rules_timer_cb(nr); + } else { + switch (nr) { + case -1: { + LittleFS.begin(); + LittleFS.format(); + WiFi.disconnect(true); + timerqueue_insert(1, 0, -2); + } break; + case -2: { + ESP.restart(); + } break; + case -3: { + setupWifi(&heishamonSettings); + } break; + case -4: { + if(rules_parse("/rules.new") == -1) { + logprintln_P(F("new ruleset failed to parse, using previous ruleset")); + rules_parse("/rules.txt"); + } else { + logprintln_P(F("new ruleset successfully parsed")); + if(LittleFS.begin()) { + LittleFS.rename("/rules.new", "/rules.txt"); + } + } + rules_boot(); + } break; + } + } + +} + void setup() { - //first get total memory before we do anything getFreeMemory(); //set boottime - getUptime(); - - + char *up = getUptime(); + free(up); + setupSerial(); setupSerial1(); - - + Serial.println(); Serial.println(F("--- HEISHAMON ---")); Serial.println(F("starting...")); @@ -615,12 +1012,20 @@ void setup() { doubleResetDetect(); WiFi.printDiag(Serial); + //initiate a wifi scan at boot to prefill the wifi scan json list + byte numSsid = WiFi.scanNetworks(); + getWifiScanResults(numSsid); + loadSettings(&heishamonSettings); + setupWifi(&heishamonSettings); setupMqtt(); setupHttp(); + sntp_setoperatingmode(SNTP_OPMODE_POLL); + sntp_init(); + switchSerial(); //switch serial to gpio13/gpio15 WiFi.printDiag(Serial1); @@ -629,24 +1034,44 @@ void setup() { dnsServer.setErrorReplyCode(DNSReplyCode::NoError); dnsServer.start(DNS_PORT, "*", apIP); + //maybe necessary but for now disable. CZ-TAW1 sends this query on boot + //if (!heishamonSettings.listenonly) send_initial_query(); + + rst_info *resetInfo = ESP.getResetInfoPtr(); + Serial1.printf(PSTR("Reset reason: %d, exception cause: %d\n"), resetInfo->reason, resetInfo->exccause); + + if(resetInfo->reason > 0 && resetInfo->reason < 4) { + if(LittleFS.begin()) { + LittleFS.rename("/rules.txt", "/rules.old"); + } + rules_setup(); + if(LittleFS.begin()) { + LittleFS.rename("/rules.old", "/rules.txt"); + } + } else { + rules_setup(); + } +} + +void send_initial_query() { + log_message(F("Requesting initial start query")); + send_command(initialQuery, INITIALQUERYSIZE); } void send_panasonic_query() { - String message = F("Requesting new panasonic data"); - log_message((char*)message.c_str()); + log_message(F("Requesting new panasonic data")); send_command(panasonicQuery, PANASONICQUERYSIZE); } void send_optionalpcb_query() { - String message = F("Sending optional PCB data"); - log_message((char*)message.c_str()); + log_message(F("Sending optional PCB data")); send_command(optionalPCBQuery, OPTIONALPCBQUERYSIZE); } void read_panasonic_data() { if (sending && ((unsigned long)(millis() - sendCommandReadTime) > SERIALTIMEOUT)) { - log_message((char*)"Previous read data attempt failed due to timeout!"); + log_message(F("Previous read data attempt failed due to timeout!")); sprintf_P(log_msg, PSTR("Received %d bytes data"), data_length); log_message(log_msg); if (heishamonSettings.logHexdump) logHex(data, data_length); @@ -663,21 +1088,19 @@ void read_panasonic_data() { } void loop() { + webserver_loop(); + // check wifi check_wifi(); // Handle OTA first. ArduinoOTA.handle(); - // then handle HTTP - httpServer.handleClient(); - // handle Websockets - webSocket.loop(); mqtt_client.loop(); read_panasonic_data(); if ((!sending) && (cmdnrel > 0)) { //check if there is a send command in the buffer - log_message((char *)"Sending command from buffer"); + log_message(F("Sending command from buffer")); popCommandBuffer(); } @@ -685,9 +1108,10 @@ void loop() { if (heishamonSettings.use_s0) s0Loop(mqtt_client, log_message, heishamonSettings.mqtt_topic_base, heishamonSettings.s0Settings); - if (heishamonSettings.SmartControlSettings.enableHeatCurve) smartControlLoop(log_message, heishamonSettings.SmartControlSettings, actData, goodreads); - - if ((!sending) && (!heishamonSettings.listenonly) && (heishamonSettings.optionalPCB)) send_optionalpcb_query(); //send this as fast as possible or else we could get warnings on heatpump + if ((!sending) && (!heishamonSettings.listenonly) && (heishamonSettings.optionalPCB) && ((unsigned long)(millis() - lastOptionalPCBRunTime) > OPTIONALPCBQUERYTIME) ) { + lastOptionalPCBRunTime = millis(); + send_optionalpcb_query(); + } // run the data query only each WAITTIME if ((unsigned long)(millis() - lastRunTime) > (1000 * heishamonSettings.waitTime)) { @@ -695,33 +1119,47 @@ void loop() { //check mqtt if ( (WiFi.isConnected()) && (!mqtt_client.connected()) ) { - log_message((char *)"Lost MQTT connection!"); + log_message(F("Lost MQTT connection!")); mqtt_reconnect(); } //log stats if (totalreads > 0 ) readpercentage = (((float)goodreads / (float)totalreads) * 100); - String message = F("Heishamon stats: Uptime: "); - message += getUptime(); + String message; + message.reserve(384); + message += F("Heishamon stats: Uptime: "); + char *up = getUptime(); + message += up; + free(up); message += F(" ## Free memory: "); message += getFreeMemory(); - message += F("% "); + message += F("% ## Heap fragmentation: "); + message += ESP.getHeapFragmentation(); + message += F("% ## Max free block: "); + message += ESP.getMaxFreeBlockSize(); + message += F(" bytes ## Free heap: "); message += ESP.getFreeHeap(); message += F(" bytes ## Wifi: "); message += getWifiQuality(); - message += F("% ## Mqtt reconnects: "); + message += F("% (RSSI: "); + message += WiFi.RSSI(); + message += F(") ## Mqtt reconnects: "); message += mqttReconnects; message += F(" ## Correct data: "); message += readpercentage; message += F("%"); log_message((char*)message.c_str()); - String stats = F("{\"uptime\":"); + String stats; + stats.reserve(384); + stats += F("{\"uptime\":"); stats += String(millis()); stats += F(",\"voltage\":"); stats += ESP.getVcc() / 1024.0; stats += F(",\"free memory\":"); stats += getFreeMemory(); + stats += F(",\"free heap\":"); + stats += ESP.getFreeHeap(); stats += F(",\"wifi\":"); stats += getWifiQuality(); stats += F(",\"mqtt reconnects\":"); @@ -741,18 +1179,20 @@ void loop() { stats += F(",\"timeout reads\":"); stats += timeoutread; stats += F("}"); - sprintf(mqtt_topic, "%s/stats", heishamonSettings.mqtt_topic_base); + sprintf_P(mqtt_topic, PSTR("%s/stats"), heishamonSettings.mqtt_topic_base); mqtt_client.publish(mqtt_topic, stats.c_str(), MQTT_RETAIN_VALUES); //get new data if (!heishamonSettings.listenonly) send_panasonic_query(); //Make sure the LWT is set to Online, even if the broker have marked it dead. - sprintf(mqtt_topic, "%s/%s", heishamonSettings.mqtt_topic_base, mqtt_willtopic); + sprintf_P(mqtt_topic, PSTR("%s/%s"), heishamonSettings.mqtt_topic_base, mqtt_willtopic); mqtt_client.publish(mqtt_topic, "Online"); if (WiFi.isConnected()) { MDNS.announce(); } } + + timerqueue_update(); } diff --git a/HeishaMon/commands.cpp b/HeishaMon/commands.cpp index 05c64c44..068b5693 100644 --- a/HeishaMon/commands.cpp +++ b/HeishaMon/commands.cpp @@ -2,6 +2,7 @@ #include //removed checksum from default query, is calculated in send_command +byte initialQuery[] = {0x31, 0x05, 0x10, 0x01, 0x00, 0x00, 0x00}; byte panasonicQuery[] = {0x71, 0x6c, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; byte optionalPCBQuery[] = {0xF1, 0x11, 0x01, 0x50, 0x00, 0x00, 0x40, 0xFF, 0xFF, 0xE5, 0xFF, 0xFF, 0x00, 0xFF, 0xEB, 0xFF, 0xFF, 0x00, 0x00}; byte panasonicSendQuery[] PROGMEM = {0xf1, 0x6c, 0x01, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; @@ -64,7 +65,7 @@ unsigned int set_heatpump_state(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_pump(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_pump_string(msg); byte pump_state = 16; @@ -87,7 +88,7 @@ unsigned int set_pump(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_max_pump_duty(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_pumpduty_string(msg); byte pumpduty = set_pumpduty_string.toInt() + 1; @@ -107,7 +108,7 @@ unsigned int set_max_pump_duty(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_quiet_mode(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_quiet_mode_string(msg); byte quiet_mode = (set_quiet_mode_string.toInt() + 1) * 8; @@ -127,7 +128,7 @@ unsigned int set_quiet_mode(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_z1_heat_request_temperature(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_temperature_string(msg); byte request_temp = set_temperature_string.toInt() + 128; @@ -147,7 +148,7 @@ unsigned int set_z1_heat_request_temperature(char *msg, unsigned char *cmd, char } unsigned int set_z1_cool_request_temperature(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_temperature_string(msg); byte request_temp = set_temperature_string.toInt() + 128; @@ -167,7 +168,7 @@ unsigned int set_z1_cool_request_temperature(char *msg, unsigned char *cmd, char } unsigned int set_z2_heat_request_temperature(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_temperature_string(msg); byte request_temp = set_temperature_string.toInt() + 128; @@ -187,7 +188,7 @@ unsigned int set_z2_heat_request_temperature(char *msg, unsigned char *cmd, char } unsigned int set_z2_cool_request_temperature(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_temperature_string(msg); byte request_temp = set_temperature_string.toInt() + 128; @@ -207,7 +208,7 @@ unsigned int set_z2_cool_request_temperature(char *msg, unsigned char *cmd, char } unsigned int set_force_DHW(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_force_DHW_string(msg); byte force_DHW_mode = 64; //hex 0x40 @@ -230,7 +231,7 @@ unsigned int set_force_DHW(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_force_defrost(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_force_defrost_string(msg); byte force_defrost_mode = 0; @@ -253,7 +254,7 @@ unsigned int set_force_defrost(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_force_sterilization(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_force_sterilization_string(msg); byte force_sterilization_mode = 0; @@ -276,7 +277,7 @@ unsigned int set_force_sterilization(char *msg, unsigned char *cmd, char *log_ms } unsigned int set_holiday_mode(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_holiday_string(msg); byte set_holiday = 16; //hex 0x10 @@ -299,7 +300,7 @@ unsigned int set_holiday_mode(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_powerful_mode(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_powerful_string(msg); byte set_powerful = (set_powerful_string.toInt() ) + 73; @@ -319,7 +320,7 @@ unsigned int set_powerful_mode(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_DHW_temp(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_DHW_temp_string(msg); byte set_DHW_temp = set_DHW_temp_string.toInt() + 128; @@ -339,7 +340,7 @@ unsigned int set_DHW_temp(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_operation_mode(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_mode_string(msg); byte set_mode; @@ -405,7 +406,7 @@ unsigned int set_curves(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_zones(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_mode_string(msg); byte set_mode; @@ -431,7 +432,7 @@ unsigned int set_zones(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_floor_heat_delta(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_temperature_string(msg); byte request_temp = set_temperature_string.toInt() + 128; @@ -451,7 +452,7 @@ unsigned int set_floor_heat_delta(char *msg, unsigned char *cmd, char *log_msg) } unsigned int set_floor_cool_delta(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_temperature_string(msg); byte request_temp = set_temperature_string.toInt() + 128; @@ -471,14 +472,14 @@ unsigned int set_floor_cool_delta(char *msg, unsigned char *cmd, char *log_msg) } unsigned int set_dhw_heat_delta(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String set_temperature_string(msg); byte request_temp = set_temperature_string.toInt() + 128; { char tmp[256] = { 0 }; - snprintf_P(tmp, 255, PSTR("set floor heat delta to %d"), request_temp - 128 ); + snprintf_P(tmp, 255, PSTR("set DHW heat delta to %d"), request_temp - 128 ); memcpy(log_msg, tmp, sizeof(tmp)); } @@ -491,7 +492,6 @@ unsigned int set_dhw_heat_delta(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_reset(char *msg, unsigned char *cmd, char *log_msg) { - unsigned len = 0; byte resetRequest = 0; String set_reset_string(msg); @@ -515,7 +515,7 @@ unsigned int set_reset(char *msg, unsigned char *cmd, char *log_msg) { } unsigned int set_heater_delay_time(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String stringValue(msg); byte byteValue = stringValue.toInt() + 1; @@ -534,7 +534,7 @@ unsigned int set_heater_delay_time(char *msg, unsigned char *cmd, char *log_msg) return sizeof(panasonicSendQuery); } unsigned int set_heater_start_delta(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String stringValue(msg); byte byteValue = stringValue.toInt() + 128; @@ -553,7 +553,7 @@ unsigned int set_heater_start_delta(char *msg, unsigned char *cmd, char *log_msg return sizeof(panasonicSendQuery); } unsigned int set_heater_stop_delta(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String stringValue(msg); byte byteValue = stringValue.toInt() + 128; @@ -572,7 +572,7 @@ unsigned int set_heater_stop_delta(char *msg, unsigned char *cmd, char *log_msg) return sizeof(panasonicSendQuery); } unsigned int set_main_schedule(char *msg, unsigned char *cmd, char *log_msg) { - unsigned int len = 0; + String stringValue(msg); byte byteValue = 64; //hex 0x40 @@ -749,8 +749,10 @@ void send_heatpump_command(char* topic, char *msg, bool (*send_command)(byte*, i unsigned int len = 0; for (unsigned int i = 0; i < sizeof(commands) / sizeof(commands[0]); i++) { - if (strcmp(topic, commands[i].name) == 0) { - len = commands[i].func(msg, cmd, log_msg); + cmdStruct tmp; + memcpy_P(&tmp, &commands[i], sizeof(tmp)); + if (strcmp(topic, tmp.name) == 0) { + len = tmp.func(msg, cmd, log_msg); log_message(log_msg); send_command(cmd, len); } @@ -759,8 +761,10 @@ void send_heatpump_command(char* topic, char *msg, bool (*send_command)(byte*, i if (optionalPCB) { //run for optional pcb commands for (unsigned int i = 0; i < sizeof(optionalCommands) / sizeof(optionalCommands[0]); i++) { - if (strcmp(topic, optionalCommands[i].name) == 0) { - len = optionalCommands[i].func(msg, log_msg); + optCmdStruct tmp; + memcpy_P(&tmp, &optionalCommands[i], sizeof(tmp)); + if (strcmp(topic, tmp.name) == 0) { + len = tmp.func(msg, log_msg); log_message(log_msg); if ((unsigned long)(millis() - lastOptionalPCBSave) > (1000 * OPTIONALPCBSAVETIME)) { // only save each 5 minutes lastOptionalPCBSave = millis(); diff --git a/HeishaMon/commands.h b/HeishaMon/commands.h index 722b5428..853a5e58 100644 --- a/HeishaMon/commands.h +++ b/HeishaMon/commands.h @@ -1,13 +1,21 @@ +#define LWIP_INTERNAL + #include #include +#define DATASIZE 203 +#define INITIALQUERYSIZE 7 +extern byte initialQuery[INITIALQUERYSIZE]; #define PANASONICQUERYSIZE 110 extern byte panasonicQuery[PANASONICQUERYSIZE]; + +#define OPTIONALPCBQUERYTIME 1000 //send optional pcb query each second #define OPTIONALPCBQUERYSIZE 19 #define OPTIONALPCBSAVETIME 300 //save each 5 minutes the current optional pcb state into flash to have valid values during reboot extern byte optionalPCBQuery[OPTIONALPCBQUERYSIZE]; + extern const char* mqtt_topic_values; extern const char* mqtt_topic_commands; extern const char* mqtt_topic_pcbvalues; @@ -61,10 +69,12 @@ unsigned int set_z2_water_temp(char *msg, char *log_msg); unsigned int set_solar_temp(char *msg, char *log_msg); unsigned int set_byte_9(char *msg, char *log_msg); -struct { - const char *name; +struct cmdStruct { + char name[28]; unsigned int (*func)(char *msg, unsigned char *cmd, char *log_msg); -} commands[] PROGMEM = { +}; + +const cmdStruct commands[] PROGMEM = { // set heatpump state to on by sending 1 { "SetHeatpump", set_heatpump_state }, // set pump state to on by sending 1 @@ -109,10 +119,12 @@ struct { { "SetMainSchedule", set_main_schedule }, }; -struct { - const char *name; +struct optCmdStruct{ + char name[28]; unsigned int (*func)(char *msg, char *log_msg); -} optionalCommands[] PROGMEM = { +}; + +const optCmdStruct optionalCommands[] PROGMEM = { // optional PCB { "SetHeatCoolMode", set_heat_cool_mode }, { "SetCompressorState", set_compressor_state }, diff --git a/HeishaMon/dallas.cpp b/HeishaMon/dallas.cpp index caf97ba0..1bfe03ac 100644 --- a/HeishaMon/dallas.cpp +++ b/HeishaMon/dallas.cpp @@ -3,6 +3,7 @@ #include #include "commands.h" #include "dallas.h" +#include "rules.h" #define MQTT_RETAIN_VALUES 1 // do we retain 1wire values? @@ -24,7 +25,7 @@ unsigned long dallasTimer = 0; unsigned int updateAllDallasTime = 30000; // will be set using heishmonSettings unsigned int dallasTimerWait = 30000; // will be set using heishmonSettings -void initDallasSensors(void (*log_message)(char*), unsigned int updateAllDallasTimeSettings, unsigned int dallasTimerWaitSettings) { +void initDallasSensors(void (*log_message)(char*), unsigned int updateAllDallasTimeSettings, unsigned int dallasTimerWaitSettings, unsigned int dallasResolution) { char log_msg[256]; updateAllDallasTime = updateAllDallasTimeSettings; dallasTimerWait = dallasTimerWaitSettings; @@ -41,6 +42,7 @@ void initDallasSensors(void (*log_message)(char*), unsigned int updateAllDallasT actDallasData = new dallasDataStruct [dallasDevicecount]; for (int j = 0 ; j < dallasDevicecount; j++) { DS18B20.getAddress(actDallasData[j].sensor, j); + DS18B20.setResolution(actDallasData[j].sensor, dallasResolution); } DS18B20.requestTemperatures(); @@ -55,13 +57,17 @@ void initDallasSensors(void (*log_message)(char*), unsigned int updateAllDallasT if (DALLASASYNC) DS18B20.setWaitForConversion(false); //async 1wire during next loops } +void resetlastalldatatime_dallas() { + lastalldatatime_dallas = 0; +} + void readNewDallasTemp(PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base) { char log_msg[256]; char mqtt_topic[256]; char valueStr[20]; bool updatenow = false; - if ((unsigned long)(millis() > lastalldatatime_dallas) > (1000 * updateAllDallasTime)) { + if ((lastalldatatime_dallas == 0) || ((unsigned long)(millis() - lastalldatatime_dallas) > (1000 * updateAllDallasTime))) { updatenow = true; lastalldatatime_dallas = millis(); } @@ -69,7 +75,7 @@ void readNewDallasTemp(PubSubClient &mqtt_client, void (*log_message)(char*), ch for (int i = 0; i < dallasDevicecount; i++) { float temp = DS18B20.getTempC(actDallasData[i].sensor); if (temp < -120.0) { - sprintf(log_msg, "Error 1wire sensor offline: %s", actDallasData[i].address); log_message(log_msg); + sprintf_P(log_msg, PSTR("Error 1wire sensor offline: %s"), actDallasData[i].address); log_message(log_msg); } else { float allowedtempdiff = (((millis() - actDallasData[i].lastgoodtime)) / 1000.0) * MAXTEMPDIFFPERSEC; if ((actDallasData[i].temperature != -127.0) and ((temp > (actDallasData[i].temperature + allowedtempdiff)) or (temp < (actDallasData[i].temperature - allowedtempdiff)))) { @@ -81,8 +87,9 @@ void readNewDallasTemp(PubSubClient &mqtt_client, void (*log_message)(char*), ch actDallasData[i].temperature = temp; sprintf(log_msg, PSTR("Received 1wire sensor temperature (%s): %.2f"), actDallasData[i].address, actDallasData[i].temperature); log_message(log_msg); - sprintf(valueStr, "%.2f", actDallasData[i].temperature); - sprintf(mqtt_topic, "%s/%s/%s", mqtt_topic_base, mqtt_topic_1wire, actDallasData[i].address); mqtt_client.publish(mqtt_topic, valueStr, MQTT_RETAIN_VALUES); + sprintf_P(valueStr, PSTR("%.2f"), actDallasData[i].temperature); + sprintf_P(mqtt_topic, PSTR("%s/%s/%s"), mqtt_topic_base, mqtt_topic_1wire, actDallasData[i].address); mqtt_client.publish(mqtt_topic, valueStr, MQTT_RETAIN_VALUES); + rules_event_cb(actDallasData[i].address); } } } @@ -90,7 +97,7 @@ void readNewDallasTemp(PubSubClient &mqtt_client, void (*log_message)(char*), ch } void dallasLoop(PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base) { - if ((DALLASASYNC) && ((unsigned long)(millis() - dallasTimer) > ((1000 * dallasTimerWait)-1000)) ) { + if ((DALLASASYNC) && ((unsigned long)(millis() - dallasTimer) > ((1000 * dallasTimerWait) - 1000)) ) { DS18B20.requestTemperatures(); // get temperatures for next run 1 second before getting the temperatures (async) } if ((unsigned long)(millis() - dallasTimer) > (1000 * dallasTimerWait)) { @@ -100,26 +107,33 @@ void dallasLoop(PubSubClient &mqtt_client, void (*log_message)(char*), char* mqt } } -String dallasJsonOutput() { - String output = F("["); +void dallasJsonOutput(struct webserver_t *client) { + webserver_send_content_P(client, PSTR("["), 1); + for (int i = 0; i < dallasDevicecount; i++) { - output = output + F("{"); - output = output + F("\"Sensor\": \"") + actDallasData[i].address + F("\","); - output = output + F("\"Temperature\": \"") + actDallasData[i].temperature + F("\""); - output = output + F("}"); - if (i < dallasDevicecount - 1) output = output + F(","); + webserver_send_content_P(client, PSTR("{\"Sensor\":\""), 11); + webserver_send_content(client, actDallasData[i].address, strlen(actDallasData[i].address)); + webserver_send_content_P(client, PSTR("\",\"Temperature\":\""), 17); + char str[64]; + dtostrf(actDallasData[i].temperature, 0, 2, str); + webserver_send_content(client, str, strlen(str)); + if (i < dallasDevicecount - 1) { + webserver_send_content_P(client, PSTR("\"},"), 3); + } else { + webserver_send_content_P(client, PSTR("\"}"), 2); + } } - output = output + "]"; - return output; + webserver_send_content_P(client, PSTR("]"), 1); } -String dallasTableOutput() { - String output = ""; +void dallasTableOutput(struct webserver_t *client) { for (int i = 0; i < dallasDevicecount; i++) { - output = output + F(""); - output = output + F("") + actDallasData[i].address + F(""); - output = output + F("") + actDallasData[i].temperature + F(""); - output = output + F(""); + webserver_send_content_P(client, PSTR(""), 8); + webserver_send_content(client, actDallasData[i].address, strlen(actDallasData[i].address)); + webserver_send_content_P(client, PSTR(""), 9); + char str[64]; + dtostrf(actDallasData[i].temperature, 0, 2, str); + webserver_send_content(client, str, strlen(str)); + webserver_send_content_P(client, PSTR(""), 10); } - return output; } diff --git a/HeishaMon/dallas.h b/HeishaMon/dallas.h index f3d523b0..76393041 100644 --- a/HeishaMon/dallas.h +++ b/HeishaMon/dallas.h @@ -1,6 +1,10 @@ +#ifndef _DALLAS_H_ +#define _DALLAS_H_ + #include #include #include +#include "src/common/webserver.h" #define MAX_DALLAS_SENSORS 15 #define ONE_WIRE_BUS 4 // DS18B20 pin, for now a static config - should be in config menu later @@ -12,7 +16,10 @@ struct dallasDataStruct { char address[17]; }; +void resetlastalldatatime_dallas(); void dallasLoop(PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base); -void initDallasSensors(void (*log_message)(char*), unsigned int updataAllDallasTimeSettings, unsigned int dallasTimerWaitSettings); -String dallasJsonOutput(void); -String dallasTableOutput(void); +void initDallasSensors(void (*log_message)(char*), unsigned int updataAllDallasTimeSettings, unsigned int dallasTimerWaitSettings, unsigned int dallasResolution); +void dallasJsonOutput(struct webserver_t *client); +void dallasTableOutput(struct webserver_t *client); + +#endif \ No newline at end of file diff --git a/HeishaMon/decode.cpp b/HeishaMon/decode.cpp index dc069e53..a45a1c73 100644 --- a/HeishaMon/decode.cpp +++ b/HeishaMon/decode.cpp @@ -1,5 +1,6 @@ #include "decode.h" #include "commands.h" +#include "rules.h" unsigned long lastalldatatime = 0; unsigned long lastalloptdatatime = 0; @@ -24,7 +25,6 @@ String getBit3and4and5(byte input) { return String(((input >> 3) & 0b111) - 1); } - String getLeft5bits(byte input) { return String((input >> 3) - 1); } @@ -47,11 +47,13 @@ String getIntMinus1Div5(byte input) { return String((((float)input - 1) / 5), 1); } + String getIntMinus1Times10(byte input) { int value = (int)input - 1; return (String)(value * 10); } + String getIntMinus1Times50(byte input) { int value = (int)input - 1; return (String)(value * 50); @@ -97,8 +99,7 @@ String getModel(char* data) { // TOP92 // return String(modelResult); } -String getEnergy(byte input) -{ +String getEnergy(byte input) { int value = ((int)input - 1) * 200; return (String)value; } @@ -128,103 +129,118 @@ String getErrorInfo(char* data) { // TOP44 // return String(Error_string); } -// Decode //////////////////////////////////////////////////////////////////////////// -void decode_heatpump_data(char* data, String actData[], PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime) { - char log_msg[256]; - char mqtt_topic[256]; - bool updatenow = false; +void resetlastalldatatime() { + lastalldatatime = 0; + lastalloptdatatime = 0; +} + +String getDataValue(char* data, unsigned int Topic_Number) { + String Topic_Value; + byte Input_Byte; + switch (Topic_Number) { //switch on topic numbers, some have special needs + case 1: + Topic_Value = getPumpFlow(data); + break; + case 11: + Topic_Value = String(word(data[183], data[182]) - 1); + break; + case 12: + Topic_Value = String(word(data[180], data[179]) - 1); + break; + case 90: + Topic_Value = String(word(data[186], data[185]) - 1); + break; + case 91: + Topic_Value = String(word(data[189], data[188]) - 1); + break; + case 44: + Topic_Value = getErrorInfo(data); + break; + case 92: + Topic_Value = getModel(data); + break; + default: + byte cpy; + memcpy_P(&cpy, &topicBytes[Topic_Number], sizeof(byte)); + Input_Byte = data[cpy]; + Topic_Value = topicFunctions[Topic_Number](Input_Byte); + break; + } + return Topic_Value; +} + +String getOptDataValue(char* data, unsigned int Topic_Number) { + String Topic_Value; + switch (Topic_Number) { //switch on topic numbers, some have special needs + case 0: + Topic_Value = String(data[4] >> 7); + break; + case 1: + Topic_Value = String((data[4] >> 5) & 0b11); + break; + case 2: + Topic_Value = String((data[4] >> 4) & 0b1); + break; + case 3: + Topic_Value = String((data[4] >> 2) & 0b11); + break; + case 4: + Topic_Value = String((data[4] >> 1) & 0b1); + break; + case 5: + Topic_Value = String((data[4] >> 0) & 0b1); + break; + case 6: + Topic_Value = String((data[5] >> 0) & 0b1); + break; + default: + break; + } + return Topic_Value; +} +// Decode //////////////////////////////////////////////////////////////////////////// +void decode_heatpump_data(char* data, char* actData, PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime) { + bool updatenow = false; + if ((lastalldatatime == 0) || ((unsigned long)(millis() - lastalldatatime) > (1000 * updateAllTime))) { + updatenow = true; + lastalldatatime = millis(); + } for (unsigned int Topic_Number = 0 ; Topic_Number < NUMBER_OF_TOPICS ; Topic_Number++) { - byte Input_Byte; String Topic_Value; - switch (Topic_Number) { //switch on topic numbers, some have special needs - case 1: - Topic_Value = getPumpFlow(data); - break; - case 11: - Topic_Value = String(word(data[183], data[182]) - 1); - break; - case 12: - Topic_Value = String(word(data[180], data[179]) - 1); - break; - case 90: - Topic_Value = String(word(data[186], data[185]) - 1); - break; - case 91: - Topic_Value = String(word(data[189], data[188]) - 1); - break; - case 44: - Topic_Value = getErrorInfo(data); - break; - case 92: - Topic_Value = getModel(data); - break; - default: - byte cpy; - memcpy_P(&cpy, &topicBytes[Topic_Number], sizeof(byte)); - Input_Byte = data[cpy]; - Topic_Value = topicFunctions[Topic_Number](Input_Byte); - break; - } - if ((unsigned long)(millis() - lastalldatatime) > (1000 * updateAllTime)) { - updatenow = true; - lastalldatatime = millis();; - } - if ((updatenow) || ( actData[Topic_Number] != Topic_Value )) { - actData[Topic_Number] = Topic_Value; + Topic_Value = getDataValue(data, Topic_Number); + + if ((updatenow) || ( getDataValue(actData, Topic_Number) != Topic_Value )) { + char log_msg[256]; + char mqtt_topic[256]; sprintf_P(log_msg, PSTR("received TOP%d %s: %s"), Topic_Number, topics[Topic_Number], Topic_Value.c_str()); log_message(log_msg); - sprintf(mqtt_topic, "%s/%s/%s", mqtt_topic_base, mqtt_topic_values, topics[Topic_Number]); + sprintf_P(mqtt_topic, PSTR("%s/%s/%s"), mqtt_topic_base, mqtt_topic_values, topics[Topic_Number]); mqtt_client.publish(mqtt_topic, Topic_Value.c_str(), MQTT_RETAIN_VALUES); + rules_new_event(topics[Topic_Number]); } } } -void decode_optional_heatpump_data(char* data, String actOptData[], PubSubClient & mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime) { - char log_msg[256]; - char mqtt_topic[256]; +void decode_optional_heatpump_data(char* data, char* actOptData, PubSubClient & mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime) { bool updatenow = false; - - + if ((lastalloptdatatime == 0) || ((unsigned long)(millis() - lastalloptdatatime) > (1000 * updateAllTime))) { + updatenow = true; + lastalloptdatatime = millis(); + } for (unsigned int Topic_Number = 0 ; Topic_Number < NUMBER_OF_OPT_TOPICS ; Topic_Number++) { - byte Input_Byte; String Topic_Value; - switch (Topic_Number) { //switch on topic numbers, some have special needs - case 0: - Topic_Value = String(data[4] >> 7); - break; - case 1: - Topic_Value = String((data[4] >> 5) & 0b11); - break; - case 2: - Topic_Value = String((data[4] >> 4) & 0b1); - break; - case 3: - Topic_Value = String((data[4] >> 2) & 0b11); - break; - case 4: - Topic_Value = String((data[4] >> 1) & 0b1); - break; - case 5: - Topic_Value = String((data[4] >> 0) & 0b1); - break; - case 6: - Topic_Value = String((data[5] >> 0) & 0b1); - break; - default: - break; - } - if ((unsigned long)(millis() - lastalloptdatatime) > (1000 * updateAllTime)) { - updatenow = true; - lastalloptdatatime = millis(); - } - if ((updatenow) || ( actOptData[Topic_Number] != Topic_Value )) { - actOptData[Topic_Number] = Topic_Value; + Topic_Value = getOptDataValue(data, Topic_Number); + + if ((updatenow) || ( getOptDataValue(actOptData, Topic_Number) != Topic_Value )) { + char log_msg[256]; + char mqtt_topic[256]; sprintf_P(log_msg, PSTR("received OPT%d %s: %s"), Topic_Number, optTopics[Topic_Number], Topic_Value.c_str()); log_message(log_msg); - sprintf(mqtt_topic, "%s/%s/%s", mqtt_topic_base, mqtt_topic_pcbvalues, optTopics[Topic_Number]); + sprintf_P(mqtt_topic, PSTR("%s/%s/%s"), mqtt_topic_base, mqtt_topic_pcbvalues, optTopics[Topic_Number]); mqtt_client.publish(mqtt_topic, Topic_Value.c_str(), MQTT_RETAIN_VALUES); + rules_new_event(optTopics[Topic_Number]); } } //response to heatpump should contain the data from heatpump on byte 4 and 5 diff --git a/HeishaMon/decode.h b/HeishaMon/decode.h index f6aeda69..18d83df5 100644 --- a/HeishaMon/decode.h +++ b/HeishaMon/decode.h @@ -5,8 +5,11 @@ #define MQTT_RETAIN_VALUES 1 -void decode_heatpump_data(char* data, String actData[], PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime); -void decode_optional_heatpump_data(char* data, String actOptData[], PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime); +void resetlastalldatatime(); + +String getDataValue(char* data, unsigned int Topic_Number); +void decode_heatpump_data(char* data, char* actData, PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime); +void decode_optional_heatpump_data(char* data, char* actOptDat, PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, unsigned int updateAllTime); String unknown(byte input); String getBit1and2(byte input); @@ -26,8 +29,10 @@ String getEnergy(byte input); String getHeatMode(byte input); String getModel(byte input); +static const char _unknown[] PROGMEM = "unknown"; + static const char *Model[] PROGMEM = { - "21", //string representation of number of known models + "23", //string representation of number of known models "WH-MDC05H3E5", "WH-MDC07H3E5", "IDU:WH-SXC09H3E5, ODU:WH-UX09HE5", @@ -49,9 +54,11 @@ static const char *Model[] PROGMEM = { "IDU: WH-SDC0709J3E5, ODU: WH-UD07JE5", "IDU: WH-SDC07H3E5-1 ODU: WH-UD07HE5-1", "WH-MDC07J3E5", + "WH-MDC09J3E5", + "IDU: WH-SDC0305J3E5 ODU: WH-UD05JE5", }; -static const byte knownModels[sizeof(Model) / sizeof(Model[0])][10] = { //stores the bytes #129 to #138 of known models in the same order as the const above +static const byte knownModels[sizeof(Model) / sizeof(Model[0])][10] PROGMEM = { //stores the bytes #129 to #138 of known models in the same order as the const above 0xE2, 0xCF, 0x0B, 0x13, 0x33, 0x32, 0xD1, 0x0C, 0x16, 0x33, 0xE2, 0xCF, 0x0B, 0x14, 0x33, 0x42, 0xD1, 0x0B, 0x17, 0x33, 0xE2, 0xCF, 0x0D, 0x77, 0x09, 0x12, 0xD0, 0x0B, 0x05, 0x11, @@ -73,12 +80,15 @@ static const byte knownModels[sizeof(Model) / sizeof(Model[0])][10] = { //stores 0x62, 0xD2, 0x0B, 0x43, 0x54, 0x42, 0xD2, 0x0C, 0x46, 0x55, 0xE2, 0xCF, 0x0C, 0x54, 0x14, 0x12, 0xD0, 0x0B, 0x14, 0x08, 0xC2, 0xD3, 0x0B, 0x34, 0x65, 0xB2, 0xD3, 0x0B, 0x95, 0x65, + 0xC2, 0xD3, 0x0B, 0x35, 0x65, 0xB2, 0xD3, 0x0B, 0x96, 0x65, + 0x62, 0xD2, 0x0B, 0x41, 0x54, 0x32, 0xD2, 0x0C, 0x45, 0x55, }; -#define NUMBER_OF_TOPICS 106 //last topic number + 1 +#define NUMBER_OF_TOPICS 107 //last topic number + 1 #define NUMBER_OF_OPT_TOPICS 7 //last topic number + 1 +#define MAX_TOPIC_LEN 41 // max length + 1 -static const char *optTopics[] PROGMEM = { +static const char optTopics[][20] PROGMEM = { "Z1_Water_Pump", // OPT0 "Z1_Mixing_Valve", // OPT1 "Z2_Water_Pump", // OPT2 @@ -88,7 +98,7 @@ static const char *optTopics[] PROGMEM = { "Alarm_State", // OPT6 }; -static const char *topics[] PROGMEM = { +static const char topics[][MAX_TOPIC_LEN] PROGMEM = { "Heatpump_State", //TOP0 "Pump_Flow", //TOP1 "Force_DHW_State", //TOP2 @@ -195,6 +205,7 @@ static const char *topics[] PROGMEM = { "Solar_Off_Delta", //TOP103 "Solar_Frost_Protection", //TOP104 "Solar_High_Limit", //TOP105 + "Pump_Flowrate_Mode", //TOP106 }; static const byte topicBytes[] PROGMEM = { //can store the index as byte (8-bit unsigned humber) as there aren't more then 255 bytes (actually only 203 bytes) to decode @@ -304,6 +315,7 @@ static const byte topicBytes[] PROGMEM = { //can store the index as byte (8-bit 62, //TOP103 63, //TOP104 64, //TOP105 + 29, //TOP106 }; typedef String (*topicFP)(byte); @@ -415,30 +427,32 @@ static const topicFP topicFunctions[] PROGMEM = { getIntMinus128, //TOP103 getIntMinus128, //TOP104 getIntMinus128, //TOP105 + getBit3and4, //TOP106 }; static const char *DisabledEnabled[] PROGMEM = {"2", "Disabled", "Enabled"}; static const char *BlockedFree[] PROGMEM = {"2", "Blocked", "Free"}; static const char *OffOn[] PROGMEM = {"2", "Off", "On"}; static const char *InactiveActive[] PROGMEM = {"2", "Inactive", "Active"}; +static const char *PumpFlowRateMode[] PROGMEM = {"2", "DeltaT", "Max flow"}; static const char *HolidayState[] PROGMEM = {"3", "Off", "Scheduled", "Active"}; static const char *OpModeDesc[] PROGMEM = {"9", "Heat", "Cool", "Auto(heat)", "DHW", "Heat+DHW", "Cool+DHW", "Auto(heat)+DHW", "Auto(cool)", "Auto(cool)+DHW"}; static const char *Powerfulmode[] PROGMEM = {"4", "Off", "30min", "60min", "90min"}; static const char *Quietmode[] PROGMEM = {"4", "Off", "Level 1", "Level 2", "Level 3"}; static const char *Valve[] PROGMEM = {"2", "Room", "DHW"}; -static const char *LitersPerMin[] PROGMEM = {"value", "l/min"}; -static const char *RotationsPerMin[] PROGMEM = {"value", "r/min"}; -static const char *Pressure[] PROGMEM = {"value", "Kgf/cm2"}; -static const char *Celsius[] PROGMEM = {"value", "°C"}; -static const char *Kelvin[] PROGMEM = {"value", "K"}; -static const char *Hertz[] PROGMEM = {"value", "Hz"}; -static const char *Counter[] PROGMEM = {"value", "count"}; -static const char *Hours[] PROGMEM = {"value", "hours"}; -static const char *Watt[] PROGMEM = {"value", "Watt"}; -static const char *ErrorState[] PROGMEM = {"value", "Error"}; -static const char *Ampere[] PROGMEM = {"value", "Ampere"}; -static const char *Minutes[] PROGMEM = {"value", "Minutes"}; -static const char *Duty[] PROGMEM = {"value", "Duty"}; +static const char *LitersPerMin[] PROGMEM = {"0", "l/min"}; +static const char *RotationsPerMin[] PROGMEM = {"0", "r/min"}; +static const char *Pressure[] PROGMEM = {"0", "Kgf/cm2"}; +static const char *Celsius[] PROGMEM = {"0", "°C"}; +static const char *Kelvin[] PROGMEM = {"0", "K"}; +static const char *Hertz[] PROGMEM = {"0", "Hz"}; +static const char *Counter[] PROGMEM = {"0", "count"}; +static const char *Hours[] PROGMEM = {"0", "hours"}; +static const char *Watt[] PROGMEM = {"0", "Watt"}; +static const char *ErrorState[] PROGMEM = {"0", "Error"}; +static const char *Ampere[] PROGMEM = {"0", "Ampere"}; +static const char *Minutes[] PROGMEM = {"0", "Minutes"}; +static const char *Duty[] PROGMEM = {"0", "Duty"}; static const char *ZonesState[] PROGMEM = {"3", "Zone1 active", "Zone2 active", "Zone1 and zone2 active"}; static const char *HeatCoolModeDesc[] PROGMEM = {"2", "Comp. Curve", "Direct"}; static const char *SolarModeDesc[] PROGMEM = {"3", "Disabled", "Buffer", "DHW"}; @@ -550,4 +564,5 @@ static const char **topicDescription[] PROGMEM = { Kelvin, //TOP103 Celsius, //TOP104 Celsius, //TOP105 + PumpFlowRateMode,//TOP106 }; diff --git a/HeishaMon/htmlcode.h b/HeishaMon/htmlcode.h index 23c17a76..9c5d9454 100644 --- a/HeishaMon/htmlcode.h +++ b/HeishaMon/htmlcode.h @@ -29,10 +29,10 @@ static const char websocketJS[] PROGMEM = " var bConnected = false;" " function startWebsockets() {" " if(typeof MozWebSocket != \"undefined\") {" - " oWebsocket = new MozWebSocket(\"ws://\" + location.host + \":81\");" + " oWebsocket = new MozWebSocket(\"ws://\" + location.host + \":80\");" " } else if(typeof WebSocket != \"undefined\") {" " /* The characters after the trailing slash are needed for a wierd IE 10 bug */" - " oWebsocket = new WebSocket(\"ws://\" + location.host + \":81/ws\");" + " oWebsocket = new WebSocket(\"ws://\" + location.host + \":80/ws\");" " }" "" " if(oWebsocket) {" @@ -62,17 +62,17 @@ static const char websocketJS[] PROGMEM = static const char refreshJS[] PROGMEM = ""; static const char selectJS[] PROGMEM = ""; + ""; static const char populatescanwifiJS[] PROGMEM = ""; + + +static const char settingsForm1[] PROGMEM = + "
" + "

Please wait, loading saved settings...

" + "
" + "
" + "

Settings

" + "
" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "
" + " Hostname:" + " " + "
" + " Wifi SSID:" + " " + " " + "
" + " Wifi password:" + " " + "
" + " Update username:" + " " + "
" + " Current update password:" + " default password: \"heisha\"" + "
" + " New update password:" + " " + "
" + " Mqtt topic base:" + " " + "
" + " Mqtt server:" + " " + "
" + " Mqtt port:" + " " + "
" + " Mqtt username:" + " " + "
" + " Mqtt password:" + " " + "
" + " NTP servers (comma separated):" + " " + "
" + " Timezone:" + " " + "
" + " How often new values are collected from heatpump:" + " seconds (min 5 sec)" + "
" + " How often all heatpump values are retransmitted to MQTT broker:" + " seconds" + "
" + " Listen only mode:" + " " + "
" + " Debug log to MQTT topic from start:" + " " + "
" + " Debug log hexdump enable from start:" + " " + "
" + " Debug log to serial1 (GPIO2):" + " " + "
" + " Emulate optional PCB:" + " " + "
" + " " + " " + " " + " " + " " + "
" + " Use 1wire DS18b20:" + " " + "
" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "
" + " How often new values are collected from 1wire:" + " seconds (min 5 sec)" + "
" + " How often all 1wire values are retransmitted to MQTT broker:" + " seconds" + "
" + " DS18b20 temperature resolution:" + " " + " " + " " + " " + "
" + " " + " " + " " + " " + " " + "
" + " Use s0 kWh metering:" + " " + "
" + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + " " + "
S0 port 1 GPIO:" + " " + "
S0 port 1 imp/kwh:" + " " + "
S0 port 1 reporting interval during standby/low power usage:" + " seconds" + "
S0 port 1 minimal pulse width:" + " milliseconds" + "
S0 port 1 maximal pulse width:" + " milliseconds" + "
S0 port 1 standby/low power usage threshold: Watt" + "
S0 port 2 GPIO:" + " " + "
S0 port 2 imp/kwh:" + " " + "
S0 port 2 reporting interval during standby/low power usage:" + " seconds" + "
S0 port 2 minimal pulse width:" + " milliseconds" + "
S0 port 2 maximal pulse width:" + " milliseconds" + "
S0 port 2 standby/low power usage threshold: Watt" + "
" + "

" + " " + "
" + "
Factory reset" + "
"; + +const char populategetsettingsJS[] PROGMEM = + ""; + +static const char showFirmwarePage[] PROGMEM = + "" + "" + "
" + "
" + "

Firmware:

" + "

" + "

Warning
If you leave the MD5 checksum empty there will be no check on the uploaded firmware which could cause a bricked HeishaMon!
In this case but also other unforseen errors during update requires you to be able to restore the firmware using a TTL cable!

" + "
" + "

" + "
"; + +static const char firmwareSuccessResponse[] PROGMEM = + "Update success! Rebooting. This page will refresh afterwards."; + +static const char firmwareFailResponse[] PROGMEM = + "Update failed! Please try again..."; + +// https://github.com/nayarsystems/posix_tz_db +struct tzStruct { + char name[32]; + char value[46]; +}; +const tzStruct tzdata[] PROGMEM = { + { "ETC/GMT", "GMT0" }, + { "Africa/Abidjan", "GMT0" }, + { "Africa/Accra", "GMT0" }, + { "Africa/Addis_Ababa", "EAT-3" }, + { "Africa/Algiers", "CET-1" }, + { "Africa/Asmara", "EAT-3" }, + { "Africa/Bamako", "GMT0" }, + { "Africa/Bangui", "WAT-1" }, + { "Africa/Banjul", "GMT0" }, + { "Africa/Bissau", "GMT0" }, + { "Africa/Blantyre", "CAT-2" }, + { "Africa/Brazzaville", "WAT-1" }, + { "Africa/Bujumbura", "CAT-2" }, + { "Africa/Cairo", "EET-2" }, + { "Africa/Casablanca", "<+01>-1" }, + { "Africa/Ceuta", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Africa/Conakry", "GMT0" }, + { "Africa/Dakar", "GMT0" }, + { "Africa/Dar_es_Salaam", "EAT-3" }, + { "Africa/Djibouti", "EAT-3" }, + { "Africa/Douala", "WAT-1" }, + { "Africa/El_Aaiun", "<+01>-1" }, + { "Africa/Freetown", "GMT0" }, + { "Africa/Gaborone", "CAT-2" }, + { "Africa/Harare", "CAT-2" }, + { "Africa/Johannesburg", "SAST-2" }, + { "Africa/Juba", "CAT-2" }, + { "Africa/Kampala", "EAT-3" }, + { "Africa/Khartoum", "CAT-2" }, + { "Africa/Kigali", "CAT-2" }, + { "Africa/Kinshasa", "WAT-1" }, + { "Africa/Lagos", "WAT-1" }, + { "Africa/Libreville", "WAT-1" }, + { "Africa/Lome", "GMT0" }, + { "Africa/Luanda", "WAT-1" }, + { "Africa/Lubumbashi", "CAT-2" }, + { "Africa/Lusaka", "CAT-2" }, + { "Africa/Malabo", "WAT-1" }, + { "Africa/Maputo", "CAT-2" }, + { "Africa/Maseru", "SAST-2" }, + { "Africa/Mbabane", "SAST-2" }, + { "Africa/Mogadishu", "EAT-3" }, + { "Africa/Monrovia", "GMT0" }, + { "Africa/Nairobi", "EAT-3" }, + { "Africa/Ndjamena", "WAT-1" }, + { "Africa/Niamey", "WAT-1" }, + { "Africa/Nouakchott", "GMT0" }, + { "Africa/Ouagadougou", "GMT0" }, + { "Africa/Porto-Novo", "WAT-1" }, + { "Africa/Sao_Tome", "GMT0" }, + { "Africa/Tripoli", "EET-2" }, + { "Africa/Tunis", "CET-1" }, + { "Africa/Windhoek", "CAT-2" }, + { "America/Adak", "HST10HDT,M3.2.0,M11.1.0" }, + { "America/Anchorage", "AKST9AKDT,M3.2.0,M11.1.0" }, + { "America/Anguilla", "AST4" }, + { "America/Antigua", "AST4" }, + { "America/Araguaina", "<-03>3" }, + { "America/Argentina/Buenos_Aires", "<-03>3" }, + { "America/Argentina/Catamarca", "<-03>3" }, + { "America/Argentina/Cordoba", "<-03>3" }, + { "America/Argentina/Jujuy", "<-03>3" }, + { "America/Argentina/La_Rioja", "<-03>3" }, + { "America/Argentina/Mendoza", "<-03>3" }, + { "America/Argentina/Rio_Gallegos", "<-03>3" }, + { "America/Argentina/Salta", "<-03>3" }, + { "America/Argentina/San_Juan", "<-03>3" }, + { "America/Argentina/San_Luis", "<-03>3" }, + { "America/Argentina/Tucuman", "<-03>3" }, + { "America/Argentina/Ushuaia", "<-03>3" }, + { "America/Aruba", "AST4" }, + { "America/Asuncion", "<-04>4<-03>,M10.1.0/0,M3.4.0/0" }, + { "America/Atikokan", "EST5" }, + { "America/Bahia", "<-03>3" }, + { "America/Bahia_Banderas", "CST6CDT,M4.1.0,M10.5.0" }, + { "America/Barbados", "AST4" }, + { "America/Belem", "<-03>3" }, + { "America/Belize", "CST6" }, + { "America/Blanc-Sablon", "AST4" }, + { "America/Boa_Vista", "<-04>4" }, + { "America/Bogota", "<-05>5" }, + { "America/Boise", "MST7MDT,M3.2.0,M11.1.0" }, + { "America/Cambridge_Bay", "MST7MDT,M3.2.0,M11.1.0" }, + { "America/Campo_Grande", "<-04>4" }, + { "America/Cancun", "EST5" }, + { "America/Caracas", "<-04>4" }, + { "America/Cayenne", "<-03>3" }, + { "America/Cayman", "EST5" }, + { "America/Chicago", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Chihuahua", "MST7MDT,M4.1.0,M10.5.0" }, + { "America/Costa_Rica", "CST6" }, + { "America/Creston", "MST7" }, + { "America/Cuiaba", "<-04>4" }, + { "America/Curacao", "AST4" }, + { "America/Danmarkshavn", "GMT0" }, + { "America/Dawson", "MST7" }, + { "America/Dawson_Creek", "MST7" }, + { "America/Denver", "MST7MDT,M3.2.0,M11.1.0" }, + { "America/Detroit", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Dominica", "AST4" }, + { "America/Edmonton", "MST7MDT,M3.2.0,M11.1.0" }, + { "America/Eirunepe", "<-05>5" }, + { "America/El_Salvador", "CST6" }, + { "America/Fortaleza", "<-03>3" }, + { "America/Fort_Nelson", "MST7" }, + { "America/Glace_Bay", "AST4ADT,M3.2.0,M11.1.0" }, + { "America/Godthab", "<-03>3<-02>,M3.5.0/-2,M10.5.0/-1" }, + { "America/Goose_Bay", "AST4ADT,M3.2.0,M11.1.0" }, + { "America/Grand_Turk", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Grenada", "AST4" }, + { "America/Guadeloupe", "AST4" }, + { "America/Guatemala", "CST6" }, + { "America/Guayaquil", "<-05>5" }, + { "America/Guyana", "<-04>4" }, + { "America/Halifax", "AST4ADT,M3.2.0,M11.1.0" }, + { "America/Havana", "CST5CDT,M3.2.0/0,M11.1.0/1" }, + { "America/Hermosillo", "MST7" }, + { "America/Indiana/Indianapolis", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Indiana/Knox", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Indiana/Marengo", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Indiana/Petersburg", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Indiana/Tell_City", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Indiana/Vevay", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Indiana/Vincennes", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Indiana/Winamac", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Inuvik", "MST7MDT,M3.2.0,M11.1.0" }, + { "America/Iqaluit", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Jamaica", "EST5" }, + { "America/Juneau", "AKST9AKDT,M3.2.0,M11.1.0" }, + { "America/Kentucky/Louisville", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Kentucky/Monticello", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Kralendijk", "AST4" }, + { "America/La_Paz", "<-04>4" }, + { "America/Lima", "<-05>5" }, + { "America/Los_Angeles", "PST8PDT,M3.2.0,M11.1.0" }, + { "America/Lower_Princes", "AST4" }, + { "America/Maceio", "<-03>3" }, + { "America/Managua", "CST6" }, + { "America/Manaus", "<-04>4" }, + { "America/Marigot", "AST4" }, + { "America/Martinique", "AST4" }, + { "America/Matamoros", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Mazatlan", "MST7MDT,M4.1.0,M10.5.0" }, + { "America/Menominee", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Merida", "CST6CDT,M4.1.0,M10.5.0" }, + { "America/Metlakatla", "AKST9AKDT,M3.2.0,M11.1.0" }, + { "America/Mexico_City", "CST6CDT,M4.1.0,M10.5.0" }, + { "America/Miquelon", "<-03>3<-02>,M3.2.0,M11.1.0" }, + { "America/Moncton", "AST4ADT,M3.2.0,M11.1.0" }, + { "America/Monterrey", "CST6CDT,M4.1.0,M10.5.0" }, + { "America/Montevideo", "<-03>3" }, + { "America/Montreal", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Montserrat", "AST4" }, + { "America/Nassau", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/New_York", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Nipigon", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Nome", "AKST9AKDT,M3.2.0,M11.1.0" }, + { "America/Noronha", "<-02>2" }, + { "America/North_Dakota/Beulah", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/North_Dakota/Center", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/North_Dakota/New_Salem", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Nuuk", "<-03>3<-02>,M3.5.0/-2,M10.5.0/-1" }, + { "America/Ojinaga", "MST7MDT,M3.2.0,M11.1.0" }, + { "America/Panama", "EST5" }, + { "America/Pangnirtung", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Paramaribo", "<-03>3" }, + { "America/Phoenix", "MST7" }, + { "America/Port-au-Prince", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Port_of_Spain", "AST4" }, + { "America/Porto_Velho", "<-04>4" }, + { "America/Puerto_Rico", "AST4" }, + { "America/Punta_Arenas", "<-03>3" }, + { "America/Rainy_River", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Rankin_Inlet", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Recife", "<-03>3" }, + { "America/Regina", "CST6" }, + { "America/Resolute", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Rio_Branco", "<-05>5" }, + { "America/Santarem", "<-03>3" }, + { "America/Santiago", "<-04>4<-03>,M9.1.6/24,M4.1.6/24" }, + { "America/Santo_Domingo", "AST4" }, + { "America/Sao_Paulo", "<-03>3" }, + { "America/Scoresbysund", "<-01>1<+00>,M3.5.0/0,M10.5.0/1" }, + { "America/Sitka", "AKST9AKDT,M3.2.0,M11.1.0" }, + { "America/St_Barthelemy", "AST4" }, + { "America/St_Johns", "NST3:30NDT,M3.2.0,M11.1.0" }, + { "America/St_Kitts", "AST4" }, + { "America/St_Lucia", "AST4" }, + { "America/St_Thomas", "AST4" }, + { "America/St_Vincent", "AST4" }, + { "America/Swift_Current", "CST6" }, + { "America/Tegucigalpa", "CST6" }, + { "America/Thule", "AST4ADT,M3.2.0,M11.1.0" }, + { "America/Thunder_Bay", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Tijuana", "PST8PDT,M3.2.0,M11.1.0" }, + { "America/Toronto", "EST5EDT,M3.2.0,M11.1.0" }, + { "America/Tortola", "AST4" }, + { "America/Vancouver", "PST8PDT,M3.2.0,M11.1.0" }, + { "America/Whitehorse", "MST7" }, + { "America/Winnipeg", "CST6CDT,M3.2.0,M11.1.0" }, + { "America/Yakutat", "AKST9AKDT,M3.2.0,M11.1.0" }, + { "America/Yellowknife", "MST7MDT,M3.2.0,M11.1.0" }, + { "Antarctica/Casey", "<+11>-11" }, + { "Antarctica/Davis", "<+07>-7" }, + { "Antarctica/DumontDUrville", "<+10>-10" }, + { "Antarctica/Macquarie", "AEST-10AEDT,M10.1.0,M4.1.0/3" }, + { "Antarctica/Mawson", "<+05>-5" }, + { "Antarctica/McMurdo", "NZST-12NZDT,M9.5.0,M4.1.0/3" }, + { "Antarctica/Palmer", "<-03>3" }, + { "Antarctica/Rothera", "<-03>3" }, + { "Antarctica/Syowa", "<+03>-3" }, + { "Antarctica/Troll", "<+00>0<+02>-2,M3.5.0/1,M10.5.0/3" }, + { "Antarctica/Vostok", "<+06>-6" }, + { "Arctic/Longyearbyen", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Asia/Aden", "<+03>-3" }, + { "Asia/Almaty", "<+06>-6" }, + { "Asia/Amman", "EET-2EEST,M2.5.4/24,M10.5.5/1" }, + { "Asia/Anadyr", "<+12>-12" }, + { "Asia/Aqtau", "<+05>-5" }, + { "Asia/Aqtobe", "<+05>-5" }, + { "Asia/Ashgabat", "<+05>-5" }, + { "Asia/Atyrau", "<+05>-5" }, + { "Asia/Baghdad", "<+03>-3" }, + { "Asia/Bahrain", "<+03>-3" }, + { "Asia/Baku", "<+04>-4" }, + { "Asia/Bangkok", "<+07>-7" }, + { "Asia/Barnaul", "<+07>-7" }, + { "Asia/Beirut", "EET-2EEST,M3.5.0/0,M10.5.0/0" }, + { "Asia/Bishkek", "<+06>-6" }, + { "Asia/Brunei", "<+08>-8" }, + { "Asia/Chita", "<+09>-9" }, + { "Asia/Choibalsan", "<+08>-8" }, + { "Asia/Colombo", "<+0530>-5:30" }, + { "Asia/Damascus", "EET-2EEST,M3.5.5/0,M10.5.5/0" }, + { "Asia/Dhaka", "<+06>-6" }, + { "Asia/Dili", "<+09>-9" }, + { "Asia/Dubai", "<+04>-4" }, + { "Asia/Dushanbe", "<+05>-5" }, + { "Asia/Famagusta", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Asia/Gaza", "EET-2EEST,M3.4.4/48,M10.5.5/1" }, + { "Asia/Hebron", "EET-2EEST,M3.4.4/48,M10.5.5/1" }, + { "Asia/Ho_Chi_Minh", "<+07>-7" }, + { "Asia/Hong_Kong", "HKT-8" }, + { "Asia/Hovd", "<+07>-7" }, + { "Asia/Irkutsk", "<+08>-8" }, + { "Asia/Jakarta", "WIB-7" }, + { "Asia/Jayapura", "WIT-9" }, + { "Asia/Jerusalem", "IST-2IDT,M3.4.4/26,M10.5.0" }, + { "Asia/Kabul", "<+0430>-4:30" }, + { "Asia/Kamchatka", "<+12>-12" }, + { "Asia/Karachi", "PKT-5" }, + { "Asia/Kathmandu", "<+0545>-5:45" }, + { "Asia/Khandyga", "<+09>-9" }, + { "Asia/Kolkata", "IST-5:30" }, + { "Asia/Krasnoyarsk", "<+07>-7" }, + { "Asia/Kuala_Lumpur", "<+08>-8" }, + { "Asia/Kuching", "<+08>-8" }, + { "Asia/Kuwait", "<+03>-3" }, + { "Asia/Macau", "CST-8" }, + { "Asia/Magadan", "<+11>-11" }, + { "Asia/Makassar", "WITA-8" }, + { "Asia/Manila", "PST-8" }, + { "Asia/Muscat", "<+04>-4" }, + { "Asia/Nicosia", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Asia/Novokuznetsk", "<+07>-7" }, + { "Asia/Novosibirsk", "<+07>-7" }, + { "Asia/Omsk", "<+06>-6" }, + { "Asia/Oral", "<+05>-5" }, + { "Asia/Phnom_Penh", "<+07>-7" }, + { "Asia/Pontianak", "WIB-7" }, + { "Asia/Pyongyang", "KST-9" }, + { "Asia/Qatar", "<+03>-3" }, + { "Asia/Qyzylorda", "<+05>-5" }, + { "Asia/Riyadh", "<+03>-3" }, + { "Asia/Sakhalin", "<+11>-11" }, + { "Asia/Samarkand", "<+05>-5" }, + { "Asia/Seoul", "KST-9" }, + { "Asia/Shanghai", "CST-8" }, + { "Asia/Singapore", "<+08>-8" }, + { "Asia/Srednekolymsk", "<+11>-11" }, + { "Asia/Taipei", "CST-8" }, + { "Asia/Tashkent", "<+05>-5" }, + { "Asia/Tbilisi", "<+04>-4" }, + { "Asia/Tehran", "<+0330>-3:30<+0430>,J79/24,J263/24" }, + { "Asia/Thimphu", "<+06>-6" }, + { "Asia/Tokyo", "JST-9" }, + { "Asia/Tomsk", "<+07>-7" }, + { "Asia/Ulaanbaatar", "<+08>-8" }, + { "Asia/Urumqi", "<+06>-6" }, + { "Asia/Ust-Nera", "<+10>-10" }, + { "Asia/Vientiane", "<+07>-7" }, + { "Asia/Vladivostok", "<+10>-10" }, + { "Asia/Yakutsk", "<+09>-9" }, + { "Asia/Yangon", "<+0630>-6:30" }, + { "Asia/Yekaterinburg", "<+05>-5" }, + { "Asia/Yerevan", "<+04>-4" }, + { "Atlantic/Azores", "<-01>1<+00>,M3.5.0/0,M10.5.0/1" }, + { "Atlantic/Bermuda", "AST4ADT,M3.2.0,M11.1.0" }, + { "Atlantic/Canary", "WET0WEST,M3.5.0/1,M10.5.0" }, + { "Atlantic/Cape_Verde", "<-01>1" }, + { "Atlantic/Faroe", "WET0WEST,M3.5.0/1,M10.5.0" }, + { "Atlantic/Madeira", "WET0WEST,M3.5.0/1,M10.5.0" }, + { "Atlantic/Reykjavik", "GMT0" }, + { "Atlantic/South_Georgia", "<-02>2" }, + { "Atlantic/Stanley", "<-03>3" }, + { "Atlantic/St_Helena", "GMT0" }, + { "Australia/Adelaide", "ACST-9:30ACDT,M10.1.0,M4.1.0/3" }, + { "Australia/Brisbane", "AEST-10" }, + { "Australia/Broken_Hill", "ACST-9:30ACDT,M10.1.0,M4.1.0/3" }, + { "Australia/Currie", "AEST-10AEDT,M10.1.0,M4.1.0/3" }, + { "Australia/Darwin", "ACST-9:30" }, + { "Australia/Eucla", "<+0845>-8:45" }, + { "Australia/Hobart", "AEST-10AEDT,M10.1.0,M4.1.0/3" }, + { "Australia/Lindeman", "AEST-10" }, + { "Australia/Lord_Howe", "<+1030>-10:30<+11>-11,M10.1.0,M4.1.0" }, + { "Australia/Melbourne", "AEST-10AEDT,M10.1.0,M4.1.0/3" }, + { "Australia/Perth", "AWST-8" }, + { "Australia/Sydney", "AEST-10AEDT,M10.1.0,M4.1.0/3" }, + { "Europe/Amsterdam", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Andorra", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Astrakhan", "<+04>-4" }, + { "Europe/Athens", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Belgrade", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Berlin", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Bratislava", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Brussels", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Bucharest", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Budapest", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Busingen", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Chisinau", "EET-2EEST,M3.5.0,M10.5.0/3" }, + { "Europe/Copenhagen", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Dublin", "IST-1GMT0,M10.5.0,M3.5.0/1" }, + { "Europe/Gibraltar", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Guernsey", "GMT0BST,M3.5.0/1,M10.5.0" }, + { "Europe/Helsinki", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Isle_of_Man", "GMT0BST,M3.5.0/1,M10.5.0" }, + { "Europe/Istanbul", "<+03>-3" }, + { "Europe/Jersey", "GMT0BST,M3.5.0/1,M10.5.0" }, + { "Europe/Kaliningrad", "EET-2" }, + { "Europe/Kiev", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Kirov", "<+03>-3" }, + { "Europe/Lisbon", "WET0WEST,M3.5.0/1,M10.5.0" }, + { "Europe/Ljubljana", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/London", "GMT0BST,M3.5.0/1,M10.5.0" }, + { "Europe/Luxembourg", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Madrid", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Malta", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Mariehamn", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Minsk", "<+03>-3" }, + { "Europe/Monaco", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Moscow", "MSK-3" }, + { "Europe/Oslo", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Paris", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Podgorica", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Prague", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Riga", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Rome", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Samara", "<+04>-4" }, + { "Europe/San_Marino", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Sarajevo", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Saratov", "<+04>-4" }, + { "Europe/Simferopol", "MSK-3" }, + { "Europe/Skopje", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Sofia", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Stockholm", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Tallinn", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Tirane", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Ulyanovsk", "<+04>-4" }, + { "Europe/Uzhgorod", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Vaduz", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Vatican", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Vienna", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Vilnius", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Volgograd", "<+03>-3" }, + { "Europe/Warsaw", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Zagreb", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Europe/Zaporozhye", "EET-2EEST,M3.5.0/3,M10.5.0/4" }, + { "Europe/Zurich", "CET-1CEST,M3.5.0,M10.5.0/3" }, + { "Indian/Antananarivo", "EAT-3" }, + { "Indian/Chagos", "<+06>-6" }, + { "Indian/Christmas", "<+07>-7" }, + { "Indian/Cocos", "<+0630>-6:30" }, + { "Indian/Comoro", "EAT-3" }, + { "Indian/Kerguelen", "<+05>-5" }, + { "Indian/Mahe", "<+04>-4" }, + { "Indian/Maldives", "<+05>-5" }, + { "Indian/Mauritius", "<+04>-4" }, + { "Indian/Mayotte", "EAT-3" }, + { "Indian/Reunion", "<+04>-4" }, + { "Pacific/Apia", "<+13>-13" }, + { "Pacific/Auckland", "NZST-12NZDT,M9.5.0,M4.1.0/3" }, + { "Pacific/Bougainville", "<+11>-11" }, + { "Pacific/Chatham", "<+1245>-12:45<+1345>,M9.5.0/2:45,M4.1.0/3:45" }, + { "Pacific/Chuuk", "<+10>-10" }, + { "Pacific/Easter", "<-06>6<-05>,M9.1.6/22,M4.1.6/22" }, + { "Pacific/Efate", "<+11>-11" }, + { "Pacific/Enderbury", "<+13>-13" }, + { "Pacific/Fakaofo", "<+13>-13" }, + { "Pacific/Fiji", "<+12>-12<+13>,M11.2.0,M1.2.3/99" }, + { "Pacific/Funafuti", "<+12>-12" }, + { "Pacific/Galapagos", "<-06>6" }, + { "Pacific/Gambier", "<-09>9" }, + { "Pacific/Guadalcanal", "<+11>-11" }, + { "Pacific/Guam", "ChST-10" }, + { "Pacific/Honolulu", "HST10" }, + { "Pacific/Kiritimati", "<+14>-14" }, + { "Pacific/Kosrae", "<+11>-11" }, + { "Pacific/Kwajalein", "<+12>-12" }, + { "Pacific/Majuro", "<+12>-12" }, + { "Pacific/Marquesas", "<-0930>9:30" }, + { "Pacific/Midway", "SST11" }, + { "Pacific/Nauru", "<+12>-12" }, + { "Pacific/Niue", "<-11>11" }, + { "Pacific/Norfolk", "<+11>-11<+12>,M10.1.0,M4.1.0/3" }, + { "Pacific/Noumea", "<+11>-11" }, + { "Pacific/Pago_Pago", "SST11" }, + { "Pacific/Palau", "<+09>-9" }, + { "Pacific/Pitcairn", "<-08>8" }, + { "Pacific/Pohnpei", "<+11>-11" }, + { "Pacific/Port_Moresby", "<+10>-10" }, + { "Pacific/Rarotonga", "<-10>10" }, + { "Pacific/Saipan", "ChST-10" }, + { "Pacific/Tahiti", "<-10>10" }, + { "Pacific/Tarawa", "<+12>-12" }, + { "Pacific/Tongatapu", "<+13>-13" }, + { "Pacific/Wake", "<+12>-12" }, + { "Pacific/Wallis", "<+12>-12" }, + { "Etc/GMT-0", "GMT0" }, + { "Etc/GMT-1", "<+01>-1" }, + { "Etc/GMT-2", "<+02>-2" }, + { "Etc/GMT-3", "<+03>-3" }, + { "Etc/GMT-4", "<+04>-4" }, + { "Etc/GMT-5", "<+05>-5" }, + { "Etc/GMT-6", "<+06>-6" }, + { "Etc/GMT-7", "<+07>-7" }, + { "Etc/GMT-8", "<+08>-8" }, + { "Etc/GMT-9", "<+09>-9" }, + { "Etc/GMT-10", "<+10>-10" }, + { "Etc/GMT-11", "<+11>-11" }, + { "Etc/GMT-12", "<+12>-12" }, + { "Etc/GMT-13", "<+13>-13" }, + { "Etc/GMT-14", "<+14>-14" }, + { "Etc/GMT0", "GMT0" }, + { "Etc/GMT+0", "GMT0" }, + { "Etc/GMT+1", "<-01>1" }, + { "Etc/GMT+2", "<-02>2" }, + { "Etc/GMT+3", "<-03>3" }, + { "Etc/GMT+4", "<-04>4" }, + { "Etc/GMT+5", "<-05>5" }, + { "Etc/GMT+6", "<-06>6" }, + { "Etc/GMT+7", "<-07>7" }, + { "Etc/GMT+8", "<-08>8" }, + { "Etc/GMT+9", "<-09>9" }, + { "Etc/GMT+10", "<-10>10" }, + { "Etc/GMT+11", "<-11>11" }, + { "Etc/GMT+12", "<-12>12" }, + { "Etc/UCT", "UTC0" }, + { "Etc/UTC", "UTC0" }, + { "Etc/Greenwich", "GMT0" }, + { "Etc/Universal", "UTC0" }, + { "Etc/Zulu", "UTC0" }, +}; + +static const char tzDataOptions[] PROGMEM = + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; diff --git a/HeishaMon/rules.cpp b/HeishaMon/rules.cpp new file mode 100755 index 00000000..e5fdbc32 --- /dev/null +++ b/HeishaMon/rules.cpp @@ -0,0 +1,1537 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include +#include + +#include "src/common/mem.h" +#include "src/common/stricmp.h" +#include "src/common/strnicmp.h" +#include "src/common/log.h" +#include "src/common/timerqueue.h" +#include "src/rules/rules.h" + +#include "dallas.h" +#include "webfunctions.h" +#include "decode.h" +#include "commands.h" + +#define MAXCOMMANDSINBUFFER 10 + +bool send_command(byte* command, int length); + +extern int dallasDevicecount; +extern dallasDataStruct *actDallasData; +extern settingsStruct heishamonSettings; +extern char actData[DATASIZE]; +extern String openTherm[2]; +static uint8_t parsing = 0; + +typedef struct vm_gvchar_t { + VM_GENERIC_FIELDS + uint8_t rule; + char value[]; +} __attribute__((packed)) vm_gvchar_t; + +typedef struct vm_gvnull_t { + VM_GENERIC_FIELDS + uint8_t rule; +} __attribute__((packed)) vm_gvnull_t; + +typedef struct vm_gvinteger_t { + VM_GENERIC_FIELDS + uint8_t rule; + int value; +} __attribute__((packed)) vm_gvinteger_t; + +typedef struct vm_gvfloat_t { + VM_GENERIC_FIELDS + uint8_t rule; + float value; +} __attribute__((packed)) vm_gvfloat_t; + +static struct rules_t **rules = NULL; +static int nrrules = 0; + +typedef struct varstack_t { + unsigned int nrbytes; + unsigned int bufsize; + unsigned char *stack; +} varstack_t; + +static struct varstack_t global_varstack; + +static struct vm_vinteger_t vinteger; +static struct vm_vfloat_t vfloat; +static struct vm_vnull_t vnull; + +struct rule_options_t rule_options; +unsigned char *mempool = (unsigned char *)MMU_SEC_HEAP; +unsigned int memptr = 0; + +static void vm_global_value_prt(char *out, int size); + +// static int readRuleFromFS(int i) { + // char fname[24]; + // memset(&fname, 0, sizeof(fname)); + // sprintf_P((char *)&fname, PSTR("/rule%d.bc"), i); + // File f = LittleFS.open(fname, "r"); + // if(!f) { + // logprintf_P(F("failed to open file: %s"), fname); + // return -1; + // } + // FREE(rules[i]->ast.buffer); + // if((rules[i]->ast.buffer = (unsigned char *)MALLOC(rules[i]->ast.bufsize)) == NULL) { + // OUT_OF_MEMORY + // } + // memset(rules[i]->ast.buffer, 0, rules[i]->ast.bufsize); + // f.readBytes((char *)rules[i]->ast.buffer, rules[i]->ast.bufsize); + // f.close(); + // return 0; +// } + +// static int writeRuleToFS(int i) { + // char fname[24]; + // memset(&fname, 0, sizeof(fname)); + // sprintf_P((char *)&fname, PSTR("/rule%d.bc"), i); + // File f = LittleFS.open(fname, "w"); + // if(!f) { + // logprintf_P(F("failed to open file: %s"), fname); + // return -1; + // } + // f.write((char *)rules[i]->ast.buffer, rules[i]->ast.bufsize); + // f.close(); + // return 0; +// } + +static int get_event(struct rules_t *obj) { + struct vm_tstart_t *start = (struct vm_tstart_t *)&obj->ast.buffer[0]; + if(obj->ast.buffer[start->go] != TEVENT) { + return -1; + } else { + return start->go; + } +} + +static int is_variable(char *text, unsigned int *pos, unsigned int size) { + int i = 1, x = 0, match = 0; + + if(size == strlen_P(PSTR("ds18b20#2800000000000000")) && strncmp_P((const char *)&text[*pos], PSTR("ds18b20#"), 8) == 0) { + return 24; + } else if(text[*pos] == '$' || text[*pos] == '#' || text[*pos] == '@' || text[*pos] == '%' || text[*pos] == '?') { + while(isalnum(text[*pos+i])) { + i++; + } + + if(text[*pos] == '%') { + if(strnicmp(&text[(*pos)+1], "hour", 4) == 0) { + return 5; + } + if(strnicmp(&text[(*pos)+1], "month", 4) == 0) { + return 6; + } + } + + if(text[*pos] == '@') { + int nrcommands = sizeof(commands)/sizeof(commands[0]); + for(x=0;xcaller > 0 && name == NULL) { + called = rules[obj->caller-1]; + + obj->caller = 0; + + return rule_run(called, 0); + } else { + for(x=0;x -1) { + if(strnicmp(name, (char *)&rules[x]->ast.buffer[get_event(rules[x])+5], strlen((char *)&rules[x]->ast.buffer[get_event(rules[x])+5])) == 0) { + called = rules[x]; + break; + } + } + if(called != NULL) { + break; + } + } + + if(called != NULL) { + called->caller = obj->nr; + + return rule_run(called, 0); + } else { + return rule_run(obj, 0); + } + } +} + +static void vm_value_clr(struct rules_t *obj, uint16_t token) { + struct varstack_t *varstack = (struct varstack_t *)obj->userdata; + struct vm_tvar_t *var = (struct vm_tvar_t *)&obj->ast.buffer[token]; + + if(var->token[1] == '$') { + var->value = 0; + } +} + +static void vm_value_cpy(struct rules_t *obj, uint16_t token) { + struct varstack_t *varstack = (struct varstack_t *)obj->userdata; + struct vm_tvar_t *var = (struct vm_tvar_t *)&obj->ast.buffer[token]; + int x = 0; + if(var->token[0] == '$') { + varstack = (struct varstack_t *)obj->userdata; + for(x=4;alignedbytes(x)nrbytes;x++) { + x = alignedbytes(x); + switch(varstack->stack[x]) { + case VINTEGER: { + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + if(strcmp((char *)foo->token, (char *)&var->token) == 0 && val->ret != token) { + var->value = foo->value; + val->ret = token; + foo->value = 0; + return; + } + x += sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *val = (struct vm_vfloat_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + + if(strcmp((char *)foo->token, (char *)var->token) == 0 && val->ret != token) { + var->value = foo->value; + val->ret = token; + foo->value = 0; + return; + } + x += sizeof(struct vm_vfloat_t)-1; + } break; + case VNULL: { + struct vm_vnull_t *val = (struct vm_vnull_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + if(strcmp((char *)foo->token, (char *)&var->token) == 0 && val->ret != token) { + var->value = foo->value; + val->ret = token; + foo->value = 0; + return; + } + x += sizeof(struct vm_vnull_t)-1; + } break; + default: { + return; + } break; + } + } + } else if(var->token[0] == '#') { + varstack = &global_varstack; + + for(x=4;alignedbytes(x)nrbytes;x++) { + x = alignedbytes(x); + switch(varstack->stack[x]) { + case VINTEGER: { + struct vm_gvinteger_t *val = (struct vm_gvinteger_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + + if(strcmp((char *)foo->token, (char *)var->token) == 0 && val->ret != token) { + var->value = x; + val->ret = token; + val->rule = obj->nr; + return; + } + x += sizeof(struct vm_gvinteger_t)-1; + } break; + case VFLOAT: { + struct vm_gvfloat_t *val = (struct vm_gvfloat_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + + if(strcmp((char *)foo->token, (char *)var->token) == 0 && val->ret != token) { + var->value = x; + val->ret = token; + val->rule = obj->nr; + return; + } + x += sizeof(struct vm_gvfloat_t)-1; + } break; + case VNULL: { + struct vm_gvnull_t *val = (struct vm_gvnull_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + + if(strcmp((char *)foo->token, (char *)var->token) == 0 && val->ret != token) { + var->value = x; + val->ret = token; + val->rule = obj->nr; + return; + } + x += sizeof(struct vm_gvnull_t)-1; + } break; + default: { + return; + } break; + } + } + } +} + +static unsigned char *vm_value_get(struct rules_t *obj, uint16_t token) { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[token]; + int i = 0; + if(node->token[0] == '$') { + struct varstack_t *varstack = (struct varstack_t *)obj->userdata; + if(node->value == 0) { + int ret = varstack->nrbytes, suffix = 0; + unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_vnull_t)); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + struct vm_vnull_t *value = (struct vm_vnull_t *)&varstack->stack[ret]; + value->type = VNULL; + value->ret = token; + node->value = ret; + + varstack->nrbytes = size; + varstack->bufsize = alignedbuffer(size); + } + + const char *key = (char *)node->token; + switch(varstack->stack[node->value]) { + case VINTEGER: { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&varstack->stack[node->value]; + } break; + case VFLOAT: { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&varstack->stack[node->value]; + } break; + case VNULL: { + struct vm_vnull_t *na = (struct vm_vnull_t *)&varstack->stack[node->value]; + } break; + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&varstack->stack[node->value]; + } break; + } + + return &varstack->stack[node->value]; + + } + if(node->token[0] == '#') { + struct varstack_t *varstack = &global_varstack; + if(node->value == 0) { + int ret = varstack->nrbytes, suffix = 0; + + unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_gvnull_t)); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + struct vm_gvnull_t *value = (struct vm_gvnull_t *)&varstack->stack[ret]; + value->type = VNULL; + value->ret = token; + value->rule = obj->nr; + node->value = ret; + + varstack->nrbytes = size; + varstack->bufsize = alignedbuffer(size); + } + + const char *key = (char *)node->token; + switch(varstack->stack[node->value]) { + case VINTEGER: { + struct vm_gvinteger_t *na = (struct vm_gvinteger_t *)&varstack->stack[node->value]; + + memset(&vinteger, 0, sizeof(struct vm_vinteger_t)); + vinteger.type = VINTEGER; + vinteger.value = (int)na->value; + + return (unsigned char *)&vinteger; + } break; + case VFLOAT: { + struct vm_gvfloat_t *na = (struct vm_gvfloat_t *)&varstack->stack[node->value]; + + memset(&vfloat, 0, sizeof(struct vm_vfloat_t)); + vfloat.type = VFLOAT; + vfloat.value = na->value; + + return (unsigned char *)&vfloat; + } break; + case VNULL: { + struct vm_gvnull_t *na = (struct vm_gvnull_t *)&varstack->stack[node->value]; + + memset(&vnull, 0, sizeof(struct vm_vnull_t)); + vnull.type = VNULL; + + return (unsigned char *)&vnull; + } break; + case VCHAR: { + return NULL; + } break; + } + + return NULL; + } + if(node->token[0] == '@') { + for(i=0;itoken[1]) == 0) { + String dataValue = actData[0] == '\0' ? "" : getDataValue(actData, i); + char *str = (char *)dataValue.c_str(); + if(strlen(str) == 0) { + memset(&vnull, 0, sizeof(struct vm_vnull_t)); + vnull.type = VNULL; + vnull.ret = token; + + return (unsigned char *)&vnull; + } else { + float var = atof(str); + float nr = 0; + + // mosquitto_publish + if(modff(var, &nr) == 0) { + memset(&vinteger, 0, sizeof(struct vm_vinteger_t)); + vinteger.type = VINTEGER; + vinteger.value = (int)var; + + return (unsigned char *)&vinteger; + } else { + memset(&vfloat, 0, sizeof(struct vm_vfloat_t)); + vfloat.type = VFLOAT; + vfloat.value = var; + + return (unsigned char *)&vfloat; + } + } + } + } + } + if(node->token[0] == '%') { + if(stricmp((char *)&node->token[1], "hour") == 0) { + memset(&vinteger, 0, sizeof(struct vm_vinteger_t)); + vinteger.type = VINTEGER; + vinteger.value = 0; + return (unsigned char *)&vinteger; + } + if(stricmp((char *)&node->token[1], "month") == 0) { + memset(&vinteger, 0, sizeof(struct vm_vinteger_t)); + vinteger.type = VINTEGER; + vinteger.value = 0; + return (unsigned char *)&vinteger; + } + } + if(node->token[0] == '?') { + if(stricmp((char *)&node->token[1], "temperature") == 0) { + if(strlen(openTherm[0].c_str()) == 0) { + memset(&vnull, 0, sizeof(struct vm_vnull_t)); + vnull.type = VNULL; + vnull.ret = token; + // printf("%s %s = NULL\n", __FUNCTION__, (char *)node->token); + return (unsigned char *)&vnull; + } else { + float var = atof(openTherm[0].c_str()); + memset(&vfloat, 0, sizeof(struct vm_vfloat_t)); + vfloat.type = VFLOAT; + vfloat.value = var; + // printf("%s %s = %g\n", __FUNCTION__, (char *)node->token, var); + return (unsigned char *)&vfloat; + } + } + if(stricmp((char *)&node->token[1], "setpoint") == 0) { + if(strlen(openTherm[1].c_str()) == 0) { + memset(&vnull, 0, sizeof(struct vm_vnull_t)); + vnull.type = VNULL; + vnull.ret = token; + // printf("%s %s = NULL\n", __FUNCTION__, (char *)node->token); + return (unsigned char *)&vnull; + } else { + float var = atof(openTherm[1].c_str()); + memset(&vfloat, 0, sizeof(struct vm_vfloat_t)); + vfloat.type = VFLOAT; + vfloat.value = var; + // printf("%s %s = %g\n", __FUNCTION__, (char *)node->token, var); + return (unsigned char *)&vfloat; + } + } + } + if(strncmp_P((const char *)node->token, PSTR("ds18b20#"), 8) == 0) { + for(i=0;itoken[8], 16) == 0) { + vfloat.type = VFLOAT; + vfloat.value = actDallasData[i].temperature; + // printf("%s %s = %g\n", __FUNCTION__, (char *)node->token, actDallasData[i].temperature); + return (unsigned char *)&vfloat; + } + } + + memset(&vnull, 0, sizeof(struct vm_vnull_t)); + vnull.type = VNULL; + + return (unsigned char *)&vnull; + } + return NULL; +} + +static int vm_value_del(struct rules_t *obj, uint16_t idx) { + struct varstack_t *varstack = (struct varstack_t *)obj->userdata; + int x = 0, ret = 0; + + if(idx == varstack->nrbytes) { + return -1; + } + switch(varstack->stack[idx]) { + case VINTEGER: { + ret = alignedbytes(sizeof(struct vm_vinteger_t)); + memmove(&varstack->stack[idx], &varstack->stack[idx+ret], varstack->nrbytes-idx-ret); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + varstack->nrbytes -= ret; + varstack->bufsize = alignedbuffer(varstack->nrbytes); + } break; + case VFLOAT: { + ret = alignedbytes(sizeof(struct vm_vfloat_t)); + memmove(&varstack->stack[idx], &varstack->stack[idx+ret], varstack->nrbytes-idx-ret); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack,alignedbuffer(varstack->nrbytes-ret))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + varstack->nrbytes -= ret; + varstack->bufsize = alignedbuffer(varstack->nrbytes); + } break; + case VNULL: { + ret = alignedbytes(sizeof(struct vm_vnull_t)); + memmove(&varstack->stack[idx], &varstack->stack[idx+ret], varstack->nrbytes-idx-ret); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + varstack->nrbytes -= ret; + varstack->bufsize = alignedbuffer(varstack->nrbytes); + } break; + default: { + return -1; + } break; + } + + /* + * Values are linked back to their root node, + * by their absolute position in the bytecode. + * If a value is deleted, these positions changes, + * so we need to update all nodes. + */ + for(x=idx;alignedbytes(x)nrbytes;x++) { + x = alignedbytes(x); + switch(varstack->stack[x]) { + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&varstack->stack[x]; + if(node->ret > 0) { + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret]; + tmp->value = x; + } + x += sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&varstack->stack[x]; + if(node->ret > 0) { + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret]; + tmp->value = x; + } + x += sizeof(struct vm_vfloat_t)-1; + } break; + default: { + return -1; + } break; + } + } + + return ret; +} + +static void vm_value_set(struct rules_t *obj, uint16_t token, uint16_t val) { + struct varstack_t *varstack = NULL; + struct vm_tvar_t *var = (struct vm_tvar_t *)&obj->ast.buffer[token]; + int ret = 0, x = 0, loop = 1; + + if(var->token[0] == '$') { + varstack = (struct varstack_t *)obj->userdata; + + const char *key = (char *)var->token; + switch(obj->varstack.buffer[val]) { + case VINTEGER: { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[val]; + } break; + case VFLOAT: { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[val]; + } break; + case VNULL: { + struct vm_vnull_t *na = (struct vm_vnull_t *)&obj->varstack.buffer[val]; + } break; + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[val]; + } break; + } + + /* + * Remove previous value linked to + * the variable being set. + */ + for(x=4;alignedbytes(x)nrbytes && loop == 1;x++) { + x = alignedbytes(x); + switch(varstack->stack[x]) { + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&varstack->stack[x]; + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret]; + if(strcmp((char *)var->token, (char *)tmp->token) == 0) { + var->value = 0; + vm_value_del(obj, x); + loop = 0; + break; + } + x += sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&varstack->stack[x]; + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret]; + if(strcmp((char *)var->token, (char *)tmp->token) == 0) { + var->value = 0; + vm_value_del(obj, x); + loop = 0; + break; + } + x += sizeof(struct vm_vfloat_t)-1; + } break; + case VNULL: { + struct vm_vnull_t *node = (struct vm_vnull_t *)&varstack->stack[x]; + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[node->ret]; + if(strcmp((char *)var->token, (char *)tmp->token) == 0) { + var->value = 0; + vm_value_del(obj, x); + loop = 0; + break; + } + x += sizeof(struct vm_vnull_t)-1; + } break; + default: { + return; + } break; + } + } + + var = (struct vm_tvar_t *)&obj->ast.buffer[token]; + if(var->value > 0) { + vm_value_del(obj, var->value); + } + var = (struct vm_tvar_t *)&obj->ast.buffer[token]; + + ret = varstack->nrbytes; + + var->value = ret; + + switch(obj->varstack.buffer[val]) { + case VINTEGER: { + unsigned int size = alignedbytes(varstack->nrbytes+sizeof(struct vm_vinteger_t)); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + struct vm_vinteger_t *cpy = (struct vm_vinteger_t *)&obj->varstack.buffer[val]; + struct vm_vinteger_t *value = (struct vm_vinteger_t *)&varstack->stack[ret]; + value->type = VINTEGER; + value->ret = token; + value->value = (int)cpy->value; + + varstack->nrbytes = size; + varstack->bufsize = alignedbuffer(size); + } break; + case VFLOAT: { + unsigned int size = alignedbytes(varstack->nrbytes+sizeof(struct vm_vfloat_t)); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + struct vm_vfloat_t *cpy = (struct vm_vfloat_t *)&obj->varstack.buffer[val]; + struct vm_vfloat_t *value = (struct vm_vfloat_t *)&varstack->stack[ret]; + value->type = VFLOAT; + value->ret = token; + value->value = cpy->value; + + varstack->nrbytes = size; + varstack->bufsize = alignedbuffer(size); + } break; + case VNULL: { + unsigned int size = alignedbytes(varstack->nrbytes+sizeof(struct vm_vnull_t)); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + struct vm_vnull_t *value = (struct vm_vnull_t *)&varstack->stack[ret]; + value->type = VNULL; + value->ret = token; + varstack->nrbytes = size; + varstack->bufsize = alignedbuffer(size); + } break; + default: { + return; + } break; + } + } else if(var->token[0] == '#') { + varstack = &global_varstack; + + const char *key = (char *)var->token; + switch(obj->varstack.buffer[val]) { + case VINTEGER: { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[val]; + } break; + case VFLOAT: { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[val]; + } break; + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[val]; + } break; + case VNULL: { + struct vm_vnull_t *na = (struct vm_vnull_t *)&obj->varstack.buffer[val]; + } break; + } + + var = (struct vm_tvar_t *)&obj->ast.buffer[token]; + int move = 0; + for(x=4;alignedbytes(x)nrbytes;x++) { + x = alignedbytes(x); + switch(varstack->stack[x]) { + case VINTEGER: { + struct vm_gvinteger_t *val = (struct vm_gvinteger_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + + if(strcmp((char *)foo->token, (char *)var->token) == 0) { + move = 1; + + ret = alignedbytes(sizeof(struct vm_gvinteger_t)); + memmove(&varstack->stack[x], &varstack->stack[x+ret], varstack->nrbytes-x-ret); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + varstack->nrbytes -= ret; + varstack->bufsize = alignedbuffer(varstack->nrbytes); + } + } break; + case VFLOAT: { + struct vm_gvfloat_t *val = (struct vm_gvfloat_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + + if(strcmp((char *)foo->token, (char *)var->token) == 0) { + move = 1; + + ret = alignedbytes(sizeof(struct vm_gvfloat_t)); + memmove(&varstack->stack[x], &varstack->stack[x+ret], varstack->nrbytes-x-ret); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + varstack->nrbytes -= ret; + varstack->bufsize = alignedbuffer(varstack->nrbytes); + } + } break; + case VNULL: { + struct vm_gvnull_t *val = (struct vm_gvnull_t *)&varstack->stack[x]; + struct vm_tvar_t *foo = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + + if(strcmp((char *)foo->token, (char *)var->token) == 0) { + move = 1; + + ret = alignedbytes(sizeof(struct vm_gvnull_t)); + memmove(&varstack->stack[x], &varstack->stack[x+ret], varstack->nrbytes-x-ret); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(varstack->nrbytes-ret))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + varstack->nrbytes -= ret; + varstack->bufsize = alignedbuffer(varstack->nrbytes); + } + } break; + default: { + return; + } break; + } + if(x == varstack->nrbytes) { + break; + } + + switch(varstack->stack[x]) { + case VINTEGER: { + if(move == 1 && x < varstack->nrbytes) { + struct vm_gvinteger_t *node = (struct vm_gvinteger_t *)&varstack->stack[x]; + if(node->ret > 0) { + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&rules[node->rule-1]->ast.buffer[node->ret]; + tmp->value = x; + } + } + x += sizeof(struct vm_gvinteger_t)-1; + } break; + case VFLOAT: { + if(move == 1 && x < varstack->nrbytes) { + struct vm_gvfloat_t *node = (struct vm_gvfloat_t *)&varstack->stack[x]; + if(node->ret > 0) { + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&rules[node->rule-1]->ast.buffer[node->ret]; + tmp->value = x; + } + } + x += sizeof(struct vm_gvfloat_t)-1; + } break; + case VNULL: { + if(move == 1 && x < varstack->nrbytes) { + struct vm_gvnull_t *node = (struct vm_gvnull_t *)&varstack->stack[x]; + if(node->ret > 0) { + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&rules[node->rule-1]->ast.buffer[node->ret]; + tmp->value = x; + } + } + x += sizeof(struct vm_gvnull_t)-1; + } break; + } + } + var = (struct vm_tvar_t *)&obj->ast.buffer[token]; + + ret = varstack->nrbytes; + + var->value = ret; + + switch(obj->varstack.buffer[val]) { + case VINTEGER: { + unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_gvinteger_t)); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + struct vm_vinteger_t *cpy = (struct vm_vinteger_t *)&obj->varstack.buffer[val]; + struct vm_gvinteger_t *value = (struct vm_gvinteger_t *)&varstack->stack[ret]; + value->type = VINTEGER; + value->ret = token; + value->value = (int)cpy->value; + value->rule = obj->nr; + + varstack->nrbytes = size; + varstack->bufsize = alignedbuffer(size); + } break; + case VFLOAT: { + unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_gvfloat_t)); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + struct vm_vfloat_t *cpy = (struct vm_vfloat_t *)&obj->varstack.buffer[val]; + struct vm_gvfloat_t *value = (struct vm_gvfloat_t *)&varstack->stack[ret]; + value->type = VFLOAT; + value->ret = token; + value->value = cpy->value; + value->rule = obj->nr; + + varstack->nrbytes = size; + varstack->bufsize = alignedbuffer(size); + } break; + case VNULL: { + unsigned int size = alignedbytes(varstack->nrbytes + sizeof(struct vm_gvnull_t)); + if((varstack->stack = (unsigned char *)REALLOC(varstack->stack, alignedbuffer(size))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + struct vm_gvnull_t *value = (struct vm_gvnull_t *)&varstack->stack[ret]; + value->type = VNULL; + value->ret = token; + value->rule = obj->nr; + + varstack->nrbytes = size; + varstack->bufsize = alignedbuffer(size); + } break; + default: { + return; + } break; + } + } else if(var->token[0] == '@') { + char *topic = NULL, *payload = NULL; + const char *key = (char *)var->token; + unsigned int len = 0; + + switch(obj->varstack.buffer[val]) { + case VINTEGER: { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[val]; + + len = snprintf_P(NULL, 0, PSTR("%d"), (int)na->value); + if((payload = (char *)MALLOC(len+1)) == NULL) { + OUT_OF_MEMORY + } + snprintf_P(payload, len+1, PSTR("%d"), (int)na->value); + + } break; + case VFLOAT: { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[val]; + + len = snprintf_P(NULL, 0, PSTR("%g"), (float)na->value); + if((payload = (char *)MALLOC(len+1)) == NULL) { + OUT_OF_MEMORY + } + snprintf_P(payload, len+1, PSTR("%g"), (float)na->value); + } break; + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[val]; + + len = snprintf_P(NULL, 0, PSTR("%s"), na->value); + if((payload = (char *)MALLOC(len+1)) == NULL) { + OUT_OF_MEMORY + } + snprintf_P(payload, len+1, PSTR("%s"), na->value); + } break; + } + + if(parsing == 0 && !heishamonSettings.listenonly) { + unsigned char cmd[256] = { 0 }; + char log_msg[256] = { 0 }; + + for(uint8_t x = 0; x < sizeof(commands) / sizeof(commands[0]); x++) { + cmdStruct tmp; + memcpy_P(&tmp, &commands[x], sizeof(tmp)); + if(strcmp((char *)&var->token[1], tmp.name) == 0) { + uint16_t len = tmp.func(payload, cmd, log_msg); + log_message(log_msg); + send_command(cmd, len); + break; + } + } + + memset(&cmd, 256, 0); + memset(&log_msg, 256, 0); + + if(heishamonSettings.optionalPCB) { + //optional commands + for(uint8_t x = 0; x < sizeof(optionalCommands) / sizeof(optionalCommands[0]); x++) { + optCmdStruct tmp; + memcpy_P(&tmp, &optionalCommands[x], sizeof(tmp)); + if(strcmp((char *)&var->token[1], tmp.name) == 0) { + uint16_t len = tmp.func(payload, log_msg); + log_message(log_msg); + break; + } + } + } + } + FREE(payload); + } +} + +static void vm_value_prt(struct rules_t *obj, char *out, int size) { + struct varstack_t *varstack = (struct varstack_t *)obj->userdata; + int x = 0, pos = 0; + + for(x=4;alignedbytes(x)nrbytes;x++) { + if(alignedbytes(x) < varstack->nrbytes) { + x = alignedbytes(x); + switch(varstack->stack[x]) { + case VINTEGER: { + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&varstack->stack[x]; + switch(obj->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + pos += snprintf_P(&out[pos], size - pos, PSTR("%s = %d\n"), node->token, val->value); + } break; + default: { + return; + } break; + } + x += sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *val = (struct vm_vfloat_t *)&varstack->stack[x]; + switch(obj->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + pos += snprintf_P(&out[pos], size - pos, PSTR("%s = %g\n"), node->token, val->value); + } break; + default: { + return; + } break; + } + x += sizeof(struct vm_vfloat_t)-1; + } break; + case VNULL: { + struct vm_vnull_t *val = (struct vm_vnull_t *)&varstack->stack[x]; + switch(obj->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + pos += snprintf_P(&out[pos], size - pos, PSTR("%s = NULL\n"), node->token); + } break; + default: { + return; + } break; + } + x += sizeof(struct vm_vnull_t)-1; + } break; + default: { + return; + } break; + } + } + } +} + +static void vm_global_value_prt(char *out, int size) { + struct varstack_t *varstack = &global_varstack; + int x = 0, pos = 0; + + for(x=4;alignedbytes(x)nrbytes;x++) { + x = alignedbytes(x); + switch(varstack->stack[x]) { + case VINTEGER: { + struct vm_gvinteger_t *val = (struct vm_gvinteger_t *)&varstack->stack[x]; + switch(rules[val->rule-1]->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + pos += snprintf_P(&out[pos], size - pos, PSTR("%d %s = %d\n"), x, node->token, val->value); + } break; + default: { + return; + } break; + } + x += sizeof(struct vm_gvinteger_t)-1; + } break; + case VFLOAT: { + struct vm_gvfloat_t *val = (struct vm_gvfloat_t *)&varstack->stack[x]; + switch(rules[val->rule-1]->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + pos += snprintf_P(&out[pos], size - pos, PSTR("%d %s = %g\n"), x, node->token, val->value); + } break; + default: { + return; + } break; + } + x += sizeof(struct vm_gvfloat_t)-1; + } break; + case VNULL: { + struct vm_gvnull_t *val = (struct vm_gvnull_t *)&varstack->stack[x]; + switch(rules[val->rule-1]->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&rules[val->rule-1]->ast.buffer[val->ret]; + pos += snprintf_P(&out[pos], size - pos, PSTR("%d %s = NULL\n"), x, node->token); + } break; + default: { + return; + } break; + } + x += sizeof(struct vm_gvnull_t)-1; + } break; + default: { + return; + } break; + } + } +} + +static void vm_clear_values(struct rules_t *obj) { + int i = 0, x = 0; + for(i=x;alignedbytes(i)ast.nrbytes;i++) { + i = alignedbytes(i); + switch(obj->ast.buffer[i]) { + case TSTART: { + i+=sizeof(struct vm_tstart_t)-1; + } break; + case TEOF: { + i+=sizeof(struct vm_teof_t)-1; + } break; + case VNULL: { + i+=sizeof(struct vm_vnull_t)-1; + } break; + case TIF: { + i+=sizeof(struct vm_tif_t)-1; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[i]; + node->value = 0; + i+=sizeof(struct vm_lparen_t)-1; + } break; + case TFALSE: + case TTRUE: { + struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[i]; + i+=sizeof(struct vm_ttrue_t)+(sizeof(node->go[0])*node->nrgo)-1; + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[i]; + node->value = 0; + i+=sizeof(struct vm_tfunction_t)+(sizeof(node->go[0])*node->nrgo)-1; + } break; + case TCEVENT: { + struct vm_tcevent_t *node = (struct vm_tcevent_t *)&obj->ast.buffer[i]; + i+=sizeof(struct vm_tcevent_t)+strlen((char *)node->token); + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[i]; + node->value = 0; + i+=sizeof(struct vm_tvar_t)+strlen((char *)node->token); + } break; + case TEVENT: { + struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[i]; + i += sizeof(struct vm_tevent_t)+strlen((char *)node->token); + } break; + case TNUMBER: { + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[i]; + i+=sizeof(struct vm_tnumber_t)+strlen((char *)node->token); + } break; + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&obj->ast.buffer[i]; + i+=sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&obj->ast.buffer[i]; + i+=sizeof(struct vm_vfloat_t)-1; + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[i]; + node->value = 0; + i+=sizeof(struct vm_toperator_t)-1; + } break; + default: { + } break; + } + } +} + +void rules_new_event(const char *event) { + static char out[1024]; + uint8_t i = 0; + for(i=0;i -1 && (rules[i]->ast.buffer[get_event(rules[i])+5] == '@' && strnicmp((char *)&rules[i]->ast.buffer[get_event(rules[i])+6], (char *)event, strlen((char *)event)) == 0)) { + logprintf_P(F("==== %s ===="), (char *)&rules[i]->ast.buffer[get_event(rules[i])+6]); + logprintf_P(F(">>> rule %d nrbytes: %d"), i, rules[i]->ast.nrbytes); + logprintf_P(F(">>> global stack nrbytes: %d"),global_varstack.nrbytes); + rule_run(rules[i], 0); + memset(&out, 0, 1024); + logprintf_P(F(">>> local variables")); + vm_value_prt(rules[i], (char *)&out, 1024); + logprintf_P(F("%s"),out); + logprintf_P(F(">>> global variables")); + memset(&out, 0, 1024); + vm_global_value_prt((char *)&out, 1024); + logprintf_P(F("%s"),out); + } + } +} + +void rules_timer_cb(int nr) { + char *name = NULL; + int x = 0, i = 0; + + i = snprintf_P(NULL, 0, PSTR("timer=%d"), nr); + if((name = (char *)MALLOC(i+2)) == NULL) { + OUT_OF_MEMORY + } + memset(name, 0, i+2); + snprintf_P(name, i+1, PSTR("timer=%d"), nr); + + // logprintf_P(F("_______ %s %s"), __FUNCTION__, name); + + for(x=0;x -1 && strcmp((char *)&rules[x]->ast.buffer[get_event(rules[x])+5], name) == 0) { + rule_run(rules[x], 0); + + char out[512]; + memset(&out, 0, 512); + logprintln_P(F("\n>>> local variables\n")); + vm_value_prt(rules[x], (char *)&out, 512); + logprintf_P(F("%s"), out); + logprintf_P(F("\n>>> global variables\n")); + memset(&out, 0, 512); + vm_global_value_prt((char *)&out, 512); + logprintf_P(F("%s"), out); + + break; + } + } + FREE(name); +} + +int rules_parse(char *file) { + File frules = LittleFS.open(file, "r"); + if(frules) { + parsing = 1; + + if(nrrules > 0) { + for(int i=0;iuserdata != NULL) { + FREE(rules[i]->userdata); + } + } + rules_gc(&rules, nrrules); + nrrules = 0; + } + memset(mempool, 0, MEMPOOL_SIZE); + + FREE(global_varstack.stack); + global_varstack.stack = NULL; + global_varstack.nrbytes = 4; + +#define BUFFER_SIZE 128 + char content[BUFFER_SIZE]; + memset(content, 0, BUFFER_SIZE); + int len = frules.size(); + int chunk = 0, len1 = 0; + + unsigned int txtoffset = alignedbuffer(MEMPOOL_SIZE-len-5); + + while(1) { + memset(content, 0, BUFFER_SIZE); + frules.seek(chunk*BUFFER_SIZE, SeekSet); + if (chunk * BUFFER_SIZE <= len) { + frules.readBytes(content, BUFFER_SIZE); + len1 = BUFFER_SIZE; + } else if ((chunk * BUFFER_SIZE) >= len && (chunk * BUFFER_SIZE) <= len + BUFFER_SIZE) { + frules.readBytes(content, len - ((chunk - 1)*BUFFER_SIZE)); + len1 = len - ((chunk - 1) * BUFFER_SIZE); + } else { + break; + } + memcpy(&mempool[txtoffset+(chunk*BUFFER_SIZE)], &content, alignedbuffer(len1)); + chunk++; + } + frules.close(); + + struct varstack_t *varstack = (struct varstack_t *)MALLOC(sizeof(struct varstack_t)); + if(varstack == NULL) { + OUT_OF_MEMORY + } + varstack->stack = NULL; + varstack->nrbytes = 4; + varstack->bufsize = 4; + + struct pbuf mem; + struct pbuf input; + memset(&mem, 0, sizeof(struct pbuf)); + memset(&input, 0, sizeof(struct pbuf)); + + mem.payload = mempool; + mem.len = 0; + mem.tot_len = MEMPOOL_SIZE; + + input.payload = &mempool[txtoffset]; + input.len = txtoffset; + input.tot_len = len; + + int ret = 0; + char *text = (char *)&mempool[txtoffset]; + while((ret = rule_initialize(&input, &rules, &nrrules, &mem, varstack)) == 0) { + varstack = (struct varstack_t *)MALLOC(sizeof(struct varstack_t)); + if(varstack == NULL) { + OUT_OF_MEMORY + } + varstack->stack = NULL; + varstack->nrbytes = 4; + varstack->bufsize = 4; + input.payload = &mempool[input.len]; + } + + logprintf_P(F("rules memory used: %d / %d"), mem.len, mem.tot_len); + + if(nrrules > 1) { + FREE(varstack); + } + + /* + * Clear all timers + */ + struct timerqueue_t *node = NULL; + while((node = timerqueue_pop()) != NULL) { + FREE(node); + } + + FREE(global_varstack.stack); + global_varstack.stack = NULL; + global_varstack.nrbytes = 4; + + if(ret == -1) { + if(nrrules > 0) { + for(int i=0;iuserdata != NULL) { + FREE(rules[i]->userdata); + } + } + rules_gc(&rules, nrrules); + } + nrrules = 0; + return -1; + } + + int i = 0; + for(i=0;iast.buffer[0]; + if(rules[i]->ast.buffer[start->go] == TEVENT) { + struct vm_tevent_t *event = (struct vm_tevent_t *)&rules[i]->ast.buffer[start->go]; + tlen = strlen((char *)event->token); + if( + ( + len+8 == tlen && + strncmp_P((char *)event->token, PSTR("ds18b20#"), 8) == 0 && + strnicmp((char *)&event->token[8], name, len) == 0 + ) || + ( + len+1 == tlen && + (event->token[0] == '@' || event->token[0] == '?') && + strnicmp((char *)&event->token[1], name, len) == 0 + ) + ) { + char out[512]; + logprintf_P(F("%s %s %s"), F("===="), event->token, F("====")); + logprintf_P(F("%s %d %s %d"), F(">>> rule"), i, F("nrbytes:"), rules[i]->ast.nrbytes); + logprintf_P(F("%s %d"), F(">>> global stack nrbytes:"), global_varstack.nrbytes); + + rules[i]->timestamp.first = micros(); + + rule_run(rules[i], 0); + + rules[i]->timestamp.second = micros(); + + logprintf_P(F("%s%d %s %d %s"), F("rule #"), rules[i]->nr, F("was executed in"), rules[i]->timestamp.second - rules[i]->timestamp.first, F("microseconds")); + + logprintln_P(F("\n>>> local variables")); + memset(&out, 0, sizeof(out)); + vm_value_prt(rules[i], (char *)&out, sizeof(out)); + logprintln(out); + logprintln_P(F(">>> global variables")); + memset(&out, 0, sizeof(out)); + vm_global_value_prt((char *)&out, sizeof(out)); + logprintln(out); + break; + } + } + } + //for(int i=0;ivarstack.buffer); + // rules[i]->varstack.nrbytes = 4; + // rules[i]->varstack.bufsize = 4; + //} + int x = 0; + for(i=0;iast.nrbytes; + } +} + +void rules_boot(void) { + for(uint8_t i=0;iast.buffer[0]; + if(rules[i]->ast.buffer[start->go] == TEVENT) { + struct vm_tevent_t *event = (struct vm_tevent_t *)&rules[i]->ast.buffer[start->go]; + if(stricmp((char *)&event->token, "System#Boot") == 0) { + char out[512]; + logprintf_P(F("==== SYSTEM#BOOT ====")); + logprintf_P(F("%s %d %s %d"), F(">>> rule"), i, F("nrbytes:"), rules[i]->ast.nrbytes); + logprintf_P(F("%s %d"), F(">>> global stack nrbytes:"), global_varstack.nrbytes); + + rules[i]->timestamp.first = micros(); + + rule_run(rules[i], 0); + + rules[i]->timestamp.second = micros(); + + logprintf_P(F("%s%d %s %d %s"), F("rule #"), rules[i]->nr, F("was executed in"), rules[i]->timestamp.second - rules[i]->timestamp.first, F("microseconds")); + + logprintln_P(F("\n>>> local variables")); + memset(&out, 0, sizeof(out)); + vm_value_prt(rules[i], (char *)&out, sizeof(out)); + logprintln(out); + logprintln_P(F(">>> global variables")); + memset(&out, 0, sizeof(out)); + vm_global_value_prt((char *)&out, sizeof(out)); + logprintln(out); + break; + } + } + } + + // unsigned long a = micros(); + // for(int i=0;ivarstack.buffer); + // rules[i]->varstack.nrbytes = 4; + // rules[i]->varstack.bufsize = 4; + // } +} + +void rules_setup(void) { + if(!LittleFS.begin()) { + return; + } + memset(mempool, 0, MEMPOOL_SIZE); + + logprintf_P(F("rules mempool size: %d"), MEMPOOL_SIZE); + + logprintln_P(F("reading rules")); + + global_varstack.stack = NULL; + global_varstack.nrbytes = 4; + + memset(&rule_options, 0, sizeof(struct rule_options_t)); + rule_options.is_token_cb = is_variable; + rule_options.is_event_cb = is_event; + rule_options.set_token_val_cb = vm_value_set; + rule_options.get_token_val_cb = vm_value_get; + rule_options.prt_token_val_cb = vm_value_prt; + rule_options.cpy_token_val_cb = vm_value_cpy; + rule_options.clr_token_val_cb = vm_value_clr; + rule_options.event_cb = event_cb; + + // if(LittleFS.exists("/rules.new")) { + // logprintln_P(F("new ruleset found, trying to parse it")); + // if(rules_parse("/rules.new") == -1) { + // logprintln_P(F("new ruleset failed to parse, using previous ruleset after restart")); + // LittleFS.remove("/rules.new"); + // ESP.restart(); + // } else { + // LittleFS.rename("/rules.new", "/rules.txt"); + // } + // } else if(LittleFS.exists("/rules.txt")) { + // if(rules_parse("/rules.txt") == -1) { + // logprintln_P(F("old ruleset failed to parse, restarting without rules")); + // LittleFS.rename("/rules.txt", "/rules.old"); + // ESP.restart(); + // } + // } else if(LittleFS.exists("/rules.old")) { + // LittleFS.rename("/rules.old", "/rules.txt"); + // } + + if(LittleFS.exists("/rules.txt")) { + rules_parse("/rules.txt"); + } + + rules_boot(); +} diff --git a/HeishaMon/rules.h b/HeishaMon/rules.h new file mode 100755 index 00000000..bb7f65e2 --- /dev/null +++ b/HeishaMon/rules.h @@ -0,0 +1,28 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef __RULES_H_ +#define __RULES_H_ + +#include +#include +#include + +#include + +#include "src/common/mem.h" + +void rules_loop(void); +void rules_boot(void); +void rules_new_event(const char *event); +int rules_parse(char *file); +void rules_setup(void); +void rules_timer_cb(int nr); +void rules_event_cb(char *name); + +#endif \ No newline at end of file diff --git a/HeishaMon/s0.cpp b/HeishaMon/s0.cpp index 74f6252f..ce6d28f5 100644 --- a/HeishaMon/s0.cpp +++ b/HeishaMon/s0.cpp @@ -7,119 +7,166 @@ #define MINREPORTEDS0TIME 5000 // how often s0 Watts are reported (not faster than this) //global array for s0 data -s0DataStruct actS0Data[NUM_S0_COUNTERS]; +volatile s0DataStruct actS0Data[NUM_S0_COUNTERS]; //global array for s0 Settings -s0SettingsStruct actS0Settings[NUM_S0_COUNTERS]; +volatile s0SettingsStruct actS0Settings[NUM_S0_COUNTERS]; -//volatile pulse detectors for s0 -volatile unsigned long new_pulse_s0[2] = {0, 0}; +//These are the interrupt routines. Make them as short as possible so we don't block main code +volatile unsigned long lastEdgeS0[NUM_S0_COUNTERS] = {0, 0}; +volatile bool badEdge[NUM_S0_COUNTERS] = {0, 0}; //two edges is a pulse, so store as bool -//These are the interrupt routines. Make them as short as possible so we don't block other interrupts (for example serial data) -ICACHE_RAM_ATTR void onS0Pulse1() { - new_pulse_s0[0] = millis(); +//for debug +/* + volatile unsigned long allLastEdgeS0[2][20]; + volatile int allLastEdgeS0Index[2] = {0, 0}; +*/ + +IRAM_ATTR void countPulse(int i) { + volatile unsigned long newEdgeS0 = millis(); + volatile unsigned long curPulseWidth = newEdgeS0 - lastEdgeS0[i]; + // debug + /* + if (allLastEdgeS0Index[i] < 20) { + allLastEdgeS0[i][allLastEdgeS0Index[i]] = curPulseWidth; + allLastEdgeS0Index[i]++; + } + */ + // end debug + if ((curPulseWidth >= actS0Settings[i].minimalPulseWidth) && (curPulseWidth <= actS0Settings[i].maximalPulseWidth) ) { + if (actS0Data[i].lastPulse > 0) { //Do not calculate watt for the first pulse since reboot because we will always report a too high watt. Better to show 0 watt at first pulse. + volatile unsigned long pulseInterval = newEdgeS0 - actS0Data[i].lastPulse; //calculate the interval between last valid pulse edge and this edge + actS0Data[i].watt = (3600000000.0 / pulseInterval) / actS0Settings[i].ppkwh; + if ((unsigned long)(actS0Data[i].nextReport - newEdgeS0) > MINREPORTEDS0TIME) { //pulse seen in standby interval so report directly + actS0Data[i].nextReport = 0; // report now + } + } + actS0Data[i].lastPulse = newEdgeS0; // store this edge to compare in next pulses for valid pulse and calculate watt + actS0Data[i].pulses++; + actS0Data[i].pulsesTotal++; + actS0Data[i].goodPulses++; + actS0Data[i].avgPulseWidth = ((actS0Data[i].avgPulseWidth * (actS0Data[i].goodPulses - 1)) + curPulseWidth ) / actS0Data[i].goodPulses; + badEdge[i] = false; //set it to false again to allow to count two bad edges as a new bad pulse because we know we had a good pulse now + } else { + if (badEdge[i]) actS0Data[i].badPulses++; //there was already an edge before so count this one as a bad pulse + badEdge[i] = !badEdge[i]; //for now count it as a bad edge (if it is a edge for a good pulse, this will reset to false a few lines above). The bool is for not counting each edge as a bad pulse, but only two bad edges. + } + lastEdgeS0[i] = newEdgeS0; //store this edge time for next use +} + +IRAM_ATTR void onS0Pulse1Change() { + countPulse(0); //port 1, index 0 of array } -ICACHE_RAM_ATTR void onS0Pulse2() { - new_pulse_s0[1] = millis(); +IRAM_ATTR void onS0Pulse2Change() { + countPulse(1); //port 2, index 1 of array } + void initS0Sensors(s0SettingsStruct s0Settings[]) { //setup s0 port 1 + + //TODO: check if this is still necessary actS0Settings[0].gpiopin = s0Settings[0].gpiopin; actS0Settings[0].ppkwh = s0Settings[0].ppkwh; actS0Settings[0].lowerPowerInterval = s0Settings[0].lowerPowerInterval; + actS0Settings[0].minimalPulseWidth = s0Settings[0].minimalPulseWidth; + actS0Settings[0].maximalPulseWidth = s0Settings[0].maximalPulseWidth; pinMode(actS0Settings[0].gpiopin, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(actS0Settings[0].gpiopin), onS0Pulse1, RISING); + attachInterrupt(digitalPinToInterrupt(actS0Settings[0].gpiopin), onS0Pulse1Change, CHANGE); actS0Data[0].nextReport = millis() + MINREPORTEDS0TIME; //initial report after interval, not directly at boot //setup s0 port 2 + + //TODO: check if this is still necessary actS0Settings[1].gpiopin = s0Settings[1].gpiopin; actS0Settings[1].ppkwh = s0Settings[1].ppkwh; actS0Settings[1].lowerPowerInterval = s0Settings[1].lowerPowerInterval; + actS0Settings[1].minimalPulseWidth = s0Settings[1].minimalPulseWidth; + actS0Settings[1].maximalPulseWidth = s0Settings[1].maximalPulseWidth; + pinMode(actS0Settings[1].gpiopin, INPUT_PULLUP); - attachInterrupt(digitalPinToInterrupt(actS0Settings[1].gpiopin), onS0Pulse2, RISING); + attachInterrupt(digitalPinToInterrupt(actS0Settings[1].gpiopin), onS0Pulse2Change, CHANGE); actS0Data[1].nextReport = millis() + MINREPORTEDS0TIME; //initial report after interval, not directly at boot } void restore_s0_Watthour(int s0Port, float watthour) { - if ((s0Port == 1) || (s0Port == 2)) { + if ((s0Port == 1) || (s0Port == 2)) { unsigned int newTotal = int(watthour * (actS0Settings[s0Port - 1].ppkwh / 1000.0)); - if (newTotal > actS0Data[s0Port - 1].pulsesTotal) actS0Data[s0Port - 1].pulsesTotal = newTotal; - } -} - -void s0SettingsCorrupt(s0SettingsStruct s0Settings[], void (*log_message)(char*)) { - for (int i = 0 ; i < NUM_S0_COUNTERS ; i++) { - if ((s0Settings[i].gpiopin != actS0Settings[i].gpiopin) || (s0Settings[i].ppkwh != actS0Settings[i].ppkwh) || (s0Settings[i].lowerPowerInterval != actS0Settings[i].lowerPowerInterval)) { - char log_msg[256]; - sprintf_P(log_msg, PSTR("S0 settings got corrupted, rebooting!") ); log_message(log_msg); - delay(1000); - ESP.restart(); + if (newTotal > actS0Data[s0Port - 1].pulsesTotal) { + noInterrupts(); + actS0Data[s0Port - 1].pulsesTotal = newTotal; + interrupts(); } } } -void s0Loop(PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, s0SettingsStruct s0Settings[]) { - //check for corruption - s0SettingsCorrupt(s0Settings, log_message); + +void s0Loop(PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, s0SettingsStruct s0Settings[]) { unsigned long millisThisLoop = millis(); for (int i = 0 ; i < NUM_S0_COUNTERS ; i++) { - //first handle new detected pulses - noInterrupts(); - unsigned long new_pulse = new_pulse_s0[i]; - interrupts(); - unsigned long pulseInterval = new_pulse - actS0Data[i].lastPulse; - if (pulseInterval > 50L) { //50ms debounce filter, this also prevents division by zero to occur a few lines further down the road if pulseInterval = 0 - if (actS0Data[i].lastPulse > 0) { //Do not calculate watt for the first pulse since reboot because we will always report a too high watt. Better to show 0 watt at first pulse. - actS0Data[i].watt = (3600000000.0 / pulseInterval) / actS0Settings[i].ppkwh; - } - actS0Data[i].lastPulse = new_pulse; - actS0Data[i].pulses++; - if ((unsigned long)(actS0Data[i].nextReport - millisThisLoop) > MINREPORTEDS0TIME) { //loop was in standby interval - actS0Data[i].nextReport = 0; // report now - } - } + char tmp_log_msg[256]; - //then report after nextReport + //report after nextReport if (millisThisLoop > actS0Data[i].nextReport) { unsigned long lastPulseInterval = millisThisLoop - actS0Data[i].lastPulse; - unsigned long calcMaxWatt = (3600000000.0 / lastPulseInterval) / actS0Settings[i].ppkwh; + unsigned long calcMaxWatt = (3600000000.0 / lastPulseInterval) / actS0Settings[i].ppkwh; //calculate the maximum watt was is possible without receiving pulses during the report interval - if (actS0Data[i].watt < ((3600000.0 / actS0Settings[i].ppkwh) / actS0Settings[i].lowerPowerInterval) ) { //watt is lower than possible in lower power interval time + if (actS0Data[i].watt < ((3600000.0 / actS0Settings[i].ppkwh) / actS0Settings[i].lowerPowerInterval) ) { //watt is lower than possible in lower power interval time, so we are in low power counting mode actS0Data[i].nextReport = millisThisLoop + 1000 * actS0Settings[i].lowerPowerInterval; - if ((actS0Data[i].watt) / 2 > calcMaxWatt) { + if ((actS0Data[i].watt) / 2 > calcMaxWatt) { //last known watt is higher than possible for the interval, so we need to bring it down fast actS0Data[i].watt = calcMaxWatt / 2; } } - else { + else { // we are in normal counting mode, report each MINREPORTEDS0TIME actS0Data[i].nextReport = millisThisLoop + MINREPORTEDS0TIME; - if (actS0Data[i].watt > calcMaxWatt) { + if (actS0Data[i].watt > calcMaxWatt) { //last known watt is higher than possible since last report, so bring it down to wat is possible actS0Data[i].watt = calcMaxWatt; } } float Watthour = (actS0Data[i].pulses * ( 1000.0 / actS0Settings[i].ppkwh)); - actS0Data[i].pulsesTotal = actS0Data[i].pulsesTotal + actS0Data[i].pulses; - actS0Data[i].pulses = 0; //per message we report new wattHour, so pulses should be zero at start new message + float WatthourTotal = (actS0Data[i].pulsesTotal * ( 1000.0 / actS0Settings[i].ppkwh)); + noInterrupts(); + actS0Data[i].pulses = 0; //per message we report new wattHour, so pulses should be zero at start new message + interrupts(); //report using mqtt char log_msg[256]; char mqtt_topic[256]; char valueStr[20]; + + //debug + /* + noInterrupts(); + int j = 0; + while (allLastEdgeS0Index[i] > 0) { + allLastEdgeS0Index[i]--; + sprintf_P(log_msg, PSTR("Pulse widths seen on S0 port %d: Width: %lu"), (i + 1), allLastEdgeS0[i][j] ); + //log_message(log_msg); + Serial1.println(log_msg); + j++; + } + interrupts(); + */ + //end debug + + sprintf_P(log_msg, PSTR("Pulses seen on S0 port %d: Good: %lu Bad: %lu Average good pulse width: %i"), (i + 1), actS0Data[i].goodPulses, actS0Data[i].badPulses, actS0Data[i].avgPulseWidth); + log_message(log_msg); + sprintf_P(log_msg, PSTR("Measured Watthour on S0 port %d: %.2f"), (i + 1), Watthour ); log_message(log_msg); sprintf(valueStr, "%.2f", Watthour); sprintf_P(mqtt_topic, PSTR("%s/%s/Watthour/%d"), mqtt_topic_base, mqtt_topic_s0, (i + 1)); mqtt_client.publish(mqtt_topic, valueStr, MQTT_RETAIN_VALUES); - float WatthourTotal = (actS0Data[i].pulsesTotal * ( 1000.0 / actS0Settings[i].ppkwh)); + sprintf(log_msg, PSTR("Measured total Watthour on S0 port %d: %.2f"), (i + 1), WatthourTotal ); log_message(log_msg); sprintf(valueStr, "%.2f", WatthourTotal); @@ -134,30 +181,90 @@ void s0Loop(PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_to } } -String s0TableOutput() { - String output = F(""); +unsigned long tablePulses[NUM_S0_COUNTERS]; + +void s0TableOutput(struct webserver_t *client) { for (int i = 0; i < NUM_S0_COUNTERS; i++) { - output = output + F(""); - output = output + F("") + (i + 1) + F(""); - output = output + F("") + actS0Data[i].watt + F(""); - output = output + F("") + (actS0Data[i].pulses * ( 1000.0 / actS0Settings[i].ppkwh)) + F(""); - output = output + F("") + (actS0Data[i].pulsesTotal * ( 1000.0 / actS0Settings[i].ppkwh)) + F(""); - output = output + F(""); + webserver_send_content_P(client, PSTR(""), 8); + + char str[12]; + itoa(i + 1, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(""), 9); + + itoa(actS0Data[i].watt, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(""), 9); + + itoa(((actS0Data[i].pulsesTotal - tablePulses[i]) * ( 1000.0 / actS0Settings[i].ppkwh)), str, 10); + webserver_send_content(client, str, strlen(str)); + + tablePulses[i] = actS0Data[i].pulsesTotal; + + webserver_send_content_P(client, PSTR(""), 9); + + itoa((actS0Data[i].pulsesTotal * (1000.0 / actS0Settings[i].ppkwh)), str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(""), 9); + + itoa((100 * (actS0Data[i].goodPulses + 1) / (actS0Data[i].goodPulses + actS0Data[i].badPulses + 1)), str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR("%"), 10); + + itoa(actS0Data[i].avgPulseWidth, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(""), 10); } - return output; } -String s0JsonOutput() { - String output = F("["); +unsigned long jsonPulses[NUM_S0_COUNTERS]; + +void s0JsonOutput(struct webserver_t *client) { + webserver_send_content_P(client, PSTR("["), 1); for (int i = 0; i < NUM_S0_COUNTERS; i++) { - output = output + F("{"); - output = output + F("\"S0 port\": \"") + (i + 1) + F("\","); - output = output + F("\"Watt\": \"") + actS0Data[i].watt + F("\","); - output = output + F("\"Watthour\": \"") + (actS0Data[i].pulses * ( 1000.0 / actS0Settings[i].ppkwh)) + F("\","); - output = output + F("\"WatthourTotal\": \"") + (actS0Data[i].pulsesTotal * ( 1000.0 / actS0Settings[i].ppkwh)) + F("\""); - output = output + F("}"); - if (i < NUM_S0_COUNTERS - 1) output = output + F(","); + webserver_send_content_P(client, PSTR("{\"S0 port\":\""), 12); + + char str[12]; + itoa(i + 1, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR("\",\"Watt\":\""), 10); + + itoa(actS0Data[i].watt, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR("\",\"Watthour\":\""), 14); + + itoa(((actS0Data[i].pulsesTotal - tablePulses[i]) * (1000.0 / actS0Settings[i].ppkwh)), str, 10); + webserver_send_content(client, str, strlen(str)); + + jsonPulses[i] = actS0Data[i].pulsesTotal; + + webserver_send_content_P(client, PSTR("\",\"WatthourTotal\":\""), 19); + + itoa((actS0Data[i].pulsesTotal * (1000.0 / actS0Settings[i].ppkwh)), str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR("\",\"PulseQuality\":\""), 18); + + itoa((100 * (actS0Data[i].goodPulses + 1) / (actS0Data[i].goodPulses + actS0Data[i].badPulses + 1)), str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR("\",\"AvgPulseWidth\":\""), 19); + + itoa(actS0Data[i].avgPulseWidth, str, 10); + webserver_send_content(client, str, strlen(str)); + + if (i < NUM_S0_COUNTERS - 1) { + webserver_send_content_P(client, PSTR("\"},"), 3); + } else { + webserver_send_content_P(client, PSTR("\"}"), 2); + } } - output = output + F("]"); - return output; + webserver_send_content_P(client, PSTR("]"), 1); } diff --git a/HeishaMon/s0.h b/HeishaMon/s0.h index 11f148cb..3292e3a8 100644 --- a/HeishaMon/s0.h +++ b/HeishaMon/s0.h @@ -1,4 +1,5 @@ #include +#include "src/common/webserver.h" #define NUM_S0_COUNTERS 2 #define DEFAULT_S0_PIN_1 12 // S0_1 pin, for now a static config - should be in config menu later @@ -8,6 +9,8 @@ struct s0SettingsStruct { byte gpiopin = 255; unsigned int ppkwh = 1000; //pulses per Wh of the connected meter unsigned int lowerPowerInterval = 60; //configurabel low power interval + unsigned int minimalPulseWidth = 25; //configurabel minimal s0 pulse width + unsigned int maximalPulseWidth = 100; //configurabel maximal s0 pulse width }; struct s0DataStruct { @@ -16,10 +19,13 @@ struct s0DataStruct { unsigned int watt = 0; //calculated average power unsigned long lastPulse = 0; //last pulse in millis unsigned long nextReport = 0; //next time we reported the s0 value in millis + unsigned long goodPulses = 0; + unsigned long badPulses = 0; + unsigned int avgPulseWidth = 0; }; void initS0Sensors(s0SettingsStruct s0Settings[]); void restore_s0_Watthour(int s0Port, float watthour); void s0Loop(PubSubClient &mqtt_client, void (*log_message)(char*), char* mqtt_topic_base, s0SettingsStruct s0Settings[]); -String s0TableOutput(void); -String s0JsonOutput(void); +void s0TableOutput(struct webserver_t *client); +void s0JsonOutput(struct webserver_t *client); diff --git a/HeishaMon/smartcontrol.cpp b/HeishaMon/smartcontrol.cpp deleted file mode 100644 index a71cc7ce..00000000 --- a/HeishaMon/smartcontrol.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include "commands.h" -#include "smartcontrol.h" - -unsigned long heatCurveTimer = 0; -bool heatCurveFirst = true; -short avgOutsideTemp = 0; -short avgOutsideTempArray[96] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - -// returns the calculated average outside temperature -String getAvgOutsideTemp() { - char avgOutsideTempStr[55]; - sprintf_P(avgOutsideTempStr, PSTR("Current calculated average outside temperature: %dC."), avgOutsideTemp); - return String(avgOutsideTempStr); -} - -bool send_command(byte* command, int length); - -void smartControlLoop(void (*log_message)(char*), SmartControlSettingsStruct SmartControlSettings, String actData[], unsigned long goodreads) { - if (goodreads > 0) { - char log_msg[256]; - if ((millis() - heatCurveTimer > (1000 * 1800)) || heatCurveFirst) { //every 0.5h - log_message((char*)"Calculate new outside temperature average"); - heatCurveTimer = millis(); - - short currentOutsideTemp = actData[14].toInt(); - if (heatCurveFirst) { - log_message((char*)"Fill average outside temperature array"); - heatCurveFirst = false; - for (unsigned int i = 0 ; i < 96 ; i++) { - avgOutsideTempArray[i] = currentOutsideTemp; - } - } else { - log_message((char*)"Add current outside temperature to average array"); - for (unsigned int i = 95 ; i > 0 ; i--) { - avgOutsideTempArray[i] = avgOutsideTempArray[i - 1]; - } - avgOutsideTempArray[0] = currentOutsideTemp; - } - - long outsideTempSum = 0; - for (unsigned int i = 0 ; i < ((SmartControlSettings.avgHourHeatCurve * 2) + 1); i++) { - outsideTempSum = outsideTempSum + avgOutsideTempArray[i]; - } - - avgOutsideTemp = int(outsideTempSum / (SmartControlSettings.avgHourHeatCurve * 2)); - sprintf_P(log_msg, PSTR("Current calculated average outside temperature: %dC."), avgOutsideTemp); - log_message(log_msg); - - log_message((char*)"Send new heat request temperature setpoint"); - short heatRequest = int(SmartControlSettings.heatCurveLookup[35]); - if (avgOutsideTemp > 15) { - heatRequest = int(SmartControlSettings.heatCurveLookup[35]); - } else if (avgOutsideTemp < -20) { - heatRequest = int(SmartControlSettings.heatCurveLookup[0]); - } else { - heatRequest = int(SmartControlSettings.heatCurveLookup[(avgOutsideTemp + 20)]); - } - sprintf(log_msg, "Current heat request temperature: %dC.", heatRequest); log_message(log_msg); - - log_msg[0] = 0; - unsigned char cmd[256] = { 0 }; - char msg[3]; - unsigned int len = 0; - sprintf(msg, "%d", heatRequest); - len = set_z1_heat_request_temperature(msg, cmd, log_msg); - log_message(log_msg); - send_command(cmd, len); - } - } -} diff --git a/HeishaMon/smartcontrol.h b/HeishaMon/smartcontrol.h deleted file mode 100644 index c1f04388..00000000 --- a/HeishaMon/smartcontrol.h +++ /dev/null @@ -1,15 +0,0 @@ -struct SmartControlSettingsStruct { - bool enableHeatCurve = false; //Enable or dissable heating curve control from Heishamon - - short avgHourHeatCurve = 0; // Outside temperature average of hours for heating curve control - short heatCurveTargetHigh = 60; // Heating curve target high temperature - short heatCurveTargetLow = 20; // Heating curve target low temperature - short heatCurveOutHigh = 15; // Heating curve outside high temperature - short heatCurveOutLow = -20; // Heating curve outside low temperature - - short heatCurveLookup[36] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // Lookup table for heating curve -}; - -String getAvgOutsideTemp(void); - -void smartControlLoop(void (*log_message)(char*), SmartControlSettingsStruct SmartControlSettings, String actData[], unsigned long goodreads); diff --git a/HeishaMon/src/common/base64.cpp b/HeishaMon/src/common/base64.cpp new file mode 100755 index 00000000..eec7d986 --- /dev/null +++ b/HeishaMon/src/common/base64.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2003 Apple Computer, Inc. All rights reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved. + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ +/* ==================================================================== + * Copyright (c) 1995-1999 The Apache Group. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. All advertising materials mentioning features or use of this + * software must display the following acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * 4. The names "Apache Server" and "Apache Group" must not be used to + * endorse or promote products derived from this software without + * prior written permission. For written permission, please contact + * apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * 6. Redistributions of any form whatsoever must retain the following + * acknowledgment: + * "This product includes software developed by the Apache Group + * for use in the Apache HTTP server project (http://www.apache.org/)." + * + * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY + * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Group and was originally based + * on public domain software written at the National Center for + * Supercomputing Applications, University of Illinois, Urbana-Champaign. + * For more information on the Apache Group and the Apache HTTP server + * project, please see . + * + */ + +/* Base64 encoder/decoder. Originally Apache file ap_base64.c + */ + +#include + +#include "base64.h" + +/* aaaack but it's fast and const should make it shared text page. */ +static const unsigned char pr2six[256] = +{ + /* ASCII table */ + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, + 64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 64, 64, 64, 64, 64, + 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64 +}; + +int Base64decode_len(const char *bufcoded) +{ + int nbytesdecoded; + register const unsigned char *bufin; + register int nprbytes; + + bufin = (const unsigned char *) bufcoded; + while (pr2six[*(bufin++)] <= 63); + + nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; + nbytesdecoded = ((nprbytes + 3) / 4) * 3; + + return nbytesdecoded + 1; +} + +int Base64decode(char *bufplain, const char *bufcoded) +{ + int nbytesdecoded; + register const unsigned char *bufin; + register unsigned char *bufout; + register int nprbytes; + + bufin = (const unsigned char *) bufcoded; + while (pr2six[*(bufin++)] <= 63); + nprbytes = (bufin - (const unsigned char *) bufcoded) - 1; + nbytesdecoded = ((nprbytes + 3) / 4) * 3; + + bufout = (unsigned char *) bufplain; + bufin = (const unsigned char *) bufcoded; + + while (nprbytes > 4) { + *(bufout++) = + (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); + *(bufout++) = + (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); + *(bufout++) = + (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); + bufin += 4; + nprbytes -= 4; + } + + /* Note: (nprbytes == 1) would be an error, so just ingore that case */ + if (nprbytes > 1) { + *(bufout++) = + (unsigned char) (pr2six[*bufin] << 2 | pr2six[bufin[1]] >> 4); + } + if (nprbytes > 2) { + *(bufout++) = + (unsigned char) (pr2six[bufin[1]] << 4 | pr2six[bufin[2]] >> 2); + } + if (nprbytes > 3) { + *(bufout++) = + (unsigned char) (pr2six[bufin[2]] << 6 | pr2six[bufin[3]]); + } + + *(bufout++) = '\0'; + nbytesdecoded -= (4 - nprbytes) & 3; + return nbytesdecoded; +} + +static const char basis_64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +int Base64encode_len(int len) +{ + return ((len + 2) / 3 * 4) + 1; +} + +int Base64encode(char *encoded, const char *string, int len) +{ + int i; + char *p; + + p = encoded; + for (i = 0; i < len - 2; i += 3) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int) (string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2) | + ((int) (string[i + 2] & 0xC0) >> 6)]; + *p++ = basis_64[string[i + 2] & 0x3F]; + } + if (i < len) { + *p++ = basis_64[(string[i] >> 2) & 0x3F]; + if (i == (len - 1)) { + *p++ = basis_64[((string[i] & 0x3) << 4)]; + *p++ = '='; + } + else { + *p++ = basis_64[((string[i] & 0x3) << 4) | + ((int) (string[i + 1] & 0xF0) >> 4)]; + *p++ = basis_64[((string[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } + + *p++ = '\0'; + return p - encoded; +} diff --git a/HeishaMon/src/common/base64.h b/HeishaMon/src/common/base64.h new file mode 100755 index 00000000..391a3621 --- /dev/null +++ b/HeishaMon/src/common/base64.h @@ -0,0 +1,13 @@ +/* + * Copyright (c) 2013 Adam Rudd. + * See LICENSE for more information + */ +#ifndef _BASE64_H +#define _BASE64_H + +int Base64decode_len(const char *bufcoded); +int Base64decode(char *bufplain, const char *bufcoded); +int Base64encode_len(int len); +int Base64encode(char *encoded, const char *string, int len); + +#endif // _BASE64_H diff --git a/HeishaMon/src/common/log.cpp b/HeishaMon/src/common/log.cpp new file mode 100755 index 00000000..e6c9912d --- /dev/null +++ b/HeishaMon/src/common/log.cpp @@ -0,0 +1,96 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +#include + +#include "mem.h" +#include "../../webfunctions.h" + +extern settingsStruct heishamonSettings; +extern PubSubClient mqtt_client; +extern const char* mqtt_logtopic; + +void _logprintln(const char *file, unsigned int line, char *msg) { + if(heishamonSettings.logSerial1) { + Serial1.print(millis()); + Serial1.print(": "); + Serial1.println(msg); + } + websocket_write_all(msg, strlen(msg)); +} + +void _logprintf(const char *file, unsigned int line, char *fmt, ...) { + char *str = NULL; + + va_list ap, apcpy; + va_copy(apcpy, ap); + va_start(apcpy, fmt); + + int bytes = vsnprintf(NULL, 0, fmt, apcpy); + + va_end(apcpy); + if((str = (char *)MALLOC(bytes+1)) == NULL) { + OUT_OF_MEMORY + } + va_start(ap, fmt); + vsprintf(str, fmt, ap); + va_end(ap); + + _logprintln(file, line, str); + + FREE(str); +} + +void _logprintln_P(const char *file, unsigned int line, const __FlashStringHelper *msg) { + PGM_P p = (PGM_P)msg; + int len = strlen_P((const char *)p); + char *str = (char *)MALLOC(len+1); + if(str == NULL) { + OUT_OF_MEMORY + } + strcpy_P(str, p); + + _logprintln(file, line, str); + + FREE(str); +} + +void _logprintf_P(const char *file, unsigned int line, const __FlashStringHelper *fmt, ...) { + PGM_P p = (PGM_P)fmt; + int len = strlen_P((const char *)p); + char *foo = (char *)MALLOC(len+1); + if(foo == NULL) { + OUT_OF_MEMORY + } + strcpy_P(foo, p); + + char *str = NULL; + + va_list ap, apcpy; + va_copy(apcpy, ap); + va_start(apcpy, fmt); + + int bytes = vsnprintf(NULL, 0, foo, apcpy); + + va_end(apcpy); + if((str = (char *)MALLOC(bytes+1)) == NULL) { + OUT_OF_MEMORY + } + va_start(ap, fmt); + vsprintf(str, foo, ap); + va_end(ap); + + _logprintln(file, line, str); + + FREE(foo); + FREE(str); +} \ No newline at end of file diff --git a/HeishaMon/src/common/log.h b/HeishaMon/src/common/log.h new file mode 100755 index 00000000..35fd8b95 --- /dev/null +++ b/HeishaMon/src/common/log.h @@ -0,0 +1,24 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _LOG_H_ +#define _LOG_H_ + +#include + +#define logprintln(a) _logprintln(__FILE__, __LINE__, a) +#define logprintf(a, ...) _logprintf(__FILE__, __LINE__, a, ##__VA_ARGS__) +#define logprintln_P(a) _logprintln_P(__FILE__, __LINE__, a) +#define logprintf_P(a, ...) _logprintf_P(__FILE__, __LINE__, a, ##__VA_ARGS__) + +void _logprintln(const char *file, unsigned int line, char *msg); +void _logprintf(const char *file, unsigned int line, char *fmt, ...); +void _logprintln_P(const char *file, unsigned int line, const __FlashStringHelper *msg); +void _logprintf_P(const char *file, unsigned int line, const __FlashStringHelper *fmt, ...); + +#endif diff --git a/HeishaMon/src/common/mem.cpp b/HeishaMon/src/common/mem.cpp new file mode 100755 index 00000000..305f3c4b --- /dev/null +++ b/HeishaMon/src/common/mem.cpp @@ -0,0 +1,19 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +unsigned int alignedbytes(int v) { + return v; +} + +unsigned int alignedbuffer(int v) { +#ifdef ESP8266 + return (v + 3) & ~0x3; +#else + return v; +#endif +} diff --git a/HeishaMon/src/common/mem.h b/HeishaMon/src/common/mem.h new file mode 100755 index 00000000..1540b431 --- /dev/null +++ b/HeishaMon/src/common/mem.h @@ -0,0 +1,23 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _MEM_H_ +#define _MEM_H_ + +unsigned int alignedbytes(int v); +unsigned int alignedbuffer(int v); + +#define OUT_OF_MEMORY while(0) { } + +#define STRDUP strdup +#define REALLOC realloc +#define CALLOC calloc +#define MALLOC malloc +#define FREE(a) do { free(a); (a) = NULL; } while(0) + +#endif diff --git a/HeishaMon/src/common/sha1.cpp b/HeishaMon/src/common/sha1.cpp new file mode 100755 index 00000000..70eb7040 --- /dev/null +++ b/HeishaMon/src/common/sha1.cpp @@ -0,0 +1,201 @@ +/******************************************************************************* + * Teeny SHA-1 + * + * The below sha1digest() calculates a SHA-1 hash value for a + * specified data buffer and generates a hex representation of the + * result. This implementation is a re-forming of the SHA-1 code at + * https://github.com/jinqiangshou/EncryptionLibrary. + * + * Copyright (c) 2017 CTrabant + * + * License: MIT, see included LICENSE file for details. + * + * To use the sha1digest() function either copy it into an existing + * project source code file or include this file in a project and put + * the declaration (example below) in the sources files where needed. + ******************************************************************************/ + +#include +#include +#include +#include + +/* Declaration: +extern int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes); +*/ + +/******************************************************************************* + * sha1digest: https://github.com/CTrabant/teeny-sha1 + * + * Calculate the SHA-1 value for supplied data buffer and generate a + * text representation in hexadecimal. + * + * Based on https://github.com/jinqiangshou/EncryptionLibrary, credit + * goes to @jinqiangshou, all new bugs are mine. + * + * @input: + * data -- data to be hashed + * databytes -- bytes in data buffer to be hashed + * + * @output: + * digest -- the result, MUST be at least 20 bytes + * hexdigest -- the result in hex, MUST be at least 41 bytes + * + * At least one of the output buffers must be supplied. The other, if not + * desired, may be set to NULL. + * + * @return: 0 on success and non-zero on error. + ******************************************************************************/ +int +sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes) +{ +#define SHA1ROTATELEFT(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + + uint32_t W[80]; + uint32_t H[] = {0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0}; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint32_t f = 0; + uint32_t k = 0; + + uint32_t idx; + uint32_t lidx; + uint32_t widx; + uint32_t didx = 0; + + int32_t wcount; + uint32_t temp; + uint64_t databits = ((uint64_t)databytes) * 8; + uint32_t loopcount = (databytes + 8) / 64 + 1; + uint32_t tailbytes = 64 * loopcount - databytes; + uint8_t datatail[128] = {0}; + + if (!digest && !hexdigest) + return -1; + + if (!data) + return -1; + + /* Pre-processing of data tail (includes padding to fill out 512-bit chunk): + Add bit '1' to end of message (big-endian) + Add 64-bit message length in bits at very end (big-endian) */ + datatail[0] = 0x80; + datatail[tailbytes - 8] = (uint8_t) (databits >> 56 & 0xFF); + datatail[tailbytes - 7] = (uint8_t) (databits >> 48 & 0xFF); + datatail[tailbytes - 6] = (uint8_t) (databits >> 40 & 0xFF); + datatail[tailbytes - 5] = (uint8_t) (databits >> 32 & 0xFF); + datatail[tailbytes - 4] = (uint8_t) (databits >> 24 & 0xFF); + datatail[tailbytes - 3] = (uint8_t) (databits >> 16 & 0xFF); + datatail[tailbytes - 2] = (uint8_t) (databits >> 8 & 0xFF); + datatail[tailbytes - 1] = (uint8_t) (databits >> 0 & 0xFF); + + /* Process each 512-bit chunk */ + for (lidx = 0; lidx < loopcount; lidx++) + { + /* Compute all elements in W */ + memset (W, 0, 80 * sizeof (uint32_t)); + + /* Break 512-bit chunk into sixteen 32-bit, big endian words */ + for (widx = 0; widx <= 15; widx++) + { + wcount = 24; + + /* Copy byte-per byte from specified buffer */ + while (didx < databytes && wcount >= 0) + { + W[widx] += (((uint32_t)data[didx]) << wcount); + didx++; + wcount -= 8; + } + /* Fill out W with padding as needed */ + while (wcount >= 0) + { + W[widx] += (((uint32_t)datatail[didx - databytes]) << wcount); + didx++; + wcount -= 8; + } + } + + /* Extend the sixteen 32-bit words into eighty 32-bit words, with potential optimization from: + "Improving the Performance of the Secure Hash Algorithm (SHA-1)" by Max Locktyukhin */ + for (widx = 16; widx <= 31; widx++) + { + W[widx] = SHA1ROTATELEFT ((W[widx - 3] ^ W[widx - 8] ^ W[widx - 14] ^ W[widx - 16]), 1); + } + for (widx = 32; widx <= 79; widx++) + { + W[widx] = SHA1ROTATELEFT ((W[widx - 6] ^ W[widx - 16] ^ W[widx - 28] ^ W[widx - 32]), 2); + } + + /* Main loop */ + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + + for (idx = 0; idx <= 79; idx++) + { + if (idx <= 19) + { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } + else if (idx >= 20 && idx <= 39) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (idx >= 40 && idx <= 59) + { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else if (idx >= 60 && idx <= 79) + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + temp = SHA1ROTATELEFT (a, 5) + f + e + k + W[idx]; + e = d; + d = c; + c = SHA1ROTATELEFT (b, 30); + b = a; + a = temp; + } + + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + } + + /* Store binary digest in supplied buffer */ + if (digest) + { + for (idx = 0; idx < 5; idx++) + { + digest[idx * 4 + 0] = (uint8_t) (H[idx] >> 24); + digest[idx * 4 + 1] = (uint8_t) (H[idx] >> 16); + digest[idx * 4 + 2] = (uint8_t) (H[idx] >> 8); + digest[idx * 4 + 3] = (uint8_t) (H[idx]); + } + } + + /* Store hex version of digest in supplied buffer */ + if (hexdigest) + { + snprintf (hexdigest, 41, "%08x%08x%08x%08x%08x", + H[0],H[1],H[2],H[3],H[4]); + } + + return 0; +} /* End of sha1digest() */ diff --git a/HeishaMon/src/common/sha1.h b/HeishaMon/src/common/sha1.h new file mode 100755 index 00000000..227b46c8 --- /dev/null +++ b/HeishaMon/src/common/sha1.h @@ -0,0 +1,18 @@ +/******************************************************************************* + * Teeny SHA-1 + * + * The below sha1digest() calculates a SHA-1 hash value for a + * specified data buffer and generates a hex representation of the + * result. This implementation is a re-forming of the SHA-1 code at + * https://github.com/jinqiangshou/EncryptionLibrary. + * + * Copyright (c) 2017 CTrabant + * + * License: MIT, see included LICENSE file for details. + * + * To use the sha1digest() function either copy it into an existing + * project source code file or include this file in a project and put + * the declaration (example below) in the sources files where needed. + ******************************************************************************/ + +int sha1digest(uint8_t *digest, char *hexdigest, const uint8_t *data, size_t databytes); \ No newline at end of file diff --git a/HeishaMon/src/common/stricmp.cpp b/HeishaMon/src/common/stricmp.cpp new file mode 100755 index 00000000..bc96e26b --- /dev/null +++ b/HeishaMon/src/common/stricmp.cpp @@ -0,0 +1,24 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include + +int stricmp(char const *a, char const *b) { + if(a == NULL || b == NULL) { + return -1; + } + + for(;a; a++, b++) { + int d = tolower(*a) - tolower(*b); + if(d != 0 || !*a) { + return d; + } + } + return -1; +} diff --git a/HeishaMon/src/common/stricmp.h b/HeishaMon/src/common/stricmp.h new file mode 100755 index 00000000..ed917f9f --- /dev/null +++ b/HeishaMon/src/common/stricmp.h @@ -0,0 +1,14 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef STRICMP +#define STRICMP + +int stricmp(char const *a, char const *b); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/common/strncasestr.cpp b/HeishaMon/src/common/strncasestr.cpp new file mode 100755 index 00000000..7f744120 --- /dev/null +++ b/HeishaMon/src/common/strncasestr.cpp @@ -0,0 +1,40 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include +#include + +unsigned char *strncasestr(unsigned char *str1, const char *str2, uint16_t size) { + uint16_t a = 0, b = 0, c = 0; + uint16_t len = strlen(str2); + for(a=0;a= 65 && ch <= 90) { + ch += 32; + } + if(ch == str2[0] && a+len <= size) { + c = a; + a++; + for(b=1;b<=len;a++, b++) { + ch = str1[a]; + if(ch >= 65 && ch <= 90) { + ch += 32; + } + if(str2[b] != ch) { + break; + } + } + if(b == len) { + return &str1[a-len]; + } + a = c; + } + } + return NULL; +} diff --git a/HeishaMon/src/common/strncasestr.h b/HeishaMon/src/common/strncasestr.h new file mode 100755 index 00000000..97fc630e --- /dev/null +++ b/HeishaMon/src/common/strncasestr.h @@ -0,0 +1,21 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef STRNCASESTR +#define STRNCASESTR + +#include +#include + +/* + * Find the first occurrence of find in s, where the search is limited to the + * first slen characters of s. + */ +unsigned char *strncasestr(unsigned char *str1, const char *str2, uint16_t len); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/common/strnicmp.cpp b/HeishaMon/src/common/strnicmp.cpp new file mode 100755 index 00000000..b6d82572 --- /dev/null +++ b/HeishaMon/src/common/strnicmp.cpp @@ -0,0 +1,29 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#include +#include + +int strnicmp(char const *a, char const *b, size_t len) { + unsigned int i = 0; + + if(a == NULL || b == NULL) { + return -1; + } + if(len == 0) { + return 0; + } + + for(;i++ +#include +#include + +unsigned char *strnstr(unsigned char *str1, const char *str2, uint16_t size) { + uint16_t a = 0, b = 0, c = 0; + uint16_t len = strlen(str2); + for(a=0;a +#include + +/* + * Find the first occurrence of find in s, where the search is limited to the + * first slen characters of s. + */ +unsigned char *strnstr(unsigned char *str1, const char *str2, uint16_t len); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/common/timerqueue.cpp b/HeishaMon/src/common/timerqueue.cpp new file mode 100644 index 00000000..0ba39352 --- /dev/null +++ b/HeishaMon/src/common/timerqueue.cpp @@ -0,0 +1,233 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #include +#endif + +#include +#include +#include +#include +#include +#include + +#include "mem.h" +#include "timerqueue.h" + +static unsigned int lasttime = 0; +static unsigned int *calls = NULL; +static unsigned int nrcalls = 0; + +#ifndef ESP8266 +static unsigned int micros() { + struct timeval tv; + gettimeofday(&tv,NULL); + + return 1000000 * tv.tv_sec + tv.tv_usec;; +} +#endif + +static void timerqueue_sort() { + int matched = 1; + while(matched) { + int a = 0; + matched = 0; + for(a=0;aremove < timerqueue[a+1]->remove || + (timerqueue[a]->remove == timerqueue[a+1]->remove && timerqueue[a]->sec > timerqueue[a+1]->sec) || + (timerqueue[a]->remove == timerqueue[a+1]->remove && timerqueue[a]->sec == timerqueue[a+1]->sec && timerqueue[a]->usec > timerqueue[a+1]->usec)) { + struct timerqueue_t *node = timerqueue[a+1]; + timerqueue[a+1] = timerqueue[a]; + timerqueue[a] = node; + matched = 1; + break; + } + } + } +} + +struct timerqueue_t *timerqueue_pop() { + if(timerqueue_size == 0) { + return NULL; + } + struct timerqueue_t *x = timerqueue[0]; + timerqueue[0] = timerqueue[timerqueue_size-1]; + + timerqueue_size--; + + if(timerqueue_size == 0) { + free(timerqueue); + timerqueue = NULL; + } else { + if((timerqueue = (struct timerqueue_t **)realloc(timerqueue, sizeof(struct timerqueue_t *)*timerqueue_size)) == NULL) { + #ifdef ESP8266 + Serial.printf("Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + #else + fprintf(stderr, "Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + exit(-1); + #endif + } + } + + int a = 0; + for(a=0;asec -= x->sec; + timerqueue[a]->usec -= x->usec; + if(timerqueue[a]->usec < 0) { + timerqueue[a]->sec -= 1; + timerqueue[a]->usec += 1000000; + } + } + + timerqueue_sort(); + + return x; +} + +struct timerqueue_t *timerqueue_peek() { + if(timerqueue_size == 0) { + return NULL; + } + return timerqueue[0]; +} + +void timerqueue_insert(int sec, int usec, int nr) { + struct timerqueue_t *node = NULL; + int a = 0, matched = 0, x = 0, y = 0; + + for(a=0;anr == nr) { + timerqueue[a]->sec = sec; + timerqueue[a]->usec = usec; + if(sec <= 0 && usec <= 0) { + timerqueue[a]->remove = 1; + for(x=0;xnr) { + if(nrcalls > 0) { + for(y=x;yremove == 1) { + struct timerqueue_t *node = timerqueue_pop(); + free(node); + } else { + break; + } + } + + return; + } else if(sec == 0 && usec == 0) { + return; + } + + if((timerqueue = (struct timerqueue_t **)realloc(timerqueue, sizeof(struct timerqueue_t *)*(timerqueue_size+1))) == NULL) { +#ifdef ESP8266 + Serial.printf("Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); +#else + fprintf(stderr, "Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + exit(-1); +#endif + } + + node = (struct timerqueue_t *)malloc(sizeof(struct timerqueue_t)); + if(node == NULL) { +#ifdef ESP8266 + Serial.printf("Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); +#else + fprintf(stderr, "Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + exit(-1); +#endif + } + memset(node, 0, sizeof(struct timerqueue_t)); + node->sec = sec; + node->usec = usec; + node->nr = nr; + + timerqueue[timerqueue_size++] = node; + timerqueue_sort(); +} + +void timerqueue_update(void) { + struct timeval tv; + unsigned int curtime = 0; + + curtime = micros(); + + unsigned int diff = curtime - lasttime; + unsigned int sec = diff / 1000000; + unsigned int usec = diff - ((diff / 1000000) * 1000000); + int a = 0, x = 0; + + lasttime = curtime; + + for(a=0;asec -= sec; + timerqueue[a]->usec -= usec; + if(timerqueue[a]->usec < 0) { + timerqueue[a]->usec = 1000000 + timerqueue[a]->usec; + timerqueue[a]->sec -= 1; + } + + if(timerqueue[a]->sec < 0 || (timerqueue[a]->sec == 0 && timerqueue[a]->usec <= 0)) { + int nr = timerqueue[a]->nr; + if((calls = (unsigned int *)realloc(calls, (nrcalls+1)*sizeof(int))) == NULL) { +#ifdef ESP8266 + Serial.printf("Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); +#else + fprintf(stderr, "Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + exit(-1); +#endif + } + calls[nrcalls++] = nr; + } + } + for(a=0;asec < 0 || (timerqueue[a]->sec == 0 && timerqueue[a]->usec == 0)) { + struct timerqueue_t *node = timerqueue_pop(); + free(node); + a--; + } + } + while(nrcalls > 0) { + timer_cb(calls[0]); + if(nrcalls > 0) { + for(a=0;a + +typedef struct timerqueue_t { + int sec; + int usec; + int nr; + int remove; +} timerqueue_t; + +extern struct timerqueue_t **timerqueue; +extern int timerqueue_size; +extern void timer_cb(int nr); + +struct timerqueue_t *timerqueue_pop(); +struct timerqueue_t *timerqueue_peek(); +void timerqueue_update(void); +void timerqueue_insert(int sec, int usec, int nr); + + +#endif \ No newline at end of file diff --git a/HeishaMon/src/common/webserver.cpp b/HeishaMon/src/common/webserver.cpp new file mode 100755 index 00000000..30ac94a5 --- /dev/null +++ b/HeishaMon/src/common/webserver.cpp @@ -0,0 +1,2464 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef __linux__ + #pragma GCC diagnostic ignored "-Wwrite-strings" + + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + + #include "webserver.h" + #include "strncasestr.h" + #include "strnstr.h" + #include "unittest.h" + #include "base64.h" + #include "sha1.h" +#else + #define LWIP_INTERNAL + + #include + #include + #include + + #define LWIP_SO_RCVBUF 1 + + #include "strncasestr.h" + #include "strnstr.h" + + #include "lwip/opt.h" + #include "lwip/tcp.h" + #include "lwip/inet.h" + #include "lwip/dns.h" + #include "lwip/init.h" + #include "lwip/errno.h" + + #include "webserver.h" + #include "base64.h" + #include "sha1.h" + + #include +#endif + +#define MIN(a,b) (((a)<(b))?(a):(b)) +#ifndef ERR_OK + #define ERR_OK 0 +#endif + +void log_message(char *string); +void log_message(const __FlashStringHelper *msg); + +struct webserver_client_t clients[WEBSERVER_MAX_CLIENTS]; +#ifdef ESP8266 +static tcp_pcb *async_server = NULL; +static WiFiServer sync_server(0); +#endif +static uint8_t *rbuffer = NULL; + +#if defined(ESP8266) +static uint16_t tcp_write_P(tcp_pcb *pcb, PGM_P buf, uint16_t len, uint8_t flags) { + char *str = (char *)malloc(len+1); + if(str == NULL) { + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + } + memset(str, 0, len+1); + strncpy_P(str, buf, len); + uint16_t ret = tcp_write(pcb, str, len, flags); + free(str); + return ret; +} +#endif + +int16_t urldecode(const unsigned char *src, int src_len, unsigned char *dst, int dst_len, int is_form_url_encoded) { + int i, j, a, b; + +#define HEXTOI(x) (isdigit(x) ? x - '0' : x - 'W') + + for(i = j = 0; i < src_len && j < dst_len - 1; i++, j++) { + if(src[i] == '%' && i < src_len - 2 && + isxdigit(*(const unsigned char *)(src + i + 1)) && + isxdigit(*(const unsigned char *)(src + i + 2))) { + a = tolower(*(const unsigned char *)(src + i + 1)); + b = tolower(*(const unsigned char *)(src + i + 2)); + dst[j] = (char)((HEXTOI(a) << 4) | HEXTOI(b)); + i += 2; + } else if(is_form_url_encoded && src[i] == '+') { + dst[j] = ' '; + } else { + dst[j] = src[i]; + } + } + + dst[j] = '\0'; // Null-terminate the destination + + return i >= src_len ? j : -1; +} + +static int webserver_parse_post(struct webserver_t *client, uint16_t size) { + struct arguments_t args; + unsigned char *ptrA = (unsigned char *)memchr(client->buffer, '=', size); + unsigned char *ptrB = (unsigned char *)memchr(client->buffer, ' ', size); + unsigned char *ptrC = (unsigned char *)memchr(client->buffer, '&', size); + unsigned char *ptrD = ptrA; + unsigned char *ptrE = ptrC; + char c = '='; + int16_t pos = 0; + int16_t posA = WEBSERVER_BUFFER_SIZE+1, posB = WEBSERVER_BUFFER_SIZE+1, posC = WEBSERVER_BUFFER_SIZE+1; + + if(ptrA != NULL) { + posA = ptrA - client->buffer; + } + if(ptrB != NULL) { + posB = ptrB - client->buffer; + } + if(ptrC != NULL) { + posC = ptrC - client->buffer; + } + + pos = MIN(posA, MIN(posB, posC)); + + if(ptrA != NULL || ptrB != NULL || ptrC != NULL) { + if(posB == pos) { + c = ' '; + ptrA = ptrB; + } + if(posC == pos) { + c = '&'; + ptrA = ptrC; + } + + /* + * & delimiter + */ + + unsigned char *ptr1 = (unsigned char *)memchr(&client->buffer[pos+1], '&', size-(pos+1)); + if(ptr1 != NULL) { + uint16_t pos1 = ptr1-client->buffer; + + int16_t pos2 = urldecode(client->buffer, + pos + 1, + client->buffer, + pos + 1, 1); + + if(pos2 > -1) { + client->buffer[pos2 - 1] = 0; + } else { + client->buffer[pos] = 0; + } + + int16_t pos3 = urldecode(&client->buffer[pos+1], + ((pos1-1)-pos) + 1, + &client->buffer[pos+1], + ((pos1-1)-pos) + 1, 1); + + if(pos3 > -1) { + client->buffer[pos + 1 + pos3] = 0; + } else { + client->buffer[pos1] = 0; + } + + args.name = &client->buffer[0]; + args.value = &client->buffer[pos+1]; + args.len = (pos1-1)-pos; + if(pos3 > -1) { + args.len = pos3 - 1; + } + + if(client->callback != NULL) { + if(client->callback(client, &args) == -1) { + return -1; /*LCOV_EXCL_LINE*/ + } + } + + if(pos2 > -1) { + client->buffer[pos2-1] = c; + client->buffer[pos] = ' '; + } else { + client->buffer[pos] = c; + } + if(pos3 > -1) { + client->buffer[pos + 1 + pos3] = '&'; + client->buffer[pos1] = ' '; + } else { + client->buffer[pos1] = '&'; + } + + memmove(&client->buffer[0], &client->buffer[pos1+1], size-(pos1+1)); + client->ptr = size-(pos1+1); + client->buffer[client->ptr] = 0; + + return 1; + } + + if(client->readlen + size == client->totallen) { + int16_t pos2 = urldecode(client->buffer, + pos + 1, + client->buffer, + pos + 1, 1); + + if(pos2 > -1) { + client->buffer[pos2 - 1] = 0; + } else { + client->buffer[pos] = 0; + } + + int16_t pos3 = urldecode(&client->buffer[pos+1], + (size - (pos + 1)) + 1, + &client->buffer[pos+1], + (size - (pos + 1)) + 1, 1); + + if(pos3 > -1) { + client->buffer[pos + 1 + pos3] = 0; + } else { + client->buffer[size] = 0; + } + + args.name = &client->buffer[0]; + args.value = &client->buffer[pos+1]; + args.len = size - (pos + 1); + + if(pos3 > -1) { + args.len = pos3 - 1; + } + + if(client->callback != NULL) { + if(client->callback(client, &args) == -1) { + return -1; /*LCOV_EXCL_LINE*/ + } + } + + memmove(&client->buffer[0], &client->buffer[size], size); + client->ptr = 0; + client->buffer[client->ptr] = 0; + + return 0; + } + + ptr1 = (unsigned char *)memrchr(client->buffer, '%', size); + if(ptr1 != NULL) { + uint16_t pos1 = ptr1 - client->buffer; + /* + * A encoded character always start with a + * percentage mark followed by two numbers. + * To properly decode an url we need to + * keep those together. + */ + if(pos1+2 >= WEBSERVER_BUFFER_SIZE) { + int16_t pos2 = urldecode(client->buffer, + pos + 1, + client->buffer, + pos + 1, 1); + + if(pos2 > -1) { + client->buffer[pos2 - 1] = 0; + } else { + client->buffer[pos] = 0; + } + + int16_t pos3 = urldecode(&client->buffer[pos+1], + (pos1 - (pos + 1)) + 1, + &client->buffer[pos+1], + (pos1 - (pos + 1)) + 1, 1); + + client->buffer[pos1] = 0; + + args.name = &client->buffer[0]; + args.value = &client->buffer[pos+1]; + args.len = (pos1 - (pos + 1)) + 1; + + if(pos3 > -1) { + args.len = pos3 - 1; + } + + if(client->callback != NULL) { + if(client->callback(client, &args) == -1) { + return -1; /*LCOV_EXCL_LINE*/ + } + } + + client->buffer[pos1] = '%'; + + if(pos2 > -1) { + client->buffer[pos2-1] = c; + client->buffer[pos] = ' '; + pos = pos2; + } else { + client->buffer[pos] = c; + } + + memmove(&client->buffer[pos+1], &client->buffer[pos1], (size-pos1)); + client->ptr = (size - (pos1 - pos)) + 1; + client->buffer[client->ptr] = 0; + + return 1; + } + } + + if(client->ptr >= WEBSERVER_BUFFER_SIZE && strncmp((char *)&client->buffer[pos+1], "HTTP/1.1", 8) != 0) { + /* + * GET end delimiter before HTTP/1.1 + */ + + ptr1 = (unsigned char *)memchr(&client->buffer[pos+1], ' ', size - (pos + 1)); + unsigned char *ptr2 = (unsigned char *)memchr(&client->buffer[pos], '&', size - (pos)); + char d = ' '; + + if(ptr1 != NULL || ptr2 != NULL) { + if((ptr1 == NULL && ptr2 != NULL) || (ptr1 != NULL && ptr2 != NULL && ptr2 < ptr1)) { + ptr1 = ptr2; + d = '&'; + } + uint16_t pos1 = ptr1 - client->buffer; + int16_t pos2 = urldecode(client->buffer, + pos + 1, + client->buffer, + pos + 1, 1); + + if(pos2 > -1) { + client->buffer[pos2 - 1] = 0; + } else { + client->buffer[pos] = 0; + } + + int16_t pos3 = -1; + if(d == ' ') { + pos3 = urldecode(&client->buffer[pos+1], + (pos1 - (pos + 1)) + 1, + &client->buffer[pos+1], + (pos1 - (pos + 1)) + 1, 1); + + client->buffer[pos1] = 0; + } + + args.name = &client->buffer[0]; + if(d == ' ') { + args.value = &client->buffer[pos+1]; + args.len = size - (pos + 1); + if(pos3 > -1) { + args.len = pos3 - 1; + } + } else { + args.value = NULL; + args.len = 0; + } + + if(client->callback != NULL) { + if(client->callback(client, &args) == -1) { + return -1; /*LCOV_EXCL_LINE*/ + } + } + + if(pos3 > -1) { + client->buffer[pos3-1] = d; + client->buffer[pos] = d; + pos1 = pos3; + } else { + client->buffer[pos1] = d; + } + if(d == '&') { + pos1++; + } + + memmove(&client->buffer[0], &client->buffer[pos1], size-(pos1)); + client->ptr = size-(pos1); + client->buffer[client->ptr] = 0; + + } else { + int16_t pos2 = urldecode(client->buffer, + pos + 1, + client->buffer, + pos + 1, 1); + + if(pos2 > -1) { + client->buffer[pos2 - 1] = 0; + } else { + client->buffer[pos] = 0; + } + + int16_t pos3 = urldecode(&client->buffer[pos+1], + size - (pos + 1) + 1, + &client->buffer[pos+1], + size - (pos + 1) + 1, 1); + + args.name = &client->buffer[0]; + args.value = &client->buffer[pos+1]; + args.len = size - (pos + 1); + + if(pos3 > -1) { + args.len = pos3 - 1; + } + + if(client->callback != NULL) { + if(client->callback(client, &args) == -1) { + return -1; /*LCOV_EXCL_LINE*/ + } + } + + if(pos2 > -1) { + client->buffer[pos2-1] = c; + client->buffer[pos] = d; + pos = pos2; + } else { + client->buffer[pos] = c; + } + + memmove(&client->buffer[pos+1], &client->buffer[size], size-(pos+1)); + client->ptr = (pos+1); + + client->buffer[client->ptr] = 0; + + return 1; + } + } + + if(ptrD == NULL && ptrE == NULL && ptrA != NULL && (posB == WEBSERVER_BUFFER_SIZE+1 || posB > 0)) { + uint16_t pos1 = ptrA-client->buffer; + + int16_t pos2 = urldecode(client->buffer, + pos + 1, + client->buffer, + pos + 1, 1); + + if(pos2 > -1) { + client->buffer[pos2 - 1] = 0; + } else { + client->buffer[pos] = 0; + } + + args.name = &client->buffer[0]; + args.value = NULL; + args.len = 0; + + if(client->callback != NULL) { + if(client->callback(client, &args) == -1) { + return -1; /*LCOV_EXCL_LINE*/ + } + } + + if(pos2 > -1) { + client->buffer[pos2-1] = c; + client->buffer[pos] = ' '; + } else { + client->buffer[pos] = c; + } + + memmove(&client->buffer[0], &client->buffer[pos1], size-(pos1)); + client->ptr = size-(pos1); + client->buffer[client->ptr] = 0; + + return 0; + } + + } + + return 0; +} + +int8_t http_parse_request(struct webserver_t *client, uint8_t **buf, uint16_t *len) { + uint16_t hasread = MIN(WEBSERVER_BUFFER_SIZE-client->ptr, *len); + + while(*len > 0) { + hasread = MIN(WEBSERVER_BUFFER_SIZE-client->ptr, (*len)); + memcpy(&client->buffer[client->ptr], &(*buf)[0], hasread); + + client->ptr += hasread; + memmove(&(*buf)[0], &(*buf)[hasread], (*len)-hasread); + + *len -= hasread; + + /* + * Request method + */ + if(client->substep == 0) { + if(memcmp_P(client->buffer, PSTR("GET "), 4) == 0) { + client->method = 0; + if(client->callback != NULL) { + client->step = WEBSERVER_CLIENT_REQUEST_METHOD; + if(client->callback != NULL) { + if(client->callback(client, (void *)"GET") == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + } + client->step = WEBSERVER_CLIENT_READ_HEADER; + } + memmove(&client->buffer[0], &client->buffer[4], client->ptr-4); + client->ptr -= 4; + client->substep = 1; + } + if(memcmp_P(client->buffer, PSTR("POST "), 5) == 0) { + client->method = 1; + client->reqtype = 0; + client->step = WEBSERVER_CLIENT_REQUEST_METHOD; + if(client->callback != NULL) { + if(client->callback(client, (void *)"POST") == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + } + client->step = WEBSERVER_CLIENT_READ_HEADER; + memmove(&client->buffer[0], &client->buffer[5], client->ptr-5); + client->ptr -= 5; + client->substep = 1; + } + } + /* + * Request URI + */ + if(client->substep == 1) { + unsigned char *ptr1 = (unsigned char *)memchr(client->buffer, '?', client->ptr); + unsigned char *ptr2 = (unsigned char *)memchr(client->buffer, ' ', client->ptr); + if(ptr2 == NULL || (ptr1 != NULL && ptr2 > ptr1)) { + if(ptr1 == NULL) { + if(client->ptr == WEBSERVER_BUFFER_SIZE) { + // Request URI two long + return -1; + } else { + return 1; + } + } else { + uint16_t pos = ptr1-client->buffer; + client->buffer[pos] = 0; + client->substep = 2; + client->step = WEBSERVER_CLIENT_REQUEST_URI; + if(client->callback != NULL) { + if(client->callback(client, client->buffer) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + } + memmove(&client->buffer[0], &client->buffer[pos+1], client->ptr-(pos+1)); + client->ptr -= (pos+1); + } + } else { + uint16_t pos = ptr2-client->buffer; + client->buffer[pos] = 0; + client->substep = 2; + client->step = WEBSERVER_CLIENT_REQUEST_URI; + if(client->callback != NULL) { + if(client->callback(client, client->buffer) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + } + client->buffer[pos] = ' '; + memmove(&client->buffer[0], &client->buffer[pos], client->ptr-(pos)); + client->ptr -= pos; + } + } + if(client->substep == 2) { + client->step = WEBSERVER_CLIENT_ARGS; + int ret = webserver_parse_post(client, client->ptr); + client->step = WEBSERVER_CLIENT_READ_HEADER; + + if(ret == -1) { + return -1; /*LCOV_EXCL_LINE*/ + } + + if(ret == 1) { + continue; + } + + if(client->ptr >= 4) { + if(memcmp_P(client->buffer, " HTTP/1.1", 9) == 0) { + client->substep = 3; + } else { + continue; + } + } + } + if(client->substep == 3) { + uint16_t i = 0; + while(i < client->ptr-2) { + if(memcmp_P(&client->buffer[i], PSTR("\r\n"), 2) == 0) { + memmove(&client->buffer[0], &client->buffer[i+2], client->ptr-(i+2)); + client->ptr -= (i + 2); + client->substep = 4; + break; + } + i++; + } + } + if(client->substep == 4) { + unsigned char *ptr = (unsigned char *)memchr(client->buffer, ':', client->ptr); + + while(ptr != NULL) { + struct arguments_t args; + uint16_t i = ptr-client->buffer, x = 0; + client->buffer[i] = 0; + args.name = &client->buffer[0]; + args.value = NULL; + x = i; + i++; + /* + * Make sure we can at least compare + * the double \r\n\r\n at the end + * of the header + */ + while(i <= client->ptr-4) { + if(memcmp_P(&client->buffer[i], PSTR("\r\n"), 2) == 0 || + (client->ptr == WEBSERVER_BUFFER_SIZE && i == WEBSERVER_BUFFER_SIZE-4)) { + while(client->buffer[x+1] == ' ') { + x++; + } + args.value = &client->buffer[x+1]; + args.len = (i-x)-1; + if((client->ptr == WEBSERVER_BUFFER_SIZE && i == WEBSERVER_BUFFER_SIZE-4)) { + args.len += 2; + } + + if(memcmp_P(args.name, PSTR("Content-Length"), 14) == 0) { + char tmp[args.len+1]; + memset(&tmp, 0, args.len+1); + memcpy(tmp, &client->buffer[x+1], args.len); + client->totallen = atoi(tmp); + } + if(memcmp_P(args.name, PSTR("Sec-WebSocket-Version"), 21) == 0) { + client->is_websocket = 1; + } + if(memcmp_P(args.name, PSTR("Sec-WebSocket-Key"), 17) == 0) { + char tmp[args.len+1]; + memset(&tmp, 0, args.len+1); + memcpy(tmp, &client->buffer[x+1], args.len); + if((client->data.websockkey = strdup(tmp)) == NULL) { +#ifdef ESP8266 + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); +#endif + } + } + if(memcmp_P(args.name, PSTR("Content-Type"), 12) == 0) { + if(strncasestr(&client->buffer[x+1], "multipart/form-data", client->ptr-(x+1)) != NULL) { + client->reqtype = 1; + char tmp[args.len+1]; + memset(&tmp, 0, args.len+1); + memcpy(tmp, &client->buffer[x+1], args.len); + { + char *ptr = strstr(tmp, "boundary="); + uint8_t pos = (ptr-tmp)+strlen("boundary="); + memmove(&tmp[0], &tmp[pos], args.len-pos); + tmp[args.len-pos] = 0; + if((client->data.boundary = strdup(tmp)) == NULL) { +#ifdef ESP8266 + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); +#endif + } + } + } + } + client->step = WEBSERVER_CLIENT_HEADER; + if(client->callback != NULL) { + if(client->callback(client, &args) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + } + client->step = WEBSERVER_CLIENT_READ_HEADER; + + client->buffer[i] = 0; + if((client->ptr == WEBSERVER_BUFFER_SIZE && i == WEBSERVER_BUFFER_SIZE-4)) { + memmove(&client->buffer[x], &client->buffer[i+2], client->ptr-(i+2)); + client->buffer[x-1] = ':'; + client->ptr -= (i + 2 - x); + } else { + memmove(&client->buffer[0], &client->buffer[i+2], client->ptr-(i+2)); + client->ptr -= (i + 2); + } + break; + } + i++; + } + if(args.value == NULL) { + client->buffer[x] = ':'; + break; + } + ptr = (unsigned char *)memchr(client->buffer, ':', client->ptr); + } + + if(client->ptr >= 2 && memcmp_P(client->buffer, PSTR("\r\n"), 2) == 0) { + memmove(&client->buffer[0], &client->buffer[2], client->ptr-2); + client->ptr -= 2; + client->readlen = 0; + if(client->ptr == 0 && *len > 0) { + client->substep = 5; + continue; + } + return 0; + } + } + if(client->substep == 5) { + return 0; + } + } + + return 1; +} + +int http_parse_body(struct webserver_t *client, char *buf, uint16_t len) { + uint16_t hasread = MIN(WEBSERVER_BUFFER_SIZE-client->ptr, len); + uint16_t pos = 0; + + while(1) { + if(pos < len) { + hasread = MIN(WEBSERVER_BUFFER_SIZE-client->ptr, len-pos);; + memcpy(&client->buffer[client->ptr], &buf[pos], hasread); + client->ptr += hasread; + pos += hasread; + } + + uint16_t toread = client->ptr; + int ret = webserver_parse_post(client, client->ptr); + if(ret == 1 || ret == 0) { + client->readlen += (toread - client->ptr); + } + + if(ret == -1) { + return -1; /*LCOV_EXCL_LINE*/ + } + if(ret == 0) { + break; + } + } + + return 0; +} + +char *strnstr(const char *haystack, const char *needle, size_t len) { + int i; + size_t needle_len; + + if(0 == (needle_len = strnlen(needle, len))) { + return (char *)haystack; + } + + for(i=0; i<=(int)(len-needle_len); i++) { + if((haystack[0] == needle[0]) && (0 == strncmp(haystack, needle, needle_len))) { + return (char *)haystack; + } + + haystack++; + } + return NULL; +} + +int http_parse_multipart_body(struct webserver_t *client, unsigned char *buf, uint16_t len) { + uint16_t hasread = MIN(WEBSERVER_BUFFER_SIZE-client->ptr, len); + uint16_t rpos = 0, loop = 1; + + while(rpos < len) { + hasread = MIN(WEBSERVER_BUFFER_SIZE-client->ptr, len-rpos); + memcpy(&client->buffer[client->ptr], &buf[rpos], hasread); + client->ptr += hasread; + rpos += hasread; + loop = 1; + + while(loop) { + switch(client->substep) { + // Boundary + case 0: { + unsigned char *ptr = strnstr(client->buffer, client->data.boundary, client->ptr); + unsigned char *ptr1 = (unsigned char *)memchr(client->buffer, '=', client->ptr); + uint16_t pos1 = 0; + if(ptr1 != NULL) { + pos1 = (ptr1-client->buffer)+1; + } + if(ptr != NULL) { + uint16_t pos = (ptr-client->buffer)+strlen(client->data.boundary); + if(pos1 > pos) { + /* + * Only compensate for the key at the + * beginning of the buffer when at least + * one key has been encountered. This is + * the case when the boundary is placed + * after the key. + */ + pos1 = 0; + } + + if(pos+1 <= client->ptr) { + if(client->buffer[pos] == '\r' && client->buffer[pos+1] == '\n') { + memmove(&client->buffer[0], &client->buffer[pos+1], client->ptr-(pos+1)); + client->ptr = client->ptr-(pos+1); + client->buffer[client->ptr] = 0; + client->readlen += ((pos+1)-pos1); + client->substep = 1; + } + } + if(pos+3 <= client->ptr) { + if(client->buffer[pos] == '-' && client->buffer[pos+1] == '-' && + client->buffer[pos+2] == '\r' && client->buffer[pos+3] == '\n') { + client->readlen += ((pos+4)-(pos1)); + if(client->readlen == client->totallen) { + if(client->data.boundary != NULL) { + free(client->data.boundary); + client->data.boundary = NULL; + } + return 0; + } else { + // Error, content length does not match end boundary + } + } else { + loop = 0; + } + } else { + loop = 0; + } + } else if(client->ptr < WEBSERVER_BUFFER_SIZE) { + loop = 0; + } else { + /* + * We encountered the boundary delimiter, but + * it wasn't the one from this request, but it + * was part of the POST body + */ + client->substep = 8; + } + if(client->substep == 0) { + loop = 0; + } + } break; + // Content-Disposition + case 1: { + unsigned char *ptr = strncasestr(client->buffer, "content-disposition:", client->ptr); + if(ptr != NULL) { + uint16_t pos = (ptr-client->buffer)+strlen("content-disposition:"); + while(client->buffer[pos++] == ' '); + pos--; + memmove(&client->buffer[0], &client->buffer[pos], client->ptr-(pos)); + client->ptr = client->ptr-(pos); + client->buffer[client->ptr] = 0; + client->readlen += pos; + client->substep = 2; + } else { + loop = 0; + } + } break; + // End of content-disposition + case 2: { + unsigned char *ptr = (unsigned char *)memchr(client->buffer, ';', client->ptr); + if(ptr != NULL) { + uint16_t pos = (ptr-client->buffer+1); + while(client->buffer[pos++] == ' '); + pos--; + memmove(&client->buffer[0], &client->buffer[pos], client->ptr-(pos)); + client->ptr -= pos; + client->buffer[client->ptr] = 0; + client->readlen += pos; + client->substep = 3; + } else { + loop = 0; + } + } break; + // Name + case 3: { + unsigned char *ptr = strncasestr(client->buffer, "name=\"", client->ptr); + if(ptr != NULL) { + uint16_t pos = (ptr-client->buffer)+strlen("name=\""); + memmove(&client->buffer[0], &client->buffer[pos], client->ptr-(pos)); + client->ptr = client->ptr-(pos); + client->readlen += pos; + client->substep = 6; + } else { + loop = 0; + } + } break; + // Filename etc. + case 4: { + unsigned char *ptr = strncasestr(client->buffer, "\";", client->ptr); + if(ptr != NULL) { + uint16_t pos = (ptr-client->buffer); + unsigned char *ptr1 = strncasestr(&client->buffer[pos], "\r\n", client->ptr-pos); + if(ptr1 != NULL) { + client->buffer[pos++] = '='; + uint16_t pos1 = (ptr1-client->buffer); + uint16_t newlen = client->ptr-((pos1+2)-pos); + memmove(&client->buffer[pos], &client->buffer[pos1+2], newlen); + client->ptr = newlen; + client->readlen += (pos1+2); + client->substep = 5; + } else { + client->substep = 6; + } + } else { + loop = 0; + } + } break; + // Content-type + case 5: { + unsigned char *ptr = (unsigned char *)memchr(client->buffer, '=', client->ptr); + if(ptr != NULL) { + uint16_t pos = (ptr-client->buffer)+1; + + if((client->ptr - pos) >= 4) { + unsigned char *ptr1 = strnstr(&client->buffer[pos], "\r\n\r\n", client->ptr-pos); + if(ptr1 != NULL) { + uint16_t pos1 = (ptr1-client->buffer)+4; + uint16_t newlen = client->ptr-(pos1-pos); + memmove(&client->buffer[pos], &client->buffer[pos1], newlen); + client->ptr = newlen; + client->readlen += (pos1-pos); + client->substep = 7; + } else { + loop = 0; + } + } else { + loop = 0; + } + } else { + loop = 0; + } + } break; + // Name + case 6: { + if(client->ptr >= 2) { + unsigned char *ptr = strnstr(client->buffer, "\";", client->ptr); + if(ptr != NULL) { + unsigned char *ptr1 = strnstr(client->buffer, "\r\n", client->ptr); + if(ptr1 != NULL) { + client->substep = 4; + } else { + loop = 0; + } + } else { + unsigned char *ptr1 = strnstr(client->buffer, "\"\r\n", client->ptr); + if(ptr1 != NULL) { + uint16_t pos = (ptr1-client->buffer); + /* + * Since we're increasing the pos + * right after, check for 5 positions + */ + if((client->ptr - pos) >= 5) { + client->buffer[pos++] = '=';; + unsigned char *ptr2 = strnstr(&client->buffer[pos], "\r\n\r\n", client->ptr-pos); + if(ptr2 != NULL) { + uint16_t pos1 = (ptr2-client->buffer)+4; + + memmove(&client->buffer[pos], &client->buffer[pos1], client->ptr-pos1); + client->ptr -= (pos1-pos); + client->readlen += pos1; + client->substep = 7; + } else { + loop = 0; + } + } else { + loop = 0; + } + } else { + loop = 0; + } + } + } else { + loop = 0; + } + } break; + // Value + case 8: + case 7: { + unsigned char *ptr = strnstr(client->buffer, "\r\n--", client->ptr); + if(ptr != NULL && client->substep != 8) { + uint16_t pos = (ptr-client->buffer); + + ptr = (unsigned char *)memchr(client->buffer, '=', client->ptr); + uint16_t vlen = 0; + + if(ptr != NULL) { + vlen = (ptr-client->buffer); + } else { + // error + return -1; /*LCOV_EXCL_LINE*/ + } + + struct arguments_t args; + client->buffer[vlen] = 0; + + args.name = &client->buffer[0]; + args.value = &client->buffer[vlen+1]; + args.len = pos-(vlen+1); + + if(client->callback != NULL) { + uint8_t ret = client->callback(client, &args); + if(ret == -1) { + return -1; + } + } + + client->buffer[vlen] = '='; + + memmove(&client->buffer[vlen+1], &client->buffer[pos], client->ptr-(pos-(vlen+1))); + client->readlen += (pos-(vlen+1)); + client->ptr -= (pos-(vlen+1)); + client->substep = 0; + } else if(client->ptr == WEBSERVER_BUFFER_SIZE) { + uint8_t ending = 0; + /* + * Double check that the CR / LN don't belong + * to the boundary delimiter. + */ + if(strncmp((char *)&client->buffer[client->ptr-2], "\r\n", 2) == 0) { + ending = 2; + } + if(client->substep == 8) { + client->substep = 7; + } + unsigned char *ptr = (unsigned char *)memchr(client->buffer, '=', client->ptr); + if(ptr != NULL) { + uint16_t pos = (ptr-client->buffer); + + struct arguments_t args; + client->buffer[pos] = 0; + + args.name = &client->buffer[0]; + args.value = &client->buffer[pos+1]; + args.len = (WEBSERVER_BUFFER_SIZE-ending)-(pos+1); + + if(client->callback != NULL) { + uint8_t ret = client->callback(client, &args); + if(ret == -1) { + return -1; + } + } + client->buffer[pos] = '='; + if(ending == 2) { + client->buffer[pos+1] = '\r'; + client->buffer[pos+2] = '\n'; + } + client->readlen += ((client->ptr-(pos+1))-ending); + client->ptr = pos+1+ending; + } else { + // error + return -1; + } + } else { + loop = 0; + } + } break; + } + } + } + + return 0; +} + +#ifdef __linux__ +static char *code_to_text(uint16_t code) { +#else +static PGM_P code_to_text(uint16_t code) { +#endif + /* LCOV_EXCL_START*/ + switch(code) { + case 100: + return PSTR("Continue"); + case 101: + return PSTR("Switching Protocols"); + /* LCOV_EXCL_STOP*/ + case 200: + return PSTR("OK"); + /* LCOV_EXCL_START*/ + case 201: + return PSTR("Created"); + case 202: + return PSTR("Accepted"); + case 203: + return PSTR("Non-Authoritative Information"); + case 204: + return PSTR("No Content"); + case 205: + return PSTR("Reset Content"); + case 206: + return PSTR("Partial Content"); + case 300: + return PSTR("Multiple Choices"); + /* LCOV_EXCL_STOP*/ + case 301: + return PSTR("Moved Permanently"); + /* LCOV_EXCL_START*/ + case 302: + return PSTR("Found"); + case 303: + return PSTR("See Other"); + case 304: + return PSTR("Not Modified"); + case 305: + return PSTR("Use Proxy"); + case 307: + return PSTR("Temporary Redirect"); + case 400: + return PSTR("Bad Request"); + case 401: + return PSTR("Unauthorized"); + case 402: + return PSTR("Payment Required"); + case 403: + return PSTR("Forbidden"); + case 404: + return PSTR("Not Found"); + case 405: + return PSTR("Method Not Allowed"); + case 406: + return PSTR("Not Acceptable"); + case 407: + return PSTR("Proxy Authentication Required"); + case 408: + return PSTR("Request Timeout"); + case 409: + return PSTR("Conflict"); + case 410: + return PSTR("Gone"); + case 411: + return PSTR("Length Required"); + case 412: + return PSTR("Precondition Failed"); + case 413: + return PSTR("Request Entity Too Large"); + case 414: + return PSTR("URI Too Long"); + case 415: + return PSTR("Unsupported Media Type"); + case 416: + return PSTR("Range not satisfiable"); + case 417: + return PSTR("Expectation Failed"); + case 500: + return PSTR("Internal Server Error"); + case 501: + return PSTR("Not Implemented"); + case 502: + return PSTR("Bad Gateway"); + case 503: + return PSTR("Service Unavailable"); + case 504: + return PSTR("Gateway Timeout"); + case 505: + return PSTR("HTTP Version not supported"); + default: + return PSTR(""); + } + /* LCOV_EXCL_STOP*/ +} + +static uint16_t webserver_create_header(struct webserver_t *client, uint16_t code, char *mimetype, uint16_t len) { + uint16_t i = 0; + unsigned char buffer[512], *p = buffer; + memset(buffer, '\0', sizeof(buffer)); + + i += snprintf_P((char *)&p[i], sizeof(buffer), PSTR("HTTP/1.1 %d %s\r\n"), code, code_to_text(code)); + if(client->callback != NULL) { + client->step = WEBSERVER_CLIENT_CREATE_HEADER; + struct header_t header; + header.buffer = &p[i]; + header.ptr = i; + + if(client->callback(client, &header) == -1) { + if(strstr_P((char *)&p[i], PSTR("\r\n\r\n")) == NULL) { + if(strstr((char *)&p[i], PSTR("\r\n")) != NULL) { + header.ptr += snprintf_P((char *)&p[header.ptr], sizeof(buffer)-header.ptr, PSTR("\r\n")); + } else { + header.ptr += snprintf_P((char *)&p[header.ptr], sizeof(buffer)-header.ptr, PSTR("\r\n\r\n")); + } + } + client->step = WEBSERVER_CLIENT_WRITE; + i = header.ptr; + return i; + } + + if(header.ptr > i && strstr_P((char *)&p[i], PSTR("\r\n")) == NULL) { + header.ptr += snprintf((char *)&p[header.ptr], sizeof(buffer)-header.ptr, PSTR("\r\n")); + } + i = header.ptr; + client->step = WEBSERVER_CLIENT_WRITE; + } + i += snprintf_P((char *)&p[i], sizeof(buffer) - i, PSTR("Server: ESP8266\r\n")); + i += snprintf_P((char *)&p[i], sizeof(buffer) - i, PSTR("Keep-Alive: timeout=15, max=100\r\n")); + i += snprintf_P((char *)&p[i], sizeof(buffer) - i, PSTR("Content-Type: %s\r\n"), mimetype); + i += snprintf_P((char *)&p[i], sizeof(buffer) - i, PSTR("Content-Length: %d\r\n\r\n"), len); + + + if(client->async == 1) { + tcp_write(client->pcb, &buffer, i, 0); + tcp_output(client->pcb); + } else { + if(client->client->write(buffer, i) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + + return i; +} + +static int webserver_process_send(struct webserver_t *client) { + struct sendlist_t *tmp = NULL; + uint16_t cpylen = client->totallen, i = 0, cpyptr = client->ptr; + unsigned char cpy[client->totallen+1]; + memset(&cpy, 0, client->totallen+1); + +#if WEBSERVER_MAX_SENDLIST == 0 + tmp = client->sendlist; +#else + uint8_t x = 0, y = 0; + for(x=0;xsendlist[x].data.ptr != NULL +#else + (client->sendlist[x].type == 1 && client->sendlist[x].data.ptr != NULL) || + (client->sendlist[x].type == 0 && strlen((char *)client->sendlist[x].data.fixed) > 0) +#endif + ) { + tmp = &client->sendlist[x]; + break; + } + } +#endif + + if(client->chunked == 1) { + while(tmp != NULL && cpylen > 0) { + if(cpyptr == 0) { + if(cpylen >= tmp->size) { + cpyptr += tmp->size; + cpylen -= tmp->size; +#if WEBSERVER_MAX_SENDLIST == 0 + tmp = tmp->next; +#else + tmp = NULL; + for(y=x+1;ysendlist[y].data.ptr != NULL +#else + (client->sendlist[y].type == 1 && client->sendlist[y].data.ptr != NULL) || + (client->sendlist[y].type == 0 && strlen((char *)client->sendlist[y].data.fixed) > 0) +#endif + ) { + tmp = &client->sendlist[y]; + x = y; + break; + } + } +#endif + cpyptr = 0; + } else { + cpyptr += cpylen; + cpylen = 0; + } + } else if(cpyptr+cpylen >= tmp->size) { + cpylen -= (tmp->size-cpyptr); +#if WEBSERVER_MAX_SENDLIST == 0 + tmp = tmp->next; +#else + tmp = NULL; + for(y=x+1;ysendlist[y].data.ptr != NULL +#else + (client->sendlist[y].type == 1 && client->sendlist[y].data.ptr != NULL) || + (client->sendlist[y].type == 0 && strlen((char *)client->sendlist[y].data.fixed) > 0) +#endif + ) { + tmp = &client->sendlist[y]; + x = y; + break; + } + } +#endif + cpyptr = 0; + } else { + cpyptr += cpylen; + cpylen = 0; + } + } + + unsigned char chunk_size[12]; + size_t n = snprintf_P((char *)chunk_size, sizeof(chunk_size), PSTR("%X\r\n"), client->totallen - cpylen); + + if(client->async == 1) { + tcp_write(client->pcb, chunk_size, n, 0); + } else { + if(client->client->write(chunk_size, n) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + i += n; + } + +#if WEBSERVER_MAX_SENDLIST == 0 + tmp = client->sendlist; +#else + x = 0, y = 0; + for(x=0;xsendlist[x].data.ptr != NULL +#else + (client->sendlist[x].type == 1 && client->sendlist[x].data.ptr != NULL) || + (client->sendlist[x].type == 0 && strlen((char *)client->sendlist[x].data.fixed) > 0) +#endif + ) { + tmp = &client->sendlist[x]; + break; + } + } +#endif + if(tmp != NULL) { + while(tmp != NULL && client->totallen > 0) { + if(client->ptr == 0) { + if(client->totallen >= tmp->size) { + if(tmp->type == 1) { + memcpy_P(cpy, &((PGM_P)tmp->data.ptr)[client->ptr], tmp->size); + if(client->async == 1) { + tcp_write(client->pcb, cpy, tmp->size, TCP_WRITE_FLAG_MORE); + } else { + if(client->client->write(cpy, tmp->size) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } else { + if(client->async == 1) { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + tcp_write(client->pcb, &((unsigned char *)tmp->data.ptr)[client->ptr], tmp->size, TCP_WRITE_FLAG_MORE); +#else + tcp_write(client->pcb, &((unsigned char *)tmp->data.fixed)[client->ptr], tmp->size, TCP_WRITE_FLAG_MORE); +#endif + } else { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + if(client->client->write(&((unsigned char *)tmp->data.ptr)[client->ptr], tmp->size) > 0) { +#else + if(client->client->write(&((unsigned char *)tmp->data.fixed)[client->ptr], tmp->size) > 0) { +#endif + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } + i += tmp->size; + client->ptr += tmp->size; + client->totallen -= tmp->size; + + if(tmp->type == 0) { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + free(tmp->data.ptr); +#else + memset(&tmp->data.fixed, 0, WEBSERVER_SENDLIST_BUFSIZE); +#endif + } + + tmp->data.ptr = NULL; +#if WEBSERVER_MAX_SENDLIST == 0 + client->sendlist = client->sendlist->next; + free(tmp); + tmp = client->sendlist; +#else + tmp = NULL; + for(y=x+1;ysendlist[y].data.ptr != NULL +#else + (client->sendlist[y].type == 1 && client->sendlist[y].data.ptr != NULL) || + (client->sendlist[y].type == 0 && strlen((char *)client->sendlist[y].data.fixed) > 0) +#endif + ) { + tmp = &client->sendlist[y]; + x = y; + break; + } + } +#endif + client->ptr = 0; + } else { + if(tmp->type == 1) { + memcpy_P(cpy, &((PGM_P)tmp->data.ptr)[client->ptr], client->totallen); + if(client->async == 1) { + tcp_write(client->pcb, cpy, client->totallen, TCP_WRITE_FLAG_MORE); + } else { + if(client->client->write(cpy, client->totallen) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } else { + if(client->async == 1) { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + tcp_write(client->pcb, &((unsigned char *)tmp->data.ptr)[client->ptr], client->totallen, TCP_WRITE_FLAG_MORE); +#else + tcp_write(client->pcb, &((unsigned char *)tmp->data.fixed)[client->ptr], client->totallen, TCP_WRITE_FLAG_MORE); +#endif + } else { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + if(client->client->write(&((unsigned char *)tmp->data.ptr)[client->ptr], client->totallen) > 0) { +#else + if(client->client->write(&((unsigned char *)tmp->data.fixed)[client->ptr], client->totallen) > 0) { +#endif + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } + i += client->totallen; + client->ptr += client->totallen; + client->totallen = 0; + } + } else if(client->ptr+client->totallen >= tmp->size) { + if(tmp->type == 1) { + memcpy_P(cpy, &((PGM_P)tmp->data.ptr)[client->ptr], (tmp->size-client->ptr)); + if(client->async == 1) { + tcp_write(client->pcb, cpy, (tmp->size-client->ptr), TCP_WRITE_FLAG_MORE); + } else { + if(client->client->write(cpy, (tmp->size-client->ptr)) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } else { + if(client->async == 1) { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + tcp_write(client->pcb, &((unsigned char *)tmp->data.ptr)[client->ptr], (tmp->size-client->ptr), TCP_WRITE_FLAG_MORE); +#else + tcp_write(client->pcb, &((unsigned char *)tmp->data.fixed)[client->ptr], (tmp->size-client->ptr), TCP_WRITE_FLAG_MORE); +#endif + } else { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + if(client->client->write(&((unsigned char *)tmp->data.ptr)[client->ptr], (tmp->size-client->ptr)) > 0) { +#else + if(client->client->write(&((unsigned char *)tmp->data.fixed)[client->ptr], (tmp->size-client->ptr)) > 0) { +#endif + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } + i += (tmp->size-client->ptr); + client->totallen -= (tmp->size-client->ptr); + + if(tmp->type == 0) { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + free(tmp->data.ptr); +#else + memset(&tmp->data.fixed, 0, WEBSERVER_SENDLIST_BUFSIZE); +#endif + } + + tmp->data.ptr = NULL; +#if WEBSERVER_MAX_SENDLIST == 0 + client->sendlist = client->sendlist->next; + free(tmp); + tmp = client->sendlist; +#else + tmp = NULL; + for(y=x+1;ysendlist[y].data.ptr != NULL +#else + (client->sendlist[y].type == 1 && client->sendlist[y].data.ptr != NULL) || + (client->sendlist[y].type == 0 && strlen((char *)client->sendlist[y].data.fixed) > 0) +#endif + ) { + tmp = &client->sendlist[y]; + x = y; + break; + } + } +#endif + client->ptr = 0; + } else { + if(tmp->type == 1) { + memcpy_P(cpy, &((PGM_P)tmp->data.ptr)[client->ptr], client->totallen); + if(client->async == 1) { + tcp_write(client->pcb, cpy, client->totallen, TCP_WRITE_FLAG_MORE); + } else { + if(client->client->write(cpy, client->totallen) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } else { + if(client->async == 1) { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + tcp_write(client->pcb, &((unsigned char *)tmp->data.ptr)[client->ptr], client->totallen, TCP_WRITE_FLAG_MORE); +#else + tcp_write(client->pcb, &((unsigned char *)tmp->data.fixed)[client->ptr], client->totallen, TCP_WRITE_FLAG_MORE); +#endif + } else { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + if(client->client->write(&((unsigned char *)tmp->data.ptr)[client->ptr], client->totallen) > 0) { +#else + if(client->client->write(&((unsigned char *)tmp->data.fixed)[client->ptr], client->totallen) > 0) { +#endif + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } + client->ptr += client->totallen; + client->totallen = 0; + } + } + if(client->chunked == 1) { + if(client->async == 1) { + tcp_write_P(client->pcb, PSTR("\r\n"), 2, TCP_WRITE_FLAG_MORE); + } else { + if(client->client->write_P((char *)PSTR("\r\n"), 2) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } + } + + if(tmp == NULL) { +#if WEBSERVER_MAX_SENDLIST > 0 + uint8_t x = 0; + for(x=0;xsendlist[x]; + if(tmp->type == 0) { + #if WEBSERVER_SENDLIST_BUFSIZE > 0 + memset(&tmp->data.fixed, 0, WEBSERVER_SENDLIST_BUFSIZE+1); + #endif + } + tmp->data.ptr = NULL; + memset(tmp, 0, sizeof(struct sendlist_t)); + } +#endif + tmp = NULL; + + client->content++; + if(client->is_websocket == 1) { + client->step = WEBSERVER_CLIENT_WEBSOCKET; + } else { + client->step = WEBSERVER_CLIENT_WRITE; + + if(client->callback(client, NULL) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + } else { + client->step = WEBSERVER_CLIENT_SENDING; + } + +#if WEBSERVER_MAX_SENDLIST == 0 + tmp = client->sendlist; +#else + for(x=0;xsendlist[x].data.ptr != NULL +#else + (client->sendlist[x].type == 1 && client->sendlist[x].data.ptr != NULL) || + (client->sendlist[x].type == 0 && strlen((char *)client->sendlist[x].data.fixed) > 0) +#endif + ) { + tmp = &client->sendlist[x]; + break; + } + } +#endif + if(tmp == NULL) { + if(client->chunked == 1) { + if(client->async == 1) { + tcp_write_P(client->pcb, PSTR("0\r\n\r\n"), 5, 0); + } else { + if(client->client->write_P((char *)PSTR("0\r\n\r\n"), 5) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + i += 5; + } else { + if(client->async == 1) { + tcp_write_P(client->pcb, PSTR("\r\n\r\n"), 4, 0); + } else { + if(client->client->write_P((char *)PSTR("\r\n\r\n"), 4) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + i += 4; + } + client->step = WEBSERVER_CLIENT_CLOSE; + client->userdata = NULL; + client->ptr = 0; + client->content = 0; + } + } + } + if(client->async == 1) { + tcp_output(client->pcb); + } + + return i; +} + +void webserver_send_content_P(struct webserver_t *client, PGM_P buf, uint16_t size) { + struct sendlist_t *node = NULL; + +#if WEBSERVER_MAX_SENDLIST == 0 + node = (struct sendlist_t *)malloc(sizeof(struct sendlist_t)); + /*LCOV_EXCL_START*/ + if(node == NULL) { + #ifdef ESP8266 + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + #endif + } +#else + uint8_t i = 0; + for(i=0;isendlist[i].data.ptr == NULL +#else + (client->sendlist[i].type == 1 && client->sendlist[i].data.ptr == NULL) || + (client->sendlist[i].type == 0 && strlen((char *)client->sendlist[i].data.fixed) == 0) +#endif + ) { + node = &client->sendlist[i]; + break; + } + } + if(node == NULL) { + #ifdef ESP8266 + log_message(PSTR("Sendlist queue is full")); + #else + printf("Sendlist queue is full\n"); + #endif + return; + } +#endif + + /*LCOV_EXCL_STOP*/ + memset(node, 0, sizeof(struct sendlist_t)); + node->data.ptr = (void *)buf; + node->size = size; + node->type = 1; + +#if WEBSERVER_MAX_SENDLIST == 0 + if(client->sendlist == NULL) { + client->sendlist = node; + client->sendlist_head = node; + } else { + client->sendlist_head->next = node; + client->sendlist_head = node; + } +#endif +} + +void webserver_send_content(struct webserver_t *client, char *buf, uint16_t size) { + struct sendlist_t *node = NULL; + +#if WEBSERVER_MAX_SENDLIST == 0 + node = (struct sendlist_t *)malloc(sizeof(struct sendlist_t)); + /*LCOV_EXCL_START*/ + if(node == NULL) { + #ifdef ESP8266 + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + #endif + } +#else + uint8_t i = 0; + for(i=0;isendlist[i].data.ptr == NULL +#else + (client->sendlist[i].type == 1 && client->sendlist[i].data.ptr == NULL) || + (client->sendlist[i].type == 0 && strlen((char *)client->sendlist[i].data.fixed) == 0) +#endif + ) { + node = &client->sendlist[i]; + break; + } + } + if(node == NULL) { + #ifdef ESP8266 + log_message(PSTR("Sendlist queue is full")); + #else + printf("Sendlist queue is full\n"); + #endif + return; + } +#endif + memset(node, 0, sizeof(struct sendlist_t)); +#if WEBSERVER_SENDLIST_BUFSIZE > 0 + int x = 0; + for(x=0;xdata.fixed[x] = buf[x]; + } +#else + if((node->data.ptr = malloc(size+1)) == NULL) { + #ifdef ESP8266 + Serial1.printf(PSTR("Out of memory %s:#%d\n"), __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + #endif + } + memcpy(node->data.ptr, buf, size); +#endif + + node->size = size; + node->type = 0; + +#if WEBSERVER_MAX_SENDLIST == 0 + if(client->sendlist == NULL) { + client->sendlist = node; + client->sendlist_head = node; + } else { + client->sendlist_head->next = node; + client->sendlist_head = node; + } +#endif +} + +int8_t webserver_send(struct webserver_t *client, uint16_t code, char *mimetype, uint16_t data_len) { + uint16_t i = 0; + if(data_len == 0) { + unsigned char buffer[512], *p = buffer; + memset(buffer, '\0', sizeof(buffer)); + + client->chunked = 1; + i = snprintf_P((char *)p, sizeof(buffer), PSTR("HTTP/1.1 %d %s\r\n"), code, code_to_text(code)); + if(client->callback != NULL) { + client->step = WEBSERVER_CLIENT_CREATE_HEADER; + struct header_t header; + header.buffer = &p[i]; + header.ptr = i; + + if(client->callback(client, &header) == -1) { + if(strstr_P((char *)&p[i], PSTR("\r\n\r\n")) == NULL) { + if(strstr_P((char *)&p[i], PSTR("\r\n")) != NULL) { + header.ptr += snprintf((char *)&p[header.ptr], sizeof(buffer)-header.ptr, PSTR("\r\n")); + } else { + header.ptr += snprintf((char *)&p[header.ptr], sizeof(buffer)-header.ptr, PSTR("\r\n\r\n")); + } + } + client->step = WEBSERVER_CLIENT_WRITE; + i = header.ptr; + goto done; + } + if(header.ptr > i && strstr_P((char *)&p[i], PSTR("\r\n")) == NULL) { + header.ptr += snprintf((char *)&p[header.ptr], sizeof(buffer)-header.ptr, PSTR("\r\n")); + } + i = header.ptr; + client->step = WEBSERVER_CLIENT_WRITE; + } + + i += snprintf((char *)&p[i], sizeof(buffer)-i, PSTR("Keep-Alive: timeout=15, max=100\r\n")); + i += snprintf((char *)&p[i], sizeof(buffer)-i, PSTR("Content-Type: %s\r\n"), mimetype); + i += snprintf((char *)&p[i], sizeof(buffer)-i, PSTR("Transfer-Encoding: chunked\r\n\r\n")); + +done: + if(client->async == 1) { + tcp_write(client->pcb, &buffer, i, 0); + tcp_output(client->pcb); + } else{ + if(client->client->write((unsigned char *)&buffer, i) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } else { + client->chunked = 0; + i = webserver_create_header(client, code, mimetype, data_len); + } + + if(i > 0) { + return 0; + } else { + return -1; + } +} + +/* LCOV_EXCL_START*/ +static void webserver_client_close(struct webserver_t *client) { + if(client->callback != NULL) { + client->callback(client, NULL); + } +#ifdef ESP8266 + char log_msg[256]; + sprintf_P(log_msg, PSTR("Closing webserver client: %s:%d"), IPAddress(client->pcb->remote_ip.addr).toString().c_str(), client->pcb->remote_port); + log_message(log_msg); + + client->step = 0; + + tcp_recv(client->pcb, NULL); + tcp_sent(client->pcb, NULL); + tcp_poll(client->pcb, NULL, 0); + + tcp_close(client->pcb); + client->pcb = NULL; + + webserver_reset_client(client); +#endif +} +/* LCOV_EXCL_STOP*/ + +#ifdef ESP8266 +err_t webserver_sent(void *arg, tcp_pcb *pcb, uint16_t len) { + uint16_t i = 0; + for(i=0;i 0) { + /* + * Leave room for chunk overhead + */ + clients[i].data.totallen -= 16; + webserver_process_send(&clients[i].data); + } + } + if(clients[i].data.step == WEBSERVER_CLIENT_CLOSE) { + webserver_client_close(&clients[i].data); + } + break; + } + } + return ERR_OK; +} +#endif + +static void send_websocket_handshake(struct webserver_t *client, const char *key) { + char cpy[61] = { 0 }; + char input[20] = { 0 }; + char encoded[20] = { 0 }; + + const char *magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + snprintf(cpy, sizeof(cpy), "%s%s", key, magic); + + sha1digest((uint8_t *)input, NULL, (uint8_t *)cpy, strlen(cpy)); + + if(Base64encode(encoded, input, 20) > 0) { + uint16_t len = snprintf(NULL, 0, + "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Accept: %s\r\n\r\n", encoded); + + char buf[len+1]; + memset(&buf, 0, len+1); + len = snprintf((char *)&buf, len+1, + "HTTP/1.1 101 Web Socket Protocol Handshake\r\n" + "Connection: Upgrade\r\n" + "Upgrade: websocket\r\n" + "Sec-WebSocket-Accept: %s\r\n\r\n", encoded); + + if(client->async == 1) { + tcp_write(client->pcb, &buf, len, 0); + tcp_output(client->pcb); + } else { + if(client->client->write((unsigned char *)buf, len) > 0) { + if(client->is_websocket == 0) { + client->lastseen = millis(); + } + } + } + } +} + +void websocket_write_P(struct webserver_t *client, PGM_P data, uint16_t data_len) { + websocket_send_header(client, WEBSOCKET_OPCODE_TEXT, data_len); + webserver_send_content_P(client, data, data_len); + client->step = WEBSERVER_CLIENT_SENDING; +} + +void websocket_write(struct webserver_t *client, char *data, uint16_t data_len) { + websocket_send_header(client, WEBSOCKET_OPCODE_TEXT, data_len); + webserver_send_content(client, (char *)data, data_len); + client->step = WEBSERVER_CLIENT_SENDING; +} + +void websocket_write_all(char *data, uint16_t data_len) { + uint8_t i = 0; + for(i=0;i> 8) & 255; + copy[3] = (data_len) & 255; + index = 4; + } else { + /** + * Size too big for ESP8266 + */ + /* + copy[1] = 127; + copy[2] = (data_len >> 56) & 255; + copy[3] = (data_len >> 48) & 255; + copy[4] = (data_len >> 40) & 255; + copy[5] = (data_len >> 32) & 255; + copy[6] = (data_len >> 24) & 255; + copy[7] = (data_len >> 16) & 255; + copy[8] = (data_len >> 8) & 255; + copy[9] = (data_len) & 255; + index = 10; + */ + } + webserver_send_content(client, (char *)copy, index); +} + +int websocket_read(struct webserver_t *client, unsigned char *buf, ssize_t buf_len) { + unsigned int i = 0, j = 0; + unsigned char mask[4]; + uint32_t packet_length = 0; + int index_first_mask = 0; + int index_first_data_byte = 0; + int opcode = buf[0] & 0xF; + + memset(&mask, '\0', 4); + packet_length = ((unsigned char)buf[1]) & 0x7F; + + index_first_mask = 2; + if(packet_length == 126) { + index_first_mask = 4; + packet_length = buf[2] << 8 | buf[3]; + } else if(packet_length == 127) { + if(buf[2] != 0 || buf[3] != 0 || buf[4] != 0 || buf[5] != 0) { + /* + * Packet length too big + */ + return -1; + } else { + packet_length = buf[6] << 24 | buf[7] << 16 | buf[8] << 8 | buf[9]; + } + index_first_mask = 10; + } + memcpy(mask, &buf[index_first_mask], 4); + index_first_data_byte = index_first_mask + 4; + + for(i = index_first_data_byte, j = 0; i < buf_len && i < packet_length + index_first_data_byte; i++, j++) { + buf[j] = buf[i] ^ mask[j % 4]; + } + buf[packet_length] = '\0'; + + switch(opcode) { + case WEBSOCKET_OPCODE_PONG: { + client->lastseen = millis(); + } break; + case WEBSOCKET_OPCODE_PING: { + websocket_send_header(client, WEBSOCKET_OPCODE_PONG, 0); + } break; + case WEBSOCKET_OPCODE_CONNECTION_CLOSE: + websocket_send_header(client, WEBSOCKET_OPCODE_CONNECTION_CLOSE, 0); + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + break; + case WEBSOCKET_OPCODE_TEXT: + if(client->callback != NULL) { + client->step = WEBSERVER_CLIENT_WEBSOCKET_TEXT; + if(client->callback(client, buf) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + client->step = WEBSERVER_CLIENT_WEBSOCKET; + } else { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + break; + } + + return packet_length; +} + +uint8_t webserver_sync_receive(struct webserver_t *client, uint8_t *rbuffer, uint16_t size) { + if(client->step == WEBSERVER_CLIENT_READ_HEADER) { + if(http_parse_request(client, &rbuffer, &size) == 0) { + if(client->is_websocket == 1 && client->data.websockkey != NULL) { + client->is_websocket = 1; + client->lastping = millis(); + send_websocket_handshake(client, (char *)client->data.websockkey); + free(client->data.websockkey); + client->data.websockkey = NULL; + client->step = WEBSERVER_CLIENT_WEBSOCKET; + } + if(client->method == 1) { + client->step = WEBSERVER_CLIENT_ARGS; + if(client->reqtype == 0) { + client->readlen = 0; + if(http_parse_body(client, (char *)rbuffer, size) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + } + } else if(client->reqtype == 1) { + client->substep = 0; + if(http_parse_multipart_body(client, (unsigned char *)rbuffer, size) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + } + } + + if(client->readlen == client->totallen) { + client->step = WEBSERVER_CLIENT_WRITE; + } + + if(client->step == WEBSERVER_CLIENT_ARGS) { + return 1; + } + } else if(client->step != WEBSERVER_CLIENT_WEBSOCKET) { + client->step = WEBSERVER_CLIENT_WRITE; + } + } + } + + if(client->step == WEBSERVER_CLIENT_WEBSOCKET && size > 0) { + websocket_read(client, rbuffer, size); + } + + if(client->step == WEBSERVER_CLIENT_ARGS) { + if(client->reqtype == 0) { + if(http_parse_body(client, (char *)rbuffer, size) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + } + } else if(client->reqtype == 1) { + if(http_parse_multipart_body(client, (unsigned char *)rbuffer, size) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + } + } + if((client->readlen+2000) > client->totallen) { + char log_msg[256]; + sprintf_P(log_msg, "Readlen: %d, Totallen: %d", client->readlen, client->totallen); + log_message(log_msg); + } + if(client->readlen == client->totallen) { + client->step = WEBSERVER_CLIENT_WRITE; + } + } + return 0; +} + +err_t webserver_async_receive(void *arg, tcp_pcb *pcb, struct pbuf *data, err_t err) { + uint16_t size = 0; + uint8_t i = 0; + + if(data == NULL) { + for(i=0;ipayload; + size = b->len; + + for(i=0;istep == WEBSERVER_CLIENT_WRITE) { + if((client->totallen = tcp_sndbuf(client->pcb)) > 0) { + client->totallen -= 16; + if(client->callback != NULL) { + if(client->callback(client, NULL) == -1) { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + client->content++; + client->ptr = 0; + } else { + client->step = WEBSERVER_CLIENT_CLOSE; + return -1; + } + } + } + break; + } + } + tcp_recved(pcb, b->len); + b = b->next; + } + pbuf_free(data); + + return ERR_OK; +} + +#ifdef ESP8266 +err_t webserver_poll(void *arg, struct tcp_pcb *pcb) { + uint8_t i = 0; + for(i=0;i WEBSERVER_CLIENT_PING_INTERVAL) { + websocket_send_header(&clients[i].data, WEBSOCKET_OPCODE_PING, 0); + clients[i].data.lastping = millis(); + } + } + if((unsigned long)(millis() - clients[i].data.lastseen) > WEBSERVER_CLIENT_TIMEOUT) { + #ifdef ESP8266 + char log_msg[256]; + sprintf_P(log_msg, PSTR("Timeout webserver client: %s:%d"), clients[i].data.client->remoteIP().toString().c_str(), clients[i].data.client->remotePort()); + log_message(log_msg); + #endif + clients[i].data.step = WEBSERVER_CLIENT_CLOSE; + webserver_client_close(&clients[i].data); + } + break; + } + } + return ERR_OK; +} +#endif + +void webserver_reset_client(struct webserver_t *client) { +#ifdef ESP8266 + if(client->pcb != NULL) { + tcp_close(client->pcb); + client->pcb = NULL; + } + if(client->client != NULL) { + client->client->stop(); + delete client->client; + client->client = NULL; + } +#endif + + client->readlen = 0; + client->reqtype = 0; + client->method = 0; + client->async = 0; + client->totallen = 0; + client->step = 0; + client->substep = 0; + client->chunked = 0; + client->ptr = 0; + client->route = 0; + client->lastseen = 0; + client->lastping = 0; + client->content = 0; + client->is_websocket = 0; + client->userdata = NULL; + + struct sendlist_t *tmp = NULL; +#if WEBSERVER_MAX_SENDLIST == 0 + while(client->sendlist) { + tmp = client->sendlist; + client->sendlist = client->sendlist->next; + if(tmp->type == 0) { + #if WEBSERVER_SENDLIST_BUFSIZE == 0 + free(tmp->data.ptr); + #else + memset(&tmp->data.fixed, 0, WEBSERVER_SENDLIST_BUFSIZE); + #endif + } + tmp->data.ptr = NULL; + free(tmp); + } +#else + uint8_t i = 0; + for(i=0;isendlist[i]; + if(tmp->type == 0) { + #if WEBSERVER_SENDLIST_BUFSIZE == 0 + free(tmp->data.ptr); + #else + memset(&tmp->data.fixed, 0, WEBSERVER_SENDLIST_BUFSIZE); + #endif + } + tmp->data.ptr = NULL; + memset(tmp, 0, sizeof(struct sendlist_t)); + } +#endif + if(client->data.boundary != NULL) { + free(client->data.boundary); + client->data.boundary = NULL; + } + if(client->data.websockkey != NULL) { + free(client->data.websockkey); + client->data.websockkey = NULL; + } + +#if WEBSERVER_MAX_SENDLIST == 0 + client->sendlist = NULL; + client->sendlist_head = NULL; +#endif + client->data.boundary = NULL; + memset(&client->buffer, 0, WEBSERVER_BUFFER_SIZE); +} + +#ifdef ESP8266 +err_t webserver_client(void *arg, tcp_pcb *pcb, err_t err) { + uint8_t i = 0; + for(i=0;iremote_ip.addr).toString().c_str(), clients[i].data.pcb->remote_port); + log_message(log_msg); + + //tcp_nagle_disable(pcb); + tcp_recv(pcb, &webserver_async_receive); + tcp_sent(pcb, &webserver_sent); + tcp_poll(pcb, &webserver_poll, 1); + break; + } + } + return ERR_OK; +} +#endif + +void webserver_loop(void) { + uint16_t size = 0; + uint8_t i = 0; + + for(i=0;i WEBSERVER_CLIENT_PING_INTERVAL) { + websocket_send_header(&clients[i].data, WEBSOCKET_OPCODE_PING, 0); + clients[i].data.lastping = millis(); + } + } + if((unsigned long)(millis() - clients[i].data.lastseen) > WEBSERVER_CLIENT_TIMEOUT) { +#ifdef ESP8266 + char log_msg[256]; + sprintf_P(log_msg, PSTR("Timeout webserver client: %s:%d"), clients[i].data.client->remoteIP().toString().c_str(), clients[i].data.client->remotePort()); + log_message(log_msg); +#endif + clients[i].data.step = WEBSERVER_CLIENT_CLOSE; + } + + if(!clients[i].data.client->connected()) { + clients[i].data.step = WEBSERVER_CLIENT_CLOSE; + } + + switch(clients[i].data.step) { + case WEBSERVER_CLIENT_CONNECTING: { + if(clients[i].data.client->available()) { + clients[i].data.step = WEBSERVER_CLIENT_READ_HEADER; + } + clients[i].data.ptr = 0; + memset(&clients[i].data.buffer, 0, WEBSERVER_BUFFER_SIZE); + } break; + case WEBSERVER_CLIENT_ARGS: + case WEBSERVER_CLIENT_WEBSOCKET: + case WEBSERVER_CLIENT_READ_HEADER: { + if(clients[i].data.client->connected() || clients[i].data.client->available()) { + if(clients[i].data.client->available()) { + uint8_t *p = (uint8_t *)rbuffer; + size = clients[i].data.client->read( + p, + WEBSERVER_READ_SIZE + ); + } + } else if(!clients[i].data.client->connected()) { + clients[i].data.step = WEBSERVER_CLIENT_CLOSE; + } else { + continue; + } + if(size > 0) { + clients[i].data.lastseen = millis(); + webserver_sync_receive(&clients[i].data, rbuffer, size); + } + } break; + case WEBSERVER_CLIENT_WRITE: { + if(clients[i].data.callback != NULL) { + if(clients[i].data.step == WEBSERVER_CLIENT_WRITE) { + if(clients[i].data.callback(&clients[i].data, NULL) == -1) { + clients[i].data.step = WEBSERVER_CLIENT_CLOSE; + } else if(clients[i].data.content > 0) { + clients[i].data.step = WEBSERVER_CLIENT_SENDING; + } else { + clients[i].data.step = WEBSERVER_CLIENT_WRITE; + clients[i].data.content++; + } + } + clients[i].data.ptr = 0; + } else { + clients[i].data.step = WEBSERVER_CLIENT_CLOSE; + continue; + } + } break; + case WEBSERVER_CLIENT_SENDING: { + clients[i].data.totallen = MTU_SIZE; + /* + * Leave room for chunk overhead + */ + clients[i].data.totallen -= 16; + webserver_process_send(&clients[i].data); + } break; +#ifdef ESP8266 + case WEBSERVER_CLIENT_CLOSE: { + if(clients[i].data.callback != NULL) { + clients[i].data.callback(&clients[i].data, NULL); + } + + char log_msg[256]; + sprintf_P(log_msg, PSTR("Closing webserver client: %s:%d"), clients[i].data.client->remoteIP().toString().c_str(), clients[i].data.client->remotePort()); + log_message(log_msg); + + clients[i].data.client->stop(); + webserver_reset_client(&clients[i].data); + } break; +#endif + } + } + +#if defined(ESP8266) + if(sync_server.hasClient()) { + for(i=0;isetNoDelay(true); + clients[i].data.client->setTimeout(5000); + + char log_msg[256]; + sprintf_P(log_msg, PSTR("New webserver client: %s:%d"), clients[i].data.client->remoteIP().toString().c_str(), clients[i].data.client->remotePort()); + log_message(log_msg); + break; + } + } + } + } +#endif +} + +#ifdef ESP8266 +int8_t webserver_start(int port, webserver_cb_t *callback, uint8_t async) { + uint8_t i = 0; + + if(async == 1) { + for(i=0;i + #include "lwip/opt.h" + #include "lwip/tcp.h" + #include "lwip/inet.h" + #include "lwip/dns.h" + #include "lwip/init.h" + #include "lwip/errno.h" + #include + #include + #include +#endif + +#if !defined(err_t) && !defined(ESP8266) + #define err_t uint8_t +#endif + +#ifndef ESP8266 +typedef struct tcp_pcb { +} tcp_pcb; + +typedef struct pbuf { + unsigned int len; + void *payload; + struct pbuf *next; +} pbuf; +#endif + +typedef struct header_t { + unsigned char *buffer; + uint16_t ptr; +} header_t; + +struct webserver_t; +extern struct webserver_client_t clients[WEBSERVER_MAX_CLIENTS]; + +typedef int8_t (webserver_cb_t)(struct webserver_t *client, void *data); + +typedef struct arguments_t { + unsigned char *name; + unsigned char *value; + uint16_t len; +} arguments_t; + +typedef struct sendlist_t { +#if WEBSERVER_SENDLIST_BUFSIZE == 0 + union { + void *ptr; + } data; +#else + union { + void *ptr; + unsigned char fixed[WEBSERVER_SENDLIST_BUFSIZE]; + } data; +#endif + uint16_t type:1; + uint16_t size:15; +#if WEBSERVER_MAX_SENDLIST == 0 + struct sendlist_t *next; +#endif +} sendlist_t; + +#ifndef ESP8266 +struct WiFiClient { + int (*write)(unsigned char *, int i); + int (*write_P)(const char *, int i); + int (*available)(); + int (*connected)(); + int (*read)(uint8_t *buffer, int size); +}; + #define PGM_P unsigned char * +#endif + +typedef struct webserver_t { + tcp_pcb *pcb; + WiFiClient *client; + unsigned long lastseen; + unsigned long lastping; + uint8_t is_websocket:1; + uint8_t reqtype:1; + uint8_t async:1; + uint8_t method:1; + uint8_t chunked:4; + uint8_t step:4; + uint8_t substep:4; + uint16_t ptr; + uint32_t totallen; + uint32_t readlen; + uint16_t content; + uint8_t route; +#if WEBSERVER_MAX_SENDLIST == 0 + struct sendlist_t *sendlist; + struct sendlist_t *sendlist_head; +#else + struct sendlist_t sendlist[WEBSERVER_MAX_SENDLIST]; +#endif + webserver_cb_t *callback; + unsigned char buffer[WEBSERVER_BUFFER_SIZE]; + union { + char *boundary; + char *websockkey; + } data; + void *userdata; +} webserver_t; + +typedef struct webserver_client_t { + struct webserver_t data; +} webserver_client_t; + +typedef enum { + WEBSERVER_CLIENT_CONNECTING = 1, + WEBSERVER_CLIENT_REQUEST_METHOD, + WEBSERVER_CLIENT_REQUEST_URI, + WEBSERVER_CLIENT_READ_HEADER, + WEBSERVER_CLIENT_CREATE_HEADER, + WEBSERVER_CLIENT_WRITE, + WEBSERVER_CLIENT_WEBSOCKET, + WEBSERVER_CLIENT_WEBSOCKET_TEXT, + WEBSERVER_CLIENT_SENDING, + WEBSERVER_CLIENT_HEADER, + WEBSERVER_CLIENT_ARGS, + WEBSERVER_CLIENT_CLOSE, +} webserver_steps; + +enum { + WEBSOCKET_OPCODE_CONTINUATION = 0x0, + WEBSOCKET_OPCODE_TEXT = 0x1, + WEBSOCKET_OPCODE_BINARY = 0x2, + WEBSOCKET_OPCODE_CONNECTION_CLOSE = 0x8, + WEBSOCKET_OPCODE_PING = 0x9, + WEBSOCKET_OPCODE_PONG = 0xa +}; + +int8_t webserver_start(int port, webserver_cb_t *callback, uint8_t async); +void webserver_loop(void); +void websocket_write_all_P(PGM_P data, uint16_t data_len); +void websocket_write_all(char *data, uint16_t data_len); +void websocket_write_P(struct webserver_t *client, PGM_P data, uint16_t data_len); +void websocket_write(struct webserver_t *client, char *data, uint16_t data_len); +void websocket_send_header(struct webserver_t *client, uint8_t opcode, uint16_t data_len); +void webserver_send_content(struct webserver_t *client, char *buf, uint16_t len); +void webserver_send_content_P(struct webserver_t *client, PGM_P buf, uint16_t len); +err_t webserver_async_receive(void *arg, tcp_pcb *pcb, struct pbuf *data, err_t err); +uint8_t webserver_sync_receive(struct webserver_t *client, uint8_t *rbuffer, uint16_t size); +void webserver_loop(void); +int16_t urldecode(const unsigned char *src, int src_len, unsigned char *dst, int dst_len, int is_form_url_encoded); +int8_t webserver_send(struct webserver_t *client, uint16_t code, char *mimetype, uint16_t data_len); +void webserver_client_stop(struct webserver_t *client); +void webserver_reset_client(struct webserver_t *client); + +#endif diff --git a/HeishaMon/src/rules/function.cpp b/HeishaMon/src/rules/function.cpp new file mode 100755 index 00000000..20a1a979 --- /dev/null +++ b/HeishaMon/src/rules/function.cpp @@ -0,0 +1,45 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "rules.h" +#include "function.h" + +#include "functions/max.h" +#include "functions/min.h" +#include "functions/coalesce.h" +#include "functions/settimer.h" +#include "functions/isset.h" +#include "functions/round.h" + +struct rule_function_t rule_functions[] = { + { "max", rule_function_max_callback }, + { "min", rule_function_min_callback }, + { "coalesce", rule_function_coalesce_callback }, + { "settimer", rule_function_set_timer_callback }, + { "isset", rule_function_isset_callback }, + { "round", rule_function_round_callback } +}; + +unsigned int nr_rule_functions = sizeof(rule_functions)/sizeof(rule_functions[0]); diff --git a/HeishaMon/src/rules/function.h b/HeishaMon/src/rules/function.h new file mode 100755 index 00000000..efb55a2e --- /dev/null +++ b/HeishaMon/src/rules/function.h @@ -0,0 +1,22 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULE_FUNCTION_H_ +#define _RULE_FUNCTION_H_ + +#include "rules.h" /* rewrite */ + +struct rule_function_t { + const char *name; + int (*callback)(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret); +} __attribute__((packed)); + +extern struct rule_function_t rule_functions[]; +extern unsigned int nr_rule_functions; + +#endif diff --git a/HeishaMon/src/rules/functions/coalesce.cpp b/HeishaMon/src/rules/functions/coalesce.cpp new file mode 100755 index 00000000..95b13f5f --- /dev/null +++ b/HeishaMon/src/rules/functions/coalesce.cpp @@ -0,0 +1,74 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_function_coalesce_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret) { +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + *ret = obj->varstack.nrbytes; + + int i = 0; + for(i=0;ivarstack.buffer[argv[i]] == VNULL) { + continue; + } else { + switch(obj->varstack.buffer[argv[i]]) { + case VINTEGER: { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&obj->varstack.buffer[argv[i]]; + out->type = VINTEGER; + out->ret = 0; + out->value = val->value; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + return 0; + } break; + case VFLOAT: { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vfloat_t *val = (struct vm_vfloat_t *)&obj->varstack.buffer[argv[i]]; + out->type = VFLOAT; + out->ret = 0; + out->value = val->value; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + return 0; + } break; + /* LCOV_EXCL_START*/ + case VCHAR: { + exit(-1); + } break; + /* LCOV_EXCL_STOP*/ + default: { + + } break; + } + } + } + + return 0; +} diff --git a/HeishaMon/src/rules/functions/coalesce.h b/HeishaMon/src/rules/functions/coalesce.h new file mode 100755 index 00000000..64afe7c2 --- /dev/null +++ b/HeishaMon/src/rules/functions/coalesce.h @@ -0,0 +1,17 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_COALESCE_H_ +#define _RULES_COALESCE_H_ + +#include +#include "../rules.h" + +int rule_function_coalesce_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/functions/isset.cpp b/HeishaMon/src/rules/functions/isset.cpp new file mode 100755 index 00000000..2662548e --- /dev/null +++ b/HeishaMon/src/rules/functions/isset.cpp @@ -0,0 +1,54 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_function_isset_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret) { +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + if(argc != 1) { + return -1; + } + + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + switch(obj->varstack.buffer[argv[0]]) { + case VNULL: { + out->value = 0; + } break; + default: { + out->value = 1; + } break; + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} diff --git a/HeishaMon/src/rules/functions/isset.h b/HeishaMon/src/rules/functions/isset.h new file mode 100755 index 00000000..edb3d784 --- /dev/null +++ b/HeishaMon/src/rules/functions/isset.h @@ -0,0 +1,17 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_ISSET_H_ +#define _RULES_ISSET_H_ + +#include +#include "../rules.h" + +int rule_function_isset_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/functions/max.cpp b/HeishaMon/src/rules/functions/max.cpp new file mode 100755 index 00000000..8af0ff39 --- /dev/null +++ b/HeishaMon/src/rules/functions/max.cpp @@ -0,0 +1,56 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_function_max_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret) { +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + out->value = 0; + + int i = 0; + for(i=0;ivarstack.buffer[argv[i]]) { + case VINTEGER: { + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&obj->varstack.buffer[argv[i]]; + if(i == 0) { + out->value = val->value; + } else if(val->value > out->value) { + out->value = val->value; + } + } break; + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} diff --git a/HeishaMon/src/rules/functions/max.h b/HeishaMon/src/rules/functions/max.h new file mode 100755 index 00000000..aa661c09 --- /dev/null +++ b/HeishaMon/src/rules/functions/max.h @@ -0,0 +1,17 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_MAX_H_ +#define _RULES_MAX_H_ + +#include +#include "../rules.h" + +int rule_function_max_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/functions/min.cpp b/HeishaMon/src/rules/functions/min.cpp new file mode 100755 index 00000000..db02b9f6 --- /dev/null +++ b/HeishaMon/src/rules/functions/min.cpp @@ -0,0 +1,55 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_function_min_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret) { +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + int i = 0; + for(i=0;ivarstack.buffer[argv[i]]) { + case VINTEGER: { + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&obj->varstack.buffer[argv[i]]; + if(i == 0) { + out->value = val->value; + } else if(val->value < out->value) { + out->value = val->value; + } + } break; + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} diff --git a/HeishaMon/src/rules/functions/min.h b/HeishaMon/src/rules/functions/min.h new file mode 100755 index 00000000..13670fa3 --- /dev/null +++ b/HeishaMon/src/rules/functions/min.h @@ -0,0 +1,17 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_MIN_H_ +#define _RULES_MIN_H_ + +#include +#include "../rules.h" + +int rule_function_min_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/functions/round.cpp b/HeishaMon/src/rules/functions/round.cpp new file mode 100755 index 00000000..3c0f3ed6 --- /dev/null +++ b/HeishaMon/src/rules/functions/round.cpp @@ -0,0 +1,56 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_function_round_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret) { +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + if(argc != 1) { + return -1; + } + + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + switch(obj->varstack.buffer[argv[0]]) { + case VINTEGER: { + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&obj->varstack.buffer[argv[0]]; + out->value = val->value; + } break; + case VFLOAT: { + struct vm_vfloat_t *val = (struct vm_vfloat_t *)&obj->varstack.buffer[argv[0]]; + out->value = (int)val->value; + } break; + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} diff --git a/HeishaMon/src/rules/functions/round.h b/HeishaMon/src/rules/functions/round.h new file mode 100755 index 00000000..a3688255 --- /dev/null +++ b/HeishaMon/src/rules/functions/round.h @@ -0,0 +1,17 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_ROUND_H_ +#define _RULES_ROUND_H_ + +#include +#include "../rules.h" + +int rule_function_round_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/functions/settimer.cpp b/HeishaMon/src/rules/functions/settimer.cpp new file mode 100755 index 00000000..0ac204c0 --- /dev/null +++ b/HeishaMon/src/rules/functions/settimer.cpp @@ -0,0 +1,90 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/log.h" +#include "../../common/mem.h" +#include "../rules.h" +#include "../../common/timerqueue.h" + +int rule_function_set_timer_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret) { + struct timerqueue_t *node = NULL; + struct itimerval it_val; + int i = 0, x = 0, sec = 0, usec = 0, nr = 0; + + if(argc > 2) { + return -1; + } + + if(argc == 2) { + if(obj->varstack.buffer[argv[0]] != VINTEGER) { + return -1; + } + if(obj->varstack.buffer[argv[1]] != VINTEGER) { + return -1; + } + + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&obj->varstack.buffer[argv[0]]; + nr = val->value; + + val = (struct vm_vinteger_t *)&obj->varstack.buffer[argv[1]]; + + timerqueue_insert(val->value, 0, nr); + + logprintf_P(F("%s set timer #%d to %d seconds"), __FUNCTION__, nr, val->value); + } + + if(argc == 1) { + if(obj->varstack.buffer[argv[0]] != VINTEGER) { + return -1; + } + + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&obj->varstack.buffer[argv[0]]; + nr = val->value; + + *ret = obj->varstack.nrbytes; + + unsigned int size = 0; + + int a = 0; + for(a=0;anr == nr) { + size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + out->value = timerqueue[a]->sec; + break; + } + } + if(size == 0) { + size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + + struct vm_vnull_t *out = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VNULL; + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } + + return 0; +} diff --git a/HeishaMon/src/rules/functions/settimer.h b/HeishaMon/src/rules/functions/settimer.h new file mode 100755 index 00000000..1ee667e9 --- /dev/null +++ b/HeishaMon/src/rules/functions/settimer.h @@ -0,0 +1,18 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_SET_TIMER_H_ +#define _RULES_SET_TIMER_H_ + +#include +#include "../rules.h" + +int rule_function_set_timer_callback(struct rules_t *obj, uint16_t argc, uint16_t *argv, int *ret); +void inline timer_cb(void); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operator.cpp b/HeishaMon/src/rules/operator.cpp new file mode 100755 index 00000000..3b972fae --- /dev/null +++ b/HeishaMon/src/rules/operator.cpp @@ -0,0 +1,61 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../common/mem.h" +#include "operator.h" + +#include "operators/eq.h" +#include "operators/ne.h" +#include "operators/plus.h" +#include "operators/multiply.h" +#include "operators/and.h" +#include "operators/mod.h" +#include "operators/or.h" +#include "operators/divide.h" +#include "operators/ge.h" +#include "operators/gt.h" +#include "operators/lt.h" +#include "operators/le.h" +#include "operators/power.h" +#include "operators/minus.h" + +struct rule_operator_t rule_operators[] = { + { "==", 30, 1, rule_operator_eq_callback }, + { "!=", 30, 1, rule_operator_ne_callback }, + { "+", 60, 1, rule_operator_plus_callback }, + { "-", 60, 1, rule_operator_minus_callback }, + { "*", 70, 1, rule_operator_multiply_callback }, + { "%", 70, 1, rule_operator_mod_callback }, + { "&&", 20, 1, rule_operator_and_callback }, + { "||", 10, 1, rule_operator_or_callback }, + { "/", 70, 1, rule_operator_divide_callback }, + { ">=", 30, 1, rule_operator_ge_callback }, + { "<=", 30, 1, rule_operator_le_callback }, + { "<", 30, 1, rule_operator_lt_callback }, + { ">", 30, 1, rule_operator_gt_callback }, + { "^", 80, 2, rule_operator_power_callback }, +}; + +unsigned int nr_rule_operators = sizeof(rule_operators)/sizeof(rule_operators[0]); \ No newline at end of file diff --git a/HeishaMon/src/rules/operator.h b/HeishaMon/src/rules/operator.h new file mode 100755 index 00000000..3f303573 --- /dev/null +++ b/HeishaMon/src/rules/operator.h @@ -0,0 +1,24 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULE_OPERATOR_H_ +#define _RULE_OPERATOR_H_ + +#include "rules.h" /* rewrite */ + +struct rule_operator_t { + const char *name; + int precedence; + int associativity; + int (*callback)(struct rules_t *obj, int a, int b, int *ret); +} __attribute__((packed)); + +extern struct rule_operator_t rule_operators[]; +extern unsigned int nr_rule_operators; + +#endif diff --git a/HeishaMon/src/rules/operators/and.cpp b/HeishaMon/src/rules/operators/and.cpp new file mode 100755 index 00000000..e36a0fce --- /dev/null +++ b/HeishaMon/src/rules/operators/and.cpp @@ -0,0 +1,110 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + + +int rule_operator_and_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + /* + * Values can only be equal when the type matches + */ + switch(obj->varstack.buffer[a]) { + case VNULL: { + out->value = 0; + } break; + case VINTEGER: { + struct vm_vinteger_t *n = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + if(n->value > 0) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d\n", __FUNCTION__, n->value); +#endif +/* LCOV_EXCL_STOP*/ + + } break; + case VFLOAT: { + struct vm_vfloat_t *n = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + if(n->value > 0) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + out->value = 1; + } break; + /* LCOV_EXCL_STOP*/ + } + switch(obj->varstack.buffer[b]) { + case VNULL: { + out->value = 0; + } break; + case VINTEGER: { + struct vm_vinteger_t *n = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + if(n->value > 0 && out->value == 1) { + out->value = 1; + } else { + out->value = 0; + } + +#ifdef DEBUG + printf("%s %d\n", __FUNCTION__, n->value); +#endif + + } break; + case VFLOAT: { + struct vm_vfloat_t *n = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + if(n->value > 0 && out->value == 1) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + out->value = 1; + } break; + /* LCOV_EXCL_STOP*/ + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/and.h b/HeishaMon/src/rules/operators/and.h new file mode 100755 index 00000000..2cc4f98d --- /dev/null +++ b/HeishaMon/src/rules/operators/and.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_AND_H_ +#define _RULES_AND_H_ + +#include "../rules.h" + +int rule_operator_and_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/divide.cpp b/HeishaMon/src/rules/operators/divide.cpp new file mode 100755 index 00000000..bc00c1e6 --- /dev/null +++ b/HeishaMon/src/rules/operators/divide.cpp @@ -0,0 +1,82 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_divide_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + if((obj->varstack.buffer[a]) == VNULL || (obj->varstack.buffer[b]) == VNULL) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + + struct vm_vnull_t *out = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + out->ret = 0; + out->type = VNULL; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VCHAR || (obj->varstack.buffer[b]) == VCHAR) { + } else if((obj->varstack.buffer[a]) == VFLOAT || (obj->varstack.buffer[b]) == VFLOAT) { + } else { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + float var = (float)na->value / (float)nb->value; + float nr = 0; + + if(modff(var, &nr) == 0) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + out->ret = 0; + out->type = VINTEGER; + out->value = (int)var; + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + out->ret = 0; + out->type = VFLOAT; + out->value = var; + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } + } + + return 0; +} diff --git a/HeishaMon/src/rules/operators/divide.h b/HeishaMon/src/rules/operators/divide.h new file mode 100755 index 00000000..4296853f --- /dev/null +++ b/HeishaMon/src/rules/operators/divide.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_DIVIDE_H_ +#define _RULES_DIVIDE_H_ + +#include "../rules.h" + +int rule_operator_divide_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/eq.cpp b/HeishaMon/src/rules/operators/eq.cpp new file mode 100755 index 00000000..b533e4eb --- /dev/null +++ b/HeishaMon/src/rules/operators/eq.cpp @@ -0,0 +1,93 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_eq_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + /* + * Values can only be equal when the type matches + */ + if((obj->varstack.buffer[a]) != (obj->varstack.buffer[b])) { + out->value = 0; + } else { + switch(obj->varstack.buffer[a]) { + case VNULL: { + out->value = 1; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VINTEGER: { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + if(na->value == nb->value) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + } break; + case VFLOAT: { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + if(fabs((float)na->value-(float)nb->value) < EPSILON) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[a]; + struct vm_vchar_t *nb = (struct vm_vchar_t *)&obj->varstack.buffer[b]; + if(strcmp((char *)na->value, (char *)nb->value) == 0) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* LCOV_EXCL_STOP*/ + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/eq.h b/HeishaMon/src/rules/operators/eq.h new file mode 100755 index 00000000..1fa8c244 --- /dev/null +++ b/HeishaMon/src/rules/operators/eq.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_EQ_H_ +#define _RULES_EQ_H_ + +#include "../rules.h" + +int rule_operator_eq_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/ge.cpp b/HeishaMon/src/rules/operators/ge.cpp new file mode 100755 index 00000000..80e5af35 --- /dev/null +++ b/HeishaMon/src/rules/operators/ge.cpp @@ -0,0 +1,129 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_ge_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + /* + * Values can only be equal when the type matches + */ + if( + ( + obj->varstack.buffer[a] == VNULL || obj->varstack.buffer[a] == VCHAR || + obj->varstack.buffer[b] == VNULL || obj->varstack.buffer[b] == VCHAR + ) + && + (obj->varstack.buffer[a] != obj->varstack.buffer[b]) + ) { + out->value = 0; + } else { + switch(obj->varstack.buffer[a]) { + case VNULL: { + out->value = 0; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VINTEGER: { + float av = 0.0; + float bv = 0.0; + if(obj->varstack.buffer[a] == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + av = (float)na->value; + } + if(obj->varstack.buffer[b] == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + bv = nb->value; + } + if(obj->varstack.buffer[b] == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + bv = (float)nb->value; + } + if(av > bv || fabs(av-bv) < EPSILON) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, av, bv); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VFLOAT: { + float av = 0.0; + float bv = 0.0; + if(obj->varstack.buffer[a] == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + av = na->value; + } + if(obj->varstack.buffer[b] == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + bv = nb->value; + } + if(obj->varstack.buffer[b] == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + bv = (float)nb->value; + } + if(av > bv || fabs(av-bv) < EPSILON) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, av, bv); +#endif +/* LCOV_EXCL_STOP*/ + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[a]; + struct vm_vchar_t *nb = (struct vm_vchar_t *)&obj->varstack.buffer[b]; + if(na->value >= nb->value) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* LCOV_EXCL_STOP*/ + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/ge.h b/HeishaMon/src/rules/operators/ge.h new file mode 100755 index 00000000..f9b38fc2 --- /dev/null +++ b/HeishaMon/src/rules/operators/ge.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_GE_H_ +#define _RULES_GE_H_ + +#include "../rules.h" + +int rule_operator_ge_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/gt.cpp b/HeishaMon/src/rules/operators/gt.cpp new file mode 100755 index 00000000..e54e880a --- /dev/null +++ b/HeishaMon/src/rules/operators/gt.cpp @@ -0,0 +1,129 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_gt_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + /* + * Values can only be equal when the type matches + */ + if( + ( + obj->varstack.buffer[a] == VNULL || obj->varstack.buffer[a] == VCHAR || + obj->varstack.buffer[b] == VNULL || obj->varstack.buffer[b] == VCHAR + ) + && + (obj->varstack.buffer[a] != obj->varstack.buffer[b]) + ) { + out->value = 0; + } else { + switch(obj->varstack.buffer[a]) { + case VNULL: { + out->value = 0; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VINTEGER: { + float av = 0.0; + float bv = 0.0; + if(obj->varstack.buffer[a] == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + av = (float)na->value; + } + if(obj->varstack.buffer[b] == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + bv = nb->value; + } + if(obj->varstack.buffer[b] == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + bv = (float)nb->value; + } + if(av > bv) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, av, bv); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VFLOAT: { + float av = 0.0; + float bv = 0.0; + if(obj->varstack.buffer[a] == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + av = na->value; + } + if(obj->varstack.buffer[b] == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + bv = nb->value; + } + if(obj->varstack.buffer[b] == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + bv = (float)nb->value; + } + if(av > bv) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, av, bv); +#endif +/* LCOV_EXCL_STOP*/ + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[a]; + struct vm_vchar_t *nb = (struct vm_vchar_t *)&obj->varstack.buffer[b]; + if(na->value > nb->value) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* LCOV_EXCL_STOP*/ + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/gt.h b/HeishaMon/src/rules/operators/gt.h new file mode 100755 index 00000000..f2c39708 --- /dev/null +++ b/HeishaMon/src/rules/operators/gt.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_GT_H_ +#define _RULES_GT_H_ + +#include "../rules.h" + +int rule_operator_gt_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/le.cpp b/HeishaMon/src/rules/operators/le.cpp new file mode 100755 index 00000000..86f6a005 --- /dev/null +++ b/HeishaMon/src/rules/operators/le.cpp @@ -0,0 +1,129 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_le_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + /* + * Values can only be equal when the type matches + */ + if( + ( + obj->varstack.buffer[a] == VNULL || obj->varstack.buffer[a] == VCHAR || + obj->varstack.buffer[b] == VNULL || obj->varstack.buffer[b] == VCHAR + ) + && + (obj->varstack.buffer[a] != obj->varstack.buffer[b]) + ) { + out->value = 0; + } else { + switch(obj->varstack.buffer[a]) { + case VNULL: { + out->value = 0; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VINTEGER: { + float av = 0.0; + float bv = 0.0; + if(obj->varstack.buffer[a] == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + av = (float)na->value; + } + if(obj->varstack.buffer[b] == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + bv = nb->value; + } + if(obj->varstack.buffer[b] == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + bv = (float)nb->value; + } + if(av < bv || fabs(av-bv) < EPSILON) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, av, bv); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VFLOAT: { + float av = 0.0; + float bv = 0.0; + if(obj->varstack.buffer[a] == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + av = na->value; + } + if(obj->varstack.buffer[b] == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + bv = nb->value; + } + if(obj->varstack.buffer[b] == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + bv = (float)nb->value; + } + if(av < bv || fabs(av-bv) < EPSILON) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, av, bv); +#endif +/* LCOV_EXCL_STOP*/ + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[a]; + struct vm_vchar_t *nb = (struct vm_vchar_t *)&obj->varstack.buffer[b]; + if(na->value <= nb->value) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* LCOV_EXCL_STOP*/ + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/le.h b/HeishaMon/src/rules/operators/le.h new file mode 100755 index 00000000..0ac2a9ed --- /dev/null +++ b/HeishaMon/src/rules/operators/le.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_LE_H_ +#define _RULES_LE_H_ + +#include "../rules.h" + +int rule_operator_le_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/lt.cpp b/HeishaMon/src/rules/operators/lt.cpp new file mode 100755 index 00000000..70c87a26 --- /dev/null +++ b/HeishaMon/src/rules/operators/lt.cpp @@ -0,0 +1,129 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_lt_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + /* + * Values can only be equal when the type matches + */ + if( + ( + obj->varstack.buffer[a] == VNULL || obj->varstack.buffer[a] == VCHAR || + obj->varstack.buffer[b] == VNULL || obj->varstack.buffer[b] == VCHAR + ) + && + (obj->varstack.buffer[a] != obj->varstack.buffer[b]) + ) { + out->value = 0; + } else { + switch(obj->varstack.buffer[a]) { + case VNULL: { + out->value = 0; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VINTEGER: { + float av = 0.0; + float bv = 0.0; + if(obj->varstack.buffer[a] == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + av = (float)na->value; + } + if(obj->varstack.buffer[b] == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + bv = nb->value; + } + if(obj->varstack.buffer[b] == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + bv = (float)nb->value; + } + if(av < bv) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, av, bv); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VFLOAT: { + float av = 0.0; + float bv = 0.0; + if(obj->varstack.buffer[a] == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + av = na->value; + } + if(obj->varstack.buffer[b] == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + bv = nb->value; + } + if(obj->varstack.buffer[b] == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + bv = (float)nb->value; + } + if(av < bv) { + out->value = 1; + } else { + out->value = 0; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, av, bv); +#endif +/* LCOV_EXCL_STOP*/ + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[a]; + struct vm_vchar_t *nb = (struct vm_vchar_t *)&obj->varstack.buffer[b]; + if(na->value >= nb->value) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* LCOV_EXCL_STOP*/ + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/lt.h b/HeishaMon/src/rules/operators/lt.h new file mode 100755 index 00000000..0ad7fbcd --- /dev/null +++ b/HeishaMon/src/rules/operators/lt.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_LT_H_ +#define _RULES_LT_H_ + +#include "../rules.h" + +int rule_operator_lt_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/minus.cpp b/HeishaMon/src/rules/operators/minus.cpp new file mode 100755 index 00000000..09a156b6 --- /dev/null +++ b/HeishaMon/src/rules/operators/minus.cpp @@ -0,0 +1,118 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_minus_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + if((obj->varstack.buffer[a]) == VNULL || (obj->varstack.buffer[b]) == VNULL) { + struct vm_vnull_t *out = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + out->ret = 0; + out->type = VNULL; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VCHAR || (obj->varstack.buffer[b]) == VCHAR) { + } else if((obj->varstack.buffer[a]) == VFLOAT && (obj->varstack.buffer[b]) == VFLOAT) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + + out->ret = 0; + out->type = VFLOAT; + out->value = na->value - nb->value; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VFLOAT || (obj->varstack.buffer[b]) == VFLOAT) { + float f = 0; + int i = 0; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VFLOAT; + + if((obj->varstack.buffer[a]) == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + f = na->value; + } else if((obj->varstack.buffer[a]) == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + i = na->value; + } + if((obj->varstack.buffer[b]) == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + f = nb->value; + out->value = i - f; + } else if((obj->varstack.buffer[b]) == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + i = nb->value; + out->value = f - i; + } + + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %d\n", __FUNCTION__, f, i); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + + out->ret = 0; + out->type = VINTEGER; + out->value = na->value - nb->value; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } + + return 0; +} diff --git a/HeishaMon/src/rules/operators/minus.h b/HeishaMon/src/rules/operators/minus.h new file mode 100755 index 00000000..a4add058 --- /dev/null +++ b/HeishaMon/src/rules/operators/minus.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_MINUS_H_ +#define _RULES_MINUS_H_ + +#include "../rules.h" + +int rule_operator_minus_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/mod.cpp b/HeishaMon/src/rules/operators/mod.cpp new file mode 100755 index 00000000..f94c494d --- /dev/null +++ b/HeishaMon/src/rules/operators/mod.cpp @@ -0,0 +1,119 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_mod_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + if((obj->varstack.buffer[a]) == VNULL || (obj->varstack.buffer[b]) == VNULL) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + + struct vm_vnull_t *out = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + out->ret = 0; + out->type = VNULL; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VCHAR || (obj->varstack.buffer[b]) == VCHAR) { + } else if((obj->varstack.buffer[a]) == VFLOAT && (obj->varstack.buffer[b]) == VFLOAT) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + + out->ret = 0; + out->type = VFLOAT; + out->value = fmod(na->value, nb->value); + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VFLOAT || (obj->varstack.buffer[b]) == VFLOAT) { + + float f = 0; + int i = 0; + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VFLOAT; + + if((obj->varstack.buffer[a]) == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + f = na->value; + } else if((obj->varstack.buffer[a]) == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + i = na->value; + out->value = fmodf(f, (float)i); + } + if((obj->varstack.buffer[b]) == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + f = nb->value; + out->value = fmodf(f, (float)i); + } else if((obj->varstack.buffer[b]) == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + i = nb->value; + out->value = fmodf((float)i, f); + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %d\n", __FUNCTION__, f, i); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + + out->ret = 0; + out->type = VINTEGER; + out->value = na->value % nb->value; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } + + return 0; +} diff --git a/HeishaMon/src/rules/operators/mod.h b/HeishaMon/src/rules/operators/mod.h new file mode 100755 index 00000000..40aa9f4c --- /dev/null +++ b/HeishaMon/src/rules/operators/mod.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_MOD_H_ +#define _RULES_MOD_H_ + +#include "../rules.h" + +int rule_operator_mod_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/multiply.cpp b/HeishaMon/src/rules/operators/multiply.cpp new file mode 100755 index 00000000..6cb73202 --- /dev/null +++ b/HeishaMon/src/rules/operators/multiply.cpp @@ -0,0 +1,98 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_multiply_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + if((obj->varstack.buffer[a]) == VNULL || (obj->varstack.buffer[b]) == VNULL) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + + struct vm_vnull_t *out = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + out->ret = 0; + out->type = VNULL; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VCHAR || (obj->varstack.buffer[b]) == VCHAR) { + } else if((obj->varstack.buffer[a]) == VFLOAT || (obj->varstack.buffer[b]) == VFLOAT) { + float f = 0; + int i = 0; + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + if((obj->varstack.buffer[a]) == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + f = na->value; + } else if((obj->varstack.buffer[a]) == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + i = na->value; + } + if((obj->varstack.buffer[b]) == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + f = nb->value; + } else if((obj->varstack.buffer[b]) == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + i = nb->value; + } + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VFLOAT; + out->value = i * f; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %d\n", __FUNCTION__, f, i); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + out->ret = 0; + out->type = VINTEGER; + out->value = na->value * nb->value; + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } + + + return 0; +} diff --git a/HeishaMon/src/rules/operators/multiply.h b/HeishaMon/src/rules/operators/multiply.h new file mode 100755 index 00000000..2b90cc7d --- /dev/null +++ b/HeishaMon/src/rules/operators/multiply.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_MULTIPLY_H_ +#define _RULES_MULTIPLY_H_ + +#include "../rules.h" + +int rule_operator_multiply_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/ne.cpp b/HeishaMon/src/rules/operators/ne.cpp new file mode 100755 index 00000000..5f4d939d --- /dev/null +++ b/HeishaMon/src/rules/operators/ne.cpp @@ -0,0 +1,99 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_ne_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + /* + * Values can only be equal when the type matches + */ + if((obj->varstack.buffer[a]) != (obj->varstack.buffer[b])) { + out->value = 1; + } else { + switch(obj->varstack.buffer[a]) { + case VNULL: { + out->value = 1; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VINTEGER: { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + if(na->value == nb->value) { + out->value = 0; + } else { + out->value = 1; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + } break; + case VFLOAT: { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + if(fabs((float)na->value-(float)nb->value) < EPSILON) { + out->value = 0; + } else { + out->value = 1; + } + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + struct vm_vchar_t *na = (struct vm_vchar_t *)&obj->varstack.buffer[a]; + struct vm_vchar_t *nb = (struct vm_vchar_t *)&obj->varstack.buffer[b]; + if(strcmp((char *)na->value, (char *)nb->value) == 0) { + out->value = 0; + } else { + out->value = 1; + } + } break; + /* LCOV_EXCL_STOP*/ + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/ne.h b/HeishaMon/src/rules/operators/ne.h new file mode 100755 index 00000000..783a6a6d --- /dev/null +++ b/HeishaMon/src/rules/operators/ne.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_NE_H_ +#define _RULES_NE_H_ + +#include "../rules.h" + +int rule_operator_ne_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/or.cpp b/HeishaMon/src/rules/operators/or.cpp new file mode 100755 index 00000000..ceec2adf --- /dev/null +++ b/HeishaMon/src/rules/operators/or.cpp @@ -0,0 +1,112 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + + +int rule_operator_or_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VINTEGER; + + if(obj->varstack.buffer[a] == VNULL && obj->varstack.buffer[b] == VNULL) { + out->value = 0; +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + } else { + /* + * Values can only be equal when the type matches + */ + switch(obj->varstack.buffer[a]) { + case VINTEGER: { + struct vm_vinteger_t *n = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + if(n->value > 0) { + out->value = 1; + } else { + out->value = 0; + } +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d\n", __FUNCTION__, n->value); +#endif +/* LCOV_EXCL_STOP*/ + + } break; + case VFLOAT: { + struct vm_vfloat_t *n = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + if(n->value > 0) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + out->value = 1; + } break; + /* LCOV_EXCL_STOP*/ + } + switch(obj->varstack.buffer[b]) { + case VINTEGER: { + struct vm_vinteger_t *n = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + if(n->value > 0 || out->value == 1) { + out->value = 1; + } else { + out->value = 0; + } +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d\n", __FUNCTION__, n->value); +#endif +/* LCOV_EXCL_STOP*/ + } break; + case VFLOAT: { + struct vm_vfloat_t *n = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + if(n->value > 0 || out->value == 1) { + out->value = 1; + } else { + out->value = 0; + } + } break; + /* + * FIXME + */ + /* LCOV_EXCL_START*/ + case VCHAR: { + out->value = 1; + } break; + /* LCOV_EXCL_STOP*/ + } + } + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + + return 0; +} \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/or.h b/HeishaMon/src/rules/operators/or.h new file mode 100755 index 00000000..fcd6aeb9 --- /dev/null +++ b/HeishaMon/src/rules/operators/or.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_OR_H_ +#define _RULES_OR_H_ + +#include "../rules.h" + +int rule_operator_or_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/plus.cpp b/HeishaMon/src/rules/operators/plus.cpp new file mode 100755 index 00000000..e793047c --- /dev/null +++ b/HeishaMon/src/rules/operators/plus.cpp @@ -0,0 +1,116 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_plus_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + if((obj->varstack.buffer[a]) == VNULL || (obj->varstack.buffer[b]) == VNULL) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + + struct vm_vnull_t *out = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + out->ret = 0; + out->type = VNULL; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VCHAR || (obj->varstack.buffer[b]) == VCHAR) { + } else if((obj->varstack.buffer[a]) == VFLOAT && (obj->varstack.buffer[b]) == VFLOAT) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + + out->ret = 0; + out->type = VFLOAT; + out->value = na->value + nb->value; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %g\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VFLOAT || (obj->varstack.buffer[b]) == VFLOAT) { + float f = 0; + int i = 0; + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + if((obj->varstack.buffer[a]) == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + f = na->value; + } else if((obj->varstack.buffer[a]) == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + i = na->value; + } + if((obj->varstack.buffer[b]) == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + f = nb->value; + } else if((obj->varstack.buffer[b]) == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + i = nb->value; + } + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + out->ret = 0; + out->type = VFLOAT; + out->value = i + f; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %d\n", __FUNCTION__, f, i); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + + out->ret = 0; + out->type = VINTEGER; + out->value = na->value + nb->value; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } + + return 0; +} diff --git a/HeishaMon/src/rules/operators/plus.h b/HeishaMon/src/rules/operators/plus.h new file mode 100755 index 00000000..ecfa361a --- /dev/null +++ b/HeishaMon/src/rules/operators/plus.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_PLUS_H_ +#define _RULES_PLUS_H_ + +#include "../rules.h" + +int rule_operator_plus_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/operators/power.cpp b/HeishaMon/src/rules/operators/power.cpp new file mode 100755 index 00000000..be7578ff --- /dev/null +++ b/HeishaMon/src/rules/operators/power.cpp @@ -0,0 +1,116 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#include +#include +#include +#include + +#include "../function.h" +#include "../../common/mem.h" +#include "../rules.h" + +int rule_operator_power_callback(struct rules_t *obj, int a, int b, int *ret) { + *ret = obj->varstack.nrbytes; + + if((obj->varstack.buffer[a]) == VNULL || (obj->varstack.buffer[b]) == VNULL) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + + struct vm_vnull_t *out = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + out->ret = 0; + out->type = VNULL; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s NULL\n", __FUNCTION__); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VCHAR || (obj->varstack.buffer[b]) == VCHAR) { + } else if((obj->varstack.buffer[a]) == VFLOAT || (obj->varstack.buffer[b]) == VFLOAT) { + float f = 0; + int i = 0; + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *out = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + if((obj->varstack.buffer[a]) == VFLOAT) { + struct vm_vfloat_t *na = (struct vm_vfloat_t *)&obj->varstack.buffer[a]; + f = na->value; + } else if((obj->varstack.buffer[a]) == VINTEGER) { + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + i = na->value; + } + if((obj->varstack.buffer[b]) == VFLOAT) { + struct vm_vfloat_t *nb = (struct vm_vfloat_t *)&obj->varstack.buffer[b]; + if((obj->varstack.buffer[a]) == VFLOAT) { + out->value = pow(f, nb->value); +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("1 %s %g %g\n", __FUNCTION__,f, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + } else if((obj->varstack.buffer[a]) == VINTEGER) { + out->value = pow(i, nb->value); +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("2 %s %d %g\n", __FUNCTION__,i, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + } + } else if((obj->varstack.buffer[b]) == VINTEGER) { + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + out->value = pow(f, nb->value); +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("3 %s %g %d\n", __FUNCTION__,f, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + } + + out->ret = 0; + out->type = VFLOAT; + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("%s %g %d\n", __FUNCTION__, f, i); +#endif +/* LCOV_EXCL_STOP*/ + + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } else if((obj->varstack.buffer[a]) == VINTEGER && (obj->varstack.buffer[b]) == VINTEGER) { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *out = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + + struct vm_vinteger_t *na = (struct vm_vinteger_t *)&obj->varstack.buffer[a]; + struct vm_vinteger_t *nb = (struct vm_vinteger_t *)&obj->varstack.buffer[b]; + out->ret = 0; + out->type = VINTEGER; + out->value = pow(na->value, nb->value); + +/* LCOV_EXCL_START*/ +#ifdef DEBUG + printf("4 %s %d %d\n", __FUNCTION__, na->value, nb->value); +#endif +/* LCOV_EXCL_STOP*/ + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } + + + return 0; +} diff --git a/HeishaMon/src/rules/operators/power.h b/HeishaMon/src/rules/operators/power.h new file mode 100755 index 00000000..52f27035 --- /dev/null +++ b/HeishaMon/src/rules/operators/power.h @@ -0,0 +1,16 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifndef _RULES_POWER_H_ +#define _RULES_POWER_H_ + +#include "../rules.h" + +int rule_operator_power_callback(struct rules_t *obj, int a, int b, int *ret); + +#endif \ No newline at end of file diff --git a/HeishaMon/src/rules/rules.cpp b/HeishaMon/src/rules/rules.cpp new file mode 100755 index 00000000..5fcde699 --- /dev/null +++ b/HeishaMon/src/rules/rules.cpp @@ -0,0 +1,4619 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + +#ifdef ESP8266 + #pragma GCC diagnostic warning "-fpermissive" +#endif + +#ifndef ESP8266 + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include + #include +#else + #include +#endif +#include + +#include "../common/mem.h" +#include "../common/log.h" +#include "../common/strnicmp.h" +#include "../common/mem.h" +#include "../common/log.h" +#include "rules.h" +#include "operator.h" +#include "function.h" + +struct vm_cache_t { + uint8_t type; + uint16_t step; + uint16_t start; + uint16_t end; +} __attribute__((packed)) **vmcache; +static unsigned int nrcache = 0; + +/*LCOV_EXCL_START*/ +#ifdef DEBUG +static void print_tree(struct rules_t *obj); +static void print_bytecode(struct rules_t *obj); +#endif +/*LCOV_EXCL_STOP*/ + +unsigned int alignedvarstack(int v) { +#ifdef ESP8266 + return (v + MAX_VARSTACK_NODE_SIZE) - ((v + MAX_VARSTACK_NODE_SIZE) % MAX_VARSTACK_NODE_SIZE); +#else + return v; +#endif +} + +void rules_gc(struct rules_t ***rules, unsigned int nrrules) { + unsigned int i = 0; + for(i=0;iast.buffer); + // FREE((*rules)[i]->varstack.buffer); + // FREE((*rules)[i]); + } + // FREE((*rules)); + // (*rules) = NULL; + +#ifndef ESP8266 + /* + * Should never happen + */ + /* LCOV_EXCL_START*/ + for(i=0;i 0 && nrdot == 0 && text[*pos] == '.') + ) + ) { + if(text[*pos] == '.') { + nrdot++; + } + (*pos)++; + i++; + } + return 0; + } else { + return -1; + } +} + +static int lexer_parse_string(char *text, unsigned int *pos) { + unsigned int len = strlen(&text[*pos]) + *pos; + while(*pos <= len && + text[*pos] != ' ' && + text[*pos] != ',' && + text[*pos] != ';' && + text[*pos] != '(' && + text[*pos] != ')') { + (*pos)++; + } + return 0; +} + +// static int lexer_parse_quoted_string(struct rules_t *obj, struct lexer_t *lexer, int *start) { + + // int x = lexer->current_char[0]; + // if(lexer->current_char[0] == '\'' || lexer->current_char[0] == '"') { + // lexer->text[lexer->pos] = '\0'; + // } + // *start = ++lexer->pos; + // if(lexer->pos < lexer->len) { + + // while(lexer->pos < lexer->len) { + // if(x != '\'' && lexer->pos > 0 && lexer->text[lexer->pos] == '\'') { + // /* + // * Skip escape + // */ + // lexer->current_char = &lexer->text[lexer->(*pos)++]; + // } else if(x == '\'' && lexer->pos > 0 && lexer->text[lexer->pos] == '\\' && lexer->text[lexer->pos+1] == '\'') { + // /* + // * Remove escape + // */ + // memmove(&lexer->text[lexer->pos], &lexer->text[lexer->pos+1], lexer->len-lexer->pos-1); + // lexer->text[lexer->len-1] = '\0'; + + // lexer->current_char = &lexer->text[lexer->(*pos)++]; + // lexer->len--; + // } else if(x == '"' && lexer->pos > 0 && lexer->text[lexer->pos] == '\\' && lexer->text[lexer->pos+1] == '"') { + // /* + // * Remove escape + // */ + // memmove(&lexer->text[lexer->pos], &lexer->text[lexer->pos+1], lexer->len-lexer->pos-1); + // lexer->text[lexer->len-1] = '\0'; + + // lexer->current_char = &lexer->text[lexer->(*pos)++]; + // lexer->len--; + // } else if(x != '"' && lexer->pos > 0 && lexer->text[lexer->pos] == '"') { + // /* + // * Skip escape + // */ + // lexer->current_char = &lexer->text[lexer->(*pos)++]; + // } else if(lexer->text[lexer->pos] == x) { + // break; + // } else { + // lexer->(*pos)++; + // } + // } + + // if(lexer->current_char[0] != '"' && lexer->current_char[0] != '\'') { + // printf("err: %s %d\n", __FUNCTION__, __LINE__); + // exit(-1); + // } else if(lexer->pos <= lexer->len) { + // lexer_isolate_token(obj, lexer, x, -1); + // return 0; + // } else { + // return -1; + // } + // } + // return -1; +// } + +static int lexer_parse_skip_characters(char *text, unsigned int len, unsigned int *pos) { + while(*pos <= len && + (text[*pos] == ' ' || + text[*pos] == '\n' || + text[*pos] == '\t' || + text[*pos] == '\r')) { + (*pos)++; + } + return 0; +} + +static int rule_prepare(char **text, unsigned int *nrbytes, unsigned int *len) { + unsigned int pos = 0, nrblocks = 0, tpos = 0; + + *nrbytes = alignedbytes(sizeof(struct vm_tstart_t)); +#ifdef DEBUG + printf("TSTART: %lu\n", sizeof(struct vm_tstart_t)); +#endif + + while(pos < *len) { + lexer_parse_skip_characters(*text, *len, &pos); + + // if(lexer->current_char[0] == '\'' || lexer->current_char[0] == '"') { + // ret = lexer_parse_quoted_string(obj, lexer, &tpos); + // type = TSTRING; + // lexer->(*pos)++; + // if(lexer->text[lexer->pos] == ' ') { + // lexer->text[lexer->pos] = '\0'; + // } else { + // printf("err: %s %d\n", __FUNCTION__, __LINE__); + // exit(-1); + // } + // } else + + if(isdigit((*text)[pos]) || ((*text)[pos] == '-' && pos < *len && isdigit((*text)[(pos)+1]))) { + int newlen = 0; + lexer_parse_number(&(*text)[pos], &newlen); + + char tmp = (*text)[pos+newlen]; + (*text)[pos+newlen] = 0; + + float var = atof((char *)&(*text)[pos]); + float nr = 0; + + if(modff(var, &nr) == 0) { + /* + * This range of integers + * take less bytes when stored + * as ascii characters. + */ + if(var < 100 && var > -9) { +#ifdef DEBUG + printf("TNUMBER: %lu\n", sizeof(struct vm_tnumber_t)+newlen+1); +#endif + *nrbytes += alignedbytes(sizeof(struct vm_tnumber_t)+newlen+1); + } else { +#ifdef DEBUG + printf("TNUMBER: %lu\n", sizeof(struct vm_vinteger_t)); +#endif + *nrbytes += alignedbytes(sizeof(struct vm_vinteger_t)); + } + } else { +#ifdef DEBUG + printf("TNUMBER: %lu\n", sizeof(struct vm_vfloat_t)); +#endif + *nrbytes += alignedbytes(sizeof(struct vm_vfloat_t)); + } + + if(newlen < 4) { + switch(newlen) { + case 1: { + // printf("TNUMBER1: %d\n", tpos); + (*text)[tpos++] = TNUMBER1; + } break; + case 2: { + // printf("TNUMBER2: %d\n", tpos); + (*text)[tpos++] = TNUMBER2; + } break; + case 3: { + // printf("TNUMBER3: %d\n", tpos); + (*text)[tpos++] = TNUMBER3; + } break; + } + memcpy(&(*text)[tpos], &(*text)[pos], newlen); + tpos += newlen; + } else { + + if(modff(var, &nr) == 0) { + /* + * This range of integers + * take less bytes when stored + * as ascii characters. + */ + // printf("VINTEGER: %d\n", tpos); + (*text)[tpos++] = VINTEGER; + int x = var; + memcpy(&(*text)[tpos], &x, sizeof(int)); + tpos += sizeof(int); + } else { + // printf("VFLOAT: %d\n", tpos); + (*text)[tpos++] = VFLOAT; + memcpy(&(*text)[tpos], &var, sizeof(float)); + tpos += sizeof(float); + } + } + + if(tmp == ' ' || tmp == '\n' || tmp != '\t' || tmp != '\r') { + (*text)[pos+newlen] = tmp; + pos += newlen; + } else { + int x = 0; + while(tmp != ' ' && tmp != '\n' && tmp != '\t' && tmp != '\r') { + char tmp1 = (*text)[pos+newlen+1]; + (*text)[pos+newlen+1] = tmp; + tmp = tmp1; + newlen += 1; + x++; + } + + if((*text)[pos+newlen] == ' ' || (*text)[pos+newlen] == '\t' || + (*text)[pos+newlen] == '\r' || (*text)[pos+newlen] == '\n') { + (*text)[pos+newlen] = tmp; + } + pos += newlen-x+1; + } + + + } else if(strnicmp((char *)&(*text)[pos], "if", 2) == 0) { + *nrbytes += alignedbytes(sizeof(struct vm_tif_t)); + *nrbytes += alignedbytes(sizeof(struct vm_ttrue_t)); +#ifdef DEBUG + printf("TIF: %lu\n", sizeof(struct vm_tif_t)); + printf("TTRUE: %lu\n", sizeof(struct vm_ttrue_t)); +#endif + + /* + * An additional TTRUE slot + */ + *nrbytes += alignedbytes(sizeof(uint16_t)); +#ifdef DEBUG + printf("TTRUE: %lu\n", sizeof(uint16_t)); +#endif + // printf("TIF: %d\n", tpos); + (*text)[tpos++] = TIF; + + pos+=2; + nrblocks++; + } else if(strnicmp(&(*text)[pos], "on", 2) == 0) { + pos+=2; + lexer_parse_skip_characters((*text), *len, &pos); + int s = pos; + lexer_parse_string((*text), &pos); + + { + unsigned int len = pos - s; + *nrbytes += alignedbytes(sizeof(struct vm_tevent_t)+len+1); + *nrbytes += alignedbytes(sizeof(struct vm_ttrue_t)); +#ifdef DEBUG + printf("TEVENT: %lu\n", sizeof(struct vm_tevent_t)+len+1); + printf("TTRUE: %lu\n", sizeof(struct vm_ttrue_t)); +#endif + // printf("TEVENT: %d\n", tpos); + (*text)[tpos++] = TEVENT; + memcpy(&(*text)[tpos], &(*text)[s], len); + tpos += len; + + /* + * An additional TTRUE slot + */ + *nrbytes += alignedbytes(sizeof(uint16_t)); +#ifdef DEBUG + printf("TTRUE: %lu\n", sizeof(uint16_t)); +#endif + } + nrblocks++; + } else if(strnicmp((char *)&(*text)[pos], "else", 4) == 0) { + *nrbytes += alignedbytes(sizeof(struct vm_ttrue_t)); +#ifdef DEBUG + printf("TTRUE: %lu\n", sizeof(struct vm_ttrue_t)); +#endif + pos+=4; + (*text)[tpos++] = TELSE; + } else if(strnicmp((char *)&(*text)[pos], "then", 4) == 0) { + pos+=4; + // printf("TTHEN: %d\n", tpos); + (*text)[tpos++] = TTHEN; + } else if(strnicmp((char *)&(*text)[pos], "end", 3) == 0) { + pos+=3; + nrblocks--; + // printf("TEND: %d\n", tpos); + (*text)[tpos++] = TEND; + } else if(strnicmp((char *)&(*text)[pos], "NULL", 4) == 0) { + *nrbytes += alignedbytes(sizeof(struct vm_vnull_t)); +#ifdef DEBUG + printf("VNULL: %lu\n", sizeof(struct vm_vnull_t)); +#endif + // printf("VNULL: %d\n", tpos); + (*text)[tpos++] = VNULL; + pos+=4; + } else if((*text)[pos] == ',') { + /* + * An additional function argument slot + */ + *nrbytes += alignedbytes(sizeof(uint16_t)); +#ifdef DEBUG + printf("TFUNCTION: %lu\n", sizeof(uint16_t)); +#endif + // printf("TCOMMA: %d\n", tpos); + (*text)[tpos++] = TCOMMA; + pos++; + } else if((*text)[pos] == '(') { + *nrbytes += alignedbytes(sizeof(struct vm_lparen_t)); +#ifdef DEBUG + printf("LPAREN: %lu\n", sizeof(struct vm_lparen_t)); +#endif + // printf("LPAREN: %d\n", tpos); + (*text)[tpos++] = LPAREN; + pos++; + } else if((*text)[pos] == ')') { + pos++; + // printf("RPAREN: %d\n", tpos); + (*text)[tpos++] = RPAREN; + } else if((*text)[pos] == '=' && pos < *len && (*text)[(pos)+1] != '=') { + pos++; + // printf("TASSIGN: %d\n", tpos); + (*text)[tpos++] = TASSIGN; + } else if((*text)[pos] == ';') { + /* + * An additional TTRUE slot + */ + *nrbytes += alignedbytes(sizeof(uint16_t)); +#ifdef DEBUG + printf("TTRUE: %lu\n", sizeof(uint16_t)); +#endif + pos++; + // printf("TSEMICOLON: %d\n", tpos); + (*text)[tpos++] = TSEMICOLON; + } else { + unsigned int a = 0, b = *len-(pos)-1; + int len1 = 0; + for(a=(pos);a<*len;a++) { + if((*text)[a] == ' ' || (*text)[a] == '(' || (*text)[a] == ')' || + (*text)[a] == ',' || (*text)[a] == ';') { + b = a-(pos); + break; + } + } + + if((len1 = is_function((*text), &pos, b)) > -1) { + *nrbytes += alignedbytes(sizeof(struct vm_tfunction_t)+sizeof(uint16_t)); + *nrbytes -= alignedbytes(sizeof(struct vm_lparen_t)); +#ifdef DEBUG + printf("TFUNCTION: %lu\n", sizeof(struct vm_tfunction_t)+sizeof(uint16_t)); +#endif + + // printf("TFUNCTION: %d\n", tpos); + (*text)[tpos++] = TFUNCTION; + (*text)[tpos++] = len1; + pos += b; + } else if((len1 = is_operator((*text), &pos, b)) > -1) { + *nrbytes += alignedbytes(sizeof(struct vm_toperator_t)); +#ifdef DEBUG + printf("TOPERATOR: %lu\n", sizeof(struct vm_toperator_t)); +#endif + pos += b; + + // printf("TOPERATOR: %d\n", tpos); + (*text)[tpos++] = TOPERATOR; + (*text)[tpos++] = len1; + } else if(rule_options.is_token_cb != NULL && (len1 = rule_options.is_token_cb((*text), &pos, b)) > -1) { + *nrbytes += alignedbytes(sizeof(struct vm_tvar_t)+len1+1); +#ifdef DEBUG + printf("TVAR: %lu\n", sizeof(struct vm_tvar_t)+len1+1); +#endif + + // printf("TVAR: %d\n", tpos); + (*text)[tpos++] = TVAR; + memcpy(&(*text)[tpos], &(*text)[pos], len1); + tpos += len1; + pos += len1; + } else if(rule_options.is_event_cb != NULL && (len1 = rule_options.is_event_cb((*text), &pos, b)) > -1) { + if((pos)+b < *len && (*text)[(pos)+b] == '(' && (pos)+b+1 < *len && (*text)[(pos)+b+1] == ')') { + lexer_parse_skip_characters((*text), *len, &pos); + int s = pos; + lexer_parse_string((*text), &pos); + + { + unsigned int len = pos - s; + + *nrbytes += alignedbytes(sizeof(struct vm_tcevent_t)+len+1); +#ifdef DEBUG + printf("TCEVENT: %lu\n", sizeof(struct vm_tcevent_t)+len+1); +#endif + // printf("TCEVENT: %d\n", tpos); + (*text)[tpos++] = TCEVENT; + memcpy(&(*text)[tpos], &(*text)[s], len); + tpos += len; + } + pos += 2; + } else { + if((*len - pos) > 5) { + logprintf_P(F("ERROR: unknown token '%.5s...'"), &(*text)[pos]); + } else { + logprintf_P(F("ERROR: unknown token '%.5s'"), &(*text)[pos]); + } + return -1; + } + } else { + if((*len - pos) > 5) { + logprintf_P(F("ERROR: unknown token '%.5s...'"), &(*text)[pos]); + } else { + logprintf_P(F("ERROR: unknown token '%.5s'"), &(*text)[pos]); + } + return -1; + } + } + + if(nrblocks == 0) { + /* + * Remove one go slot because of the root + * if or on block + */ + *nrbytes -= alignedbytes(sizeof(uint16_t)); + *nrbytes += alignedbytes(sizeof(struct vm_teof_t)); +#ifdef DEBUG + printf("TEOF: %lu\n", sizeof(struct vm_teof_t)); +#endif + + // printf("TEOF: %d\n", tpos); + (*text)[tpos++] = TEOF; + unsigned int oldpos = pos; + lexer_parse_skip_characters((*text), *len, &pos); + if(*len == pos) { + return 0; + } + memmove(&(*text)[oldpos], &(*text)[pos-1], *len-(pos)+1); + (*text)[oldpos] = 0; + *len -= (pos-oldpos-1); + (*text)[*len] = 0; + *len = oldpos; + return 0; + } + + lexer_parse_skip_characters((*text), *len, &pos); + } + + /* + * Remove one go slot because of the root + * if or on block + */ + *nrbytes -= alignedbytes(sizeof(uint16_t)); + *nrbytes += alignedbytes(sizeof(struct vm_teof_t)); +#ifdef DEBUG + printf("TEOF: %lu\n", sizeof(struct vm_teof_t)); +#endif + // printf("TEOF: %d\n", tpos); + (*text)[tpos++] = TEOF; + return 0; +} + +static int lexer_peek(char **text, int skip, int *type, int *start, int *len) { + int nr = 0, loop = 1, i = 0; + while(loop) { + *type = (*text)[i]; + *start = i; + *len = 0; + switch((*text)[i]) { + case VNULL: + case TSEMICOLON: + case TEND: + case TASSIGN: + case RPAREN: + case TCOMMA: + case LPAREN: + case TELSE: + case TTHEN: + case TIF: { + i += 1; + } break; + case TNUMBER1: { + *len = 1; + i += 2; + } break; + case TNUMBER2: { + *len = 2; + i += 3; + } break; + case TNUMBER3: { + *len = 3; + i += 4; + } break; + case VINTEGER: { + i += 1+sizeof(int); + } break; + case VFLOAT: { + i += 1+sizeof(float); + } break; + case TFUNCTION: + case TOPERATOR: { + i += 2; + } break; + case TEOF: { + i += 1; + loop = 0; + } break; + case TCEVENT: + case TEVENT: + case TVAR: { + i++; + while((*text)[i] > 32 && (*text)[i] < 126) { + i++; + } + *len = i - *start - 1; + } break; + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + } + if(skip == nr++) { + return i; + } + } + return -1; +} + +static int vm_parent(char **text, struct rules_t *obj, int type, int start, int len, unsigned int opt) { + unsigned int ret = alignedbytes(obj->ast.nrbytes), size = 0, i = 0; + + switch(type) { + case TSTART: { + size = alignedbytes(ret+sizeof(struct vm_tstart_t)); + assert(size <= obj->ast.bufsize); + struct vm_tstart_t *node = (struct vm_tstart_t *)&obj->ast.buffer[ret]; + node->type = type; + node->go = 0; + node->ret = 0; + + obj->ast.nrbytes = size; + } break; + case TIF: { + size = alignedbytes(ret+sizeof(struct vm_tif_t)); + assert(size <= obj->ast.bufsize); + struct vm_tif_t *node = (struct vm_tif_t *)&obj->ast.buffer[ret]; + node->type = type; + node->ret = 0; + node->go = 0; + node->true_ = 0; + node->false_ = 0; + + obj->ast.nrbytes = size; + } break; + case LPAREN: { + size = alignedbytes(ret+sizeof(struct vm_lparen_t)); + assert(size <= obj->ast.bufsize); + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[ret]; + node->type = type; + node->ret = 0; + node->go = 0; + node->value = 0; + + obj->ast.nrbytes = size; + } break; + case TOPERATOR: { + size = alignedbytes(ret+sizeof(struct vm_toperator_t)); + assert(size <= obj->ast.bufsize); + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[ret]; + node->type = type; + node->ret = 0; + node->token = (*text)[start+1]; + node->left = 0; + node->right = 0; + node->value = 0; + + obj->ast.nrbytes = size; + } break; + case VINTEGER: { + size = alignedbytes(obj->ast.nrbytes+sizeof(struct vm_vinteger_t)); + assert(size <= obj->ast.bufsize); + + struct vm_vinteger_t *value = (struct vm_vinteger_t *)&obj->ast.buffer[obj->ast.nrbytes]; + value->type = VINTEGER; + value->ret = ret; + memcpy(&value->value, &(*text)[start+1], sizeof(int)); + obj->ast.nrbytes = size; + } break; + case VFLOAT: { + size = alignedbytes(obj->ast.nrbytes+sizeof(struct vm_vfloat_t)); + assert(size <= obj->ast.bufsize); + + struct vm_vfloat_t *value = (struct vm_vfloat_t *)&obj->ast.buffer[obj->ast.nrbytes]; + value->type = VFLOAT; + value->ret = ret; + memcpy(&value->value, &(*text)[start+1], sizeof(float)); + obj->ast.nrbytes = size; + } break; + case TNUMBER1: + case TNUMBER2: + case TNUMBER3: { + char tmp = (*text)[start+1+len]; + (*text)[start+1+len] = 0; + float var = atof((char *)&(*text)[start+1]); + float nr = 0; + + if(modff(var, &nr) == 0) { + /* + * This range of integers + * take less bytes when stored + * as ascii characters. + */ + if(var < 100 && var > -9) { + size = alignedbytes(ret+sizeof(struct vm_tnumber_t)+len+1); + assert(size <= obj->ast.bufsize); + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[ret]; + node->type = TNUMBER; + node->ret = 0; + memcpy(node->token, &(*text)[start+1], len); + + obj->ast.nrbytes = size; + } else { + size = alignedbytes(obj->ast.nrbytes+sizeof(struct vm_vinteger_t)); + assert(size <= obj->ast.bufsize); + struct vm_vinteger_t *value = (struct vm_vinteger_t *)&obj->ast.buffer[obj->ast.nrbytes]; + value->type = VINTEGER; + value->ret = ret; + value->value = (int)var; + obj->ast.nrbytes = size; + } + } else { + size = alignedbytes(obj->ast.nrbytes + sizeof(struct vm_vfloat_t)); + assert(size <= obj->ast.bufsize); + struct vm_vfloat_t *value = (struct vm_vfloat_t *)&obj->ast.buffer[obj->ast.nrbytes]; + value->type = VFLOAT; + value->ret = ret; + value->value = var; + obj->ast.nrbytes = size; + } + (*text)[start+1+len] = tmp; + } break; + case TFALSE: + case TTRUE: { + size = alignedbytes(ret+sizeof(struct vm_ttrue_t)+(sizeof(uint16_t)*opt)); + assert(size <= obj->ast.bufsize); + struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[ret]; + node->type = type; + node->ret = 0; + node->nrgo = opt; + for(i=0;igo[i] = 0; + } + + obj->ast.nrbytes = size; + } break; + case TFUNCTION: { + size = alignedbytes(ret+sizeof(struct vm_tfunction_t)+(sizeof(uint16_t)*opt)); + assert(size <= obj->ast.bufsize); + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[ret]; + node->token = (*text)[start+1]; + node->type = type; + node->ret = 0; + node->nrgo = opt; + for(i=0;igo[i] = 0; + } + node->value = 0; + + obj->ast.nrbytes = size; + } break; + case VNULL: { + size = alignedbytes(ret+sizeof(struct vm_vnull_t)); + assert(size <= obj->ast.bufsize); + struct vm_vnull_t *node = (struct vm_vnull_t *)&obj->ast.buffer[ret]; + node->type = type; + node->ret = 0; + + obj->ast.nrbytes = size; + } break; + case TVAR: { + size = alignedbytes(ret+sizeof(struct vm_tvar_t)+len+1); + assert(size <= obj->ast.bufsize); + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[ret]; + node->type = type; + node->ret = 0; + node->go = 0; + node->value = 0; + + memcpy(node->token, &(*text)[start+1], len); + + obj->ast.nrbytes = size; + } break; + case TEVENT: { + size = alignedbytes(ret+sizeof(struct vm_tevent_t)+len+1); + assert(size <= obj->ast.bufsize); + struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[ret]; + node->type = type; + node->ret = 0; + node->go = 0; + + memcpy(node->token, &(*text)[start+1], len); + + obj->ast.nrbytes = size; + } break; + case TCEVENT: { + size = alignedbytes(ret+sizeof(struct vm_tcevent_t)+len+1); + assert(size <= obj->ast.bufsize); + struct vm_tcevent_t *node = (struct vm_tcevent_t *)&obj->ast.buffer[ret]; + + node->type = type; + node->ret = 0; + /* + * memcpy triggers a -Wstringop-overflow here + */ + memcpy(node->token, &(*text)[start+1], len); + + obj->ast.nrbytes = size; + } break; + case TEOF: { + size = alignedbytes(ret+sizeof(struct vm_teof_t)); + assert(size <= obj->ast.bufsize); + struct vm_teof_t *node = (struct vm_teof_t *)&obj->ast.buffer[ret]; + node->type = type; + + obj->ast.nrbytes = size; + } break; + default: { + return -1; + } break; + } + + return ret; +} + +static int vm_rewind2(struct rules_t *obj, int step, int type, int type2) { + int tmp = step; + while(1) { + if((obj->ast.buffer[tmp]) == type || (type2 > -1 && (obj->ast.buffer[tmp]) == type2)) { + return tmp; + } else { + switch(obj->ast.buffer[tmp]) { + case TSTART: { + return 0; + } break; + case TIF: { + struct vm_tif_t *node = (struct vm_tif_t *)&obj->ast.buffer[tmp]; + tmp = node->ret; + } break; + case TEVENT: { + struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[tmp]; + tmp = node->ret; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[tmp]; + tmp = node->ret; + } break; + case TFALSE: + case TTRUE: { + struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[tmp]; + tmp = node->ret; + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[tmp]; + tmp = node->ret; + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[tmp]; + tmp = node->ret; + } break; + /* LCOV_EXCL_START*/ + case TCEVENT: + case TNUMBER1: + case TNUMBER2: + case TNUMBER3: + case VINTEGER: + case VFLOAT: + case VNULL: + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + } + } + return 0; +} + +static int vm_rewind(struct rules_t *obj, int step, int type) { + return vm_rewind2(obj, step, type, -1); +} + +static void vm_cache_add(int type, int step, int start, int end) { + if((vmcache = (struct vm_cache_t **)REALLOC(vmcache, sizeof(struct vm_cache_t *)*((nrcache)+1))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + if((vmcache[nrcache] = (struct vm_cache_t *)MALLOC(sizeof(struct vm_cache_t))) == NULL) { + OUT_OF_MEMORY /*LCOV_EXCL_LINE*/ + } + vmcache[nrcache]->type = type; + vmcache[nrcache]->step = step; + vmcache[nrcache]->start = start; + vmcache[nrcache]->end = end; + + nrcache++; +#ifdef DEBUG + printf("cache entries: %d\n", nrcache); /*LCOV_EXCL_LINE*/ +#endif +} + +static void vm_cache_del(int start) { + unsigned int x = 0, y = 0; + for(x=0;xstart == start) { + /*LCOV_EXCL_START*/ + /* + * The cache should be popped in a lifo manner. + * Therefor, moving element after the current + * one popped should never occur. + */ + for(y=x;ytype == type && vmcache[x]->start == start) { + return vmcache[x]; + } + } + return NULL; +} + +static int lexer_parse_math_order(char **text, int *length, struct rules_t *obj, int type, int *pos, int *step_out, int offset, int source) { + int b = 0, c = 0, step = 0, first = 1, start = 0, len = 0; + + while(1) { + int right = 0; + + if(lexer_peek(text, (*pos), &b, &start, &len) < 0 || b != TOPERATOR) { + break; + } + step = vm_parent(text, obj, b, start, len, 0); + (*pos)++; + + if(lexer_peek(text, (*pos), &c, &start, &len) >= 0) { + switch(c) { + case LPAREN: { + int oldpos = (*pos); + struct vm_cache_t *x = vm_cache_get(LPAREN, (*pos)); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + (*pos) = x->end; + right = x->step; + vm_cache_del(oldpos); + } break; + case TFUNCTION: { + int oldpos = (*pos); + struct vm_cache_t *x = vm_cache_get(TFUNCTION, (*pos)); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + (*pos) = x->end; + right = x->step; + vm_cache_del(oldpos); + } break; + case TVAR: + case VNULL: + case TNUMBER1: + case TNUMBER2: + case TNUMBER3: + case VINTEGER: + case VFLOAT: { + right = vm_parent(text, obj, c, start, len, 0); + (*pos)++; + } break; + default: { + logprintf_P(F("ERROR: Expected a parenthesis block, function, number or variable")); + return -1; + } break; + } + } + + struct vm_tgeneric_t *node = (struct vm_tgeneric_t *)&obj->ast.buffer[right]; + node->ret = step; + + struct vm_toperator_t *op2 = (struct vm_toperator_t *)&obj->ast.buffer[step]; + op2->left = *step_out; + op2->right = right; + + switch(obj->ast.buffer[*step_out]) { + case TNUMBER: + case TNUMBER1: + case TNUMBER2: + case TNUMBER3: { + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case VNULL: { + struct vm_vnull_t *node = (struct vm_vnull_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + struct vm_toperator_t *op1 = NULL; + + if(type == LPAREN) { + if(first == 1/* && *step_out > obj->pos.parsed*/) { + struct vm_tgeneric_t *node = (struct vm_tgeneric_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } + } else if(type == TOPERATOR) { + if(first == 1) { + struct vm_tgeneric_t *node = (struct vm_tgeneric_t *)&obj->ast.buffer[step]; + node->ret = source; + + op2->ret = step; + if((obj->ast.buffer[source]) == TIF) { + struct vm_tif_t *node = (struct vm_tif_t *)&obj->ast.buffer[source]; + node->go = step; + } + } + } + + if((obj->ast.buffer[step]) == TOPERATOR && (obj->ast.buffer[*step_out]) == TOPERATOR) { + struct vm_toperator_t *op3 = NULL; + op1 = (struct vm_toperator_t *)&obj->ast.buffer[*step_out]; + + int idx1 = op1->token; + int idx2 = op2->token; + + int x = rule_operators[idx1].precedence; + int y = rule_operators[idx2].precedence; + int a = rule_operators[idx2].associativity; + + if(y > x || (x == y && a == 2)) { + if(a == 1) { + op2->left = op1->right; + op3 = (struct vm_toperator_t *)&obj->ast.buffer[op2->left]; + op3->ret = step; + op1->right = step; + op2->ret = *step_out; + } else { + /* + * Find the last operator with an operator + * as the last factor. + */ + int tmp = op1->right; + if((obj->ast.buffer[tmp]) != LPAREN && + (obj->ast.buffer[tmp]) != TFUNCTION && + (obj->ast.buffer[tmp]) != TNUMBER && + (obj->ast.buffer[tmp]) != VFLOAT && + (obj->ast.buffer[tmp]) != VINTEGER && + (obj->ast.buffer[tmp]) != VNULL) { + while((obj->ast.buffer[tmp]) == TOPERATOR) { + if((obj->ast.buffer[((struct vm_toperator_t *)&obj->ast.buffer[tmp])->right]) == TOPERATOR) { + tmp = ((struct vm_toperator_t *)&obj->ast.buffer[tmp])->right; + } else { + break; + } + } + } else { + tmp = *step_out; + } + + op3 = (struct vm_toperator_t *)&obj->ast.buffer[tmp]; + int tright = op3->right; + op3->right = step; + + switch((obj->ast.buffer[tright])) { + case TNUMBER: { + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&obj->ast.buffer[*step_out]; + node->ret = step; + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[tright]; + node->ret = step; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[tright]; + node->ret = step; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + op2->left = tright; + op2->ret = tmp; + } + step = *step_out; + } else { + op1->ret = step; + *step_out = step; + } + } + + *step_out = step; + + first = 0; + } + + return step; +} + +static int rule_parse(char **text, int *length, struct rules_t *obj) { + int type = 0, type1 = 0, go = -1, step_out = -1, step = 0, pos = 0; + int has_paren = -1, has_function = -1, has_if = -1, has_on = -1; + int loop = 1, startnode = 0, r_rewind = -1, offset = 0, start = 0, len = 0; + + /* LCOV_EXCL_START*/ + if(lexer_peek(text, 0, &type, &start, &len) < 0) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + if(type != TIF && type != TEVENT) { + logprintf_P(F("ERROR: Expected an 'if' or an 'on' statement")); + return -1; + } + + startnode = vm_parent(text, obj, TSTART, 0, 0, 0); + + while(loop) { +#ifdef ESP8266 + delay(0); +#endif + if(go > -1) { + switch(go) { + /* LCOV_EXCL_START*/ + case TSTART: { + /* + * This should never be reached, see + * the go = TSTART comment below. + */ + // loop = 0; + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } break; + case TEVENT: { + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + if((step_out == -1 || (obj->ast.buffer[step_out]) == TTRUE) && type != TTHEN) { + if(type == TEVENT) { + if(offset > 0) { + logprintf_P(F("ERROR: nested 'on' block")); + return -1; + } + + step_out = vm_parent(text, obj, TEVENT, start, len, 0); + pos++; + + if(lexer_peek(text, pos, &type, &start, &len) >= 0 && type == TTHEN) { + pos++; + + /* + * Predict how many go slots we need to + * reserve for the TRUE / FALSE nodes. + */ + int y = pos, nrexpressions = 0; + while(lexer_peek(text, y++, &type, &start, &len) >= 0) { + if(type == TEOF) { + break; + } + if(type == TIF) { + struct vm_cache_t *cache = vm_cache_get(TIF, y-1); + nrexpressions++; + y = cache->end; + continue; + } + if(type == TEND) { + break; + } + if(type == TSEMICOLON) { + nrexpressions++; + } + } + + if(nrexpressions == 0) { + logprintf_P(F("ERROR: On block without body")); + return -1; + } +#ifdef DEBUG + printf("nrexpressions: %d\n", nrexpressions);/*LCOV_EXCL_LINE*/ +#endif + + /* + * The last parameter is used for + * for the number of operations the + * TRUE of FALSE will forward to. + */ + step = vm_parent(text, obj, TTRUE, start, len, nrexpressions); + + struct vm_ttrue_t *a = (struct vm_ttrue_t *)&obj->ast.buffer[step]; + struct vm_tevent_t *b = (struct vm_tevent_t *)&obj->ast.buffer[step_out]; + + b->go = step; + a->ret = step_out; + + go = TEVENT; + + step_out = step; + } else { + logprintf_P(F("ERROR: Expected a 'then' token")); + return -1; + } + } else { + switch(type) { + case TVAR: { + go = TVAR; + continue; + } break; + case TCEVENT: { + go = TCEVENT; + continue; + } break; + case TFUNCTION: { + struct vm_cache_t *x = vm_cache_get(TFUNCTION, pos); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + if(lexer_peek(text, x->end, &type, &start, &len) >= 0) { + switch(type) { + case TSEMICOLON: { + int tmp = vm_rewind2(obj, step_out, TTRUE, TFALSE); + struct vm_tfunction_t *f = (struct vm_tfunction_t *)&obj->ast.buffer[x->step]; + struct vm_ttrue_t *t = (struct vm_ttrue_t *)&obj->ast.buffer[step_out]; + f->ret = tmp; + + int i = 0; + for(i=0;inrgo;i++) { + if(t->go[i] == 0) { + t->go[i] = x->step; + break; + } + } + + /* LCOV_EXCL_START*/ + if(i == t->nrgo) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + go = TEVENT; + step_out = tmp; + pos = x->end + 1; + vm_cache_del(x->start); + } break; + case TOPERATOR: { + go = type; + } break; + default: { + logprintf_P(F("ERROR: Expected an semicolon or operator")); + return -1; + } break; + } + } + continue; + } break; + case TIF: { + struct vm_cache_t *cache = vm_cache_get(TIF, pos); + /* LCOV_EXCL_START*/ + if(cache == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + step = cache->step; + pos = cache->end; + + /* + * After an cached IF block has been linked + * it can be removed from cache + */ + vm_cache_del(cache->start); + + struct vm_tif_t *i = (struct vm_tif_t *)&obj->ast.buffer[step]; + + /* + * Attach IF block to TRUE / FALSE node + */ + + i->ret = step_out; + switch(obj->ast.buffer[step_out]) { + case TFALSE: + case TTRUE: { + int x = 0; + struct vm_ttrue_t *t = (struct vm_ttrue_t *)&obj->ast.buffer[step_out]; + /* + * Attach the IF block to an empty + * TRUE / FALSE go slot. + */ + for(x=0;xnrgo;x++) { + if(t->go[x] == 0) { + t->go[x] = step; + break; + } + } + /* LCOV_EXCL_START*/ + if(x == t->nrgo) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + continue; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } break; + case TEOF: + case TEND: { + go = -1; + + int tmp = vm_rewind(obj, step_out, TEVENT); + /* + * Should never happen + */ + /* LCOV_EXCL_START*/ + if(has_on > 0) { + logprintf_P(F("ERROR: On block inside on block")); + return -1; + } + /* LCOV_EXCL_START*/ + + if(has_on == 0) { + struct vm_tstart_t *b = (struct vm_tstart_t *)&obj->ast.buffer[startnode]; + struct vm_tif_t *a = (struct vm_tif_t *)&obj->ast.buffer[tmp]; + b->go = tmp; + a->ret = startnode; + + step = vm_parent(text, obj, TEOF, 0, 0, 0); +#ifdef DEBUG + printf("nr steps: %d\n", step);/*LCOV_EXCL_LINE*/ +#endif + tmp = vm_rewind(obj, tmp, TSTART); + + struct vm_tstart_t *node = (struct vm_tstart_t *)&obj->ast.buffer[tmp]; + node->ret = step; + + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + pos++; + } + + /* + * If we are the root IF block + */ + if(has_on == 0) { + loop = 0; + } + + pos = 0; + has_paren = -1; + has_function = -1; + has_if = -1; + has_on = -1; + step_out = -1; + + continue; + } break; + default: { + logprintf_P(F("ERROR: Unexpected token")); + return -1; + } break; + } + } + } else { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + } break; + case TIF: { + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + if( + ( + step_out == -1 || + (obj->ast.buffer[step_out]) == TTRUE || + (obj->ast.buffer[step_out]) == TFALSE + ) && (type != TTHEN && type != TELSE) + ) { + + /* + * Link cached IF blocks together + */ + struct vm_cache_t *cache = vm_cache_get(TIF, pos); + if(cache != NULL) { + step = cache->step; + + if(lexer_peek(text, cache->end, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + struct vm_tif_t *i = (struct vm_tif_t *)&obj->ast.buffer[step]; + + /* + * Attach IF block to TRUE / FALSE node + */ + + i->ret = step_out; + pos = cache->end; + + /* + * After an cached IF block has been linked + * it can be removed from cache + */ + vm_cache_del(cache->start); + + switch(obj->ast.buffer[step_out]) { + case TFALSE: + case TTRUE: { + int x = 0; + struct vm_ttrue_t *t = (struct vm_ttrue_t *)&obj->ast.buffer[step_out]; + /* + * Attach the IF block to an empty + * TRUE / FALSE go slot. + */ + for(x=0;xnrgo;x++) { + if(t->go[x] == 0) { + t->go[x] = step; + break; + } + } + /* LCOV_EXCL_START*/ + if(x == t->nrgo) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + /* + * An IF block directly followed by another IF block + */ + if(lexer_peek(text, pos, &type, &start, &len) >= 0 && type == TIF) { + go = TIF; + continue; + } + } + + if(type == TIF) { + step = vm_parent(text, obj, TIF, start, len, 0); + struct vm_tif_t *a = (struct vm_tif_t *)&obj->ast.buffer[step]; + + if(pos == 0) { + struct vm_tstart_t *b = (struct vm_tstart_t *)&obj->ast.buffer[start]; + b->go = step; + a->ret = start; + } + + pos++; + + if(lexer_peek(text, pos+1, &type, &start, &len) >= 0 && type == TOPERATOR) { + go = TOPERATOR; + step_out = step; + continue; + } else if(lexer_peek(text, pos, &type, &start, &len) >= 0 && type == LPAREN) { + step_out = step; + + /* + * Link cached parenthesis blocks + */ + struct vm_cache_t *cache = vm_cache_get(LPAREN, pos); + /* LCOV_EXCL_START*/ + if(cache == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + /* + * If this parenthesis is part of a operator + * let the operator handle it. + */ + if(lexer_peek(text, cache->end, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + + struct vm_lparen_t *c = (struct vm_lparen_t *)&obj->ast.buffer[cache->step]; + if(type == TOPERATOR) { + go = TOPERATOR; + step_out = step; + continue; + } else if(type == TTHEN) { + step_out = c->go; + go = TIF; + a->go = cache->step; + c->ret = step; + } else { + logprintf_P(F("ERROR: Unexpected token")); + return -1; + } + + pos = cache->end; + + vm_cache_del(cache->start); + continue; + } else if(lexer_peek(text, pos, &type, &start, &len) >= 0 && type == TFUNCTION) { + step_out = step; + + /* + * Link cached parenthesis blocks + */ + struct vm_cache_t *cache = vm_cache_get(TFUNCTION, pos); + + if(lexer_peek(text, cache->end, &type, &start, &len) >= 0 && type == TOPERATOR) { + go = TOPERATOR; + step_out = step; + continue; + } else { + logprintf_P(F("ERROR: Function without operator in if condition")); + return -1; + } + } else { + logprintf_P(F("ERROR: Expected a parenthesis block, function or operator")); + return -1; + } + } else { + switch(type) { + case TVAR: { + go = TVAR; + continue; + } break; + case TCEVENT: { + go = TCEVENT; + continue; + } break; + case TELSE: { + go = TIF; + continue; + } break; + case TFUNCTION: { + struct vm_cache_t *x = vm_cache_get(TFUNCTION, pos); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + if(lexer_peek(text, x->end, &type, &start, &len) >= 0) { + switch(type) { + case TSEMICOLON: { + int tmp = vm_rewind2(obj, step_out, TTRUE, TFALSE); + struct vm_tfunction_t *f = (struct vm_tfunction_t *)&obj->ast.buffer[x->step]; + struct vm_ttrue_t *t = (struct vm_ttrue_t *)&obj->ast.buffer[step_out]; + f->ret = tmp; + + int i = 0; + for(i=0;inrgo;i++) { + if(t->go[i] == 0) { + t->go[i] = x->step; + break; + } + } + + if(i == t->nrgo) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + go = TIF; + step_out = tmp; + pos = x->end + 1; + vm_cache_del(x->start); + } break; + case TOPERATOR: { + go = type; + } break; + default: { + logprintf_P(F("ERROR: Expected an semicolon or operator")); + return -1; + } break; + } + } + continue; + } break; + case TEOF: + case TEND: { + go = -1; + + int tmp = vm_rewind(obj, step_out, TIF); + if(has_if > 0) { + r_rewind = has_if; + + if(lexer_peek(text, pos, &type, &start, &len) < 0 || type != TEND) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + pos++; + + vm_cache_add(TIF, tmp, has_if, pos); + } + if(has_if == 0) { + struct vm_tstart_t *b = (struct vm_tstart_t *)&obj->ast.buffer[startnode]; + struct vm_tif_t *a = (struct vm_tif_t *)&obj->ast.buffer[tmp]; + b->go = tmp; + a->ret = startnode; + + step = vm_parent(text, obj, TEOF, 0, 0, 0); +#ifdef DEBUG + printf("nr steps: %d\n", step);/*LCOV_EXCL_LINE*/ +#endif + tmp = vm_rewind(obj, tmp, TSTART); + + struct vm_tstart_t *node = (struct vm_tstart_t *)&obj->ast.buffer[tmp]; + node->ret = step; + + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + pos++; + } + + /* + * If we are the root IF block + */ + if(has_if == 0) { + loop = 0; + } + + pos = 0; + has_paren = -1; + has_function = -1; + has_if = -1; + has_on = -1; + step_out = -1; + + continue; + } break; + default: { + logprintf_P(F("ERROR: Unexpected token")); + return -1; + } break; + } + } + } else { + int t = 0; + + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + pos++; + + if(type == TTHEN) { + t = TTRUE; + } else if(type == TELSE) { + t = TFALSE; + } + + /* + * Predict how many go slots we need to + * reserve for the TRUE / FALSE nodes. + */ + int y = pos, nrexpressions = 0, z = 0; + + while((z = lexer_peek(text, y++, &type, &start, &len)) >= 0) { + if(type == TEOF) { + break; + } + if(type == TIF) { + struct vm_cache_t *cache = vm_cache_get(TIF, y-1); + if(cache == NULL) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + nrexpressions++; + y = cache->end; + continue; + } + if(type == TEND) { + break; + } + if(type == TSEMICOLON) { + nrexpressions++; + } + if(t == TTRUE && type == TELSE) { + break; + } + } + + if(nrexpressions == 0) { + logprintf_P(F("ERROR: If block without body")); + return -1; + } +#ifdef DEBUG + printf("nrexpressions: %d\n", nrexpressions);/*LCOV_EXCL_LINE*/ +#endif + /* + * If we came from further in the script + * make sure we hook our 'then' and 'else' + * to the last condition. + */ + step_out = vm_rewind(obj, step_out, TIF); + + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + /* + * The last parameter is used for + * for the number of operations the + * TRUE of FALSE will forward to. + */ + step = vm_parent(text, obj, t, 0, 0, nrexpressions); + + struct vm_ttrue_t *a = (struct vm_ttrue_t *)&obj->ast.buffer[step]; + struct vm_tif_t *b = (struct vm_tif_t *)&obj->ast.buffer[step_out]; + + if(t == TTRUE) { + b->true_ = step; + } else { + b->false_ = step; + } + a->ret = step_out; + + go = TIF; + + step_out = step; + } + } break; + case TOPERATOR: { + int a = -1; + int source = step_out; + + if(lexer_peek(text, pos, &a, &start, &len) >= 0) { + switch(a) { + case LPAREN: { + struct vm_cache_t *x = vm_cache_get(LPAREN, pos); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + int oldpos = pos; + pos = x->end; + step_out = x->step; + vm_cache_del(oldpos); + } break; + case TFUNCTION: { + struct vm_cache_t *x = vm_cache_get(TFUNCTION, pos); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + int oldpos = pos; + pos = x->end; + step_out = x->step; + vm_cache_del(oldpos); + } break; + case TVAR: + case VNULL: + case TNUMBER1: + case TNUMBER2: + case TNUMBER3: + case VFLOAT: + case VINTEGER: { + step_out = vm_parent(text, obj, a, start, len, 0); + pos++; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + } + + /* + * The return value is the positition + * of the root operator + */ + int step = lexer_parse_math_order(text, length, obj, TOPERATOR, &pos, &step_out, offset, source); + if(step == -1) { + return -1; + } + + { + struct vm_tgeneric_t *node = (struct vm_tgeneric_t *)&obj->ast.buffer[step_out]; + node->ret = source; + + switch(obj->ast.buffer[source]) { + case TIF: { + struct vm_tif_t *node = (struct vm_tif_t *)&obj->ast.buffer[source]; + node->go = step; + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[source]; + node->go = step; + } break; + /* + * If this operator block is a function + * argument, attach it to a empty go slot + */ + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[source]; + int i = 0; + for(i=0;inrgo;i++) { + if(node->go[i] == 0) { + node->go[i] = step; + break; + } + } + go = TFUNCTION; + step_out = step; + continue; + } break; + default: { + logprintf_P(F("ERROR: Unexpected token")); + return -1; + } break; + } + } + if(lexer_peek(text, pos, &type, &start, &len) >= 0) { + switch(type) { + case TTHEN: { + go = TIF; + } break; + case TSEMICOLON: { + go = TVAR; + } break; + default: { + logprintf_P(F("ERROR: Unexpected token")); + return -1; + } break; + } + } + } break; + case VNULL: { + struct vm_vnull_t *node = NULL; + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + step = vm_parent(text, obj, VNULL, start, len, 0); + pos++; + switch((obj->ast.buffer[step_out])) { + case TVAR: { + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[step_out]; + tmp->go = step; + } break; + /* + * FIXME: Think of a rule that can trigger this + */ + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + node = (struct vm_vnull_t *)&obj->ast.buffer[step]; + node->ret = step_out; + + int tmp = vm_rewind2(obj, step_out, TVAR, TOPERATOR); + go = (obj->ast.buffer[tmp]); + step_out = step; + } break; + /* LCOV_EXCL_START*/ + case TSEMICOLON: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + case TVAR: { + /* + * We came from the TRUE node from the IF root, + * which means we start parsing the variable name. + */ + if((obj->ast.buffer[step_out]) == TTRUE || (obj->ast.buffer[step_out]) == TFALSE) { + struct vm_tvar_t *node = NULL; + struct vm_ttrue_t *node1 = NULL; + + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + step = vm_parent(text, obj, TVAR, start, len, 0); + + pos++; + + node = (struct vm_tvar_t *)&obj->ast.buffer[step]; + node1 = (struct vm_ttrue_t *)&obj->ast.buffer[step_out]; + + int x = 0; + for(x=0;xnrgo;x++) { + if(node1->go[x] == 0) { + node1->go[x] = step; + break; + } + } + + /* LCOV_EXCL_START*/ + if(x == node1->nrgo) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + node->ret = step_out; + + step_out = step; + + if(lexer_peek(text, pos, &type, &start, &len) < 0 || type != TASSIGN) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + pos++; + + if(lexer_peek(text, pos+1, &type, &start, &len) >= 0 && type == TOPERATOR) { + switch(type) { + case TOPERATOR: { + go = TOPERATOR; + step_out = step; + continue; + } break; + } + } else if(lexer_peek(text, pos, &type, &start, &len) >= 0) { + switch(type) { + case VINTEGER: + case VFLOAT: + case TNUMBER1: + case TNUMBER2: + case TNUMBER3: { + int foo = vm_parent(text, obj, type, start, len, 0); + struct vm_tgeneric_t *a = (struct vm_tgeneric_t *)&obj->ast.buffer[foo]; + node = (struct vm_tvar_t *)&obj->ast.buffer[step]; + node->go = foo; + a->ret = step; + + pos++; + + go = TVAR; + } break; + case TVAR: { + go = TVAR; + step_out = step; + } break; + case VNULL: { + go = VNULL; + step_out = step; + } break; + case TFUNCTION: { + struct vm_cache_t *x = vm_cache_get(TFUNCTION, pos); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + if(lexer_peek(text, x->end, &type, &start, &len) >= 0) { + switch(type) { + case TSEMICOLON: { + struct vm_tfunction_t *f = (struct vm_tfunction_t *)&obj->ast.buffer[x->step]; + struct vm_tvar_t *v = (struct vm_tvar_t *)&obj->ast.buffer[step_out]; + f->ret = step; + v->go = x->step; + + int tmp = vm_rewind2(obj, step_out, TTRUE, TFALSE); + int tmp1 = vm_rewind2(obj, tmp, TIF, TEVENT); + + go = obj->ast.buffer[tmp1]; + step_out = tmp; + pos = x->end + 1; + vm_cache_del(x->start); + } break; + case TOPERATOR: { + go = type; + } break; + default: { + logprintf_P(F("ERROR: Expected an semicolon or operator")); + return -1; + } break; + } + } + } break; + case LPAREN: { + int oldpos = pos; + struct vm_cache_t *x = vm_cache_get(LPAREN, pos); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + + /* LCOV_EXCL_STOP*/ + if(lexer_peek(text, x->end, &type, &start, &len) >= 0) { + if(type == TSEMICOLON) { + struct vm_lparen_t *l = (struct vm_lparen_t *)&obj->ast.buffer[x->step]; + struct vm_tvar_t *v = (struct vm_tvar_t *)&obj->ast.buffer[step_out]; + l->ret = step; + v->go = x->step; + + int tmp = vm_rewind2(obj, step_out, TTRUE, TFALSE); + + go = TIF; + step_out = tmp; + + pos = x->end + 1; + + vm_cache_del(oldpos); + } else { + go = type; + } + } + } break; + default: { + logprintf_P(F("ERROR: Unexpected token")); + return -1; + } break; + } + } else { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + /* + * The variable has been called as a value + */ + } else if((obj->ast.buffer[step_out]) == TVAR && lexer_peek(text, pos, &type, &start, &len) >= 0 && type != TSEMICOLON) { + step = vm_parent(text, obj, TVAR, start, len, 0); + pos++; + + struct vm_tvar_t *in = (struct vm_tvar_t *)&obj->ast.buffer[step]; + struct vm_tvar_t *out = (struct vm_tvar_t *)&obj->ast.buffer[step_out]; + in->ret = step_out; + out->go = step; + + if(lexer_peek(text, pos, &type, &start, &len) >= 0) { + + switch(type) { + case TSEMICOLON: { + go = TVAR; + } break; + default: { + logprintf_P(F("ERROR: Expected a semicolon")); + return -1; + } break; + } + } + } else { + if(lexer_peek(text, pos, &type, &start, &len) < 0 || type != TSEMICOLON) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + int tmp = step_out; + pos++; + while(1) { + if((obj->ast.buffer[tmp]) != TTRUE && + (obj->ast.buffer[tmp]) != TFALSE) { + struct vm_tgeneric_t *node = (struct vm_tgeneric_t *)&obj->ast.buffer[tmp]; + tmp = node->ret; + } else { + int tmp1 = vm_rewind2(obj, tmp, TIF, TEVENT); + go = obj->ast.buffer[tmp1]; + step_out = tmp; + break; + } + } + } + } break; + case TCEVENT: { + struct vm_tcevent_t *node = NULL; + /* LCOV_EXCL_START*/ + if(lexer_peek(text, pos, &type, &start, &len) < 0 || type != TCEVENT) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + step = vm_parent(text, obj, TCEVENT, start, len, 0); + + node = (struct vm_tcevent_t *)&obj->ast.buffer[step]; + + pos++; + + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + if(type != TSEMICOLON) { + logprintf_P(F("ERROR: Expected a semicolon")); + return -1; + } + + pos++; + + int tmp = vm_rewind2(obj, step_out, TTRUE, TFALSE); + struct vm_ttrue_t *node1 = (struct vm_ttrue_t *)&obj->ast.buffer[tmp]; + + int x = 0; + for(x=0;xnrgo;x++) { + if(node1->go[x] == 0) { + node1->go[x] = step; + break; + } + } + + /* LCOV_EXCL_START*/ + if(x == node1->nrgo) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + node->ret = step_out; + + int tmp1 = vm_rewind2(obj, step_out, TIF, TEVENT); + go = obj->ast.buffer[tmp1]; + step_out = tmp; + } break; + case LPAREN: { + int a = -1, b = -1; + + if(lexer_peek(text, has_paren+1, &a, &start, &len) >= 0) { + switch(a) { + case LPAREN: { + struct vm_cache_t *x = vm_cache_get(LPAREN, has_paren+1); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + pos = x->end; + step_out = x->step; + vm_cache_del(has_paren+1); + } break; + case TFUNCTION: { + struct vm_cache_t *x = vm_cache_get(TFUNCTION, has_paren+1); + /* LCOV_EXCL_START*/ + if(x == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + step_out = x->step; + pos = x->end; + vm_cache_del(has_paren+1); + } break; + case TVAR: + case VNULL: + case VINTEGER: + case VFLOAT: + case TNUMBER1: + case TNUMBER2: + case TNUMBER3: { + pos = has_paren + 1; + step_out = vm_parent(text, obj, a, start, len, 0); + pos++; + } break; + case RPAREN: { + logprintf_P(F("ERROR: Empty parenthesis block")); + return -1; + } break; + case TOPERATOR: { + logprintf_P(F("ERROR: Unexpected operator")); + return -1; + } break; + default: { + logprintf_P(F("ERROR: Unexpected token")); + return -1; + } + } + } + + /* + * The return value is the positition + * of the root operator + */ + int step = lexer_parse_math_order(text, length, obj, LPAREN, &pos, &step_out, offset, 0); + + if(lexer_peek(text, pos, &b, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + if(b != RPAREN) { + logprintf_P(F("ERROR: Expected a right parenthesis")); + return -1; + } + + pos++; + + { + struct vm_tgeneric_t *node = (struct vm_tgeneric_t *)&obj->ast.buffer[step_out]; + node->ret = 0; + } + + if(lexer_peek(text, has_paren, &type, &start, &len) < 0 || type != LPAREN) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + step = vm_parent(text, obj, LPAREN, start, len, 0); + + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[step]; + node->go = step_out; + + vm_cache_add(LPAREN, step, has_paren, pos); + + r_rewind = has_paren; + + struct vm_tgeneric_t *node1 = (struct vm_tgeneric_t *)&obj->ast.buffer[step_out]; + node1->ret = step; + + go = -1; + + pos = 0; + + has_paren = -1; + has_function = -1; + has_if = -1; + has_on = -1; + step_out = -1; + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = NULL; + int arg = 0; + + /* + * We've entered the function name, + * so we start by creating a proper + * root node. + */ + if(step_out == -1) { + pos = has_function+1; + if(lexer_peek(text, has_function, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + /* + * Determine how many arguments this + * function has. + */ + int y = pos + 1, nrargs = 0; + while(lexer_peek(text, y++, &type, &start, &len) >= 0) { + if(type == TEOF || type == RPAREN) { + break; + } + if(type == LPAREN) { + struct vm_cache_t *cache = vm_cache_get(LPAREN, y-1); + if(cache == NULL) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + y = cache->end; + continue; + } + if(type == TFUNCTION) { + struct vm_cache_t *cache = vm_cache_get(TFUNCTION, y-1); + if(cache == NULL) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + y = cache->end; + continue; + } + if(type == TCOMMA) { + nrargs++; + } + } +#ifdef DEBUG + printf("nrarguments: %d\n", nrargs + 1);/*LCOV_EXCL_LINE*/ +#endif + if(lexer_peek(text, has_function, &type, &start, &len) < 0 || type != TFUNCTION) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + step = vm_parent(text, obj, TFUNCTION, start, len, nrargs + 1); + + pos++; + + /* + * When the root node has been created + * we parse the different arguments. + */ + } else { + int i = 0; + step = vm_rewind(obj, step_out, TFUNCTION); + + node = (struct vm_tfunction_t *)&obj->ast.buffer[step]; + + /* + * Find the argument we've just + * looked up. + */ + for(i=0;inrgo;i++) { + if(node->go[i] == step_out) { + arg = i+1; + break; + } + } + + /* + * Look for the next token in the list + * of arguments + */ + while(1) { + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + if(type == TCOMMA) { + pos++; + break; + } + if(type == RPAREN) { + break; + } + } + + lexer_peek(text, pos, &type, &start, &len); + } + + /* + * When this token is a semicolon + * we're done looking up the argument + * nodes. In the other case, we try to + * lookup the other arguments in the + * list. + */ + if(type != RPAREN) { + while(1) { + if(lexer_peek(text, pos + 1, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } else if(type == TOPERATOR) { + go = TOPERATOR; + step_out = step; + break; + } + + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + switch(type) { + /* + * If the arguments are a parenthesis or + * another function, link those together. + */ + case LPAREN: { + struct vm_cache_t *cache = vm_cache_get(LPAREN, pos); + /* LCOV_EXCL_START*/ + if(cache == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + node = (struct vm_tfunction_t *)&obj->ast.buffer[step]; + struct vm_lparen_t *paren = (struct vm_lparen_t *)&obj->ast.buffer[cache->step]; + int oldpos = pos; + pos = cache->end; + node->go[arg++] = cache->step; + paren->ret = step; + vm_cache_del(oldpos); + } break; + case TFUNCTION: { + struct vm_cache_t *cache = vm_cache_get(TFUNCTION, pos); + /* LCOV_EXCL_START*/ + if(cache == NULL) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + + node = (struct vm_tfunction_t *)&obj->ast.buffer[step]; + struct vm_tfunction_t *func = (struct vm_tfunction_t *)&obj->ast.buffer[cache->step]; + /* LCOV_EXCL_STOP*/ + int oldpos = pos; + pos = cache->end; + + node->go[arg++] = cache->step; + func->ret = step; + vm_cache_del(oldpos); + } break; + case TNUMBER1: + case TNUMBER2: + case TNUMBER3: { + int a = vm_parent(text, obj, type, start, len, 0); + + pos++; + + node = (struct vm_tfunction_t *)&obj->ast.buffer[step]; + node->go[arg++] = a; + + struct vm_tnumber_t *tmp = (struct vm_tnumber_t *)&obj->ast.buffer[a]; + tmp->ret = step; + } break; + case VNULL: + case TVAR: { + int a = vm_parent(text, obj, type, start, len, 0); + + pos++; + + node = (struct vm_tfunction_t *)&obj->ast.buffer[step]; + node->go[arg++] = a; + + struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[a]; + tmp->ret = step; + } break; + default: { + logprintf_P(F("ERROR: Unexpected token")); + return -1; + } break; + } + + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + /* + * A right parenthesis means we've + * reached the end of the argument list + */ + if(type == RPAREN) { + pos++; + break; + } else if(type != TCOMMA) { + logprintf_P(F("ERROR: Expected a closing parenthesis")); + return -1; + } + + pos++; + } + } else { + if(lexer_peek(text, pos, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + + pos++; + } + + /* + * When we're back to the function + * root, cache it for further linking. + */ + if(go == TFUNCTION) { + vm_cache_add(TFUNCTION, step, has_function, pos); + + r_rewind = has_function; + + go = -1; + + pos = 0; + + has_paren = -1; + has_function = -1; + has_if = -1; + has_on = -1; + step_out = -1; + } + } break; + } + } else { + /* + * Start looking for the furthest + * nestable token, so we can cache + * it first. + */ + if(r_rewind == -1) { + if(lexer_peek(text, pos++, &type, &start, &len) < 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + /* + * If we found the furthest, rewind back step + * by step until we are at the beginning again. + */ + } else { + if(r_rewind > 0 && lexer_peek(text, --r_rewind, &type, &start, &len) < 0) { + go = -1; + } + pos = r_rewind+1; + } + if(type == TFUNCTION) { + has_function = pos-1; + } else if(type == TIF) { + has_if = pos-1; + } else if(type == TEVENT) { + has_on = pos-1; + } else if(type == LPAREN && + lexer_peek(text, pos-2, &type1, &start, &len) >= 0 && + type1 != TFUNCTION && type1 != TCEVENT) { + has_paren = pos-1; + } + if(has_function != -1 || has_paren != -1 || has_if != -1 || has_on != -1) { + if(type == TEOF || r_rewind > -1) { + if(MAX(has_function, MAX(has_paren, MAX(has_if, has_on))) == has_function) { + offset = (pos = has_function)+1; + go = TFUNCTION; + continue; + } else if(MAX(has_function, MAX(has_paren, MAX(has_if, has_on))) == has_if) { + offset = (pos = has_if); + if(has_if > 0) { + offset++; + } + go = TIF; + continue; + } else if(MAX(has_function, MAX(has_paren, MAX(has_if, has_on))) == has_on) { + offset = (pos = has_on); + go = TEVENT; + if(has_on > 0) { + offset++; + } + continue; + } else { + offset = (pos = has_paren)+1; + go = LPAREN; + continue; + } + has_paren = -1; + has_if = -1; + has_on = -1; + has_function = -1; + step_out = -1; + pos = 0; + } + } else if(r_rewind <= 0) { + /* + * This should never be reached, because at + * r_rewind = 0 either an on or if token + * should be found, already parsing the full + * rule. + */ + /* LCOV_EXCL_START*/ + /* + go = TSTART; + pos = 0; + continue; + */ + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + /* LCOV_EXCL_STOP*/ + } + } + } + +/* LCOV_EXCL_START*/ + struct vm_tstart_t *node = (struct vm_tstart_t *)&obj->ast.buffer[0]; + if(node->go == 0) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } +/* LCOV_EXCL_STOP*/ + return 0; +} + +/*LCOV_EXCL_START*/ +#ifdef DEBUG +static void print_ast(struct rules_t *obj) { + int i = 0, x = 0; + + for(i=x;alignedbytes(i)ast.nrbytes;i++) { + i = alignedbytes(i); + switch(obj->ast.buffer[i]) { + case TSTART: { + struct vm_tstart_t *node = (struct vm_tstart_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"START\"]\n", i); + printf("\"%d\" -> \"%d\"\n", i, node->go); + printf("\"%d\" -> \"%d\"\n", i, node->ret); + i+=sizeof(struct vm_tstart_t)-1; + } break; + case TEOF: { + printf("\"%d\"[label=\"EOF\"]\n", i); + i+=sizeof(struct vm_teof_t)-1; + } break; + case VNULL: { + printf("\"%d\"[label=\"NULL\"]\n", i); + i+=sizeof(struct vm_vnull_t)-1; + } break; + case TIF: { + struct vm_tif_t *node = (struct vm_tif_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"IF\"]\n", i); + printf("\"%d\" -> \"%d\"\n", i, node->go); + // printf("\"%i\" -> \"%i\"\n", i, node->ret); + if(node->true_ > 0) { + printf("\"%d\" -> \"%d\"\n", i, node->true_); + } + if(node->false_ > 0) { + printf("\"%d\" -> \"%d\"\n", i, node->false_); + } + printf("{ rank=same edge[style=invis]"); + if(node->true_ > 0) { + printf(" \"%d\" -> \"%d\";", node->go, node->true_); + } + if(node->false_ > 0) { + printf(" \"%d\" -> \"%d\";", node->true_, node->false_); + } + printf(" rankdir = LR}\n"); + i+=sizeof(struct vm_tif_t)-1; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"(\"]\n", i); + printf("\"%d\" -> \"%d\"\n", i, node->go); + // printf("\"%d\" -> \"%d\"\n", i, node->ret); + i+=sizeof(struct vm_lparen_t)-1; + } break; + case TFALSE: + case TTRUE: { + int x = 0; + struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[i]; + if((obj->ast.buffer[i]) == TFALSE) { + printf("\"%d\"[label=\"FALSE\"]\n", i); + } else { + printf("\"%d\"[label=\"TRUE\"]\n", i); + } + for(x=0;xnrgo;x++) { + printf("\"%d\" -> \"%d\"\n", i, node->go[x]); + } + printf("{ rank=same edge[style=invis] "); + for(x=0;xnrgo-1;x++) { + printf("\"%d\" -> \"%d\"", node->go[x], node->go[x+1]); + if(x+1 < node->nrgo-1) { + printf(" -> "); + } + } + printf(" rankdir = LR}\n"); + i+=sizeof(struct vm_ttrue_t)+(sizeof(uint16_t)*node->nrgo)-1; + } break; + case TFUNCTION: { + int x = 0; + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"%s\"]\n", i, rule_functions[node->token].name); + for(x=0;xnrgo;x++) { + printf("\"%d\" -> \"%d\"\n", i, node->go[x]); + } + // printf("\"%d\" -> \"%d\"\n", i, node->ret); + i+=sizeof(struct vm_tfunction_t)+(sizeof(uint16_t)*node->nrgo)-1; + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"%s\"]\n", i, node->token); + if(node->go > 0) { + printf("\"%d\" -> \"%d\"\n", i, node->go); + } + // printf("\"%d\" -> \"%d\"\n", i, node->ret); + i+=sizeof(struct vm_tvar_t)+strlen((char *)node->token); + } break; + case TNUMBER: { + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"%s\"]\n", i, node->token); + i+=sizeof(struct vm_tnumber_t)+strlen((char *)node->token); + } break; + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"%d\"]\n", i, node->value); + i+=sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"%g\"]\n", i, node->value); + i+=sizeof(struct vm_vfloat_t)-1; + } break; + case TEVENT: { + struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"%s\"]\n", i, node->token); + // printf("\"%i\" -> \"%i\"\n", i, node->ret); + if(node->go > 0) { + printf("\"%d\" -> \"%d\"\n", i, node->go); + } + i+=sizeof(struct vm_tevent_t)+strlen((char *)node->token); + } break; + case TCEVENT: { + struct vm_tcevent_t *node = (struct vm_tcevent_t *)&obj->ast.buffer[i]; + printf("\"%d\"[label=\"%s\"]\n", i, node->token); + // printf("\"%i\" -> \"%i\"\n", i, node->ret); + i+=sizeof(struct vm_tcevent_t)+strlen((char *)node->token); + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[i]; + + printf("\"%d\"[label=\"%s\"]\n", i, rule_operators[node->token].name); + printf("\"%d\" -> \"%d\"\n", i, node->right); + printf("\"%d\" -> \"%d\"\n", i, node->left); + printf("{ rank=same edge[style=invis] \"%d\" -> \"%d\" rankdir = LR}\n", node->left, node->right); + // printf("\"%d\" -> \"%d\"\n", i, node->ret); + i+=sizeof(struct vm_toperator_t)-1; + } break; + default: { + } break; + } + } +} + +static void print_steps(struct rules_t *obj) { + int i = 0, x = 0; + + for(i=x;alignedbytes(i)ast.nrbytes;i++) { + i = alignedbytes(i); + switch(obj->ast.buffer[i]) { + case TSTART: { + struct vm_tstart_t *node = (struct vm_tstart_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"START\"]\n", i); + printf("\"%d-3\"[label=\"%d\" shape=square]\n", i, node->ret); + printf("\"%d-4\"[label=\"%d\" shape=square]\n", i, node->go); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + printf("\"%d-2\" -> \"%d-4\"\n", i, i); + i+=sizeof(struct vm_tstart_t)-1; + } break; + case TEOF: { + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"EOF\"]\n", i); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + i+=sizeof(struct vm_teof_t)-1; + } break; + case VNULL: { + struct vm_vnull_t *node = (struct vm_vnull_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"NULL\"]\n", i); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + i+=sizeof(struct vm_vnull_t)-1; + } break; + case TIF: { + struct vm_tif_t *node = (struct vm_tif_t *)&obj->ast.buffer[i]; + printf("\"%d-2\"[label=\"IF\"]\n", i); + printf("\"%d-2\" -> \"%d-4\"\n", i, i); + if(node->true_ > 0) { + printf("\"%d-2\" -> \"%d-6\"\n", i, i); + } + if(node->false_ > 0) { + printf("\"%d-2\" -> \"%d-7\"\n", i, i); + } + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + if(node->true_ > 0) { + printf("\"%d-6\"[label=\"%d\" shape=square]\n", i, node->true_); + } + if(node->false_ > 0) { + printf("\"%d-7\"[label=\"%d\" shape=square]\n", i, node->false_); + } + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + printf("\"%d-4\"[label=\"%d\" shape=square]\n", i, node->go); + i+=sizeof(struct vm_tif_t)-1; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"(\"]\n", i); + printf("\"%d-2\" -> \"%d-4\"\n", i, i); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + printf("\"%d-4\"[label=\"%d\" shape=square]\n", i, node->go); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + i+=sizeof(struct vm_lparen_t)-1; + } break; + case TFALSE: + case TTRUE: { + int x = 0; + struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + if((obj->ast.buffer[i]) == TFALSE) { + printf("\"%d-2\"[label=\"FALSE\"]\n", i); + } else { + printf("\"%d-2\"[label=\"TRUE\"]\n", i); + } + for(x=0;xnrgo;x++) { + printf("\"%d-2\" -> \"%d-%d\"\n", i, i, x+3); + printf("\"%d-%d\"[label=\"%d\" shape=square]\n", i, x+3, node->go[x]); + } + printf("\"%d-2\" -> \"%d-%d\"\n", i, i, x+4); + printf("\"%d-%d\"[label=\"%d\" shape=diamond]\n", i, x+4, node->ret); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + i+=sizeof(struct vm_ttrue_t)+(sizeof(uint16_t)*node->nrgo)-1; + } break; + case TFUNCTION: { + int x = 0; + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"%s\"]\n", i, rule_functions[node->token].name); + for(x=0;xnrgo;x++) { + printf("\"%d-2\" -> \"%d-%d\"\n", i, i, x+3); + printf("\"%d-%d\"[label=\"%d\" shape=square]\n", i, x+3, node->go[x]); + } + printf("\"%d-2\" -> \"%d-%d\"\n", i, i, x+4); + printf("\"%d-%d\"[label=\"%d\" shape=diamond]\n", i, x+4, node->ret); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + i+=sizeof(struct vm_tfunction_t)+(sizeof(uint16_t)*node->nrgo)-1; + } break; + case TCEVENT: { + struct vm_tcevent_t *node = (struct vm_tcevent_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"%s()\"]\n", i, node->token); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + i+=sizeof(struct vm_tcevent_t)+strlen((char *)node->token); + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"%s\"]\n", i, node->token); + if(node->go > 0) { + printf("\"%d-2\" -> \"%d-4\"\n", i, i); + } + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + if(node->go > 0) { + printf("\"%d-4\"[label=\"%d\" shape=square]\n", i, node->go); + } + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + i+=sizeof(struct vm_tvar_t)+strlen((char *)node->token); + } break; + case TEVENT: { + struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"%s\"]\n", i, node->token); + if(node->go > 0) { + printf("\"%d-2\" -> \"%d-4\"\n", i, i); + } + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + if(node->go > 0) { + printf("\"%d-4\"[label=\"%d\" shape=square]\n", i, node->go); + } + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + i+=sizeof(struct vm_tevent_t)+strlen((char *)node->token); + } break; + case TNUMBER: { + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"%s\"]\n", i, node->token); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + i+=sizeof(struct vm_tnumber_t)+strlen((char *)node->token)-1; + } break; + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"%d\"]\n", i, node->value); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + i+=sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-2\"[label=\"%g\"]\n", i, node->value); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=diamond]\n", i, node->ret); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + i+=sizeof(struct vm_vfloat_t)-1; + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[i]; + printf("\"%d-1\"[label=\"%d\" shape=square]\n", i, i); + printf("\"%d-3\"[label=\"%d\" shape=square]\n", i, node->left); + printf("\"%d-4\"[label=\"%d\" shape=square]\n", i, node->right); + printf("\"%d-2\"[label=\"%s\"]\n", i, rule_operators[node->token].name); + printf("\"%d-1\" -> \"%d-2\"\n", i, i); + printf("\"%d-5\"[label=\"%d\" shape=diamond]\n", i, node->ret); + printf("\"%d-2\" -> \"%d-3\"\n", i, i); + printf("\"%d-2\" -> \"%d-4\"\n", i, i); + printf("\"%d-2\" -> \"%d-5\"\n", i, i); + i+=sizeof(struct vm_toperator_t)-1; + } break; + default: { + } break; + } + } +} + +static void print_tree(struct rules_t *obj) { + print_steps(obj); + print_ast(obj); +} +#endif +/*LCOV_EXCL_STOP*/ + +static int vm_value_set(struct rules_t *obj, int step, int ret) { + int out = obj->varstack.nrbytes; + +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, __LINE__, out); +#endif + switch(obj->ast.buffer[step]) { + case TNUMBER: { + float var = 0; + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[step]; + var = atof((char *)node->token); + + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *value = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + value->type = VINTEGER; + value->ret = ret; + value->value = (int)var; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); +#ifdef DEBUG + printf("%s %d %d %d\n", __FUNCTION__, __LINE__, out, (int)var); +#endif + } break; + case VFLOAT: { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *value = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vfloat_t *cpy = (struct vm_vfloat_t *)&obj->ast.buffer[step]; + value->type = VFLOAT; + value->ret = ret; + value->value = cpy->value; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); +#ifdef DEBUG + printf("%s %d %d %g\n", __FUNCTION__, __LINE__, out, cpy->value); +#endif + } break; + case VINTEGER: { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *value = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + struct vm_vinteger_t *cpy = (struct vm_vinteger_t *)&obj->ast.buffer[step]; + value->type = VINTEGER; + value->ret = ret; + value->value = cpy->value; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); +#ifdef DEBUG + printf("%s %d %d %d\n", __FUNCTION__, __LINE__, out, cpy->value); +#endif + } break; + case VNULL: { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + + struct vm_vnull_t *value = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + value->type = VNULL; + value->ret = ret; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); +#ifdef DEBUG + printf("%s %d %d NULL\n", __FUNCTION__, __LINE__, out); +#endif + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + return out; +} + +static int vm_value_upd_pos(struct rules_t *obj, int val, int step) { + switch(obj->ast.buffer[step]) { + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[step]; + node->value = val; + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[step]; + node->value = val; + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[step]; + node->value = val; + } break; + case VNULL: { + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + return 0; +} + +static int vm_value_clone(struct rules_t *obj, unsigned char *val) { + int ret = obj->varstack.nrbytes; + + switch(val[0]) { + case VINTEGER: { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vinteger_t)); + + struct vm_vinteger_t *cpy = (struct vm_vinteger_t *)&val[0]; + struct vm_vinteger_t *value = (struct vm_vinteger_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + value->type = VINTEGER; + value->ret = 0; + value->value = (int)cpy->value; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } break; + case VFLOAT: { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vfloat_t)); + + struct vm_vfloat_t *cpy = (struct vm_vfloat_t *)&val[0]; + struct vm_vfloat_t *value = (struct vm_vfloat_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + value->type = VFLOAT; + value->ret = 0; + value->value = cpy->value; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } break; + case VNULL: { + unsigned int size = alignedbytes(obj->varstack.nrbytes+sizeof(struct vm_vnull_t)); + + struct vm_vnull_t *value = (struct vm_vnull_t *)&obj->varstack.buffer[obj->varstack.nrbytes]; + value->type = VNULL; + value->ret = 0; + obj->varstack.nrbytes = size; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + return ret; +} + +static int vm_value_del(struct rules_t *obj, unsigned int idx) { + int x = 0, ret = 0; + + if(idx == obj->varstack.nrbytes) { + return -1; + } + +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, __LINE__, idx); +#endif + switch(obj->varstack.buffer[idx]) { + case VINTEGER: { + ret = alignedbytes(sizeof(struct vm_vinteger_t)); + memmove(&obj->varstack.buffer[idx], &obj->varstack.buffer[idx+ret], obj->varstack.nrbytes-idx-ret); + + obj->varstack.nrbytes -= ret; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } break; + case VFLOAT: { + ret = alignedbytes(sizeof(struct vm_vfloat_t)); + memmove(&obj->varstack.buffer[idx], &obj->varstack.buffer[idx+ret], obj->varstack.nrbytes-idx-ret); + + obj->varstack.nrbytes -= ret; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } break; + case VNULL: { + ret = alignedbytes(sizeof(struct vm_vnull_t)); + memmove(&obj->varstack.buffer[idx], &obj->varstack.buffer[idx+ret], obj->varstack.nrbytes-idx-ret); + + obj->varstack.nrbytes -= ret; + obj->varstack.bufsize = MAX(obj->varstack.bufsize, alignedvarstack(obj->varstack.nrbytes)); + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + /* + * Values are linked back to their root node, + * by their absolute position in the bytecode. + * If a value is deleted, these positions changes, + * so we need to update all nodes. + */ + for(x=idx;alignedbytes(x)varstack.nrbytes;x++) { + x = alignedbytes(x); +#ifdef DEBUG + printf("%s %d %d\n", __FUNCTION__, __LINE__, x); +#endif + switch(obj->varstack.buffer[x]) { + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&obj->varstack.buffer[x]; + if(node->ret > 0) { + vm_value_upd_pos(obj, x, node->ret); + } + x += sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&obj->varstack.buffer[x]; + if(node->ret > 0) { + vm_value_upd_pos(obj, x, node->ret); + } + x += sizeof(struct vm_vfloat_t)-1; + } break; + case VNULL: { + struct vm_vnull_t *node = (struct vm_vnull_t *)&obj->varstack.buffer[x]; + if(node->ret > 0) { + vm_value_upd_pos(obj, x, node->ret); + } + x += sizeof(struct vm_vnull_t)-1; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + } + + return ret; +} + +/*LCOV_EXCL_START*/ +void valprint(struct rules_t *obj, char *out, int size) { + int x = 0, pos = 0; + memset(out, 0, size); + /* + * This is only used for debugging purposes + */ + for(x=4;alignedbytes(x)varstack.nrbytes;x++) { + if(alignedbytes(x) < obj->varstack.nrbytes) { + x = alignedbytes(x); + switch(obj->varstack.buffer[x]) { + case VINTEGER: { + struct vm_vinteger_t *val = (struct vm_vinteger_t *)&obj->varstack.buffer[x]; + switch(obj->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = %d", node->token, val->value); + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = %d", rule_functions[node->token].name, val->value); + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = %d", rule_operators[node->token].name, val->value); + } break; + default: { + // printf("err: %s %d %d\n", __FUNCTION__, __LINE__, obj->ast.buffer[val->ret]); + // exit(-1); + } break; + } + x += sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *val = (struct vm_vfloat_t *)&obj->varstack.buffer[x]; + switch(obj->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = %g", node->token, val->value); + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = %g", rule_functions[node->token].name, val->value); + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = %g", rule_operators[node->token].name, val->value); + } break; + default: { + // printf("err: %s %d\n", __FUNCTION__, __LINE__); + // exit(-1); + } break; + } + x += sizeof(struct vm_vfloat_t)-1; + } break; + case VNULL: { + struct vm_vnull_t *val = (struct vm_vnull_t *)&obj->varstack.buffer[x]; + switch(obj->ast.buffer[val->ret]) { + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = NULL", node->token); + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = NULL", rule_functions[node->token].name); + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[val->ret]; + pos += snprintf(&out[pos], size - pos, "%s = NULL", rule_operators[node->token].name); + } break; + default: { + // printf("err: %s %d\n", __FUNCTION__, __LINE__); + // exit(-1); + } break; + } + x += sizeof(struct vm_vnull_t)-1; + } break; + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + } break; + } + } + } + + if(rule_options.prt_token_val_cb != NULL) { + rule_options.prt_token_val_cb(obj, out, size); + } +} +/*LCOV_EXCL_START*/ + +static void vm_clear_values(struct rules_t *obj) { + int i = 0; + for(i=0;alignedbytes(i)ast.nrbytes;i++) { + + i = alignedbytes(i); + switch(obj->ast.buffer[i]) { + case TSTART: { + i+=sizeof(struct vm_tstart_t)-1; + } break; + case TEOF: { + i+=sizeof(struct vm_teof_t)-1; + } break; + case VNULL: { + i+=sizeof(struct vm_vnull_t)-1; + } break; + case TIF: { + i+=sizeof(struct vm_tif_t)-1; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[i]; + node->value = 0; + i+=sizeof(struct vm_lparen_t)-1; + } break; + case TFALSE: + case TTRUE: { + struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[i]; + i+=sizeof(struct vm_ttrue_t)+(sizeof(uint16_t)*node->nrgo)-1; + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[i]; + node->value = 0; + i+=sizeof(struct vm_tfunction_t)+(sizeof(uint16_t)*node->nrgo)-1; + } break; + case TCEVENT: { + struct vm_tcevent_t *node = (struct vm_tcevent_t *)&obj->ast.buffer[i]; + i+=sizeof(struct vm_tcevent_t)+strlen((char *)node->token); + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[i]; + if(rule_options.clr_token_val_cb != NULL) { + rule_options.clr_token_val_cb(obj, i); + } else { + node->value = 0; + } + i+=sizeof(struct vm_tvar_t)+strlen((char *)node->token); + } break; + case TEVENT: { + struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[i]; + i += sizeof(struct vm_tevent_t)+strlen((char *)node->token); + } break; + case TNUMBER: { + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[i]; + i+=sizeof(struct vm_tnumber_t)+strlen((char *)node->token); + } break; + case VINTEGER: { + i+=sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + i+=sizeof(struct vm_vfloat_t)-1; + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[i]; + node->value = 0; + i+=sizeof(struct vm_toperator_t)-1; + } break; + default: { + } break; + } + } +} + +int rule_run(struct rules_t *obj, int validate) { +#ifdef DEBUG + printf("----------\n"); + printf("%s %d\n", __FUNCTION__, obj->nr); + printf("----------\n"); +#endif + + int go = 0, ret = -1, i = -1, start = -1; + go = start = 0; + + while(go != -1) { +#ifdef ESP8266 + ESP.wdtFeed(); +#endif + +/*LCOV_EXCL_START*/ +#ifdef DEBUG + printf("goto: %d, ret: %d, bytes: %d\n", go, ret, obj->ast.nrbytes); + printf("AST stack is %d bytes, local stack is %d bytes\n", obj->ast.nrbytes, obj->varstack.nrbytes); + printf("AST bufsize is %d bytes, local bufsize is %d bytes\n", obj->ast.bufsize, obj->varstack.bufsize); + + { + char out[1024]; + valprint(obj, (char *)&out, 1024); + printf("%s\n", out); + } +#endif +/*LCOV_EXCL_STOP*/ + + switch(obj->ast.buffer[go]) { + case TSTART: { + struct vm_tstart_t *node = (struct vm_tstart_t *)&obj->ast.buffer[go]; + if(ret > -1) { + go = -1; + } else { + if(obj->cont.go > 0) { + go = obj->cont.go; + ret = obj->cont.ret; + obj->cont.go = 0; + obj->cont.ret = 0; + } else { + vm_clear_values(obj); + go = node->go; + } + } + } break; + case TEVENT: { + struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[go]; + if(node->go == ret) { + ret = go; + go = node->ret; + } else { + go = node->go; + ret = go; + } + } break; + case TIF: { + struct vm_tif_t *node = (struct vm_tif_t *)&obj->ast.buffer[go]; + int val = -1; + if(ret > -1) { + switch(obj->ast.buffer[ret]) { + case TOPERATOR: { + struct vm_toperator_t *op = (struct vm_toperator_t *)&obj->ast.buffer[ret]; + struct vm_vinteger_t *tmp = (struct vm_vinteger_t *)&obj->varstack.buffer[op->value]; + + val = tmp->value; + vm_value_del(obj, op->value); + + /* + * Reassign node due to various (unsigned char *)REALLOC's + */ + node = (struct vm_tif_t *)&obj->ast.buffer[go]; + } break; + case LPAREN: { + struct vm_lparen_t *op = (struct vm_lparen_t *)&obj->ast.buffer[ret]; + struct vm_vinteger_t *tmp = (struct vm_vinteger_t *)&obj->varstack.buffer[op->value]; + + val = tmp->value; + vm_value_del(obj, op->value); + + /* + * Reassign node due to various (unsigned char *)REALLOC's + */ + node = (struct vm_tif_t *)&obj->ast.buffer[go]; + } break; + case TTRUE: + case TFALSE: + case TIF: + case TEVENT: + break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + } + + if(node->false_ == ret && node->false_ > 0) { + ret = go; + go = node->ret; + } else if(node->true_ == ret) { + if(node->false_ != 0 && (val == 0 || validate == 1)) { + go = node->false_; + ret = go; + } else { + ret = go; + go = node->ret; + } + } else if(node->go == ret) { + if(node->false_ != 0 && val == 0 && validate == 0) { + go = node->false_; + ret = go; + } else if(val == 1 || validate == 1) { + ret = go; + go = node->true_; + } else { + ret = go; + go = node->ret; + } + } else { + go = node->go; + } + } break; + case TCEVENT: { + struct vm_tcevent_t *node = (struct vm_tcevent_t *)&obj->ast.buffer[go]; + + if(rule_options.event_cb == NULL) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: No 'event_cb' set to handle events")); + return -1; + /* LCOV_EXCL_STOP*/ + } + + obj->cont.ret = go; + obj->cont.go = node->ret; + + /* + * Tail recursive + */ + return rule_options.event_cb(obj, (char *)node->token); + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[go]; + + int match = 0, tmp = go; + + for(i=0;inrgo;i++) { + if(node->go[i] == ret) { + match = 1; + if(i+1 < node->nrgo) { + switch(obj->ast.buffer[node->go[i+1]]) { + case TNUMBER: + case VINTEGER: + case VFLOAT: { + ret = go; + go = node->go[i+1]; + } break; + case VNULL: { + ret = go; + go = node->go[i+1]; + } break; + case LPAREN: { + ret = go; + go = node->go[i+1]; + } break; + case TOPERATOR: { + ret = go; + go = node->go[i+1]; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + } else { + go = 0; + } + break; + } + } + if(match == 0) { + ret = go; + go = node->go[0]; + } + + if(go == 0) { + go = tmp; + unsigned int idx = node->token, i = 0, shift = 0; + int c = 0; + uint16_t values[node->nrgo]; + memset(&values, 0, node->nrgo); + + /* LCOV_EXCL_START*/ + if(idx > nr_rule_functions) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + for(i=0;inrgo;i++) { + switch(obj->ast.buffer[node->go[i]]) { + case TNUMBER: + case VINTEGER: + case VFLOAT: { + values[i] = vm_value_set(obj, node->go[i], 0); + + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_tfunction_t *)&obj->ast.buffer[go]; + } break; + case LPAREN: { + struct vm_lparen_t *tmp = (struct vm_lparen_t *)&obj->ast.buffer[node->go[i]]; + struct vm_tgeneric_t *val = (struct vm_tgeneric_t *)&obj->varstack.buffer[tmp->value]; + values[i] = tmp->value; + tmp->value = 0; + val->ret = 0; + } break; + case TOPERATOR: { + struct vm_toperator_t *tmp = (struct vm_toperator_t *)&obj->ast.buffer[node->go[i]]; + struct vm_tgeneric_t *val = (struct vm_tgeneric_t *)&obj->varstack.buffer[tmp->value]; + values[i] = tmp->value; + tmp->value = 0; + val->ret = 0; + } break; + case TFUNCTION: { + struct vm_tfunction_t *tmp = (struct vm_tfunction_t *)&obj->ast.buffer[node->go[i]]; + struct vm_tgeneric_t *val = (struct vm_tgeneric_t *)&obj->varstack.buffer[tmp->value]; + values[i] = tmp->value; + tmp->value = 0; + val->ret = 0; + } break; + case VNULL: { + values[i] = vm_value_set(obj, node->go[i], node->go[i]); + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_tfunction_t *)&obj->ast.buffer[go]; + } break; + case TVAR: { + if(rule_options.get_token_val_cb != NULL && rule_options.cpy_token_val_cb != NULL) { + rule_options.cpy_token_val_cb(obj, node->go[i]); // TESTME + unsigned char *val = rule_options.get_token_val_cb(obj, node->go[i]); + /* LCOV_EXCL_START*/ + if(val == NULL) { + logprintf_P(F("FATAL: 'get_token_val_cb' did not return a value")); + return -1; + } + /* LCOV_EXCL_STOP*/ + values[i] = vm_value_clone(obj, val); + + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_tfunction_t *)&obj->ast.buffer[go]; + } else { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: No '[get|cpy]_token_val_cb' set to handle variables")); + return -1; + /* LCOV_EXCL_STOP*/ + } + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + } + + if(rule_functions[idx].callback(obj, node->nrgo, values, &c) != 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: function call '%s' failed"), rule_functions[idx].name); + return -1; + /* LCOV_EXCL_STOP*/ + } + + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_tfunction_t *)&obj->ast.buffer[go]; + if(c > 0) { + switch(obj->varstack.buffer[c]) { + case VINTEGER: { + struct vm_vinteger_t *tmp = (struct vm_vinteger_t *)&obj->varstack.buffer[c]; + tmp->ret = go; + node->value = c; + } break; + case VNULL: { + struct vm_vnull_t *tmp = (struct vm_vnull_t *)&obj->varstack.buffer[c]; + tmp->ret = go; + node->value = c; + } break; + case VFLOAT: { + struct vm_vfloat_t *tmp = (struct vm_vfloat_t *)&obj->varstack.buffer[c]; + tmp->ret = go; + node->value = c; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + } else { + node->value = 0; + } + + for(i=0;inrgo;i++) { + switch(obj->varstack.buffer[values[i] - shift]) { + case VFLOAT: + case VNULL: + case VINTEGER: { + shift += vm_value_del(obj, values[i] - shift); + + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_tfunction_t *)&obj->ast.buffer[go]; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + } + + ret = go; + go = node->ret; + } + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + + if(node->right == ret || + ( + ( + obj->ast.buffer[node->left] == VFLOAT || + obj->ast.buffer[node->left] == VINTEGER || + obj->ast.buffer[node->left] == TNUMBER + ) && + ( + obj->ast.buffer[node->right] == VFLOAT || + obj->ast.buffer[node->right] == VINTEGER || + obj->ast.buffer[node->right] == TNUMBER + ) + ) + ) { + int a = 0, b = 0, c = 0, step = 0; + step = node->left; + + switch(obj->ast.buffer[step]) { + case TNUMBER: + case VFLOAT: + case VINTEGER: { + a = vm_value_set(obj, step, step); + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + } break; + case TOPERATOR: { + struct vm_toperator_t *tmp = (struct vm_toperator_t *)&obj->ast.buffer[step]; + a = tmp->value; + tmp->value = 0; + } break; + case LPAREN: { + struct vm_lparen_t *tmp = (struct vm_lparen_t *)&obj->ast.buffer[step]; + a = tmp->value; + tmp->value = 0; + } break; + case TFUNCTION: { + struct vm_tfunction_t *tmp = (struct vm_tfunction_t *)&obj->ast.buffer[step]; + a = tmp->value; + tmp->value = 0; + } break; + case VNULL: { + a = vm_value_set(obj, step, step); + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + } break; + case TVAR: { + /* + * If vars are seperate steps + */ + // struct vm_tvar_t *tmp = (struct vm_tvar_t *)&obj->ast.buffer[step]; + // a = tmp->value; + + if(rule_options.get_token_val_cb != NULL && rule_options.cpy_token_val_cb != NULL) { + rule_options.cpy_token_val_cb(obj, step); // TESTME + unsigned char *val = rule_options.get_token_val_cb(obj, step); + + /* LCOV_EXCL_START*/ + if(val == NULL) { + logprintf_P(F("FATAL: 'get_token_val_cb' did not return a value")); + return -1; + } + /* LCOV_EXCL_STOP*/ + a = vm_value_clone(obj, val); + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + } else { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: No '[get|cpy]_token_val_cb' set to handle variables")); + return -1; + /* LCOV_EXCL_STOP*/ + } + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + step = node->right; + + switch(obj->ast.buffer[step]) { + case TNUMBER: + case VFLOAT: + case VINTEGER: { + b = vm_value_set(obj, step, step); + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + } break; + case TOPERATOR: { + struct vm_toperator_t *tmp = (struct vm_toperator_t *)&obj->ast.buffer[step]; + b = tmp->value; + tmp->value = 0; + } break; + case LPAREN: { + struct vm_lparen_t *tmp = (struct vm_lparen_t *)&obj->ast.buffer[step]; + b = tmp->value; + tmp->value = 0; + } break; + case TFUNCTION: { + struct vm_tfunction_t *tmp = (struct vm_tfunction_t *)&obj->ast.buffer[step]; + b = tmp->value; + tmp->value = 0; + } break; + case VNULL: { + b = vm_value_set(obj, step, step); + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + } break; + case TVAR: { + /* + * If vars are seperate steps + */ + if(rule_options.get_token_val_cb != NULL && rule_options.cpy_token_val_cb != NULL) { + rule_options.cpy_token_val_cb(obj, step); // TESTME + unsigned char *val = rule_options.get_token_val_cb(obj, step); + + /* LCOV_EXCL_START*/ + if(val == NULL) { + logprintf_P(F("FATAL: 'get_token_val_cb' did not return a value")); + return -1; + } + /* LCOV_EXCL_STOP*/ + b = vm_value_clone(obj, val); + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + } else { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: No '[get|cpy]_token_val_cb' set to handle variables")); + return -1; + /* LCOV_EXCL_STOP*/ + } + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + unsigned int idx = node->token; + + /* LCOV_EXCL_START*/ + if(idx > nr_rule_operators) { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } + /* LCOV_EXCL_STOP*/ + + if(rule_operators[idx].callback(obj, a, b, &c) != 0) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: operator call '%s' failed"), rule_operators[idx].name); + return -1; + /* LCOV_EXCL_STOP*/ + } + + /* + * Reassign node due to possible (unsigned char *)REALLOC's + * in the callbacks + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + + switch(obj->varstack.buffer[c]) { + case VINTEGER: { + struct vm_vinteger_t *tmp = (struct vm_vinteger_t *)&obj->varstack.buffer[c]; + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + tmp->ret = go; + node->value = c; + } break; + case VFLOAT: { + struct vm_vfloat_t *tmp = (struct vm_vfloat_t *)&obj->varstack.buffer[c]; + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + tmp->ret = go; + node->value = c; + } break; + case VNULL: { + struct vm_vnull_t *tmp = (struct vm_vnull_t *)&obj->varstack.buffer[c]; + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + tmp->ret = go; + node->value = c; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + vm_value_del(obj, MAX(a, b)); + + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + + vm_value_del(obj, MIN(a, b)); + + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_toperator_t *)&obj->ast.buffer[go]; + + ret = go; + go = node->ret; + } else if(node->left == ret/* || node->left < obj->pos.parsed*/) { + ret = go; + go = node->right; + } else { + ret = go; + go = node->left; + } + } break; + case TNUMBER: + case VFLOAT: + case VINTEGER: { + int tmp = ret; + ret = go; + go = tmp; + } break; + case TSTRING: { + int tmp = ret; + ret = go; + go = tmp; + } break; + case VNULL: { + int tmp = ret; + ret = go; + go = tmp; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[go]; + if(node->go == ret) { + switch(obj->ast.buffer[node->go]) { + case TOPERATOR: { + struct vm_toperator_t *tmp = (struct vm_toperator_t *)&obj->ast.buffer[ret]; + node->value = tmp->value; + tmp->value = 0; + } break; + case LPAREN: { + struct vm_lparen_t *tmp = (struct vm_lparen_t *)&obj->ast.buffer[ret]; + node->value = tmp->value; + tmp->value = 0; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + ret = go; + go = node->ret; + } else { + ret = go; + go = node->go; + } + } break; + case TFALSE: + case TTRUE: { + struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[go]; + switch(obj->ast.buffer[ret]) { + case TVAR: { + } break; + // case TOPERATOR: { + // struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[ret]; + // idx = node->value; + // node->value = 0; + // } break; + case TFUNCTION: { + struct vm_tfunction_t *tmp = (struct vm_tfunction_t *)&obj->ast.buffer[ret]; + if(tmp->value > 0) { + vm_value_del(obj, tmp->value); + } + node = (struct vm_ttrue_t *)&obj->ast.buffer[go]; + } break; + case TIF: + case TEVENT: + case TCEVENT: + case TTRUE: + case TFALSE: { + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + // if((obj->ast.buffer[ret]) == TOPERATOR) { + // vm_value_del(obj, idx); + + // /* + // * Reassign node due to various (unsigned char *)REALLOC's + // */ + // node = (struct vm_ttrue_t *)&obj->ast.buffer[go]; + // } + + int match = 0, tmp = go; + + for(i=0;inrgo;i++) { + if(node->go[i] == ret) { + match = 1; + if(i+1 < node->nrgo) { + ret = go; + go = node->go[i+1]; + } else { + go = 0; + } + break; + } + } + if(match == 0) { + ret = go; + go = node->go[0]; + } + + if(go == 0) { + go = node->ret; + ret = tmp; + } + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[go]; + + if(node->go == 0) { + if(rule_options.cpy_token_val_cb != NULL) { + rule_options.cpy_token_val_cb(obj, go); + } + + /* + * Reassign node due to various (unsigned char *)REALLOC's + */ + node = (struct vm_tvar_t *)&obj->ast.buffer[go]; + + ret = go; + go = node->ret; + } else { + /* + * When we can find the value in the + * prepared rule and not as a separate + * node. + */ + if(node->go == ret) { + int idx = 0, shift = 0; + + switch(obj->ast.buffer[node->go]) { + case TOPERATOR: { + struct vm_toperator_t *tmp = (struct vm_toperator_t *)&obj->ast.buffer[ret]; + struct vm_tgeneric_t *val = (struct vm_tgeneric_t *)&obj->varstack.buffer[tmp->value]; + idx = tmp->value; + tmp->value = 0; + val->ret = go; + } break; + case TFUNCTION: { + struct vm_tfunction_t *tmp = (struct vm_tfunction_t *)&obj->ast.buffer[ret]; + struct vm_tgeneric_t *val = (struct vm_tgeneric_t *)&obj->varstack.buffer[tmp->value]; + idx = tmp->value; + tmp->value = 0; + val->ret = go; + } break; + case TNUMBER: + case VFLOAT: + case VINTEGER: { + idx = vm_value_set(obj, node->go, go); + + /* + * Reassign node due to various (unsigned char *)REALLOC's + */ + node = (struct vm_tvar_t *)&obj->ast.buffer[go]; + } break; + case VNULL: { + idx = vm_value_set(obj, node->go, go); + /* + * Reassign node due to various (unsigned char *)REALLOC's + */ + node = (struct vm_tvar_t *)&obj->ast.buffer[go]; + } break; + /* + * Clone variable value to new variable + */ + case TVAR: { + if(rule_options.get_token_val_cb != NULL && rule_options.cpy_token_val_cb != NULL) { + rule_options.cpy_token_val_cb(obj, ret); // TESTME + unsigned char *val = rule_options.get_token_val_cb(obj, ret); + /* LCOV_EXCL_START*/ + if(val == NULL) { + logprintf_P(F("FATAL: 'get_token_val_cb' did not return a value")); + return -1; + } + /* LCOV_EXCL_STOP*/ + idx = vm_value_clone(obj, val); + } else { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: No '[get|cpy]_token_val_cb' set to handle variables")); + return -1; + /* LCOV_EXCL_STOP*/ + } + /* + * Reassign node due to possible reallocs + */ + node = (struct vm_tvar_t *)&obj->ast.buffer[go]; + } break; + case LPAREN: { + struct vm_lparen_t *tmp = (struct vm_lparen_t *)&obj->ast.buffer[ret]; + struct vm_tgeneric_t *val = (struct vm_tgeneric_t *)&obj->varstack.buffer[tmp->value - shift]; + idx = tmp->value - shift; + tmp->value = 0; + val->ret = go; + } break; + /* LCOV_EXCL_START*/ + default: { + logprintf_P(F("FATAL: Internal error in %s #%d"), __FUNCTION__, __LINE__); + return -1; + } break; + /* LCOV_EXCL_STOP*/ + } + + if(idx > -1) { + if(rule_options.set_token_val_cb == NULL) { + /* LCOV_EXCL_START*/ + logprintf_P(F("FATAL: No 'set_token_val_cb' set to handle variables")); + return -1; + /* LCOV_EXCL_STOP*/ + } + + rule_options.set_token_val_cb(obj, go, idx); + + vm_value_del(obj, idx); + + /* + * Reassign node due to various (unsigned char *)REALLOC's + */ + node = (struct vm_tvar_t *)&obj->ast.buffer[go]; + } else { + node->value = 0; + } + + ret = go; + go = node->ret; + } else { + ret = go; + go = node->go; + } + } + } break; + } + } + + /* + * Tail recursive + */ + if(obj->caller > 0) { + return rule_options.event_cb(obj, NULL); + } + + return 0; +} + +/*LCOV_EXCL_START*/ +#ifdef DEBUG +void print_bytecode(struct rules_t *obj) { + unsigned int i = 0; + + for(i=0;iast.nrbytes;i++) { + i = alignedbytes(i); + printf("%d", i); + switch(obj->ast.buffer[i]) { + case TIF: { + struct vm_tif_t *node = (struct vm_tif_t *)&obj->ast.buffer[i]; + printf("(TIF)[9]["); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("go: %d, ", node->go); + printf("true_: %d, ", node->true_); + printf("false: %d]\n", node->false_); + i += sizeof(struct vm_tif_t)-1; + } break; + case LPAREN: { + struct vm_lparen_t *node = (struct vm_lparen_t *)&obj->ast.buffer[i]; + printf("(LPAREN)[7]["); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("go: %d, ", node->go); + printf("value: %d]\n", node->value); + i += sizeof(struct vm_lparen_t)-1; + } break; + case TVAR: { + struct vm_tvar_t *node = (struct vm_tvar_t *)&obj->ast.buffer[i]; + printf("(TVAR)[%lu][", 7+strlen((char *)node->token)+1); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("go: %d, ", node->go); + printf("value: %d, ", node->value); + printf("token: %s]\n", node->token); + i += sizeof(struct vm_tvar_t)+strlen((char *)node->token); + } break; + case TEVENT: { + struct vm_tevent_t *node = (struct vm_tevent_t *)&obj->ast.buffer[i]; + printf("(TEVENT)[%lu][", 5+strlen((char *)node->token)+1); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("go: %d, ", node->go); + printf("token: %s]\n", node->token); + i += sizeof(struct vm_tevent_t)+strlen((char *)node->token); + } break; + case TCEVENT: { + struct vm_tcevent_t *node = (struct vm_tcevent_t *)&obj->ast.buffer[i]; + printf("(TCEVENT)[%lu][", 3+strlen((char *)node->token)+1); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("token: %s]\n", node->token); + i += sizeof(struct vm_tcevent_t)+strlen((char *)node->token); + } break; + case TSTART: { + struct vm_tstart_t *node = (struct vm_tstart_t *)&obj->ast.buffer[i]; + printf("(TSTART)[5]["); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("go: %d]\n", node->go); + i += sizeof(struct vm_tstart_t)-1; + } break; + /* LCOV_EXCL_START*/ + case TNUMBER: { + struct vm_tnumber_t *node = (struct vm_tnumber_t *)&obj->ast.buffer[i]; + printf("(TNUMBER)[%lu][", 3+strlen((char *)node->token)+1); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("token: %s]\n", node->token); + i += sizeof(struct vm_tnumber_t)+strlen((char *)node->token); + } break; + /* LCOV_EXCL_STOP*/ + case VINTEGER: { + struct vm_vinteger_t *node = (struct vm_vinteger_t *)&obj->ast.buffer[i]; + printf("(VINTEGER)[%lu][", sizeof(struct vm_vinteger_t)); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("value: %d]\n", node->value); + i += sizeof(struct vm_vinteger_t)-1; + } break; + case VFLOAT: { + struct vm_vfloat_t *node = (struct vm_vfloat_t *)&obj->ast.buffer[i]; + printf("(VFLOAT)[%lu][", sizeof(struct vm_vfloat_t)); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("value: %g]\n", node->value); + i += sizeof(struct vm_vfloat_t)-1; + } break; + case TFALSE: + case TTRUE: { + struct vm_ttrue_t *node = (struct vm_ttrue_t *)&obj->ast.buffer[i]; + printf("(TTRUE)[%lu][", 4+(node->nrgo*sizeof(node->go[0]))); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("nrgo: %d, ", node->nrgo); + printf("go["); + int x = 0; + for(x=0;xnrgo;x++) { + printf("%d: %d", x, node->go[x]); + if(node->nrgo-1 > x) { + printf(", "); + } + } + printf("]]\n"); + i += sizeof(struct vm_ttrue_t)+(sizeof(node->go[0])*node->nrgo)-1; + } break; + case TFUNCTION: { + struct vm_tfunction_t *node = (struct vm_tfunction_t *)&obj->ast.buffer[i]; + printf("(TFUNCTION)[%lu][", 8+(node->nrgo*sizeof(node->go[0]))); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("token: %d, ", node->token); + printf("value: %d, ", node->value); + printf("nrgo: %d, ", node->nrgo); + printf("go["); + int x = 0; + for(x=0;xnrgo;x++) { + printf("%d: %d", x, node->go[x]); + if(node->nrgo-1 > x) { + printf(", "); + } + } + printf("]]\n"); + i += sizeof(struct vm_tfunction_t)+(sizeof(node->go[0])*node->nrgo)-1; + } break; + case TOPERATOR: { + struct vm_toperator_t *node = (struct vm_toperator_t *)&obj->ast.buffer[i]; + printf("(TOPERATOR)[10]["); + printf("type: %d, ", node->type); + printf("ret: %d, ", node->ret); + printf("token: %d, ", node->token); + printf("left: %d, ", node->left); + printf("right: %d, ", node->right); + printf("value: %d]\n", node->value); + i += sizeof(struct vm_toperator_t)-1; + } break; + case TEOF: { + struct vm_teof_t *node = (struct vm_teof_t *)&obj->ast.buffer[i]; + printf("(TEOF)[1][type: %d]\n", node->type); + i += sizeof(struct vm_teof_t)-1; + } break; + case VNULL: { + struct vm_vnull_t *node = (struct vm_vnull_t *)&obj->ast.buffer[i]; + printf("(VNULL)[3]["); + printf("type: %d, ", node->type); + printf("ret: %d]\n", node->ret); + i += sizeof(struct vm_vnull_t)-1; + } break; + } + } +} +#endif +/*LCOV_EXCL_STOP*/ + +int rule_initialize(struct pbuf *input, struct rules_t ***rules, int *nrrules, struct pbuf *mempool, void *userdata) { + unsigned int nrbytes = 0, len = strlen((char *)input->payload), newlen = len; + unsigned int suggested_varstack_size = 0; + + if(mempool->len < 512) { + mempool->len = 512; + } + if(*nrrules >= 64) { +#ifdef ESP8266 + Serial1.println(PSTR("more than the maximum of 64 rule blocks defined")); +#else + printf("more than the maximum of 64 rule blocks defined\n"); +#endif + } + + if(input->len < alignedbuffer(mempool->len)) { +#ifdef ESP8266 + Serial1.println(PSTR("not enough free space in rules mempool")); +#else + printf("not enough free space in rules mempool\n"); +#endif + } + + if(len == 0) { + return 1; + } + *rules = (struct rules_t **)&((unsigned char *)mempool->payload)[0]; + (*rules)[*nrrules] = (struct rules_t *)&((unsigned char *)mempool->payload)[alignedbuffer(mempool->len)]; + memset((*rules)[*nrrules], 0, sizeof(struct rules_t)); + mempool->len += sizeof(struct rules_t); + + (*rules)[*nrrules]->userdata = userdata; + + struct rules_t *obj = (*rules)[*nrrules]; + obj->nr = (*nrrules)+1; + (*nrrules)++; + + obj->ast.nrbytes = 0; + obj->ast.bufsize = 0; + obj->varstack.nrbytes = 4; + obj->varstack.bufsize = 4; + +/*LCOV_EXCL_START*/ +#if defined(DEBUG) or defined(ESP8266) + #ifdef ESP8266 + obj->timestamp.first = micros(); + #else + clock_gettime(CLOCK_MONOTONIC, &obj->timestamp.first); + #endif +#endif +/*LCOV_EXCL_STOP*/ + + if(rule_prepare((char **)&input->payload, &nrbytes, &newlen) == -1) { + return -1; + } + +/*LCOV_EXCL_START*/ +#if defined(DEBUG) or defined(ESP8266) + #ifdef ESP8266 + obj->timestamp.second = micros(); + + logprintf_P(F("rule #%d was prepared in %d microseconds"), obj->nr, obj->timestamp.second - obj->timestamp.first); + #else + clock_gettime(CLOCK_MONOTONIC, &obj->timestamp.second); + + printf("rule #%d was prepared in %.6f seconds\n", obj->nr, + ((double)obj->timestamp.second.tv_sec + 1.0e-9*obj->timestamp.second.tv_nsec) - + ((double)obj->timestamp.first.tv_sec + 1.0e-9*obj->timestamp.first.tv_nsec)); + #endif +#endif +/*LCOV_EXCL_STOP*/ + + +/*LCOV_EXCL_START*/ +#if defined(DEBUG) or defined(ESP8266) + #ifdef ESP8266 + obj->timestamp.first = micros(); + #else + clock_gettime(CLOCK_MONOTONIC, &obj->timestamp.first); + #endif +#endif +/*LCOV_EXCL_STOP*/ + + { + obj->ast.bufsize = alignedbuffer(nrbytes); + + obj->ast.buffer = (unsigned char *)&((unsigned char *)mempool->payload)[alignedbuffer(mempool->len)]; + mempool->len += obj->ast.bufsize; + + suggested_varstack_size = (input->len-mempool->len); + + /* + * The memoffset will be increased below + * as soon as we know how many bytes + * we maximally need. + */ + obj->varstack.buffer = &((unsigned char *)mempool->payload)[mempool->len]; + + memset(obj->ast.buffer, 0, obj->ast.bufsize); + memset(obj->varstack.buffer, 0, suggested_varstack_size); + + if(rule_parse((char **)&input->payload, (int *)&newlen, obj) == -1) { + return -1; + } + + input->len += newlen; + if(((char *)input->payload)[newlen] == 0 && len > newlen) { + input->len += 1; + } + } + +/*LCOV_EXCL_START*/ +#if defined(DEBUG) or defined(ESP8266) + #ifdef ESP8266 + obj->timestamp.second = micros(); + + logprintf_P(F("rule #%d was parsed in %d microseconds"), obj->nr, obj->timestamp.second - obj->timestamp.first); + logprintf_P(F("bytecode is %d bytes"), obj->ast.nrbytes); + #else + clock_gettime(CLOCK_MONOTONIC, &obj->timestamp.second); + + printf("rule #%d was parsed in %.6f seconds\n", obj->nr, + ((double)obj->timestamp.second.tv_sec + 1.0e-9*obj->timestamp.second.tv_nsec) - + ((double)obj->timestamp.first.tv_sec + 1.0e-9*obj->timestamp.first.tv_nsec)); + + printf("bytecode is %d bytes\n", obj->ast.nrbytes); + #endif +#endif +/*LCOV_EXCL_STOP*/ + +/*LCOV_EXCL_START*/ +#ifdef DEBUG + #ifndef ESP8266 + print_bytecode(obj); + printf("\n"); + print_tree(obj); + #endif +#endif +/*LCOV_EXCL_STOP*/ + +/*LCOV_EXCL_START*/ +#if defined(DEBUG) or defined(ESP8266) + #ifdef ESP8266 + obj->timestamp.first = micros(); + #else + clock_gettime(CLOCK_MONOTONIC, &obj->timestamp.first); + #endif +#endif +/*LCOV_EXCL_STOP*/ + + if(rule_run(obj, 1) == -1) { + return -1; + } + + /* + * Reserve space for the actual maximum + * varstack buffer size + */ + mempool->len += alignedbuffer(obj->varstack.bufsize); + +/*LCOV_EXCL_START*/ +#if defined(DEBUG) or defined(ESP8266) + #ifdef ESP8266 + obj->timestamp.second = micros(); + + logprintf_P(F("rule #%d was executed in %d microseconds"), obj->nr, obj->timestamp.second - obj->timestamp.first); + logprintf_P(F("bytecode is %d bytes"), obj->ast.nrbytes); + #else + clock_gettime(CLOCK_MONOTONIC, &obj->timestamp.second); + + printf("rule #%d was executed in %.6f seconds\n", obj->nr, + ((double)obj->timestamp.second.tv_sec + 1.0e-9*obj->timestamp.second.tv_nsec) - + ((double)obj->timestamp.first.tv_sec + 1.0e-9*obj->timestamp.first.tv_nsec)); + + printf("bytecode is %d bytes\n", obj->ast.nrbytes); + #endif +#endif +/*LCOV_EXCL_STOP*/ + + return 0; +} diff --git a/HeishaMon/src/rules/rules.h b/HeishaMon/src/rules/rules.h new file mode 100755 index 00000000..dd90febb --- /dev/null +++ b/HeishaMon/src/rules/rules.h @@ -0,0 +1,256 @@ +/* + Copyright (C) CurlyMo + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. +*/ + + +#ifndef _RULES_H_ +#define _RULES_H_ + +#include + +#ifndef ESP8266 + #define F + #define MEMPOOL_SIZE 16000 + typedef struct pbuf { + struct pbuf *next; + void *payload; + uint16_t tot_len; + uint16_t len; + uint8_t type; + uint8_t flags; + uint16_t ref; + } pbuf; +#else + #include + #include "lwip/pbuf.h" + #define MEMPOOL_SIZE MMU_SEC_HEAP_SIZE +#endif + +#define EPSILON 0.000001 + +/* + * max(sizeof(vm_vfloat_t), sizeof(vm_vinteger_t), sizeof(vm_vnull_t)) + */ +#define MAX_VARSTACK_NODE_SIZE 7 + +#define MAX(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a > _b ? _a : _b; }) + +#define MIN(a,b) \ + ({ __typeof__ (a) _a = (a); \ + __typeof__ (b) _b = (b); \ + _a < _b ? _a : _b; }) + +typedef enum { + TOPERATOR = 1, + TFUNCTION = 2, + TSTRING = 3, + TNUMBER = 4, + TNUMBER1 = 5, + TNUMBER2 = 6, + TNUMBER3 = 7, + TEOF = 8, + LPAREN = 9, + RPAREN = 10, + TCOMMA = 11, + TIF = 12, + TELSE = 13, + TTHEN = 14, + TEVENT = 15, + TCEVENT = 16, + TEND = 17, + TVAR = 18, + TASSIGN = 19, + TSEMICOLON = 20, + TTRUE = 21, + TFALSE = 22, + TSTART = 23, + VCHAR = 24, + VINTEGER = 25, + VFLOAT = 26, + VNULL = 27, +} token_types; + +typedef struct rules_t { + unsigned short nr; + + struct { +#if defined(DEBUG) or defined(ESP8266) + #ifdef ESP8266 + unsigned long first; + unsigned long second; + #else + struct timespec first; + struct timespec second; + #endif +#endif + } timestamp; + + struct { + int parsed; + int vars; + } pos; + + /* Continue here after we processed + * another rule call. + */ + struct { + uint16_t go; + uint16_t ret; + } cont; + + /* To which rule do we return after + * being called from another rule. + */ + int caller; + + struct { + unsigned char *buffer; + unsigned int nrbytes; + unsigned int bufsize; + } ast; + + struct { + unsigned char *buffer; + unsigned int nrbytes; + unsigned int bufsize; + } varstack; + + void *userdata; +} rules_t; + +typedef struct rule_options_t { + /* + * Identifying callbacks + */ + int (*is_token_cb)(char *text, unsigned int *pos, unsigned int size); + int (*is_event_cb)(char *text, unsigned int *pos, unsigned int size); + + /* + * Variables + */ + unsigned char *(*get_token_val_cb)(struct rules_t *obj, uint16_t token); + void (*cpy_token_val_cb)(struct rules_t *obj, uint16_t token); + void (*clr_token_val_cb)(struct rules_t *obj, uint16_t token); + void (*set_token_val_cb)(struct rules_t *obj, uint16_t token, uint16_t val); + void (*prt_token_val_cb)(struct rules_t *obj, char *out, int size); + + /* + * Events + */ + int (*event_cb)(struct rules_t *obj, char *name); +} rule_options_t; + +extern struct rule_options_t rule_options; + +/* + * Each position field is the closest + * aligned width of 11 bits. + */ +#define VM_GENERIC_FIELDS \ + uint8_t type; \ + uint16_t ret; + +typedef struct vm_vchar_t { + VM_GENERIC_FIELDS + char value[]; +} __attribute__((packed)) vm_vchar_t; + +typedef struct vm_vnull_t { + VM_GENERIC_FIELDS +} __attribute__((packed)) vm_vnull_t; + +typedef struct vm_vinteger_t { + VM_GENERIC_FIELDS + int value; +} __attribute__((packed)) vm_vinteger_t; + +typedef struct vm_vfloat_t { + VM_GENERIC_FIELDS + float value; +} __attribute__((packed)) vm_vfloat_t; + +typedef struct vm_tgeneric_t { + VM_GENERIC_FIELDS +} __attribute__((packed)) vm_tgeneric_t; + +typedef struct vm_tstart_t { + VM_GENERIC_FIELDS + uint16_t go; +} __attribute__((packed)) vm_tstart_t; + +typedef struct vm_tif_t { + VM_GENERIC_FIELDS + uint16_t go; + uint16_t true_; + uint16_t false_; +} __attribute__((packed)) vm_tif_t; + +typedef struct vm_lparen_t { + VM_GENERIC_FIELDS + uint16_t go; + uint16_t value; +} __attribute__((packed)) vm_lparen_t; + +typedef struct vm_tnumber_t { + VM_GENERIC_FIELDS + uint8_t token[]; +} __attribute__((packed)) vm_tnumber_t; + +typedef struct vm_ttrue_t { + VM_GENERIC_FIELDS + uint8_t nrgo; + uint16_t go[]; +} __attribute__((packed)) vm_ttrue_t; + +typedef struct vm_tfunction_t { + VM_GENERIC_FIELDS + uint16_t token; + uint16_t value; + uint8_t nrgo; + uint16_t go[]; +} __attribute__((packed)) vm_tfunction_t; + +typedef struct vm_tvar_t { + VM_GENERIC_FIELDS + uint16_t go; + uint16_t value; + uint8_t token[]; +} __attribute__((packed)) vm_tvar_t; + +typedef struct vm_tevent_t { + VM_GENERIC_FIELDS + uint16_t go; + uint8_t token[]; +} __attribute__((packed)) vm_tevent_t; + +typedef struct vm_tcevent_t { + VM_GENERIC_FIELDS + uint8_t token[]; +} __attribute__((packed)) vm_tcevent_t; + +typedef struct vm_toperator_t { + VM_GENERIC_FIELDS + uint8_t token; + uint16_t left; + uint16_t right; + uint16_t value; +} __attribute__((packed)) vm_toperator_t; + +typedef struct vm_teof_t { + uint8_t type; +} __attribute__((packed)) vm_teof_t; + +unsigned int alignedvarstack(int v); +int rule_initialize(struct pbuf *input, struct rules_t ***rules, int *nrrules, struct pbuf *mempool, void *userdata); +void rules_gc(struct rules_t ***obj, unsigned int nrrules); +int rule_run(struct rules_t *obj, int validate); +void valprint(struct rules_t *obj, char *out, int size); + +#endif diff --git a/HeishaMon/version.h b/HeishaMon/version.h index 1acf19e9..c894d221 100644 --- a/HeishaMon/version.h +++ b/HeishaMon/version.h @@ -1 +1 @@ -static const char* heishamon_version = "2.0"; +static const char* heishamon_version = "3.0"; diff --git a/HeishaMon/webfunctions.cpp b/HeishaMon/webfunctions.cpp old mode 100644 new mode 100755 index 92b3a027..18563d33 --- a/HeishaMon/webfunctions.cpp +++ b/HeishaMon/webfunctions.cpp @@ -3,24 +3,28 @@ #include "version.h" #include "htmlcode.h" #include "commands.h" +#include "src/common/webserver.h" +#include "src/common/timerqueue.h" -#include -#include +#include "lwip/apps/sntp.h" +#include "lwip/dns.h" +#include #include //https://github.com/bblanchon/ArduinoJson +#include #define UPTIME_OVERFLOW 4294967295 // Uptime overflow value -static int numSsid = 0; - -void log_message(char* string); +static String wifiJsonList = ""; +static uint8_t ntpservers = 0; -void getWifiScanResults(int networksFound) { - numSsid = networksFound; -} +void log_message(char* string); +void log_message(const __FlashStringHelper *msg); int dBmToQuality(int dBm) { + if (dBm == 31) + return -1; if (dBm <= -100) return 0; if (dBm >= -50) @@ -28,6 +32,48 @@ int dBmToQuality(int dBm) { return 2 * (dBm + 100); } + +void getWifiScanResults(int numSsid) { + if (numSsid > 0) { //found wifi networks + wifiJsonList = "["; + int indexes[numSsid]; + for (int i = 0; i < numSsid; i++) { //fill the sorted list with normal indexes first + indexes[i] = i; + } + for (int i = 0; i < numSsid; i++) { //then sort + for (int j = i + 1; j < numSsid; j++) { + if (WiFi.RSSI(indexes[j]) > WiFi.RSSI(indexes[i])) { + int temp = indexes[j]; + indexes[j] = indexes[i]; + indexes[i] = temp; + } + } + } + String ssid; + for (int i = 0; i < numSsid; i++) { //then remove duplicates + if (indexes[i] == -1) continue; + ssid = WiFi.SSID(indexes[i]); + for (int j = i + 1; j < numSsid; j++) { + if (ssid == WiFi.SSID(indexes[j])) { + indexes[j] = -1; + } + } + } + bool firstSSID = true; + for (int i = 0; i < numSsid; i++) { //then output json + if (indexes[i] == -1) { + continue; + } + if (!firstSSID) { + wifiJsonList = wifiJsonList + ","; + } + wifiJsonList = wifiJsonList + "{\"ssid\":\"" + WiFi.SSID(indexes[i]) + "\", \"rssi\": \"" + dBmToQuality(WiFi.RSSI(indexes[i])) + "%\"}"; + firstSSID = false; + } + wifiJsonList = wifiJsonList + "]"; + } +} + int getWifiQuality() { if (WiFi.status() != WL_CONNECTED) return -1; @@ -44,7 +90,7 @@ int getFreeMemory() { } // returns system uptime in seconds -String getUptime() { +char *getUptime(void) { static uint32_t last_uptime = 0; static uint8_t uptime_overflows = 0; @@ -54,28 +100,81 @@ String getUptime() { last_uptime = millis(); uint32_t t = uptime_overflows * (UPTIME_OVERFLOW / 1000) + (last_uptime / 1000); - char uptime[200]; uint8_t d = t / 86400L; uint8_t h = ((t % 86400L) / 3600L) % 60; uint32_t rem = t % 3600L; uint8_t m = rem / 60; uint8_t sec = rem % 60; - sprintf_P(uptime, PSTR("%d day%s %d hour%s %d minute%s %d second%s"), d, (d == 1) ? "" : "s", h, (h == 1) ? "" : "s", m, (m == 1) ? "" : "s", sec, (sec == 1) ? "" : "s"); - return String(uptime); + + unsigned int len = snprintf_P(NULL, 0, PSTR("%d day%s %d hour%s %d minute%s %d second%s"), d, (d == 1) ? "" : "s", h, (h == 1) ? "" : "s", m, (m == 1) ? "" : "s", sec, (sec == 1) ? "" : "s"); + + char *str = (char *)malloc(len + 2); + if (str == NULL) { + Serial1.printf("Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + } + + memset(str, 0, len + 2); + snprintf_P(str, len + 1, PSTR("%d day%s %d hour%s %d minute%s %d second%s"), d, (d == 1) ? "" : "s", h, (h == 1) ? "" : "s", m, (m == 1) ? "" : "s", sec, (sec == 1) ? "" : "s"); + return str; +} + +void ntp_dns_found(const char *name, const ip4_addr *addr, void *arg) { + sntp_stop(); + sntp_setserver(ntpservers++, addr); + sntp_init(); +} + +void ntpReload(settingsStruct *heishamonSettings) { + ip_addr_t addr; + uint8_t len = strlen(heishamonSettings->ntp_servers); + uint8_t ptr = 0, i = 0; + ntpservers = 0; + for (i = 0; i <= len; i++) { + if (heishamonSettings->ntp_servers[i] == ',') { + heishamonSettings->ntp_servers[i] = 0; + + uint8_t err = dns_gethostbyname(&heishamonSettings->ntp_servers[ptr], &addr, ntp_dns_found, 0); + if (err == ERR_OK) { + sntp_stop(); + sntp_setserver(ntpservers++, &addr); + sntp_init(); + } + heishamonSettings->ntp_servers[i++] = ','; + while (heishamonSettings->ntp_servers[i] == ' ') { + i++; + } + ptr = i; + } + } + + uint8_t err = dns_gethostbyname(&heishamonSettings->ntp_servers[ptr], &addr, ntp_dns_found, 0); + if (err == ERR_OK) { + sntp_stop(); + sntp_setserver(ntpservers++, &addr); + sntp_init(); + } + + sntp_stop(); + tzStruct tz; + memcpy_P(&tz, &tzdata[heishamonSettings->timezone], sizeof(tz)); + setTZ(tz.value); + sntp_init(); } void loadSettings(settingsStruct *heishamonSettings) { //read configuration from FS json - log_message("mounting FS..."); + log_message(F("mounting FS...")); if (LittleFS.begin()) { - log_message((char *)"mounted file system"); + log_message(F("mounted file system")); if (LittleFS.exists("/config.json")) { //file exists, reading and loading - log_message((char *)"reading config file"); + log_message(F("reading config file")); File configFile = LittleFS.open("/config.json", "r"); if (configFile) { - log_message((char *)"opened config file"); + log_message(F("opened config file")); size_t size = configFile.size(); // Allocate a buffer to store contents of the file. std::unique_ptr buf(new char[size]); @@ -87,7 +186,7 @@ void loadSettings(settingsStruct *heishamonSettings) { serializeJson(jsonDoc, log_msg); log_message(log_msg); if (!error) { - log_message((char *)"\nparsed json"); + log_message(F("parsed json")); //read updated parameters, make sure no overflow if ( jsonDoc["wifi_ssid"] ) strncpy(heishamonSettings->wifi_ssid, jsonDoc["wifi_ssid"], sizeof(heishamonSettings->wifi_ssid)); if ( jsonDoc["wifi_password"] ) strncpy(heishamonSettings->wifi_password, jsonDoc["wifi_password"], sizeof(heishamonSettings->wifi_password)); @@ -98,6 +197,8 @@ void loadSettings(settingsStruct *heishamonSettings) { if ( jsonDoc["mqtt_port"] ) strncpy(heishamonSettings->mqtt_port, jsonDoc["mqtt_port"], sizeof(heishamonSettings->mqtt_port)); if ( jsonDoc["mqtt_username"] ) strncpy(heishamonSettings->mqtt_username, jsonDoc["mqtt_username"], sizeof(heishamonSettings->mqtt_username)); if ( jsonDoc["mqtt_password"] ) strncpy(heishamonSettings->mqtt_password, jsonDoc["mqtt_password"], sizeof(heishamonSettings->mqtt_password)); + if ( jsonDoc["ntp_servers"] ) strncpy(heishamonSettings->ntp_servers, jsonDoc["ntp_servers"], sizeof(heishamonSettings->ntp_servers)); + if ( jsonDoc["timezone"]) heishamonSettings->timezone = jsonDoc["timezone"]; heishamonSettings->use_1wire = ( jsonDoc["use_1wire"] == "enabled" ) ? true : false; heishamonSettings->use_s0 = ( jsonDoc["use_s0"] == "enabled" ) ? true : false; heishamonSettings->listenonly = ( jsonDoc["listenonly"] == "enabled" ) ? true : false; @@ -109,6 +210,8 @@ void loadSettings(settingsStruct *heishamonSettings) { if (heishamonSettings->waitTime < 5) heishamonSettings->waitTime = 5; if ( jsonDoc["waitDallasTime"]) heishamonSettings->waitDallasTime = jsonDoc["waitDallasTime"]; if (heishamonSettings->waitDallasTime < 5) heishamonSettings->waitDallasTime = 5; + if ( jsonDoc["dallasResolution"]) heishamonSettings->dallasResolution = jsonDoc["dallasResolution"]; + if ((heishamonSettings->dallasResolution < 9) || (heishamonSettings->dallasResolution > 12) ) heishamonSettings->dallasResolution = 12; if ( jsonDoc["updateAllTime"]) heishamonSettings->updateAllTime = jsonDoc["updateAllTime"]; if (heishamonSettings->updateAllTime < heishamonSettings->waitTime) heishamonSettings->updateAllTime = heishamonSettings->waitTime; if ( jsonDoc["updataAllDallasTime"]) heishamonSettings->updataAllDallasTime = jsonDoc["updataAllDallasTime"]; @@ -116,11 +219,16 @@ void loadSettings(settingsStruct *heishamonSettings) { if (jsonDoc["s0_1_gpio"]) heishamonSettings->s0Settings[0].gpiopin = jsonDoc["s0_1_gpio"]; if (jsonDoc["s0_1_ppkwh"]) heishamonSettings->s0Settings[0].ppkwh = jsonDoc["s0_1_ppkwh"]; if (jsonDoc["s0_1_interval"]) heishamonSettings->s0Settings[0].lowerPowerInterval = jsonDoc["s0_1_interval"]; + if (jsonDoc["s0_1_minpulsewidth"]) heishamonSettings->s0Settings[0].minimalPulseWidth = jsonDoc["s0_1_minpulsewidth"]; + if (jsonDoc["s0_1_maxpulsewidth"]) heishamonSettings->s0Settings[0].maximalPulseWidth = jsonDoc["s0_1_maxpulsewidth"]; if (jsonDoc["s0_2_gpio"]) heishamonSettings->s0Settings[1].gpiopin = jsonDoc["s0_2_gpio"]; if (jsonDoc["s0_2_ppkwh"]) heishamonSettings->s0Settings[1].ppkwh = jsonDoc["s0_2_ppkwh"]; if (jsonDoc["s0_2_interval"] ) heishamonSettings->s0Settings[1].lowerPowerInterval = jsonDoc["s0_2_interval"]; + if (jsonDoc["s0_2_minpulsewidth"]) heishamonSettings->s0Settings[1].minimalPulseWidth = jsonDoc["s0_2_minpulsewidth"]; + if (jsonDoc["s0_2_maxpulsewidth"]) heishamonSettings->s0Settings[1].maximalPulseWidth = jsonDoc["s0_2_maxpulsewidth"]; + ntpReload(heishamonSettings); } else { - log_message("Failed to load json config, forcing config reset."); + log_message(F("Failed to load json config, forcing config reset.")); WiFi.persistent(true); WiFi.disconnect(); WiFi.persistent(false); @@ -129,50 +237,20 @@ void loadSettings(settingsStruct *heishamonSettings) { } } else { - log_message("No config.json exists! Forcing a config reset."); + log_message(F("No config.json exists! Forcing a config reset.")); WiFi.persistent(true); WiFi.disconnect(); WiFi.persistent(false); } - - if (LittleFS.exists("/heatcurve.json")) { - //file exists, reading and loading - log_message("reading heatingcurve file"); - File configFile = LittleFS.open("/heatcurve.json", "r"); - if (configFile) { - log_message("opened heating curve config file"); - size_t size = configFile.size(); - // Allocate a buffer to store contents of the file. - std::unique_ptr buf(new char[size]); - - configFile.readBytes(buf.get(), size); - DynamicJsonDocument jsonDoc(1024); - DeserializationError error = deserializeJson(jsonDoc, buf.get()); - serializeJson(jsonDoc, Serial); - if (!error) { - heishamonSettings->SmartControlSettings.enableHeatCurve =( jsonDoc["enableHeatCurve"] == "enabled" ) ? true : false; - if ( jsonDoc["avgHourHeatCurve"]) heishamonSettings->SmartControlSettings.avgHourHeatCurve = jsonDoc["avgHourHeatCurve"]; - if ( jsonDoc["heatCurveTargetHigh"]) heishamonSettings->SmartControlSettings.heatCurveTargetHigh = jsonDoc["heatCurveTargetHigh"]; - if ( jsonDoc["heatCurveTargetLow"]) heishamonSettings->SmartControlSettings.heatCurveTargetLow = jsonDoc["heatCurveTargetLow"]; - if ( jsonDoc["heatCurveOutHigh"]) heishamonSettings->SmartControlSettings.heatCurveOutHigh = jsonDoc["heatCurveOutHigh"]; - if ( jsonDoc["heatCurveOutLow"]) heishamonSettings->SmartControlSettings.heatCurveOutLow = jsonDoc["heatCurveOutLow"]; - for (unsigned int i = 0 ; i < 36 ; i++) { - if ( jsonDoc["heatCurveLookup"][i]) heishamonSettings->SmartControlSettings.heatCurveLookup[i] = jsonDoc["heatCurveLookup"][i]; - } - } - configFile.close(); - } - } } else { - log_message("failed to mount FS"); + log_message(F("failed to mount FS")); } //end read } void setupWifi(settingsStruct *heishamonSettings) { - - log_message((char *)"Wifi reconnecting with new configuration..."); + log_message(F("Wifi reconnecting with new configuration...")); //no sleep wifi WiFi.setSleepMode(WIFI_NONE_SLEEP); WiFi.mode(WIFI_AP_STA); @@ -180,8 +258,9 @@ void setupWifi(settingsStruct *heishamonSettings) { WiFi.softAPdisconnect(true); if (heishamonSettings->wifi_ssid[0] != '\0') { - log_message((char *)"Wifi client mode..."); - WiFi.persistent(true); + log_message(F("Wifi client mode...")); + //WiFi.persistent(true); //breaks stuff + if (heishamonSettings->wifi_password[0] == '\0') { WiFi.begin(heishamonSettings->wifi_ssid); } else { @@ -189,186 +268,61 @@ void setupWifi(settingsStruct *heishamonSettings) { } } else { - log_message((char *)"Wifi hotspot mode..."); + log_message(F("Wifi hotspot mode...")); WiFi.softAPConfig(apIP, apIP, IPAddress(255, 255, 255, 0)); - WiFi.softAP("HeishaMon-Setup"); + WiFi.softAP(F("HeishaMon-Setup")); } if (heishamonSettings->wifi_hostname[0] == '\0') { //Set hostname on wifi rather than ESP_xxxxx - WiFi.hostname("HeishaMon"); + WiFi.hostname(F("HeishaMon")); } else { WiFi.hostname(heishamonSettings->wifi_hostname); } - //initiate a wifi scan at boot to fill the wifi scan list - WiFi.scanNetworksAsync(getWifiScanResults); } -void handleRoot(ESP8266WebServer *httpServer, float readpercentage, int mqttReconnects, settingsStruct *heishamonSettings) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->send(200, "text/html", ""); - httpServer->sendContent_P(webHeader); - httpServer->sendContent_P(webCSS); - httpServer->sendContent_P(webBodyStart); - httpServer->sendContent_P(webBodyRoot1); - httpServer->sendContent(heishamon_version); - httpServer->sendContent_P(webBodyRoot2); - - if (heishamonSettings->use_1wire) httpServer->sendContent_P(webBodyRootDallasTab); - if (heishamonSettings->use_s0) httpServer->sendContent_P(webBodyRootS0Tab); - httpServer->sendContent_P(webBodyRootConsoleTab); - httpServer->sendContent_P(webBodyEndDiv); - - httpServer->sendContent_P(webBodyRootStatusWifi); - httpServer->sendContent(String(getWifiQuality())); - httpServer->sendContent_P(webBodyRootStatusMemory); - httpServer->sendContent(String(getFreeMemory())); - httpServer->sendContent_P(webBodyRootStatusReceived); - httpServer->sendContent(String(readpercentage)); - httpServer->sendContent_P(webBodyRootStatusReconnects); - httpServer->sendContent(String(mqttReconnects)); - httpServer->sendContent_P(webBodyRootStatusUptime); - httpServer->sendContent(getUptime()); - httpServer->sendContent_P(webBodyEndDiv); - - httpServer->sendContent_P(webBodyRootHeatpumpValues); - if (heishamonSettings->use_1wire)httpServer->sendContent_P(webBodyRootDallasValues); - if (heishamonSettings->use_s0) httpServer->sendContent_P(webBodyRootS0Values); - httpServer->sendContent_P(webBodyRootConsole); - - httpServer->sendContent_P(menuJS); - httpServer->sendContent_P(refreshJS); - httpServer->sendContent_P(selectJS); - httpServer->sendContent_P(websocketJS); - httpServer->sendContent_P(webFooter); - httpServer->sendContent(""); - httpServer->client().stop(); -} - -void handleTableRefresh(ESP8266WebServer *httpServer, String actData[]) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->send(200, "text/html", ""); - if (httpServer->hasArg("1wire")) { - httpServer->sendContent(dallasTableOutput()); - } else if (httpServer->hasArg("s0")) { - httpServer->sendContent(s0TableOutput()); - } else { - for (unsigned int topic = 0 ; topic < NUMBER_OF_TOPICS ; topic++) { - String topicdesc; - const char *valuetext = "value"; - if (strcmp_P(valuetext, topicDescription[topic][0]) == 0) { - topicdesc = topicDescription[topic][1]; - } - else { - int value = actData[topic].toInt(); - int maxvalue = atoi(topicDescription[topic][0]); - if ((value < 0) || (value > maxvalue)) { - topicdesc = "unknown"; - } - else { - topicdesc = topicDescription[topic][value + 1]; //plus one, because 0 is the maxvalue container - } - } - String tabletext = ""; - tabletext = tabletext + "TOP" + topic + ""; - tabletext = tabletext + "" + topics[topic] + ""; - tabletext = tabletext + "" + actData[topic] + ""; - tabletext = tabletext + "" + topicdesc + ""; - tabletext = tabletext + ""; - httpServer->sendContent(tabletext); - } +int handleFactoryReset(struct webserver_t *client) { + switch (client->content) { + case 0: { + webserver_send(client, 200, (char *)"text/html", 0); + webserver_send_content_P(client, webHeader, strlen_P(webHeader)); + webserver_send_content_P(client, webCSS, strlen_P(webCSS)); + webserver_send_content_P(client, refreshMeta, strlen_P(refreshMeta)); + } break; + case 1: { + webserver_send_content_P(client, webBodyStart, strlen_P(webBodyStart)); + webserver_send_content_P(client, webBodyRebootWarning, strlen_P(webBodyRebootWarning)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); + } break; + case 2: { + timerqueue_insert(1, 0, -1); // Start reboot sequence + } break; } - httpServer->sendContent(""); - httpServer->client().stop(); -} -void handleJsonOutput(ESP8266WebServer *httpServer, String actData[]) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->sendHeader("Access-Control-Allow-Origin", "*"); - httpServer->send(200, "application/json", ""); - //begin json - String tabletext = F("{"); - //heatpump values in json - tabletext = tabletext + F("\"heatpump\":["); - httpServer->sendContent(tabletext); - for (unsigned int topic = 0 ; topic < NUMBER_OF_TOPICS ; topic++) { - String topicdesc; - const char *valuetext = "value"; - if (strcmp_P(valuetext, topicDescription[topic][0]) == 0) { - topicdesc = topicDescription[topic][1]; - } - else { - int value = actData[topic].toInt(); - int maxvalue = atoi(topicDescription[topic][0]); - if ((value < 0) || (value > maxvalue)) { - topicdesc = "unknown"; - } - else { - topicdesc = topicDescription[topic][value + 1]; //plus one, because 0 is the maxvalue container - } - } - tabletext = F("{"); - tabletext = tabletext + F("\"Topic\": \"TOP") + topic + F("\","); - tabletext = tabletext + F("\"Name\": \"") + topics[topic] + F("\","); - tabletext = tabletext + F("\"Value\": \"") + actData[topic] + F("\","); - tabletext = tabletext + F("\"Description\": \"") + topicdesc + F("\""); - tabletext = tabletext + F("}"); - if (topic < NUMBER_OF_TOPICS - 1) { - tabletext = tabletext + F(","); - } - httpServer->sendContent(tabletext); - } - tabletext = F("]"); - httpServer->sendContent(tabletext); - //1wire data in json - tabletext = F(",\"1wire\":"); - tabletext = tabletext + dallasJsonOutput(); - httpServer->sendContent(tabletext); - //s0 data in json - tabletext = F(",\"s0\":"); - tabletext = tabletext + s0JsonOutput(); - httpServer->sendContent(tabletext); - //end json string - tabletext = F("}"); - httpServer->sendContent(tabletext); - httpServer->sendContent(""); - httpServer->client().stop(); + return 0; } -void handleFactoryReset(ESP8266WebServer *httpServer) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->send(200, "text/html", ""); - httpServer->sendContent_P(webHeader); - httpServer->sendContent_P(webCSS); - httpServer->sendContent_P(refreshMeta); - httpServer->sendContent_P(webBodyStart); - httpServer->sendContent_P(webBodyFactoryResetWarning); - httpServer->sendContent_P(menuJS); - httpServer->sendContent_P(webFooter); - httpServer->sendContent(""); - httpServer->client().stop(); - delay(1000); - LittleFS.begin(); - LittleFS.format(); - WiFi.disconnect(true); - delay(1000); - ESP.restart(); -} +int handleReboot(struct webserver_t *client) { + switch (client->content) { + case 0: { + webserver_send(client, 200, (char *)"text/html", 0); + webserver_send_content_P(client, webHeader, strlen_P(webHeader)); + webserver_send_content_P(client, webCSS, strlen_P(webCSS)); + webserver_send_content_P(client, refreshMeta, strlen_P(refreshMeta)); + } break; + case 1: { + webserver_send_content_P(client, webBodyStart, strlen_P(webBodyStart)); + webserver_send_content_P(client, webBodyRebootWarning, strlen_P(webBodyRebootWarning)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); + } break; + case 2: { + timerqueue_insert(5, 0, -2); // Start reboot sequence + } break; + } -void handleReboot(ESP8266WebServer *httpServer) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->send(200, "text/html", ""); - httpServer->sendContent_P(webHeader); - httpServer->sendContent_P(webCSS); - httpServer->sendContent_P(refreshMeta); - httpServer->sendContent_P(webBodyStart); - httpServer->sendContent_P(webBodyRebootWarning); - httpServer->sendContent_P(menuJS); - httpServer->sendContent_P(webFooter); - httpServer->sendContent(""); - httpServer->client().stop(); - delay(1000); - ESP.restart(); + return 0; } void settingsToJson(DynamicJsonDocument &jsonDoc, settingsStruct *heishamonSettings) { @@ -419,6 +373,7 @@ void settingsToJson(DynamicJsonDocument &jsonDoc, settingsStruct *heishamonSetti } jsonDoc["waitTime"] = heishamonSettings->waitTime; jsonDoc["waitDallasTime"] = heishamonSettings->waitDallasTime; + jsonDoc["dallasResolution"] = heishamonSettings->dallasResolution; jsonDoc["updateAllTime"] = heishamonSettings->updateAllTime; jsonDoc["updataAllDallasTime"] = heishamonSettings->updataAllDallasTime; } @@ -433,718 +388,853 @@ void saveJsonToConfig(DynamicJsonDocument &jsonDoc) { } } -bool handleSettings(ESP8266WebServer *httpServer, settingsStruct *heishamonSettings) { - //check if POST was made with save settings - if (httpServer->args()) { - bool reconnectWiFi = false; - DynamicJsonDocument jsonDoc(1024); +int saveSettings(struct webserver_t *client, settingsStruct *heishamonSettings) { + const char *wifi_ssid = NULL; + const char *wifi_password = NULL; + const char *new_ota_password = NULL; + const char *current_ota_password = NULL; + const char *use_s0 = NULL; + + bool reconnectWiFi = false; + bool wrongPassword = false; + DynamicJsonDocument jsonDoc(1024); + + settingsToJson(jsonDoc, heishamonSettings); //stores current settings in a json document + + jsonDoc["listenonly"] = String(""); + jsonDoc["logMqtt"] = String(""); + jsonDoc["logHexdump"] = String(""); + jsonDoc["logSerial1"] = String(""); + jsonDoc["optionalPCB"] = String(""); + jsonDoc["use_1wire"] = String(""); + jsonDoc["use_s0"] = String(""); + + struct websettings_t *tmp = (struct websettings_t *)client->userdata; + while (tmp) { + if (strcmp(tmp->name.c_str(), "wifi_hostname") == 0) { + jsonDoc["wifi_hostname"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "mqtt_topic_base") == 0) { + jsonDoc["mqtt_topic_base"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "mqtt_server") == 0) { + jsonDoc["mqtt_server"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "mqtt_port") == 0) { + jsonDoc["mqtt_port"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "mqtt_username") == 0) { + jsonDoc["mqtt_username"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "mqtt_password") == 0) { + jsonDoc["mqtt_password"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "use_1wire") == 0) { + jsonDoc["use_1wire"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "use_s0") == 0) { + jsonDoc["use_s0"] = tmp->value; + if (strcmp(tmp->value.c_str(), "enabled") == 0) { + use_s0 = tmp->value.c_str(); + } + } else if (strcmp(tmp->name.c_str(), "listenonly") == 0) { + jsonDoc["listenonly"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "logMqtt") == 0) { + jsonDoc["logMqtt"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "logHexdump") == 0) { + jsonDoc["logHexdump"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "logSerial1") == 0) { + jsonDoc["logSerial1"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "optionalPCB") == 0) { + jsonDoc["optionalPCB"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "ntp_servers") == 0) { + jsonDoc["ntp_servers"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "timezone") == 0) { + jsonDoc["timezone"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "waitTime") == 0) { + jsonDoc["waitTime"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "waitDallasTime") == 0) { + jsonDoc["waitDallasTime"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "updateAllTime") == 0) { + jsonDoc["updateAllTime"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "dallasResolution") == 0) { + jsonDoc["dallasResolution"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "updataAllDallasTime") == 0) { + jsonDoc["updataAllDallasTime"] = tmp->value; + } else if (strcmp(tmp->name.c_str(), "wifi_ssid") == 0) { + wifi_ssid = tmp->value.c_str(); + } else if (strcmp(tmp->name.c_str(), "wifi_password") == 0) { + wifi_password = tmp->value.c_str(); + } else if (strcmp(tmp->name.c_str(), "new_ota_password") == 0) { + new_ota_password = tmp->value.c_str(); + } else if (strcmp(tmp->name.c_str(), "current_ota_password") == 0) { + current_ota_password = tmp->value.c_str(); + } + tmp = tmp->next; + } - settingsToJson(jsonDoc, heishamonSettings); //stores current settings in a json document + tmp = (struct websettings_t *)client->userdata; + while (tmp) { + if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_1_gpio") == 0) { + jsonDoc["s0_1_gpio"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_1_ppkwh") == 0) { + jsonDoc["s0_1_ppkwh"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_1_interval") == 0) { + jsonDoc["s0_1_interval"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_1_minpulsewidth") == 0) { + jsonDoc["s0_1_minpulsewidth"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_1_maxpulsewidth") == 0) { + jsonDoc["s0_1_maxpulsewidth"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_2_gpio") == 0) { + jsonDoc["s0_2_gpio"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_2_ppkwh") == 0) { + jsonDoc["s0_2_ppkwh"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_2_ppkwh") == 0) { + jsonDoc["s0_2_ppkwh"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_2_interval") == 0) { + jsonDoc["s0_2_interval"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_2_minpulsewidth") == 0) { + jsonDoc["s0_2_minpulsewidth"] = tmp->value; + } else if (use_s0 != NULL && strcmp(tmp->name.c_str(), "s0_2_maxpulsewidth") == 0) { + jsonDoc["s0_2_maxpulsewidth"] = tmp->value; + } + tmp = tmp->next; + } - //then overwrite with new settings - if (httpServer->hasArg("wifi_hostname")) { - jsonDoc["wifi_hostname"] = httpServer->arg("wifi_hostname"); - } - if (httpServer->hasArg("wifi_ssid") && httpServer->hasArg("wifi_password")) { - if (strcmp(jsonDoc["wifi_ssid"], httpServer->arg("wifi_ssid").c_str()) != 0 || strcmp(jsonDoc["wifi_password"], httpServer->arg("wifi_password").c_str()) != 0) { - reconnectWiFi = true; - } - } - if (httpServer->hasArg("wifi_ssid")) { - jsonDoc["wifi_ssid"] = httpServer->arg("wifi_ssid").c_str(); - } - if (httpServer->hasArg("wifi_password")) { - jsonDoc["wifi_password"] = httpServer->arg("wifi_password").c_str(); - } - if (httpServer->hasArg("new_ota_password") && (httpServer->arg("new_ota_password") != NULL) && (httpServer->arg("current_ota_password") != NULL) ) { - if (httpServer->hasArg("current_ota_password") && (strcmp(heishamonSettings->ota_password, httpServer->arg("current_ota_password").c_str()) == 0 )) { - jsonDoc["ota_password"] = httpServer->arg("new_ota_password"); - } - else { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->send(200, "text/html", ""); - httpServer->sendContent_P(webHeader); - httpServer->sendContent_P(webCSS); - httpServer->sendContent_P(webBodyStart); - httpServer->sendContent_P(webBodySettings1); - httpServer->sendContent_P(webBodySettingsResetPasswordWarning); - httpServer->sendContent_P(refreshMeta); - httpServer->sendContent_P(webFooter); - httpServer->sendContent(""); - httpServer->client().stop(); - return true; - } - } - if (httpServer->hasArg("mqtt_topic_base")) { - jsonDoc["mqtt_topic_base"] = httpServer->arg("mqtt_topic_base"); - } - if (httpServer->hasArg("mqtt_server")) { - jsonDoc["mqtt_server"] = httpServer->arg("mqtt_server"); - } - if (httpServer->hasArg("mqtt_port")) { - jsonDoc["mqtt_port"] = httpServer->arg("mqtt_port"); - } - if (httpServer->hasArg("mqtt_username")) { - jsonDoc["mqtt_username"] = httpServer->arg("mqtt_username"); - } - if (httpServer->hasArg("mqtt_password")) { - jsonDoc["mqtt_password"] = httpServer->arg("mqtt_password"); - } - if (httpServer->hasArg("use_1wire")) { - jsonDoc["use_1wire"] = "enabled"; - } else { - jsonDoc["use_1wire"] = "disabled"; - } - if (httpServer->hasArg("use_s0")) { - jsonDoc["use_s0"] = "enabled"; - if (httpServer->hasArg("s0_1_gpio")) jsonDoc["s0_1_gpio"] = httpServer->arg("s0_1_gpio"); - if (httpServer->hasArg("s0_1_ppkwh")) jsonDoc["s0_1_ppkwh"] = httpServer->arg("s0_1_ppkwh"); - if (httpServer->hasArg("s0_1_interval")) jsonDoc["s0_1_interval"] = httpServer->arg("s0_1_interval"); - if (httpServer->hasArg("s0_2_gpio")) jsonDoc["s0_2_gpio"] = httpServer->arg("s0_2_gpio"); - if (httpServer->hasArg("s0_2_ppkwh")) jsonDoc["s0_2_ppkwh"] = httpServer->arg("s0_2_ppkwh"); - if (httpServer->hasArg("s0_2_interval")) jsonDoc["s0_2_interval"] = httpServer->arg("s0_2_interval"); - } else { - jsonDoc["use_s0"] = "disabled"; - } - if (httpServer->hasArg("listenonly")) { - jsonDoc["listenonly"] = "enabled"; - } else { - jsonDoc["listenonly"] = "disabled"; - } - if (httpServer->hasArg("logMqtt")) { - jsonDoc["logMqtt"] = "enabled"; + if (new_ota_password != NULL && strlen(new_ota_password) > 0 && current_ota_password != NULL && strlen(current_ota_password) > 0) { + if (strcmp(heishamonSettings->ota_password, current_ota_password) == 0) { + jsonDoc["ota_password"] = new_ota_password; } else { - jsonDoc["logMqtt"] = "disabled"; - } - if (httpServer->hasArg("logHexdump")) { - jsonDoc["logHexdump"] = "enabled"; - } else { - jsonDoc["logHexdump"] = "disabled"; - } - if (httpServer->hasArg("logSerial1")) { - jsonDoc["logSerial1"] = "enabled"; - } else { - jsonDoc["logSerial1"] = "disabled"; - } - if (httpServer->hasArg("optionalPCB")) { - jsonDoc["optionalPCB"] = "enabled"; - } else { - jsonDoc["optionalPCB"] = "disabled"; - } - if (httpServer->hasArg("waitTime")) { - jsonDoc["waitTime"] = httpServer->arg("waitTime"); - } - if (httpServer->hasArg("waitDallasTime")) { - jsonDoc["waitDallasTime"] = httpServer->arg("waitDallasTime"); - } - if (httpServer->hasArg("updateAllTime")) { - jsonDoc["updateAllTime"] = httpServer->arg("updateAllTime"); - } - if (httpServer->hasArg("updataAllDallasTime")) { - jsonDoc["updataAllDallasTime"] = httpServer->arg("updataAllDallasTime"); + wrongPassword = true; } + } - saveJsonToConfig(jsonDoc); //save to config file - loadSettings(heishamonSettings); //load config file to current settings - - if (reconnectWiFi) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->send(200, "text/html", ""); - httpServer->sendContent_P(webHeader); - httpServer->sendContent_P(webCSS); - httpServer->sendContent_P(webBodyStart); - httpServer->sendContent_P(webBodySettings1); - httpServer->sendContent_P(webBodySettingsNewWifiWarning); - httpServer->sendContent_P(refreshMeta); - httpServer->sendContent_P(webFooter); - httpServer->sendContent(""); - httpServer->client().stop(); - setupWifi(heishamonSettings); - return true; + if (wifi_password != NULL && wifi_ssid != NULL && strlen(wifi_ssid) > 0 && strlen(wifi_password) > 0) { + if (strcmp(jsonDoc["wifi_ssid"], wifi_ssid) != 0 || strcmp(jsonDoc["wifi_password"], wifi_password) != 0) { + reconnectWiFi = true; } + } + if (wifi_ssid != NULL) { + jsonDoc["wifi_ssid"] = String(wifi_ssid); + } + if (wifi_password != NULL) { + jsonDoc["wifi_password"] = String(wifi_password); + } + saveJsonToConfig(jsonDoc); //save to config file + loadSettings(heishamonSettings); //load config file to current settings + while (client->userdata) { + tmp = (struct websettings_t *)client->userdata; + client->userdata = ((struct websettings_t *)(client->userdata))->next; + delete tmp; } - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->send(200, "text/html", ""); - httpServer->sendContent_P(webHeader); - httpServer->sendContent_P(webCSS); - httpServer->sendContent_P(webBodyStart); - httpServer->sendContent_P(webBodySettings1); - - String httptext = F("
"); - httptext = httptext + F("

Settings

"); - httptext = httptext + F("
"); - httptext = httptext + F(""); - httptext = httptext + F(""); - httptext = httptext + F(""); - httptext = httptext + F(""); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F("Hostname:"); - httptext = httptext + F("wifi_hostname + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("Wifi SSID:"); - // wifi scan select box - httptext = httptext + F("wifi_ssid + F("\">"); - httptext = httptext + F(""); - // - httptext = httptext + F("
"); - httptext = httptext + F("Wifi password:"); - httptext = httptext + F("wifi_password + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("Update username:"); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F("Current update password:"); - httptext = httptext + F(" default password: \"heisha\""); - httptext = httptext + F("
"); - httptext = httptext + F("New update password:"); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F("Mqtt topic base:"); - httptext = httptext + F("mqtt_topic_base + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("Mqtt server:"); - httptext = httptext + F("mqtt_server + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("Mqtt port:"); - httptext = httptext + F("mqtt_port + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("Mqtt username:"); - httptext = httptext + F("mqtt_username + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("Mqtt password:"); - httptext = httptext + F("mqtt_password + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("How often new values are collected from heatpump:"); - httptext = httptext + F("waitTime + F("\"> seconds (min 5 sec)"); - httptext = httptext + F("
"); - httptext = httptext + F("How often all heatpump values are retransmitted to MQTT broker:"); - httptext = httptext + F("updateAllTime + F("\"> seconds"); - httptext = httptext + F("
"); - - httpServer->sendContent(httptext); - httptext = F("Listen only mode:"); - if (heishamonSettings->listenonly) { - httptext = httptext + F(""); - } else { - httptext = httptext + F(""); - } - httptext = httptext + F("
"); - httptext = httptext + F("Debug log to MQTT topic from start:"); - if (heishamonSettings->logMqtt) { - httptext = httptext + F(""); - } else { - httptext = httptext + F(""); + if (wrongPassword) { + client->route = 111; + return 0; } - httptext = httptext + F("
"); - httptext = httptext + F("Debug log hexdump enable from start:"); - if (heishamonSettings->logHexdump) { - httptext = httptext + F(""); - } else { - httptext = httptext + F(""); + + if (reconnectWiFi) { + client->route = 112; + return 0; } - httptext = httptext + F("
"); - httptext = httptext + F("Debug log to serial1 (GPIO2):"); - if (heishamonSettings->logSerial1) { - httptext = httptext + F(""); - } else { - httptext = httptext + F(""); + + client->route = 113; + return 0; +} + +int cacheSettings(struct webserver_t *client, struct arguments_t * args) { + struct websettings_t *tmp = (struct websettings_t *)client->userdata; + while (tmp) { + /* + * this part is useless as websettings is always NULL at start of a new POST + * it will only interrate over already POSTed args which are pushed on the list below + * we only need to find the tail of the list + * / + * + if (strcmp(tmp->name.c_str(), (char *)args->name) == 0) { + char *cpy = (char *)malloc(args->len + 1); + memset(cpy, 0, args->len + 1); + memcpy(cpy, args->value, args->len); + tmp->value += cpy; + free(cpy); + break; + } + */ + tmp = tmp->next; } - httptext = httptext + F("
"); - httptext = httptext + F("Emulate optional PCB:"); - if (heishamonSettings->optionalPCB) { - httptext = httptext + F(""); - } else { - httptext = httptext + F(""); + if (tmp == NULL) { + websettings_t *node = new websettings_t; + if (node == NULL) { + Serial1.printf("Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + } + node->next = NULL; + node->name += (char *)args->name; + if (args->value != NULL) { + char *cpy = (char *)malloc(args->len + 1); + if (node == NULL) { + Serial1.printf("Out of memory %s:#%d\n", __FUNCTION__, __LINE__); + ESP.restart(); + exit(-1); + } + memset(cpy, 0, args->len + 1); + strncpy(cpy, (char *)args->value, args->len); + node->value += cpy; + free(cpy); + } + + node->next = (struct websettings_t *)client->userdata; + client->userdata = node; } - httptext = httptext + F("
"); - httpServer->sendContent(httptext); + return 0; +} - // 1wire - httptext = F(""); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F("Use 1wire DS18b20:"); - if (heishamonSettings->use_1wire) { - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F(""); - } else { - httptext = httptext + F(""); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F(""); +int settingsNewPassword(struct webserver_t *client, settingsStruct *heishamonSettings) { + switch (client->content) { + case 0: { + webserver_send(client, 200, (char *)"text/html", 0); + webserver_send_content_P(client, webHeader, strlen_P(webHeader)); + webserver_send_content_P(client, webCSS, strlen_P(webCSS)); + webserver_send_content_P(client, webBodyStart, strlen_P(webBodyStart)); + } break; + case 1: { + webserver_send_content_P(client, webBodySettings1, strlen_P(webBodySettings1)); + webserver_send_content_P(client, webBodySettingsResetPasswordWarning, strlen_P(webBodySettingsResetPasswordWarning)); + } break; + case 2: { + webserver_send_content_P(client, refreshMeta, strlen_P(refreshMeta)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); + } break; + case 3: { + setupConditionals(); + } break; } - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F("How often new values are collected from 1wire:"); - httptext = httptext + F("waitDallasTime + F("\"> seconds (min 5 sec)"); - httptext = httptext + F("
"); - httptext = httptext + F("How often all 1wire values are retransmitted to MQTT broker:"); - httptext = httptext + F("updataAllDallasTime + F("\"> seconds"); - httptext = httptext + F("
"); - - httpServer->sendContent(httptext); - // s0 - httptext = F(""); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F("Use s0 kWh metering:"); - if (heishamonSettings->use_s0) { - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F(""); - } else { - httptext = httptext + F(""); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F(""); + + return 0; +} + +int settingsReconnectWifi(struct webserver_t *client, settingsStruct *heishamonSettings) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/html", 0); + webserver_send_content_P(client, webHeader, strlen_P(webHeader)); + webserver_send_content_P(client, webCSS, strlen_P(webCSS)); + webserver_send_content_P(client, webBodyStart, strlen_P(webBodyStart)); + webserver_send_content_P(client, webBodySettings1, strlen_P(webBodySettings1)); + } else if (client->content == 2) { + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, webBodySettingsNewWifiWarning, strlen_P(webBodySettingsNewWifiWarning)); + webserver_send_content_P(client, refreshMeta, strlen_P(refreshMeta)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); + timerqueue_insert(5, 0, -3); //handle wifi reconnect after 5 sec to make sure all above data is sent to client so no memory leak is introduced } - //begin default S0 pins hack - if (heishamonSettings->s0Settings[0].gpiopin == 255) heishamonSettings->s0Settings[0].gpiopin = DEFAULT_S0_PIN_1; - if (heishamonSettings->s0Settings[1].gpiopin == 255) heishamonSettings->s0Settings[1].gpiopin = DEFAULT_S0_PIN_2; - //end default S0 pins hack - for (int i = 0; i < NUM_S0_COUNTERS; i++) { - httptext = httptext + F(""); + + return 0; +} + +int getSettings(struct webserver_t *client, settingsStruct *heishamonSettings) { + switch (client->content) { + case 0: { + webserver_send(client, 200, (char *)"application/json", 0); + webserver_send_content_P(client, PSTR("{\"wifi_hostname\":\""), 18); + webserver_send_content(client, heishamonSettings->wifi_hostname, strlen(heishamonSettings->wifi_hostname)); + webserver_send_content_P(client, PSTR("\",\"wifi_ssid\":\""), 15); + webserver_send_content(client, heishamonSettings->wifi_ssid, strlen(heishamonSettings->wifi_ssid)); + } break; + case 1: { + webserver_send_content_P(client, PSTR("\",\"wifi_password\":\""), 19); + webserver_send_content(client, heishamonSettings->wifi_password, strlen(heishamonSettings->wifi_password)); + webserver_send_content_P(client, PSTR("\",\"current_ota_password\":\""), 26); + webserver_send_content_P(client, PSTR("\",\"new_ota_password\":\""), 22); + } break; + case 2: { + webserver_send_content_P(client, PSTR("\",\"mqtt_topic_base\":\""), 21); + webserver_send_content(client, heishamonSettings->mqtt_topic_base, strlen(heishamonSettings->mqtt_topic_base)); + webserver_send_content_P(client, PSTR("\",\"mqtt_server\":\""), 17); + webserver_send_content(client, heishamonSettings->mqtt_server, strlen(heishamonSettings->mqtt_server)); + } break; + case 3: { + webserver_send_content_P(client, PSTR("\",\"mqtt_port\":\""), 15); + webserver_send_content(client, heishamonSettings->mqtt_port, strlen(heishamonSettings->mqtt_port)); + webserver_send_content_P(client, PSTR("\",\"mqtt_username\":\""), 19); + webserver_send_content(client, heishamonSettings->mqtt_username, strlen(heishamonSettings->mqtt_username)); + } break; + case 4: { + webserver_send_content_P(client, PSTR("\",\"mqtt_password\":\""), 19); + webserver_send_content(client, heishamonSettings->mqtt_password, strlen(heishamonSettings->mqtt_password)); + webserver_send_content_P(client, PSTR("\",\"ntp_servers\":\""), 17); + webserver_send_content(client, heishamonSettings->ntp_servers, strlen(heishamonSettings->ntp_servers)); + webserver_send_content_P(client, PSTR("\",\"timezone\":"), 13); + + { + char str[20]; + itoa(heishamonSettings->timezone, str, 10); + webserver_send_content(client, str, strlen(str)); + } + + webserver_send_content_P(client, PSTR(",\"waitTime\":"), 12); + + { + char str[20]; + itoa(heishamonSettings->waitTime, str, 10); + webserver_send_content(client, str, strlen(str)); + } + } break; + case 5: { + char str[20]; + webserver_send_content_P(client, PSTR(",\"updateAllTime\":"), 17); + + itoa(heishamonSettings->updateAllTime, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"listenonly\":"), 14); + + itoa(heishamonSettings->listenonly, str, 10); + webserver_send_content(client, str, strlen(str)); + } break; + case 6: { + char str[20]; + webserver_send_content_P(client, PSTR(",\"logMqtt\":"), 11); + + itoa(heishamonSettings->logMqtt, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"logHexdump\":"), 14); + + itoa(heishamonSettings->logHexdump, str, 10); + webserver_send_content(client, str, strlen(str)); + } break; + case 7: { + char str[20]; + webserver_send_content_P(client, PSTR(",\"logSerial1\":"), 14); + + itoa(heishamonSettings->logSerial1, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"optionalPCB\":"), 15); + + itoa(heishamonSettings->optionalPCB, str, 10); + webserver_send_content(client, str, strlen(str)); + } break; + case 8: { + char str[20]; + webserver_send_content_P(client, PSTR(",\"use_1wire\":"), 13); + + itoa(heishamonSettings->use_1wire, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"waitDallasTime\":"), 18); + + itoa(heishamonSettings->waitDallasTime, str, 10); + webserver_send_content(client, str, strlen(str)); + } break; + case 9: { + char str[20]; + webserver_send_content_P(client, PSTR(",\"updataAllDallasTime\":"), 23); + + itoa(heishamonSettings->updataAllDallasTime, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"dallasResolution\":"), 20); + + itoa(heishamonSettings->dallasResolution , str, 10); + webserver_send_content(client, str, strlen(str)); + } break; + case 10: { + char str[20]; + webserver_send_content_P(client, PSTR(",\"use_s0\":"), 10); + + itoa(heishamonSettings->use_s0, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_1_gpio\":"), 13); + + int i = 0; + + if (heishamonSettings->s0Settings[i].gpiopin == 255) heishamonSettings->s0Settings[i].gpiopin = DEFAULT_S0_PIN_1; //dirty hack + itoa(heishamonSettings->s0Settings[i].gpiopin, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_1_ppkwh\":"), 14); + + itoa(heishamonSettings->s0Settings[i].ppkwh, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_1_interval\":"), 17); + + itoa(heishamonSettings->s0Settings[i].lowerPowerInterval, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_1_minpulsewidth\":"), 22); + + itoa(heishamonSettings->s0Settings[i].minimalPulseWidth, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_1_maxpulsewidth\":"), 22); + + itoa(heishamonSettings->s0Settings[i].maximalPulseWidth, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_1_minwatt\":"), 16); + + itoa((int) round((3600 * 1000 / heishamonSettings->s0Settings[i].ppkwh) / heishamonSettings->s0Settings[i].lowerPowerInterval), str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_2_gpio\":"), 13); + } break; + case 11: { + char str[20]; + int i = 1; + + if (heishamonSettings->s0Settings[i].gpiopin == 255) heishamonSettings->s0Settings[i].gpiopin = DEFAULT_S0_PIN_2; //dirty hack + itoa(heishamonSettings->s0Settings[i].gpiopin, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_2_ppkwh\":"), 14); + + itoa(heishamonSettings->s0Settings[i].ppkwh, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_2_interval\":"), 17); + + itoa(heishamonSettings->s0Settings[i].lowerPowerInterval, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_2_minpulsewidth\":"), 22); + + itoa(heishamonSettings->s0Settings[i].minimalPulseWidth, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_2_maxpulsewidth\":"), 22); + + itoa(heishamonSettings->s0Settings[i].maximalPulseWidth, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(",\"s0_2_minwatt\":"), 16); + + itoa((int) round((3600 * 1000 / heishamonSettings->s0Settings[i].ppkwh) / heishamonSettings->s0Settings[i].lowerPowerInterval), str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR("}"), 1); + } break; } - httptext = httptext + F("
"); - httptext = httptext + F("S0 port ") + (i + 1) + F(" GPIO:"); - httptext = httptext + F("s0Settings[i].gpiopin + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("S0 port ") + (i + 1) + F(" imp/kwh:"); - httptext = httptext + F("s0Settings[i].ppkwh) + F("\">"); - httptext = httptext + F("
"); - httptext = httptext + F("S0 port ") + (i + 1) + F(" reporting interval during standby/low power usage:"); - httptext = httptext + F("s0Settings[i].lowerPowerInterval) + F("\"> seconds"); - httptext = httptext + F("
"); - httptext = httptext + F("S0 port ") + (i + 1) + F(" standby/low power usage threshold: Watt"); - httptext = httptext + F("
"); - - httptext = httptext + F("

"); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F("
Factory reset"); - httptext = httptext + F("
"); - httpServer->sendContent(httptext); - - httpServer->sendContent_P(menuJS); - httpServer->sendContent_P(settingsJS); - httpServer->sendContent_P(populatescanwifiJS); - httpServer->sendContent_P(changewifissidJS); - httpServer->sendContent_P(webFooter); - httpServer->sendContent(""); - httpServer->client().stop(); - - /* - * need to reload some settings in main loop if save was done - */ - if (httpServer->args()) { - return true; + return 0; +} + +int handleSettings(struct webserver_t *client) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/html", 0); + webserver_send_content_P(client, webHeader, strlen_P(webHeader)); + webserver_send_content_P(client, webCSS, strlen_P(webCSS)); + webserver_send_content_P(client, webBodyStart, strlen_P(webBodyStart)); + } else if (client->content == 1) { + webserver_send_content_P(client, webBodySettings1, strlen_P(webBodySettings1)); + webserver_send_content_P(client, settingsForm1, strlen_P(settingsForm1)); + webserver_send_content_P(client, tzDataOptions, strlen_P(tzDataOptions)); + } else if (client->content == 2) { + webserver_send_content_P(client, settingsForm2, strlen_P(settingsForm2)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, settingsJS, strlen_P(settingsJS)); + webserver_send_content_P(client, populategetsettingsJS, strlen_P(populategetsettingsJS)); + } else if (client->content == 3) { + webserver_send_content_P(client, populatescanwifiJS, strlen_P(populatescanwifiJS)); + webserver_send_content_P(client, changewifissidJS, strlen_P(changewifissidJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); } - else { - return false; + + return 0; +} + +int handleWifiScan(struct webserver_t *client) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"application/json", 0); + char *str = (char *)wifiJsonList.c_str(); + webserver_send_content(client, str, strlen(str)); } + //initatie a new async scan for next try + WiFi.scanNetworksAsync(getWifiScanResults); + return 0; } -void handleSmartcontrol(ESP8266WebServer *httpServer, settingsStruct *heishamonSettings, String actData[]) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->send(200, "text/html", ""); - httpServer->sendContent_P(webHeader); - httpServer->sendContent_P(webCSS); - httpServer->sendContent_P(webBodyStart); - httpServer->sendContent_P(webBodySmartcontrol1); - httpServer->sendContent_P(webBodySmartcontrol2); - - String httptext = F("
"); - httpServer->sendContent(httptext); - httpServer->sendContent_P(webBodyEndDiv); - - //Heating curve - httpServer->sendContent_P(webBodySmartcontrolHeatingcurve1); - - //check if POST was made with save settings, if yes then save and reboot - if (httpServer->args()) { - DynamicJsonDocument jsonDoc(1024); - //set jsonDoc with current settings - if ( heishamonSettings->SmartControlSettings.enableHeatCurve) { - jsonDoc["enableHeatCurve"] = "enabled"; - } else { - jsonDoc["enableHeatCurve"] = "disabled"; - } - jsonDoc["avgHourHeatCurve"] = heishamonSettings->SmartControlSettings.avgHourHeatCurve; - jsonDoc["heatCurveTargetHigh"] = heishamonSettings->SmartControlSettings.heatCurveTargetHigh; - jsonDoc["heatCurveTargetLow"] = heishamonSettings->SmartControlSettings.heatCurveTargetLow; - jsonDoc["heatCurveOutHigh"] = heishamonSettings->SmartControlSettings.heatCurveOutHigh; - jsonDoc["heatCurveOutLow"] = heishamonSettings->SmartControlSettings.heatCurveOutLow; - for (unsigned int i = 0 ; i < 36 ; i++) { - jsonDoc["heatCurveLookup"][i] = heishamonSettings->SmartControlSettings.heatCurveLookup[i]; - } +int handleDebug(struct webserver_t *client, char *hex, byte hex_len) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/plain", 0); + char log_msg[254]; - //then overwrite with new settings - if (httpServer->hasArg("heatingcurve")) { - jsonDoc["enableHeatCurve"] = "enabled"; - } else { - jsonDoc["enableHeatCurve"] = "disabled"; - } - if (httpServer->hasArg("average-time")) { - jsonDoc["avgHourHeatCurve"] = httpServer->arg("average-time"); - } - if (httpServer->hasArg("hcth")) { - jsonDoc["heatCurveTargetHigh"] = httpServer->arg("hcth"); - } - if (httpServer->hasArg("hctl")) { - jsonDoc["heatCurveTargetLow"] = httpServer->arg("hctl"); - } - if (httpServer->hasArg("hcoh")) { - jsonDoc["heatCurveOutHigh"] = httpServer->arg("hcoh"); - } - if (httpServer->hasArg("hcol")) { - jsonDoc["heatCurveOutLow"] = httpServer->arg("hcol"); - } - if (httpServer->hasArg("lookup0")) { - jsonDoc["heatCurveLookup"][0] = httpServer->arg("lookup0"); - } - if (httpServer->hasArg("lookup1")) { - jsonDoc["heatCurveLookup"][1] = httpServer->arg("lookup1"); - } - if (httpServer->hasArg("lookup2")) { - jsonDoc["heatCurveLookup"][2] = httpServer->arg("lookup2"); - } - if (httpServer->hasArg("lookup3")) { - jsonDoc["heatCurveLookup"][3] = httpServer->arg("lookup3"); - } - if (httpServer->hasArg("lookup4")) { - jsonDoc["heatCurveLookup"][4] = httpServer->arg("lookup4"); - } - if (httpServer->hasArg("lookup5")) { - jsonDoc["heatCurveLookup"][5] = httpServer->arg("lookup5"); - } - if (httpServer->hasArg("lookup6")) { - jsonDoc["heatCurveLookup"][6] = httpServer->arg("lookup6"); - } - if (httpServer->hasArg("lookup7")) { - jsonDoc["heatCurveLookup"][7] = httpServer->arg("lookup7"); - } - if (httpServer->hasArg("lookup8")) { - jsonDoc["heatCurveLookup"][8] = httpServer->arg("lookup8"); - } - if (httpServer->hasArg("lookup9")) { - jsonDoc["heatCurveLookup"][9] = httpServer->arg("lookup9"); - } - if (httpServer->hasArg("lookup10")) { - jsonDoc["heatCurveLookup"][10] = httpServer->arg("lookup10"); - } - if (httpServer->hasArg("lookup11")) { - jsonDoc["heatCurveLookup"][11] = httpServer->arg("lookup11"); - } - if (httpServer->hasArg("lookup12")) { - jsonDoc["heatCurveLookup"][12] = httpServer->arg("lookup12"); - } - if (httpServer->hasArg("lookup13")) { - jsonDoc["heatCurveLookup"][13] = httpServer->arg("lookup13"); - } - if (httpServer->hasArg("lookup14")) { - jsonDoc["heatCurveLookup"][14] = httpServer->arg("lookup14"); - } - if (httpServer->hasArg("lookup15")) { - jsonDoc["heatCurveLookup"][15] = httpServer->arg("lookup15"); - } - if (httpServer->hasArg("lookup16")) { - jsonDoc["heatCurveLookup"][16] = httpServer->arg("lookup16"); - } - if (httpServer->hasArg("lookup17")) { - jsonDoc["heatCurveLookup"][17] = httpServer->arg("lookup17"); - } - if (httpServer->hasArg("lookup18")) { - jsonDoc["heatCurveLookup"][18] = httpServer->arg("lookup18"); - } - if (httpServer->hasArg("lookup19")) { - jsonDoc["heatCurveLookup"][19] = httpServer->arg("lookup19"); - } - if (httpServer->hasArg("lookup20")) { - jsonDoc["heatCurveLookup"][20] = httpServer->arg("lookup20"); - } - if (httpServer->hasArg("lookup21")) { - jsonDoc["heatCurveLookup"][21] = httpServer->arg("lookup21"); - } - if (httpServer->hasArg("lookup22")) { - jsonDoc["heatCurveLookup"][22] = httpServer->arg("lookup22"); - } - if (httpServer->hasArg("lookup23")) { - jsonDoc["heatCurveLookup"][23] = httpServer->arg("lookup23"); - } - if (httpServer->hasArg("lookup24")) { - jsonDoc["heatCurveLookup"][24] = httpServer->arg("lookup24"); - } - if (httpServer->hasArg("lookup25")) { - jsonDoc["heatCurveLookup"][25] = httpServer->arg("lookup25"); - } - if (httpServer->hasArg("lookup26")) { - jsonDoc["heatCurveLookup"][26] = httpServer->arg("lookup26"); - } - if (httpServer->hasArg("lookup27")) { - jsonDoc["heatCurveLookup"][27] = httpServer->arg("lookup27"); - } - if (httpServer->hasArg("lookup28")) { - jsonDoc["heatCurveLookup"][28] = httpServer->arg("lookup28"); - } - if (httpServer->hasArg("lookup29")) { - jsonDoc["heatCurveLookup"][29] = httpServer->arg("lookup29"); - } - if (httpServer->hasArg("lookup30")) { - jsonDoc["heatCurveLookup"][30] = httpServer->arg("lookup30"); - } - if (httpServer->hasArg("lookup31")) { - jsonDoc["heatCurveLookup"][31] = httpServer->arg("lookup31"); - } - if (httpServer->hasArg("lookup32")) { - jsonDoc["heatCurveLookup"][32] = httpServer->arg("lookup32"); - } - if (httpServer->hasArg("lookup33")) { - jsonDoc["heatCurveLookup"][33] = httpServer->arg("lookup33"); - } - if (httpServer->hasArg("lookup34")) { - jsonDoc["heatCurveLookup"][34] = httpServer->arg("lookup34"); - } - if (httpServer->hasArg("lookup35")) { - jsonDoc["heatCurveLookup"][35] = httpServer->arg("lookup35"); - } - if (LittleFS.begin()) { - File configFile = LittleFS.open("/heatcurve.json", "w"); - if (configFile) { - serializeJson(jsonDoc, configFile); - configFile.close(); - delay(1000); - - httpServer->sendContent_P(webBodySettingsSaveMessage); - httpServer->sendContent_P(refreshMeta); - httpServer->sendContent_P(webFooter); - httpServer->sendContent(""); - httpServer->client().stop(); - delay(1000); - ESP.restart(); +#define LOGHEXBYTESPERLINE 32 + for (int i = 0; i < hex_len; i += LOGHEXBYTESPERLINE) { + char buffer [(LOGHEXBYTESPERLINE * 3) + 1]; + buffer[LOGHEXBYTESPERLINE * 3] = '\0'; + for (int j = 0; ((j < LOGHEXBYTESPERLINE) && ((i + j) < hex_len)); j++) { + sprintf(&buffer[3 * j], PSTR("%02X "), hex[i + j]); } + uint8_t len = sprintf_P(log_msg, PSTR("data: %s\n"), buffer); + webserver_send_content(client, log_msg, len); } } - int heatingMode = actData[76].toInt(); - if (heatingMode == 1) { - httptext = F("
"); - if (heishamonSettings->SmartControlSettings.enableHeatCurve == true) { - httptext = httptext + F(""); - } else { - httptext = httptext + F(""); - } - httptext = httptext + F("

"); - httptext = httptext + F("SmartControlSettings.heatCurveTargetHigh + F("\" min=\"20\" max=\"60\" required>"); - httptext = httptext + F("
"); - httptext = httptext + F("SmartControlSettings.heatCurveTargetLow + F("\" min=\"20\" max=\"60\" required>"); - httptext = httptext + F("
"); - httptext = httptext + F("SmartControlSettings.heatCurveOutHigh + F("\" min=\"-20\" max=\"15\" required>"); - httptext = httptext + F("
"); - httptext = httptext + F("SmartControlSettings.heatCurveOutLow + F("\" min=\"-20\" max=\"15\" required>"); - httptext = httptext + F("


"); - httptext = httptext + F(""); - httptext = httptext + F("
"); - httptext = httptext + F("

"); - httptext = httptext + getAvgOutsideTemp(); - httptext = httptext + F("

"); - httpServer->sendContent(httptext); - httpServer->sendContent_P(webBodyEndDiv); - - httpServer->sendContent_P(webBodySmartcontrolHeatingcurveSVG); - httpServer->sendContent_P(webBodySmartcontrolHeatingcurve2); - httpServer->sendContent_P(webBodyEndDiv); - } else { - httptext = F("Heating mode must be \"direct heating\" to enable this option"); - httpServer->sendContent(httptext); - httpServer->sendContent_P(webBodyEndDiv); + return 0; +} + +void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) { + switch (type) { + case WStype_DISCONNECTED: + break; + case WStype_CONNECTED: { + } break; + case WStype_TEXT: + break; + case WStype_BIN: + break; + case WStype_PONG: { + } break; + default: + break; } +} - httpServer->sendContent_P(webBodyEndDiv); - - //Other example - // httpServer->sendContent_P(webBodySmartcontrolOtherexample); - // httptext = "...Loading..."; - // httptext = httptext + ""; - // httpServer->sendContent(httptext); - // httpServer->sendContent_P(webBodyEndDiv); - - httptext = ""; - httpServer->sendContent(httptext); - httpServer->sendContent_P(webBodyEndDiv); - - httpServer->sendContent_P(menuJS); - httpServer->sendContent_P(selectJS); - // httpServer->sendContent_P(heatingCurveJS); - httpServer->sendContent_P(webFooter); - httpServer->sendContent(""); - httpServer->client().stop(); +int handleRoot(struct webserver_t *client, float readpercentage, int mqttReconnects, settingsStruct *heishamonSettings) { + switch (client->content) { + case 0: { + webserver_send(client, 200, (char *)"text/html", 0); + webserver_send_content_P(client, webHeader, strlen_P(webHeader)); + webserver_send_content_P(client, webCSS, strlen_P(webCSS)); + webserver_send_content_P(client, webBodyStart, strlen_P(webBodyStart)); + webserver_send_content_P(client, webBodyRoot1, strlen_P(webBodyRoot1)); + } break; + case 1: { + webserver_send_content_P(client, heishamon_version, strlen_P(heishamon_version)); + webserver_send_content_P(client, webBodyRoot2, strlen_P(webBodyRoot2)); + if (heishamonSettings->use_1wire) { + webserver_send_content_P(client, webBodyRootDallasTab, strlen_P(webBodyRootDallasTab)); + } + if (heishamonSettings->use_s0) { + webserver_send_content_P(client, webBodyRootS0Tab, strlen_P(webBodyRootS0Tab)); + } + webserver_send_content_P(client, webBodyRootConsoleTab, strlen_P(webBodyRootConsoleTab)); + } break; + case 2: { + webserver_send_content_P(client, webBodyEndDiv, strlen_P(webBodyEndDiv)); + webserver_send_content_P(client, webBodyRootStatusWifi, strlen_P(webBodyRootStatusWifi)); + char str[200]; + itoa(getWifiQuality(), str, 10); + webserver_send_content(client, (char *)str, strlen(str)); + webserver_send_content_P(client, webBodyRootStatusMemory, strlen_P(webBodyRootStatusMemory)); + } break; + case 3: { + char str[200]; + itoa(getFreeMemory(), str, 10); + webserver_send_content(client, (char *)str, strlen(str)); + webserver_send_content_P(client, webBodyRootStatusReceived, strlen_P(webBodyRootStatusReceived)); + str[200]; + itoa(readpercentage, str, 10); + webserver_send_content(client, (char *)str, strlen(str)); + } break; + case 4: { + webserver_send_content_P(client, webBodyRootStatusReconnects, strlen_P(webBodyRootStatusReconnects)); + char str[200]; + itoa(mqttReconnects, str, 10); + webserver_send_content(client, (char *)str, strlen(str)); + webserver_send_content_P(client, webBodyRootStatusUptime, strlen_P(webBodyRootStatusUptime)); + char *up = getUptime(); + webserver_send_content(client, up, strlen(up)); + free(up); + } break; + case 5: { + webserver_send_content_P(client, webBodyEndDiv, strlen_P(webBodyEndDiv)); + webserver_send_content_P(client, webBodyRootHeatpumpValues, strlen_P(webBodyRootHeatpumpValues)); + if (heishamonSettings->use_1wire) { + webserver_send_content_P(client, webBodyRootDallasValues, strlen_P(webBodyRootDallasValues)); + } + if (heishamonSettings->use_s0) { + webserver_send_content_P(client, webBodyRootS0Values, strlen_P(webBodyRootS0Values)); + } + webserver_send_content_P(client, webBodyRootConsole, strlen_P(webBodyRootConsole)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + } break; + case 6: { + webserver_send_content_P(client, refreshJS, strlen_P(refreshJS)); + webserver_send_content_P(client, selectJS, strlen_P(selectJS)); + webserver_send_content_P(client, websocketJS, strlen_P(websocketJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); + } break; + } + return 0; } -void handleWifiScan(ESP8266WebServer *httpServer) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->sendHeader("Access-Control-Allow-Origin", "*"); - httpServer->send(200, "application/json", ""); +int handleTableRefresh(struct webserver_t *client, char* actData) { + int ret = 0; - if (numSsid > 0) { //found wifi networks - String httptext = "["; - int indexes[numSsid]; - for (int i = 0; i < numSsid; i++) { //fill the sorted list with normal indexes first - indexes[i] = i; + if (client->route == 11) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/html", 0); + dallasTableOutput(client); } - for (int i = 0; i < numSsid; i++) { //then sort - for (int j = i + 1; j < numSsid; j++) { - if (WiFi.RSSI(indexes[j]) > WiFi.RSSI(indexes[i])) { - int temp = indexes[j]; - indexes[j] = indexes[i]; - indexes[i] = temp; - } - } + } else if (client->route == 12) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/html", 0); + s0TableOutput(client); } - String ssid; - for (int i = 0; i < numSsid; i++) { //then remove duplicates - if (indexes[i] == -1) continue; - ssid = WiFi.SSID(indexes[i]); - for (int j = i + 1; j < numSsid; j++) { - if (ssid == WiFi.SSID(indexes[j])) { - indexes[j] = -1; - } - } + } else if (client->route == 10) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/html", 0); } - bool firstSSID = true; - for (int i = 0; i < numSsid; i++) { //then output json - if (indexes[i] == -1) continue; - if (!firstSSID) { - httptext = httptext + ","; + if (client->content < NUMBER_OF_TOPICS) { + for (uint8_t topic = client->content; topic < NUMBER_OF_TOPICS && topic < client->content + 4; topic++) { + + webserver_send_content_P(client, PSTR("TOP"), 11); + + char str[12]; + itoa(topic, str, 10); + webserver_send_content(client, str, strlen(str)); + + webserver_send_content_P(client, PSTR(""), 9); + webserver_send_content_P(client, topics[topic], strlen_P(topics[topic])); + webserver_send_content_P(client, PSTR(""), 9); + + { + String dataValue = actData[0] == '\0' ? "" : getDataValue(actData, topic); + char* str = (char *)dataValue.c_str(); + webserver_send_content(client, str, strlen(str)); + } + + webserver_send_content_P(client, PSTR(""), 9); + + int maxvalue = atoi(topicDescription[topic][0]); + int value = actData[0] == '\0' ? 0 : getDataValue(actData, topic).toInt(); + if (maxvalue == 0) { //this takes the special case where the description is a real value description instead of a mode, so value should take first index (= 0 + 1) + value = 0; + } + if ((value < 0) || (value > maxvalue)) { + webserver_send_content_P(client, _unknown, strlen_P(_unknown)); + } + else { + webserver_send_content_P(client, topicDescription[topic][value + 1], strlen_P(topicDescription[topic][value + 1])); + + } + + webserver_send_content_P(client, PSTR(""), 10); } - httptext = httptext + "{\"ssid\":\"" + WiFi.SSID(indexes[i]) + "\", \"rssi\": \"" + dBmToQuality(WiFi.RSSI(indexes[i])) + "%\"}"; - firstSSID = false; + // The webserver also increases by 1 + client->content += 3; } - httptext = httptext + "]"; - httpServer->sendContent(httptext); } - httpServer->sendContent(""); - httpServer->client().stop(); - - //initatie a new async scan for next try - WiFi.scanNetworksAsync(getWifiScanResults); + return 0; } +int handleJsonOutput(struct webserver_t *client, char* actData) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"application/json", 0); + webserver_send_content_P(client, PSTR("{\"heatpump\":["), 13); + } else if (client->content < NUMBER_OF_TOPICS) { + for (uint8_t topic = client->content - 1; topic < NUMBER_OF_TOPICS && topic < client->content + 4 ; topic++) { -bool send_command(byte* command, int length); + webserver_send_content_P(client, PSTR("{\"Topic\":\"TOP"), 13); -void handleREST(ESP8266WebServer *httpServer, bool optionalPCB) { + { + char str[12]; + itoa(topic, str, 10); + webserver_send_content(client, str, strlen(str)); + } - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->sendHeader("Access-Control-Allow-Origin", "*"); - httpServer->send(200, "text/plain", ""); + webserver_send_content_P(client, PSTR("\",\"Name\":\""), 10); - String httptext = ""; - if (httpServer->method() == HTTP_GET) { - for (uint8_t i = 0; i < httpServer->args(); i++) { - unsigned char cmd[256] = { 0 }; - char log_msg[256] = { 0 }; - unsigned int len = 0; + webserver_send_content_P(client, topics[topic], strlen_P(topics[topic])); - for (uint8_t x = 0; x < sizeof(commands) / sizeof(commands[0]); x++) { - if (strcmp(httpServer->argName(i).c_str(), commands[x].name) == 0) { - len = commands[x].func((char *)httpServer->arg(i).c_str(), cmd, log_msg); - httptext = httptext + log_msg + "\n"; - log_message(log_msg); - send_command(cmd, len); - } + webserver_send_content_P(client, PSTR("\",\"Value\":\""), 11); + + { + String dataValue = getDataValue(actData, topic); + char* str = (char *)dataValue.c_str(); + webserver_send_content(client, str, strlen(str)); + } + + webserver_send_content_P(client, PSTR("\",\"Description\":\""), 17); + + int maxvalue = atoi(topicDescription[topic][0]); + int value = actData[0] == '\0' ? 0 : getDataValue(actData, topic).toInt(); + if (maxvalue == 0) { //this takes the special case where the description is a real value description instead of a mode, so value should take first index (= 0 + 1) + value = 0; + } + if ((value < 0) || (value > maxvalue)) { + webserver_send_content_P(client, _unknown, strlen_P(_unknown)); + } + else { + webserver_send_content_P(client, topicDescription[topic][value + 1], strlen_P(topicDescription[topic][value + 1])); + } + + webserver_send_content_P(client, PSTR("\"}"), 2); + + if (topic < NUMBER_OF_TOPICS - 1) { + webserver_send_content_P(client, PSTR(","), 1); } } - if (optionalPCB) { - //optional commands - for (uint8_t i = 0; i < httpServer->args(); i++) { - unsigned char cmd[256] = { 0 }; - char log_msg[256] = { 0 }; - unsigned int len = 0; - - for (uint8_t x = 0; x < sizeof(optionalCommands) / sizeof(optionalCommands[0]); x++) { - if (strcmp(httpServer->argName(i).c_str(), optionalCommands[x].name) == 0) { - len = optionalCommands[x].func((char *)httpServer->arg(i).c_str(), log_msg); - httptext = httptext + log_msg + "\n"; - log_message(log_msg); + // The webserver also increases by 4 + client->content += 4; + if (client->content > NUMBER_OF_TOPICS) { + client->content = NUMBER_OF_TOPICS; + } + } else if (client->content == NUMBER_OF_TOPICS + 1) { + webserver_send_content_P(client, PSTR("],\"1wire\":"), 10); + + dallasJsonOutput(client); + } else if (client->content == NUMBER_OF_TOPICS + 2) { + webserver_send_content_P(client, PSTR(",\"s0\":"), 6); + + s0JsonOutput(client); + + webserver_send_content_P(client, PSTR("}"), 1); + } + return 0; +} + +int showRules(struct webserver_t *client) { + uint16_t len = 0, len1 = 0; + + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/html", 0); + webserver_send_content_P(client, webHeader, strlen_P(webHeader)); + webserver_send_content_P(client, webCSS, strlen_P(webCSS)); + webserver_send_content_P(client, webBodyStart, strlen_P(webBodyStart)); + webserver_send_content_P(client, showRulesPage1, strlen_P(showRulesPage1)); + if (LittleFS.begin()) { + client->userdata = new fs::File(LittleFS.open("/rules.txt", "r")); + } + } else if (client->userdata != NULL) { +#define BUFFER_SIZE 128 + File *f = (File *)client->userdata; + char content[BUFFER_SIZE]; + memset(content, 0, BUFFER_SIZE); + if (f && *f) { + len = f->size(); + } + + if (len > 0) { + f->seek((client->content - 1)*BUFFER_SIZE, SeekSet); + if (client->content * BUFFER_SIZE <= len) { + f->readBytes(content, BUFFER_SIZE); + len1 = BUFFER_SIZE; + } else if ((client->content * BUFFER_SIZE) >= len && (client->content * BUFFER_SIZE) <= len + BUFFER_SIZE) { + f->readBytes(content, len - ((client->content - 1)*BUFFER_SIZE)); + len1 = len - ((client->content - 1) * BUFFER_SIZE); + } else { + len1 = 0; + } + + if (len1 > 0) { + webserver_send_content(client, content, len1); + if (len1 < BUFFER_SIZE) { + if (f) { + if (*f) { + f->close(); + } + delete f; + } + client->userdata = NULL; + webserver_send_content_P(client, showRulesPage2, strlen_P(showRulesPage2)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); + } + } else if (client->content == 1) { + if (f) { + if (*f) { + f->close(); } + delete f; } + client->userdata = NULL; + webserver_send_content_P(client, showRulesPage2, strlen_P(showRulesPage2)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); } - } + } else if (client->content == 1) { + if (f) { + if (*f) { + f->close(); + } + delete f; + } + client->userdata = NULL; + webserver_send_content_P(client, showRulesPage2, strlen_P(showRulesPage2)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); + } + } else if (client->content == 1) { + webserver_send_content_P(client, showRulesPage2, strlen_P(showRulesPage2)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); } - httpServer->sendContent(httptext); - httpServer->sendContent(""); - httpServer->client().stop(); + return 0; } -void handleDebug(ESP8266WebServer *httpServer, char *hex, byte hex_len) { - httpServer->setContentLength(CONTENT_LENGTH_UNKNOWN); - httpServer->sendHeader("Access-Control-Allow-Origin", "*"); - httpServer->send(200, "text/plain", ""); - char log_msg[256]; +int showFirmware(struct webserver_t *client) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/html", 0); + webserver_send_content_P(client, webHeader, strlen_P(webHeader)); + webserver_send_content_P(client, webCSS, strlen_P(webCSS)); + webserver_send_content_P(client, webBodyStart, strlen_P(webBodyStart)); + } else if (client->content == 1) { + webserver_send_content_P(client, showFirmwarePage, strlen_P(showFirmwarePage)); + webserver_send_content_P(client, menuJS, strlen_P(menuJS)); + webserver_send_content_P(client, webFooter, strlen_P(webFooter)); + } -#define LOGHEXBYTESPERLINE 32 - for (int i = 0; i < hex_len; i += LOGHEXBYTESPERLINE) { - char buffer [(LOGHEXBYTESPERLINE * 3) + 1]; - buffer[LOGHEXBYTESPERLINE * 3] = '\0'; - for (int j = 0; ((j < LOGHEXBYTESPERLINE) && ((i + j) < hex_len)); j++) { - sprintf(&buffer[3 * j], PSTR("%02X "), hex[i + j]); - } - sprintf_P(log_msg, PSTR("data: %s"), buffer ); httpServer->sendContent(log_msg); httpServer->sendContent("\n"); + return 0; +} + +int showFirmwareSuccess(struct webserver_t *client) { + if (client->content == 0) { + webserver_send(client, 200, (char *)"text/html", strlen_P(firmwareSuccessResponse)); + webserver_send_content_P(client, firmwareSuccessResponse, strlen_P(firmwareSuccessResponse)); } + return 0; +} - httpServer->sendContent(""); - httpServer->client().stop(); +static void printUpdateError(char **out, uint8_t size) { + uint8_t len = 0; + len = snprintf_P(*out, size, PSTR("ERROR[%u]: "), Update.getError()); + if (Update.getError() == UPDATE_ERROR_OK) { + snprintf_P(&(*out)[len], size - len, PSTR("No Error")); + } else if (Update.getError() == UPDATE_ERROR_WRITE) { + snprintf_P(&(*out)[len], size - len, PSTR("Flash Write Failed")); + } else if (Update.getError() == UPDATE_ERROR_ERASE) { + snprintf_P(&(*out)[len], size - len, PSTR("Flash Erase Failed")); + } else if (Update.getError() == UPDATE_ERROR_READ) { + snprintf_P(&(*out)[len], size - len, PSTR("Flash Read Failed")); + } else if (Update.getError() == UPDATE_ERROR_SPACE) { + snprintf_P(&(*out)[len], size - len, PSTR("Not Enough Space")); + } else if (Update.getError() == UPDATE_ERROR_SIZE) { + snprintf_P(&(*out)[len], size - len, PSTR("Bad Size Given")); + } else if (Update.getError() == UPDATE_ERROR_STREAM) { + snprintf_P(&(*out)[len], size - len, PSTR("Stream Read Timeout")); +#ifdef UPDATE_ERROR_NO_DATA + } else if (Update.getError() == UPDATE_ERROR_NO_DATA) { + snprintf_P(&(*out)[len], size - len, PSTR("No data supplied")); +#endif + } else if (Update.getError() == UPDATE_ERROR_MD5) { + snprintf_P(&(*out)[len], size - len, PSTR("MD5 Failed\n")); + } else if (Update.getError() == UPDATE_ERROR_SIGN) { + snprintf_P(&(*out)[len], size - len, PSTR("Signature verification failed")); + } else if (Update.getError() == UPDATE_ERROR_FLASH_CONFIG) { + snprintf_P(&(*out)[len], size - len, PSTR("Flash config wrong real: %d IDE: %d\n"), ESP.getFlashChipRealSize(), ESP.getFlashChipSize()); + } else if (Update.getError() == UPDATE_ERROR_NEW_FLASH_CONFIG) { + snprintf_P(&(*out)[len], size - len, PSTR("new Flash config wrong real: %d\n"), ESP.getFlashChipRealSize()); + } else if (Update.getError() == UPDATE_ERROR_MAGIC_BYTE) { + snprintf_P(&(*out)[len], size - len, PSTR("Magic byte is wrong, not 0xE9")); + } else if (Update.getError() == UPDATE_ERROR_BOOTSTRAP) { + snprintf_P(&(*out)[len], size - len, PSTR("Invalid bootstrapping state, reset ESP8266 before updating")); + } else { + snprintf_P(&(*out)[len], size - len, PSTR("UNKNOWN")); + } } -void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length) { - switch (type) { - case WStype_DISCONNECTED: - break; - case WStype_CONNECTED: { - } break; - case WStype_TEXT: - break; - case WStype_BIN: - break; - case WStype_PONG: { - } break; - default: - break; + +int showFirmwareFail(struct webserver_t *client) { + if (client->content == 0) { + char str[255] = { '\0' }, *p = str; + printUpdateError(&p, sizeof(str)); + + webserver_send(client, 200, (char *)"text/html", strlen_P(firmwareFailResponse) + strlen(str)); + webserver_send_content_P(client, firmwareFailResponse, strlen_P(firmwareFailResponse)); + webserver_send_content(client, str, strlen(str)); } + return 0; } diff --git a/HeishaMon/webfunctions.h b/HeishaMon/webfunctions.h old mode 100644 new mode 100755 index 150ec8ff..53943a7d --- a/HeishaMon/webfunctions.h +++ b/HeishaMon/webfunctions.h @@ -1,27 +1,34 @@ +#define LWIP_INTERNAL + #include -#include #include #include #include #include #include +#include "src/common/webserver.h" #include "dallas.h" #include "s0.h" #include "gpio.h" -#include "smartcontrol.h" + +#define HEATPUMP_VALUE_LEN 16 + +void log_message(char* string); static IPAddress apIP(192, 168, 4, 1); struct settingsStruct { - unsigned int waitTime = 5; // how often data is read from heatpump - unsigned int waitDallasTime = 5; // how often temps are read from 1wire - unsigned int updateAllTime = 300; // how often all data is resend to mqtt - unsigned int updataAllDallasTime = 300; //how often all 1wire data is resent to mqtt + uint16_t waitTime = 5; // how often data is read from heatpump + uint16_t waitDallasTime = 5; // how often temps are read from 1wire + uint16_t dallasResolution = 12; // dallas temp resolution (9 to 12) + uint16_t updateAllTime = 300; // how often all data is resend to mqtt + uint16_t updataAllDallasTime = 300; //how often all 1wire data is resent to mqtt + uint16_t timezone = 0; const char* update_path = "/firmware"; const char* update_username = "admin"; - char wifi_ssid[40] = ""; - char wifi_password[40] = ""; + char wifi_ssid[33] = ""; + char wifi_password[65] = ""; char wifi_hostname[40] = "HeishaMon"; char ota_password[40] = "heisha"; char mqtt_server[40]; @@ -29,6 +36,7 @@ struct settingsStruct { char mqtt_username[64]; char mqtt_password[64]; char mqtt_topic_base[40] = "panasonic_heat_pump"; + char ntp_servers[254] = "pool.ntp.org"; bool listenonly = false; //listen only so heishamon can be installed parallel to cz-taw1, set commands will not work though bool optionalPCB = false; //do we emulate an optional PCB? @@ -40,26 +48,43 @@ struct settingsStruct { s0SettingsStruct s0Settings[NUM_S0_COUNTERS]; gpioSettingsStruct gpioSettings; - SmartControlSettingsStruct SmartControlSettings; }; +struct websettings_t { + String name; + String value; + struct websettings_t *next; +}; + +void setupConditionals(); int getFreeMemory(void); -String getUptime(void); +char *getUptime(void); void setupWifi(settingsStruct *heishamonSettings); int getWifiQuality(void); int getFreeMemory(void); +void ntpReload(settingsStruct *heishamonSettings); -void handleRoot(ESP8266WebServer *httpServer, float readpercentage, int mqttReconnects, settingsStruct *heishamonSettings); -void handleTableRefresh(ESP8266WebServer *httpServer, String actData[]); -void handleJsonOutput(ESP8266WebServer *httpServer, String actData[]); -void handleFactoryReset(ESP8266WebServer *httpServer); -void handleReboot(ESP8266WebServer *httpServer); -void handleDebug(ESP8266WebServer *httpServer, char *hex, byte hex_len); +void log_message(char *string); +int8_t webserver_cb(struct webserver_t *client, void *data); +void getWifiScanResults(int numSsid); +int handleRoot(struct webserver_t *client, float readpercentage, int mqttReconnects, settingsStruct *heishamonSettings); +int handleTableRefresh(struct webserver_t *client, char* actData); +int handleJsonOutput(struct webserver_t *client, char* actData); +int handleFactoryReset(struct webserver_t *client); +int handleReboot(struct webserver_t *client); +int handleDebug(struct webserver_t *client, char *hex, byte hex_len); void settingsToJson(DynamicJsonDocument &jsonDoc, settingsStruct *heishamonSettings); void saveJsonToConfig(DynamicJsonDocument &jsonDoc); void loadSettings(settingsStruct *heishamonSettings); -bool handleSettings(ESP8266WebServer *httpServer, settingsStruct *heishamonSettings); -void handleWifiScan(ESP8266WebServer *httpServer); -void handleSmartcontrol(ESP8266WebServer *httpServer, settingsStruct *heishamonSettings, String actData[]); -void handleREST(ESP8266WebServer *httpServer, bool optionalPCB); +int getSettings(struct webserver_t *client, settingsStruct *heishamonSettings); +int handleSettings(struct webserver_t *client); +int saveSettings(struct webserver_t *client, settingsStruct *heishamonSettings); +int settingsReconnectWifi(struct webserver_t *client, settingsStruct *heishamonSettings); +int settingsNewPassword(struct webserver_t *client, settingsStruct *heishamonSettings); +int cacheSettings(struct webserver_t *client, struct arguments_t * args); +int handleWifiScan(struct webserver_t *client); void webSocketEvent(uint8_t num, WStype_t type, uint8_t *payload, size_t length); +int showRules(struct webserver_t *client); +int showFirmware(struct webserver_t *client); +int showFirmwareSuccess(struct webserver_t *client); +int showFirmwareFail(struct webserver_t *client); diff --git a/Integrations/NodeRed b/Integrations/NodeRed new file mode 160000 index 00000000..bfdb00d3 --- /dev/null +++ b/Integrations/NodeRed @@ -0,0 +1 @@ +Subproject commit bfdb00d3be9471e7b31b58005a84c0b24762381c diff --git a/LIBSUSED.md b/LIBSUSED.md index e36b7399..b7e6976f 100644 --- a/LIBSUSED.md +++ b/LIBSUSED.md @@ -3,10 +3,10 @@ | lib | version | download | | ---- | ---- | ---- | -|wifimanager by tzapu | 0.16.0 | https://github.com/tzapu/WiFiManager/releases/tag/0.16.0 | +|ringbuffer by jean-luc - locoduino | 1.0.3 | https://github.com/Locoduino/RingBuffer/releases/tag/1.0.3 | |pubsubclient by nick o'leary | 2.8.0 | https://github.com/knolleary/pubsubclient/releases/tag/v2.8 | |doubleresetdetect by jens-christian skibakk | 1.0.0 | https://github.com/jenscski/DoubleResetDetect/releases/tag/1.0.0 | -|arduinojson by benoit blanchon | 6.17.2 | https://github.com/bblanchon/ArduinoJson/releases/tag/v6.17.2 | +|arduinojson by benoit blanchon | 6.18.3 | https://github.com/bblanchon/ArduinoJson/releases/tag/v6.18.3 | |dallastemperature | 3.9.0 | https://github.com/milesburton/Arduino-Temperature-Control-Library/releases/tag/3.9.0 | -|onewire | 2.3.5 | https://www.pjrc.com/teensy/td_libs_OneWire.html| -|arduinoWebSockets | 2.3.6 | https://github.com/Links2004/arduinoWebSockets| +|onewire | 2.3.5 | https://www.pjrc.com/teensy/td_libs_OneWire.html | +|WebSockets by Markus Sattler | 2.3.5 | https://github.com/Links2004/arduinoWebSockets/releases/tag/2.3.5 | diff --git a/MQTT-Topics.md b/MQTT-Topics.md index f3976cac..058ba714 100644 --- a/MQTT-Topics.md +++ b/MQTT-Topics.md @@ -122,6 +122,7 @@ TOP102 | main/Solar_On_Delta | Solar heating delta on TOP103 | main/Solar_Off_Delta | solar heating delta off TOP104 | main/Solar_Frost_Protection | Solar frost protection temp TOP105 | main/Solar_High_Limit | Solar max temp limit +TOP106 | main/Pump_Flowrate_mode | Settings for pump flow rate (0=DeltaT, 1=Maximum flow, J-series only) @@ -170,7 +171,7 @@ SET16 | SetCurves | Set zones heat/cool curves | JSON document (see below) SET17 | SetZones | Set zones to active | 0 = zone 1 active, 1 = zone2 active, 2 = zone1 and zone2 active SET18 | SetFloorHeatDelta | Set floor heating delta in Kelvin | 1-15 SET19 | SetFloorCoolDelta | Set floor cooling delta in Kelvin | 1-15 -SET20 | SetDHWHeatDelta | Set DHW heating delta in Kelvin | 1-15 +SET20 | SetDHWHeatDelta | Set DHW heating delta in Kelvin | -15 to -1 (negative value) SET21 | SetHeaterDelayTime | Set heater start delay time (only J-series) | in minutes SET22 | SetHeaterStartDelta | Set heater start delta T (only J-series) | in kelvin SET23 | SetHeaterStopDelta | Set heater stop delta T (only J-series) | in kelvin diff --git a/ProtocolByteDecrypt.md b/ProtocolByteDecrypt.md index 4b008c0c..81d2219d 100644 --- a/ProtocolByteDecrypt.md +++ b/ProtocolByteDecrypt.md @@ -31,7 +31,7 @@ | TOP | 26 | 55 | (hex) Biwalent Off=55, Biwalent alternative =56, Biwalent parallel=5A | Biwalent settings | | TOP | 27 | 05 | SG Ready Control on/off (bit5and6) ,Demand Control on/off (bit7and8) | SG Ready Control, Demand Control | | TOP76+TOP81 | 28 | 09 | (hex) 09 - Compensation curve heat and direct cool, 05 - both compensation curves , 0a - direct heat and direct cool, 06 - heat direct, cool compensation curve | Operation Setup -Installer -water temperature heating on status and cooling | -| TOP | 29 | 00 | | 0 byte | +| TOP106 | 29 | 00 | | 3d and 4th bit setting for J-series deltaT or max flow switch | | TOP | 30 | 00 | | 0 byte | | TOP | 31 | 00 | | 0 byte | | TOP | 32 | 00 | | 0 byte | diff --git a/README.md b/README.md index d3326416..0a6646f6 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ Suomen kielellä [README_FI.md](README_FI.md) luettavissa täällä. *Help on translation to other languages is welcome.* # Current releases -Current release is version 1. The [compiled binary](binaries/HeishaMon.ino.d1-v0.8b.bin) can be installed on a Wemos D1 mini, on the HeishaMon PCB and generally on any ESP8266 based board compatible with Wemos build settings (at least 4MB flash). You can also download the code and compile it yourself (see required libraries below). \ +Current release is version 2. The [compiled binary](binaries/HeishaMon.ino.d1-v2.0.bin) can be installed on a Wemos D1 mini, on the HeishaMon PCB and generally on any ESP8266 based board compatible with Wemos build settings (at least 4MB flash). You can also download the code and compile it yourself (see required libraries below). # Using the software HeishaMon is able to communicate with the Panasonic Aquarea H & J-series. [Confirmed by users types of HP you can find here](HeatPumpType.md) \ If you want to compile this image yourself be sure to use the mentioned libraries and support for a filesystem on the esp8266 so select the correct flash option in arduino ide for that. -When starting for the first time an open-wifi-hotspot will be visible allowing you to configure your wifi network and your MQTT server. Configuration page will be located at http://192.168.4.1 . \ -If you ever want to factory reset, just double reset the esp8266 within 0.1 second. It will then format the filesystem and remove the wifi setting and start the wifi hotspot again. \ +When starting, without a configured wifi, an open-wifi-hotspot will be visible allowing you to configure your wifi network and your MQTT server. Configuration page will be located at http://192.168.4.1 . \ + After configuring and booting the image will be able to read and talk to your heatpump. The GPIO13/GPIO15 connection will be used for communications so you can keep your computer/uploader connected to the board if you want. \ Serial 1 (GPIO2) can be used to connect another serial line (GND and TX from the board only) to read some debugging data. @@ -36,18 +36,195 @@ A json output of all received data (heatpump and 1wire) is available at the url Within the 'integrations' folder you can find examples how to connect your automation platform to the HeishaMon. -# Do not advise to use set commands too often due possible eeprom failure on Panasonic as its not tested well. +# Rules functionality +The rules functionality allows you to control the heatpump from within the HeishaMon itself. Which makes it much more reliable then having to deal with external domotica over WiFi. When posting a new ruleset, it is immidiatly validated and when valid used. When a new ruleset is invalid it will be ignored and the old ruleset will be loaded again. You can check the console for feedback on this. If somehow a new valid ruleset crashes the HeishaMon, it will be automatically disabled the next reboot allowing you to make changes. This prevents the HeishaMon getting into a boot loop. + +The techniques used in the rule library allows you to work with very large rulesets, but best practice is to keep it below 10.000 bytes. + +Notice that sending commands to the heatpump is done asynced. So, commands sent to the heatpump at the beginning of your syntax will not immediatly be reflected in the values from the heatpump later on. Therefor, heatpump values should therefor be read from the heatpump itself instead of those based on the values you keep yourself. + +## Syntax +Two general rules are that spaces are mandatory and all lines are terminated by a semicolon. + +### Variables +The ruleset uses the following variable structure: + +- `#`: Globals +These variables can be accessed throughout the ruleset. Don't use globals for all your variables, because it will persistantly use memory. + +- `$`: Locals +These variables live inside a rule block. When a ruleblock finishes, these variables will be cleaned up, freeing any memory used. + +- `@`: Heatpump parameters +These are the same as listed in the Manage Topics documentation page and as found on the HeishaMon homepage. So the heatpump state value is named `@Heatpump_State`. + +- `?`: Thermostat parameters +These variables reflect parameters read from the connected thermostat when using the OpenTherm functionality. When OpenTherm is supported this documentation will be extended with more precise information. + +- `ds18b20#2800000000000000`: Dallas 1-wire temperature values +Use these variables to read the temperature of the connected sensors. These values cannot be used as an event and are as an exception readonly. Off course, the id of the sensor should be places after the hashtag. + +All variables are either read of write enabled. Writing to either the Heatpump of Thermostat parameters directly control these devices. So `@Heatpump_State = 1` will actually turn the heatpump off. + +When a variable is called but not yet set to a value, the value will be `NULL`. -# Debug led indications -On first boot the debug led will turn on after 10 seconds to let you know that there is no config yet and a HeishaMon-Setup wifi portal should be available. -A factory reset can be performed on the web interface but if the web interface is unavailable you can perform a double reset. The double reset should be performed not too fast but also not too slow. Usually halve a second between both resets should do the trick. To indicate that the double reset performed a factory reset, the blue led will flash rapidly (You need to press reset again now to start HeishaMon-Setup wifi portal). -During normal running of the software, the blue led will flash on textual debug output (if enabled in the settings). This would cause the led to flash a few times about each 5 seconds. +Variables can be of boolean (`1` or `0`), float (`3.14`), or integer (`10`) type. + +### Events or functions +Rules are written in `event` or `function` blocks. These are blocks that are triggered when something happened; either a new heatpump or thermostat value has been received or a timer fired. Or can be used are plain functions + +``` +on [event] then + [...] +end + +on [name] then + [...] +end +``` + +Events can be Heatpump or thermostat parameters or timers: +``` +on @Heatpump_State then + [...] +end + +on ?setpoint then + [...] +end + +on timer=1 then + [...] +end +``` + +When defining function, you just name your block and then you can call it from anywhere else: +``` +on foobar then + [...] +end + +on @Heatpump_State then + foobar(); +end +``` + +There is currently one special function that calls when the system is booted on when a new ruleset is saved: +``` +on System#Boot then + [...] +end +``` + +This special function can be used to initially set your globals or certain timers. + +### Operators +Regular operators are supported with their standard associativity and precedence. This allows you to also use regular math. +- `&&`: And +- `||`: Or +- `==`: Equals` +- `>=`: Greater or equal then +- `>`: Greater then +- `<`: Lesser then +- `<=`: Lesser or equal then +- `-`: Minus +- `%`: Modulus +- `*`: Multiply +- `/`: Divide +- `+`: Plus +- `^`: Power + +Parenthesis can be used to prioritize operators as it would work in regular math. + +### Functions +- `coalesce` +Returns the first value not `NULL`. E.g., `$b = NULL; $a = coalesce($b, 1);` will return 1. This function accepts an unlimited number of arguments. + +- `max` +Returns the maximum value of the input parameters. + +- `min` +Returns the minimum value of the input parameters. + +- `isset` +Return boolean true when the input variable is still `NULL` in any other cases it will return false. + +- `round` +Rounds the input float to the nearest integer. + +- `setTimer` +Sets a timer to trigger in X seconds. The first parameter is the timer number and the second parameters the number of seconds before it fires. A timer only fires once so it has to be re-set for recurring events. When a timer triggers it will can the timer event as described above. + +### Conditions +The only supported conditions are `if` and `else`, `elseif` isn't supported: + +``` +if [condition] then + [...] +else + if [condition] then + [...] + end +end +``` + +### Examples +Once the rules system is in used by more and more users, additional examples will be added to the documentation. + +*Calculating WAR* +``` +on calcWar then + $Ta1 = 32; + $Tb1 = 14; + $Ta2 = 41; + $Tb2 = -4; + + #maxTa = $Ta1; + + if @Outside_Temp >= $Tb1 then + #maxTa = $Ta1; + else + if @Outside_Temp <= $Tb2 then + #maxTa = $Ta2; + else + #maxTa = $Ta1 + (($Tb1 - @Outside_Temp) * ($Ta2 - $Ta1) / ($Tb1 - $Tb2)); + end + end +end +``` + +*Thermostat setpoint* +``` +on ?temperature then + calcWar(); + + $margin = 0.25; + + if ?temperature > (?setpoint + $margin) then + if @Heatpump_State == 1 then + @Heatpump_State = 0; + end + else + if ?temperature < (?setpoint - $margin) then + if @Heatpump_State == 0 then + @Heatpump_State = 1; + end + else + @SetZ1HeatRequestTemperature = round(#maxTa); + end + end +end +``` + +# Factory reset +A factory reset can be performed on the web interface but if the web interface is unavailable you can perform a double reset. The double reset should be performed not too fast but also not too slow. Usually halve a second between both resets should do the trick. To indicate that the double reset performed a factory reset, the blue led will flash rapidly (You need to press reset again now to restart HeishaMon back to normal where a WiFi hotspot should be visible again). # Further information Below you can find some technical details about the project. How to build your own cables. How to build your own PCB etc. ## Connection details: -Communication can be established thru one of the two sockets: CN-CNT or CN-NMODE, which are hardwired/shortcut, so there is no possibility to use them both at the same time for more then one device (except sniffing). \ +Communication can be established thru one of the two sockets: CN-CNT or CN-NMODE. If you have an existing Panasonic CZ-TAW1 WiFi interface that you want to replace with HeishaMon, it is only a matter of plugging the cable out from CZ-TAW1 and reconnecting to your HeishaMon device. However it is not possible to use HeishaMon and the original CZ-TAW1 module together as an active device. It is however possible to put HeishaMon on "Listen Only" mode which will allow HeishaMon and the original CZ-TAW1 module to co-exist. The only downside to this is that HeishaMon is unable to send commands and use the optional PCB option. + Communication parameters: TTL 5V UART 9600,8,E,1 \ \ CN-CNT Pin-out (from top to bottom) \ @@ -64,6 +241,12 @@ CN-NMODE Pin-out (from left to right) \ 2 - 0-5V RX (to heatpump) \ 1 - GND +HeishaMon will receive power from the Panasonic over the cable (5v power). + +## Long distance connection +It it possible to connect the HeishaMon over a long distance. Up to 5 meter is working with normal cabling. For longer distances a TTL-to-RS485 configuration as show in the picture below is possible. The however requires HeishaMon to be powered externally using 5v power (for example from an USB cable). + +![TTL-over-RS485 HeishaMon long distance](optional-long-distance-heishamon.png) ## Where to get connectors @@ -74,8 +257,8 @@ CN-NMODE Pin-out (from left to right) \ Use some 24 AWG shielded 4-conductors cable. -## How to connect -The PCB's needed to connect to the heatpump are designed by project members and are listed below. \ +## The HeishaMon hardware itself +The PCB's needed to connect to the heatpump are designed by project members and are listed below. The most important part of the hardware is a level shifting between 5v from the Panasonic to 3.3v of the HeishaMon and a GPIO13/GPIO15 enable line after boot. \ [PCD Designs from the project members](PCB_Designs.md) \ [Picture Wemos D1 beta](WEMOSD1.JPG) \ [Picture ESP12-F](NewHeishamon.JPG) @@ -83,26 +266,18 @@ The PCB's needed to connect to the heatpump are designed by project members and To make things easy you can order a completed PCB from some project members: \ [Tindie shop](https://www.tindie.com/stores/thehognl/) from Igor Ybema (aka TheHogNL) based in the Netherlands -If you have an existing Panasonic CZ-TAW1 WiFi interface that you want to replace with HeishaMon, it is only a matter of plugging the cable out from CZ-TAW1 and reconnecting to your HeishaMon device. - -## Building the test arduino image +## Building the arduino image yourself boards: \ -esp8266 by esp8266 community version 2.6.3 [Arduino](https://github.com/esp8266/Arduino/releases/tag/2.6.3) +esp8266 by esp8266 community version 3.0.2 [Arduino](https://github.com/esp8266/Arduino/releases/tag/3.0.2) -[libs we use](LIBSUSED.md) +All the [libs we use](LIBSUSED.md) necessary for compiling. ## MQTT topics [Current list of documented MQTT topics can be found here](MQTT-Topics.md) ## DS18b20 1-wire support -The software also supports ds18b20 1-wire temperature sensors reading. A proper 1-wire configuration (with 4.7kohm pull-up resistor) connected to GPIO4 will be read each configured secs (minimal 5) and send at the panasonic_heat_pump/1wire/"sensor-hex-address" topic. - - -## Protocol info packet: -To get information from heat pump, "magic" packet should be send to CN-CNT: - -`71 6c 01 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 12` +The software also supports ds18b20 1-wire temperature sensors reading. A proper 1-wire configuration (with 4.7kohm pull-up resistor) connected to GPIO4 will be read each configured secs (minimal 5) and send at the panasonic_heat_pump/1wire/"sensor-hex-address" topic. On the pre-made boards this 4.7kohm resistor is already installed. ## Protocol byte decrypt info: diff --git a/Wemos Panasonic Aquarea CN-CNT shield through whole.png b/Wemos Panasonic Aquarea CN-CNT shield through whole.png deleted file mode 100644 index d9f0eb02..00000000 Binary files a/Wemos Panasonic Aquarea CN-CNT shield through whole.png and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v0.2b.bin b/binaries/HeishaMon.ino.d1-v0.2b.bin deleted file mode 100644 index f3d4551d..00000000 Binary files a/binaries/HeishaMon.ino.d1-v0.2b.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v0.3b.bin b/binaries/HeishaMon.ino.d1-v0.3b.bin deleted file mode 100644 index f2637de6..00000000 Binary files a/binaries/HeishaMon.ino.d1-v0.3b.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v0.4b.bin b/binaries/HeishaMon.ino.d1-v0.4b.bin deleted file mode 100644 index e32912ca..00000000 Binary files a/binaries/HeishaMon.ino.d1-v0.4b.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v0.5b.bin b/binaries/HeishaMon.ino.d1-v0.5b.bin deleted file mode 100644 index 8c9b5010..00000000 Binary files a/binaries/HeishaMon.ino.d1-v0.5b.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v0.6b.bin b/binaries/HeishaMon.ino.d1-v0.6b.bin deleted file mode 100644 index 608a8c49..00000000 Binary files a/binaries/HeishaMon.ino.d1-v0.6b.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v0.7b.bin b/binaries/HeishaMon.ino.d1-v0.7b.bin deleted file mode 100644 index 5d35db8f..00000000 Binary files a/binaries/HeishaMon.ino.d1-v0.7b.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v0.8b.bin b/binaries/HeishaMon.ino.d1-v0.8b.bin deleted file mode 100644 index 1fe50c93..00000000 Binary files a/binaries/HeishaMon.ino.d1-v0.8b.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v0.9b.bin b/binaries/HeishaMon.ino.d1-v0.9b.bin deleted file mode 100644 index 9febbe88..00000000 Binary files a/binaries/HeishaMon.ino.d1-v0.9b.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v1.0.a-jw.bin b/binaries/HeishaMon.ino.d1-v1.0.a-jw.bin deleted file mode 100644 index 9f123dde..00000000 Binary files a/binaries/HeishaMon.ino.d1-v1.0.a-jw.bin and /dev/null differ diff --git a/binaries/HeishaMon.ino.d1-v3.0.bin b/binaries/HeishaMon.ino.d1-v3.0.bin new file mode 100644 index 00000000..17e3dc6a Binary files /dev/null and b/binaries/HeishaMon.ino.d1-v3.0.bin differ diff --git a/binaries/README.md b/binaries/README.md index 0f7ff0db..4dd867d7 100644 --- a/binaries/README.md +++ b/binaries/README.md @@ -6,4 +6,5 @@ The LittleFS versions will, after updating to this version, reset your HeishaMon From version 1.0 some topics are changed so you need to update your automation for this. The sensors are now in /main/ (before /sdc/) and the commands are expected in /commands/ (before in root topic). Check MQTT-Topics.md for the overview of all topics +The latest production release is v3.0. If you decide to try out a later development version, you should be able to restore the firmware using a USB-TTL cable as sometimes upgrading to a development versions seems to fail and brick the heishamon pcb. diff --git a/optional-long-distance-heishamon.png b/optional-long-distance-heishamon.png new file mode 100644 index 00000000..21b1bb4e Binary files /dev/null and b/optional-long-distance-heishamon.png differ