esphome 2024.10.2__py3-none-any.whl → 2024.11.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 (239) hide show
  1. esphome/__main__.py +22 -4
  2. esphome/automation.py +29 -2
  3. esphome/components/animation/__init__.py +5 -8
  4. esphome/components/animation/animation.cpp +1 -1
  5. esphome/components/audio/__init__.py +9 -0
  6. esphome/components/audio/audio.h +21 -0
  7. esphome/components/axs15231/__init__.py +6 -0
  8. esphome/components/axs15231/touchscreen/__init__.py +36 -0
  9. esphome/components/axs15231/touchscreen/axs15231_touchscreen.cpp +64 -0
  10. esphome/components/axs15231/touchscreen/axs15231_touchscreen.h +27 -0
  11. esphome/components/bme68x_bsec2/__init__.py +1 -1
  12. esphome/components/bme68x_bsec2/bme68x_bsec2.cpp +50 -47
  13. esphome/components/bme68x_bsec2/bme68x_bsec2.h +0 -2
  14. esphome/components/bytebuffer/__init__.py +5 -0
  15. esphome/components/bytebuffer/bytebuffer.h +421 -0
  16. esphome/components/climate/__init__.py +14 -13
  17. esphome/components/datetime/__init__.py +3 -3
  18. esphome/components/debug/debug_esp32.cpp +16 -8
  19. esphome/components/dfplayer/dfplayer.cpp +132 -6
  20. esphome/components/dfplayer/dfplayer.h +19 -53
  21. esphome/components/display/display.cpp +142 -0
  22. esphome/components/display/display.h +7 -0
  23. esphome/components/es8311/__init__.py +0 -0
  24. esphome/components/es8311/audio_dac.py +70 -0
  25. esphome/components/es8311/es8311.cpp +227 -0
  26. esphome/components/es8311/es8311.h +135 -0
  27. esphome/components/es8311/es8311_const.h +195 -0
  28. esphome/components/esp32/boards.py +199 -1
  29. esphome/components/esp32/gpio.py +3 -1
  30. esphome/components/esp32_ble/const_esp32c6.h +7 -0
  31. esphome/components/esp32_ble_client/ble_client_base.h +1 -1
  32. esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +3 -0
  33. esphome/components/esp32_ble_tracker/esp32_ble_tracker.h +2 -1
  34. esphome/components/esp32_rmt_led_strip/led_strip.cpp +2 -2
  35. esphome/components/esp32_rmt_led_strip/led_strip.h +2 -0
  36. esphome/components/esp32_rmt_led_strip/light.py +3 -1
  37. esphome/components/esp8266/gpio.py +7 -5
  38. esphome/components/ethernet/__init__.py +55 -1
  39. esphome/components/ethernet/ethernet_component.cpp +14 -1
  40. esphome/components/ethernet/ethernet_component.h +7 -1
  41. esphome/components/font/__init__.py +213 -108
  42. esphome/components/gp8403/output/__init__.py +1 -1
  43. esphome/components/host/gpio.py +6 -4
  44. esphome/components/http_request/__init__.py +12 -0
  45. esphome/components/http_request/http_request.h +65 -3
  46. esphome/components/http_request/http_request_arduino.cpp +4 -3
  47. esphome/components/http_request/http_request_idf.cpp +12 -14
  48. esphome/components/http_request/ota/ota_http_request.cpp +1 -1
  49. esphome/components/http_request/update/http_request_update.cpp +1 -1
  50. esphome/components/i2c_device/__init__.py +26 -0
  51. esphome/components/i2c_device/i2c_device.cpp +17 -0
  52. esphome/components/i2c_device/i2c_device.h +18 -0
  53. esphome/components/i2s_audio/__init__.py +1 -3
  54. esphome/components/i2s_audio/speaker/__init__.py +12 -4
  55. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +426 -200
  56. esphome/components/i2s_audio/speaker/i2s_audio_speaker.h +92 -33
  57. esphome/components/ili9xxx/display.py +5 -1
  58. esphome/components/image/__init__.py +5 -8
  59. esphome/components/image/image.cpp +14 -14
  60. esphome/components/image/image.h +20 -24
  61. esphome/components/internal_temperature/internal_temperature.cpp +51 -2
  62. esphome/components/internal_temperature/internal_temperature.h +1 -0
  63. esphome/components/ld2420/ld2420.cpp +1 -1
  64. esphome/components/libretiny/gpio.py +4 -2
  65. esphome/components/light/__init__.py +32 -1
  66. esphome/components/light/automation.py +39 -32
  67. esphome/components/light/effects.py +36 -36
  68. esphome/components/light/light_state.cpp +6 -16
  69. esphome/components/light/light_state.h +34 -0
  70. esphome/components/light/types.py +3 -1
  71. esphome/components/logger/logger_esp32.cpp +15 -0
  72. esphome/components/lvgl/__init__.py +202 -95
  73. esphome/components/lvgl/automation.py +42 -40
  74. esphome/components/lvgl/binary_sensor/__init__.py +8 -15
  75. esphome/components/lvgl/defines.py +14 -8
  76. esphome/components/lvgl/encoders.py +11 -8
  77. esphome/components/lvgl/keypads.py +77 -0
  78. esphome/components/lvgl/light/__init__.py +6 -8
  79. esphome/components/lvgl/lv_validation.py +2 -4
  80. esphome/components/lvgl/lvcode.py +3 -9
  81. esphome/components/lvgl/lvgl_esphome.cpp +210 -89
  82. esphome/components/lvgl/lvgl_esphome.h +113 -30
  83. esphome/components/lvgl/lvgl_proxy.h +17 -0
  84. esphome/components/lvgl/number/__init__.py +10 -15
  85. esphome/components/lvgl/schemas.py +4 -2
  86. esphome/components/lvgl/select/__init__.py +12 -37
  87. esphome/components/lvgl/select/lvgl_select.h +27 -33
  88. esphome/components/lvgl/sensor/__init__.py +8 -14
  89. esphome/components/lvgl/styles.py +3 -4
  90. esphome/components/lvgl/switch/__init__.py +8 -13
  91. esphome/components/lvgl/text/__init__.py +5 -6
  92. esphome/components/lvgl/text_sensor/__init__.py +15 -15
  93. esphome/components/lvgl/touchscreens.py +2 -3
  94. esphome/components/lvgl/trigger.py +7 -9
  95. esphome/components/lvgl/types.py +9 -3
  96. esphome/components/lvgl/widgets/__init__.py +32 -21
  97. esphome/components/lvgl/widgets/animimg.py +4 -3
  98. esphome/components/lvgl/widgets/dropdown.py +22 -10
  99. esphome/components/lvgl/widgets/img.py +2 -0
  100. esphome/components/lvgl/widgets/msgbox.py +6 -5
  101. esphome/components/lvgl/widgets/obj.py +4 -2
  102. esphome/components/lvgl/widgets/page.py +3 -2
  103. esphome/components/lvgl/widgets/qrcode.py +54 -0
  104. esphome/components/lvgl/widgets/roller.py +21 -14
  105. esphome/components/lvgl/widgets/tileview.py +2 -1
  106. esphome/components/max17043/__init__.py +1 -0
  107. esphome/components/max17043/automation.h +20 -0
  108. esphome/components/max17043/max17043.cpp +98 -0
  109. esphome/components/max17043/max17043.h +29 -0
  110. esphome/components/max17043/sensor.py +77 -0
  111. esphome/components/media_player/__init__.py +11 -0
  112. esphome/components/media_player/automation.h +10 -0
  113. esphome/components/media_player/media_player.cpp +4 -0
  114. esphome/components/midea/air_conditioner.cpp +17 -1
  115. esphome/components/mlx90393/sensor.py +1 -1
  116. esphome/components/modbus/modbus.cpp +24 -12
  117. esphome/components/modbus_controller/__init__.py +31 -1
  118. esphome/components/modbus_controller/automation.h +16 -0
  119. esphome/components/modbus_controller/const.py +2 -0
  120. esphome/components/modbus_controller/modbus_controller.cpp +14 -2
  121. esphome/components/modbus_controller/modbus_controller.h +9 -0
  122. esphome/components/mopeka_pro_check/mopeka_pro_check.cpp +40 -21
  123. esphome/components/mopeka_pro_check/mopeka_pro_check.h +9 -2
  124. esphome/components/mopeka_pro_check/sensor.py +41 -0
  125. esphome/components/mqtt/__init__.py +36 -0
  126. esphome/components/mqtt/mqtt_client.cpp +27 -3
  127. esphome/components/mqtt/mqtt_client.h +27 -2
  128. esphome/components/mqtt/mqtt_climate.cpp +4 -2
  129. esphome/components/mqtt/mqtt_component.cpp +6 -0
  130. esphome/components/mqtt/mqtt_component.h +4 -0
  131. esphome/components/mqtt/mqtt_const.h +6 -0
  132. esphome/components/online_image/online_image.cpp +2 -8
  133. esphome/components/online_image/online_image.h +2 -6
  134. esphome/components/opentherm/__init__.py +35 -9
  135. esphome/components/opentherm/binary_sensor/__init__.py +33 -0
  136. esphome/components/opentherm/const.py +11 -0
  137. esphome/components/opentherm/generate.py +142 -0
  138. esphome/components/opentherm/hub.cpp +130 -24
  139. esphome/components/opentherm/hub.h +62 -9
  140. esphome/components/opentherm/input.h +18 -0
  141. esphome/components/opentherm/input.py +51 -0
  142. esphome/components/opentherm/number/__init__.py +74 -0
  143. esphome/components/opentherm/number/number.cpp +40 -0
  144. esphome/components/opentherm/number/number.h +31 -0
  145. esphome/components/opentherm/opentherm.cpp +30 -0
  146. esphome/components/opentherm/opentherm.h +34 -2
  147. esphome/components/opentherm/opentherm_macros.h +151 -0
  148. esphome/components/opentherm/output/__init__.py +47 -0
  149. esphome/components/opentherm/output/output.cpp +18 -0
  150. esphome/components/opentherm/output/output.h +33 -0
  151. esphome/components/opentherm/schema.py +814 -0
  152. esphome/components/opentherm/sensor/__init__.py +51 -0
  153. esphome/components/opentherm/switch/__init__.py +43 -0
  154. esphome/components/opentherm/switch/switch.cpp +28 -0
  155. esphome/components/opentherm/switch/switch.h +20 -0
  156. esphome/components/opentherm/validate.py +31 -0
  157. esphome/components/pcd8544/display.py +8 -4
  158. esphome/components/prometheus/prometheus_handler.cpp +176 -14
  159. esphome/components/prometheus/prometheus_handler.h +25 -7
  160. esphome/components/qspi_amoled/display.py +1 -141
  161. esphome/components/qspi_dbi/display.py +185 -0
  162. esphome/components/qspi_dbi/models.py +64 -0
  163. esphome/components/{qspi_amoled/qspi_amoled.cpp → qspi_dbi/qspi_dbi.cpp} +95 -46
  164. esphome/components/{qspi_amoled/qspi_amoled.h → qspi_dbi/qspi_dbi.h} +26 -15
  165. esphome/components/rp2040/__init__.py +6 -3
  166. esphome/components/rp2040/gpio.py +5 -3
  167. esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.cpp +20 -0
  168. esphome/components/rpi_dpi_rgb/rpi_dpi_rgb.h +3 -2
  169. esphome/components/rtttl/rtttl.cpp +4 -1
  170. esphome/components/rtttl/rtttl.h +1 -0
  171. esphome/components/sdl/sdl_esphome.cpp +22 -5
  172. esphome/components/sdl/sdl_esphome.h +1 -0
  173. esphome/components/sdm_meter/sdm_meter.cpp +1 -1
  174. esphome/components/sensor/__init__.py +18 -8
  175. esphome/components/sensor/filter.cpp +19 -18
  176. esphome/components/sensor/filter.h +9 -10
  177. esphome/components/sgp4x/sgp4x.cpp +40 -74
  178. esphome/components/sgp4x/sgp4x.h +5 -3
  179. esphome/components/speaker/__init__.py +51 -5
  180. esphome/components/speaker/automation.h +25 -0
  181. esphome/components/speaker/speaker.h +72 -1
  182. esphome/components/spi/__init__.py +15 -14
  183. esphome/components/spi_device/__init__.py +4 -15
  184. esphome/components/ssd1306_spi/display.py +6 -2
  185. esphome/components/ssd1322_spi/display.py +6 -2
  186. esphome/components/ssd1325_spi/display.py +6 -2
  187. esphome/components/ssd1327_spi/display.py +6 -2
  188. esphome/components/ssd1331_spi/display.py +6 -2
  189. esphome/components/ssd1351_spi/display.py +6 -2
  190. esphome/components/st7567_spi/display.py +6 -2
  191. esphome/components/st7701s/display.py +5 -1
  192. esphome/components/st7735/display.py +10 -5
  193. esphome/components/st7789v/display.py +12 -7
  194. esphome/components/statsd/statsd.cpp +2 -0
  195. esphome/components/statsd/statsd.h +2 -0
  196. esphome/components/sun/sun.h +3 -0
  197. esphome/components/tc74/__init__.py +1 -0
  198. esphome/components/tc74/sensor.py +32 -0
  199. esphome/components/tc74/tc74.cpp +68 -0
  200. esphome/components/tc74/tc74.h +28 -0
  201. esphome/components/touchscreen/__init__.py +41 -50
  202. esphome/components/touchscreen/touchscreen.h +4 -8
  203. esphome/components/tuya/fan/tuya_fan.cpp +1 -1
  204. esphome/components/udp/udp_component.cpp +6 -3
  205. esphome/components/udp/udp_component.h +4 -2
  206. esphome/components/waveshare_epaper/display.py +6 -2
  207. esphome/components/web_server/web_server.cpp +22 -0
  208. esphome/components/web_server/web_server.h +3 -0
  209. esphome/components/weikai/weikai.h +2 -2
  210. esphome/components/wifi/wifi_component.cpp +2 -2
  211. esphome/components/wifi/wifi_component_esp32_arduino.cpp +4 -4
  212. esphome/components/wifi/wifi_component_esp8266.cpp +4 -4
  213. esphome/components/wifi/wifi_component_esp_idf.cpp +2 -2
  214. esphome/components/xpt2046/touchscreen/__init__.py +7 -32
  215. esphome/config_validation.py +3 -1
  216. esphome/const.py +9 -2
  217. esphome/core/defines.h +8 -2
  218. esphome/core/helpers.cpp +32 -17
  219. esphome/core/helpers.h +32 -16
  220. esphome/core/ring_buffer.cpp +2 -2
  221. esphome/core/ring_buffer.h +2 -2
  222. esphome/dashboard/core.py +25 -0
  223. esphome/dashboard/status/mdns.py +3 -4
  224. esphome/dashboard/web_server.py +54 -19
  225. esphome/espota2.py +36 -35
  226. esphome/helpers.py +68 -16
  227. esphome/mqtt.py +9 -2
  228. esphome/storage_json.py +4 -0
  229. esphome/writer.py +7 -18
  230. esphome/zeroconf.py +8 -6
  231. {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/METADATA +7 -5
  232. {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/RECORD +237 -191
  233. esphome/core/bytebuffer.cpp +0 -167
  234. esphome/core/bytebuffer.h +0 -144
  235. /esphome/components/{qspi_amoled → qspi_dbi}/__init__.py +0 -0
  236. {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/LICENSE +0 -0
  237. {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/WHEEL +0 -0
  238. {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/entry_points.txt +0 -0
  239. {esphome-2024.10.2.dist-info → esphome-2024.11.0.dist-info}/top_level.txt +0 -0
@@ -15,23 +15,33 @@ void Modbus::setup() {
15
15
  void Modbus::loop() {
16
16
  const uint32_t now = millis();
17
17
 
18
- if (now - this->last_modbus_byte_ > 50) {
19
- this->rx_buffer_.clear();
20
- this->last_modbus_byte_ = now;
21
- }
22
- // stop blocking new send commands after send_wait_time_ ms regardless if a response has been received since then
23
- if (now - this->last_send_ > send_wait_time_) {
24
- waiting_for_response = 0;
25
- }
26
-
27
18
  while (this->available()) {
28
19
  uint8_t byte;
29
20
  this->read_byte(&byte);
30
21
  if (this->parse_modbus_byte_(byte)) {
31
22
  this->last_modbus_byte_ = now;
32
23
  } else {
24
+ size_t at = this->rx_buffer_.size();
25
+ if (at > 0) {
26
+ ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse failed", at);
27
+ this->rx_buffer_.clear();
28
+ }
29
+ }
30
+ }
31
+
32
+ if (now - this->last_modbus_byte_ > 50) {
33
+ size_t at = this->rx_buffer_.size();
34
+ if (at > 0) {
35
+ ESP_LOGV(TAG, "Clearing buffer of %d bytes - timeout", at);
33
36
  this->rx_buffer_.clear();
34
37
  }
38
+
39
+ // stop blocking new send commands after sent_wait_time_ ms after response received
40
+ if (now - this->last_send_ > send_wait_time_) {
41
+ if (waiting_for_response > 0)
42
+ ESP_LOGV(TAG, "Stop waiting for response from %d", waiting_for_response);
43
+ waiting_for_response = 0;
44
+ }
35
45
  }
36
46
  }
37
47
 
@@ -39,7 +49,7 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
39
49
  size_t at = this->rx_buffer_.size();
40
50
  this->rx_buffer_.push_back(byte);
41
51
  const uint8_t *raw = &this->rx_buffer_[0];
42
- ESP_LOGV(TAG, "Modbus received Byte %d (0X%x)", byte, byte);
52
+ ESP_LOGVV(TAG, "Modbus received Byte %d (0X%x)", byte, byte);
43
53
  // Byte 0: modbus address (match all)
44
54
  if (at == 0)
45
55
  return true;
@@ -144,8 +154,10 @@ bool Modbus::parse_modbus_byte_(uint8_t byte) {
144
154
  ESP_LOGW(TAG, "Got Modbus frame from unknown address 0x%02X! ", address);
145
155
  }
146
156
 
147
- // return false to reset buffer
148
- return false;
157
+ // reset buffer
158
+ ESP_LOGV(TAG, "Clearing buffer of %d bytes - parse succeeded", at);
159
+ this->rx_buffer_.clear();
160
+ return true;
149
161
  }
150
162
 
151
163
  void Modbus::dump_config() {
@@ -25,6 +25,8 @@ from .const import (
25
25
  CONF_MODBUS_CONTROLLER_ID,
26
26
  CONF_OFFLINE_SKIP_UPDATES,
27
27
  CONF_ON_COMMAND_SENT,
28
+ CONF_ON_ONLINE,
29
+ CONF_ON_OFFLINE,
28
30
  CONF_REGISTER_COUNT,
29
31
  CONF_REGISTER_TYPE,
30
32
  CONF_RESPONSE_SIZE,
@@ -114,6 +116,14 @@ ModbusCommandSentTrigger = modbus_controller_ns.class_(
114
116
  "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_)
115
117
  )
116
118
 
119
+ ModbusOnlineTrigger = modbus_controller_ns.class_(
120
+ "ModbusOnlineTrigger", automation.Trigger.template(cg.int_, cg.int_)
121
+ )
122
+
123
+ ModbusOfflineTrigger = modbus_controller_ns.class_(
124
+ "ModbusOfflineTrigger", automation.Trigger.template(cg.int_, cg.int_)
125
+ )
126
+
117
127
  _LOGGER = logging.getLogger(__name__)
118
128
 
119
129
  ModbusServerRegisterSchema = cv.Schema(
@@ -146,6 +156,16 @@ CONFIG_SCHEMA = cv.All(
146
156
  ),
147
157
  }
148
158
  ),
159
+ cv.Optional(CONF_ON_ONLINE): automation.validate_automation(
160
+ {
161
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger),
162
+ }
163
+ ),
164
+ cv.Optional(CONF_ON_OFFLINE): automation.validate_automation(
165
+ {
166
+ cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger),
167
+ }
168
+ ),
149
169
  }
150
170
  )
151
171
  .extend(cv.polling_component_schema("60s"))
@@ -284,7 +304,17 @@ async def to_code(config):
284
304
  for conf in config.get(CONF_ON_COMMAND_SENT, []):
285
305
  trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
286
306
  await automation.build_automation(
287
- trigger, [(int, "function_code"), (int, "address")], conf
307
+ trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf
308
+ )
309
+ for conf in config.get(CONF_ON_ONLINE, []):
310
+ trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
311
+ await automation.build_automation(
312
+ trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf
313
+ )
314
+ for conf in config.get(CONF_ON_OFFLINE, []):
315
+ trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
316
+ await automation.build_automation(
317
+ trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf
288
318
  )
289
319
 
290
320
 
@@ -15,5 +15,21 @@ class ModbusCommandSentTrigger : public Trigger<int, int> {
15
15
  }
16
16
  };
17
17
 
18
+ class ModbusOnlineTrigger : public Trigger<int, int> {
19
+ public:
20
+ ModbusOnlineTrigger(ModbusController *a_modbuscontroller) {
21
+ a_modbuscontroller->add_on_online_callback(
22
+ [this](int function_code, int address) { this->trigger(function_code, address); });
23
+ }
24
+ };
25
+
26
+ class ModbusOfflineTrigger : public Trigger<int, int> {
27
+ public:
28
+ ModbusOfflineTrigger(ModbusController *a_modbuscontroller) {
29
+ a_modbuscontroller->add_on_offline_callback(
30
+ [this](int function_code, int address) { this->trigger(function_code, address); });
31
+ }
32
+ };
33
+
18
34
  } // namespace modbus_controller
