mirror of
				https://github.com/esphome/esphome.git
				synced 2025-11-04 00:51:49 +00:00 
			
		
		
		
	Compare commits
	
		
			12 Commits
		
	
	
		
			2022.4.0b1
			...
			2022.4.0b4
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					41d9059a2f | ||
| 
						 | 
					e26e0d7c01 | ||
| 
						 | 
					ad41c07a1f | ||
| 
						 | 
					5732f3b044 | ||
| 
						 | 
					712115b6ce | ||
| 
						 | 
					9283559c6b | ||
| 
						 | 
					6b393438e9 | ||
| 
						 | 
					343b9ab455 | ||
| 
						 | 
					dcb226b202 | ||
| 
						 | 
					2243021b58 | ||
| 
						 | 
					d5134e88b1 | ||
| 
						 | 
					c59adf612f | 
@@ -173,6 +173,7 @@ esphome/components/select/* @esphome/core
 | 
			
		||||
esphome/components/sensirion_common/* @martgras
 | 
			
		||||
esphome/components/sensor/* @esphome/core
 | 
			
		||||
esphome/components/sgp40/* @SenexCrenshaw
 | 
			
		||||
esphome/components/shelly_dimmer/* @edge90 @rnauber
 | 
			
		||||
esphome/components/sht4x/* @sjtrny
 | 
			
		||||
esphome/components/shutdown/* @esphome/core @jsuanet
 | 
			
		||||
esphome/components/sim800l/* @glmnet
 | 
			
		||||
 
 | 
			
		||||
@@ -98,6 +98,8 @@ CELL_VOLTAGE_SCHEMA = sensor.sensor_schema(
 | 
			
		||||
    unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
    device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
    state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
    icon=ICON_FLASH,
 | 
			
		||||
    accuracy_decimals=3,
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = cv.All(
 | 
			
		||||
 
 | 
			
		||||
@@ -143,37 +143,37 @@ CONFIG_SCHEMA = cv.Schema(
 | 
			
		||||
        cv.Optional("power_delivered_l1"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_delivered_l2"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_delivered_l3"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_returned_l1"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_returned_l2"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("power_returned_l3"): sensor.sensor_schema(
 | 
			
		||||
            unit_of_measurement=UNIT_KILOWATT,
 | 
			
		||||
            accuracy_decimals=3,
 | 
			
		||||
            device_class=DEVICE_CLASS_CURRENT,
 | 
			
		||||
            device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            state_class=STATE_CLASS_MEASUREMENT,
 | 
			
		||||
        ),
 | 
			
		||||
        cv.Optional("reactive_power_delivered_l1"): sensor.sensor_schema(
 | 
			
		||||
 
 | 
			
		||||
@@ -23,13 +23,13 @@ std::string build_json(const json_build_t &f) {
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  const size_t free_heap = ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
#elif defined(USE_ESP32)
 | 
			
		||||
  const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
 | 
			
		||||
  const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  const size_t request_size = std::min(free_heap - 2048, (size_t) 5120);
 | 
			
		||||
  const size_t request_size = std::min(free_heap, (size_t) 512);
 | 
			
		||||
 | 
			
		||||
  DynamicJsonDocument json_document(request_size);
 | 
			
		||||
  if (json_document.memoryPool().buffer() == nullptr) {
 | 
			
		||||
  if (json_document.capacity() == 0) {
 | 
			
		||||
    ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes",
 | 
			
		||||
             request_size, free_heap);
 | 
			
		||||
    return "{}";
 | 
			
		||||
@@ -37,7 +37,7 @@ std::string build_json(const json_build_t &f) {
 | 
			
		||||
  JsonObject root = json_document.to<JsonObject>();
 | 
			
		||||
  f(root);
 | 
			
		||||
  json_document.shrinkToFit();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGV(TAG, "Size after shrink %u bytes", json_document.capacity());
 | 
			
		||||
  std::string output;
 | 
			
		||||
  serializeJson(json_document, output);
 | 
			
		||||
  return output;
 | 
			
		||||
@@ -51,13 +51,13 @@ void parse_json(const std::string &data, const json_parse_t &f) {
 | 
			
		||||
#ifdef USE_ESP8266
 | 
			
		||||
  const size_t free_heap = ESP.getMaxFreeBlockSize();  // NOLINT(readability-static-accessed-through-instance)
 | 
			
		||||
#elif defined(USE_ESP32)
 | 
			
		||||
  const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
 | 
			
		||||
  const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_8BIT);
 | 
			
		||||
#endif
 | 
			
		||||
  bool pass = false;
 | 
			
		||||
  size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5));
 | 
			
		||||
  size_t request_size = std::min(free_heap, (size_t)(data.size() * 1.5));
 | 
			
		||||
  do {
 | 
			
		||||
    DynamicJsonDocument json_document(request_size);
 | 
			
		||||
    if (json_document.memoryPool().buffer() == nullptr) {
 | 
			
		||||
    if (json_document.capacity() == 0) {
 | 
			
		||||
      ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size,
 | 
			
		||||
               free_heap);
 | 
			
		||||
      return;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								esphome/components/shelly_dimmer/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/shelly_dimmer/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
CODEOWNERS = ["@rnauber", "@edge90"]
 | 
			
		||||
							
								
								
									
										158
									
								
								esphome/components/shelly_dimmer/dev_table.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										158
									
								
								esphome/components/shelly_dimmer/dev_table.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,158 @@
 | 
			
		||||
/*
 | 
			
		||||
  stm32flash - Open Source ST STM32 flash program for Arduino
 | 
			
		||||
  Copyright (C) 2010 Geoffrey McRae <geoff@spacevs.com>
 | 
			
		||||
  Copyright (C) 2014-2015 Antonio Borneo <borneo.antonio@gmail.com>
 | 
			
		||||
 | 
			
		||||
  This program is free software; you can redistribute it and/or
 | 
			
		||||
  modify it under the terms of the GNU General Public License
 | 
			
		||||
  as published by the Free Software Foundation; either version 2
 | 
			
		||||
  of the License, or (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
  This program is distributed in the hope that it will be useful,
 | 
			
		||||
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
  GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
  You should have received a copy of the GNU General Public License
 | 
			
		||||
  along with this program; if not, write to the Free Software
 | 
			
		||||
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 | 
			
		||||
*/
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#ifdef USE_SHD_FIRMWARE_DATA
 | 
			
		||||
#include "stm32flash.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace shelly_dimmer {
 | 
			
		||||
 | 
			
		||||
constexpr uint32_t SZ_128 = 0x00000080;
 | 
			
		||||
constexpr uint32_t SZ_256 = 0x00000100;
 | 
			
		||||
constexpr uint32_t SZ_1K = 0x00000400;
 | 
			
		||||
constexpr uint32_t SZ_2K = 0x00000800;
 | 
			
		||||
constexpr uint32_t SZ_16K = 0x00004000;
 | 
			
		||||
constexpr uint32_t SZ_32K = 0x00008000;
 | 
			
		||||
constexpr uint32_t SZ_64K = 0x00010000;
 | 
			
		||||
constexpr uint32_t SZ_128K = 0x00020000;
 | 
			
		||||
constexpr uint32_t SZ_256K = 0x00040000;
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Page-size for page-by-page flash erase.
 | 
			
		||||
 * Arrays are zero terminated; last non-zero value is automatically repeated
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
/* fixed size pages */
 | 
			
		||||
constexpr uint32_t p_128[] = {SZ_128, 0};  // NOLINT
 | 
			
		||||
constexpr uint32_t p_256[] = {SZ_256, 0};  // NOLINT
 | 
			
		||||
constexpr uint32_t p_1k[] = {SZ_1K, 0};    // NOLINT
 | 
			
		||||
constexpr uint32_t p_2k[] = {SZ_2K, 0};    // NOLINT
 | 
			
		||||
/* F2 and F4 page size */
 | 
			
		||||
constexpr uint32_t f2f4[] = {SZ_16K, SZ_16K, SZ_16K, SZ_16K, SZ_64K, SZ_128K, 0};  // NOLINT
 | 
			
		||||
/* F4 dual bank page size */
 | 
			
		||||
constexpr uint32_t f4db[] = {SZ_16K,  SZ_16K, SZ_16K, SZ_16K, SZ_64K, SZ_128K, SZ_128K,  // NOLINT
 | 
			
		||||
                             SZ_128K, SZ_16K, SZ_16K, SZ_16K, SZ_16K, SZ_64K,  SZ_128K, 0};
 | 
			
		||||
/* F7 page size */
 | 
			
		||||
constexpr uint32_t f7[] = {SZ_32K, SZ_32K, SZ_32K, SZ_32K, SZ_128K, SZ_256K, 0};  // NOLINT
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Device table, corresponds to the "Bootloader device-dependant parameters"
 | 
			
		||||
 * table in ST document AN2606.
 | 
			
		||||
 * Note that the option bytes upper range is inclusive!
 | 
			
		||||
 */
 | 
			
		||||
