esphome 2025.2.2__py3-none-any.whl → 2025.3.0b2__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 (146) hide show
  1. esphome/__main__.py +9 -1
  2. esphome/components/api/api_connection.cpp +426 -70
  3. esphome/components/api/api_connection.h +117 -25
  4. esphome/components/api/api_pb2.cpp +33 -0
  5. esphome/components/api/api_pb2.h +4 -0
  6. esphome/components/api/api_server.cpp +2 -2
  7. esphome/components/api/list_entities.cpp +76 -22
  8. esphome/components/api/list_entities.h +1 -0
  9. esphome/components/api/subscribe_state.h +2 -0
  10. esphome/components/audio/__init__.py +1 -1
  11. esphome/components/audio/audio_decoder.cpp +43 -11
  12. esphome/components/audio/audio_reader.cpp +2 -2
  13. esphome/components/audio/audio_resampler.cpp +4 -2
  14. esphome/components/audio/audio_transfer_buffer.cpp +19 -9
  15. esphome/components/audio/audio_transfer_buffer.h +7 -2
  16. esphome/components/bluetooth_proxy/bluetooth_proxy.h +8 -0
  17. esphome/components/bmp085/bmp085.cpp +1 -1
  18. esphome/components/chsc6x/__init__.py +2 -0
  19. esphome/components/chsc6x/chsc6x_touchscreen.cpp +47 -0
  20. esphome/components/chsc6x/chsc6x_touchscreen.h +34 -0
  21. esphome/components/chsc6x/touchscreen.py +33 -0
  22. esphome/components/climate/__init__.py +0 -1
  23. esphome/components/cst816/binary_sensor/__init__.py +2 -25
  24. esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +3 -14
  25. esphome/components/cst816/touchscreen/cst816_touchscreen.h +0 -4
  26. esphome/components/esp32_ble_beacon/__init__.py +3 -1
  27. esphome/components/esp8266/gpio.py +1 -2
  28. esphome/components/font/__init__.py +185 -185
  29. esphome/components/font/font.cpp +4 -4
  30. esphome/components/font/font.h +1 -0
  31. esphome/components/haier/climate.py +11 -10
  32. esphome/components/hbridge/switch/hbridge_switch.cpp +2 -2
  33. esphome/components/heatpumpir/climate.py +2 -1
  34. esphome/components/heatpumpir/heatpumpir.cpp +1 -0
  35. esphome/components/heatpumpir/heatpumpir.h +1 -0
  36. esphome/components/i2c/__init__.py +6 -6
  37. esphome/components/i2c/i2c_bus_esp_idf.cpp +6 -2
  38. esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
  39. esphome/components/ili9xxx/display.py +1 -0
  40. esphome/components/ili9xxx/ili9xxx_display.h +5 -0
  41. esphome/components/ili9xxx/ili9xxx_init.h +59 -0
  42. esphome/components/ld2450/__init__.py +51 -0
  43. esphome/components/ld2450/binary_sensor.py +47 -0
  44. esphome/components/ld2450/button/__init__.py +45 -0
  45. esphome/components/ld2450/button/reset_button.cpp +9 -0
  46. esphome/components/ld2450/button/reset_button.h +18 -0
  47. esphome/components/ld2450/button/restart_button.cpp +9 -0
  48. esphome/components/ld2450/button/restart_button.h +18 -0
  49. esphome/components/ld2450/ld2450.cpp +876 -0
  50. esphome/components/ld2450/ld2450.h +234 -0
  51. esphome/components/ld2450/number/__init__.py +121 -0
  52. esphome/components/ld2450/number/presence_timeout_number.cpp +12 -0
  53. esphome/components/ld2450/number/presence_timeout_number.h +18 -0
  54. esphome/components/ld2450/number/zone_coordinate_number.cpp +14 -0
  55. esphome/components/ld2450/number/zone_coordinate_number.h +19 -0
  56. esphome/components/ld2450/select/__init__.py +56 -0
  57. esphome/components/ld2450/select/baud_rate_select.cpp +12 -0
  58. esphome/components/ld2450/select/baud_rate_select.h +18 -0
  59. esphome/components/ld2450/select/zone_type_select.cpp +12 -0
  60. esphome/components/ld2450/select/zone_type_select.h +18 -0
  61. esphome/components/ld2450/sensor.py +156 -0
  62. esphome/components/ld2450/switch/__init__.py +45 -0
  63. esphome/components/ld2450/switch/bluetooth_switch.cpp +12 -0
  64. esphome/components/ld2450/switch/bluetooth_switch.h +18 -0
  65. esphome/components/ld2450/switch/multi_target_switch.cpp +12 -0
  66. esphome/components/ld2450/switch/multi_target_switch.h +18 -0
  67. esphome/components/ld2450/text_sensor.py +62 -0
  68. esphome/components/lvgl/defines.py +0 -2
  69. esphome/components/lvgl/font.cpp +1 -1
  70. esphome/components/lvgl/lvgl_esphome.cpp +27 -19
  71. esphome/components/lvgl/widgets/img.py +1 -3
  72. esphome/components/mcp2515/mcp2515.cpp +1 -0
  73. esphome/components/mdns/__init__.py +1 -1
  74. esphome/components/mixer/speaker/mixer_speaker.cpp +6 -1
  75. esphome/components/mixer/speaker/mixer_speaker.h +2 -0
  76. esphome/components/mlx90393/sensor.py +53 -33
  77. esphome/components/mlx90393/sensor_mlx90393.cpp +4 -0
  78. esphome/components/mlx90393/sensor_mlx90393.h +8 -3
  79. esphome/components/mqtt/__init__.py +2 -2
  80. esphome/components/msa3xx/__init__.py +189 -0
  81. esphome/components/msa3xx/binary_sensor.py +40 -0
  82. esphome/components/msa3xx/msa3xx.cpp +417 -0
  83. esphome/components/msa3xx/msa3xx.h +311 -0
  84. esphome/components/msa3xx/sensor.py +42 -0
  85. esphome/components/msa3xx/text_sensor.py +38 -0
  86. esphome/components/nfc/binary_sensor/__init__.py +4 -4
  87. esphome/components/opentherm/binary_sensor/__init__.py +4 -4
  88. esphome/components/opentherm/generate.py +6 -6
  89. esphome/components/opentherm/sensor/__init__.py +5 -6
  90. esphome/components/packages/__init__.py +35 -11
  91. esphome/components/pn532/binary_sensor.py +4 -4
  92. esphome/components/rc522/binary_sensor.py +4 -4
  93. esphome/components/resampler/speaker/resampler_speaker.h +2 -0
  94. esphome/components/socket/bsd_sockets_impl.cpp +1 -0
  95. esphome/components/socket/lwip_sockets_impl.cpp +1 -0
  96. esphome/components/socket/socket.h +3 -1
  97. esphome/components/speaker/speaker.h +2 -2
  98. esphome/components/ssd1306_base/__init__.py +7 -7
  99. esphome/components/thermostat/climate.py +1 -1
  100. esphome/components/tmp1075/tmp1075.cpp +7 -11
  101. esphome/components/tmp1075/tmp1075.h +1 -2
  102. esphome/components/tormatic/__init__.py +1 -0
  103. esphome/components/tormatic/cover.py +47 -0
  104. esphome/components/tormatic/tormatic_cover.cpp +355 -0
  105. esphome/components/tormatic/tormatic_cover.h +60 -0
  106. esphome/components/tormatic/tormatic_protocol.h +211 -0
  107. esphome/components/touchscreen/binary_sensor/__init__.py +3 -0
  108. esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +7 -1
  109. esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +3 -1
  110. esphome/components/touchscreen/touchscreen.cpp +3 -4
  111. esphome/components/udp/udp_component.h +4 -1
  112. esphome/components/web_server/list_entities.cpp +70 -66
  113. esphome/components/web_server/list_entities.h +43 -22
  114. esphome/components/web_server/web_server.cpp +345 -68
  115. esphome/components/web_server/web_server.h +138 -6
  116. esphome/components/web_server_base/__init__.py +1 -1
  117. esphome/components/web_server_idf/__init__.py +2 -0
  118. esphome/components/web_server_idf/web_server_idf.cpp +177 -30
  119. esphome/components/web_server_idf/web_server_idf.h +53 -4
  120. esphome/config_validation.py +23 -125
  121. esphome/const.py +5 -1
  122. esphome/core/config.py +12 -4
  123. esphome/core/defines.h +1 -1
  124. esphome/core/helpers.h +24 -3
  125. esphome/core/time.cpp +1 -0
  126. esphome/cpp_generator.py +3 -3
  127. esphome/dashboard/core.py +30 -21
  128. esphome/dashboard/dns.py +7 -1
  129. esphome/dashboard/entries.py +83 -16
  130. esphome/dashboard/settings.py +0 -4
  131. esphome/dashboard/status/mdns.py +43 -14
  132. esphome/dashboard/status/mqtt.py +22 -9
  133. esphome/dashboard/status/ping.py +54 -10
  134. esphome/dashboard/web_server.py +56 -24
  135. esphome/storage_json.py +4 -0
  136. esphome/wizard.py +13 -17
  137. esphome/writer.py +1 -3
  138. esphome/yaml_util.py +36 -33
  139. esphome/zeroconf.py +9 -21
  140. {esphome-2025.2.2.dist-info → esphome-2025.3.0b2.dist-info}/METADATA +7 -7
  141. {esphome-2025.2.2.dist-info → esphome-2025.3.0b2.dist-info}/RECORD +145 -105
  142. esphome/components/cst816/binary_sensor/cst816_button.h +0 -27
  143. {esphome-2025.2.2.dist-info → esphome-2025.3.0b2.dist-info}/LICENSE +0 -0
  144. {esphome-2025.2.2.dist-info → esphome-2025.3.0b2.dist-info}/WHEEL +0 -0
  145. {esphome-2025.2.2.dist-info → esphome-2025.3.0b2.dist-info}/entry_points.txt +0 -0
  146. {esphome-2025.2.2.dist-info → esphome-2025.3.0b2.dist-info}/top_level.txt +0 -0