19
35
  } // namespace esphome
@@ -9,6 +9,8 @@ CONF_MAX_CMD_RETRIES = "max_cmd_retries"
9
9
  CONF_MODBUS_CONTROLLER_ID = "modbus_controller_id"
10
10
  CONF_MODBUS_FUNCTIONCODE = "modbus_functioncode"
11
11
  CONF_ON_COMMAND_SENT = "on_command_sent"
12
+ CONF_ON_ONLINE = "on_online"
13
+ CONF_ON_OFFLINE = "on_offline"
12
14
  CONF_RAW_ENCODE = "raw_encode"
13
15
  CONF_REGISTER_COUNT = "register_count"
14
16
  CONF_REGISTER_TYPE = "register_type"
@@ -32,8 +32,10 @@ bool ModbusController::send_next_command_() {
32
32
  r.skip_updates_counter = this->offline_skip_updates_;
33
33
  }
34
34
  }
35
+
36
+ this->module_offline_ = true;
37
+ this->offline_callback_.call((int) command->function_code, command->register_address);
35
38
  }
36
- this->module_offline_ = true;
37
39
  ESP_LOGD(TAG, "Modbus command to device=%d register=0x%02X no response received - removed from send queue",
38
40
  this->address_, command->register_address);
39
41
  this->command_queue_.pop_front();
