" D_UPLOAD " " D_FAILED " "));
WSContentSend_P(PSTR("%06x'>" D_FAILED "
"), WebColor(COL_TEXT_WARNING));
#ifdef USE_RF_FLASH
- if (Web.upload_error < 14) {
+ if (Web.upload_error < 15) {
#else
- if (Web.upload_error < 10) {
+ if ((Web.upload_error < 10) || (14 == Web.upload_error)) {
+ if (14 == Web.upload_error) { Web.upload_error = 10; }
#endif
GetTextIndexed(error, sizeof(error), Web.upload_error -1, kUploadErrors);
} else {
@@ -2211,7 +2331,7 @@ void HandleUploadLoop(void)
Update.end(); // End esp8266 update session
Web.upload_file_type = UPL_EFM8BB1;
- Web.upload_error = SnfBrUpdateInit();
+ Web.upload_error = SnfBrUpdateInit(); // 10, 11
if (Web.upload_error != 0) { return; }
} else
#endif // USE_RF_FLASH
@@ -2219,21 +2339,23 @@ void HandleUploadLoop(void)
if ((WEMOS == my_module_type) && (upload.buf[0] == ':')) { // Check if this is a ARDUINO SLAVE hex file
Update.end(); // End esp8266 update session
Web.upload_file_type = UPL_TASMOTASLAVE;
- Web.upload_error = TasmotaSlave_UpdateInit();
+ Web.upload_error = TasmotaSlave_UpdateInit(); // 0
if (Web.upload_error != 0) { return; }
} else
#endif
{
- if (upload.buf[0] != 0xE9) {
+ if ((upload.buf[0] != 0xE9) && (upload.buf[0] != 0x1F)) { // 0x1F is gzipped 0xE9
Web.upload_error = 3; // Magic byte is not 0xE9
return;
}
- uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
- if(bin_flash_size > ESP.getFlashChipRealSize()) {
- Web.upload_error = 4; // Program flash size is larger than real flash size
- return;
+ if (0xE9 == upload.buf[0]) {
+ uint32_t bin_flash_size = ESP.magicFlashChipSize((upload.buf[3] & 0xf0) >> 4);
+ if (bin_flash_size > ESP.getFlashChipRealSize()) {
+ Web.upload_error = 4; // Program flash size is larger than real flash size
+ return;
+ }
+// upload.buf[2] = 3; // Force DOUT - ESP8285
}
-// upload.buf[2] = 3; // Force DOUT - ESP8285
}
}
}
@@ -2254,13 +2376,13 @@ void HandleUploadLoop(void)
free(efm8bb1_update);
efm8bb1_update = nullptr;
if (result != 0) {
- Web.upload_error = abs(result); // 2 = Not enough space, 8 = File invalid
+ Web.upload_error = abs(result); // 2 = Not enough space, 8 = File invalid, 12, 13
return;
}
}
ssize_t result = rf_search_and_write(upload.buf, upload.currentSize);
if (result < 0) {
- Web.upload_error = abs(result);
+ Web.upload_error = abs(result); // 8, 12, 13
return;
} else if (result > 0) {
if ((size_t)result > upload.currentSize) {
@@ -2350,6 +2472,10 @@ void HandleUploadLoop(void)
Web.upload_error = 6; // Upload failed. Enable logging 3
return;
}
+ if (!VersionCompatible()) {
+ Web.upload_error = 14; // Not compatible
+ return;
+ }
}
if (!Web.upload_error) {
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_UPLOAD D_SUCCESSFUL " %u bytes. " D_RESTARTING), upload.totalSize);
@@ -2382,12 +2508,12 @@ void HandleHttpCommand(void)
AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_COMMAND));
bool valid = true;
- if (Settings.web_password[0] != 0) {
- char tmp1[sizeof(Settings.web_password)];
+ if (strlen(SettingsText(SET_WEBPWD))) {
+ char tmp1[33];
WebGetArg("user", tmp1, sizeof(tmp1));
- char tmp2[sizeof(Settings.web_password)];
+ char tmp2[strlen(SettingsText(SET_WEBPWD)) +1];
WebGetArg("password", tmp2, sizeof(tmp2));
- if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, Settings.web_password))) { valid = false; }
+ if (!(!strcmp(tmp1, WEB_USERNAME) && !strcmp(tmp2, SettingsText(SET_WEBPWD)))) { valid = false; }
}
WSContentBegin(200, CT_JSON);
@@ -2605,7 +2731,7 @@ int WebSend(char *buffer)
if (user) {
user = strtok_r(user, ":", &password); // user = |admin|, password = |joker|
if (user && password) {
- char userpass[128];
+ char userpass[200];
snprintf_P(userpass, sizeof(userpass), PSTR("user=%s&password=%s&"), user, password);
url += userpass; // url = |http://192.168.178.86/cm?user=admin&password=joker&|
}
@@ -2698,7 +2824,8 @@ const char kWebCommands[] PROGMEM = "|" // No prefix
#ifdef USE_SENDMAIL
D_CMND_SENDMAIL "|"
#endif
- D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|" D_CMND_WEBSENSOR;
+ D_CMND_WEBSERVER "|" D_CMND_WEBPASSWORD "|" D_CMND_WEBLOG "|" D_CMND_WEBREFRESH "|" D_CMND_WEBSEND "|" D_CMND_WEBCOLOR "|"
+ D_CMND_WEBSENSOR "|" D_CMND_WEBBUTTON "|" D_CMND_CORS;
void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_EMULATION
@@ -2707,7 +2834,8 @@ void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_SENDMAIL
&CmndSendmail,
#endif
- &CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor, &CmndWebSensor };
+ &CmndWebServer, &CmndWebPassword, &CmndWeblog, &CmndWebRefresh, &CmndWebSend, &CmndWebColor,
+ &CmndWebSensor, &CmndWebButton, &CmndCors };
/*********************************************************************************************\
* Commands
@@ -2716,6 +2844,7 @@ void (* const WebCommand[])(void) PROGMEM = {
#ifdef USE_EMULATION
void CmndEmulation(void)
{
+#if defined(USE_EMULATION_WEMO) || defined(USE_EMULATION_HUE)
#if defined(USE_EMULATION_WEMO) && defined(USE_EMULATION_HUE)
if ((XdrvMailbox.payload >= EMUL_NONE) && (XdrvMailbox.payload < EMUL_MAX)) {
#else
@@ -2729,6 +2858,7 @@ void CmndEmulation(void)
Settings.flag2.emulation = XdrvMailbox.payload;
restart_flag = 2;
}
+#endif
ResponseCmndNumber(Settings.flag2.emulation);
}
#endif // USE_EMULATION
@@ -2760,9 +2890,9 @@ void CmndWebServer(void)
void CmndWebPassword(void)
{
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.web_password))) {
- strlcpy(Settings.web_password, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data, sizeof(Settings.web_password));
- ResponseCmndChar(Settings.web_password);
+ if (XdrvMailbox.data_len > 0) {
+ SettingsUpdateText(SET_WEBPWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data);
+ ResponseCmndChar(SettingsText(SET_WEBPWD));
} else {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
@@ -2828,6 +2958,28 @@ void CmndWebSensor(void)
ResponseJsonEnd();
}
+void CmndWebButton(void)
+{
+ if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_BUTTON_TEXT)) {
+ if (!XdrvMailbox.usridx) {
+ ResponseCmndAll(SET_BUTTON1, MAX_BUTTON_TEXT);
+ } else {
+ if (XdrvMailbox.data_len > 0) {
+ SettingsUpdateText(SET_BUTTON1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
+ }
+ ResponseCmndIdxChar(SettingsText(SET_BUTTON1 + XdrvMailbox.index -1));
+ }
+ }
+}
+
+void CmndCors(void)
+{
+ if (XdrvMailbox.data_len > 0) {
+ SettingsUpdateText(SET_CORS, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? WEB_PASSWORD : XdrvMailbox.data);
+ }
+ ResponseCmndChar(SettingsText(SET_CORS));
+}
+
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
@@ -2840,7 +2992,11 @@ bool Xdrv01(uint8_t function)
case FUNC_LOOP:
PollDnsWebserver();
#ifdef USE_EMULATION
+#ifdef USE_DEVICE_GROUPS
+ if (Settings.flag2.emulation || Settings.flag4.device_groups_enabled) { PollUdp(); }
+#else // USE_DEVICE_GROUPS
if (Settings.flag2.emulation) { PollUdp(); }
+#endif // USE_DEVICE_GROUPS
#endif // USE_EMULATION
break;
case FUNC_COMMAND:
diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino
index 49f35d579..c766ab247 100644
--- a/tasmota/xdrv_02_mqtt.ino
+++ b/tasmota/xdrv_02_mqtt.ino
@@ -1,7 +1,7 @@
/*
xdrv_02_mqtt.ino - mqtt support for Tasmota
- Copyright (C) 2019 Theo Arends
+ Copyright (C) 2020 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
@@ -32,9 +32,7 @@ const char kMqttCommands[] PROGMEM = "|" // No prefix
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
D_CMND_MQTTFINGERPRINT "|"
#endif
-#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
D_CMND_MQTTUSER "|" D_CMND_MQTTPASSWORD "|"
-#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
D_CMND_TLSKEY "|"
#endif
@@ -46,9 +44,7 @@ void (* const MqttCommand[])(void) PROGMEM = {
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
&CmndMqttFingerprint,
#endif
-#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
&CmndMqttUser, &CmndMqttPassword,
-#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
&CmndTlsKey,
#endif
@@ -91,10 +87,6 @@ tls_dir_t tls_dir; // memory copy of tls_dir from flash
#endif // USE_MQTT_AWS_IOT
-// A typical AWS IoT endpoint is 50 characters long, it does not fit
-// in MqttHost field (32 chars). We need to concatenate both MqttUser and MqttHost
-char AWS_endpoint[65]; // aWS IOT endpoint, concatenation of user and host
-
// check whether the fingerprint is filled with a single value
// Filled with 0x00 = accept any fingerprint and learn it for next time
// Filled with 0xFF = accept any fingerpring forever
@@ -106,21 +98,6 @@ bool is_fingerprint_mono_value(uint8_t finger[20], uint8_t value) {
}
return true;
}
-
-#ifdef USE_MQTT_AWS_IOT
-void setLongMqttHost(const char *mqtt_host) {
- if (strlen(mqtt_host) <= sizeof(Settings.mqtt_host)) {
- strlcpy(Settings.mqtt_host, mqtt_host, sizeof(Settings.mqtt_host));
- Settings.mqtt_user[0] = 0;
- } else {
- // need to split in mqtt_user first then mqtt_host
- strlcpy(Settings.mqtt_user, mqtt_host, sizeof(Settings.mqtt_user));
- strlcpy(Settings.mqtt_host, &mqtt_host[sizeof(Settings.mqtt_user)-1], sizeof(Settings.mqtt_host));
- }
- strlcpy(AWS_endpoint, mqtt_host, sizeof(AWS_endpoint));
-}
-#endif // USE_MQTT_AWS_IOT
-
#endif // USE_MQTT_TLS
void MakeValidMqtt(uint32_t option, char* str)
@@ -165,10 +142,10 @@ void MqttDiscoverServer(void)
}
}
#endif // MDNS_HOSTNAME
- snprintf_P(Settings.mqtt_host, sizeof(Settings.mqtt_host), MDNS.IP(i).toString().c_str());
+ SettingsUpdateText(SET_MQTT_HOST, MDNS.IP(i).toString().c_str());
Settings.mqtt_port = MDNS.port(i);
- AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS D_MQTT_SERVICE_FOUND " %s, " D_IP_ADDRESS " %s, " D_PORT " %d"), MDNS.hostname(i).c_str(), Settings.mqtt_host, Settings.mqtt_port);
+ AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS D_MQTT_SERVICE_FOUND " %s, " D_IP_ADDRESS " %s, " D_PORT " %d"), MDNS.hostname(i).c_str(), SettingsText(SET_MQTT_HOST), Settings.mqtt_port);
}
}
#endif // MQTT_HOST_DISCOVERY
@@ -187,7 +164,7 @@ void MqttDiscoverServer(void)
// Max message size calculated by PubSubClient is (MQTT_MAX_PACKET_SIZE < 5 + 2 + strlen(topic) + plength)
#if (MQTT_MAX_PACKET_SIZE -TOPSZ -7) < MIN_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 1000"
+ #error "MQTT_MAX_PACKET_SIZE is too small in libraries/PubSubClient/src/PubSubClient.h, increase it to at least 1200"
#endif
#ifdef USE_MQTT_TLS
@@ -202,8 +179,6 @@ void MqttInit(void)
tlsClient = new BearSSL::WiFiClientSecure_light(1024,1024);
#ifdef USE_MQTT_AWS_IOT
- snprintf_P(AWS_endpoint, sizeof(AWS_endpoint), PSTR("%s%s"), Settings.mqtt_user, Settings.mqtt_host);
-
loadTlsDir(); // load key and certificate data from Flash
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
AWS_IoT_Private_Key,
@@ -246,6 +221,15 @@ void MqttUnsubscribeLib(const char *topic)
bool MqttPublishLib(const char* topic, bool retained)
{
+ // If Prefix1 equals Prefix2 disable next MQTT subscription to prevent loop
+ if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) {
+ char *str = strstr(topic, SettingsText(SET_MQTTPREFIX1));
+ if (str == topic) {
+ mqtt_cmnd_blocked_reset = 4; // Allow up to four seconds before resetting residual cmnd blocks
+ mqtt_cmnd_blocked++;
+ }
+ }
+
bool result = MqttClient.publish(topic, mqtt_data, retained);
yield(); // #3313
return result;
@@ -261,14 +245,10 @@ void MqttDataHandler(char* mqtt_topic, uint8_t* mqtt_data, unsigned int data_len
if (data_len >= MQTT_MAX_PACKET_SIZE) { return; }
// Do not execute multiple times if Prefix1 equals Prefix2
- if (!strcmp(Settings.mqtt_prefix[0], Settings.mqtt_prefix[1])) {
- char *str = strstr(mqtt_topic, Settings.mqtt_prefix[0]);
- if ((str == mqtt_topic) && mqtt_cmnd_publish) {
- if (mqtt_cmnd_publish > 3) {
- mqtt_cmnd_publish -= 3;
- } else {
- mqtt_cmnd_publish = 0;
- }
+ if (!strcmp(SettingsText(SET_MQTTPREFIX1), SettingsText(SET_MQTTPREFIX2))) {
+ char *str = strstr(mqtt_topic, SettingsText(SET_MQTTPREFIX1));
+ if ((str == mqtt_topic) && mqtt_cmnd_blocked) {
+ mqtt_cmnd_blocked--;
return;
}
}
@@ -316,52 +296,41 @@ void MqttUnsubscribe(const char *topic)
void MqttPublishLogging(const char *mxtime)
{
- if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
- if (MqttIsConnected()) {
+ char saved_mqtt_data[strlen(mqtt_data) +1];
+ memcpy(saved_mqtt_data, mqtt_data, sizeof(saved_mqtt_data));
- char saved_mqtt_data[MESSZ];
- memcpy(saved_mqtt_data, mqtt_data, sizeof(saved_mqtt_data));
-// ResponseTime_P(PSTR(",\"Log\":{\"%s\"}}"), log_data); // Will fail as some messages contain JSON
- Response_P(PSTR("%s%s"), mxtime, log_data); // No JSON and ugly!!
+// ResponseTime_P(PSTR(",\"Log\":{\"%s\"}}"), log_data); // Will fail as some messages contain JSON
+ Response_P(PSTR("%s%s"), mxtime, log_data); // No JSON and ugly!!
+ char stopic[TOPSZ];
+ GetTopic_P(stopic, STAT, mqtt_topic, PSTR("LOGGING"));
+ MqttPublishLib(stopic, false);
- char romram[33];
- char stopic[TOPSZ];
- snprintf_P(romram, sizeof(romram), PSTR("LOGGING"));
- GetTopic_P(stopic, STAT, mqtt_topic, romram);
-
- char *me;
- if (!strcmp(Settings.mqtt_prefix[0], Settings.mqtt_prefix[1])) {
- me = strstr(stopic, Settings.mqtt_prefix[0]);
- if (me == stopic) {
- mqtt_cmnd_publish += 3;
- }
- }
- MqttPublishLib(stopic, false);
-
- memcpy(mqtt_data, saved_mqtt_data, sizeof(saved_mqtt_data));
- }
- }
+ memcpy(mqtt_data, saved_mqtt_data, sizeof(saved_mqtt_data));
}
-void MqttPublishDirect(const char* topic, bool retained)
+void MqttPublish(const char* topic, bool retained)
{
- char sretained[CMDSZ];
- char slog_type[20];
-
#ifdef USE_DEBUG_DRIVER
- ShowFreeMem(PSTR("MqttPublishDirect"));
+ ShowFreeMem(PSTR("MqttPublish"));
#endif
+#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT) || defined(MQTT_NO_RETAIN)
+// if (retained) {
+// AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("Retained are not supported by AWS IoT, using retained = false."));
+// }
+ retained = false; // AWS IoT does not support retained, it will disconnect if received
+#endif
+
+ char sretained[CMDSZ];
sretained[0] = '\0';
+ char slog_type[20];
snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_RESULT));
if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
- if (MqttIsConnected()) {
- if (MqttPublishLib(topic, retained)) {
- snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_MQTT));
- if (retained) {
- snprintf_P(sretained, sizeof(sretained), PSTR(" (" D_RETAINED ")"));
- }
+ if (MqttPublishLib(topic, retained)) {
+ snprintf_P(slog_type, sizeof(slog_type), PSTR(D_LOG_MQTT));
+ if (retained) {
+ snprintf_P(sretained, sizeof(sretained), PSTR(" (" D_RETAINED ")"));
}
}
}
@@ -379,25 +348,6 @@ void MqttPublishDirect(const char* topic, bool retained)
}
}
-void MqttPublish(const char* topic, bool retained)
-{
- char *me;
-#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
- if (retained) {
- AddLog_P(LOG_LEVEL_INFO, S_LOG_MQTT, PSTR("Retained are not supported by AWS IoT, using retained = false."));
- }
- retained = false; // AWS IoT does not support retained, it will disconnect if received
-#endif
-
- if (!strcmp(Settings.mqtt_prefix[0],Settings.mqtt_prefix[1])) {
- me = strstr(topic,Settings.mqtt_prefix[0]);
- if (me == topic) {
- mqtt_cmnd_publish += 3;
- }
- }
- MqttPublishDirect(topic, retained);
-}
-
void MqttPublish(const char* topic)
{
MqttPublish(topic, false);
@@ -412,7 +362,7 @@ void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retain
* prefix 5 = stat using subtopic or RESULT
* prefix 6 = tele using subtopic or RESULT
*/
- char romram[33];
+ char romram[64];
char stopic[TOPSZ];
snprintf_P(romram, sizeof(romram), ((prefix > 3) && !Settings.flag.mqtt_response) ? S_RSLT_RESULT : subtopic); // SetOption4 - Switch between MQTT RESULT or COMMAND
@@ -422,6 +372,36 @@ void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic, bool retain
prefix &= 3;
GetTopic_P(stopic, prefix, mqtt_topic, romram);
MqttPublish(stopic, retained);
+
+#ifdef USE_MQTT_AWS_IOT
+ if ((prefix > 0) && (Settings.flag4.awsiot_shadow)) { // placeholder for SetOptionXX
+ // compute the target topic
+ char *topic = SettingsText(SET_MQTT_TOPIC);
+ char topic2[strlen(topic)+1]; // save buffer onto stack
+ strcpy(topic2, topic);
+ // replace any '/' with '_'
+ char *s = topic2;
+ while (*s) {
+ if ('/' == *s) {
+ *s = '_';
+ }
+ s++;
+ }
+ // update topic is "$aws/things/
/shadow/update"
+ snprintf_P(romram, sizeof(romram), PSTR("$aws/things/%s/shadow/update"), topic2);
+
+ // copy buffer
+ char *mqtt_save = (char*) malloc(strlen(mqtt_data)+1);
+ if (!mqtt_save) { return; } // abort
+ strcpy(mqtt_save, mqtt_data);
+ snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"state\":{\"reported\":%s}}"), mqtt_save);
+ free(mqtt_save);
+
+ bool result = MqttClient.publish(romram, mqtt_data, false);
+ AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_MQTT "Updated shadow: %s"), romram);
+ yield(); // #3313
+ }
+#endif // USE_MQTT_AWS_IOT
}
void MqttPublishPrefixTopic_P(uint32_t prefix, const char* subtopic)
@@ -505,11 +485,7 @@ void MqttDisconnected(int state)
MqttClient.disconnect();
-#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
- AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), AWS_endpoint, Settings.mqtt_port, state, Mqtt.retry_counter);
-#else
- AddLog_P2(LOG_LEVEL_INFO, 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, state, Mqtt.retry_counter);
-#endif
+ AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CONNECT_FAILED_TO " %s:%d, rc %d. " D_RETRY_IN " %d " D_UNIT_SECOND), SettingsText(SET_MQTT_HOST), Settings.mqtt_port, state, Mqtt.retry_counter);
rules_flag.mqtt_disconnected = 1;
}
@@ -533,7 +509,7 @@ void MqttConnected(void)
GetTopic_P(stopic, CMND, mqtt_topic, PSTR("#"));
MqttSubscribe(stopic);
- if (strstr_P(Settings.mqtt_fulltopic, MQTT_TOKEN_TOPIC) != nullptr) {
+ if (strstr_P(SettingsText(SET_MQTT_FULLTOPIC), MQTT_TOKEN_TOPIC) != nullptr) {
GetGroupTopic_P(stopic, PSTR("#")); // SetOption75 0: %prefix%/nothing/%topic% = cmnd/nothing//# or SetOption75 1: cmnd/
MqttSubscribe(stopic);
GetFallbackTopic_P(stopic, PSTR("#"));
@@ -550,12 +526,23 @@ void MqttConnected(void)
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "1"));
#ifdef USE_WEBSERVER
if (Settings.webserver) {
+#if LWIP_IPV6
+ Response_P(PSTR("{\"" D_JSON_WEBSERVER_MODE "\":\"%s\",\"" D_CMND_HOSTNAME "\":\"%s\",\"" D_CMND_IPADDRESS "\":\"%s\",\"IPv6Address\":\"%s\"}"),
+ (2 == Settings.webserver) ? D_ADMIN : D_USER, my_hostname, WiFi.localIP().toString().c_str(),WifiGetIPv6().c_str());
+#else
Response_P(PSTR("{\"" D_JSON_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());
+#endif // LWIP_IPV6 = 1
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "2"));
}
#endif // USE_WEBSERVER
- Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":\"%s\"}"), GetResetReasonInfo().c_str());
+ Response_P(PSTR("{\"" D_JSON_RESTARTREASON "\":"));
+ if (CrashFlag()) {
+ CrashDump();
+ } else {
+ ResponseAppend_P(PSTR("\"%s\""), GetResetReason().c_str());
+ }
+ ResponseJsonEnd();
MqttPublishPrefixTopic_P(TELE, PSTR(D_RSLT_INFO "3"));
MqttPublishAllPowerState();
if (Settings.tele_period) {
@@ -583,7 +570,7 @@ void MqttReconnect(void)
MqttDiscoverServer();
#endif // MQTT_HOST_DISCOVERY
#endif // USE_DISCOVERY
- if (!strlen(Settings.mqtt_host) || !Settings.mqtt_port) {
+ if (!strlen(SettingsText(SET_MQTT_HOST)) || !Settings.mqtt_port) {
Mqtt.allowed = false;
}
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
@@ -610,8 +597,12 @@ void MqttReconnect(void)
char *mqtt_user = nullptr;
char *mqtt_pwd = nullptr;
- if (strlen(Settings.mqtt_user) > 0) mqtt_user = Settings.mqtt_user;
- if (strlen(Settings.mqtt_pwd) > 0) mqtt_pwd = Settings.mqtt_pwd;
+ if (strlen(SettingsText(SET_MQTT_USER))) {
+ mqtt_user = SettingsText(SET_MQTT_USER);
+ }
+ if (strlen(SettingsText(SET_MQTT_PWD))) {
+ mqtt_pwd = SettingsText(SET_MQTT_PWD);
+ }
GetTopic_P(stopic, TELE, mqtt_topic, S_LWT);
Response_P(S_OFFLINE);
@@ -634,10 +625,8 @@ void MqttReconnect(void)
tlsClient->setClientECCert(AWS_IoT_Client_Certificate,
AWS_IoT_Private_Key,
0xFFFF /* all usages, don't care */, 0);
- MqttClient.setServer(AWS_endpoint, Settings.mqtt_port);
-#else
- MqttClient.setServer(Settings.mqtt_host, Settings.mqtt_port);
#endif
+ MqttClient.setServer(SettingsText(SET_MQTT_HOST), Settings.mqtt_port);
uint32_t mqtt_connect_time = millis();
#if defined(USE_MQTT_TLS) && !defined(USE_MQTT_TLS_CA_CERT)
@@ -651,8 +640,7 @@ void MqttReconnect(void)
tlsClient->setPubKeyFingerprint(Settings.mqtt_fingerprint[0], Settings.mqtt_fingerprint[1], allow_all_fingerprints);
#endif
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
- AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), AWS_endpoint);
- //if (MqttClient.connect(mqtt_client, nullptr, nullptr, nullptr, 0, false, nullptr)) {
+ AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "AWS IoT endpoint: %s"), SettingsText(SET_MQTT_HOST));
if (MqttClient.connect(mqtt_client, nullptr, nullptr, stopic, 1, false, mqtt_data, MQTT_CLEAN_SESSION)) {
#else
if (MqttClient.connect(mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, true, mqtt_data, MQTT_CLEAN_SESSION)) {
@@ -709,11 +697,6 @@ void MqttCheck(void)
if (!MqttIsConnected()) {
global_state.mqtt_down = 1;
if (!Mqtt.retry_counter) {
-#ifdef USE_DISCOVERY
-#ifdef MQTT_HOST_DISCOVERY
- if (!strlen(Settings.mqtt_host) && !Wifi.mdns_begun) { return; }
-#endif // MQTT_HOST_DISCOVERY
-#endif // USE_DISCOVERY
MqttReconnect();
} else {
Mqtt.retry_counter--;
@@ -723,10 +706,19 @@ void MqttCheck(void)
}
} else {
global_state.mqtt_down = 0;
- if (Mqtt.initial_connection_state) MqttReconnect();
+ if (Mqtt.initial_connection_state) {
+ MqttReconnect();
+ }
}
}
+bool ButtonTopicActive(void)
+{
+ char key_topic[TOPSZ];
+ Format(key_topic, SettingsText(SET_MQTT_BUTTON_TOPIC), sizeof(key_topic));
+ return ((strlen(key_topic) != 0) && strcmp(key_topic, "0"));
+}
+
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
@@ -749,27 +741,25 @@ void CmndMqttFingerprint(void)
}
#endif
-#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password are disabled with AWS IoT
void CmndMqttUser(void)
{
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_user))) {
- strlcpy(Settings.mqtt_user, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_USER : XdrvMailbox.data, sizeof(Settings.mqtt_user));
+ if (XdrvMailbox.data_len > 0) {
+ SettingsUpdateText(SET_MQTT_USER, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_USER : XdrvMailbox.data);
restart_flag = 2;
}
- ResponseCmndChar(Settings.mqtt_user);
+ ResponseCmndChar(SettingsText(SET_MQTT_USER));
}
void CmndMqttPassword(void)
{
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_pwd))) {
- strlcpy(Settings.mqtt_pwd, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_PASS : XdrvMailbox.data, sizeof(Settings.mqtt_pwd));
- ResponseCmndChar(Settings.mqtt_pwd);
+ if (XdrvMailbox.data_len > 0) {
+ SettingsUpdateText(SET_MQTT_PWD, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_PASS : XdrvMailbox.data);
+ ResponseCmndChar(SettingsText(SET_MQTT_PWD));
restart_flag = 2;
} else {
Response_P(S_JSON_COMMAND_ASTERISK, XdrvMailbox.command);
}
}
-#endif // USE_MQTT_AWS_IOT
void CmndMqttlog(void)
{
@@ -781,19 +771,11 @@ void CmndMqttlog(void)
void CmndMqttHost(void)
{
-#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len <= sizeof(Settings.mqtt_host) + sizeof(Settings.mqtt_user) - 2)) {
- setLongMqttHost((SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data);
+ if (XdrvMailbox.data_len > 0) {
+ SettingsUpdateText(SET_MQTT_HOST, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data);
restart_flag = 2;
}
- ResponseCmndChar(AWS_endpoint);
-#else
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_host))) {
- strlcpy(Settings.mqtt_host, (SC_CLEAR == Shortcut()) ? "" : (SC_DEFAULT == Shortcut()) ? MQTT_HOST : XdrvMailbox.data, sizeof(Settings.mqtt_host));
- restart_flag = 2;
- }
- ResponseCmndChar(Settings.mqtt_host);
-#endif
+ ResponseCmndChar(SettingsText(SET_MQTT_HOST));
}
void CmndMqttPort(void)
@@ -816,70 +798,78 @@ void CmndMqttRetry(void)
void CmndStateText(void)
{
- if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 4)) {
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.state_text[0]))) {
- for (uint32_t i = 0; i <= XdrvMailbox.data_len; i++) {
- if (XdrvMailbox.data[i] == ' ') XdrvMailbox.data[i] = '_';
+ if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_STATE_TEXT)) {
+ if (!XdrvMailbox.usridx) {
+ ResponseCmndAll(SET_STATE_TXT1, MAX_STATE_TEXT);
+ } else {
+ if (XdrvMailbox.data_len > 0) {
+ for (uint32_t i = 0; i <= XdrvMailbox.data_len; i++) {
+ if (XdrvMailbox.data[i] == ' ') XdrvMailbox.data[i] = '_';
+ }
+ SettingsUpdateText(SET_STATE_TXT1 + XdrvMailbox.index -1, XdrvMailbox.data);
}
- strlcpy(Settings.state_text[XdrvMailbox.index -1], XdrvMailbox.data, sizeof(Settings.state_text[0]));
+ ResponseCmndIdxChar(GetStateText(XdrvMailbox.index -1));
}
- ResponseCmndIdxChar(GetStateText(XdrvMailbox.index -1));
}
}
void CmndMqttClient(void)
{
- if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_client))) {
- strlcpy(Settings.mqtt_client, (SC_DEFAULT == Shortcut()) ? MQTT_CLIENT_ID : XdrvMailbox.data, sizeof(Settings.mqtt_client));
+ if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
+ SettingsUpdateText(SET_MQTT_CLIENT, (SC_DEFAULT == Shortcut()) ? MQTT_CLIENT_ID : XdrvMailbox.data);
restart_flag = 2;
}
- ResponseCmndChar(Settings.mqtt_client);
+ ResponseCmndChar(SettingsText(SET_MQTT_CLIENT));
}
void CmndFullTopic(void)
{
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_fulltopic))) {
+ if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(1, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
char stemp1[TOPSZ];
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_FULLTOPIC : XdrvMailbox.data, sizeof(stemp1));
- if (strcmp(stemp1, Settings.mqtt_fulltopic)) {
+ if (strcmp(stemp1, SettingsText(SET_MQTT_FULLTOPIC))) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); // Offline or remove previous retained topic
- strlcpy(Settings.mqtt_fulltopic, stemp1, sizeof(Settings.mqtt_fulltopic));
+ SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp1);
restart_flag = 2;
}
}
- ResponseCmndChar(Settings.mqtt_fulltopic);
+ ResponseCmndChar(SettingsText(SET_MQTT_FULLTOPIC));
}
void CmndPrefix(void)
{
- if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= 3)) {
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_prefix[0]))) {
- MakeValidMqtt(0, XdrvMailbox.data);
- strlcpy(Settings.mqtt_prefix[XdrvMailbox.index -1], (SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index)?SUB_PREFIX:(2==XdrvMailbox.index)?PUB_PREFIX:PUB_PREFIX2 : XdrvMailbox.data, sizeof(Settings.mqtt_prefix[0]));
-// if (Settings.mqtt_prefix[XdrvMailbox.index -1][strlen(Settings.mqtt_prefix[XdrvMailbox.index -1])] == '/') Settings.mqtt_prefix[XdrvMailbox.index -1][strlen(Settings.mqtt_prefix[XdrvMailbox.index -1])] = 0;
- restart_flag = 2;
+ if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_MQTT_PREFIXES)) {
+ if (!XdrvMailbox.usridx) {
+ ResponseCmndAll(SET_MQTTPREFIX1, MAX_MQTT_PREFIXES);
+ } else {
+ if (XdrvMailbox.data_len > 0) {
+ MakeValidMqtt(0, XdrvMailbox.data);
+ SettingsUpdateText(SET_MQTTPREFIX1 + XdrvMailbox.index -1,
+ (SC_DEFAULT == Shortcut()) ? (1==XdrvMailbox.index) ? SUB_PREFIX : (2==XdrvMailbox.index) ? PUB_PREFIX : PUB_PREFIX2 : XdrvMailbox.data);
+ restart_flag = 2;
+ }
+ ResponseCmndIdxChar(SettingsText(SET_MQTTPREFIX1 + XdrvMailbox.index -1));
}
- ResponseCmndIdxChar(Settings.mqtt_prefix[XdrvMailbox.index -1]);
}
}
void CmndPublish(void)
{
if (XdrvMailbox.data_len > 0) {
- char *mqtt_part = strtok(XdrvMailbox.data, " ");
+ char *payload_part;
+ char *mqtt_part = strtok_r(XdrvMailbox.data, " ", &payload_part);
if (mqtt_part) {
char stemp1[TOPSZ];
strlcpy(stemp1, mqtt_part, sizeof(stemp1));
- mqtt_part = strtok(nullptr, " ");
- if (mqtt_part) {
- strlcpy(mqtt_data, mqtt_part, sizeof(mqtt_data));
+ if ((payload_part != nullptr) && strlen(payload_part)) {
+ strlcpy(mqtt_data, payload_part, sizeof(mqtt_data));
} else {
mqtt_data[0] = '\0';
}
- MqttPublishDirect(stemp1, (XdrvMailbox.index == 2));
+ MqttPublish(stemp1, (XdrvMailbox.index == 2));
// ResponseCmndDone();
mqtt_data[0] = '\0';
}
@@ -888,60 +878,67 @@ void CmndPublish(void)
void CmndGroupTopic(void)
{
- if ((XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_grptopic))) {
+#ifdef USE_DEVICE_GROUPS
+ uint32_t settings_text_index = (XdrvMailbox.index <= 1 ? SET_MQTT_GRP_TOPIC : SET_MQTT_GRP_TOPIC2 + XdrvMailbox.index - 2);
+#endif // USE_DEVICE_GROUPS
+ if (XdrvMailbox.data_len > 0) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
- strlcpy(Settings.mqtt_grptopic, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data, sizeof(Settings.mqtt_grptopic));
+#ifdef USE_DEVICE_GROUPS
+ SettingsUpdateText(settings_text_index, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
+#else // USE_DEVICE_GROUPS
+ SettingsUpdateText(SET_MQTT_GRP_TOPIC, (SC_DEFAULT == Shortcut()) ? MQTT_GRPTOPIC : XdrvMailbox.data);
+#endif // USE_DEVICE_GROUPS
restart_flag = 2;
}
- ResponseCmndChar(Settings.mqtt_grptopic);
+ ResponseCmndChar(SettingsText(SET_MQTT_GRP_TOPIC));
}
void CmndTopic(void)
{
- if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.mqtt_topic))) {
+ if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
char stemp1[TOPSZ];
strlcpy(stemp1, (SC_DEFAULT == Shortcut()) ? MQTT_TOPIC : XdrvMailbox.data, sizeof(stemp1));
- if (strcmp(stemp1, Settings.mqtt_topic)) {
+ if (strcmp(stemp1, SettingsText(SET_MQTT_TOPIC))) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P(TELE, PSTR(D_LWT), true); // Offline or remove previous retained topic
- strlcpy(Settings.mqtt_topic, stemp1, sizeof(Settings.mqtt_topic));
+ SettingsUpdateText(SET_MQTT_TOPIC, stemp1);
restart_flag = 2;
}
}
- ResponseCmndChar(Settings.mqtt_topic);
+ ResponseCmndChar(SettingsText(SET_MQTT_TOPIC));
}
void CmndButtonTopic(void)
{
- if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.button_topic))) {
+ if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
switch (Shortcut()) {
- case SC_CLEAR: strlcpy(Settings.button_topic, "", sizeof(Settings.button_topic)); break;
- case SC_DEFAULT: strlcpy(Settings.button_topic, mqtt_topic, sizeof(Settings.button_topic)); break;
- case SC_USER: strlcpy(Settings.button_topic, MQTT_BUTTON_TOPIC, sizeof(Settings.button_topic)); break;
- default: strlcpy(Settings.button_topic, XdrvMailbox.data, sizeof(Settings.button_topic));
+ case SC_CLEAR: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, ""); break;
+ case SC_DEFAULT: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, mqtt_topic); break;
+ case SC_USER: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, MQTT_BUTTON_TOPIC); break;
+ default: SettingsUpdateText(SET_MQTT_BUTTON_TOPIC, XdrvMailbox.data);
}
}
- ResponseCmndChar(Settings.button_topic);
+ ResponseCmndChar(SettingsText(SET_MQTT_BUTTON_TOPIC));
}
void CmndSwitchTopic(void)
{
- if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0) && (XdrvMailbox.data_len < sizeof(Settings.switch_topic))) {
+ if (!XdrvMailbox.grpflg && (XdrvMailbox.data_len > 0)) {
MakeValidMqtt(0, XdrvMailbox.data);
if (!strcmp(XdrvMailbox.data, mqtt_client)) { SetShortcutDefault(); }
switch (Shortcut()) {
- case SC_CLEAR: strlcpy(Settings.switch_topic, "", sizeof(Settings.switch_topic)); break;
- case SC_DEFAULT: strlcpy(Settings.switch_topic, mqtt_topic, sizeof(Settings.switch_topic)); break;
- case SC_USER: strlcpy(Settings.switch_topic, MQTT_SWITCH_TOPIC, sizeof(Settings.switch_topic)); break;
- default: strlcpy(Settings.switch_topic, XdrvMailbox.data, sizeof(Settings.switch_topic));
+ case SC_CLEAR: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, ""); break;
+ case SC_DEFAULT: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, mqtt_topic); break;
+ case SC_USER: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, MQTT_SWITCH_TOPIC); break;
+ default: SettingsUpdateText(SET_MQTT_SWITCH_TOPIC, XdrvMailbox.data);
}
}
- ResponseCmndChar(Settings.switch_topic);
+ ResponseCmndChar(SettingsText(SET_MQTT_SWITCH_TOPIC));
}
void CmndButtonRetain(void)
@@ -1005,8 +1002,10 @@ void CmndSensorRetain(void)
\*********************************************************************************************/
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
-const static uint16_t tls_spi_start_sector = SPIFFS_END + 4; // 0xXXFF
-const static uint8_t* tls_spi_start = (uint8_t*) ((tls_spi_start_sector * SPI_FLASH_SEC_SIZE) + 0x40200000); // 0x40XFF000
+// const static uint16_t tls_spi_start_sector = SPIFFS_END + 4; // 0xXXFF
+// const static uint8_t* tls_spi_start = (uint8_t*) ((tls_spi_start_sector * SPI_FLASH_SEC_SIZE) + 0x40200000); // 0x40XFF000
+const static uint16_t tls_spi_start_sector = 0xFF; // Force last bank of first MB
+const static uint8_t* tls_spi_start = (uint8_t*) 0x402FF000; // 0x402FF000
const static size_t tls_spi_len = 0x1000; // 4kb blocs
const static size_t tls_block_offset = 0x0400;
const static size_t tls_block_len = 0x0400; // 1kb
@@ -1071,6 +1070,9 @@ void CmndTlsKey(void) {
}
memcpy_P(spi_buffer, tls_spi_start, tls_spi_len);
+ // remove any white space from the base64
+ RemoveAllSpaces(XdrvMailbox.data);
+
// allocate buffer for decoded base64
uint32_t bin_len = decode_base64_length((unsigned char*)XdrvMailbox.data);
uint8_t *bin_buf = nullptr;
@@ -1160,8 +1162,12 @@ void CmndTlsDump(void) {
uint32_t start = (uint32_t)tls_spi_start + tls_block_offset;
uint32_t end = start + tls_block_len -1;
for (uint32_t pos = start; pos < end; pos += 0x10) {
- uint32_t* values = (uint32_t*)(pos);
- Serial.printf_P(PSTR("%08x: %08x %08x %08x %08x\n"), pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3]));
+ uint32_t* values = (uint32_t*)(pos);
+#ifdef ARDUINO_ESP8266_RELEASE_2_3_0
+ Serial.printf("%08x: %08x %08x %08x %08x\n", pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3]));
+#else
+ Serial.printf_P(PSTR("%08x: %08x %08x %08x %08x\n"), pos, bswap32(values[0]), bswap32(values[1]), bswap32(values[2]), bswap32(values[3]));
+#endif
}
}
#endif // DEBUG_DUMP_TLS
@@ -1187,10 +1193,8 @@ const char HTTP_FORM_MQTT1[] PROGMEM =
"" D_PORT " (" STR(MQTT_PORT) ")
"
"" D_CLIENT " (%s)
";
const char HTTP_FORM_MQTT2[] PROGMEM =
-#if !defined(USE_MQTT_TLS) || !defined(USE_MQTT_AWS_IOT) // user and password disabled with AWS IoT
"" D_USER " (" MQTT_USER ")
"
"" D_PASSWORD "
"
-#endif // USE_MQTT_AWS_IOT
"" D_TOPIC " = %%topic%% (%s)
"
"" D_FULL_TOPIC " (%s)
";
@@ -1206,22 +1210,18 @@ void HandleMqttConfiguration(void)
return;
}
- char str[sizeof(Settings.mqtt_client)];
+ char str[TOPSZ];
WSContentStart_P(S_CONFIGURE_MQTT);
WSContentSendStyle();
WSContentSend_P(HTTP_FORM_MQTT1,
-#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
- AWS_endpoint,
-#else
- Settings.mqtt_host,
-#endif
+ SettingsText(SET_MQTT_HOST),
Settings.mqtt_port,
- Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, Settings.mqtt_client);
+ Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, SettingsText(SET_MQTT_CLIENT));
WSContentSend_P(HTTP_FORM_MQTT2,
- (Settings.mqtt_user[0] == '\0') ? "0" : Settings.mqtt_user,
- Format(str, MQTT_TOPIC, sizeof(str)), MQTT_TOPIC, Settings.mqtt_topic,
- MQTT_FULLTOPIC, MQTT_FULLTOPIC, Settings.mqtt_fulltopic);
+ (!strlen(SettingsText(SET_MQTT_USER))) ? "0" : SettingsText(SET_MQTT_USER),
+ Format(str, MQTT_TOPIC, sizeof(str)), MQTT_TOPIC, SettingsText(SET_MQTT_TOPIC),
+ MQTT_FULLTOPIC, MQTT_FULLTOPIC, SettingsText(SET_MQTT_FULLTOPIC));
WSContentSend_P(HTTP_FORM_END);
WSContentSpaceButton(BUTTON_CONFIGURATION);
WSContentStop();
@@ -1229,7 +1229,7 @@ void HandleMqttConfiguration(void)
void MqttSaveSettings(void)
{
- char tmp[100];
+ char tmp[TOPSZ];
char stemp[TOPSZ];
char stemp2[TOPSZ];
@@ -1239,32 +1239,28 @@ void MqttSaveSettings(void)
WebGetArg("mf", tmp, sizeof(tmp));
strlcpy(stemp2, (!strlen(tmp)) ? MQTT_FULLTOPIC : tmp, sizeof(stemp2));
MakeValidMqtt(1, stemp2);
- if ((strcmp(stemp, Settings.mqtt_topic)) || (strcmp(stemp2, Settings.mqtt_fulltopic))) {
+ if ((strcmp(stemp, SettingsText(SET_MQTT_TOPIC))) || (strcmp(stemp2, SettingsText(SET_MQTT_FULLTOPIC)))) {
Response_P((Settings.flag.mqtt_offline) ? S_OFFLINE : ""); // SetOption10 - Control MQTT LWT message format
MqttPublishPrefixTopic_P(TELE, S_LWT, true); // Offline or remove previous retained topic
}
- strlcpy(Settings.mqtt_topic, stemp, sizeof(Settings.mqtt_topic));
- strlcpy(Settings.mqtt_fulltopic, stemp2, sizeof(Settings.mqtt_fulltopic));
+ SettingsUpdateText(SET_MQTT_TOPIC, stemp);
+ SettingsUpdateText(SET_MQTT_FULLTOPIC, stemp2);
WebGetArg("mh", tmp, sizeof(tmp));
-#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
- setLongMqttHost((!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp);
-#else
- strlcpy(Settings.mqtt_host, (!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp, sizeof(Settings.mqtt_host));
-#endif
+ SettingsUpdateText(SET_MQTT_HOST, (!strlen(tmp)) ? MQTT_HOST : (!strcmp(tmp,"0")) ? "" : tmp);
WebGetArg("ml", tmp, sizeof(tmp));
Settings.mqtt_port = (!strlen(tmp)) ? MQTT_PORT : atoi(tmp);
WebGetArg("mc", tmp, sizeof(tmp));
- strlcpy(Settings.mqtt_client, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp, sizeof(Settings.mqtt_client));
+ SettingsUpdateText(SET_MQTT_CLIENT, (!strlen(tmp)) ? MQTT_CLIENT_ID : tmp);
#if defined(USE_MQTT_TLS) && defined(USE_MQTT_AWS_IOT)
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"),
- AWS_endpoint, Settings.mqtt_port, Settings.mqtt_client, Settings.mqtt_topic, Settings.mqtt_fulltopic);
+ SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC));
#else // USE_MQTT_AWS_IOT
WebGetArg("mu", tmp, sizeof(tmp));
- strlcpy(Settings.mqtt_user, (!strlen(tmp)) ? MQTT_USER : (!strcmp(tmp,"0")) ? "" : tmp, sizeof(Settings.mqtt_user));
+ SettingsUpdateText(SET_MQTT_USER, (!strlen(tmp)) ? MQTT_USER : (!strcmp(tmp,"0")) ? "" : tmp);
WebGetArg("mp", tmp, sizeof(tmp));
- strlcpy(Settings.mqtt_pwd, (!strlen(tmp)) ? "" : (!strcmp(tmp, D_ASTERISK_PWD)) ? Settings.mqtt_pwd : tmp, sizeof(Settings.mqtt_pwd));
+ SettingsUpdateText(SET_MQTT_PWD, (!strlen(tmp)) ? "" : (!strcmp(tmp, D_ASTERISK_PWD)) ? SettingsText(SET_MQTT_PWD) : tmp);
AddLog_P2(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT D_CMND_MQTTHOST " %s, " D_CMND_MQTTPORT " %d, " D_CMND_MQTTCLIENT " %s, " D_CMND_MQTTUSER " %s, " D_CMND_TOPIC " %s, " D_CMND_FULLTOPIC " %s"),
- Settings.mqtt_host, Settings.mqtt_port, Settings.mqtt_client, Settings.mqtt_user, Settings.mqtt_topic, Settings.mqtt_fulltopic);
+ SettingsText(SET_MQTT_HOST), Settings.mqtt_port, SettingsText(SET_MQTT_CLIENT), SettingsText(SET_MQTT_USER), SettingsText(SET_MQTT_TOPIC), SettingsText(SET_MQTT_FULLTOPIC));
#endif
}
#endif // USE_WEBSERVER
diff --git a/tasmota/xdrv_03_energy.ino b/tasmota/xdrv_03_energy.ino
index 6d8be5c2b..bb74b991d 100644
--- a/tasmota/xdrv_03_energy.ino
+++ b/tasmota/xdrv_03_energy.ino
@@ -1,7 +1,7 @@
/*
xdrv_03_energy.ino - Energy sensor support for Tasmota
- Copyright (C) 2019 Theo Arends
+ Copyright (C) 2020 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
@@ -289,14 +289,19 @@ void EnergyMarginCheck(void)
if (Settings.energy_power_delta) {
uint16_t delta = abs(Energy.power_history[0] - energy_power_u);
- uint16_t min_power = (Energy.power_history[0] > energy_power_u) ? energy_power_u : Energy.power_history[0];
-
- DEBUG_DRIVER_LOG(PSTR("NRG: Delta %d, Power %d"), delta, min_power);
-
- if ((delta > 0) && (min_power > 0)) { // Fix divide by 0 exception (#6741)
- if (((Settings.energy_power_delta < 101) && (((delta * 100) / min_power) > Settings.energy_power_delta)) || // 1..100 = Percentage
- ((Settings.energy_power_delta > 100) && (delta > (Settings.energy_power_delta -100)))) { // 101..32000 = Absolute
- Energy.power_delta = true;
+ if (delta > 0) {
+ if (Settings.energy_power_delta < 101) { // 1..100 = Percentage
+ uint16_t min_power = (Energy.power_history[0] > energy_power_u) ? energy_power_u : Energy.power_history[0];
+ if (0 == min_power) { min_power++; } // Fix divide by 0 exception (#6741)
+ if (((delta * 100) / min_power) > Settings.energy_power_delta) {
+ Energy.power_delta = true;
+ }
+ } else { // 101..32000 = Absolute
+ if (delta > (Settings.energy_power_delta -100)) {
+ Energy.power_delta = true;
+ }
+ }
+ if (Energy.power_delta) {
Energy.power_history[1] = Energy.active_power[0]; // We only want one report so reset history
Energy.power_history[2] = Energy.active_power[0];
}
@@ -360,7 +365,7 @@ void EnergyMarginCheck(void)
EnergyMqttShow();
SetAllPower(POWER_ALL_OFF, SRC_MAXPOWER);
if (!Energy.mplr_counter) {
- Energy.mplr_counter = Settings.param[P_MAX_POWER_RETRY] +1;
+ Energy.mplr_counter = Settings.param[P_MAX_POWER_RETRY] +1; // SetOption33 - Max Power Retry count
}
Energy.mplw_counter = Settings.energy_max_power_limit_window;
}
@@ -385,6 +390,7 @@ void EnergyMarginCheck(void)
ResponseTime_P(PSTR(",\"" D_JSON_MAXPOWERREACHEDRETRY "\":\"%s\"}"), GetStateText(0));
MqttPublishPrefixTopic_P(STAT, S_RSLT_WARNING);
EnergyMqttShow();
+ SetAllPower(POWER_ALL_OFF, SRC_MAXPOWER);
}
}
}
@@ -517,26 +523,19 @@ void CmndEnergyReset(void)
}
}
else if ((XdrvMailbox.index > 3) && (XdrvMailbox.index <= 5)) {
- char *p;
- char *str = strtok_r(XdrvMailbox.data, ", ", &p);
- int32_t position = -1;
- uint32_t values[2];
-
- while ((str != nullptr) && (position < 1)) {
- uint32_t value = strtoul(str, nullptr, 10);
- position++;
- values[position] = value *100;
- str = strtok_r(nullptr, ", ", &p);
- }
+ uint32_t values[2] = { 0 };
+ uint32_t position = ParseParameters(2, values);
+ values[0] *= 100;
+ values[1] *= 100;
switch (XdrvMailbox.index)
{
case 4:
// Reset energy_usage.usage totals
- if (position > -1) {
+ if (position > 0) {
RtcSettings.energy_usage.usage1_kWhtotal = values[0];
}
- if (position > 0) {
+ if (position > 1) {
RtcSettings.energy_usage.usage2_kWhtotal = values[1];
}
Settings.energy_usage.usage1_kWhtotal = RtcSettings.energy_usage.usage1_kWhtotal;
@@ -544,10 +543,10 @@ void CmndEnergyReset(void)
break;
case 5:
// Reset energy_usage.return totals
- if (position > -1) {
+ if (position > 0) {
RtcSettings.energy_usage.return1_kWhtotal = values[0];
}
- if (position > 0) {
+ if (position > 1) {
RtcSettings.energy_usage.return2_kWhtotal = values[1];
}
Settings.energy_usage.return1_kWhtotal = RtcSettings.energy_usage.return1_kWhtotal;
@@ -1062,15 +1061,12 @@ void EnergyShow(bool json)
#ifdef USE_WEBSERVER
} else {
if (Energy.voltage_available) {
- WSContentSend_PD(PSTR("{s}" D_VOLTAGE "{m}%s " D_UNIT_VOLT "{e}"),
- EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common));
+ WSContentSend_PD(HTTP_SNS_VOLTAGE, EnergyFormat(value_chr, voltage_chr[0], json, Energy.voltage_common));
}
if (Energy.current_available) {
- WSContentSend_PD(PSTR("{s}" D_CURRENT "{m}%s " D_UNIT_AMPERE "{e}"),
- EnergyFormat(value_chr, current_chr[0], json));
+ WSContentSend_PD(HTTP_SNS_CURRENT, EnergyFormat(value_chr, current_chr[0], json));
}
- WSContentSend_PD(PSTR("{s}" D_POWERUSAGE "{m}%s " D_UNIT_WATT "{e}"),
- EnergyFormat(value_chr, active_power_chr[0], json));
+ WSContentSend_PD(HTTP_SNS_POWER, EnergyFormat(value_chr, active_power_chr[0], json));
if (!Energy.type_dc) {
if (Energy.current_available && Energy.voltage_available) {
WSContentSend_PD(HTTP_ENERGY_SNS1, EnergyFormat(value_chr, apparent_power_chr[0], json),
diff --git a/tasmota/xdrv_04_light.ino b/tasmota/xdrv_04_light.ino
index 7f1e66fc6..4c331b273 100644
--- a/tasmota/xdrv_04_light.ino
+++ b/tasmota/xdrv_04_light.ino
@@ -1,7 +1,7 @@
/*
xdrv_04_light.ino - PWM, WS2812 and sonoff led support for Tasmota
- Copyright (C) 2019 Theo Arends
+ Copyright (C) 2020 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
@@ -79,6 +79,7 @@
* .b For white bulbs with Cold/Warm colortone, use changeCW() or changeCT()
* to change color-tone. Set overall brightness separately.
* Color-tone temperature can range from 153 (Cold) to 500 (Warm).
+ * SetOption82 can expand the rendering from 200-380 due to Alexa reduced range.
* CW channels are stored at full brightness to avoid rounding errors.
* .c Alternatively, you can set all 5 channels at once with changeChannels(),
* in this case it will also set the corresponding brightness.
@@ -112,12 +113,11 @@
* to adjust leds with different power
* .g If rgbwwTable[4] is zero, blend RGB with White and adjust the level of
* White channel according to rgbwwTable[3]
- * .h Avoid PMW values between 1008 and 1022, issue #1146
- * .i Scale ranges from 10 bits to 0..PWMRange (by default 1023) so no change
+ * .h Scale ranges from 10 bits to 0..PWMRange (by default 1023) so no change
* by default.
- * .j Apply port remapping from Option37
- * .k Invert PWM value if port is of type PMWxi instead of PMWx
- * .l Apply PWM value with analogWrite() - if pin is configured
+ * .i Apply port remapping from Option37
+ * .j Invert PWM value if port is of type PMWxi instead of PMWx
+ * .k Apply PWM value with analogWrite() - if pin is configured
*
\*********************************************************************************************/
@@ -161,73 +161,89 @@ struct LCwColor {
const uint8_t MAX_FIXED_COLD_WARM = 4;
const LCwColor kFixedColdWarm[MAX_FIXED_COLD_WARM] PROGMEM = { 0,0, 255,0, 0,255, 128,128 };
-// New version of Gamma correction table, with adaptative resolution
-// from 11 bits (lower values) to 8 bits (upper values).
-// We're using the fact that lower values are small and can fit within 8 bits
-// To save flash space, the array is only 8 bits uint
-#ifdef XFUNC_PTR_IN_ROM
-const uint8_t _ledTable[] PROGMEM = {
-#else
-const uint8_t _ledTable[] = {
-#endif
- // 11 bits resolution
- 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, // 11 bits, 0..2047
- 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6, 6, 7, // 11 bits, 0..2047
- 7, 8, 8, 9, 10, 10, 11, 12, 12, 13, 14, 15, 16, 17, 18, 19, // 11 bits, 0..2047
- 20, 21, 22, 24, 25, 26, 28, 29, 30, 32, 33, 35, 37, 38, 40, 42, // 11 bits, 0..2047
- // 10 bits resolution
- 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 36, 37, 38, 39, // 10 bits, 0..1023
- 41, 42, 44, 45, 47, 48, 50, 51, 53, 55, 56, 58, 60, 62, 64, 65, // 10 bits, 0..1023
- 67, 69, 71, 73, 75, 78, 80, 82, 84, 86, 89, 91, 93, 96, 98,101, // 10 bits, 0..1023
- 103,106,108,111,114,116,119,122,125,128,131,134,137,140,143,146, // 10 bits, 0..1023
- // 9 bits resolution
- 75, 77, 78, 80, 82, 84, 85, 87, 89, 91, 93, 94, 96, 98,100,102, // 9 bits, 0..511
- 104,106,108,110,112,115,117,119,121,123,125,128,130,132,135,137, // 9 bits, 0..511
- 140,142,144,147,149,152,155,157,160,163,165,168,171,173,176,179, // 9 bits, 0..511
- 182,185,188,191,194,197,200,203,206,209,212,215,219,222,225,229, // 9 bits, 0..511
- // 8 bits resolution
- 116,118,120,121,123,125,127,128,130,132,134,136,138,139,141,143, // 8 bits, 0..255
- 145,147,149,151,153,155,157,159,161,163,165,168,170,172,174,176, // 8 bits, 0..255
- 178,181,183,185,187,190,192,194,197,199,201,204,206,209,211,214, // 8 bits, 0..255
- 216,219,221,224,226,229,232,234,237,240,242,245,248,250,253,255 // 8 bits, 0..255
+// CT min and max
+const uint16_t CT_MIN = 153; // 6500K
+const uint16_t CT_MAX = 500; // 2000K
+// Ranges used for Alexa
+const uint16_t CT_MIN_ALEXA = 200; // also 5000K
+const uint16_t CT_MAX_ALEXA = 380; // also 2600K
+
+// New version of Gamma correction compute
+// Instead of a table, we do a multi-linear approximation, which is close enough
+// At low levels, the slope is a bit higher than actual gamma, to make changes smoother
+// Internal resolution is 10 bits.
+
+typedef struct gamma_table_t {
+ uint16_t to_src;
+ uint16_t to_gamma;
+} gamma_table_t;
+
+const gamma_table_t gamma_table[] = { // don't put in PROGMEM for performance reasons
+ { 1, 1 },
+ { 4, 1 },
+ { 209, 13 },
+ { 312, 41 },
+ { 457, 106 },
+ { 626, 261 },
+ { 762, 450 },
+ { 895, 703 },
+ { 1023, 1023 },
+ { 0xFFFF, 0xFFFF } // fail-safe if out of range
+};
+
+// simplified Gamma table for Fade, cheating a little at low brightness
+const gamma_table_t gamma_table_fast[] = {
+ { 384, 192 },
+ { 768, 576 },
+ { 1023, 1023 },
+ { 0xFFFF, 0xFFFF } // fail-safe if out of range
};
// For reference, below are the computed gamma tables, via ledGamma()
// for 8 bits output:
-// 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-// 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-// 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3,
-// 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6,
-// 6, 6, 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 10, 10, 10,
-// 11, 11, 11, 12, 12, 12, 13, 13, 14, 14, 14, 15, 15, 16, 16, 17,
-// 17, 18, 18, 19, 19, 20, 20, 21, 21, 22, 23, 23, 24, 24, 25, 26,
-// 26, 27, 27, 28, 29, 29, 30, 31, 32, 32, 33, 34, 35, 35, 36, 37,
-// 38, 39, 39, 40, 41, 42, 43, 44, 45, 46, 47, 47, 48, 49, 50, 51,
-// 52, 53, 54, 55, 56, 58, 59, 60, 61, 62, 63, 64, 65, 66, 68, 69,
-// 70, 71, 72, 74, 75, 76, 78, 79, 80, 82, 83, 84, 86, 87, 88, 90,
-// 91, 93, 94, 96, 97, 99,100,102,103,105,106,108,110,111,113,115,
-//116,118,120,121,123,125,127,128,130,132,134,136,138,139,141,143,
-//145,147,149,151,153,155,157,159,161,163,165,168,170,172,174,176,
-//178,181,183,185,187,190,192,194,197,199,201,204,206,209,211,214,
-//216,219,221,224,226,229,232,234,237,240,242,245,248,250,253,255
+// 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+// 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2,
+// 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3,
+// 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 6, 6, 6,
+// 6, 7, 7, 7, 7, 8, 8, 8, 9, 9, 9, 9, 10, 10, 10, 11,
+// 11, 12, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 17, 18,
+// 18, 19, 19, 20, 20, 21, 21, 21, 22, 22, 23, 23, 24, 24, 25, 25,
+// 25, 26, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 36, 37, 38,
+// 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 50, 51, 52, 53,
+// 54, 55, 56, 57, 58, 59, 60, 61, 61, 62, 63, 64, 65, 67, 68, 69,
+// 71, 72, 73, 75, 76, 78, 79, 80, 82, 83, 85, 86, 87, 89, 90, 91,
+// 93, 94, 95, 97, 98,100,101,102,104,105,107,108,109,111,112,114,
+// 116,118,120,122,124,125,127,129,131,133,135,137,139,141,143,144,
+// 146,148,150,152,154,156,158,160,162,164,166,168,170,171,173,175,
+// 178,180,183,185,188,190,193,195,198,200,203,205,208,210,213,215,
+// 218,220,223,225,228,230,233,235,238,240,243,245,248,250,253,255
//
// and for 10 bits output:
-// 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-// 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 4,
-// 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 8, 8, 9, 9, 10,
-// 10, 11, 11, 12, 13, 13, 14, 15, 15, 16, 17, 18, 19, 19, 20, 21,
-// 22, 23, 24, 25, 26, 27, 28, 30, 31, 32, 33, 34, 36, 37, 38, 39,
-// 41, 42, 44, 45, 47, 48, 50, 51, 53, 55, 56, 58, 60, 62, 64, 65,
-// 67, 69, 71, 73, 75, 78, 80, 82, 84, 86, 89, 91, 93, 96, 98,101,
-//103,106,108,111,114,116,119,122,125,128,131,134,137,140,143,146,
-//151,155,157,161,165,169,171,175,179,183,187,189,193,197,201,205,
-//209,213,217,221,225,231,235,239,243,247,251,257,261,265,271,275,
-//281,285,289,295,299,305,311,315,321,327,331,337,343,347,353,359,
-//365,371,377,383,389,395,401,407,413,419,425,431,439,445,451,459,
-//467,475,483,487,495,503,511,515,523,531,539,547,555,559,567,575,
-//583,591,599,607,615,623,631,639,647,655,663,675,683,691,699,707,
-//715,727,735,743,751,763,771,779,791,799,807,819,827,839,847,859,
-//867,879,887,899,907,919,931,939,951,963,971,983,995,1003,1015,1023
+// 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4,
+// 5, 5, 5, 5, 5, 6, 6, 6, 6, 7, 7, 7, 7, 8, 8, 8,
+// 8, 8, 9, 9, 9, 9, 10, 10, 10, 10, 11, 11, 11, 11, 12, 12,
+// 12, 12, 13, 13, 13, 14, 15, 16, 17, 18, 20, 21, 22, 23, 24, 25,
+// 26, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43,
+// 45, 47, 49, 50, 52, 54, 56, 58, 59, 61, 63, 65, 67, 68, 70, 72,
+// 74, 76, 77, 79, 81, 83, 84, 86, 88, 90, 92, 93, 95, 97, 99, 101,
+// 102, 104, 106, 110, 113, 117, 121, 124, 128, 132, 135, 139, 143, 146, 150, 154,
+// 158, 162, 166, 169, 173, 177, 180, 184, 188, 191, 195, 199, 202, 206, 210, 213,
+// 217, 221, 224, 228, 232, 235, 239, 243, 246, 250, 254, 257, 261, 267, 272, 278,
+// 283, 289, 294, 300, 305, 311, 317, 322, 328, 333, 339, 344, 350, 356, 361, 367,
+// 372, 378, 383, 389, 394, 400, 406, 411, 417, 422, 428, 433, 439, 444, 450, 458,
+// 465, 473, 480, 488, 496, 503, 511, 518, 526, 534, 541, 549, 557, 564, 572, 579,
+// 587, 595, 602, 610, 617, 627, 635, 642, 650, 657, 665, 673, 680, 688, 695, 703,
+// 713, 723, 733, 743, 753, 763, 773, 783, 793, 803, 813, 823, 833, 843, 853, 863,
+// 873, 883, 893, 903, 913, 923, 933, 943, 953, 963, 973, 983, 993,1003,1013,1023
+//
+// Output for Dimmer 0..100 values
+// 0, 1, 2, 3, 3, 4, 4, 5, 5, 6, 7, 7, 8, 8, 9,
+// 10, 10, 11, 12, 12, 13, 15, 17, 21, 23, 26, 28, 31, 34, 37,
+// 40, 43, 49, 52, 58, 61, 67, 70, 76, 79, 84, 90, 93, 99,102,
+// 110,117,128,135,146,158,166,177,184,195,202,213,221,232,239,
+// 250,261,272,289,300,317,328,344,356,372,389,400,417,428,444,
+// 458,480,496,518,534,557,579,595,617,635,657,673,695,713,743,
+// 773,793,823,843,873,893,923,943,973,993,1023
struct LIGHT {
uint32_t strip_timer_counter = 0; // Bars and Gradient
@@ -255,15 +271,13 @@ struct LIGHT {
bool update = true;
bool pwm_multi_channels = false; // SetOption68, treat each PWM channel as an independant dimmer
+ bool fade_initialized = false; // dont't fade at startup
bool fade_running = false;
- uint8_t fade_start_8[LST_MAX] = {0,0,0,0,0};
- uint8_t fade_cur_8[LST_MAX];
- uint8_t fade_end_8[LST_MAX]; // 8 bits resolution target channel values
uint16_t fade_start_10[LST_MAX] = {0,0,0,0,0};
uint16_t fade_cur_10[LST_MAX];
uint16_t fade_end_10[LST_MAX]; // 10 bits resolution target channel values
- uint16_t fade_counter = 0; // fade timer in ticks (50ms)
- uint16_t fade_duration = 0; // duration of fade in ticks (50ms)
+ uint16_t fade_duration = 0; // duration of fade in milliseconds
+ uint32_t fade_start = 0; // fade start time in milliseconds, compared to millis()
} Light;
power_t LightPower(void)
@@ -271,6 +285,16 @@ power_t LightPower(void)
return Light.power; // Make external
}
+// IRAM variant for rotary
+#ifndef ARDUINO_ESP8266_RELEASE_2_3_0 // Fix core 2.5.x ISR not in IRAM Exception
+power_t LightPowerIRAM(void) ICACHE_RAM_ATTR;
+#endif // ARDUINO_ESP8266_RELEASE_2_3_0
+
+power_t LightPowerIRAM(void)
+{
+ return Light.power; // Make external
+}
+
uint8_t LightDevice(void)
{
return Light.device; // Make external
@@ -328,12 +352,19 @@ class LightStateClass {
uint8_t _b = 255; // 0..255
uint8_t _subtype = 0; // local copy of Light.subtype, if we need multiple lights
- uint16_t _ct = 153; // 153..500, default to 153 (cold white)
+ uint16_t _ct = CT_MIN; // 153..500, default to 153 (cold white)
uint8_t _wc = 255; // white cold channel
uint8_t _ww = 0; // white warm channel
uint8_t _briCT = 255;
uint8_t _color_mode = LCM_RGB; // RGB by default
+ // the CT range below represents the rendered range,
+ // This is due to Alexa whose CT range is 199..383
+ // Hence setting Min=200 and Max=380 makes Alexa use the full range
+ // Please note that you can still set CT to 153..500, but any
+ // value below _ct_min_range or above _ct_max_range not change the CT
+ uint16_t _ct_min_range = CT_MIN; // the minimum CT rendered range
+ uint16_t _ct_max_range = CT_MAX; // the maximum CT rendered range
public:
LightStateClass() {
@@ -351,7 +382,7 @@ class LightStateClass {
// LST_COLDWARM: LCM_CT
// LST_RGB: LCM_RGB
// LST_RGBW: LCM_RGB, LCM_CT or LCM_BOTH
- // LST_RGBWC: LCM_RGB, LCM_CT or LCM_BOTH
+ // LST_RGBCW: LCM_RGB, LCM_CT or LCM_BOTH
uint8_t setColorMode(uint8_t cm) {
uint8_t prev_cm = _color_mode;
if (cm < LCM_RGB) { cm = LCM_RGB; }
@@ -371,7 +402,7 @@ class LightStateClass {
break;
case LST_RGBW:
- case LST_RGBWC:
+ case LST_RGBCW:
_color_mode = cm;
break;
}
@@ -482,8 +513,23 @@ class LightStateClass {
return BriToDimmer(bri);
}
- inline uint16_t getCT() {
- return _ct; // 153..500
+ inline uint16_t getCT() const {
+ return _ct; // 153..500, or CT_MIN..CT_MAX
+ }
+
+ // get the CT value within the range into a 10 bits 0..1023 value
+ uint16_t getCT10bits() const {
+ return changeUIntScale(_ct, _ct_min_range, _ct_max_range, 0, 1023);
+ }
+
+ inline void setCTRange(uint16_t ct_min_range, uint16_t ct_max_range) {
+ _ct_min_range = ct_min_range;
+ _ct_max_range = ct_max_range;
+ }
+
+ inline void getCTRange(uint16_t *ct_min_range, uint16_t *ct_max_range) const {
+ if (ct_min_range) { *ct_min_range = _ct_min_range; }
+ if (ct_max_range) { *ct_max_range = _ct_max_range; }
}
// get current color in XY format
@@ -530,8 +576,8 @@ class LightStateClass {
// disable ct mode
setColorMode(LCM_RGB); // try deactivating CT mode, setColorMode() will check which is legal
} else {
- ct = (ct < 153 ? 153 : (ct > 500 ? 500 : ct));
- _ww = changeUIntScale(ct, 153, 500, 0, 255);
+ ct = (ct < CT_MIN ? CT_MIN : (ct > CT_MAX ? CT_MAX : ct));
+ _ww = changeUIntScale(ct, _ct_min_range, _ct_max_range, 0, 255);
_wc = 255 - _ww;
_ct = ct;
addCTMode();
@@ -571,7 +617,7 @@ class LightStateClass {
_ww = changeUIntScale(w, 0, max, 0, 255);
_wc = changeUIntScale(c, 0, max, 0, 255);
}
- _ct = changeUIntScale(w, 0, sum, 153, 500);
+ _ct = changeUIntScale(w, 0, sum, _ct_min_range, _ct_max_range);
addCTMode(); // activate CT mode if needed
if (_color_mode & LCM_CT) { _briCT = free_range ? max : (sum > 255 ? 255 : sum); }
}
@@ -844,6 +890,15 @@ public:
return prev;
}
+ void setAlexaCTRange(bool alexa_ct_range) {
+ // depending on SetOption82, full or limited CT range
+ if (alexa_ct_range) {
+ _state->setCTRange(CT_MIN_ALEXA, CT_MAX_ALEXA); // 200..380
+ } else {
+ _state->setCTRange(CT_MIN, CT_MAX); // 153..500
+ }
+ }
+
inline bool isCTRGBLinked() {
return _ct_rgb_linked;
}
@@ -910,8 +965,8 @@ public:
void changeCTB(uint16_t new_ct, uint8_t briCT) {
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
*
- * ct = 153 = 2000K = Warm = CCWW = 00FF
- * ct = 500 = 6500K = Cold = CCWW = FF00
+ * ct = 153 = 6500K = Cold = CCWW = FF00
+ * ct = 500 = 2000K = Warm = CCWW = 00FF
*/
// don't set CT if not supported
if ((LST_COLDWARM != Light.subtype) && (LST_RGBW > Light.subtype)) {
@@ -930,9 +985,11 @@ public:
switch (mode) {
case 1:
changeBriRGB(bri);
+ if (_ct_rgb_linked) { _state->setColorMode(LCM_RGB); } // try to force CT
break;
case 2:
changeBriCT(bri);
+ if (_ct_rgb_linked) { _state->setColorMode(LCM_CT); } // try to force CT
break;
default:
changeBri(bri);
@@ -994,8 +1051,8 @@ public:
current_color[1] = w;
break;
case LST_RGBW:
- case LST_RGBWC:
- if (LST_RGBWC == Light.subtype) {
+ case LST_RGBCW:
+ if (LST_RGBCW == Light.subtype) {
current_color[3] = c;
current_color[4] = w;
} else {
@@ -1072,36 +1129,64 @@ public:
LightStateClass light_state = LightStateClass();
LightControllerClass light_controller = LightControllerClass(light_state);
+/*********************************************************************************************\
+ * Change scales from 8 bits to 10 bits and vice versa
+\*********************************************************************************************/
+// 8 to 10 to 8 is garanteed to give the same result
+uint16_t change8to10(uint8_t v) {
+ return changeUIntScale(v, 0, 255, 0, 1023);
+}
+// change from 10 bits to 8 bits, but any non-zero input will be non-zero
+uint8_t change10to8(uint16_t v) {
+ return (0 == v) ? 0 : changeUIntScale(v, 4, 1023, 1, 255);
+}
+
/*********************************************************************************************\
* Gamma correction
\*********************************************************************************************/
// Calculate the gamma corrected value for LEDS
-// You can request 11, 10, 9 or 8 bits resolution via 'bits_out' parameter
-uint16_t ledGamma(uint8_t v, uint16_t bits_out = 8) {
- uint16_t result;
- // bits_resolution: the resolution of _ledTable[v], between 8 and 11
- uint32_t bits_resolution = 11 - (v / 64); // 8..11
- int32_t bits_correction = bits_out - bits_resolution; // -3..3
-#ifdef XFUNC_PTR_IN_ROM
- uint32_t uncorrected_value = pgm_read_byte(_ledTable + v); // 0..255
-#else
- uint32_t uncorrected_value = _ledTable[v]; // 0..255
-#endif
- if (0 == bits_correction) {
- // we already match the required resolution, no change
- result = uncorrected_value;
- } else if (bits_correction > 0) {
- // the output resolution is higher than our value, we need to extrapolate
- // we shift by bits_correction, and force last bits to 1
- uint32_t bits_mask = (1 << bits_correction) - 1; // 1, 3, 7
- result = (uncorrected_value << bits_correction) | bits_mask;
- } else { // bits_correction < 0
- // our resolution is too high, we need to remove bits
- // we add 1, 3 or 7 to force rouding to the nearest high value
- uint32_t bits_mask = (1 << -bits_correction) - 1; // 1, 3, 7
- result = ((uncorrected_value + bits_mask) >> -bits_correction);
+uint16_t ledGamma_internal(uint16_t v, const struct gamma_table_t *gt_ptr) {
+ uint16_t from_src = 0;
+ uint16_t from_gamma = 0;
+
+ for (const gamma_table_t *gt = gt_ptr; ; gt++) {
+ uint16_t to_src = gt->to_src;
+ uint16_t to_gamma = gt->to_gamma;
+ if (v <= to_src) {
+ return changeUIntScale(v, from_src, to_src, from_gamma, to_gamma);
+ }
+ from_src = to_src;
+ from_gamma = to_gamma;
}
- return result;
+}
+// Calculate the reverse gamma value for LEDS
+uint16_t ledGammaReverse_internal(uint16_t vg, const struct gamma_table_t *gt_ptr) {
+ uint16_t from_src = 0;
+ uint16_t from_gamma = 0;
+
+ for (const gamma_table_t *gt = gt_ptr; ; gt++) {
+ uint16_t to_src = gt->to_src;
+ uint16_t to_gamma = gt->to_gamma;
+ if (vg <= to_gamma) {
+ return changeUIntScale(vg, from_gamma, to_gamma, from_src, to_src);
+ }
+ from_src = to_src;
+ from_gamma = to_gamma;
+ }
+}
+
+// 10 bits in, 10 bits out
+uint16_t ledGamma10_10(uint16_t v) {
+ return ledGamma_internal(v, gamma_table);
+}
+// 10 bits resolution, 8 bits in
+uint16_t ledGamma10(uint8_t v) {
+ return ledGamma10_10(change8to10(v));
+}
+
+// Legacy function
+uint8_t ledGamma(uint8_t v) {
+ return change10to8(ledGamma10(v));
}
/********************************************************************************************/
@@ -1189,6 +1274,7 @@ void LightInit(void)
light_controller.setSubType(Light.subtype);
light_controller.loadSettings();
+ light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
if (LST_SINGLE == Light.subtype) {
Settings.light_color[0] = 255; // One channel only supports Dimmer but needs max color
@@ -1250,11 +1336,15 @@ void LightUpdateColorMapping(void)
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("%d colors: %d %d %d %d %d") ,Settings.param[P_RGB_REMAP], Light.color_remap[0],Light.color_remap[1],Light.color_remap[2],Light.color_remap[3],Light.color_remap[4]);
}
+uint8_t LightGetDimmer(uint8_t dimmer) {
+ return light_state.getDimmer(dimmer);
+}
+
void LightSetDimmer(uint8_t dimmer) {
light_controller.changeDimmer(dimmer);
}
-uint32_t LightGetHSB(uint16_t *hue,uint8_t *sat, uint8_t *bri) {
+void LightGetHSB(uint16_t *hue, uint8_t *sat, uint8_t *bri) {
light_state.getHSB(hue, sat, bri);
}
@@ -1307,11 +1397,11 @@ void LightSetColorTemp(uint16_t ct)
{
/* Color Temperature (https://developers.meethue.com/documentation/core-concepts)
*
- * ct = 153 = 2000K = Warm = CCWW = 00FF
- * ct = 500 = 6500K = Cold = CCWW = FF00
+ * ct = 153 = 6500K = Cold = CCWW = FF00
+ * ct = 600 = 2000K = Warm = CCWW = 00FF
*/
// don't set CT if not supported
- if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
+ if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
return;
}
light_controller.changeCTB(ct, light_state.getBriCT());
@@ -1320,7 +1410,7 @@ void LightSetColorTemp(uint16_t ct)
uint16_t LightGetColorTemp(void)
{
// don't calculate CT for unsupported devices
- if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
+ if ((LST_COLDWARM != Light.subtype) && (LST_RGBCW != Light.subtype)) {
return 0;
}
return (light_state.getColorMode() & LCM_CT) ? light_state.getCT() : 0;
@@ -1391,13 +1481,23 @@ void LightState(uint8_t append)
if (Light.subtype > LST_SINGLE) {
ResponseAppend_P(PSTR(",\"" D_CMND_COLOR "\":\"%s\""), LightGetColor(scolor));
- uint16_t hue;
- uint8_t sat, bri;
- light_state.getHSB(&hue, &sat, &bri);
- sat = changeUIntScale(sat, 0, 255, 0, 100);
- bri = changeUIntScale(bri, 0, 255, 0, 100);
+ if (LST_RGB <= Light.subtype) {
+ uint16_t hue;
+ uint8_t sat, bri;
+ light_state.getHSB(&hue, &sat, &bri);
+ sat = changeUIntScale(sat, 0, 255, 0, 100);
+ bri = changeUIntScale(bri, 0, 255, 0, 100);
- ResponseAppend_P(PSTR(",\"" D_CMND_HSBCOLOR "\":\"%d,%d,%d\""), hue,sat,bri);
+ ResponseAppend_P(PSTR(",\"" D_CMND_HSBCOLOR "\":\"%d,%d,%d\""), hue,sat,bri);
+ }
+ // Add White level
+ if ((LST_COLDWARM == Light.subtype) || (LST_RGBW <= Light.subtype)) {
+ ResponseAppend_P(PSTR(",\"" D_CMND_WHITE "\":%d"), light_state.getDimmer(2));
+ }
+ // Add CT
+ if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
+ ResponseAppend_P(PSTR(",\"" D_CMND_COLORTEMPERATURE "\":%d"), light_state.getCT());
+ }
// Add status for each channel
ResponseAppend_P(PSTR(",\"" D_CMND_CHANNEL "\":[" ));
for (uint32_t i = 0; i < Light.subtype; i++) {
@@ -1409,9 +1509,6 @@ void LightState(uint8_t append)
}
ResponseAppend_P(PSTR("]"));
}
- if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
- ResponseAppend_P(PSTR(",\"" D_CMND_COLORTEMPERATURE "\":%d"), light_state.getCT());
- }
if (append) {
if (Light.subtype >= LST_RGB) {
@@ -1519,13 +1616,21 @@ void LightCycleColor(int8_t direction)
if (0 == direction) {
if (Light.random == Light.wheel) {
Light.random = random(255);
+
+ uint8_t my_dir = (Light.random < Light.wheel -128) ? 1 :
+ (Light.random < Light.wheel ) ? 0 :
+ (Light.random > Light.wheel +128) ? 0 : 1; // Increment or Decrement and roll-over
+ Light.random = (Light.random & 0xFE) | my_dir;
+
+// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: random %d"), Light.random);
}
- direction = (Light.random < Light.wheel) ? -1 : 1;
+// direction = (Light.random < Light.wheel) ? -1 : 1;
+ direction = (Light.random &0x01) ? 1 : -1;
}
Light.wheel += direction;
uint16_t hue = changeUIntScale(Light.wheel, 0, 255, 0, 359); // Scale to hue to keep amount of steps low (max 255 instead of 359)
-// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("DBG: random %d, wheel %d, hue %d"), Light.random, Light.wheel, hue);
+// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: random %d, wheel %d, hue %d"), Light.random, Light.wheel, hue);
uint8_t sat;
light_state.getHSB(nullptr, &sat, nullptr); // Allow user control over Saturation
@@ -1569,25 +1674,31 @@ void LightSetPower(void)
// Light.power tells which lights or channels (SetOption68) are on/off
void LightAnimate(void)
{
- uint8_t cur_col[LST_MAX];
uint16_t light_still_on = 0;
bool power_off = false;
+ // make sure we update CT range in case SetOption82 was changed
+ light_controller.setAlexaCTRange(Settings.flag4.alexa_ct_range);
Light.strip_timer_counter++;
- if (!Light.power) { // All channels powered off
- Light.strip_timer_counter = 0;
- if (!Light.fade_running) {
- sleep = Settings.sleep;
- }
- if (Settings.light_scheme >= LS_MAX) {
- power_off = true;
- }
- } else {
+
+ // set sleep parameter: either settings,
+ // or set a maximum of PWM_MAX_SLEEP if light is on or Fade is running
+ if (Light.power || Light.fade_running) {
if (Settings.sleep > PWM_MAX_SLEEP) {
sleep = PWM_MAX_SLEEP; // set a maxumum value of 50 milliseconds to ensure that animations are smooth
} else {
sleep = Settings.sleep; // or keep the current sleep if it's lower than 50
}
+ } else {
+ sleep = Settings.sleep;
+ }
+
+ if (!Light.power) { // All channels powered off
+ Light.strip_timer_counter = 0;
+ if (Settings.light_scheme >= LS_MAX) {
+ power_off = true;
+ }
+ } else {
switch (Settings.light_scheme) {
case LS_POWER:
light_controller.calcLevels(Light.new_color);
@@ -1612,8 +1723,16 @@ void LightAnimate(void)
Light.new_color[i] = Light.current_color[i];
}
} else {
+/*
Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\"}"));
MqttPublishPrefixTopic_P(TELE, PSTR(D_CMND_WAKEUP));
+*/
+ Response_P(PSTR("{\"" D_CMND_WAKEUP "\":\"" D_JSON_DONE "\""));
+ LightState(1);
+ ResponseJsonEnd();
+ MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_WAKEUP));
+ XdrvRulesProcess();
+
Light.wakeup_active = 0;
Settings.light_scheme = LS_POWER;
}
@@ -1648,159 +1767,190 @@ void LightAnimate(void)
Light.update = true;
}
if (Light.update) {
- uint16_t cur_col_10bits[LST_MAX]; // 10 bits version of cur_col for PWM
+#ifdef USE_DEVICE_GROUPS
+ if (Light.power) LightSendDeviceGroupStatus();
+#endif // USE_DEVICE_GROUPS
+
+ uint16_t cur_col_10[LST_MAX]; // 10 bits resolution
Light.update = false;
// first set 8 and 10 bits channels
for (uint32_t i = 0; i < LST_MAX; i++) {
- cur_col[i] = Light.last_color[i] = Light.new_color[i];
+ Light.last_color[i] = Light.new_color[i];
// Extend from 8 to 10 bits if no correction (in case no gamma correction is required)
- cur_col_10bits[i] = changeUIntScale(cur_col[i], 0, 255, 0, 1023);
+ cur_col_10[i] = change8to10(Light.new_color[i]);
}
if (Light.pwm_multi_channels) {
- calcGammaMultiChannels(cur_col, cur_col_10bits);
+ calcGammaMultiChannels(cur_col_10);
} else {
- calcGammaBulbs(cur_col, cur_col_10bits);
- if (PHILIPS == my_module_type) {
- calcGammaCTPwm(cur_col, cur_col_10bits);
- }
+ calcGammaBulbs(cur_col_10);
// Now see if we need to mix RGB and True White
- // Valid only for LST_RGBW, LST_RGBWC, rgbwwTable[4] is zero, and white is zero (see doc)
- if ((LST_RGBW <= Light.subtype) && (0 == Settings.rgbwwTable[4]) && (0 == cur_col[3]+cur_col[4])) {
- uint32_t min_rgb_10 = min3(cur_col_10bits[0], cur_col_10bits[1], cur_col_10bits[2]);
- uint8_t min_rgb = min3(cur_col[0], cur_col[1], cur_col[2]);
+ // Valid only for LST_RGBW, LST_RGBCW, rgbwwTable[4] is zero, and white is zero (see doc)
+ if ((LST_RGBW <= Light.subtype) && (0 == Settings.rgbwwTable[4]) && (0 == cur_col_10[3]+cur_col_10[4])) {
+ uint32_t min_rgb_10 = min3(cur_col_10[0], cur_col_10[1], cur_col_10[2]);
for (uint32_t i=0; i<3; i++) {
// substract white and adjust according to rgbwwTable
- uint32_t adjust10 = changeUIntScale(Settings.rgbwwTable[i], 0, 255, 0, 1023);
- cur_col_10bits[i] = changeUIntScale(cur_col_10bits[i] - min_rgb_10, 0, 1023, 0, adjust10);
- cur_col[i] = changeUIntScale(cur_col[i] - min_rgb, 0, 255, 0, Settings.rgbwwTable[i]);
+ uint32_t adjust10 = change8to10(Settings.rgbwwTable[i]);
+ cur_col_10[i] = changeUIntScale(cur_col_10[i] - min_rgb_10, 0, 1023, 0, adjust10);
}
+
// compute the adjusted white levels for 10 and 8 bits
- uint32_t white_10 = changeUIntScale(min_rgb_10, 0, 255, 0, Settings.rgbwwTable[3]); // set white power down corrected with rgbwwTable[3]
- uint32_t white = changeUIntScale(min_rgb, 0, 255, 0, Settings.rgbwwTable[3]); // set white power down corrected with rgbwwTable[3]
+ uint32_t adjust_w_10 = changeUIntScale(Settings.rgbwwTable[3], 0, 255, 0, 1023);
+ uint32_t white_10 = changeUIntScale(min_rgb_10, 0, 1023, 0, adjust_w_10); // set white power down corrected with rgbwwTable[3]
if (LST_RGBW == Light.subtype) {
// we simply set the white channel
- cur_col_10bits[3] = white_10;
- cur_col[3] = white;
- } else { // LST_RGBWC
+ cur_col_10[3] = white_10;
+ } else { // LST_RGBCW
// we distribute white between cold and warm according to CT value
- uint32_t ct = light_state.getCT();
- cur_col_10bits[4] = changeUIntScale(ct, 153, 500, 0, white_10);
- cur_col_10bits[3] = white_10 - cur_col_10bits[4];
- cur_col[4] = changeUIntScale(ct, 153, 500, 0, white);
- cur_col[3] = white - cur_col[4];
+ uint32_t ct = light_state.getCT10bits();
+ cur_col_10[4] = changeUIntScale(ct, 0, 1023, 0, white_10);
+ cur_col_10[3] = white_10 - cur_col_10[4];
}
}
}
+ // Apply RGBWWTable only if Settings.rgbwwTable[4] != 0
+ if (0 != Settings.rgbwwTable[4]) {
+ for (uint32_t i = 0; i 1008) && (cur_col_10bits[i] < 1023)) {
- cur_col_10bits[i] = 1008;
- }
-#endif
// scale from 0..1023 to 0..pwm_range, but keep any non-zero value to at least 1
- cur_col_10bits[i] = (cur_col_10bits[i] > 0) ? changeUIntScale(cur_col_10bits[i], 1, 1023, 1, Settings.pwm_range) : 0;
+ cur_col_10[i] = (cur_col_10[i] > 0) ? changeUIntScale(cur_col_10[i], 1, 1023, 1, Settings.pwm_range) : 0;
}
// apply port remapping on both 8 bits and 10 bits versions
- uint8_t orig_col[LST_MAX];
uint16_t orig_col_10bits[LST_MAX];
- memcpy(orig_col, cur_col, sizeof(orig_col));
- memcpy(orig_col_10bits, cur_col_10bits, sizeof(orig_col_10bits));
+ memcpy(orig_col_10bits, cur_col_10, sizeof(orig_col_10bits));
for (uint32_t i = 0; i < LST_MAX; i++) {
- cur_col[i] = orig_col[Light.color_remap[i]];
- cur_col_10bits[i] = orig_col_10bits[Light.color_remap[i]];
+ cur_col_10[i] = orig_col_10bits[Light.color_remap[i]];
}
- if (!Settings.light_fade || power_off) { // no fade
+ if (!Settings.light_fade || skip_light_fade || power_off || (!Light.fade_initialized)) { // no fade
// record the current value for a future Fade
- memcpy(Light.fade_start_8, cur_col, sizeof(Light.fade_start_8));
- memcpy(Light.fade_start_10, cur_col_10bits, sizeof(Light.fade_start_10));
+ memcpy(Light.fade_start_10, cur_col_10, sizeof(Light.fade_start_10));
// push the final values at 8 and 10 bits resolution to the PWMs
- LightSetOutputs(cur_col, cur_col_10bits);
+ LightSetOutputs(cur_col_10);
+ Light.fade_initialized = true; // it is now ok to fade
} else { // fade on
if (Light.fade_running) {
// if fade is running, we take the curring value as the start for the next fade
- memcpy(Light.fade_start_8, Light.fade_cur_8, sizeof(Light.fade_start_8));
memcpy(Light.fade_start_10, Light.fade_cur_10, sizeof(Light.fade_start_10));
}
- memcpy(Light.fade_end_8, cur_col, sizeof(Light.fade_start_8));
- memcpy(Light.fade_end_10, cur_col_10bits, sizeof(Light.fade_start_10));
+ memcpy(Light.fade_end_10, cur_col_10, sizeof(Light.fade_start_10));
Light.fade_running = true;
- Light.fade_counter = 0;
Light.fade_duration = 0; // set the value to zero to force a recompute
+ Light.fade_start = 0;
// Fade will applied immediately below
}
}
if (Light.fade_running) {
- LightApplyFade();
- // AddLog_P2(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d - %d %d %d %d %d"),
- // Light.fade_cur_8[0], Light.fade_cur_8[1], Light.fade_cur_8[2], Light.fade_cur_8[3], Light.fade_cur_8[4],
- // Light.fade_cur_10[0], Light.fade_cur_10[1], Light.fade_cur_10[2], Light.fade_cur_10[3], Light.fade_cur_10[4]);
+ if (LightApplyFade()) {
+ // AddLog_P2(LOG_LEVEL_INFO, PSTR("LightApplyFade %d %d %d %d %d"),
+ // Light.fade_cur_10[0], Light.fade_cur_10[1], Light.fade_cur_10[2], Light.fade_cur_10[3], Light.fade_cur_10[4]);
- LightSetOutputs(Light.fade_cur_8, Light.fade_cur_10);
+ LightSetOutputs(Light.fade_cur_10);
+ }
}
}
}
-void LightApplyFade(void) {
+bool isChannelGammaCorrected(uint32_t channel) {
+ if (!Settings.light_correction) { return false; } // Gamma correction not activated
+ if (channel >= Light.subtype) { return false; } // Out of range
+
+ if (PHILIPS == my_module_type) {
+ if ((LST_COLDWARM == Light.subtype) && (1 == channel)) { return false; } // PMW reserved for CT
+ if ((LST_RGBCW == Light.subtype) && (4 == channel)) { return false; } // PMW reserved for CT
+ }
+ return true;
+}
+
+// Calculate the Gamma correction, if any, for fading, using the fast Gamma curve (10 bits in+out)
+uint16_t fadeGamma(uint32_t channel, uint16_t v) {
+ if (isChannelGammaCorrected(channel)) {
+ return ledGamma_internal(v, gamma_table_fast);
+ } else {
+ return v;
+ }
+}
+uint16_t fadeGammaReverse(uint32_t channel, uint16_t vg) {
+ if (isChannelGammaCorrected(channel)) {
+ return ledGammaReverse_internal(vg, gamma_table_fast);
+ } else {
+ return vg;
+ }
+}
+
+bool LightApplyFade(void) { // did the value chanegd and needs to be applied
+ static uint32_t last_millis = 0;
+ uint32_t now = millis();
+
+ if ((now - last_millis) <= 5) {
+ return false; // the value was not changed in the last 5 milliseconds, ignore
+ }
+ last_millis = now;
// Check if we need to calculate the duration
if (0 == Light.fade_duration) {
- Light.fade_counter = 0;
+ Light.fade_start = now;
// compute the distance between start and and color (max of distance for each channel)
uint32_t distance = 0;
for (uint32_t i = 0; i < Light.subtype; i++) {
- int32_t channel_distance = Light.fade_end_10[i] - Light.fade_start_10[i];
+ int32_t channel_distance = fadeGammaReverse(i, Light.fade_end_10[i]) - fadeGammaReverse(i, Light.fade_start_10[i]);
if (channel_distance < 0) { channel_distance = - channel_distance; }
if (channel_distance > distance) { distance = channel_distance; }
}
if (distance > 0) {
// compute the duration of the animation
// Note: Settings.light_speed is the number of half-seconds for a 100% fade,
- // i.e. light_speed=1 means 1024 steps in 10 ticks (500ms)
- Light.fade_duration = (distance * Settings.light_speed * 10) / 1024;
- // Also postpone the save_data for the duration of the Fade (in seconds)
- uint32_t delay_seconds = 1 + (Light.fade_duration + 19) / 20; // add one more second
- // AddLog_P2(LOG_LEVEL_INFO, PSTR("delay_seconds %d, save_data_counter %d"), delay_seconds, save_data_counter);
- if (save_data_counter < delay_seconds) {
- save_data_counter = delay_seconds; // pospone
+ // i.e. light_speed=1 means 1024 steps in 500ms
+ Light.fade_duration = (distance * Settings.light_speed * 500) / 1023;
+ if (Settings.save_data) {
+ // Also postpone the save_data for the duration of the Fade (in seconds)
+ uint32_t delay_seconds = 1 + (Light.fade_duration + 999) / 1000; // add one more second
+ // AddLog_P2(LOG_LEVEL_INFO, PSTR("delay_seconds %d, save_data_counter %d"), delay_seconds, save_data_counter);
+ if (save_data_counter < delay_seconds) {
+ save_data_counter = delay_seconds; // pospone
+ }
}
} else {
// no fade needed, we keep the duration at zero, it will fallback directly to end of fade
+ Light.fade_running = false;
}
}
- Light.fade_counter++;
- if (Light.fade_counter <= Light.fade_duration) { // fade not finished
+ uint16_t fade_current = now - Light.fade_start; // number of milliseconds since start of fade
+ if (fade_current <= Light.fade_duration) { // fade not finished
+ //Serial.printf("Fade: %d / %d - ", fade_current, Light.fade_duration);
for (uint32_t i = 0; i < Light.subtype; i++) {
- Light.fade_cur_8[i] = changeUIntScale(Light.fade_counter,
- 0, Light.fade_duration,
- Light.fade_start_8[i], Light.fade_end_8[i]);
- Light.fade_cur_10[i] = changeUIntScale(Light.fade_counter,
- 0, Light.fade_duration,
- Light.fade_start_10[i], Light.fade_end_10[i]);
+ Light.fade_cur_10[i] = fadeGamma(i,
+ changeUIntScale(fadeGammaReverse(i, fade_current),
+ 0, Light.fade_duration,
+ fadeGammaReverse(i, Light.fade_start_10[i]),
+ fadeGammaReverse(i, Light.fade_end_10[i])));
+ // Light.fade_cur_10[i] = changeUIntScale(fade_current,
+ // 0, Light.fade_duration,
+ // Light.fade_start_10[i], Light.fade_end_10[i]);
}
} else {
// stop fade
//AddLop_P2(LOG_LEVEL_DEBUG, PSTR("Stop fade"));
Light.fade_running = false;
- Light.fade_counter = 0;
+ Light.fade_start = 0;
Light.fade_duration = 0;
// set light to target value
- memcpy(Light.fade_cur_8, Light.fade_end_8, sizeof(Light.fade_end_8));
memcpy(Light.fade_cur_10, Light.fade_end_10, sizeof(Light.fade_end_10));
// record the last value for next start
- memcpy(Light.fade_start_8, Light.fade_end_8, sizeof(Light.fade_start_8));
memcpy(Light.fade_start_10, Light.fade_end_10, sizeof(Light.fade_start_10));
}
-
+ return true;
}
// On entry we take the 5 channels 8 bits entry, and we apply Power modifiers
@@ -1836,19 +1986,24 @@ void LightApplyPower(uint8_t new_color[LST_MAX], power_t power) {
}
}
-void LightSetOutputs(const uint8_t *cur_col, const uint16_t *cur_col_10bits) {
+void LightSetOutputs(const uint16_t *cur_col_10) {
// now apply the actual PWM values, adjusted and remapped 10-bits range
if (light_type < LT_PWM6) { // only for direct PWM lights, not for Tuya, Armtronix...
for (uint32_t i = 0; i < (Light.subtype - Light.pwm_offset); i++) {
if (pin[GPIO_PWM1 +i] < 99) {
- //AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "Cur_Col%d 10 bits %d, Pwm%d %d"), i, cur_col_10bits[i], i+1, cur_col[i]);
- analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - cur_col_10bits[(i + Light.pwm_offset)] : cur_col_10bits[(i + Light.pwm_offset)]);
+ //AddLog_P2(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION "Cur_Col%d 10 bits %d"), i, cur_col_10[i]);
+ analogWrite(pin[GPIO_PWM1 +i], bitRead(pwm_inverted, i) ? Settings.pwm_range - cur_col_10[(i + Light.pwm_offset)] : cur_col_10[(i + Light.pwm_offset)]);
}
}
}
- // AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("LGT: R %02X(%d) G %02X(%d) B %02X(%d), CW %02X(%d) WW %02x(%d), D %d"),
- // cur_col[0], cur_col_10bits[0], cur_col[1], cur_col_10bits[1], cur_col[2], cur_col_10bits[2], cur_col[3], cur_col_10bits[3], cur_col[4], cur_col_10bits[4], light_state.getDimmer());
+// char msg[24];
+// AddLog_P2(LOG_LEVEL_DEBUG, PSTR("LGT: Channels %s"), ToHex_P((const unsigned char *)cur_col_10, 10, msg, sizeof(msg)));
+
+ uint8_t cur_col[LST_MAX];
+ for (uint32_t i = 0; i < LST_MAX; i++) {
+ cur_col[i] = change10to8(cur_col_10[i]);
+ }
// Some devices need scaled RGB like Sonoff L1
// TODO, should be probably moved to the Sonoff L1 support code
uint8_t scale_col[3];
@@ -1856,8 +2011,6 @@ void LightSetOutputs(const uint8_t *cur_col, const uint16_t *cur_col_10bits) {
for (uint32_t i = 0; i < 3; i++) {
scale_col[i] = (0 == max) ? 255 : (255 > max) ? changeUIntScale(cur_col[i], 0, max, 0, 255) : cur_col[i];
}
- // AddLog_P2(LOG_LEVEL_DEBUG_MORE, PSTR("LGT: R%d(%d) G%d(%d) B%d(%d), C%d(%d) W%d(%d), D%d"),
- // cur_col[0], scale_col[0], cur_col[1], scale_col[1], cur_col[2], scale_col[2], cur_col[3], scale_col[3], cur_col[4], scale_col[4], light_state.getDimmer());
char *tmp_data = XdrvMailbox.data;
char *tmp_topic = XdrvMailbox.topic;
@@ -1869,83 +2022,154 @@ void LightSetOutputs(const uint8_t *cur_col, const uint16_t *cur_col_10bits) {
XdrvMailbox.topic = tmp_topic;
}
-// Do specific computation is SetOption73 is on, Color Temp is a separate PWM channel
-void calcGammaCTPwm(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
- // Xiaomi Philips bulbs follow a different scheme:
- uint8_t cold, warm; // channel 1 is the color tone, mapped to cold channel (0..255)
- light_state.getCW(&cold, &warm);
- // channels for white are always the last two channels
- uint32_t cw1 = Light.subtype - 1; // address for the ColorTone PWM
- uint32_t cw0 = Light.subtype - 2; // address for the White Brightness PWM
- // overall brightness
- uint16_t pxBri = cur_col[cw0] + cur_col[cw1];
- if (pxBri > 255) { pxBri = 255; }
- cur_col[cw1] = changeUIntScale(cold, 0, cold + warm, 0, 255); //
- cur_col_10bits[cw1] = changeUIntScale(cur_col[cw1], 0, 255, 0, 1023);
- // channel 0=intensity, channel1=temperature
- if (Settings.light_correction) { // gamma correction
- cur_col[cw0] = ledGamma(pxBri);
- cur_col_10bits[cw0] = ledGamma(pxBri, 10); // 10 bits gamma correction
- } else {
- cur_col[cw0] = pxBri;
- cur_col_10bits[cw0] = changeUIntScale(pxBri, 0, 255, 0, 1023); // no gamma, extend to 10 bits
- }
-}
-
// Just apply basic Gamma to each channel
-void calcGammaMultiChannels(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
+void calcGammaMultiChannels(uint16_t cur_col_10[5]) {
// Apply gamma correction for 8 and 10 bits resolutions, if needed
if (Settings.light_correction) {
for (uint32_t i = 0; i < LST_MAX; i++) {
- cur_col_10bits[i] = ledGamma(cur_col[i], 10);
- cur_col[i] = ledGamma(cur_col[i]);
+ cur_col_10[i] = ledGamma10_10(cur_col_10[i]);
}
}
}
-void calcGammaBulbs(uint8_t cur_col[5], uint16_t cur_col_10bits[5]) {
+void calcGammaBulbs(uint16_t cur_col_10[5]) {
// Apply gamma correction for 8 and 10 bits resolutions, if needed
- if (Settings.light_correction) {
- // First apply combined correction to the overall white power
- if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) {
- uint8_t w_idx[2] = {0, 1}; // if LST_COLDWARM, channels 0 and 1
- if (LST_RGBWC == Light.subtype) { // if LST_RGBWC, channels 3 and 4
- w_idx[0] = 3;
- w_idx[1] = 4;
- }
- uint16_t white_bri = cur_col[w_idx[0]] + cur_col[w_idx[1]];
- // if sum of both channels is > 255, then channels are probablu uncorrelated
- if (white_bri <= 255) {
- // we calculate the gamma corrected sum of CW + WW
- uint16_t white_bri_10bits = ledGamma(white_bri, 10);
- uint8_t white_bri_8bits = ledGamma(white_bri);
- // then we split the total energy among the cold and warm leds
- cur_col_10bits[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_10bits);
- cur_col_10bits[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_10bits);
- cur_col[w_idx[0]] = changeUIntScale(cur_col[w_idx[0]], 0, white_bri, 0, white_bri_8bits);
- cur_col[w_idx[1]] = changeUIntScale(cur_col[w_idx[1]], 0, white_bri, 0, white_bri_8bits);
+
+ // First apply combined correction to the overall white power
+ if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) {
+ // channels for white are always the last two channels
+ uint32_t cw1 = Light.subtype - 1; // address for the ColorTone PWM
+ uint32_t cw0 = Light.subtype - 2; // address for the White Brightness PWM
+ uint16_t white_bri10 = cur_col_10[cw0] + cur_col_10[cw1]; // cumulated brightness
+ uint16_t white_bri10_1023 = (white_bri10 > 1023) ? 1023 : white_bri10; // max 1023
+
+ if (PHILIPS == my_module_type) { // channel 1 is the color tone, mapped to cold channel (0..255)
+ // Xiaomi Philips bulbs follow a different scheme:
+ cur_col_10[cw1] = light_state.getCT10bits();
+ // channel 0=intensity, channel1=temperature
+ if (Settings.light_correction) { // gamma correction
+ cur_col_10[cw0] = ledGamma10_10(white_bri10_1023); // 10 bits gamma correction
} else {
- cur_col_10bits[w_idx[0]] = ledGamma(cur_col[w_idx[0]], 10);
- cur_col_10bits[w_idx[1]] = ledGamma(cur_col[w_idx[1]], 10);
- cur_col[w_idx[0]] = ledGamma(cur_col[w_idx[0]]);
- cur_col[w_idx[1]] = ledGamma(cur_col[w_idx[1]]);
+ cur_col_10[cw0] = white_bri10_1023; // no gamma, extend to 10 bits
+ }
+ } else if (Settings.light_correction) {
+ // if sum of both channels is > 255, then channels are probably uncorrelated
+ if (white_bri10 <= 1031) { // take a margin of 8 above 1023 to account for rounding errors
+ // we calculate the gamma corrected sum of CW + WW
+ uint16_t white_bri_gamma10 = ledGamma10_10(white_bri10_1023);
+ // then we split the total energy among the cold and warm leds
+ cur_col_10[cw0] = changeUIntScale(cur_col_10[cw0], 0, white_bri10_1023, 0, white_bri_gamma10);
+ cur_col_10[cw1] = changeUIntScale(cur_col_10[cw1], 0, white_bri10_1023, 0, white_bri_gamma10);
+ } else {
+ cur_col_10[cw0] = ledGamma10_10(cur_col_10[cw0]);
+ cur_col_10[cw1] = ledGamma10_10(cur_col_10[cw1]);
}
}
+ }
+
+ if (Settings.light_correction) {
// then apply gamma correction to RGB channels
if (LST_RGB <= Light.subtype) {
for (uint32_t i = 0; i < 3; i++) {
- cur_col_10bits[i] = ledGamma(cur_col[i], 10);
- cur_col[i] = ledGamma(cur_col[i]);
+ cur_col_10[i] = ledGamma10_10(cur_col_10[i]);
}
}
// If RGBW or Single channel, also adjust White channel
- if ((LST_COLDWARM != Light.subtype) && (LST_RGBWC != Light.subtype)) {
- cur_col_10bits[3] = ledGamma(cur_col[3], 10);
- cur_col[3] = ledGamma(cur_col[3]);
+ if ((LST_SINGLE == Light.subtype) || (LST_RGBW == Light.subtype)) {
+ cur_col_10[Light.subtype - 1] = ledGamma10_10(cur_col_10[Light.subtype - 1]);
}
}
}
+#ifdef USE_DEVICE_GROUPS
+void LightSendDeviceGroupStatus()
+{
+ uint8_t channels[LST_MAX];
+ light_state.getChannels(channels);
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme, DGR_ITEM_LIGHT_CHANNELS, channels,
+ DGR_ITEM_LIGHT_BRI, (power ? light_state.getBri() : 0));
+}
+
+void LightHandleDeviceGroupRequest()
+{
+ static bool send_state = false;
+ uint32_t value = XdrvMailbox.payload;
+ switch (XdrvMailbox.command_code) {
+ case DGR_ITEM_EOL:
+ LightAnimate();
+ if (send_state && !(XdrvMailbox.index & DGR_FLAG_MORE_TO_COME)) {
+ light_controller.saveSettings();
+ if (Settings.flag3.hass_tele_on_power) { // SetOption59 - Send tele/%topic%/STATE in addition to stat/%topic%/RESULT
+ MqttPublishTeleState();
+ }
+ send_state = false;
+ }
+ break;
+ case DGR_ITEM_LIGHT_BRI:
+ if (light_state.getBri() != value) {
+ light_controller.changeBri(value);
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_LIGHT_SCHEME:
+ if (Settings.light_scheme != value) {
+ Settings.light_scheme = value;
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_LIGHT_CHANNELS:
+ light_controller.changeChannels((uint8_t *)XdrvMailbox.data);
+ send_state = true;
+ break;
+ case DGR_ITEM_LIGHT_FIXED_COLOR:
+ {
+ struct XDRVMAILBOX save_XdrvMailbox;
+ power_t save_power = Light.power;
+
+ if (value) {
+ bool save_decimal_text = Settings.flag.decimal_text;
+ char str[16];
+ XdrvMailbox.index = 2;
+ XdrvMailbox.data_len = sprintf_P(str, PSTR("%u"), value);
+ XdrvMailbox.data = str;
+ CmndSupportColor();
+ Settings.flag.decimal_text = save_decimal_text;
+ }
+ else {
+ Light.fixed_color_index = 0;
+ XdrvMailbox.index = 1;
+ XdrvMailbox.payload = light_state.BriToDimmer(light_state.getBri());
+ CmndWhite();
+ }
+ if (Light.power != save_power) {
+ XdrvMailbox.index = save_power;
+ LightSetPower();
+ }
+ XdrvMailbox = save_XdrvMailbox;
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_LIGHT_FADE:
+ if (Settings.light_fade != value) {
+ Settings.light_fade = value;
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_LIGHT_SPEED:
+ if (Settings.light_speed != value && value > 0 && value <= 40) {
+ Settings.light_speed = value;
+ send_state = true;
+ }
+ break;
+ case DGR_ITEM_STATUS:
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_PARTIAL_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade,
+ DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
+ LightSendDeviceGroupStatus();
+ break;
+ }
+}
+#endif // USE_DEVICE_GROUPS
+
/*********************************************************************************************\
* Commands
\*********************************************************************************************/
@@ -2011,7 +2235,7 @@ bool LightColorEntry(char *buffer, uint32_t buffer_length)
memcpy_P(&Light.entry_color, &kFixedColdWarm[value -200], 2);
entry_type = 1; // Hexadecimal
}
- else if (LST_RGBWC == Light.subtype) {
+ else if (LST_RGBCW == Light.subtype) {
memcpy_P(&Light.entry_color[3], &kFixedColdWarm[value -200], 2);
entry_type = 1; // Hexadecimal
}
@@ -2072,6 +2296,13 @@ void CmndSupportColor(void)
void CmndColor(void)
{
+ // Color - Show current RGBWW color state
+ // Color1 - Change color to RGBWW
+ // Color2 - Change color to RGBWW but retain brightness (=dimmer)
+ // Color3 - Change color to RGB of WS2812 Clock Second
+ // Color4 - Change color to RGB of WS2812 Clock Minute
+ // Color5 - Change color to RGB of WS2812 Clock Hour
+ // Color6 - Change color to RGB of WS2812 Clock Marker
if ((Light.subtype > LST_SINGLE) && (XdrvMailbox.index > 0) && (XdrvMailbox.index <= 6)) {
CmndSupportColor();
}
@@ -2079,23 +2310,25 @@ void CmndColor(void)
void CmndWhite(void)
{
- if ((Light.subtype == LST_RGBW) && (XdrvMailbox.index == 1)) {
+ // White - Show current White (=Dimmer2) state
+ // White 0..100 - Set White colors dimmer state
+ if (Light.pwm_multi_channels) { return; }
+ if ( ((Light.subtype >= LST_RGBW) || (LST_COLDWARM == Light.subtype)) && (XdrvMailbox.index == 1)) {
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
- uint32_t whiteBri = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
- char scolor[LIGHT_COLOR_SIZE];
- snprintf_P(scolor, sizeof(scolor), PSTR("0,0,0,%d"), whiteBri);
- light_state.setBri(whiteBri); // save target Bri, will be confirmed below
- XdrvMailbox.data = scolor;
- XdrvMailbox.data_len = strlen(scolor);
+ light_controller.changeDimmer(XdrvMailbox.payload, 2);
+ LightPreparePower(2);
} else {
- XdrvMailbox.data_len = 0;
+ ResponseCmndNumber(light_state.getDimmer(2));
}
- CmndSupportColor();
}
}
void CmndChannel(void)
{
+ // Channel - Show current Channel state
+ // Channel 0..100 - Set Channel dimmer state
+ // Channel + - Incerement Channel in steps of 10
+ // Channel - - Decrement Channel in steps of 10
if ((XdrvMailbox.index >= Light.device) && (XdrvMailbox.index < Light.device + Light.subtype )) {
uint32_t light_index = XdrvMailbox.index - Light.device;
power_t coldim = 0; // bit flag to update
@@ -2140,49 +2373,35 @@ void CmndChannel(void)
void CmndHsbColor(void)
{
+ // HsbColor - Show current HSB
+ // HsbColor 360,100,100 - Set Hue, Saturation and Brighthness
+ // HsbColor 360,100 - Set Hue and Saturation
+ // HsbColor 360 - Set Hue
+ // HsbColor1 360 - Set Hue
+ // HsbColor2 100 - Set Saturation
+ // HsbColor3 100 - Set Brightness
if (Light.subtype >= LST_RGB) {
- bool validHSB = (XdrvMailbox.data_len > 0);
- if (validHSB) {
- uint16_t HSB[3];
- if (strstr(XdrvMailbox.data, ",") != nullptr) { // Command with 3 comma separated parameters, Hue (0 1) && (XdrvMailbox.index < 4)) {
- HSB[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload,0,100,0,255);
- } else {
- validHSB = false;
+ if (XdrvMailbox.data_len > 0) {
+ uint16_t c_hue;
+ uint8_t c_sat;
+ light_state.getHSB(&c_hue, &c_sat, nullptr);
+ uint32_t HSB[3];
+ HSB[0] = c_hue;
+ HSB[1] = c_sat;
+ HSB[2] = light_state.getBriRGB();
+ if ((2 == XdrvMailbox.index) || (3 == XdrvMailbox.index)) {
+ if ((uint32_t)XdrvMailbox.payload > 100) { XdrvMailbox.payload = 100; }
+ HSB[XdrvMailbox.index-1] = changeUIntScale(XdrvMailbox.payload, 0, 100, 0, 255);
+ } else {
+ uint32_t paramcount = ParseParameters(3, HSB);
+ if (HSB[0] > 360) { HSB[0] = 360; }
+ for (uint32_t i = 1; i < paramcount; i++) {
+ if (HSB[i] > 100) { HSB[i] == 100; }
+ HSB[i] = changeUIntScale(HSB[i], 0, 100, 0, 255); // change sat and bri to 0..255
}
}
- if (validHSB) {
- light_controller.changeHSB(HSB[0], HSB[1], HSB[2]);
- LightPreparePower(1);
- MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_COLOR));
- }
+ light_controller.changeHSB(HSB[0], HSB[1], HSB[2]);
+ LightPreparePower(1);
} else {
LightState(0);
}
@@ -2191,6 +2410,11 @@ void CmndHsbColor(void)
void CmndScheme(void)
{
+ // Scheme 0..12 - Select one of schemes 0 to 12
+ // Scheme 2 - Select scheme 2
+ // Scheme 2,0 - Select scheme 2 with color wheel set to 0 (HSB Red)
+ // Scheme + - Select next scheme
+ // Scheme - - Select previous scheme
if (Light.subtype >= LST_RGB) {
uint32_t max_scheme = Light.max_scheme;
@@ -2203,7 +2427,14 @@ void CmndScheme(void)
}
}
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= max_scheme)) {
+ uint32_t parm[2];
+ if (ParseParameters(2, parm) > 1) {
+ Light.wheel = parm[1];
+ }
Settings.light_scheme = XdrvMailbox.payload;
+#ifdef USE_DEVICE_GROUPS
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SCHEME, Settings.light_scheme);
+#endif // USE_DEVICE_GROUPS
if (LS_WAKEUP == Settings.light_scheme) {
Light.wakeup_active = 3;
}
@@ -2220,8 +2451,10 @@ void CmndScheme(void)
void CmndWakeup(void)
{
+ // Wakeup - Start wakeup light
+ // Wakeup 0..100 - Start wakeup light to dimmer value 0..100
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 100)) {
- Settings.light_dimmer = XdrvMailbox.payload;
+ light_controller.changeDimmer(XdrvMailbox.payload);
}
Light.wakeup_active = 3;
Settings.light_scheme = LS_WAKEUP;
@@ -2231,18 +2464,23 @@ void CmndWakeup(void)
void CmndColorTemperature(void)
{
- if ((LST_COLDWARM == Light.subtype) || (LST_RGBWC == Light.subtype)) { // ColorTemp
+ // CT - Show current color temperature
+ // CT 153..500 - Set color temperature
+ // CT + - Incerement color temperature in steps of 34
+ // CT - - Decrement color temperature in steps of 34
+ if (Light.pwm_multi_channels) { return; }
+ if ((LST_COLDWARM == Light.subtype) || (LST_RGBCW == Light.subtype)) { // ColorTemp
uint32_t ct = light_state.getCT();
if (1 == XdrvMailbox.data_len) {
if ('+' == XdrvMailbox.data[0]) {
- XdrvMailbox.payload = (ct > (500-34)) ? 500 : ct + 34;
+ XdrvMailbox.payload = (ct > (CT_MAX-34)) ? CT_MAX : ct + 34;
}
else if ('-' == XdrvMailbox.data[0]) {
- XdrvMailbox.payload = (ct < (153+34)) ? 153 : ct - 34;
+ XdrvMailbox.payload = (ct < (CT_MIN+34)) ? CT_MIN : ct - 34;
}
}
- if ((XdrvMailbox.payload >= 153) && (XdrvMailbox.payload <= 500)) { // https://developers.meethue.com/documentation/core-concepts
- light_controller.changeCTB(XdrvMailbox.payload, light_state.getBri());
+ if ((XdrvMailbox.payload >= CT_MIN) && (XdrvMailbox.payload <= CT_MAX)) { // https://developers.meethue.com/documentation/core-concepts
+ light_controller.changeCTB(XdrvMailbox.payload, light_state.getBriCT());
LightPreparePower(2);
} else {
ResponseCmndNumber(ct);
@@ -2252,8 +2490,21 @@ void CmndColorTemperature(void)
void CmndDimmer(void)
{
+ // Dimmer - Show current Dimmer state
+ // Dimmer0 0..100 - Change both RGB and W(W) Dimmers
+ // Dimmer1 0..100 - Change RGB Dimmer
+ // Dimmer2 0..100 - Change W(W) Dimmer
+ // Dimmer3 0..100 - Change both RGB and W(W) Dimmers with no fading
+ // Dimmer + - Incerement Dimmer in steps of 10
+ // Dimmer - - Decrement Dimmer in steps of 10
uint32_t dimmer;
- if (XdrvMailbox.index > 2) { XdrvMailbox.index = 1; }
+ if (XdrvMailbox.index == 3) {
+ skip_light_fade = true;
+ XdrvMailbox.index = 0;
+ }
+ else if (XdrvMailbox.index > 2) {
+ XdrvMailbox.index = 1;
+ }
if ((light_controller.isCTRGBLinked()) || (0 == XdrvMailbox.index)) {
dimmer = light_state.getDimmer();
@@ -2286,24 +2537,24 @@ void CmndDimmer(void)
}
}
Light.update = true;
+ if (skip_light_fade) LightAnimate();
} else {
ResponseCmndNumber(dimmer);
}
+ skip_light_fade = false;
}
+#endif // USE_LIGHT
+#if defined(USE_LIGHT) || defined(USE_PWM_DIMMER)
void CmndDimmerRange(void)
{
+ // DimmerRange - Show current dimmer range as used by Tuya and PS16DZ Dimmers
+ // DimmerRange 0,100 - Set dimmer hardware range from 0 to 100 and restart
if (XdrvMailbox.data_len > 0) {
- char *p;
- uint8_t i = 0;
- uint16_t parm[2];
+ uint32_t parm[2];
parm[0] = Settings.dimmer_hw_min;
parm[1] = Settings.dimmer_hw_max;
- for (char *str = strtok_r(XdrvMailbox.data, ", ", &p); str && i < 2; str = strtok_r(nullptr, ", ", &p)) {
- parm[i] = strtoul(str, nullptr, 0);
- i++;
- }
-
+ ParseParameters(2, parm);
if (parm[0] < parm[1]) {
Settings.dimmer_hw_min = parm[0];
Settings.dimmer_hw_max = parm[1];
@@ -2311,13 +2562,22 @@ void CmndDimmerRange(void)
Settings.dimmer_hw_min = parm[1];
Settings.dimmer_hw_max = parm[0];
}
- restart_flag = 2;
+#ifdef USE_DEVICE_GROUPS
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_DIMMER_RANGE, Settings.dimmer_hw_min | Settings.dimmer_hw_max << 16);
+#endif // USE_DEVICE_GROUPS
+ if (PWM_DIMMER != my_module_type) restart_flag = 2;
}
Response_P(PSTR("{\"" D_CMND_DIMMER_RANGE "\":{\"Min\":%d,\"Max\":%d}}"), Settings.dimmer_hw_min, Settings.dimmer_hw_max);
}
+#endif // #if defined(USE_LIGHT) || defined(USE_PWM_DIMMER)
+#ifdef USE_LIGHT
void CmndLedTable(void)
{
+ // LedTable - Show current LedTable state
+ // LedTable 0 - Turn LedTable Off
+ // LedTable On - Turn LedTable On
+ // LedTable Toggle - Toggle LedTable state
if ((XdrvMailbox.payload >= 0) && (XdrvMailbox.payload <= 2)) {
switch (XdrvMailbox.payload) {
case 0: // Off
@@ -2335,33 +2595,32 @@ void CmndLedTable(void)
void CmndRgbwwTable(void)
{
+ // RgbWwTable - Show current RGBWW State
+ // RgbWwTable 255,255,255,255,255 - Set RGBWW state to maximum
if ((XdrvMailbox.data_len > 0)) {
- if (strstr(XdrvMailbox.data, ",") != nullptr) { // Command with up to 5 comma separated parameters
- for (uint32_t i = 0; i < LST_RGBWC; i++) {
- char *substr;
-
- if (0 == i) {
- substr = strtok(XdrvMailbox.data, ",");
- } else {
- substr = strtok(nullptr, ",");
- }
- if (substr != nullptr) {
- Settings.rgbwwTable[i] = atoi(substr);
- }
- }
+ uint32_t parm[LST_RGBCW -1];
+ uint32_t parmcount = ParseParameters(LST_RGBCW, parm);
+ for (uint32_t i = 0; i < parmcount; i++) {
+ Settings.rgbwwTable[i] = parm[i];
}
Light.update = true;
}
char scolor[LIGHT_COLOR_SIZE];
scolor[0] = '\0';
- for (uint32_t i = 0; i < LST_RGBWC; i++) {
+ for (uint32_t i = 0; i < LST_RGBCW; i++) {
snprintf_P(scolor, sizeof(scolor), PSTR("%s%s%d"), scolor, (i > 0) ? "," : "", Settings.rgbwwTable[i]);
}
- ResponseCmndIdxChar(scolor);
+ ResponseCmndChar(scolor);
}
+#endif // USE_LIGHT
+#if defined(USE_LIGHT) || defined(USE_PWM_DIMMER)
void CmndFade(void)
{
+ // Fade - Show current Fade state
+ // Fade 0 - Turn Fade Off
+ // Fade On - Turn Fade On
+ // Fade Toggle - Toggle Fade state
switch (XdrvMailbox.payload) {
case 0: // Off
case 1: // On
@@ -2371,12 +2630,21 @@ void CmndFade(void)
Settings.light_fade ^= 1;
break;
}
+#ifdef USE_DEVICE_GROUPS
+ if (XdrvMailbox.payload >= 0 && XdrvMailbox.payload <= 2) SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_FADE, Settings.light_fade);
+#endif // USE_DEVICE_GROUPS
+#ifdef USE_LIGHT
if (!Settings.light_fade) { Light.fade_running = false; }
+#endif // USE_LIGHT
ResponseCmndStateText(Settings.light_fade);
}
void CmndSpeed(void)
-{ // 1 - fast, 40 - very slow
+{
+ // Speed 1 - Fast
+ // Speed 40 - Very slow
+ // Speed + - Increment Speed
+ // Speed - - Decrement Speed
if (1 == XdrvMailbox.data_len) {
if (('+' == XdrvMailbox.data[0]) && (Settings.light_speed > 1)) {
XdrvMailbox.payload = Settings.light_speed - 1;
@@ -2387,12 +2655,19 @@ void CmndSpeed(void)
}
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload <= 40)) {
Settings.light_speed = XdrvMailbox.payload;
+#ifdef USE_DEVICE_GROUPS
+ SendLocalDeviceGroupMessage(DGR_MSGTYP_UPDATE, DGR_ITEM_LIGHT_SPEED, Settings.light_speed);
+#endif // USE_DEVICE_GROUPS
}
ResponseCmndNumber(Settings.light_speed);
}
+#endif // #if defined(USE_LIGHT) || defined(USE_PWM_DIMMER)
+#ifdef USE_LIGHT
void CmndWakeupDuration(void)
{
+ // WakeUpDuration - Show current Wake Up duration in seconds
+ // WakeUpDuration 60 - Set Wake Up duration to 60 seconds
if ((XdrvMailbox.payload > 0) && (XdrvMailbox.payload < 3001)) {
Settings.light_wakeup = XdrvMailbox.payload;
Light.wakeup_active = 0;
@@ -2401,7 +2676,8 @@ void CmndWakeupDuration(void)
}
void CmndUndocA(void)
-{ // Theos legacy status
+{
+ // Theos legacy status
char scolor[LIGHT_COLOR_SIZE];
LightGetColor(scolor, true); // force hex whatever Option 17
scolor[6] = '\0'; // RGB only
@@ -2419,16 +2695,31 @@ bool Xdrv04(uint8_t function)
bool result = false;
if (FUNC_MODULE_INIT == function) {
- return LightModuleInit();
+#ifdef USE_PWM_DIMMER
+ if (PWM_DIMMER != my_module_type)
+#endif // USE_PWM_DIMMER
+ return LightModuleInit();
}
else if (light_type) {
switch (function) {
case FUNC_SERIAL:
result = XlgtCall(FUNC_SERIAL);
break;
+ case FUNC_LOOP:
+ if (Light.fade_running) {
+ if (LightApplyFade()) {
+ LightSetOutputs(Light.fade_cur_10);
+ }
+ }
+ break;
case FUNC_EVERY_50_MSECOND:
LightAnimate();
break;
+#ifdef USE_DEVICE_GROUPS
+ case FUNC_DEVICE_GROUP_REQUEST:
+ LightHandleDeviceGroupRequest();
+ break;
+#endif // USE_DEVICE_GROUPS
case FUNC_SET_POWER:
LightSetPower();
break;
diff --git a/tasmota/xdrv_05_irremote.ino b/tasmota/xdrv_05_irremote.ino
index 7c66c4b98..6c6e16fdb 100644
--- a/tasmota/xdrv_05_irremote.ino
+++ b/tasmota/xdrv_05_irremote.ino
@@ -1,7 +1,7 @@
/*
xdrv_05_irremote.ino - infra red support for Tasmota
- Copyright (C) 2019 Heiko Krupp, Lazar Obradovic and Theo Arends
+ Copyright (C) 2020 Heiko Krupp, Lazar Obradovic and 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
@@ -181,7 +181,7 @@ uint32_t IrRemoteCmndIrSendJson(void)
// IRsend { "protocol": "RC5", "bits": 12, "data":"0xC86" }
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
- char dataBufUc[XdrvMailbox.data_len];
+ char dataBufUc[XdrvMailbox.data_len + 1];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
if (strlen(dataBufUc) < 8) {
diff --git a/tasmota/xdrv_05_irremote_full.ino b/tasmota/xdrv_05_irremote_full.ino
index d01291c7c..14de34cbc 100644
--- a/tasmota/xdrv_05_irremote_full.ino
+++ b/tasmota/xdrv_05_irremote_full.ino
@@ -1,7 +1,7 @@
/*
xdrv_05_irremote_full.ino - complete integration of IRremoteESP8266 for Tasmota
- Copyright (C) 2019 Heiko Krupp, Lazar Obradovic, Theo Arends, Stephan Hadinger
+ Copyright (C) 2020 Heiko Krupp, Lazar Obradovic, Theo Arends, Stephan Hadinger
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
@@ -280,7 +280,7 @@ uint32_t IrRemoteCmndIrHvacJson(void)
char parm_uc[12];
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("IRHVAC: Received %s"), XdrvMailbox.data);
- char dataBufUc[XdrvMailbox.data_len];
+ char dataBufUc[XdrvMailbox.data_len + 1];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; }
@@ -393,7 +393,7 @@ uint32_t IrRemoteCmndIrSendJson(void)
// ArduinoJSON entry used to calculate jsonBuf: JSON_OBJECT_SIZE(3) + 40 = 96
// IRsend { "protocol": "RC5", "bits": 12, "data":"0xC86" }
// IRsend { "protocol": "SAMSUNG", "bits": 32, "data": 551502015 }
- char dataBufUc[XdrvMailbox.data_len];
+ char dataBufUc[XdrvMailbox.data_len + 1];
UpperCase(dataBufUc, XdrvMailbox.data);
RemoveSpace(dataBufUc);
if (strlen(dataBufUc) < 8) { return IE_INVALID_JSON; }
diff --git a/tasmota/xdrv_06_snfbridge.ino b/tasmota/xdrv_06_snfbridge.ino
index 9f8a852d5..c8e9db67e 100644
--- a/tasmota/xdrv_06_snfbridge.ino
+++ b/tasmota/xdrv_06_snfbridge.ino
@@ -1,7 +1,7 @@
/*
xdrv_06_snfbridge.ino - sonoff RF bridge 433 support for Tasmota
- Copyright (C) 2019 Theo Arends and Erik Andrén Zachrisson (fw update)
+ Copyright (C) 2020 Theo Arends and Erik Andrén Zachrisson (fw update)
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
@@ -578,8 +578,7 @@ bool Xdrv06(uint8_t function)
SonoffBridgeSendCommand(0xA7); // Stop reading RF signals enabling iTead default RF handling
break;
case FUNC_PRE_INIT:
- Settings.flag.mqtt_serial = 0; // CMND_SERIALSEND and CMND_SERIALLOG
- baudrate = 19200;
+ SetSerial(19200, TS_SERIAL_8N1);
break;
}
}
diff --git a/tasmota/xdrv_07_domoticz.ino b/tasmota/xdrv_07_domoticz.ino
index f5e8af19c..90563eeed 100644
--- a/tasmota/xdrv_07_domoticz.ino
+++ b/tasmota/xdrv_07_domoticz.ino
@@ -1,7 +1,7 @@
/*
xdrv_07_domoticz.ino - domoticz support for Tasmota
- Copyright (C) 2019 Theo Arends
+ Copyright (C) 2020 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
@@ -51,6 +51,10 @@ uint32_t domoticz_fan_debounce = 0; // iFan02 state debounce timer
bool domoticz_subscribe = false;
bool domoticz_update_flag = true;
+#ifdef USE_SHUTTER
+bool domoticz_is_shutter = false;
+#endif // USE_SHUTTER
+
int DomoticzBatteryQuality(void)
{
// Battery 0%: ESP 2.6V (minimum operating voltage is 2.5)
@@ -108,6 +112,11 @@ void MqttPublishDomoticzPowerState(uint8_t device)
if (Settings.flag.mqtt_enabled) { // SetOption3 - Enable MQTT
if ((device < 1) || (device > devices_present)) { device = 1; }
if (Settings.domoticz_relay_idx[device -1]) {
+#ifdef USE_SHUTTER
+ if (domoticz_is_shutter) {
+ // Shutter is updated by sensor update - power state should not be sent
+ } else {
+#endif // USE_SHUTTER
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan() && (device > 1)) {
// Fan handled by MqttPublishDomoticzFanState
@@ -121,6 +130,9 @@ void MqttPublishDomoticzPowerState(uint8_t device)
#ifdef USE_SONOFF_IFAN
}
#endif // USE_SONOFF_IFAN
+#ifdef USE_SHUTTER
+ }
+#endif //USE_SHUTTER
}
}
}
@@ -140,6 +152,13 @@ void DomoticzMqttUpdate(void)
if (domoticz_update_timer <= 0) {
domoticz_update_timer = Settings.domoticz_update_timer;
for (uint32_t i = 1; i <= devices_present; i++) {
+#ifdef USE_SHUTTER
+ if (domoticz_is_shutter)
+ {
+ // no power state updates for shutters
+ break;
+ }
+#endif // USE_SHUTTER
#ifdef USE_SONOFF_IFAN
if (IsModuleIfan() && (i > 1)) {
MqttPublishDomoticzFanState();
@@ -163,6 +182,7 @@ void DomoticzMqttSubscribe(void)
domoticz_subscribe = true;
}
}
+
if (domoticz_subscribe) {
char stopic[TOPSZ];
snprintf_P(stopic, sizeof(stopic), PSTR(DOMOTICZ_OUT_TOPIC "/#")); // domoticz topic
@@ -230,6 +250,8 @@ bool DomoticzMqttData(void)
for (uint32_t i = 0; i < maxdev; i++) {
if (idx == Settings.domoticz_relay_idx[i]) {
bool iscolordimmer = strcmp_P(domoticz["dtype"],PSTR("Color Switch")) == 0;
+ bool isShutter = strcmp_P(domoticz["dtype"],PSTR("Light/Switch")) == 0 & strncmp_P(domoticz["switchType"],PSTR("Blinds"), 6) == 0;
+
char stemp1[10];
snprintf_P(stemp1, sizeof(stemp1), PSTR("%d"), i +1);
#ifdef USE_SONOFF_IFAN
@@ -252,7 +274,31 @@ bool DomoticzMqttData(void)
found = true;
} else
#endif // USE_SONOFF_IFAN
+#ifdef USE_SHUTTER
+ if (isShutter)
+ {
+ if (domoticz.containsKey("nvalue")) {
+ nvalue = domoticz["nvalue"];
+ }
+
+ uint8_t position = 0;
+ if (domoticz.containsKey("svalue1")) {
+ position = domoticz["svalue1"];
+ }
+ if (nvalue != 2) {
+ position = nvalue == 0 ? 0 : 100;
+ }
+
+ snprintf_P(XdrvMailbox.topic, TOPSZ, PSTR("/" D_PRFX_SHUTTER D_CMND_SHUTTER_POSITION));
+ snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), position);
+ XdrvMailbox.data_len = position > 99 ? 3 : (position > 9 ? 2 : 1);
+
+ found = true;
+ } else
+#endif // USE_SHUTTER
+#ifdef USE_LIGHT
if (iscolordimmer && 10 == nvalue) { // Color_SetColor
+ // https://www.domoticz.com/wiki/Domoticz_API/JSON_URL%27s#Set_a_light_to_a_certain_color_or_color_temperature
JsonObject& color = domoticz["Color"];
uint16_t level = nvalue = domoticz["svalue1"];
uint16_t r = color["r"]; r = r * level / 100;
@@ -260,8 +306,19 @@ bool DomoticzMqttData(void)
uint16_t b = color["b"]; b = b * level / 100;
uint16_t cw = color["cw"]; cw = cw * level / 100;
uint16_t ww = color["ww"]; ww = ww * level / 100;
- snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_COLOR));
- snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%02x%02x%02x%02x%02x"), r, g, b, cw, ww);
+ uint16_t m = 0;
+ uint16_t t = 0;
+ if (color.containsKey("m")) {
+ m = color["m"];
+ t = color["t"];
+ }
+ if (2 == m) { // White with color temperature. Valid fields: t
+ snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_BACKLOG));
+ snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR(D_CMND_COLORTEMPERATURE " %d;" D_CMND_DIMMER " %d"), changeUIntScale(t, 0, 255, CT_MIN, CT_MAX), level);
+ } else {
+ snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_COLOR));
+ snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%02x%02x%02x%02x%02x"), r, g, b, cw, ww);
+ }
found = true;
}
else if ((!iscolordimmer && 2 == nvalue) || // gswitch_sSetLevel
@@ -277,8 +334,9 @@ bool DomoticzMqttData(void)
snprintf_P(XdrvMailbox.topic, XdrvMailbox.index, PSTR("/" D_CMND_DIMMER));
snprintf_P(XdrvMailbox.data, XdrvMailbox.data_len, PSTR("%d"), nvalue);
found = true;
- }
- else if (1 == nvalue || 0 == nvalue) {
+ } else
+#endif // USE_LIGHT
+ if (1 == nvalue || 0 == nvalue) {
if (((power >> i) &1) == (power_t)nvalue) {
return true; // Stop loop
}
@@ -592,6 +650,9 @@ bool Xdrv07(uint8_t function)
#endif // USE_WEBSERVER
case FUNC_MQTT_SUBSCRIBE:
DomoticzMqttSubscribe();
+#ifdef USE_SHUTTER
+ if (Settings.domoticz_sensor_idx[DZ_SHUTTER]) { domoticz_is_shutter = true; }
+#endif // USE_SHUTTER
break;
case FUNC_MQTT_INIT:
domoticz_update_timer = 2;
diff --git a/tasmota/xdrv_08_serial_bridge.ino b/tasmota/xdrv_08_serial_bridge.ino
index 2d9856186..c4f530a21 100644
--- a/tasmota/xdrv_08_serial_bridge.ino
+++ b/tasmota/xdrv_08_serial_bridge.ino
@@ -1,7 +1,7 @@
/*
xdrv_08_serial_bridge.ino - serial bridge support for Tasmota
- Copyright (C) 2019 Theo Arends and Dániel Zoltán Tolnai
+ Copyright (C) 2020 Theo Arends and Dániel Zoltán Tolnai
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
@@ -71,8 +71,11 @@ void SerialBridgeInput(void)
if (serial_bridge_in_byte_counter && (millis() > (serial_bridge_polling_window + SERIAL_POLLING))) {
serial_bridge_buffer[serial_bridge_in_byte_counter] = 0; // Serial data completed
char hex_char[(serial_bridge_in_byte_counter * 2) + 2];
- Response_P(PSTR("{\"" D_JSON_SSERIALRECEIVED "\":\"%s\"}"),
- (serial_bridge_raw) ? ToHex_P((unsigned char*)serial_bridge_buffer, serial_bridge_in_byte_counter, hex_char, sizeof(hex_char)) : serial_bridge_buffer);
+ bool assume_json = (!serial_bridge_raw && (serial_bridge_buffer[0] == '{'));
+ Response_P(PSTR("{\"" D_JSON_SSERIALRECEIVED "\":%s%s%s}"),
+ (assume_json) ? "" : """",
+ (serial_bridge_raw) ? ToHex_P((unsigned char*)serial_bridge_buffer, serial_bridge_in_byte_counter, hex_char, sizeof(hex_char)) : serial_bridge_buffer,
+ (assume_json) ? "" : """");
MqttPublishPrefixTopic_P(RESULT_OR_TELE, PSTR(D_JSON_SSERIALRECEIVED));
XdrvRulesProcess();
serial_bridge_in_byte_counter = 0;
diff --git a/tasmota/xdrv_09_timers.ino b/tasmota/xdrv_09_timers.ino
index a71960695..0804ce286 100644
--- a/tasmota/xdrv_09_timers.ino
+++ b/tasmota/xdrv_09_timers.ino
@@ -1,7 +1,7 @@
/*
xdrv_09_timers.ino - timer support for Tasmota
- Copyright (C) 2019 Theo Arends
+ Copyright (C) 2020 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
@@ -364,7 +364,7 @@ void CmndTimer(void)
#if defined(USE_RULES)==0 && defined(USE_SCRIPT)==0
if (devices_present) {
#endif
- char dataBufUc[XdrvMailbox.data_len];
+ char dataBufUc[XdrvMailbox.data_len + 1];
UpperCase(dataBufUc, XdrvMailbox.data);
StaticJsonBuffer<256> jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(dataBufUc);
@@ -554,10 +554,10 @@ const char HTTP_TIMER_SCRIPT2[] PROGMEM =
"o=qs('#ho');"
"e=o.childElementCount;"
"if(b==1){"
- "qs('#dr').disabled='';"
+ "qs('#dr').style.visibility='';"
"if(e>12){for(i=12;i<=23;i++){o.removeChild(o.lastElementChild);}}" // Create offset hours select options
"}else{"
- "qs('#dr').disabled='disabled';"
+ "qs('#dr').style.visibility='hidden';"
"if(e<23){for(i=12;i<=23;i++){ce(i,o);}}" // Create hours select options
"}"
"}";
@@ -583,7 +583,7 @@ const char HTTP_TIMER_SCRIPT3[] PROGMEM =
"if(m==0){s|=l;}" // Get time
#ifdef USE_SUNRISE
"if((m==1)||(m==2)){"
- "if(qs('#dr').selectedIndex>0){l+=720;}" // If negative offset, add 12h to given offset time
+ "if(qs('#dr').selectedIndex>0){if(l>0){l+=720;}}" // If negative offset and delta-time > 0, add 12h to given offset time
"s|=l&0x7FF;" // Save offset instead of time
"}"
#endif
@@ -733,12 +733,13 @@ void HandleTimerConfiguration(void)
void TimerSaveSettings(void)
{
char tmp[MAX_TIMERS *12]; // Need space for MAX_TIMERS x 10 digit numbers separated by a comma
+ char message[LOGSZ];
Timer timer;
Settings.flag3.timers_enable = WebServer->hasArg("e0"); // CMND_TIMERS
WebGetArg("t0", tmp, sizeof(tmp));
char *p = tmp;
- snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_CMND_TIMERS " %d"), Settings.flag3.timers_enable); // CMND_TIMERS
+ snprintf_P(message, sizeof(message), PSTR(D_LOG_MQTT D_CMND_TIMERS " %d"), Settings.flag3.timers_enable); // CMND_TIMERS
for (uint32_t i = 0; i < MAX_TIMERS; i++) {
timer.data = strtol(p, &p, 10);
p++; // Skip comma
@@ -747,9 +748,9 @@ void TimerSaveSettings(void)
Settings.timer[i].data = timer.data;
if (flag) TimerSetRandomWindow(i);
}
- snprintf_P(log_data, sizeof(log_data), PSTR("%s,0x%08X"), log_data, Settings.timer[i].data);
+ snprintf_P(message, sizeof(message), PSTR("%s,0x%08X"), message, Settings.timer[i].data);
}
- AddLog(LOG_LEVEL_DEBUG);
+ AddLog_P(LOG_LEVEL_DEBUG, message);
}
#endif // USE_TIMERS_WEB
#endif // USE_WEBSERVER
diff --git a/tasmota/xdrv_10_rules.ino b/tasmota/xdrv_10_rules.ino
index 441722662..2509d6f6f 100644
--- a/tasmota/xdrv_10_rules.ino
+++ b/tasmota/xdrv_10_rules.ino
@@ -1,7 +1,7 @@
/*
xdrv_10_rules.ino - rule support for Tasmota
- Copyright (C) 2019 ESP Easy Group and Theo Arends
+ Copyright (C) 2020 ESP Easy Group and 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
@@ -174,8 +174,8 @@ char rules_vars[MAX_RULE_VARS][33] = {{ 0 }};
#if (MAX_RULE_VARS>16)
#error MAX_RULE_VARS is bigger than 16
#endif
-#if (MAX_RULE_MEMS>5)
-#error MAX_RULE_MEMS is bigger than 5
+#if (MAX_RULE_MEMS>16)
+#error MAX_RULE_MEMS is bigger than 16
#endif
/*******************************************************************************************/
@@ -217,7 +217,7 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1);
if (rule_param.startsWith(stemp)) {
- rule_param = Settings.mems[i];
+ rule_param = SettingsText(SET_MEM1 + i);
break;
}
}
@@ -255,28 +255,43 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
}
// Step2: Search rule_task and rule_name
- StaticJsonBuffer<1024> jsonBuf;
- JsonObject &root = jsonBuf.parseObject(event);
- if (!root.success()) { return false; } // No valid JSON data
-
- const char* str_value;
- if ((pos = rule_name.indexOf("[")) > 0) { // "CURRENT[1]"
- int rule_name_idx = rule_name.substring(pos +1).toInt();
+ int rule_name_idx = 0;
+ if ((pos = rule_name.indexOf("[")) > 0) { // "SUBTYPE1#CURRENT[1]"
+ rule_name_idx = rule_name.substring(pos +1).toInt();
if ((rule_name_idx < 1) || (rule_name_idx > 6)) { // Allow indexes 1 to 6
rule_name_idx = 1;
}
- rule_name = rule_name.substring(0, pos); // "CURRENT"
- str_value = root[rule_task][rule_name][rule_name_idx -1]; // "ENERGY" and "CURRENT" and 0
+ rule_name = rule_name.substring(0, pos); // "SUBTYPE1#CURRENT"
+ }
+
+ StaticJsonBuffer<1024> jsonBuf;
+ JsonObject &root = jsonBuf.parseObject(event);
+ if (!root.success()) { return false; } // No valid JSON data
+ if (!root[rule_task].success()) { return false; } // No rule_task in JSON data
+
+ JsonObject &obj1 = root[rule_task];
+ JsonObject *obj = &obj1;
+ String subtype;
+ uint32_t i = 0;
+ while ((pos = rule_name.indexOf("#")) > 0) { // "SUBTYPE1#SUBTYPE2#CURRENT"
+ subtype = rule_name.substring(0, pos);
+ if (!(*obj)[subtype].success()) { return false; } // No subtype in JSON data
+ JsonObject &obj2 = (*obj)[subtype];
+ obj = &obj2;
+ rule_name = rule_name.substring(pos +1);
+ if (i++ > 10) { return false; } // Abandon possible loop
+ }
+ if (!(*obj)[rule_name].success()) { return false; } // No name in JSON data
+ const char* str_value;
+ if (rule_name_idx) {
+ str_value = (*obj)[rule_name][rule_name_idx -1]; // "CURRENT[1]"
} else {
- str_value = root[rule_task][rule_name]; // "INA219" and "CURRENT"
+ str_value = (*obj)[rule_name]; // "CURRENT"
}
//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Task %s, Name %s, Value |%s|, TrigCnt %d, TrigSt %d, Source %s, Json %s"),
// rule_task.c_str(), rule_name.c_str(), rule_svalue, Rules.trigger_count[rule_set], bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set]), event.c_str(), (str_value) ? str_value : "none");
- if (!root[rule_task][rule_name].success()) { return false; }
- // No value but rule_name is ok
-
Rules.event_value = str_value; // Prepare %value%
// Step 3: Compare rule (value)
@@ -315,6 +330,9 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
}
} else match = true;
+//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Match 1 %d"), match);
+
+
if (bitRead(Settings.rule_once, rule_set)) {
if (match) { // Only allow match state changes
if (!bitRead(Rules.triggers[rule_set], Rules.trigger_count[rule_set])) {
@@ -327,6 +345,8 @@ bool RulesRuleMatch(uint8_t rule_set, String &event, String &rule)
}
}
+//AddLog_P2(LOG_LEVEL_DEBUG, PSTR("RUL: Match 2 %d"), match);
+
return match;
}
@@ -437,8 +457,14 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
commands.trim();
String ucommand = commands;
ucommand.toUpperCase();
+
// if (!ucommand.startsWith("BACKLOG")) { commands = "backlog " + commands; } // Always use Backlog to prevent power race exception
- if (ucommand.indexOf("EVENT ") != -1) { commands = "backlog " + commands; } // Always use Backlog with event to prevent rule event loop exception
+ // Use Backlog with event to prevent rule event loop exception unless IF is used which uses an implicit backlog
+ if ((ucommand.indexOf("IF ") == -1) &&
+ (ucommand.indexOf("EVENT ") != -1) &&
+ (ucommand.indexOf("BACKLOG ") == -1)) {
+ commands = "backlog " + commands;
+ }
RulesVarReplace(commands, F("%VALUE%"), Rules.event_value);
for (uint32_t i = 0; i < MAX_RULE_VARS; i++) {
@@ -447,12 +473,12 @@ bool RuleSetProcess(uint8_t rule_set, String &event_saved)
}
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
snprintf_P(stemp, sizeof(stemp), PSTR("%%MEM%d%%"), i +1);
- RulesVarReplace(commands, stemp, Settings.mems[i]);
+ RulesVarReplace(commands, stemp, SettingsText(SET_MEM1 +i));
}
RulesVarReplace(commands, F("%TIME%"), String(MinutesPastMidnight()));
RulesVarReplace(commands, F("%UPTIME%"), String(MinutesUptime()));
RulesVarReplace(commands, F("%TIMESTAMP%"), GetDateAndTime(DT_LOCAL));
- RulesVarReplace(commands, F("%TOPIC%"), Settings.mqtt_topic);
+ RulesVarReplace(commands, F("%TOPIC%"), SettingsText(SET_MQTT_TOPIC));
#if defined(USE_TIMERS) && defined(USE_SUNRISE)
RulesVarReplace(commands, F("%SUNRISE%"), String(SunMinutes(0)));
RulesVarReplace(commands, F("%SUNSET%"), String(SunMinutes(1)));
@@ -557,8 +583,7 @@ void RulesEvery50ms(void)
#else
if (pin[GPIO_SWT1 +i] < 99) {
#endif // USE_TM1638
- bool swm = ((FOLLOW_INV == Settings.switchmode[i]) || (PUSHBUTTON_INV == Settings.switchmode[i]) || (PUSHBUTTONHOLD_INV == Settings.switchmode[i]));
- snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (swm ^ SwitchLastState(i)));
+ snprintf_P(json_event, sizeof(json_event), PSTR("{\"" D_JSON_SWITCH "%d\":{\"Boot\":%d}}"), i +1, (SwitchState(i)));
RulesProcessEvent(json_event);
}
}
@@ -608,7 +633,7 @@ void RulesEvery50ms(void)
for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
if (bitRead(Rules.mems_event, i)) {
bitClear(Rules.mems_event, i);
- snprintf_P(json_event, sizeof(json_event), PSTR("{\"Mem%d\":{\"State\":%s}}"), i+1, Settings.mems[i]);
+ snprintf_P(json_event, sizeof(json_event), PSTR("{\"Mem%d\":{\"State\":%s}}"), i+1, SettingsText(SET_MEM1 +i));
RulesProcessEvent(json_event);
break;
}
@@ -1002,7 +1027,7 @@ bool findNextVariableValue(char * &pVarname, float &value)
} else if (sVarName.startsWith(F("MEM"))) {
int index = sVarName.substring(3).toInt();
if (index > 0 && index <= MAX_RULE_MEMS) {
- value = CharToFloat(Settings.mems[index -1]);
+ value = CharToFloat(SettingsText(SET_MEM1 + index -1));
}
} else if (sVarName.equals(F("TIME"))) {
value = MinutesPastMidnight();
@@ -1400,8 +1425,7 @@ bool evaluateLogicalExpression(const char * expression, int len)
memcpy(expbuff, expression, len);
expbuff[len] = '\0';
- //snprintf_P(log_data, sizeof(log_data), PSTR("EvalLogic: |%s|"), expbuff);
- //AddLog(LOG_LEVEL_DEBUG);
+ //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("EvalLogic: |%s|"), expbuff);
char * pointer = expbuff;
LinkedList values;
LinkedList logicOperators;
@@ -1527,8 +1551,7 @@ void ExecuteCommandBlock(const char * commands, int len)
memcpy(cmdbuff, commands, len);
cmdbuff[len] = '\0';
- //snprintf_P(log_data, sizeof(log_data), PSTR("ExecCmd: |%s|"), cmdbuff);
- //AddLog(LOG_LEVEL_DEBUG);
+ //AddLog_P2(LOG_LEVEL_DEBUG, PSTR("ExecCmd: |%s|"), cmdbuff);
char oneCommand[len + 1]; //To put one command
int insertPosition = 0; //When insert into backlog, we should do it by 0, 1, 2 ...
char * pos = cmdbuff;
@@ -1808,25 +1831,21 @@ void CmndMemory(void)
{
if ((XdrvMailbox.index > 0) && (XdrvMailbox.index <= MAX_RULE_MEMS)) {
if (!XdrvMailbox.usridx) {
- mqtt_data[0] = '\0';
- for (uint32_t i = 0; i < MAX_RULE_MEMS; i++) {
- ResponseAppend_P(PSTR("%c\"Mem%d\":\"%s\""), (i) ? ',' : '{', i +1, Settings.mems[i]);
- }
- ResponseJsonEnd();
+ ResponseCmndAll(SET_MEM1, MAX_RULE_MEMS);
} else {
if (XdrvMailbox.data_len > 0) {
#ifdef USE_EXPRESSION
if (XdrvMailbox.data[0] == '=') { // Spaces already been skipped in data
- dtostrfd(evaluateExpression(XdrvMailbox.data + 1, XdrvMailbox.data_len - 1), Settings.flag2.calc_resolution, Settings.mems[XdrvMailbox.index -1]);
+ dtostrfd(evaluateExpression(XdrvMailbox.data + 1, XdrvMailbox.data_len - 1), Settings.flag2.calc_resolution, SettingsText(SET_MEM1 + XdrvMailbox.index -1));
} else {
- strlcpy(Settings.mems[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[XdrvMailbox.index -1]));
+ SettingsUpdateText(SET_MEM1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
}
#else
- strlcpy(Settings.mems[XdrvMailbox.index -1], ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data, sizeof(Settings.mems[XdrvMailbox.index -1]));
+ SettingsUpdateText(SET_MEM1 + XdrvMailbox.index -1, ('"' == XdrvMailbox.data[0]) ? "" : XdrvMailbox.data);
#endif // USE_EXPRESSION
bitSet(Rules.mems_event, XdrvMailbox.index -1);
}
- ResponseCmndIdxChar(Settings.mems[XdrvMailbox.index -1]);
+ ResponseCmndIdxChar(SettingsText(SET_MEM1 + XdrvMailbox.index -1));
}
}
}
diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino
index 0bab141d7..30ed26598 100755
--- a/tasmota/xdrv_10_scripter.ino
+++ b/tasmota/xdrv_10_scripter.ino
@@ -1,7 +1,7 @@
/*
xdrv_10_scripter.ino - script support for Tasmota
- Copyright (C) 2019 Gerhard Mutz and Theo Arends
+ Copyright (C) 2020 Gerhard Mutz and 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
@@ -56,7 +56,8 @@ keywords if then else endif, or, and are better readable for beginners (others m
#define SCRIPT_MAXSSIZE 48
#define SCRIPT_EOL '\n'
#define SCRIPT_FLOAT_PRECISION 2
-#define SCRIPT_MAXPERM (MAX_RULE_MEMS*10)-4/sizeof(float)
+#define PMEM_SIZE sizeof(Settings.script_pram)
+#define SCRIPT_MAXPERM (PMEM_SIZE)-4/sizeof(float)
#define MAX_SCRIPT_SIZE MAX_RULE_SIZE*MAX_RULE_SETS
// offsets epoch readings by 1.1.2019 00:00:00 to fit into float with second resolution
@@ -1030,8 +1031,10 @@ char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,Jso
if ((*jo).is(vn)) {
if (!strncmp(str_value,"ON",2)) {
if (fp) *fp=1;
+ goto nexit;
} else if (!strncmp(str_value,"OFF",3)) {
if (fp) *fp=0;
+ goto nexit;
} else {
*vtype=STR_RES;
tind->bits.constant=1;
@@ -1039,6 +1042,7 @@ char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,Jso
if (sp) strlcpy(sp,str_value,SCRIPT_MAXSSIZE);
return lp+len;
}
+
} else {
if (fp) {
if (!strncmp(vn.c_str(),"Epoch",5)) {
@@ -1047,6 +1051,7 @@ char *isvar(char *lp, uint8_t *vtype,struct T_INDEX *tind,float *fp,char *sp,Jso
*fp=CharToFloat((char*)str_value);
}
}
+ nexit:
*vtype=NUM_RES;
tind->bits.constant=1;
tind->bits.is_string=0;
@@ -1366,7 +1371,7 @@ chknext:
goto exit;
}
if (!strncmp(vname,"gtopic",6)) {
- if (sp) strlcpy(sp,Settings.mqtt_grptopic,glob_script_mem.max_ssize);
+ if (sp) strlcpy(sp,SettingsText(SET_MQTT_GRP_TOPIC),glob_script_mem.max_ssize);
goto strexit;
}
break;
@@ -1491,6 +1496,37 @@ chknext:
fvar=!global_state.mqtt_down;
goto exit;
}
+ if (!strncmp(vname,"mp(",3)) {
+ lp+=3;
+ float fvar1;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar1,0);
+ SCRIPT_SKIP_SPACES
+ while (*lp!=')') {
+ char *opp=lp;
+ lp++;
+ float fvar2;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
+ SCRIPT_SKIP_SPACES
+ fvar=fvar1;
+ if ((*opp=='<' && fvar1' && fvar1>fvar2) ||
+ (*opp=='=' && fvar1==fvar2))
+ {
+ if (*lp!='<' && *lp!='>' && *lp!='=' && *lp!=')' && *lp!=SCRIPT_EOL) {
+ float fvar3;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar3,0);
+ SCRIPT_SKIP_SPACES
+ fvar=fvar3;
+ } else {
+ fvar=fvar2;
+ }
+ break;
+ }
+ while (*lp!='<' && *lp!='>' && *lp!='=' && *lp!=')' && *lp!=SCRIPT_EOL) lp++;
+ }
+ len=0;
+ goto exit;
+ }
break;
case 'p':
if (!strncmp(vname,"pin[",4)) {
@@ -1523,15 +1559,15 @@ chknext:
goto exit;
}
if (!strncmp(vname,"prefix1",7)) {
- if (sp) strlcpy(sp,Settings.mqtt_prefix[0],glob_script_mem.max_ssize);
+ if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX1),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"prefix2",7)) {
- if (sp) strlcpy(sp,Settings.mqtt_prefix[1],glob_script_mem.max_ssize);
+ if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX2),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"prefix3",7)) {
- if (sp) strlcpy(sp,Settings.mqtt_prefix[2],glob_script_mem.max_ssize);
+ if (sp) strlcpy(sp,SettingsText(SET_MQTTPREFIX3),glob_script_mem.max_ssize);
goto strexit;
}
if (!strncmp(vname,"pow(",4)) {
@@ -1571,7 +1607,7 @@ chknext:
case 'r':
if (!strncmp(vname,"ram",3)) {
- fvar=glob_script_mem.script_mem_size+(glob_script_mem.script_size)+(MAX_RULE_MEMS*10);
+ fvar=glob_script_mem.script_mem_size+(glob_script_mem.script_size)+(PMEM_SIZE);
goto exit;
}
break;
@@ -1583,7 +1619,7 @@ chknext:
if (!strncmp(vname,"sw[",3)) {
// tasmota switch state
GetNumericResult(vname+3,OPER_EQU,&fvar,0);
- fvar=SwitchLastState((uint8_t)fvar);
+ fvar=SwitchLastState((uint32_t)fvar);
// skip ] bracket
len++;
goto exit;
@@ -1709,6 +1745,30 @@ chknext:
len=0;
goto exit;
}
+#endif
+#ifdef USE_SML_SCRIPT_CMD
+ if (!strncmp(vname,"sml(",4)) {
+ lp+=4;
+ float fvar1;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar1,0);
+ SCRIPT_SKIP_SPACES
+ float fvar2;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar2,0);
+ SCRIPT_SKIP_SPACES
+ if (fvar2==0) {
+ float fvar3;
+ lp=GetNumericResult(lp,OPER_EQU,&fvar3,0);
+ fvar=SML_SetBaud(fvar1,fvar3);
+ } else {
+ char str[SCRIPT_MAXSSIZE];
+ lp=GetStringResult(lp,OPER_EQU,str,0);
+ fvar=SML_Write(fvar1,str);
+ }
+ lp++;
+ fvar=0;
+ len=0;
+ goto exit;
+ }
#endif
break;
case 't':
@@ -1740,7 +1800,7 @@ chknext:
goto strexit;
}
if (!strncmp(vname,"topic",5)) {
- if (sp) strlcpy(sp,Settings.mqtt_topic,glob_script_mem.max_ssize);
+ if (sp) strlcpy(sp,SettingsText(SET_MQTT_TOPIC),glob_script_mem.max_ssize);
goto strexit;
}
#ifdef USE_DISPLAY
@@ -2199,8 +2259,7 @@ void Replace_Cmd_Vars(char *srcbuf,char *dstbuf,uint16_t dstsize) {
void toLog(const char *str) {
if (!str) return;
- snprintf_P(log_data, sizeof(log_data), PSTR("%s"),str);
- AddLog(LOG_LEVEL_INFO);
+ AddLog_P(LOG_LEVEL_INFO, str);
}
@@ -2677,8 +2736,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
}
cmd[count]=*lp++;
}
- //snprintf_P(log_data, sizeof(log_data), tmp);
- //AddLog(LOG_LEVEL_INFO);
+ //AddLog_P(LOG_LEVEL_INFO, tmp);
// replace vars in cmd
char *tmp=cmdmem+SCRIPT_CMDMEM/2;
Replace_Cmd_Vars(cmd,tmp,SCRIPT_CMDMEM/2);
@@ -2690,8 +2748,7 @@ int16_t Run_Scripter(const char *type, int8_t tlen, char *js) {
} else {
if (!sflag) {
tasm_cmd_activ=1;
- snprintf_P(log_data, sizeof(log_data), PSTR("Script: performs \"%s\""), tmp);
- AddLog(glob_script_mem.script_loglevel&0x7f);
+ AddLog_P2(glob_script_mem.script_loglevel&0x7f, PSTR("Script: performs \"%s\""), tmp);
} else if (sflag==2) {
// allow recursive call
} else {
@@ -2991,7 +3048,7 @@ void ScripterEvery100ms(void) {
if (fast_script==99) Run_Scripter(">F",2,0);
}
-//mems[MAX_RULE_MEMS] is 50 bytes in 6.5
+//mems[5] is 50 bytes in 6.5
// can hold 11 floats or floats + strings
// should report overflow later
void Scripter_save_pvars(void) {
@@ -3003,7 +3060,7 @@ void Scripter_save_pvars(void) {
if (vtp[count].bits.is_permanent && !vtp[count].bits.is_string) {
uint8_t index=vtp[count].index;
mlen+=sizeof(float);
- if (mlen>MAX_RULE_MEMS*10) {
+ if (mlen>PMEM_SIZE) {
vtp[count].bits.is_permanent=0;
return;
}
@@ -3017,7 +3074,7 @@ void Scripter_save_pvars(void) {
char *sp=glob_script_mem.glob_snp+(index*glob_script_mem.max_ssize);
uint8_t slen=strlen(sp);
mlen+=slen+1;
- if (mlen>MAX_RULE_MEMS*10) {
+ if (mlen>PMEM_SIZE) {
vtp[count].bits.is_permanent=0;
return;
}
@@ -3031,11 +3088,6 @@ void Scripter_save_pvars(void) {
#ifdef USE_WEBSERVER
#define WEB_HANDLE_SCRIPT "s10"
-#define D_CONFIGURE_SCRIPT "Edit script"
-#define D_SCRIPT "edit script"
-#define D_SDCARD_UPLOAD "file upload"
-#define D_SDCARD_DIR "sd card directory"
-#define D_UPL_DONE "Done"
const char S_CONFIGURE_SCRIPT[] PROGMEM = D_CONFIGURE_SCRIPT;
@@ -3049,7 +3101,7 @@ const char HTTP_FORM_SCRIPT[] PROGMEM =
const char HTTP_FORM_SCRIPT1[] PROGMEM =
"
"
- "script enable "
+ "" D_SCRIPT_ENABLE " "
"