@@ -76,7 +76,7 @@ class Speaker {
76
76
  }
77
77
  #endif
78
78
  };
79
- float get_volume() { return this->volume_; }
79
+ virtual float get_volume() { return this->volume_; }
80
80
 
81
81
  virtual void set_mute_state(bool mute_state) {
82
82
  this->mute_state_ = mute_state;
@@ -90,7 +90,7 @@ class Speaker {
90
90
  }
91
91
  #endif
92
92
  }
93
- bool get_mute_state() { return this->mute_state_; }
93
+ virtual bool get_mute_state() { return this->mute_state_; }
94
94
 
95
95
  #ifdef USE_AUDIO_DAC
96
96
  void set_audio_dac(audio_dac::AudioDac *audio_dac) { this->audio_dac_ = audio_dac; }
@@ -1,15 +1,17 @@
1
- import esphome.codegen as cg
2
- import esphome.config_validation as cv
3
1
  from esphome import pins
2
+ import esphome.codegen as cg
4
3
  from esphome.components import display
4
+ import esphome.config_validation as cv
5
5
  from esphome.const import (
6
+ CONF_BRIGHTNESS,
7
+ CONF_CONTRAST,
6
8
  CONF_EXTERNAL_VCC,
9
+ CONF_INVERT,
7
10
  CONF_LAMBDA,
8
11
  CONF_MODEL,
12
+ CONF_OFFSET_X,
13
+ CONF_OFFSET_Y,
9
14
  CONF_RESET_PIN,
10
- CONF_BRIGHTNESS,
11
- CONF_CONTRAST,
12
- CONF_INVERT,
13
15
  )
