/* sonoff.ino - Sonoff-Tasmota firmware for iTead Sonoff, Wemos and NodeMCU hardware Copyright (C) 2017 Theo Arends This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /*==================================================== Prerequisites: - Change libraries/PubSubClient/src/PubSubClient.h #define MQTT_MAX_PACKET_SIZE 512 - Select IDE Tools - Flash Mode: "DOUT" - Select IDE Tools - Flash Size: "1M (no SPIFFS)" ====================================================*/ #define VERSION 0x05090105 // 5.9.1e // Location specific includes #include "sonoff.h" // Enumaration used in user_config.h #include "user_config.h" // Fixed user configurable options #include "user_config_override.h" // Configuration overrides for user_config.h #include "i18n.h" // Language support configured by user_config.h #include "sonoff_template.h" // Hardware configuration #include "sonoff_post.h" // Configuration overrides for all previous includes // Libraries #include // MQTT // Max message size calculated by PubSubClient is (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength) #if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MESSZ // If the max message size is too small, throw an error at compile time. See PubSubClient.cpp line 359 #error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 512" #endif #include // RTC, HLW8012, OSWatch #include // MQTT, Ota, WifiManager #include // MQTT, Ota #include // Ota #include // Webserver, Updater #include // WemoHue, IRremote, Domoticz #ifdef USE_WEBSERVER #include // WifiManager, Webserver #include // WifiManager #endif // USE_WEBSERVER #ifdef USE_DISCOVERY #include // MQTT, Webserver #endif // USE_DISCOVERY #ifdef USE_I2C #include // I2C support library #endif // USE_I2C #ifdef USE_SPI #include // SPI support, TFT #endif // USE_SPI // Structs #include "settings.h" enum TasmotaCommands { CMND_BACKLOG, CMND_DELAY, CMND_POWER, CMND_STATUS, CMND_POWERONSTATE, CMND_PULSETIME, CMND_BLINKTIME, CMND_BLINKCOUNT, CMND_SAVEDATA, CMND_SETOPTION, CMND_TEMPERATURE_RESOLUTION, CMND_HUMIDITY_RESOLUTION, CMND_PRESSURE_RESOLUTION, CMND_POWER_RESOLUTION, CMND_VOLTAGE_RESOLUTION, CMND_CURRENT_RESOLUTION, CMND_ENERGY_RESOLUTION, CMND_MODULE, CMND_MODULES, CMND_GPIO, CMND_GPIOS, CMND_PWM, CMND_PWMFREQUENCY, CMND_PWMRANGE, CMND_COUNTER, CMND_COUNTERTYPE, CMND_COUNTERDEBOUNCE, CMND_SLEEP, CMND_UPGRADE, CMND_UPLOAD, CMND_OTAURL, CMND_SERIALLOG, CMND_SYSLOG, CMND_LOGHOST, CMND_LOGPORT, CMND_IPADDRESS, CMND_NTPSERVER, CMND_AP, CMND_SSID, CMND_PASSWORD, CMND_HOSTNAME, CMND_WIFICONFIG, CMND_FRIENDLYNAME, CMND_SWITCHMODE, CMND_WEBSERVER, CMND_WEBPASSWORD, CMND_WEBLOG, CMND_EMULATION, CMND_TELEPERIOD, CMND_RESTART, CMND_RESET, CMND_TIMEZONE, CMND_ALTITUDE, CMND_LEDPOWER, CMND_LEDSTATE, CMND_CFGDUMP, CMND_I2CSCAN, CMND_INA219MODE, CMND_EXCEPTION }; const char kTasmotaCommands[] PROGMEM = D_CMND_BACKLOG "|" D_CMND_DELAY "|" D_CMND_POWER "|" D_CMND_STATUS "|" D_CMND_POWERONSTATE "|" D_CMND_PULSETIME "|" D_CMND_BLINKTIME "|" D_CMND_BLINKCOUNT "|" D_CMND_SAVEDATA "|" D_CMND_SETOPTION "|" D_CMND_TEMPERATURE_RESOLUTION "|" D_CMND_HUMIDITY_RESOLUTION "|" D_CMND_PRESSURE_RESOLUTION "|" D_CMND_POWER_RESOLUTION "|" D_CMND_VOLTAGE_RESOLUTION "|" D_CMND_CURRENT_RESOLUTION "|" D_CMND_ENERGY_RESOLUTION "|" D_CMND_MODULE "|" D_CMND_MODULES "|" D_CMND_GPIO "|" D_CMND_GPIOS "|" D_CMND_PWM "|" D_CMND_PWMFREQUENCY "|" D_CMND_PWMRANGE "|" D_CMND_COUNTER "|" D_CMND_COUNTERTYPE "|" D_CMND_COUNTERDEBOUNCE "|" D_CMND_SLEEP "|" D_CMND_UPGRADE "|" D_CMND_UPLOAD "|" D_CMND_OTAURL "|" D_CMND_SERIALLOG "|" D_CMND_SYSLOG "|" D_CMND_LOGHOST "|" D_CMND_LOGPORT "|" D_CMND_IPADDRESS "|" D_CMND_NTPSERVER "|" D_CMND_AP "|" D_CMND_SSID "|" D_CMND_PASSWORD "|" D_CMND_HOSTNAME "|" D_CMND_WIFICONFIG "|" D_CMND_FRIENDLYNAME "|" D_CMND_SWITCHMODE "|" D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_EMULATION "|" D_CMND_TELEPERIOD "|" D_CMND_RESTART "|" D_CMND_RESET "|" D_CMND_TIMEZONE "|" D_CMND_ALTITUDE "|" D_CMND_LEDPOWER "|" D_CMND_LEDSTATE "|" D_CMND_CFGDUMP "|" D_CMND_I2CSCAN "|" D_CMND_INA219MODE #ifdef DEBUG_THEO "|" D_CMND_EXCEPTION #endif ; enum MqttCommands { CMND_MQTTHOST, CMND_MQTTPORT, CMND_MQTTRETRY, CMND_STATETEXT, CMND_MQTTFINGERPRINT, CMND_MQTTCLIENT, CMND_MQTTUSER, CMND_MQTTPASSWORD, CMND_FULLTOPIC, CMND_PREFIX, CMND_GROUPTOPIC, CMND_TOPIC, CMND_BUTTONTOPIC, CMND_SWITCHTOPIC, CMND_BUTTONRETAIN, CMND_SWITCHRETAIN, CMND_POWERRETAIN, CMND_SENSORRETAIN }; const char kMqttCommands[] PROGMEM = D_CMND_MQTTHOST "|" D_CMND_MQTTPORT "|" D_CMND_MQTTRETRY "|" D_CMND_STATETEXT "|" D_CMND_MQTTFINGERPRINT "|" D_CMND_MQTTCLIENT "|" D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|" D_CMND_FULLTOPIC "|" D_CMND_PREFIX "|" D_CMND_GROUPTOPIC "|" D_CMND_TOPIC "|" D_CMND_BUTTONTOPIC "|" D_CMND_SWITCHTOPIC "|" D_CMND_BUTTONRETAIN "|" D_CMND_SWITCHRETAIN "|" D_CMND_POWERRETAIN "|" D_CMND_SENSORRETAIN ; const char kOptionOff[] PROGMEM = "OFF|" D_OFF "|" D_FALSE "|" D_STOP "|" D_CELSIUS ; const char kOptionOn[] PROGMEM = "ON|" D_ON "|" D_TRUE "|" D_START "|" D_FAHRENHEIT "|" D_USER ; const char kOptionToggle[] PROGMEM = "TOGGLE|" D_TOGGLE "|" D_ADMIN ; const char kOptionBlink[] PROGMEM = "BLINK|" D_BLINK ; const char kOptionBlinkOff[] PROGMEM = "BLINKOFF|" D_BLINKOFF ; // Global variables int baudrate = APP_BAUDRATE; // Serial interface baud rate byte serial_in_byte; // Received byte int serial_in_byte_counter = 0; // Index in receive buffer byte dual_hex_code = 0; // Sonoff dual input flag uint16_t dual_button_code = 0; // Sonoff dual received code int16_t save_data_counter; // Counter and flag for config save to Flash uint8_t mqtt_retry_counter = 0; // MQTT connection retry counter uint8_t fallback_topic_flag = 0; // Use Topic or FallbackTopic unsigned long state_loop_timer = 0; // State loop timer int state = 0; // State per second flag int mqtt_connection_flag = 2; // MQTT connection messages flag int ota_state_flag = 0; // OTA state flag int ota_result = 0; // OTA result byte ota_retry_counter = OTA_ATTEMPTS; // OTA retry counter int restart_flag = 0; // Sonoff restart flag int wifi_state_flag = WIFI_RESTART; // Wifi state flag int uptime = 0; // Current uptime in hours boolean latest_uptime_flag = true; // Signal latest uptime int tele_period = 0; // Tele period timer byte web_log_index = 0; // Index in Web log buffer byte reset_web_log_flag = 0; // Reset web console log byte devices_present = 0; // Max number of devices supported int status_update_timer = 0; // Refresh initial status uint16_t pulse_timer[MAX_PULSETIMERS] = { 0 }; // Power off timer uint16_t blink_timer = 0; // Power cycle timer uint16_t blink_counter = 0; // Number of blink cycles power_t blink_power; // Blink power state power_t blink_mask = 0; // Blink relay active mask power_t blink_powersave; // Blink start power save state uint16_t mqtt_cmnd_publish = 0; // ignore flag for publish command power_t latching_power = 0; // Power state at latching start uint8_t latching_relay_pulse = 0; // Latching relay pulse timer uint8_t backlog_index = 0; // Command backlog index uint8_t backlog_pointer = 0; // Command backlog pointer uint8_t backlog_mutex = 0; // Command backlog pending uint16_t backlog_delay = 0; // Command backlog delay uint8_t interlock_mutex = 0; // Interlock power command pending #ifdef USE_MQTT_TLS WiFiClientSecure EspClient; // Wifi Secure Client #else WiFiClient EspClient; // Wifi Client #endif PubSubClient MqttClient(EspClient); // MQTT Client WiFiUDP PortUdp; // UDP Syslog and Alexa power_t power = 0; // Current copy of Settings.power byte syslog_level; // Current copy of Settings.syslog_level uint16_t syslog_timer = 0; // Timer to re-enable syslog_level byte seriallog_level; // Current copy of Settings.seriallog_level uint16_t seriallog_timer = 0; // Timer to disable Seriallog uint8_t sleep; // Current copy of Settings.sleep uint8_t stop_flash_rotate = 0; // Allow flash configuration rotation int blinks = 201; // Number of LED blinks uint8_t blinkstate = 0; // LED state uint8_t blockgpio0 = 4; // Block GPIO0 for 4 seconds after poweron to workaround Wemos D1 RTS circuit uint8_t lastbutton[MAX_KEYS] = { NOT_PRESSED, NOT_PRESSED, NOT_PRESSED, NOT_PRESSED }; // Last button states uint8_t holdbutton[MAX_KEYS] = { 0 }; // Timer for button hold uint8_t multiwindow[MAX_KEYS] = { 0 }; // Max time between button presses to record press count uint8_t multipress[MAX_KEYS] = { 0 }; // Number of button presses within multiwindow uint8_t lastwallswitch[MAX_SWITCHES]; // Last wall switch states uint8_t holdwallswitch[MAX_SWITCHES] = { 0 }; // Timer for wallswitch push button hold mytmplt my_module; // Active copy of Module name and GPIOs uint8_t pin[GPIO_MAX]; // Possible pin configurations power_t rel_inverted = 0; // Relay inverted flag (1 = (0 = On, 1 = Off)) uint8_t led_inverted = 0; // LED inverted flag (1 = (0 = On, 1 = Off)) uint8_t pwm_inverted = 0; // PWM inverted flag (1 = inverted) uint8_t dht_flg = 0; // DHT configured uint8_t hlw_flg = 0; // Power monitor configured uint8_t i2c_flg = 0; // I2C configured uint8_t spi_flg = 0; // SPI configured uint8_t light_type = 0; // Light types boolean mdns_begun = false; uint8_t xsns_present = 0; // Number of External Sensors found boolean (*xsns_func_ptr[XSNS_MAX])(byte); // External Sensor Function Pointers for simple implementation of sensors char version[16]; // Version string from VERSION define char my_hostname[33]; // Composed Wifi hostname char mqtt_client[33]; // Composed MQTT Clientname char serial_in_buffer[INPUT_BUFFER_SIZE + 2]; // Receive buffer char mqtt_data[MESSZ]; // MQTT publish buffer char log_data[TOPSZ + MESSZ]; // Logging String web_log[MAX_LOG_LINES]; // Web log buffer String backlog[MAX_BACKLOG]; // Command backlog /********************************************************************************************/ void GetMqttClient(char* output, const char* input, byte size) { char *token; uint8_t digits = 0; if (strstr(input, "%")) { strlcpy(output, input, size); token = strtok(output, "%"); if (strstr(input, "%") == input) { output[0] = '\0'; } else { token = strtok(NULL, ""); } if (token != NULL) { digits = atoi(token); if (digits) { snprintf_P(output, size, PSTR("%s%c0%dX"), output, '%', digits); snprintf_P(output, size, output, ESP.getChipId()); } } } if (!digits) { strlcpy(output, input, size); } } void GetTopic_P(char *stopic, byte prefix, char *topic, const char* subtopic) { char romram[CMDSZ]; String fulltopic; snprintf_P(romram, sizeof(romram), subtopic); if (fallback_topic_flag) { fulltopic = FPSTR(kPrefixes[prefix]); fulltopic += F("/"); fulltopic += mqtt_client; } else { fulltopic = Settings.mqtt_fulltopic; if ((0 == prefix) && (-1 == fulltopic.indexOf(F(MQTT_TOKEN_PREFIX)))) { fulltopic += F("/" MQTT_TOKEN_PREFIX); // Need prefix for commands to handle mqtt topic loops } for (byte i = 0; i < 3; i++) { if ('\0' == Settings.mqtt_prefix[i][0]) { snprintf_P(Settings.mqtt_prefix[i], sizeof(Settings.mqtt_prefix[i]), kPrefixes[i]); } } fulltopic.replace(F(MQTT_TOKEN_PREFIX), Settings.mqtt_prefix[prefix]); fulltopic.replace(F(MQTT_TOKEN_TOPIC), topic); } fulltopic.replace(F("#"), ""); fulltopic.replace(F("//"), "/"); if (!fulltopic.endsWith("/")) { fulltopic += "/"; } snprintf_P(stopic, TOPSZ, PSTR("%s%s"), fulltopic.c_str(), romram); } char* GetStateText(byte state) { if (state > 3) { state = 1; } return Settings.state_text[state]; } /********************************************************************************************/ void SetLatchingRelay(power_t power, uint8_t state) { power &= 1; if (2 == state) { // Reset relay state = 0; latching_power = power; latching_relay_pulse = 0; } else if (state && !latching_relay_pulse) { // Set port power to On latching_power = power; latching_relay_pulse = 2; // max 200mS (initiated by stateloop()) } if (pin[GPIO_REL1 +latching_power] < 99) { digitalWrite(pin[GPIO_REL1 +latching_power], bitRead(rel_inverted, latching_power) ? !state : state); } } void SetDevicePower(power_t rpower) { uint8_t state; if (4 == Settings.poweronstate) { // All on and stay on power = (1 << devices_present) -1; rpower = power; } if (Settings.flag.interlock) { // Allow only one or no relay set power_t mask = 1; uint8_t count = 0; for (byte i = 0; i < devices_present; i++) { if (rpower & mask) { count++; } mask <<= 1; } if (count > 1) { power = 0; rpower = 0; } } if (light_type) { LightSetPower(bitRead(rpower, devices_present -1)); } if ((SONOFF_DUAL == Settings.module) || (CH4 == Settings.module)) { Serial.write(0xA0); Serial.write(0x04); Serial.write(rpower &0xFF); Serial.write(0xA1); Serial.write('\n'); Serial.flush(); } else if (EXS_RELAY == Settings.module) { SetLatchingRelay(rpower, 1); } else { for (byte i = 0; i < devices_present; i++) { state = rpower &1; if ((i < MAX_RELAYS) && (pin[GPIO_REL1 +i] < 99)) { digitalWrite(pin[GPIO_REL1 +i], bitRead(rel_inverted, i) ? !state : state); } rpower >>= 1; } } HlwSetPowerSteadyCounter(2); } void SetLedPower(uint8_t state) { if (state) { state = 1; } digitalWrite(pin[GPIO_LED1], (bitRead(led_inverted, 0)) ? !state : state); } /********************************************************************************************/ void MqttSubscribe(char *topic) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_SUBSCRIBE_TO " %s"), topic); AddLog(LOG_LEVEL_DEBUG); MqttClient.subscribe(topic); MqttClient.loop(); // Solve LmacRxBlk:1 messages } void MqttPublishDirect(const char* topic, boolean retained) { if (Settings.flag.mqtt_enabled) { if (MqttClient.publish(topic, mqtt_data, retained)) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT "%s = %s%s"), topic, mqtt_data, (retained) ? " (" D_RETAINED ")" : ""); // MqttClient.loop(); // Do not use here! Will block previous publishes } else { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_RESULT "%s = %s"), topic, mqtt_data); } } else { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_RESULT "%s = %s"), strrchr(topic,'/')+1, mqtt_data); } AddLog(LOG_LEVEL_INFO); if (Settings.ledstate &0x04) { blinks++; } } void MqttPublish(const char* topic, boolean retained) { char *me; if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1])) { me = strstr(topic,Settings.mqtt_prefix[0]); if (me == topic) { mqtt_cmnd_publish += 8; } } MqttPublishDirect(topic, retained); } void MqttPublish(const char* topic) { MqttPublish(topic, false); } void MqttPublishPrefixTopic_P(uint8_t prefix, const char* subtopic, boolean retained) { /* prefix 0 = cmnd using subtopic * prefix 1 = stat using subtopic * prefix 2 = tele using subtopic * prefix 4 = cmnd using subtopic or RESULT * prefix 5 = stat using subtopic or RESULT * prefix 6 = tele using subtopic or RESULT */ char romram[16]; char stopic[TOPSZ]; snprintf_P(romram, sizeof(romram), ((prefix > 3) && !Settings.flag.mqtt_response) ? S_RSLT_RESULT : subtopic); for (byte i = 0; i < strlen(romram); i++) { romram[i] = toupper(romram[i]); } prefix &= 3; GetTopic_P(stopic, prefix, Settings.mqtt_topic, romram); MqttPublish(stopic, retained); } void MqttPublishPrefixTopic_P(uint8_t prefix, const char* subtopic) { MqttPublishPrefixTopic_P(prefix, subtopic, false); } void MqttPublishPowerState(byte device) { char stopic[TOPSZ]; char scommand[16]; if ((device < 1) || (device > devices_present)) { device = 1; } GetPowerDevice(scommand, device, sizeof(scommand)); GetTopic_P(stopic, 1, Settings.mqtt_topic, (Settings.flag.mqtt_response) ? scommand : S_RSLT_RESULT); snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, scommand, GetStateText(bitRead(power, device -1))); MqttPublish(stopic); GetTopic_P(stopic, 1, Settings.mqtt_topic, scommand); snprintf_P(mqtt_data, sizeof(mqtt_data), GetStateText(bitRead(power, device -1))); MqttPublish(stopic, Settings.flag.mqtt_power_retain); } void MqttPublishPowerBlinkState(byte device) { char scommand[16]; if ((device < 1) || (device > devices_present)) { device = 1; } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"%s\":\"" D_BLINK " %s\"}"), GetPowerDevice(scommand, device, sizeof(scommand)), GetStateText(bitRead(blink_mask, device -1))); MqttPublishPrefixTopic_P(5, S_RSLT_POWER); } void MqttConnected() { char stopic[TOPSZ]; if (Settings.flag.mqtt_enabled) { // Satisfy iobroker (#299) mqtt_data[0] = '\0'; MqttPublishPrefixTopic_P(0, S_RSLT_POWER); GetTopic_P(stopic, 0, Settings.mqtt_topic, PSTR("#")); MqttSubscribe(stopic); if (strstr(Settings.mqtt_fulltopic, MQTT_TOKEN_TOPIC) != NULL) { GetTopic_P(stopic, 0, Settings.mqtt_grptopic, PSTR("#")); MqttSubscribe(stopic); fallback_topic_flag = 1; GetTopic_P(stopic, 0, mqtt_client, PSTR("#")); fallback_topic_flag = 0; MqttSubscribe(stopic); } #ifdef USE_DOMOTICZ DomoticzMqttSubscribe(); #endif // USE_DOMOTICZ } if (mqtt_connection_flag) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_MODULE "\":\"%s\",\"" D_VERSION "\":\"%s\",\"" D_FALLBACKTOPIC "\":\"%s\",\"" D_CMND_GROUPTOPIC "\":\"%s\"}"), my_module.name, version, mqtt_client, Settings.mqtt_grptopic); MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_INFO "1")); #ifdef USE_WEBSERVER if (Settings.webserver) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\"}"), (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str()); MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_INFO "2")); } #endif // USE_WEBSERVER snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_RESTARTREASON "\":\"%s\"}"), (GetResetReason() == "Exception") ? ESP.getResetInfo().c_str() : GetResetReason().c_str()); MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_INFO "3")); if (Settings.tele_period) { tele_period = Settings.tele_period -9; } status_update_timer = 2; #ifdef USE_DOMOTICZ DomoticzSetUpdateTimer(2); #endif // USE_DOMOTICZ } mqtt_connection_flag = 0; } void MqttReconnect() { char stopic[TOPSZ]; mqtt_retry_counter = Settings.mqtt_retry; if (!Settings.flag.mqtt_enabled) { MqttConnected(); return; } #ifdef USE_EMULATION UdpDisconnect(); #endif // USE_EMULATION if (mqtt_connection_flag > 1) { #ifdef USE_MQTT_TLS AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_FINGERPRINT)); if (!EspClient.connect(Settings.mqtt_host, Settings.mqtt_port)) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_TLS_CONNECT_FAILED_TO " %s:%d. " D_RETRY_IN " %d " D_UNIT_SECOND), Settings.mqtt_host, Settings.mqtt_port, mqtt_retry_counter); AddLog(LOG_LEVEL_DEBUG); return; } if (EspClient.verify(Settings.mqtt_fingerprint, Settings.mqtt_host)) { AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_VERIFIED)); } else { AddLog_P(LOG_LEVEL_DEBUG, S_LOG_MQTT, PSTR(D_INSECURE)); } EspClient.stop(); yield(); #endif // USE_MQTT_TLS MqttClient.setCallback(MqttDataCallback); mqtt_connection_flag = 1; mqtt_retry_counter = 1; return; } AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_ATTEMPTING_CONNECTION)); #ifndef USE_MQTT_TLS #ifdef USE_DISCOVERY #ifdef MQTT_HOST_DISCOVERY // if (!strlen(MQTT_HOST)) { if (!strlen(Settings.mqtt_host)) { MdnsDiscoverMqttServer(); } #endif // MQTT_HOST_DISCOVERY #endif // USE_DISCOVERY #endif // USE_MQTT_TLS MqttClient.setServer(Settings.mqtt_host, Settings.mqtt_port); GetTopic_P(stopic, 2, Settings.mqtt_topic, S_LWT); snprintf_P(mqtt_data, sizeof(mqtt_data), S_OFFLINE); char *mqtt_user = NULL; char *mqtt_pwd = NULL; if (strlen(Settings.mqtt_user) > 0) { mqtt_user = Settings.mqtt_user; } if (strlen(Settings.mqtt_pwd) > 0) { mqtt_pwd = Settings.mqtt_pwd; } if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data)) { AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR(D_CONNECTED)); mqtt_retry_counter = 0; snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(D_ONLINE)); MqttPublish(stopic, true); MqttConnected(); } else { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), Settings.mqtt_host, Settings.mqtt_port, MqttClient.state(), mqtt_retry_counter); //status codes are documented here http://pubsubclient.knolleary.net/api.html#state AddLog(LOG_LEVEL_INFO); } } /********************************************************************************************/ boolean MqttCommand(boolean grpflg, char *type, uint16_t index, char *dataBuf, uint16_t data_len, int16_t payload, uint16_t payload16) { char command [CMDSZ]; boolean serviced = true; char stemp1[TOPSZ]; char stemp2[10]; char scommand[CMDSZ]; uint16_t i; int command_code = GetCommandCode(command, sizeof(command), type, kMqttCommands); if (CMND_MQTTHOST == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_host))) { strlcpy(Settings.mqtt_host, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_HOST : dataBuf, sizeof(Settings.mqtt_host)); restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_host); } else if (CMND_MQTTPORT == command_code) { if (payload16 > 0) { Settings.mqtt_port = (1 == payload16) ? MQTT_PORT : payload16; restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.mqtt_port); } else if (CMND_MQTTRETRY == command_code) { if ((payload >= MQTT_RETRY_SECS) && (payload < 32001)) { Settings.mqtt_retry = payload; mqtt_retry_counter = Settings.mqtt_retry; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.mqtt_retry); } else if ((CMND_STATETEXT == command_code) && (index > 0) && (index <= 4)) { if ((data_len > 0) && (data_len < sizeof(Settings.state_text[0]))) { for(i = 0; i <= data_len; i++) { if (dataBuf[i] == ' ') { dataBuf[i] = '_'; } } strlcpy(Settings.state_text[index -1], dataBuf, sizeof(Settings.state_text[0])); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, GetStateText(index -1)); } #ifdef USE_MQTT_TLS else if (CMND_MQTTFINGERPRINT == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_fingerprint))) { strlcpy(Settings.mqtt_fingerprint, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_FINGERPRINT : dataBuf, sizeof(Settings.mqtt_fingerprint)); restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_fingerprint); } #endif else if ((CMND_MQTTCLIENT == command_code) && !grpflg) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_client))) { strlcpy(Settings.mqtt_client, (1 == payload) ? MQTT_CLIENT_ID : dataBuf, sizeof(Settings.mqtt_client)); restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_client); } else if (CMND_MQTTUSER == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_user))) { strlcpy(Settings.mqtt_user, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_USER : dataBuf, sizeof(Settings.mqtt_user)); restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_user); } else if (CMND_MQTTPASSWORD == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_pwd))) { strlcpy(Settings.mqtt_pwd, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? MQTT_PASS : dataBuf, sizeof(Settings.mqtt_pwd)); restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_pwd); } else if (CMND_FULLTOPIC == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_fulltopic))) { MakeValidMqtt(1, dataBuf); if (!strcmp(dataBuf, mqtt_client)) { payload = 1; } strlcpy(stemp1, (1 == payload) ? MQTT_FULLTOPIC : dataBuf, sizeof(stemp1)); if (strcmp(stemp1, Settings.mqtt_fulltopic)) { snprintf_P(mqtt_data, sizeof(mqtt_data), (Settings.flag.mqtt_offline) ? S_OFFLINE : ""); MqttPublishPrefixTopic_P(2, PSTR(D_LWT), true); // Offline or remove previous retained topic strlcpy(Settings.mqtt_fulltopic, stemp1, sizeof(Settings.mqtt_fulltopic)); restart_flag = 2; } } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_fulltopic); } else if ((CMND_PREFIX == command_code) && (index > 0) && (index <= 3)) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_prefix[0]))) { MakeValidMqtt(0, dataBuf); strlcpy(Settings.mqtt_prefix[index -1], (1 == payload) ? (1==index)?SUB_PREFIX:(2==index)?PUB_PREFIX:PUB_PREFIX2 : dataBuf, sizeof(Settings.mqtt_prefix[0])); // if (Settings.mqtt_prefix[index -1][strlen(Settings.mqtt_prefix[index -1])] == '/') Settings.mqtt_prefix[index -1][strlen(Settings.mqtt_prefix[index -1])] = 0; restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.mqtt_prefix[index -1]); } else if (CMND_GROUPTOPIC == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_grptopic))) { MakeValidMqtt(0, dataBuf); if (!strcmp(dataBuf, mqtt_client)) { payload = 1; } strlcpy(Settings.mqtt_grptopic, (1 == payload) ? MQTT_GRPTOPIC : dataBuf, sizeof(Settings.mqtt_grptopic)); restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_grptopic); } else if ((CMND_TOPIC == command_code) && !grpflg) { if ((data_len > 0) && (data_len < sizeof(Settings.mqtt_topic))) { MakeValidMqtt(0, dataBuf); if (!strcmp(dataBuf, mqtt_client)) { payload = 1; } strlcpy(stemp1, (1 == payload) ? MQTT_TOPIC : dataBuf, sizeof(stemp1)); if (strcmp(stemp1, Settings.mqtt_topic)) { snprintf_P(mqtt_data, sizeof(mqtt_data), (Settings.flag.mqtt_offline) ? S_OFFLINE : ""); MqttPublishPrefixTopic_P(2, PSTR(D_LWT), true); // Offline or remove previous retained topic strlcpy(Settings.mqtt_topic, stemp1, sizeof(Settings.mqtt_topic)); restart_flag = 2; } } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.mqtt_topic); } else if ((CMND_BUTTONTOPIC == command_code) && !grpflg) { if ((data_len > 0) && (data_len < sizeof(Settings.button_topic))) { MakeValidMqtt(0, dataBuf); if (!strcmp(dataBuf, mqtt_client)) { payload = 1; } strlcpy(Settings.button_topic, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? Settings.mqtt_topic : dataBuf, sizeof(Settings.button_topic)); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.button_topic); } else if (CMND_SWITCHTOPIC == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.switch_topic))) { MakeValidMqtt(0, dataBuf); if (!strcmp(dataBuf, mqtt_client)) { payload = 1; } strlcpy(Settings.switch_topic, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? Settings.mqtt_topic : dataBuf, sizeof(Settings.switch_topic)); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.switch_topic); } else if (CMND_BUTTONRETAIN == command_code) { if ((payload >= 0) && (payload <= 1)) { strlcpy(Settings.button_topic, Settings.mqtt_topic, sizeof(Settings.button_topic)); if (!payload) { for(i = 1; i <= MAX_KEYS; i++) { send_button_power(0, i, 9); // Clear MQTT retain in broker } } Settings.flag.mqtt_button_retain = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.mqtt_button_retain)); } else if (CMND_SWITCHRETAIN == command_code) { if ((payload >= 0) && (payload <= 1)) { strlcpy(Settings.button_topic, Settings.mqtt_topic, sizeof(Settings.button_topic)); if (!payload) { for(i = 1; i <= MAX_SWITCHES; i++) { send_button_power(1, i, 9); // Clear MQTT retain in broker } } Settings.flag.mqtt_switch_retain = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.mqtt_switch_retain)); } else if (CMND_POWERRETAIN == command_code) { if ((payload >= 0) && (payload <= 1)) { if (!payload) { for(i = 1; i <= devices_present; i++) { // Clear MQTT retain in broker GetTopic_P(stemp1, 1, Settings.mqtt_topic, GetPowerDevice(scommand, i, sizeof(scommand))); mqtt_data[0] = '\0'; MqttPublish(stemp1, Settings.flag.mqtt_power_retain); } } Settings.flag.mqtt_power_retain = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.mqtt_power_retain)); } else if (CMND_SENSORRETAIN == command_code) { if ((payload >= 0) && (payload <= 1)) { if (!payload) { mqtt_data[0] = '\0'; MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_ENERGY), Settings.flag.mqtt_sensor_retain); } Settings.flag.mqtt_sensor_retain = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(Settings.flag.mqtt_sensor_retain)); } #ifdef USE_DOMOTICZ else if (DomoticzCommand(type, index, dataBuf, data_len, payload)) { // Serviced } #endif // USE_DOMOTICZ else { serviced = false; } return serviced; } /********************************************************************************************/ void MqttDataCallback(char* topic, byte* data, unsigned int data_len) { char *str; if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1])) { str = strstr(topic,Settings.mqtt_prefix[0]); if ((str == topic) && mqtt_cmnd_publish) { if (mqtt_cmnd_publish > 8) { mqtt_cmnd_publish -= 8; } else { mqtt_cmnd_publish = 0; } return; } } char topicBuf[TOPSZ]; char dataBuf[data_len+1]; char command [CMDSZ]; char stemp1[TOPSZ]; char *p; char *mtopic = NULL; char *type = NULL; byte otype = 0; byte ptype = 0; byte jsflg = 0; byte lines = 1; uint16_t i = 0; uint16_t grpflg = 0; uint16_t index; uint32_t address; strncpy(topicBuf, topic, sizeof(topicBuf)); for (i = 0; i < data_len; i++) { if (!isspace(data[i])) { break; } } data_len -= i; memcpy(dataBuf, data +i, sizeof(dataBuf)); dataBuf[sizeof(dataBuf)-1] = 0; snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_RESULT D_RECEIVED_TOPIC " %s, " D_DATA_SIZE " %d, " D_DATA " %s"), topicBuf, data_len, dataBuf); AddLog(LOG_LEVEL_DEBUG_MORE); // if (LOG_LEVEL_DEBUG_MORE <= seriallog_level) Serial.println(dataBuf); #ifdef USE_DOMOTICZ if (Settings.flag.mqtt_enabled) { if (DomoticzMqttData(topicBuf, sizeof(topicBuf), dataBuf, sizeof(dataBuf))) { return; } } #endif // USE_DOMOTICZ grpflg = (strstr(topicBuf, Settings.mqtt_grptopic) != NULL); fallback_topic_flag = (strstr(topicBuf, mqtt_client) != NULL); type = strrchr(topicBuf, '/') +1; // Last part of received topic is always the command (type) index = 1; if (type != NULL) { for (i = 0; i < strlen(type); i++) { type[i] = toupper(type[i]); } while (isdigit(type[i-1])) { i--; } if (i < strlen(type)) { index = atoi(type +i); } type[i] = '\0'; } snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_RESULT D_GROUP " %d, " D_INDEX " %d, " D_COMMAND " %s, " D_DATA " %s"), grpflg, index, type, dataBuf); AddLog(LOG_LEVEL_DEBUG); if (type != NULL) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_COMMAND "\":\"" D_ERROR "\"}")); if (Settings.ledstate &0x02) { blinks++; } if (!strcmp(dataBuf,"?")) { data_len = 0; } int16_t payload = -99; // No payload uint16_t payload16 = 0; long lnum = strtol(dataBuf, &p, 10); if (p != dataBuf) { payload = (int16_t) lnum; // -32766 - 32767 payload16 = (uint16_t) lnum; // 0 - 65535 } backlog_delay = MIN_BACKLOG_DELAY; // Reset backlog delay if ((GetCommandCode(command, sizeof(command), dataBuf, kOptionOff) >= 0) || !strcasecmp(dataBuf, Settings.state_text[0])) { payload = 0; } if ((GetCommandCode(command, sizeof(command), dataBuf, kOptionOn) >= 0) || !strcasecmp(dataBuf, Settings.state_text[1])) { payload = 1; } if ((GetCommandCode(command, sizeof(command), dataBuf, kOptionToggle) >= 0) || !strcasecmp(dataBuf, Settings.state_text[2])) { payload = 2; } if (GetCommandCode(command, sizeof(command), dataBuf, kOptionBlink) >= 0) { payload = 3; } if (GetCommandCode(command, sizeof(command), dataBuf, kOptionBlinkOff) >= 0) { payload = 4; } // snprintf_P(log_data, sizeof(log_data), PSTR("RSLT: Payload %d, Payload16 %d"), payload, payload16); // AddLog(LOG_LEVEL_DEBUG); int command_code = GetCommandCode(command, sizeof(command), type, kTasmotaCommands); if (CMND_BACKLOG == command_code) { if (data_len) { char *blcommand = strtok(dataBuf, ";"); while (blcommand != NULL) { backlog[backlog_index] = String(blcommand); backlog_index++; /* if (backlog_index >= MAX_BACKLOG) { backlog_index = 0; } */ backlog_index &= 0xF; blcommand = strtok(NULL, ";"); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_APPENDED); } else { uint8_t blflag = (backlog_pointer == backlog_index); backlog_pointer = backlog_index; snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, blflag ? D_EMPTY : D_ABORTED); } } else if (CMND_DELAY == command_code) { if ((payload >= MIN_BACKLOG_DELAY) && (payload <= 3600)) { backlog_delay = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, backlog_delay); } else if ((CMND_POWER == command_code) && (index > 0) && (index <= devices_present)) { if ((payload < 0) || (payload > 4)) { payload = 9; } ExecuteCommandPower(index, payload); fallback_topic_flag = 0; return; } else if (CMND_STATUS == command_code) { if ((payload < 0) || (payload > MAX_STATUS)) { payload = 99; } PublishStatus(payload); fallback_topic_flag = 0; return; } else if ((CMND_POWERONSTATE == command_code) && (Settings.module != MOTOR)) { /* 0 = Keep relays off after power on * 1 = Turn relays on after power on * 2 = Toggle relays after power on * 3 = Set relays to last saved state after power on * 4 = Turn relays on and disable any relay control (used for Sonoff Pow to always measure power) */ if ((payload >= 0) && (payload <= 4)) { Settings.poweronstate = payload; if (4 == Settings.poweronstate) { for (byte i = 1; i <= devices_present; i++) { ExecuteCommandPower(i, 1); } } } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.poweronstate); } else if ((CMND_PULSETIME == command_code) && (index > 0) && (index <= MAX_PULSETIMERS)) { if (data_len > 0) { Settings.pulse_timer[index -1] = payload16; // 0 - 65535 pulse_timer[index -1] = 0; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_NVALUE, command, index, Settings.pulse_timer[index -1]); } else if (CMND_BLINKTIME == command_code) { if ((payload > 2) && (payload <= 3600)) { Settings.blinktime = payload; if (blink_timer) { blink_timer = Settings.blinktime; } } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.blinktime); } else if (CMND_BLINKCOUNT == command_code) { if (data_len > 0) { Settings.blinkcount = payload16; // 0 - 65535 if (blink_counter) { blink_counter = Settings.blinkcount *2; } } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.blinkcount); } else if (light_type && LightCommand(type, index, dataBuf, data_len, payload)) { // Serviced } else if (CMND_SAVEDATA == command_code) { if ((payload >= 0) && (payload <= 3600)) { Settings.save_data = payload; save_data_counter = Settings.save_data; } if (Settings.flag.save_state) { Settings.power = power; } SettingsSave(0); if (Settings.save_data > 1) { snprintf_P(stemp1, sizeof(stemp1), PSTR(D_EVERY " %d " D_UNIT_SECOND), Settings.save_data); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, (Settings.save_data > 1) ? stemp1 : GetStateText(Settings.save_data)); } else if ((CMND_SETOPTION == command_code) && ((index >= 0) && (index <= 17)) || ((index > 31) && (index <= P_MAX_PARAM8 +31))) { if (index <= 31) { ptype = 0; // SetOption0 .. 31 } else { ptype = 1; // SetOption32 .. index = index -32; } if (payload >= 0) { if (0 == ptype) { // SetOption0 .. 31 if (payload <= 1) { switch (index) { case 3: // mqtt case 15: // pwm_control restart_flag = 2; case 0: // save_state case 1: // button_restrict case 2: // value_units case 4: // mqtt_response case 8: // temperature_conversion case 10: // mqtt_offline case 11: // button_swap case 12: // stop_flash_rotate case 13: // button_single case 14: // interlock case 16: // ws_clock_reverse case 17: // decimal_text bitWrite(Settings.flag.data, index, payload); } if (12 == index) { // stop_flash_rotate stop_flash_rotate = payload; SettingsSave(2); } } } else { // SetOption32 .. switch (index) { case P_HOLD_TIME: if ((payload >= 1) && (payload <= 100)) { Settings.param[P_HOLD_TIME] = payload; } break; case P_MAX_POWER_RETRY: if ((payload >= 1) && (payload <= 250)) { Settings.param[P_MAX_POWER_RETRY] = payload; } break; } } } if (ptype) { snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), Settings.param[index]); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, (ptype) ? index +32 : index, (ptype) ? stemp1 : GetStateText(bitRead(Settings.flag.data, index))); } else if (CMND_TEMPERATURE_RESOLUTION == command_code) { if ((payload >= 0) && (payload <= 3)) { Settings.flag2.temperature_resolution = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.temperature_resolution); } else if (CMND_HUMIDITY_RESOLUTION == command_code) { if ((payload >= 0) && (payload <= 3)) { Settings.flag2.humidity_resolution = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.humidity_resolution); } else if (CMND_PRESSURE_RESOLUTION == command_code) { if ((payload >= 0) && (payload <= 3)) { Settings.flag2.pressure_resolution = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.pressure_resolution); } else if (CMND_POWER_RESOLUTION == command_code) { if ((payload >= 0) && (payload <= 3)) { Settings.flag2.wattage_resolution = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.wattage_resolution); } else if (CMND_VOLTAGE_RESOLUTION == command_code) { if ((payload >= 0) && (payload <= 3)) { Settings.flag2.voltage_resolution = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.voltage_resolution); } else if (CMND_CURRENT_RESOLUTION == command_code) { if ((payload >= 0) && (payload <= 3)) { Settings.flag2.current_resolution = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.current_resolution); } else if (CMND_ENERGY_RESOLUTION == command_code) { if ((payload >= 0) && (payload <= 5)) { Settings.flag2.energy_resolution = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.energy_resolution); } else if (CMND_MODULE == command_code) { if ((payload > 0) && (payload <= MAXMODULE)) { payload--; Settings.last_module = Settings.module; Settings.module = payload; if (Settings.last_module != payload) { for (byte i = 0; i < MAX_GPIO_PIN; i++) { Settings.my_gp.io[i] = 0; } } restart_flag = 2; } snprintf_P(stemp1, sizeof(stemp1), kModules[Settings.module].name); snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_SVALUE, command, Settings.module +1, stemp1); } else if (CMND_MODULES == command_code) { for (byte i = 0; i < MAXMODULE; i++) { if (!jsflg) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_MODULES "%d\":\""), lines); } else { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,"), mqtt_data); } jsflg = 1; snprintf_P(stemp1, sizeof(stemp1), kModules[i].name); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%d (%s)"), mqtt_data, i +1, stemp1); if ((strlen(mqtt_data) > 200) || (i == MAXMODULE -1)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"}"), mqtt_data); MqttPublishPrefixTopic_P(5, type); jsflg = 0; lines++; } } mqtt_data[0] = '\0'; } else if ((CMND_GPIO == command_code) && (index < MAX_GPIO_PIN)) { mytmplt cmodule; memcpy_P(&cmodule, &kModules[Settings.module], sizeof(cmodule)); if ((GPIO_USER == cmodule.gp.io[index]) && (payload >= 0) && (payload < GPIO_SENSOR_END)) { for (byte i = 0; i < MAX_GPIO_PIN; i++) { if ((GPIO_USER == cmodule.gp.io[i]) && (Settings.my_gp.io[i] == payload)) { Settings.my_gp.io[i] = 0; } } Settings.my_gp.io[index] = payload; restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{")); byte jsflg = 0; for (byte i = 0; i < MAX_GPIO_PIN; i++) { if (GPIO_USER == cmodule.gp.io[i]) { if (jsflg) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,"), mqtt_data); } jsflg = 1; snprintf_P(stemp1, sizeof(stemp1), kSensors[Settings.my_gp.io[i]]); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_GPIO "%d\":\"%d (%s)\""), mqtt_data, i, Settings.my_gp.io[i], stemp1); } } if (jsflg) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); } else { snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_NOT_SUPPORTED); } } else if (CMND_GPIOS == command_code) { for (byte i = 0; i < GPIO_SENSOR_END; i++) { if (!jsflg) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_GPIOS "%d\":\""), lines); } else { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,"), mqtt_data); } jsflg = 1; snprintf_P(stemp1, sizeof(stemp1), kSensors[i]); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%d (%s)"), mqtt_data, i, stemp1); if ((strlen(mqtt_data) > 200) || (i == GPIO_SENSOR_END -1)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"}"), mqtt_data); MqttPublishPrefixTopic_P(5, type); jsflg = 0; lines++; } } mqtt_data[0] = '\0'; } else if ((CMND_PWM == command_code) && !light_type && (index > 0) && (index <= MAX_PWMS)) { if ((payload >= 0) && (payload <= Settings.pwm_range) && (pin[GPIO_PWM1 + index -1] < 99)) { Settings.pwm_value[index -1] = payload; analogWrite(pin[GPIO_PWM1 + index -1], bitRead(pwm_inverted, index -1) ? Settings.pwm_range - payload : payload); } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_PWM "\":{")); bool first = true; for (byte i = 0; i < MAX_PWMS; i++) { if(pin[GPIO_PWM1 + i] < 99) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s%s\"" D_CMND_PWM "%d\":%d"), mqtt_data, first ? "" : ",", i+1, Settings.pwm_value[i]); first = false; } } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}}"),mqtt_data); } else if (CMND_PWMFREQUENCY == command_code) { if ((1 == payload) || ((payload >= 100) && (payload <= 4000))) { Settings.pwm_frequency = (1 == payload) ? PWM_FREQ : payload; analogWriteFreq(Settings.pwm_frequency); // Default is 1000 (core_esp8266_wiring_pwm.c) } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.pwm_frequency); } else if (CMND_PWMRANGE == command_code) { if ((1 == payload) || ((payload > 254) && (payload < 1024))) { Settings.pwm_range = (1 == payload) ? PWM_RANGE : payload; for (byte i; i < MAX_PWMS; i++) { if (Settings.pwm_value[i] > Settings.pwm_range) { Settings.pwm_value[i] = Settings.pwm_range; } } analogWriteRange(Settings.pwm_range); // Default is 1023 (Arduino.h) } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.pwm_range); } else if ((CMND_COUNTER == command_code) && (index > 0) && (index <= MAX_COUNTERS)) { if ((data_len > 0) && (pin[GPIO_CNTR1 + index -1] < 99)) { RtcSettings.pulse_counter[index -1] = payload16; Settings.pulse_counter[index -1] = payload16; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_NVALUE, command, index, RtcSettings.pulse_counter[index -1]); } else if ((CMND_COUNTERTYPE == command_code) && (index > 0) && (index <= MAX_COUNTERS)) { if ((payload >= 0) && (payload <= 1) && (pin[GPIO_CNTR1 + index -1] < 99)) { bitWrite(Settings.pulse_counter_type, index -1, payload &1); RtcSettings.pulse_counter[index -1] = 0; Settings.pulse_counter[index -1] = 0; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_NVALUE, command, index, bitRead(Settings.pulse_counter_type, index -1)); } else if (CMND_COUNTERDEBOUNCE == command_code) { if ((data_len > 0) && (payload16 < 32001)) { Settings.pulse_counter_debounce = payload16; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.pulse_counter_debounce); } else if (CMND_SLEEP == command_code) { if ((payload >= 0) && (payload < 251)) { if ((!Settings.sleep && payload) || (Settings.sleep && !payload)) { restart_flag = 2; } Settings.sleep = payload; sleep = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_UNIT_NVALUE_UNIT, command, sleep, (Settings.flag.value_units) ? " " D_UNIT_MILLISECOND : "", Settings.sleep, (Settings.flag.value_units) ? " " D_UNIT_MILLISECOND : ""); } else if ((CMND_UPGRADE == command_code) || (CMND_UPLOAD == command_code)) { // Check if the payload is numerically 1, and had no trailing chars. // e.g. "1foo" or "1.2.3" could fool us. // Check if the version we have been asked to upgrade to is higher than our current version. // We also need at least 3 chars to make a valid version number string. if (((1 == data_len) && (1 == payload)) || ((data_len >= 3) && NewerVersion(dataBuf))) { ota_state_flag = 3; snprintf_P(mqtt_data, sizeof(mqtt_data), "{\"%s\":\"" D_VERSION " %s " D_FROM " %s\"}", command, version, Settings.ota_url); } else { snprintf_P(mqtt_data, sizeof(mqtt_data), "{\"%s\":\"" D_ONE_OR_GT "\"}", command, version); } } else if (CMND_OTAURL == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.ota_url))) strlcpy(Settings.ota_url, (1 == payload) ? OTA_URL : dataBuf, sizeof(Settings.ota_url)); snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.ota_url); } else if (CMND_SERIALLOG == command_code) { if ((payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_ALL)) { Settings.seriallog_level = payload; seriallog_level = payload; seriallog_timer = 0; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, command, Settings.seriallog_level, seriallog_level); } else if (CMND_SYSLOG == command_code) { if ((payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_ALL)) { Settings.syslog_level = payload; syslog_level = (Settings.flag2.emulation) ? 0 : payload; syslog_timer = 0; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_ACTIVE_NVALUE, command, Settings.syslog_level, syslog_level); } else if (CMND_LOGHOST == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.syslog_host))) { strlcpy(Settings.syslog_host, (1 == payload) ? SYS_LOG_HOST : dataBuf, sizeof(Settings.syslog_host)); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.syslog_host); } else if (CMND_LOGPORT == command_code) { if (payload16 > 0) { Settings.syslog_port = (1 == payload16) ? SYS_LOG_PORT : payload16; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.syslog_port); } else if ((CMND_IPADDRESS == command_code) && (index > 0) && (index <= 4)) { if (ParseIp(&address, dataBuf)) { Settings.ip_address[index -1] = address; // restart_flag = 2; } snprintf_P(stemp1, sizeof(stemp1), PSTR(" (%s)"), WiFi.localIP().toString().c_str()); snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE_SVALUE, command, index, IPAddress(Settings.ip_address[index -1]).toString().c_str(), (1 == index) ? stemp1:""); } else if ((CMND_NTPSERVER == command_code) && (index > 0) && (index <= 3)) { if ((data_len > 0) && (data_len < sizeof(Settings.ntp_server[0]))) { strlcpy(Settings.ntp_server[index -1], (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? (1==index)?NTP_SERVER1:(2==index)?NTP_SERVER2:NTP_SERVER3 : dataBuf, sizeof(Settings.ntp_server[0])); for (i = 0; i < strlen(Settings.ntp_server[index -1]); i++) { if (Settings.ntp_server[index -1][i] == ',') { Settings.ntp_server[index -1][i] = '.'; } } restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.ntp_server[index -1]); } else if (CMND_AP == command_code) { if ((payload >= 0) && (payload <= 2)) { switch (payload) { case 0: // Toggle Settings.sta_active ^= 1; break; case 1: // AP1 case 2: // AP2 Settings.sta_active = payload -1; } restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_SVALUE, command, Settings.sta_active +1, Settings.sta_ssid[Settings.sta_active]); } else if ((CMND_SSID == command_code) && (index > 0) && (index <= 2)) { if ((data_len > 0) && (data_len < sizeof(Settings.sta_ssid[0]))) { strlcpy(Settings.sta_ssid[index -1], (1 == payload) ? (1 == index) ? STA_SSID1 : STA_SSID2 : dataBuf, sizeof(Settings.sta_ssid[0])); Settings.sta_active = index -1; restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.sta_ssid[index -1]); } else if ((CMND_PASSWORD == command_code) && (index > 0) && (index <= 2)) { if ((data_len > 0) && (data_len < sizeof(Settings.sta_pwd[0]))) { strlcpy(Settings.sta_pwd[index -1], (1 == payload) ? (1 == index) ? STA_PASS1 : STA_PASS2 : dataBuf, sizeof(Settings.sta_pwd[0])); Settings.sta_active = index -1; restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.sta_pwd[index -1]); } else if ((CMND_HOSTNAME == command_code) && !grpflg) { if ((data_len > 0) && (data_len < sizeof(Settings.hostname))) { strlcpy(Settings.hostname, (1 == payload) ? WIFI_HOSTNAME : dataBuf, sizeof(Settings.hostname)); if (strstr(Settings.hostname,"%")) { strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname)); } restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.hostname); } else if (CMND_WIFICONFIG == command_code) { if ((payload >= WIFI_RESTART) && (payload < MAX_WIFI_OPTION)) { Settings.sta_config = payload; wifi_state_flag = Settings.sta_config; snprintf_P(stemp1, sizeof(stemp1), kWifiConfig[Settings.sta_config]); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_WIFICONFIG "\":\"%s " D_SELECTED "\"}"), stemp1); if (WifiState() != WIFI_RESTART) { // snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s after restart"), mqtt_data); restart_flag = 2; } } else { snprintf_P(stemp1, sizeof(stemp1), kWifiConfig[Settings.sta_config]); snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_SVALUE, command, Settings.sta_config, stemp1); } } else if ((CMND_FRIENDLYNAME == command_code) && (index > 0) && (index <= 4)) { if ((data_len > 0) && (data_len < sizeof(Settings.friendlyname[0]))) { if (1 == index) { snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME)); } else { snprintf_P(stemp1, sizeof(stemp1), PSTR(FRIENDLY_NAME "%d"), index); } strlcpy(Settings.friendlyname[index -1], (1 == payload) ? stemp1 : dataBuf, sizeof(Settings.friendlyname[index -1])); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_SVALUE, command, index, Settings.friendlyname[index -1]); } else if ((CMND_SWITCHMODE == command_code) && (index > 0) && (index <= MAX_SWITCHES)) { if ((payload >= 0) && (payload < MAX_SWITCH_OPTION)) { Settings.switchmode[index -1] = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_INDEX_NVALUE, command, index, Settings.switchmode[index-1]); } #ifdef USE_WEBSERVER else if (CMND_WEBSERVER == command_code) { if ((payload >= 0) && (payload <= 2)) { Settings.webserver = payload; } if (Settings.webserver) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_WEBSERVER "\":\"" D_ACTIVE_FOR " %s " D_ON_DEVICE " %s " D_WITH_IP_ADDRESS " %s\"}"), (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str()); } else { snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(0)); } } else if (CMND_WEBPASSWORD == command_code) { if ((data_len > 0) && (data_len < sizeof(Settings.web_password))) { strlcpy(Settings.web_password, (!strcmp(dataBuf,"0")) ? "" : (1 == payload) ? WEB_PASSWORD : dataBuf, sizeof(Settings.web_password)); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, Settings.web_password); } else if (CMND_WEBLOG == command_code) { if ((payload >= LOG_LEVEL_NONE) && (payload <= LOG_LEVEL_ALL)) { Settings.weblog_level = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.weblog_level); } #ifdef USE_EMULATION else if (CMND_EMULATION == command_code) { if ((payload >= EMUL_NONE) && (payload < EMUL_MAX)) { Settings.flag2.emulation = payload; restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.flag2.emulation); } #endif // USE_EMULATION #endif // USE_WEBSERVER else if (CMND_TELEPERIOD == command_code) { if ((payload >= 0) && (payload < 3601)) { Settings.tele_period = (1 == payload) ? TELE_PERIOD : payload; if ((Settings.tele_period > 0) && (Settings.tele_period < 10)) { Settings.tele_period = 10; // Do not allow periods < 10 seconds } tele_period = Settings.tele_period; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE_UNIT, command, Settings.tele_period, (Settings.flag.value_units) ? " " D_UNIT_SECOND : ""); } else if (CMND_RESTART == command_code) { switch (payload) { case 1: restart_flag = 2; snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_RESTARTING); break; case 99: AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING)); ESP.restart(); break; default: snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_ONE_TO_RESTART); } } else if (CMND_RESET == command_code) { switch (payload) { case 1: restart_flag = 211; snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command , D_RESET_AND_RESTARTING); break; case 2: restart_flag = 212; snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_RESET "\":\"" D_ERASE ", " D_RESET_AND_RESTARTING "\"}")); break; default: snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_ONE_TO_RESET); } } else if (CMND_TIMEZONE == command_code) { if ((data_len > 0) && (((payload >= -13) && (payload <= 13)) || (99 == payload))) { Settings.timezone = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.timezone); } else if (CMND_ALTITUDE == command_code) { if ((data_len > 0) && ((payload >= -30000) && (payload <= 30000))) { Settings.altitude = payload; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.altitude); } else if (CMND_LEDPOWER == command_code) { if ((payload >= 0) && (payload <= 2)) { Settings.ledstate &= 8; switch (payload) { case 0: // Off case 1: // On Settings.ledstate = payload << 3; break; case 2: // Toggle Settings.ledstate ^= 8; break; } blinks = 0; SetLedPower(Settings.ledstate &8); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, GetStateText(bitRead(Settings.ledstate, 3))); } else if (CMND_LEDSTATE ==command_code) { if ((payload >= 0) && (payload < MAX_LED_OPTION)) { Settings.ledstate = payload; if (!Settings.ledstate) { SetLedPower(0); } } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.ledstate); } else if (CMND_CFGDUMP == command_code) { SettingsDump(dataBuf); snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_DONE); } #ifdef USE_I2C else if ((CMND_I2CSCAN == command_code) && i2c_flg) { I2cScan(mqtt_data, sizeof(mqtt_data)); } #endif // USE_I2C #ifdef USE_INA219 else if (CMND_INA219MODE == command_code) { if ((payload >= 0) && (payload <= 2)) { Settings.ina219_mode = payload; restart_flag = 2; } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.ina219_mode); } #endif // USE_INA219 else if (Settings.flag.mqtt_enabled && MqttCommand(grpflg, type, index, dataBuf, data_len, payload, payload16)) { // Serviced } else if (hlw_flg && HlwCommand(type, index, dataBuf, data_len, payload)) { // Serviced } else if ((SONOFF_BRIDGE == Settings.module) && SonoffBridgeCommand(type, index, dataBuf, data_len, payload)) { // Serviced } #ifdef USE_IR_REMOTE else if ((pin[GPIO_IRSEND] < 99) && IrSendCommand(type, index, dataBuf, data_len, payload)) { // Serviced } #endif // USE_IR_REMOTE #ifdef DEBUG_THEO else if (CMND_EXCEPTION == command_code) { if (data_len > 0) { ExceptionTest(payload); } snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, D_DONE); } #endif // DEBUG_THEO else { type = NULL; } } if (type == NULL) { blinks = 201; snprintf_P(topicBuf, sizeof(topicBuf), PSTR(D_COMMAND)); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_COMMAND "\":\"" D_UNKNOWN "\"}")); type = (char*)topicBuf; } if (mqtt_data[0] != '\0') { MqttPublishPrefixTopic_P(5, type); } fallback_topic_flag = 0; } /********************************************************************************************/ boolean send_button_power(byte key, byte device, byte state) { // key 0 = button_topic // key 1 = switch_topic // state 0 = off // state 1 = on // state 2 = toggle // state 3 = hold // state 9 = clear retain flag char stopic[TOPSZ]; char scommand[CMDSZ]; char stemp1[10]; boolean result = false; char *key_topic = (key) ? Settings.switch_topic : Settings.button_topic; if (Settings.flag.mqtt_enabled && MqttClient.connected() && (strlen(key_topic) != 0) && strcmp(key_topic, "0")) { if (!key && (device > devices_present)) { device = 1; } GetTopic_P(stopic, 0, key_topic, GetPowerDevice(scommand, device, sizeof(scommand), key)); if (9 == state) { mqtt_data[0] = '\0'; } else { if ((!strcmp(Settings.mqtt_topic, key_topic) || !strcmp(Settings.mqtt_grptopic, key_topic)) && (2 == state)) { state = ~(power >> (device -1)) &1; } snprintf_P(mqtt_data, sizeof(mqtt_data), GetStateText(state)); } #ifdef USE_DOMOTICZ if (!(DomoticzButton(key, device, state, strlen(mqtt_data)))) { MqttPublishDirect(stopic, (key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain); } #else MqttPublishDirect(stopic, (key) ? Settings.flag.mqtt_switch_retain : Settings.flag.mqtt_button_retain); #endif // USE_DOMOTICZ result = true; } return result; } void ExecuteCommandPower(byte device, byte state) { // device = Relay number 1 and up // state 0 = Relay Off // state 1 = Relay On (turn off after Settings.pulse_timer * 100 mSec if enabled) // state 2 = Toggle relay // state 3 = Blink relay // state 4 = Stop blinking relay // state 6 = Relay Off and no publishPowerState // state 7 = Relay On and no publishPowerState // state 9 = Show power state uint8_t publish_power = 1; if ((6 == state) || (7 == state)) { state &= 1; publish_power = 0; } if ((device < 1) || (device > devices_present)) { device = 1; } if (device <= MAX_PULSETIMERS) { pulse_timer[(device -1)] = 0; } power_t mask = 1 << (device -1); if (state <= 2) { if ((blink_mask & mask)) { blink_mask &= (POWER_MASK ^ mask); // Clear device mask MqttPublishPowerBlinkState(device); } if (Settings.flag.interlock && !interlock_mutex) { // Clear all but masked relay interlock_mutex = 1; for (byte i = 0; i < devices_present; i++) { power_t imask = 1 << i; if ((power & imask) && (mask != imask)) { ExecuteCommandPower(i +1, 0); } } interlock_mutex = 0; } switch (state) { case 0: { // Off power &= (POWER_MASK ^ mask); break; } case 1: // On power |= mask; break; case 2: // Toggle power ^= mask; } SetDevicePower(power); #ifdef USE_DOMOTICZ DomoticzUpdatePowerState(device); #endif // USE_DOMOTICZ if (device <= MAX_PULSETIMERS) { pulse_timer[(device -1)] = (power & mask) ? Settings.pulse_timer[(device -1)] : 0; } } else if (3 == state) { // Blink if (!(blink_mask & mask)) { blink_powersave = (blink_powersave & (POWER_MASK ^ mask)) | (power & mask); // Save state blink_power = (power >> (device -1))&1; // Prep to Toggle } blink_timer = 1; blink_counter = ((!Settings.blinkcount) ? 64000 : (Settings.blinkcount *2)) +1; blink_mask |= mask; // Set device mask MqttPublishPowerBlinkState(device); return; } else if (4 == state) { // No Blink byte flag = (blink_mask & mask); blink_mask &= (POWER_MASK ^ mask); // Clear device mask MqttPublishPowerBlinkState(device); if (flag) { ExecuteCommandPower(device, (blink_powersave >> (device -1))&1); // Restore state } return; } if (publish_power) { MqttPublishPowerState(device); } } void StopAllPowerBlink() { power_t mask; for (byte i = 1; i <= devices_present; i++) { mask = 1 << (i -1); if (blink_mask & mask) { blink_mask &= (POWER_MASK ^ mask); // Clear device mask MqttPublishPowerBlinkState(i); ExecuteCommandPower(i, (blink_powersave >> (i -1))&1); // Restore state } } } void ExecuteCommand(char *cmnd) { char stopic[CMDSZ]; char svalue[INPUT_BUFFER_SIZE]; char *start; char *token; token = strtok(cmnd, " "); if (token != NULL) { start = strrchr(token, '/'); // Skip possible cmnd/sonoff/ preamble if (start) { token = start +1; } } snprintf_P(stopic, sizeof(stopic), PSTR("/%s"), (token == NULL) ? "" : token); token = strtok(NULL, ""); // snprintf_P(svalue, sizeof(svalue), (token == NULL) ? "" : token); // Fails with command FullTopic home/%prefix%/%topic% as it processes %p of %prefix% strlcpy(svalue, (token == NULL) ? "" : token, sizeof(svalue)); // Fixed 5.8.0b MqttDataCallback(stopic, (byte*)svalue, strlen(svalue)); } void PublishStatus(uint8_t payload) { uint8_t option = 1; // Workaround MQTT - TCP/IP stack queueing when SUB_PREFIX = PUB_PREFIX if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1]) && (!payload)) { option++; } if ((!Settings.flag.mqtt_enabled) && (6 == payload)) { payload = 99; } if ((!hlw_flg) && ((8 == payload) || (9 == payload))) { payload = 99; } if ((0 == payload) || (99 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS "\":{\"" D_CMND_MODULE "\":%d,\"" D_CMND_FRIENDLYNAME "\":\"%s\",\"" D_CMND_TOPIC "\":\"%s\",\"" D_CMND_BUTTONTOPIC "\":\"%s\",\"" D_CMND_POWER "\":%d,\"" D_CMND_POWERONSTATE "\":%d,\"" D_CMND_LEDSTATE "\":%d,\"" D_CMND_SAVEDATA "\":%d,\"" D_SAVESTATE "\":%d,\"" D_CMND_BUTTONRETAIN "\":%d,\"" D_CMND_POWERRETAIN "\":%d}}"), Settings.module +1, Settings.friendlyname[0], Settings.mqtt_topic, Settings.button_topic, power, Settings.poweronstate, Settings.ledstate, Settings.save_data, Settings.flag.save_state, Settings.flag.mqtt_button_retain, Settings.flag.mqtt_power_retain); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS)); } if ((0 == payload) || (1 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS1_PARAMETER "\":{\"" D_BAUDRATE "\":%d,\"" D_CMND_GROUPTOPIC "\":\"%s\",\"" D_CMND_OTAURL "\":\"%s\",\"" D_UPTIME "\":%d,\"" D_CMND_SLEEP "\":%d,\"" D_BOOTCOUNT "\":%d,\"" D_SAVECOUNT "\":%d,\"" D_SAVEADDRESS "\":\"%X\"}}"), baudrate, Settings.mqtt_grptopic, Settings.ota_url, uptime, Settings.sleep, Settings.bootcount, Settings.save_flag, GetSettingsAddress()); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "1")); } if ((0 == payload) || (2 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS2_FIRMWARE "\":{\"" D_VERSION "\":\"%s\",\"" D_BUILDDATETIME "\":\"%s\",\"" D_BOOTVERSION "\":%d,\"" D_COREVERSION "\":\"%s\",\"" D_SDKVERSION "\":\"%s\"}}"), version, GetBuildDateAndTime().c_str(), ESP.getBootVersion(), ESP.getCoreVersion().c_str(), ESP.getSdkVersion()); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "2")); } if ((0 == payload) || (3 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS3_LOGGING "\":{\"" D_CMND_SERIALLOG "\":%d,\"" D_CMND_WEBLOG "\":%d,\"" D_CMND_SYSLOG "\":%d,\"" D_CMND_LOGHOST "\":\"%s\",\"" D_CMND_LOGPORT "\":%d,\"" D_CMND_SSID "1\":\"%s\",\"" D_CMND_SSID "2\":\"%s\",\"" D_CMND_TELEPERIOD "\":%d,\"" D_CMND_SETOPTION "\":\"%08X\"}}"), Settings.seriallog_level, Settings.weblog_level, Settings.syslog_level, Settings.syslog_host, Settings.syslog_port, Settings.sta_ssid[0], Settings.sta_ssid[1], Settings.tele_period, Settings.flag.data); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "3")); } if ((0 == payload) || (4 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS4_MEMORY "\":{\"" D_PROGRAMSIZE "\":%d,\"" D_FREEMEMORY "\":%d,\"" D_HEAPSIZE "\":%d,\"" D_PROGRAMFLASHSIZE "\":%d,\"" D_FLASHSIZE "\":%d,\"" D_FLASHMODE "\":%d}}"), ESP.getSketchSize()/1024, ESP.getFreeSketchSpace()/1024, ESP.getFreeHeap()/1024, ESP.getFlashChipSize()/1024, ESP.getFlashChipRealSize()/1024, ESP.getFlashChipMode()); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "4")); } if ((0 == payload) || (5 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS5_NETWORK "\":{\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"" D_GATEWAY "\":\"%s\",\"" D_SUBNETMASK "\":\"%s\",\"" D_DNSSERVER "\":\"%s\",\"" D_MAC "\":\"%s\",\"" D_CMND_WEBSERVER "\":%d,\"" D_CMND_WIFICONFIG "\":%d}}"), my_hostname, WiFi.localIP().toString().c_str(), IPAddress(Settings.ip_address[1]).toString().c_str(), IPAddress(Settings.ip_address[2]).toString().c_str(), IPAddress(Settings.ip_address[3]).toString().c_str(), WiFi.macAddress().c_str(), Settings.webserver, Settings.sta_config); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "5")); } if (((0 == payload) || (6 == payload)) && Settings.flag.mqtt_enabled) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS6_MQTT "\":{\"" D_CMND_MQTTHOST "\":\"%s\",\"" D_CMND_MQTTPORT "\":%d,\"" D_CMND_MQTTCLIENT D_MASK "\":\"%s\",\"" D_CMND_MQTTCLIENT "\":\"%s\",\"" D_CMND_MQTTUSER "\":\"%s\",\"MAX_PACKET_SIZE\":%d,\"KEEPALIVE\":%d}}"), Settings.mqtt_host, Settings.mqtt_port, Settings.mqtt_client, mqtt_client, Settings.mqtt_user, MQTT_MAX_PACKET_SIZE, MQTT_KEEPALIVE); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "6")); } if ((0 == payload) || (7 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_UTC_TIME "\":\"%s\",\"" D_LOCAL_TIME "\":\"%s\",\"" D_STARTDST "\":\"%s\",\"" D_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%d}}"), GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(), GetTime(3).c_str(), Settings.timezone); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "7")); } if (hlw_flg) { if ((0 == payload) || (8 == payload)) { HlwMqttStatus(); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "8")); } if ((0 == payload) || (9 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS9_MARGIN "\":{\"" D_CMND_POWERLOW "\":%d,\"" D_CMND_POWERHIGH "\":%d,\"" D_CMND_VOLTAGELOW "\":%d,\"" D_CMND_VOLTAGEHIGH "\":%d,\"" D_CMND_CURRENTLOW "\":%d,\"" D_CMND_CURRENTHIGH "\":%d}}"), Settings.hlw_pmin, Settings.hlw_pmax, Settings.hlw_umin, Settings.hlw_umax, Settings.hlw_imin, Settings.hlw_imax); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "9")); } } if ((0 == payload) || (10 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS10_SENSOR "\":")); MqttShowSensor(); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "10")); } if ((0 == payload) || (11 == payload)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS11_STATUS "\":")); MqttShowState(); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "11")); } } void MqttShowState() { char stemp1[16]; snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s{\"" D_TIME "\":\"%s\",\"" D_UPTIME "\":%d"), mqtt_data, GetDateAndTime().c_str(), uptime); #ifdef USE_ADC_VCC dtostrfd((double)ESP.getVcc()/1000, 3, stemp1); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_VCC "\":%s"), mqtt_data, stemp1); #endif for (byte i = 0; i < devices_present; i++) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"%s\":\"%s\""), mqtt_data, GetPowerDevice(stemp1, i +1, sizeof(stemp1)), GetStateText(bitRead(power, i))); } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_WIFI "\":{\"" D_AP "\":%d,\"" D_SSID "\":\"%s\",\"" D_RSSI "\":%d,\"" D_APMAC_ADDRESS "\":\"%s\"}}"), mqtt_data, Settings.sta_active +1, Settings.sta_ssid[Settings.sta_active], WifiGetRssiAsQuality(WiFi.RSSI()), WiFi.BSSIDstr().c_str()); } boolean MqttShowSensor() { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s{\"" D_TIME "\":\"%s\""), mqtt_data, GetDateAndTime().c_str()); int json_data_start = strlen(mqtt_data); for (byte i = 0; i < MAX_SWITCHES; i++) { if (pin[GPIO_SWT1 +i] < 99) { boolean swm = ((FOLLOW_INV == Settings.switchmode[i]) || (PUSHBUTTON_INV == Settings.switchmode[i]) || (PUSHBUTTONHOLD_INV == Settings.switchmode[i])); snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_SWITCH "%d\":\"%s\""), mqtt_data, i +1, GetStateText(swm ^ lastwallswitch[i])); } } XsnsCall(FUNC_XSNS_JSON_APPEND); boolean json_data_available = (strlen(mqtt_data) - json_data_start); if (strstr_P(mqtt_data, PSTR(D_TEMPERATURE))) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s,\"" D_TEMPERATURE_UNIT "\":\"%c\""), mqtt_data, TempUnit()); } snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}"), mqtt_data); return json_data_available; } /********************************************************************************************/ void PerformEverySecond() { if (blockgpio0) { blockgpio0--; } for (byte i = 0; i < MAX_PULSETIMERS; i++) { if (pulse_timer[i] > 111) { pulse_timer[i]--; } } if (seriallog_timer) { seriallog_timer--; if (!seriallog_timer) { if (seriallog_level) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SERIAL_LOGGING_DISABLED)); } seriallog_level = 0; } } if (syslog_timer) { // Restore syslog level syslog_timer--; if (!syslog_timer) { syslog_level = (Settings.flag2.emulation) ? 0 : Settings.syslog_level; if (Settings.syslog_level) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_SYSLOG_LOGGING_REENABLED)); // Might trigger disable again (on purpose) } } } #ifdef USE_DOMOTICZ DomoticzMqttUpdate(); #endif // USE_DOMOTICZ if (status_update_timer) { status_update_timer--; if (!status_update_timer) { for (byte i = 1; i <= devices_present; i++) { MqttPublishPowerState(i); } } } if (Settings.tele_period) { tele_period++; if (tele_period == Settings.tele_period -1) { XsnsCall(FUNC_XSNS_PREP); } if (tele_period >= Settings.tele_period) { tele_period = 0; mqtt_data[0] = '\0'; MqttShowState(); MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_STATE)); mqtt_data[0] = '\0'; if (MqttShowSensor()) { MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_SENSOR), Settings.flag.mqtt_sensor_retain); } XsnsCall(FUNC_XSNS_MQTT_SHOW); } } if (hlw_flg) { HlwMarginCheck(); } if ((2 == RtcTime.minute) && latest_uptime_flag) { latest_uptime_flag = false; uptime++; snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_TIME "\":\"%s\",\"" D_UPTIME "\":%d}"), GetDateAndTime().c_str(), uptime); MqttPublishPrefixTopic_P(2, PSTR(D_RSLT_UPTIME)); } if ((3 == RtcTime.minute) && !latest_uptime_flag) { latest_uptime_flag = true; } } /*********************************************************************************************\ * Button handler with single press only or multi-press and hold on all buttons \*********************************************************************************************/ void ButtonHandler() { uint8_t button = NOT_PRESSED; uint8_t button_present = 0; char scmnd[20]; uint8_t maxdev = (devices_present > MAX_KEYS) ? MAX_KEYS : devices_present; for (byte i = 0; i < maxdev; i++) { button = NOT_PRESSED; button_present = 0; if (!i && ((SONOFF_DUAL == Settings.module) || (CH4 == Settings.module))) { button_present = 1; if (dual_button_code) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_BUTTON " " D_CODE " %04X"), dual_button_code); AddLog(LOG_LEVEL_DEBUG); button = PRESSED; if (0xF500 == dual_button_code) { // Button hold holdbutton[i] = (Settings.param[P_HOLD_TIME] * (STATES / 10)) -1; } dual_button_code = 0; } } else { if ((pin[GPIO_KEY1 +i] < 99) && !blockgpio0) { button_present = 1; button = digitalRead(pin[GPIO_KEY1 +i]); } } if (button_present) { if (SONOFF_4CHPRO == Settings.module) { if (holdbutton[i]) { holdbutton[i]--; } boolean button_pressed = false; if ((PRESSED == button) && (NOT_PRESSED == lastbutton[i])) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_BUTTON " %d " D_LEVEL_10), i +1); AddLog(LOG_LEVEL_DEBUG); holdbutton[i] = STATES; button_pressed = true; } if ((NOT_PRESSED == button) && (PRESSED == lastbutton[i])) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_BUTTON " %d " D_LEVEL_01), i +1); AddLog(LOG_LEVEL_DEBUG); if (!holdbutton[i]) { // Do not allow within 1 second button_pressed = true; } } if (button_pressed) { if (!send_button_power(0, i +1, 2)) { // Execute Toggle command via MQTT if ButtonTopic is set ExecuteCommandPower(i +1, 2); // Execute Toggle command internally } } } else { if ((PRESSED == button) && (NOT_PRESSED == lastbutton[i])) { if (Settings.flag.button_single) { // Allow only single button press for immediate action snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_BUTTON " %d " D_IMMEDIATE), i +1); AddLog(LOG_LEVEL_DEBUG); if (!send_button_power(0, i +1, 2)) { // Execute Toggle command via MQTT if ButtonTopic is set ExecuteCommandPower(i +1, 2); // Execute Toggle command internally } } else { multipress[i] = (multiwindow[i]) ? multipress[i] +1 : 1; snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_BUTTON " %d " D_MULTI_PRESS " %d"), i +1, multipress[i]); AddLog(LOG_LEVEL_DEBUG); multiwindow[i] = STATES /2; // 0.5 second multi press window } blinks = 201; } if (NOT_PRESSED == button) { holdbutton[i] = 0; } else { holdbutton[i]++; if (Settings.flag.button_single) { // Allow only single button press for immediate action if (holdbutton[i] == Settings.param[P_HOLD_TIME] * (STATES / 10) * 4) { // Button hold for four times longer // Settings.flag.button_single = 0; snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_SETOPTION "13 0")); // Disable single press only ExecuteCommand(scmnd); } } else { if (holdbutton[i] == Settings.param[P_HOLD_TIME] * (STATES / 10)) { // Button hold multipress[i] = 0; if (!Settings.flag.button_restrict) { // No button restriction snprintf_P(scmnd, sizeof(scmnd), PSTR(D_CMND_RESET " 1")); ExecuteCommand(scmnd); } else { send_button_power(0, i +1, 3); // Execute Hold command via MQTT if ButtonTopic is set } } } } if (!Settings.flag.button_single) { // Allow multi-press if (multiwindow[i]) { multiwindow[i]--; } else { if (!restart_flag && !holdbutton[i] && (multipress[i] > 0) && (multipress[i] < MAX_BUTTON_COMMANDS +3)) { boolean single_press = false; if (multipress[i] < 3) { // Single or Double press if ((SONOFF_DUAL == Settings.module) || (CH4 == Settings.module)) { single_press = true; } else { single_press = (Settings.flag.button_swap +1 == multipress[i]); multipress[i] = 1; } } if (single_press && send_button_power(0, i + multipress[i], 2)) { // Execute Toggle command via MQTT if ButtonTopic is set // Success } else { if (multipress[i] < 3) { // Single or Double press if (WifiState()) { // WPSconfig, Smartconfig or Wifimanager active restart_flag = 1; } else { ExecuteCommandPower(i + multipress[i], 2); // Execute Toggle command internally } } else { // 3 - 7 press if (!Settings.flag.button_restrict) { snprintf_P(scmnd, sizeof(scmnd), kCommands[multipress[i] -3]); ExecuteCommand(scmnd); } } } multipress[i] = 0; } } } } } lastbutton[i] = button; } } /*********************************************************************************************\ * Switch handler \*********************************************************************************************/ void SwitchHandler() { uint8_t button = NOT_PRESSED; uint8_t switchflag; for (byte i = 0; i < MAX_SWITCHES; i++) { if (pin[GPIO_SWT1 +i] < 99) { if (holdwallswitch[i]) { holdwallswitch[i]--; if (0 == holdwallswitch[i]) { send_button_power(1, i +1, 3); // Execute command via MQTT } } button = digitalRead(pin[GPIO_SWT1 +i]); if (button != lastwallswitch[i]) { switchflag = 3; switch (Settings.switchmode[i]) { case TOGGLE: switchflag = 2; // Toggle break; case FOLLOW: switchflag = button &1; // Follow wall switch state break; case FOLLOW_INV: switchflag = ~button &1; // Follow inverted wall switch state break; case PUSHBUTTON: if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i])) { switchflag = 2; // Toggle with pushbutton to Gnd } break; case PUSHBUTTON_INV: if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i])) { switchflag = 2; // Toggle with releasing pushbutton from Gnd } break; case PUSHBUTTONHOLD: if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i])) { holdwallswitch[i] = Settings.param[P_HOLD_TIME] * (STATES / 10); } if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i]) && (holdwallswitch[i])) { holdwallswitch[i] = 0; switchflag = 2; // Toggle with pushbutton to Gnd } break; case PUSHBUTTONHOLD_INV: if ((NOT_PRESSED == button) && (PRESSED == lastwallswitch[i])) { holdwallswitch[i] = Settings.param[P_HOLD_TIME] * (STATES / 10); } if ((PRESSED == button) && (NOT_PRESSED == lastwallswitch[i]) && (holdwallswitch[i])) { holdwallswitch[i] = 0; switchflag = 2; // Toggle with pushbutton to Gnd } break; } if (switchflag < 3) { if (!send_button_power(1, i +1, switchflag)) { // Execute command via MQTT ExecuteCommandPower(i +1, switchflag); // Execute command internally (if i < devices_present) } } lastwallswitch[i] = button; } } } } /*********************************************************************************************\ * State loop \*********************************************************************************************/ void StateLoop() { power_t power_now; state_loop_timer = millis() + (1000 / STATES); state++; /*-------------------------------------------------------------------------------------------*\ * Every second \*-------------------------------------------------------------------------------------------*/ if (STATES == state) { state = 0; PerformEverySecond(); } /*-------------------------------------------------------------------------------------------*\ * Every 0.1 second \*-------------------------------------------------------------------------------------------*/ if (!(state % (STATES/10))) { if (mqtt_cmnd_publish) { mqtt_cmnd_publish--; // Clean up } if (latching_relay_pulse) { latching_relay_pulse--; if (!latching_relay_pulse) { SetLatchingRelay(0, 0); } } for (byte i = 0; i < MAX_PULSETIMERS; i++) { if ((pulse_timer[i] > 0) && (pulse_timer[i] < 112)) { pulse_timer[i]--; if (!pulse_timer[i]) { ExecuteCommandPower(i +1, 0); } } } if (blink_mask) { blink_timer--; if (!blink_timer) { blink_timer = Settings.blinktime; blink_counter--; if (!blink_counter) { StopAllPowerBlink(); } else { blink_power ^= 1; power_now = (power & (POWER_MASK ^ blink_mask)) | ((blink_power) ? blink_mask : 0); SetDevicePower(power_now); } } } // Backlog if (backlog_delay) { backlog_delay--; } if ((backlog_pointer != backlog_index) && !backlog_delay && !backlog_mutex) { backlog_mutex = 1; ExecuteCommand((char*)backlog[backlog_pointer].c_str()); backlog_mutex = 0; backlog_pointer++; /* if (backlog_pointer >= MAX_BACKLOG) { backlog_pointer = 0; } */ backlog_pointer &= 0xF; } } #ifdef USE_IR_REMOTE #ifdef USE_IR_RECEIVE if (pin[GPIO_IRRECV] < 99) { IrReceiveCheck(); // check if there's anything on IR side } #endif // USE_IR_RECEIVE #endif // USE_IR_REMOTE /*-------------------------------------------------------------------------------------------*\ * Every 0.05 second \*-------------------------------------------------------------------------------------------*/ ButtonHandler(); SwitchHandler(); if (light_type) { LightAnimate(); } /*-------------------------------------------------------------------------------------------*\ * Every 0.2 second \*-------------------------------------------------------------------------------------------*/ if (!(state % ((STATES/10)*2))) { if (blinks || restart_flag || ota_state_flag) { if (restart_flag || ota_state_flag) { blinkstate = 1; // Stay lit } else { blinkstate ^= 1; // Blink } if ((!(Settings.ledstate &0x08)) && ((Settings.ledstate &0x06) || (blinks > 200) || (blinkstate))) { SetLedPower(blinkstate); } if (!blinkstate) { blinks--; if (200 == blinks) { blinks = 0; } } } else { if (Settings.ledstate &1) { boolean tstate = power; if ((SONOFF_TOUCH == Settings.module) || (SONOFF_T11 == Settings.module) || (SONOFF_T12 == Settings.module) || (SONOFF_T13 == Settings.module)) { tstate = (!power) ? 1 : 0; } SetLedPower(tstate); } } } /*-------------------------------------------------------------------------------------------*\ * Every second at 0.2 second interval \*-------------------------------------------------------------------------------------------*/ switch (state) { case (STATES/10)*2: if (ota_state_flag && (backlog_pointer == backlog_index)) { ota_state_flag--; if (2 == ota_state_flag) { ota_retry_counter = OTA_ATTEMPTS; ESPhttpUpdate.rebootOnUpdate(false); SettingsSave(1); // Free flash for OTA update } if (ota_state_flag <= 0) { #ifdef USE_WEBSERVER if (Settings.webserver) { StopWebserver(); } #endif // USE_WEBSERVER ota_state_flag = 92; ota_result = 0; ota_retry_counter--; if (ota_retry_counter) { // snprintf_P(log_data, sizeof(log_data), PSTR("OTA: Attempt %d"), OTA_ATTEMPTS - ota_retry_counter); // AddLog(LOG_LEVEL_INFO); ota_result = (HTTP_UPDATE_FAILED != ESPhttpUpdate.update(Settings.ota_url)); if (!ota_result) { ota_state_flag = 2; } } } if (90 == ota_state_flag) { // Allow MQTT to reconnect ota_state_flag = 0; if (ota_result) { SetFlashModeDout(); // Force DOUT for both ESP8266 and ESP8285 snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(D_SUCCESSFUL ". " D_RESTARTING)); } else { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR(D_FAILED " %s"), ESPhttpUpdate.getLastErrorString().c_str()); } restart_flag = 2; // Restart anyway to keep memory clean webserver MqttPublishPrefixTopic_P(1, PSTR(D_CMND_UPGRADE)); } } break; case (STATES/10)*4: if (MidnightNow()) { CounterSaveState(); } if (save_data_counter && (backlog_pointer == backlog_index)) { save_data_counter--; if (save_data_counter <= 0) { if (Settings.flag.save_state) { power_t mask = POWER_MASK; for (byte i = 0; i < MAX_PULSETIMERS; i++) { if ((Settings.pulse_timer[i] > 0) && (Settings.pulse_timer[i] < 30)) { // 3 seconds mask &= ~(1 << i); } } if (!((Settings.power &mask) == (power &mask))) { Settings.power = power; } } else { Settings.power = 0; } SettingsSave(0); save_data_counter = Settings.save_data; } } if (restart_flag && (backlog_pointer == backlog_index)) { if (211 == restart_flag) { SettingsDefault(); restart_flag = 2; } if (212 == restart_flag) { SettingsErase(); SettingsDefault(); restart_flag = 2; } if (Settings.flag.save_state) { Settings.power = power; } else { Settings.power = 0; } if (hlw_flg) { HlwSaveState(); } CounterSaveState(); SettingsSave(0); restart_flag--; if (restart_flag <= 0) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION D_RESTARTING)); ESP.restart(); } } break; case (STATES/10)*6: WifiCheck(wifi_state_flag); wifi_state_flag = WIFI_RESTART; break; case (STATES/10)*8: if (WL_CONNECTED == WiFi.status()) { if (Settings.flag.mqtt_enabled) { if (!MqttClient.connected()) { if (!mqtt_retry_counter) { MqttReconnect(); } else { mqtt_retry_counter--; } } } else { if (!mqtt_retry_counter) { MqttReconnect(); } } } break; } } /********************************************************************************************/ void SerialInput() { while (Serial.available()) { yield(); serial_in_byte = Serial.read(); /*-------------------------------------------------------------------------------------------*\ * Sonoff dual 19200 baud serial interface \*-------------------------------------------------------------------------------------------*/ if (dual_hex_code) { dual_hex_code--; if (dual_hex_code) { dual_button_code = (dual_button_code << 8) | serial_in_byte; serial_in_byte = 0; } else { if (serial_in_byte != 0xA1) { dual_button_code = 0; // 0xA1 - End of Sonoff dual button code } } } if (0xA0 == serial_in_byte) { // 0xA0 - Start of Sonoff dual button code serial_in_byte = 0; dual_button_code = 0; dual_hex_code = 3; } /*-------------------------------------------------------------------------------------------*\ * Sonoff bridge 19200 baud serial interface \*-------------------------------------------------------------------------------------------*/ if (SonoffBridgeSerialInput()) { serial_in_byte_counter = 0; Serial.flush(); return; } /*-------------------------------------------------------------------------------------------*/ if (serial_in_byte > 127) { // binary data... serial_in_byte_counter = 0; Serial.flush(); return; } if (isprint(serial_in_byte)) { if (serial_in_byte_counter < INPUT_BUFFER_SIZE) { // add char to string if it still fits serial_in_buffer[serial_in_byte_counter++] = serial_in_byte; } else { serial_in_byte_counter = 0; } } /*-------------------------------------------------------------------------------------------*\ * Sonoff SC 19200 baud serial interface \*-------------------------------------------------------------------------------------------*/ if (serial_in_byte == '\x1B') { // Sonoff SC status from ATMEGA328P serial_in_buffer[serial_in_byte_counter] = 0; // serial data completed SonoffScSerialInput(serial_in_buffer); serial_in_byte_counter = 0; Serial.flush(); return; } /*-------------------------------------------------------------------------------------------*/ else if (serial_in_byte == '\n') { serial_in_buffer[serial_in_byte_counter] = 0; // serial data completed seriallog_level = (Settings.seriallog_level < LOG_LEVEL_INFO) ? LOG_LEVEL_INFO : Settings.seriallog_level; snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_COMMAND "%s"), serial_in_buffer); AddLog(LOG_LEVEL_INFO); ExecuteCommand(serial_in_buffer); serial_in_byte_counter = 0; Serial.flush(); return; } } } /********************************************************************************************/ void GpioInit() { uint8_t mpin; mytmplt def_module; if (!Settings.module || (Settings.module >= MAXMODULE)) { Settings.module = MODULE; Settings.last_module = MODULE; } memcpy_P(&def_module, &kModules[Settings.module], sizeof(def_module)); strlcpy(my_module.name, def_module.name, sizeof(my_module.name)); for (byte i = 0; i < MAX_GPIO_PIN; i++) { if (Settings.my_gp.io[i] > GPIO_NONE) { my_module.gp.io[i] = Settings.my_gp.io[i]; } if ((def_module.gp.io[i] > GPIO_NONE) && (def_module.gp.io[i] < GPIO_USER)) { my_module.gp.io[i] = def_module.gp.io[i]; } } for (byte i = 0; i < GPIO_MAX; i++) { pin[i] = 99; } for (byte i = 0; i < MAX_GPIO_PIN; i++) { mpin = my_module.gp.io[i]; // snprintf_P(log_data, sizeof(log_data), PSTR("DBG: gpio pin %d, mpin %d"), i, mpin); // AddLog(LOG_LEVEL_DEBUG); if (mpin) { if ((mpin >= GPIO_REL1_INV) && (mpin < (GPIO_REL1_INV + MAX_RELAYS))) { bitSet(rel_inverted, mpin - GPIO_REL1_INV); mpin -= (GPIO_REL1_INV - GPIO_REL1); } else if ((mpin >= GPIO_LED1_INV) && (mpin < (GPIO_LED1_INV + MAX_LEDS))) { bitSet(led_inverted, mpin - GPIO_LED1_INV); mpin -= (GPIO_LED1_INV - GPIO_LED1); } else if ((mpin >= GPIO_PWM1_INV) && (mpin < (GPIO_PWM1_INV + MAX_PWMS))) { bitSet(pwm_inverted, mpin - GPIO_PWM1_INV); mpin -= (GPIO_PWM1_INV - GPIO_PWM1); } #ifdef USE_DHT else if ((mpin >= GPIO_DHT11) && (mpin <= GPIO_DHT22)) { if (DhtSetup(i, mpin)) { dht_flg = 1; mpin = GPIO_DHT11; } else { mpin = 0; } } #endif // USE_DHT } if (mpin) { pin[mpin] = i; } } if (2 == pin[GPIO_TXD]) { Serial.set_tx(2); } analogWriteRange(Settings.pwm_range); // Default is 1023 (Arduino.h) analogWriteFreq(Settings.pwm_frequency); // Default is 1000 (core_esp8266_wiring_pwm.c) #ifdef USE_I2C i2c_flg = ((pin[GPIO_I2C_SCL] < 99) && (pin[GPIO_I2C_SDA] < 99)); if (i2c_flg) { Wire.begin(pin[GPIO_I2C_SDA], pin[GPIO_I2C_SCL]); } #endif // USE_I2C devices_present = 1; if (Settings.flag.pwm_control) { light_type = LT_BASIC; for (byte i = 0; i < MAX_PWMS; i++) { if (pin[GPIO_PWM1 +i] < 99) { light_type++; // Use Dimmer/Color control for all PWM as SetOption15 = 1 } } } if (SONOFF_BRIDGE == Settings.module) { baudrate = 19200; } if (SONOFF_DUAL == Settings.module) { devices_present = 2; baudrate = 19200; } else if (CH4 == Settings.module) { devices_present = 4; baudrate = 19200; } else if (SONOFF_SC == Settings.module) { devices_present = 0; baudrate = 19200; } else if ((H801 == Settings.module) || (MAGICHOME == Settings.module) || (ARILUX_LC01 == Settings.module) || (ARILUX_LC11 == Settings.module)) { // PWM RGBCW led if (!Settings.flag.pwm_control) { light_type = LT_BASIC; // Use basic PWM control if SetOption15 = 0 } } else if (SONOFF_BN == Settings.module) { // PWM Single color led (White) light_type = LT_PWM1; } else if (SONOFF_LED == Settings.module) { // PWM Dual color led (White warm and cold) light_type = LT_PWM2; } else if (AILIGHT == Settings.module) { // RGBW led light_type = LT_RGBW; } else if (SONOFF_B1 == Settings.module) { // RGBWC led light_type = LT_RGBWC; } else { if (!light_type) { devices_present = 0; } for (byte i = 0; i < MAX_RELAYS; i++) { if (pin[GPIO_REL1 +i] < 99) { pinMode(pin[GPIO_REL1 +i], OUTPUT); devices_present++; } } } for (byte i = 0; i < MAX_KEYS; i++) { if (pin[GPIO_KEY1 +i] < 99) { pinMode(pin[GPIO_KEY1 +i], (16 == pin[GPIO_KEY1 +i]) ? INPUT_PULLDOWN_16 : INPUT_PULLUP); } } for (byte i = 0; i < MAX_LEDS; i++) { if (pin[GPIO_LED1 +i] < 99) { pinMode(pin[GPIO_LED1 +i], OUTPUT); digitalWrite(pin[GPIO_LED1 +i], bitRead(led_inverted, i)); } } for (byte i = 0; i < MAX_SWITCHES; i++) { if (pin[GPIO_SWT1 +i] < 99) { pinMode(pin[GPIO_SWT1 +i], (16 == pin[GPIO_SWT1 +i]) ? INPUT_PULLDOWN_16 :INPUT_PULLUP); lastwallswitch[i] = digitalRead(pin[GPIO_SWT1 +i]); // set global now so doesn't change the saved power state on first switch check } } #ifdef USE_WS2812 if (!light_type && (pin[GPIO_WS2812] < 99)) { // RGB led devices_present++; light_type = LT_WS2812; } #endif // USE_WS2812 if (light_type) { // Any Led light under Dimmer/Color control LightInit(); } else { for (byte i = 0; i < MAX_PWMS; i++) { if (pin[GPIO_PWM1 +i] < 99) { pinMode(pin[GPIO_PWM1 +i], OUTPUT); analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - Settings.pwm_value[i] : Settings.pwm_value[i]); } } } if (EXS_RELAY == Settings.module) { SetLatchingRelay(0,2); SetLatchingRelay(1,2); } SetLedPower(Settings.ledstate &8); #ifdef USE_IR_REMOTE if (pin[GPIO_IRSEND] < 99) { IrSendInit(); } #ifdef USE_IR_RECEIVE if (pin[GPIO_IRRECV] < 99) { IrReceiveInit(); } #endif // USE_IR_RECEIVE #endif // USE_IR_REMOTE hlw_flg = ((pin[GPIO_HLW_SEL] < 99) && (pin[GPIO_HLW_CF1] < 99) && (pin[GPIO_HLW_CF] < 99)); } extern "C" { extern struct rst_info resetInfo; } void setup() { byte idx; Serial.begin(baudrate); delay(10); Serial.println(); seriallog_level = LOG_LEVEL_INFO; // Allow specific serial messages until config loaded snprintf_P(version, sizeof(version), PSTR("%d.%d.%d"), VERSION >> 24 & 0xff, VERSION >> 16 & 0xff, VERSION >> 8 & 0xff); if (VERSION & 0x1f) { idx = strlen(version); version[idx] = 96 + (VERSION & 0x1f); version[idx +1] = 0; } SettingsLoad(); SettingsDelta(); OsWatchInit(); seriallog_level = Settings.seriallog_level; seriallog_timer = SERIALLOG_TIMER; #ifndef USE_EMULATION Settings.flag2.emulation = 0; #endif // USE_EMULATION syslog_level = (Settings.flag2.emulation) ? 0 : Settings.syslog_level; stop_flash_rotate = Settings.flag.stop_flash_rotate; save_data_counter = Settings.save_data; sleep = Settings.sleep; Settings.bootcount++; snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_BOOT_COUNT " %d"), Settings.bootcount); AddLog(LOG_LEVEL_DEBUG); GpioInit(); if (Serial.baudRate() != baudrate) { if (seriallog_level) { snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_APPLICATION D_SET_BAUDRATE_TO " %d"), baudrate); AddLog(LOG_LEVEL_INFO); } delay(100); Serial.flush(); Serial.begin(baudrate); delay(10); Serial.println(); } if (strstr(Settings.hostname, "%")) { strlcpy(Settings.hostname, WIFI_HOSTNAME, sizeof(Settings.hostname)); snprintf_P(my_hostname, sizeof(my_hostname)-1, Settings.hostname, Settings.mqtt_topic, ESP.getChipId() & 0x1FFF); } else { snprintf_P(my_hostname, sizeof(my_hostname)-1, Settings.hostname); } WifiConnect(); GetMqttClient(mqtt_client, Settings.mqtt_client, sizeof(mqtt_client)); if (MOTOR == Settings.module) { Settings.poweronstate = 1; // Needs always on else in limbo! } if (4 == Settings.poweronstate) { // Allways on SetDevicePower(1); } else { if ((resetInfo.reason == REASON_DEFAULT_RST) || (resetInfo.reason == REASON_EXT_SYS_RST)) { switch (Settings.poweronstate) { case 0: // All off power = 0; SetDevicePower(power); break; case 1: // All on power = (1 << devices_present) -1; SetDevicePower(power); break; case 2: // All saved state toggle power = Settings.power & ((1 << devices_present) -1) ^ POWER_MASK; if (Settings.flag.save_state) { SetDevicePower(power); } break; case 3: // All saved state power = Settings.power & ((1 << devices_present) -1); if (Settings.flag.save_state) { SetDevicePower(power); } break; } } else { power = Settings.power & ((1 << devices_present) -1); if (Settings.flag.save_state) { SetDevicePower(power); } } } // Issue #526 and #909 for (byte i = 0; i < devices_present; i++) { if ((i < MAX_RELAYS) && (pin[GPIO_REL1 +i] < 99)) { bitWrite(power, i, digitalRead(pin[GPIO_REL1 +i]) ^ bitRead(rel_inverted, i)); } if ((i < MAX_PULSETIMERS) && bitRead(power, i)) { pulse_timer[i] = Settings.pulse_timer[i]; } } blink_powersave = power; XSnsInit(); RtcInit(); snprintf_P(log_data, sizeof(log_data), PSTR(D_PROJECT " %s %s (" D_CMND_TOPIC " %s, " D_FALLBACK " %s, " D_CMND_GROUPTOPIC " %s) " D_VERSION " %s"), PROJECT, Settings.friendlyname[0], Settings.mqtt_topic, mqtt_client, Settings.mqtt_grptopic, version); AddLog(LOG_LEVEL_INFO); } void loop() { OsWatchLoop(); #ifdef USE_WEBSERVER PollDnsWebserver(); #endif // USE_WEBSERVER #ifdef USE_EMULATION if (Settings.flag2.emulation) { PollUdp(); } #endif // USE_EMULATION #ifdef USE_ARILUX_RF if (pin[GPIO_ALIRFRCV] < 99) { AriluxRfHandler(); } #endif // USE_ARILUX_RF if (millis() >= state_loop_timer) { StateLoop(); } if (Settings.flag.mqtt_enabled) { MqttClient.loop(); } if (Serial.available()){ SerialInput(); } // yield(); // yield == delay(0), delay contains yield, auto yield in loop delay(sleep); // https://github.com/esp8266/Arduino/issues/2021 }