mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 07:03:55 +00:00 
			
		
		
		
	Merge branch 'integration' into memory_api
This commit is contained in:
		| @@ -56,6 +56,13 @@ class CustomAPIDevice { | |||||||
|     auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);  // NOLINT |     auto *service = new CustomAPIDeviceService<T, Ts...>(name, arg_names, (T *) this, callback);  // NOLINT | ||||||
|     global_api_server->register_user_service(service); |     global_api_server->register_user_service(service); | ||||||
|   } |   } | ||||||
|  | #else | ||||||
|  |   template<typename T, typename... Ts> | ||||||
|  |   void register_service(void (T::*callback)(Ts...), const std::string &name, | ||||||
|  |                         const std::array<std::string, sizeof...(Ts)> &arg_names) { | ||||||
|  |     static_assert( | ||||||
|  |         false, "register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration"); | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|   /** Register a custom native API service that will show up in Home Assistant. |   /** Register a custom native API service that will show up in Home Assistant. | ||||||
| @@ -81,6 +88,11 @@ class CustomAPIDevice { | |||||||
|     auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);  // NOLINT |     auto *service = new CustomAPIDeviceService<T>(name, {}, (T *) this, callback);  // NOLINT | ||||||
|     global_api_server->register_user_service(service); |     global_api_server->register_user_service(service); | ||||||
|   } |   } | ||||||
|  | #else | ||||||
|  |   template<typename T> void register_service(void (T::*callback)(), const std::string &name) { | ||||||
|  |     static_assert( | ||||||
|  |         false, "register_service() requires 'custom_services: true' in the 'api:' section of your YAML configuration"); | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_API_HOMEASSISTANT_STATES | #ifdef USE_API_HOMEASSISTANT_STATES | ||||||
| @@ -135,6 +147,20 @@ class CustomAPIDevice { | |||||||
|     auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); |     auto f = std::bind(callback, (T *) this, entity_id, std::placeholders::_1); | ||||||
|     global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f); |     global_api_server->subscribe_home_assistant_state(entity_id, optional<std::string>(attribute), f); | ||||||
|   } |   } | ||||||
|  | #else | ||||||
|  |   template<typename T> | ||||||
|  |   void subscribe_homeassistant_state(void (T::*callback)(std::string), const std::string &entity_id, | ||||||
|  |                                      const std::string &attribute = "") { | ||||||
|  |     static_assert(false, "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " | ||||||
|  |                          "of your YAML configuration"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   template<typename T> | ||||||
|  |   void subscribe_homeassistant_state(void (T::*callback)(std::string, std::string), const std::string &entity_id, | ||||||
|  |                                      const std::string &attribute = "") { | ||||||
|  |     static_assert(false, "subscribe_homeassistant_state() requires 'homeassistant_states: true' in the 'api:' section " | ||||||
|  |                          "of your YAML configuration"); | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
| #ifdef USE_API_HOMEASSISTANT_SERVICES | #ifdef USE_API_HOMEASSISTANT_SERVICES | ||||||
| @@ -222,6 +248,26 @@ class CustomAPIDevice { | |||||||
|     } |     } | ||||||
|     global_api_server->send_homeassistant_service_call(resp); |     global_api_server->send_homeassistant_service_call(resp); | ||||||
|   } |   } | ||||||
|  | #else | ||||||
|  |   void call_homeassistant_service(const std::string &service_name) { | ||||||
|  |     static_assert(false, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' section " | ||||||
|  |                          "of your YAML configuration"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void call_homeassistant_service(const std::string &service_name, const std::map<std::string, std::string> &data) { | ||||||
|  |     static_assert(false, "call_homeassistant_service() requires 'homeassistant_services: true' in the 'api:' section " | ||||||
|  |                          "of your YAML configuration"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void fire_homeassistant_event(const std::string &event_name) { | ||||||
|  |     static_assert(false, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' section of " | ||||||
|  |                          "your YAML configuration"); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void fire_homeassistant_event(const std::string &service_name, const std::map<std::string, std::string> &data) { | ||||||
|  |     static_assert(false, "fire_homeassistant_event() requires 'homeassistant_services: true' in the 'api:' section of " | ||||||
|  |                          "your YAML configuration"); | ||||||
|  |   } | ||||||
| #endif | #endif | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -20,12 +20,11 @@ static const size_t MAX_BUTTONS = 4;  // max number of buttons scanned | |||||||
|  |  | ||||||
| #define ERROR_CHECK(err) \ | #define ERROR_CHECK(err) \ | ||||||
|   if ((err) != i2c::ERROR_OK) { \ |   if ((err) != i2c::ERROR_OK) { \ | ||||||
|     this->status_set_warning("Communication failure"); \ |     this->status_set_warning(ESP_LOG_MSG_COMM_FAIL); \ | ||||||
|     return; \ |     return; \ | ||||||
|   } |   } | ||||||
|  |  | ||||||
| void GT911Touchscreen::setup() { | void GT911Touchscreen::setup() { | ||||||
|   i2c::ErrorCode err; |  | ||||||
|   if (this->reset_pin_ != nullptr) { |   if (this->reset_pin_ != nullptr) { | ||||||
|     this->reset_pin_->setup(); |     this->reset_pin_->setup(); | ||||||
|     this->reset_pin_->digital_write(false); |     this->reset_pin_->digital_write(false); | ||||||
| @@ -35,9 +34,14 @@ void GT911Touchscreen::setup() { | |||||||
|       this->interrupt_pin_->digital_write(false); |       this->interrupt_pin_->digital_write(false); | ||||||
|     } |     } | ||||||
|     delay(2); |     delay(2); | ||||||
|     this->reset_pin_->digital_write(true); |     this->reset_pin_->digital_write(true);  // wait 50ms after reset | ||||||
|     delay(50);  // NOLINT |     this->set_timeout(50, [this] { this->setup_internal_(); }); | ||||||
|  |     return; | ||||||
|   } |   } | ||||||
|  |   this->setup_internal_(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void GT911Touchscreen::setup_internal_() { | ||||||
|   if (this->interrupt_pin_ != nullptr) { |   if (this->interrupt_pin_ != nullptr) { | ||||||
|     // set pre-configured input mode |     // set pre-configured input mode | ||||||
|     this->interrupt_pin_->setup(); |     this->interrupt_pin_->setup(); | ||||||
| @@ -45,7 +49,7 @@ void GT911Touchscreen::setup() { | |||||||
|  |  | ||||||
|   // check the configuration of the int line. |   // check the configuration of the int line. | ||||||
|   uint8_t data[4]; |   uint8_t data[4]; | ||||||
|   err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); |   i2c::ErrorCode err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); | ||||||
|   if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) { |   if (err != i2c::ERROR_OK && this->address_ == PRIMARY_ADDRESS) { | ||||||
|     this->address_ = SECONDARY_ADDRESS; |     this->address_ = SECONDARY_ADDRESS; | ||||||
|     err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); |     err = this->write(GET_SWITCHES, sizeof(GET_SWITCHES)); | ||||||
| @@ -53,7 +57,7 @@ void GT911Touchscreen::setup() { | |||||||
|   if (err == i2c::ERROR_OK) { |   if (err == i2c::ERROR_OK) { | ||||||
|     err = this->read(data, 1); |     err = this->read(data, 1); | ||||||
|     if (err == i2c::ERROR_OK) { |     if (err == i2c::ERROR_OK) { | ||||||
|       ESP_LOGD(TAG, "Read from switches at address 0x%02X: 0x%02X", this->address_, data[0]); |       ESP_LOGD(TAG, "Switches ADDR: 0x%02X DATA: 0x%02X", this->address_, data[0]); | ||||||
|       if (this->interrupt_pin_ != nullptr) { |       if (this->interrupt_pin_ != nullptr) { | ||||||
|         this->attach_interrupt_(this->interrupt_pin_, |         this->attach_interrupt_(this->interrupt_pin_, | ||||||
|                                 (data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE); |                                 (data[0] & 1) ? gpio::INTERRUPT_FALLING_EDGE : gpio::INTERRUPT_RISING_EDGE); | ||||||
| @@ -75,16 +79,24 @@ void GT911Touchscreen::setup() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (err != i2c::ERROR_OK) { |     if (err != i2c::ERROR_OK) { | ||||||
|       this->mark_failed("Failed to read calibration"); |       this->mark_failed("Calibration error"); | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   if (err != i2c::ERROR_OK) { |   if (err != i2c::ERROR_OK) { | ||||||
|     this->mark_failed("Failed to communicate"); |     this->mark_failed(ESP_LOG_MSG_COMM_FAIL); | ||||||
|  |     return; | ||||||
|   } |   } | ||||||
|  |   this->setup_done_ = true; | ||||||
| } | } | ||||||
|  |  | ||||||
| void GT911Touchscreen::update_touches() { | void GT911Touchscreen::update_touches() { | ||||||
|  |   this->skip_update_ = true;  // skip send touch events by default, set to false after successful error checks | ||||||
|  |   if (!this->setup_done_) { | ||||||
|  |     return; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   i2c::ErrorCode err; |   i2c::ErrorCode err; | ||||||
|   uint8_t touch_state = 0; |   uint8_t touch_state = 0; | ||||||
|   uint8_t data[MAX_TOUCHES + 1][8];  // 8 bytes each for each point, plus extra space for the key byte |   uint8_t data[MAX_TOUCHES + 1][8];  // 8 bytes each for each point, plus extra space for the key byte | ||||||
| @@ -97,7 +109,6 @@ void GT911Touchscreen::update_touches() { | |||||||
|   uint8_t num_of_touches = touch_state & 0x07; |   uint8_t num_of_touches = touch_state & 0x07; | ||||||
|  |  | ||||||
|   if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) { |   if ((touch_state & 0x80) == 0 || num_of_touches > MAX_TOUCHES) { | ||||||
|     this->skip_update_ = true;  // skip send touch events, touchscreen is not ready yet. |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -107,6 +118,7 @@ void GT911Touchscreen::update_touches() { | |||||||
|   err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1); |   err = this->read(data[0], sizeof(data[0]) * num_of_touches + 1); | ||||||
|   ERROR_CHECK(err); |   ERROR_CHECK(err); | ||||||
|  |  | ||||||
|  |   this->skip_update_ = false;  // All error checks passed, send touch events | ||||||
|   for (uint8_t i = 0; i != num_of_touches; i++) { |   for (uint8_t i = 0; i != num_of_touches; i++) { | ||||||
|     uint16_t id = data[i][0]; |     uint16_t id = data[i][0]; | ||||||
|     uint16_t x = encode_uint16(data[i][2], data[i][1]); |     uint16_t x = encode_uint16(data[i][2], data[i][1]); | ||||||
|   | |||||||
| @@ -15,8 +15,20 @@ class GT911ButtonListener { | |||||||
|  |  | ||||||
| class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { | class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice { | ||||||
|  public: |  public: | ||||||
|  |   /// @brief Initialize the GT911 touchscreen. | ||||||
|  |   /// | ||||||
|  |   /// If @ref reset_pin_ is set, the touchscreen will be hardware reset, | ||||||
|  |   /// and the rest of the setup will be scheduled to run 50ms later using @ref set_timeout() | ||||||
|  |   /// to allow the device to stabilize after reset. | ||||||
|  |   /// | ||||||
|  |   /// If @ref interrupt_pin_ is set, it will be temporarily configured during reset | ||||||
|  |   /// to control I2C address selection. | ||||||
|  |   /// | ||||||
|  |   /// After the timeout, or immediately if no reset is performed, @ref setup_internal_() | ||||||
|  |   /// is called to complete the initialization. | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |   bool can_proceed() override { return this->setup_done_; } | ||||||
|  |  | ||||||
|   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } |   void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; } | ||||||
|   void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } |   void set_reset_pin(GPIOPin *pin) { this->reset_pin_ = pin; } | ||||||
| @@ -25,8 +37,20 @@ class GT911Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice | |||||||
|  protected: |  protected: | ||||||
|   void update_touches() override; |   void update_touches() override; | ||||||
|  |  | ||||||
|   InternalGPIOPin *interrupt_pin_{}; |   /// @brief Perform the internal setup routine for the GT911 touchscreen. | ||||||
|   GPIOPin *reset_pin_{}; |   /// | ||||||
|  |   /// This function checks the I2C address, configures the interrupt pin (if available), | ||||||
|  |   /// reads the touchscreen mode from the controller, and attempts to read calibration | ||||||
|  |   /// data (maximum X and Y values) if not already set. | ||||||
|  |   /// | ||||||
|  |   /// On success, sets @ref setup_done_ to true. | ||||||
|  |   /// On failure, calls @ref mark_failed() with an appropriate error message. | ||||||
|  |   void setup_internal_(); | ||||||
|  |   /// @brief True if the touchscreen setup has completed successfully. | ||||||
|  |   bool setup_done_{false}; | ||||||
|  |  | ||||||
|  |   InternalGPIOPin *interrupt_pin_{nullptr}; | ||||||
|  |   GPIOPin *reset_pin_{nullptr}; | ||||||
|   std::vector<GT911ButtonListener *> button_listeners_; |   std::vector<GT911ButtonListener *> button_listeners_; | ||||||
|   uint8_t button_state_{0xFF};  // last button state. Initial FF guarantees first update. |   uint8_t button_state_{0xFF};  // last button state. Initial FF guarantees first update. | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -24,9 +24,6 @@ static const uint32_t READ_DURATION_MS = 16; | |||||||
| static const size_t TASK_STACK_SIZE = 4096; | static const size_t TASK_STACK_SIZE = 4096; | ||||||
| static const ssize_t TASK_PRIORITY = 23; | static const ssize_t TASK_PRIORITY = 23; | ||||||
|  |  | ||||||
| // Use an exponential moving average to correct a DC offset with weight factor 1/1000 |  | ||||||
| static const int32_t DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR = 1000; |  | ||||||
|  |  | ||||||
| static const char *const TAG = "i2s_audio.microphone"; | static const char *const TAG = "i2s_audio.microphone"; | ||||||
|  |  | ||||||
| enum MicrophoneEventGroupBits : uint32_t { | enum MicrophoneEventGroupBits : uint32_t { | ||||||
| @@ -381,26 +378,57 @@ void I2SAudioMicrophone::mic_task(void *params) { | |||||||
| } | } | ||||||
|  |  | ||||||
| void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) { | void I2SAudioMicrophone::fix_dc_offset_(std::vector<uint8_t> &data) { | ||||||
|  |   /** | ||||||
|  |    * From https://www.musicdsp.org/en/latest/Filters/135-dc-filter.html: | ||||||
|  |    * | ||||||
|  |    *     y(n) = x(n) - x(n-1) + R * y(n-1) | ||||||
|  |    *     R = 1 - (pi * 2 * frequency / samplerate) | ||||||
|  |    * | ||||||
|  |    * From https://en.wikipedia.org/wiki/Hearing_range: | ||||||
|  |    *     The human range is commonly given as 20Hz up. | ||||||
|  |    * | ||||||
|  |    * From https://en.wikipedia.org/wiki/High-resolution_audio: | ||||||
|  |    *     A reasonable upper bound for sample rate seems to be 96kHz. | ||||||
|  |    * | ||||||
|  |    * Calculate R value for 20Hz on a 96kHz sample rate: | ||||||
|  |    *     R = 1 - (pi * 2 * 20 / 96000) | ||||||
|  |    *     R = 0.9986910031 | ||||||
|  |    * | ||||||
|  |    * Transform floating point to bit-shifting approximation: | ||||||
|  |    *     output = input - prev_input + R * prev_output | ||||||
|  |    *     output = input - prev_input + (prev_output - (prev_output >> S)) | ||||||
|  |    * | ||||||
|  |    * Approximate bit-shift value S from R: | ||||||
|  |    *     R = 1 - (1 >> S) | ||||||
|  |    *     R = 1 - (1 / 2^S) | ||||||
|  |    *     R = 1 - 2^-S | ||||||
|  |    *     0.9986910031 = 1 - 2^-S | ||||||
|  |    *     S = 9.57732 ~= 10 | ||||||
|  |    * | ||||||
|  |    * Actual R from S: | ||||||
|  |    *     R = 1 - 2^-10 = 0.9990234375 | ||||||
|  |    * | ||||||
|  |    * Confirm this has effect outside human hearing on 96000kHz sample: | ||||||
|  |    *     0.9990234375 = 1 - (pi * 2 * f / 96000) | ||||||
|  |    *     f = 14.9208Hz | ||||||
|  |    * | ||||||
|  |    * Confirm this has effect outside human hearing on PDM 16kHz sample: | ||||||
|  |    *     0.9990234375 = 1 - (pi * 2 * f / 16000) | ||||||
|  |    *     f = 2.4868Hz | ||||||
|  |    * | ||||||
|  |    */ | ||||||
|  |   const uint8_t dc_filter_shift = 10; | ||||||
|   const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1); |   const size_t bytes_per_sample = this->audio_stream_info_.samples_to_bytes(1); | ||||||
|   const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size()); |   const uint32_t total_samples = this->audio_stream_info_.bytes_to_samples(data.size()); | ||||||
|  |  | ||||||
|   if (total_samples == 0) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   int64_t offset_accumulator = 0; |  | ||||||
|   for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) { |   for (uint32_t sample_index = 0; sample_index < total_samples; ++sample_index) { | ||||||
|     const uint32_t byte_index = sample_index * bytes_per_sample; |     const uint32_t byte_index = sample_index * bytes_per_sample; | ||||||
|     int32_t sample = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample); |     int32_t input = audio::unpack_audio_sample_to_q31(&data[byte_index], bytes_per_sample); | ||||||
|     offset_accumulator += sample; |     int32_t output = input - this->dc_offset_prev_input_ + | ||||||
|     sample -= this->dc_offset_; |                      (this->dc_offset_prev_output_ - (this->dc_offset_prev_output_ >> dc_filter_shift)); | ||||||
|     audio::pack_q31_as_audio_sample(sample, &data[byte_index], bytes_per_sample); |     this->dc_offset_prev_input_ = input; | ||||||
|  |     this->dc_offset_prev_output_ = output; | ||||||
|  |     audio::pack_q31_as_audio_sample(output, &data[byte_index], bytes_per_sample); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   const int32_t new_offset = offset_accumulator / total_samples; |  | ||||||
|   this->dc_offset_ = new_offset / DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR + |  | ||||||
|                      (DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR - 1) * this->dc_offset_ / |  | ||||||
|                          DC_OFFSET_MOVING_AVERAGE_COEFFICIENT_DENOMINATOR; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) { | size_t I2SAudioMicrophone::read_(uint8_t *buf, size_t len, TickType_t ticks_to_wait) { | ||||||
|   | |||||||
| @@ -82,7 +82,8 @@ class I2SAudioMicrophone : public I2SAudioIn, public microphone::Microphone, pub | |||||||
|  |  | ||||||
|   bool correct_dc_offset_; |   bool correct_dc_offset_; | ||||||
|   bool locked_driver_{false}; |   bool locked_driver_{false}; | ||||||
|   int32_t dc_offset_{0}; |   int32_t dc_offset_prev_input_{0}; | ||||||
|  |   int32_t dc_offset_prev_output_{0}; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| }  // namespace i2s_audio | }  // namespace i2s_audio | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user