Add support for improv as used by esp-web-tools

This commit is contained in:
Theo Arends 2022-04-02 11:43:29 +02:00
parent 4b127aca00
commit c3132594d3
10 changed files with 408 additions and 8 deletions

View File

@ -9,6 +9,7 @@ Note: `minimal` variant is not listed as it shouldn't be used outside of the [up
| Feature or Sensor | l | t | k | s | i | d | Remarks | Feature or Sensor | l | t | k | s | i | d | Remarks
|-----------------------|---|-------|---|---|---|---|-------- |-----------------------|---|-------|---|---|---|---|--------
| MY_LANGUAGE en_GB | x | x / x | x | x | x | x | | MY_LANGUAGE en_GB | x | x / x | x | x | x | x |
| USE_IMPROV | - | x / x | x | x | x | x |
| USE_UFILESYS | - | - / x | - | - | - | - | | USE_UFILESYS | - | - / x | - | - | - | - |
| USE_ARDUINO_OTA | - | - / - | - | - | - | - | | USE_ARDUINO_OTA | - | - / - | - | - | - | - |
| USE_DOMOTICZ | - | x / x | x | x | x | - | | USE_DOMOTICZ | - | x / x | x | x | x | - |

View File

@ -3,7 +3,17 @@ All notable changes to this project will be documented in this file.
## [Unreleased] - Development ## [Unreleased] - Development
## [11.0.0.4] ## [11.0.0.5]
### Added
- Support for improv as used by esp-web-tools
### Changed
### Fixed
## [11.0.0.4] 20220402
### Added ### Added
- Command ``RtcNtpserver 0/1`` to enable Tasmota NTP server when enabled by define ``RTC_NTP_SERVER`` - Command ``RtcNtpserver 0/1`` to enable Tasmota NTP server when enabled by define ``RTC_NTP_SERVER``
- NeoPool JSON modules, power module, cell info, chlorine, conductivity and ionization - NeoPool JSON modules, power module, cell info, chlorine, conductivity and ionization

View File

