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/sim800l/* @glmnet | ||||||
| esphome/components/sm2135/* @BoukeHaarsma23 | esphome/components/sm2135/* @BoukeHaarsma23 | ||||||
| esphome/components/socket/* @esphome/core | esphome/components/socket/* @esphome/core | ||||||
|  | esphome/components/sonoff_d1/* @anatoly-savchenkov | ||||||
| esphome/components/spi/* @esphome/core | esphome/components/spi/* @esphome/core | ||||||
| esphome/components/ssd1322_base/* @kbx81 | esphome/components/ssd1322_base/* @kbx81 | ||||||
| esphome/components/ssd1322_spi/* @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 |     name: Icicle Lights | ||||||
|     pin_a: out |     pin_a: out | ||||||
|     pin_b: out2 |     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: | servo: | ||||||
|   id: my_servo |   id: my_servo | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user