mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	Merge branch 'dev' of https://github.com/esphome/esphome into dev
This commit is contained in:
		
							
								
								
									
										1
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/ci-docker.yml
									
									
									
									
										vendored
									
									
								
							| @@ -18,6 +18,7 @@ jobs: | ||||
|     name: Build docker containers | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         arch: [amd64, armv7, aarch64] | ||||
|         build_type: ["hassio", "docker"] | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/ci.yml
									
									
									
									
										vendored
									
									
								
							| @@ -44,6 +44,7 @@ jobs: | ||||
|     container: esphome/esphome-lint:latest | ||||
|     # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         split: [1, 2, 3, 4] | ||||
|     steps: | ||||
| @@ -107,6 +108,7 @@ jobs: | ||||
|   test: | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|           test: | ||||
|           - test1 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release-dev.yml
									
									
									
									
										vendored
									
									
								
							| @@ -41,6 +41,7 @@ jobs: | ||||
|     container: esphome/esphome-lint:latest | ||||
|     # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         split: [1, 2, 3, 4] | ||||
|     steps: | ||||
| @@ -104,6 +105,7 @@ jobs: | ||||
|   test: | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|           test: | ||||
|           - test1 | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release.yml
									
									
									
									
										vendored
									
									
								
							| @@ -40,6 +40,7 @@ jobs: | ||||
