esphome 2024.6.6__py3-none-any.whl → 2024.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. esphome/components/aht10/aht10.cpp +4 -2
  2. esphome/components/climate/climate.cpp +10 -6
  3. esphome/components/climate/climate_traits.h +3 -3
  4. esphome/components/cover/cover.h +2 -2
  5. esphome/components/esp32_camera/__init__.py +6 -3
  6. esphome/components/esp32_can/canbus.py +3 -0
  7. esphome/components/ethernet/ethernet_component.cpp +8 -3
  8. esphome/components/font/__init__.py +2 -28
  9. esphome/components/gree/climate.py +1 -0
  10. esphome/components/gree/gree.cpp +11 -3
  11. esphome/components/gree/gree.h +5 -1
  12. esphome/components/haier/binary_sensor/__init__.py +4 -4
  13. esphome/components/haier/button/__init__.py +1 -1
  14. esphome/components/haier/climate.py +43 -9
  15. esphome/components/haier/haier_base.cpp +4 -0
  16. esphome/components/haier/haier_base.h +11 -1
  17. esphome/components/haier/hon_climate.cpp +109 -55
  18. esphome/components/haier/hon_climate.h +7 -1
  19. esphome/components/haier/hon_packet.h +5 -0
  20. esphome/components/haier/sensor/__init__.py +5 -5
  21. esphome/components/haier/smartair2_climate.cpp +1 -0
  22. esphome/components/haier/text_sensor/__init__.py +4 -4
  23. esphome/components/heatpumpir/climate.py +12 -5
  24. esphome/components/heatpumpir/heatpumpir.cpp +11 -0
  25. esphome/components/heatpumpir/heatpumpir.h +11 -0
  26. esphome/components/http_request/http_request_arduino.cpp +7 -2
  27. esphome/components/http_request/update/http_request_update.cpp +6 -7
  28. esphome/components/http_request/update/http_request_update.h +0 -3
  29. esphome/components/i2s_audio/__init__.py +10 -0
  30. esphome/components/i2s_audio/microphone/__init__.py +7 -0
  31. esphome/components/i2s_audio/microphone/i2s_audio_microphone.cpp +2 -3
  32. esphome/components/i2s_audio/microphone/i2s_audio_microphone.h +3 -0
  33. esphome/components/image/__init__.py +2 -29
  34. esphome/components/improv_serial/improv_serial_component.cpp +9 -8
  35. esphome/components/ltr390/ltr390.cpp +44 -29
  36. esphome/components/ltr390/ltr390.h +9 -5
  37. esphome/components/ltr390/sensor.py +35 -5
  38. esphome/components/mdns/__init__.py +3 -3
  39. esphome/components/mdns/mdns_component.cpp +3 -1
  40. esphome/components/mdns/mdns_component.h +3 -1
  41. esphome/components/mdns/mdns_esp32.cpp +2 -1
  42. esphome/components/mdns/mdns_esp8266.cpp +2 -1
  43. esphome/components/mdns/mdns_host.cpp +2 -1
  44. esphome/components/mdns/mdns_libretiny.cpp +2 -1
  45. esphome/components/mdns/mdns_rp2040.cpp +2 -1
  46. esphome/components/micro_wake_word/__init__.py +205 -56
  47. esphome/components/micro_wake_word/micro_wake_word.cpp +225 -275
  48. esphome/components/micro_wake_word/micro_wake_word.h +77 -107
  49. esphome/components/micro_wake_word/preprocessor_settings.h +20 -0
  50. esphome/components/micro_wake_word/streaming_model.cpp +189 -0
  51. esphome/components/micro_wake_word/streaming_model.h +84 -0
  52. esphome/components/mitsubishi/mitsubishi.cpp +1 -0
  53. esphome/components/modbus_controller/text_sensor/__init__.py +2 -1
  54. esphome/components/modbus_controller/text_sensor/modbus_textsensor.cpp +4 -1
  55. esphome/components/modbus_controller/text_sensor/modbus_textsensor.h +1 -1
  56. esphome/components/number/__init__.py +2 -0
  57. esphome/components/ota/ota_backend_arduino_esp32.cpp +22 -7
  58. esphome/components/ota/ota_backend_arduino_esp8266.cpp +23 -8
  59. esphome/components/ota/ota_backend_arduino_libretiny.cpp +22 -7
  60. esphome/components/ota/ota_backend_arduino_rp2040.cpp +22 -7
  61. esphome/components/pmsa003i/pmsa003i.cpp +9 -0
  62. esphome/components/qspi_amoled/display.py +16 -4
  63. esphome/components/qspi_amoled/qspi_amoled.cpp +16 -0
  64. esphome/components/qspi_amoled/qspi_amoled.h +0 -3
  65. esphome/components/remote_base/dooya_protocol.cpp +4 -4
  66. esphome/components/remote_base/rc_switch_protocol.cpp +1 -1
  67. esphome/components/restart/button/__init__.py +2 -0
  68. esphome/components/script/__init__.py +1 -1
  69. esphome/components/sensor/__init__.py +2 -0
  70. esphome/components/tuya/tuya.cpp +8 -2
  71. esphome/components/tuya/tuya.h +3 -1
  72. esphome/components/uart/__init__.py +72 -9
  73. esphome/components/uart/uart_component_esp32_arduino.cpp +18 -4
  74. esphome/components/uart/uart_component_esp_idf.cpp +22 -2
  75. esphome/components/uart/uart_component_host.cpp +295 -0
  76. esphome/components/uart/uart_component_host.h +38 -0
  77. esphome/components/uptime/sensor.py +44 -11
  78. esphome/components/uptime/{uptime_sensor.cpp → uptime_seconds_sensor.cpp} +11 -7
  79. esphome/components/uptime/{uptime_sensor.h → uptime_seconds_sensor.h} +2 -2
  80. esphome/components/uptime/uptime_timestamp_sensor.cpp +39 -0
  81. esphome/components/uptime/uptime_timestamp_sensor.h +30 -0
  82. esphome/components/veml7700/veml7700.cpp +1 -1
  83. esphome/components/veml7700/veml7700.h +5 -5
  84. esphome/components/voice_assistant/voice_assistant.cpp +4 -2
  85. esphome/components/web_server/server_index_v2.h +42 -41
  86. esphome/components/web_server/server_index_v3.h +368 -367
  87. esphome/components/wifi/wifi_component_esp_idf.cpp +1 -1
  88. esphome/components/wifi/wifi_component_pico_w.cpp +18 -2
  89. esphome/components/wireguard/__init__.py +1 -1
  90. esphome/components/x9c/output.py +7 -1
  91. esphome/const.py +2 -1
  92. esphome/core/defines.h +1 -0
  93. esphome/core/helpers.cpp +2 -2
  94. esphome/core/helpers.h +1 -1
  95. esphome/external_files.py +26 -0
  96. {esphome-2024.6.6.dist-info → esphome-2024.7.0.dist-info}/METADATA +1 -1
  97. {esphome-2024.6.6.dist-info → esphome-2024.7.0.dist-info}/RECORD +101 -95
  98. esphome/components/micro_wake_word/audio_preprocessor_int8_model_data.h +0 -493
  99. {esphome-2024.6.6.dist-info → esphome-2024.7.0.dist-info}/LICENSE +0 -0
  100. {esphome-2024.6.6.dist-info → esphome-2024.7.0.dist-info}/WHEEL +0 -0
  101. {esphome-2024.6.6.dist-info → esphome-2024.7.0.dist-info}/entry_points.txt +0 -0
  102. {esphome-2024.6.6.dist-info → esphome-2024.7.0.dist-info}/top_level.txt +0 -0
