mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 06:33:51 +00:00 
			
		
		
		
	Add Sonoff D1 Dimmer support (#2775)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							da336247eb
						
					
				
				
					commit
					8b2c032da6
				
			| @@ -177,6 +177,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet | ||||
| esphome/components/sim800l/* @glmnet | ||||
| esphome/components/sm2135/* @BoukeHaarsma23 | ||||
| esphome/components/socket/* @esphome/core | ||||
| esphome/components/sonoff_d1/* @anatoly-savchenkov | ||||
| esphome/components/spi/* @esphome/core | ||||
| esphome/components/ssd1322_base/* @kbx81 | ||||
| esphome/components/ssd1322_spi/* @kbx81 | ||||
|   | ||||
							
								
								
									
										1
									
								
								esphome/components/sonoff_d1/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								esphome/components/sonoff_d1/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| CODEOWNERS = ["@anatoly-savchenkov"] | ||||
							
								
								
									
										43
									
								
								esphome/components/sonoff_d1/light.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								esphome/components/sonoff_d1/light.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,43 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import uart, light | ||||
| from esphome.const import ( | ||||
|     CONF_OUTPUT_ID, | ||||
|     CONF_MIN_VALUE, | ||||
|     CONF_MAX_VALUE, | ||||
| ) | ||||
|  | ||||
| CONF_USE_RM433_REMOTE = "use_rm433_remote" | ||||
|  | ||||
| DEPENDENCIES = ["uart", "light"] | ||||
|  | ||||
| sonoff_d1_ns = cg.esphome_ns.namespace("sonoff_d1") | ||||
| SonoffD1Output = sonoff_d1_ns.class_( | ||||
|     "SonoffD1Output", cg.Component, uart.UARTDevice, light.LightOutput | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = ( | ||||
|     light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend( | ||||
|         { | ||||
|             cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SonoffD1Output), | ||||
|             cv.Optional(CONF_USE_RM433_REMOTE, default=False): cv.boolean, | ||||
|             cv.Optional(CONF_MIN_VALUE, default=0): cv.int_range(min=0, max=100), | ||||
|             cv.Optional(CONF_MAX_VALUE, default=100): cv.int_range(min=0, max=100), | ||||
|         } | ||||
|     ) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
| ) | ||||
| FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||
|     "sonoff_d1", baud_rate=9600, require_tx=True, require_rx=True | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_OUTPUT_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await uart.register_uart_device(var, config) | ||||
|     cg.add(var.set_use_rm433_remote(config[CONF_USE_RM433_REMOTE])) | ||||
|     cg.add(var.set_min_value(config[CONF_MIN_VALUE])) | ||||
|     cg.add(var.set_max_value(config[CONF_MAX_VALUE])) | ||||
|     await light.register_light(var, config) | ||||
							
								
								
									
										308
									
								
								esphome/components/sonoff_d1/sonoff_d1.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										308
									
								
								esphome/components/sonoff_d1/sonoff_d1.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,308 @@ | ||||
| /* | ||||
|   sonoff_d1.cpp - Sonoff D1 Dimmer support for ESPHome | ||||
|  | ||||
|   Copyright © 2021 Anatoly Savchenkov | ||||
|   Copyright © 2020 Jeff Rescignano | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy of this software | ||||
|   and associated documentation files (the “Software”), to deal in the Software without | ||||
|   restriction, including without limitation the rights to use, copy, modify, merge, publish, | ||||
|   distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom | ||||
|   the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all copies or | ||||
|   substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING | ||||
|   BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
|   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||||
|   DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
|   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
|   ----- | ||||
|  | ||||
|   If modifying this file, in addition to the license above, please ensure to include links back to the original code: | ||||
|   https://jeffresc.dev/blog/2020-10-10 | ||||
|   https://github.com/JeffResc/Sonoff-D1-Dimmer | ||||
|   https://github.com/arendst/Tasmota/blob/2d4a6a29ebc7153dbe2717e3615574ac1c84ba1d/tasmota/xdrv_37_sonoff_d1.ino#L119-L131 | ||||
|  | ||||
|   ----- | ||||
| */ | ||||
|  | ||||
| /*********************************************************************************************\ | ||||
|  * Sonoff D1 dimmer 433 | ||||
|  * Mandatory/Optional | ||||
|  *  ^  0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F 10 | ||||
|  *  M AA 55                                              - Header | ||||
|  *  M       01 04                                        - Version? | ||||
|  *  M             00 0A                                  - Following data length (10 bytes) | ||||
|  *  O                   01                               - Power state (00 = off, 01 = on, FF = ignore) | ||||
|  *  O                      64                            - Dimmer percentage (01 to 64 = 1 to 100%, 0 - ignore) | ||||
|  *  O                         FF FF FF FF FF FF FF FF    - Not used | ||||
|  *  M                                                 6C - CRC over bytes 2 to F (Addition) | ||||
| \*********************************************************************************************/ | ||||
| #include <cmath> | ||||
| #include "sonoff_d1.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sonoff_d1 { | ||||
|  | ||||
| static const char *const TAG = "sonoff_d1"; | ||||
|  | ||||
| uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) { | ||||
|   uint8_t crc = 0; | ||||
|   for (int i = 2; i < len - 1; i++) { | ||||
|     crc += cmd[i]; | ||||
|   } | ||||
|   return crc; | ||||
| } | ||||
|  | ||||
| void SonoffD1Output::populate_checksum_(uint8_t *cmd, const size_t len) { | ||||
|   // Update the checksum | ||||
|   cmd[len - 1] = this->calc_checksum_(cmd, len); | ||||
| } | ||||
|  | ||||
| void SonoffD1Output::skip_command_() { | ||||
|   size_t garbage = 0; | ||||
|   // Read out everything from the UART FIFO | ||||
|   while (this->available()) { | ||||
|     uint8_t value = this->read(); | ||||
|     ESP_LOGW(TAG, "[%04d] Skip %02d: 0x%02x from the dimmer", this->write_count_, garbage, value); | ||||
|     garbage++; | ||||
|   } | ||||
|  | ||||
|   // Warn about unexpected bytes in the protocol with UART dimmer | ||||
|   if (garbage) | ||||
|     ESP_LOGW(TAG, "[%04d] Skip %d bytes from the dimmer", this->write_count_, garbage); | ||||
| } | ||||
|  | ||||
| // This assumes some data is already available | ||||
| bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) { | ||||
|   // Do consistency check | ||||
|   if (cmd == nullptr || len < 7) { | ||||
|     ESP_LOGW(TAG, "[%04d] Too short command buffer (actual len is %d bytes, minimal is 7)", this->write_count_, len); | ||||
|     return false; | ||||
|   } | ||||
|  | ||||
|   // Read a minimal packet | ||||
|   if (this->read_array(cmd, 6)) { | ||||
|     ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_); | ||||
|     ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, 6).c_str()); | ||||
|  | ||||
|     if (cmd[0] != 0xAA || cmd[1] != 0x55) { | ||||
|       ESP_LOGW(TAG, "[%04d] RX: wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]); | ||||
|       this->skip_command_(); | ||||
|       return false; | ||||
|     } | ||||
|     if ((cmd[5] + 7 /*mandatory header + crc suffix length*/) > len) { | ||||
|       ESP_LOGW(TAG, "[%04d] RX: Payload length is unexpected (%d, max expected %d)", this->write_count_, cmd[5], | ||||
|                len - 7); | ||||
|       this->skip_command_(); | ||||
|       return false; | ||||
|     } | ||||
|     if (this->read_array(&cmd[6], cmd[5] + 1 /*checksum suffix*/)) { | ||||
|       ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(&cmd[6], cmd[5] + 1).c_str()); | ||||
|  | ||||
|       // Check the checksum | ||||
|       uint8_t valid_checksum = this->calc_checksum_(cmd, cmd[5] + 7); | ||||
|       if (valid_checksum != cmd[cmd[5] + 7 - 1]) { | ||||
|         ESP_LOGW(TAG, "[%04d] RX: checksum mismatch (%d, expected %d)", this->write_count_, cmd[cmd[5] + 7 - 1], | ||||
|                  valid_checksum); | ||||
|         this->skip_command_(); | ||||
|         return false; | ||||
|       } | ||||
|       len = cmd[5] + 7 /*mandatory header + suffix length*/; | ||||
|  | ||||
|       // Read remaining gardbled data (just in case, I don't see where this can appear now) | ||||
|       this->skip_command_(); | ||||
|       return true; | ||||
|     } | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "[%04d] RX: feedback timeout", this->write_count_); | ||||
|     this->skip_command_(); | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) { | ||||
|   // Expected acknowledgement from rf chip | ||||
|   uint8_t ref_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00}; | ||||
|   uint8_t buffer[sizeof(ref_buffer)] = {0}; | ||||
|   uint32_t pos = 0, buf_len = sizeof(ref_buffer); | ||||
|  | ||||
|   // Update the reference checksum | ||||
|   this->populate_checksum_(ref_buffer, sizeof(ref_buffer)); | ||||
|  | ||||
|   // Read ack code, this either reads 7 bytes or exits with a timeout | ||||
|   this->read_command_(buffer, buf_len); | ||||
|  | ||||
|   // Compare response with expected response | ||||
|   while (pos < sizeof(ref_buffer) && ref_buffer[pos] == buffer[pos]) { | ||||
|     pos++; | ||||
|   } | ||||
|   if (pos == sizeof(ref_buffer)) { | ||||
|     ESP_LOGD(TAG, "[%04d] Acknowledge received", this->write_count_); | ||||
|     return true; | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "[%04d] Unexpected acknowledge received (possible clash of RF/HA commands), expected ack was:", | ||||
|              this->write_count_); | ||||
|     ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(ref_buffer, sizeof(ref_buffer)).c_str()); | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_ack) { | ||||
|   // Do some consistency checks | ||||
|   if (len < 7) { | ||||
|     ESP_LOGW(TAG, "[%04d] Too short command (actual len is %d bytes, minimal is 7)", this->write_count_, len); | ||||
|     return false; | ||||
|   } | ||||
|   if (cmd[0] != 0xAA || cmd[1] != 0x55) { | ||||
|     ESP_LOGW(TAG, "[%04d] Wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]); | ||||
|     return false; | ||||
|   } | ||||
|   if ((cmd[5] + 7 /*mandatory header + suffix length*/) != len) { | ||||
|     ESP_LOGW(TAG, "[%04d] Payload length field does not match packet lenght (%d, expected %d)", this->write_count_, | ||||
|              cmd[5], len - 7); | ||||
|     return false; | ||||
|   } | ||||
|   this->populate_checksum_(cmd, len); | ||||
|  | ||||
|   // Need retries here to handle the following cases: | ||||
|   // 1. On power up companion MCU starts to respond with a delay, so few first commands are ignored | ||||
|   // 2. UART command initiated by this component can clash with a command initiated by RF | ||||
|   uint32_t retries = 10; | ||||
|   do { | ||||
|     ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_); | ||||
|     ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, len).c_str()); | ||||
|     this->write_array(cmd, len); | ||||
|     this->write_count_++; | ||||
|     if (!needs_ack) | ||||
|       return true; | ||||
|     retries--; | ||||
|   } while (!this->read_ack_(cmd, len) && retries > 0); | ||||
|  | ||||
|   if (retries) { | ||||
|     return true; | ||||
|   } else { | ||||
|     ESP_LOGE(TAG, "[%04d] Unable to write to the dimmer", this->write_count_); | ||||
|   } | ||||
|   return false; | ||||
| } | ||||
|  | ||||
| bool SonoffD1Output::control_dimmer_(const bool binary, const uint8_t brightness) { | ||||
|   // Include our basic code from the Tasmota project, thank you again! | ||||
|   //                    0     1     2     3     4     5     6     7     8 | ||||
|   uint8_t cmd[17] = {0xAA, 0x55, 0x01, 0x04, 0x00, 0x0A, 0x00, 0x00, 0xFF, | ||||
|                      // 9     10    11    12    13    14    15    16 | ||||
|                      0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00}; | ||||
|  | ||||
|   cmd[6] = binary; | ||||
|   cmd[7] = remap<uint8_t, uint8_t>(brightness, 0, 100, this->min_value_, this->max_value_); | ||||
|   ESP_LOGI(TAG, "[%04d] Setting dimmer state to %s, raw brightness=%d", this->write_count_, ONOFF(binary), cmd[7]); | ||||
|   return this->write_command_(cmd, sizeof(cmd)); | ||||
| } | ||||
|  | ||||
| void SonoffD1Output::process_command_(const uint8_t *cmd, const size_t len) { | ||||
|   if (cmd[2] == 0x01 && cmd[3] == 0x04 && cmd[4] == 0x00 && cmd[5] == 0x0A) { | ||||
|     uint8_t ack_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00}; | ||||
|     // Ack a command from RF to ESP to prevent repeating commands | ||||
|     this->write_command_(ack_buffer, sizeof(ack_buffer), false); | ||||
|     ESP_LOGI(TAG, "[%04d] RF sets dimmer state to %s, raw brightness=%d", this->write_count_, ONOFF(cmd[6]), cmd[7]); | ||||
|     const uint8_t new_brightness = remap<uint8_t, uint8_t>(cmd[7], this->min_value_, this->max_value_, 0, 100); | ||||
|     const bool new_state = cmd[6]; | ||||
|  | ||||
|     // Got light change state command. In all cases we revert the command immediately | ||||
|     // since we want to rely on ESP controlled transitions | ||||
|     if (new_state != this->last_binary_ || new_brightness != this->last_brightness_) { | ||||
|       this->control_dimmer_(this->last_binary_, this->last_brightness_); | ||||
|     } | ||||
|  | ||||
|     if (!this->use_rm433_remote_) { | ||||
|       // If RF remote is not used, this is a known ghost RF command | ||||
|       ESP_LOGI(TAG, "[%04d] Ghost command from RF detected, reverted", this->write_count_); | ||||
|     } else { | ||||
|       // If remote is used, initiate transition to the new state | ||||
|       this->publish_state_(new_state, new_brightness); | ||||
|     } | ||||
|   } else { | ||||
|     ESP_LOGW(TAG, "[%04d] Unexpected command received", this->write_count_); | ||||
|   } | ||||
| } | ||||
|  | ||||
| void SonoffD1Output::publish_state_(const bool is_on, const uint8_t brightness) { | ||||
|   if (light_state_) { | ||||
|     ESP_LOGV(TAG, "Publishing new state: %s, brightness=%d", ONOFF(is_on), brightness); | ||||
|     auto call = light_state_->make_call(); | ||||
|     call.set_state(is_on); | ||||
|     if (brightness != 0) { | ||||
|       // Brightness equal to 0 has a special meaning. | ||||
|       // D1 uses 0 as "previously set brightness". | ||||
|       // Usually zero brightness comes inside light ON command triggered by RF remote. | ||||
|       // Since we unconditionally override commands coming from RF remote in process_command_(), | ||||
|       // here we mimic the original behavior but with LightCall functionality | ||||
|       call.set_brightness((float) brightness / 100.0f); | ||||
|     } | ||||
|     call.perform(); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Set the device's traits | ||||
| light::LightTraits SonoffD1Output::get_traits() { | ||||
|   auto traits = light::LightTraits(); | ||||
|   traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS}); | ||||
|   return traits; | ||||
| } | ||||
|  | ||||
| void SonoffD1Output::write_state(light::LightState *state) { | ||||
|   bool binary; | ||||
|   float brightness; | ||||
|  | ||||
|   // Fill our variables with the device's current state | ||||
|   state->current_values_as_binary(&binary); | ||||
|   state->current_values_as_brightness(&brightness); | ||||
|  | ||||
|   // Convert ESPHome's brightness (0-1) to the device's internal brightness (0-100) | ||||
|   const uint8_t calculated_brightness = std::round(brightness * 100); | ||||
|  | ||||
|   if (calculated_brightness == 0) { | ||||
|     // if(binary) ESP_LOGD(TAG, "current_values_as_binary() returns true for zero brightness"); | ||||
|     binary = false; | ||||
|   } | ||||
|  | ||||
|   // If a new value, write to the dimmer | ||||
|   if (binary != this->last_binary_ || calculated_brightness != this->last_brightness_) { | ||||
|     if (this->control_dimmer_(binary, calculated_brightness)) { | ||||
|       this->last_brightness_ = calculated_brightness; | ||||
|       this->last_binary_ = binary; | ||||
|     } else { | ||||
|       // Return to original value if failed to write to the dimmer | ||||
|       // TODO: Test me, can be tested if high-voltage part is not connected | ||||
|       ESP_LOGW(TAG, "Failed to update the dimmer, publishing the previous state"); | ||||
|       this->publish_state_(this->last_binary_, this->last_brightness_); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| void SonoffD1Output::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "Sonoff D1 Dimmer: '%s'", this->light_state_ ? this->light_state_->get_name().c_str() : ""); | ||||
|   ESP_LOGCONFIG(TAG, "  Use RM433 Remote: %s", ONOFF(this->use_rm433_remote_)); | ||||
|   ESP_LOGCONFIG(TAG, "  Minimal brightness: %d", this->min_value_); | ||||
|   ESP_LOGCONFIG(TAG, "  Maximal brightness: %d", this->max_value_); | ||||
| } | ||||
|  | ||||
| void SonoffD1Output::loop() { | ||||
|   // Read commands from the dimmer | ||||
|   // RF chip notifies ESP about remotely changed state with the same commands as we send | ||||
|   if (this->available()) { | ||||
|     ESP_LOGV(TAG, "Have some UART data in loop()"); | ||||
|     uint8_t buffer[17] = {0}; | ||||
|     size_t len = sizeof(buffer); | ||||
|     if (this->read_command_(buffer, len)) { | ||||
|       this->process_command_(buffer, len); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| }  // namespace sonoff_d1 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										85
									
								
								esphome/components/sonoff_d1/sonoff_d1.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								esphome/components/sonoff_d1/sonoff_d1.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | ||||
| #pragma once | ||||
|  | ||||
| /* | ||||
|   sonoff_d1.h - Sonoff D1 Dimmer support for ESPHome | ||||
|  | ||||
|   Copyright © 2021 Anatoly Savchenkov | ||||
|   Copyright © 2020 Jeff Rescignano | ||||
|  | ||||
|   Permission is hereby granted, free of charge, to any person obtaining a copy of this software | ||||
|   and associated documentation files (the “Software”), to deal in the Software without | ||||
|   restriction, including without limitation the rights to use, copy, modify, merge, publish, | ||||
|   distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom | ||||
|   the Software is furnished to do so, subject to the following conditions: | ||||
|  | ||||
|   The above copyright notice and this permission notice shall be included in all copies or | ||||
|   substantial portions of the Software. | ||||
|  | ||||
|   THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING | ||||
|   BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||||
|   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, | ||||
|   DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | ||||
|   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||||
|  | ||||
|   ----- | ||||
|  | ||||
|   If modifying this file, in addition to the license above, please ensure to include links back to the original code: | ||||
|   https://jeffresc.dev/blog/2020-10-10 | ||||
|   https://github.com/JeffResc/Sonoff-D1-Dimmer | ||||
|   https://github.com/arendst/Tasmota/blob/2d4a6a29ebc7153dbe2717e3615574ac1c84ba1d/tasmota/xdrv_37_sonoff_d1.ino#L119-L131 | ||||
|  | ||||
|   ----- | ||||
| */ | ||||
|  | ||||
| #include "esphome/core/log.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/components/light/light_output.h" | ||||
| #include "esphome/components/light/light_state.h" | ||||
| #include "esphome/components/light/light_traits.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace sonoff_d1 { | ||||
|  | ||||
| class SonoffD1Output : public light::LightOutput, public uart::UARTDevice, public Component { | ||||
|  public: | ||||
|   // LightOutput methods | ||||
|   light::LightTraits get_traits() override; | ||||
|   void setup_state(light::LightState *state) override { this->light_state_ = state; } | ||||
|   void write_state(light::LightState *state) override; | ||||
|  | ||||
|   // Component methods | ||||
|   void setup() override{}; | ||||
|   void loop() override; | ||||
|   void dump_config() override; | ||||
|   float get_setup_priority() const override { return esphome::setup_priority::DATA; } | ||||
|  | ||||
|   // Custom methods | ||||
|   void set_use_rm433_remote(const bool use_rm433_remote) { this->use_rm433_remote_ = use_rm433_remote; } | ||||
|   void set_min_value(const uint8_t min_value) { this->min_value_ = min_value; } | ||||
|   void set_max_value(const uint8_t max_value) { this->max_value_ = max_value; } | ||||
|  | ||||
|  protected: | ||||
|   uint8_t min_value_{0}; | ||||
|   uint8_t max_value_{100}; | ||||
|   bool use_rm433_remote_{false}; | ||||
|   bool last_binary_{false}; | ||||
|   uint8_t last_brightness_{0}; | ||||
|   int write_count_{0}; | ||||
|   int read_count_{0}; | ||||
|   light::LightState *light_state_{nullptr}; | ||||
|  | ||||
|   uint8_t calc_checksum_(const uint8_t *cmd, size_t len); | ||||
|   void populate_checksum_(uint8_t *cmd, size_t len); | ||||
|   void skip_command_(); | ||||
|   bool read_command_(uint8_t *cmd, size_t &len); | ||||
|   bool read_ack_(const uint8_t *cmd, size_t len); | ||||
|   bool write_command_(uint8_t *cmd, size_t len, bool needs_ack = true); | ||||
|   bool control_dimmer_(bool binary, uint8_t brightness); | ||||
|   void process_command_(const uint8_t *cmd, size_t len); | ||||
|   void publish_state_(bool is_on, uint8_t brightness); | ||||
| }; | ||||
|  | ||||
| }  // namespace sonoff_d1 | ||||
| }  // namespace esphome | ||||
| @@ -1229,6 +1229,12 @@ light: | ||||
|     name: Icicle Lights | ||||
|     pin_a: out | ||||
|     pin_b: out2 | ||||
|   - platform: sonoff_d1 | ||||
|     uart_id: uart2 | ||||
|     use_rm433_remote: False | ||||
|     name: Sonoff D1 Dimmer | ||||
|     id: d1_light | ||||
|     restore_mode: RESTORE_DEFAULT_OFF | ||||
|  | ||||
| servo: | ||||
|   id: my_servo | ||||
|   | ||||
		Reference in New Issue
	
	Block a user