@@ -68,8 +70,10 @@ void ModbusController::on_modbus_data(const std::vector<uint8_t> &data) {
68
70
  r.skip_updates_counter = 0;
69
71
  }
70
72
  }
73
+ // Restore module online state
74
+ this->module_offline_ = false;
75
+ this->online_callback_.call((int) current_command->function_code, current_command->register_address);
71
76
  }
72
- this->module_offline_ = false;
73
77
 
74
78
  // Move the commandItem to the response queue
75
79
  current_command->payload = data;
@@ -670,5 +674,13 @@ void ModbusController::add_on_command_sent_callback(std::function<void(int, int)
670
674
  this->command_sent_callback_.add(std::move(callback));
671
675
  }
672
676
 
677
+ void ModbusController::add_on_online_callback(std::function<void(int, int)> &&callback) {
678
+ this->online_callback_.add(std::move(callback));
679
+ }
680
+
681
+ void ModbusController::add_on_offline_callback(std::function<void(int, int)> &&callback) {
682
+ this->offline_callback_.add(std::move(callback));
683
+ }
684
+
673
685
  } // namespace modbus_controller
674
686
  } // namespace esphome
@@ -468,6 +468,10 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
468
468
  bool get_module_offline() { return module_offline_; }
469
469
  /// Set callback for commands