@@ -1,21 +1,18 @@
1
1
  #pragma once
2
2
 
3
- /**
4
- * This is a workaround until we can figure out a way to get
5
- * the tflite-micro idf component code available in CI
6
- *
7
- * */
8
- //
9
- #ifndef CLANG_TIDY
10
-
11
3
  #ifdef USE_ESP_IDF
12
4
 
5
+ #include "preprocessor_settings.h"
6
+ #include "streaming_model.h"
7
+
13
8
  #include "esphome/core/automation.h"
14
9
  #include "esphome/core/component.h"
15
10
  #include "esphome/core/ring_buffer.h"
16
11
 
17
12
  #include "esphome/components/microphone/microphone.h"
18
13
 
14
+ #include <frontend_util.h>
15
+
19
16
  #include <tensorflow/lite/core/c/common.h>
20
17
  #include <tensorflow/lite/micro/micro_interpreter.h>
21
18
  #include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
@@ -23,35 +20,6 @@
23
20
  namespace esphome {
24
21
  namespace micro_wake_word {
25
22
 
26
- // The following are dictated by the preprocessor model
27
- //
28
- // The number of features the audio preprocessor generates per slice
29
- static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40;
30
- // How frequently the preprocessor generates a new set of features
31
- static const uint8_t FEATURE_STRIDE_MS = 20;
32
- // Duration of each slice used as input into the preprocessor
33
- static const uint8_t FEATURE_DURATION_MS = 30;
34
- // Audio sample frequency in hertz
35
- static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000;
36
- // The number of old audio samples that are saved to be part of the next feature window
37
- static const uint16_t HISTORY_SAMPLES_TO_KEEP =
38
- ((FEATURE_DURATION_MS - FEATURE_STRIDE_MS) * (AUDIO_SAMPLE_FREQUENCY / 1000));
39
- // The number of new audio samples to receive to be included with the next feature window
40
- static const uint16_t NEW_SAMPLES_TO_GET = (FEATURE_STRIDE_MS * (AUDIO_SAMPLE_FREQUENCY / 1000));
41
- // The total number of audio samples included in the feature window
42
- static const uint16_t SAMPLE_DURATION_COUNT = FEATURE_DURATION_MS * AUDIO_SAMPLE_FREQUENCY / 1000;
43
- // Number of bytes in memory needed for the preprocessor arena
44
- static const uint32_t PREPROCESSOR_ARENA_SIZE = 9528;
45
-
46
- // The following configure the streaming wake word model
47
- //
48
- // The number of audio slices to process before accepting a positive detection
49
- static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74;
50
-
51
- // Number of bytes in memory needed for the streaming wake word model
52
- static const uint32_t STREAMING_MODEL_ARENA_SIZE = 64000;
53
- static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024;
54
-
55
23
  enum State {
56
24
  IDLE,
57
25
  START_MICROPHONE,
@@ -61,6 +29,9 @@ enum State {
61
29
  STOPPING_MICROPHONE,
62
30
  };
63
31
 
32
+ // The number of audio slices to process before accepting a positive detection
33
+ static const uint8_t MIN_SLICES_BEFORE_DETECTION = 74;
34
+
64
35
  class MicroWakeWord : public Component {
65
36
  public:
66
37
  void setup() override;
@@ -73,28 +44,21 @@ class MicroWakeWord : public Component {
73
44
 
74
45
  bool is_running() const { return this->state_ != State::IDLE; }
75
46
 
76
- bool initialize_models();
77
-
78
- std::string get_wake_word() { return this->wake_word_; }
79
-
80
- // Increasing either of these will reduce the rate of false acceptances while increasing the false rejection rate
81
- void set_probability_cutoff(float probability_cutoff) { this->probability_cutoff_ = probability_cutoff; }
82
- void set_sliding_window_average_size(size_t size);
47
+ void set_features_step_size(uint8_t step_size) { this->features_step_size_ = step_size; }
83
48
 
84
49
  void set_microphone(microphone::Microphone *microphone) { this->microphone_ = microphone; }
85
50
 
86
51
  Trigger<std::string> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; }
87
52
 
88
- void set_model_start(const uint8_t *model_start) { this->model_start_ = model_start; }
89
- void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; }
90
-
91
- protected:
92
- void set_state_(State state);
93
- int read_microphone_();
53
+ void add_wake_word_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size,
54
+ const std::string &wake_word, size_t tensor_arena_size);
94
55
 
95
- const uint8_t *model_start_;
96
- std::string wake_word_;
56
+ #ifdef USE_MICRO_WAKE_WORD_VAD
57
+ void add_vad_model(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size,
58
+ size_t tensor_arena_size);
59
+ #endif
97
60
 
61
+ protected:
98
62
  microphone::Microphone *microphone_{nullptr};
99
63
  Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>();
100
64
  State state_{State::IDLE};
@@ -102,85 +66,93 @@ class MicroWakeWord : public Component {
102
66
 
103
67
  std::unique_ptr<RingBuffer> ring_buffer_;
104
68
 
105
- int16_t *input_buffer_;
69
+ std::vector<WakeWordModel> wake_word_models_;
106
70
 
107
- const tflite::Model *preprocessor_model_{nullptr};
108
- const tflite::Model *streaming_model_{nullptr};
109
- tflite::MicroInterpreter *streaming_interpreter_{nullptr};
110
- tflite::MicroInterpreter *preprocessor_interperter_{nullptr};
71
+ #ifdef USE_MICRO_WAKE_WORD_VAD
72
+ std::unique_ptr<VADModel> vad_model_;
73
+ #endif
111
74
 
112
- std::vector<float> recent_streaming_probabilities_;
113
- size_t last_n_index_{0};
75
+ tflite::MicroMutableOpResolver<20> streaming_op_resolver_;
114
76
 
115
- float probability_cutoff_{0.5};
116
- size_t sliding_window_average_size_{10};
77
+ // Audio frontend handles generating spectrogram features
78
+ struct FrontendConfig frontend_config_;
79
+ struct FrontendState frontend_state_;
117
80
 
118
- // When the wake word detection first starts or after the word has been detected once, we ignore this many audio
119
- // feature slices before accepting a positive detection again
81
+ // When the wake word detection first starts, we ignore this many audio
82
+ // feature slices before accepting a positive detection
120
83
  int16_t ignore_windows_{-MIN_SLICES_BEFORE_DETECTION};
121
84
 
122
- uint8_t *streaming_var_arena_{nullptr};
123
- uint8_t *streaming_tensor_arena_{nullptr};
124
- uint8_t *preprocessor_tensor_arena_{nullptr};
125
- int8_t *new_features_data_{nullptr};
126
-
127
- tflite::MicroResourceVariables *mrv_{nullptr};
85
+ uint8_t features_step_size_;
128
86
 
129
- // Stores audio fed into feature generator preprocessor
130
- int16_t *preprocessor_audio_buffer_;
87
+ // Stores audio read from the microphone before being added to the ring buffer.
88
+ int16_t *input_buffer_{nullptr};
89
+ // Stores audio to be fed into the audio frontend for generating features.
90
+ int16_t *preprocessor_audio_buffer_{nullptr};
131
91
 
132
92
  bool detected_{false};
93
+ std::string detected_wake_word_{""};
133
94
 
134
- /** Detects if wake word has been said
135
- *
136
- * If enough audio samples are available, it will generate one slice of new features.
137
- * If the streaming model predicts the wake word, then the nonstreaming model confirms it.
138
- * @param ring_Buffer Ring buffer containing raw audio samples
139
- * @return True if the wake word is detected, false otherwise
140
- */
141
- bool detect_wake_word_();
95
+ void set_state_(State state);
142
96
 
143
- /// @brief Returns true if there are enough audio samples in the buffer to generate another slice of features
144
- bool slice_available_();
97
+ /// @brief Tests if there are enough samples in the ring buffer to generate new features.
98
+ /// @return True if enough samples, false otherwise.
99
+ bool has_enough_samples_();
145
100
 
146
- /** Shifts previous feature slices over by one and generates a new slice of features
101
+ /** Reads audio from microphone into the ring buffer
147
102
  *
148
- * @param ring_buffer ring buffer containing raw audio samples
149
- * @return True if a new slice of features was generated, false otherwise
103
+ * Audio data (16000 kHz with int16 samples) is read into the input_buffer_.
104
+ * Verifies the ring buffer has enough space for all audio data. If not, it logs
105
+ * a warning and resets the ring buffer entirely.
106
+ * @return Number of bytes written to the ring buffer
150
107
  */
151
- bool update_features_();
108
+ size_t read_microphone_();
109
+
110
+ /// @brief Allocates memory for input_buffer_, preprocessor_audio_buffer_, and ring_buffer_
111
+ /// @return True if successful, false otherwise
112
+ bool allocate_buffers_();
113
+
114
+ /// @brief Frees memory allocated for input_buffer_ and preprocessor_audio_buffer_
115
+ void deallocate_buffers_();
152
116
 
153
- /** Generates features from audio samples
117
+ /// @brief Loads streaming models and prepares the feature generation frontend
118
+ /// @return True if successful, false otherwise
119
+ bool load_models_();
120
+
121
+ /// @brief Deletes each model's TFLite interpreters and frees tensor arena memory. Frees memory used by the feature
122
+ /// generation frontend.
123
+ void unload_models_();
124
+
125
+ /** Performs inference with each configured model
154
126
  *
155
- * Adapted from TFLite micro speech example
156
- * @param audio_data Pointer to array with the audio samples
157
- * @param audio_data_size The number of samples to use as input to the preprocessor model
158
- * @param feature_output Array that will store the features
159
- * @return True if successful, false otherwise.
127
+ * If enough audio samples are available, it will generate one slice of new features.
128
+ * It then loops through and performs inference with each of the loaded models.
160
129
  */
161
- bool generate_single_feature_(const int16_t *audio_data, int audio_data_size,
162
- int8_t feature_output[PREPROCESSOR_FEATURE_SIZE]);
130
+ void update_model_probabilities_();
163
131
 
164
- /** Performs inference over the most recent feature slice with the streaming model
132
+ /** Checks every model's recent probabilities to determine if the wake word has been predicted
165
133
  *
166
- * @return Probability of the wake word between 0.0 and 1.0
134
+ * Verifies the models have processed enough new samples for accurate predictions.
135
+ * Sets detected_wake_word_ to the wake word, if one is detected.
136
+ * @return True if a wake word is predicted, false otherwise
167
137
  */
168
- float perform_streaming_inference_();
138
+ bool detect_wake_words_();
169
139
 
170
- /** Strides the audio samples by keeping the last 10 ms of the previous slice
140
+ /** Generates features for a window of audio samples
171
141
  *
172
- * Adapted from the TFLite micro speech example
173
- * @param ring_buffer Ring buffer containing raw audio samples
174
- * @param audio_samples Pointer to an array that will store the strided audio samples
175
- * @return True if successful, false otherwise
142
+ * Reads samples from the ring buffer and feeds them into the preprocessor frontend.
143
+ * Adapted from TFLite microspeech frontend.
144
+ * @param features int8_t array to store the audio features
145
+ * @return True if successful, false otherwise.
176
146
  */
177
- bool stride_audio_samples_(int16_t **audio_samples);
147
+ bool generate_features_for_window_(int8_t features[PREPROCESSOR_FEATURE_SIZE]);
178
148
 
179
- /// @brief Returns true if successfully registered the preprocessor's TensorFlow operations
180
- bool register_preprocessor_ops_(tflite::MicroMutableOpResolver<18> &op_resolver);
149
+ /// @brief Resets the ring buffer, ignore_windows_, and sliding window probabilities
150
+ void reset_states_();
181
151
 
182
152
  /// @brief Returns true if successfully registered the streaming model's TensorFlow operations
183
- bool register_streaming_ops_(tflite::MicroMutableOpResolver<17> &op_resolver);
153
+ bool register_streaming_ops_(tflite::MicroMutableOpResolver<20> &op_resolver);
154
+
155
+ inline uint16_t new_samples_to_get_() { return (this->features_step_size_ * (AUDIO_SAMPLE_FREQUENCY / 1000)); }
184
156
  };
185
157
 
186
158
  template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<MicroWakeWord> {
@@ -202,5 +174,3 @@ template<typename... Ts> class IsRunningCondition : public Condition<Ts...>, pub
202
174
  } // namespace esphome
203
175
 
204
176
  #endif // USE_ESP_IDF
205
-
206
- #endif // CLANG_TIDY
@@ -0,0 +1,20 @@
1
+ #pragma once
2
+
3
+ #ifdef USE_ESP_IDF
4
+
5
+ #include <cstdint>
6
+
7
+ namespace esphome {
8
+ namespace micro_wake_word {
9
+
10
+ // The number of features the audio preprocessor generates per slice
11
+ static const uint8_t PREPROCESSOR_FEATURE_SIZE = 40;
12
+ // Duration of each slice used as input into the preprocessor
13
+ static const uint8_t FEATURE_DURATION_MS = 30;
14
+ // Audio sample frequency in hertz
15
+ static const uint16_t AUDIO_SAMPLE_FREQUENCY = 16000;
16
+
17
+ } // namespace micro_wake_word
18
+ } // namespace esphome
19
+
20
+ #endif
@@ -0,0 +1,189 @@
1
+ #ifdef USE_ESP_IDF
2
+
3
+ #include "streaming_model.h"
4
+
5
+ #include "esphome/core/hal.h"
6
+ #include "esphome/core/helpers.h"
7
+ #include "esphome/core/log.h"
8
+
9
+ static const char *const TAG = "micro_wake_word";
10
+
11
+ namespace esphome {
12
+ namespace micro_wake_word {
13
+
14
+ void WakeWordModel::log_model_config() {
15
+ ESP_LOGCONFIG(TAG, " - Wake Word: %s", this->wake_word_.c_str());
16
+ ESP_LOGCONFIG(TAG, " Probability cutoff: %.3f", this->probability_cutoff_);
17
+ ESP_LOGCONFIG(TAG, " Sliding window size: %d", this->sliding_window_size_);
18
+ }
19
+
20
+ void VADModel::log_model_config() {
21
+ ESP_LOGCONFIG(TAG, " - VAD Model");
22
+ ESP_LOGCONFIG(TAG, " Probability cutoff: %.3f", this->probability_cutoff_);
23
+ ESP_LOGCONFIG(TAG, " Sliding window size: %d", this->sliding_window_size_);
24
+ }
25
+
26
+ bool StreamingModel::load_model(tflite::MicroMutableOpResolver<20> &op_resolver) {
27
+ ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
28
+
29
+ if (this->tensor_arena_ == nullptr) {
30
+ this->tensor_arena_ = arena_allocator.allocate(this->tensor_arena_size_);
31
+ if (this->tensor_arena_ == nullptr) {
32
+ ESP_LOGE(TAG, "Could not allocate the streaming model's tensor arena.");
33
+ return false;
34
+ }
35
+ }
36
+
37
+ if (this->var_arena_ == nullptr) {
38
+ this->var_arena_ = arena_allocator.allocate(STREAMING_MODEL_VARIABLE_ARENA_SIZE);
39
+ if (this->var_arena_ == nullptr) {
40
+ ESP_LOGE(TAG, "Could not allocate the streaming model's variable tensor arena.");
41
+ return false;
42
+ }
43
+ this->ma_ = tflite::MicroAllocator::Create(this->var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE);
44
+ this->mrv_ = tflite::MicroResourceVariables::Create(this->ma_, 20);
45
+ }
46
+
47
+ const tflite::Model *model = tflite::GetModel(this->model_start_);
48
+ if (model->version() != TFLITE_SCHEMA_VERSION) {
49
+ ESP_LOGE(TAG, "Streaming model's schema is not supported");
50
+ return false;
51
+ }
52
+
53
+ if (this->interpreter_ == nullptr) {
54
+ this->interpreter_ = make_unique<tflite::MicroInterpreter>(
55
+ tflite::GetModel(this->model_start_), op_resolver, this->tensor_arena_, this->tensor_arena_size_, this->mrv_);
56
+ if (this->interpreter_->AllocateTensors() != kTfLiteOk) {
57
+ ESP_LOGE(TAG, "Failed to allocate tensors for the streaming model");
58
+ return false;
59
+ }
60
+
61
+ // Verify input tensor matches expected values
62
+ // Dimension 3 will represent the first layer stride, so skip it may vary
63
+ TfLiteTensor *input = this->interpreter_->input(0);
64
+ if ((input->dims->size != 3) || (input->dims->data[0] != 1) ||
65
+ (input->dims->data[2] != PREPROCESSOR_FEATURE_SIZE)) {
66
+ ESP_LOGE(TAG, "Streaming model tensor input dimensions has improper dimensions.");
67
+ return false;
68
+ }
69
+
70
+ if (input->type != kTfLiteInt8) {
71
+ ESP_LOGE(TAG, "Streaming model tensor input is not int8.");
72
+ return false;
73
+ }
74
+
75
+ // Verify output tensor matches expected values
76
+ TfLiteTensor *output = this->interpreter_->output(0);
77
+ if ((output->dims->size != 2) || (output->dims->data[0] != 1) || (output->dims->data[1] != 1)) {
78
+ ESP_LOGE(TAG, "Streaming model tensor output dimension is not 1x1.");
79
+ }
80
+
81
+ if (output->type != kTfLiteUInt8) {
82
+ ESP_LOGE(TAG, "Streaming model tensor output is not uint8.");
83
+ return false;
84
+ }
85
+ }
86
+
87
+ return true;
88
+ }
89
+
90
+ void StreamingModel::unload_model() {
91
+ this->interpreter_.reset();
92
+
93
+ ExternalRAMAllocator<uint8_t> arena_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
94
+
95
+ arena_allocator.deallocate(this->tensor_arena_, this->tensor_arena_size_);
96
+ this->tensor_arena_ = nullptr;
97
+ arena_allocator.deallocate(this->var_arena_, STREAMING_MODEL_VARIABLE_ARENA_SIZE);
98
+ this->var_arena_ = nullptr;
99
+ }
100
+
101
+ bool StreamingModel::perform_streaming_inference(const int8_t features[PREPROCESSOR_FEATURE_SIZE]) {
102
+ if (this->interpreter_ != nullptr) {
103
+ TfLiteTensor *input = this->interpreter_->input(0);
104
+
105
+ std::memmove(
106
+ (int8_t *) (tflite::GetTensorData<int8_t>(input)) + PREPROCESSOR_FEATURE_SIZE * this->current_stride_step_,
107
+ features, PREPROCESSOR_FEATURE_SIZE);
108
+ ++this->current_stride_step_;
109
+
110
+ uint8_t stride = this->interpreter_->input(0)->dims->data[1];
111
+
112
+ if (this->current_stride_step_ >= stride) {
113
+ this->current_stride_step_ = 0;
114
+
115
+ TfLiteStatus invoke_status = this->interpreter_->Invoke();
116
+ if (invoke_status != kTfLiteOk) {
117
+ ESP_LOGW(TAG, "Streaming interpreter invoke failed");
118
+ return false;
119
+ }
120
+
121
+ TfLiteTensor *output = this->interpreter_->output(0);
122
+
123
+ ++this->last_n_index_;
124
+ if (this->last_n_index_ == this->sliding_window_size_)
125
+ this->last_n_index_ = 0;
126
+ this->recent_streaming_probabilities_[this->last_n_index_] = output->data.uint8[0]; // probability;
127
+ }
128
+ return true;
129
+ }
130
+ ESP_LOGE(TAG, "Streaming interpreter is not initialized.");
131
+ return false;
132
+ }
133
+
134
+ void StreamingModel::reset_probabilities() {
135
+ for (auto &prob : this->recent_streaming_probabilities_) {
136
+ prob = 0;
137
+ }
138
+ }
139
+
140
+ WakeWordModel::WakeWordModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size,
141
+ const std::string &wake_word, size_t tensor_arena_size) {
142
+ this->model_start_ = model_start;
143
+ this->probability_cutoff_ = probability_cutoff;
144
+ this->sliding_window_size_ = sliding_window_average_size;
145
+ this->recent_streaming_probabilities_.resize(sliding_window_average_size, 0);
146
+ this->wake_word_ = wake_word;
147
+ this->tensor_arena_size_ = tensor_arena_size;
148
+ };
149
+
150
+ bool WakeWordModel::determine_detected() {
151
+ int32_t sum = 0;
152
+ for (auto &prob : this->recent_streaming_probabilities_) {
153
+ sum += prob;
154
+ }
155
+
156
+ float sliding_window_average = static_cast<float>(sum) / static_cast<float>(255 * this->sliding_window_size_);
157
+
158
+ // Detect the wake word if the sliding window average is above the cutoff
159
+ if (sliding_window_average > this->probability_cutoff_) {
160
+ ESP_LOGD(TAG, "The '%s' model sliding average probability is %.3f and most recent probability is %.3f",
161
+ this->wake_word_.c_str(), sliding_window_average,
162
+ this->recent_streaming_probabilities_[this->last_n_index_] / (255.0));
163
+ return true;
164
+ }
165
+ return false;
166
+ }
167
+
168
+ VADModel::VADModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size,
169
+ size_t tensor_arena_size) {
170
+ this->model_start_ = model_start;
171
+ this->probability_cutoff_ = probability_cutoff;
172
+ this->sliding_window_size_ = sliding_window_size;
173
+ this->recent_streaming_probabilities_.resize(sliding_window_size, 0);
174
+ this->tensor_arena_size_ = tensor_arena_size;
175
+ };
176
+
177
+ bool VADModel::determine_detected() {
178
+ uint8_t max = 0;
179
+ for (auto &prob : this->recent_streaming_probabilities_) {
180
+ max = std::max(prob, max);
181
+ }
182
+
183
+ return max > this->probability_cutoff_;
184
+ }
185
+
186
+ } // namespace micro_wake_word
187
+ } // namespace esphome
188
+
189
+ #endif
@@ -0,0 +1,84 @@
1
+ #pragma once
2
+
3
+ #ifdef USE_ESP_IDF
4
+
5
+ #include "preprocessor_settings.h"
6
+
7
+ #include <tensorflow/lite/core/c/common.h>
8
+ #include <tensorflow/lite/micro/micro_interpreter.h>
9
+ #include <tensorflow/lite/micro/micro_mutable_op_resolver.h>
10
+
11
+ namespace esphome {
12
+ namespace micro_wake_word {
13
+
14
+ static const uint32_t STREAMING_MODEL_VARIABLE_ARENA_SIZE = 1024;
15
+
16
+ class StreamingModel {
17
+ public:
18
+ virtual void log_model_config() = 0;
19
+ virtual bool determine_detected() = 0;
20
+
21
+ bool perform_streaming_inference(const int8_t features[PREPROCESSOR_FEATURE_SIZE]);
22
+
23
+ /// @brief Sets all recent_streaming_probabilities to 0
24
+ void reset_probabilities();
25
+
26
+ /// @brief Allocates tensor and variable arenas and sets up the model interpreter
27
+ /// @param op_resolver MicroMutableOpResolver object that must exist until the model is unloaded
28
+ /// @return True if successful, false otherwise
29
+ bool load_model(tflite::MicroMutableOpResolver<20> &op_resolver);
30
+
31
+ /// @brief Destroys the TFLite interpreter and frees the tensor and variable arenas' memory
32
+ void unload_model();
33
+
34
+ protected:
35
+ uint8_t current_stride_step_{0};
36
+
37
+ float probability_cutoff_;
38
+ size_t sliding_window_size_;
39
+ size_t last_n_index_{0};
40
+ size_t tensor_arena_size_;
41
+ std::vector<uint8_t> recent_streaming_probabilities_;
42
+
43
+ const uint8_t *model_start_;
44
+ uint8_t *tensor_arena_{nullptr};
45
+ uint8_t *var_arena_{nullptr};
46
+ std::unique_ptr<tflite::MicroInterpreter> interpreter_;
47
+ tflite::MicroResourceVariables *mrv_{nullptr};
48
+ tflite::MicroAllocator *ma_{nullptr};
49
+ };
50
+
51
+ class WakeWordModel final : public StreamingModel {
52
+ public:
53
+ WakeWordModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_average_size,
54
+ const std::string &wake_word, size_t tensor_arena_size);
55
+
56
+ void log_model_config() override;
57
+
58
+ /// @brief Checks for the wake word by comparing the mean probability in the sliding window with the probability
59
+ /// cutoff
60
+ /// @return True if wake word is detected, false otherwise
61
+ bool determine_detected() override;
62
+
63
+ const std::string &get_wake_word() const { return this->wake_word_; }
64
+
65
+ protected:
66
+ std::string wake_word_;
67
+ };
68
+
69
+ class VADModel final : public StreamingModel {
70
+ public:
71
+ VADModel(const uint8_t *model_start, float probability_cutoff, size_t sliding_window_size, size_t tensor_arena_size);
72
+
73
+ void log_model_config() override;
74
+
75
+ /// @brief Checks for voice activity by comparing the max probability in the sliding window with the probability
76
+ /// cutoff
77
+ /// @return True if voice activity is detected, false otherwise
78
+ bool determine_detected() override;
79
+ };
80
+
81
+ } // namespace micro_wake_word
82
+ } // namespace esphome
83
+
84
+ #endif
@@ -52,6 +52,7 @@ const uint8_t MITSUBISHI_BYTE16 = 0X00;
52
52
 
