esphome 2025.2.1__py3-none-any.whl → 2025.3.0b1__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.
- esphome/__main__.py +9 -1
- esphome/components/api/api_connection.cpp +426 -70
- esphome/components/api/api_connection.h +117 -25
- esphome/components/api/api_pb2.cpp +9 -0
- esphome/components/api/api_pb2.h +1 -0
- esphome/components/api/api_server.cpp +2 -2
- esphome/components/api/list_entities.cpp +76 -22
- esphome/components/api/list_entities.h +1 -0
- esphome/components/api/subscribe_state.h +2 -0
- esphome/components/audio/audio_reader.cpp +7 -7
- esphome/components/audio/audio_reader.h +1 -1
- esphome/components/bluetooth_proxy/bluetooth_proxy.h +8 -0
- esphome/components/bmp085/bmp085.cpp +1 -1
- esphome/components/chsc6x/__init__.py +2 -0
- esphome/components/chsc6x/chsc6x_touchscreen.cpp +47 -0
- esphome/components/chsc6x/chsc6x_touchscreen.h +34 -0
- esphome/components/chsc6x/touchscreen.py +33 -0
- esphome/components/climate/__init__.py +0 -1
- esphome/components/cst816/binary_sensor/__init__.py +2 -25
- esphome/components/cst816/touchscreen/cst816_touchscreen.cpp +3 -14
- esphome/components/cst816/touchscreen/cst816_touchscreen.h +0 -4
- esphome/components/esp32_ble_beacon/__init__.py +3 -1
- esphome/components/esp32_ble_tracker/esp32_ble_tracker.cpp +2 -2
- esphome/components/esp8266/gpio.py +1 -2
- esphome/components/font/__init__.py +185 -185
- esphome/components/font/font.cpp +4 -4
- esphome/components/font/font.h +1 -0
- esphome/components/haier/climate.py +11 -10
- esphome/components/hbridge/switch/hbridge_switch.cpp +2 -2
- esphome/components/heatpumpir/climate.py +2 -1
- esphome/components/heatpumpir/heatpumpir.cpp +1 -0
- esphome/components/heatpumpir/heatpumpir.h +1 -0
- esphome/components/i2c/__init__.py +6 -6
- esphome/components/i2c/i2c_bus_esp_idf.cpp +6 -2
- esphome/components/i2s_audio/speaker/i2s_audio_speaker.cpp +1 -1
- esphome/components/ili9xxx/display.py +1 -0
- esphome/components/ili9xxx/ili9xxx_display.h +5 -0
- esphome/components/ili9xxx/ili9xxx_init.h +59 -0
- esphome/components/ld2450/__init__.py +51 -0
- esphome/components/ld2450/binary_sensor.py +47 -0
- esphome/components/ld2450/button/__init__.py +45 -0
- esphome/components/ld2450/button/reset_button.cpp +9 -0
- esphome/components/ld2450/button/reset_button.h +18 -0
- esphome/components/ld2450/button/restart_button.cpp +9 -0
- esphome/components/ld2450/button/restart_button.h +18 -0
- esphome/components/ld2450/ld2450.cpp +876 -0
- esphome/components/ld2450/ld2450.h +234 -0
- esphome/components/ld2450/number/__init__.py +121 -0
- esphome/components/ld2450/number/presence_timeout_number.cpp +12 -0
- esphome/components/ld2450/number/presence_timeout_number.h +18 -0
- esphome/components/ld2450/number/zone_coordinate_number.cpp +14 -0
- esphome/components/ld2450/number/zone_coordinate_number.h +19 -0
- esphome/components/ld2450/select/__init__.py +56 -0
- esphome/components/ld2450/select/baud_rate_select.cpp +12 -0
- esphome/components/ld2450/select/baud_rate_select.h +18 -0
- esphome/components/ld2450/select/zone_type_select.cpp +12 -0
- esphome/components/ld2450/select/zone_type_select.h +18 -0
- esphome/components/ld2450/sensor.py +156 -0
- esphome/components/ld2450/switch/__init__.py +45 -0
- esphome/components/ld2450/switch/bluetooth_switch.cpp +12 -0
- esphome/components/ld2450/switch/bluetooth_switch.h +18 -0
- esphome/components/ld2450/switch/multi_target_switch.cpp +12 -0
- esphome/components/ld2450/switch/multi_target_switch.h +18 -0
- esphome/components/ld2450/text_sensor.py +62 -0
- esphome/components/ltr390/ltr390.cpp +7 -7
- esphome/components/ltr390/ltr390.h +0 -1
- esphome/components/lvgl/defines.py +0 -2
- esphome/components/lvgl/font.cpp +1 -1
- esphome/components/lvgl/lvgl_esphome.cpp +27 -19
- esphome/components/lvgl/widgets/img.py +1 -3
- esphome/components/mcp2515/mcp2515.cpp +1 -0
- esphome/components/mlx90393/sensor.py +53 -33
- esphome/components/mlx90393/sensor_mlx90393.cpp +4 -0
- esphome/components/mlx90393/sensor_mlx90393.h +8 -3
- esphome/components/mqtt/__init__.py +2 -2
- esphome/components/msa3xx/__init__.py +189 -0
- esphome/components/msa3xx/binary_sensor.py +40 -0
- esphome/components/msa3xx/msa3xx.cpp +417 -0
- esphome/components/msa3xx/msa3xx.h +311 -0
- esphome/components/msa3xx/sensor.py +42 -0
- esphome/components/msa3xx/text_sensor.py +38 -0
- esphome/components/nfc/binary_sensor/__init__.py +4 -4
- esphome/components/opentherm/binary_sensor/__init__.py +4 -4
- esphome/components/opentherm/generate.py +6 -6
- esphome/components/opentherm/sensor/__init__.py +5 -6
- esphome/components/packages/__init__.py +35 -11
- esphome/components/pn532/binary_sensor.py +4 -4
- esphome/components/rc522/binary_sensor.py +4 -4
- esphome/components/socket/bsd_sockets_impl.cpp +1 -0
- esphome/components/socket/lwip_sockets_impl.cpp +1 -0
- esphome/components/socket/socket.h +3 -1
- esphome/components/ssd1306_base/__init__.py +7 -7
- esphome/components/thermostat/climate.py +1 -1
- esphome/components/tmp1075/tmp1075.cpp +7 -11
- esphome/components/tmp1075/tmp1075.h +1 -2
- esphome/components/tormatic/__init__.py +1 -0
- esphome/components/tormatic/cover.py +47 -0
- esphome/components/tormatic/tormatic_cover.cpp +355 -0
- esphome/components/tormatic/tormatic_cover.h +60 -0
- esphome/components/tormatic/tormatic_protocol.h +211 -0
- esphome/components/touchscreen/binary_sensor/__init__.py +3 -0
- esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.cpp +7 -1
- esphome/components/touchscreen/binary_sensor/touchscreen_binary_sensor.h +3 -1
- esphome/components/touchscreen/touchscreen.cpp +3 -4
- esphome/components/udp/udp_component.h +4 -1
- esphome/components/web_server/list_entities.cpp +70 -66
- esphome/components/web_server/list_entities.h +43 -22
- esphome/components/web_server/web_server.cpp +345 -68
- esphome/components/web_server/web_server.h +138 -6
- esphome/components/web_server_base/__init__.py +1 -1
- esphome/components/web_server_idf/__init__.py +2 -0
- esphome/components/web_server_idf/web_server_idf.cpp +177 -30
- esphome/components/web_server_idf/web_server_idf.h +53 -4
- esphome/config_validation.py +23 -125
- esphome/const.py +5 -1
- esphome/core/config.py +12 -4
- esphome/core/defines.h +1 -1
- esphome/core/helpers.h +5 -3
- esphome/core/time.cpp +1 -0
- esphome/cpp_generator.py +3 -3
- esphome/dashboard/core.py +30 -21
- esphome/dashboard/dns.py +7 -1
- esphome/dashboard/entries.py +83 -16
- esphome/dashboard/settings.py +0 -4
- esphome/dashboard/status/mdns.py +43 -14
- esphome/dashboard/status/mqtt.py +22 -9
- esphome/dashboard/status/ping.py +54 -10
- esphome/dashboard/web_server.py +56 -24
- esphome/storage_json.py +4 -0
- esphome/wizard.py +13 -17
- esphome/writer.py +1 -3
- esphome/yaml_util.py +36 -33
- esphome/zeroconf.py +9 -21
- {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/METADATA +5 -5
- {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/RECORD +139 -99
- esphome/components/cst816/binary_sensor/cst816_button.h +0 -27
- {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/LICENSE +0 -0
- {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/WHEEL +0 -0
- {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/entry_points.txt +0 -0
- {esphome-2025.2.1.dist-info → esphome-2025.3.0b1.dist-info}/top_level.txt +0 -0
@@ -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
|
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
|
22
|
-
if (!this->read_byte(
|
23
|
-
|
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, ®value)) {
|
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 %
|
88
|
+
ESP_LOGV(TAG, "'%s' - sending configuration %02x", this->name_.c_str(), config_.regvalue);
|
93
89
|
log_config_();
|
94
|
-
if (!this->
|
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
|
-
|
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
|