mirror of
https://github.com/anklimov/lighthub
synced 2025-12-06 11:49:51 +03:00
1599 lines
46 KiB
C++
1599 lines
46 KiB
C++
/* 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 <ESP8266WiFi.h>
|
||
#include <user_interface.h>
|
||
WiFiClient ethClient;
|
||
#endif
|
||
|
||
#ifdef ARDUINO_ARCH_ESP32
|
||
#include <WiFiClient.h>
|
||
#include <WiFi.h>
|
||
#include <HTTPClient.h>
|
||
#include <EEPROM.h>
|
||
#include "Ethernet3.h"
|
||
WiFiClient ethClient;
|
||
#endif
|
||
|
||
#ifdef ARDUINO_ARCH_STM32F1
|
||
#include "HttpClient.h"
|
||
//#include <EthernetClient.h>
|
||
//#include "UIPEthernet.h"
|
||
//#include "UIPUdp.h"
|
||
#include <SPI.h>
|
||
#include <Ethernet_STM.h>
|
||
|
||
#include "Dns.h"
|
||
//#include "utility/logging.h"
|
||
#include <EEPROM.h>
|
||
|
||
EthernetClient ethClient;
|
||
#endif
|
||
|
||
#ifdef NRF5
|
||
#include <NRFFlashStorage.h>
|
||
NRFFlashStorage EEPROM;
|
||
EthernetClient ethClient;
|
||
#endif
|
||
|
||
#ifdef SYSLOG_ENABLE
|
||
#include <Syslog.h>
|
||
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<<F("\n[")<<topic<<F("] ");
|
||
if (!payload) return;
|
||
|
||
payload[length] = 0;
|
||
int fr = freeRam();
|
||
if (fr < 250) {
|
||
debugSerial<<F("OOM!");
|
||
return;
|
||
}
|
||
|
||
for (int i = 0; i < length; i++)
|
||
debugSerial<<((char) payload[i]);
|
||
debugSerial<<endl;
|
||
|
||
if(!strcmp(topic,CMDTOPIC)) {
|
||
cmd_parse((char *)payload);
|
||
return;
|
||
}
|
||
|
||
short intopic = 0;
|
||
{
|
||
char buf[MQTT_TOPIC_LENGTH + 1];
|
||
strncpy_P(buf, inprefix, sizeof(buf));
|
||
intopic = strncmp(topic, buf, strlen(inprefix));
|
||
}
|
||
// in Retaining status - trying to restore previous state from retained output topic. Retained input topics are not relevant.
|
||
if ((lanStatus == RETAINING_COLLECTING) && !intopic) {
|
||
debugSerial<<F("Skipping..");
|
||
return;
|
||
}
|
||
char subtopic[MQTT_SUBJECT_LENGTH] = "";
|
||
// int cmd = 0;
|
||
//cmd = txt2cmd((char *) payload);
|
||
char *t;
|
||
if (t = strrchr(topic, '/'))
|
||
strncpy(subtopic, t + 1, MQTT_SUBJECT_LENGTH - 1);
|
||
Item item(subtopic);
|
||
if (item.isValid()) {
|
||
if (item.itemType == CH_GROUP && (lanStatus == RETAINING_COLLECTING))
|
||
return; //Do not restore group channels - they consist not relevant data
|
||
item.Ctrl((char *)payload, !(lanStatus == RETAINING_COLLECTING));
|
||
} //valid item
|
||
}
|
||
|
||
void printIPAddress(IPAddress ipAddress) {
|
||
for (byte i = 0; i < 4; i++)
|
||
#ifdef WITH_PRINTEX_LIB
|
||
(i < 3) ? debugSerial << (ipAddress[i]) << F(".") : debugSerial << (ipAddress[i])<<F(", ");
|
||
#else
|
||
(i < 3) ? debugSerial << _DEC(ipAddress[i]) << F(".") : debugSerial << _DEC(ipAddress[i]) << F(", ");
|
||
#endif
|
||
}
|
||
|
||
void printMACAddress() {
|
||
debugSerial<<F("Configured MAC:");
|
||
for (byte i = 0; i < 6; i++)
|
||
#ifdef WITH_PRINTEX_LIB
|
||
(i < 5) ?debugSerial<<hex <<(mac[i])<<F(":"):debugSerial<<hex<<(mac[i])<<endl;
|
||
#else
|
||
(i < 5) ?debugSerial<<_HEX(mac[i])<<F(":"):debugSerial<<_HEX(mac[i])<<endl;
|
||
#endif
|
||
}
|
||
|
||
void restoreState() {
|
||
// Once connected, publish an announcement... // Once connected, publish an announcement...
|
||
//mqttClient.publish("/myhome/out/RestoreState", "ON");
|
||
};
|
||
|
||
lan_status lanLoop() {
|
||
|
||
#ifdef NOETHER
|
||
lanStatus=DO_NOTHING;//-14;
|
||
#endif
|
||
|
||
switch (lanStatus) {
|
||
case INITIAL_STATE:
|
||
if (millis() > 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<<F("Accepting commands...\n");
|
||
break;
|
||
}
|
||
|
||
case OPERATION:
|
||
if (!mqttClient.connected()) lanStatus = IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;//2;
|
||
break;
|
||
|
||
case AWAITING_ADDRESS:
|
||
if (millis() > 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<<F("No link");
|
||
if (mqttClient.connected()) mqttClient.disconnect();
|
||
nextLanCheckTime = millis() + 30000;
|
||
lanStatus = AWAITING_ADDRESS;//-10;
|
||
break;
|
||
case DHCP_CHECK_RENEW_FAIL:
|
||
debugSerial<<F("Error: renewed fail");
|
||
if (mqttClient.connected()) mqttClient.disconnect();
|
||
nextLanCheckTime = millis() + 1000;
|
||
lanStatus = AWAITING_ADDRESS;//-10;
|
||
break;
|
||
|
||
case DHCP_CHECK_RENEW_OK:
|
||
debugSerial<<F("Renewed success. IP address:");
|
||
printIPAddress(Ethernet.localIP());
|
||
break;
|
||
|
||
case DHCP_CHECK_REBIND_FAIL:
|
||
debugSerial<<F("Error: rebind fail");
|
||
if (mqttClient.connected()) mqttClient.disconnect();
|
||
nextLanCheckTime = millis() + 1000;
|
||
lanStatus = AWAITING_ADDRESS;//-10;
|
||
break;
|
||
|
||
case DHCP_CHECK_REBIND_OK:
|
||
debugSerial<<F("Rebind success. IP address:");
|
||
printIPAddress(Ethernet.localIP());
|
||
break;
|
||
|
||
default:
|
||
break;
|
||
|
||
}
|
||
|
||
wdt_en();
|
||
#endif
|
||
}
|
||
|
||
return lanStatus;
|
||
|
||
}
|
||
|
||
|
||
void onMQTTConnect(){
|
||
char topic[64] = "";
|
||
char buf[128] = "";
|
||
|
||
// High level homie topics publishing
|
||
strncpy_P(topic, outprefix, sizeof(topic));
|
||
strncat_P(topic, state_P, sizeof(topic));
|
||
mqttClient.publish_P(topic,ready_P,true);
|
||
|
||
strncpy_P(topic, outprefix, sizeof(topic));
|
||
strncat_P(topic, homie_P, sizeof(topic));
|
||
mqttClient.publish_P(topic,homiever_P,true);
|
||
|
||
strncpy_P(topic, outprefix, sizeof(topic));
|
||
strncat_P(topic, name_P, sizeof(topic));
|
||
mqttClient.publish_P(topic,nameval_P,true);
|
||
|
||
strncpy_P(topic, outprefix, sizeof(topic));
|
||
strncat_P(topic, name_P, sizeof(topic));
|
||
mqttClient.publish_P(topic,nameval_P,true);
|
||
|
||
strncpy_P(topic, outprefix, sizeof(topic));
|
||
strncat_P(topic, stats_P, sizeof(topic));
|
||
mqttClient.publish_P(topic,statsval_P,true);
|
||
|
||
strncat_P(topic, stats_P, sizeof(topic));
|
||
if (items) {
|
||
char datatype[32]="\0";
|
||
char format [64]="\0";
|
||
aJsonObject * item = items->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<<F("Syslog params:")<<syslogServer<<syslogPort<<syslogDeviceHostname<<syslogAppname;
|
||
udpSyslog.server(syslogServer, syslogPort);
|
||
udpSyslog.deviceHostname(syslogDeviceHostname);
|
||
udpSyslog.appName(syslogAppname);
|
||
udpSyslog.defaultPriority(LOG_KERN);
|
||
udpSyslog.log(LOG_INFO, F("UDP Syslog initialized!"));
|
||
debugSerial<<F("UDP Syslog initialized!\n");
|
||
}
|
||
#endif
|
||
|
||
if (!mqttClient.connected() && mqttArr && ((n = aJson.getArraySize(mqttArr)) > 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<<F("Using MQTT password from config");
|
||
}
|
||
|
||
mqttClient.setServer(servername, port);
|
||
mqttClient.setCallback(mqttCallback);
|
||
|
||
char willMessage[16];
|
||
char willTopic[32];
|
||
|
||
strncpy_P(willMessage,disconnected_P,sizeof(willMessage));
|
||
|
||
strncpy_P(willTopic, outprefix, sizeof(willTopic));
|
||
strncat_P(willTopic, state_P, sizeof(willTopic));
|
||
|
||
|
||
debugSerial<<F("\nAttempting MQTT connection to ")<<servername<<F(":")<<port<<F(" user:")<<user<<F(" ...");
|
||
wdt_dis(); //potential unsafe for ethernetIdle(), but needed to avoid cyclic reboot if mosquitto out of order
|
||
if (mqttClient.connect(client_id, user, password,willTopic,MQTTQOS1,true,willMessage)) {
|
||
mqttErrorRate = 0;
|
||
debugSerial<<F("connected as ")<<client_id <<endl;
|
||
wdt_en();
|
||
configOk = true;
|
||
// ... Temporary subscribe to status topic
|
||
char buf[MQTT_TOPIC_LENGTH];
|
||
|
||
strncpy_P(buf, outprefix, sizeof(buf));
|
||
strncat(buf, "#", sizeof(buf));
|
||
mqttClient.subscribe(buf);
|
||
|
||
//Subscribing for command topics
|
||
strncpy_P(buf, inprefix, sizeof(buf));
|
||
strncat(buf, "#", sizeof(buf));
|
||
Serial.println(buf);
|
||
mqttClient.subscribe(buf);
|
||
|
||
//restoreState();
|
||
onMQTTConnect();
|
||
// if (_once) {DMXput(); _once=0;}
|
||
lanStatus = RETAINING_COLLECTING;//4;
|
||
nextLanCheckTime = millis() + 5000;
|
||
debugSerial<<F("Awaiting for retained topics");
|
||
} else {
|
||
debugSerial<<F("failed, rc=")<<mqttClient.state()<<F(" try again in 5 seconds");
|
||
nextLanCheckTime = millis() + 5000;
|
||
#ifdef RESTART_LAN_ON_MQTT_ERRORS
|
||
mqttErrorRate++;
|
||
if(mqttErrorRate>50){
|
||
debugSerial<<F("Too many MQTT connection errors. Restart LAN"));
|
||
mqttErrorRate=0;
|
||
#ifdef RESET_PIN
|
||
resetHard();
|
||
#endif
|
||
lanStatus=INITIAL_STATE;
|
||
return;
|
||
}
|
||
#endif
|
||
|
||
lanStatus = RECONNECT;//12;
|
||
}
|
||
}
|
||
}
|
||
|
||
void onInitialStateInitLAN() {
|
||
#if defined(ARDUINO_ARCH_ESP8266) and defined(WIFI_MANAGER_DISABLE)
|
||
if(!wifiInitialized) {
|
||
WiFi.mode(WIFI_STA);
|
||
debugSerial<<F("WIFI AP/Password:")<<QUOTE(ESP_WIFI_AP)<<F("/")<<QUOTE(ESP_WIFI_PWD);
|
||
wifi_set_macaddr(STATION_IF,mac);
|
||
WiFi.begin(QUOTE(ESP_WIFI_AP), QUOTE(ESP_WIFI_PWD));
|
||
wifiInitialized = true;
|
||
}
|
||
#endif
|
||
|
||
#ifdef ARDUINO_ARCH_ESP32
|
||
if(!wifiInitialized) {
|
||
WiFi.mode(WIFI_STA);
|
||
WiFi.disconnect();
|
||
debugSerial<<F("WIFI AP/Password:")<<QUOTE(ESP_WIFI_AP)<<F("/")<<QUOTE(ESP_WIFI_PWD);
|
||
WiFi.begin(QUOTE(ESP_WIFI_AP), QUOTE(ESP_WIFI_PWD));
|
||
|
||
int wifi_connection_wait = 10000;
|
||
while (WiFi.status() != WL_CONNECTED && wifi_connection_wait > 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<<F("WiFi connected. IP address: ")<<WiFi.localIP();
|
||
lanStatus = HAVE_IP_ADDRESS;//1;
|
||
} else
|
||
{
|
||
debugSerial<<F("Problem with WiFi!");
|
||
nextLanCheckTime = millis() + DHCP_RETRY_INTERVAL/5;
|
||
}
|
||
#endif
|
||
|
||
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__)||defined(ARDUINO_ARCH_STM32F1)
|
||
#ifdef W5500_CS_PIN
|
||
Ethernet.w5500_cspin = W5500_CS_PIN;
|
||
debugSerial<<F("Use W5500 pin: ");
|
||
debugSerial<<(Ethernet.w5500_cspin);
|
||
#endif
|
||
IPAddress ip, dns, gw, mask;
|
||
int res = 1;
|
||
debugSerial<<F("Starting lan");
|
||
if (ipLoadFromFlash(OFFSET_IP, ip)) {
|
||
debugSerial<<"Loaded from flash IP:";
|
||
printIPAddress(ip);
|
||
if (ipLoadFromFlash(OFFSET_DNS, dns)) {
|
||
debugSerial<<" DNS:";
|
||
printIPAddress(dns);
|
||
if (ipLoadFromFlash(OFFSET_GW, gw)) {
|
||
debugSerial<<" GW:";
|
||
printIPAddress(gw);
|
||
if (ipLoadFromFlash(OFFSET_MASK, mask)) {
|
||
debugSerial<<" MASK:";
|
||
printIPAddress(mask);
|
||
Ethernet.begin(mac, ip, dns, gw, mask);
|
||
} else Ethernet.begin(mac, ip, dns, gw);
|
||
} else Ethernet.begin(mac, ip, dns);
|
||
} else Ethernet.begin(mac, ip);
|
||
debugSerial<<endl;
|
||
lanStatus = HAVE_IP_ADDRESS;
|
||
}
|
||
else {
|
||
debugSerial<<"\nNo IP data found in flash\n";
|
||
wdt_dis();
|
||
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__)
|
||
res = Ethernet.begin(mac, 12000);
|
||
#endif
|
||
#if defined(ARDUINO_ARCH_STM32F1)
|
||
res = Ethernet.begin(mac);
|
||
#endif
|
||
wdt_en();
|
||
wdt_res();
|
||
|
||
|
||
if (res == 0) {
|
||
debugSerial<<F("Failed to configure Ethernet using DHCP. You can set ip manually!")<<F("'ip [ip[,dns[,gw[,subnet]]]]' - set static IP\n");
|
||
lanStatus = AWAITING_ADDRESS;//-10;
|
||
nextLanCheckTime = millis() + DHCP_RETRY_INTERVAL;
|
||
#ifdef RESET_PIN
|
||
resetHard();
|
||
#endif
|
||
} else {
|
||
debugSerial<<F("Got IP address:");
|
||
printIPAddress(Ethernet.localIP());
|
||
lanStatus = HAVE_IP_ADDRESS;//1;
|
||
}
|
||
}
|
||
#endif
|
||
}
|
||
|
||
#ifdef ARDUINO_ARCH_STM32F1
|
||
void softRebootFunc() {
|
||
nvic_sys_reset();
|
||
}
|
||
#endif
|
||
|
||
#if defined(ARDUINO_ARCH_AVR) || defined(__SAM3X8E__)
|
||
void (*softRebootFunc)(void) = 0;
|
||
|
||
void printCurentLanConfig();
|
||
|
||
#endif
|
||
|
||
#if defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||
void softRebootFunc(){
|
||
debugSerial<<F("ESP.restart();");
|
||
ESP.restart();
|
||
}
|
||
#endif
|
||
|
||
void resetHard() {
|
||
#ifdef RESET_PIN
|
||
debugSerial<<F("Reset Arduino with digital pin ");
|
||
debugSerial<<QUOTE(RESET_PIN);
|
||
delay(500);
|
||
pinMode(RESET_PIN, OUTPUT);
|
||
digitalWrite(RESET_PIN,LOW);
|
||
delay(500);
|
||
digitalWrite(RESET_PIN,HIGH);
|
||
delay(500);
|
||
#endif
|
||
}
|
||
|
||
#ifdef _owire
|
||
|
||
void Changed(int i, DeviceAddress addr, float currentTemp) {
|
||
char addrstr[32] = "NIL";
|
||
//char addrbuf[17];
|
||
char valstr[16] = "NIL";
|
||
char *owEmitString = NULL;
|
||
char *owItem = NULL;
|
||
|
||
SetBytes(addr, 8, addrstr);
|
||
addrstr[17] = 0;
|
||
|
||
aJsonObject *owObj = aJson.getObjectItem(owArr, addrstr);
|
||
if (owObj) {
|
||
owEmitString = aJson.getObjectItem(owObj, "emit")->valuestring;
|
||
|
||
if (owEmitString) {
|
||
printFloatValueToStr(currentTemp,valstr);
|
||
debugSerial<<owEmitString<<F("=")<<valstr<<endl;
|
||
if ((currentTemp == -127.0) || (currentTemp == 85.0) || (currentTemp == 0.0)) //ToDo: 1-w short circuit mapped to "0" celsium
|
||
return;
|
||
|
||
#ifdef WITH_DOMOTICZ
|
||
aJsonObject *idx = aJson.getObjectItem(owObj, "idx");
|
||
if (idx && idx->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<<F("1w-item not found in config")<<endl;
|
||
}
|
||
}
|
||
|
||
#endif //_owire
|
||
|
||
void cmdFunctionHelp(int arg_cnt, char **args)
|
||
{
|
||
printFirmwareVersionAndBuildOptions();
|
||
#ifdef SYSLOG_ENABLE
|
||
// udpSyslog.logf(LOG_INFO, "free RAM: %d",freeRam());
|
||
#endif
|
||
printCurentLanConfig();
|
||
// printFreeRam();
|
||
debugSerial<<F("\nUse these commands: 'help' - this text\n"
|
||
"'mac de:ad:be:ef:fe:00' set and store MAC-address in EEPROM\n"
|
||
"'ip [ip[,dns[,gw[,subnet]]]]' - set static IP\n"
|
||
"'save' - save config in NVRAM\n"
|
||
"'get' [config addr]' - get config from pre-configured URL and store addr\n"
|
||
"'load' - load config from NVRAM\n"
|
||
"'pwd' - define MQTT password\n"
|
||
"'kill' - test watchdog\n"
|
||
"'clear' - clear EEPROM\n"
|
||
"'reboot' - reboot controller");
|
||
}
|
||
void printCurentLanConfig() {
|
||
debugSerial << F("Current LAN config(ip,dns,gw,subnet):");
|
||
printIPAddress(Ethernet.localIP());
|
||
printIPAddress(Ethernet.dnsServerIP());
|
||
printIPAddress(Ethernet.gatewayIP());
|
||
printIPAddress(Ethernet.subnetMask());
|
||
}
|
||
|
||
void cmdFunctionKill(int arg_cnt, char **args) {
|
||
for (byte i = 1; i < 20; i++) {
|
||
delay(1000);
|
||
debugSerial<<i;
|
||
};
|
||
}
|
||
|
||
void cmdFunctionReboot(int arg_cnt, char **args) {
|
||
debugSerial<<F("Soft rebooting...");
|
||
//// softRebootFunc();
|
||
}
|
||
|
||
void applyConfig() {
|
||
if (!root) return;
|
||
|
||
#ifdef _dmxin
|
||
int itemsCount;
|
||
dmxArr = aJson.getObjectItem(root, "dmxin");
|
||
if (dmxArr && (itemsCount = aJson.getArraySize(dmxArr))) {
|
||
DMXinSetup(itemsCount * 4);
|
||
debugSerial<<F("DMX in started. Channels:")<<itemsCount * 4;
|
||
}
|
||
#endif
|
||
#ifdef _dmxout
|
||
int maxChannels;
|
||
aJsonObject *dmxoutArr = aJson.getObjectItem(root, "dmx");
|
||
if (dmxoutArr && aJson.getArraySize(dmxoutArr) >=1 ) {
|
||
DMXoutSetup(maxChannels = aJson.getArrayItem(dmxoutArr, 1)->valueint);
|
||
debugSerial<<F("DMX out started. Channels: ")<<maxChannels;
|
||
}
|
||
#endif
|
||
#ifdef _modbus
|
||
modbusArr = aJson.getObjectItem(root, "modbus");
|
||
#endif
|
||
|
||
#ifdef _owire
|
||
owArr = aJson.getObjectItem(root, "ow");
|
||
if (owArr && !owReady) {
|
||
aJsonObject *item = owArr->child;
|
||
owReady = owSetup(&Changed);
|
||
if (owReady) debugSerial<<F("One wire Ready\n");
|
||
t_count = 0;
|
||
while (item && owReady) {
|
||
if ((item->type == aJson_Object)) {
|
||
DeviceAddress addr;
|
||
//debugSerial<<F("Add:")),debugSerial<<item->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<<F("Pin:")<<pin<<F("=")<<k;
|
||
}
|
||
break;
|
||
} //switch
|
||
} //isValid
|
||
item = item->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<<F("\nConfigured:")<<F("\nitems ");
|
||
printBool(items);
|
||
debugSerial<<F("\ninputs ");
|
||
printBool(inputs);
|
||
#ifndef MODBUS_DISABLE
|
||
debugSerial<<F("\nmodbus ");
|
||
printBool(modbusArr);
|
||
#endif
|
||
debugSerial<<F("\nmqtt ");
|
||
printBool(mqttArr);
|
||
#ifdef _owire
|
||
debugSerial<<F("\n1-wire ");
|
||
printBool(owArr);
|
||
#endif
|
||
#ifdef SYSLOG_ENABLE
|
||
debugSerial<<F("\nudp syslog ");
|
||
printBool(udpSyslogArr);
|
||
#endif
|
||
debugSerial << endl;
|
||
}
|
||
|
||
void cmdFunctionLoad(int arg_cnt, char **args) {
|
||
loadConfigFromEEPROM();
|
||
restoreState();
|
||
}
|
||
|
||
int loadConfigFromEEPROM()
|
||
{
|
||
char ch;
|
||
debugSerial<<F("loading Config");
|
||
|
||
ch = EEPROM.read(EEPROM_offset);
|
||
if (ch == '{') {
|
||
aJsonEEPROMStream as = aJsonEEPROMStream(EEPROM_offset);
|
||
aJson.deleteItem(root);
|
||
root = aJson.parse(&as);
|
||
if (!root) {
|
||
debugSerial<<F("\nload failed");
|
||
return 0;
|
||
}
|
||
debugSerial<<F("\nLoaded\n");
|
||
applyConfig();
|
||
ethClient.stop(); //Refresh MQTT connect to get retained info
|
||
return 1;
|
||
} else {
|
||
debugSerial<<F("\nNo stored config\n");
|
||
return 0;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
void cmdFunctionReq(int arg_cnt, char **args) {
|
||
mqttConfigRequest(arg_cnt, args);
|
||
restoreState();
|
||
}
|
||
|
||
|
||
int mqttConfigRequest(int arg_cnt, char **args)
|
||
{
|
||
char buf[25] = "/";
|
||
debugSerial<<F("\nrequest MQTT Config");
|
||
SetBytes((uint8_t *) mac, 6, buf + 1);
|
||
buf[13] = 0;
|
||
strncat(buf, "/resp/#", 25);
|
||
debugSerial<<buf;
|
||
mqttClient.subscribe(buf);
|
||
buf[13] = 0;
|
||
strncat(buf, "/req/conf", 25);
|
||
debugSerial<<buf;
|
||
mqttClient.publish(buf, "1");
|
||
}
|
||
|
||
|
||
int mqttConfigResp(char *as) {
|
||
debugSerial<<F("got MQTT Config");
|
||
root = aJson.parse(as);
|
||
|
||
if (!root) {
|
||
debugSerial<<F("\nload failed");
|
||
return 0;
|
||
}
|
||
debugSerial<<F("\nLoaded");
|
||
applyConfig();
|
||
return 1;
|
||
}
|
||
|
||
void cmdFunctionSave(int arg_cnt, char **args)
|
||
{
|
||
aJsonEEPROMStream jsonEEPROMStream = aJsonEEPROMStream(EEPROM_offset);
|
||
debugSerial<<F("Saving config to EEPROM..");
|
||
aJson.print(root, &jsonEEPROMStream);
|
||
jsonEEPROMStream.putEOF();
|
||
debugSerial<<F("Saved to EEPROM");
|
||
}
|
||
|
||
void cmdFunctionIp(int arg_cnt, char **args)
|
||
{
|
||
IPAddress ip0(0, 0, 0, 0);
|
||
IPAddress ip;
|
||
DNSClient dns;
|
||
switch (arg_cnt) {
|
||
case 5:
|
||
if (dns.inet_aton(args[4], ip)) saveFlash(OFFSET_MASK, ip);
|
||
else saveFlash(OFFSET_MASK, ip0);
|
||
case 4:
|
||
if (dns.inet_aton(args[3], ip)) saveFlash(OFFSET_GW, ip);
|
||
else saveFlash(OFFSET_GW, ip0);
|
||
case 3:
|
||
if (dns.inet_aton(args[2], ip)) saveFlash(OFFSET_DNS, ip);
|
||
else saveFlash(OFFSET_DNS, ip0);
|
||
case 2:
|
||
if (dns.inet_aton(args[1], ip)) saveFlash(OFFSET_IP, ip);
|
||
else saveFlash(OFFSET_IP, ip0);
|
||
break;
|
||
case 1:
|
||
IPAddress current_ip = Ethernet.localIP();
|
||
IPAddress current_mask = Ethernet.subnetMask();
|
||
IPAddress current_gw = Ethernet.gatewayIP();
|
||
IPAddress current_dns = Ethernet.dnsServerIP();
|
||
saveFlash(OFFSET_IP, current_ip);
|
||
saveFlash(OFFSET_MASK, current_mask);
|
||
saveFlash(OFFSET_GW, current_gw);
|
||
saveFlash(OFFSET_DNS, current_dns);
|
||
debugSerial<<F("Saved current config(ip,dns,gw,subnet):");
|
||
printIPAddress(current_ip);
|
||
printIPAddress(current_dns);
|
||
printIPAddress(current_gw);
|
||
printIPAddress(current_mask);
|
||
}
|
||
debugSerial<<F("Saved\n");
|
||
}
|
||
|
||
void cmdFunctionClearEEPROM(int arg_cnt, char **args){
|
||
for (int i = 0; i < 512; i++)
|
||
EEPROM.write(i, 0);
|
||
debugSerial<<F("EEPROM cleared\n");
|
||
|
||
}
|
||
|
||
void cmdFunctionPwd(int arg_cnt, char **args)
|
||
{ char empty[]="";
|
||
if (arg_cnt)
|
||
saveFlash(OFFSET_MQTT_PWD,args[1]);
|
||
else saveFlash(OFFSET_MQTT_PWD,empty);
|
||
debugSerial<<F("Password updated\n");
|
||
}
|
||
|
||
void cmdFunctionSetMac(int arg_cnt, char **args) {
|
||
if (sscanf(args[1], "%x:%x:%x:%x:%x:%x%с", &mac[0], &mac[1], &mac[2], &mac[3], &mac[4], &mac[5]) < 6) {
|
||
debugSerial<<F("could not parse: ")<<args[1];
|
||
return;
|
||
}
|
||
printMACAddress();
|
||
for (short i = 0; i < 6; i++) { EEPROM.write(i, mac[i]); }
|
||
debugSerial<<F("Updated\n");
|
||
}
|
||
|
||
void cmdFunctionGet(int arg_cnt, char **args) {
|
||
lanStatus= loadConfigFromHttp(arg_cnt, args);
|
||
ethClient.stop(); //Refresh MQTT connect to get retained info
|
||
//restoreState();
|
||
}
|
||
|
||
void printBool(bool arg) { (arg) ? debugSerial<<F("+") : debugSerial<<F("-"); }
|
||
|
||
void saveFlash(short n, char *str) {
|
||
short i;
|
||
short len=strlen(str);
|
||
if (len>31) len=31;
|
||
for(int i=0;i<len;i++) EEPROM.write(n+i,str[i]);
|
||
EEPROM.write(n+len,0);
|
||
}
|
||
|
||
int loadFlash(short n, char *str, short l) {
|
||
short i;
|
||
uint8_t ch = EEPROM.read(n);
|
||
if (!ch || (ch == 0xff)) return 0;
|
||
for (i=0;i<l-1 && (str[i] = EEPROM.read(n++));i++);
|
||
str[i]=0;
|
||
return 1;
|
||
}
|
||
|
||
void saveFlash(short n, IPAddress& ip) {
|
||
for(int i=0;i<4;i++) EEPROM.write(n++,ip[i]);
|
||
}
|
||
|
||
int ipLoadFromFlash(short n, IPAddress &ip) {
|
||
for (int i = 0; i < 4; i++)
|
||
ip[i] = EEPROM.read(n++);
|
||
return (ip[0] && (ip[0] != 0xff));
|
||
}
|
||
lan_status loadConfigFromHttp(int arg_cnt, char **args)
|
||
{
|
||
int responseStatusCode = 0;
|
||
char ch;
|
||
char URI[40];
|
||
char configServer[32]="";
|
||
if (arg_cnt > 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<<F("Config URI: http://")<<configServer<<URI<<endl;
|
||
|
||
#if defined(ARDUINO_ARCH_AVR)
|
||
FILE *configStream;
|
||
//byte hserver[] = { 192,168,88,2 };
|
||
wdt_dis();
|
||
HTTPClient hclient(configServer, 80);
|
||
HTTPClient hclientPrint(configServer, 80);
|
||
// FILE is the return STREAM type of the HTTPClient
|
||
configStream = hclient.getURI(URI);
|
||
responseStatusCode = hclient.getLastReturnCode();
|
||
wdt_en();
|
||
|
||
if (configStream != NULL) {
|
||
if (responseStatusCode == 200) {
|
||
|
||
debugSerial<<F("got Config\n");
|
||
char c;
|
||
aJsonFileStream as = aJsonFileStream(configStream);
|
||
noInterrupts();
|
||
aJson.deleteItem(root);
|
||
root = aJson.parse(&as);
|
||
interrupts();
|
||
// debugSerial<<F("Parsed."));
|
||
hclient.closeStream(configStream); // this is very important -- be sure to close the STREAM
|
||
|
||
if (!root) {
|
||
debugSerial<<F("Config parsing failed\n");
|
||
nextLanCheckTime = millis() + 15000;
|
||
return READ_RE_CONFIG;//-11;
|
||
} else {
|
||
// char *outstr = aJson.print(root);
|
||
// debugSerial<<outstr);
|
||
// free(outstr);
|
||
debugSerial<<F("Applying.\n");
|
||
applyConfig();
|
||
|
||
|
||
}
|
||
|
||
} else {
|
||
debugSerial<<F("ERROR: Server returned ");
|
||
debugSerial<<responseStatusCode;
|
||
nextLanCheckTime = millis() + 5000;
|
||
return READ_RE_CONFIG;//-11;
|
||
}
|
||
|
||
} else {
|
||
debugSerial<<F("failed to connect");
|
||
debugSerial<<F(" try again in 5 seconds\n");
|
||
nextLanCheckTime = millis() + 5000;
|
||
return READ_RE_CONFIG;//-11;
|
||
}
|
||
#endif
|
||
#if defined(__SAM3X8E__) || defined(ARDUINO_ARCH_STM32F1)
|
||
String response;
|
||
EthernetClient configEthClient;
|
||
HttpClient htclient = HttpClient(configEthClient, configServer, 80);
|
||
//htclient.stop(); //_socket =MAX
|
||
htclient.setHttpResponseTimeout(4000);
|
||
wdt_res();
|
||
//debugSerial<<"making GET request");get
|
||
htclient.beginRequest();
|
||
htclient.get(URI);
|
||
htclient.endRequest();
|
||
|
||
|
||
// read the status code and body of the response
|
||
responseStatusCode = htclient.responseStatusCode();
|
||
response = htclient.responseBody();
|
||
htclient.stop();
|
||
wdt_res();
|
||
debugSerial<<F("HTTP Status code: ");
|
||
debugSerial<<responseStatusCode;
|
||
//debugSerial<<"GET Response: ");
|
||
|
||
if (responseStatusCode == 200) {
|
||
aJson.deleteItem(root);
|
||
root = aJson.parse((char *) response.c_str());
|
||
|
||
if (!root) {
|
||
debugSerial<<F("Config parsing failed\n");
|
||
return READ_RE_CONFIG;//-11; //Load from NVRAM
|
||
} else {
|
||
debugSerial<<response;
|
||
applyConfig();
|
||
}
|
||
} else {
|
||
debugSerial<<F("Config retrieving failed\n");
|
||
return READ_RE_CONFIG;//-11; //Load from NVRAM
|
||
}
|
||
#endif
|
||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266)
|
||
HTTPClient httpClient;
|
||
String fullURI = "http://";
|
||
fullURI+=configServer;
|
||
fullURI+=URI;
|
||
httpClient.begin(fullURI);
|
||
int httpResponseCode = httpClient.GET();
|
||
if (httpResponseCode > 0) {
|
||
debugSerial.printf("[HTTP] GET... code: %d\n", httpResponseCode);
|
||
if (httpResponseCode == HTTP_CODE_OK) {
|
||
String response = httpClient.getString();
|
||
debugSerial<<response;
|
||
aJson.deleteItem(root);
|
||
root = aJson.parse((char *) response.c_str());
|
||
if (!root) {
|
||
debugSerial<<F("Config parsing failed\n");
|
||
return READ_RE_CONFIG;
|
||
} else {
|
||
debugSerial<<F("Config OK, Applying\n");
|
||
applyConfig();
|
||
}
|
||
}
|
||
} else {
|
||
debugSerial.printf("[HTTP] GET... failed, error: %s\n", httpClient.errorToString(httpResponseCode).c_str());
|
||
httpClient.end();
|
||
return READ_RE_CONFIG;
|
||
}
|
||
httpClient.end();
|
||
#endif
|
||
|
||
return IP_READY_CONFIG_LOADED_CONNECTING_TO_BROKER;
|
||
}
|
||
|
||
void preTransmission() {
|
||
#ifdef CONTROLLINO
|
||
// set DE and RE on HIGH
|
||
PORTJ |= B01100000;
|
||
#else
|
||
digitalWrite(TXEnablePin, 1);
|
||
#endif
|
||
}
|
||
|
||
void postTransmission() {
|
||
#ifdef CONTROLLINO
|
||
// set DE and RE on LOW
|
||
PORTJ &= B10011111;
|
||
#else
|
||
digitalWrite(TXEnablePin, 0);
|
||
#endif
|
||
}
|
||
|
||
void setup_main() {
|
||
setupCmdArduino();
|
||
printFirmwareVersionAndBuildOptions();
|
||
|
||
#ifdef SD_CARD_INSERTED
|
||
sd_card_w5100_setup();
|
||
#endif
|
||
setupMacAddress();
|
||
loadConfigFromEEPROM();
|
||
|
||
#ifdef _modbus
|
||
#ifdef CONTROLLINO
|
||
//set PORTJ pin 5,6 direction (RE,DE)
|
||
DDRJ |= B01100000;
|
||
//set RE,DE on LOW
|
||
PORTJ &= B10011111;
|
||
#else
|
||
pinMode(TXEnablePin, OUTPUT);
|
||
#endif
|
||
modbusSerial.begin(MODBUS_SERIAL_BAUD);
|
||
node.idle(&modbusIdle);
|
||
node.preTransmission(preTransmission);
|
||
node.postTransmission(postTransmission);
|
||
#endif
|
||
|
||
delay(20);
|
||
//owReady = 0;
|
||
|
||
#ifdef _owire
|
||
if (net) net->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<<F("\nLazyhome.ru LightHub controller ")<<F(QUOTE(PIO_SRC_REV))<<F(" C++ version:")<<F(QUOTE(__cplusplus))<<endl;
|
||
#ifdef CONTROLLINO
|
||
debugSerial<<F("\n(+)CONTROLLINO");
|
||
#endif
|
||
#ifdef WATCH_DOG_TICKER_DISABLE
|
||
debugSerial<<F("\n(-)WATCHDOG");
|
||
#else
|
||
debugSerial<<F("\n(+)WATCHDOG");
|
||
#endif
|
||
debugSerial<<F("\nConfig server:")<<F(CONFIG_SERVER)<<F("\nFirmware MAC Address ")<<F(QUOTE(CUSTOM_FIRMWARE_MAC));
|
||
#ifdef DISABLE_FREERAM_PRINT
|
||
debugSerial<<F("\n(-)FreeRam printing");
|
||
#else
|
||
debugSerial<<F("\n(+)FreeRam printing");
|
||
#endif
|
||
|
||
#ifdef USE_1W_PIN
|
||
debugSerial<<F("\n(-)DS2482-100 USE_1W_PIN=")<<QUOTE(USE_1W_PIN);
|
||
#else
|
||
debugSerial<<F("\n(+)DS2482-100");
|
||
#endif
|
||
|
||
#ifdef Wiz5500
|
||
debugSerial<<F("\n(+)WizNet5500");
|
||
#endif
|
||
|
||
#ifdef DMX_DISABLE
|
||
debugSerial<<F("\n(-)DMX");
|
||
#else
|
||
debugSerial<<F("\n(+)DMX");
|
||
#endif
|
||
|
||
#ifdef MODBUS_DISABLE
|
||
debugSerial<<F("\n(-)MODBUS");
|
||
#else
|
||
debugSerial<<F("\n(+)MODBUS");
|
||
#endif
|
||
|
||
#ifdef OWIRE_DISABLE
|
||
debugSerial<<F("\n(-)OWIRE");
|
||
#else
|
||
debugSerial<<F("\n(+)OWIRE");
|
||
#endif
|
||
#ifndef DHT_COUNTER_DISABLE
|
||
debugSerial<<F("\n(+)DHT COUNTER");
|
||
#else
|
||
debugSerial<<F("\n(-)DHT COUNTER");
|
||
#endif
|
||
|
||
#ifdef SD_CARD_INSERTED
|
||
debugSerial<<F("\n(+)SDCARD");
|
||
#endif
|
||
|
||
#ifdef RESET_PIN
|
||
debugSerial<<F("\n(+)HARDRESET on pin=")<<QUOTE(RESET_PIN);
|
||
#else
|
||
debugSerial<<F("\n(-)HARDRESET, using soft");
|
||
#endif
|
||
|
||
#ifdef RESTART_LAN_ON_MQTT_ERRORS
|
||
debugSerial<<F("\n(+)RESTART_LAN_ON_MQTT_ERRORS");
|
||
#else
|
||
debugSerial<<F("\n(-)RESTART_LAN_ON_MQTT_ERRORS");
|
||
#endif
|
||
debugSerial<<endl;
|
||
|
||
|
||
// WDT_Disable( WDT ) ;
|
||
|
||
Serial.println("Reading 128 bits unique identifier \n\r" ) ;
|
||
ReadUniqueID( UniqueID ) ;
|
||
|
||
Serial.print ("ID: ") ;
|
||
for (byte b = 0 ; b < 4 ; b++)
|
||
Serial.print ((unsigned int) UniqueID [b], HEX) ;
|
||
Serial.println () ;
|
||
|
||
|
||
}
|
||
void publishStat(){
|
||
long fr = freeRam();
|
||
char topic[64];
|
||
char intbuf[16];
|
||
long ut = millis()/1000;
|
||
|
||
// debugSerial<<F("\nfree RAM: ")<<fr;
|
||
|
||
strncpy_P(topic, outprefix, sizeof(topic));
|
||
strncat_P(topic, stats_P, sizeof(topic));
|
||
strncat(topic, "/", sizeof(topic));
|
||
strncat_P(topic, freeheap_P, sizeof(topic));
|
||
|
||
mqttClient.publish(topic,itoa(fr,intbuf,10),true);
|
||
|
||
strncpy_P(topic, outprefix, sizeof(topic));
|
||
strncat_P(topic, stats_P, sizeof(topic));
|
||
strncat(topic, "/", sizeof(topic));
|
||
strncat_P(topic, uptime_P, sizeof(topic));
|
||
|
||
mqttClient.publish(topic,itoa(ut,intbuf,10),true);
|
||
}
|
||
|
||
void setupMacAddress() {
|
||
#if defined(__SAM3X8E__)
|
||
byte firmwareMacAddress[6];
|
||
firmwareMacAddress[0]=0xDE;
|
||
firmwareMacAddress[1]=0xAD;
|
||
for (byte b = 0 ; b < 4 ; b++)
|
||
firmwareMacAddress[b+2]=UniqueID [b] ;
|
||
#else
|
||
#ifdef DEFAULT_FIRMWARE_MAC
|
||
byte firmwareMacAddress[6] = DEFAULT_FIRMWARE_MAC;//comma(,) separated hex-array, hard-coded
|
||
#endif
|
||
#endif
|
||
#ifdef CUSTOM_FIRMWARE_MAC
|
||
byte firmwareMacAddress[6];
|
||
const char *macStr = QUOTE(CUSTOM_FIRMWARE_MAC);//colon(:) separated from build options
|
||
parseBytes(macStr, ':', firmwareMacAddress, 6, 16);
|
||
#endif
|
||
|
||
bool isMacValid = false;
|
||
for (short i = 0; i < 6; i++) {
|
||
mac[i] = EEPROM.read(i);
|
||
if (mac[i] != 0 && mac[i] != 0xff) isMacValid = true;
|
||
}
|
||
if (!isMacValid) {
|
||
debugSerial<<F("No MAC configured: set firmware's MAC\n");
|
||
memcpy(mac, firmwareMacAddress, 6);
|
||
}
|
||
printMACAddress();
|
||
}
|
||
|
||
void setupCmdArduino() {
|
||
cmdInit(uint32_t(SERIAL_BAUD));
|
||
debugSerial<<(F(">>>"));
|
||
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<<F("#"));
|
||
// udpSyslog.log(LOG_INFO, "Ping syslog:");
|
||
#endif
|
||
|
||
}
|
||
|
||
void owIdle(void) {
|
||
#ifdef _artnet
|
||
if (artnet && (lanStatus>=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<<thermoItem->name<<F(" Expired\n");
|
||
|
||
} else {
|
||
if (!(--aJson.getArrayItem(thermoExtensionArray, IET_ATTEMPTS)->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<<F(" OFF");
|
||
} else {
|
||
if (curTemp < thermoSetting - THERMO_GIST_CELSIUS) {
|
||
digitalWrite(thermoPin, HIGH);
|
||
debugSerial<<F(" ON");
|
||
} //too cold
|
||
else if (curTemp >= thermoSetting) {
|
||
digitalWrite(thermoPin, LOW);
|
||
debugSerial<<F(" OFF");
|
||
} //Reached settings
|
||
else debugSerial<<F(" -target zone-"); // Nothing to do
|
||
}
|
||
thermostatCheckPrinted = true;
|
||
}
|
||
}
|
||
}
|
||
|
||
nextThermostatCheck = millis() + THERMOSTAT_CHECK_PERIOD;
|
||
publishStat();
|
||
#ifndef DISABLE_FREERAM_PRINT
|
||
(thermostatCheckPrinted) ? debugSerial<<F("\nfree:")<<freeRam()<<" " : debugSerial<<F(" ")<<freeRam()<<F(" ");
|
||
#endif
|
||
|
||
|
||
}
|
||
|
||
short thermoSetCurTemp(char *name, float t) {
|
||
if (items) {
|
||
aJsonObject *thermoItem = aJson.getObjectItem(items, name);
|
||
if (isThermostatWithMinArraySize(thermoItem, 4)) {
|
||
aJsonObject *extArray = NULL;
|
||
|
||
if (aJson.getArraySize(thermoItem) == 4) //No thermo extension yet
|
||
{
|
||
extArray = aJson.createArray(); //Create Ext Array
|
||
|
||
aJsonObject *ocurt = aJson.createItem(t); //Create float
|
||
aJsonObject *oattempts = aJson.createItem(T_ATTEMPTS); //Create int
|
||
aJson.addItemToArray(extArray, ocurt);
|
||
aJson.addItemToArray(extArray, oattempts);
|
||
aJson.addItemToArray(thermoItem, extArray); //Adding to thermoItem
|
||
}
|
||
else if (extArray = aJson.getArrayItem(thermoItem, I_EXT)) {
|
||
aJsonObject *att = aJson.getArrayItem(extArray, IET_ATTEMPTS);
|
||
aJson.getArrayItem(extArray, IET_TEMP)->valuefloat = t;
|
||
if (att->valueint == 0) mqttClient.publish("/alarmoff/snsr", thermoItem->name);
|
||
att->valueint = (int) T_ATTEMPTS;
|
||
}
|
||
}
|
||
}
|
||
}
|