@ -103,7 +103,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
[Complete list](BUILDS.md) of available feature and sensors. [Complete list](BUILDS.md) of available feature and sensors.
## Changelog v11.0.0.4 ## Changelog v11.0.0.5
### Added ### Added
- Command ``SetOption135 1`` to disable LVGL splash screen - Command ``SetOption135 1`` to disable LVGL splash screen
- Command ``SetOption136 1`` to disable single sensor reports from Tuya devices while keeping teleperiod reports [#15216](https://github.com/arendst/Tasmota/issues/15216) - Command ``SetOption136 1`` to disable single sensor reports from Tuya devices while keeping teleperiod reports [#15216](https://github.com/arendst/Tasmota/issues/15216)
@ -114,6 +114,7 @@ The latter links can be used for OTA upgrades too like ``OtaUrl http://ota.tasmo
- NeoPool commands ``NPpHMin``, ``NPpHMax``, ``NPpH``, ``NPRedox``, ``NPHydrolysis``, ``NPIonization``, ``NPChlorine`` and ``NPControl`` [#15015](https://github.com/arendst/Tasmota/issues/15015) - NeoPool commands ``NPpHMin``, ``NPpHMax``, ``NPpH``, ``NPRedox``, ``NPHydrolysis``, ``NPIonization``, ``NPChlorine`` and ``NPControl`` [#15015](https://github.com/arendst/Tasmota/issues/15015)
- NeoPool system voltages display - NeoPool system voltages display
- TasmotaSerial implement ``end()`` - TasmotaSerial implement ``end()``
- Support for improv as used by esp-web-tools
- Support for up to four DS3502 digital potentiometers with command ``Wiper<x> 0..127`` - Support for up to four DS3502 digital potentiometers with command ``Wiper<x> 0..127``
- Support for ADE7880 3 phase energy monitor as used in Shelly 3EM [#13515](https://github.com/arendst/Tasmota/issues/13515) - Support for ADE7880 3 phase energy monitor as used in Shelly 3EM [#13515](https://github.com/arendst/Tasmota/issues/13515)
- Support for PCF85363 RTC as used in Shelly 3EM [#13515](https://github.com/arendst/Tasmota/issues/13515) - Support for PCF85363 RTC as used in Shelly 3EM [#13515](https://github.com/arendst/Tasmota/issues/13515)

View File

@ -397,6 +397,7 @@
// -- Wifi Config tools --------------------------- // -- Wifi Config tools ---------------------------
#define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 13 as used by Wi-Fi Manager web GUI #define WIFI_SOFT_AP_CHANNEL 1 // Soft Access Point Channel number between 1 and 13 as used by Wi-Fi Manager web GUI
#define USE_IMPROV // Add support for IMPROV serial protocol as used by esp-web-tools (+2k code)
// -- ESP-NOW ------------------------------------- // -- ESP-NOW -------------------------------------
//#define USE_TASMESH // Enable Tasmota Mesh using ESP-NOW (+11k code) //#define USE_TASMESH // Enable Tasmota Mesh using ESP-NOW (+11k code)

View File

@ -833,8 +833,9 @@ typedef struct {
uint32_t baudrate; // 2CC uint32_t baudrate; // 2CC
uint32_t ultradeepsleep; // 2D0 uint32_t ultradeepsleep; // 2D0
uint16_t deepsleep_slip; // 2D4 uint16_t deepsleep_slip; // 2D4
uint8_t improv_state; // 2D6
uint8_t free_2d6[2]; // 2D6 uint8_t free_2d7[1]; // 2D7
int32_t energy_kWhtoday_ph[3]; // 2D8 int32_t energy_kWhtoday_ph[3]; // 2D8
int32_t energy_kWhtotal_ph[3]; // 2E4 int32_t energy_kWhtotal_ph[3]; // 2E4

View File

@ -803,7 +803,9 @@ void ResponseAppendFeatures(void)
#if defined(USE_I2C) && defined(USE_DS3502) #if defined(USE_I2C) && defined(USE_DS3502)
feature8 |= 0x02000000; // xdrv_61_ds3502.ino feature8 |= 0x02000000; // xdrv_61_ds3502.ino
#endif #endif
// feature8 |= 0x04000000; #ifdef USE_IMPROV
feature8 |= 0x04000000; // xdrv_62_improv.ino
#endif
// feature8 |= 0x08000000; // feature8 |= 0x08000000;
// feature8 |= 0x10000000; // feature8 |= 0x10000000;

View File

@ -782,6 +782,7 @@
#undef FIRMWARE_DISPLAYS // Disable tasmota-display with display drivers enabled #undef FIRMWARE_DISPLAYS // Disable tasmota-display with display drivers enabled
#undef FIRMWARE_IR // Disable tasmota-ir with IR full protocols activated #undef FIRMWARE_IR // Disable tasmota-ir with IR full protocols activated
#undef USE_IMPROV // Disable support for IMPROV serial protocol as used by esp-web-tools (+2k code)
#undef USE_TASMESH // Disable Tasmota Mesh using ESP-NOW (+11k code) #undef USE_TASMESH // Disable Tasmota Mesh using ESP-NOW (+11k code)
#undef USE_ARDUINO_OTA // Disable support for Arduino OTA #undef USE_ARDUINO_OTA // Disable support for Arduino OTA
#undef USE_INFLUXDB // Disable influxdb support (+5k code) #undef USE_INFLUXDB // Disable influxdb support (+5k code)

View File

@ -20,6 +20,6 @@
#ifndef _TASMOTA_VERSION_H_ #ifndef _TASMOTA_VERSION_H_
#define _TASMOTA_VERSION_H_ #define _TASMOTA_VERSION_H_
const uint32_t VERSION = 0x0B000004; // 11.0.0.4 const uint32_t VERSION = 0x0B000005; // 11.0.0.5
#endif // _TASMOTA_VERSION_H_ #endif // _TASMOTA_VERSION_H_

383
tasmota/xdrv_62_improv.ino Normal file
View File

@ -0,0 +1,383 @@
/*
xdrv_62_improv.ino - IMPROV support for Tasmota
SPDX-FileCopyrightText: 2022 Theo Arends
SPDX-License-Identifier: GPL-3.0-only
*/
#ifdef USE_IMPROV
/*********************************************************************************************\
* Serial implementation of IMPROV for initial wifi configuration using esp-web-tools
*
* See https://esphome.github.io/esp-web-tools/ and https://www.improv-wifi.com/serial/
\*********************************************************************************************/
#define XDRV_62 62
#define IMPROV_WIFI_TIMEOUT 30 // Max seconds wait for wifi connection after reconfig
//#define IMPROV_DEBUG
enum ImprovError {
IMPROV_ERROR_NONE = 0x00,
IMPROV_ERROR_INVALID_RPC = 0x01,
IMPROV_ERROR_UNKNOWN_RPC = 0x02,
IMPROV_ERROR_UNABLE_TO_CONNECT = 0x03,
IMPROV_ERROR_NOT_AUTHORIZED = 0x04,
IMPROV_ERROR_UNKNOWN = 0xFF,
};
enum ImprovState {
IMPROV_STATE_STOPPED = 0x00,
IMPROV_STATE_AWAITING_AUTHORIZATION = 0x01,
IMPROV_STATE_AUTHORIZED = 0x02,
IMPROV_STATE_PROVISIONING = 0x03,
IMPROV_STATE_PROVISIONED = 0x04,
};
enum ImprovCommand {
IMPROV_UNKNOWN = 0x00,
IMPROV_WIFI_SETTINGS = 0x01,
IMPROV_GET_CURRENT_STATE = 0x02,
IMPROV_GET_DEVICE_INFO = 0x03,
IMPROV_GET_WIFI_NETWORKS = 0x04,
IMPROV_BAD_CHECKSUM = 0xFF,
};
enum ImprovSerialType {
IMPROV_TYPE_CURRENT_STATE = 0x01,
IMPROV_TYPE_ERROR_STATE = 0x02,
IMPROV_TYPE_RPC = 0x03,
IMPROV_TYPE_RPC_RESPONSE = 0x04
};
static const uint8_t IMPROV_SERIAL_VERSION = 1;
struct IMPROV {
uint32_t last_read_byte;
uint8_t wifi_timeout;
uint8_t seriallog_level;
bool message;
} Improv;
/*********************************************************************************************/
void ImprovWriteData(uint8_t* data, uint32_t size) {
data[0] = 'I';
data[1] = 'M';
data[2] = 'P';
data[3] = 'R';
data[4] = 'O';
data[5] = 'V';
data[6] = IMPROV_SERIAL_VERSION; // 0x01
uint8_t checksum = 0x00;
for (uint32_t i = 0; i < size -1; i++) {
checksum += data[i];
}
data[size -1] = checksum;
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("IMP: Send '%*_H'"), size, data);
// Serial.write(data, size);
for (uint32_t i = 0; i < size; i++) {
Serial.write(data[i]);
}
Serial.write('\n');
}
void ImprovSendCmndState(uint32_t command, uint32_t state) {
uint8_t data[11];
data[7] = command;
data[8] = 1;
data[9] = state;
ImprovWriteData(data, sizeof(data));
}
void ImprovSendState(uint32_t state) {
#ifdef IMPROV_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("IMP: State %d"), state);
#endif
RtcSettings.improv_state = state;
ImprovSendCmndState(IMPROV_TYPE_CURRENT_STATE, state); // 0x01
}
void ImprovSendError(uint32_t error) {
#ifdef IMPROV_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("IMP: Error %d"), error);
#endif
ImprovSendCmndState(IMPROV_TYPE_ERROR_STATE, error); // 0x02
}
void ImprovSendResponse(uint8_t* response, uint32_t size) {
uint8_t data[9 + size];
data[7] = IMPROV_TYPE_RPC_RESPONSE; // 0x04
data[8] = size -1;
memcpy(data +9, response, size);
ImprovWriteData(data, sizeof(data));
}
void ImprovSendSetting(uint32_t command) {
char data[100];
uint32_t len = 0;
#ifdef USE_WEBSERVER
len = ext_snprintf_P(data, sizeof(data), PSTR("01|http://%_I:%d|"), (uint32_t)WiFi.localIP(), WEB_PORT);
uint32_t str_pos = 2;
for (uint32_t i = 3; i < len; i++) {
if ('|' == data[i]) {
data[str_pos] = i - str_pos -1;
}
}
len -= 3;
#endif // USE_WEBSERVER
data[0] = command;
data[1] = len;
ImprovSendResponse((uint8_t*)data, len +3);
}
bool ImprovParseSerialByte(void) {
// 0 1 2 3 4 5 6 7 8 9 8 + le +1
// I M P R O V ve ty le data ... \n
// 49 4D 50 52 4F 56 01 xx yy ........ 0A
if (6 == TasmotaGlobal.serial_in_byte_counter) {
return (IMPROV_SERIAL_VERSION == TasmotaGlobal.serial_in_byte);
}
if (TasmotaGlobal.serial_in_byte_counter <= 8) {
return true; // Wait for type and length
}
uint32_t data_len = TasmotaGlobal.serial_in_buffer[8];
if (TasmotaGlobal.serial_in_byte_counter <= 9 + data_len) { // Receive including '\n'
return true; // Wait for data
}
AddLog(LOG_LEVEL_DEBUG_MORE, PSTR("IMP: Rcvd '%*_H'"), TasmotaGlobal.serial_in_byte_counter, TasmotaGlobal.serial_in_buffer);
TasmotaGlobal.serial_in_byte_counter--; // Drop '\n'
uint8_t checksum = 0x00;
for (uint32_t i = 0; i < TasmotaGlobal.serial_in_byte_counter; i++) {
checksum += TasmotaGlobal.serial_in_buffer[i];
}
if (checksum != TasmotaGlobal.serial_in_buffer[TasmotaGlobal.serial_in_byte_counter]) {
ImprovSendError(IMPROV_ERROR_INVALID_RPC); // 0x01 - CRC error
return false;
}
uint32_t type = TasmotaGlobal.serial_in_buffer[7];
if (IMPROV_TYPE_RPC == type) { // 0x03
uint32_t data_length = TasmotaGlobal.serial_in_buffer[10];
if (data_length != data_len - 2) {
return false;
}
uint32_t command = TasmotaGlobal.serial_in_buffer[9];
switch (command) {
case IMPROV_WIFI_SETTINGS: { // 0x01
// if (RtcSettings.improv_state != IMPROV_STATE_AUTHORIZED) {
// ImprovSendError(IMPROV_ERROR_NOT_AUTHORIZED); // 0x04
// } else {
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
// I M P R O V vs ty le co dl sl s s i d pl p a s s w o r d cr
uint32_t ssid_length = TasmotaGlobal.serial_in_buffer[11];
uint32_t ssid_end = 12 + ssid_length;
uint32_t pass_length = TasmotaGlobal.serial_in_buffer[ssid_end];
uint32_t pass_start = ssid_end + 1;
uint32_t pass_end = pass_start + pass_length;
TasmotaGlobal.serial_in_buffer[ssid_end] = '\0';
char* ssid = &TasmotaGlobal.serial_in_buffer[12];
TasmotaGlobal.serial_in_buffer[pass_end] = '\0';
char* password = &TasmotaGlobal.serial_in_buffer[pass_start];
#ifdef IMPROV_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("IMP: Ssid '%s', Password '%s'"), ssid, password);
#endif // IMPROV_DEBUG
Improv.wifi_timeout = IMPROV_WIFI_TIMEOUT; // Set WiFi connect timeout
ImprovSendState(IMPROV_STATE_PROVISIONING);
Settings->flag4.network_wifi = 1; // Enable WiFi
char cmnd[TOPSZ];
snprintf_P(cmnd, sizeof(cmnd), PSTR(D_CMND_BACKLOG "0 " D_CMND_SSID "1 %s;" D_CMND_PASSWORD "1 %s"), ssid, password);
ExecuteCommand(cmnd, SRC_SERIAL); // Set SSID and Password and restart
// }
break;
}
case IMPROV_GET_CURRENT_STATE: { // 0x02
ImprovSendState(RtcSettings.improv_state);
if (IMPROV_STATE_PROVISIONED == RtcSettings.improv_state) {
ImprovSendSetting(IMPROV_GET_CURRENT_STATE);
}
break;
}
case IMPROV_GET_DEVICE_INFO: { // 0x03
char data[200];
uint32_t len = snprintf_P(data, sizeof(data), PSTR("01|Tasmota|%s|%s|%s|"),
TasmotaGlobal.version, GetDeviceHardware().c_str(), SettingsText(SET_DEVICENAME));
data[0] = IMPROV_GET_DEVICE_INFO;
data[1] = len -3;
uint32_t str_pos = 2;
for (uint32_t i = 3; i < len; i++) {
if ('|' == data[i]) {
data[str_pos] = i - str_pos -1;
str_pos = i;
}
}
ImprovSendResponse((uint8_t*)data, len);
break;
}
case IMPROV_GET_WIFI_NETWORKS: { // 0x04
char data[200];
int n = WiFi.scanNetworks();
if (n) {
// Sort networks
int indices[n];
for (uint32_t i = 0; i < n; i++) {
indices[i] = i;
}
// RSSI SORT
for (uint32_t i = 0; i < n; i++) {
for (uint32_t j = i + 1; j < n; j++) {
if (WiFi.RSSI(indices[j]) > WiFi.RSSI(indices[i])) {
std::swap(indices[i], indices[j]);
}
}
}
// Remove duplicates ( must be RSSI sorted )
for (uint32_t i = 0; i < n; i++) {
if (-1 == indices[i]) { continue; }
String cssid = WiFi.SSID(indices[i]);
uint32_t cschn = WiFi.channel(indices[i]);
for (uint32_t j = i + 1; j < n; j++) {
if ((cssid == WiFi.SSID(indices[j])) && (cschn == WiFi.channel(indices[j]))) {
indices[j] = -1; // set dup aps to index -1
}
}
}
// Send networks
for (uint32_t i = 0; i < n; i++) {
if (-1 == indices[i]) { continue; } // Skip dups
int32_t rssi = WiFi.RSSI(indices[i]);
String ssid_copy = WiFi.SSID(indices[i]);
if (!ssid_copy.length()) { ssid_copy = F("no_name"); }
// Send each ssid separately to avoid overflowing the buffer
uint32_t len = snprintf_P(data, sizeof(data), PSTR("01|%s|%d|%s|"), ssid_copy.c_str(), rssi, (ENC_TYPE_NONE == WiFi.encryptionType(indices[i]))?"NO":"YES");
data[0] = IMPROV_GET_WIFI_NETWORKS;
data[1] = len -3;
uint32_t str_pos = 2;
for (uint32_t i = 3; i < len; i++) {
if ('|' == data[i]) {
data[str_pos] = i - str_pos -1;
str_pos = i;
}
}
ImprovSendResponse((uint8_t*)data, len);
}
}
// Send empty response to signify the end of the list.
data[0] = IMPROV_GET_WIFI_NETWORKS;
data[1] = 0; // Empty string
ImprovSendResponse((uint8_t*)data, 3);
break;
}
/*
case IMPROV_BAD_CHECKSUM: { // 0xFF
break;
}
*/
default:
ImprovSendError(IMPROV_ERROR_UNKNOWN_RPC); // 0x02 - Unknown payload
}
}
return false;
}
/*********************************************************************************************/
bool ImprovSerialInput(void) {
// Check if received data is IMPROV data
if (6 == TasmotaGlobal.serial_in_byte_counter) {
TasmotaGlobal.serial_in_buffer[TasmotaGlobal.serial_in_byte_counter] = 0;
if (!strcmp_P(TasmotaGlobal.serial_in_buffer, PSTR("IMPROV"))) {
Improv.seriallog_level = TasmotaGlobal.seriallog_level;
TasmotaGlobal.seriallog_level = 0; // Disable seriallogging interfering with IMPROV
Improv.last_read_byte = millis();
Improv.message = true;
}
}
if (Improv.message) {
uint32_t now = millis();
if (now - Improv.last_read_byte < 50) {
TasmotaGlobal.serial_in_buffer[TasmotaGlobal.serial_in_byte_counter] = TasmotaGlobal.serial_in_byte;
if (ImprovParseSerialByte()) {
TasmotaGlobal.serial_in_byte_counter++;
TasmotaGlobal.serial_in_byte = 0;
Improv.last_read_byte = now;
return false;
}
}
Improv.message = false;
TasmotaGlobal.seriallog_level = Improv.seriallog_level; // Restore seriallogging
return true;
}
return false;
}
void ImprovEverySecond(void) {
if (Improv.wifi_timeout) {
Improv.wifi_timeout--;
if (Improv.wifi_timeout < IMPROV_WIFI_TIMEOUT -3) { // Tasmota restarts after ssid or password change
if ((!TasmotaGlobal.global_state.wifi_down)) {
Improv.wifi_timeout = 0;
if (IMPROV_STATE_AUTHORIZED == RtcSettings.improv_state) {
RtcSettings.improv_state = IMPROV_STATE_PROVISIONED;
}
if (IMPROV_STATE_PROVISIONING == RtcSettings.improv_state) {
ImprovSendState(IMPROV_STATE_PROVISIONED);
ImprovSendSetting(IMPROV_WIFI_SETTINGS);
}
return;
}
}
if (!Improv.wifi_timeout) {
if (IMPROV_STATE_PROVISIONING == RtcSettings.improv_state) {
ImprovSendError(IMPROV_ERROR_UNABLE_TO_CONNECT); // 0x03 - WiFi connect timeout
ImprovSendState(IMPROV_STATE_AUTHORIZED);
}
}
}
}
void ImprovInit(void) {
if (!RtcSettings.improv_state) {
RtcSettings.improv_state = IMPROV_STATE_AUTHORIZED; // Power on state (persistent during restarts)
}
Improv.wifi_timeout = IMPROV_WIFI_TIMEOUT; // Try to update state after restart
#ifdef IMPROV_DEBUG
AddLog(LOG_LEVEL_DEBUG, PSTR("IMP: State %d"), RtcSettings.improv_state);
#endif // IMPROV_DEBUG
}
/*********************************************************************************************\
* Interface
\*********************************************************************************************/
bool Xdrv62(uint8_t function) {
bool result = false;
switch (function) {
case FUNC_EVERY_SECOND:
ImprovEverySecond();
break;
case FUNC_SERIAL:
result = ImprovSerialInput();
break;
case FUNC_PRE_INIT:
ImprovInit();
break;
}
return result;
}
#endif // USE_IMPROV

View File

@ -191,7 +191,7 @@ a_setoption = [[
"(PWM) force PWM lights to start at same phase, default is to spread phases to minimze overlap (also needed for H-bridge)", "(PWM) force PWM lights to start at same phase, default is to spread phases to minimze overlap (also needed for H-bridge)",
"(Display & LVGL) force disabling default splash screen", "(Display & LVGL) force disabling default splash screen",
"(TuyaSNS) When ON disable publish single SNS value on Tuya Receive (keep Teleperiod)", "(TuyaSNS) When ON disable publish single SNS value on Tuya Receive (keep Teleperiod)",
"", "(Tuya) When Set, avoid the (mqtt-) publish of Tuya MCU Heartbeat response if SetOption66 is active",
"","","","", "","","","",
"","","","" "","","",""
]] ]]
@ -266,7 +266,7 @@ a_features = [[
"USE_HRG15","USE_VINDRIKTNING","USE_SCD40","USE_HM330X", "USE_HRG15","USE_VINDRIKTNING","USE_SCD40","USE_HM330X",
"USE_HDC2010","USE_LSC_MCSL","USE_SONOFF_SPM","USE_SHIFT595", "USE_HDC2010","USE_LSC_MCSL","USE_SONOFF_SPM","USE_SHIFT595",
"USE_SDM230","USE_CM110x","USE_BL6523","USE_ADE7880", "USE_SDM230","USE_CM110x","USE_BL6523","USE_ADE7880",
"USE_PCF85363","USE_DS3502","","", "USE_PCF85363","USE_DS3502","USE_IMPROV","",
"","","","" "","","",""
]] ]]
@ -295,7 +295,7 @@ else:
obj = json.load(fp) obj = json.load(fp)
def StartDecode(): def StartDecode():
print ("\n*** decode-status.py v11.0.0.4 by Theo Arends and Jacek Ziolkowski ***") print ("\n*** decode-status.py v11.0.0.5 by Theo Arends and Jacek Ziolkowski ***")
# print("Decoding\n{}".format(obj)) # print("Decoding\n{}".format(obj))