14
16
 
15
17
  ssd1306_base_ns = cg.esphome_ns.namespace("ssd1306_base")
@@ -18,8 +20,6 @@ SSD1306Model = ssd1306_base_ns.enum("SSD1306Model")
18
20
 
19
21
  CONF_FLIP_X = "flip_x"
20
22
  CONF_FLIP_Y = "flip_y"
21
- CONF_OFFSET_X = "offset_x"
22
- CONF_OFFSET_Y = "offset_y"
23
23
 
24
24
  MODELS = {
25
25
  "SSD1306_128X32": SSD1306Model.SSD1306_MODEL_128_32,
@@ -137,7 +137,7 @@ def validate_temperature_preset(preset, root_config, name, requirements):
137
137
 
138
138
 
139
139
  def generate_comparable_preset(config, name):
140
- comparable_preset = f"{CONF_PRESET}:\n" f" - {CONF_NAME}: {name}\n"
140
+ comparable_preset = f"{CONF_PRESET}:\n - {CONF_NAME}: {name}\n"
141
141
 
142
142
  if CONF_DEFAULT_TARGET_TEMPERATURE_LOW in config:
143
143
  comparable_preset += f" {CONF_DEFAULT_TARGET_TEMPERATURE_LOW}: {config[CONF_DEFAULT_TARGET_TEMPERATURE_LOW]}\n"
@@ -18,14 +18,9 @@ static uint16_t temp2regvalue(float temp);
18
18
  static float regvalue2temp(uint16_t regvalue);
19
19
 
20
20
  void TMP1075Sensor::setup() {
21
- uint8_t die_id;
22
- if (!this->read_byte(REG_DIEID, &die_id)) {
23
- ESP_LOGW(TAG, "'%s' - unable to read ID", this->name_.c_str());
24
- this->mark_failed();
25
- return;
26
- }
27
- if (die_id != EXPECT_DIEID) {
28
- ESP_LOGW(TAG, "'%s' - unexpected ID 0x%x found, expected 0x%x", this->name_.c_str(), die_id, EXPECT_DIEID);
21
+ uint8_t cfg;
22
+ if (!this->read_byte(REG_CFGR, &cfg)) {
23
+ ESP_LOGE(TAG, "'%s' - unable to read", this->name_.c_str());
29
24
  this->mark_failed();
30
25
  return;
31
26
  }
@@ -37,9 +32,10 @@ void TMP1075Sensor::update() {
37
32
  uint16_t regvalue;
38
33
  if (!read_byte_16(REG_TEMP, &regvalue)) {
39
34
  ESP_LOGW(TAG, "'%s' - unable to read temperature register", this->name_.c_str());
40
- this->status_set_warning();
35
+ this->status_set_warning("can't read");
41
36
  return;
42
37
  }
38
+ this->status_clear_warning();
43
39
 
44
40
  const float temp = regvalue2temp(regvalue);
45
41
  this->publish_state(temp);
@@ -89,9 +85,9 @@ void TMP1075Sensor::write_config() {
89
85
  }
90
86
 
91
87
  void TMP1075Sensor::send_config_() {
92
- ESP_LOGV(TAG, "'%s' - sending configuration %04x", this->name_.c_str(), config_.regvalue);
88
+ ESP_LOGV(TAG, "'%s' - sending configuration %02x", this->name_.c_str(), config_.regvalue);
93
89
  log_config_();
94
- if (!this->write_byte_16(REG_CFGR, config_.regvalue)) {
90
+ if (!this->write_byte(REG_CFGR, config_.regvalue)) {
95
91
  ESP_LOGW(TAG, "'%s' - unable to write configuration register", this->name_.c_str());
96
92
  return;
97
93
  }
@@ -36,9 +36,8 @@ struct TMP1075Config {
36
36
  uint8_t shutdown : 1; // Sets the device in shutdown mode to conserve power.
37
37
  // 0: Device is in continuous conversion
38
38
  // 1: Device is in shutdown mode
39
- uint8_t unused : 8;
40
39
  } fields;
41
- uint16_t regvalue;
40
+ uint8_t regvalue;
42
41
  };
43
42
  };
44
43
 
@@ -0,0 +1 @@
1
+ CODEOWNERS = ["@ti-mo"]
@@ -0,0 +1,47 @@
1
+ import esphome.codegen as cg
2
+ import esphome.config_validation as cv
3
+ from esphome.components import cover, uart
4
+ from esphome.const import (
5
+ CONF_CLOSE_DURATION,
6
+ CONF_ID,
7
+ CONF_OPEN_DURATION,
8
+ )
9
+
10
+ tormatic_ns = cg.esphome_ns.namespace("tormatic")
11
+ Tormatic = tormatic_ns.class_("Tormatic", cover.Cover, cg.PollingComponent)
12
+
13
+ CONFIG_SCHEMA = (
14
+ cover.COVER_SCHEMA.extend(uart.UART_DEVICE_SCHEMA)
15
+ .extend(cv.polling_component_schema("300ms"))
16
+ .extend(
17
+ {
18
+ cv.GenerateID(): cv.declare_id(Tormatic),
19
+ cv.Optional(
20
+ CONF_OPEN_DURATION, default="15s"
21
+ ): cv.positive_time_period_milliseconds,
22
+ cv.Optional(
23
+ CONF_CLOSE_DURATION, default="22s"
24
+ ): cv.positive_time_period_milliseconds,
25
+ }
26
+ )
27
+ )
28
+
29
+ FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
30
+ "tormatic",
31
+ baud_rate=9600,
32
+ require_tx=True,
33
+ require_rx=True,
34
+ data_bits=8,
35
+ parity="NONE",
36
+ stop_bits=1,
37
+ )
38
+
39
+
40
+ async def to_code(config):
41
+ var = cg.new_Pvariable(config[CONF_ID])
42
+ await cg.register_component(var, config)
43
+ await cover.register_cover(var, config)
44
+ await uart.register_uart_device(var, config)
45
+
46
+ cg.add(var.set_close_duration(config[CONF_CLOSE_DURATION]))
47
+ cg.add(var.set_open_duration(config[CONF_OPEN_DURATION]))
@@ -0,0 +1,355 @@
1
+ #include <vector>
2
+
3
+ #include "tormatic_cover.h"
4
+
5
+ using namespace std;
6
+
7
+ namespace esphome {
8
+ namespace tormatic {
9
+
10
+ static const char *const TAG = "tormatic.cover";
11
+
12
+ using namespace esphome::cover;
13
+
14
+ void Tormatic::setup() {
15
+ auto restore = this->restore_state_();
16
+ if (restore.has_value()) {
17
+ restore->apply(this);
18
+ return;
19
+ }
20
+
21
+ // Assume gate is closed without preexisting state.
22
+ this->position = 0.0f;
23
+ }
24
+
25
+ cover::CoverTraits Tormatic::get_traits() {
26
+ auto traits = CoverTraits();
27
+ traits.set_supports_stop(true);
28
+ traits.set_supports_position(true);
29
+ traits.set_is_assumed_state(false);
30
+ return traits;
31
+ }
32
+
33
+ void Tormatic::dump_config() {
34
+ LOG_COVER("", "Tormatic Cover", this);
35
+ this->check_uart_settings(9600, 1, uart::UART_CONFIG_PARITY_NONE, 8);
36
+
37
+ ESP_LOGCONFIG(TAG, " Open Duration: %.1fs", this->open_duration_ / 1e3f);
38
+ ESP_LOGCONFIG(TAG, " Close Duration: %.1fs", this->close_duration_ / 1e3f);
39
+
40
+ auto restore = this->restore_state_();
41
+ if (restore.has_value()) {
42
+ ESP_LOGCONFIG(TAG, " Saved position %d%%", (int) (restore->position * 100.f));
43
+ }
44
+ }
45
+
46
+ void Tormatic::update() { this->request_gate_status_(); }
47
+
48
+ void Tormatic::loop() {
49
+ auto o_status = this->read_gate_status_();
50
+ if (o_status) {
51
+ auto status = o_status.value();
52
+
53
+ this->recalibrate_duration_(status);
54
+ this->handle_gate_status_(status);
55
+ }
56
+
57
+ this->recompute_position_();
58
+ this->stop_at_target_();
59
+ }
60
+
61
+ void Tormatic::control(const cover::CoverCall &call) {
62
+ if (call.get_stop()) {
63
+ this->send_gate_command_(PAUSED);
64
+ return;
65
+ }
66
+
67
+ if (call.get_position().has_value()) {
68
+ auto pos = call.get_position().value();
69
+ this->control_position_(pos);
70
+ return;
71
+ }
72
+ }
73
+
74
+ // Wrap the Cover's publish_state with a rate limiter. Publishes if the last
75
+ // publish was longer than ratelimit milliseconds ago. 0 to disable.
76
+ void Tormatic::publish_state(bool save, uint32_t ratelimit) {
77
+ auto now = millis();
78
+ if ((now - this->last_publish_time_) < ratelimit) {
79
+ return;
80
+ }
81
+ this->last_publish_time_ = now;
82
+
83
+ Cover::publish_state(save);
84
+ };
85
+
86
+ // Recalibrate the gate's estimated open or close duration based on the
87
+ // actual time the operation took.
88
+ void Tormatic::recalibrate_duration_(GateStatus s) {
89
+ if (this->current_status_ == s) {
90
+ return;
91
+ }
92
+
93
+ auto now = millis();
94
+ auto old = this->current_status_;
95
+
96
+ // Gate paused halfway through opening or closing, invalidate the start time
97
+ // of the current operation. Close/open durations can only be accurately
98
+ // calibrated on full open or close cycle due to motor acceleration.
99
+ if (s == PAUSED) {
100
+ ESP_LOGD(TAG, "Gate paused, clearing direction start time");
101
+ this->direction_start_time_ = 0;
102
+ return;
103
+ }
104
+
105
+ // Record the start time of a state transition if the gate was in the fully
106
+ // open or closed position before the command.
107
+ if ((old == CLOSED && s == OPENING) || (old == OPENED && s == CLOSING)) {
108
+ ESP_LOGD(TAG, "Gate started moving from fully open or closed state");
109
+ this->direction_start_time_ = now;
110
+ return;
111
+ }
112
+
113
+ // The gate was resumed from a paused state, don't attempt recalibration.
114
+ if (this->direction_start_time_ == 0) {
115
+ return;
116
+ }
117
+
118
+ if (s == OPENED) {
119
+ this->open_duration_ = now - this->direction_start_time_;
120
+ ESP_LOGI(TAG, "Recalibrated the gate's open duration to %dms", this->open_duration_);
121
+ }
122
+ if (s == CLOSED) {
123
+ this->close_duration_ = now - this->direction_start_time_;
124
+ ESP_LOGI(TAG, "Recalibrated the gate's close duration to %dms", this->close_duration_);
125
+ }
126
+
127
+ this->direction_start_time_ = 0;
128
+ }
129
+
130
+ // Set the Cover's internal state based on a status message
131
+ // received from the unit.
132
+ void Tormatic::handle_gate_status_(GateStatus s) {
133
+ if (this->current_status_ == s) {
134
+ return;
135
+ }
136
+
137
+ ESP_LOGI(TAG, "Status changed from %s to %s", gate_status_to_str(this->current_status_), gate_status_to_str(s));
138
+
139
+ switch (s) {
140
+ case OPENED:
141
+ // The Novoferm 423 doesn't respond to the first 'Close' command after
142
+ // being opened completely. Sending a pause command after opening fixes
143
+ // that.
144
+ this->send_gate_command_(PAUSED);
145
+
146
+ this->position = COVER_OPEN;
147
+ break;
148
+ case CLOSED:
149
+ this->position = COVER_CLOSED;
150
+ break;
151
+ default:
152
+ break;
153
+ }
154
+
155
+ this->current_status_ = s;
156
+ this->current_operation = gate_status_to_cover_operation(s);
157
+
158
+ this->publish_state(true);
159
+
160
+ // This timestamp is used to generate position deltas on every loop() while
161
+ // the gate is moving. Bump it on each state transition so the first tick
162
+ // doesn't generate a huge delta.
163
+ this->last_recompute_time_ = millis();
164
+ }
165
+
166
+ // Recompute the gate's position and publish the results while
167
+ // the gate is moving. No-op when the gate is idle.
168
+ void Tormatic::recompute_position_() {
169
+ if (this->current_operation == COVER_OPERATION_IDLE) {
170
+ return;
171
+ }
172
+
173
+ const uint32_t now = millis();
174
+ uint32_t diff = now - this->last_recompute_time_;
175
+
176
+ auto direction = +1.0f;
177
+ uint32_t duration = this->open_duration_;
178
+ if (this->current_operation == COVER_OPERATION_CLOSING) {
179
+ direction = -1.0f;
180
+ duration = this->close_duration_;
181
+ }
182
+
183
+ auto delta = direction * diff / duration;
184
+
185
+ this->position = clamp(this->position + delta, COVER_CLOSED, COVER_OPEN);
186
+
187
+ this->last_recompute_time_ = now;
188
+
189
+ this->publish_state(true, 250);
190
+ }
191
+
192
+ // Start moving the gate in the direction of the target position.
193
+ void Tormatic::control_position_(float target) {
194
+ if (target == this->position) {
195
+ return;
196
+ }
197
+
198
+ if (target == COVER_OPEN) {
199
+ ESP_LOGI(TAG, "Fully opening gate");
200
+ this->send_gate_command_(OPENED);
201
+ return;
202
+ }
203
+ if (target == COVER_CLOSED) {
204
+ ESP_LOGI(TAG, "Fully closing gate");
205
+ this->send_gate_command_(CLOSED);
206
+ return;
207
+ }
208
+
209
+ // Don't set target position when fully opening or closing the gate, the gate
210
+ // stops automatically when it reaches the configured open/closed positions.
211
+ this->target_position_ = target;
212
+
213
+ if (target > this->position) {
214
+ ESP_LOGI(TAG, "Opening gate towards %.1f", target);
215
+ this->send_gate_command_(OPENED);
216
+ return;
217
+ }
218
+
219
+ if (target < this->position) {
220
+ ESP_LOGI(TAG, "Closing gate towards %.1f", target);
221
+ this->send_gate_command_(CLOSED);
222
+ return;
223
+ }
224
+ }
225
+
226
+ // Stop the gate if it is moving at or beyond its target position. Target
227
+ // position is only set when the gate is requested to move to a halfway
228
+ // position.
229
+ void Tormatic::stop_at_target_() {
230
+ if (this->current_operation == COVER_OPERATION_IDLE) {
231
+ return;
232
+ }
233
+ if (!this->target_position_) {
234
+ return;
235
+ }
236
+ auto target = this->target_position_.value();
237
+
238
+ if (this->current_operation == COVER_OPERATION_OPENING && this->position < target) {
239
+ return;
240
+ }
241
+ if (this->current_operation == COVER_OPERATION_CLOSING && this->position > target) {
242
+ return;
243
+ }
244
+
245
+ this->send_gate_command_(PAUSED);
246
+ this->target_position_.reset();
247
+ }
248
+
249
+ // Read a GateStatus from the unit. The unit only sends messages in response to
250
+ // status requests or commands, so a message needs to be sent first.
251
+ optional<GateStatus> Tormatic::read_gate_status_() {
252
+ if (this->available() < sizeof(MessageHeader)) {
253
+ return {};
254
+ }
255
+
256
+ auto o_hdr = this->read_data_<MessageHeader>();
257
+ if (!o_hdr) {
258
+ ESP_LOGE(TAG, "Timeout reading message header");
259
+ return {};
260
+ }
261
+ auto hdr = o_hdr.value();
262
+
263
+ switch (hdr.type) {
264
+ case STATUS: {
265
+ if (hdr.payload_size() != sizeof(StatusReply)) {
266
+ ESP_LOGE(TAG, "Header specifies payload size %d but size of StatusReply is %d", hdr.payload_size(),
267
+ sizeof(StatusReply));
268
+ }
269
+
270
+ // Read a StatusReply requested by update().
271
+ auto o_status = this->read_data_<StatusReply>();
272
+ if (!o_status) {
273
+ return {};
274
+ }
275
+ auto status = o_status.value();
276
+
277
+ return status.state;
278
+ }
279
+
280
+ case COMMAND:
281
+ // Commands initiated by control() are simply echoed back by the unit, but
282
+ // don't guarantee that the unit's internal state has been transitioned,
283
+ // nor that the motor started moving. A subsequent status request may
284
+ // still return the previous state. Discard these messages, don't use them
285
+ // to drive the Cover state machine.
286
+ break;
287
+
288
+ default:
289
+ // Unknown message type, drain the remaining amount of bytes specified in
290
+ // the header.
291
+ ESP_LOGE(TAG, "Reading remaining %d payload bytes of unknown type 0x%x", hdr.payload_size(), hdr.type);
292
+ break;
293
+ }
294
+
295
+ // Drain any unhandled payload bytes described by the message header, if any.
296
+ this->drain_rx_(hdr.payload_size());
297
+
298
+ return {};
299
+ }
300
+
301
+ // Send a message to the unit requesting the gate's status.
302
+ void Tormatic::request_gate_status_() {
303
+ ESP_LOGV(TAG, "Requesting gate status");
304
+ StatusRequest req(GATE);
305
+ this->send_message_(STATUS, req);
306
+ }
307
+
308
+ // Send a message to the unit issuing a command.
309
+ void Tormatic::send_gate_command_(GateStatus s) {
310
+ ESP_LOGI(TAG, "Sending gate command %s", gate_status_to_str(s));
311
+ CommandRequestReply req(s);
312
+ this->send_message_(COMMAND, req);
313
+ }
314
+
315
+ template<typename T> void Tormatic::send_message_(MessageType t, T req) {
316
+ MessageHeader hdr(t, ++this->seq_tx_, sizeof(req));
317
+
318
+ auto out = serialize(hdr);
319
+ auto reqv = serialize(req);
320
+ out.insert(out.end(), reqv.begin(), reqv.end());
321
+
322
+ this->write_array(out);
323
+ }
324
+
325
+ template<typename T> optional<T> Tormatic::read_data_() {
326
+ T obj;
327
+ uint32_t start = millis();
328
+
329
+ auto ok = this->read_array((uint8_t *) &obj, sizeof(obj));
330
+ if (!ok) {
331
+ // Couldn't read object successfully, timeout?
332
+ return {};
333
+ }
334
+ obj.byteswap();
335
+
336
+ ESP_LOGV(TAG, "Read %s in %d ms", obj.print().c_str(), millis() - start);
337
+ return obj;
338
+ }
339
+
340
+ // Drain up to n amount of bytes from the uart rx buffer.
341
+ void Tormatic::drain_rx_(uint16_t n) {
342
+ uint8_t data;
343
+ uint16_t count = 0;
344
+ while (this->available()) {
345
+ this->read_byte(&data);
346
+ count++;
347
+
348
+ if (n > 0 && count >= n) {
349
+ return;
350
+ }
351
+ }
352
+ }
353
+
354
+ } // namespace tormatic
355
+ } // namespace esphome
@@ -0,0 +1,60 @@
1
+ #pragma once
2
+
3
+ #include "esphome/components/uart/uart.h"
4
+ #include "esphome/components/cover/cover.h"
5
+
6
+ #include "tormatic_protocol.h"
7
+
8
+ namespace esphome {
9
+ namespace tormatic {
10
+
11
+ using namespace esphome::cover;
12
+
13
+ class Tormatic : public cover::Cover, public uart::UARTDevice, public PollingComponent {
14
+ public:
15
+ void setup() override;
16
+ void loop() override;
17
+ void update() override;
18
+ void dump_config() override;
19
+ float get_setup_priority() const override { return setup_priority::DATA; };
20
+
21
+ void set_open_duration(uint32_t duration) { this->open_duration_ = duration; }
22
+ void set_close_duration(uint32_t duration) { this->close_duration_ = duration; }
23
+
24
+ void publish_state(bool save = true, uint32_t ratelimit = 0);
25
+
26
+ cover::CoverTraits get_traits() override;
27
+
28
+ protected:
29
+ void control(const cover::CoverCall &call) override;
30
+
31
+ void recalibrate_duration_(GateStatus s);
32
+ void recompute_position_();
33
+ void control_position_(float target);
34
+ void stop_at_target_();
35
+
36
+ template<typename T> void send_message_(MessageType t, T r);
37
+ template<typename T> optional<T> read_data_();
38
+ void drain_rx_(uint16_t n = 0);
39
+
40
+ void request_gate_status_();
41
+ optional<GateStatus> read_gate_status_();
42
+
43
+ void send_gate_command_(GateStatus s);
44
+ void handle_gate_status_(GateStatus s);
45
+
46
+ uint32_t seq_tx_{0};
47
+
48
+ GateStatus current_status_{PAUSED};
49
+
50
+ uint32_t open_duration_{0};
51
+ uint32_t close_duration_{0};
52
+ uint32_t last_publish_time_{0};
53
+ uint32_t last_recompute_time_{0};
54
+ uint32_t direction_start_time_{0};
55
+ GateStatus next_command_{OPENED};
56
+ optional<float> target_position_{};
57
+ };
58
+
59
+ } // namespace tormatic
60
+ } // namespace esphome