7 #ifdef USE_SHD_FIRMWARE_DATA 12 #include <HardwareSerial.h> 22 constexpr
char TAG[] =
"shelly_dimmer";
24 constexpr uint8_t SHELLY_DIMMER_ACK_TIMEOUT = 200;
25 constexpr uint8_t SHELLY_DIMMER_MAX_RETRIES = 3;
26 constexpr uint16_t SHELLY_DIMMER_MAX_BRIGHTNESS = 1000;
29 constexpr uint8_t SHELLY_DIMMER_PROTO_START_BYTE = 0x01;
30 constexpr uint8_t SHELLY_DIMMER_PROTO_END_BYTE = 0x04;
33 constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH = 0x01;
34 constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_POLL = 0x10;
35 constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_VERSION = 0x11;
36 constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS = 0x20;
39 constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE = 2;
40 constexpr uint8_t SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE = 10;
41 constexpr uint8_t SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE = 4 + 72 + 3;
44 #ifdef USE_SHD_FIRMWARE_DATA 45 constexpr uint8_t STM_FIRMWARE[]
PROGMEM = USE_SHD_FIRMWARE_DATA;
46 constexpr uint32_t STM_FIRMWARE_SIZE_IN_BYTES =
sizeof(STM_FIRMWARE);
50 constexpr
float POWER_SCALING_FACTOR = 880373;
51 constexpr
float VOLTAGE_SCALING_FACTOR = 347800;
52 constexpr
float CURRENT_SCALING_FACTOR = 1448;
55 template<
typename T,
size_t N> constexpr
size_t size(
const T (&)[N]) noexcept {
return N; }
60 namespace 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.");
104 ESP_LOGI(TAG,
"Initializing Shelly Dimmer...");
110 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_POLL,
nullptr, 0);
118 ESP_LOGCONFIG(TAG,
"ShellyDimmer:");
122 ESP_LOGCONFIG(TAG,
" Leading Edge: %s", YESNO(this->
leading_edge_));
129 LOG_UPDATE_INTERVAL(
this);
132 ESP_LOGCONFIG(TAG,
" STM32 required firmware version: %d.%d", USE_SHD_FIRMWARE_MAJOR_VERSION,
133 USE_SHD_FIRMWARE_MINOR_VERSION);
137 ESP_LOGE(TAG,
" Firmware version mismatch, put 'update: true' in the yaml to flash an update.");
151 ESP_LOGV(TAG,
"Not sending unchanged value");
154 ESP_LOGD(TAG,
"Brightness update: %d (raw: %f)", brightness_int, brightness);
158 #ifdef USE_SHD_FIRMWARE_DATA 160 ESP_LOGW(TAG,
"Starting STM32 firmware upgrade");
167 ESP_LOGW(TAG,
"Failed to initialize STM32");
173 ESP_LOGW(TAG,
"Failed to erase STM32 flash memory");
177 static constexpr uint32_t BUFFER_SIZE = 256;
181 uint8_t buffer[BUFFER_SIZE];
182 const uint8_t *p = STM_FIRMWARE;
184 uint32_t addr = stm32->dev->fl_start;
185 const uint32_t
end = addr + STM_FIRMWARE_SIZE_IN_BYTES;
187 while (addr < end && offset < STM_FIRMWARE_SIZE_IN_BYTES) {
188 const uint32_t left_of_buffer = std::min(end - addr, BUFFER_SIZE);
189 const uint32_t
len = std::min(left_of_buffer, STM_FIRMWARE_SIZE_IN_BYTES - offset);
195 std::memcpy(buffer, p, BUFFER_SIZE);
199 ESP_LOGW(TAG,
"Failed to write to STM32 flash memory");
207 ESP_LOGI(TAG,
"STM32 firmware upgrade successful");
215 if (brightness == 0.0) {
223 const uint8_t payload[] = {
225 static_cast<uint8_t
>(brightness & 0xff),
226 static_cast<uint8_t>(brightness >> 8),
228 static_assert(size(payload) == SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE,
"Invalid payload size");
230 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_SWITCH, payload, SHELLY_DIMMER_PROTO_CMD_SWITCH_SIZE);
236 const uint16_t fade_rate = std::min(uint16_t{100}, this->
fade_rate_);
238 float brightness = 0.0;
239 if (this->
state_ !=
nullptr) {
243 ESP_LOGD(TAG,
"Brightness update: %d (raw: %f)", brightness_int, brightness);
245 const uint8_t payload[] = {
247 static_cast<uint8_t
>(brightness_int & 0xff),
248 static_cast<uint8_t>(brightness_int >> 8),
253 static_cast<uint8_t
>(fade_rate & 0xff),
254 static_cast<uint8_t>(fade_rate >> 8),
262 static_assert(size(payload) == SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE,
"Invalid payload size");
264 this->
send_command_(SHELLY_DIMMER_PROTO_CMD_SETTINGS, payload, SHELLY_DIMMER_PROTO_CMD_SETTINGS_SIZE);
271 ESP_LOGD(TAG,
"Sending command: 0x%02x (%d bytes) payload 0x%s", cmd, len,
format_hex(payload, len).c_str());
274 uint8_t frame[SHELLY_DIMMER_PROTO_MAX_FRAME_SIZE];
275 const size_t frame_len = this->
frame_command_(frame, cmd, payload, len);
278 int retries = SHELLY_DIMMER_MAX_RETRIES;
283 ESP_LOGD(TAG,
"Command sent, waiting for reply");
284 const uint32_t tx_time =
millis();
285 while (
millis() - tx_time < SHELLY_DIMMER_ACK_TIMEOUT) {
291 ESP_LOGW(TAG,
"Timeout while waiting for reply");
293 ESP_LOGW(TAG,
"Failed to send command");
301 data[0] = SHELLY_DIMMER_PROTO_START_BYTE;
302 data[1] = ++this->
seq_;
307 if (payload !=
nullptr) {
308 std::memcpy(data + 4, payload, len);
314 data[pos++] =
static_cast<uint8_t
>(csum >> 8);
315 data[pos++] =
static_cast<uint8_t
>(csum & 0xff);
316 data[pos++] = SHELLY_DIMMER_PROTO_END_BYTE;
325 return c == SHELLY_DIMMER_PROTO_START_BYTE ? 1 : -1;
326 }
else if (pos < 4) {
332 const uint8_t payload_len = this->
buffer_[3];
333 if ((4 + payload_len + 3) > SHELLY_DIMMER_BUFFER_SIZE) {
337 if (pos < 4 + payload_len + 1) {
342 if (pos == 4 + payload_len + 1) {
344 const uint16_t csum = (this->
buffer_[pos - 1] << 8 | c);
346 if (csum != csum_verify) {
352 if (pos == 4 + payload_len + 2) {
354 return c == SHELLY_DIMMER_PROTO_END_BYTE ? 0 : -1;
361 const uint8_t c = this->
read();
364 ESP_LOGV(TAG,
"Read byte: 0x%02x (pos %d)", c, this->
buffer_pos_);
391 const uint8_t payload_len = this->
buffer_[3];
393 ESP_LOGD(TAG,
"Got frame: 0x%02x", cmd);
397 if (seq != this->
seq_) {
401 const uint8_t *payload = &this->
buffer_[4];
405 case SHELLY_DIMMER_PROTO_CMD_POLL: {
406 if (payload_len < 16) {
410 const uint8_t hw_version = payload[0];
412 const uint16_t brightness =
encode_uint16(payload[3], payload[2]);
414 const uint32_t power_raw =
encode_uint32(payload[7], payload[6], payload[5], payload[4]);
416 const uint32_t voltage_raw =
encode_uint32(payload[11], payload[10], payload[9], payload[8]);
418 const uint32_t current_raw =
encode_uint32(payload[15], payload[14], payload[13], payload[12]);
420 const uint16_t fade_rate = payload[16];
424 power = POWER_SCALING_FACTOR /
static_cast<float>(power_raw);
428 if (voltage_raw > 0) {
429 voltage = VOLTAGE_SCALING_FACTOR /
static_cast<float>(voltage_raw);
433 if (current_raw > 0) {
434 current = CURRENT_SCALING_FACTOR /
static_cast<float>(current_raw);
437 ESP_LOGI(TAG,
"Got dimmer data:");
438 ESP_LOGI(TAG,
" HW version: %d", hw_version);
439 ESP_LOGI(TAG,
" Brightness: %d", brightness);
440 ESP_LOGI(TAG,
" Fade rate: %d", fade_rate);
441 ESP_LOGI(TAG,
" Power: %f W", power);
442 ESP_LOGI(TAG,
" Voltage: %f V", voltage);
443 ESP_LOGI(TAG,
" Current: %f A", 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");
501 #ifndef USE_ESP_IDF // workaround for reconfiguring the uart 503 Serial.begin(115200, SERIAL_8N1);
514 #ifndef USE_ESP_IDF // workaround for reconfiguring the uart 516 Serial.begin(115200, SERIAL_8E1);
527 #endif // USE_ESP8266 size_t frame_command_(uint8_t *data, uint8_t cmd, const uint8_t *payload, size_t len)
Frames a given command payload.
virtual void digital_write(bool value)=0
void reset_normal_boot_()
Reset STM32 to boot the regular firmware.
bool is_running_configured_version() const
This class represents the communication layer between the front-end MQTT layer and the hardware outpu...
sensor::Sensor * current_sensor_
void send_settings_()
Sends dimmer configuration.
void write_array(const uint8_t *data, size_t len)
std::string format_hex(const uint8_t *data, size_t length)
Format the byte array data of length len in lowercased hex.
std::array< uint8_t, SHELLY_DIMMER_BUFFER_SIZE > buffer_
int handle_byte_(uint8_t c)
Handles a single byte as part of a protocol frame.
void reset_dfu_boot_()
Reset STM32 to boot into DFU mode to enable firmware upgrades.
stm32_err_t stm32_write_memory(const stm32_unique_ptr &stm, uint32_t address, const uint8_t *data, const unsigned int len)
bool read_frame_()
Reads a response frame.
void write_state(light::LightState *state) override
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.
void reset_(bool boot0)
Reset STM32 with the BOOT0 pin set to the given value.
constexpr auto STREAM_SERIAL
uint32_t IRAM_ATTR HOT millis()
bool send_command_(uint8_t cmd, const uint8_t *payload, uint8_t len)
Sends a command and waits for an acknowledgement.
uint16_t warmup_brightness_
sensor::Sensor * power_sensor_
light::LightState * state_
uint16_t convert_brightness_(float brightness)
Convert relative brightness into a dimmer brightness value.
uint16_t shelly_dimmer_checksum(const uint8_t *buf, int len)
Computes a crappy checksum as defined by the Shelly Dimmer protocol.
bool upgrade_firmware_()
Performs a firmware upgrade.
void publish_state(float state)
Publish a new state to the front-end.
void current_values_as_brightness(float *brightness)
stm32_unique_ptr stm32_init(uart::UARTDevice *stream, const uint8_t flags, const char init)
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
constexpr auto STM32_MASS_ERASE
stm32_err_t stm32_erase_memory(const stm32_unique_ptr &stm, uint32_t spage, uint32_t pages)
bool handle_frame_()
Handles a complete frame.
const uint8_t ESPHOME_WEBSERVER_INDEX_HTML [] PROGMEM
virtual void mark_failed()
Mark this component as failed.
void dump_config() override
Implementation of SPI Controller mode.
sensor::Sensor * voltage_sensor_
void send_brightness_(uint16_t brightness)
Sends the given brightness value.
void IRAM_ATTR HOT delay(uint32_t ms)