mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Merge branch 'dev' into camera-platform
This commit is contained in:
		| @@ -442,6 +442,7 @@ esphome/components/sun/* @OttoWinter | |||||||
| esphome/components/sun_gtil2/* @Mat931 | esphome/components/sun_gtil2/* @Mat931 | ||||||
| esphome/components/switch/* @esphome/core | esphome/components/switch/* @esphome/core | ||||||
| esphome/components/switch/binary_sensor/* @ssieb | esphome/components/switch/binary_sensor/* @ssieb | ||||||
|  | esphome/components/sx127x/* @swoboda1337 | ||||||
| esphome/components/syslog/* @clydebarrow | esphome/components/syslog/* @clydebarrow | ||||||
| esphome/components/t6615/* @tylermenezes | esphome/components/t6615/* @tylermenezes | ||||||
| esphome/components/tc74/* @sethgirvan | esphome/components/tc74/* @sethgirvan | ||||||
|   | |||||||
| @@ -95,19 +95,6 @@ APIConnection::~APIConnection() { | |||||||
| #endif | #endif | ||||||
| } | } | ||||||
|  |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP |  | ||||||
| void APIConnection::log_batch_item_(const DeferredBatch::BatchItem &item) { |  | ||||||
|   // Set log-only mode |  | ||||||
|   this->flags_.log_only_mode = true; |  | ||||||
|  |  | ||||||
|   // Call the creator - it will create the message and log it via encode_message_to_buffer |  | ||||||
|   item.creator(item.entity, this, std::numeric_limits<uint16_t>::max(), true, item.message_type); |  | ||||||
|  |  | ||||||
|   // Clear log-only mode |  | ||||||
|   this->flags_.log_only_mode = false; |  | ||||||
| } |  | ||||||
| #endif |  | ||||||
|  |  | ||||||
| void APIConnection::loop() { | void APIConnection::loop() { | ||||||
|   if (this->flags_.next_close) { |   if (this->flags_.next_close) { | ||||||
|     // requested a disconnect |     // requested a disconnect | ||||||
| @@ -159,15 +146,25 @@ void APIConnection::loop() { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Process deferred batch if scheduled |   // Process deferred batch if scheduled and timer has expired | ||||||
|   if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { |   if (this->flags_.batch_scheduled && now - this->deferred_batch_.batch_start_time >= this->get_batch_delay_ms_()) { | ||||||
|     this->process_batch_(); |     this->process_batch_(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (!this->list_entities_iterator_.completed()) { |   if (!this->list_entities_iterator_.completed()) { | ||||||
|     this->list_entities_iterator_.advance(); |     this->process_iterator_batch_(this->list_entities_iterator_); | ||||||
|   } else if (!this->initial_state_iterator_.completed()) { |   } else if (!this->initial_state_iterator_.completed()) { | ||||||
|     this->initial_state_iterator_.advance(); |     this->process_iterator_batch_(this->initial_state_iterator_); | ||||||
|  |  | ||||||
|  |     // If we've completed initial states, process any remaining and clear the flag | ||||||
|  |     if (this->initial_state_iterator_.completed()) { | ||||||
|  |       // Process any remaining batched messages immediately | ||||||
|  |       if (!this->deferred_batch_.empty()) { | ||||||
|  |         this->process_batch_(); | ||||||
|  |       } | ||||||
|  |       // Now that everything is sent, enable immediate sending for future state changes | ||||||
|  |       this->flags_.should_try_send_immediately = true; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (this->flags_.sent_ping) { |   if (this->flags_.sent_ping) { | ||||||
| @@ -305,7 +302,7 @@ uint16_t APIConnection::encode_message_to_buffer(ProtoMessage &msg, uint16_t mes | |||||||
|  |  | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { | bool APIConnection::send_binary_sensor_state(binary_sensor::BinarySensor *binary_sensor) { | ||||||
|   return this->schedule_message_(binary_sensor, &APIConnection::try_send_binary_sensor_state, |   return this->send_message_smart_(binary_sensor, &APIConnection::try_send_binary_sensor_state, | ||||||
|                                    BinarySensorStateResponse::MESSAGE_TYPE); |                                    BinarySensorStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -333,7 +330,7 @@ uint16_t APIConnection::try_send_binary_sensor_info(EntityBase *entity, APIConne | |||||||
|  |  | ||||||
| #ifdef USE_COVER | #ifdef USE_COVER | ||||||
| bool APIConnection::send_cover_state(cover::Cover *cover) { | bool APIConnection::send_cover_state(cover::Cover *cover) { | ||||||
|   return this->schedule_message_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(cover, &APIConnection::try_send_cover_state, CoverStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_cover_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                              bool is_single) { |                                              bool is_single) { | ||||||
| @@ -394,7 +391,7 @@ void APIConnection::cover_command(const CoverCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_FAN | #ifdef USE_FAN | ||||||
| bool APIConnection::send_fan_state(fan::Fan *fan) { | bool APIConnection::send_fan_state(fan::Fan *fan) { | ||||||
|   return this->schedule_message_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(fan, &APIConnection::try_send_fan_state, FanStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_fan_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                            bool is_single) { |                                            bool is_single) { | ||||||
| @@ -453,7 +450,7 @@ void APIConnection::fan_command(const FanCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_LIGHT | #ifdef USE_LIGHT | ||||||
| bool APIConnection::send_light_state(light::LightState *light) { | bool APIConnection::send_light_state(light::LightState *light) { | ||||||
|   return this->schedule_message_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(light, &APIConnection::try_send_light_state, LightStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_light_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                              bool is_single) { |                                              bool is_single) { | ||||||
| @@ -545,7 +542,7 @@ void APIConnection::light_command(const LightCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { | bool APIConnection::send_sensor_state(sensor::Sensor *sensor) { | ||||||
|   return this->schedule_message_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(sensor, &APIConnection::try_send_sensor_state, SensorStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -577,7 +574,7 @@ uint16_t APIConnection::try_send_sensor_info(EntityBase *entity, APIConnection * | |||||||
|  |  | ||||||
| #ifdef USE_SWITCH | #ifdef USE_SWITCH | ||||||
| bool APIConnection::send_switch_state(switch_::Switch *a_switch) { | bool APIConnection::send_switch_state(switch_::Switch *a_switch) { | ||||||
|   return this->schedule_message_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(a_switch, &APIConnection::try_send_switch_state, SwitchStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_switch_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -614,7 +611,7 @@ void APIConnection::switch_command(const SwitchCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_TEXT_SENSOR | #ifdef USE_TEXT_SENSOR | ||||||
| bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { | bool APIConnection::send_text_sensor_state(text_sensor::TextSensor *text_sensor) { | ||||||
|   return this->schedule_message_(text_sensor, &APIConnection::try_send_text_sensor_state, |   return this->send_message_smart_(text_sensor, &APIConnection::try_send_text_sensor_state, | ||||||
|                                    TextSensorStateResponse::MESSAGE_TYPE); |                                    TextSensorStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -642,7 +639,7 @@ uint16_t APIConnection::try_send_text_sensor_info(EntityBase *entity, APIConnect | |||||||
|  |  | ||||||
| #ifdef USE_CLIMATE | #ifdef USE_CLIMATE | ||||||
| bool APIConnection::send_climate_state(climate::Climate *climate) { | bool APIConnection::send_climate_state(climate::Climate *climate) { | ||||||
|   return this->schedule_message_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(climate, &APIConnection::try_send_climate_state, ClimateStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_climate_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                                bool is_single) { |                                                bool is_single) { | ||||||
| @@ -742,7 +739,7 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| bool APIConnection::send_number_state(number::Number *number) { | bool APIConnection::send_number_state(number::Number *number) { | ||||||
|   return this->schedule_message_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(number, &APIConnection::try_send_number_state, NumberStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_number_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -782,7 +779,7 @@ void APIConnection::number_command(const NumberCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_DATETIME_DATE | #ifdef USE_DATETIME_DATE | ||||||
| bool APIConnection::send_date_state(datetime::DateEntity *date) { | bool APIConnection::send_date_state(datetime::DateEntity *date) { | ||||||
|   return this->schedule_message_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(date, &APIConnection::try_send_date_state, DateStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_date_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                             bool is_single) { |                                             bool is_single) { | ||||||
| @@ -816,7 +813,7 @@ void APIConnection::date_command(const DateCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_DATETIME_TIME | #ifdef USE_DATETIME_TIME | ||||||
| bool APIConnection::send_time_state(datetime::TimeEntity *time) { | bool APIConnection::send_time_state(datetime::TimeEntity *time) { | ||||||
|   return this->schedule_message_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(time, &APIConnection::try_send_time_state, TimeStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_time_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                             bool is_single) { |                                             bool is_single) { | ||||||
| @@ -850,7 +847,7 @@ void APIConnection::time_command(const TimeCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_DATETIME_DATETIME | #ifdef USE_DATETIME_DATETIME | ||||||
| bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { | bool APIConnection::send_datetime_state(datetime::DateTimeEntity *datetime) { | ||||||
|   return this->schedule_message_(datetime, &APIConnection::try_send_datetime_state, |   return this->send_message_smart_(datetime, &APIConnection::try_send_datetime_state, | ||||||
|                                    DateTimeStateResponse::MESSAGE_TYPE); |                                    DateTimeStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_datetime_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -886,7 +883,7 @@ void APIConnection::datetime_command(const DateTimeCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_TEXT | #ifdef USE_TEXT | ||||||
| bool APIConnection::send_text_state(text::Text *text) { | bool APIConnection::send_text_state(text::Text *text) { | ||||||
|   return this->schedule_message_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(text, &APIConnection::try_send_text_state, TextStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_text_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -924,7 +921,7 @@ void APIConnection::text_command(const TextCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_SELECT | #ifdef USE_SELECT | ||||||
| bool APIConnection::send_select_state(select::Select *select) { | bool APIConnection::send_select_state(select::Select *select) { | ||||||
|   return this->schedule_message_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(select, &APIConnection::try_send_select_state, SelectStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_select_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -979,7 +976,7 @@ void esphome::api::APIConnection::button_command(const ButtonCommandRequest &msg | |||||||
|  |  | ||||||
| #ifdef USE_LOCK | #ifdef USE_LOCK | ||||||
| bool APIConnection::send_lock_state(lock::Lock *a_lock) { | bool APIConnection::send_lock_state(lock::Lock *a_lock) { | ||||||
|   return this->schedule_message_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(a_lock, &APIConnection::try_send_lock_state, LockStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
|  |  | ||||||
| uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_lock_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -1023,7 +1020,7 @@ void APIConnection::lock_command(const LockCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_VALVE | #ifdef USE_VALVE | ||||||
| bool APIConnection::send_valve_state(valve::Valve *valve) { | bool APIConnection::send_valve_state(valve::Valve *valve) { | ||||||
|   return this->schedule_message_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(valve, &APIConnection::try_send_valve_state, ValveStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_valve_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                              bool is_single) { |                                              bool is_single) { | ||||||
| @@ -1063,7 +1060,7 @@ void APIConnection::valve_command(const ValveCommandRequest &msg) { | |||||||
|  |  | ||||||
| #ifdef USE_MEDIA_PLAYER | #ifdef USE_MEDIA_PLAYER | ||||||
| bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { | bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) { | ||||||
|   return this->schedule_message_(media_player, &APIConnection::try_send_media_player_state, |   return this->send_message_smart_(media_player, &APIConnection::try_send_media_player_state, | ||||||
|                                    MediaPlayerStateResponse::MESSAGE_TYPE); |                                    MediaPlayerStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_media_player_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
| @@ -1325,7 +1322,7 @@ void APIConnection::voice_assistant_set_configuration(const VoiceAssistantSetCon | |||||||
|  |  | ||||||
| #ifdef USE_ALARM_CONTROL_PANEL | #ifdef USE_ALARM_CONTROL_PANEL | ||||||
| bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | bool APIConnection::send_alarm_control_panel_state(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { | ||||||
|   return this->schedule_message_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, |   return this->send_message_smart_(a_alarm_control_panel, &APIConnection::try_send_alarm_control_panel_state, | ||||||
|                                    AlarmControlPanelStateResponse::MESSAGE_TYPE); |                                    AlarmControlPanelStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, | uint16_t APIConnection::try_send_alarm_control_panel_state(EntityBase *entity, APIConnection *conn, | ||||||
| @@ -1409,7 +1406,7 @@ uint16_t APIConnection::try_send_event_info(EntityBase *entity, APIConnection *c | |||||||
|  |  | ||||||
| #ifdef USE_UPDATE | #ifdef USE_UPDATE | ||||||
| bool APIConnection::send_update_state(update::UpdateEntity *update) { | bool APIConnection::send_update_state(update::UpdateEntity *update) { | ||||||
|   return this->schedule_message_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE); |   return this->send_message_smart_(update, &APIConnection::try_send_update_state, UpdateStateResponse::MESSAGE_TYPE); | ||||||
| } | } | ||||||
| uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | uint16_t APIConnection::try_send_update_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                               bool is_single) { |                                               bool is_single) { | ||||||
| @@ -1756,11 +1753,16 @@ void APIConnection::process_batch_() { | |||||||
|  |  | ||||||
|     if (payload_size > 0 && |     if (payload_size > 0 && | ||||||
|         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { |         this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, item.message_type)) { | ||||||
|       this->deferred_batch_.clear(); | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |       // Log messages after send attempt for VV debugging | ||||||
|  |       // It's safe to use the buffer for logging at this point regardless of send result | ||||||
|  |       this->log_batch_item_(item); | ||||||
|  | #endif | ||||||
|  |       this->clear_batch_(); | ||||||
|     } else if (payload_size == 0) { |     } else if (payload_size == 0) { | ||||||
|       // Message too large |       // Message too large | ||||||
|       ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type); |       ESP_LOGW(TAG, "Message too large to send: type=%u", item.message_type); | ||||||
|       this->deferred_batch_.clear(); |       this->clear_batch_(); | ||||||
|     } |     } | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -1869,7 +1871,7 @@ void APIConnection::process_batch_() { | |||||||
|     this->schedule_batch_(); |     this->schedule_batch_(); | ||||||
|   } else { |   } else { | ||||||
|     // All items processed |     // All items processed | ||||||
|     this->deferred_batch_.clear(); |     this->clear_batch_(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,6 +18,8 @@ namespace api { | |||||||
|  |  | ||||||
| // Keepalive timeout in milliseconds | // Keepalive timeout in milliseconds | ||||||
| static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; | static constexpr uint32_t KEEPALIVE_TIMEOUT_MS = 60000; | ||||||
|  | // Maximum number of entities to process in a single batch during initial state/info sending | ||||||
|  | static constexpr size_t MAX_INITIAL_PER_BATCH = 20; | ||||||
|  |  | ||||||
| class APIConnection : public APIServerConnection { | class APIConnection : public APIServerConnection { | ||||||
|  public: |  public: | ||||||
| @@ -296,6 +298,20 @@ class APIConnection : public APIServerConnection { | |||||||
|   static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, |   static uint16_t encode_message_to_buffer(ProtoMessage &msg, uint16_t message_type, APIConnection *conn, | ||||||
|                                            uint32_t remaining_size, bool is_single); |                                            uint32_t remaining_size, bool is_single); | ||||||
|  |  | ||||||
|  |   // Helper method to process multiple entities from an iterator in a batch | ||||||
|  |   template<typename Iterator> void process_iterator_batch_(Iterator &iterator) { | ||||||
|  |     size_t initial_size = this->deferred_batch_.size(); | ||||||
|  |     while (!iterator.completed() && (this->deferred_batch_.size() - initial_size) < MAX_INITIAL_PER_BATCH) { | ||||||
|  |       iterator.advance(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // If the batch is full, process it immediately | ||||||
|  |     // Note: iterator.advance() already calls schedule_batch_() via schedule_message_() | ||||||
|  |     if (this->deferred_batch_.size() >= MAX_INITIAL_PER_BATCH) { | ||||||
|  |       this->process_batch_(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
|   static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, |   static uint16_t try_send_binary_sensor_state(EntityBase *entity, APIConnection *conn, uint32_t remaining_size, | ||||||
|                                                bool is_single); |                                                bool is_single); | ||||||
| @@ -583,6 +599,7 @@ class APIConnection : public APIServerConnection { | |||||||
|     uint8_t next_close : 1; |     uint8_t next_close : 1; | ||||||
|     uint8_t batch_scheduled : 1; |     uint8_t batch_scheduled : 1; | ||||||
|     uint8_t batch_first_message : 1;          // For batch buffer allocation |     uint8_t batch_first_message : 1;          // For batch buffer allocation | ||||||
|  |     uint8_t should_try_send_immediately : 1;  // True after initial states are sent | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|     uint8_t log_only_mode : 1; |     uint8_t log_only_mode : 1; | ||||||
| #endif | #endif | ||||||
| @@ -609,11 +626,50 @@ class APIConnection : public APIServerConnection { | |||||||
|  |  | ||||||
|   bool schedule_batch_(); |   bool schedule_batch_(); | ||||||
|   void process_batch_(); |   void process_batch_(); | ||||||
|  |   void clear_batch_() { | ||||||
|  |     this->deferred_batch_.clear(); | ||||||
|  |     this->flags_.batch_scheduled = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
| #ifdef HAS_PROTO_MESSAGE_DUMP | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|   void log_batch_item_(const DeferredBatch::BatchItem &item); |   // Helper to log a proto message from a MessageCreator object | ||||||
|  |   void log_proto_message_(EntityBase *entity, const MessageCreator &creator, uint16_t message_type) { | ||||||
|  |     this->flags_.log_only_mode = true; | ||||||
|  |     creator(entity, this, MAX_PACKET_SIZE, true, message_type); | ||||||
|  |     this->flags_.log_only_mode = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void log_batch_item_(const DeferredBatch::BatchItem &item) { | ||||||
|  |     // Use the helper to log the message | ||||||
|  |     this->log_proto_message_(item.entity, item.creator, item.message_type); | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |   // Helper method to send a message either immediately or via batching | ||||||
|  |   bool send_message_smart_(EntityBase *entity, MessageCreatorPtr creator, uint16_t message_type) { | ||||||
|  |     // Try to send immediately if: | ||||||
|  |     // 1. We should try to send immediately (should_try_send_immediately = true) | ||||||
|  |     // 2. Batch delay is 0 (user has opted in to immediate sending) | ||||||
|  |     // 3. Buffer has space available | ||||||
|  |     if (this->flags_.should_try_send_immediately && this->get_batch_delay_ms_() == 0 && | ||||||
|  |         this->helper_->can_write_without_blocking()) { | ||||||
|  |       // Now actually encode and send | ||||||
|  |       if (creator(entity, this, MAX_PACKET_SIZE, true) && | ||||||
|  |           this->send_buffer(ProtoWriteBuffer{&this->parent_->get_shared_buffer_ref()}, message_type)) { | ||||||
|  | #ifdef HAS_PROTO_MESSAGE_DUMP | ||||||
|  |         // Log the message in verbose mode | ||||||
|  |         this->log_proto_message_(entity, MessageCreator(creator), message_type); | ||||||
|  | #endif | ||||||
|  |         return true; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // If immediate send failed, fall through to batching | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Fall back to scheduled batching | ||||||
|  |     return this->schedule_message_(entity, creator, message_type); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Helper function to schedule a deferred message with known message type |   // Helper function to schedule a deferred message with known message type | ||||||
|   bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) { |   bool schedule_message_(EntityBase *entity, MessageCreator creator, uint16_t message_type) { | ||||||
|     this->deferred_batch_.add_item(entity, std::move(creator), message_type); |     this->deferred_batch_.add_item(entity, std::move(creator), message_type); | ||||||
|   | |||||||
| @@ -5,16 +5,14 @@ namespace microphone { | |||||||
|  |  | ||||||
| void Microphone::add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback) { | void Microphone::add_data_callback(std::function<void(const std::vector<uint8_t> &)> &&data_callback) { | ||||||
|   std::function<void(const std::vector<uint8_t> &)> mute_handled_callback = |   std::function<void(const std::vector<uint8_t> &)> mute_handled_callback = | ||||||
|       [this, data_callback](const std::vector<uint8_t> &data) { data_callback(this->silence_audio_(data)); }; |       [this, data_callback](const std::vector<uint8_t> &data) { | ||||||
|   this->data_callbacks_.add(std::move(mute_handled_callback)); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| std::vector<uint8_t> Microphone::silence_audio_(std::vector<uint8_t> data) { |  | ||||||
|         if (this->mute_state_) { |         if (this->mute_state_) { | ||||||
|     std::memset((void *) data.data(), 0, data.size()); |           data_callback(std::vector<uint8_t>(data.size(), 0)); | ||||||
|   } |         } else { | ||||||
|  |           data_callback(data); | ||||||
|   return data; |         }; | ||||||
|  |       }; | ||||||
|  |   this->data_callbacks_.add(std::move(mute_handled_callback)); | ||||||
| } | } | ||||||
|  |  | ||||||
| }  // namespace microphone | }  // namespace microphone | ||||||
|   | |||||||
| @@ -33,8 +33,6 @@ class Microphone { | |||||||
|   audio::AudioStreamInfo get_audio_stream_info() { return this->audio_stream_info_; } |   audio::AudioStreamInfo get_audio_stream_info() { return this->audio_stream_info_; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   std::vector<uint8_t> silence_audio_(std::vector<uint8_t> data); |  | ||||||
|  |  | ||||||
|   State state_{STATE_STOPPED}; |   State state_{STATE_STOPPED}; | ||||||
|   bool mute_state_{false}; |   bool mute_state_{false}; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -63,6 +63,7 @@ BASE_SCHEMA = cv.All( | |||||||
|     cv.Schema( |     cv.Schema( | ||||||
|         { |         { | ||||||
|             cv.Required(CONF_URL): cv.url, |             cv.Required(CONF_URL): cv.url, | ||||||
|  |             cv.Optional(CONF_PATH): cv.string, | ||||||
|             cv.Optional(CONF_USERNAME): cv.string, |             cv.Optional(CONF_USERNAME): cv.string, | ||||||
|             cv.Optional(CONF_PASSWORD): cv.string, |             cv.Optional(CONF_PASSWORD): cv.string, | ||||||
|             cv.Exclusive(CONF_FILE, CONF_FILES): validate_yaml_filename, |             cv.Exclusive(CONF_FILE, CONF_FILES): validate_yaml_filename, | ||||||
| @@ -116,6 +117,9 @@ def _process_base_package(config: dict) -> dict: | |||||||
|     ) |     ) | ||||||
|     files = [] |     files = [] | ||||||
|  |  | ||||||
|  |     if base_path := config.get(CONF_PATH): | ||||||
|  |         repo_dir = repo_dir / base_path | ||||||
|  |  | ||||||
|     for file in config[CONF_FILES]: |     for file in config[CONF_FILES]: | ||||||
|         if isinstance(file, str): |         if isinstance(file, str): | ||||||
|             files.append({CONF_PATH: file, CONF_VARS: {}}) |             files.append({CONF_PATH: file, CONF_VARS: {}}) | ||||||
|   | |||||||
| @@ -1,19 +1,76 @@ | |||||||
| import esphome.codegen as cg | import esphome.codegen as cg | ||||||
| from esphome.components import binary_sensor | from esphome.components import binary_sensor | ||||||
| from esphome.const import CONF_ID | import esphome.config_validation as cv | ||||||
|  | from esphome.const import ( | ||||||
|  |     CONF_DATA, | ||||||
|  |     CONF_ID, | ||||||
|  |     CONF_NAME, | ||||||
|  |     CONF_STATUS, | ||||||
|  |     CONF_TYPE, | ||||||
|  |     DEVICE_CLASS_CONNECTIVITY, | ||||||
|  |     ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  | ) | ||||||
|  | import esphome.final_validate as fv | ||||||
|  |  | ||||||
| from . import ( | from . import ( | ||||||
|  |     CONF_ENCRYPTION, | ||||||
|  |     CONF_PING_PONG_ENABLE, | ||||||
|     CONF_PROVIDER, |     CONF_PROVIDER, | ||||||
|  |     CONF_PROVIDERS, | ||||||
|     CONF_REMOTE_ID, |     CONF_REMOTE_ID, | ||||||
|     CONF_TRANSPORT_ID, |     CONF_TRANSPORT_ID, | ||||||
|  |     PacketTransport, | ||||||
|     packet_transport_sensor_schema, |     packet_transport_sensor_schema, | ||||||
|  |     provider_name_validate, | ||||||
| ) | ) | ||||||
|  |  | ||||||
| CONFIG_SCHEMA = packet_transport_sensor_schema(binary_sensor.binary_sensor_schema()) | STATUS_SENSOR_SCHEMA = binary_sensor.binary_sensor_schema( | ||||||
|  |     device_class=DEVICE_CLASS_CONNECTIVITY, | ||||||
|  |     entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||||
|  | ).extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_TRANSPORT_ID): cv.use_id(PacketTransport), | ||||||
|  |         cv.Required(CONF_PROVIDER): provider_name_validate, | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = cv.typed_schema( | ||||||
|  |     { | ||||||
|  |         CONF_DATA: packet_transport_sensor_schema(binary_sensor.binary_sensor_schema()), | ||||||
|  |         CONF_STATUS: STATUS_SENSOR_SCHEMA, | ||||||
|  |     }, | ||||||
|  |     key=CONF_TYPE, | ||||||
|  |     default_type=CONF_DATA, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def _final_validate(config): | ||||||
|  |     if config[CONF_TYPE] != CONF_STATUS: | ||||||
|  |         # Only run this validation if a status sensor is being configured | ||||||
|  |         return config | ||||||
|  |     full_config = fv.full_config.get() | ||||||
|  |     transport_path = full_config.get_path_for_id(config[CONF_TRANSPORT_ID])[:-1] | ||||||
|  |     transport_config = full_config.get_config_for_path(transport_path) | ||||||
|  |     if transport_config[CONF_PING_PONG_ENABLE] and any( | ||||||
|  |         CONF_ENCRYPTION in p | ||||||
|  |         for p in transport_config[CONF_PROVIDERS] | ||||||
|  |         if p[CONF_NAME] == config[CONF_PROVIDER] | ||||||
|  |     ): | ||||||
|  |         return config | ||||||
|  |     raise cv.Invalid( | ||||||
|  |         "Status sensor requires ping-pong to be enabled and the nominated provider to use encryption." | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | FINAL_VALIDATE_SCHEMA = _final_validate | ||||||
|  |  | ||||||
|  |  | ||||||
| async def to_code(config): | async def to_code(config): | ||||||
|     var = await binary_sensor.new_binary_sensor(config) |     var = await binary_sensor.new_binary_sensor(config) | ||||||
|     comp = await cg.get_variable(config[CONF_TRANSPORT_ID]) |     comp = await cg.get_variable(config[CONF_TRANSPORT_ID]) | ||||||
|  |     if config[CONF_TYPE] == CONF_STATUS: | ||||||
|  |         cg.add(comp.set_provider_status_sensor(config[CONF_PROVIDER], var)) | ||||||
|  |         cg.add_define("USE_STATUS_SENSOR") | ||||||
|  |     else:  # CONF_DATA is default | ||||||
|         remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID)) |         remote_id = str(config.get(CONF_REMOTE_ID) or config.get(CONF_ID)) | ||||||
|         cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var)) |         cg.add(comp.add_remote_binary_sensor(config[CONF_PROVIDER], remote_id, var)) | ||||||
|   | |||||||
| @@ -317,8 +317,37 @@ void PacketTransport::update() { | |||||||
|   auto now = millis() / 1000; |   auto now = millis() / 1000; | ||||||
|   if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { |   if (this->last_key_time_ + this->ping_pong_recyle_time_ < now) { | ||||||
|     this->resend_ping_key_ = this->ping_pong_enable_; |     this->resend_ping_key_ = this->ping_pong_enable_; | ||||||
|  |     ESP_LOGV(TAG, "Ping request, age %u", now - this->last_key_time_); | ||||||
|     this->last_key_time_ = now; |     this->last_key_time_ = now; | ||||||
|   } |   } | ||||||
|  |   for (const auto &provider : this->providers_) { | ||||||
|  |     uint32_t key_response_age = now - provider.second.last_key_response_time; | ||||||
|  |     if (key_response_age > (this->ping_pong_recyle_time_ * 2u)) { | ||||||
|  | #ifdef USE_STATUS_SENSOR | ||||||
|  |       if (provider.second.status_sensor != nullptr && provider.second.status_sensor->state) { | ||||||
|  |         ESP_LOGI(TAG, "Ping status for %s timeout at %u with age %u", provider.first.c_str(), now, key_response_age); | ||||||
|  |         provider.second.status_sensor->publish_state(false); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_SENSOR | ||||||
|  |       for (auto &sensor : this->remote_sensors_[provider.first]) { | ||||||
|  |         sensor.second->publish_state(NAN); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  | #ifdef USE_BINARY_SENSOR | ||||||
|  |       for (auto &sensor : this->remote_binary_sensors_[provider.first]) { | ||||||
|  |         sensor.second->invalidate_state(); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |     } else { | ||||||
|  | #ifdef USE_STATUS_SENSOR | ||||||
|  |       if (provider.second.status_sensor != nullptr && !provider.second.status_sensor->state) { | ||||||
|  |         ESP_LOGI(TAG, "Ping status for %s restored at %u with age %u", provider.first.c_str(), now, key_response_age); | ||||||
|  |         provider.second.status_sensor->publish_state(true); | ||||||
|  |       } | ||||||
|  | #endif | ||||||
|  |     } | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| void PacketTransport::add_key_(const char *name, uint32_t key) { | void PacketTransport::add_key_(const char *name, uint32_t key) { | ||||||
| @@ -437,7 +466,8 @@ void PacketTransport::process_(const std::vector<uint8_t> &data) { | |||||||
|     if (decoder.decode(PING_KEY, key) == DECODE_OK) { |     if (decoder.decode(PING_KEY, key) == DECODE_OK) { | ||||||
|       if (key == this->ping_key_) { |       if (key == this->ping_key_) { | ||||||
|         ping_key_seen = true; |         ping_key_seen = true; | ||||||
|         ESP_LOGV(TAG, "Found good ping key %X", (unsigned) key); |         provider.last_key_response_time = millis() / 1000; | ||||||
|  |         ESP_LOGV(TAG, "Found good ping key %X at timestamp %" PRIu32, (unsigned) key, provider.last_key_response_time); | ||||||
|       } else { |       } else { | ||||||
|         ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key); |         ESP_LOGV(TAG, "Unknown ping key %X", (unsigned) key); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ | |||||||
| #ifdef USE_BINARY_SENSOR | #ifdef USE_BINARY_SENSOR | ||||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | #include "esphome/components/binary_sensor/binary_sensor.h" | ||||||
| #endif | #endif | ||||||
| # |  | ||||||
| #include <vector> | #include <vector> | ||||||
| #include <map> | #include <map> | ||||||
|  |  | ||||||
| @@ -27,6 +27,10 @@ struct Provider { | |||||||
|   std::vector<uint8_t> encryption_key; |   std::vector<uint8_t> encryption_key; | ||||||
|   const char *name; |   const char *name; | ||||||
|   uint32_t last_code[2]; |   uint32_t last_code[2]; | ||||||
|  |   uint32_t last_key_response_time; | ||||||
|  | #ifdef USE_STATUS_SENSOR | ||||||
|  |   binary_sensor::BinarySensor *status_sensor{nullptr}; | ||||||
|  | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| @@ -75,10 +79,7 @@ class PacketTransport : public PollingComponent { | |||||||
|  |  | ||||||
|   void add_provider(const char *hostname) { |   void add_provider(const char *hostname) { | ||||||
|     if (this->providers_.count(hostname) == 0) { |     if (this->providers_.count(hostname) == 0) { | ||||||
|       Provider provider; |       Provider provider{}; | ||||||
|       provider.encryption_key = std::vector<uint8_t>{}; |  | ||||||
|       provider.last_code[0] = 0; |  | ||||||
|       provider.last_code[1] = 0; |  | ||||||
|       provider.name = hostname; |       provider.name = hostname; | ||||||
|       this->providers_[hostname] = provider; |       this->providers_[hostname] = provider; | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| @@ -97,6 +98,11 @@ class PacketTransport : public PollingComponent { | |||||||
|   void set_provider_encryption(const char *name, std::vector<uint8_t> key) { |   void set_provider_encryption(const char *name, std::vector<uint8_t> key) { | ||||||
|     this->providers_[name].encryption_key = std::move(key); |     this->providers_[name].encryption_key = std::move(key); | ||||||
|   } |   } | ||||||
|  | #ifdef USE_STATUS_SENSOR | ||||||
|  |   void set_provider_status_sensor(const char *name, binary_sensor::BinarySensor *sensor) { | ||||||
|  |     this->providers_[name].status_sensor = sensor; | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|   void set_platform_name(const char *name) { this->platform_name_ = name; } |   void set_platform_name(const char *name) { this->platform_name_ = name; } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   | |||||||
							
								
								
									
										325
									
								
								esphome/components/sx127x/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										325
									
								
								esphome/components/sx127x/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,325 @@ | |||||||
|  | from esphome import automation, pins | ||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components import spi | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.const import CONF_DATA, CONF_FREQUENCY, CONF_ID | ||||||
|  |  | ||||||
|  | MULTI_CONF = True | ||||||
|  | CODEOWNERS = ["@swoboda1337"] | ||||||
|  | DEPENDENCIES = ["spi"] | ||||||
|  |  | ||||||
|  | CONF_SX127X_ID = "sx127x_id" | ||||||
|  |  | ||||||
|  | CONF_AUTO_CAL = "auto_cal" | ||||||
|  | CONF_BANDWIDTH = "bandwidth" | ||||||
|  | CONF_BITRATE = "bitrate" | ||||||
|  | CONF_BITSYNC = "bitsync" | ||||||
|  | CONF_CODING_RATE = "coding_rate" | ||||||
|  | CONF_CRC_ENABLE = "crc_enable" | ||||||
|  | CONF_DEVIATION = "deviation" | ||||||
|  | CONF_DIO0_PIN = "dio0_pin" | ||||||
|  | CONF_MODULATION = "modulation" | ||||||
|  | CONF_ON_PACKET = "on_packet" | ||||||
|  | CONF_PA_PIN = "pa_pin" | ||||||
|  | CONF_PA_POWER = "pa_power" | ||||||
|  | CONF_PA_RAMP = "pa_ramp" | ||||||
|  | CONF_PACKET_MODE = "packet_mode" | ||||||
|  | CONF_PAYLOAD_LENGTH = "payload_length" | ||||||
|  | CONF_PREAMBLE_DETECT = "preamble_detect" | ||||||
|  | CONF_PREAMBLE_ERRORS = "preamble_errors" | ||||||
|  | CONF_PREAMBLE_POLARITY = "preamble_polarity" | ||||||
|  | CONF_PREAMBLE_SIZE = "preamble_size" | ||||||
|  | CONF_RST_PIN = "rst_pin" | ||||||
|  | CONF_RX_FLOOR = "rx_floor" | ||||||
|  | CONF_RX_START = "rx_start" | ||||||
|  | CONF_SHAPING = "shaping" | ||||||
|  | CONF_SPREADING_FACTOR = "spreading_factor" | ||||||
|  | CONF_SYNC_VALUE = "sync_value" | ||||||
|  |  | ||||||
|  | sx127x_ns = cg.esphome_ns.namespace("sx127x") | ||||||
|  | SX127x = sx127x_ns.class_("SX127x", cg.Component, spi.SPIDevice) | ||||||
|  | SX127xListener = sx127x_ns.class_("SX127xListener") | ||||||
|  | SX127xBw = sx127x_ns.enum("SX127xBw") | ||||||
|  | SX127xOpMode = sx127x_ns.enum("SX127xOpMode") | ||||||
|  | SX127xPaConfig = sx127x_ns.enum("SX127xPaConfig") | ||||||
|  | SX127xPaRamp = sx127x_ns.enum("SX127xPaRamp") | ||||||
|  | SX127xModemCfg1 = sx127x_ns.enum("SX127xModemCfg1") | ||||||
|  |  | ||||||
|  | BW = { | ||||||
|  |     "2_6kHz": SX127xBw.SX127X_BW_2_6, | ||||||
|  |     "3_1kHz": SX127xBw.SX127X_BW_3_1, | ||||||
|  |     "3_9kHz": SX127xBw.SX127X_BW_3_9, | ||||||
|  |     "5_2kHz": SX127xBw.SX127X_BW_5_2, | ||||||
|  |     "6_3kHz": SX127xBw.SX127X_BW_6_3, | ||||||
|  |     "7_8kHz": SX127xBw.SX127X_BW_7_8, | ||||||
|  |     "10_4kHz": SX127xBw.SX127X_BW_10_4, | ||||||
|  |     "12_5kHz": SX127xBw.SX127X_BW_12_5, | ||||||
|  |     "15_6kHz": SX127xBw.SX127X_BW_15_6, | ||||||
|  |     "20_8kHz": SX127xBw.SX127X_BW_20_8, | ||||||
|  |     "25_0kHz": SX127xBw.SX127X_BW_25_0, | ||||||
|  |     "31_3kHz": SX127xBw.SX127X_BW_31_3, | ||||||
|  |     "41_7kHz": SX127xBw.SX127X_BW_41_7, | ||||||
|  |     "50_0kHz": SX127xBw.SX127X_BW_50_0, | ||||||
|  |     "62_5kHz": SX127xBw.SX127X_BW_62_5, | ||||||
|  |     "83_3kHz": SX127xBw.SX127X_BW_83_3, | ||||||
|  |     "100_0kHz": SX127xBw.SX127X_BW_100_0, | ||||||
|  |     "125_0kHz": SX127xBw.SX127X_BW_125_0, | ||||||
|  |     "166_7kHz": SX127xBw.SX127X_BW_166_7, | ||||||
|  |     "200_0kHz": SX127xBw.SX127X_BW_200_0, | ||||||
|  |     "250_0kHz": SX127xBw.SX127X_BW_250_0, | ||||||
|  |     "500_0kHz": SX127xBw.SX127X_BW_500_0, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CODING_RATE = { | ||||||
|  |     "CR_4_5": SX127xModemCfg1.CODING_RATE_4_5, | ||||||
|  |     "CR_4_6": SX127xModemCfg1.CODING_RATE_4_6, | ||||||
|  |     "CR_4_7": SX127xModemCfg1.CODING_RATE_4_7, | ||||||
|  |     "CR_4_8": SX127xModemCfg1.CODING_RATE_4_8, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | MOD = { | ||||||
|  |     "LORA": SX127xOpMode.MOD_LORA, | ||||||
|  |     "FSK": SX127xOpMode.MOD_FSK, | ||||||
|  |     "OOK": SX127xOpMode.MOD_OOK, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | PA_PIN = { | ||||||
|  |     "RFO": SX127xPaConfig.PA_PIN_RFO, | ||||||
|  |     "BOOST": SX127xPaConfig.PA_PIN_BOOST, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | RAMP = { | ||||||
|  |     "10us": SX127xPaRamp.PA_RAMP_10, | ||||||
|  |     "12us": SX127xPaRamp.PA_RAMP_12, | ||||||
|  |     "15us": SX127xPaRamp.PA_RAMP_15, | ||||||
|  |     "20us": SX127xPaRamp.PA_RAMP_20, | ||||||
|  |     "25us": SX127xPaRamp.PA_RAMP_25, | ||||||
|  |     "31us": SX127xPaRamp.PA_RAMP_31, | ||||||
|  |     "40us": SX127xPaRamp.PA_RAMP_40, | ||||||
|  |     "50us": SX127xPaRamp.PA_RAMP_50, | ||||||
|  |     "62us": SX127xPaRamp.PA_RAMP_62, | ||||||
|  |     "100us": SX127xPaRamp.PA_RAMP_100, | ||||||
|  |     "125us": SX127xPaRamp.PA_RAMP_125, | ||||||
|  |     "250us": SX127xPaRamp.PA_RAMP_250, | ||||||
|  |     "500us": SX127xPaRamp.PA_RAMP_500, | ||||||
|  |     "1000us": SX127xPaRamp.PA_RAMP_1000, | ||||||
|  |     "2000us": SX127xPaRamp.PA_RAMP_2000, | ||||||
|  |     "3400us": SX127xPaRamp.PA_RAMP_3400, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | SHAPING = { | ||||||
|  |     "CUTOFF_BR_X_2": SX127xPaRamp.CUTOFF_BR_X_2, | ||||||
|  |     "CUTOFF_BR_X_1": SX127xPaRamp.CUTOFF_BR_X_1, | ||||||
|  |     "GAUSSIAN_BT_0_3": SX127xPaRamp.GAUSSIAN_BT_0_3, | ||||||
|  |     "GAUSSIAN_BT_0_5": SX127xPaRamp.GAUSSIAN_BT_0_5, | ||||||
|  |     "GAUSSIAN_BT_1_0": SX127xPaRamp.GAUSSIAN_BT_1_0, | ||||||
|  |     "NONE": SX127xPaRamp.SHAPING_NONE, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | RunImageCalAction = sx127x_ns.class_( | ||||||
|  |     "RunImageCalAction", automation.Action, cg.Parented.template(SX127x) | ||||||
|  | ) | ||||||
|  | SendPacketAction = sx127x_ns.class_( | ||||||
|  |     "SendPacketAction", automation.Action, cg.Parented.template(SX127x) | ||||||
|  | ) | ||||||
|  | SetModeTxAction = sx127x_ns.class_( | ||||||
|  |     "SetModeTxAction", automation.Action, cg.Parented.template(SX127x) | ||||||
|  | ) | ||||||
|  | SetModeRxAction = sx127x_ns.class_( | ||||||
|  |     "SetModeRxAction", automation.Action, cg.Parented.template(SX127x) | ||||||
|  | ) | ||||||
|  | SetModeSleepAction = sx127x_ns.class_( | ||||||
|  |     "SetModeSleepAction", automation.Action, cg.Parented.template(SX127x) | ||||||
|  | ) | ||||||
|  | SetModeStandbyAction = sx127x_ns.class_( | ||||||
|  |     "SetModeStandbyAction", automation.Action, cg.Parented.template(SX127x) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_raw_data(value): | ||||||
|  |     if isinstance(value, str): | ||||||
|  |         return value.encode("utf-8") | ||||||
|  |     if isinstance(value, list): | ||||||
|  |         return cv.Schema([cv.hex_uint8_t])(value) | ||||||
|  |     raise cv.Invalid( | ||||||
|  |         "data must either be a string wrapped in quotes or a list of bytes" | ||||||
|  |     ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def validate_config(config): | ||||||
|  |     if config[CONF_MODULATION] == "LORA": | ||||||
|  |         bws = [ | ||||||
|  |             "7_8kHz", | ||||||
|  |             "10_4kHz", | ||||||
|  |             "15_6kHz", | ||||||
|  |             "20_8kHz", | ||||||
|  |             "31_3kHz", | ||||||
|  |             "41_7kHz", | ||||||
|  |             "62_5kHz", | ||||||
|  |             "125_0kHz", | ||||||
|  |             "250_0kHz", | ||||||
|  |             "500_0kHz", | ||||||
|  |         ] | ||||||
|  |         if config[CONF_BANDWIDTH] not in bws: | ||||||
|  |             raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is not available with LORA") | ||||||
|  |         if CONF_DIO0_PIN not in config: | ||||||
|  |             raise cv.Invalid("Cannot use LoRa without dio0_pin") | ||||||
|  |         if 0 < config[CONF_PREAMBLE_SIZE] < 6: | ||||||
|  |             raise cv.Invalid("Minimum preamble size is 6 with LORA") | ||||||
|  |         if config[CONF_SPREADING_FACTOR] == 6 and config[CONF_PAYLOAD_LENGTH] == 0: | ||||||
|  |             raise cv.Invalid("Payload length must be set when spreading factor is 6") | ||||||
|  |     else: | ||||||
|  |         if config[CONF_BANDWIDTH] == "500_0kHz": | ||||||
|  |             raise cv.Invalid(f"{config[CONF_BANDWIDTH]} is only available with LORA") | ||||||
|  |         if CONF_BITSYNC not in config: | ||||||
|  |             raise cv.Invalid("Config 'bitsync' required with FSK/OOK") | ||||||
|  |         if CONF_PACKET_MODE not in config: | ||||||
|  |             raise cv.Invalid("Config 'packet_mode' required with FSK/OOK") | ||||||
|  |         if config[CONF_PACKET_MODE] and CONF_DIO0_PIN not in config: | ||||||
|  |             raise cv.Invalid("Config 'dio0_pin' required in packet mode") | ||||||
|  |         if config[CONF_PAYLOAD_LENGTH] > 64: | ||||||
|  |             raise cv.Invalid("Payload length must be <= 64 with FSK/OOK") | ||||||
|  |     if config[CONF_PA_PIN] == "RFO" and config[CONF_PA_POWER] > 15: | ||||||
|  |         raise cv.Invalid("PA power must be <= 15 dbm when using the RFO pin") | ||||||
|  |     if config[CONF_PA_PIN] == "BOOST" and config[CONF_PA_POWER] < 2: | ||||||
|  |         raise cv.Invalid("PA power must be >= 2 dbm when using the BOOST pin") | ||||||
|  |     return config | ||||||
|  |  | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = ( | ||||||
|  |     cv.Schema( | ||||||
|  |         { | ||||||
|  |             cv.GenerateID(): cv.declare_id(SX127x), | ||||||
|  |             cv.Optional(CONF_AUTO_CAL, default=True): cv.boolean, | ||||||
|  |             cv.Optional(CONF_BANDWIDTH, default="125_0kHz"): cv.enum(BW), | ||||||
|  |             cv.Optional(CONF_BITRATE, default=4800): cv.int_range(min=500, max=300000), | ||||||
|  |             cv.Optional(CONF_BITSYNC): cv.boolean, | ||||||
|  |             cv.Optional(CONF_CODING_RATE, default="CR_4_5"): cv.enum(CODING_RATE), | ||||||
|  |             cv.Optional(CONF_CRC_ENABLE, default=False): cv.boolean, | ||||||
|  |             cv.Optional(CONF_DEVIATION, default=5000): cv.int_range(min=0, max=100000), | ||||||
|  |             cv.Optional(CONF_DIO0_PIN): pins.internal_gpio_input_pin_schema, | ||||||
|  |             cv.Required(CONF_FREQUENCY): cv.int_range(min=137000000, max=1020000000), | ||||||
|  |             cv.Required(CONF_MODULATION): cv.enum(MOD), | ||||||
|  |             cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), | ||||||
|  |             cv.Optional(CONF_PA_PIN, default="BOOST"): cv.enum(PA_PIN), | ||||||
|  |             cv.Optional(CONF_PA_POWER, default=17): cv.int_range(min=0, max=17), | ||||||
|  |             cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP), | ||||||
|  |             cv.Optional(CONF_PACKET_MODE): cv.boolean, | ||||||
|  |             cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256), | ||||||
|  |             cv.Optional(CONF_PREAMBLE_DETECT, default=0): cv.int_range(min=0, max=3), | ||||||
|  |             cv.Optional(CONF_PREAMBLE_ERRORS, default=0): cv.int_range(min=0, max=31), | ||||||
|  |             cv.Optional(CONF_PREAMBLE_POLARITY, default=0xAA): cv.All( | ||||||
|  |                 cv.hex_int, cv.one_of(0xAA, 0x55) | ||||||
|  |             ), | ||||||
|  |             cv.Optional(CONF_PREAMBLE_SIZE, default=0): cv.int_range(min=0, max=65535), | ||||||
|  |             cv.Required(CONF_RST_PIN): pins.internal_gpio_output_pin_schema, | ||||||
|  |             cv.Optional(CONF_RX_FLOOR, default=-94): cv.float_range(min=-128, max=-1), | ||||||
|  |             cv.Optional(CONF_RX_START, default=True): cv.boolean, | ||||||
|  |             cv.Optional(CONF_SHAPING, default="NONE"): cv.enum(SHAPING), | ||||||
|  |             cv.Optional(CONF_SPREADING_FACTOR, default=7): cv.int_range(min=6, max=12), | ||||||
|  |             cv.Optional(CONF_SYNC_VALUE, default=[]): cv.ensure_list(cv.hex_uint8_t), | ||||||
|  |         }, | ||||||
|  |     ) | ||||||
|  |     .extend(cv.COMPONENT_SCHEMA) | ||||||
|  |     .extend(spi.spi_device_schema(True, 8e6, "mode0")) | ||||||
|  |     .add_extra(validate_config) | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var = cg.new_Pvariable(config[CONF_ID]) | ||||||
|  |     await cg.register_component(var, config) | ||||||
|  |     await spi.register_spi_device(var, config) | ||||||
|  |     if CONF_ON_PACKET in config: | ||||||
|  |         await automation.build_automation( | ||||||
|  |             var.get_packet_trigger(), | ||||||
|  |             [ | ||||||
|  |                 (cg.std_vector.template(cg.uint8), "x"), | ||||||
|  |                 (cg.float_, "rssi"), | ||||||
|  |                 (cg.float_, "snr"), | ||||||
|  |             ], | ||||||
|  |             config[CONF_ON_PACKET], | ||||||
|  |         ) | ||||||
|  |     if CONF_DIO0_PIN in config: | ||||||
|  |         dio0_pin = await cg.gpio_pin_expression(config[CONF_DIO0_PIN]) | ||||||
|  |         cg.add(var.set_dio0_pin(dio0_pin)) | ||||||
|  |     rst_pin = await cg.gpio_pin_expression(config[CONF_RST_PIN]) | ||||||
|  |     cg.add(var.set_rst_pin(rst_pin)) | ||||||
|  |     cg.add(var.set_auto_cal(config[CONF_AUTO_CAL])) | ||||||
|  |     cg.add(var.set_bandwidth(config[CONF_BANDWIDTH])) | ||||||
|  |     cg.add(var.set_frequency(config[CONF_FREQUENCY])) | ||||||
|  |     cg.add(var.set_deviation(config[CONF_DEVIATION])) | ||||||
|  |     cg.add(var.set_modulation(config[CONF_MODULATION])) | ||||||
|  |     if config[CONF_MODULATION] != "LORA": | ||||||
|  |         cg.add(var.set_bitrate(config[CONF_BITRATE])) | ||||||
|  |         cg.add(var.set_bitsync(config[CONF_BITSYNC])) | ||||||
|  |         cg.add(var.set_packet_mode(config[CONF_PACKET_MODE])) | ||||||
|  |     cg.add(var.set_pa_pin(config[CONF_PA_PIN])) | ||||||
|  |     cg.add(var.set_pa_ramp(config[CONF_PA_RAMP])) | ||||||
|  |     cg.add(var.set_pa_power(config[CONF_PA_POWER])) | ||||||
|  |     cg.add(var.set_shaping(config[CONF_SHAPING])) | ||||||
|  |     cg.add(var.set_crc_enable(config[CONF_CRC_ENABLE])) | ||||||
|  |     cg.add(var.set_payload_length(config[CONF_PAYLOAD_LENGTH])) | ||||||
|  |     cg.add(var.set_preamble_detect(config[CONF_PREAMBLE_DETECT])) | ||||||
|  |     cg.add(var.set_preamble_size(config[CONF_PREAMBLE_SIZE])) | ||||||
|  |     cg.add(var.set_preamble_polarity(config[CONF_PREAMBLE_POLARITY])) | ||||||
|  |     cg.add(var.set_preamble_errors(config[CONF_PREAMBLE_ERRORS])) | ||||||
|  |     cg.add(var.set_coding_rate(config[CONF_CODING_RATE])) | ||||||
|  |     cg.add(var.set_spreading_factor(config[CONF_SPREADING_FACTOR])) | ||||||
|  |     cg.add(var.set_sync_value(config[CONF_SYNC_VALUE])) | ||||||
|  |     cg.add(var.set_rx_floor(config[CONF_RX_FLOOR])) | ||||||
|  |     cg.add(var.set_rx_start(config[CONF_RX_START])) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | NO_ARGS_ACTION_SCHEMA = automation.maybe_simple_id( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.use_id(SX127x), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "sx127x.run_image_cal", RunImageCalAction, NO_ARGS_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "sx127x.set_mode_tx", SetModeTxAction, NO_ARGS_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "sx127x.set_mode_rx", SetModeRxAction, NO_ARGS_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "sx127x.set_mode_sleep", SetModeSleepAction, NO_ARGS_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | @automation.register_action( | ||||||
|  |     "sx127x.set_mode_standby", SetModeStandbyAction, NO_ARGS_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | async def no_args_action_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     await cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     return var | ||||||
|  |  | ||||||
|  |  | ||||||
|  | SEND_PACKET_ACTION_SCHEMA = cv.maybe_simple_value( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(): cv.use_id(SX127x), | ||||||
|  |         cv.Required(CONF_DATA): cv.templatable(validate_raw_data), | ||||||
|  |     }, | ||||||
|  |     key=CONF_DATA, | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @automation.register_action( | ||||||
|  |     "sx127x.send_packet", SendPacketAction, SEND_PACKET_ACTION_SCHEMA | ||||||
|  | ) | ||||||
|  | async def send_packet_action_to_code(config, action_id, template_arg, args): | ||||||
|  |     var = cg.new_Pvariable(action_id, template_arg) | ||||||
|  |     await cg.register_parented(var, config[CONF_ID]) | ||||||
|  |     data = config[CONF_DATA] | ||||||
|  |     if isinstance(data, bytes): | ||||||
|  |         data = list(data) | ||||||
|  |     if cg.is_template(data): | ||||||
|  |         templ = await cg.templatable(data, args, cg.std_vector.template(cg.uint8)) | ||||||
|  |         cg.add(var.set_data_template(templ)) | ||||||
|  |     else: | ||||||
|  |         cg.add(var.set_data_static(data)) | ||||||
|  |     return var | ||||||
							
								
								
									
										62
									
								
								esphome/components/sx127x/automation.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								esphome/components/sx127x/automation.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/components/sx127x/sx127x.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace sx127x { | ||||||
|  |  | ||||||
|  | template<typename... Ts> class RunImageCalAction : public Action<Ts...>, public Parented<SX127x> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->run_image_cal(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SendPacketAction : public Action<Ts...>, public Parented<SX127x> { | ||||||
|  |  public: | ||||||
|  |   void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) { | ||||||
|  |     this->data_func_ = func; | ||||||
|  |     this->static_ = false; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void set_data_static(const std::vector<uint8_t> &data) { | ||||||
|  |     this->data_static_ = data; | ||||||
|  |     this->static_ = true; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void play(Ts... x) override { | ||||||
|  |     if (this->static_) { | ||||||
|  |       this->parent_->transmit_packet(this->data_static_); | ||||||
|  |     } else { | ||||||
|  |       this->parent_->transmit_packet(this->data_func_(x...)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   bool static_{false}; | ||||||
|  |   std::function<std::vector<uint8_t>(Ts...)> data_func_{}; | ||||||
|  |   std::vector<uint8_t> data_static_{}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SetModeTxAction : public Action<Ts...>, public Parented<SX127x> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->set_mode_tx(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SetModeRxAction : public Action<Ts...>, public Parented<SX127x> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->set_mode_rx(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SetModeSleepAction : public Action<Ts...>, public Parented<SX127x> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->set_mode_sleep(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | template<typename... Ts> class SetModeStandbyAction : public Action<Ts...>, public Parented<SX127x> { | ||||||
|  |  public: | ||||||
|  |   void play(Ts... x) override { this->parent_->set_mode_standby(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace sx127x | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										26
									
								
								esphome/components/sx127x/packet_transport/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								esphome/components/sx127x/packet_transport/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,26 @@ | |||||||
|  | import esphome.codegen as cg | ||||||
|  | from esphome.components.packet_transport import ( | ||||||
|  |     PacketTransport, | ||||||
|  |     new_packet_transport, | ||||||
|  |     transport_schema, | ||||||
|  | ) | ||||||
|  | import esphome.config_validation as cv | ||||||
|  | from esphome.cpp_types import PollingComponent | ||||||
|  |  | ||||||
|  | from .. import CONF_SX127X_ID, SX127x, SX127xListener, sx127x_ns | ||||||
|  |  | ||||||
|  | SX127xTransport = sx127x_ns.class_( | ||||||
|  |     "SX127xTransport", PacketTransport, PollingComponent, SX127xListener | ||||||
|  | ) | ||||||
|  |  | ||||||
|  | CONFIG_SCHEMA = transport_schema(SX127xTransport).extend( | ||||||
|  |     { | ||||||
|  |         cv.GenerateID(CONF_SX127X_ID): cv.use_id(SX127x), | ||||||
|  |     } | ||||||
|  | ) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | async def to_code(config): | ||||||
|  |     var, _ = await new_packet_transport(config) | ||||||
|  |     sx127x = await cg.get_variable(config[CONF_SX127X_ID]) | ||||||
|  |     cg.add(var.set_parent(sx127x)) | ||||||
| @@ -0,0 +1,26 @@ | |||||||
|  | #include "esphome/core/log.h" | ||||||
|  | #include "esphome/core/application.h" | ||||||
|  | #include "sx127x_transport.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace sx127x { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "sx127x_transport"; | ||||||
|  |  | ||||||
|  | void SX127xTransport::setup() { | ||||||
|  |   PacketTransport::setup(); | ||||||
|  |   this->parent_->register_listener(this); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127xTransport::update() { | ||||||
|  |   PacketTransport::update(); | ||||||
|  |   this->updated_ = true; | ||||||
|  |   this->resend_data_ = true; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127xTransport::send_packet(const std::vector<uint8_t> &buf) const { this->parent_->transmit_packet(buf); } | ||||||
|  |  | ||||||
|  | void SX127xTransport::on_packet(const std::vector<uint8_t> &packet, float rssi, float snr) { this->process_(packet); } | ||||||
|  |  | ||||||
|  | }  // namespace sx127x | ||||||
|  | }  // namespace esphome | ||||||
| @@ -0,0 +1,25 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include "esphome/components/sx127x/sx127x.h" | ||||||
|  | #include "esphome/components/packet_transport/packet_transport.h" | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace sx127x { | ||||||
|  |  | ||||||
|  | class SX127xTransport : public packet_transport::PacketTransport, public Parented<SX127x>, public SX127xListener { | ||||||
|  |  public: | ||||||
|  |   void setup() override; | ||||||
|  |   void update() override; | ||||||
|  |   void on_packet(const std::vector<uint8_t> &packet, float rssi, float snr) override; | ||||||
|  |   float get_setup_priority() const override { return setup_priority::AFTER_WIFI; } | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void send_packet(const std::vector<uint8_t> &buf) const override; | ||||||
|  |   bool should_send() override { return true; } | ||||||
|  |   size_t get_max_packet_size() override { return this->parent_->get_max_packet_size(); } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace sx127x | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										493
									
								
								esphome/components/sx127x/sx127x.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										493
									
								
								esphome/components/sx127x/sx127x.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,493 @@ | |||||||
|  | #include "sx127x.h" | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  | #include "esphome/core/log.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace sx127x { | ||||||
|  |  | ||||||
|  | static const char *const TAG = "sx127x"; | ||||||
|  | static const uint32_t FXOSC = 32000000u; | ||||||
|  | static const uint16_t RAMP[16] = {3400, 2000, 1000, 500, 250, 125, 100, 62, 50, 40, 31, 25, 20, 15, 12, 10}; | ||||||
|  | static const uint32_t BW_HZ[22] = {2604,  3125,  3906,  5208,  6250,  7812,   10416,  12500,  15625,  20833,  25000, | ||||||
|  |                                    31250, 41666, 50000, 62500, 83333, 100000, 125000, 166666, 200000, 250000, 500000}; | ||||||
|  | static const uint8_t BW_LORA[22] = {BW_7_8,   BW_7_8,   BW_7_8,   BW_7_8,   BW_7_8,   BW_7_8,  BW_10_4, BW_15_6, | ||||||
|  |                                     BW_15_6,  BW_20_8,  BW_31_3,  BW_31_3,  BW_41_7,  BW_62_5, BW_62_5, BW_125_0, | ||||||
|  |                                     BW_125_0, BW_125_0, BW_250_0, BW_250_0, BW_250_0, BW_500_0}; | ||||||
|  | static const uint8_t BW_FSK_OOK[22] = {RX_BW_2_6,   RX_BW_3_1,   RX_BW_3_9,   RX_BW_5_2,  RX_BW_6_3,   RX_BW_7_8, | ||||||
|  |                                        RX_BW_10_4,  RX_BW_12_5,  RX_BW_15_6,  RX_BW_20_8, RX_BW_25_0,  RX_BW_31_3, | ||||||
|  |                                        RX_BW_41_7,  RX_BW_50_0,  RX_BW_62_5,  RX_BW_83_3, RX_BW_100_0, RX_BW_125_0, | ||||||
|  |                                        RX_BW_166_7, RX_BW_200_0, RX_BW_250_0, RX_BW_250_0}; | ||||||
|  | static const int32_t RSSI_OFFSET_HF = 157; | ||||||
|  | static const int32_t RSSI_OFFSET_LF = 164; | ||||||
|  |  | ||||||
|  | uint8_t SX127x::read_register_(uint8_t reg) { | ||||||
|  |   this->enable(); | ||||||
|  |   this->write_byte(reg & 0x7F); | ||||||
|  |   uint8_t value = this->read_byte(); | ||||||
|  |   this->disable(); | ||||||
|  |   return value; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::write_register_(uint8_t reg, uint8_t value) { | ||||||
|  |   this->enable(); | ||||||
|  |   this->write_byte(reg | 0x80); | ||||||
|  |   this->write_byte(value); | ||||||
|  |   this->disable(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::read_fifo_(std::vector<uint8_t> &packet) { | ||||||
|  |   this->enable(); | ||||||
|  |   this->write_byte(REG_FIFO & 0x7F); | ||||||
|  |   this->read_array(packet.data(), packet.size()); | ||||||
|  |   this->disable(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::write_fifo_(const std::vector<uint8_t> &packet) { | ||||||
|  |   this->enable(); | ||||||
|  |   this->write_byte(REG_FIFO | 0x80); | ||||||
|  |   this->write_array(packet.data(), packet.size()); | ||||||
|  |   this->disable(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::setup() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "Running setup"); | ||||||
|  |  | ||||||
|  |   // setup reset | ||||||
|  |   this->rst_pin_->setup(); | ||||||
|  |  | ||||||
|  |   // setup dio0 | ||||||
|  |   if (this->dio0_pin_) { | ||||||
|  |     this->dio0_pin_->setup(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // start spi | ||||||
|  |   this->spi_setup(); | ||||||
|  |  | ||||||
|  |   // configure rf | ||||||
|  |   this->configure(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::configure() { | ||||||
|  |   // toggle chip reset | ||||||
|  |   this->rst_pin_->digital_write(false); | ||||||
|  |   delayMicroseconds(1000); | ||||||
|  |   this->rst_pin_->digital_write(true); | ||||||
|  |   delayMicroseconds(10000); | ||||||
|  |  | ||||||
|  |   // check silicon version to make sure hw is ok | ||||||
|  |   if (this->read_register_(REG_VERSION) != 0x12) { | ||||||
|  |     this->mark_failed(); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // enter sleep mode | ||||||
|  |   this->set_mode_(MOD_FSK, MODE_SLEEP); | ||||||
|  |  | ||||||
|  |   // set freq | ||||||
|  |   uint64_t frf = ((uint64_t) this->frequency_ << 19) / FXOSC; | ||||||
|  |   this->write_register_(REG_FRF_MSB, (uint8_t) ((frf >> 16) & 0xFF)); | ||||||
|  |   this->write_register_(REG_FRF_MID, (uint8_t) ((frf >> 8) & 0xFF)); | ||||||
|  |   this->write_register_(REG_FRF_LSB, (uint8_t) ((frf >> 0) & 0xFF)); | ||||||
|  |  | ||||||
|  |   // enter standby mode | ||||||
|  |   this->set_mode_(MOD_FSK, MODE_STDBY); | ||||||
|  |  | ||||||
|  |   // run image cal | ||||||
|  |   this->run_image_cal(); | ||||||
|  |  | ||||||
|  |   // go back to sleep | ||||||
|  |   this->set_mode_sleep(); | ||||||
|  |  | ||||||
|  |   // config pa | ||||||
|  |   if (this->pa_pin_ == PA_PIN_BOOST) { | ||||||
|  |     this->pa_power_ = std::max(this->pa_power_, (uint8_t) 2); | ||||||
|  |     this->pa_power_ = std::min(this->pa_power_, (uint8_t) 17); | ||||||
|  |     this->write_register_(REG_PA_CONFIG, (this->pa_power_ - 2) | this->pa_pin_ | PA_MAX_POWER); | ||||||
|  |   } else { | ||||||
|  |     this->pa_power_ = std::min(this->pa_power_, (uint8_t) 14); | ||||||
|  |     this->write_register_(REG_PA_CONFIG, (this->pa_power_ - 0) | this->pa_pin_ | PA_MAX_POWER); | ||||||
|  |   } | ||||||
|  |   if (this->modulation_ != MOD_LORA) { | ||||||
|  |     this->write_register_(REG_PA_RAMP, this->pa_ramp_ | this->shaping_); | ||||||
|  |   } else { | ||||||
|  |     this->write_register_(REG_PA_RAMP, this->pa_ramp_); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // configure modem | ||||||
|  |   if (this->modulation_ != MOD_LORA) { | ||||||
|  |     this->configure_fsk_ook_(); | ||||||
|  |   } else { | ||||||
|  |     this->configure_lora_(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // switch to rx or sleep | ||||||
|  |   if (this->rx_start_) { | ||||||
|  |     this->set_mode_rx(); | ||||||
|  |   } else { | ||||||
|  |     this->set_mode_sleep(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::configure_fsk_ook_() { | ||||||
|  |   // set the channel bw | ||||||
|  |   this->write_register_(REG_RX_BW, BW_FSK_OOK[this->bandwidth_]); | ||||||
|  |  | ||||||
|  |   // set fdev | ||||||
|  |   uint32_t fdev = std::min((this->deviation_ * 4096) / 250000, (uint32_t) 0x3FFF); | ||||||
|  |   this->write_register_(REG_FDEV_MSB, (uint8_t) ((fdev >> 8) & 0xFF)); | ||||||
|  |   this->write_register_(REG_FDEV_LSB, (uint8_t) ((fdev >> 0) & 0xFF)); | ||||||
|  |  | ||||||
|  |   // set bitrate | ||||||
|  |   uint64_t bitrate = (FXOSC + this->bitrate_ / 2) / this->bitrate_;  // round up | ||||||
|  |   this->write_register_(REG_BITRATE_MSB, (uint8_t) ((bitrate >> 8) & 0xFF)); | ||||||
|  |   this->write_register_(REG_BITRATE_LSB, (uint8_t) ((bitrate >> 0) & 0xFF)); | ||||||
|  |  | ||||||
|  |   // configure rx and afc | ||||||
|  |   uint8_t trigger = (this->preamble_detect_ > 0) ? TRIGGER_PREAMBLE : TRIGGER_RSSI; | ||||||
|  |   this->write_register_(REG_AFC_FEI, AFC_AUTO_CLEAR_ON); | ||||||
|  |   if (this->modulation_ == MOD_FSK) { | ||||||
|  |     this->write_register_(REG_RX_CONFIG, AFC_AUTO_ON | AGC_AUTO_ON | trigger); | ||||||
|  |   } else { | ||||||
|  |     this->write_register_(REG_RX_CONFIG, AGC_AUTO_ON | trigger); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // configure packet mode | ||||||
|  |   if (this->packet_mode_) { | ||||||
|  |     uint8_t crc_mode = (this->crc_enable_) ? CRC_ON : CRC_OFF; | ||||||
|  |     this->write_register_(REG_FIFO_THRESH, TX_START_FIFO_EMPTY); | ||||||
|  |     if (this->payload_length_ > 0) { | ||||||
|  |       this->write_register_(REG_PAYLOAD_LENGTH_LSB, this->payload_length_); | ||||||
|  |       this->write_register_(REG_PACKET_CONFIG_1, crc_mode | FIXED_LENGTH); | ||||||
|  |     } else { | ||||||
|  |       this->write_register_(REG_PAYLOAD_LENGTH_LSB, this->get_max_packet_size() - 1); | ||||||
|  |       this->write_register_(REG_PACKET_CONFIG_1, crc_mode | VARIABLE_LENGTH); | ||||||
|  |     } | ||||||
|  |     this->write_register_(REG_PACKET_CONFIG_2, PACKET_MODE); | ||||||
|  |   } else { | ||||||
|  |     this->write_register_(REG_PACKET_CONFIG_2, CONTINUOUS_MODE); | ||||||
|  |   } | ||||||
|  |   this->write_register_(REG_DIO_MAPPING1, DIO0_MAPPING_00); | ||||||
|  |  | ||||||
|  |   // config bit synchronizer | ||||||
|  |   uint8_t polarity = (this->preamble_polarity_ == 0xAA) ? PREAMBLE_AA : PREAMBLE_55; | ||||||
|  |   if (!this->sync_value_.empty()) { | ||||||
|  |     uint8_t size = this->sync_value_.size() - 1; | ||||||
|  |     this->write_register_(REG_SYNC_CONFIG, AUTO_RESTART_PLL_LOCK | polarity | SYNC_ON | size); | ||||||
|  |     for (uint32_t i = 0; i < this->sync_value_.size(); i++) { | ||||||
|  |       this->write_register_(REG_SYNC_VALUE1 + i, this->sync_value_[i]); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     this->write_register_(REG_SYNC_CONFIG, AUTO_RESTART_PLL_LOCK | polarity); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // config preamble detector | ||||||
|  |   if (this->preamble_detect_ > 0) { | ||||||
|  |     uint8_t size = (this->preamble_detect_ - 1) << PREAMBLE_DETECTOR_SIZE_SHIFT; | ||||||
|  |     uint8_t tol = this->preamble_errors_ << PREAMBLE_DETECTOR_TOL_SHIFT; | ||||||
|  |     this->write_register_(REG_PREAMBLE_DETECT, PREAMBLE_DETECTOR_ON | size | tol); | ||||||
|  |   } else { | ||||||
|  |     this->write_register_(REG_PREAMBLE_DETECT, PREAMBLE_DETECTOR_OFF); | ||||||
|  |   } | ||||||
|  |   this->write_register_(REG_PREAMBLE_SIZE_MSB, this->preamble_size_ >> 16); | ||||||
|  |   this->write_register_(REG_PREAMBLE_SIZE_LSB, this->preamble_size_ & 0xFF); | ||||||
|  |  | ||||||
|  |   // config sync generation and setup ook threshold | ||||||
|  |   uint8_t bitsync = this->bitsync_ ? BIT_SYNC_ON : BIT_SYNC_OFF; | ||||||
|  |   this->write_register_(REG_OOK_PEAK, bitsync | OOK_THRESH_STEP_0_5 | OOK_THRESH_PEAK); | ||||||
|  |   this->write_register_(REG_OOK_AVG, OOK_AVG_RESERVED | OOK_THRESH_DEC_1_8); | ||||||
|  |  | ||||||
|  |   // set rx floor | ||||||
|  |   this->write_register_(REG_OOK_FIX, 256 + int(this->rx_floor_ * 2.0)); | ||||||
|  |   this->write_register_(REG_RSSI_THRESH, std::abs(int(this->rx_floor_ * 2.0))); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::configure_lora_() { | ||||||
|  |   // config modem | ||||||
|  |   uint8_t header_mode = this->payload_length_ > 0 ? IMPLICIT_HEADER : EXPLICIT_HEADER; | ||||||
|  |   uint8_t crc_mode = (this->crc_enable_) ? RX_PAYLOAD_CRC_ON : RX_PAYLOAD_CRC_OFF; | ||||||
|  |   uint8_t spreading_factor = this->spreading_factor_ << SPREADING_FACTOR_SHIFT; | ||||||
|  |   this->write_register_(REG_MODEM_CONFIG1, BW_LORA[this->bandwidth_] | this->coding_rate_ | header_mode); | ||||||
|  |   this->write_register_(REG_MODEM_CONFIG2, spreading_factor | crc_mode); | ||||||
|  |  | ||||||
|  |   // config fifo and payload length | ||||||
|  |   this->write_register_(REG_FIFO_TX_BASE_ADDR, 0x00); | ||||||
|  |   this->write_register_(REG_FIFO_RX_BASE_ADDR, 0x00); | ||||||
|  |   this->write_register_(REG_PAYLOAD_LENGTH, std::max(this->payload_length_, (uint32_t) 1)); | ||||||
|  |  | ||||||
|  |   // config preamble | ||||||
|  |   if (this->preamble_size_ >= 6) { | ||||||
|  |     this->write_register_(REG_PREAMBLE_LEN_MSB, this->preamble_size_ >> 16); | ||||||
|  |     this->write_register_(REG_PREAMBLE_LEN_LSB, this->preamble_size_ & 0xFF); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // optimize detection | ||||||
|  |   float duration = 1000.0f * std::pow(2, this->spreading_factor_) / BW_HZ[this->bandwidth_]; | ||||||
|  |   if (duration > 16) { | ||||||
|  |     this->write_register_(REG_MODEM_CONFIG3, MODEM_AGC_AUTO_ON | LOW_DATA_RATE_OPTIMIZE_ON); | ||||||
|  |   } else { | ||||||
|  |     this->write_register_(REG_MODEM_CONFIG3, MODEM_AGC_AUTO_ON); | ||||||
|  |   } | ||||||
|  |   if (this->spreading_factor_ == 6) { | ||||||
|  |     this->write_register_(REG_DETECT_OPTIMIZE, 0xC5); | ||||||
|  |     this->write_register_(REG_DETECT_THRESHOLD, 0x0C); | ||||||
|  |   } else { | ||||||
|  |     this->write_register_(REG_DETECT_OPTIMIZE, 0xC3); | ||||||
|  |     this->write_register_(REG_DETECT_THRESHOLD, 0x0A); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   // config sync word | ||||||
|  |   if (!this->sync_value_.empty()) { | ||||||
|  |     this->write_register_(REG_SYNC_WORD, this->sync_value_[0]); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | size_t SX127x::get_max_packet_size() { | ||||||
|  |   if (this->payload_length_ > 0) { | ||||||
|  |     return this->payload_length_; | ||||||
|  |   } | ||||||
|  |   if (this->modulation_ == MOD_LORA) { | ||||||
|  |     return 256; | ||||||
|  |   } else { | ||||||
|  |     return 64; | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::transmit_packet(const std::vector<uint8_t> &packet) { | ||||||
|  |   if (this->payload_length_ > 0 && this->payload_length_ != packet.size()) { | ||||||
|  |     ESP_LOGE(TAG, "Packet size does not match config"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (packet.empty() || packet.size() > this->get_max_packet_size()) { | ||||||
|  |     ESP_LOGE(TAG, "Packet size out of range"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (this->modulation_ == MOD_LORA) { | ||||||
|  |     this->set_mode_standby(); | ||||||
|  |     if (this->payload_length_ == 0) { | ||||||
|  |       this->write_register_(REG_PAYLOAD_LENGTH, packet.size()); | ||||||
|  |     } | ||||||
|  |     this->write_register_(REG_IRQ_FLAGS, 0xFF); | ||||||
|  |     this->write_register_(REG_FIFO_ADDR_PTR, 0); | ||||||
|  |     this->write_fifo_(packet); | ||||||
|  |     this->set_mode_tx(); | ||||||
|  |   } else { | ||||||
|  |     this->set_mode_standby(); | ||||||
|  |     if (this->payload_length_ == 0) { | ||||||
|  |       this->write_register_(REG_FIFO, packet.size()); | ||||||
|  |     } | ||||||
|  |     this->write_fifo_(packet); | ||||||
|  |     this->set_mode_tx(); | ||||||
|  |   } | ||||||
|  |   // wait until transmit completes, typically the delay will be less than 100 ms | ||||||
|  |   uint32_t start = millis(); | ||||||
|  |   while (!this->dio0_pin_->digital_read()) { | ||||||
|  |     if (millis() - start > 4000) { | ||||||
|  |       ESP_LOGE(TAG, "Transmit packet failure"); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (this->rx_start_) { | ||||||
|  |     this->set_mode_rx(); | ||||||
|  |   } else { | ||||||
|  |     this->set_mode_sleep(); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr) { | ||||||
|  |   for (auto &listener : this->listeners_) { | ||||||
|  |     listener->on_packet(packet, rssi, snr); | ||||||
|  |   } | ||||||
|  |   this->packet_trigger_->trigger(packet, rssi, snr); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::loop() { | ||||||
|  |   if (this->dio0_pin_ == nullptr || !this->dio0_pin_->digital_read()) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   if (this->modulation_ == MOD_LORA) { | ||||||
|  |     uint8_t status = this->read_register_(REG_IRQ_FLAGS); | ||||||
|  |     this->write_register_(REG_IRQ_FLAGS, 0xFF); | ||||||
|  |     if ((status & PAYLOAD_CRC_ERROR) == 0) { | ||||||
|  |       uint8_t bytes = this->read_register_(REG_NB_RX_BYTES); | ||||||
|  |       uint8_t addr = this->read_register_(REG_FIFO_RX_CURR_ADDR); | ||||||
|  |       uint8_t rssi = this->read_register_(REG_PKT_RSSI_VALUE); | ||||||
|  |       int8_t snr = (int8_t) this->read_register_(REG_PKT_SNR_VALUE); | ||||||
|  |       std::vector<uint8_t> packet(bytes); | ||||||
|  |       this->write_register_(REG_FIFO_ADDR_PTR, addr); | ||||||
|  |       this->read_fifo_(packet); | ||||||
|  |       if (this->frequency_ > 700000000) { | ||||||
|  |         this->call_listeners_(packet, (float) rssi - RSSI_OFFSET_HF, (float) snr / 4); | ||||||
|  |       } else { | ||||||
|  |         this->call_listeners_(packet, (float) rssi - RSSI_OFFSET_LF, (float) snr / 4); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } else if (this->packet_mode_) { | ||||||
|  |     std::vector<uint8_t> packet; | ||||||
|  |     uint8_t payload_length = this->payload_length_; | ||||||
|  |     if (payload_length == 0) { | ||||||
|  |       payload_length = this->read_register_(REG_FIFO); | ||||||
|  |     } | ||||||
|  |     packet.resize(payload_length); | ||||||
|  |     this->read_fifo_(packet); | ||||||
|  |     this->call_listeners_(packet, 0.0f, 0.0f); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::run_image_cal() { | ||||||
|  |   uint32_t start = millis(); | ||||||
|  |   uint8_t mode = this->read_register_(REG_OP_MODE); | ||||||
|  |   if ((mode & MODE_MASK) != MODE_STDBY) { | ||||||
|  |     ESP_LOGE(TAG, "Need to be in standby for image cal"); | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |   if (mode & MOD_LORA) { | ||||||
|  |     this->set_mode_(MOD_FSK, MODE_SLEEP); | ||||||
|  |     this->set_mode_(MOD_FSK, MODE_STDBY); | ||||||
|  |   } | ||||||
|  |   if (this->auto_cal_) { | ||||||
|  |     this->write_register_(REG_IMAGE_CAL, IMAGE_CAL_START | AUTO_IMAGE_CAL_ON | TEMP_THRESHOLD_10C); | ||||||
|  |   } else { | ||||||
|  |     this->write_register_(REG_IMAGE_CAL, IMAGE_CAL_START); | ||||||
|  |   } | ||||||
|  |   while (this->read_register_(REG_IMAGE_CAL) & IMAGE_CAL_RUNNING) { | ||||||
|  |     if (millis() - start > 20) { | ||||||
|  |       ESP_LOGE(TAG, "Image cal failure"); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (mode & MOD_LORA) { | ||||||
|  |     this->set_mode_(this->modulation_, MODE_SLEEP); | ||||||
|  |     this->set_mode_(this->modulation_, MODE_STDBY); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::set_mode_(uint8_t modulation, uint8_t mode) { | ||||||
|  |   uint32_t start = millis(); | ||||||
|  |   this->write_register_(REG_OP_MODE, modulation | mode); | ||||||
|  |   while (true) { | ||||||
|  |     uint8_t curr = this->read_register_(REG_OP_MODE) & MODE_MASK; | ||||||
|  |     if ((curr == mode) || (mode == MODE_RX && curr == MODE_RX_FS)) { | ||||||
|  |       if (mode == MODE_SLEEP) { | ||||||
|  |         this->write_register_(REG_OP_MODE, modulation | mode); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     if (millis() - start > 20) { | ||||||
|  |       ESP_LOGE(TAG, "Set mode failure"); | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::set_mode_rx() { | ||||||
|  |   this->set_mode_(this->modulation_, MODE_RX); | ||||||
|  |   if (this->modulation_ == MOD_LORA) { | ||||||
|  |     this->write_register_(REG_IRQ_FLAGS_MASK, 0x00); | ||||||
|  |     this->write_register_(REG_DIO_MAPPING1, DIO0_MAPPING_00); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::set_mode_tx() { | ||||||
|  |   this->set_mode_(this->modulation_, MODE_TX); | ||||||
|  |   if (this->modulation_ == MOD_LORA) { | ||||||
|  |     this->write_register_(REG_IRQ_FLAGS_MASK, 0x00); | ||||||
|  |     this->write_register_(REG_DIO_MAPPING1, DIO0_MAPPING_01); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void SX127x::set_mode_standby() { this->set_mode_(this->modulation_, MODE_STDBY); } | ||||||
|  |  | ||||||
|  | void SX127x::set_mode_sleep() { this->set_mode_(this->modulation_, MODE_SLEEP); } | ||||||
|  |  | ||||||
|  | void SX127x::dump_config() { | ||||||
|  |   ESP_LOGCONFIG(TAG, "SX127x:"); | ||||||
|  |   LOG_PIN("  CS Pin: ", this->cs_); | ||||||
|  |   LOG_PIN("  RST Pin: ", this->rst_pin_); | ||||||
|  |   LOG_PIN("  DIO0 Pin: ", this->dio0_pin_); | ||||||
|  |   const char *shaping = "NONE"; | ||||||
|  |   if (this->shaping_ == CUTOFF_BR_X_2) { | ||||||
|  |     shaping = "CUTOFF_BR_X_2"; | ||||||
|  |   } else if (this->shaping_ == CUTOFF_BR_X_1) { | ||||||
|  |     shaping = "CUTOFF_BR_X_1"; | ||||||
|  |   } else if (this->shaping_ == GAUSSIAN_BT_0_3) { | ||||||
|  |     shaping = "GAUSSIAN_BT_0_3"; | ||||||
|  |   } else if (this->shaping_ == GAUSSIAN_BT_0_5) { | ||||||
|  |     shaping = "GAUSSIAN_BT_0_5"; | ||||||
|  |   } else if (this->shaping_ == GAUSSIAN_BT_1_0) { | ||||||
|  |     shaping = "GAUSSIAN_BT_1_0"; | ||||||
|  |   } | ||||||
|  |   const char *pa_pin = "RFO"; | ||||||
|  |   if (this->pa_pin_ == PA_PIN_BOOST) { | ||||||
|  |     pa_pin = "BOOST"; | ||||||
|  |   } | ||||||
|  |   ESP_LOGCONFIG(TAG, | ||||||
|  |                 "  Auto Cal: %s\n" | ||||||
|  |                 "  Frequency: %" PRIu32 " Hz\n" | ||||||
|  |                 "  Bandwidth: %" PRIu32 " Hz\n" | ||||||
|  |                 "  PA Pin: %s\n" | ||||||
|  |                 "  PA Power: %" PRIu8 " dBm\n" | ||||||
|  |                 "  PA Ramp: %" PRIu16 " us\n" | ||||||
|  |                 "  Shaping: %s", | ||||||
|  |                 TRUEFALSE(this->auto_cal_), this->frequency_, BW_HZ[this->bandwidth_], pa_pin, this->pa_power_, | ||||||
|  |                 RAMP[this->pa_ramp_], shaping); | ||||||
|  |   if (this->modulation_ == MOD_FSK) { | ||||||
|  |     ESP_LOGCONFIG(TAG, "  Deviation: %" PRIu32 " Hz", this->deviation_); | ||||||
|  |   } | ||||||
|  |   if (this->modulation_ == MOD_LORA) { | ||||||
|  |     const char *cr = "4/8"; | ||||||
|  |     if (this->coding_rate_ == CODING_RATE_4_5) { | ||||||
|  |       cr = "4/5"; | ||||||
|  |     } else if (this->coding_rate_ == CODING_RATE_4_6) { | ||||||
|  |       cr = "4/6"; | ||||||
|  |     } else if (this->coding_rate_ == CODING_RATE_4_7) { | ||||||
|  |       cr = "4/7"; | ||||||
|  |     } | ||||||
|  |     ESP_LOGCONFIG(TAG, | ||||||
|  |                   "  Modulation: LORA\n" | ||||||
|  |                   "  Preamble Size: %" PRIu16 "\n" | ||||||
|  |                   "  Spreading Factor: %" PRIu8 "\n" | ||||||
|  |                   "  Coding Rate: %s\n" | ||||||
|  |                   "  CRC Enable: %s", | ||||||
|  |                   this->preamble_size_, this->spreading_factor_, cr, TRUEFALSE(this->crc_enable_)); | ||||||
|  |     if (this->payload_length_ > 0) { | ||||||
|  |       ESP_LOGCONFIG(TAG, "  Payload Length: %" PRIu32, this->payload_length_); | ||||||
|  |     } | ||||||
|  |     if (!this->sync_value_.empty()) { | ||||||
|  |       ESP_LOGCONFIG(TAG, "  Sync Value: 0x%02x", this->sync_value_[0]); | ||||||
|  |     } | ||||||
|  |   } else { | ||||||
|  |     ESP_LOGCONFIG(TAG, | ||||||
|  |                   "  Modulation: %s\n" | ||||||
|  |                   "  Bitrate: %" PRIu32 "b/s\n" | ||||||
|  |                   "  Bitsync: %s\n" | ||||||
|  |                   "  Rx Start: %s\n" | ||||||
|  |                   "  Rx Floor: %.1f dBm\n" | ||||||
|  |                   "  Packet Mode: %s", | ||||||
|  |                   this->modulation_ == MOD_FSK ? "FSK" : "OOK", this->bitrate_, TRUEFALSE(this->bitsync_), | ||||||
|  |                   TRUEFALSE(this->rx_start_), this->rx_floor_, TRUEFALSE(this->packet_mode_)); | ||||||
|  |     if (this->packet_mode_) { | ||||||
|  |       ESP_LOGCONFIG(TAG, "  CRC Enable: %s", TRUEFALSE(this->crc_enable_)); | ||||||
|  |     } | ||||||
|  |     if (this->payload_length_ > 0) { | ||||||
|  |       ESP_LOGCONFIG(TAG, "  Payload Length: %" PRIu32, this->payload_length_); | ||||||
|  |     } | ||||||
|  |     if (!this->sync_value_.empty()) { | ||||||
|  |       ESP_LOGCONFIG(TAG, "  Sync Value: 0x%s", format_hex(this->sync_value_).c_str()); | ||||||
|  |     } | ||||||
|  |     if (this->preamble_size_ > 0 || this->preamble_detect_ > 0) { | ||||||
|  |       ESP_LOGCONFIG(TAG, | ||||||
|  |                     "  Preamble Polarity: 0x%X\n" | ||||||
|  |                     "  Preamble Size: %" PRIu16 "\n" | ||||||
|  |                     "  Preamble Detect: %" PRIu8 "\n" | ||||||
|  |                     "  Preamble Errors: %" PRIu8, | ||||||
|  |                     this->preamble_polarity_, this->preamble_size_, this->preamble_detect_, this->preamble_errors_); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |   if (this->is_failed()) { | ||||||
|  |     ESP_LOGE(TAG, "Configuring SX127x failed"); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | }  // namespace sx127x | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										125
									
								
								esphome/components/sx127x/sx127x.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								esphome/components/sx127x/sx127x.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "sx127x_reg.h" | ||||||
|  | #include "esphome/components/spi/spi.h" | ||||||
|  | #include "esphome/core/automation.h" | ||||||
|  | #include "esphome/core/component.h" | ||||||
|  | #include <vector> | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace sx127x { | ||||||
|  |  | ||||||
|  | enum SX127xBw : uint8_t { | ||||||
|  |   SX127X_BW_2_6, | ||||||
|  |   SX127X_BW_3_1, | ||||||
|  |   SX127X_BW_3_9, | ||||||
|  |   SX127X_BW_5_2, | ||||||
|  |   SX127X_BW_6_3, | ||||||
|  |   SX127X_BW_7_8, | ||||||
|  |   SX127X_BW_10_4, | ||||||
|  |   SX127X_BW_12_5, | ||||||
|  |   SX127X_BW_15_6, | ||||||
|  |   SX127X_BW_20_8, | ||||||
|  |   SX127X_BW_25_0, | ||||||
|  |   SX127X_BW_31_3, | ||||||
|  |   SX127X_BW_41_7, | ||||||
|  |   SX127X_BW_50_0, | ||||||
|  |   SX127X_BW_62_5, | ||||||
|  |   SX127X_BW_83_3, | ||||||
|  |   SX127X_BW_100_0, | ||||||
|  |   SX127X_BW_125_0, | ||||||
|  |   SX127X_BW_166_7, | ||||||
|  |   SX127X_BW_200_0, | ||||||
|  |   SX127X_BW_250_0, | ||||||
|  |   SX127X_BW_500_0, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SX127xListener { | ||||||
|  |  public: | ||||||
|  |   virtual void on_packet(const std::vector<uint8_t> &packet, float rssi, float snr) = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class SX127x : public Component, | ||||||
|  |                public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING, | ||||||
|  |                                      spi::DATA_RATE_8MHZ> { | ||||||
|  |  public: | ||||||
|  |   size_t get_max_packet_size(); | ||||||
|  |   float get_setup_priority() const override { return setup_priority::PROCESSOR; } | ||||||
|  |   void setup() override; | ||||||
|  |   void loop() override; | ||||||
|  |   void dump_config() override; | ||||||
|  |   void set_auto_cal(bool auto_cal) { this->auto_cal_ = auto_cal; } | ||||||
|  |   void set_bandwidth(SX127xBw bandwidth) { this->bandwidth_ = bandwidth; } | ||||||
|  |   void set_bitrate(uint32_t bitrate) { this->bitrate_ = bitrate; } | ||||||
|  |   void set_bitsync(bool bitsync) { this->bitsync_ = bitsync; } | ||||||
|  |   void set_coding_rate(uint8_t coding_rate) { this->coding_rate_ = coding_rate; } | ||||||
|  |   void set_crc_enable(bool crc_enable) { this->crc_enable_ = crc_enable; } | ||||||
|  |   void set_deviation(uint32_t deviation) { this->deviation_ = deviation; } | ||||||
|  |   void set_dio0_pin(InternalGPIOPin *dio0_pin) { this->dio0_pin_ = dio0_pin; } | ||||||
|  |   void set_frequency(uint32_t frequency) { this->frequency_ = frequency; } | ||||||
|  |   void set_mode_rx(); | ||||||
|  |   void set_mode_tx(); | ||||||
|  |   void set_mode_standby(); | ||||||
|  |   void set_mode_sleep(); | ||||||
|  |   void set_modulation(uint8_t modulation) { this->modulation_ = modulation; } | ||||||
|  |   void set_pa_pin(uint8_t pin) { this->pa_pin_ = pin; } | ||||||
|  |   void set_pa_power(uint8_t power) { this->pa_power_ = power; } | ||||||
|  |   void set_pa_ramp(uint8_t ramp) { this->pa_ramp_ = ramp; } | ||||||
|  |   void set_packet_mode(bool packet_mode) { this->packet_mode_ = packet_mode; } | ||||||
|  |   void set_payload_length(uint8_t payload_length) { this->payload_length_ = payload_length; } | ||||||
|  |   void set_preamble_errors(uint8_t preamble_errors) { this->preamble_errors_ = preamble_errors; } | ||||||
|  |   void set_preamble_polarity(uint8_t preamble_polarity) { this->preamble_polarity_ = preamble_polarity; } | ||||||
|  |   void set_preamble_size(uint16_t preamble_size) { this->preamble_size_ = preamble_size; } | ||||||
|  |   void set_preamble_detect(uint8_t preamble_detect) { this->preamble_detect_ = preamble_detect; } | ||||||
|  |   void set_rst_pin(InternalGPIOPin *rst_pin) { this->rst_pin_ = rst_pin; } | ||||||
|  |   void set_rx_floor(float floor) { this->rx_floor_ = floor; } | ||||||
|  |   void set_rx_start(bool start) { this->rx_start_ = start; } | ||||||
|  |   void set_shaping(uint8_t shaping) { this->shaping_ = shaping; } | ||||||
|  |   void set_spreading_factor(uint8_t spreading_factor) { this->spreading_factor_ = spreading_factor; } | ||||||
|  |   void set_sync_value(const std::vector<uint8_t> &sync_value) { this->sync_value_ = sync_value; } | ||||||
|  |   void run_image_cal(); | ||||||
|  |   void configure(); | ||||||
|  |   void transmit_packet(const std::vector<uint8_t> &packet); | ||||||
|  |   void register_listener(SX127xListener *listener) { this->listeners_.push_back(listener); } | ||||||
|  |   Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() const { return this->packet_trigger_; }; | ||||||
|  |  | ||||||
|  |  protected: | ||||||
|  |   void configure_fsk_ook_(); | ||||||
|  |   void configure_lora_(); | ||||||
|  |   void set_mode_(uint8_t modulation, uint8_t mode); | ||||||
|  |   void write_fifo_(const std::vector<uint8_t> &packet); | ||||||
|  |   void read_fifo_(std::vector<uint8_t> &packet); | ||||||
|  |   void write_register_(uint8_t reg, uint8_t value); | ||||||
|  |   void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr); | ||||||
|  |   uint8_t read_register_(uint8_t reg); | ||||||
|  |   Trigger<std::vector<uint8_t>, float, float> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, float>()}; | ||||||
|  |   std::vector<SX127xListener *> listeners_; | ||||||
|  |   std::vector<uint8_t> sync_value_; | ||||||
|  |   InternalGPIOPin *dio0_pin_{nullptr}; | ||||||
|  |   InternalGPIOPin *rst_pin_{nullptr}; | ||||||
|  |   SX127xBw bandwidth_; | ||||||
|  |   uint32_t bitrate_; | ||||||
|  |   uint32_t deviation_; | ||||||
|  |   uint32_t frequency_; | ||||||
|  |   uint32_t payload_length_; | ||||||
|  |   uint16_t preamble_size_; | ||||||
|  |   uint8_t coding_rate_; | ||||||
|  |   uint8_t modulation_; | ||||||
|  |   uint8_t pa_pin_; | ||||||
|  |   uint8_t pa_power_; | ||||||
|  |   uint8_t pa_ramp_; | ||||||
|  |   uint8_t preamble_detect_; | ||||||
|  |   uint8_t preamble_errors_; | ||||||
|  |   uint8_t preamble_polarity_; | ||||||
|  |   uint8_t shaping_; | ||||||
|  |   uint8_t spreading_factor_; | ||||||
|  |   float rx_floor_; | ||||||
|  |   bool auto_cal_{false}; | ||||||
|  |   bool bitsync_{false}; | ||||||
|  |   bool crc_enable_{false}; | ||||||
|  |   bool packet_mode_{false}; | ||||||
|  |   bool rx_start_{false}; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace sx127x | ||||||
|  | }  // namespace esphome | ||||||
							
								
								
									
										295
									
								
								esphome/components/sx127x/sx127x_reg.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										295
									
								
								esphome/components/sx127x/sx127x_reg.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,295 @@ | |||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include "esphome/core/hal.h" | ||||||
|  |  | ||||||
|  | namespace esphome { | ||||||
|  | namespace sx127x { | ||||||
|  |  | ||||||
|  | enum SX127xReg : uint8_t { | ||||||
|  |   // Common registers | ||||||
|  |   REG_FIFO = 0x00, | ||||||
|  |   REG_OP_MODE = 0x01, | ||||||
|  |   REG_BITRATE_MSB = 0x02, | ||||||
|  |   REG_BITRATE_LSB = 0x03, | ||||||
|  |   REG_FDEV_MSB = 0x04, | ||||||
|  |   REG_FDEV_LSB = 0x05, | ||||||
|  |   REG_FRF_MSB = 0x06, | ||||||
|  |   REG_FRF_MID = 0x07, | ||||||
|  |   REG_FRF_LSB = 0x08, | ||||||
|  |   REG_PA_CONFIG = 0x09, | ||||||
|  |   REG_PA_RAMP = 0x0A, | ||||||
|  |   REG_DIO_MAPPING1 = 0x40, | ||||||
|  |   REG_DIO_MAPPING2 = 0x41, | ||||||
|  |   REG_VERSION = 0x42, | ||||||
|  |   // FSK/OOK registers | ||||||
|  |   REG_RX_CONFIG = 0x0D, | ||||||
|  |   REG_RSSI_THRESH = 0x10, | ||||||
|  |   REG_RX_BW = 0x12, | ||||||
|  |   REG_OOK_PEAK = 0x14, | ||||||
|  |   REG_OOK_FIX = 0x15, | ||||||
|  |   REG_OOK_AVG = 0x16, | ||||||
|  |   REG_AFC_FEI = 0x1A, | ||||||
|  |   REG_PREAMBLE_DETECT = 0x1F, | ||||||
|  |   REG_PREAMBLE_SIZE_MSB = 0x25, | ||||||
|  |   REG_PREAMBLE_SIZE_LSB = 0x26, | ||||||
|  |   REG_SYNC_CONFIG = 0x27, | ||||||
|  |   REG_SYNC_VALUE1 = 0x28, | ||||||
|  |   REG_SYNC_VALUE2 = 0x29, | ||||||
|  |   REG_SYNC_VALUE3 = 0x2A, | ||||||
|  |   REG_SYNC_VALUE4 = 0x2B, | ||||||
|  |   REG_SYNC_VALUE5 = 0x2C, | ||||||
|  |   REG_SYNC_VALUE6 = 0x2D, | ||||||
|  |   REG_SYNC_VALUE7 = 0x2E, | ||||||
|  |   REG_SYNC_VALUE8 = 0x2F, | ||||||
|  |   REG_PACKET_CONFIG_1 = 0x30, | ||||||
|  |   REG_PACKET_CONFIG_2 = 0x31, | ||||||
|  |   REG_PAYLOAD_LENGTH_LSB = 0x32, | ||||||
|  |   REG_FIFO_THRESH = 0x35, | ||||||
|  |   REG_IMAGE_CAL = 0x3B, | ||||||
|  |   // LoRa registers | ||||||
|  |   REG_FIFO_ADDR_PTR = 0x0D, | ||||||
|  |   REG_FIFO_TX_BASE_ADDR = 0x0E, | ||||||
|  |   REG_FIFO_RX_BASE_ADDR = 0x0F, | ||||||
|  |   REG_FIFO_RX_CURR_ADDR = 0x10, | ||||||
|  |   REG_IRQ_FLAGS_MASK = 0x11, | ||||||
|  |   REG_IRQ_FLAGS = 0x12, | ||||||
|  |   REG_NB_RX_BYTES = 0x13, | ||||||
|  |   REG_MODEM_STAT = 0x18, | ||||||
|  |   REG_PKT_SNR_VALUE = 0x19, | ||||||
|  |   REG_PKT_RSSI_VALUE = 0x1A, | ||||||
|  |   REG_RSSI_VALUE = 0x1B, | ||||||
|  |   REG_HOP_CHANNEL = 0x1C, | ||||||
|  |   REG_MODEM_CONFIG1 = 0x1D, | ||||||
|  |   REG_MODEM_CONFIG2 = 0x1E, | ||||||
|  |   REG_SYMB_TIMEOUT_LSB = 0x1F, | ||||||
|  |   REG_PREAMBLE_LEN_MSB = 0x20, | ||||||
|  |   REG_PREAMBLE_LEN_LSB = 0x21, | ||||||
|  |   REG_PAYLOAD_LENGTH = 0x22, | ||||||
|  |   REG_HOP_PERIOD = 0x24, | ||||||
|  |   REG_FIFO_RX_BYTE_ADDR = 0x25, | ||||||
|  |   REG_MODEM_CONFIG3 = 0x26, | ||||||
|  |   REG_FEI_MSB = 0x28, | ||||||
|  |   REG_FEI_MIB = 0x29, | ||||||
|  |   REG_FEI_LSB = 0x2A, | ||||||
|  |   REG_DETECT_OPTIMIZE = 0x31, | ||||||
|  |   REG_INVERT_IQ = 0x33, | ||||||
|  |   REG_DETECT_THRESHOLD = 0x37, | ||||||
|  |   REG_SYNC_WORD = 0x39, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xOpMode : uint8_t { | ||||||
|  |   MOD_LORA = 0x80, | ||||||
|  |   ACCESS_FSK_REGS = 0x40, | ||||||
|  |   ACCESS_LORA_REGS = 0x00, | ||||||
|  |   MOD_OOK = 0x20, | ||||||
|  |   MOD_FSK = 0x00, | ||||||
|  |   ACCESS_LF_REGS = 0x08, | ||||||
|  |   ACCESS_HF_REGS = 0x00, | ||||||
|  |   MODE_CAD = 0x07, | ||||||
|  |   MODE_RX_SINGLE = 0x06, | ||||||
|  |   MODE_RX = 0x05, | ||||||
|  |   MODE_RX_FS = 0x04, | ||||||
|  |   MODE_TX = 0x03, | ||||||
|  |   MODE_TX_FS = 0x02, | ||||||
|  |   MODE_STDBY = 0x01, | ||||||
|  |   MODE_SLEEP = 0x00, | ||||||
|  |   MODE_MASK = 0x07, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xPaConfig : uint8_t { | ||||||
|  |   PA_PIN_BOOST = 0x80, | ||||||
|  |   PA_PIN_RFO = 0x00, | ||||||
|  |   PA_MAX_POWER = 0x70, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xPaRamp : uint8_t { | ||||||
|  |   CUTOFF_BR_X_2 = 0x40, | ||||||
|  |   CUTOFF_BR_X_1 = 0x20, | ||||||
|  |   GAUSSIAN_BT_0_3 = 0x60, | ||||||
|  |   GAUSSIAN_BT_0_5 = 0x40, | ||||||
|  |   GAUSSIAN_BT_1_0 = 0x20, | ||||||
|  |   SHAPING_NONE = 0x00, | ||||||
|  |   PA_RAMP_10 = 0x0F, | ||||||
|  |   PA_RAMP_12 = 0x0E, | ||||||
|  |   PA_RAMP_15 = 0x0D, | ||||||
|  |   PA_RAMP_20 = 0x0C, | ||||||
|  |   PA_RAMP_25 = 0x0B, | ||||||
|  |   PA_RAMP_31 = 0x0A, | ||||||
|  |   PA_RAMP_40 = 0x09, | ||||||
|  |   PA_RAMP_50 = 0x08, | ||||||
|  |   PA_RAMP_62 = 0x07, | ||||||
|  |   PA_RAMP_100 = 0x06, | ||||||
|  |   PA_RAMP_125 = 0x05, | ||||||
|  |   PA_RAMP_250 = 0x04, | ||||||
|  |   PA_RAMP_500 = 0x03, | ||||||
|  |   PA_RAMP_1000 = 0x02, | ||||||
|  |   PA_RAMP_2000 = 0x01, | ||||||
|  |   PA_RAMP_3400 = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xDioMapping1 : uint8_t { | ||||||
|  |   DIO0_MAPPING_00 = 0x00, | ||||||
|  |   DIO0_MAPPING_01 = 0x40, | ||||||
|  |   DIO0_MAPPING_10 = 0x80, | ||||||
|  |   DIO0_MAPPING_11 = 0xC0, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xRxConfig : uint8_t { | ||||||
|  |   RESTART_ON_COLLISION = 0x80, | ||||||
|  |   RESTART_NO_LOCK = 0x40, | ||||||
|  |   RESTART_PLL_LOCK = 0x20, | ||||||
|  |   AFC_AUTO_ON = 0x10, | ||||||
|  |   AGC_AUTO_ON = 0x08, | ||||||
|  |   TRIGGER_NONE = 0x00, | ||||||
|  |   TRIGGER_RSSI = 0x01, | ||||||
|  |   TRIGGER_PREAMBLE = 0x06, | ||||||
|  |   TRIGGER_ALL = 0x07, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xRxBw : uint8_t { | ||||||
|  |   RX_BW_2_6 = 0x17, | ||||||
|  |   RX_BW_3_1 = 0x0F, | ||||||
|  |   RX_BW_3_9 = 0x07, | ||||||
|  |   RX_BW_5_2 = 0x16, | ||||||
|  |   RX_BW_6_3 = 0x0E, | ||||||
|  |   RX_BW_7_8 = 0x06, | ||||||
|  |   RX_BW_10_4 = 0x15, | ||||||
|  |   RX_BW_12_5 = 0x0D, | ||||||
|  |   RX_BW_15_6 = 0x05, | ||||||
|  |   RX_BW_20_8 = 0x14, | ||||||
|  |   RX_BW_25_0 = 0x0C, | ||||||
|  |   RX_BW_31_3 = 0x04, | ||||||
|  |   RX_BW_41_7 = 0x13, | ||||||
|  |   RX_BW_50_0 = 0x0B, | ||||||
|  |   RX_BW_62_5 = 0x03, | ||||||
|  |   RX_BW_83_3 = 0x12, | ||||||
|  |   RX_BW_100_0 = 0x0A, | ||||||
|  |   RX_BW_125_0 = 0x02, | ||||||
|  |   RX_BW_166_7 = 0x11, | ||||||
|  |   RX_BW_200_0 = 0x09, | ||||||
|  |   RX_BW_250_0 = 0x01, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xOokPeak : uint8_t { | ||||||
|  |   BIT_SYNC_ON = 0x20, | ||||||
|  |   BIT_SYNC_OFF = 0x00, | ||||||
|  |   OOK_THRESH_AVG = 0x10, | ||||||
|  |   OOK_THRESH_PEAK = 0x08, | ||||||
|  |   OOK_THRESH_FIXED = 0x00, | ||||||
|  |   OOK_THRESH_STEP_6_0 = 0x07, | ||||||
|  |   OOK_THRESH_STEP_5_0 = 0x06, | ||||||
|  |   OOK_THRESH_STEP_4_0 = 0x05, | ||||||
|  |   OOK_THRESH_STEP_3_0 = 0x04, | ||||||
|  |   OOK_THRESH_STEP_2_0 = 0x03, | ||||||
|  |   OOK_THRESH_STEP_1_5 = 0x02, | ||||||
|  |   OOK_THRESH_STEP_1_0 = 0x01, | ||||||
|  |   OOK_THRESH_STEP_0_5 = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xOokAvg : uint8_t { | ||||||
|  |   OOK_THRESH_DEC_16 = 0xE0, | ||||||
|  |   OOK_THRESH_DEC_8 = 0xC0, | ||||||
|  |   OOK_THRESH_DEC_4 = 0xA0, | ||||||
|  |   OOK_THRESH_DEC_2 = 0x80, | ||||||
|  |   OOK_THRESH_DEC_1_8 = 0x60, | ||||||
|  |   OOK_THRESH_DEC_1_4 = 0x40, | ||||||
|  |   OOK_THRESH_DEC_1_2 = 0x20, | ||||||
|  |   OOK_THRESH_DEC_1 = 0x00, | ||||||
|  |   OOK_AVG_RESERVED = 0x10, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xAfcFei : uint8_t { | ||||||
|  |   AFC_AUTO_CLEAR_ON = 0x01, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xPreambleDetect : uint8_t { | ||||||
|  |   PREAMBLE_DETECTOR_ON = 0x80, | ||||||
|  |   PREAMBLE_DETECTOR_OFF = 0x00, | ||||||
|  |   PREAMBLE_DETECTOR_SIZE_SHIFT = 5, | ||||||
|  |   PREAMBLE_DETECTOR_TOL_SHIFT = 0, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xSyncConfig : uint8_t { | ||||||
|  |   AUTO_RESTART_PLL_LOCK = 0x80, | ||||||
|  |   AUTO_RESTART_NO_LOCK = 0x40, | ||||||
|  |   AUTO_RESTART_OFF = 0x00, | ||||||
|  |   PREAMBLE_55 = 0x20, | ||||||
|  |   PREAMBLE_AA = 0x00, | ||||||
|  |   SYNC_ON = 0x10, | ||||||
|  |   SYNC_OFF = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xPacketConfig1 : uint8_t { | ||||||
|  |   VARIABLE_LENGTH = 0x80, | ||||||
|  |   FIXED_LENGTH = 0x00, | ||||||
|  |   CRC_ON = 0x10, | ||||||
|  |   CRC_OFF = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xPacketConfig2 : uint8_t { | ||||||
|  |   CONTINUOUS_MODE = 0x00, | ||||||
|  |   PACKET_MODE = 0x40, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xFifoThresh : uint8_t { | ||||||
|  |   TX_START_FIFO_EMPTY = 0x80, | ||||||
|  |   TX_START_FIFO_LEVEL = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xImageCal : uint8_t { | ||||||
|  |   AUTO_IMAGE_CAL_ON = 0x80, | ||||||
|  |   IMAGE_CAL_START = 0x40, | ||||||
|  |   IMAGE_CAL_RUNNING = 0x20, | ||||||
|  |   TEMP_CHANGE = 0x08, | ||||||
|  |   TEMP_THRESHOLD_20C = 0x06, | ||||||
|  |   TEMP_THRESHOLD_15C = 0x04, | ||||||
|  |   TEMP_THRESHOLD_10C = 0x02, | ||||||
|  |   TEMP_THRESHOLD_5C = 0x00, | ||||||
|  |   TEMP_MONITOR_OFF = 0x01, | ||||||
|  |   TEMP_MONITOR_ON = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xIrqFlags : uint8_t { | ||||||
|  |   RX_TIMEOUT = 0x80, | ||||||
|  |   RX_DONE = 0x40, | ||||||
|  |   PAYLOAD_CRC_ERROR = 0x20, | ||||||
|  |   VALID_HEADER = 0x10, | ||||||
|  |   TX_DONE = 0x08, | ||||||
|  |   CAD_DONE = 0x04, | ||||||
|  |   FHSS_CHANGE_CHANNEL = 0x02, | ||||||
|  |   CAD_DETECTED = 0x01, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xModemCfg1 : uint8_t { | ||||||
|  |   BW_7_8 = 0x00, | ||||||
|  |   BW_10_4 = 0x10, | ||||||
|  |   BW_15_6 = 0x20, | ||||||
|  |   BW_20_8 = 0x30, | ||||||
|  |   BW_31_3 = 0x40, | ||||||
|  |   BW_41_7 = 0x50, | ||||||
|  |   BW_62_5 = 0x60, | ||||||
|  |   BW_125_0 = 0x70, | ||||||
|  |   BW_250_0 = 0x80, | ||||||
|  |   BW_500_0 = 0x90, | ||||||
|  |   CODING_RATE_4_5 = 0x02, | ||||||
|  |   CODING_RATE_4_6 = 0x04, | ||||||
|  |   CODING_RATE_4_7 = 0x06, | ||||||
|  |   CODING_RATE_4_8 = 0x08, | ||||||
|  |   IMPLICIT_HEADER = 0x01, | ||||||
|  |   EXPLICIT_HEADER = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xModemCfg2 : uint8_t { | ||||||
|  |   SPREADING_FACTOR_SHIFT = 4, | ||||||
|  |   TX_CONTINOUS_MODE = 0x08, | ||||||
|  |   RX_PAYLOAD_CRC_ON = 0x04, | ||||||
|  |   RX_PAYLOAD_CRC_OFF = 0x00, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | enum SX127xModemCfg3 : uint8_t { | ||||||
|  |   LOW_DATA_RATE_OPTIMIZE_ON = 0x08, | ||||||
|  |   MODEM_AGC_AUTO_ON = 0x04, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | }  // namespace sx127x | ||||||
|  | }  // namespace esphome | ||||||
| @@ -87,6 +87,7 @@ | |||||||
| #define USE_SELECT | #define USE_SELECT | ||||||
| #define USE_SENSOR | #define USE_SENSOR | ||||||
| #define USE_STATUS_LED | #define USE_STATUS_LED | ||||||
|  | #define USE_STATUS_SENSOR | ||||||
| #define USE_SWITCH | #define USE_SWITCH | ||||||
| #define USE_TEXT | #define USE_TEXT | ||||||
| #define USE_TEXT_SENSOR | #define USE_TEXT_SENSOR | ||||||
|   | |||||||
| @@ -13,7 +13,7 @@ platformio==6.1.18  # When updating platformio, also update /docker/Dockerfile | |||||||
| esptool==4.9.0 | esptool==4.9.0 | ||||||
| click==8.1.7 | click==8.1.7 | ||||||
| esphome-dashboard==20250514.0 | esphome-dashboard==20250514.0 | ||||||
| aioesphomeapi==34.0.0 | aioesphomeapi==34.1.0 | ||||||
| zeroconf==0.147.0 | zeroconf==0.147.0 | ||||||
| puremagic==1.29 | puremagic==1.29 | ||||||
| ruamel.yaml==0.18.14 # dashboard_import | ruamel.yaml==0.18.14 # dashboard_import | ||||||
|   | |||||||
| @@ -5,7 +5,8 @@ packages: | |||||||
|   - !include package.yaml |   - !include package.yaml | ||||||
|   - github://esphome/esphome/tests/components/template/common.yaml@dev |   - github://esphome/esphome/tests/components/template/common.yaml@dev | ||||||
|   - url: https://github.com/esphome/esphome |   - url: https://github.com/esphome/esphome | ||||||
|     file: tests/components/absolute_humidity/common.yaml |     path: tests/components/absolute_humidity | ||||||
|  |     file: common.yaml | ||||||
|     ref: dev |     ref: dev | ||||||
|     refresh: 1d |     refresh: 1d | ||||||
|  |  | ||||||
|   | |||||||
| @@ -7,7 +7,8 @@ packages: | |||||||
|   shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev |   shorthand: github://esphome/esphome/tests/components/template/common.yaml@dev | ||||||
|   github: |   github: | ||||||
|     url: https://github.com/esphome/esphome |     url: https://github.com/esphome/esphome | ||||||
|     file: tests/components/absolute_humidity/common.yaml |     path: tests/components/absolute_humidity | ||||||
|  |     file: common.yaml | ||||||
|     ref: dev |     ref: dev | ||||||
|     refresh: 1d |     refresh: 1d | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,5 +36,9 @@ binary_sensor: | |||||||
|   - platform: packet_transport |   - platform: packet_transport | ||||||
|     provider: unencrypted-device |     provider: unencrypted-device | ||||||
|     id: other_binary_sensor_id |     id: other_binary_sensor_id | ||||||
|  |   - platform: packet_transport | ||||||
|  |     provider: some-device-name | ||||||
|  |     type: status | ||||||
|  |     name: Some-Device Status | ||||||
|   - platform: template |   - platform: template | ||||||
|     id: binary_sensor_id1 |     id: binary_sensor_id1 | ||||||
|   | |||||||
							
								
								
									
										45
									
								
								tests/components/sx127x/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								tests/components/sx127x/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | |||||||
|  | spi: | ||||||
|  |   clk_pin: ${clk_pin} | ||||||
|  |   mosi_pin: ${mosi_pin} | ||||||
|  |   miso_pin: ${miso_pin} | ||||||
|  |  | ||||||
|  | sx127x: | ||||||
|  |   cs_pin: ${cs_pin} | ||||||
|  |   rst_pin: ${rst_pin} | ||||||
|  |   dio0_pin: ${dio0_pin} | ||||||
|  |   pa_pin: BOOST | ||||||
|  |   pa_power: 17 | ||||||
|  |   pa_ramp: 40us | ||||||
|  |   bitsync: true | ||||||
|  |   bitrate: 4800 | ||||||
|  |   bandwidth: 50_0kHz | ||||||
|  |   frequency: 433920000 | ||||||
|  |   modulation: FSK | ||||||
|  |   deviation: 5000 | ||||||
|  |   rx_start: true | ||||||
|  |   rx_floor: -90 | ||||||
|  |   packet_mode: true | ||||||
|  |   payload_length: 8 | ||||||
|  |   sync_value: [0x33, 0x33] | ||||||
|  |   shaping: NONE | ||||||
|  |   preamble_size: 2 | ||||||
|  |   preamble_detect: 2 | ||||||
|  |   preamble_errors: 8 | ||||||
|  |   preamble_polarity: 0x55 | ||||||
|  |   on_packet: | ||||||
|  |     then: | ||||||
|  |       - sx127x.send_packet: | ||||||
|  |           data: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] | ||||||
|  |  | ||||||
|  | button: | ||||||
|  |   - platform: template | ||||||
|  |     name: "SX127x Button" | ||||||
|  |     on_press: | ||||||
|  |       then: | ||||||
|  |         - sx127x.set_mode_standby | ||||||
|  |         - sx127x.run_image_cal | ||||||
|  |         - sx127x.set_mode_tx | ||||||
|  |         - sx127x.set_mode_sleep | ||||||
|  |         - sx127x.set_mode_rx | ||||||
|  |         - sx127x.send_packet: | ||||||
|  |             data: [0xC5, 0x51, 0x78, 0x82, 0xB7, 0xF9, 0x9C, 0x5C] | ||||||
							
								
								
									
										9
									
								
								tests/components/sx127x/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/components/sx127x/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | substitutions: | ||||||
|  |   clk_pin: GPIO5 | ||||||
|  |   mosi_pin: GPIO27 | ||||||
|  |   miso_pin: GPIO19 | ||||||
|  |   cs_pin: GPIO18 | ||||||
|  |   rst_pin: GPIO23 | ||||||
|  |   dio0_pin: GPIO26 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										9
									
								
								tests/components/sx127x/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/components/sx127x/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | substitutions: | ||||||
|  |   clk_pin: GPIO5 | ||||||
|  |   mosi_pin: GPIO18 | ||||||
|  |   miso_pin: GPIO19 | ||||||
|  |   cs_pin: GPIO1 | ||||||
|  |   rst_pin: GPIO2 | ||||||
|  |   dio0_pin: GPIO3 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										9
									
								
								tests/components/sx127x/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/components/sx127x/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | substitutions: | ||||||
|  |   clk_pin: GPIO5 | ||||||
|  |   mosi_pin: GPIO18 | ||||||
|  |   miso_pin: GPIO19 | ||||||
|  |   cs_pin: GPIO1 | ||||||
|  |   rst_pin: GPIO2 | ||||||
|  |   dio0_pin: GPIO3 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										9
									
								
								tests/components/sx127x/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/components/sx127x/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | substitutions: | ||||||
|  |   clk_pin: GPIO5 | ||||||
|  |   mosi_pin: GPIO27 | ||||||
|  |   miso_pin: GPIO19 | ||||||
|  |   cs_pin: GPIO18 | ||||||
|  |   rst_pin: GPIO23 | ||||||
|  |   dio0_pin: GPIO26 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										9
									
								
								tests/components/sx127x/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/components/sx127x/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | substitutions: | ||||||
|  |   clk_pin: GPIO5 | ||||||
|  |   mosi_pin: GPIO13 | ||||||
|  |   miso_pin: GPIO12 | ||||||
|  |   cs_pin: GPIO1 | ||||||
|  |   rst_pin: GPIO2 | ||||||
|  |   dio0_pin: GPIO3 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
							
								
								
									
										9
									
								
								tests/components/sx127x/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								tests/components/sx127x/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | substitutions: | ||||||
|  |   clk_pin: GPIO2 | ||||||
|  |   mosi_pin: GPIO3 | ||||||
|  |   miso_pin: GPIO4 | ||||||
|  |   cs_pin: GPIO5 | ||||||
|  |   rst_pin: GPIO6 | ||||||
|  |   dio0_pin: GPIO7 | ||||||
|  |  | ||||||
|  | <<: !include common.yaml | ||||||
| @@ -0,0 +1,43 @@ | |||||||
|  | esphome: | ||||||
|  |   name: rapid-transitions-test | ||||||
|  | host: | ||||||
|  | api: | ||||||
|  |   batch_delay: 0ms  # Enable immediate sending for rapid transitions | ||||||
|  | logger: | ||||||
|  |   level: DEBUG | ||||||
|  |  | ||||||
|  | # Add a sensor that updates frequently to trigger lambda evaluations | ||||||
|  | sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: "Update Trigger" | ||||||
|  |     id: update_trigger | ||||||
|  |     lambda: |- | ||||||
|  |       return 0; | ||||||
|  |     update_interval: 10ms | ||||||
|  |     internal: true | ||||||
|  |  | ||||||
|  | # Simulate an IR remote binary sensor with rapid ON/OFF transitions | ||||||
|  | binary_sensor: | ||||||
|  |   - platform: template | ||||||
|  |     name: "Simulated IR Remote Button" | ||||||
|  |     id: ir_remote_button | ||||||
|  |     lambda: |- | ||||||
|  |       // Simulate rapid button presses every ~100ms | ||||||
|  |       // Each "press" is ON for ~30ms then OFF | ||||||
|  |       uint32_t now = millis(); | ||||||
|  |       uint32_t press_cycle = now % 100;  // 100ms cycle | ||||||
|  |  | ||||||
|  |       // ON for first 30ms of each cycle | ||||||
|  |       if (press_cycle < 30) { | ||||||
|  |         // Only log state change | ||||||
|  |         if (!id(ir_remote_button).state) { | ||||||
|  |           ESP_LOGD("test", "Button ON at %u", now); | ||||||
|  |         } | ||||||
|  |         return true; | ||||||
|  |       } else { | ||||||
|  |         // Only log state change | ||||||
|  |         if (id(ir_remote_button).state) { | ||||||
|  |           ESP_LOGD("test", "Button OFF at %u", now); | ||||||
|  |         } | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
							
								
								
									
										58
									
								
								tests/integration/test_batch_delay_zero_rapid_transitions.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								tests/integration/test_batch_delay_zero_rapid_transitions.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | |||||||
|  | """Integration test for API batch_delay: 0 with rapid state transitions.""" | ||||||
|  |  | ||||||
|  | from __future__ import annotations | ||||||
|  |  | ||||||
|  | import asyncio | ||||||
|  | import time | ||||||
|  |  | ||||||
|  | from aioesphomeapi import BinarySensorInfo, BinarySensorState, EntityState | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | from .types import APIClientConnectedFactory, RunCompiledFunction | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.mark.asyncio | ||||||
|  | async def test_batch_delay_zero_rapid_transitions( | ||||||
|  |     yaml_config: str, | ||||||
|  |     run_compiled: RunCompiledFunction, | ||||||
|  |     api_client_connected: APIClientConnectedFactory, | ||||||
|  | ) -> None: | ||||||
|  |     """Test that rapid binary sensor transitions are preserved with batch_delay: 0ms.""" | ||||||
|  |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|  |         # Track state changes | ||||||
|  |         state_changes: list[tuple[bool, float]] = [] | ||||||
|  |  | ||||||
|  |         def on_state(state: EntityState) -> None: | ||||||
|  |             """Track state changes with timestamps.""" | ||||||
|  |             if isinstance(state, BinarySensorState): | ||||||
|  |                 state_changes.append((state.state, time.monotonic())) | ||||||
|  |  | ||||||
|  |         # Subscribe to state changes | ||||||
|  |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|  |         # Wait for entity info | ||||||
|  |         entity_info, _ = await client.list_entities_services() | ||||||
|  |         binary_sensors = [e for e in entity_info if isinstance(e, BinarySensorInfo)] | ||||||
|  |         assert len(binary_sensors) == 1, "Expected 1 binary sensor" | ||||||
|  |  | ||||||
|  |         # Collect states for 2 seconds | ||||||
|  |         await asyncio.sleep(2.1) | ||||||
|  |  | ||||||
|  |         # Count ON->OFF transitions | ||||||
|  |         on_off_count = 0 | ||||||
|  |         for i in range(1, len(state_changes)): | ||||||
|  |             if state_changes[i - 1][0] and not state_changes[i][0]:  # ON to OFF | ||||||
|  |                 on_off_count += 1 | ||||||
|  |  | ||||||
|  |         # With batch_delay: 0, we should capture rapid transitions | ||||||
|  |         # The test timing can be variable in CI, so we're being conservative | ||||||
|  |         # We mainly want to verify that we capture multiple rapid transitions | ||||||
|  |         assert on_off_count >= 5, ( | ||||||
|  |             f"Expected at least 5 ON->OFF transitions with batch_delay: 0ms, got {on_off_count}. " | ||||||
|  |             "Rapid transitions may have been lost." | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  |         # Also verify that state changes are happening frequently | ||||||
|  |         assert len(state_changes) >= 10, ( | ||||||
|  |             f"Expected at least 10 state changes, got {len(state_changes)}" | ||||||
|  |         ) | ||||||
| @@ -74,37 +74,41 @@ async def test_host_mode_empty_string_options( | |||||||
|         # If we got here without protobuf decoding errors, the fix is working |         # If we got here without protobuf decoding errors, the fix is working | ||||||
|         # The bug would have caused "Invalid protobuf message" errors with trailing bytes |         # The bug would have caused "Invalid protobuf message" errors with trailing bytes | ||||||
|  |  | ||||||
|         # Also verify we can interact with the select entities |         # Also verify we can receive state updates for select entities | ||||||
|         # Subscribe to state changes |         # This ensures empty strings work properly in state messages too | ||||||
|         states: dict[int, EntityState] = {} |         states: dict[int, EntityState] = {} | ||||||
|         state_change_future: asyncio.Future[None] = loop.create_future() |         states_received_future: asyncio.Future[None] = loop.create_future() | ||||||
|  |         expected_select_keys = {empty_first.key, empty_middle.key, empty_last.key} | ||||||
|  |         received_select_keys = set() | ||||||
|  |  | ||||||
|         def on_state(state: EntityState) -> None: |         def on_state(state: EntityState) -> None: | ||||||
|             """Track state changes.""" |             """Track state changes.""" | ||||||
|             states[state.key] = state |             states[state.key] = state | ||||||
|             # When we receive the state change for our select, resolve the future |             # Track which select entities we've received states for | ||||||
|             if state.key == empty_first.key and not state_change_future.done(): |             if state.key in expected_select_keys: | ||||||
|                 state_change_future.set_result(None) |                 received_select_keys.add(state.key) | ||||||
|  |                 # Once we have all select states, we're done | ||||||
|  |                 if ( | ||||||
|  |                     received_select_keys == expected_select_keys | ||||||
|  |                     and not states_received_future.done() | ||||||
|  |                 ): | ||||||
|  |                     states_received_future.set_result(None) | ||||||
|  |  | ||||||
|         client.subscribe_states(on_state) |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|         # Try setting a select to an empty string option |         # Wait for initial states with timeout | ||||||
|         # This further tests that empty strings are handled correctly |  | ||||||
|         client.select_command(empty_first.key, "") |  | ||||||
|  |  | ||||||
|         # Wait for state update with timeout |  | ||||||
|         try: |         try: | ||||||
|             await asyncio.wait_for(state_change_future, timeout=5.0) |             await asyncio.wait_for(states_received_future, timeout=5.0) | ||||||
|         except asyncio.TimeoutError: |         except asyncio.TimeoutError: | ||||||
|             pytest.fail( |             pytest.fail( | ||||||
|                 "Did not receive state update after setting select to empty string" |                 f"Did not receive states for all select entities. " | ||||||
|  |                 f"Expected keys: {expected_select_keys}, Received: {received_select_keys}" | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         # Verify the state was set to empty string |         # Verify we received states for all select entities | ||||||
|         assert empty_first.key in states |         assert empty_first.key in states | ||||||
|         select_state = states[empty_first.key] |         assert empty_middle.key in states | ||||||
|         assert hasattr(select_state, "state") |         assert empty_last.key in states | ||||||
|         assert select_state.state == "" |  | ||||||
|  |  | ||||||
|         # The test passes if no protobuf decoding errors occurred |         # The main test is that we got here without protobuf errors | ||||||
|         # With the bug, we would have gotten "Invalid protobuf message" errors |         # The select entities with empty string options were properly encoded | ||||||
|   | |||||||
| @@ -46,14 +46,22 @@ async def test_host_mode_fan_preset( | |||||||
|         # Subscribe to states |         # Subscribe to states | ||||||
|         states: dict[int, FanState] = {} |         states: dict[int, FanState] = {} | ||||||
|         state_event = asyncio.Event() |         state_event = asyncio.Event() | ||||||
|  |         initial_states_received = set() | ||||||
|  |  | ||||||
|         def on_state(state: FanState) -> None: |         def on_state(state: FanState) -> None: | ||||||
|             if isinstance(state, FanState): |             if isinstance(state, FanState): | ||||||
|                 states[state.key] = state |                 states[state.key] = state | ||||||
|  |                 initial_states_received.add(state.key) | ||||||
|                 state_event.set() |                 state_event.set() | ||||||
|  |  | ||||||
|         client.subscribe_states(on_state) |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|  |         # Wait for initial states to be received for all fans | ||||||
|  |         expected_fan_keys = {fan.key for fan in fans} | ||||||
|  |         while initial_states_received != expected_fan_keys: | ||||||
|  |             state_event.clear() | ||||||
|  |             await asyncio.wait_for(state_event.wait(), timeout=2.0) | ||||||
|  |  | ||||||
|         # Test 1: Turn on fan without speed or preset - should set speed to 100% |         # Test 1: Turn on fan without speed or preset - should set speed to 100% | ||||||
|         state_event.clear() |         state_event.clear() | ||||||
|         client.fan_command( |         client.fan_command( | ||||||
|   | |||||||
| @@ -22,36 +22,51 @@ async def test_host_mode_many_entities( | |||||||
|     async with run_compiled(yaml_config), api_client_connected() as client: |     async with run_compiled(yaml_config), api_client_connected() as client: | ||||||
|         # Subscribe to state changes |         # Subscribe to state changes | ||||||
|         states: dict[int, EntityState] = {} |         states: dict[int, EntityState] = {} | ||||||
|         entity_count_future: asyncio.Future[int] = loop.create_future() |         sensor_count_future: asyncio.Future[int] = loop.create_future() | ||||||
|  |  | ||||||
|         def on_state(state: EntityState) -> None: |         def on_state(state: EntityState) -> None: | ||||||
|             states[state.key] = state |             states[state.key] = state | ||||||
|             # When we have received states from a good number of entities, resolve the future |             # Count sensor states specifically | ||||||
|             if len(states) >= 50 and not entity_count_future.done(): |             sensor_states = [ | ||||||
|                 entity_count_future.set_result(len(states)) |                 s | ||||||
|  |                 for s in states.values() | ||||||
|  |                 if hasattr(s, "state") and isinstance(s.state, float) | ||||||
|  |             ] | ||||||
|  |             # When we have received states from at least 50 sensors, resolve the future | ||||||
|  |             if len(sensor_states) >= 50 and not sensor_count_future.done(): | ||||||
|  |                 sensor_count_future.set_result(len(sensor_states)) | ||||||
|  |  | ||||||
|         client.subscribe_states(on_state) |         client.subscribe_states(on_state) | ||||||
|  |  | ||||||
|         # Wait for states from at least 50 entities with timeout |         # Wait for states from at least 50 sensors with timeout | ||||||
|         try: |         try: | ||||||
|             entity_count = await asyncio.wait_for(entity_count_future, timeout=10.0) |             sensor_count = await asyncio.wait_for(sensor_count_future, timeout=10.0) | ||||||
|         except asyncio.TimeoutError: |         except asyncio.TimeoutError: | ||||||
|  |             sensor_states = [ | ||||||
|  |                 s | ||||||
|  |                 for s in states.values() | ||||||
|  |                 if hasattr(s, "state") and isinstance(s.state, float) | ||||||
|  |             ] | ||||||
|             pytest.fail( |             pytest.fail( | ||||||
|                 f"Did not receive states from at least 50 entities within 10 seconds. " |                 f"Did not receive states from at least 50 sensors within 10 seconds. " | ||||||
|                 f"Received {len(states)} states: {list(states.keys())}" |                 f"Received {len(sensor_states)} sensor states out of {len(states)} total states" | ||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         # Verify we received a good number of entity states |         # Verify we received a good number of entity states | ||||||
|         assert entity_count >= 50, f"Expected at least 50 entities, got {entity_count}" |         assert len(states) >= 50, ( | ||||||
|         assert len(states) >= 50, f"Expected at least 50 states, got {len(states)}" |             f"Expected at least 50 total states, got {len(states)}" | ||||||
|  |         ) | ||||||
|  |  | ||||||
|         # Verify we have different entity types by checking some expected values |         # Verify we have the expected sensor states | ||||||
|         sensor_states = [ |         sensor_states = [ | ||||||
|             s |             s | ||||||
|             for s in states.values() |             for s in states.values() | ||||||
|             if hasattr(s, "state") and isinstance(s.state, float) |             if hasattr(s, "state") and isinstance(s.state, float) | ||||||
|         ] |         ] | ||||||
|  |  | ||||||
|  |         assert sensor_count >= 50, ( | ||||||
|  |             f"Expected at least 50 sensor states, got {sensor_count}" | ||||||
|  |         ) | ||||||
|         assert len(sensor_states) >= 50, ( |         assert len(sensor_states) >= 50, ( | ||||||
|             f"Expected at least 50 sensor states, got {len(sensor_states)}" |             f"Expected at least 50 sensor states, got {len(sensor_states)}" | ||||||
|         ) |         ) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user