7#ifdef USE_SHD_FIRMWARE_DATA
12#include <HardwareSerial.h>
22constexpr char TAG[] =
"shelly_dimmer";
24constexpr uint8_t SHELLY_DIMMER_ACK_TIMEOUT = 200;
25constexpr uint8_t SHELLY_DIMMER_MAX_RETRIES = 3;
26constexpr uint16_t SHELLY_DIMMER_MAX_BRIGHTNESS = 1000;
29constexpr uint8_t SHELLY_DIMMER_PROTO_START_BYTE = 0x01;
30constexpr uint8_t SHELLY_DIMMER_PROTO_END_BYTE = 0x04;
33constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH = 0x01;
34constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_POLL = 0x10;
35constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_VERSION = 0x11;
36constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS = 0x20;
39constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE = 2;
40constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE = 10;
41constexpr uint8_t SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE = 4 + 72 + 3;
44#ifdef USE_SHD_FIRMWARE_DATA
45constexpr uint8_t STM_FIRMWARE[]
PROGMEM = USE_SHD_FIRMWARE_DATA;
46constexpr uint32_t STM_FIRMWARE_SIZE_IN_BYTES =
sizeof(STM_FIRMWARE);
50constexpr float POWER_SCALING_FACTOR = 880373;
51constexpr float VOLTAGE_SCALING_FACTOR = 347800;
52constexpr float CURRENT_SCALING_FACTOR = 1448;
55template<
typename T,
size_t N>
constexpr size_t size(
const T (&)[N])
noexcept {
return N; }
63 return std::accumulate<decltype(buf), uint16_t>(buf, buf +
len, 0);
74 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION,
nullptr, 0);
75 ESP_LOGI(TAG,
"STM32 current firmware version: %d.%d, desired version: %d.%d", this->
version_major_,
76 this->
version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION);
79#ifdef USE_SHD_FIRMWARE_DATA
81 ESP_LOGW(TAG,
"Failed to upgrade firmware");
87 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION,
nullptr, 0);
89 ESP_LOGE(TAG,
"STM32 firmware upgrade already performed, but version is still incorrect");
94 ESP_LOGW(TAG,
"Firmware version mismatch, put 'update: true' in the yaml to flash an update.");
107 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_POLL,
nullptr, 0);
117 " Leading Edge: %s\n"
118 " Warmup Brightness: %d\n"
119 " Minimum Brightness: %d\n"
120 " Maximum Brightness: %d\n"
121 " STM32 current firmware version: %d.%d\n"
122 " STM32 required firmware version: %d.%d",
125 USE_SHD_FIRMWARE_MINOR_VERSION);
128 LOG_UPDATE_INTERVAL(
this);
132 ESP_LOGE(TAG,
" Firmware version mismatch, put 'update: true' in the yaml to flash an update.");
142 state->current_values_as_brightness(&brightness);
146 ESP_LOGV(TAG,
"Not sending unchanged value");
149 ESP_LOGD(TAG,
"Brightness update: %d (raw: %f)", brightness_int, brightness);
153#ifdef USE_SHD_FIRMWARE_DATA
155 ESP_LOGW(TAG,
"Starting STM32 firmware upgrade");
162 ESP_LOGW(TAG,
"Failed to initialize STM32");
168 ESP_LOGW(TAG,
"Failed to erase STM32 flash memory");
172 static constexpr uint32_t BUFFER_SIZE = 256;
176 uint8_t buffer[BUFFER_SIZE];
177 const uint8_t *p = STM_FIRMWARE;
179 uint32_t addr = stm32->dev->fl_start;
180 const uint32_t end = addr + STM_FIRMWARE_SIZE_IN_BYTES;
182 while (addr <
end && offset < STM_FIRMWARE_SIZE_IN_BYTES) {
183 const uint32_t left_of_buffer = std::min(
end - addr, BUFFER_SIZE);
184 const uint32_t len = std::min(left_of_buffer, STM_FIRMWARE_SIZE_IN_BYTES - offset);
190 std::memcpy(buffer, p,
len);
194 ESP_LOGW(TAG,
"Failed to write to STM32 flash memory");
202 ESP_LOGI(TAG,
"STM32 firmware upgrade successful");
210 if (brightness == 0.0) {
218 const uint8_t payload[] = {
220 static_cast<uint8_t
>(brightness & 0xff),
221 static_cast<uint8_t
>(brightness >> 8),
223 static_assert(
size(payload) == SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE,
"Invalid payload size");
225 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_SWITCH, payload, SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE);
231 const uint16_t fade_rate = std::min(uint16_t{100}, this->
fade_rate_);
233 float brightness = 0.0;
234 if (this->
state_ !=
nullptr) {
238 ESP_LOGD(TAG,
"Brightness update: %d (raw: %f)", brightness_int, brightness);
240 const uint8_t payload[] = {
242 static_cast<uint8_t
>(brightness_int & 0xff),
243 static_cast<uint8_t
>(brightness_int >> 8),
248 static_cast<uint8_t
>(fade_rate & 0xff),
249 static_cast<uint8_t
>(fade_rate >> 8),
257 static_assert(
size(payload) == SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE,
"Invalid payload size");
259 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_SETTINGS, payload, SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE);
267 char hex_buf[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE * 2 + 1];
268 ESP_LOGD(TAG,
"Sending command: 0x%02x (%d bytes) payload 0x%s", cmd,
len,
272 uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE];
276 int retries = SHELLY_DIMMER_MAX_RETRIES;
281 ESP_LOGD(TAG,
"Command sent, waiting for reply");
283 while (
millis() - tx_time < SHELLY_DIMMER_ACK_TIMEOUT) {
289 ESP_LOGW(TAG,
"Timeout while waiting for reply");
291 ESP_LOGW(TAG,
"Failed to send command");
299 data[0] = SHELLY_DIMMER_PROTO_START_BYTE;
300 data[1] = ++this->
seq_;
305 if (payload !=
nullptr) {
306 std::memcpy(data + 4, payload,
len);
312 data[
pos++] =
static_cast<uint8_t
>(csum >> 8);
313 data[
pos++] =
static_cast<uint8_t
>(csum & 0xff);
314 data[
pos++] = SHELLY_DIMMER_PROTO_END_BYTE;
323 return c == SHELLY_DIMMER_PROTO_START_BYTE ? 1 : -1;
324 }
else if (
pos < 4) {
330 const uint8_t payload_len = this->
buffer_[3];
331 if ((4 + payload_len + 3) > SHELLY_DIMMER_BUFFER_SIZE) {
335 if (
pos < 4 + payload_len + 1) {
340 if (
pos == 4 + payload_len + 1) {
342 const uint16_t csum = (this->
buffer_[pos - 1] << 8 | c);
344 if (csum != csum_verify) {
350 if (
pos == 4 + payload_len + 2) {
352 return c == SHELLY_DIMMER_PROTO_END_BYTE ? 0 : -1;
359 const uint8_t c = this->
read();
362 ESP_LOGV(TAG,
"Read byte: 0x%02x (pos %d)", c, this->
buffer_pos_);
388 const uint8_t cmd = this->
buffer_[2];
389 const uint8_t payload_len = this->
buffer_[3];
391 ESP_LOGD(TAG,
"Got frame: 0x%02x", cmd);
399 const uint8_t *payload = &this->
buffer_[4];
403 case SHELLY_DIMMER_PROTO_CMD_POLL: {
404 if (payload_len < 17) {
408 const uint8_t hw_version = payload[0];
410 const uint16_t brightness =
encode_uint16(payload[3], payload[2]);
418 const uint16_t fade_rate = payload[16];
422 power = POWER_SCALING_FACTOR /
static_cast<float>(power_raw);
426 if (voltage_raw > 0) {
427 voltage = VOLTAGE_SCALING_FACTOR /
static_cast<float>(voltage_raw);
431 if (current_raw > 0) {
432 current = CURRENT_SCALING_FACTOR /
static_cast<float>(current_raw);
443 hw_version, brightness, fade_rate, power, voltage, current);
458 case SHELLY_DIMMER_PROTO_CMD_VERSION: {
459 if (payload_len < 2) {
467 case SHELLY_DIMMER_PROTO_CMD_SWITCH:
468 case SHELLY_DIMMER_PROTO_CMD_SETTINGS: {
469 return payload_len >= 1 && payload[0] == 0x01;
478 ESP_LOGD(TAG,
"Reset STM32, boot0=%d", boot0);
495 ESP_LOGD(TAG,
"Reset STM32 done");
503 Serial.begin(115200, SERIAL_8N1);
516 Serial.begin(115200, SERIAL_8E1);
void mark_failed()
Mark this component as failed.
virtual void digital_write(bool value)=0
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
void current_values_as_brightness(float *brightness)
void publish_state(float state)
Publish a new state to the front-end.
sensor::Sensor * power_sensor_
void write_state(light::LightState *state) override
void reset_dfu_boot_()
Reset STM32 to boot into DFU mode to enable firmware upgrades.
bool upgrade_firmware_()
Performs a firmware upgrade.
sensor::Sensor * voltage_sensor_
void reset_normal_boot_()
Reset STM32 to boot the regular firmware.
bool handle_frame_()
Handles a complete frame.
sensor::Sensor * current_sensor_
void send_brightness_(uint16_t brightness)
Sends the given brightness value.
void dump_config() override
void send_settings_()
Sends dimmer configuration.
int handle_byte_(uint8_t c)
Handles a single byte as part of a protocol frame.
std::array< uint8_t, SHELLY_DIMMER_BUFFER_SIZE > buffer_
uint16_t convert_brightness_(float brightness)
Convert relative brightness into a dimmer brightness value.
light::LightState * state_
bool is_running_configured_version() const
size_t frame_command_(uint8_t *data, uint8_t cmd, const uint8_t *payload, size_t len)
Frames a given command payload.
void reset_(bool boot0)
Reset STM32 with the BOOT0 pin set to the given value.
bool read_frame_()
Reads a response frame.
uint16_t warmup_brightness_
bool send_command_(uint8_t cmd, const uint8_t *payload, uint8_t len)
Sends a command and waits for an acknowledgement.
void write_array(const uint8_t *data, size_t len)
uint16_t shelly_dimmer_checksum(const uint8_t *buf, int len)
Computes a crappy checksum as defined by the Shelly Dimmer protocol.
stm32_unique_ptr stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char init)
constexpr auto STREAM_SERIAL
stm32_err_t stm32_write_memory(const stm32_unique_ptr &stm, uint32_t address, const uint8_t *data, const unsigned int len)
constexpr auto STM32_MASS_ERASE
stm32_err_t stm32_erase_memory(const stm32_unique_ptr &stm, uint32_t spage, uint32_t pages)
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
void HOT delay(uint32_t ms)
uint32_t IRAM_ATTR HOT millis()
char * format_hex_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length)
Format byte array as lowercase hex to buffer (base implementation).
T remap(U value, U min, U max, T min_out, T max_out)
Remap value from the range (min, max) to (min_out, max_out).
const uint8_t ESPHOME_WEBSERVER_INDEX_HTML[] PROGMEM