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; }
60namespace shelly_dimmer {
64 return std::accumulate<decltype(buf), uint16_t>(buf, buf +
len, 0);
75 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION,
nullptr, 0);
76 ESP_LOGI(TAG,
"STM32 current firmware version: %d.%d, desired version: %d.%d", this->
version_major_,
77 this->
version_minor_, USE_SHD_FIRMWARE_MAJOR_VERSION, USE_SHD_FIRMWARE_MINOR_VERSION);
80#ifdef USE_SHD_FIRMWARE_DATA
82 ESP_LOGW(TAG,
"Failed to upgrade firmware");
88 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_VERSION,
nullptr, 0);
90 ESP_LOGE(TAG,
"STM32 firmware upgrade already performed, but version is still incorrect");
95 ESP_LOGW(TAG,
"Firmware version mismatch, put 'update: true' in the yaml to flash an update.");
108 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_POLL,
nullptr, 0);
118 " Leading Edge: %s\n"
119 " Warmup Brightness: %d\n"
120 " Minimum Brightness: %d\n"
121 " Maximum Brightness: %d\n"
122 " STM32 current firmware version: %d.%d\n"
123 " STM32 required firmware version: %d.%d",
126 USE_SHD_FIRMWARE_MINOR_VERSION);
129 LOG_UPDATE_INTERVAL(
this);
133 ESP_LOGE(TAG,
" Firmware version mismatch, put 'update: true' in the yaml to flash an update.");
143 state->current_values_as_brightness(&brightness);
147 ESP_LOGV(TAG,
"Not sending unchanged value");
150 ESP_LOGD(TAG,
"Brightness update: %d (raw: %f)", brightness_int, brightness);
154#ifdef USE_SHD_FIRMWARE_DATA
156 ESP_LOGW(TAG,
"Starting STM32 firmware upgrade");
163 ESP_LOGW(TAG,
"Failed to initialize STM32");
169 ESP_LOGW(TAG,
"Failed to erase STM32 flash memory");
173 static constexpr uint32_t BUFFER_SIZE = 256;
177 uint8_t buffer[BUFFER_SIZE];
178 const uint8_t *p = STM_FIRMWARE;
180 uint32_t addr = stm32->dev->fl_start;
181 const uint32_t
end = addr + STM_FIRMWARE_SIZE_IN_BYTES;
183 while (addr <
end && offset < STM_FIRMWARE_SIZE_IN_BYTES) {
184 const uint32_t left_of_buffer = std::min(
end - addr, BUFFER_SIZE);
185 const uint32_t
len = std::min(left_of_buffer, STM_FIRMWARE_SIZE_IN_BYTES - offset);
191 std::memcpy(buffer, p, BUFFER_SIZE);
195 ESP_LOGW(TAG,
"Failed to write to STM32 flash memory");
203 ESP_LOGI(TAG,
"STM32 firmware upgrade successful");
211 if (brightness == 0.0) {
219 const uint8_t payload[] = {
221 static_cast<uint8_t
>(brightness & 0xff),
222 static_cast<uint8_t
>(brightness >> 8),
224 static_assert(
size(payload) == SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE,
"Invalid payload size");
226 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_SWITCH, payload, SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE);
232 const uint16_t fade_rate = std::min(uint16_t{100}, this->
fade_rate_);
234 float brightness = 0.0;
235 if (this->
state_ !=
nullptr) {
239 ESP_LOGD(TAG,
"Brightness update: %d (raw: %f)", brightness_int, brightness);
241 const uint8_t payload[] = {
243 static_cast<uint8_t
>(brightness_int & 0xff),
244 static_cast<uint8_t
>(brightness_int >> 8),
249 static_cast<uint8_t
>(fade_rate & 0xff),
250 static_cast<uint8_t
>(fade_rate >> 8),
258 static_assert(
size(payload) == SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE,
"Invalid payload size");
260 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_SETTINGS, payload, SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE);
268 char hex_buf[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE * 2 + 1];
269 ESP_LOGD(TAG,
"Sending command: 0x%02x (%d bytes) payload 0x%s", cmd,
len,
273 uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE];
277 int retries = SHELLY_DIMMER_MAX_RETRIES;
282 ESP_LOGD(TAG,
"Command sent, waiting for reply");
283 const uint32_t tx_time =
millis();
284 while (
millis() - tx_time < SHELLY_DIMMER_ACK_TIMEOUT) {
290 ESP_LOGW(TAG,
"Timeout while waiting for reply");
292 ESP_LOGW(TAG,
"Failed to send command");
300 data[0] = SHELLY_DIMMER_PROTO_START_BYTE;
301 data[1] = ++this->
seq_;
306 if (payload !=
nullptr) {
307 std::memcpy(data + 4, payload,
len);
313 data[
pos++] =
static_cast<uint8_t
>(csum >> 8);
314 data[
pos++] =
static_cast<uint8_t
>(csum & 0xff);
315 data[
pos++] = SHELLY_DIMMER_PROTO_END_BYTE;
324 return c == SHELLY_DIMMER_PROTO_START_BYTE ? 1 : -1;
325 }
else if (
pos < 4) {
331 const uint8_t payload_len = this->
buffer_[3];
332 if ((4 + payload_len + 3) > SHELLY_DIMMER_BUFFER_SIZE) {
336 if (
pos < 4 + payload_len + 1) {
341 if (
pos == 4 + payload_len + 1) {
343 const uint16_t csum = (this->
buffer_[pos - 1] << 8 | c);
345 if (csum != csum_verify) {
351 if (
pos == 4 + payload_len + 2) {
353 return c == SHELLY_DIMMER_PROTO_END_BYTE ? 0 : -1;
360 const uint8_t c = this->
read();
363 ESP_LOGV(TAG,
"Read byte: 0x%02x (pos %d)", c, this->
buffer_pos_);
389 const uint8_t cmd = this->
buffer_[2];
390 const uint8_t payload_len = this->
buffer_[3];
392 ESP_LOGD(TAG,
"Got frame: 0x%02x", cmd);
400 const uint8_t *payload = &this->
buffer_[4];
404 case SHELLY_DIMMER_PROTO_CMD_POLL: {
405 if (payload_len < 16) {
409 const uint8_t hw_version = payload[0];
411 const uint16_t brightness =
encode_uint16(payload[3], payload[2]);
413 const uint32_t power_raw =
encode_uint32(payload[7], payload[6], payload[5], payload[4]);
415 const uint32_t voltage_raw =
encode_uint32(payload[11], payload[10], payload[9], payload[8]);
417 const uint32_t current_raw =
encode_uint32(payload[15], payload[14], payload[13], payload[12]);
419 const uint16_t fade_rate = payload[16];
423 power = POWER_SCALING_FACTOR /
static_cast<float>(power_raw);
427 if (voltage_raw > 0) {
428 voltage = VOLTAGE_SCALING_FACTOR /
static_cast<float>(voltage_raw);
432 if (current_raw > 0) {
433 current = CURRENT_SCALING_FACTOR /
static_cast<float>(current_raw);
444 hw_version, brightness, fade_rate, power, voltage, current);
459 case SHELLY_DIMMER_PROTO_CMD_VERSION: {
460 if (payload_len < 2) {
468 case SHELLY_DIMMER_PROTO_CMD_SWITCH:
469 case SHELLY_DIMMER_PROTO_CMD_SETTINGS: {
470 return payload_len >= 1 && payload[0] == 0x01;
479 ESP_LOGD(TAG,
"Reset STM32, boot0=%d", boot0);
496 ESP_LOGD(TAG,
"Reset STM32 done");
504 Serial.begin(115200, SERIAL_8N1);
517 Serial.begin(115200, SERIAL_8E1);
virtual 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)
Providing packet encoding functions for exchanging data with a remote host.
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 IRAM_ATTR 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