diff --git a/.gitignore b/.gitignore index 96c63651b..503ccf780 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,8 @@ .clang_complete .gcc-flags.json .cache +data +unpacked_fs tasmota/user_config_override.h build build_output diff --git a/API.md b/API.md index 7bb77735b..b631172ed 100644 --- a/API.md +++ b/API.md @@ -14,8 +14,8 @@ Callback Id | Bool | xdrv | xsns | xnrg | xlgt | Description ----------------------------|------|------|------|------|------|---------------------------------- FUNC_SETTINGS_OVERRIDE | | x | | | | Override start-up settings FUNC_PIN_STATE | x | 1 | 2 | | | At GPIO configuration -FUNC_MODULE_INIT | x | 1 | | | 2 | Init module specific parameters -FUNC_PRE_INIT | | 1 | | 2 | | Once GPIO have been established +FUNC_MODULE_INIT | x | 3 | 1 | | 2 | Init module specific parameters +FUNC_PRE_INIT | | 1 | 3 | 2 | | Once GPIO have been established FUNC_INIT | | 1 | 3 | 2 | | At end of initialisation FUNC_LOOP | | 1 | 2 | | | In main loop FUNC_EVERY_50_MSECOND | | 1 | 2 | | | @@ -34,7 +34,7 @@ FUNC_COMMAND_SENSOR | x | | x | | | When command Se FUNC_MQTT_SUBSCRIBE | | x | | | | At end of MQTT subscriptions FUNC_MQTT_INIT | | x | | | | Once at end of MQTT connection FUNC_MQTT_DATA | x | x | | | | Before decoding command -FUNC_SET_POWER | | x | | | | Before setting relays +FUNC_SET_POWER | | 1 | 2 | | | Before setting relays FUNC_SET_DEVICE_POWER | x | x | | | | Set relay FUNC_SHOW_SENSOR | | x | | | | When FUNC_JSON_APPEND completes FUNC_ANY_KEY | | x | | | | @@ -49,6 +49,8 @@ FUNC_WEB_ADD_MAIN_BUTTON | | 1 | 2 | | | Add a main butt FUNC_WEB_ADD_HANDLER | | 1 | 2 | | | Add a webserver handler FUNC_SET_CHANNELS | | 2 | | | 1 | FUNC_SET_SCHEME | | | | | x | +FUNC_HOTPLUG_SCAN | | | x | | | +FUNC_DEVICE_GROUP_ITEM | | x | | | | The numbers represent the sequence of execution @@ -89,12 +91,15 @@ CFG: Loaded from flash at FB, Count 1581 xdrv - FUNC_SETTINGS_OVERRIDE xdrv - FUNC_PIN_STATE xsns - FUNC_PIN_STATE +xsns - FUNC_MODULE_INIT xdrv - FUNC_MODULE_INIT xlgt - FUNC_MODULE_INIT xdrv - FUNC_PRE_INIT xnrg - FUNC_PRE_INIT +xsns - FUNC_PRE_INIT SRC: Restart xdrv - FUNC_SET_POWER +xsns - FUNC_SET_POWER xlgt - FUNC_SET_CHANNELS xdrv - FUNC_SET_DEVICE_POWER Project tasmota Wemos 2 Version 7.0.0.3(tasmota)-STAGE diff --git a/CHANGELOG.md b/CHANGELOG.md index c4eb29802..79e41be9e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file. - Support for BS814A-2 8-button touch buttons by Peter Franck (#10447) - Support for up to 4 I2C SEESAW_SOIL Capacitance & Temperature sensors by Peter Franck (#10481) - ESP8266 Support for 2MB and up linker files with 1MB and up LittleFS +- ESP32 support for TLS MQTT using BearSSL (same as ESP8266) ### Breaking Changed - ESP32 switch from default SPIFFS to default LittleFS file system loosing current (zigbee) files @@ -18,6 +19,7 @@ All notable changes to this project will be documented in this file. ### Changed - Force initial default state ``SetOption57 1`` to scan wifi network every 44 minutes for strongest signal (#10395) +- Command ``Sleep 0`` removes any sleep from wifi modem except when ESP32 BLE is active ## [9.2.0.2] 20210105 ### Added diff --git a/I2CDEVICES.md b/I2CDEVICES.md index 22616d670..301b48111 100644 --- a/I2CDEVICES.md +++ b/I2CDEVICES.md @@ -75,7 +75,7 @@ Index | Define | Driver | Device | Address(es) | Description 50 | USE_VEML7700 | xsns_71 | VEML7700 | 0x10 | Ambient light intensity sensor 51 | USE_MCP9808 | xsns_72 | MCP9808 | 0x18 - 0x1F | Temperature sensor 52 | USE_HP303B | xsns_73 | HP303B | 0x76 - 0x77 | Pressure and temperature sensor - 53 | USE_MLX90640 | xdrv_84 | MLX90640 | 0x33 | IR array temperature sensor + 53 | USE_MLX90640 | xdrv_43 | MLX90640 | 0x33 | IR array temperature sensor 54 | USE_VL53L1X | xsns_77 | VL53L1X | 0x29 | Time-of-flight (ToF) distance sensor 55 | USE_EZOPH | xsns_78 | EZOPH | 0x61 - 0x70 | pH sensor 55 | USE_EZOORP | xsns_78 | EZOORP | 0x61 - 0x70 | ORP sensor diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 5981c4022..b3f8e38a0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -96,6 +96,7 @@ The attached binaries can also be downloaded from http://ota.tasmota.com/tasmota - Replaced RA8876 GPIO selection from ``SPI CS`` by ``RA8876 CS`` ### Changed +- Command ``Sleep 0`` removes any sleep from wifi modem except when ESP32 BLE is active - Logging from heap to stack freeing 700 bytes RAM - Disabled ``USE_LIGHT`` light support for ZBBridge saving 17.6kB [#10374](https://github.com/arendst/Tasmota/issues/10374) - Force initial default state ``SetOption57 1`` to scan wifi network every 44 minutes for strongest signal [#10395](https://github.com/arendst/Tasmota/issues/10395) diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp index 0e26883ba..9f932040c 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP32Wifi.cpp @@ -28,14 +28,12 @@ #undef WiFi #endif -void WiFiClass32::setSleepMode(int iSleepMode) -{ - // WIFI_MODEM_SLEEP - WiFi.setSleep(iSleepMode != WIFI_MODEM_SLEEP); +void WiFiClass32::setSleepMode(int iSleepMode) { + // WIFI_LIGHT_SLEEP and WIFI_MODEM_SLEEP + WiFi.setSleep(iSleepMode != WIFI_NONE_SLEEP); } -int WiFiClass32::getPhyMode() -{ +int WiFiClass32::getPhyMode() { int phy_mode = 0; // " BGNL" uint8_t protocol_bitmap; if (esp_wifi_get_protocol(WIFI_IF_STA, &protocol_bitmap) == ESP_OK) { @@ -47,12 +45,10 @@ int WiFiClass32::getPhyMode() return phy_mode; } -void WiFiClass32::wps_disable() -{ +void WiFiClass32::wps_disable() { } -void WiFiClass32::setOutputPower(int n) -{ +void WiFiClass32::setOutputPower(int n) { wifi_power_t p = WIFI_POWER_2dBm; if (n > 19) p = WIFI_POWER_19_5dBm; @@ -75,28 +71,23 @@ void WiFiClass32::setOutputPower(int n) WiFi.setTxPower(p); } -void WiFiClass32::forceSleepBegin() -{ +void WiFiClass32::forceSleepBegin() { } -void WiFiClass32::forceSleepWake() -{ +void WiFiClass32::forceSleepWake() { } -bool WiFiClass32::getNetworkInfo(uint8_t i, String &ssid, uint8_t &encType, int32_t &rssi, uint8_t *&bssid, int32_t &channel, bool &hidden_scan) -{ +bool WiFiClass32::getNetworkInfo(uint8_t i, String &ssid, uint8_t &encType, int32_t &rssi, uint8_t *&bssid, int32_t &channel, bool &hidden_scan) { hidden_scan = false; return WiFi.getNetworkInfo(i, ssid, encType, rssi, bssid, channel); } -void wifi_station_disconnect() -{ +void wifi_station_disconnect() { // erase ap: empty ssid, ... WiFi.disconnect(true, true); } -void wifi_station_dhcpc_start() -{ +void wifi_station_dhcpc_start() { } WiFiClass32 WiFi32; diff --git a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h index 9649f49f5..4a7c3ccc9 100644 --- a/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h +++ b/lib/libesp32/ESP32-to-ESP8266-compat/src/ESP8266WiFi.h @@ -28,6 +28,7 @@ #define ENC_TYPE_TKIP WIFI_AUTH_WPA_WPA2_PSK #define ENC_TYPE_AUTO WIFI_AUTH_MAX + 1 +#define WIFI_NONE_SLEEP 0 #define WIFI_LIGHT_SLEEP 1 #define WIFI_MODEM_SLEEP 2 diff --git a/pio-tools/download_fs.py b/pio-tools/download_fs.py new file mode 100644 index 000000000..4343adee3 --- /dev/null +++ b/pio-tools/download_fs.py @@ -0,0 +1,334 @@ +# Written by Maximilian Gerhardt +# 29th December 2020 +# License: Apache +# Expanded from functionality provided by PlatformIO's espressif32 and espressif8266 platforms, credited below. +# This script provides functions to download the filesystem (SPIFFS or LittleFS) from a running ESP32 / ESP8266 +# over the serial bootloader using esptool.py, and mklittlefs / mkspiffs for extracting. +# run by either using the VSCode task "Custom" -> "Download Filesystem" +# or by doing 'pio run -t downloadfs' (with optional '-e ') from the commandline. +# output will be saved, by default, in the "unpacked_fs" of the project. +# this folder can be changed by writing 'custom_unpack_dir = some_other_dir' in the corresponding platformio.ini +# environment. +import re +import sys +from os.path import isfile, join +from enum import Enum +import typing +from platformio.builder.tools.pioupload import AutodetectUploadPort +import os +import subprocess +import shutil + +Import("env") +platform = env.PioPlatform() +board = env.BoardConfig() +mcu = board.get("build.mcu", "esp32") +# Hack for using mklittlefs instead of mkspiffs -> needed since littlefs is not supported with this for ESP32 +if env["PIOPLATFORM"] == "espressif32": + #print("Replace MKSPIFFSTOOL with mklittlefs") + env.Replace( MKSPIFFSTOOL=platform.get_package_dir("tool-mklittlefs") + '/mklittlefs' ) + +# needed for later +AutodetectUploadPort(env) + +class FSType(Enum): + SPIFFS="spiffs" + LITTLEFS="littlefs" + FATFS="fatfs" + +class FSInfo: + def __init__(self, fs_type, start, length, page_size, block_size): + self.fs_type = fs_type + self.start = start + self.length = length + self.page_size = page_size + self.block_size = block_size + def __repr__(self): + return f"FS type {self.fs_type} Start {hex(self.start)} Len {self.length} Page size {self.page_size} Block size {self.block_size}" + # extract command supposed to be implemented by subclasses + def get_extract_cmd(self, input_file, output_dir): + raise NotImplementedError() + +class LittleFSInfo(FSInfo): + def __init__(self, start, length, page_size, block_size): + if env["PIOPLATFORM"] == "espressif32": + #for ESP32: retrieve and evaluate, e.g. to mkspiffs_espressif32_arduino + self.tool = env.subst(env["MKSPIFFSTOOL"]) + else: + self.tool = env["MKFSTOOL"] # from mkspiffs package + self.tool = join(platform.get_package_dir("tool-mklittlefs"), self.tool) + super().__init__(FSType.LITTLEFS, start, length, page_size, block_size) + def __repr__(self): + return f"FS type {self.fs_type} Start {hex(self.start)} Len {self.length} Page size {self.page_size} Block size {self.block_size} Tool: {self.tool}" + def get_extract_cmd(self, input_file, output_dir): + return [self.tool, "-b", str(self.block_size), "-p", str(self.page_size), "--unpack", output_dir, input_file] + + +class SPIFFSInfo(FSInfo): + def __init__(self, start, length, page_size, block_size): + if env["PIOPLATFORM"] == "espressif32": + #for ESP32: retrieve and evaluate, e.g. to mkspiffs_espressif32_arduino + self.tool = env.subst(env["MKSPIFFSTOOL"]) + else: + self.tool = env["MKFSTOOL"] # from mkspiffs package + self.tool = join(platform.get_package_dir("tool-mkspiffs"), self.tool) + super().__init__(FSType.SPIFFS, start, length, page_size, block_size) + def __repr__(self): + return f"FS type {self.fs_type} Start {hex(self.start)} Len {self.length} Page size {self.page_size} Block size {self.block_size} Tool: {self.tool}" + def get_extract_cmd(self, input_file, output_dir): + return f'"{self.tool}" -b {self.block_size} -p {self.page_size} --unpack "{output_dir}" "{input_file}"' + +# SPIFFS helpers copied from ESP32, https://github.com/platformio/platform-espressif32/blob/develop/builder/main.py +# Copyright 2014-present PlatformIO +# Licensed under the Apache License, Version 2.0 (the "License"); + +def _parse_size(value): + if isinstance(value, int): + return value + elif value.isdigit(): + return int(value) + elif value.startswith("0x"): + return int(value, 16) + elif value[-1].upper() in ("K", "M"): + base = 1024 if value[-1].upper() == "K" else 1024 * 1024 + return int(value[:-1]) * base + return value + +def _parse_partitions(env): + partitions_csv = env.subst("$PARTITIONS_TABLE_CSV") + if not isfile(partitions_csv): + sys.stderr.write("Could not find the file %s with partitions " + "table.\n" % partitions_csv) + env.Exit(1) + return + + result = [] + next_offset = 0 + with open(partitions_csv) as fp: + for line in fp.readlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + tokens = [t.strip() for t in line.split(",")] + if len(tokens) < 5: + continue + partition = { + "name": tokens[0], + "type": tokens[1], + "subtype": tokens[2], + "offset": tokens[3] or next_offset, + "size": tokens[4], + "flags": tokens[5] if len(tokens) > 5 else None + } + result.append(partition) + next_offset = (_parse_size(partition['offset']) + + _parse_size(partition['size'])) + return result + +def esp32_fetch_spiffs_size(env): + spiffs = None + for p in _parse_partitions(env): + if p['type'] == "data" and p['subtype'] == "spiffs": + spiffs = p + if not spiffs: + sys.stderr.write( + env.subst("Could not find the `spiffs` section in the partitions " + "table $PARTITIONS_TABLE_CSV\n")) + env.Exit(1) + return + env["SPIFFS_START"] = _parse_size(spiffs['offset']) + env["SPIFFS_SIZE"] = _parse_size(spiffs['size']) + env["SPIFFS_PAGE"] = int("0x100", 16) + env["SPIFFS_BLOCK"] = int("0x1000", 16) + +## FS helpers for ESP8266 +# copied from https://github.com/platformio/platform-espressif8266/blob/develop/builder/main.py +# Copyright 2014-present PlatformIO +# Licensed under the Apache License, Version 2.0 (the "License"); + +def _get_board_f_flash(env): + frequency = env.subst("$BOARD_F_FLASH") + frequency = str(frequency).replace("L", "") + return int(int(frequency) / 1000000) + +def _parse_ld_sizes(ldscript_path): + assert ldscript_path + result = {} + # get flash size from board's manifest + result['flash_size'] = int(env.BoardConfig().get("upload.maximum_size", 0)) + # get flash size from LD script path + match = re.search(r"\.flash\.(\d+[mk]).*\.ld", ldscript_path) + if match: + result['flash_size'] = _parse_size(match.group(1)) + + appsize_re = re.compile( + r"irom0_0_seg\s*:.+len\s*=\s*(0x[\da-f]+)", flags=re.I) + filesystem_re = re.compile( + r"PROVIDE\s*\(\s*_%s_(\w+)\s*=\s*(0x[\da-f]+)\s*\)" % "FS" + if "arduino" in env.subst("$PIOFRAMEWORK") + else "SPIFFS", + flags=re.I, + ) + with open(ldscript_path) as fp: + for line in fp.readlines(): + line = line.strip() + if not line or line.startswith("/*"): + continue + match = appsize_re.search(line) + if match: + result['app_size'] = _parse_size(match.group(1)) + continue + match = filesystem_re.search(line) + if match: + result['fs_%s' % match.group(1)] = _parse_size( + match.group(2)) + return result + +def _get_flash_size(env): + ldsizes = _parse_ld_sizes(env.GetActualLDScript()) + if ldsizes['flash_size'] < 1048576: + return "%dK" % (ldsizes['flash_size'] / 1024) + return "%dM" % (ldsizes['flash_size'] / 1048576) + +def esp8266_fetch_fs_size(env): + ldsizes = _parse_ld_sizes(env.GetActualLDScript()) + for key in ldsizes: + if key.startswith("fs_"): + env[key.upper()] = ldsizes[key] + + assert all([ + k in env + for k in ["FS_START", "FS_END", "FS_PAGE", "FS_BLOCK"] + ]) + + # esptool flash starts from 0 + for k in ("FS_START", "FS_END"): + _value = 0 + if env[k] < 0x40300000: + _value = env[k] & 0xFFFFF + elif env[k] < 0x411FB000: + _value = env[k] & 0xFFFFFF + _value -= 0x200000 # correction + else: + _value = env[k] & 0xFFFFFF + _value += 0xE00000 # correction + + env[k] = _value + +def esp8266_get_esptoolpy_reset_flags(resetmethod): + # no dtr, no_sync + resets = ("no_reset_no_sync", "soft_reset") + if resetmethod == "nodemcu": + # dtr + resets = ("default_reset", "hard_reset") + elif resetmethod == "ck": + # no dtr + resets = ("no_reset", "soft_reset") + + return ["--before", resets[0], "--after", resets[1]] + +## Script interface functions + +def get_fs_type_start_and_length(): + platform = env["PIOPLATFORM"] + if platform == "espressif32": + print("Retrieving filesystem info for ESP32. Assuming SPIFFS.") + print("Partition file: " + str(env.subst("$PARTITIONS_TABLE_CSV"))) + esp32_fetch_spiffs_size(env) + return SPIFFSInfo(env["SPIFFS_START"], env["SPIFFS_SIZE"], env["SPIFFS_PAGE"], env["SPIFFS_BLOCK"]) + elif platform == "espressif8266": + print("Retrieving filesystem info for ESP8266.") + filesystem = board.get("build.filesystem", "spiffs") + if filesystem not in ("spiffs", "littlefs"): + print("Unrecognized board_build.filesystem option '" + str(filesystem) + "'.") + env.Exit(1) + # fetching sizes is the same for all filesystems + esp8266_fetch_fs_size(env) + print("FS_START: " + hex(env["FS_START"])) + print("FS_END: " + hex(env["FS_END"])) + print("FS_PAGE: " + hex(env["FS_PAGE"])) + print("FS_BLOCK: " + hex(env["FS_BLOCK"])) + if filesystem == "spiffs": + print("Recognized SPIFFS filesystem.") + return SPIFFSInfo(env["FS_START"], env["FS_END"] - env["FS_START"], env["FS_PAGE"], env["FS_BLOCK"]) + elif filesystem == "littlefs": + print("Recognized LittleFS filesystem.") + return LittleFSInfo(env["FS_START"], env["FS_END"] - env["FS_START"], env["FS_PAGE"], env["FS_BLOCK"]) + else: + print("Unrecongized configuration.") + pass + +def download_fs(fs_info: FSInfo): + esptoolpy = join(platform.get_package_dir("tool-esptoolpy") or "", "esptool.py") + fs_file = join(env["PROJECT_DIR"], f"downloaded_fs_{hex(fs_info.start)}_{hex(fs_info.length)}.bin") + esptoolpy_flags = [ + "--chip", mcu, + "--port", env.subst("$UPLOAD_PORT"), + "--baud", env.subst("$UPLOAD_SPEED"), + "--before", "default_reset", + "--after", "hard_reset", + "read_flash", + hex(fs_info.start), + hex(fs_info.length), + fs_file + ] + esptoolpy_cmd = [env["PYTHONEXE"], esptoolpy] + esptoolpy_flags + print("Executing flash download command.") + print(esptoolpy_cmd) + try: + returncode = subprocess.call(esptoolpy_cmd, shell=False) + print("Downloaded filesystem binary.") + return (True, fs_file) + except subprocess.CalledProcessError as exc: + print("Downloading failed with " + str(exc)) + return (False, "") + +def unpack_fs(fs_info: FSInfo, downloaded_file: str): + # by writing custom_unpack_dir = some_dir in the platformio.ini, one can + # control the unpack directory + unpack_dir = env.GetProjectOption("custom_unpack_dir", "unpacked_fs") + #unpack_dir = "unpacked_fs" + try: + if os.path.exists(unpack_dir): + shutil.rmtree(unpack_dir) + except Exception as exc: + print("Exception while attempting to remove the folder '" + str(unpack_dir) + "': " + str(exc)) + if not os.path.exists(unpack_dir): + os.makedirs(unpack_dir) + + cmd = fs_info.get_extract_cmd(downloaded_file, unpack_dir) + print("Executing extraction command: " + str(cmd)) + try: + returncode = subprocess.call(cmd, shell=False) + print("Unpacked filesystem.") + return (True, unpack_dir) + except subprocess.CalledProcessError as exc: + print("Unpacking filesystem failed with " + str(exc)) + return (False, "") + +def display_fs(extracted_dir): + # extract command already nicely lists all extracted files. + # no need to display that ourselves. just display a summary + file_count = sum([len(files) for r, d, files in os.walk(extracted_dir)]) + print("Extracted " + str(file_count) + " file(s) from filesystem.") + +def command_download_fs(*args, **kwargs): + print("Entrypoint") + #print(env.Dump()) + info = get_fs_type_start_and_length() + print("Parsed FS info: " + str(info)) + download_ok, downloaded_file = download_fs(info) + print("Download was okay: " + str(download_ok) + ". File at: "+ str(downloaded_file)) + unpack_ok, unpacked_dir = unpack_fs(info, downloaded_file) + if unpack_ok is True: + display_fs(unpacked_dir) + +env.AddCustomTarget( + name="downloadfs", + dependencies=None, + actions=[ + command_download_fs + ], + title="Download Filesystem", + description="Downloads and displays files stored in the target ESP32/ESP8266" +) diff --git a/platformio.ini b/platformio.ini index c03175200..12ccfcc90 100644 --- a/platformio.ini +++ b/platformio.ini @@ -66,6 +66,8 @@ default_envs = ${build_envs.default_envs} [common] framework = arduino board = esp01_1m +board_build.filesystem = littlefs +custom_unpack_dir = unpacked_littlefs board_build.flash_mode = dout board_build.ldscript = eagle.flash.1m.ld @@ -76,7 +78,8 @@ build_flags = ${core.build_flags} board_build.f_cpu = 80000000L board_build.f_flash = 40000000L -monitor_speed = 115200 +monitor_speed = 74880 +monitor_port = COM5 upload_speed = 115200 ; *** Upload Serial reset method for Wemos and NodeMCU upload_resetmethod = nodemcu @@ -98,6 +101,7 @@ extra_scripts = pio-tools/strip-floats.py pio-tools/name-firmware.py pio-tools/gzip-firmware.py pio-tools/override_copy.py + pio-tools/download_fs.py [esp_defaults] ; *** remove undesired all warnings diff --git a/platformio_override_sample.ini b/platformio_override_sample.ini index 3d624c30f..4db6a41b3 100644 --- a/platformio_override_sample.ini +++ b/platformio_override_sample.ini @@ -48,6 +48,19 @@ build_flags = ${core.build_flags} ; -DDEBUG_TASMOTA_DRIVER ; -DDEBUG_TASMOTA_SENSOR +; *** CAUTION *** This setting is ONLY possible since 12.01.2021 with development version !!! +; *** Enable only if you exactly know what are you doing +; *** If you try with earlier builds a serial erase and flash is probably needed +; +; Build variant 1MB = 1MB firmware no filesystem (default) +;board_build.ldscript = eagle.flash.1m.ld +; Build variant 2MB = 1MB firmware, +744k OTA, 256k filesystem (Zigbee Bridge, most Shelly devices) +;board_build.ldscript = eagle.flash.2m256.ld +; Build variant 4MB = 1MB firmware, +1MB OTA, 2MB filesystem (WEMOS D1 Mini, NodeMCU, Sonoff POW) +;board_build.ldscript = eagle.flash.4m2m.ld +; Build variant 16MB = 1MB firmware, +1MB OTA, 14MB filesystem (WEMOS D1 Mini pro, Ledunia (=32MB)) +;board_build.ldscript = eagle.flash.16m14m.ld + ; set CPU frequency to 80MHz (default) or 160MHz ;board_build.f_cpu = 160000000L @@ -103,39 +116,27 @@ build_flags = ${esp82xx_defaults.build_flags} -DWAVEFORM_LOCKED_PWM -Wno-switch-unreachable - [common32] -platform = ${core32.platform} platform_packages = ${core32.platform_packages} build_unflags = ${core32.build_unflags} build_flags = ${core32.build_flags} -board = esp32dev -board_build.ldscript = esp32_out.ld -board_build.partitions = esp32_partition_app1984k_spiffs64k.csv -board_build.flash_mode = ${common.board_build.flash_mode} -board_build.f_flash = ${common.board_build.f_flash} -board_build.f_cpu = ${common.board_build.f_cpu} -monitor_speed = ${common.monitor_speed} -upload_port = ${common.upload_port} -upload_resetmethod = ${common.upload_resetmethod} -upload_speed = 921600 -extra_scripts = ${common.extra_scripts} +upload_port = COM4 lib_extra_dirs = ${library.lib_extra_dirs} ; *** ESP32 lib. ALWAYS needed for ESP32 !!! lib/libesp32 + [core32] ; Activate Stage Core32 by removing ";" in next 3 lines, if you want to override the standard core32 ;platform_packages = ${core32_stage.platform_packages} ;build_unflags = ${core32_stage.build_unflags} ;build_flags = ${core32_stage.build_flags} - [core32_stage] -platform_packages = framework-arduinoespressif32 @ https://github.com/espressif/arduino-esp32.git#2452c1fb539246e47a715b74a3ad25b8a7ebbec7 +platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/arduino-esp32/releases/download/1.0.5-rc6/esp32-1.0.5-rc6.zip platformio/tool-mklittlefs @ ~1.203.200522 build_unflags = ${esp32_defaults.build_unflags} build_flags = ${esp32_defaults.build_flags} - -DESP32_STAGE=true + ;-DESP32_STAGE=true [library] lib_ldf_mode = chain+ @@ -165,6 +166,7 @@ lib_extra_dirs = [env:tasmota32solo1] extends = env:tasmota32 platform_packages = framework-arduinoespressif32 @ https://github.com/Jason2866/esp32-arduino-lib-builder/raw/framework-arduinoespressif32/framework-arduinoespressif32-release_v3.3-solo1-4b325f52e.tar.gz + platformio/tool-mklittlefs @ ~1.203.200522 platformio/tool-esptoolpy @ ~1.30000.0 build_unflags = ${esp32_defaults.build_unflags} build_flags = ${common32.build_flags} diff --git a/platformio_tasmota32.ini b/platformio_tasmota32.ini index 7234d9417..a41222bb4 100644 --- a/platformio_tasmota32.ini +++ b/platformio_tasmota32.ini @@ -47,6 +47,8 @@ platform_packages = ${core32.platform_packages} build_unflags = ${core32.build_unflags} build_flags = ${core32.build_flags} board = esp32dev +board_build.filesystem = ${common.board_build.filesystem} +custom_unpack_dir = ${common.custom_unpack_dir} board_build.ldscript = esp32_out.ld board_build.partitions = esp32_partition_app1984k_spiffs64k.csv board_build.flash_mode = ${common.board_build.flash_mode} diff --git a/platformio_tasmota_env.ini b/platformio_tasmota_env.ini index f24794b68..ce05c138f 100644 --- a/platformio_tasmota_env.ini +++ b/platformio_tasmota_env.ini @@ -3,6 +3,7 @@ platform = ${common.platform} platform_packages = ${common.platform_packages} framework = ${common.framework} board = ${common.board} +board_build.filesystem = ${common.board_build.filesystem} board_build.ldscript = ${common.board_build.ldscript} board_build.flash_mode = ${common.board_build.flash_mode} board_build.f_flash = ${common.board_build.f_flash} @@ -64,6 +65,7 @@ build_flags = ${common.build_flags} ${irremoteesp_full.build_flags} [env:tasmota-zbbridge] build_flags = ${common.build_flags} -DFIRMWARE_ZBBRIDGE +board_build.ldscript = eagle.flash.2m256.ld board_build.f_cpu = 160000000L lib_extra_dirs = lib/lib_ssl diff --git a/tasmota/StackThunk_light.cpp b/tasmota/StackThunk_light.cpp index aea7ae438..b85e19033 100644 --- a/tasmota/StackThunk_light.cpp +++ b/tasmota/StackThunk_light.cpp @@ -28,6 +28,7 @@ #include "my_user_config.h" #include "tasmota_configurations.h" +#if defined(ESP8266) && defined(USE_TLS) #include #include #include "StackThunk_light.h" @@ -121,27 +122,29 @@ uint32_t stack_thunk_light_get_max_usage() } /* Print the stack from the first used 16-byte chunk to the top, decodable by the exception decoder */ -void stack_thunk_light_dump_stack() -{ - uint32_t *pos = stack_thunk_light_top; - while (pos < stack_thunk_light_ptr) { - if ((pos[0] != _stackPaint) || (pos[1] != _stackPaint) || (pos[2] != _stackPaint) || (pos[3] != _stackPaint)) - break; - pos += 4; - } - ets_printf(">>>stack>>>\n"); - while (pos < stack_thunk_light_ptr) { - ets_printf("%08x: %08x %08x %08x %08x\n", (int32_t)pos, pos[0], pos[1], pos[2], pos[3]); - pos += 4; - } - ets_printf("<<>>stack>>>\n"); +// while (pos < stack_thunk_light_ptr) { +// ets_printf("%08x: %08x %08x %08x %08x\n", (int32_t)pos, pos[0], pos[1], pos[2], pos[3]); +// pos += 4; +// } +// ets_printf("<< +#ifdef ESP8266 + #include +#endif #include "c_types.h" #include @@ -57,11 +59,15 @@ extern "C" { #include "coredecls.h" #define LOG_HEAP_SIZE(a) _Log_heap_size(a) void _Log_heap_size(const char *msg) { +#ifdef ESP8266 register uint32_t *sp asm("a1"); int freestack = 4 * (sp - g_pcont->stack); Serial.printf("%s %d, Fragmentation=%d, Thunkstack=%d, Free stack=%d, FreeContStack=%d\n", msg, ESP.getFreeHeap(), ESP.getHeapFragmentation(), stack_thunk_light_get_max_usage(), freestack, ESP.getFreeContStack()); +#elif defined(ESP32) + Serial.printf("> Heap %s = %d\n", msg, uxTaskGetStackHighWaterMark(nullptr)); +#endif } #else #define LOG_HEAP_SIZE(a) @@ -71,6 +77,7 @@ void _Log_heap_size(const char *msg) { extern uint32_t UtcTime(void); extern uint32_t CfgTime(void); +#ifdef ESP8266 // Stack thunk is not needed with ESP32 // Stack thunked versions of calls // Initially in BearSSLHelpers.h extern "C" { @@ -164,6 +171,8 @@ unsigned char *min_br_ssl_engine_sendrec_buf(const br_ssl_engine_context *cc, si #define br_ssl_engine_sendrec_ack min_br_ssl_engine_sendrec_ack #define br_ssl_engine_sendrec_buf min_br_ssl_engine_sendrec_buf +#endif // ESP8266 + //#define DEBUG_ESP_SSL #ifdef DEBUG_ESP_SSL //#define DEBUG_BSSL(fmt, ...) DEBUG_ESP_PORT.printf_P((PGM_P)PSTR( "BSSL:" fmt), ## __VA_ARGS__) @@ -201,19 +210,23 @@ void WiFiClientSecure_light::_clear() { // Constructor WiFiClientSecure_light::WiFiClientSecure_light(int recv, int xmit) : WiFiClient() { _clear(); -LOG_HEAP_SIZE("StackThunk before"); + // LOG_HEAP_SIZE("StackThunk before"); //stack_thunk_light_add_ref(); -LOG_HEAP_SIZE("StackThunk after"); + // LOG_HEAP_SIZE("StackThunk after"); // now finish the setup setBufferSizes(recv, xmit); // reasonable minimum allocateBuffers(); } WiFiClientSecure_light::~WiFiClientSecure_light() { +#ifdef ESP8266 if (_client) { _client->unref(); _client = nullptr; } +#elif defined(ESP32) + stop(); +#endif //_cipher_list = nullptr; // std::shared will free if last reference _freeSSL(); } @@ -258,6 +271,7 @@ void WiFiClientSecure_light::setBufferSizes(int recv, int xmit) { _iobuf_out_size = xmit; } +#ifdef ESP8266 bool WiFiClientSecure_light::stop(unsigned int maxWaitMs) { bool ret = WiFiClient::stop(maxWaitMs); // calls our virtual flush() _freeSSL(); @@ -268,6 +282,17 @@ bool WiFiClientSecure_light::flush(unsigned int maxWaitMs) { (void) _run_until(BR_SSL_SENDAPP); return WiFiClient::flush(maxWaitMs); } +#elif defined(ESP32) +void WiFiClientSecure_light::stop(void) { + WiFiClient::stop(); // calls our virtual flush() + _freeSSL(); +} + +void WiFiClientSecure_light::flush(void) { + (void) _run_until(BR_SSL_SENDAPP); + WiFiClient::flush(); +} +#endif int WiFiClientSecure_light::connect(IPAddress ip, uint16_t port) { DEBUG_BSSL("connect(%s,%d)", ip.toString().c_str(), port); @@ -307,7 +332,11 @@ void WiFiClientSecure_light::_freeSSL() { } bool WiFiClientSecure_light::_clientConnected() { +#ifdef ESP8266 return (_client && _client->state() == ESTABLISHED); +#elif defined(ESP32) + return WiFiClient::connected(); +#endif } uint8_t WiFiClientSecure_light::connected() { @@ -489,7 +518,7 @@ size_t WiFiClientSecure_light::peekBytes(uint8_t *buffer, size_t length) { achieved, this function returns 0. On error, it returns -1. */ int WiFiClientSecure_light::_run_until(unsigned target, bool blocking) { -//LOG_HEAP_SIZE("_run_until 1"); + //LOG_HEAP_SIZE("_run_until 1"); if (!ctx_present()) { DEBUG_BSSL("_run_until: Not connected\n"); return -1; @@ -506,9 +535,15 @@ int WiFiClientSecure_light::_run_until(unsigned target, bool blocking) { return -1; } +#ifdef ESP8266 if (!(_client->state() == ESTABLISHED) && !WiFiClient::available()) { return (state & target) ? 0 : -1; } +#elif defined(ESP32) + if (!_clientConnected() && !WiFiClient::available()) { + return (state & target) ? 0 : -1; + } +#endif /* If there is some record data to send, do it. This takes @@ -898,12 +933,16 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) { do { // used to exit on Out of Memory error and keep all cleanup code at the same place // ============================================================ // allocate Thunk stack, move to alternate stack and initialize +#ifdef ESP8266 stack_thunk_light_add_ref(); +#endif // ESP8266 LOG_HEAP_SIZE("Thunk allocated"); DEBUG_BSSL("_connectSSL: start connection\n"); _freeSSL(); clearLastError(); - if (!stack_thunk_light_get_stack_bot()) break; +#ifdef ESP8266 + if (!stack_thunk_light_get_stack_bot()) break; +#endif // ESP8266 _ctx_present = true; _eng = &_sc->eng; // Allocation/deallocation taken care of by the _sc shared_ptr @@ -964,10 +1003,12 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) { } #endif LOG_HEAP_SIZE("_connectSSL.end"); +#ifdef ESP8266 _max_thunkstack_use = stack_thunk_light_get_max_usage(); stack_thunk_light_del_ref(); //stack_thunk_light_repaint(); LOG_HEAP_SIZE("_connectSSL.end, freeing StackThunk"); +#endif // ESP8266 #ifdef USE_MQTT_TLS_CA_CERT free(x509_minimal); @@ -982,7 +1023,9 @@ bool WiFiClientSecure_light::_connectSSL(const char* hostName) { // if we arrived here, this means we had an OOM error, cleaning up setLastError(ERR_OOM); DEBUG_BSSL("_connectSSL: Out of memory\n"); +#ifdef ESP8266 stack_thunk_light_del_ref(); +#endif #ifdef USE_MQTT_TLS_CA_CERT free(x509_minimal); #else diff --git a/tasmota/WiFiClientSecureLightBearSSL.h b/tasmota/WiFiClientSecureLightBearSSL.h index f480a9d42..f88a9f17f 100755 --- a/tasmota/WiFiClientSecureLightBearSSL.h +++ b/tasmota/WiFiClientSecureLightBearSSL.h @@ -43,7 +43,11 @@ class WiFiClientSecure_light : public WiFiClient { uint8_t connected() override; size_t write(const uint8_t *buf, size_t size) override; + #ifdef ESP8266 size_t write_P(PGM_P buf, size_t size) override; + #else + size_t write_P(PGM_P buf, size_t size); + #endif size_t write(const char *buf) { return write((const uint8_t*)buf, strlen(buf)); } @@ -55,11 +59,17 @@ class WiFiClientSecure_light : public WiFiClient { int available() override; int read() override; int peek() override; + #ifdef ESP8266 size_t peekBytes(uint8_t *buffer, size_t length) override; bool flush(unsigned int maxWaitMs); bool stop(unsigned int maxWaitMs); void flush() override { (void)flush(0); } void stop() override { (void)stop(0); } + #else + size_t peekBytes(uint8_t *buffer, size_t length); + void flush() override; + void stop() override; + #endif // Only check SHA1 fingerprint of public key void setPubKeyFingerprint(const uint8_t *f1, const uint8_t *f2, diff --git a/tasmota/i18n.h b/tasmota/i18n.h index 0d18f016a..3262bef09 100644 --- a/tasmota/i18n.h +++ b/tasmota/i18n.h @@ -680,7 +680,7 @@ #define D_LOG_KNX "KNX: " #define D_LOG_LOG "LOG: " // Logging #define D_LOG_MODULE "MOD: " // Module -#define D_LOG_MDNS "DNS: " // mDNS +#define D_LOG_MDNS "mDN: " // mDNS #define D_LOG_MQTT "MQT: " // MQTT #define D_LOG_OTHER "OTH: " // Other #define D_LOG_RESULT "RSL: " // Result diff --git a/tasmota/language/de_DE.h b/tasmota/language/de_DE.h index 3ae7a842d..071bfaaa0 100644 --- a/tasmota/language/de_DE.h +++ b/tasmota/language/de_DE.h @@ -73,7 +73,7 @@ #define D_COMMAND "Befehl" #define D_CONNECTED "verbunden" #define D_CORS_DOMAIN "CORS Domain" -#define D_COUNT "zählen" +#define D_COUNT "Anzahl" // used as a noun throughout #define D_COUNTER "Zähler" #define D_CT_POWER "CT Power" #define D_CURRENT "Strom" // As in Voltage and Current @@ -143,8 +143,8 @@ #define D_POWERUSAGE_REACTIVE "Blindleistung" #define D_PRESSURE "Luftdruck" #define D_PRESSUREATSEALEVEL "Luftdruck auf Meereshöhe" -#define D_PROGRAM_FLASH_SIZE "Ges. Flash Speicher" -#define D_PROGRAM_SIZE "Ben. Flash Speicher" +#define D_PROGRAM_FLASH_SIZE "Flash nutzbar" +#define D_PROGRAM_SIZE "Größe Programm" #define D_PROJECT "Projekt" #define D_RAIN "Regen" #define D_RANGE "Bereich" @@ -333,7 +333,7 @@ #define D_PROGRAM_VERSION "Tasmota Version" #define D_BUILD_DATE_AND_TIME "Build-Datum & -Uhrzeit" #define D_CORE_AND_SDK_VERSION "Core-/SDK-Version" -#define D_FLASH_WRITE_COUNT "Anz. Flash Schreibzugriffe" +#define D_FLASH_WRITE_COUNT "Anz. Flash-Schreibzyklen" #define D_MAC_ADDRESS "MAC-Adresse" #define D_MQTT_HOST "MQTT Host" #define D_MQTT_PORT "MQTT Port" @@ -343,12 +343,12 @@ #define D_MQTT_GROUP_TOPIC "MQTT Group Topic" #define D_MQTT_FULL_TOPIC "MQTT Full Topic" #define D_MQTT_NO_RETAIN "MQTT No Retain" -#define D_MDNS_DISCOVERY "mDNS-Ermittlung" -#define D_MDNS_ADVERTISE "mDNS-Bekanntmachung" +#define D_MDNS_DISCOVERY "mDNS-Erkennung" +#define D_MDNS_ADVERTISE "mDNS-Freigaben" #define D_ESP_CHIP_ID "ESP Chip ID" #define D_FLASH_CHIP_ID "Flash Chip ID" -#define D_FLASH_CHIP_SIZE "Realer Flash Speicher" -#define D_FREE_PROGRAM_SPACE "Verf. Flash Speicher" +#define D_FLASH_CHIP_SIZE "Größe Flash-Chip" +#define D_FREE_PROGRAM_SPACE "Flash frei" #define D_UPGRADE_BY_WEBSERVER "Update über Web-Server" #define D_OTA_URL "OTA-URL" diff --git a/tasmota/my_user_config.h b/tasmota/my_user_config.h index cad1a2e83..12f47d073 100644 --- a/tasmota/my_user_config.h +++ b/tasmota/my_user_config.h @@ -828,10 +828,10 @@ #define THERMOSTAT_TEMP_BAND_NO_PEAK_DET 1 // Default temperature band in thenths of degrees celsius within no peak will be detected #define THERMOSTAT_TIME_STD_DEV_PEAK_DET_OK 10 // Default standard deviation in minutes of the oscillation periods within the peak detection is successful -// -- PID and Timeprop ------------------------------ -//#define USE_TIMEPROP // Add support for the timeprop feature (+0k8 code) +// -- PID and Timeprop ------------------------------ // Both together will add +12k1 code +// #define use TIMEPROP // Add support for the timeprop feature (+9k1 code) // For details on the configuration please see the header of tasmota/xdrv_48_timeprop.ino -//#define USE_PID // Add suport for the PID feature (+11k1 code) +// #define USE_PID // Add suport for the PID feature (+11k2 code) // For details on the configuration please see the header of tasmota/xdrv_49_pid.ino // -- End of general directives --------------------- diff --git a/tasmota/settings.ino b/tasmota/settings.ino index 518eb03d2..b2d438df6 100644 --- a/tasmota/settings.ino +++ b/tasmota/settings.ino @@ -154,19 +154,20 @@ bool RtcRebootValid(void) * 0x000xxxxx - Unzipped binary code end * 0x000x1000 - First page used by Core OTA * :::: - * 0x000F2FFF + * 0x000F2FFF 0x000F5FFF 0x000F5FFF ****************************************************************************** * Next 32k is overwritten by OTA - * 0x000F3000 - 4k Tasmota Quick Power Cycle counter (SETTINGS_LOCATION - CFG_ROTATES) - First four bytes only - * 0x000F3FFF - * 0x000F4000 - 4k First Tasmota rotating settings page + * 0x000F3000 0x000F6000 0x000F6000 - 4k Tasmota Quick Power Cycle counter (SETTINGS_LOCATION - CFG_ROTATES) - First four bytes only + * 0x000F3FFF 0x000F6FFF 0x000F6FFF + * 0x000F4000 0x000F7000 0x000F7000 - 4k First Tasmota rotating settings page * :::: - * 0x000FA000 - 4k Last Tasmota rotating settings page = Last page used by Core OTA (SETTINGS_LOCATION) - * 0x000FAFFF + * 0x000FA000 0x000FD000 0x000FD000 - 4k Last Tasmota rotating settings page = Last page used by Core OTA (SETTINGS_LOCATION) + * 0x000FAFFF 0x000FDFFF 0x000FDFFF ****************************************************************************** - * 0x000FB000 0x000FB000 - 15k9 Not used + * 0x000FE000 0x000FE000 - 3k9 Not used * 0x000FEFF0 0x000FEFF0 - 4k1 Empty * 0x000FFFFF 0x000FFFFF + * * 0x000FB000 0x00100000 0x00100000 - 0k, 980k or 2980k Core FS start (LittleFS) * 0x000FB000 0x001FA000 0x003FA000 - 0k, 980k or 2980k Core FS end (LittleFS) * 0x001FAFFF 0x003FAFFF @@ -189,7 +190,8 @@ extern "C" { #ifdef ESP8266 extern "C" uint32_t _FS_start; // 1M = 0x402fb000, 2M = 0x40300000, 4M = 0x40300000 -uint32_t SETTINGS_LOCATION = (((uint32_t)&_FS_start - 0x40200000) / SPI_FLASH_SEC_SIZE) -1; // 0xFA, 0xFF or 0xFF +const uint32_t FLASH_FS_START = (((uint32_t)&_FS_start - 0x40200000) / SPI_FLASH_SEC_SIZE); +uint32_t SETTINGS_LOCATION = FLASH_FS_START -1; // 0xFA, 0x0FF or 0x0FF // From libraries/EEPROM/EEPROM.cpp EEPROMClass extern "C" uint32_t _EEPROM_start; // 1M = 0x402FB000, 2M = 0x403FB000, 4M = 0x405FB000 @@ -205,14 +207,16 @@ uint32_t SETTINGS_LOCATION = FLASH_EEPROM_START; #endif // ESP32 -const uint8_t CFG_ROTATES = 7; // Number of flash sectors used (handles uploads) +const uint8_t CFG_ROTATES = 7; // Number of flash sectors used (handles uploads) uint32_t settings_location = FLASH_EEPROM_START; uint32_t settings_crc32 = 0; uint8_t *settings_buffer = nullptr; void SettingsInit(void) { - if (SETTINGS_LOCATION > 0xFA) { SETTINGS_LOCATION = 0xFA; } // Skip empty partition part + if (SETTINGS_LOCATION > 0xFA) { + SETTINGS_LOCATION = 0xFD; // Skip empty partition part and keep in first 1M + } } /********************************************************************************************/ @@ -534,6 +538,9 @@ void SettingsSave(uint8_t rotate) Settings.cfg_crc32 = GetSettingsCrc32(); #ifdef ESP8266 +#ifdef USE_UFILESYS + TfsSaveFile(TASM_FILE_SETTINGS, (const uint8_t*)&Settings, sizeof(Settings)); +#endif // USE_UFILESYS if (ESP.flashEraseSector(settings_location)) { ESP.flashWrite(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(Settings)); } @@ -559,39 +566,66 @@ void SettingsSave(uint8_t rotate) void SettingsLoad(void) { #ifdef ESP8266 - // Load configuration from eeprom or one of 7 slots below if first valid load does not stop_flash_rotate + // Load configuration from optional file and flash (eeprom and 7 additonal slots) if first valid load does not stop_flash_rotate // Activated with version 8.4.0.2 - Fails to read any config before version 6.6.0.11 settings_location = 0; uint32_t save_flag = 0; - uint32_t flash_location = FLASH_EEPROM_START; - for (uint32_t i = 0; i < CFG_ROTATES; i++) { // Read all config pages in search of valid and latest - if (1 == i) { flash_location = SETTINGS_LOCATION; } - ESP.flashRead(flash_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(Settings)); + uint32_t max_slots = CFG_ROTATES +1; + uint32_t flash_location; + uint32_t slot = 1; +#ifdef USE_UFILESYS + if (TfsLoadFile(TASM_FILE_SETTINGS, (uint8_t*)&Settings, sizeof(Settings))) { + flash_location = 1; + slot = 0; + } +#endif // USE_UFILESYS + while (slot <= max_slots) { // Read all config pages in search of valid and latest + if (slot > 0) { + flash_location = (1 == slot) ? FLASH_EEPROM_START : (2 == slot) ? SETTINGS_LOCATION : flash_location -1; + ESP.flashRead(flash_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(Settings)); + } if ((Settings.cfg_crc32 != 0xFFFFFFFF) && (Settings.cfg_crc32 != 0x00000000) && (Settings.cfg_crc32 == GetSettingsCrc32())) { - if (Settings.save_flag > save_flag) { // Find latest page based on incrementing save_flag + if (Settings.save_flag > save_flag) { // Find latest page based on incrementing save_flag save_flag = Settings.save_flag; settings_location = flash_location; - if (Settings.flag.stop_flash_rotate && (0 == i)) { // Stop if only eeprom area should be used and it is valid + if (Settings.flag.stop_flash_rotate && (1 == slot)) { // Stop if only eeprom area should be used and it is valid break; } } } - flash_location--; + slot++; delay(1); } if (settings_location > 0) { - ESP.flashRead(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(Settings)); - AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_LOADED_FROM_FLASH_AT " %X, " D_COUNT " %lu"), settings_location, Settings.save_flag); +#ifdef USE_UFILESYS + if (1 == settings_location) { + TfsLoadFile(TASM_FILE_SETTINGS, (uint8_t*)&Settings, sizeof(Settings)); + AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG "Loaded from File, " D_COUNT " %lu"), Settings.save_flag); + } else +#endif // USE_UFILESYS + { + ESP.flashRead(settings_location * SPI_FLASH_SEC_SIZE, (uint32*)&Settings, sizeof(Settings)); + AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG D_LOADED_FROM_FLASH_AT " %X, " D_COUNT " %lu"), settings_location, Settings.save_flag); + } } #endif // ESP8266 #ifdef ESP32 uint32_t source = SettingsRead(&Settings, sizeof(Settings)); + if (source) { settings_location = 1; } AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG "Loaded from %s, " D_COUNT " %lu"), (source)?"File":"Nvm", Settings.save_flag); #endif // ESP32 #ifndef FIRMWARE_MINIMAL if ((0 == settings_location) || (Settings.cfg_holder != (uint16_t)CFG_HOLDER)) { // Init defaults if cfg_holder differs from user settings in my_user_config.h - SettingsDefault(); +#ifdef USE_UFILESYS + if (TfsLoadFile(TASM_FILE_SETTINGS_LKG, (uint8_t*)&Settings, sizeof(Settings)) && (Settings.cfg_crc32 == GetSettingsCrc32())) { + settings_location = 1; + AddLog_P(LOG_LEVEL_NONE, PSTR(D_LOG_CONFIG "Loaded from LKG File, " D_COUNT " %lu"), Settings.save_flag); + } else +#endif // USE_UFILESYS + { + SettingsDefault(); + } } settings_crc32 = GetSettingsCrc32(); #endif // FIRMWARE_MINIMAL @@ -604,25 +638,6 @@ uint32_t CfgTime(void) { return Settings.cfg_timestamp; } -void EspErase(uint32_t start_sector, uint32_t end_sector) -{ - bool serial_output = (LOG_LEVEL_DEBUG_MORE <= TasmotaGlobal.seriallog_level); - for (uint32_t sector = start_sector; sector < end_sector; sector++) { - - bool result = ESP.flashEraseSector(sector); // Arduino core - erases flash as seen by SDK -// bool result = !SPIEraseSector(sector); // SDK - erases flash as seen by SDK -// bool result = EsptoolEraseSector(sector); // Esptool - erases flash completely (slow) - - if (serial_output) { - Serial.printf_P(PSTR(D_LOG_APPLICATION D_ERASED_SECTOR " %d %s\n"), sector, (result) ? D_OK : D_ERROR); - delay(10); - } else { - yield(); - } - OsWatchLoop(); - } -} - #ifdef ESP8266 void SettingsErase(uint8_t type) { @@ -635,54 +650,52 @@ void SettingsErase(uint8_t type) The default erase function is EspTool (EsptoolErase) - 0 = Erase from program end until end of flash as seen by SDK - 1 = Erase 16k SDK parameter area near end of flash as seen by SDK (0x0xFCxxx - 0x0xFFFFF) solving possible wifi errors - 2 = Erase Tasmota parameter area (0x0xF3xxx - 0x0xFBFFF) + 0 = Erase from program end until end of flash as seen by SDK including optional filesystem + 1 = Erase 16k SDK parameter area near end of flash as seen by SDK (0x0XFCxxx - 0x0XFFFFF) solving possible wifi errors + 2 = Erase from program end until end of flash as seen by SDK excluding optional filesystem 3 = Erase Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF) 4 = Erase SDK parameter area used for wifi calibration (0x0FCxxx - 0x0FCFFF) */ #ifndef FIRMWARE_MINIMAL + // Reset 2 = Erase all flash from program end to end of physical flash uint32_t _sectorStart = (ESP.getSketchSize() / SPI_FLASH_SEC_SIZE) + 1; uint32_t _sectorEnd = ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE; // Flash size as reported by hardware - if (1 == type) { + if (1 == type) { // Reset 3 = SDK parameter area // source Esp.cpp and core_esp8266_phy.cpp - _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4; // SDK parameter area - } - else if (2 == type) { - _sectorStart = SETTINGS_LOCATION - CFG_ROTATES; // Tasmota parameter area (0x0F3xxx - 0x0FAFFF) - _sectorEnd = SETTINGS_LOCATION; - } - else if (3 == type) { - _sectorStart = SETTINGS_LOCATION - CFG_ROTATES; // Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF) - _sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; // Flash size as seen by SDK - } - else if (4 == type) { -// _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4; // SDK phy area and Core calibration sector (0x0FC000) - _sectorStart = FLASH_EEPROM_START +1; // SDK phy area and Core calibration sector (0x0FC000) - _sectorEnd = _sectorStart +1; // SDK end of phy area and Core calibration sector (0x0FCFFF) + _sectorStart = (ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE) - 4; } + else if (2 == type) { // Reset 5, 6 = Erase all flash from program end to end of physical flash but skip filesystem /* - else if (5 == type) { - _sectorStart = (ESP.getFlashChipRealSize() / SPI_FLASH_SEC_SIZE) -4; // SDK phy area and Core calibration sector (0xxFC000) - _sectorEnd = _sectorStart +1; // SDK end of phy area and Core calibration sector (0xxFCFFF) - } +#ifdef USE_UFILESYS + TfsDeleteFile(TASM_FILE_SETTINGS); // Not needed as it is recreated by set defaults before restart +#endif */ - else { - return; + EsptoolErase(_sectorStart, FLASH_FS_START); + _sectorStart = FLASH_EEPROM_START; + _sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; // Flash size as seen by SDK + } + else if (3 == type) { // QPC Reached = QPC and Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF) +#ifdef USE_UFILESYS + TfsDeleteFile(TASM_FILE_SETTINGS); +#endif + EsptoolErase(SETTINGS_LOCATION - CFG_ROTATES, SETTINGS_LOCATION +1); + _sectorStart = FLASH_EEPROM_START; + _sectorEnd = ESP.getFlashChipSize() / SPI_FLASH_SEC_SIZE; // Flash size as seen by SDK + } + else if (4 == type) { // WIFI_FORCE_RF_CAL_ERASE = SDK wifi calibration + _sectorStart = FLASH_EEPROM_START +1; // SDK phy area and Core calibration sector (0x0XFC000) + _sectorEnd = _sectorStart +1; // SDK end of phy area and Core calibration sector (0x0XFCFFF) } - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " from 0x%08X to 0x%08X"), _sectorStart * SPI_FLASH_SEC_SIZE, (_sectorEnd * SPI_FLASH_SEC_SIZE) -1); - -// EspErase(_sectorStart, _sectorEnd); // Arduino core and SDK - erases flash as seen by SDK - EsptoolErase(_sectorStart, _sectorEnd); // Esptool - erases flash completely + EsptoolErase(_sectorStart, _sectorEnd); // Esptool - erases flash completely #endif // FIRMWARE_MINIMAL } #endif // ESP8266 void SettingsSdkErase(void) { - WiFi.disconnect(false); // Delete SDK wifi config + WiFi.disconnect(false); // Delete SDK wifi config SettingsErase(1); delay(1000); } diff --git a/tasmota/support_command.ino b/tasmota/support_command.ino index 7faef10d5..1c5e32474 100644 --- a/tasmota/support_command.ino +++ b/tasmota/support_command.ino @@ -727,12 +727,12 @@ void CmndRestart(void) switch (XdrvMailbox.payload) { case 1: TasmotaGlobal.restart_flag = 2; - ResponseCmndChar(D_JSON_RESTARTING); + ResponseCmndChar(PSTR(D_JSON_RESTARTING)); break; case 2: TasmotaGlobal.restart_flag = 2; TasmotaGlobal.restart_halt = true; - ResponseCmndChar(D_JSON_HALTING); + ResponseCmndChar(PSTR(D_JSON_HALTING)); break; case -1: CmndCrash(); // force a crash @@ -967,8 +967,9 @@ void CmndSetoption(void) bitWrite(Settings.flag5.data, pindex, XdrvMailbox.payload); switch (pindex) { case 1: // SetOption115 - Enable ESP32 MI32 - Settings.flag3.sleep_normal = 1; // SetOption60 - Enable normal sleep instead of dynamic sleep - TasmotaGlobal.restart_flag = 2; + if (0 == XdrvMailbox.payload) { + TasmotaGlobal.restart_flag = 2; + } break; } } @@ -1208,7 +1209,7 @@ void CmndGpio(void) if (jsflg2) { ResponseClear(); } else { - ResponseCmndChar(D_JSON_NOT_SUPPORTED); + ResponseCmndChar(PSTR(D_JSON_NOT_SUPPORTED)); } } } diff --git a/tasmota/support_esp32.ino b/tasmota/support_esp32.ino index 40ef5fe43..7bbad6468 100644 --- a/tasmota/support_esp32.ino +++ b/tasmota/support_esp32.ino @@ -138,30 +138,26 @@ int32_t NvmErase(const char *sNvsName) { void SettingsErase(uint8_t type) { // SDK and Tasmota data is held in default NVS partition - // Tasmota data is held also in file /settings on default filesystem + // Tasmota data is held also in file /.settings on default filesystem // cal_data - SDK PHY calibration data as documented in esp_phy_init.h // qpc - Tasmota Quick Power Cycle state // main - Tasmota Settings data int32_t r1, r2, r3; switch (type) { - case 0: // Reset 2, 5, 6 = Erase all flash from program end to end of physical flash + case 0: // Reset 2 = Erase all flash from program end to end of physical flash + case 2: // Reset 5, 6 = Erase all flash from program end to end of physical flash excluding filesystem // nvs_flash_erase(); // Erase RTC, PHY, sta.mac, ap.sndchan, ap.mac, Tasmota etc. r1 = NvmErase("qpc"); r2 = NvmErase("main"); r3 = TfsDeleteFile(TASM_FILE_SETTINGS); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " Tasmota data (%d,%d,%d)"), r1, r2, r3); break; - case 1: case 4: // Reset 3 or WIFI_FORCE_RF_CAL_ERASE = SDK parameter area + case 1: // Reset 3 = SDK parameter area + case 4: // WIFI_FORCE_RF_CAL_ERASE = SDK parameter area r1 = esp_phy_erase_cal_data_in_nvs(); // r1 = NvmErase("cal_data"); AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " PHY data (%d)"), r1); break; - case 2: // Not used = QPC and Tasmota parameter area (0x0F3xxx - 0x0FBFFF) - r1 = NvmErase("qpc"); - r2 = NvmErase("main"); - r3 = TfsDeleteFile(TASM_FILE_SETTINGS); - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " Tasmota data (%d,%d,%d)"), r1, r2, r3); - break; case 3: // QPC Reached = QPC, Tasmota and SDK parameter area (0x0F3xxx - 0x0FFFFF) // nvs_flash_erase(); // Erase RTC, PHY, sta.mac, ap.sndchan, ap.mac, Tasmota etc. r1 = NvmErase("qpc"); diff --git a/tasmota/support_esptool.ino b/tasmota/support_esptool.ino index f100a4706..a066838af 100644 --- a/tasmota/support_esptool.ino +++ b/tasmota/support_esptool.ino @@ -95,6 +95,8 @@ bool EsptoolEraseSector(uint32_t sector) void EsptoolErase(uint32_t start_sector, uint32_t end_sector) { + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_APPLICATION D_ERASE " from 0x%08X to 0x%08X"), start_sector * SPI_FLASH_SEC_SIZE, (end_sector * SPI_FLASH_SEC_SIZE) -1); + int next_erase_sector = start_sector; int remaining_erase_sector = end_sector - start_sector; diff --git a/tasmota/support_network.ino b/tasmota/support_network.ino index 0a41b933a..b2bb73bf0 100644 --- a/tasmota/support_network.ino +++ b/tasmota/support_network.ino @@ -36,6 +36,7 @@ void StartMdns(void) { // mdns_delayed_start--; // } else { // mdns_delayed_start = Settings.param[P_MDNS_DELAYED_START]; + MDNS.end(); // close existing or MDNS.begin will fail Mdns.begun = (uint8_t)MDNS.begin(TasmotaGlobal.hostname); AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_MDNS "%s"), (Mdns.begun) ? D_INITIALIZED : D_FAILED); // } diff --git a/tasmota/support_rtc.ino b/tasmota/support_rtc.ino index e9afa4597..08f7cfc3d 100644 --- a/tasmota/support_rtc.ino +++ b/tasmota/support_rtc.ino @@ -455,16 +455,13 @@ void RtcSecond(void) Rtc.midnight_now = true; } -#ifdef ESP32 if (mutex) { // Time is just synced and is valid - // Sync RTOS time to be used by SD Card time stamps + // Sync Core/RTOS time to be used by file system time stamps struct timeval tv; tv.tv_sec = Rtc.local_time; tv.tv_usec = 0; settimeofday(&tv, nullptr); } -#endif // ESP32 - } RtcTime.year += 1970; diff --git a/tasmota/support_tasmota.ino b/tasmota/support_tasmota.ino index 08205d23d..e4ae34177 100644 --- a/tasmota/support_tasmota.ino +++ b/tasmota/support_tasmota.ino @@ -886,6 +886,15 @@ void PerformEverySecond(void) ESP_getSketchSize(); // Init sketchsize as it can take up to 2 seconds } #endif + +#ifdef USE_UFILESYS + static bool settings_lkg = false; // Settings saved as Last Known Good + // Copy Settings as Last Known Good if no changes have been saved since 30 minutes + if (!settings_lkg && (UtcTime() > START_VALID_TIME) && (Settings.cfg_timestamp < UtcTime() - (30 * 60))) { + TfsSaveFile(TASM_FILE_SETTINGS_LKG, (const uint8_t*)&Settings, sizeof(Settings)); + settings_lkg = true; + } +#endif } /*-------------------------------------------------------------------------------------------*\ @@ -1134,7 +1143,7 @@ void Every250mSeconds(void) // Backup mqtt host, port, client, username and password // } if ((215 == TasmotaGlobal.restart_flag) || (216 == TasmotaGlobal.restart_flag)) { - SettingsErase(0); // Erase all flash from program end to end of physical flash + SettingsErase(2); // Erase all flash from program end to end of physical excluding optional filesystem } SettingsDefault(); // Restore current SSIDs and Passwords @@ -1169,7 +1178,7 @@ void Every250mSeconds(void) } TasmotaGlobal.restart_flag--; if (TasmotaGlobal.restart_flag <= 0) { - AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION "%s"), (TasmotaGlobal.restart_halt) ? "Halted" : D_RESTARTING); + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_APPLICATION "%s"), (TasmotaGlobal.restart_halt) ? PSTR("Halted") : PSTR(D_RESTARTING)); EspRestart(); } } @@ -1616,8 +1625,8 @@ void GpioInit(void) if (!TasmotaGlobal.soft_spi_enabled) { bool valid_cs = (ValidSpiPinUsed(GPIO_SPI_CS) || ValidSpiPinUsed(GPIO_RC522_CS) || - ValidSpiPinUsed(GPIO_NRF24_CS) || - ValidSpiPinUsed(GPIO_ILI9341_CS) || + (ValidSpiPinUsed(GPIO_NRF24_CS) && ValidSpiPinUsed(GPIO_NRF24_DC)) || + (ValidSpiPinUsed(GPIO_ILI9341_CS) && ValidSpiPinUsed(GPIO_ILI9341_DC)) || ValidSpiPinUsed(GPIO_EPAPER29_CS) || ValidSpiPinUsed(GPIO_EPAPER42_CS) || ValidSpiPinUsed(GPIO_ILI9488_CS) || @@ -1625,17 +1634,11 @@ void GpioInit(void) ValidSpiPinUsed(GPIO_RA8876_CS) || ValidSpiPinUsed(GPIO_ST7789_DC) || // ST7789 CS may be omitted so chk DC too ValidSpiPinUsed(GPIO_ST7789_CS) || - ValidSpiPinUsed(GPIO_SSD1331_CS) || + (ValidSpiPinUsed(GPIO_SSD1331_CS) && ValidSpiPinUsed(GPIO_SSD1331_DC)) || ValidSpiPinUsed(GPIO_SDCARD_CS) ); - bool valid_dc = (ValidSpiPinUsed(GPIO_SPI_DC) || - ValidSpiPinUsed(GPIO_NRF24_DC) || - ValidSpiPinUsed(GPIO_ILI9341_DC) || - ValidSpiPinUsed(GPIO_ST7789_DC) || - ValidSpiPinUsed(GPIO_SSD1331_DC) - ); // If SPI_CS and/or SPI_DC is used they must be valid - TasmotaGlobal.spi_enabled = (valid_cs && valid_dc) ? SPI_MOSI_MISO : SPI_NONE; + TasmotaGlobal.spi_enabled = (valid_cs) ? SPI_MOSI_MISO : SPI_NONE; if (TasmotaGlobal.spi_enabled) { TasmotaGlobal.my_module.io[12] = AGPIO(GPIO_SPI_MISO); SetPin(12, AGPIO(GPIO_SPI_MISO)); diff --git a/tasmota/support_udp.ino b/tasmota/support_udp.ino index 0462513d3..07aa6736e 100644 --- a/tasmota/support_udp.ino +++ b/tasmota/support_udp.ino @@ -32,7 +32,6 @@ IPAddress udp_remote_ip; // M-Search remote IP address uint16_t udp_remote_port; // M-Search remote port bool udp_connected = false; -bool udp_response_mutex = false; // M-Search response mutex to control re-entry #ifdef ESP8266 #ifndef UDP_MAX_PACKETS @@ -89,14 +88,12 @@ bool UdpConnect(void) if (UdpCtx.listen(&addr, 1900)) { // port 1900 // OK AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED)); - udp_response_mutex = false; udp_connected = true; } #endif // ESP8266 #ifdef ESP32 if (PortUdp.beginMulticast(WiFi.localIP(), IPAddress(239,255,255,250), 1900)) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_UPNP D_MULTICAST_REJOINED)); - udp_response_mutex = false; udp_connected = true; #endif // ESP32 } @@ -123,28 +120,29 @@ void PollUdp(void) packet->buf[packet->len] = 0; // add NULL at the end of the packet char * packet_buffer = (char*) &packet->buf; int32_t len = packet->len; + AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); #endif // ESP8266 #ifdef ESP32 - while (PortUdp.parsePacket()) { + while (uint32_t pack_len = PortUdp.parsePacket()) { char packet_buffer[UDP_BUFFER_SIZE]; // buffer to hold incoming UDP/SSDP packet int32_t len = PortUdp.read(packet_buffer, UDP_BUFFER_SIZE -1); packet_buffer[len] = 0; + PortUdp.flush(); + AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d/%d)"), len, pack_len); #endif // ESP32 - AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: Packet (%d)"), len); + // AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("\n%s"), packet_buffer); // Simple Service Discovery Protocol (SSDP) if (Settings.flag2.emulation) { #if defined(USE_SCRIPT_HUE) || defined(USE_ZIGBEE) - if (!udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { + if (TasmotaGlobal.devices_present && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { #else - if (TasmotaGlobal.devices_present && !udp_response_mutex && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { + if (TasmotaGlobal.devices_present && (strstr_P(packet_buffer, PSTR("M-SEARCH")) != nullptr)) { #endif if (0 == udp_last_received) { udp_last_received = millis(); - udp_response_mutex = true; - #ifdef ESP8266 udp_remote_ip = packet->srcaddr; udp_remote_port = packet->srcport; @@ -159,35 +157,34 @@ void PollUdp(void) LowerCase(packet_buffer, packet_buffer); RemoveSpace(packet_buffer); + bool udp_proccessed = false; // make sure we process the packet only once #ifdef USE_EMULATION_WEMO - if (EMUL_WEMO == Settings.flag2.emulation) { + if (!udp_proccessed && (EMUL_WEMO == Settings.flag2.emulation)) { if (strstr_P(packet_buffer, URN_BELKIN_DEVICE) != nullptr) { // type1 echo dot 2g, echo 1g's WemoRespondToMSearch(1); - return; + udp_proccessed = true; } else if ((strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || // type2 Echo 2g (echo & echo plus) (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { WemoRespondToMSearch(2); - return; + udp_proccessed = true; } } #endif // USE_EMULATION_WEMO #ifdef USE_EMULATION_HUE - if (EMUL_HUE == Settings.flag2.emulation) { + if (!udp_proccessed && (EMUL_HUE == Settings.flag2.emulation)) { + AddLog_P(LOG_LEVEL_DEBUG_MORE, PSTR("UDP: HUE")); if ((strstr_P(packet_buffer, PSTR(":device:basic:1")) != nullptr) || (strstr_P(packet_buffer, UPNP_ROOTDEVICE) != nullptr) || (strstr_P(packet_buffer, SSDPSEARCH_ALL) != nullptr) || (strstr_P(packet_buffer, SSDP_ALL) != nullptr)) { HueRespondToMSearch(); - return; + udp_proccessed = true; } } #endif // USE_EMULATION_HUE - - udp_response_mutex = false; - continue; } } } diff --git a/tasmota/support_wifi.ino b/tasmota/support_wifi.ino index 845e115e6..15f5e10c2 100644 --- a/tasmota/support_wifi.ino +++ b/tasmota/support_wifi.ino @@ -153,11 +153,24 @@ void WiFiSetSleepMode(void) */ // Sleep explanation: https://github.com/esp8266/Arduino/blob/3f0c601cfe81439ce17e9bd5d28994a7ed144482/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp#L255 +/* if (TasmotaGlobal.sleep && Settings.flag3.sleep_normal) { // SetOption60 - Enable normal sleep instead of dynamic sleep WiFi.setSleepMode(WIFI_LIGHT_SLEEP); // Allow light sleep during idle times } else { WiFi.setSleepMode(WIFI_MODEM_SLEEP); // Disable sleep (Esp8288/Arduino core and sdk default) } +*/ + if (0 == TasmotaGlobal.sleep) { + if (!TasmotaGlobal.wifi_stay_asleep) { + WiFi.setSleepMode(WIFI_NONE_SLEEP); // Disable sleep + } + } else { + if (Settings.flag3.sleep_normal) { // SetOption60 - Enable normal sleep instead of dynamic sleep + WiFi.setSleepMode(WIFI_LIGHT_SLEEP); // Allow light sleep during idle times + } else { + WiFi.setSleepMode(WIFI_MODEM_SLEEP); // Sleep (Esp8288/Arduino core and sdk default) + } + } WifiSetOutputPower(); } @@ -735,6 +748,7 @@ uint32_t WifiGetNtp(void) { char* ntp_server; bool resolved_ip = false; for (uint32_t i = 0; i <= MAX_NTP_SERVERS; i++) { + if (ntp_server_id > 2) { ntp_server_id = 0; } if (i < MAX_NTP_SERVERS) { ntp_server = SettingsText(SET_NTPSERVER1 + ntp_server_id); } else { @@ -747,7 +761,6 @@ uint32_t WifiGetNtp(void) { if (resolved_ip) { break; } } ntp_server_id++; - if (ntp_server_id > 2) { ntp_server_id = 0; } } if (!resolved_ip) { // AddLog_P(LOG_LEVEL_DEBUG, PSTR("NTP: No server found")); @@ -785,8 +798,7 @@ uint32_t WifiGetNtp(void) { packet_buffer[15] = 52; if (udp.beginPacket(time_server_ip, 123) == 0) { // NTP requests are to port 123 - ntp_server_id++; - if (ntp_server_id > 2) { ntp_server_id = 0; } // Next server next time + ntp_server_id++; // Next server next time udp.stop(); return 0; } @@ -806,6 +818,7 @@ uint32_t WifiGetNtp(void) { // Leap-Indicator: unknown (clock unsynchronized) // See: https://github.com/letscontrolit/ESPEasy/issues/2886#issuecomment-586656384 AddLog_P(LOG_LEVEL_DEBUG, PSTR("NTP: IP %s unsynched"), time_server_ip.toString().c_str()); + ntp_server_id++; // Next server next time return 0; } @@ -816,6 +829,7 @@ uint32_t WifiGetNtp(void) { secs_since_1900 |= (uint32_t)packet_buffer[42] << 8; secs_since_1900 |= (uint32_t)packet_buffer[43]; if (0 == secs_since_1900) { // No time stamp received + ntp_server_id++; // Next server next time return 0; } return secs_since_1900 - 2208988800UL; @@ -825,6 +839,7 @@ uint32_t WifiGetNtp(void) { // Timeout. AddLog_P(LOG_LEVEL_DEBUG, PSTR("NTP: No reply")); udp.stop(); + ntp_server_id++; // Next server next time return 0; } diff --git a/tasmota/tasmota.ino b/tasmota/tasmota.ino index 36511aa9e..2407e61b1 100644 --- a/tasmota/tasmota.ino +++ b/tasmota/tasmota.ino @@ -125,6 +125,7 @@ struct { bool skip_light_fade; // Temporarily skip light fading bool restart_halt; // Do not restart but stay in wait loop bool module_changed; // Indicate module changed since last restart + bool wifi_stay_asleep; // Allow sleep only incase of ESP32 BLE StateBitfield global_state; // Global states (currently Wifi and Mqtt) (8 bits) uint8_t spi_enabled; // SPI configured diff --git a/tasmota/tasmota_globals.h b/tasmota/tasmota_globals.h index 330a1a87e..4524410dd 100644 --- a/tasmota/tasmota_globals.h +++ b/tasmota/tasmota_globals.h @@ -225,8 +225,9 @@ const uint16_t LOG_BUFFER_SIZE = 4000; // Max number of characters in lo #error "Arduino ESP8266 Core versions before 2.7.1 are not supported" #endif -#define TASM_FILE_SETTINGS "/.settings" -#define TASM_FILE_ZIGBEE "/zb" +#define TASM_FILE_SETTINGS "/.settings" // Settings binary blob +#define TASM_FILE_SETTINGS_LKG "/.settings.lkg" // Last Known Good Settings binary blob +#define TASM_FILE_ZIGBEE "/zb" // Zigbee settings blob as used by CC2530 on ESP32 #ifndef MQTT_MAX_PACKET_SIZE #define MQTT_MAX_PACKET_SIZE 1200 // Bytes diff --git a/tasmota/xdrv_01_webserver.ino b/tasmota/xdrv_01_webserver.ino index 229d9af1d..d68cf9cd0 100644 --- a/tasmota/xdrv_01_webserver.ino +++ b/tasmota/xdrv_01_webserver.ino @@ -475,14 +475,14 @@ void StartWebserver(int type, IPAddress ipweb) String ipv6_addr = WifiGetIPv6(); if (ipv6_addr!="") { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s and IPv6 global address %s "), - NetworkHostname(), (Mdns.begun) ? ".local" : "", ipweb.toString().c_str(), ipv6_addr.c_str()); + NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", ipweb.toString().c_str(), ipv6_addr.c_str()); } else { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), - NetworkHostname(), (Mdns.begun) ? ".local" : "", ipweb.toString().c_str()); + NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", ipweb.toString().c_str()); } #else AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_HTTP D_WEBSERVER_ACTIVE_ON " %s%s " D_WITH_IP_ADDRESS " %s"), - NetworkHostname(), (Mdns.begun) ? ".local" : "", ipweb.toString().c_str()); + NetworkHostname(), (Mdns.begun) ? PSTR(".local") : "", ipweb.toString().c_str()); #endif // LWIP_IPV6 = 1 TasmotaGlobal.rules_flag.http_init = 1; } @@ -744,7 +744,7 @@ void WSContentSendStyle_P(const char* formatP, ...) bool sip = (static_cast(WiFi.softAPIP()) != 0); WSContentSend_P(PSTR("

%s%s (%s%s%s)

"), // tasmota.local (192.168.2.12, 192.168.4.1) NetworkHostname(), - (Mdns.begun) ? ".local" : "", + (Mdns.begun) ? PSTR(".local") : "", (lip) ? WiFi.localIP().toString().c_str() : "", (lip && sip) ? ", " : "", (sip) ? WiFi.softAPIP().toString().c_str() : ""); @@ -1230,15 +1230,15 @@ bool HandleRootStatusRefresh(void) uint32_t fsize = (TasmotaGlobal.devices_present < 5) ? 70 - (TasmotaGlobal.devices_present * 8) : 32; #ifdef USE_SONOFF_IFAN if (IsModuleIfan()) { - WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(TasmotaGlobal.power, 0)) ? "bold" : "normal", 54, GetStateText(bitRead(TasmotaGlobal.power, 0))); + WSContentSend_P(HTTP_DEVICE_STATE, 36, (bitRead(TasmotaGlobal.power, 0)) ? PSTR("bold") : PSTR("normal"), 54, GetStateText(bitRead(TasmotaGlobal.power, 0))); uint32_t fanspeed = GetFanspeed(); snprintf_P(svalue, sizeof(svalue), PSTR("%d"), fanspeed); - WSContentSend_P(HTTP_DEVICE_STATE, 64, (fanspeed) ? "bold" : "normal", 54, (fanspeed) ? svalue : GetStateText(0)); + WSContentSend_P(HTTP_DEVICE_STATE, 64, (fanspeed) ? PSTR("bold") : PSTR("normal"), 54, (fanspeed) ? svalue : GetStateText(0)); } else { #endif // USE_SONOFF_IFAN for (uint32_t idx = 1; idx <= TasmotaGlobal.devices_present; idx++) { snprintf_P(svalue, sizeof(svalue), PSTR("%d"), bitRead(TasmotaGlobal.power, idx -1)); - WSContentSend_P(HTTP_DEVICE_STATE, 100 / TasmotaGlobal.devices_present, (bitRead(TasmotaGlobal.power, idx -1)) ? "bold" : "normal", fsize, (TasmotaGlobal.devices_present < 5) ? GetStateText(bitRead(TasmotaGlobal.power, idx -1)) : svalue); + WSContentSend_P(HTTP_DEVICE_STATE, 100 / TasmotaGlobal.devices_present, (bitRead(TasmotaGlobal.power, idx -1)) ? PSTR("bold") : PSTR("normal"), fsize, (TasmotaGlobal.devices_present < 5) ? GetStateText(bitRead(TasmotaGlobal.power, idx -1)) : svalue); } #ifdef USE_SONOFF_IFAN } @@ -1438,7 +1438,7 @@ void HandleTemplateConfiguration(void) for (uint32_t i = 0; i < MAX_GPIO_PIN; i++) { if (!FlashPin(i)) { WSContentSend_P(PSTR("" D_GPIO "%d"), - ((9==i)||(10==i)) ? WebColor(COL_TEXT_WARNING) : WebColor(COL_TEXT), i, (0==i) ? " style='width:150px'" : "", i, i); + ((9==i)||(10==i)) ? WebColor(COL_TEXT_WARNING) : WebColor(COL_TEXT), i, (0==i) ? PSTR(" style='width:150px'") : "", i, i); WSContentSend_P(PSTR(""), i); } } @@ -1824,7 +1824,7 @@ void HandleLoggingConfiguration(void) idx); for (uint32_t i = LOG_LEVEL_NONE; i <= LOG_LEVEL_DEBUG_MORE; i++) { WSContentSend_P(PSTR("%d %s"), - (i == llevel) ? " selected" : "", i, i, + (i == llevel) ? PSTR(" selected") : "", i, i, GetTextIndexed(stemp1, sizeof(stemp1), i, kLoggingLevels)); } WSContentSend_P(PSTR("

")); @@ -1880,8 +1880,8 @@ void HandleOtherConfiguration(void) TemplateJson(); char stemp[strlen(TasmotaGlobal.mqtt_data) +1]; strlcpy(stemp, TasmotaGlobal.mqtt_data, sizeof(stemp)); // Get JSON template - WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? " checked disabled" : "", - (Settings.flag.mqtt_enabled) ? " checked" : "", // SetOption3 - Enable MQTT + WSContentSend_P(HTTP_FORM_OTHER, stemp, (USER_MODULE == Settings.module) ? PSTR(" checked disabled") : "", + (Settings.flag.mqtt_enabled) ? PSTR(" checked") : "", // SetOption3 - Enable MQTT SettingsText(SET_FRIENDLYNAME1), SettingsText(SET_DEVICENAME)); uint32_t maxfn = (TasmotaGlobal.devices_present > MAX_FRIENDLYNAMES) ? MAX_FRIENDLYNAMES : (!TasmotaGlobal.devices_present) ? 1 : TasmotaGlobal.devices_present; @@ -1911,9 +1911,9 @@ void HandleOtherConfiguration(void) if (i < EMUL_MAX) { WSContentSend_P(PSTR("%s %s
"), // Different id only used for labels i, i, - (i == Settings.flag2.emulation) ? " checked" : "", + (i == Settings.flag2.emulation) ? PSTR(" checked") : "", GetTextIndexed(stemp, sizeof(stemp), i, kEmulationOptions), - (i == EMUL_NONE) ? "" : (i == EMUL_WEMO) ? D_SINGLE_DEVICE : D_MULTI_DEVICE); + (i == EMUL_NONE) ? "" : (i == EMUL_WEMO) ? PSTR(D_SINGLE_DEVICE) : PSTR(D_MULTI_DEVICE)); } } WSContentSend_P(PSTR("

")); @@ -2086,7 +2086,7 @@ void HandleInformation(void) #ifdef ESP32 #ifdef USE_ETHERNET if (static_cast(EthernetLocalIP()) != 0) { - WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), EthernetHostname(), (Mdns.begun) ? ".local" : ""); + WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), EthernetHostname(), (Mdns.begun) ? PSTR(".local") : ""); WSContentSend_P(PSTR("}1" D_MAC_ADDRESS "}2%s"), EthernetMacAddress().c_str()); WSContentSend_P(PSTR("}1" D_IP_ADDRESS " (eth)}2%s"), EthernetLocalIP().toString().c_str()); WSContentSend_P(PSTR("}1
}2
")); @@ -2096,7 +2096,7 @@ void HandleInformation(void) if (Settings.flag4.network_wifi) { int32_t rssi = WiFi.RSSI(); WSContentSend_P(PSTR("}1" D_AP "%d " D_SSID " (" D_RSSI ")}2%s (%d%%, %d dBm)"), Settings.sta_active +1, HtmlEscape(SettingsText(SET_STASSID1 + Settings.sta_active)).c_str(), WifiGetRssiAsQuality(rssi), rssi); - WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), TasmotaGlobal.hostname, (Mdns.begun) ? ".local" : ""); + WSContentSend_P(PSTR("}1" D_HOSTNAME "}2%s%s"), TasmotaGlobal.hostname, (Mdns.begun) ? PSTR(".local") : ""); #if LWIP_IPV6 String ipv6_addr = WifiGetIPv6(); if (ipv6_addr != "") { @@ -2169,15 +2169,15 @@ void HandleInformation(void) #ifdef ESP8266 WSContentSend_P(PSTR("}1" D_FLASH_CHIP_ID "}20x%06X"), ESP.getFlashChipId()); #endif - WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%dkB"), ESP.getFlashChipRealSize() / 1024); - WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%dkB"), ESP.getFlashChipSize() / 1024); - WSContentSend_P(PSTR("}1" D_PROGRAM_SIZE "}2%dkB"), ESP_getSketchSize() / 1024); - WSContentSend_P(PSTR("}1" D_FREE_PROGRAM_SPACE "}2%dkB"), ESP.getFreeSketchSpace() / 1024); - WSContentSend_P(PSTR("}1" D_FREE_MEMORY "}2%dkB"), freeMem / 1024); + WSContentSend_P(PSTR("}1" D_FLASH_CHIP_SIZE "}2%d kB"), ESP.getFlashChipRealSize() / 1024); + WSContentSend_P(PSTR("}1" D_PROGRAM_FLASH_SIZE "}2%d kB"), ESP.getFlashChipSize() / 1024); + WSContentSend_P(PSTR("}1" D_PROGRAM_SIZE "}2%d kB"), ESP_getSketchSize() / 1024); + WSContentSend_P(PSTR("}1" D_FREE_PROGRAM_SPACE "}2%d kB"), ESP.getFreeSketchSpace() / 1024); + WSContentSend_P(PSTR("}1" D_FREE_MEMORY "}2%d kB"), freeMem / 1024); #ifdef ESP32 if (psramFound()) { - WSContentSend_P(PSTR("}1" D_PSR_MAX_MEMORY "}2%dkB"), ESP.getPsramSize() / 1024); - WSContentSend_P(PSTR("}1" D_PSR_FREE_MEMORY "}2%dkB"), ESP.getFreePsram() / 1024); + WSContentSend_P(PSTR("}1" D_PSR_MAX_MEMORY "}2%d kB"), ESP.getPsramSize() / 1024); + WSContentSend_P(PSTR("}1" D_PSR_FREE_MEMORY "}2%d kB"), ESP.getFreePsram() / 1024); } #endif WSContentSend_P(PSTR("")); @@ -2522,7 +2522,7 @@ void HandleUploadLoop(void) { return; } if (upload.totalSize && !(upload.totalSize % 102400)) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "Progress %dkB"), upload.totalSize / 1024); + AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPLOAD "Progress %d kB"), upload.totalSize / 1024); } } @@ -2788,7 +2788,7 @@ bool CaptivePortal(void) if ((WifiIsInManagerMode()) && !ValidIpAddress(Webserver->hostHeader().c_str())) { AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_HTTP D_REDIRECTED)); - Webserver->sendHeader(F("Location"), String("http://") + Webserver->client().localIP().toString(), true); + Webserver->sendHeader(F("Location"), String(F("http://")) + Webserver->client().localIP().toString(), true); WSSend(302, CT_PLAIN, ""); // Empty content inhibits Content-length header so we have to close the socket ourselves. Webserver->client().stop(); // Stop is needed because we sent no content length return true; diff --git a/tasmota/xdrv_02_mqtt.ino b/tasmota/xdrv_02_mqtt.ino index dcd21930f..9a0acba75 100644 --- a/tasmota/xdrv_02_mqtt.ino +++ b/tasmota/xdrv_02_mqtt.ino @@ -679,8 +679,13 @@ void MqttReconnect(void) { if (MqttClient.connect(TasmotaGlobal.mqtt_client, mqtt_user, mqtt_pwd, stopic, 1, lwt_retain, TasmotaGlobal.mqtt_data, MQTT_CLEAN_SESSION)) { #ifdef USE_MQTT_TLS if (Mqtt.mqtt_tls) { +#ifdef ESP8266 AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connected in %d ms, max ThunkStack used %d"), millis() - mqtt_connect_time, tlsClient->getMaxThunkStackUse()); +#elif defined(ESP32) + AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "TLS connected in %d ms, stack low mark %d"), + millis() - mqtt_connect_time, uxTaskGetStackHighWaterMark(nullptr)); +#endif if (!tlsClient->getMFLNStatus()) { AddLog_P(LOG_LEVEL_INFO, PSTR(D_LOG_MQTT "MFLN not supported by TLS server")); } @@ -1275,7 +1280,7 @@ void HandleMqttConfiguration(void) SettingsText(SET_MQTT_HOST), Settings.mqtt_port, #ifdef USE_MQTT_TLS - Mqtt.mqtt_tls ? " checked" : "", // SetOption102 - Enable MQTT TLS + Mqtt.mqtt_tls ? PSTR(" checked") : "", // SetOption102 - Enable MQTT TLS #endif // USE_MQTT_TLS Format(str, MQTT_CLIENT_ID, sizeof(str)), MQTT_CLIENT_ID, SettingsText(SET_MQTT_CLIENT)); WSContentSend_P(HTTP_FORM_MQTT2, diff --git a/tasmota/xdrv_03_energy.ino b/tasmota/xdrv_03_energy.ino index 7c7ba932e..f57431203 100644 --- a/tasmota/xdrv_03_energy.ino +++ b/tasmota/xdrv_03_energy.ino @@ -537,6 +537,7 @@ void EnergyEverySecond(void) } if (!data_valid) { Energy.start_energy = 0; + AddLog_P(LOG_LEVEL_DEBUG, PSTR("NRG: Energy reset by " STR(ENERGY_WATCHDOG) " seconds invalid data")); XnrgCall(FUNC_ENERGY_RESET); } diff --git a/tasmota/xdrv_09_timers.ino b/tasmota/xdrv_09_timers.ino index c40045a31..0f6292e13 100644 --- a/tasmota/xdrv_09_timers.ino +++ b/tasmota/xdrv_09_timers.ino @@ -305,7 +305,7 @@ void PrepShowTimer(uint32_t index) char days[8] = { 0 }; for (uint32_t i = 0; i < 7; i++) { uint8_t mask = 1 << i; - snprintf(days, sizeof(days), "%s%d", days, ((xtimer.days & mask) > 0)); + snprintf(days, sizeof(days), PSTR("%s%d"), days, ((xtimer.days & mask) > 0)); } char soutput[80]; @@ -857,7 +857,7 @@ void HandleTimerConfiguration(void) WSContentSend_P(HTTP_TIMER_SCRIPT5, MAX_TIMERS, TasmotaGlobal.devices_present); WSContentSend_P(HTTP_TIMER_SCRIPT6, TasmotaGlobal.devices_present); WSContentSendStyle_P(HTTP_TIMER_STYLE, WebColor(COL_FORM)); - WSContentSend_P(HTTP_FORM_TIMER1, (Settings.flag3.timers_enable) ? " checked" : ""); // CMND_TIMERS + WSContentSend_P(HTTP_FORM_TIMER1, (Settings.flag3.timers_enable) ? PSTR(" checked") : ""); // CMND_TIMERS for (uint32_t i = 0; i < MAX_TIMERS; i++) { WSContentSend_P(PSTR("%s%u"), (i > 0) ? "," : "", Settings.timer[i].data); } diff --git a/tasmota/xdrv_10_scripter.ino b/tasmota/xdrv_10_scripter.ino index 8f1a3e2be..cd07098f8 100755 --- a/tasmota/xdrv_10_scripter.ino +++ b/tasmota/xdrv_10_scripter.ino @@ -4550,53 +4550,59 @@ int16_t Run_script_sub(const char *type, int8_t tlen, JsonParserObject *jo) { if (*ctype=='#') { // check for parameter ctype += tlen; - if (*ctype=='(' && *(lp+tlen)=='(') { - float fparam; - numeric = 1; - glob_script_mem.glob_error = 0; - GetNumericArgument((char*)ctype, OPER_EQU, &fparam, 0); - if (glob_script_mem.glob_error==1) { - // was string, not number - numeric = 0; - // get the string - GetStringArgument((char*)ctype + 1, OPER_EQU, cmpstr, 0); - } - lp += tlen; - if (*lp=='(') { - // fetch destination - lp++; - lp = isvar(lp, &vtype, &ind, 0, 0, 0); - if (vtype!=VAR_NV) { - // found variable as result - uint8_t index = glob_script_mem.type[ind.index].index; - if ((vtype&STYPE)==0) { - // numeric result - dfvar = &glob_script_mem.fvars[index]; - if (numeric) { - *dfvar = fparam; - } else { - // mismatch - *dfvar = CharToFloat(cmpstr); - } - } else { - // string result - sindex = index; - if (!numeric) { - strlcpy(glob_script_mem.glob_snp + (sindex * glob_script_mem.max_ssize), cmpstr, glob_script_mem.max_ssize); - } else { - // mismatch - dtostrfd(fparam, 6, glob_script_mem.glob_snp + (sindex * glob_script_mem.max_ssize)); - } + char nxttok = '('; + char *argptr = ctype+tlen; + + lp += tlen; + do { + if (*ctype==nxttok && *lp==nxttok) { + float fparam; + numeric = 1; + glob_script_mem.glob_error = 0; + argptr = GetNumericArgument((char*)ctype + 1, OPER_EQU, &fparam, 0); + if (glob_script_mem.glob_error==1) { + // was string, not number + numeric = 0; + // get the string + argptr = GetStringArgument((char*)ctype + 1, OPER_EQU, cmpstr, 0); + } + if (*lp==nxttok) { + // fetch destination + lp++; + lp = isvar(lp, &vtype, &ind, 0, 0, 0); + if (vtype!=VAR_NV) { + // found variable as result + uint8_t index = glob_script_mem.type[ind.index].index; + if ((vtype&STYPE)==0) { + // numeric result + dfvar = &glob_script_mem.fvars[index]; + if (numeric) { + *dfvar = fparam; + } else { + // mismatch + *dfvar = CharToFloat(cmpstr); + } + } else { + // string result + sindex = index; + if (!numeric) { + strlcpy(glob_script_mem.glob_snp + (sindex * glob_script_mem.max_ssize), cmpstr, glob_script_mem.max_ssize); + } else { + // mismatch + dtostrfd(fparam, 6, glob_script_mem.glob_snp + (sindex * glob_script_mem.max_ssize)); + } + } } } + } else { + if (*ctype==nxttok || (*lp!=SCRIPT_EOL && *lp!='?')) { + // revert + section = 0; + } } - } else { - lp += tlen; - if (*ctype=='(' || (*lp!=SCRIPT_EOL && *lp!='?')) { - // revert - section = 0; - } - } + nxttok = ' '; + ctype = argptr; + } while (*lp==' ' && (section == 1) ); } } } @@ -4973,9 +4979,9 @@ void HandleScriptConfiguration(void) { #ifdef xSCRIPT_STRIP_COMMENTS uint16_t ssize = glob_script_mem.script_size; if (bitRead(Settings.rule_enabled, 1)) ssize *= 2; - WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? " checked" : "",ssize); + WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? PSTR(" checked") : "",ssize); #else - WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? " checked" : "",glob_script_mem.script_size); + WSContentSend_P(HTTP_FORM_SCRIPT1,1,1,bitRead(Settings.rule_enabled,0) ? PSTR(" checked") : "",glob_script_mem.script_size); #endif // script is to large for WSContentSend_P diff --git a/tasmota/xdrv_12_home_assistant.ino b/tasmota/xdrv_12_home_assistant.ino index 8ef33c91f..bf4664886 100644 --- a/tasmota/xdrv_12_home_assistant.ino +++ b/tasmota/xdrv_12_home_assistant.ino @@ -197,7 +197,7 @@ const char HASS_DISCOVER_DEVICE[] PROGMEM = // Basic par "\"tp\":[\"%s\",\"%s\",\"%s\"]," // Topics for command, stat and tele "\"rl\":[%s],\"swc\":[%s],\"swn\":[%s],\"btn\":[%s]," // Inputs / Outputs "\"so\":{\"4\":%d,\"11\":%d,\"13\":%d,\"17\":%d,\"20\":%d," // SetOptions - "\"30\":%d,\"68\":%d,\"73\":%d,\"82\":%d,\"114\":%d}," + "\"30\":%d,\"68\":%d,\"73\":%d,\"82\":%d,\"114\":%d,\"117\":%d}," "\"lk\":%d,\"lt_st\":%d,\"sho\":[%s],\"ver\":1}"; // Light SubType, Shutter Options and Discovery version typedef struct HASS { @@ -341,7 +341,7 @@ void NewHAssDiscovery(void) TasmotaGlobal.version, TasmotaGlobal.mqtt_topic, SettingsText(SET_MQTT_FULLTOPIC), SUB_PREFIX, PUB_PREFIX, PUB_PREFIX2, Hass.RelLst, stemp3, stemp4, stemp5, Settings.flag.mqtt_response, Settings.flag.button_swap, Settings.flag.button_single, Settings.flag.decimal_text, Settings.flag.not_power_linked, Settings.flag.hass_light, Settings.flag3.pwm_multi_channels, Settings.flag3.mqtt_buttons, Settings.flag4.alexa_ct_range, Settings.flag5.mqtt_switches, - light_controller.isCTRGBLinked(), Light.subtype, stemp6); + Settings.flag5.fade_fixed_duration, light_controller.isCTRGBLinked(), Light.subtype, stemp6); } MqttPublish(stopic, true); diff --git a/tasmota/xdrv_20_hue.ino b/tasmota/xdrv_20_hue.ino index 84e9ed508..4da625847 100644 --- a/tasmota/xdrv_20_hue.ino +++ b/tasmota/xdrv_20_hue.ino @@ -175,7 +175,7 @@ String HueBridgeId(void) String temp = WiFi.macAddress(); temp.replace(":", ""); String bridgeid = temp.substring(0, 6); - bridgeid += "FFFE"; + bridgeid += F("FFFE"); bridgeid += temp.substring(6); return bridgeid; // 5CCF7FFFFE139F3D } @@ -225,8 +225,6 @@ void HueRespondToMSearch(void) // Do not use AddLog_P( here (interrupt routine) if syslog or mqttlog is enabled. UDP/TCP will force exception 9 AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_HUE " %s " D_TO " %s:%d"), message, udp_remote_ip.toString().c_str(), udp_remote_port); - - udp_response_mutex = false; } /*********************************************************************************************\ diff --git a/tasmota/xdrv_21_wemo.ino b/tasmota/xdrv_21_wemo.ino index 96e937d21..18f793824 100644 --- a/tasmota/xdrv_21_wemo.ino +++ b/tasmota/xdrv_21_wemo.ino @@ -76,8 +76,6 @@ void WemoRespondToMSearch(int echo_type) // Do not use AddLog_P( here (interrupt routine) if syslog or mqttlog is enabled. UDP/TCP will force exception 9 AddLog_P(LOG_LEVEL_DEBUG, PSTR(D_LOG_UPNP D_WEMO " " D_JSON_TYPE " %d, %s " D_TO " %s:%d"), echo_type, message, udp_remote_ip.toString().c_str(), udp_remote_port); - - udp_response_mutex = false; } /*********************************************************************************************\ diff --git a/tasmota/xdrv_21_wemo_multi.ino b/tasmota/xdrv_21_wemo_multi.ino index ec3fb7d42..1a0b3ac6c 100644 --- a/tasmota/xdrv_21_wemo_multi.ino +++ b/tasmota/xdrv_21_wemo_multi.ino @@ -422,8 +422,6 @@ void WemoRespondToMSearch(int echo_type) { for (uint32_t i = 0; i < numOfWemoSwitch; i++) { wemoDevice[i]->WemoRespondToMSearch(echo_type); } - - udp_response_mutex = false; } /*********************************************************************************************\ diff --git a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino index 80a69441c..ec7d773e4 100644 --- a/tasmota/xdrv_23_zigbee_2a_devices_impl.ino +++ b/tasmota/xdrv_23_zigbee_2a_devices_impl.ino @@ -681,9 +681,9 @@ void Z_Device::jsonAddConfig(Z_attribute_list & attr_list) const { for (auto & data_elt : data) { char key[8]; if (data_elt.validConfig()) { - snprintf_P(key, sizeof(key), "?%02X.%1X", data_elt.getEndpoint(), data_elt.getConfig()); + snprintf_P(key, sizeof(key), PSTR("?%02X.%1X"), data_elt.getEndpoint(), data_elt.getConfig()); } else { - snprintf_P(key, sizeof(key), "?%02X", data_elt.getEndpoint()); + snprintf_P(key, sizeof(key), PSTR("?%02X"), data_elt.getEndpoint()); } key[0] = Z_Data::DataTypeToChar(data_elt.getType()); arr_data.addStr(key); diff --git a/tasmota/xdrv_23_zigbee_5_converters.ino b/tasmota/xdrv_23_zigbee_5_converters.ino index 5cfb19d06..fe4d219c0 100644 --- a/tasmota/xdrv_23_zigbee_5_converters.ino +++ b/tasmota/xdrv_23_zigbee_5_converters.ino @@ -1903,7 +1903,7 @@ void ZCLFrame::syntheticAqaraVibration(class Z_attribute_list &attr_list, class y = buf2.get16(2); x = buf2.get16(4); char temp[32]; - snprintf_P(temp, sizeof(temp), "[%i,%i,%i]", x, y, z); + snprintf_P(temp, sizeof(temp), PSTR("[%i,%i,%i]"), x, y, z); attr.setStrRaw(temp); // calculate angles float X = x; @@ -1912,7 +1912,7 @@ void ZCLFrame::syntheticAqaraVibration(class Z_attribute_list &attr_list, class int32_t Angle_X = 0.5f + atanf(X/sqrtf(z*z+y*y)) * f_180pi; int32_t Angle_Y = 0.5f + atanf(Y/sqrtf(x*x+z*z)) * f_180pi; int32_t Angle_Z = 0.5f + atanf(Z/sqrtf(x*x+y*y)) * f_180pi; - snprintf_P(temp, sizeof(temp), "[%i,%i,%i]", Angle_X, Angle_Y, Angle_Z); + snprintf_P(temp, sizeof(temp), PSTR("[%i,%i,%i]"), Angle_X, Angle_Y, Angle_Z); attr_list.addAttributePMEM(PSTR("AqaraAngles")).setStrRaw(temp); } } diff --git a/tasmota/xdrv_23_zigbee_7_0_statemachine.ino b/tasmota/xdrv_23_zigbee_7_0_statemachine.ino index 7c00cfb42..cc23c7a92 100644 --- a/tasmota/xdrv_23_zigbee_7_0_statemachine.ino +++ b/tasmota/xdrv_23_zigbee_7_0_statemachine.ino @@ -785,7 +785,7 @@ static const Zigbee_Instruction zb_prog[] PROGMEM = { ZI_WAIT_UNTIL(5000, ZBR_RSTACK) // wait for RSTACK message // Init device and probe version - ZI_SEND(ZBS_VERSION) ZI_WAIT_RECV_FUNC(1000, ZBR_VERSION, &EZ_ReceiveCheckVersion) // check EXT PAN ID + ZI_SEND(ZBS_VERSION) ZI_WAIT_RECV_FUNC(5000, ZBR_VERSION, &EZ_ReceiveCheckVersion) // check EXT PAN ID // configure EFR32 ZI_MQTT_STATE(ZIGBEE_STATUS_STARTING, kConfiguredCoord) diff --git a/tasmota/xdrv_23_zigbee_7_5_map.ino b/tasmota/xdrv_23_zigbee_7_5_map.ino index 790720320..c9013b665 100644 --- a/tasmota/xdrv_23_zigbee_7_5_map.ino +++ b/tasmota/xdrv_23_zigbee_7_5_map.ino @@ -171,7 +171,7 @@ void Z_Mapper::dumpInternals(void) const { char hex[8]; snprintf(hex, sizeof(hex), PSTR("%d"), edge.lqi); - WSContentSend_P("{from:\"0x%04X\",to:\"0x%04X\",label:\"%s\",color:\"#%03X\"},", + WSContentSend_P(PSTR("{from:\"0x%04X\",to:\"0x%04X\",label:\"%s\",color:\"#%03X\"},"), edge.node_1, edge.node_2, (edge.lqi > 0) ? hex : "", lqi_color); } WSContentSend_P(PSTR("],")); diff --git a/tasmota/xdrv_23_zigbee_A_impl.ino b/tasmota/xdrv_23_zigbee_A_impl.ino index ce690c4db..ec9a454ac 100644 --- a/tasmota/xdrv_23_zigbee_A_impl.ino +++ b/tasmota/xdrv_23_zigbee_A_impl.ino @@ -2081,7 +2081,7 @@ void ZigbeeShow(bool json) if (zigbee.permit_end_time) { // PermitJoin in progress - WSContentSend_P(msg[ZB_WEB_PERMITJOIN_ACTIVE], D_ZIGBEE_PERMITJOIN_ACTIVE); + WSContentSend_P(msg[ZB_WEB_PERMITJOIN_ACTIVE], PSTR(D_ZIGBEE_PERMITJOIN_ACTIVE)); } #endif } diff --git a/tasmota/xdrv_28_pcf8574.ino b/tasmota/xdrv_28_pcf8574.ino index 58bb5239c..b79f3caa6 100644 --- a/tasmota/xdrv_28_pcf8574.ino +++ b/tasmota/xdrv_28_pcf8574.ino @@ -168,7 +168,7 @@ void HandlePcf8574(void) WSContentStart_P(D_CONFIGURE_PCF8574); WSContentSendStyle(); - WSContentSend_P(HTTP_FORM_I2C_PCF8574_1, (Settings.flag3.pcf8574_ports_inverted) ? " checked" : ""); // SetOption81 - Invert all ports on PCF8574 devices + WSContentSend_P(HTTP_FORM_I2C_PCF8574_1, (Settings.flag3.pcf8574_ports_inverted) ? PSTR(" checked") : ""); // SetOption81 - Invert all ports on PCF8574 devices WSContentSend_P(HTTP_TABLE100); for (uint32_t idx = 0; idx < Pcf8574.max_devices; idx++) { for (uint32_t idx2 = 0; idx2 < 8; idx2++) { // 8 ports on PCF8574 @@ -177,8 +177,8 @@ void HandlePcf8574(void) idx +1, idx2, idx2 + 8*idx, idx2 + 8*idx, - ((helper & Settings.pcf8574_config[idx]) >> idx2 == 0) ? " selected " : " ", - ((helper & Settings.pcf8574_config[idx]) >> idx2 == 1) ? " selected " : " " + ((helper & Settings.pcf8574_config[idx]) >> idx2 == 0) ? PSTR(" selected ") : " ", + ((helper & Settings.pcf8574_config[idx]) >> idx2 == 1) ? PSTR(" selected ") : " " ); } } diff --git a/tasmota/xdrv_43_mlx90640.ino b/tasmota/xdrv_43_mlx90640.ino index 18255c079..53cbcfe07 100644 --- a/tasmota/xdrv_43_mlx90640.ino +++ b/tasmota/xdrv_43_mlx90640.ino @@ -418,12 +418,12 @@ void MLX90640HandleWebGuiResponse(void){ if(_line==0){_buf[0]=1000+MLX90640.Ta;} //ambient temperature modulation hack else{_buf[0]=(float)_line;} memcpy((char*)&_buf[1],(char*)&MLX90640.To[_line*64],64*4); - Webserver->send(200,PSTR("application/octet-stream"),(const char*)&_buf,65*4); + Webserver->send_P(200,PSTR("application/octet-stream"),(const char*)&_buf,65*4); return; } WebGetArg("up", tmp, sizeof(tmp)); // update POI to browser if (strlen(tmp)==1) { - Webserver->send(200,PSTR("application/octet-stream"),(const char*)&MLX90640.pois,MLX90640_POI_NUM*2); + Webserver->send_P(200,PSTR("application/octet-stream"),(const char*)&MLX90640.pois,MLX90640_POI_NUM*2); return; } else if (strlen(tmp)>2) { // receive updated POI from browser diff --git a/tasmota/xdrv_48_timeprop.ino b/tasmota/xdrv_48_timeprop.ino index bddf2653f..5589868de 100644 --- a/tasmota/xdrv_48_timeprop.ino +++ b/tasmota/xdrv_48_timeprop.ino @@ -18,6 +18,7 @@ */ #ifdef USE_TIMEPROP +#ifndef FIRMWARE_MINIMAL /*********************************************************************************************\ * Code to drive one or more relays in a time proportioned manner give a * required power value. @@ -79,7 +80,23 @@ #define TIMEPROP_RELAYS 1, 2 // which relay to control 1:8 * Publish values between 0 and 1 to the topic(s) described above -\*********************************************************************************************/ + * +**/ + + + +#define D_CMND_TIMEPROP "timeprop_" +#define D_CMND_TIMEPROP_SETPOWER "setpower_" // add index no on end (0:8) and data is power 0:1 + +#include "Timeprop.h" + +enum TimepropCommands { CMND_TIMEPROP_SETPOWER }; +const char kTimepropCommands[] PROGMEM = D_CMND_TIMEPROP_SETPOWER; + +static Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; +static int relayNos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; +static long currentRelayStates = 0; // current actual relay states. Bit 0 first relay + #ifndef TIMEPROP_NUM_OUTPUTS #define TIMEPROP_NUM_OUTPUTS 1 // how many outputs to control (with separate alogorithm for each) @@ -103,8 +120,6 @@ #define TIMEPROP_RELAYS 1 // which relay to control 1:8 #endif -#include "Timeprop.h" - struct { Timeprop timeprops[TIMEPROP_NUM_OUTPUTS]; int relay_nos[TIMEPROP_NUM_OUTPUTS] = {TIMEPROP_RELAYS}; @@ -162,7 +177,51 @@ void TimepropXdrvPower(void) { /* char *data; */ /* } XdrvMailbox; */ -// To get here post with topic cmnd/timeprop_setpower_n where n is index into Tprop.timeprops 0:7 +// To get here post with topic cmnd/timeprop_setpower_n where n is index into timeprops 0:7 +bool TimepropCommand() +{ + char command [CMDSZ]; + bool serviced = true; + uint8_t ua_prefix_len = strlen(D_CMND_TIMEPROP); // to detect prefix of command + /* + snprintf_P(log_data, sizeof(log_data), "Command called: " + "index: %d data_len: %d payload: %d topic: %s data: %s\n", + XdrvMailbox.index, + XdrvMailbox.data_len, + XdrvMailbox.payload, + (XdrvMailbox.payload >= 0 ? XdrvMailbox.topic : ""), + (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); + + AddLog(LOG_LEVEL_INFO); + */ + if (0 == strncasecmp_P(XdrvMailbox.topic, PSTR(D_CMND_TIMEPROP), ua_prefix_len)) { + // command starts with timeprop_ + int command_code = GetCommandCode(command, sizeof(command), XdrvMailbox.topic + ua_prefix_len, kTimepropCommands); + if (CMND_TIMEPROP_SETPOWER == command_code) { + /* + snprintf_P(log_data, sizeof(log_data), "Timeprop command timeprop_setpower: " + "index: %d data_len: %d payload: %d topic: %s data: %s", + XdrvMailbox.index, + XdrvMailbox.data_len, + XdrvMailbox.payload, + (XdrvMailbox.payload >= 0 ? XdrvMailbox.topic : ""), + (XdrvMailbox.data_len >= 0 ? XdrvMailbox.data : "")); + AddLog(LOG_LEVEL_INFO); + */ + if (XdrvMailbox.index >=0 && XdrvMailbox.index < TIMEPROP_NUM_OUTPUTS) { + timeprops[XdrvMailbox.index].setPower( atof(XdrvMailbox.data), Tprop.current_time_secs ); + } + snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"" D_CMND_TIMEPROP D_CMND_TIMEPROP_SETPOWER "%d\":\"%s\"}"), + XdrvMailbox.index, XdrvMailbox.data); + } + else { + serviced = false; + } + } else { + serviced = false; + } + return serviced; +} /*********************************************************************************************\ * Interface @@ -180,6 +239,9 @@ bool Xdrv48(byte function) { case FUNC_EVERY_SECOND: TimepropEverySecond(); break; + case FUNC_COMMAND: + result = TimepropCommand(); + break; case FUNC_SET_POWER: TimepropXdrvPower(); break; @@ -187,4 +249,5 @@ bool Xdrv48(byte function) { return result; } +#endif // FIRMWARE_MINIMAL #endif // USE_TIMEPROP diff --git a/tasmota/xdrv_49_pid.ino b/tasmota/xdrv_49_pid.ino index 7da87ac7d..15097106b 100644 --- a/tasmota/xdrv_49_pid.ino +++ b/tasmota/xdrv_49_pid.ino @@ -18,6 +18,7 @@ */ #ifdef USE_PID +#ifndef FIRMWARE_MINIMAL /*********************************************************************************************\ * Uses the library https://github.com/colinl/process-control.git from Github * In user_config_override.h include code as follows: @@ -117,11 +118,6 @@ #define PID_REPORT_MORE_SETTINGS // If defined, the SENSOR output will provide more extensive json // output in the PID section -// #define PID_BACKWARD_COMPATIBLE // Preserve the backward compatible reporting of PID power via - // `%topic%/PID {"power":"0.000"}` This is now available in - // `%topic$/SENSOR {..., "PID":{"PidPower":0.00}}` - // Don't use unless you know that you need it - * Help with using the PID algorithm and with loop tuning can be found at * http://blog.clanlaw.org.uk/2018/01/09/PID-tuning-with-node-red-contrib-pid.html * This is directed towards using the algorithm in the node-red node node-red-contrib-pid but the algorithm here is based on @@ -271,26 +267,27 @@ void CmndSetPv(void) { // this runs it at the next second Pid.run_pid_now = true; } + ResponseCmndFloat(atof(XdrvMailbox.data), 1); } void CmndSetSp(void) { Pid.pid.setSp(atof(XdrvMailbox.data)); - ResponseCmndNumber(atof(XdrvMailbox.data)); + ResponseCmndFloat(atof(XdrvMailbox.data), 1); } void CmndSetPb(void) { Pid.pid.setPb(atof(XdrvMailbox.data)); - ResponseCmndNumber(atof(XdrvMailbox.data)); + ResponseCmndFloat(atof(XdrvMailbox.data), 1); } void CmndSetTi(void) { Pid.pid.setTi(atof(XdrvMailbox.data)); - ResponseCmndNumber(atof(XdrvMailbox.data)); + ResponseCmndFloat(atof(XdrvMailbox.data), 1); } void CmndSetTd(void) { Pid.pid.setTd(atof(XdrvMailbox.data)); - ResponseCmndNumber(atof(XdrvMailbox.data)); + ResponseCmndFloat(atof(XdrvMailbox.data), 1); } void CmndSetInitialInt(void) { @@ -300,7 +297,7 @@ void CmndSetInitialInt(void) { void CmndSetDSmooth(void) { Pid.pid.setDSmooth(atof(XdrvMailbox.data)); - ResponseCmndNumber(atof(XdrvMailbox.data)); + ResponseCmndFloat(atof(XdrvMailbox.data), 1); } void CmndSetAuto(void) { @@ -310,7 +307,7 @@ void CmndSetAuto(void) { void CmndSetManualPower(void) { Pid.pid.setManualPower(atof(XdrvMailbox.data)); - ResponseCmndNumber(atof(XdrvMailbox.data)); + ResponseCmndFloat(atof(XdrvMailbox.data), 1); } void CmndSetMaxInterval(void) { @@ -391,14 +388,14 @@ void PIDShowValues(void) { void PIDRun(void) { double power = Pid.pid.tick(Pid.current_time_secs); -#ifdef PID_BACKWARD_COMPATIBLE +#ifdef PID_DONT_USE_PID_TOPIC // This part is left inside to regularly publish the PID Power via // `%topic%/PID {"power":"0.000"}` char str_buf[FLOATSZ]; dtostrfd(power, 3, str_buf); snprintf_P(TasmotaGlobal.mqtt_data, sizeof(TasmotaGlobal.mqtt_data), PSTR("{\"%s\":\"%s\"}"), "power", str_buf); MqttPublishPrefixTopic_P(TELE, "PID", false); -#endif // PID_BACKWARD_COMPATIBLE +#endif // PID_DONT_USE_PID_TOPIC #if defined PID_SHUTTER // send output as a position from 0-100 to defined shutter @@ -443,4 +440,5 @@ bool Xdrv49(byte function) { } return result; } +#endif //FIRMWARE_MINIMAL #endif // USE_PID diff --git a/tasmota/xdrv_50_filesystem.ino b/tasmota/xdrv_50_filesystem.ino index 56d4ecfd8..0b7fc53d2 100644 --- a/tasmota/xdrv_50_filesystem.ino +++ b/tasmota/xdrv_50_filesystem.ino @@ -72,25 +72,28 @@ ufsfree free size in kB #include "FS.h" #endif // ESP32 -// global file system pointer +// Global file system pointer FS *ufsp; -// flash file system pointer on esp32 +// Flash file system pointer FS *ffsp; -// local pointer for file managment +// Local pointer for file managment FS *dfsp; char ufs_path[48]; File ufs_upload_file; uint8_t ufs_dir; -// 0 = none, 1 = SD, 2 = ffat, 3 = littlefs +// 0 = None, 1 = SD, 2 = ffat, 3 = littlefs uint8_t ufs_type; uint8_t ffs_type; bool download_busy; + + + /*********************************************************************************************/ -// init flash file system +// Init flash file system void UfsInitOnce(void) { ufs_type = 0; ffsp = 0; @@ -130,21 +133,13 @@ void UfsInitOnce(void) { void UfsInit(void) { UfsInitOnce(); if (ufs_type) { - AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: Type %d mounted with %d kB free"), ufs_type, UfsInfo(1, 0)); + AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: FlashFS mounted with %d kB free"), UfsInfo(1, 0)); } } - #ifdef USE_SDCARD void UfsCheckSDCardInit(void) { - -#ifdef ESP8266 - if (PinUsed(GPIO_SPI_CLK) && PinUsed(GPIO_SPI_MOSI) && PinUsed(GPIO_SPI_MISO)) { -#endif // ESP8266 - -#ifdef ESP32 if (TasmotaGlobal.spi_enabled) { -#endif // ESP32 int8_t cs = SDCARD_CS_PIN; if (PinUsed(GPIO_SDCARD_CS)) { cs = Pin(GPIO_SDCARD_CS); @@ -172,10 +167,10 @@ void UfsCheckSDCardInit(void) { // make sd card the global filesystem #ifdef ESP8266 // on esp8266 sdcard info takes several seconds !!!, so we ommit it here - AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: SDCARD mounted")); + AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: SDCard mounted")); #endif // ESP8266 #ifdef ESP32 - AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: SDCARD mounted with %d kB free"), UfsInfo(1, 0)); + AddLog_P(LOG_LEVEL_INFO, PSTR("UFS: SDCard mounted with %d kB free"), UfsInfo(1, 0)); #endif // ESP32 } } @@ -274,38 +269,12 @@ uint8_t UfsReject(char *name) { return 0; } -// Format number with thousand marker - Not international as '.' is decimal on most countries -void UfsForm1000(uint32_t number, char *dp, char sc) { - char str[32]; - sprintf(str, "%d", number); - char *sp = str; - uint32_t inum = strlen(sp)/3; - uint32_t fnum = strlen(sp)%3; - if (!fnum) { inum--; } - for (uint32_t count = 0; count <= inum; count++) { - if (fnum) { - memcpy(dp, sp, fnum); - dp += fnum; - sp += fnum; - fnum = 0; - } else { - memcpy(dp, sp, 3); - dp += 3; - sp += 3; - } - if (count != inum) { - *dp++ = sc; - } - } - *dp = 0; -} - /*********************************************************************************************\ * Tfs low level functions \*********************************************************************************************/ bool TfsFileExists(const char *fname){ - if (!ufs_type) { return false; } + if (!ffs_type) { return false; } bool yes = ffsp->exists(fname); if (!yes) { @@ -315,7 +284,7 @@ bool TfsFileExists(const char *fname){ } bool TfsSaveFile(const char *fname, const uint8_t *buf, uint32_t len) { - if (!ufs_type) { return false; } + if (!ffs_type) { return false; } File file = ffsp->open(fname, "w"); if (!file) { @@ -329,7 +298,7 @@ bool TfsSaveFile(const char *fname, const uint8_t *buf, uint32_t len) { } bool TfsInitFile(const char *fname, uint32_t len, uint8_t init_value) { - if (!ufs_type) { return false; } + if (!ffs_type) { return false; } File file = ffsp->open(fname, "w"); if (!file) { @@ -345,7 +314,7 @@ bool TfsInitFile(const char *fname, uint32_t len, uint8_t init_value) { } bool TfsLoadFile(const char *fname, uint8_t *buf, uint32_t len) { - if (!ufs_type) { return false; } + if (!ffs_type) { return false; } if (!TfsFileExists(fname)) { return false; } File file = ffsp->open(fname, "r"); @@ -360,7 +329,7 @@ bool TfsLoadFile(const char *fname, uint8_t *buf, uint32_t len) { } bool TfsDeleteFile(const char *fname) { - if (!ufs_type) { return false; } + if (!ffs_type) { return false; } if (!ffsp->remove(fname)) { AddLog_P(LOG_LEVEL_INFO, PSTR("TFS: Delete failed")); @@ -380,24 +349,48 @@ void (* const kUFSCommand[])(void) PROGMEM = { &UFSInfo, &UFSType, &UFSSize, &UFSFree, &UFSDelete}; void UFSInfo(void) { - Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}}"), ufs_type, UfsInfo(0, 0), UfsInfo(1, 0)); + Response_P(PSTR("{\"Ufs\":{\"Type\":%d,\"Size\":%d,\"Free\":%d}"), ufs_type, UfsInfo(0, 0), UfsInfo(1, 0)); + if (ffs_type && (ffs_type != ufs_type)) { + ResponseAppend_P(PSTR(",{\"Type\":%d,\"Size\":%d,\"Free\":%d}"), ffs_type, UfsInfo(0, 1), UfsInfo(1, 1)); + } + ResponseJsonEnd(); } void UFSType(void) { - ResponseCmndNumber(ufs_type); + if (ffs_type && (ffs_type != ufs_type)) { + Response_P(PSTR("{\"%s\":[%d,%d]}"), XdrvMailbox.command, ufs_type, ffs_type); + } else { + ResponseCmndNumber(ufs_type); + } } void UFSSize(void) { - ResponseCmndNumber(UfsInfo(0, 0)); + if (ffs_type && (ffs_type != ufs_type)) { + Response_P(PSTR("{\"%s\":[%d,%d]}"), XdrvMailbox.command, UfsInfo(0, 0), UfsInfo(0, 1)); + } else { + ResponseCmndNumber(UfsInfo(0, 0)); + } } void UFSFree(void) { - ResponseCmndNumber(UfsInfo(1, 0)); + if (ffs_type && (ffs_type != ufs_type)) { + Response_P(PSTR("{\"%s\":[%d,%d]}"), XdrvMailbox.command, UfsInfo(1, 0), UfsInfo(1, 1)); + } else { + ResponseCmndNumber(UfsInfo(1, 0)); + } } void UFSDelete(void) { + // UfsDelete sdcard or flashfs file if only one of them available + // UfsDelete2 flashfs file if available if (XdrvMailbox.data_len > 0) { - if (!TfsDeleteFile(XdrvMailbox.data)) { + bool result = false; + if (ffs_type && (ffs_type != ufs_type) && (2 == XdrvMailbox.index)) { + result = TfsDeleteFile(XdrvMailbox.data); + } else { + result = (ufs_type && ufsp->remove(XdrvMailbox.data)); + } + if (!result) { ResponseCmndChar(D_JSON_FAILED); } else { ResponseCmndDone(); @@ -418,7 +411,7 @@ const char UFS_FORM_FILE_UPLOAD[] PROGMEM = "
" "
 " D_MANAGE_FILE_SYSTEM " "; const char UFS_FORM_FILE_UPGc[] PROGMEM = - "