470
470
  void add_on_command_sent_callback(std::function<void(int, int)> &&callback);
471
+ /// Set callback for online changes
472
+ void add_on_online_callback(std::function<void(int, int)> &&callback);
473
+ /// Set callback for offline changes
474
+ void add_on_offline_callback(std::function<void(int, int)> &&callback);
471
475
  /// called by esphome generated code to set the max_cmd_retries.
472
476
  void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; }
473
477
  /// get how many times a command will be (re)sent if no response is received
@@ -508,7 +512,12 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
508
512
  uint16_t offline_skip_updates_;
509
513
  /// How many times we will retry a command if we get no response
510
514
  uint8_t max_cmd_retries_{4};
515
+ /// Command sent callback
511
516
  CallbackManager<void(int, int)> command_sent_callback_{};
517
+ /// Server online callback
518
+ CallbackManager<void(int, int)> online_callback_{};
519
+ /// Server offline callback
520
+ CallbackManager<void(int, int)> offline_callback_{};
512
521
  };
513
522
 
514
523
  /** Convert vector<uint8_t> response payload to float.
@@ -17,6 +17,8 @@ void MopekaProCheck::dump_config() {
17
17
  LOG_SENSOR(" ", "Temperature", this->temperature_);
18
18
  LOG_SENSOR(" ", "Battery Level", this->battery_level_);
19
19
  LOG_SENSOR(" ", "Reading Distance", this->distance_);
20
+ LOG_SENSOR(" ", "Read Quality", this->read_quality_);
21
+ LOG_SENSOR(" ", "Ignored Reads", this->ignored_reads_);
20
22
  }
21
23
 
22
24
  /**
@@ -66,34 +68,49 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
66
68
  this->battery_level_->publish_state(level);
67
69
  }
68
70
 
71
+ // Get the quality value
72
+ SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data);
73
+ if (this->read_quality_ != nullptr) {
74
+ this->read_quality_->publish_state(static_cast<int>(quality_value));
75
+ }
76
+
77
+ // Determine if we have a good enough quality of read to report level and distance
78
+ // sensors. This sensor is reported regardless of distance or level sensors being enabled
79
+ if (quality_value < this->min_signal_quality_) {
80
+ ESP_LOGW(TAG, "Read Quality too low to report distance or level");
81
+ this->ignored_read_count_++;
82
+ } else {
83
+ // reset to zero since read quality was sufficient
84
+ this->ignored_read_count_ = 0;
85
+ }
86
+ // Report number of contiguous ignored reads if sensor defined
87
+ if (this->ignored_reads_ != nullptr) {
88
+ this->ignored_reads_->publish_state(this->ignored_read_count_);
89
+ }
90
+
69
91
  // Get distance and level if either are sensors
70
92
  if ((this->distance_ != nullptr) || (this->level_ != nullptr)) {
71
93
  uint32_t distance_value = this->parse_distance_(manu_data.data);
72
- SensorReadQuality quality_value = this->parse_read_quality_(manu_data.data);
73
94
  ESP_LOGD(TAG, "Distance Sensor: Quality (0x%X) Distance (%" PRId32 "mm)", quality_value, distance_value);
74
- if (quality_value < QUALITY_HIGH) {
75
- ESP_LOGW(TAG, "Poor read quality.");
76
- }
77
- if (quality_value < QUALITY_MED) {
78
- // if really bad reading set to 0
79
- ESP_LOGW(TAG, "Setting distance to 0");
80
- distance_value = 0;
81
- }
82
95
 
83
- // update distance sensor
84
- if (this->distance_ != nullptr) {
85
- this->distance_->publish_state(distance_value);
86
- }
96
+ // only update distance and level sensors if read quality was sufficient. This can be determined by
97
+ // if the ignored_read_count is zero.
98
+ if (this->ignored_read_count_ == 0) {
99
+ // update distance sensor
100
+ if (this->distance_ != nullptr) {
101
+ this->distance_->publish_state(distance_value);
102
+ }
87
103
 
88
- // update level sensor
89
- if (this->level_ != nullptr) {
90
- uint8_t tank_level = 0;
91
- if (distance_value >= this->full_mm_) {
92
- tank_level = 100; // cap at 100%
93
- } else if (distance_value > this->empty_mm_) {
94
- tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_));
104
+ // update level sensor
105
+ if (this->level_ != nullptr) {
106
+ uint8_t tank_level = 0;
107
+ if (distance_value >= this->full_mm_) {
108
+ tank_level = 100; // cap at 100%
109
+ } else if (distance_value > this->empty_mm_) {
110
+ tank_level = ((100.0f / (this->full_mm_ - this->empty_mm_)) * (distance_value - this->empty_mm_));
111
+ }
112
+ this->level_->publish_state(tank_level);
95
113
  }
96
- this->level_->publish_state(tank_level);
97
114
  }
98
115
  }
99
116
 
@@ -131,6 +148,8 @@ uint32_t MopekaProCheck::parse_distance_(const std::vector<uint8_t> &message) {
131
148
  uint8_t MopekaProCheck::parse_temperature_(const std::vector<uint8_t> &message) { return (message[2] & 0x7F) - 40; }
132
149
 
133
150
  SensorReadQuality MopekaProCheck::parse_read_quality_(const std::vector<uint8_t> &message) {
151
+ // Since a 8 bit value is being shifted and truncated to 2 bits all possible values are defined as enumeration
152
+ // value and the static cast is safe.
134
153
  return static_cast<SensorReadQuality>(message[4] >> 6);
135
154
  }
136
155
 
@@ -24,9 +24,9 @@ enum SensorType {
24
24
  };
25
25
 
26
26
  // Sensor read quality. If sensor is poorly placed or tank level
27
- // gets too low the read quality will show and the distanace
27
+ // gets too low the read quality will show and the distance
28
28
  // measurement may be inaccurate.
29
- enum SensorReadQuality { QUALITY_HIGH = 0x3, QUALITY_MED = 0x2, QUALITY_LOW = 0x1, QUALITY_NONE = 0x0 };
29
+ enum SensorReadQuality { QUALITY_HIGH = 0x3, QUALITY_MED = 0x2, QUALITY_LOW = 0x1, QUALITY_ZERO = 0x0 };
30
30
 
31
31
  class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
32
32
  public:
@@ -35,11 +35,14 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi
35
35
  bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
36
36
  void dump_config() override;
37
37
  float get_setup_priority() const override { return setup_priority::DATA; }
38
+ void set_min_signal_quality(SensorReadQuality min) { this->min_signal_quality_ = min; };
38
39
 
39
40
  void set_level(sensor::Sensor *level) { level_ = level; };
40
41
  void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; };
41
42
  void set_battery_level(sensor::Sensor *bat) { battery_level_ = bat; };
42
43
  void set_distance(sensor::Sensor *distance) { distance_ = distance; };
44
+ void set_signal_quality(sensor::Sensor *rq) { read_quality_ = rq; };
45
+ void set_ignored_reads(sensor::Sensor *ir) { ignored_reads_ = ir; };
43
46
  void set_tank_full(float full) { full_mm_ = full; };
44
47
  void set_tank_empty(float empty) { empty_mm_ = empty; };
45
48
 
@@ -49,9 +52,13 @@ class MopekaProCheck : public Component, public esp32_ble_tracker::ESPBTDeviceLi
49
52
  sensor::Sensor *temperature_{nullptr};
50
53
  sensor::Sensor *distance_{nullptr};
51
54
  sensor::Sensor *battery_level_{nullptr};
55
+ sensor::Sensor *read_quality_{nullptr};
56
+ sensor::Sensor *ignored_reads_{nullptr};
52
57
 
53
58
  uint32_t full_mm_;
54
59
  uint32_t empty_mm_;
60
+ uint32_t ignored_read_count_ = 0;
61
+ SensorReadQuality min_signal_quality_ = QUALITY_MED;
55
62
 
56
63
  uint8_t parse_battery_level_(const std::vector<uint8_t> &message);
57
64
  uint32_t parse_distance_(const std::vector<uint8_t> &message);
@@ -5,9 +5,12 @@ from esphome.const import (
5
5
  CONF_DISTANCE,
6
6
  CONF_MAC_ADDRESS,
7
7
  CONF_ID,
8
+ ICON_COUNTER,
8
9
  ICON_THERMOMETER,
9
10
  ICON_RULER,
11
+ ICON_SIGNAL,
10
12
  UNIT_PERCENT,
13
+ UNIT_EMPTY,
11
14
  CONF_LEVEL,
12
15
  CONF_TEMPERATURE,
13
16
  DEVICE_CLASS_TEMPERATURE,
@@ -16,11 +19,15 @@ from esphome.const import (
16
19
  STATE_CLASS_MEASUREMENT,
17
20
  CONF_BATTERY_LEVEL,
18
21
  DEVICE_CLASS_BATTERY,
22
+ ENTITY_CATEGORY_DIAGNOSTIC,
19
23
  )
20
24
 
21
25
  CONF_TANK_TYPE = "tank_type"
22
26
  CONF_CUSTOM_DISTANCE_FULL = "custom_distance_full"
23
27
  CONF_CUSTOM_DISTANCE_EMPTY = "custom_distance_empty"
28
+ CONF_SIGNAL_QUALITY = "signal_quality"
29
+ CONF_MINIMUM_SIGNAL_QUALITY = "minimum_signal_quality"
30
+ CONF_IGNORED_READS = "ignored_reads"
24
31
 
25
32
  ICON_PROPANE_TANK = "mdi:propane-tank"
26
33
 
@@ -56,6 +63,14 @@ MopekaProCheck = mopeka_pro_check_ns.class_(
56
63
  "MopekaProCheck", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
57
64
  )
58
65
 
66
+ SensorReadQuality = mopeka_pro_check_ns.enum("SensorReadQuality")
67
+ SIGNAL_QUALITIES = {
68
+ "ZERO": SensorReadQuality.QUALITY_ZERO,
69
+ "LOW": SensorReadQuality.QUALITY_LOW,
70
+ "MEDIUM": SensorReadQuality.QUALITY_MED,
71
+ "HIGH": SensorReadQuality.QUALITY_HIGH,
72
+ }
73
+
59
74
  CONFIG_SCHEMA = (
60
75
  cv.Schema(
61
76
  {
@@ -89,6 +104,21 @@ CONFIG_SCHEMA = (
89
104
  device_class=DEVICE_CLASS_BATTERY,
90
105
  state_class=STATE_CLASS_MEASUREMENT,
91
106
  ),
107
+ cv.Optional(CONF_SIGNAL_QUALITY): sensor.sensor_schema(
108
+ unit_of_measurement=UNIT_EMPTY,
109
+ icon=ICON_SIGNAL,
110
+ accuracy_decimals=0,
111
+ state_class=STATE_CLASS_MEASUREMENT,
112
+ ),
113
+ cv.Optional(CONF_IGNORED_READS): sensor.sensor_schema(
114
+ unit_of_measurement=UNIT_EMPTY,
115
+ icon=ICON_COUNTER,
116
+ accuracy_decimals=0,
117
+ entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
118
+ ),
119
+ cv.Optional(CONF_MINIMUM_SIGNAL_QUALITY, default="MEDIUM"): cv.enum(
120
+ SIGNAL_QUALITIES, upper=True
121
+ ),
92
122
  }
93
123
  )
94
124
  .extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
@@ -119,6 +149,11 @@ async def to_code(config):
119
149
  cg.add(var.set_tank_empty(CONF_SUPPORTED_TANKS_MAP[t][0]))
120
150
  cg.add(var.set_tank_full(CONF_SUPPORTED_TANKS_MAP[t][1]))
121
151
 
152
+ if (
153
+ minimum_signal_quality := config.get(CONF_MINIMUM_SIGNAL_QUALITY, None)
154
+ ) is not None:
155
+ cg.add(var.set_min_signal_quality(minimum_signal_quality))
156
+
122
157
  if CONF_TEMPERATURE in config:
123
158
  sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
124
159
  cg.add(var.set_temperature(sens))
@@ -131,3 +166,9 @@ async def to_code(config):
131
166
  if CONF_BATTERY_LEVEL in config:
132
167
  sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
133
168
  cg.add(var.set_battery_level(sens))
169
+ if CONF_SIGNAL_QUALITY in config:
170
+ sens = await sensor.new_sensor(config[CONF_SIGNAL_QUALITY])
171
+ cg.add(var.set_signal_quality(sens))
172
+ if CONF_IGNORED_READS in config:
173
+ sens = await sensor.new_sensor(config[CONF_IGNORED_READS])
174
+ cg.add(var.set_ignored_reads(sens))
@@ -22,6 +22,7 @@ from esphome.const import (
22
22
  CONF_DISCOVERY_PREFIX,
23
23
  CONF_DISCOVERY_RETAIN,
24
24
  CONF_DISCOVERY_UNIQUE_ID_GENERATOR,
25
+ CONF_ENABLE_ON_BOOT,
25
26
  CONF_ID,
26
27
  CONF_KEEPALIVE,
27
28
  CONF_LEVEL,
@@ -41,6 +42,7 @@ from esphome.const import (
41
42
  CONF_SHUTDOWN_MESSAGE,
42
43
  CONF_SSL_FINGERPRINTS,
43
44
  CONF_STATE_TOPIC,
45
+ CONF_SUBSCRIBE_QOS,
44
46
  CONF_TOPIC,
45
47
  CONF_TOPIC_PREFIX,
46
48
  CONF_TRIGGER_ID,
@@ -98,6 +100,8 @@ MQTTMessage = mqtt_ns.struct("MQTTMessage")
98
100
  MQTTClientComponent = mqtt_ns.class_("MQTTClientComponent", cg.Component)
99
101
  MQTTPublishAction = mqtt_ns.class_("MQTTPublishAction", automation.Action)
100
102
  MQTTPublishJsonAction = mqtt_ns.class_("MQTTPublishJsonAction", automation.Action)
103
+ MQTTEnableAction = mqtt_ns.class_("MQTTEnableAction", automation.Action)
104
+ MQTTDisableAction = mqtt_ns.class_("MQTTDisableAction", automation.Action)
101
105
  MQTTMessageTrigger = mqtt_ns.class_(
102
106
  "MQTTMessageTrigger", automation.Trigger.template(cg.std_string), cg.Component
103
107
  )
@@ -207,6 +211,7 @@ CONFIG_SCHEMA = cv.All(
207
211
  {
208
212
  cv.GenerateID(): cv.declare_id(MQTTClientComponent),
209
213
  cv.Required(CONF_BROKER): cv.string_strict,
214
+ cv.Optional(CONF_ENABLE_ON_BOOT, default=True): cv.boolean,
210
215
  cv.Optional(CONF_PORT, default=1883): cv.port,
211
216
  cv.Optional(CONF_USERNAME, default=""): cv.string,
212
217
  cv.Optional(CONF_PASSWORD, default=""): cv.string,
@@ -324,6 +329,7 @@ async def to_code(config):
324
329
  cg.add_global(mqtt_ns.using)
325
330
 
326
331
  cg.add(var.set_broker_address(config[CONF_BROKER]))
332
+ cg.add(var.set_enable_on_boot(config[CONF_ENABLE_ON_BOOT]))
327
333
  cg.add(var.set_broker_port(config[CONF_PORT]))
328
334
  cg.add(var.set_username(config[CONF_USERNAME]))
329
335
  cg.add(var.set_password(config[CONF_PASSWORD]))
@@ -518,6 +524,8 @@ async def register_mqtt_component(var, config):
518
524
  cg.add(var.set_qos(config[CONF_QOS]))
519
525
  if CONF_RETAIN in config:
520
526
  cg.add(var.set_retain(config[CONF_RETAIN]))
527
+ if CONF_SUBSCRIBE_QOS in config:
528
+ cg.add(var.set_subscribe_qos(config[CONF_SUBSCRIBE_QOS]))
521
529
  if not config.get(CONF_DISCOVERY, True):
522
530
  cg.add(var.disable_discovery())
523
531
  if CONF_STATE_TOPIC in config:
@@ -552,3 +560,31 @@ async def register_mqtt_component(var, config):
552
560
  async def mqtt_connected_to_code(config, condition_id, template_arg, args):
553
561
  paren = await cg.get_variable(config[CONF_ID])
554
562
  return cg.new_Pvariable(condition_id, template_arg, paren)
563
+
564
+
565
+ @automation.register_action(
566
+ "mqtt.enable",
567
+ MQTTEnableAction,
568
+ cv.Schema(
569
+ {
570
+ cv.GenerateID(): cv.use_id(MQTTClientComponent),
571
+ }
572
+ ),
573
+ )
574
+ async def mqtt_enable_to_code(config, action_id, template_arg, args):
575
+ paren = await cg.get_variable(config[CONF_ID])
576
+ return cg.new_Pvariable(action_id, template_arg, paren)
577
+
578
+
579
+ @automation.register_action(
580
+ "mqtt.disable",
581
+ MQTTDisableAction,
582
+ cv.Schema(
583
+ {
584
+ cv.GenerateID(): cv.use_id(MQTTClientComponent),
585
+ }
586
+ ),
587
+ )
588
+ async def mqtt_disable_to_code(config, action_id, template_arg, args):
589
+ paren = await cg.get_variable(config[CONF_ID])
590
+ return cg.new_Pvariable(action_id, template_arg, paren)
@@ -50,6 +50,8 @@ void MQTTClientComponent::setup() {
50
50
  }
51
51
  });
52
52
  this->mqtt_backend_.set_on_disconnect([this](MQTTClientDisconnectReason reason) {
53
+ if (this->state_ == MQTT_CLIENT_DISABLED)
54
+ return;
53
55
  this->state_ = MQTT_CLIENT_DISCONNECTED;
54
56
  this->disconnect_reason_ = reason;
55
57
  });
@@ -77,8 +79,9 @@ void MQTTClientComponent::setup() {
77
79
  topic, [this](const std::string &topic, const std::string &payload) { this->send_device_info_(); }, 2);
78
80
  }
79
81
 
80
- this->last_connected_ = millis();
81
- this->start_dnslookup_();
82
+ if (this->enable_on_boot_) {
83
+ this->enable();
84
+ }
82
85
  }
83
86
 
84
87
  void MQTTClientComponent::send_device_info_() {
@@ -163,7 +166,9 @@ void MQTTClientComponent::dump_config() {
163
166
  ESP_LOGCONFIG(TAG, " Availability: '%s'", this->availability_.topic.c_str());
164
167
  }
165
168
  }
166
- bool MQTTClientComponent::can_proceed() { return network::is_disabled() || this->is_connected(); }
169
+ bool MQTTClientComponent::can_proceed() {
170
+ return network::is_disabled() || this->state_ == MQTT_CLIENT_DISABLED || this->is_connected();
171
+ }
167
172
 
168
173
  void MQTTClientComponent::start_dnslookup_() {
169
174
  for (auto &subscription : this->subscriptions_) {
@@ -339,6 +344,8 @@ void MQTTClientComponent::loop() {
339
344
  const uint32_t now = millis();
340
345
 
341
346
  switch (this->state_) {
347
+ case MQTT_CLIENT_DISABLED:
348
+ return; // Return to avoid a reboot when disabled
342
349
  case MQTT_CLIENT_DISCONNECTED:
343
350
  if (now - this->connect_begin_ > 5000) {
344
351
  this->start_dnslookup_();
@@ -501,6 +508,23 @@ bool MQTTClientComponent::publish_json(const std::string &topic, const json::jso
501
508
  return this->publish(topic, message, qos, retain);
502
509
  }
503
510
 
511
+ void MQTTClientComponent::enable() {
512
+ if (this->state_ != MQTT_CLIENT_DISABLED)
513
+ return;
514
+ ESP_LOGD(TAG, "Enabling MQTT...");
515
+ this->state_ = MQTT_CLIENT_DISCONNECTED;
516
+ this->last_connected_ = millis();
517
+ this->start_dnslookup_();
518
+ }
519
+
520
+ void MQTTClientComponent::disable() {
521
+ if (this->state_ == MQTT_CLIENT_DISABLED)
522
+ return;
523
+ ESP_LOGD(TAG, "Disabling MQTT...");
524
+ this->state_ = MQTT_CLIENT_DISABLED;
525
+ this->on_shutdown();
526
+ }
527
+
504
528
  /** Check if the message topic matches the given subscription topic
505
529
  *
506
530
  * INFO: MQTT spec mandates that topics must not be empty and must be valid NULL-terminated UTF-8 strings.
@@ -87,7 +87,8 @@ struct MQTTDiscoveryInfo {
87
87
  };
88
88
 
89
89
  enum MQTTClientState {
90
- MQTT_CLIENT_DISCONNECTED = 0,
90
+ MQTT_CLIENT_DISABLED = 0,
91
+ MQTT_CLIENT_DISCONNECTED,
91
92
  MQTT_CLIENT_RESOLVING_ADDRESS,
92
93
  MQTT_CLIENT_CONNECTING,
93
94
  MQTT_CLIENT_CONNECTED,
@@ -247,6 +248,9 @@ class MQTTClientComponent : public Component {
247
248
  void register_mqtt_component(MQTTComponent *component);
248
249
 
249
250
  bool is_connected();
251
+ void set_enable_on_boot(bool enable_on_boot) { this->enable_on_boot_ = enable_on_boot; }
252
+ void enable();
253
+ void disable();
250
254
 
251
255
  void on_shutdown() override;
252
256
 
@@ -314,10 +318,11 @@ class MQTTClientComponent : public Component {
314
318
  MQTTBackendLibreTiny mqtt_backend_;
315
319
  #endif
316
320
 
317
- MQTTClientState state_{MQTT_CLIENT_DISCONNECTED};
321
+ MQTTClientState state_{MQTT_CLIENT_DISABLED};
318
322
  network::IPAddress ip_;
319
323
  bool dns_resolved_{false};
320
324
  bool dns_resolve_error_{false};
325
+ bool enable_on_boot_{true};
321
326
  std::vector<MQTTComponent *> children_;
322
327
  uint32_t reboot_timeout_{300000};
323
328
  uint32_t connect_begin_;
@@ -414,6 +419,26 @@ template<typename... Ts> class MQTTConnectedCondition : public Condition<Ts...>
414
419
  MQTTClientComponent *parent_;
415
420
  };
416
421
 
422
+ template<typename... Ts> class MQTTEnableAction : public Action<Ts...> {
423
+ public:
424
+ MQTTEnableAction(MQTTClientComponent *parent) : parent_(parent) {}
425
+
426
+ void play(Ts... x) override { this->parent_->enable(); }
427
+
428
+ protected:
429
+ MQTTClientComponent *parent_;
430
+ };
431
+
432
+ template<typename... Ts> class MQTTDisableAction : public Action<Ts...> {
433
+ public:
434
+ MQTTDisableAction(MQTTClientComponent *parent) : parent_(parent) {}
435
+
436
+ void play(Ts... x) override { this->parent_->disable(); }
437
+
438
+ protected:
439
+ MQTTClientComponent *parent_;
440
+ };
441
+
417
442
  } // namespace mqtt
418
443
  } // namespace esphome
419
444
 
@@ -71,8 +71,10 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
71
71
  root[MQTT_MIN_TEMP] = traits.get_visual_min_temperature();
72
72
  // max_temp
73
73
  root[MQTT_MAX_TEMP] = traits.get_visual_max_temperature();
74
- // temp_step
75
- root["temp_step"] = traits.get_visual_target_temperature_step();
74
+ // target_temp_step
75
+ root[MQTT_TARGET_TEMPERATURE_STEP] = traits.get_visual_target_temperature_step();
76
+ // current_temp_step
77
+ root[MQTT_CURRENT_TEMPERATURE_STEP] = traits.get_visual_current_temperature_step();
76
78
  // temperature units are always coerced to Celsius internally
77
79
  root[MQTT_TEMPERATURE_UNIT] = "C";
78
80
 
@@ -16,6 +16,8 @@ static const char *const TAG = "mqtt.component";
16
16
 
17
17
  void MQTTComponent::set_qos(uint8_t qos) { this->qos_ = qos; }
18
18
 
19
+ void MQTTComponent::set_subscribe_qos(uint8_t qos) { this->subscribe_qos_ = qos; }
20
+
19
21
  void MQTTComponent::set_retain(bool retain) { this->retain_ = retain; }
20
22
 
21
23
  std::string MQTTComponent::get_discovery_topic_(const MQTTDiscoveryInfo &discovery_info) const {
@@ -76,6 +78,10 @@ bool MQTTComponent::send_discovery_() {
76
78
  config.command_topic = true;
77
79
 
78
80
  this->send_discovery(root, config);
81
+ // Set subscription QoS (default is 0)
82
+ if (this->subscribe_qos_ != 0) {
83
+ root[MQTT_QOS] = this->subscribe_qos_;
84
+ }
79
85
 
80
86
  // Fields from EntityBase
81
87
  if (this->get_entity()->has_own_name()) {