mirror of
https://github.com/esphome/esphome.git
synced 2025-09-06 13:22:19 +01:00
Merge branch 'helpful_custom_api_error' into integration
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