constexpr stm32_dev_t DEVICES[] = {
 | 
			
		||||
    /* ID   "name"                              SRAM-address-range      FLASH-address-range    PPS  PSize
 | 
			
		||||
       Option-byte-addr-range  System-mem-addr-range   Flags */
 | 
			
		||||
    /* F0 */
 | 
			
		||||
    {0x440, "STM32F030x8/F05xxx", 0x20000800, 0x20002000, 0x08000000, 0x08010000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFEC00, 0x1FFFF800, 0},
 | 
			
		||||
    {0x442, "STM32F030xC/F09xxx", 0x20001800, 0x20008000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFC800, 0x1FFFF800, F_OBLL},
 | 
			
		||||
    {0x444, "STM32F03xx4/6", 0x20000800, 0x20001000, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFEC00, 0x1FFFF800, 0},
 | 
			
		||||
    {0x445, "STM32F04xxx/F070x6", 0x20001800, 0x20001800, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFC400, 0x1FFFF800, 0},
 | 
			
		||||
    {0x448, "STM32F070xB/F071xx/F72xx", 0x20001800, 0x20004000, 0x08000000, 0x08020000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFC800, 0x1FFFF800, 0},
 | 
			
		||||
    /* F1 */
 | 
			
		||||
    {0x412, "STM32F10xxx Low-density", 0x20000200, 0x20002800, 0x08000000, 0x08008000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFF000, 0x1FFFF800, 0},
 | 
			
		||||
    {0x410, "STM32F10xxx Medium-density", 0x20000200, 0x20005000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800,
 | 
			
		||||
     0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0},
 | 
			
		||||
    {0x414, "STM32F10xxx High-density", 0x20000200, 0x20010000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFF000, 0x1FFFF800, 0},
 | 
			
		||||
    {0x420, "STM32F10xxx Medium-density VL", 0x20000200, 0x20002000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800,
 | 
			
		||||
     0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0},
 | 
			
		||||
    {0x428, "STM32F10xxx High-density VL", 0x20000200, 0x20008000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800,
 | 
			
		||||
     0x1FFFF80F, 0x1FFFF000, 0x1FFFF800, 0},
 | 
			
		||||
    {0x418, "STM32F105xx/F107xx", 0x20001000, 0x20010000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFB000, 0x1FFFF800, 0},
 | 
			
		||||
    {0x430, "STM32F10xxx XL-density", 0x20000800, 0x20018000, 0x08000000, 0x08100000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFE000, 0x1FFFF800, 0},
 | 
			
		||||
    /* F2 */
 | 
			
		||||
    {0x411, "STM32F2xxxx", 0x20002000, 0x20020000, 0x08000000, 0x08100000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000,
 | 
			
		||||
     0x1FFF7800, 0},
 | 
			
		||||
    /* F3 */
 | 
			
		||||
    {0x432, "STM32F373xx/F378xx", 0x20001400, 0x20008000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFD800, 0x1FFFF800, 0},
 | 
			
		||||
    {0x422, "STM32F302xB(C)/F303xB(C)/F358xx", 0x20001400, 0x2000A000, 0x08000000, 0x08040000, 2, p_2k, 0x1FFFF800,
 | 
			
		||||
     0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0},
 | 
			
		||||
    {0x439, "STM32F301xx/F302x4(6/8)/F318xx", 0x20001800, 0x20004000, 0x08000000, 0x08010000, 2, p_2k, 0x1FFFF800,
 | 
			
		||||
     0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0},
 | 
			
		||||
    {0x438, "STM32F303x4(6/8)/F334xx/F328xx", 0x20001800, 0x20003000, 0x08000000, 0x08010000, 2, p_2k, 0x1FFFF800,
 | 
			
		||||
     0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0},
 | 
			
		||||
    {0x446, "STM32F302xD(E)/F303xD(E)/F398xx", 0x20001800, 0x20010000, 0x08000000, 0x08080000, 2, p_2k, 0x1FFFF800,
 | 
			
		||||
     0x1FFFF80F, 0x1FFFD800, 0x1FFFF800, 0},
 | 
			
		||||
    /* F4 */
 | 
			
		||||
    {0x413, "STM32F40xxx/41xxx", 0x20003000, 0x20020000, 0x08000000, 0x08100000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F,
 | 
			
		||||
     0x1FFF0000, 0x1FFF7800, 0},
 | 
			
		||||
    {0x419, "STM32F42xxx/43xxx", 0x20003000, 0x20030000, 0x08000000, 0x08200000, 1, f4db, 0x1FFEC000, 0x1FFFC00F,
 | 
			
		||||
     0x1FFF0000, 0x1FFF7800, 0},
 | 
			
		||||
    {0x423, "STM32F401xB(C)", 0x20003000, 0x20010000, 0x08000000, 0x08040000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F,
 | 
			
		||||
     0x1FFF0000, 0x1FFF7800, 0},
 | 
			
		||||
    {0x433, "STM32F401xD(E)", 0x20003000, 0x20018000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F,
 | 
			
		||||
     0x1FFF0000, 0x1FFF7800, 0},
 | 
			
		||||
    {0x458, "STM32F410xx", 0x20003000, 0x20008000, 0x08000000, 0x08020000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000,
 | 
			
		||||
     0x1FFF7800, 0},
 | 
			
		||||
    {0x431, "STM32F411xx", 0x20003000, 0x20020000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000,
 | 
			
		||||
     0x1FFF7800, 0},
 | 
			
		||||
    {0x421, "STM32F446xx", 0x20003000, 0x20020000, 0x08000000, 0x08080000, 1, f2f4, 0x1FFFC000, 0x1FFFC00F, 0x1FFF0000,
 | 
			
		||||
     0x1FFF7800, 0},
 | 
			
		||||
    {0x434, "STM32F469xx", 0x20003000, 0x20060000, 0x08000000, 0x08200000, 1, f4db, 0x1FFEC000, 0x1FFFC00F, 0x1FFF0000,
 | 
			
		||||
     0x1FFF7800, 0},
 | 
			
		||||
    /* F7 */
 | 
			
		||||
    {0x449, "STM32F74xxx/75xxx", 0x20004000, 0x20050000, 0x08000000, 0x08100000, 1, f7, 0x1FFF0000, 0x1FFF001F,
 | 
			
		||||
     0x1FF00000, 0x1FF0EDC0, 0},
 | 
			
		||||
    /* L0 */
 | 
			
		||||
    {0x425, "STM32L031xx/041xx", 0x20001000, 0x20002000, 0x08000000, 0x08008000, 32, p_128, 0x1FF80000, 0x1FF8001F,
 | 
			
		||||
     0x1FF00000, 0x1FF01000, 0},
 | 
			
		||||
    {0x417, "STM32L05xxx/06xxx", 0x20001000, 0x20002000, 0x08000000, 0x08010000, 32, p_128, 0x1FF80000, 0x1FF8001F,
 | 
			
		||||
     0x1FF00000, 0x1FF01000, 0},
 | 
			
		||||
    {0x447, "STM32L07xxx/08xxx", 0x20002000, 0x20005000, 0x08000000, 0x08030000, 32, p_128, 0x1FF80000, 0x1FF8001F,
 | 
			
		||||
     0x1FF00000, 0x1FF02000, 0},
 | 
			
		||||
    /* L1 */
 | 
			
		||||
    {0x416, "STM32L1xxx6(8/B)", 0x20000800, 0x20004000, 0x08000000, 0x08020000, 16, p_256, 0x1FF80000, 0x1FF8001F,
 | 
			
		||||
     0x1FF00000, 0x1FF01000, F_NO_ME},
 | 
			
		||||
    {0x429, "STM32L1xxx6(8/B)A", 0x20001000, 0x20008000, 0x08000000, 0x08020000, 16, p_256, 0x1FF80000, 0x1FF8001F,
 | 
			
		||||
     0x1FF00000, 0x1FF01000, 0},
 | 
			
		||||
    {0x427, "STM32L1xxxC", 0x20001000, 0x20008000, 0x08000000, 0x08040000, 16, p_256, 0x1FF80000, 0x1FF8001F,
 | 
			
		||||
     0x1FF00000, 0x1FF02000, 0},
 | 
			
		||||
    {0x436, "STM32L1xxxD", 0x20001000, 0x2000C000, 0x08000000, 0x08060000, 16, p_256, 0x1FF80000, 0x1FF8009F,
 | 
			
		||||
     0x1FF00000, 0x1FF02000, 0},
 | 
			
		||||
    {0x437, "STM32L1xxxE", 0x20001000, 0x20014000, 0x08000000, 0x08080000, 16, p_256, 0x1FF80000, 0x1FF8009F,
 | 
			
		||||
     0x1FF00000, 0x1FF02000, F_NO_ME},
 | 
			
		||||
    /* L4 */
 | 
			
		||||
    {0x415, "STM32L476xx/486xx", 0x20003100, 0x20018000, 0x08000000, 0x08100000, 1, p_2k, 0x1FFF7800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFF0000, 0x1FFF7000, 0},
 | 
			
		||||
    /* These are not (yet) in AN2606: */
 | 
			
		||||
    {0x641, "Medium_Density PL", 0x20000200, 0x20005000, 0x08000000, 0x08020000, 4, p_1k, 0x1FFFF800, 0x1FFFF80F,
 | 
			
		||||
     0x1FFFF000, 0x1FFFF800, 0},
 | 
			
		||||
    {0x9a8, "STM32W-128K", 0x20000200, 0x20002000, 0x08000000, 0x08020000, 4, p_1k, 0x08040800, 0x0804080F, 0x08040000,
 | 
			
		||||
     0x08040800, 0},
 | 
			
		||||
    {0x9b0, "STM32W-256K", 0x20000200, 0x20004000, 0x08000000, 0x08040000, 4, p_2k, 0x08040800, 0x0804080F, 0x08040000,
 | 
			
		||||
     0x08040800, 0},
 | 
			
		||||
    {0x0, "", 0x0, 0x0, 0x0, 0x0, 0x0, nullptr, 0x0, 0x0, 0x0, 0x0, 0x0},
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace shelly_dimmer
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
#endif
 | 
			
		||||
							
								
								
									
										219
									
								
								esphome/components/shelly_dimmer/light.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								esphome/components/shelly_dimmer/light.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,219 @@
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
import hashlib
 | 
			
		||||
import re
 | 
			
		||||
import requests
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
import esphome.codegen as cg
 | 
			
		||||
import esphome.config_validation as cv
 | 
			
		||||
from esphome import pins
 | 
			
		||||
from esphome.components import light, sensor, uart
 | 
			
		||||
from esphome.const import (
 | 
			
		||||
    CONF_OUTPUT_ID,
 | 
			
		||||
    CONF_GAMMA_CORRECT,
 | 
			
		||||
    CONF_POWER,
 | 
			
		||||
    CONF_VOLTAGE,
 | 
			
		||||
    CONF_CURRENT,
 | 
			
		||||
    CONF_VERSION,
 | 
			
		||||
    CONF_URL,
 | 
			
		||||
    CONF_UPDATE_INTERVAL,
 | 
			
		||||
    UNIT_VOLT,
 | 
			
		||||
    UNIT_AMPERE,
 | 
			
		||||
    UNIT_WATT,
 | 
			
		||||
    DEVICE_CLASS_POWER,
 | 
			
		||||
    DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
)
 | 
			
		||||
from esphome.core import HexInt, CORE
 | 
			
		||||
 | 
			
		||||
DOMAIN = "shelly_dimmer"
 | 
			
		||||
DEPENDENCIES = ["sensor", "uart"]
 | 
			
		||||
 | 
			
		||||
shelly_dimmer_ns = cg.esphome_ns.namespace("shelly_dimmer")
 | 
			
		||||
ShellyDimmer = shelly_dimmer_ns.class_(
 | 
			
		||||
    "ShellyDimmer", light.LightOutput, cg.PollingComponent, uart.UARTDevice
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CONF_FIRMWARE = "firmware"
 | 
			
		||||
CONF_SHA256 = "sha256"
 | 
			
		||||
CONF_UPDATE = "update"
 | 
			
		||||
 | 
			
		||||
CONF_LEADING_EDGE = "leading_edge"
 | 
			
		||||
CONF_WARMUP_BRIGHTNESS = "warmup_brightness"
 | 
			
		||||
# CONF_WARMUP_TIME = "warmup_time"
 | 
			
		||||
CONF_MIN_BRIGHTNESS = "min_brightness"
 | 
			
		||||
CONF_MAX_BRIGHTNESS = "max_brightness"
 | 
			
		||||
 | 
			
		||||
CONF_NRST_PIN = "nrst_pin"
 | 
			
		||||
CONF_BOOT0_PIN = "boot0_pin"
 | 
			
		||||
 | 
			
		||||
KNOWN_FIRMWARE = {
 | 
			
		||||
    "51.5": (
 | 
			
		||||
        "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.5/shelly-dimmer-stm32_v51.5.bin",
 | 
			
		||||
        "553fc1d78ed113227af7683eaa9c26189a961c4ea9a48000fb5aa8f8ac5d7b60",
 | 
			
		||||
    ),
 | 
			
		||||
    "51.6": (
 | 
			
		||||
        "https://github.com/jamesturton/shelly-dimmer-stm32/releases/download/v51.6/shelly-dimmer-stm32_v51.6.bin",
 | 
			
		||||
        "eda483e111c914723a33f5088f1397d5c0b19333db4a88dc965636b976c16c36",
 | 
			
		||||
    ),
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def parse_firmware_version(value):
 | 
			
		||||
    match = re.match(r"(\d+).(\d+)", value)
 | 
			
		||||
    if match is None:
 | 
			
		||||
        raise ValueError(f"Not a valid version number {value}")
 | 
			
		||||
    major = int(match[1])
 | 
			
		||||
    minor = int(match[2])
 | 
			
		||||
    return major, minor
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_firmware(value):
 | 
			
		||||
    if not value[CONF_UPDATE]:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    def dl(url):
 | 
			
		||||
        try:
 | 
			
		||||
            req = requests.get(url)
 | 
			
		||||
            req.raise_for_status()
 | 
			
		||||
        except requests.exceptions.RequestException as e:
 | 
			
		||||
            raise cv.Invalid(f"Could not download firmware file ({url}): {e}")
 | 
			
		||||
 | 
			
		||||
        h = hashlib.new("sha256")
 | 
			
		||||
        h.update(req.content)
 | 
			
		||||
        return req.content, h.hexdigest()
 | 
			
		||||
 | 
			
		||||
    url = value[CONF_URL]
 | 
			
		||||
 | 
			
		||||
    if CONF_SHA256 in value:  # we have a hash, enable caching
 | 
			
		||||
        path = (
 | 
			
		||||
            Path(CORE.config_dir)
 | 
			
		||||
            / ".esphome"
 | 
			
		||||
            / DOMAIN
 | 
			
		||||
            / (value[CONF_SHA256] + "_fw_stm.bin")
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        if not path.is_file():
 | 
			
		||||
            firmware_data, dl_hash = dl(url)
 | 
			
		||||
 | 
			
		||||
            if dl_hash != value[CONF_SHA256]:
 | 
			
		||||
                raise cv.Invalid(
 | 
			
		||||
                    f"Hash mismatch for {url}: {dl_hash} != {value[CONF_SHA256]}"
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            path.parent.mkdir(exist_ok=True, parents=True)
 | 
			
		||||
            path.write_bytes(firmware_data)
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            firmware_data = path.read_bytes()
 | 
			
		||||
    else:  # no caching, download every time
 | 
			
		||||
        firmware_data, dl_hash = dl(url)
 | 
			
		||||
 | 
			
		||||
    return [HexInt(x) for x in firmware_data]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_firmware(value):
 | 
			
		||||
    config = value.copy()
 | 
			
		||||
    if CONF_URL not in config:
 | 
			
		||||
        try:
 | 
			
		||||
            config[CONF_URL], config[CONF_SHA256] = KNOWN_FIRMWARE[config[CONF_VERSION]]
 | 
			
		||||
        except KeyError as e:
 | 
			
		||||
            raise cv.Invalid(
 | 
			
		||||
                f"Firmware {config[CONF_VERSION]} is unknown, please specify an '{CONF_URL}' ..."
 | 
			
		||||
            ) from e
 | 
			
		||||
    get_firmware(config)
 | 
			
		||||
    return config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_sha256(value):
 | 
			
		||||
    value = cv.string(value)
 | 
			
		||||
    if not value.isalnum() or not len(value) == 64:
 | 
			
		||||
        raise ValueError(f"Not a valid SHA256 hex string: {value}")
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def validate_version(value):
 | 
			
		||||
    parse_firmware_version(value)
 | 
			
		||||
    return value
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIG_SCHEMA = (
 | 
			
		||||
    light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(
 | 
			
		||||
        {
 | 
			
		||||
            cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(ShellyDimmer),
 | 
			
		||||
            cv.Optional(CONF_FIRMWARE, default="51.6"): cv.maybe_simple_value(
 | 
			
		||||
                {
 | 
			
		||||
                    cv.Optional(CONF_URL): cv.url,
 | 
			
		||||
                    cv.Optional(CONF_SHA256): validate_sha256,
 | 
			
		||||
                    cv.Required(CONF_VERSION): validate_version,
 | 
			
		||||
                    cv.Optional(CONF_UPDATE, default=False): cv.boolean,
 | 
			
		||||
                },
 | 
			
		||||
                validate_firmware,  # converts a simple version key to generate the full url
 | 
			
		||||
                key=CONF_VERSION,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_NRST_PIN, default="GPIO5"): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Optional(CONF_BOOT0_PIN, default="GPIO4"): pins.gpio_output_pin_schema,
 | 
			
		||||
            cv.Optional(CONF_LEADING_EDGE, default=False): cv.boolean,
 | 
			
		||||
            cv.Optional(CONF_WARMUP_BRIGHTNESS, default=100): cv.uint16_t,
 | 
			
		||||
            # cv.Optional(CONF_WARMUP_TIME, default=20): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_MIN_BRIGHTNESS, default=0): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_MAX_BRIGHTNESS, default=1000): cv.uint16_t,
 | 
			
		||||
            cv.Optional(CONF_POWER): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_WATT,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_VOLT,
 | 
			
		||||
                accuracy_decimals=1,
 | 
			
		||||
                device_class=DEVICE_CLASS_VOLTAGE,
 | 
			
		||||
            ),
 | 
			
		||||
            cv.Optional(CONF_CURRENT): sensor.sensor_schema(
 | 
			
		||||
                unit_of_measurement=UNIT_AMPERE,
 | 
			
		||||
                device_class=DEVICE_CLASS_POWER,
 | 
			
		||||
                accuracy_decimals=2,
 | 
			
		||||
            ),
 | 
			
		||||
            # Change the default gamma_correct setting.
 | 
			
		||||
            cv.Optional(CONF_GAMMA_CORRECT, default=1.0): cv.positive_float,
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
    .extend(cv.polling_component_schema("10s"))
 | 
			
		||||
    .extend(uart.UART_DEVICE_SCHEMA)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def to_code(config):
 | 
			
		||||
    fw_hex = get_firmware(config[CONF_FIRMWARE])
 | 
			
		||||
    fw_major, fw_minor = parse_firmware_version(config[CONF_FIRMWARE][CONF_VERSION])
 | 
			
		||||
 | 
			
		||||
    if fw_hex is not None:
 | 
			
		||||
        cg.add_define("USE_SHD_FIRMWARE_DATA", fw_hex)
 | 
			
		||||
    cg.add_define("USE_SHD_FIRMWARE_MAJOR_VERSION", fw_major)
 | 
			
		||||
    cg.add_define("USE_SHD_FIRMWARE_MINOR_VERSION", fw_minor)
 | 
			
		||||
 | 
			
		||||
    var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
 | 
			
		||||
    yield cg.register_component(var, config)
 | 
			
		||||
    config.pop(
 | 
			
		||||
        CONF_UPDATE_INTERVAL
 | 
			
		||||
    )  # drop UPDATE_INTERVAL as it does not apply to the light component
 | 
			
		||||
 | 
			
		||||
    yield light.register_light(var, config)
 | 
			
		||||
    yield uart.register_uart_device(var, config)
 | 
			
		||||
 | 
			
		||||
    nrst_pin = yield cg.gpio_pin_expression(config[CONF_NRST_PIN])
 | 
			
		||||
    cg.add(var.set_nrst_pin(nrst_pin))
 | 
			
		||||
    boot0_pin = yield cg.gpio_pin_expression(config[CONF_BOOT0_PIN])
 | 
			
		||||
    cg.add(var.set_boot0_pin(boot0_pin))
 | 
			
		||||
 | 
			
		||||
    cg.add(var.set_leading_edge(config[CONF_LEADING_EDGE]))
 | 
			
		||||
    cg.add(var.set_warmup_brightness(config[CONF_WARMUP_BRIGHTNESS]))
 | 
			
		||||
    # cg.add(var.set_warmup_time(config[CONF_WARMUP_TIME]))
 | 
			
		||||
    cg.add(var.set_min_brightness(config[CONF_MIN_BRIGHTNESS]))
 | 
			
		||||
    cg.add(var.set_max_brightness(config[CONF_MAX_BRIGHTNESS]))
 | 
			
		||||
 | 
			
		||||
    for key in [CONF_POWER, CONF_VOLTAGE, CONF_CURRENT]:
 | 
			
		||||
        if key not in config:
 | 
			
		||||
            continue
 | 
			
		||||
 | 
			
		||||
        conf = config[key]
 | 
			
		||||
        sens = yield sensor.new_sensor(conf)
 | 
			
		||||
        cg.add(getattr(var, f"set_{key}_sensor")(sens))
 | 
			
		||||
							
								
								
									
										526
									
								
								esphome/components/shelly_dimmer/shelly_dimmer.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										526
									
								
								esphome/components/shelly_dimmer/shelly_dimmer.cpp
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,526 @@
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#include "esphome/core/helpers.h"
 | 
			
		||||
 | 
			
		||||
#include "shelly_dimmer.h"
 | 
			
		||||
#ifdef USE_SHD_FIRMWARE_DATA
 | 
			
		||||
#include "stm32flash.h"
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#ifndef USE_ESP_IDF
 | 
			
		||||
#include <HardwareSerial.h>
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <algorithm>
 | 
			
		||||
#include <cstring>
 | 
			
		||||
#include <memory>
 | 
			
		||||
#include <numeric>
 | 
			
		||||
 | 
			
		||||
namespace {
 | 
			
		||||
 | 
			
		||||
constexpr char TAG[] = "shelly_dimmer";
 | 
			
		||||
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_ACK_TIMEOUT = 200;  // ms
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_MAX_RETRIES = 3;
 | 
			
		||||
constexpr uint16_t SHELLY_DIMMER_MAX_BRIGHTNESS = 1000;  // 100%
 | 
			
		||||
 | 
			
		||||
// Protocol framing.
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_START_BYTE = 0x01;
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_END_BYTE = 0x04;
 | 
			
		||||
 | 
			
		||||
// Supported commands.
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH = 0x01;
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_POLL = 0x10;
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_VERSION = 0x11;
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS = 0x20;
 | 
			
		||||
 | 
			
		||||
// Command payload sizes.
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE = 2;
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE = 10;
 | 
			
		||||
constexpr uint8_t SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE = 4 + 72 + 3;
 | 
			
		||||
 | 
			
		||||
// STM Firmware
 | 
			
		||||
#ifdef USE_SHD_FIRMWARE_DATA
 | 
			
		||||
constexpr uint8_t STM_FIRMWARE[] PROGMEM = USE_SHD_FIRMWARE_DATA;
 | 
			
		||||
constexpr uint32_t STM_FIRMWARE_SIZE_IN_BYTES = sizeof(STM_FIRMWARE);
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
// Scaling Constants
 | 
			
		||||
constexpr float POWER_SCALING_FACTOR = 880373;
 | 
			
		||||
constexpr float VOLTAGE_SCALING_FACTOR = 347800;
 | 
			
		||||
constexpr float CURRENT_SCALING_FACTOR = 1448;
 | 
			
		||||
 | 
			
		||||
// Esentially std::size() for pre c++17
 | 
			
		||||
template<typename T, size_t N> constexpr size_t size(const T (&/*unused*/)[N]) noexcept { return N; }
 | 
			
		||||
 | 
			
		||||
}  // Anonymous namespace
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace shelly_dimmer {
 | 
			
		||||
 | 
			
		||||
/// Computes a crappy checksum as defined by the Shelly Dimmer protocol.
 | 
			
		||||
uint16_t shelly_dimmer_checksum(const uint8_t *buf, int len) {
 | 
			
		||||
  return std::accumulate<decltype(buf), uint16_t>(buf, buf + len, 0);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::setup() {
 | 
			
		||||
  this->pin_nrst_->setup();
 | 
			
		||||
  this->pin_boot0_->setup();
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "Initializing Shelly Dimmer...");
 | 
			
		||||
 | 
			
		||||
  // Reset the STM32 and check the firmware version.
 | 
			
		||||
  for (int i = 0; i < 2; i++) {
 | 
			
		||||
    this->reset_normal_boot_();
 | 
			
		||||
    this->send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION, nullptr, 0);
 | 
			
		||||
    ESP_LOGI(TAG, "STM32 current firmware version: %d.%d, desired version: %d.%d", this->version_major_,
 | 
			
		||||
             this->version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION);
 | 
			
		||||
    if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION ||
 | 
			
		||||
        this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) {
 | 
			
		||||
#ifdef USE_SHD_FIRMWARE_DATA
 | 
			
		||||
      // Update firmware if needed.
 | 
			
		||||
      ESP_LOGW(TAG, "Unsupported STM32 firmware version, flashing");
 | 
			
		||||
      if (i > 0) {
 | 
			
		||||
        // Upgrade was already performed but the reported version is still not right.
 | 
			
		||||
        ESP_LOGE(TAG, "STM32 firmware upgrade already performed, but version is still incorrect");
 | 
			
		||||
        this->mark_failed();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!this->upgrade_firmware_()) {
 | 
			
		||||
        ESP_LOGW(TAG, "Failed to upgrade firmware");
 | 
			
		||||
        this->mark_failed();
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Firmware upgrade completed, do the checks again.
 | 
			
		||||
      continue;
 | 
			
		||||
#else
 | 
			
		||||
      ESP_LOGW(TAG, "Firmware version mismatch, put 'update: true' in the yaml to flash an update.");
 | 
			
		||||
      this->mark_failed();
 | 
			
		||||
      return;
 | 
			
		||||
#endif
 | 
			
		||||
    }
 | 
			
		||||
    break;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->send_settings_();
 | 
			
		||||
  // Do an immediate poll to refresh current state.
 | 
			
		||||
  this->send_command_(SHELLY_DIMMER_PROTO_CMD_POLL, nullptr, 0);
 | 
			
		||||
 | 
			
		||||
  this->ready_ = true;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::update() { this->send_command_(SHELLY_DIMMER_PROTO_CMD_POLL, nullptr, 0); }
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::dump_config() {
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "ShellyDimmer:");
 | 
			
		||||
  LOG_PIN("  NRST Pin: ", this->pin_nrst_);
 | 
			
		||||
  LOG_PIN("  BOOT0 Pin: ", this->pin_boot0_);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Leading Edge: %s", YESNO(this->leading_edge_));
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Warmup Brightness: %d", this->warmup_brightness_);
 | 
			
		||||
  // ESP_LOGCONFIG(TAG, "  Warmup Time: %d", this->warmup_time_);
 | 
			
		||||
  // ESP_LOGCONFIG(TAG, "  Fade Rate: %d", this->fade_rate_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Minimum Brightness: %d", this->min_brightness_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  Maximum Brightness: %d", this->max_brightness_);
 | 
			
		||||
 | 
			
		||||
  LOG_UPDATE_INTERVAL(this);
 | 
			
		||||
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  STM32 current firmware version: %d.%d ", this->version_major_, this->version_minor_);
 | 
			
		||||
  ESP_LOGCONFIG(TAG, "  STM32 required firmware version: %d.%d", USE_SHD_FIRMWARE_MAJOR_VERSION,
 | 
			
		||||
                USE_SHD_FIRMWARE_MINOR_VERSION);
 | 
			
		||||
 | 
			
		||||
  if (this->version_major_ != USE_SHD_FIRMWARE_MAJOR_VERSION ||
 | 
			
		||||
      this->version_minor_ != USE_SHD_FIRMWARE_MINOR_VERSION) {
 | 
			
		||||
    ESP_LOGE(TAG, "  Firmware version mismatch, put 'update: true' in the yaml to flash an update.");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::write_state(light::LightState *state) {
 | 
			
		||||
  if (!this->ready_) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  float brightness;
 | 
			
		||||
  state->current_values_as_brightness(&brightness);
 | 
			
		||||
 | 
			
		||||
  const uint16_t brightness_int = this->convert_brightness_(brightness);
 | 
			
		||||
  if (brightness_int == this->brightness_) {
 | 
			
		||||
    ESP_LOGV(TAG, "Not sending unchanged value");
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGD(TAG, "Brightness update: %d (raw: %f)", brightness_int, brightness);
 | 
			
		||||
 | 
			
		||||
  this->send_brightness_(brightness_int);
 | 
			
		||||
}
 | 
			
		||||
#ifdef USE_SHD_FIRMWARE_DATA
 | 
			
		||||
bool ShellyDimmer::upgrade_firmware_() {
 | 
			
		||||
  ESP_LOGW(TAG, "Starting STM32 firmware upgrade");
 | 
			
		||||
  this->reset_dfu_boot_();
 | 
			
		||||
 | 
			
		||||
  // Could be constexpr in c++17
 | 
			
		||||
  static const auto CLOSE = [](stm32_t *stm32) { stm32_close(stm32); };
 | 
			
		||||
 | 
			
		||||
  // Cleanup with RAII
 | 
			
		||||
  std::unique_ptr<stm32_t, decltype(CLOSE)> stm32{stm32_init(this, STREAM_SERIAL, 1), CLOSE};
 | 
			
		||||
 | 
			
		||||
  if (!stm32) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to initialize STM32");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Erase STM32 flash.
 | 
			
		||||
  if (stm32_erase_memory(stm32.get(), 0, STM32_MASS_ERASE) != STM32_ERR_OK) {
 | 
			
		||||
    ESP_LOGW(TAG, "Failed to erase STM32 flash memory");
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static constexpr uint32_t BUFFER_SIZE = 256;
 | 
			
		||||
 | 
			
		||||
  // Copy the STM32 firmware over in 256-byte chunks. Note that the firmware is stored
 | 
			
		||||
  // in flash memory so all accesses need to be 4-byte aligned.
 | 
			
		||||
  uint8_t buffer[BUFFER_SIZE];
 | 
			
		||||
  const uint8_t *p = STM_FIRMWARE;
 | 
			
		||||
  uint32_t offset = 0;
 | 
			
		||||
  uint32_t addr = stm32->dev->fl_start;
 | 
			
		||||
  const uint32_t end = addr + STM_FIRMWARE_SIZE_IN_BYTES;
 | 
			
		||||
 | 
			
		||||
  while (addr < end && offset < STM_FIRMWARE_SIZE_IN_BYTES) {
 | 
			
		||||
    const uint32_t left_of_buffer = std::min(end - addr, BUFFER_SIZE);
 | 
			
		||||
    const uint32_t len = std::min(left_of_buffer, STM_FIRMWARE_SIZE_IN_BYTES - offset);
 | 
			
		||||
 | 
			
		||||
    if (len == 0) {
 | 
			
		||||
      break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    std::memcpy(buffer, p, BUFFER_SIZE);
 | 
			
		||||
    p += BUFFER_SIZE;
 | 
			
		||||
 | 
			
		||||
    if (stm32_write_memory(stm32.get(), addr, buffer, len) != STM32_ERR_OK) {
 | 
			
		||||
      ESP_LOGW(TAG, "Failed to write to STM32 flash memory");
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    addr += len;
 | 
			
		||||
    offset += len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  ESP_LOGI(TAG, "STM32 firmware upgrade successful");
 | 
			
		||||
 | 
			
		||||
  return true;
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
uint16_t ShellyDimmer::convert_brightness_(float brightness) {
 | 
			
		||||
  // Special case for zero as only zero means turn off completely.
 | 
			
		||||
  if (brightness == 0.0) {
 | 
			
		||||
    return 0;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return remap<uint16_t, float>(brightness, 0.0f, 1.0f, this->min_brightness_, this->max_brightness_);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::send_brightness_(uint16_t brightness) {
 | 
			
		||||
  const uint8_t payload[] = {
 | 
			
		||||
      // Brightness (%) * 10.
 | 
			
		||||
      static_cast<uint8_t>(brightness & 0xff),
 | 
			
		||||
      static_cast<uint8_t>(brightness >> 8),
 | 
			
		||||
  };
 | 
			
		||||
  static_assert(size(payload) == SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE, "Invalid payload size");
 | 
			
		||||
 | 
			
		||||
  this->send_command_(SHELLY_DIMMER_PROTO_CMD_SWITCH, payload, SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE);
 | 
			
		||||
 | 
			
		||||
  this->brightness_ = brightness;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::send_settings_() {
 | 
			
		||||
  const uint16_t fade_rate = std::min(uint16_t{100}, this->fade_rate_);
 | 
			
		||||
 | 
			
		||||
  float brightness = 0.0;
 | 
			
		||||
  if (this->state_ != nullptr) {
 | 
			
		||||
    this->state_->current_values_as_brightness(&brightness);
 | 
			
		||||
  }
 | 
			
		||||
  const uint16_t brightness_int = this->convert_brightness_(brightness);
 | 
			
		||||
  ESP_LOGD(TAG, "Brightness update: %d (raw: %f)", brightness_int, brightness);
 | 
			
		||||
 | 
			
		||||
  const uint8_t payload[] = {
 | 
			
		||||
      // Brightness (%) * 10.
 | 
			
		||||
      static_cast<uint8_t>(brightness_int & 0xff),
 | 
			
		||||
      static_cast<uint8_t>(brightness_int >> 8),
 | 
			
		||||
      // Leading / trailing edge [0x01 = leading, 0x02 = trailing].
 | 
			
		||||
      this->leading_edge_ ? uint8_t{0x01} : uint8_t{0x02},
 | 
			
		||||
      0x00,
 | 
			
		||||
      // Fade rate.
 | 
			
		||||
      static_cast<uint8_t>(fade_rate & 0xff),
 | 
			
		||||
      static_cast<uint8_t>(fade_rate >> 8),
 | 
			
		||||
      // Warmup brightness.
 | 
			
		||||
      static_cast<uint8_t>(this->warmup_brightness_ & 0xff),
 | 
			
		||||
      static_cast<uint8_t>(this->warmup_brightness_ >> 8),
 | 
			
		||||
      // Warmup time.
 | 
			
		||||
      static_cast<uint8_t>(this->warmup_time_ & 0xff),
 | 
			
		||||
      static_cast<uint8_t>(this->warmup_time_ >> 8),
 | 
			
		||||
  };
 | 
			
		||||
  static_assert(size(payload) == SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE, "Invalid payload size");
 | 
			
		||||
 | 
			
		||||
  this->send_command_(SHELLY_DIMMER_PROTO_CMD_SETTINGS, payload, SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE);
 | 
			
		||||
 | 
			
		||||
  // Also send brightness separately as it is ignored above.
 | 
			
		||||
  this->send_brightness_(brightness_int);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ShellyDimmer::send_command_(uint8_t cmd, const uint8_t *const payload, uint8_t len) {
 | 
			
		||||
  ESP_LOGD(TAG, "Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len, format_hex(payload, len).c_str());
 | 
			
		||||
 | 
			
		||||
  // Prepare a command frame.
 | 
			
		||||
  uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE];
 | 
			
		||||
  const size_t frame_len = this->frame_command_(frame, cmd, payload, len);
 | 
			
		||||
 | 
			
		||||
  // Write the frame and wait for acknowledgement.
 | 
			
		||||
  int retries = SHELLY_DIMMER_MAX_RETRIES;
 | 
			
		||||
  while (retries--) {
 | 
			
		||||
    this->write_array(frame, frame_len);
 | 
			
		||||
    this->flush();
 | 
			
		||||
 | 
			
		||||
    ESP_LOGD(TAG, "Command sent, waiting for reply");
 | 
			
		||||
    const uint32_t tx_time = millis();
 | 
			
		||||
    while (millis() - tx_time < SHELLY_DIMMER_ACK_TIMEOUT) {
 | 
			
		||||
      if (this->read_frame_()) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      delay(1);
 | 
			
		||||
    }
 | 
			
		||||
    ESP_LOGW(TAG, "Timeout while waiting for reply");
 | 
			
		||||
  }
 | 
			
		||||
  ESP_LOGW(TAG, "Failed to send command");
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t ShellyDimmer::frame_command_(uint8_t *data, uint8_t cmd, const uint8_t *const payload, size_t len) {
 | 
			
		||||
  size_t pos = 0;
 | 
			
		||||
 | 
			
		||||
  // Generate a frame.
 | 
			
		||||
  data[0] = SHELLY_DIMMER_PROTO_START_BYTE;
 | 
			
		||||
  data[1] = ++this->seq_;
 | 
			
		||||
  data[2] = cmd;
 | 
			
		||||
  data[3] = len;
 | 
			
		||||
  pos += 4;
 | 
			
		||||
 | 
			
		||||
  if (payload != nullptr) {
 | 
			
		||||
    std::memcpy(data + 4, payload, len);
 | 
			
		||||
    pos += len;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Calculate checksum for the payload.
 | 
			
		||||
  const uint16_t csum = shelly_dimmer_checksum(data + 1, 3 + len);
 | 
			
		||||
  data[pos++] = static_cast<uint8_t>(csum >> 8);
 | 
			
		||||
  data[pos++] = static_cast<uint8_t>(csum & 0xff);
 | 
			
		||||
  data[pos++] = SHELLY_DIMMER_PROTO_END_BYTE;
 | 
			
		||||
  return pos;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int ShellyDimmer::handle_byte_(uint8_t c) {
 | 
			
		||||
  const uint8_t pos = this->buffer_pos_;
 | 
			
		||||
 | 
			
		||||
  if (pos == 0) {
 | 
			
		||||
    // Must be start byte.
 | 
			
		||||
    return c == SHELLY_DIMMER_PROTO_START_BYTE ? 1 : -1;
 | 
			
		||||
  } else if (pos < 4) {
 | 
			
		||||
    // Header.
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // Decode payload length from header.
 | 
			
		||||
  const uint8_t payload_len = this->buffer_[3];
 | 
			
		||||
  if ((4 + payload_len + 3) > SHELLY_DIMMER_BUFFER_SIZE) {
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (pos < 4 + payload_len + 1) {
 | 
			
		||||
    // Payload.
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (pos == 4 + payload_len + 1) {
 | 
			
		||||
    // Verify checksum.
 | 
			
		||||
    const uint16_t csum = (this->buffer_[pos - 1] << 8 | c);
 | 
			
		||||
    const uint16_t csum_verify = shelly_dimmer_checksum(&this->buffer_[1], 3 + payload_len);
 | 
			
		||||
    if (csum != csum_verify) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    }
 | 
			
		||||
    return 1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (pos == 4 + payload_len + 2) {
 | 
			
		||||
    // Must be end byte.
 | 
			
		||||
    return c == SHELLY_DIMMER_PROTO_END_BYTE ? 0 : -1;
 | 
			
		||||
  }
 | 
			
		||||
  return -1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ShellyDimmer::read_frame_() {
 | 
			
		||||
  while (this->available()) {
 | 
			
		||||
    const uint8_t c = this->read();
 | 
			
		||||
    this->buffer_[this->buffer_pos_] = c;
 | 
			
		||||
 | 
			
		||||
    ESP_LOGV(TAG, "Read byte: 0x%02x (pos %d)", c, this->buffer_pos_);
 | 
			
		||||
 | 
			
		||||
    switch (this->handle_byte_(c)) {
 | 
			
		||||
      case 0: {
 | 
			
		||||
        // Frame successfully received.
 | 
			
		||||
        this->handle_frame_();
 | 
			
		||||
        this->buffer_pos_ = 0;
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      case -1: {
 | 
			
		||||
        // Failure.
 | 
			
		||||
        this->buffer_pos_ = 0;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
      case 1: {
 | 
			
		||||
        // Need more data.
 | 
			
		||||
        this->buffer_pos_++;
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool ShellyDimmer::handle_frame_() {
 | 
			
		||||
  const uint8_t seq = this->buffer_[1];
 | 
			
		||||
  const uint8_t cmd = this->buffer_[2];
 | 
			
		||||
  const uint8_t payload_len = this->buffer_[3];
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Got frame: 0x%02x", cmd);
 | 
			
		||||
 | 
			
		||||
  // Compare with expected identifier as the frame is always a response to
 | 
			
		||||
  // our previously sent command.
 | 
			
		||||
  if (seq != this->seq_) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const uint8_t *payload = &this->buffer_[4];
 | 
			
		||||
 | 
			
		||||
  // Handle response.
 | 
			
		||||
  switch (cmd) {
 | 
			
		||||
    case SHELLY_DIMMER_PROTO_CMD_POLL: {
 | 
			
		||||
      if (payload_len < 16) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      const uint8_t hw_version = payload[0];
 | 
			
		||||
      // payload[1] is unused.
 | 
			
		||||
      const uint16_t brightness = encode_uint16(payload[3], payload[2]);
 | 
			
		||||
 | 
			
		||||
      const uint32_t power_raw = encode_uint32(payload[7], payload[6], payload[5], payload[4]);
 | 
			
		||||
 | 
			
		||||
      const uint32_t voltage_raw = encode_uint32(payload[11], payload[10], payload[9], payload[8]);
 | 
			
		||||
 | 
			
		||||
      const uint32_t current_raw = encode_uint32(payload[15], payload[14], payload[13], payload[12]);
 | 
			
		||||
 | 
			
		||||
      const uint16_t fade_rate = payload[16];
 | 
			
		||||
 | 
			
		||||
      float power = 0;
 | 
			
		||||
      if (power_raw > 0) {
 | 
			
		||||
        power = POWER_SCALING_FACTOR / static_cast<float>(power_raw);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      float voltage = 0;
 | 
			
		||||
      if (voltage_raw > 0) {
 | 
			
		||||
        voltage = VOLTAGE_SCALING_FACTOR / static_cast<float>(voltage_raw);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      float current = 0;
 | 
			
		||||
      if (current_raw > 0) {
 | 
			
		||||
        current = CURRENT_SCALING_FACTOR / static_cast<float>(current_raw);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      ESP_LOGI(TAG, "Got dimmer data:");
 | 
			
		||||
      ESP_LOGI(TAG, "  HW version: %d", hw_version);
 | 
			
		||||
      ESP_LOGI(TAG, "  Brightness: %d", brightness);
 | 
			
		||||
      ESP_LOGI(TAG, "  Fade rate:  %d", fade_rate);
 | 
			
		||||
      ESP_LOGI(TAG, "  Power:      %f W", power);
 | 
			
		||||
      ESP_LOGI(TAG, "  Voltage:    %f V", voltage);
 | 
			
		||||
      ESP_LOGI(TAG, "  Current:    %f A", current);
 | 
			
		||||
 | 
			
		||||
      // Update sensors.
 | 
			
		||||
      if (this->power_sensor_ != nullptr) {
 | 
			
		||||
        this->power_sensor_->publish_state(power);
 | 
			
		||||
      }
 | 
			
		||||
      if (this->voltage_sensor_ != nullptr) {
 | 
			
		||||
        this->voltage_sensor_->publish_state(voltage);
 | 
			
		||||
      }
 | 
			
		||||
      if (this->current_sensor_ != nullptr) {
 | 
			
		||||
        this->current_sensor_->publish_state(current);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case SHELLY_DIMMER_PROTO_CMD_VERSION: {
 | 
			
		||||
      if (payload_len < 2) {
 | 
			
		||||
        return false;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      this->version_minor_ = payload[0];
 | 
			
		||||
      this->version_major_ = payload[1];
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    case SHELLY_DIMMER_PROTO_CMD_SWITCH:
 | 
			
		||||
    case SHELLY_DIMMER_PROTO_CMD_SETTINGS: {
 | 
			
		||||
      return !(payload_len < 1 || payload[0] != 0x01);
 | 
			
		||||
    }
 | 
			
		||||
    default: {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::reset_(bool boot0) {
 | 
			
		||||
  ESP_LOGD(TAG, "Reset STM32, boot0=%d", boot0);
 | 
			
		||||
 | 
			
		||||
  this->pin_boot0_->digital_write(boot0);
 | 
			
		||||
  this->pin_nrst_->digital_write(false);
 | 
			
		||||
 | 
			
		||||
  // Wait 50ms for the STM32 to reset.
 | 
			
		||||
  delay(50);  // NOLINT
 | 
			
		||||
 | 
			
		||||
  // Clear receive buffer.
 | 
			
		||||
  while (this->available()) {
 | 
			
		||||
    this->read();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  this->pin_nrst_->digital_write(true);
 | 
			
		||||
  // Wait 50ms for the STM32 to boot.
 | 
			
		||||
  delay(50);  // NOLINT
 | 
			
		||||
 | 
			
		||||
  ESP_LOGD(TAG, "Reset STM32 done");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::reset_normal_boot_() {
 | 
			
		||||
  // set NONE parity in normal mode
 | 
			
		||||
 | 
			
		||||
#ifndef USE_ESP_IDF  // workaround for reconfiguring the uart
 | 
			
		||||
  Serial.end();
 | 
			
		||||
  Serial.begin(115200, SERIAL_8N1);
 | 
			
		||||
  Serial.flush();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->flush();
 | 
			
		||||
  this->reset_(false);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void ShellyDimmer::reset_dfu_boot_() {
 | 
			
		||||
  // set EVEN parity in bootloader mode
 | 
			
		||||
 | 
			
		||||
#ifndef USE_ESP_IDF  // workaround for reconfiguring the uart
 | 
			
		||||
  Serial.end();
 | 
			
		||||
  Serial.begin(115200, SERIAL_8E1);
 | 
			
		||||
  Serial.flush();
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
  this->flush();
 | 
			
		||||
  this->reset_(true);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
}  // namespace shelly_dimmer
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										117
									
								
								esphome/components/shelly_dimmer/shelly_dimmer.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								esphome/components/shelly_dimmer/shelly_dimmer.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,117 @@
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/component.h"
 | 
			
		||||
#include "esphome/core/log.h"
 | 
			
		||||
#include "esphome/components/light/light_output.h"
 | 
			
		||||
#include "esphome/components/sensor/sensor.h"
 | 
			
		||||
#include "esphome/components/uart/uart.h"
 | 
			
		||||
 | 
			
		||||
#include <array>
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace shelly_dimmer {
 | 
			
		||||
 | 
			
		||||
class ShellyDimmer : public PollingComponent, public light::LightOutput, public uart::UARTDevice {
 | 
			
		||||
 private:
 | 
			
		||||
  static constexpr uint16_t SHELLY_DIMMER_BUFFER_SIZE = 256;
 | 
			
		||||
 | 
			
		||||
 public:
 | 
			
		||||
  float get_setup_priority() const override { return setup_priority::LATE; }
 | 
			
		||||
 | 
			
		||||
  void setup() override;
 | 
			
		||||
  void update() override;
 | 
			
		||||
  void dump_config() override;
 | 
			
		||||
 | 
			
		||||
  light::LightTraits get_traits() override {
 | 
			
		||||
    auto traits = light::LightTraits();
 | 
			
		||||
    traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
 | 
			
		||||
    return traits;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setup_state(light::LightState *state) override { this->state_ = state; }
 | 
			
		||||
  void write_state(light::LightState *state) override;
 | 
			
		||||
 | 
			
		||||
  void set_nrst_pin(GPIOPin *nrst_pin) { this->pin_nrst_ = nrst_pin; }
 | 
			
		||||
  void set_boot0_pin(GPIOPin *boot0_pin) { this->pin_boot0_ = boot0_pin; }
 | 
			
		||||
 | 
			
		||||
  void set_leading_edge(bool leading_edge) { this->leading_edge_ = leading_edge; }
 | 
			
		||||
  void set_warmup_brightness(uint16_t warmup_brightness) { this->warmup_brightness_ = warmup_brightness; }
 | 
			
		||||
  void set_warmup_time(uint16_t warmup_time) { this->warmup_time_ = warmup_time; }
 | 
			
		||||
  void set_fade_rate(uint16_t fade_rate) { this->fade_rate_ = fade_rate; }
 | 
			
		||||
  void set_min_brightness(uint16_t min_brightness) { this->min_brightness_ = min_brightness; }
 | 
			
		||||
  void set_max_brightness(uint16_t max_brightness) { this->max_brightness_ = max_brightness; }
 | 
			
		||||
 | 
			
		||||
  void set_power_sensor(sensor::Sensor *power_sensor) { this->power_sensor_ = power_sensor; }
 | 
			
		||||
  void set_voltage_sensor(sensor::Sensor *voltage_sensor) { this->voltage_sensor_ = voltage_sensor; }
 | 
			
		||||
  void set_current_sensor(sensor::Sensor *current_sensor) { this->current_sensor_ = current_sensor; }
 | 
			
		||||
 | 
			
		||||
 protected:
 | 
			
		||||
  GPIOPin *pin_nrst_;
 | 
			
		||||
  GPIOPin *pin_boot0_;
 | 
			
		||||
 | 
			
		||||
  // Frame parser state.
 | 
			
		||||
  uint8_t seq_{0};
 | 
			
		||||
  std::array<uint8_t, SHELLY_DIMMER_BUFFER_SIZE> buffer_;
 | 
			
		||||
  uint8_t buffer_pos_{0};
 | 
			
		||||
 | 
			
		||||
  // Firmware version.
 | 
			
		||||
  uint8_t version_major_;
 | 
			
		||||
  uint8_t version_minor_;
 | 
			
		||||
 | 
			
		||||
  // Configuration.
 | 
			
		||||
  bool leading_edge_{false};
 | 
			
		||||
  uint16_t warmup_brightness_{100};
 | 
			
		||||
  uint16_t warmup_time_{20};
 | 
			
		||||
  uint16_t fade_rate_{0};
 | 
			
		||||
  uint16_t min_brightness_{0};
 | 
			
		||||
  uint16_t max_brightness_{1000};
 | 
			
		||||
 | 
			
		||||
  light::LightState *state_{nullptr};
 | 
			
		||||
  sensor::Sensor *power_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *voltage_sensor_{nullptr};
 | 
			
		||||
  sensor::Sensor *current_sensor_{nullptr};
 | 
			
		||||
 | 
			
		||||
  bool ready_{false};
 | 
			
		||||
  uint16_t brightness_;
 | 
			
		||||
 | 
			
		||||
  /// Convert relative brightness into a dimmer brightness value.
 | 
			
		||||
  uint16_t convert_brightness_(float brightness);
 | 
			
		||||
 | 
			
		||||
  /// Sends the given brightness value.
 | 
			
		||||
  void send_brightness_(uint16_t brightness);
 | 
			
		||||
 | 
			
		||||
  /// Sends dimmer configuration.
 | 
			
		||||
  void send_settings_();
 | 
			
		||||
 | 
			
		||||
  /// Performs a firmware upgrade.
 | 
			
		||||
  bool upgrade_firmware_();
 | 
			
		||||
 | 
			
		||||
  /// Sends a command and waits for an acknowledgement.
 | 
			
		||||
  bool send_command_(uint8_t cmd, const uint8_t *payload, uint8_t len);
 | 
			
		||||
 | 
			
		||||
  /// Frames a given command payload.
 | 
			
		||||
  size_t frame_command_(uint8_t *data, uint8_t cmd, const uint8_t *payload, size_t len);
 | 
			
		||||
 | 
			
		||||
  /// Handles a single byte as part of a protocol frame.
 | 
			
		||||
  ///
 | 
			
		||||
  /// Returns -1 on failure, 0 when finished and 1 when more bytes needed.
 | 
			
		||||
  int handle_byte_(uint8_t c);
 | 
			
		||||
 | 
			
		||||
  /// Reads a response frame.
 | 
			
		||||
  bool read_frame_();
 | 
			
		||||
 | 
			
		||||
  /// Handles a complete frame.
 | 
			
		||||
  bool handle_frame_();
 | 
			
		||||
 | 
			
		||||
  /// Reset STM32 with the BOOT0 pin set to the given value.
 | 
			
		||||
  void reset_(bool boot0);
 | 
			
		||||
 | 
			
		||||
  /// Reset STM32 to boot the regular firmware.
 | 
			
		||||
  void reset_normal_boot_();
 | 
			
		||||
 | 
			
		||||
  /// Reset STM32 to boot into DFU mode to enable firmware upgrades.
 | 
			
		||||
  void reset_dfu_boot_();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
}  // namespace shelly_dimmer
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
							
								
								
									
										1061
									
								
								esphome/components/shelly_dimmer/stm32flash.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1061
									
								
								esphome/components/shelly_dimmer/stm32flash.cpp
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										129
									
								
								esphome/components/shelly_dimmer/stm32flash.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								esphome/components/shelly_dimmer/stm32flash.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
/*
 | 
			
		||||
  stm32flash - Open Source ST STM32 flash program for Arduino
 | 
			
		||||
  Copyright (C) 2010 Geoffrey McRae <geoff@spacevs.com>
 | 
			
		||||
 | 
			
		||||
  This program is free software; you can redistribute it and/or
 | 
			
		||||
  modify it under the terms of the GNU General Public License
 | 
			
		||||
  as published by the Free Software Foundation; either version 2
 | 
			
		||||
  of the License, or (at your option) any later version.
 | 
			
		||||
 | 
			
		||||
  This program is distributed in the hope that it will be useful,
 | 
			
		||||
  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
			
		||||
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | 
			
		||||
  GNU General Public License for more details.
 | 
			
		||||
 | 
			
		||||
  You should have received a copy of the GNU General Public License
 | 
			
		||||
  along with this program; if not, write to the Free Software
 | 
			
		||||
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
#pragma once
 | 
			
		||||
 | 
			
		||||
#include "esphome/core/defines.h"
 | 
			
		||||
#ifdef USE_SHD_FIRMWARE_DATA
 | 
			
		||||
 | 
			
		||||
#include <cstdint>
 | 
			
		||||
#include "esphome/components/uart/uart.h"
 | 
			
		||||
 | 
			
		||||
namespace esphome {
 | 
			
		||||
namespace shelly_dimmer {
 | 
			
		||||
 | 
			
		||||
/* flags */
 | 
			
		||||
constexpr auto STREAM_OPT_BYTE = (1 << 0);      /* byte (not frame) oriented */
 | 
			
		||||
constexpr auto STREAM_OPT_GVR_ETX = (1 << 1);   /* cmd GVR returns protection status */
 | 
			
		||||
constexpr auto STREAM_OPT_CMD_INIT = (1 << 2);  /* use INIT cmd to autodetect speed */
 | 
			
		||||
constexpr auto STREAM_OPT_RETRY = (1 << 3);     /* allowed read() retry after timeout */
 | 
			
		||||
constexpr auto STREAM_OPT_I2C = (1 << 4);       /* i2c */
 | 
			
		||||
constexpr auto STREAM_OPT_STRETCH_W = (1 << 5); /* warning for no-stretching commands */
 | 
			
		||||
 | 
			
		||||
constexpr auto STREAM_SERIAL = (STREAM_OPT_BYTE | STREAM_OPT_GVR_ETX | STREAM_OPT_CMD_INIT | STREAM_OPT_RETRY);
 | 
			
		||||
constexpr auto STREAM_I2C = (STREAM_OPT_I2C | STREAM_OPT_STRETCH_W);
 | 
			
		||||
 | 
			
		||||
constexpr auto STM32_MAX_RX_FRAME = 256;           /* cmd read memory */
 | 
			
		||||
constexpr auto STM32_MAX_TX_FRAME = (1 + 256 + 1); /* cmd write memory */
 | 
			
		||||
 | 
			
		||||
constexpr auto STM32_MAX_PAGES = 0x0000ffff;
 | 
			
		||||
constexpr auto STM32_MASS_ERASE = 0x00100000; /* > 2 x max_pages */
 | 
			
		||||
 | 
			
		||||
using stm32_err_t = enum Stm32Err {
 | 
			
		||||
  STM32_ERR_OK = 0,
 | 
			
		||||
  STM32_ERR_UNKNOWN, /* Generic error */
 | 
			
		||||
  STM32_ERR_NACK,
 | 
			
		||||
  STM32_ERR_NO_CMD, /* Command not available in bootloader */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using flags_t = enum Flags {
 | 
			
		||||
  F_NO_ME = 1 << 0, /* Mass-Erase not supported */
 | 
			
		||||
  F_OBLL = 1 << 1,  /* OBL_LAUNCH required */
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using stm32_cmd_t = struct Stm32Cmd {
 | 
			
		||||
  uint8_t get;
 | 
			
		||||
  uint8_t gvr;
 | 
			
		||||
  uint8_t gid;
 | 
			
		||||
  uint8_t rm;
 | 
			
		||||
  uint8_t go;
 | 
			
		||||
  uint8_t wm;
 | 
			
		||||
  uint8_t er; /* this may be extended erase */
 | 
			
		||||
  uint8_t wp;
 | 
			
		||||
  uint8_t uw;
 | 
			
		||||
  uint8_t rp;
 | 
			
		||||
  uint8_t ur;
 | 
			
		||||
  uint8_t crc;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using stm32_dev_t = struct Stm32Dev {  // NOLINT
 | 
			
		||||
  const uint16_t id;
 | 
			
		||||
  const char *name;
 | 
			
		||||
  const uint32_t ram_start, ram_end;
 | 
			
		||||
  const uint32_t fl_start, fl_end;
 | 
			
		||||
  const uint16_t fl_pps;  // pages per sector
 | 
			
		||||
  const uint32_t *fl_ps;  // page size
 | 
			
		||||
  const uint32_t opt_start, opt_end;
 | 
			
		||||
  const uint32_t mem_start, mem_end;
 | 
			
		||||
  const uint32_t flags;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
using stm32_t = struct Stm32 {
 | 
			
		||||
  uart::UARTDevice *stream;
 | 
			
		||||
  uint8_t flags;
 | 
			
		||||
  struct VarlenCmd *cmd_get_reply;
 | 
			
		||||
  uint8_t bl_version;
 | 
			
		||||
  uint8_t version;
 | 
			
		||||
  uint8_t option1, option2;
 | 
			
		||||
  uint16_t pid;
 | 
			
		||||
  stm32_cmd_t *cmd;
 | 
			
		||||
  const stm32_dev_t *dev;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Specify the length of reply for command GET
 | 
			
		||||
 * This is helpful for frame-oriented protocols, e.g. i2c, to avoid time
 | 
			
		||||
 * consuming try-fail-timeout-retry operation.
 | 
			
		||||
 * On byte-oriented protocols, i.e. UART, this information would be skipped
 | 
			
		||||
 * after read the first byte, so not needed.
 | 
			
		||||
 */
 | 
			
		||||
struct VarlenCmd {
 | 
			
		||||
  uint8_t version;
 | 
			
		||||
  uint8_t length;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
stm32_t *stm32_init(uart::UARTDevice *stream, uint8_t flags, char init);
 | 
			
		||||
void stm32_close(stm32_t *stm);
 | 
			
		||||
stm32_err_t stm32_read_memory(const stm32_t *stm, uint32_t address, uint8_t *data, unsigned int len);
 | 
			
		||||
stm32_err_t stm32_write_memory(const stm32_t *stm, uint32_t address, const uint8_t *data, unsigned int len);
 | 
			
		||||
stm32_err_t stm32_wunprot_memory(const stm32_t *stm);
 | 
			
		||||
stm32_err_t stm32_wprot_memory(const stm32_t *stm);
 | 
			
		||||
stm32_err_t stm32_erase_memory(const stm32_t *stm, uint32_t spage, uint32_t pages);
 | 
			
		||||
stm32_err_t stm32_go(const stm32_t *stm, uint32_t address);
 | 
			
		||||
stm32_err_t stm32_reset_device(const stm32_t *stm);
 | 
			
		||||
stm32_err_t stm32_readprot_memory(const stm32_t *stm);
 | 
			
		||||
stm32_err_t stm32_runprot_memory(const stm32_t *stm);
 | 
			
		||||
stm32_err_t stm32_crc_memory(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc);
 | 
			
		||||
stm32_err_t stm32_crc_wrapper(const stm32_t *stm, uint32_t address, uint32_t length, uint32_t *crc);
 | 
			
		||||
uint32_t stm32_sw_crc(uint32_t crc, uint8_t *buf, unsigned int len);
 | 
			
		||||
 | 
			
		||||
}  // namespace shelly_dimmer
 | 
			
		||||
}  // namespace esphome
 | 
			
		||||
 | 
			
		||||
#endif  // USE_SHD_FIRMWARE_DATA
 | 
			
		||||
@@ -126,6 +126,13 @@ WIFI_NETWORK_AP = WIFI_NETWORK_BASE.extend(
 | 
			
		||||
    }
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def wifi_network_ap(value):
 | 
			
		||||
    if value is None:
 | 
			
		||||
        value = {}
 | 
			
		||||
    return WIFI_NETWORK_AP(value)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
WIFI_NETWORK_STA = WIFI_NETWORK_BASE.extend(
 | 
			
		||||
    {
 | 
			
		||||
        cv.Optional(CONF_BSSID): cv.mac_address,
 | 
			
		||||
@@ -252,7 +259,7 @@ CONFIG_SCHEMA = cv.All(
 | 
			
		||||
            cv.Optional(CONF_PASSWORD): validate_password,
 | 
			
		||||
            cv.Optional(CONF_MANUAL_IP): STA_MANUAL_IP_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_EAP): EAP_AUTH_SCHEMA,
 | 
			
		||||
            cv.Optional(CONF_AP): WIFI_NETWORK_AP,
 | 
			
		||||
            cv.Optional(CONF_AP): wifi_network_ap,
 | 
			
		||||
            cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name,
 | 
			
		||||
            cv.Optional(
 | 
			
		||||
                CONF_REBOOT_TIMEOUT, default="15min"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
"""Constants used by esphome."""
 | 
			
		||||
 | 
			
		||||
__version__ = "2022.4.0b1"
 | 
			
		||||
__version__ = "2022.4.0b4"
 | 
			
		||||
 | 
			
		||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -93,3 +93,9 @@
 | 
			
		||||
//#define USE_BSEC  // Requires a library with proprietary license.
 | 
			
		||||
 | 
			
		||||
#define USE_DASHBOARD_IMPORT
 | 
			
		||||
 | 
			
		||||
// Dummy firmware payload for shelly_dimmer
 | 
			
		||||
#define USE_SHD_FIRMWARE_MAJOR_VERSION 56
 | 
			
		||||
#define USE_SHD_FIRMWARE_MINOR_VERSION 5
 | 
			
		||||
#define USE_SHD_FIRMWARE_DATA \
 | 
			
		||||
  {}
 | 
			
		||||
 
 | 
			
		||||
@@ -1751,6 +1751,18 @@ light:
 | 
			
		||||
        to: 25
 | 
			
		||||
      - single_light_id: ${roomname}_lights
 | 
			
		||||
 | 
			
		||||
  - platform: shelly_dimmer
 | 
			
		||||
    name: "Shelly Dimmer Light"
 | 
			
		||||
    power:
 | 
			
		||||
      name: "Shelly Dimmer Power"
 | 
			
		||||
    voltage:
 | 
			
		||||
      name: "Shelly Dimmer Voltage"
 | 
			
		||||
    current:
 | 
			
		||||
      name: "Shelly Dimmer Current"
 | 
			
		||||
    max_brightness: 500
 | 
			
		||||
    firmware: "51.6"
 | 
			
		||||
    uart_id: uart0
 | 
			
		||||
 | 
			
		||||
remote_transmitter:
 | 
			
		||||
  - pin: 32
 | 
			
		||||
    carrier_duty_percent: 100%
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user