" D_FS_SIZE " %s kB - " D_FS_FREE " %s kB"; + "
" D_FS_SIZE " %s MB - " D_FS_FREE " %s MB"; const char UFS_FORM_FILE_UPGc1[] PROGMEM = "   %s"; @@ -451,7 +444,6 @@ const char UFS_FORM_SDC_HREFdel[] PROGMEM = "🔥"; // 🔥 #endif // GUI_TRASH_FILE - void UfsDirectory(void) { if (!HttpCheckPriviledgedAccess()) { return; } @@ -461,8 +453,6 @@ void UfsDirectory(void) { strcpy(ufs_path, "/"); - - if (Webserver->hasArg("download")) { String stmp = Webserver->arg("download"); char *cp = (char*)stmp.c_str(); @@ -494,15 +484,14 @@ void UfsDirectory(void) { WSContentSendStyle(); WSContentSend_P(UFS_FORM_FILE_UPLOAD); - char ts[16]; - char fs[16]; - UfsForm1000(UfsInfo(0, ufs_dir == 2 ? 1:0), ts, '.'); - UfsForm1000(UfsInfo(1, ufs_dir == 2 ? 1:0), fs, '.'); - - WSContentSend_P(UFS_FORM_FILE_UPGc, WebColor(COL_TEXT), ts, fs); + char ts[FLOATSZ]; + dtostrfd((float)UfsInfo(0, ufs_dir == 2 ? 1:0) / 1000, 3, ts); + char fs[FLOATSZ]; + dtostrfd((float)UfsInfo(1, ufs_dir == 2 ? 1:0) / 1000, 3, fs); + WSContentSend_PD(UFS_FORM_FILE_UPGc, WebColor(COL_TEXT), ts, fs); if (ufs_dir) { - WSContentSend_P(UFS_FORM_FILE_UPGc1, WiFi.localIP().toString().c_str(),ufs_dir == 1 ? 2:1, ufs_dir == 1 ? "SDCard":"FlashFS"); + WSContentSend_P(UFS_FORM_FILE_UPGc1, WiFi.localIP().toString().c_str(), (ufs_dir == 1)?2:1, (ufs_dir == 1)?PSTR("SDCard"):PSTR("FlashFS")); } WSContentSend_P(UFS_FORM_FILE_UPGc2); diff --git a/tasmota/xdrv_interface.ino b/tasmota/xdrv_interface.ino index a5e0f6479..77c93c6da 100644 --- a/tasmota/xdrv_interface.ino +++ b/tasmota/xdrv_interface.ino @@ -416,7 +416,123 @@ bool (* const xdrv_func_ptr[])(uint8_t) = { // Driver Function Pointers #endif #ifdef XDRV_99 - &Xdrv99 + &Xdrv99, +#endif + +#ifdef XDRV_100 + &Xdrv100, +#endif + +#ifdef XDRV_101 + &Xdrv101, +#endif + +#ifdef XDRV_102 + &Xdrv102, +#endif + +#ifdef XDRV_103 + &Xdrv103, +#endif + +#ifdef XDRV_104 + &Xdrv104, +#endif + +#ifdef XDRV_105 + &Xdrv105, +#endif + +#ifdef XDRV_106 + &Xdrv106, +#endif + +#ifdef XDRV_107 + &Xdrv107, +#endif + +#ifdef XDRV_108 + &Xdrv108, +#endif + +#ifdef XDRV_109 + &Xdrv109, +#endif + +#ifdef XDRV_110 + &Xdrv110, +#endif + +#ifdef XDRV_111 + &Xdrv111, +#endif + +#ifdef XDRV_112 + &Xdrv112, +#endif + +#ifdef XDRV_113 + &Xdrv113, +#endif + +#ifdef XDRV_114 + &Xdrv114, +#endif + +#ifdef XDRV_115 + &Xdrv115, +#endif + +#ifdef XDRV_116 + &Xdrv116, +#endif + +#ifdef XDRV_117 + &Xdrv117, +#endif + +#ifdef XDRV_118 + &Xdrv118, +#endif + +#ifdef XDRV_119 + &Xdrv119, +#endif + +#ifdef XDRV_120 + &Xdrv120, +#endif + +#ifdef XDRV_121 + &Xdrv121, +#endif + +#ifdef XDRV_122 + &Xdrv122, +#endif + +#ifdef XDRV_123 + &Xdrv123, +#endif + +#ifdef XDRV_124 + &Xdrv124, +#endif + +#ifdef XDRV_125 + &Xdrv125, +#endif + +#ifdef XDRV_126 + &Xdrv126, +#endif + +#ifdef XDRV_127 + &Xdrv127, +#endif + +#ifdef XDRV_128 + &Xdrv128 #endif }; @@ -825,7 +941,123 @@ const uint8_t kXdrvList[] = { #endif #ifdef XDRV_99 - XDRV_99 + XDRV_99, +#endif + +#ifdef XDRV_100 + Xdrv100, +#endif + +#ifdef XDRV_101 + Xdrv101, +#endif + +#ifdef XDRV_102 + Xdrv102, +#endif + +#ifdef XDRV_103 + Xdrv103, +#endif + +#ifdef XDRV_104 + Xdrv104, +#endif + +#ifdef XDRV_105 + Xdrv105, +#endif + +#ifdef XDRV_106 + Xdrv106, +#endif + +#ifdef XDRV_107 + Xdrv107, +#endif + +#ifdef XDRV_108 + Xdrv108, +#endif + +#ifdef XDRV_109 + Xdrv109, +#endif + +#ifdef XDRV_110 + Xdrv110, +#endif + +#ifdef XDRV_111 + Xdrv111, +#endif + +#ifdef XDRV_112 + Xdrv112, +#endif + +#ifdef XDRV_113 + Xdrv113, +#endif + +#ifdef XDRV_114 + Xdrv114, +#endif + +#ifdef XDRV_115 + Xdrv115, +#endif + +#ifdef XDRV_116 + Xdrv116, +#endif + +#ifdef XDRV_117 + Xdrv117, +#endif + +#ifdef XDRV_118 + Xdrv118, +#endif + +#ifdef XDRV_119 + Xdrv119, +#endif + +#ifdef XDRV_120 + Xdrv120, +#endif + +#ifdef XDRV_121 + Xdrv121, +#endif + +#ifdef XDRV_122 + Xdrv122, +#endif + +#ifdef XDRV_123 + Xdrv123, +#endif + +#ifdef XDRV_124 + Xdrv124, +#endif + +#ifdef XDRV_125 + Xdrv125, +#endif + +#ifdef XDRV_126 + Xdrv126, +#endif + +#ifdef XDRV_127 + Xdrv127, +#endif + +#ifdef XDRV_128 + Xdrv128 #endif }; diff --git a/tasmota/xdsp_interface.ino b/tasmota/xdsp_interface.ino index 1ce18ff6c..3ca49f3d6 100644 --- a/tasmota/xdsp_interface.ino +++ b/tasmota/xdsp_interface.ino @@ -87,7 +87,71 @@ bool (* const xdsp_func_ptr[])(uint8_t) = { // Display Function Pointers #endif #ifdef XDSP_16 - &Xdsp16 + &Xdsp16, +#endif + +#ifdef XDSP_17 + &Xdsp17, +#endif + +#ifdef XDSP_18 + &Xdsp18, +#endif + +#ifdef XDSP_19 + &Xdsp19, +#endif + +#ifdef XDSP_20 + &Xdsp20, +#endif + +#ifdef XDSP_21 + &Xdsp21, +#endif + +#ifdef XDSP_22 + &Xdsp22, +#endif + +#ifdef XDSP_23 + &Xdsp23, +#endif + +#ifdef XDSP_24 + &Xdsp24, +#endif + +#ifdef XDSP_25 + &Xdsp25, +#endif + +#ifdef XDSP_26 + &Xdsp26, +#endif + +#ifdef XDSP_27 + &Xdsp27, +#endif + +#ifdef XDSP_28 + &Xdsp28, +#endif + +#ifdef XDSP_29 + &Xdsp29, +#endif + +#ifdef XDSP_30 + &Xdsp30, +#endif + +#ifdef XDSP_31 + &Xdsp31, +#endif + +#ifdef XDSP_32 + &Xdsp32 #endif }; diff --git a/tasmota/xsns_52_ibeacon.ino b/tasmota/xsns_52_ibeacon.ino index 731189827..723446700 100755 --- a/tasmota/xsns_52_ibeacon.ino +++ b/tasmota/xsns_52_ibeacon.ino @@ -279,16 +279,13 @@ void ESP32Init() { if (TasmotaGlobal.global_state.wifi_down) { return; } + TasmotaGlobal.wifi_stay_asleep = true; if (WiFi.getSleep() == false) { - if (0 == Settings.flag3.sleep_normal) { - AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: About to restart to put WiFi modem in sleep mode"),"BLE"); - Settings.flag3.sleep_normal = 1; // SetOption60 - Enable normal sleep instead of dynamic sleep - TasmotaGlobal.restart_flag = 2; - } - return; + AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Put WiFi modem in sleep mode"),"BLE"); + WiFi.setSleep(true); // Sleep } - AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Initializing Blueetooth..."),"BLE"); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("%s: Initializing Bluetooth..."),"BLE"); if (!ESP32BLE.mode.init) { NimBLEDevice::init(""); diff --git a/tasmota/xsns_62_MI_ESP32.ino b/tasmota/xsns_62_MI_ESP32.ino index 9e9c70dc5..689b5a72a 100644 --- a/tasmota/xsns_62_MI_ESP32.ino +++ b/tasmota/xsns_62_MI_ESP32.ino @@ -589,17 +589,17 @@ int MI32_decryptPacket(char *_buf, uint16_t _bufSize, uint32_t _type){ MI32_ReverseMAC(packet->MAC); uint8_t _bindkey[16] = {0x0}; bool foundNoKey = true; - AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: search key for MAC: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Search key for MAC: %02x %02x %02x %02x %02x %02x"), packet->MAC[0], packet->MAC[1], packet->MAC[2], packet->MAC[3], packet->MAC[4], packet->MAC[5]); for(uint32_t i=0; iMAC,MIBLEbindKeys[i].MAC,sizeof(packet->MAC))==0){ memcpy(_bindkey,MIBLEbindKeys[i].key,sizeof(_bindkey)); - AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: decryption Key found")); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Decryption Key found")); foundNoKey = false; break; } } if(foundNoKey){ - AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: no Key found !!")); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: No Key found !!")); return -2; } @@ -619,7 +619,7 @@ int MI32_decryptPacket(char *_buf, uint16_t _bufSize, uint32_t _type){ ret = br_ccm_check_tag(&ctx, &tag); - AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: Err:%i, Decrypted : %02x %02x %02x %02x %02x "), ret, packet->payload[1],packet->payload[2],packet->payload[3],packet->payload[4],packet->payload[5]); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Err:%i, Decrypted : %02x %02x %02x %02x %02x "), ret, packet->payload[1],packet->payload[2],packet->payload[3],packet->payload[4],packet->payload[5]); return ret-1; } #endif // USE_MI_DECRYPTION @@ -660,7 +660,7 @@ uint32_t MIBLEgetSensorSlot(uint8_t (&_MAC)[6], uint16_t _type, uint8_t counter) bool _success = false; for (uint32_t i=0;i19) { - AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: Scan buffer full")); + AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Scan buffer full")); MI32.state.beaconScanCounter = 1; return; } for(auto _scanResult : MIBLEscanResult){ if(memcmp(addr,_scanResult.MAC,6)==0){ - // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: known device")); + // AddLog_P(LOG_LEVEL_INFO,PSTR("M32: known device")); return; } } @@ -1585,12 +1582,12 @@ void MI32addBeacon(uint8_t index, char* data){ _new.time = 0; if(memcmp(_empty,_new.MAC,6) == 0){ _new.active = false; - AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: beacon%u deactivated"), index); + AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Beacon%u deactivated"), index); } else{ _new.active = true; MI32.mode.activeBeacon = 1; - AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: beacon added with MAC: %s"), _MAC); + AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Beacon added with MAC: %s"), _MAC); } } @@ -1845,7 +1842,7 @@ void CmndMi32Time(void) { if (XdrvMailbox.data_len > 0) { if (MIBLEsensors.size() > XdrvMailbox.payload) { if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) { - AddLog_P(LOG_LEVEL_DEBUG, PSTR("MI32: will set Time")); + AddLog_P(LOG_LEVEL_DEBUG, PSTR("M32: Will set Time")); MI32.state.sensor = XdrvMailbox.payload; MI32.mode.canScan = 0; MI32.mode.canConnect = 0; @@ -1875,7 +1872,7 @@ void CmndMi32Unit(void) { if (XdrvMailbox.data_len > 0) { if (MIBLEsensors.size() > XdrvMailbox.payload) { if ((LYWSD02 == MIBLEsensors[XdrvMailbox.payload].type) || (MHOC303 == MIBLEsensors[XdrvMailbox.payload].type)) { - AddLog_P(LOG_LEVEL_DEBUG,PSTR("MI32: will set Unit")); + AddLog_P(LOG_LEVEL_DEBUG,PSTR("M32: Will set Unit")); MI32.state.sensor = XdrvMailbox.payload; MI32.mode.canScan = 0; MI32.mode.canConnect = 0; @@ -1925,11 +1922,11 @@ void CmndMi32Block(void){ switch (XdrvMailbox.index) { case 0: MIBLEBlockList.clear(); - // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: size of ilist: %u"), MIBLEBlockList.size()); - ResponseCmndIdxChar(PSTR("block list cleared")); + // AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Size of ilist: %u"), MIBLEBlockList.size()); + ResponseCmndIdxChar(PSTR("Block list cleared")); break; case 1: - ResponseCmndIdxChar(PSTR("show block list")); + ResponseCmndIdxChar(PSTR("Show block list")); break; } } @@ -1955,7 +1952,7 @@ void CmndMi32Block(void){ ResponseCmndIdxChar(XdrvMailbox.data); MI32removeMIBLEsensor(_MACasBytes.buf); } - // AddLog_P(LOG_LEVEL_INFO,PSTR("MI32: size of ilist: %u"), MIBLEBlockList.size()); + // AddLog_P(LOG_LEVEL_INFO,PSTR("M32: Size of ilist: %u"), MIBLEBlockList.size()); break; } } diff --git a/tasmota/xsns_81_seesaw_soil.ino b/tasmota/xsns_81_seesaw_soil.ino index aec9054f0..87c679a1a 100644 --- a/tasmota/xsns_81_seesaw_soil.ino +++ b/tasmota/xsns_81_seesaw_soil.ino @@ -20,143 +20,310 @@ #ifdef USE_I2C #ifdef USE_SEESAW_SOIL + /*********************************************************************************************\ - * SEESAW_SOIL - Capacitance & Temperature Sensor + * SEESAW_SOIL - Capacitice Soil Moisture & Temperature Sensor * * I2C Address: 0x36, 0x37, 0x38, 0x39 * + * This version of the driver replaces all delay loops by a state machine. So the number + * of instruction cycles consumed has been reduced dramatically. The sensors are reset, + * detected, commanded and read all at once. So the reading times won't increase with the + * number of sensors attached. The detection of sensors does not happen in FUNC_INIT any + * more. All i2c handling happens in the 50ms state machine. + * The memory footprint has suffered a little bit from this redesign, naturally. + * + * Memory footprint: 1444 bytes flash / 68 bytes RAM + * * NOTE: #define SEESAW_SOIL_PUBLISH enables immediate MQTT on soil moisture change * otherwise the moisture value will only be emitted every TelePeriod * #define SEESAW_SOIL_RAW enables displaying analog capacitance input in the * web page for calibration purposes + * #define SEESAW_SOIL_PERSISTENT_NAMING to get sensor names indexed by i2c address \*********************************************************************************************/ #define XSNS_81 81 #define XI2C_56 56 // See I2CDEVICES.md -#include "Adafruit_seesaw.h" +#include "Adafruit_seesaw.h" // we only use definitions, no code -#define SEESAW_SOIL_MAX_SENSORS 4 -#define SEESAW_SOIL_START_ADDRESS 0x36 +#define SEESAW_SOIL_MAX_SENSORS 4 +#define SEESAW_SOIL_START_ADDRESS 0x36 + // I2C state machine +#define STATE_IDLE 0x00 +#define STATE_RESET 0x01 +#define STATE_INIT 0x02 +#define STATE_DETECT 0x04 +#define STATE_COMMAND_TEMP 0x08 +#define STATE_READ_TEMP 0x10 +#define STATE_COMMAND_MOIST 0x20 +#define STATE_READ_MOIST 0x40 + // I2C commands +#define COMMAND_RESET 0x01 +#define COMMAND_ID 0x02 +#define COMMAND_TEMP 0x04 +#define COMMAND_MOIST 0x08 + // I2C delays +#define DELAY_DETECT 1 // ms delay before reading ID +#define DELAY_TEMP 1 // ms delay between command and reading +#define DELAY_MOIST 5 // ms delay between command and reading +#define DELAY_RESET 500 // ms delay after slave reset -const char SeeSoilName[] = "SeeSoil"; // spaces not allowed for Homeassistant integration/mqtt topics -uint8_t SeeSoilCount = 0; // global sensor count +// Convert capacitance into a moisture. +// From observation, a free air reading is at 320, immersed in tap water, reading is 1014 +// So let's make a scale that converts those (apparent) facts into a percentage +#define MAX_CAPACITANCE 1020.0f // subject to calibration +#define MIN_CAPACITANCE 320 // subject to calibration +#define CAP_TO_MOIST(c) ((max((int)(c),MIN_CAPACITANCE)-MIN_CAPACITANCE)/(MAX_CAPACITANCE-MIN_CAPACITANCE)*100) struct SEESAW_SOIL { - Adafruit_seesaw *ss; // instance pointer - uint16_t capacitance; - float temperature; - uint8_t address; -} SeeSoil[SEESAW_SOIL_MAX_SENSORS]; + const char name[8] = "SeeSoil"; // spaces not allowed for Homeassistant integration/mqtt topics + uint8_t count = 0; // global sensor count (0xFF = not initialized) + uint8_t state = STATE_IDLE; // current state + bool present = false; // driver active +} SeeSoil; -// Used to convert capacitance into a moisture. -// From observation, a free air reading is at 320 -// Immersed in tap water, reading is 1014 -// Appears to be a 10-bit device, readings close to 1020 -// So let's make a scale that converts those (apparent) facts into a percentage -#define MAX_CAPACITANCE 1020.0f // subject to calibration -#define MIN_CAPACITANCE 320 // subject to calibration -#define CAP_TO_MOIST(c) ((max((int)(c),MIN_CAPACITANCE)-MIN_CAPACITANCE)/(MAX_CAPACITANCE-MIN_CAPACITANCE)) +struct SEESAW_SOIL_SNS { + uint8_t address; // i2c address + float moisture; + float temperature; +#ifdef SEESAW_SOIL_RAW + uint16_t capacitance; // raw analog reading +#endif // SEESAW_SOIL_RAW +} SeeSoilSNS[SEESAW_SOIL_MAX_SENSORS]; -/********************************************************************************************/ +/*********************************************************************************************\ + * i2c routines +\*********************************************************************************************/ -void SEESAW_SOILDetect(void) { - Adafruit_seesaw *SSptr=0; - - for (uint32_t i = 0; i < SEESAW_SOIL_MAX_SENSORS; i++) { +void seeSoilInit(void) { + for (int i = 0; i < SEESAW_SOIL_MAX_SENSORS; i++) { int addr = SEESAW_SOIL_START_ADDRESS + i; - if (!I2cSetDevice(addr)) { continue; } - - if (!SSptr) { // don't have an object, - SSptr = new Adafruit_seesaw(); // allocate one - } - if (SSptr->begin(addr)) { - SeeSoil[SeeSoilCount].ss = SSptr; // save copy of pointer - SSptr = 0; // mark that we took it - SeeSoil[SeeSoilCount].address = addr; - SeeSoil[SeeSoilCount].temperature = NAN; - SeeSoil[SeeSoilCount].capacitance = 0; - I2cSetActiveFound(SeeSoil[SeeSoilCount].address, SeeSoilName); - SeeSoilCount++; - } + if ( ! I2cSetDevice(addr) ) { continue; } + seeSoilCommand(COMMAND_RESET); } - if (SSptr) { - delete SSptr; // used object for detection, didn't find anything so we don't need this object + SeeSoil.state = STATE_RESET; + SeeSoil.present = true; +} + +void seeSoilEvery50ms(void){ // i2c state machine + static uint32_t state_time; + + uint32_t time_diff = millis() - state_time; + + switch (SeeSoil.state) { + case STATE_RESET: // reset was just issued + SeeSoil.state = STATE_INIT; + break; + case STATE_INIT: // wait for sensors to settle + if (time_diff < DELAY_RESET) { return; } + seeSoilCommand(COMMAND_ID); // send hardware id commands + SeeSoil.state = STATE_DETECT; + break; + case STATE_DETECT: // detect sensors + if (time_diff < DELAY_DETECT) { return; } + seeSoilDetect(); + SeeSoil.state=STATE_COMMAND_TEMP; + break; + case STATE_COMMAND_TEMP: // send temperature commands + seeSoilCommand(COMMAND_TEMP); + SeeSoil.state = STATE_READ_TEMP; + break; + case STATE_READ_TEMP: + if (time_diff < DELAY_TEMP) { return; } + seeSoilRead(COMMAND_TEMP); // read temperature values + SeeSoil.state = STATE_COMMAND_MOIST; + break; + case STATE_COMMAND_MOIST: // send moisture commands + seeSoilCommand(COMMAND_MOIST); + SeeSoil.state = STATE_READ_MOIST; + break; + case STATE_READ_MOIST: + if (time_diff < DELAY_MOIST) { return; } + seeSoilRead(COMMAND_MOIST); // read moisture values + SeeSoil.state = STATE_COMMAND_TEMP; + break; + } + state_time = millis(); +} + +void seeSoilDetect(void) { // detect sensors + uint8_t buf; + + SeeSoil.count = 0; + SeeSoil.present = false; + for (int i = 0; i < SEESAW_SOIL_MAX_SENSORS; i++) { + uint32_t addr = SEESAW_SOIL_START_ADDRESS + i; + if ( ! I2cSetDevice(addr)) { continue; } + if (1 != Wire.requestFrom((uint8_t) addr, (uint8_t) 1)) { continue; } + buf = (uint8_t) Wire.read(); + if (buf != SEESAW_HW_ID_CODE) { // check hardware id +#ifdef DEBUG_SEESAW_SOIL + AddLog_P(LOG_LEVEL_DEBUG, PSTR("SEE: HWID mismatch ADDR=%X, ID=%X"), addr, buf); +#endif // DEBUG_SEESAW_SOIL + continue; + } + SeeSoilSNS[SeeSoil.count].address = addr; + SeeSoilSNS[SeeSoil.count].temperature = NAN; + SeeSoilSNS[SeeSoil.count].moisture = NAN; +#ifdef SEESAW_SOIL_RAW + SeeSoilSNS[SeeSoil.count].capacitance = 0; // raw analog reading +#endif // SEESAW_SOIL_RAW + I2cSetActiveFound(SeeSoilSNS[SeeSoil.count].address, SeeSoil.name); + SeeSoil.count++; + SeeSoil.present = true; +#ifdef DEBUG_SEESAW_SOIL + AddLog_P(LOG_LEVEL_DEBUG, PSTR("SEE: FOUND sensor %u at %02X"), i, addr); +#endif // DEBUG_SEESAW_SOIL } } -void SEESAW_SOILEverySecond(void) { // update sensor values and publish if changed -#ifdef SEESAW_SOIL_PUBLISH - uint32_t old_moist; -#endif // SEESAW_SOIL_PUBLISH +void seeSoilCommand(uint32_t command) { // issue commands to sensors + uint8_t regLow; + uint8_t regHigh = SEESAW_STATUS_BASE; + uint32_t count = SeeSoil.count; - for (uint32_t i = 0; i < SeeSoilCount; i++) { - SeeSoil[i].temperature = ConvertTemp(SeeSoil[i].ss->getTemp()); -#ifdef SEESAW_SOIL_PUBLISH - old_moist = uint32_t (CAP_TO_MOIST(SeeSoil[i].capacitance)*100); -#endif // SEESAW_SOIL_PUBLISH - SeeSoil[i].capacitance = SeeSoil[i].ss->touchRead(0); -#ifdef SEESAW_SOIL_PUBLISH - if (uint32_t (CAP_TO_MOIST(SeeSoil[i].capacitance)*100) != old_moist) { - Response_P(PSTR("{")); // send values to MQTT & rules - SEESAW_SOILJson(i); - ResponseJsonEnd(); - MqttPublishTeleSensor(); + switch (command) { + case COMMAND_RESET: + count = SEESAW_SOIL_MAX_SENSORS; + regLow = SEESAW_STATUS_SWRST; + break; + case COMMAND_ID: + count = SEESAW_SOIL_MAX_SENSORS; + regLow = SEESAW_STATUS_HW_ID; + break; + case COMMAND_TEMP: + regLow = SEESAW_STATUS_TEMP; + break; + case COMMAND_MOIST: + regHigh = SEESAW_TOUCH_BASE; + regLow = SEESAW_TOUCH_CHANNEL_OFFSET; + break; + default: +#ifdef DEBUG_SEESAW_SOIL + AddLog_P(LOG_LEVEL_DEBUG, PSTR("SEE: ILL CMD:%02X"), command); +#endif // DEBUG_SEESAW_SOIL + return; + } + for (int i = 0; i < count; i++) { + uint32_t addr = (command & (COMMAND_RESET|COMMAND_ID)) ? SEESAW_SOIL_START_ADDRESS + i : SeeSoilSNS[i].address; + Wire.beginTransmission((uint8_t) addr); + Wire.write((uint8_t) regHigh); + Wire.write((uint8_t) regLow); + uint32_t err = Wire.endTransmission(); +#ifdef DEBUG_SEESAW_SOIL + AddLog_P(LOG_LEVEL_DEBUG, PSTR("SEE: SNS=%u ADDR=%02X CMD=%02X ERR=%u"), i, addr, command, err); +#endif // DEBUG_SEESAW_SOIL } -#endif // SEESAW_SOIL_PUBLISH +} + +void seeSoilRead(uint32_t command) { // read values from sensors + uint8_t buf[4]; + uint32_t num; + int32_t ret; + + num = (command == COMMAND_TEMP) ? 4 : 2; // response size in bytes + + for (int i = 0; i < SeeSoil.count; i++) { // for all sensors + if (num != Wire.requestFrom((uint8_t) SeeSoilSNS[i].address, (uint8_t) num)) { continue; } + bzero(buf, sizeof(buf)); + for (int b = 0; b < num; b++) { + buf[b] = (uint8_t) Wire.read(); + } + if (command == COMMAND_TEMP) { + ret = ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | + ((uint32_t)buf[2] << 8) | (uint32_t)buf[3]; + SeeSoilSNS[i].temperature = ConvertTemp((1.0 / (1UL << 16)) * ret); + } else { // COMMAND_MOIST + ret = (uint32_t)buf[0] << 8 | (uint32_t)buf[1]; + SeeSoilSNS[i].moisture = CAP_TO_MOIST(ret); +#ifdef SEESAW_SOIL_RAW + SeeSoilSNS[i].capacitance = ret; +#endif // SEESAW_SOIL_RAW + } +#ifdef DEBUG_SEESAW_SOIL + AddLog_P(LOG_LEVEL_DEBUG, PSTR("SEE: READ #%u ADDR=%02X NUM=%u RET=%X"), i, SeeSoilSNS[i].address, num, ret); +#endif // DEBUG_SEESAW_SOIL } } -void SEESAW_SOILShow(bool json) { +/*********************************************************************************************\ + * JSON routines +\*********************************************************************************************/ + +#ifdef SEESAW_SOIL_PUBLISH +void seeSoilEverySecond(void) { // update sensor values and publish if changed + static uint16_t old_moist[SEESAW_SOIL_MAX_SENSORS]; + static bool firstcall = true; + + for (int i = 0; i < SeeSoil.count; i++) { + if (firstcall) { firstcall = false; } + else { + if ((uint32_t) SeeSoilSNS[i].moisture != old_moist[i]) { + Response_P(PSTR("{")); // send values to MQTT & rules + seeSoilJson(i); + ResponseJsonEnd(); + MqttPublishTeleSensor(); + } + } + old_moist[i] = (uint32_t) SeeSoilSNS[i].moisture; + } +} +#endif // SEESAW_SOIL_PUBLISH + +void seeSoilShow(bool json) { char temperature[FLOATSZ]; - char sensor_name[sizeof(SeeSoilName) + 3]; + char sensor_name[sizeof(SeeSoil.name) + 3]; - for (uint32_t i = 0; i < SeeSoilCount; i++) { - dtostrfd(SeeSoil[i].temperature, Settings.flag2.temperature_resolution, temperature); - SEESAW_SOILName(i, sensor_name, sizeof(sensor_name)); + for (uint32_t i = 0; i < SeeSoil.count; i++) { + dtostrfd(SeeSoilSNS[i].temperature, Settings.flag2.temperature_resolution, temperature); + seeSoilName(i, sensor_name, sizeof(sensor_name)); if (json) { - ResponseAppend_P(PSTR(",")); // compose tele json - SEESAW_SOILJson(i); + ResponseAppend_P(PSTR(",")); // compose tele json + seeSoilJson(i); if (0 == TasmotaGlobal.tele_period) { #ifdef USE_DOMOTICZ - DomoticzTempHumPressureSensor(SeeSoil[i].temperature, CAP_TO_MOIST(SeeSoil[i].capacitance)*100, -42.0f); + DomoticzTempHumPressureSensor(SeeSoilSNS[i].temperature, SeeSoilSNS[i].moisture, -42.0f); #endif // USE_DOMOTICZ #ifdef USE_KNX - KnxSensor(KNX_TEMPERATURE, SeeSoil[i].temperature); - KnxSensor(KNX_HUMIDITY, CAP_TO_MOIST(SeeSoil[i].capacitance) * 100); + KnxSensor(KNX_TEMPERATURE, SeeSoilSNS[i].temperature); + KnxSensor(KNX_HUMIDITY, SeeSoilSNS[i].moisture); #endif // USE_KNX } #ifdef USE_WEBSERVER } else { #ifdef SEESAW_SOIL_RAW - WSContentSend_PD(HTTP_SNS_ANALOG, sensor_name, 0, SeeSoil[i].capacitance); // dump raw value + WSContentSend_PD(HTTP_SNS_ANALOG, sensor_name, 0, SeeSoilSNS[i].capacitance); #endif // SEESAW_SOIL_RAW - WSContentSend_PD(HTTP_SNS_MOISTURE, sensor_name, - uint32_t (CAP_TO_MOIST(SeeSoil[i].capacitance)*100)); // web page formats as integer (%d) percent + WSContentSend_PD(HTTP_SNS_MOISTURE, sensor_name, (uint32_t) SeeSoilSNS[i].moisture); WSContentSend_PD(HTTP_SNS_TEMP, sensor_name, temperature, TempUnit()); #endif // USE_WEBSERVER } } // for each sensor connected } -void SEESAW_SOILJson(int no) { // common json +void seeSoilJson(int no) { // common json char temperature[FLOATSZ]; - char sensor_name[sizeof(SeeSoilName) + 3]; + char sensor_name[sizeof(SeeSoil.name) + 3]; - SEESAW_SOILName(no, sensor_name, sizeof(sensor_name)); - dtostrfd(SeeSoil[no].temperature, Settings.flag2.temperature_resolution, temperature); + seeSoilName(no, sensor_name, sizeof(sensor_name)); + dtostrfd(SeeSoilSNS[no].temperature, Settings.flag2.temperature_resolution, temperature); ResponseAppend_P(PSTR ("\"%s\":{\"" D_JSON_ID "\":\"%02X\",\"" D_JSON_TEMPERATURE "\":%s,\"" D_JSON_MOISTURE "\":%u}"), - sensor_name, SeeSoil[no].address, temperature, uint32_t (CAP_TO_MOIST(SeeSoil[no].capacitance)*100)); + sensor_name, SeeSoilSNS[no].address, temperature, (uint32_t) SeeSoilSNS[no].moisture); } -void SEESAW_SOILName(int no, char *name, int len) // generates a sensor name +void seeSoilName(int no, char *name, int len) // generates a sensor name { - if (SeeSoilCount > 1) { - snprintf_P(name, len, PSTR("%s%c%u"), SeeSoilName, IndexSeparator(), no + 1); +#ifdef SEESAW_SOIL_PERSISTENT_NAMING + snprintf_P(name, len, PSTR("%s%c%02X"), SeeSoil.name, IndexSeparator(), SeeSoilSNS[no].address); +#else + if (SeeSoil.count > 1) { + snprintf_P(name, len, PSTR("%s%c%u"), SeeSoil.name, IndexSeparator(), no + 1); } else { - strlcpy(name, SeeSoilName, len); + strlcpy(name, SeeSoil.name, len); } +#endif // SEESAW_SOIL_PERSISTENT_NAMING } /*********************************************************************************************\ @@ -169,19 +336,24 @@ bool Xsns81(uint8_t function) bool result = false; if (FUNC_INIT == function) { - SEESAW_SOILDetect(); + seeSoilInit(); } - else if (SeeSoilCount){ + else if (SeeSoil.present){ switch (function) { - case FUNC_EVERY_SECOND: - SEESAW_SOILEverySecond(); + case FUNC_EVERY_50_MSECOND: + seeSoilEvery50ms(); break; + #ifdef SEESAW_SOIL_PUBLISH + case FUNC_EVERY_SECOND: + seeSoilEverySecond(); + break; + #endif // SEESAW_SOIL_PUBLISH case FUNC_JSON_APPEND: - SEESAW_SOILShow(1); + seeSoilShow(1); break; #ifdef USE_WEBSERVER case FUNC_WEB_SENSOR: - SEESAW_SOILShow(0); + seeSoilShow(0); break; #endif // USE_WEBSERVER } diff --git a/tasmota/xsns_interface.ino b/tasmota/xsns_interface.ino index 21918f22c..f41de7920 100644 --- a/tasmota/xsns_interface.ino +++ b/tasmota/xsns_interface.ino @@ -825,27 +825,144 @@ const uint8_t kXsnsList[] = { #endif #ifdef XSNS_99 - XSNS_99 + XSNS_99, #endif + +#ifdef XSNS_100 + XSNS_100, +#endif + +#ifdef XSNS_101 + XSNS_101, +#endif + +#ifdef XSNS_102 + XSNS_102, +#endif + +#ifdef XSNS_103 + XSNS_103, +#endif + +#ifdef XSNS_104 + XSNS_104, +#endif + +#ifdef XSNS_105 + XSNS_105, +#endif + +#ifdef XSNS_106 + XSNS_106, +#endif + +#ifdef XSNS_107 + XSNS_107, +#endif + +#ifdef XSNS_108 + XSNS_108, +#endif + +#ifdef XSNS_109 + XSNS_109, +#endif + +#ifdef XSNS_110 + XSNS_110, +#endif + +#ifdef XSNS_111 + XSNS_111, +#endif + +#ifdef XSNS_112 + XSNS_112, +#endif + +#ifdef XSNS_113 + XSNS_113, +#endif + +#ifdef XSNS_114 + XSNS_114, +#endif + +#ifdef XSNS_115 + XSNS_115, +#endif + +#ifdef XSNS_116 + XSNS_116, +#endif + +#ifdef XSNS_117 + XSNS_117, +#endif + +#ifdef XSNS_118 + XSNS_118, +#endif + +#ifdef XSNS_119 + XSNS_119, +#endif + +#ifdef XSNS_120 + XSNS_120, +#endif + +#ifdef XSNS_121 + XSNS_121, +#endif + +#ifdef XSNS_122 + XSNS_122, +#endif + +#ifdef XSNS_123 + XSNS_123, +#endif + +#ifdef XSNS_124 + XSNS_124, +#endif + +#ifdef XSNS_125 + XSNS_125, +#endif + +#ifdef XSNS_126 + XSNS_126, +#endif + +#ifdef XSNS_127 + XSNS_127, +#endif + +#ifdef XSNS_128 + XSNS_128 +#endif + }; /*********************************************************************************************/ -bool XsnsEnabled(uint32_t sns_index) -{ +bool XsnsEnabled(uint32_t sns_index) { if (sns_index < sizeof(kXsnsList)) { #ifdef XFUNC_PTR_IN_ROM uint32_t index = pgm_read_byte(kXsnsList + sns_index); #else uint32_t index = kXsnsList[sns_index]; #endif - return bitRead(Settings.sensors[index / 32], index % 32); + if (index < MAX_XSNS_DRIVERS) { + return bitRead(Settings.sensors[index / 32], index % 32); + } } return true; } -void XsnsSensorState(void) -{ +void XsnsSensorState(void) { ResponseAppend_P(PSTR("\"")); // Use string for enable/disable signal for (uint32_t i = 0; i < sizeof(kXsnsList); i++) { #ifdef XFUNC_PTR_IN_ROM @@ -866,8 +983,7 @@ void XsnsSensorState(void) * Function call to all xsns \*********************************************************************************************/ -bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index) -{ +bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index) { if (0 == xsns_present) { xsns_index = 0; return false; @@ -891,8 +1007,7 @@ bool XsnsNextCall(uint8_t Function, uint8_t &xsns_index) return xsns_func_ptr[xsns_index](Function); } -bool XsnsCall(uint8_t Function) -{ +bool XsnsCall(uint8_t Function) { bool result = false; DEBUG_TRACE_LOG(PSTR("SNS: %d"), Function);