53
53
  climate::ClimateTraits MitsubishiClimate::traits() {
54
54
  auto traits = climate::ClimateTraits();
55
+ traits.set_supports_current_temperature(this->sensor_ != nullptr);
55
56
  traits.set_supports_action(false);
56
57
  traits.set_visual_min_temperature(MITSUBISHI_TEMP_MIN);
57
58
  traits.set_visual_max_temperature(MITSUBISHI_TEMP_MAX);
@@ -37,6 +37,7 @@ RAW_ENCODING = {
37
37
  "NONE": RawEncoding.NONE,
38
38
  "HEXBYTES": RawEncoding.HEXBYTES,
39
39
  "COMMA": RawEncoding.COMMA,
40
+ "ANSI": RawEncoding.ANSI,
40
41
  }
41
42
 
42
43
  CONFIG_SCHEMA = cv.All(
@@ -49,7 +50,7 @@ CONFIG_SCHEMA = cv.All(
49
50
  cv.Optional(CONF_REGISTER_TYPE): cv.enum(MODBUS_REGISTER_TYPE),
50
51
  cv.Optional(CONF_REGISTER_COUNT, default=0): cv.positive_int,
51
52
  cv.Optional(CONF_RESPONSE_SIZE, default=2): cv.positive_int,
52
- cv.Optional(CONF_RAW_ENCODE, default="NONE"): cv.enum(RAW_ENCODING),
53
+ cv.Optional(CONF_RAW_ENCODE, default="ANSI"): cv.enum(RAW_ENCODING),
53
54
  }
54
55
  ),
55
56
  validate_modbus_register,
@@ -27,8 +27,11 @@ void ModbusTextSensor::parse_and_publish(const std::vector<uint8_t> &data) {
27
27
  sprintf(buffer, index != this->offset ? ",%d" : "%d", b);
28
28
  output << buffer;
29
29
  break;
30
+ case RawEncoding::ANSI:
31
+ if (b < 0x20)
32
+ break;
33
+ // FALLTHROUGH
30
34
  // Anything else no encoding
31
- case RawEncoding::NONE:
32
35
  default:
33
36
  output << (char) b;
34
37
  break;
@@ -9,7 +9,7 @@
9
9
  namespace esphome {
10
10
  namespace modbus_controller {
11
11
 
12
- enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2 };
12
+ enum class RawEncoding { NONE = 0, HEXBYTES = 1, COMMA = 2, ANSI = 3 };
13
13
 
14
14
  class ModbusTextSensor : public Component, public text_sensor::TextSensor, public SensorItem {
15
15
  public:
@@ -26,6 +26,7 @@ from esphome.const import (
26
26
  DEVICE_CLASS_BATTERY,
27
27
  DEVICE_CLASS_CARBON_DIOXIDE,
28
28
  DEVICE_CLASS_CARBON_MONOXIDE,
29
+ DEVICE_CLASS_CONDUCTIVITY,
29
30
  DEVICE_CLASS_CURRENT,
30
31
  DEVICE_CLASS_DATA_RATE,
31
32
  DEVICE_CLASS_DATA_SIZE,
@@ -82,6 +83,7 @@ DEVICE_CLASSES = [
82
83
  DEVICE_CLASS_BATTERY,
83
84
  DEVICE_CLASS_CARBON_DIOXIDE,
84
85
  DEVICE_CLASS_CARBON_MONOXIDE,
86
+ DEVICE_CLASS_CONDUCTIVITY,
85
87
  DEVICE_CLASS_CURRENT,
86
88
  DEVICE_CLASS_DATA_RATE,
87
89
  DEVICE_CLASS_DATA_SIZE,