|     container: esphome/esphome-lint:latest | ||||
|     # Split clang-tidy check into 4 jobs. Each one will check 1/4th of the .cpp files | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|         split: [1, 2, 3, 4] | ||||
|     steps: | ||||
| @@ -103,6 +104,7 @@ jobs: | ||||
|   test: | ||||
|     runs-on: ubuntu-latest | ||||
|     strategy: | ||||
|       fail-fast: false | ||||
|       matrix: | ||||
|           test: | ||||
|           - test1 | ||||
|   | ||||
| @@ -12,8 +12,10 @@ void DaikinClimate::transmit_state() { | ||||
|                               0x00, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00}; | ||||
|  | ||||
|   remote_state[21] = this->operation_mode_(); | ||||
|   remote_state[24] = this->fan_speed_(); | ||||
|   remote_state[22] = this->temperature_(); | ||||
|   uint16_t fan_speed = this->fan_speed_(); | ||||
|   remote_state[24] = fan_speed >> 8; | ||||
|   remote_state[25] = fan_speed & 0xff; | ||||
|  | ||||
|   // Calculate checksum | ||||
|   for (int i = 16; i < 34; i++) { | ||||
| @@ -90,25 +92,38 @@ uint8_t DaikinClimate::operation_mode_() { | ||||
|   return operating_mode; | ||||
| } | ||||
|  | ||||
| uint8_t DaikinClimate::fan_speed_() { | ||||
|   uint8_t fan_speed; | ||||
| uint16_t DaikinClimate::fan_speed_() { | ||||
|   uint16_t fan_speed; | ||||
|   switch (this->fan_mode) { | ||||
|     case climate::CLIMATE_FAN_LOW: | ||||
|       fan_speed = DAIKIN_FAN_1; | ||||
|       fan_speed = DAIKIN_FAN_1 << 8; | ||||
|       break; | ||||
|     case climate::CLIMATE_FAN_MEDIUM: | ||||
|       fan_speed = DAIKIN_FAN_3; | ||||
|       fan_speed = DAIKIN_FAN_3 << 8; | ||||
|       break; | ||||
|     case climate::CLIMATE_FAN_HIGH: | ||||
|       fan_speed = DAIKIN_FAN_5; | ||||
|       fan_speed = DAIKIN_FAN_5 << 8; | ||||
|       break; | ||||
|     case climate::CLIMATE_FAN_AUTO: | ||||
|     default: | ||||
|       fan_speed = DAIKIN_FAN_AUTO; | ||||
|       fan_speed = DAIKIN_FAN_AUTO << 8; | ||||
|   } | ||||
|  | ||||
|   // If swing is enabled switch first 4 bits to 1111 | ||||
|   return this->swing_mode == climate::CLIMATE_SWING_VERTICAL ? fan_speed | 0xF : fan_speed; | ||||
|   switch (this->swing_mode) { | ||||
|     case climate::CLIMATE_SWING_VERTICAL: | ||||
|       fan_speed |= 0x0F00; | ||||
|       break; | ||||
|     case climate::CLIMATE_SWING_HORIZONTAL: | ||||
|       fan_speed |= 0x000F; | ||||
|       break; | ||||
|     case climate::CLIMATE_SWING_BOTH: | ||||
|       fan_speed |= 0x0F0F; | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
|   return fan_speed; | ||||
| } | ||||
|  | ||||
| uint8_t DaikinClimate::temperature_() { | ||||
| @@ -159,13 +174,19 @@ bool DaikinClimate::parse_state_frame_(const uint8_t frame[]) { | ||||
|     this->target_temperature = temperature >> 1; | ||||
|   } | ||||
|   uint8_t fan_mode = frame[8]; | ||||
|   if (fan_mode & 0xF) | ||||
|   uint8_t swing_mode = frame[9]; | ||||
|   if (fan_mode & 0xF && swing_mode & 0xF) | ||||
|     this->swing_mode = climate::CLIMATE_SWING_BOTH; | ||||
|   else if (fan_mode & 0xF) | ||||
|     this->swing_mode = climate::CLIMATE_SWING_VERTICAL; | ||||
|   else if (swing_mode & 0xF) | ||||
|     this->swing_mode = climate::CLIMATE_SWING_HORIZONTAL; | ||||
|   else | ||||
|     this->swing_mode = climate::CLIMATE_SWING_OFF; | ||||
|   switch (fan_mode & 0xF0) { | ||||
|     case DAIKIN_FAN_1: | ||||
|     case DAIKIN_FAN_2: | ||||
|     case DAIKIN_FAN_SILENT: | ||||
|       this->fan_mode = climate::CLIMATE_FAN_LOW; | ||||
|       break; | ||||
|     case DAIKIN_FAN_3: | ||||
|   | ||||
| @@ -21,6 +21,7 @@ const uint8_t DAIKIN_MODE_ON = 0x01; | ||||
|  | ||||
| // Fan Speed | ||||
| const uint8_t DAIKIN_FAN_AUTO = 0xA0; | ||||
| const uint8_t DAIKIN_FAN_SILENT = 0xB0; | ||||
| const uint8_t DAIKIN_FAN_1 = 0x30; | ||||
| const uint8_t DAIKIN_FAN_2 = 0x40; | ||||
| const uint8_t DAIKIN_FAN_3 = 0x50; | ||||
| @@ -46,13 +47,14 @@ class DaikinClimate : public climate_ir::ClimateIR { | ||||
|             DAIKIN_TEMP_MIN, DAIKIN_TEMP_MAX, 1.0f, true, true, | ||||
|             std::vector<climate::ClimateFanMode>{climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, | ||||
|                                                  climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, | ||||
|             std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL}) {} | ||||
|             std::vector<climate::ClimateSwingMode>{climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, | ||||
|                                                    climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {} | ||||
|  | ||||
|  protected: | ||||
|   // Transmit via IR the state of this climate controller. | ||||
|   void transmit_state() override; | ||||
|   uint8_t operation_mode_(); | ||||
|   uint8_t fan_speed_(); | ||||
|   uint16_t fan_speed_(); | ||||
|   uint8_t temperature_(); | ||||
|   // Handle received IR Buffer | ||||
|   bool on_receive(remote_base::RemoteReceiveData data) override; | ||||
|   | ||||
| @@ -299,7 +299,7 @@ void DisplayBuffer::printf(int x, int y, Font *font, TextAlign align, const char | ||||
| void DisplayBuffer::printf(int x, int y, Font *font, const char *format, ...) { | ||||
|   va_list arg; | ||||
|   va_start(arg, format); | ||||
|   this->vprintf_(x, y, font, COLOR_ON, TextAlign::CENTER_LEFT, format, arg); | ||||
|   this->vprintf_(x, y, font, COLOR_ON, TextAlign::TOP_LEFT, format, arg); | ||||
|   va_end(arg); | ||||
| } | ||||
| void DisplayBuffer::set_writer(display_writer_t &&writer) { this->writer_ = writer; } | ||||
|   | ||||
| @@ -36,7 +36,10 @@ const uint8_t FUJITSU_GENERAL_FAN_HIGH_BYTE10 = 0x01; | ||||
| const uint8_t FUJITSU_GENERAL_FAN_MEDIUM_BYTE10 = 0x02; | ||||
| const uint8_t FUJITSU_GENERAL_FAN_LOW_BYTE10 = 0x03; | ||||
| const uint8_t FUJITSU_GENERAL_FAN_SILENT_BYTE10 = 0x04; | ||||
| const uint8_t FUJITSU_GENERAL_SWING_MASK_BYTE10 = 0b00010000; | ||||
| const uint8_t FUJITSU_GENERAL_SWING_NONE_BYTE10 = 0x00; | ||||
| const uint8_t FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 = 0x01; | ||||
| const uint8_t FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 = 0x02; | ||||
| const uint8_t FUJITSU_GENERAL_SWING_BOTH_BYTE10 = 0x03; | ||||
| const uint8_t FUJITSU_GENERAL_BASE_BYTE10 = 0x00; | ||||
|  | ||||
| const uint8_t FUJITSU_GENERAL_BASE_BYTE11 = 0x00; | ||||
| @@ -74,7 +77,12 @@ const uint16_t FUJITSU_GENERAL_TRL_SPACE = 8000; | ||||
|  | ||||
| const uint32_t FUJITSU_GENERAL_CARRIER_FREQUENCY = 38000; | ||||
|  | ||||
| FujitsuGeneralClimate::FujitsuGeneralClimate() : ClimateIR(FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1) {} | ||||
| FujitsuGeneralClimate::FujitsuGeneralClimate() | ||||
|     : ClimateIR( | ||||
|           FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX, 1.0f, true, true, | ||||
|           {climate::CLIMATE_FAN_AUTO, climate::CLIMATE_FAN_LOW, climate::CLIMATE_FAN_MEDIUM, climate::CLIMATE_FAN_HIGH}, | ||||
|           {climate::CLIMATE_SWING_OFF, climate::CLIMATE_SWING_VERTICAL, climate::CLIMATE_SWING_HORIZONTAL, | ||||
|            climate::CLIMATE_SWING_BOTH}) {} | ||||
|  | ||||
| void FujitsuGeneralClimate::transmit_state() { | ||||
|   if (this->mode == climate::CLIMATE_MODE_OFF) { | ||||
| @@ -101,8 +109,8 @@ void FujitsuGeneralClimate::transmit_state() { | ||||
|   remote_state[15] = FUJITSU_GENERAL_BASE_BYTE15; | ||||
|  | ||||
|   // Set temperature | ||||
|   uint8_t safecelsius = std::max((uint8_t) this->target_temperature, FUJITSU_GENERAL_TEMP_MIN); | ||||
|   safecelsius = std::min(safecelsius, FUJITSU_GENERAL_TEMP_MAX); | ||||
|   auto safecelsius = | ||||
|       (uint8_t) roundf(clamp(this->target_temperature, FUJITSU_GENERAL_TEMP_MIN, FUJITSU_GENERAL_TEMP_MAX)); | ||||
|   remote_state[8] = (byte) safecelsius - 16; | ||||
|   remote_state[8] = remote_state[8] << 4; | ||||
|  | ||||
| @@ -119,18 +127,52 @@ void FujitsuGeneralClimate::transmit_state() { | ||||
|     case climate::CLIMATE_MODE_HEAT: | ||||
|       remote_state[9] = FUJITSU_GENERAL_MODE_HEAT_BYTE9; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_DRY: | ||||
|       remote_state[9] = FUJITSU_GENERAL_MODE_DRY_BYTE9; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_FAN_ONLY: | ||||
|       remote_state[9] = FUJITSU_GENERAL_MODE_FAN_BYTE9; | ||||
|       break; | ||||
|     case climate::CLIMATE_MODE_AUTO: | ||||
|     default: | ||||
|       remote_state[9] = FUJITSU_GENERAL_MODE_AUTO_BYTE9; | ||||
|       break; | ||||
|       // TODO: CLIMATE_MODE_FAN_ONLY, CLIMATE_MODE_DRY, CLIMATE_MODE_10C are missing in esphome | ||||
|       // TODO: CLIMATE_MODE_10C are missing in esphome | ||||
|   } | ||||
|  | ||||
|   // TODO: missing support for fan speed | ||||
|   remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10; | ||||
|   // Set fan | ||||
|   switch (this->fan_mode) { | ||||
|     case climate::CLIMATE_FAN_HIGH: | ||||
|       remote_state[10] = FUJITSU_GENERAL_FAN_HIGH_BYTE10; | ||||
|       break; | ||||
|     case climate::CLIMATE_FAN_MEDIUM: | ||||
|       remote_state[10] = FUJITSU_GENERAL_FAN_MEDIUM_BYTE10; | ||||
|       break; | ||||
|     case climate::CLIMATE_FAN_LOW: | ||||
|       remote_state[10] = FUJITSU_GENERAL_FAN_LOW_BYTE10; | ||||
|       break; | ||||
|     case climate::CLIMATE_FAN_AUTO: | ||||
|     default: | ||||
|       remote_state[10] = FUJITSU_GENERAL_FAN_AUTO_BYTE10; | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   // TODO: missing support for swing | ||||
|   // remote_state[10] = (byte) remote_state[10] | FUJITSU_GENERAL_SWING_MASK_BYTE10; | ||||
|   // Set swing | ||||
|   switch (this->swing_mode) { | ||||
|     case climate::CLIMATE_SWING_VERTICAL: | ||||
|       remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_VERTICAL_BYTE10 << 4); | ||||
|       break; | ||||
|     case climate::CLIMATE_SWING_HORIZONTAL: | ||||
|       remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_HORIZONTAL_BYTE10 << 4); | ||||
|       break; | ||||
|     case climate::CLIMATE_SWING_BOTH: | ||||
|       remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_BOTH_BYTE10 << 4); | ||||
|       break; | ||||
|     case climate::CLIMATE_SWING_OFF: | ||||
|     default: | ||||
|       remote_state[10] = (byte) remote_state[10] | (FUJITSU_GENERAL_SWING_NONE_BYTE10 << 4); | ||||
|       break; | ||||
|   } | ||||
|  | ||||
|   // TODO: missing support for outdoor unit low noise | ||||
|   // remote_state[14] = (byte) remote_state[14] | FUJITSU_GENERAL_OUTDOOR_UNIT_LOW_NOISE_BYTE14; | ||||
|   | ||||
| @@ -17,7 +17,7 @@ class AQICalculator : public AbstractAQICalculator { | ||||
|  | ||||
|   int index_grid_[AMOUNT_OF_LEVELS][2] = {{0, 51}, {51, 100}, {101, 150}, {151, 200}, {201, 300}, {301, 500}}; | ||||
|  | ||||
|   int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 45}, {36, 55}, {56, 150}, {151, 250}, {251, 500}}; | ||||
|   int pm2_5_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 12}, {13, 35}, {36, 55}, {56, 150}, {151, 250}, {251, 500}}; | ||||
|  | ||||
|   int pm10_0_calculation_grid_[AMOUNT_OF_LEVELS][2] = {{0, 54},    {55, 154},  {155, 254}, | ||||
|                                                        {255, 354}, {355, 424}, {425, 604}}; | ||||
|   | ||||
| @@ -102,17 +102,18 @@ class LightTurnOnTrigger : public Trigger<> { | ||||
|  public: | ||||
|   LightTurnOnTrigger(LightState *a_light) { | ||||
|     a_light->add_new_remote_values_callback([this, a_light]() { | ||||
|       auto is_on = a_light->current_values.is_on(); | ||||
|       // using the remote value because of transitions we need to trigger as early as possible | ||||
|       auto is_on = a_light->remote_values.is_on(); | ||||
|       // only trigger when going from off to on | ||||
|       auto should_trigger = is_on && !last_on_; | ||||
|       auto should_trigger = is_on && !this->last_on_; | ||||
|       // Set new state immediately so that trigger() doesn't devolve | ||||
|       // into infinite loop | ||||
|       last_on_ = is_on; | ||||
|       this->last_on_ = is_on; | ||||
|       if (should_trigger) { | ||||
|         this->trigger(); | ||||
|       } | ||||
|     }); | ||||
|     last_on_ = a_light->current_values.is_on(); | ||||
|     this->last_on_ = a_light->current_values.is_on(); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
| @@ -122,22 +123,14 @@ class LightTurnOnTrigger : public Trigger<> { | ||||
| class LightTurnOffTrigger : public Trigger<> { | ||||
|  public: | ||||
|   LightTurnOffTrigger(LightState *a_light) { | ||||
|     a_light->add_new_remote_values_callback([this, a_light]() { | ||||
|     a_light->add_new_target_state_reached_callback([this, a_light]() { | ||||
|       auto is_on = a_light->current_values.is_on(); | ||||
|       // only trigger when going from on to off | ||||
|       auto should_trigger = !is_on && last_on_; | ||||
|       // Set new state immediately so that trigger() doesn't devolve | ||||
|       // into infinite loop | ||||
|       last_on_ = is_on; | ||||
|       if (should_trigger) { | ||||
|       if (!is_on) { | ||||
|         this->trigger(); | ||||
|       } | ||||
|     }); | ||||
|     last_on_ = a_light->current_values.is_on(); | ||||
|   } | ||||
|  | ||||
|  protected: | ||||
|   bool last_on_; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class AddressableSet : public Action<Ts...> { | ||||
|   | ||||
| @@ -145,6 +145,7 @@ void LightState::loop() { | ||||
|   if (this->transformer_ != nullptr) { | ||||
|     if (this->transformer_->is_finished()) { | ||||
|       this->remote_values = this->current_values = this->transformer_->get_end_values(); | ||||
|       this->target_state_reached_callback_.call(); | ||||
|       if (this->transformer_->publish_at_end()) | ||||
|         this->publish_state(); | ||||
|       this->transformer_ = nullptr; | ||||
| @@ -336,6 +337,9 @@ void LightCall::perform() { | ||||
|     this->parent_->set_immediately_(v, this->publish_); | ||||
|   } | ||||
|  | ||||
|   if (!this->has_transition_()) { | ||||
|     this->parent_->target_state_reached_callback_.call(); | ||||
|   } | ||||
|   if (this->publish_) { | ||||
|     this->parent_->publish_state(); | ||||
|   } | ||||
| @@ -752,6 +756,10 @@ void LightState::current_values_as_cwww(float *cold_white, float *warm_white, bo | ||||
| void LightState::add_new_remote_values_callback(std::function<void()> &&send_callback) { | ||||
|   this->remote_values_callback_.add(std::move(send_callback)); | ||||
| } | ||||
| void LightState::add_new_target_state_reached_callback(std::function<void()> &&send_callback) { | ||||
|   this->target_state_reached_callback_.add(std::move(send_callback)); | ||||
| } | ||||
|  | ||||
| LightEffect *LightState::get_active_effect_() { | ||||
|   if (this->active_effect_index_ == 0) | ||||
|     return nullptr; | ||||
|   | ||||
| @@ -242,6 +242,13 @@ class LightState : public Nameable, public Component { | ||||
|    */ | ||||
|   void add_new_remote_values_callback(std::function<void()> &&send_callback); | ||||
|  | ||||
|   /** | ||||
|    * The callback is called once the state of current_values and remote_values are equal | ||||
|    * | ||||
|    * @param send_callback | ||||
|    */ | ||||
|   void add_new_target_state_reached_callback(std::function<void()> &&send_callback); | ||||
|  | ||||
|   /// Return whether the light has any effects that meet the trait requirements. | ||||
|   bool supports_effects(); | ||||
|  | ||||
| @@ -318,6 +325,12 @@ class LightState : public Nameable, public Component { | ||||
|    * starting with the beginning of the transition. | ||||
|    */ | ||||
|   CallbackManager<void()> remote_values_callback_{}; | ||||
|  | ||||
|   /** Callback to call when the state of current_values and remote_values are equal | ||||
|    * This should be called once the state of current_values changed and equals the state of remote_values | ||||
|    */ | ||||
|   CallbackManager<void()> target_state_reached_callback_{}; | ||||
|  | ||||
|   LightOutput *output_;  ///< Store the output to allow effects to have more access. | ||||
|   /// Whether the light value should be written in the next cycle. | ||||
|   bool next_write_{true}; | ||||
|   | ||||
| @@ -11,6 +11,7 @@ CONF_SCROLL_DWELL = 'scroll_dwell' | ||||
| CONF_SCROLL_DELAY = 'scroll_delay' | ||||
| CONF_SCROLL_ENABLE = 'scroll_enable' | ||||
| CONF_SCROLL_MODE = 'scroll_mode' | ||||
| CONF_REVERSE_ENABLE = 'reverse_enable' | ||||
|  | ||||
| SCROLL_MODES = { | ||||
|     'CONTINUOUS': 0, | ||||
| @@ -39,6 +40,7 @@ CONFIG_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend({ | ||||
|     cv.Optional(CONF_SCROLL_SPEED, default='250ms'): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_SCROLL_DELAY, default='1000ms'): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_SCROLL_DWELL, default='1000ms'): cv.positive_time_period_milliseconds, | ||||
|     cv.Optional(CONF_REVERSE_ENABLE, default=False): cv.boolean, | ||||
| }).extend(cv.polling_component_schema('500ms')).extend(spi.spi_device_schema(cs_pin_required=True)) | ||||
|  | ||||
|  | ||||
| @@ -56,6 +58,7 @@ def to_code(config): | ||||
|     cg.add(var.set_scroll_delay(config[CONF_SCROLL_DELAY])) | ||||
|     cg.add(var.set_scroll(config[CONF_SCROLL_ENABLE])) | ||||
|     cg.add(var.set_scroll_mode(config[CONF_SCROLL_MODE])) | ||||
|     cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) | ||||
|  | ||||
|     if CONF_LAMBDA in config: | ||||
|         lambda_ = yield cg.process_lambda(config[CONF_LAMBDA], [(MAX7219ComponentRef, 'it')], | ||||
|   | ||||
| @@ -108,7 +108,11 @@ void MAX7219Component::display() { | ||||
|   // Send the data to the chip | ||||
|   for (uint8_t i = 0; i < this->num_chips_; i++) { | ||||
|     for (uint8_t j = 0; j < 8; j++) { | ||||
|       pixels[j] = this->max_displaybuffer_[i * 8 + j]; | ||||
|       if (this->reverse_) { | ||||
|         pixels[j] = this->max_displaybuffer_[(this->num_chips_ - i - 1) * 8 + j]; | ||||
|       } else { | ||||
|         pixels[j] = this->max_displaybuffer_[i * 8 + j]; | ||||
|       } | ||||
|     } | ||||
|     this->send64pixels(i, pixels); | ||||
|   } | ||||
| @@ -229,7 +233,7 @@ void MAX7219Component::send64pixels(uint8_t chip, const uint8_t pixels[8]) { | ||||
|       b = pixels[col]; | ||||
|     } else if (this->orientation_ == 2) { | ||||
|       for (uint8_t i = 0; i < 8; i++) { | ||||
|         b |= ((pixels[i] >> (7 - col)) << (7 - i)); | ||||
|         b |= ((pixels[i] >> (7 - col)) & 1) << i; | ||||
|       } | ||||
|     } else { | ||||
|       b = pixels[7 - col]; | ||||
|   | ||||
| @@ -52,6 +52,7 @@ class MAX7219Component : public PollingComponent, | ||||
|   void set_scroll_delay(uint16_t delay) { this->scroll_delay_ = delay; }; | ||||
|   void set_scroll(bool on_off) { this->scroll_ = on_off; }; | ||||
|   void set_scroll_mode(uint8_t mode) { this->scroll_mode_ = mode; }; | ||||
|   void set_reverse(bool on_off) { this->reverse_ = on_off; }; | ||||
|  | ||||
|   void send_char(byte chip, byte data); | ||||
|   void send64pixels(byte chip, const byte pixels[8]); | ||||
| @@ -87,6 +88,7 @@ class MAX7219Component : public PollingComponent, | ||||
|   uint8_t intensity_;  /// Intensity of the display from 0 to 15 (most) | ||||
|   uint8_t num_chips_; | ||||
|   bool scroll_; | ||||
|   bool reverse_; | ||||
|   bool update_{false}; | ||||
|   uint16_t scroll_speed_; | ||||
|   uint16_t scroll_delay_; | ||||
|   | ||||
| @@ -29,10 +29,9 @@ void FloatOutput::set_level(float state) { | ||||
|     this->power_.unrequest(); | ||||
|   } | ||||
| #endif | ||||
|  | ||||
|   float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_; | ||||
|   if (this->is_inverted()) | ||||
|     adjusted_value = 1.0f - adjusted_value; | ||||
|     state = 1.0f - state; | ||||
|   float adjusted_value = (state * (this->max_power_ - this->min_power_)) + this->min_power_; | ||||
|   this->write_state(adjusted_value); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -10,10 +10,11 @@ import tzlocal | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome import automation | ||||
| from esphome.const import CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \ | ||||
| from esphome.const import CONF_ID, CONF_CRON, CONF_DAYS_OF_MONTH, CONF_DAYS_OF_WEEK, CONF_HOURS, \ | ||||
|     CONF_MINUTES, CONF_MONTHS, CONF_ON_TIME, CONF_SECONDS, CONF_TIMEZONE, CONF_TRIGGER_ID, \ | ||||
|     CONF_AT, CONF_SECOND, CONF_HOUR, CONF_MINUTE | ||||
| from esphome.core import coroutine, coroutine_with_priority | ||||
| from esphome.automation import Condition | ||||
|  | ||||
| _LOGGER = logging.getLogger(__name__) | ||||
|  | ||||
| @@ -24,6 +25,7 @@ time_ns = cg.esphome_ns.namespace('time') | ||||
| RealTimeClock = time_ns.class_('RealTimeClock', cg.Component) | ||||
| CronTrigger = time_ns.class_('CronTrigger', automation.Trigger.template(), cg.Component) | ||||
| ESPTime = time_ns.struct('ESPTime') | ||||
| TimeHasTimeCondition = time_ns.class_('TimeHasTimeCondition', Condition) | ||||
|  | ||||
|  | ||||
| def _tz_timedelta(td): | ||||
| @@ -328,3 +330,11 @@ def register_time(time_var, config): | ||||
| def to_code(config): | ||||
|     cg.add_define('USE_TIME') | ||||
|     cg.add_global(time_ns.using) | ||||
|  | ||||
|  | ||||
| @automation.register_condition('time.has_time', TimeHasTimeCondition, cv.Schema({ | ||||
|     cv.GenerateID(): cv.use_id(RealTimeClock), | ||||
| })) | ||||
| def time_has_time_to_code(config, condition_id, template_arg, args): | ||||
|     paren = yield cg.get_variable(config[CONF_ID]) | ||||
|     yield cg.new_Pvariable(condition_id, template_arg, paren) | ||||
|   | ||||
| @@ -2,6 +2,7 @@ | ||||
|  | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/automation.h" | ||||
| #include <stdlib.h> | ||||
| #include <time.h> | ||||
| #include <bitset> | ||||
| @@ -133,5 +134,14 @@ class RealTimeClock : public Component { | ||||
|   std::string timezone_{}; | ||||
| }; | ||||
|  | ||||
| template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> { | ||||
|  public: | ||||
|   TimeHasTimeCondition(RealTimeClock *parent) : parent_(parent) {} | ||||
|   bool check(Ts... x) override { return this->parent_->now().is_valid(); } | ||||
|  | ||||
|  protected: | ||||
|   RealTimeClock *parent_; | ||||
| }; | ||||
|  | ||||
| }  // namespace time | ||||
| }  // namespace esphome | ||||
|   | ||||
| @@ -31,6 +31,7 @@ void write_row(AsyncResponseStream *stream, Nameable *obj, const std::string &kl | ||||
|   stream->print("</td><td></td><td>"); | ||||
|   stream->print(action.c_str()); | ||||
|   stream->print("</td>"); | ||||
|   stream->print("</tr>"); | ||||
| } | ||||
|  | ||||
| UrlMatch match_url(const std::string &url, bool only_domain = false) { | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| voluptuous==0.11.7 | ||||
| PyYAML==5.3.1 | ||||
| paho-mqtt==1.5.0 | ||||
| paho-mqtt==1.5.1 | ||||
| colorlog==4.2.1 | ||||
| tornado==6.0.4 | ||||
| protobuf==3.13.0 | ||||
|   | ||||
| @@ -6,7 +6,7 @@ pexpect==4.8.0 | ||||
|  | ||||
| # Unit tests | ||||
| pytest==6.0.2 | ||||
| pytest-cov==2.10.0 | ||||
| pytest-cov==2.10.1 | ||||
| pytest-mock==3.3.1 | ||||
| asyncmock==0.4.2 | ||||
| hypothesis==5.21.0 | ||||
|   | ||||
| @@ -8,6 +8,7 @@ esphome: | ||||
|     - wait_until: | ||||
|         - api.connected | ||||
|         - wifi.connected | ||||
|         - time.has_time | ||||
|   includes: | ||||
|     - custom.h | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user