/* Copyright © 2017-2018 Andrey Klimov. All rights reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. Homepage: http://lazyhome.ru GIT: https://github.com/anklimov/lighthub e-mail anklimov@gmail.com * * * Done: * MQMT/openhab * 1-wire * DMX - out * DMX IN * 1809 strip out (discarded) * Modbus master Out * DHCP * JSON config * cli * PWM Out 7,8,9 * 1-w relay out * Termostat out Todo (backlog) === rotary encoder local ctrl ? analog in local ctrl Smooth regulation/fading PID Termostat out ? dmx relay out Relay array channel Relay DMX array channel Config URL & MQTT password commandline configuration 1-wire Update refactoring (save memory) Topic configuration Timer Modbus response check control/debug (Commandline) over MQTT more Modbus dimmers todo DUE related: PWM freq fix Config webserver SSL todo ESP: Config webserver SSL ESP32 PWM Out */ #include "Arduino.h" #include "main.h" #include "options.h" #include "utils.h" #include "homiedef.h" #if defined(__SAM3X8E__) DueFlashStorage EEPROM; EthernetClient ethClient; #endif #if defined(ARDUINO_ARCH_AVR) EthernetClient ethClient; #endif #ifdef ARDUINO_ARCH_ESP8266 #include #include WiFiClient ethClient; #endif #ifdef ARDUINO_ARCH_ESP32 #include #include #include #include #include "Ethernet3.h" WiFiClient ethClient; #endif #ifdef ARDUINO_ARCH_STM32F1 #include "HttpClient.h" //#include //#include "UIPEthernet.h" //#include "UIPUdp.h" #include #include #include "Dns.h" //#include "utility/logging.h" #include EthernetClient ethClient; #endif #ifdef NRF5 #include NRFFlashStorage EEPROM; EthernetClient ethClient; #endif #ifdef SYSLOG_ENABLE #include EthernetUDP udpSyslogClient; Syslog udpSyslog(udpSyslogClient, SYSLOG_PROTO_IETF); unsigned long nextSyslogPingTime; #endif lan_status lanStatus = INITIAL_STATE; const char outprefix[] PROGMEM = OUTTOPIC; const char inprefix[] PROGMEM = INTOPIC; const char configserver[] PROGMEM = CONFIG_SERVER; unsigned int UniqueID[5] = {0,0,0,0,0}; aJsonObject *root = NULL; aJsonObject *items = NULL; aJsonObject *inputs = NULL; aJsonObject *mqttArr = NULL; #ifndef MODBUS_DISABLE aJsonObject *modbusArr = NULL; #endif #ifdef _owire aJsonObject *owArr = NULL; #endif #ifdef _dmxout aJsonObject *dmxArr = NULL; #endif #ifdef SYSLOG_ENABLE aJsonObject *udpSyslogArr = NULL; #endif unsigned long nextPollingCheck = 0; unsigned long nextInputCheck = 0; unsigned long nextLanCheckTime = 0; unsigned long nextThermostatCheck = 0; aJsonObject *pollingItem = NULL; bool owReady = false; bool configOk = false; #ifdef _modbus ModbusMaster node; #endif byte mac[6]; PubSubClient mqttClient(ethClient); bool wifiInitialized; int mqttErrorRate; void watchdogSetup(void) {} //Do not remove - strong re-definition WDT Init for DUE void mqttCallback(char *topic, byte *payload, unsigned int length) { debugSerial< nextLanCheckTime) onInitialStateInitLAN(); break; case HAVE_IP_ADDRESS: if (!configOk) lanStatus = loadConfigFromHttp(0, NULL); else lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER; #ifdef _artnet if (artnet) artnet->begin(); #endif break; case IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER: wdt_res(); ip_ready_config_loaded_connecting_to_broker(); break; case RETAINING_COLLECTING: if (millis() > nextLanCheckTime) { char buf[MQTT_TOPIC_LENGTH]; //Unsubscribe from status topics.. strncpy_P(buf, outprefix, sizeof(buf)); strncat(buf, "#", sizeof(buf)); mqttClient.unsubscribe(buf); lanStatus = OPERATION;//3; debugSerial< nextLanCheckTime) lanStatus = INITIAL_STATE;//0; break; case RECONNECT: if (millis() > nextLanCheckTime) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2; break; case READ_RE_CONFIG: if (loadConfigFromEEPROM()) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2; else { nextLanCheckTime = millis() + 5000; lanStatus = AWAITING_ADDRESS;//-10; } break; case DO_NOTHING:; } { #if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__) wdt_dis(); if (lanStatus > 0) switch (Ethernet.maintain()) { case NO_LINK: debugSerial<child; while (items && item) if (item->type == aJson_Array && aJson.getArraySize(item)>0) { strncat_P(buf,item->name,sizeof(buf)); strncat(buf,",",sizeof(buf)); switch ( aJson.getArrayItem(item, I_TYPE)->valueint) { case CH_THERMO: strncat_P(datatype,float_P,sizeof(datatype)); break; case CH_RELAY: strncpy_P(datatype,enum_P,sizeof(datatype)); strncpy_P(format,enumformat_P,sizeof(format)); break; case CH_RGBW: case CH_RGB: strncpy_P(datatype,color_P,sizeof(datatype)); strncpy_P(format,hsv_P,sizeof(format)); break; case CH_DIMMER: case CH_MODBUS: case CH_PWM: case CH_VCTEMP: case CH_VC: strncpy_P(datatype,int_P,sizeof(datatype)); strncpy_P(format,intformat_P,sizeof(format)); break; } //switch strncpy_P(topic, outprefix, sizeof(topic)); strncat_P(topic,item->name,sizeof(topic)); strncat(topic,"/",sizeof(topic)); strncat_P(topic,datatype_P,sizeof(topic)); mqttClient.publish(topic,datatype,true); strncpy_P(topic, outprefix, sizeof(topic)); strncat_P(topic,item->name,sizeof(topic)); strncat(topic,"/",sizeof(topic)); strncat_P(topic,format_P,sizeof(topic)); mqttClient.publish(topic,format,true); item = item->next; } //if strncpy_P(topic, outprefix, sizeof(topic)); strncat_P(topic, nodes_P, sizeof(topic)); mqttClient.publish(topic,buf,true); } } void ip_ready_config_loaded_connecting_to_broker() { short n = 0; int port = 1883; char empty = 0; char *user = ∅ char passwordBuf[16] = ""; char *password = passwordBuf; #ifdef SYSLOG_ENABLE debugSerial<<"debugSerial:"; delay(100); if (udpSyslogArr && aJson.getArraySize(udpSyslogArr)) { char *syslogServer = aJson.getArrayItem(udpSyslogArr, 0)->valuestring; int syslogPort = aJson.getArrayItem(udpSyslogArr, 1)->valueint; char *syslogDeviceHostname = aJson.getArrayItem(udpSyslogArr, 2)->valuestring; char *syslogAppname = aJson.getArrayItem(udpSyslogArr, 3)->valuestring; debugSerial< 1)) { char *client_id = aJson.getArrayItem(mqttArr, 0)->valuestring; char *servername = aJson.getArrayItem(mqttArr, 1)->valuestring; if (n >= 3) port = aJson.getArrayItem(mqttArr, 2)->valueint; if (n >= 4) user = aJson.getArrayItem(mqttArr, 3)->valuestring; if (!loadFlash(OFFSET_MQTT_PWD, passwordBuf, sizeof(passwordBuf)) && (n >= 5)) { password = aJson.getArrayItem(mqttArr, 4)->valuestring; debugSerial<50){ debugSerial< 0) { delay(500); wifi_connection_wait -= 500; debugSerial<<"."; } wifiInitialized = true; } #endif #if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266) if (WiFi.status() == WL_CONNECTED) { debugSerial<valuestring; if (owEmitString) { printFloatValueToStr(currentTemp,valstr); debugSerial<valuestring) {//DOMOTICZ json format support debugSerial << endl << idx->valuestring << F(" Domoticz valstr:"); char valstr[50]; sprintf(valstr, "{\"idx\":%s,\"svalue\":\"%.1f\"}", idx->valuestring, currentTemp); debugSerial << valstr; mqttClient.publish(owEmitString, valstr); return; } #endif strcpy_P(addrstr, outprefix); strncat(addrstr, owEmitString, sizeof(addrstr)); mqttClient.publish(addrstr, valstr); } owItem = aJson.getObjectItem(owObj, "item")->valuestring; if (owItem) thermoSetCurTemp(owItem, currentTemp); ///TODO: Refactore using Items interface else debugSerial<=1 ) { DMXoutSetup(maxChannels = aJson.getArrayItem(dmxoutArr, 1)->valueint); debugSerial<child; owReady = owSetup(&Changed); if (owReady) debugSerial<type == aJson_Object)) { DeviceAddress addr; //debugSerial<name); SetAddr(item->name, addr); owAdd(addr); } item = item->next; } } #endif items = aJson.getObjectItem(root, "items"); // Digital output related Items initialization pollingItem=NULL; if (items) { aJsonObject * item = items->child; while (items && item) if (item->type == aJson_Array && aJson.getArraySize(item)>1) { Item it(item); if (it.isValid()) { int pin=it.getArg(); int cmd = it.getCmd(); switch (it.itemType) { case CH_THERMO: if (cmd<1) it.setCmd(CMD_OFF); case CH_RELAY: { int k; pinMode(pin, OUTPUT); digitalWrite(pin, k = ((cmd == CMD_ON) ? HIGH : LOW)); debugSerial<next; } //if pollingItem = items->child; } inputs = aJson.getObjectItem(root, "in"); mqttArr = aJson.getObjectItem(root, "mqtt"); #ifdef SYSLOG_ENABLE udpSyslogArr = aJson.getObjectItem(root, "syslog"); #endif printConfigSummary(); } void printConfigSummary() { debugSerial<31) len=31; for(int i=0;i 1) { strncpy(configServer, args[1], sizeof(configServer) - 1); saveFlash(OFFSET_CONFIGSERVER, configServer); } else if (!loadFlash(OFFSET_CONFIGSERVER, configServer)) strncpy_P(configServer,configserver,sizeof(configServer)); #ifndef DEVICE_NAME snprintf(URI, sizeof(URI), "/%02x-%02x-%02x-%02x-%02x-%02x.config.json", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); #else #ifndef FLASH_64KB snprintf(URI, sizeof(URI), "/%s_config.json",QUOTE(DEVICE_NAME)); #else strncpy_P(URI, "/", sizeof(URI)); strncat(URI, QUOTE(DEVICE_NAME), sizeof(URI)); strncat(URI, "_config.json", sizeof(URI)); #endif #endif debugSerial< 0) { debugSerial.printf("[HTTP] GET... code: %d\n", httpResponseCode); if (httpResponseCode == HTTP_CODE_OK) { String response = httpClient.getString(); debugSerial<idle(&owIdle); #endif mqttClient.setCallback(mqttCallback); #ifdef _artnet ArtnetSetup(); #endif #if defined(ARDUINO_ARCH_ESP8266) and not defined(WIFI_MANAGER_DISABLE) WiFiManager wifiManager; #if defined(ESP_WIFI_AP) and defined(ESP_WIFI_PWD) wifiManager.autoConnect(QUOTE(ESP_WIFI_AP), QUOTE(ESP_WIFI_PWD)); #else wifiManager.autoConnect(); #endif #endif delay(LAN_INIT_DELAY);//for LAN-shield initializing //TODO: checkForRemoteSketchUpdate(); } void printFirmwareVersionAndBuildOptions() { debugSerial<>>")); cmdAdd("help", cmdFunctionHelp); cmdAdd("save", cmdFunctionSave); cmdAdd("load", cmdFunctionLoad); cmdAdd("get", cmdFunctionGet); #ifndef FLASH_64KB cmdAdd("mac", cmdFunctionSetMac); #endif cmdAdd("kill", cmdFunctionKill); cmdAdd("req", cmdFunctionReq); cmdAdd("ip", cmdFunctionIp); cmdAdd("pwd", cmdFunctionPwd); cmdAdd("clear",cmdFunctionClearEEPROM); cmdAdd("reboot",cmdFunctionReboot); } void loop_main() { wdt_res(); cmdPoll(); if (lanLoop() > HAVE_IP_ADDRESS) { mqttClient.loop(); #ifdef _artnet if (artnet) artnet->read(); #endif } #ifdef _owire if (owReady && owArr) owLoop(); #endif #ifdef _dmxin // unsigned long lastpacket = DMXSerial.noDataSince(); DMXCheck(); #endif if (items) { #ifndef MODBUS_DISABLE if (lanStatus != RETAINING_COLLECTING) pollingLoop(); #endif #ifdef _owire thermoLoop(); #endif } inputLoop(); #if defined (_espdmx) dmxout.update(); #endif #ifdef SYSLOG_ENABLE // debugSerial<=HAVE_IP_ADDRESS)) artnet->read(); #endif wdt_res(); return; #ifdef _dmxin DMXCheck(); #endif #if defined (_espdmx) dmxout.update(); #endif } void ethernetIdle(void){ wdt_res(); inputLoop(); }; void modbusIdle(void) { wdt_res(); if (lanLoop() > 1) { mqttClient.loop(); #ifdef _artnet if (artnet) artnet->read(); #endif inputLoop(); } #ifdef _dmxin DMXCheck(); #endif #if defined (_espdmx) dmxout.update(); #endif } void inputLoop(void) { if (!inputs) return; if (millis() > nextInputCheck) { aJsonObject *input = inputs->child; while (input) { if ((input->type == aJson_Object)) { Input in(input); in.poll(); } input = input->next; } nextInputCheck = millis() + INTERVAL_CHECK_INPUT; } } #ifndef MODBUS_DISABLE void pollingLoop(void) { boolean done = false; if (millis() > nextPollingCheck) { while (pollingItem && !done) { if (pollingItem->type == aJson_Array) { Item it(pollingItem); nextPollingCheck = millis() + it.Poll(); //INTERVAL_CHECK_MODBUS; done = true; }//if pollingItem = pollingItem->next; if (!pollingItem) { pollingItem = items->child; return; } //start from 1-st element } //while }//if } #endif bool isThermostatWithMinArraySize(aJsonObject *item, int minimalArraySize) { return (item->type == aJson_Array) && (aJson.getArrayItem(item, I_TYPE)->valueint == CH_THERMO) && (aJson.getArraySize(item) >= minimalArraySize); } bool thermoDisabledOrDisconnected(aJsonObject *thermoExtensionArray, int thermoStateCommand) { return thermoStateCommand == CMD_OFF || thermoStateCommand == CMD_HALT || aJson.getArrayItem(thermoExtensionArray, IET_ATTEMPTS)->valueint == 0; } //TODO: refactoring void thermoLoop(void) { if (millis() < nextThermostatCheck) return; bool thermostatCheckPrinted = false; for (aJsonObject *thermoItem = items->child; thermoItem; thermoItem = thermoItem->next) { if (isThermostatWithMinArraySize(thermoItem, 5)) { aJsonObject *thermoExtensionArray = aJson.getArrayItem(thermoItem, I_EXT); if (thermoExtensionArray && (aJson.getArraySize(thermoExtensionArray) > 1)) { int thermoPin = aJson.getArrayItem(thermoItem, I_ARG)->valueint; float thermoSetting = aJson.getArrayItem(thermoItem, I_VAL)->valueint; /// int thermoStateCommand = aJson.getArrayItem(thermoItem, I_CMD)->valueint; float curTemp = aJson.getArrayItem(thermoExtensionArray, IET_TEMP)->valuefloat; if (!aJson.getArrayItem(thermoExtensionArray, IET_ATTEMPTS)->valueint) { debugSerial<name<valueint)) mqttClient.publish("/alarm/snsr", thermoItem->name); } if (curTemp > THERMO_OVERHEAT_CELSIUS) mqttClient.publish("/alarm/ovrht", thermoItem->name); debugSerial << endl << thermoItem->name << F(" Set:") << thermoSetting << F(" Cur:") << curTemp << F(" cmd:") << thermoStateCommand; pinMode(thermoPin, OUTPUT); if (thermoDisabledOrDisconnected(thermoExtensionArray, thermoStateCommand)) { digitalWrite(thermoPin, LOW); debugSerial<= thermoSetting) { digitalWrite(thermoPin, LOW); debugSerial<valuefloat = t; if (att->valueint == 0) mqttClient.publish("/alarmoff/snsr", thermoItem->name); att->valueint = (int) T_ATTEMPTS; } } } }