mirror of
https://github.com/esphome/esphome.git
synced 2025-11-01 15:41:52 +00:00
Compare commits
135 Commits
jesserockz
...
2023.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b5927322e6 | ||
|
|
1cf4107e1c | ||
|
|
c12408326c | ||
|
|
4434e59e5a | ||
|
|
45180d98f6 | ||
|
|
44494ad18e | ||
|
|
1447536906 | ||
|
|
27ec517084 | ||
|
|
ce1f034bac | ||
|
|
f1f96f16e9 | ||
|
|
7665e9b076 | ||
|
|
227d94f38d | ||
|
|
b724ae9e0e | ||
|
|
df6cc14201 | ||
|
|
d981d7859d | ||
|
|
0f1ec515c1 | ||
|
|
78e18256f7 | ||
|
|
4899dfe642 | ||
|
|
d6b6e94059 | ||
|
|
310355a00b | ||
|
|
8cf26d6f3c | ||
|
|
b15a10f905 | ||
|
|
5dcf1debd7 | ||
|
|
9b57e1ac1d | ||
|
|
68683e3a50 | ||
|
|
d83324c4dc | ||
|
|
ecde4c1d2d | ||
|
|
bd8e470726 | ||
|
|
d2913fe627 | ||
|
|
43acc7dc2c | ||
|
|
e2a16d758b | ||
|
|
17ea0efb08 | ||
|
|
2fbd33267e | ||
|
|
cf3977f088 | ||
|
|
d20d4947ac | ||
|
|
7810ad40d7 | ||
|
|
7e1e799b3a | ||
|
|
dfafc41ce6 | ||
|
|
e460792c43 | ||
|
|
a9dc491a54 | ||
|
|
ac6693f177 | ||
|
|
c6742117d3 | ||
|
|
b5c47b9669 | ||
|
|
40df3aa55e | ||
|
|
393ca64d70 | ||
|
|
d3627f0972 | ||
|
|
124ab31f22 | ||
|
|
1b66fa5004 | ||
|
|
9494c27ad8 | ||
|
|
3facfa5c21 | ||
|
|
93ddce2e79 | ||
|
|
0bf6e21e1a | ||
|
|
6b7b076875 | ||
|
|
8d6ffb9169 | ||
|
|
4af4649e23 | ||
|
|
8bcddef39d | ||
|
|
4ac96ccea2 | ||
|
|
3c5de77ae9 | ||
|
|
a2925b1d37 | ||
|
|
73748e9e20 | ||
|
|
75c9823899 | ||
|
|
c8c0bd3351 | ||
|
|
e1cdeb7c8f | ||
|
|
7f97f42552 | ||
|
|
aa7f3569ec | ||
|
|
2d0a08442e | ||
|
|
d2380756b2 | ||
|
|
925e3cb6c9 | ||
|
|
6757acba56 | ||
|
|
5cc91cdd95 | ||
|
|
2b41886819 | ||
|
|
72c6efd6a0 | ||
|
|
a1f1804112 | ||
|
|
a8b1ceb4e9 | ||
|
|
4fb0f7f8c6 | ||
|
|
958cadeca8 | ||
|
|
00f2655f1a | ||
|
|
074f5029eb | ||
|
|
1691976587 | ||
|
|
60e6b4d21e | ||
|
|
5750591df2 | ||
|
|
a75da54455 | ||
|
|
de7f6c3f5f | ||
|
|
4245480656 | ||
|
|
1824c8131e | ||
|
|
4e9606d2e0 | ||
|
|
78500fa933 | ||
|
|
9c69b98a49 | ||
|
|
e6d8ef98d3 | ||
|
|
3f1af1690b | ||
|
|
84374b6b1e | ||
|
|
391316c9b5 | ||
|
|
705c62ebd7 | ||
|
|
cb520c00a5 | ||
|
|
2f24138345 | ||
|
|
96512b80cc | ||
|
|
fcb9b51978 | ||
|
|
f408f1a368 | ||
|
|
7d8d563c62 | ||
|
|
0a1f705fda | ||
|
|
1952c1880b | ||
|
|
b03967dac1 | ||
|
|
bcae2596a6 | ||
|
|
fc0347c86c | ||
|
|
d9563d4de1 | ||
|
|
cc7e2bf8db | ||
|
|
5d98e2923b | ||
|
|
07197d12f6 | ||
|
|
7b0a298497 | ||
|
|
21679cf2ba | ||
|
|
4be7cd12a1 | ||
|
|
dee4d0ccb7 | ||
|
|
7209dd8bae | ||
|
|
ab736c89bb | ||
|
|
6911639617 | ||
|
|
b9720d0715 | ||
|
|
47b3267ed4 | ||
|
|
e16ba2adb5 | ||
|
|
0a19b1e32c | ||
|
|
bae9a950c0 | ||
|
|
72b2943332 | ||
|
|
4ec0ef7548 | ||
|
|
25bc6761f6 | ||
|
|
81b6562c25 | ||
|
|
ae74189fc2 | ||
|
|
9e516efe10 | ||
|
|
366e29439e | ||
|
|
1c9c700d7f | ||
|
|
b2e6b9d31f | ||
|
|
7623f63846 | ||
|
|
2bfaf9dce3 | ||
|
|
5c2c1560bb | ||
|
|
f7096ab78e | ||
|
|
98f8feb625 | ||
|
|
9944ca414e |
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
@@ -138,14 +138,18 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
steps:
|
||||
- env:
|
||||
TOKEN: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
# yamllint disable rule:line-length
|
||||
run: |
|
||||
curl \
|
||||
-u ":$TOKEN" \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
|
||||
-d '{"ref":"main","inputs":{"version":"${{ github.event.release.tag_name }}","content":${{ toJSON(github.event.release.body) }}}}'
|
||||
# yamllint enable rule:line-length
|
||||
- name: Trigger Workflow
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{ secrets.DEPLOY_HA_ADDON_REPO_TOKEN }}
|
||||
script: |
|
||||
github.rest.actions.createWorkflowDispatch({
|
||||
owner: "esphome",
|
||||
repo: "home-assistant-addon",
|
||||
workflow_id: "bump-version.yml",
|
||||
ref: "main",
|
||||
inputs: {
|
||||
version: "${{ github.event.release.tag_name }}",
|
||||
content: ${{ toJSON(github.event.release.body) }}
|
||||
}
|
||||
})
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -128,3 +128,5 @@ tests/.esphome/
|
||||
|
||||
sdkconfig.*
|
||||
!sdkconfig.defaults
|
||||
|
||||
.tests/
|
||||
@@ -119,6 +119,7 @@ esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/key_collector/* @ssieb
|
||||
esphome/components/key_provider/* @ssieb
|
||||
esphome/components/lcd_menu/* @numo68
|
||||
esphome/components/ld2410/* @sebcaps
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
@@ -274,6 +275,7 @@ esphome/components/uart/* @esphome/core
|
||||
esphome/components/ufire_ec/* @pvizeli
|
||||
esphome/components/ufire_ise/* @pvizeli
|
||||
esphome/components/ultrasonic/* @OttoWinter
|
||||
esphome/components/vbus/* @ssieb
|
||||
esphome/components/version/* @esphome/core
|
||||
esphome/components/wake_on_lan/* @willwill2will54
|
||||
esphome/components/web_server_base/* @OttoWinter
|
||||
|
||||
@@ -26,7 +26,7 @@ RUN \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
curl=7.74.0-1.3+deb11u3 \
|
||||
curl=7.74.0-1.3+deb11u5 \
|
||||
openssh-client=1:8.4p1-5+deb11u1 \
|
||||
&& rm -rf \
|
||||
/tmp/* \
|
||||
|
||||
@@ -787,6 +787,7 @@ enum ClimateFanMode {
|
||||
CLIMATE_FAN_MIDDLE = 6;
|
||||
CLIMATE_FAN_FOCUS = 7;
|
||||
CLIMATE_FAN_DIFFUSE = 8;
|
||||
CLIMATE_FAN_QUIET = 9;
|
||||
}
|
||||
enum ClimateSwingMode {
|
||||
CLIMATE_SWING_OFF = 0;
|
||||
|
||||
@@ -235,6 +235,8 @@ template<> const char *proto_enum_to_string<enums::ClimateFanMode>(enums::Climat
|
||||
return "CLIMATE_FAN_FOCUS";
|
||||
case enums::CLIMATE_FAN_DIFFUSE:
|
||||
return "CLIMATE_FAN_DIFFUSE";
|
||||
case enums::CLIMATE_FAN_QUIET:
|
||||
return "CLIMATE_FAN_QUIET";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
|
||||
@@ -99,6 +99,7 @@ enum ClimateFanMode : uint32_t {
|
||||
CLIMATE_FAN_MIDDLE = 6,
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
CLIMATE_FAN_QUIET = 9,
|
||||
};
|
||||
enum ClimateSwingMode : uint32_t {
|
||||
CLIMATE_SWING_OFF = 0,
|
||||
|
||||
@@ -22,6 +22,8 @@ from esphome.const import (
|
||||
CONF_MODE_STATE_TOPIC,
|
||||
CONF_ON_STATE,
|
||||
CONF_PRESET,
|
||||
CONF_PRESET_COMMAND_TOPIC,
|
||||
CONF_PRESET_STATE_TOPIC,
|
||||
CONF_SWING_MODE,
|
||||
CONF_SWING_MODE_COMMAND_TOPIC,
|
||||
CONF_SWING_MODE_STATE_TOPIC,
|
||||
@@ -73,6 +75,7 @@ CLIMATE_FAN_MODES = {
|
||||
"MIDDLE": ClimateFanMode.CLIMATE_FAN_MIDDLE,
|
||||
"FOCUS": ClimateFanMode.CLIMATE_FAN_FOCUS,
|
||||
"DIFFUSE": ClimateFanMode.CLIMATE_FAN_DIFFUSE,
|
||||
"QUIET": ClimateFanMode.CLIMATE_FAN_QUIET,
|
||||
}
|
||||
|
||||
validate_climate_fan_mode = cv.enum(CLIMATE_FAN_MODES, upper=True)
|
||||
@@ -142,6 +145,12 @@ CLIMATE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).
|
||||
cv.Optional(CONF_MODE_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_PRESET_STATE_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
cv.Optional(CONF_SWING_MODE_COMMAND_TOPIC): cv.All(
|
||||
cv.requires_component("mqtt"), cv.publish_topic
|
||||
),
|
||||
@@ -216,7 +225,12 @@ async def setup_climate_core_(var, config):
|
||||
cg.add(mqtt_.set_custom_mode_command_topic(config[CONF_MODE_COMMAND_TOPIC]))
|
||||
if CONF_MODE_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_mode_state_topic(config[CONF_MODE_STATE_TOPIC]))
|
||||
|
||||
if CONF_PRESET_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_preset_command_topic(config[CONF_PRESET_COMMAND_TOPIC])
|
||||
)
|
||||
if CONF_PRESET_STATE_TOPIC in config:
|
||||
cg.add(mqtt_.set_custom_preset_state_topic(config[CONF_PRESET_STATE_TOPIC]))
|
||||
if CONF_SWING_MODE_COMMAND_TOPIC in config:
|
||||
cg.add(
|
||||
mqtt_.set_custom_swing_mode_command_topic(
|
||||
|
||||
@@ -174,6 +174,8 @@ ClimateCall &ClimateCall::set_fan_mode(const std::string &fan_mode) {
|
||||
this->set_fan_mode(CLIMATE_FAN_FOCUS);
|
||||
} else if (str_equals_case_insensitive(fan_mode, "DIFFUSE")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_DIFFUSE);
|
||||
} else if (str_equals_case_insensitive(fan_mode, "QUIET")) {
|
||||
this->set_fan_mode(CLIMATE_FAN_QUIET);
|
||||
} else {
|
||||
if (this->parent_->get_traits().supports_custom_fan_mode(fan_mode)) {
|
||||
this->custom_fan_mode_ = fan_mode;
|
||||
|
||||
@@ -62,6 +62,8 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode fan_mode) {
|
||||
return LOG_STR("FOCUS");
|
||||
case climate::CLIMATE_FAN_DIFFUSE:
|
||||
return LOG_STR("DIFFUSE");
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
return LOG_STR("QUIET");
|
||||
default:
|
||||
return LOG_STR("UNKNOWN");
|
||||
}
|
||||
|
||||
@@ -62,6 +62,8 @@ enum ClimateFanMode : uint8_t {
|
||||
CLIMATE_FAN_FOCUS = 7,
|
||||
/// The fan mode is set to Diffuse
|
||||
CLIMATE_FAN_DIFFUSE = 8,
|
||||
/// The fan mode is set to Quiet
|
||||
CLIMATE_FAN_QUIET = 9,
|
||||
};
|
||||
|
||||
/// Enum for all modes a climate swing can be in
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace climate {
|
||||
* - supports action - if the climate device supports reporting the active
|
||||
* current action of the device with the action property.
|
||||
* - supports fan modes - optionally, if it has a fan which can be configured in different ways:
|
||||
* - on, off, auto, high, medium, low, middle, focus, diffuse
|
||||
* - on, off, auto, high, medium, low, middle, focus, diffuse, quiet
|
||||
* - supports swing modes - optionally, if it has a swing which can be configured in different ways:
|
||||
* - off, both, vertical, horizontal
|
||||
*
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import base64
|
||||
import secrets
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
@@ -73,6 +75,7 @@ def import_config(
|
||||
project_name: str,
|
||||
import_url: str,
|
||||
network: str = CONF_WIFI,
|
||||
encryption: bool = False,
|
||||
) -> None:
|
||||
p = Path(path)
|
||||
|
||||
@@ -80,15 +83,40 @@ def import_config(
|
||||
raise FileExistsError
|
||||
|
||||
if project_name == "esphome.web":
|
||||
if "esp32c3" in import_url:
|
||||
board = "esp32-c3-devkitm-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32s2" in import_url:
|
||||
board = "esp32-s2-saola-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32s3" in import_url:
|
||||
board = "esp32-s3-devkitc-1"
|
||||
platform = "ESP32"
|
||||
elif "esp32" in import_url:
|
||||
board = "esp32dev"
|
||||
platform = "ESP32"
|
||||
elif "esp8266" in import_url:
|
||||
board = "esp01_1m"
|
||||
platform = "ESP8266"
|
||||
elif "pico-w" in import_url:
|
||||
board = "pico-w"
|
||||
platform = "RP2040"
|
||||
|
||||
kwargs = {
|
||||
"name": name,
|
||||
"friendly_name": friendly_name,
|
||||
"platform": platform,
|
||||
"board": board,
|
||||
"ssid": "!secret wifi_ssid",
|
||||
"psk": "!secret wifi_password",
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
kwargs["api_encryption_key"] = key
|
||||
|
||||
p.write_text(
|
||||
wizard_file(
|
||||
name=name,
|
||||
friendly_name=friendly_name,
|
||||
platform="ESP32" if "esp32" in import_url else "ESP8266",
|
||||
board="esp32dev" if "esp32" in import_url else "esp01_1m",
|
||||
ssid="!secret wifi_ssid",
|
||||
psk="!secret wifi_password",
|
||||
),
|
||||
wizard_file(**kwargs),
|
||||
encoding="utf8",
|
||||
)
|
||||
else:
|
||||
@@ -115,6 +143,11 @@ def import_config(
|
||||
"packages": {project_name: import_url},
|
||||
"esphome": esphome_core,
|
||||
}
|
||||
if encryption:
|
||||
noise_psk = secrets.token_bytes(32)
|
||||
key = base64.b64encode(noise_psk).decode()
|
||||
config["api"] = {"encryption": {"key": key}}
|
||||
|
||||
output = dump(config)
|
||||
|
||||
if network == CONF_WIFI:
|
||||
|
||||
@@ -111,6 +111,7 @@ class DemoClimate : public climate::Climate, public Component {
|
||||
climate::CLIMATE_FAN_MIDDLE,
|
||||
climate::CLIMATE_FAN_FOCUS,
|
||||
climate::CLIMATE_FAN_DIFFUSE,
|
||||
climate::CLIMATE_FAN_QUIET,
|
||||
});
|
||||
traits.set_supported_custom_fan_modes({"Auto Low", "Auto High"});
|
||||
traits.set_supported_swing_modes({
|
||||
|
||||
@@ -15,6 +15,84 @@ static const char *const TAG = "display";
|
||||
const Color COLOR_OFF(0, 0, 0, 0);
|
||||
const Color COLOR_ON(255, 255, 255, 255);
|
||||
|
||||
void Rect::expand(int16_t horizontal, int16_t vertical) {
|
||||
if (this->is_set() && (this->w >= (-2 * horizontal)) && (this->h >= (-2 * vertical))) {
|
||||
this->x = this->x - horizontal;
|
||||
this->y = this->y - vertical;
|
||||
this->w = this->w + (2 * horizontal);
|
||||
this->h = this->h + (2 * vertical);
|
||||
}
|
||||
}
|
||||
|
||||
void Rect::extend(Rect rect) {
|
||||
if (!this->is_set()) {
|
||||
this->x = rect.x;
|
||||
this->y = rect.y;
|
||||
this->w = rect.w;
|
||||
this->h = rect.h;
|
||||
} else {
|
||||
if (this->x > rect.x) {
|
||||
this->x = rect.x;
|
||||
}
|
||||
if (this->y > rect.y) {
|
||||
this->y = rect.y;
|
||||
}
|
||||
if (this->x2() < rect.x2()) {
|
||||
this->w = rect.x2() - this->x;
|
||||
}
|
||||
if (this->y2() < rect.y2()) {
|
||||
this->h = rect.y2() - this->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
void Rect::shrink(Rect rect) {
|
||||
if (!this->inside(rect)) {
|
||||
(*this) = Rect();
|
||||
} else {
|
||||
if (this->x < rect.x) {
|
||||
this->x = rect.x;
|
||||
}
|
||||
if (this->y < rect.y) {
|
||||
this->y = rect.y;
|
||||
}
|
||||
if (this->x2() > rect.x2()) {
|
||||
this->w = rect.x2() - this->x;
|
||||
}
|
||||
if (this->y2() > rect.y2()) {
|
||||
this->h = rect.y2() - this->y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Rect::inside(int16_t x, int16_t y, bool absolute) { // NOLINT
|
||||
if (!this->is_set()) {
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return ((x >= 0) && (x <= this->w) && (y >= 0) && (y <= this->h));
|
||||
} else {
|
||||
return ((x >= this->x) && (x <= this->x2()) && (y >= this->y) && (y <= this->y2()));
|
||||
}
|
||||
}
|
||||
|
||||
bool Rect::inside(Rect rect, bool absolute) {
|
||||
if (!this->is_set() || !rect.is_set()) {
|
||||
return true;
|
||||
}
|
||||
if (absolute) {
|
||||
return ((rect.x <= this->w) && (rect.w >= 0) && (rect.y <= this->h) && (rect.h >= 0));
|
||||
} else {
|
||||
return ((rect.x <= this->x2()) && (rect.x2() >= this->x) && (rect.y <= this->y2()) && (rect.y2() >= this->y));
|
||||
}
|
||||
}
|
||||
|
||||
void Rect::info(const std::string &prefix) {
|
||||
if (this->is_set()) {
|
||||
ESP_LOGI(TAG, "%s [%3d,%3d,%3d,%3d]", prefix.c_str(), this->x, this->y, this->w, this->h);
|
||||
} else
|
||||
ESP_LOGI(TAG, "%s ** IS NOT SET **", prefix.c_str());
|
||||
}
|
||||
|
||||
void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
this->buffer_ = allocator.allocate(buffer_length);
|
||||
@@ -24,6 +102,7 @@ void DisplayBuffer::init_internal_(uint32_t buffer_length) {
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
void DisplayBuffer::fill(Color color) { this->filled_rectangle(0, 0, this->get_width(), this->get_height(), color); }
|
||||
void DisplayBuffer::clear() { this->fill(COLOR_OFF); }
|
||||
int DisplayBuffer::get_width() {
|
||||
@@ -50,6 +129,9 @@ int DisplayBuffer::get_height() {
|
||||
}
|
||||
void DisplayBuffer::set_rotation(DisplayRotation rotation) { this->rotation_ = rotation; }
|
||||
void HOT DisplayBuffer::draw_pixel_at(int x, int y, Color color) {
|
||||
if (!this->get_clipping().inside(x, y))
|
||||
return; // NOLINT
|
||||
|
||||
switch (this->rotation_) {
|
||||
case DISPLAY_ROTATION_0_DEGREES:
|
||||
break;
|
||||
@@ -368,6 +450,10 @@ void DisplayBuffer::do_update_() {
|
||||
} else if (this->writer_.has_value()) {
|
||||
(*this->writer_)(*this);
|
||||
}
|
||||
// remove all not ended clipping regions
|
||||
while (is_clipping()) {
|
||||
end_clipping();
|
||||
}
|
||||
}
|
||||
void DisplayOnPageChangeTrigger::process(DisplayPage *from, DisplayPage *to) {
|
||||
if ((this->from_ == nullptr || this->from_ == from) && (this->to_ == nullptr || this->to_ == to))
|
||||
@@ -392,6 +478,41 @@ void DisplayBuffer::strftime(int x, int y, Font *font, const char *format, time:
|
||||
}
|
||||
#endif
|
||||
|
||||
void DisplayBuffer::start_clipping(Rect rect) {
|
||||
if (!this->clipping_rectangle_.empty()) {
|
||||
Rect r = this->clipping_rectangle_.back();
|
||||
rect.shrink(r);
|
||||
}
|
||||
this->clipping_rectangle_.push_back(rect);
|
||||
}
|
||||
void DisplayBuffer::end_clipping() {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "clear: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.pop_back();
|
||||
}
|
||||
}
|
||||
void DisplayBuffer::extend_clipping(Rect add_rect) {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "add: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.back().extend(add_rect);
|
||||
}
|
||||
}
|
||||
void DisplayBuffer::shrink_clipping(Rect add_rect) {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
ESP_LOGE(TAG, "add: Clipping is not set.");
|
||||
} else {
|
||||
this->clipping_rectangle_.back().shrink(add_rect);
|
||||
}
|
||||
}
|
||||
Rect DisplayBuffer::get_clipping() {
|
||||
if (this->clipping_rectangle_.empty()) {
|
||||
return Rect();
|
||||
} else {
|
||||
return this->clipping_rectangle_.back();
|
||||
}
|
||||
}
|
||||
bool Glyph::get_pixel(int x, int y) const {
|
||||
const int x_data = x - this->glyph_data_->offset_x;
|
||||
const int y_data = y - this->glyph_data_->offset_y;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "display_color_utils.h"
|
||||
|
||||
#include <cstdarg>
|
||||
#include <vector>
|
||||
|
||||
@@ -100,6 +99,32 @@ enum DisplayRotation {
|
||||
DISPLAY_ROTATION_270_DEGREES = 270,
|
||||
};
|
||||
|
||||
static const int16_t VALUE_NO_SET = 32766;
|
||||
|
||||
class Rect {
|
||||
public:
|
||||
int16_t x; ///< X coordinate of corner
|
||||
int16_t y; ///< Y coordinate of corner
|
||||
int16_t w; ///< Width of region
|
||||
int16_t h; ///< Height of region
|
||||
|
||||
Rect() : x(VALUE_NO_SET), y(VALUE_NO_SET), w(VALUE_NO_SET), h(VALUE_NO_SET) {} // NOLINT
|
||||
inline Rect(int16_t x, int16_t y, int16_t w, int16_t h) ALWAYS_INLINE : x(x), y(y), w(w), h(h) {}
|
||||
inline int16_t x2() { return this->x + this->w; }; ///< X coordinate of corner
|
||||
inline int16_t y2() { return this->y + this->h; }; ///< Y coordinate of corner
|
||||
|
||||
inline bool is_set() ALWAYS_INLINE { return (this->h != VALUE_NO_SET) && (this->w != VALUE_NO_SET); }
|
||||
|
||||
void expand(int16_t horizontal, int16_t vertical);
|
||||
|
||||
void extend(Rect rect);
|
||||
void shrink(Rect rect);
|
||||
|
||||
bool inside(Rect rect, bool absolute = false);
|
||||
bool inside(int16_t x, int16_t y, bool absolute = false);
|
||||
void info(const std::string &prefix = "rect info:");
|
||||
};
|
||||
|
||||
class Font;
|
||||
class Image;
|
||||
class DisplayBuffer;
|
||||
@@ -126,6 +151,7 @@ class DisplayBuffer {
|
||||
int get_width();
|
||||
/// Get the height of the image in pixels with rotation applied.
|
||||
int get_height();
|
||||
|
||||
/// Set a single pixel at the specified coordinates to the given color.
|
||||
void draw_pixel_at(int x, int y, Color color = COLOR_ON);
|
||||
|
||||
@@ -374,6 +400,49 @@ class DisplayBuffer {
|
||||
*/
|
||||
virtual DisplayType get_display_type() = 0;
|
||||
|
||||
/** Set the clipping rectangle for further drawing
|
||||
*
|
||||
* @param[in] rect: Pointer to Rect for clipping (or NULL for entire screen)
|
||||
*
|
||||
* return true if success, false if error
|
||||
*/
|
||||
void start_clipping(Rect rect);
|
||||
void start_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
start_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** Add a rectangular region to the invalidation region
|
||||
* - This is usually called when an element has been modified
|
||||
*
|
||||
* @param[in] rect: Rectangle to add to the invalidation region
|
||||
*/
|
||||
void extend_clipping(Rect rect);
|
||||
void extend_clipping(int16_t left, int16_t top, int16_t right, int16_t bottom) {
|
||||
this->extend_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** substract a rectangular region to the invalidation region
|
||||
* - This is usually called when an element has been modified
|
||||
*
|
||||
* @param[in] rect: Rectangle to add to the invalidation region
|
||||
*/
|
||||
void shrink_clipping(Rect rect);
|
||||
void shrink_clipping(uint16_t left, uint16_t top, uint16_t right, uint16_t bottom) {
|
||||
this->shrink_clipping(Rect(left, top, right - left, bottom - top));
|
||||
};
|
||||
|
||||
/** Reset the invalidation region
|
||||
*/
|
||||
void end_clipping();
|
||||
|
||||
/** Get the current the clipping rectangle
|
||||
*
|
||||
* return rect for active clipping region
|
||||
*/
|
||||
Rect get_clipping();
|
||||
|
||||
bool is_clipping() const { return !this->clipping_rectangle_.empty(); }
|
||||
|
||||
protected:
|
||||
void vprintf_(int x, int y, Font *font, Color color, TextAlign align, const char *format, va_list arg);
|
||||
|
||||
@@ -390,6 +459,7 @@ class DisplayBuffer {
|
||||
DisplayPage *previous_page_{nullptr};
|
||||
std::vector<DisplayOnPageChangeTrigger *> on_page_change_triggers_;
|
||||
bool auto_clear_enabled_{true};
|
||||
std::vector<Rect> clipping_rectangle_;
|
||||
};
|
||||
|
||||
class DisplayPage {
|
||||
|
||||
@@ -8,6 +8,7 @@ from esphome.const import (
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
CONF_NUMBER,
|
||||
CONF_FORMAT,
|
||||
CONF_MODE,
|
||||
@@ -32,7 +33,6 @@ CONF_BACK = "back"
|
||||
CONF_TEXT = "text"
|
||||
CONF_SELECT = "select"
|
||||
CONF_SWITCH = "switch"
|
||||
CONF_CUSTOM = "custom"
|
||||
CONF_ITEMS = "items"
|
||||
CONF_ON_TEXT = "on_text"
|
||||
CONF_OFF_TEXT = "off_text"
|
||||
|
||||
@@ -123,11 +123,8 @@ def validate_gpio_pin(value):
|
||||
|
||||
def validate_supports(value):
|
||||
mode = value[CONF_MODE]
|
||||
is_input = mode[CONF_INPUT]
|
||||
is_output = mode[CONF_OUTPUT]
|
||||
is_open_drain = mode[CONF_OPEN_DRAIN]
|
||||
is_pullup = mode[CONF_PULLUP]
|
||||
is_pulldown = mode[CONF_PULLDOWN]
|
||||
variant = CORE.data[KEY_ESP32][KEY_VARIANT]
|
||||
if variant not in _esp32_validations:
|
||||
raise cv.Invalid(f"Unsupported ESP32 variant {variant}")
|
||||
@@ -138,26 +135,6 @@ def validate_supports(value):
|
||||
)
|
||||
|
||||
value = _esp32_validations[variant].usage_validation(value)
|
||||
if CORE.using_arduino:
|
||||
# (input, output, open_drain, pullup, pulldown)
|
||||
supported_modes = {
|
||||
# INPUT
|
||||
(True, False, False, False, False),
|
||||
# OUTPUT
|
||||
(False, True, False, False, False),
|
||||
# INPUT_PULLUP
|
||||
(True, False, False, True, False),
|
||||
# INPUT_PULLDOWN
|
||||
(True, False, False, False, True),
|
||||
# OUTPUT_OPEN_DRAIN
|
||||
(False, True, True, False, False),
|
||||
}
|
||||
key = (is_input, is_output, is_open_drain, is_pullup, is_pulldown)
|
||||
if key not in supported_modes:
|
||||
raise cv.Invalid(
|
||||
"This pin mode is not supported on ESP32 for arduino frameworks",
|
||||
[CONF_MODE],
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ CONF_BLE_ID = "ble_id"
|
||||
|
||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
|
||||
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ void ESP32ImprovComponent::send_response_(std::vector<uint8_t> &response) {
|
||||
}
|
||||
|
||||
void ESP32ImprovComponent::start() {
|
||||
if (this->state_ != improv::STATE_STOPPED)
|
||||
if (this->should_start_ || this->state_ != improv::STATE_STOPPED)
|
||||
return;
|
||||
|
||||
ESP_LOGD(TAG, "Setting Improv to start");
|
||||
|
||||
@@ -33,6 +33,7 @@ ETHERNET_TYPES = {
|
||||
"RTL8201": EthernetType.ETHERNET_TYPE_RTL8201,
|
||||
"DP83848": EthernetType.ETHERNET_TYPE_DP83848,
|
||||
"IP101": EthernetType.ETHERNET_TYPE_IP101,
|
||||
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
|
||||
}
|
||||
|
||||
emac_rmii_clock_gpio_t = cg.global_ns.enum("emac_rmii_clock_gpio_t")
|
||||
|
||||
339
esphome/components/ethernet/esp_eth_phy_jl1101.c
Normal file
339
esphome/components/ethernet/esp_eth_phy_jl1101.c
Normal file
@@ -0,0 +1,339 @@
|
||||
// Copyright 2019 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/cdefs.h>
|
||||
#include "esp_log.h"
|
||||
#include "esp_eth.h"
|
||||
#include "eth_phy_regs_struct.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_rom_gpio.h"
|
||||
#include "esp_rom_sys.h"
|
||||
|
||||
static const char *TAG = "jl1101";
|
||||
#define PHY_CHECK(a, str, goto_tag, ...) \
|
||||
do { \
|
||||
if (!(a)) { \
|
||||
ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
|
||||
goto goto_tag; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/***************Vendor Specific Register***************/
|
||||
|
||||
/**
|
||||
* @brief PSR(Page Select Register)
|
||||
*
|
||||
*/
|
||||
typedef union {
|
||||
struct {
|
||||
uint16_t page_select : 8; /* Select register page, default is 0 */
|
||||
uint16_t reserved : 8; /* Reserved */
|
||||
};
|
||||
uint16_t val;
|
||||
} psr_reg_t;
|
||||
#define ETH_PHY_PSR_REG_ADDR (0x1F)
|
||||
|
||||
typedef struct {
|
||||
esp_eth_phy_t parent;
|
||||
esp_eth_mediator_t *eth;
|
||||
int addr;
|
||||
uint32_t reset_timeout_ms;
|
||||
uint32_t autonego_timeout_ms;
|
||||
eth_link_t link_status;
|
||||
int reset_gpio_num;
|
||||
} phy_jl1101_t;
|
||||
|
||||
static esp_err_t jl1101_page_select(phy_jl1101_t *jl1101, uint32_t page) {
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
psr_reg_t psr = {.page_select = page};
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_PSR_REG_ADDR, psr.val) == ESP_OK, "write PSR failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_update_link_duplex_speed(phy_jl1101_t *jl1101) {
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
eth_speed_t speed = ETH_SPEED_10M;
|
||||
eth_duplex_t duplex = ETH_DUPLEX_HALF;
|
||||
bmcr_reg_t bmcr;
|
||||
bmsr_reg_t bmsr;
|
||||
uint32_t peer_pause_ability = false;
|
||||
anlpar_reg_t anlpar;
|
||||
PHY_CHECK(jl1101_page_select(jl1101, 0) == ESP_OK, "select page 0 failed", err);
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)) == ESP_OK, "read BMSR failed",
|
||||
err);
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_ANLPAR_REG_ADDR, &(anlpar.val)) == ESP_OK,
|
||||
"read ANLPAR failed", err);
|
||||
eth_link_t link = bmsr.link_status ? ETH_LINK_UP : ETH_LINK_DOWN;
|
||||
/* check if link status changed */
|
||||
if (jl1101->link_status != link) {
|
||||
/* when link up, read negotiation result */
|
||||
if (link == ETH_LINK_UP) {
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
if (bmcr.speed_select) {
|
||||
speed = ETH_SPEED_100M;
|
||||
} else {
|
||||
speed = ETH_SPEED_10M;
|
||||
}
|
||||
if (bmcr.duplex_mode) {
|
||||
duplex = ETH_DUPLEX_FULL;
|
||||
} else {
|
||||
duplex = ETH_DUPLEX_HALF;
|
||||
}
|
||||
PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_SPEED, (void *) speed) == ESP_OK, "change speed failed", err);
|
||||
PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_DUPLEX, (void *) duplex) == ESP_OK, "change duplex failed", err);
|
||||
/* if we're in duplex mode, and peer has the flow control ability */
|
||||
if (duplex == ETH_DUPLEX_FULL && anlpar.symmetric_pause) {
|
||||
peer_pause_ability = 1;
|
||||
} else {
|
||||
peer_pause_ability = 0;
|
||||
}
|
||||
PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_PAUSE, (void *) peer_pause_ability) == ESP_OK,
|
||||
"change pause ability failed", err);
|
||||
}
|
||||
PHY_CHECK(eth->on_state_changed(eth, ETH_STATE_LINK, (void *) link) == ESP_OK, "change link failed", err);
|
||||
jl1101->link_status = link;
|
||||
}
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_set_mediator(esp_eth_phy_t *phy, esp_eth_mediator_t *eth) {
|
||||
PHY_CHECK(eth, "can't set mediator to null", err);
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
jl1101->eth = eth;
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_get_link(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
/* Updata information about link, speed, duplex */
|
||||
PHY_CHECK(jl1101_update_link_duplex_speed(jl1101) == ESP_OK, "update link duplex speed failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_reset(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
jl1101->link_status = ETH_LINK_DOWN;
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
bmcr_reg_t bmcr = {.reset = 1};
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
|
||||
/* Wait for reset complete */
|
||||
uint32_t to = 0;
|
||||
for (to = 0; to < jl1101->reset_timeout_ms / 50; to++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(50));
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
if (!bmcr.reset) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
PHY_CHECK(to < jl1101->reset_timeout_ms / 50, "reset timeout", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_reset_hw(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
if (jl1101->reset_gpio_num >= 0) {
|
||||
esp_rom_gpio_pad_select_gpio(jl1101->reset_gpio_num);
|
||||
gpio_set_direction(jl1101->reset_gpio_num, GPIO_MODE_OUTPUT);
|
||||
gpio_set_level(jl1101->reset_gpio_num, 0);
|
||||
esp_rom_delay_us(100); // insert min input assert time
|
||||
gpio_set_level(jl1101->reset_gpio_num, 1);
|
||||
}
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_negotiate(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
/* in case any link status has changed, let's assume we're in link down status */
|
||||
jl1101->link_status = ETH_LINK_DOWN;
|
||||
/* Restart auto negotiation */
|
||||
bmcr_reg_t bmcr = {
|
||||
.speed_select = 1, /* 100Mbps */
|
||||
.duplex_mode = 1, /* Full Duplex */
|
||||
.en_auto_nego = 1, /* Auto Negotiation */
|
||||
.restart_auto_nego = 1 /* Restart Auto Negotiation */
|
||||
};
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
|
||||
/* Wait for auto negotiation complete */
|
||||
bmsr_reg_t bmsr;
|
||||
uint32_t to = 0;
|
||||
for (to = 0; to < jl1101->autonego_timeout_ms / 100; to++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMSR_REG_ADDR, &(bmsr.val)) == ESP_OK, "read BMSR failed",
|
||||
err);
|
||||
if (bmsr.auto_nego_complete) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Auto negotiation failed, maybe no network cable plugged in, so output a warning */
|
||||
if (to >= jl1101->autonego_timeout_ms / 100) {
|
||||
ESP_LOGW(TAG, "auto negotiation timeout");
|
||||
}
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_pwrctl(esp_eth_phy_t *phy, bool enable) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
bmcr_reg_t bmcr;
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
if (!enable) {
|
||||
/* Enable IEEE Power Down Mode */
|
||||
bmcr.power_down = 1;
|
||||
} else {
|
||||
/* Disable IEEE Power Down Mode */
|
||||
bmcr.power_down = 0;
|
||||
}
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, bmcr.val) == ESP_OK, "write BMCR failed", err);
|
||||
if (!enable) {
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
PHY_CHECK(bmcr.power_down == 1, "power down failed", err);
|
||||
} else {
|
||||
/* wait for power up complete */
|
||||
uint32_t to = 0;
|
||||
for (to = 0; to < jl1101->reset_timeout_ms / 10; to++) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_BMCR_REG_ADDR, &(bmcr.val)) == ESP_OK, "read BMCR failed",
|
||||
err);
|
||||
if (bmcr.power_down == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
PHY_CHECK(to < jl1101->reset_timeout_ms / 10, "power up timeout", err);
|
||||
}
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_set_addr(esp_eth_phy_t *phy, uint32_t addr) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
jl1101->addr = addr;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_get_addr(esp_eth_phy_t *phy, uint32_t *addr) {
|
||||
PHY_CHECK(addr, "addr can't be null", err);
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
*addr = jl1101->addr;
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_del(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
free(jl1101);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_advertise_pause_ability(esp_eth_phy_t *phy, uint32_t ability) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
/* Set PAUSE function ability */
|
||||
anar_reg_t anar;
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_ANAR_REG_ADDR, &(anar.val)) == ESP_OK, "read ANAR failed",
|
||||
err);
|
||||
if (ability) {
|
||||
anar.asymmetric_pause = 1;
|
||||
anar.symmetric_pause = 1;
|
||||
} else {
|
||||
anar.asymmetric_pause = 0;
|
||||
anar.symmetric_pause = 0;
|
||||
}
|
||||
PHY_CHECK(eth->phy_reg_write(eth, jl1101->addr, ETH_PHY_ANAR_REG_ADDR, anar.val) == ESP_OK, "write ANAR failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_init(esp_eth_phy_t *phy) {
|
||||
phy_jl1101_t *jl1101 = __containerof(phy, phy_jl1101_t, parent);
|
||||
esp_eth_mediator_t *eth = jl1101->eth;
|
||||
// Detect PHY address
|
||||
if (jl1101->addr == ESP_ETH_PHY_ADDR_AUTO) {
|
||||
PHY_CHECK(esp_eth_detect_phy_addr(eth, &jl1101->addr) == ESP_OK, "Detect PHY address failed", err);
|
||||
}
|
||||
/* Power on Ethernet PHY */
|
||||
PHY_CHECK(jl1101_pwrctl(phy, true) == ESP_OK, "power control failed", err);
|
||||
/* Reset Ethernet PHY */
|
||||
PHY_CHECK(jl1101_reset(phy) == ESP_OK, "reset failed", err);
|
||||
/* Check PHY ID */
|
||||
phyidr1_reg_t id1;
|
||||
phyidr2_reg_t id2;
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_IDR1_REG_ADDR, &(id1.val)) == ESP_OK, "read ID1 failed", err);
|
||||
PHY_CHECK(eth->phy_reg_read(eth, jl1101->addr, ETH_PHY_IDR2_REG_ADDR, &(id2.val)) == ESP_OK, "read ID2 failed", err);
|
||||
PHY_CHECK(id1.oui_msb == 0x937C && id2.oui_lsb == 0x10 && id2.vendor_model == 0x2, "wrong chip ID", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
static esp_err_t jl1101_deinit(esp_eth_phy_t *phy) {
|
||||
/* Power off Ethernet PHY */
|
||||
PHY_CHECK(jl1101_pwrctl(phy, false) == ESP_OK, "power control failed", err);
|
||||
return ESP_OK;
|
||||
err:
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config) {
|
||||
PHY_CHECK(config, "can't set phy config to null", err);
|
||||
phy_jl1101_t *jl1101 = calloc(1, sizeof(phy_jl1101_t));
|
||||
PHY_CHECK(jl1101, "calloc jl1101 failed", err);
|
||||
jl1101->addr = config->phy_addr;
|
||||
jl1101->reset_gpio_num = config->reset_gpio_num;
|
||||
jl1101->reset_timeout_ms = config->reset_timeout_ms;
|
||||
jl1101->link_status = ETH_LINK_DOWN;
|
||||
jl1101->autonego_timeout_ms = config->autonego_timeout_ms;
|
||||
jl1101->parent.reset = jl1101_reset;
|
||||
jl1101->parent.reset_hw = jl1101_reset_hw;
|
||||
jl1101->parent.init = jl1101_init;
|
||||
jl1101->parent.deinit = jl1101_deinit;
|
||||
jl1101->parent.set_mediator = jl1101_set_mediator;
|
||||
jl1101->parent.negotiate = jl1101_negotiate;
|
||||
jl1101->parent.get_link = jl1101_get_link;
|
||||
jl1101->parent.pwrctl = jl1101_pwrctl;
|
||||
jl1101->parent.get_addr = jl1101_get_addr;
|
||||
jl1101->parent.set_addr = jl1101_set_addr;
|
||||
jl1101->parent.advertise_pause_ability = jl1101_advertise_pause_ability;
|
||||
jl1101->parent.del = jl1101_del;
|
||||
|
||||
return &(jl1101->parent);
|
||||
err:
|
||||
return NULL;
|
||||
}
|
||||
#endif /* USE_ESP32 */
|
||||
@@ -71,6 +71,10 @@ void EthernetComponent::setup() {
|
||||
phy = esp_eth_phy_new_ip101(&phy_config);
|
||||
break;
|
||||
}
|
||||
case ETHERNET_TYPE_JL1101: {
|
||||
phy = esp_eth_phy_new_jl1101(&phy_config);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
this->mark_failed();
|
||||
return;
|
||||
|
||||
@@ -18,6 +18,7 @@ enum EthernetType {
|
||||
ETHERNET_TYPE_RTL8201,
|
||||
ETHERNET_TYPE_DP83848,
|
||||
ETHERNET_TYPE_IP101,
|
||||
ETHERNET_TYPE_JL1101,
|
||||
};
|
||||
|
||||
struct ManualIP {
|
||||
@@ -82,6 +83,7 @@ class EthernetComponent : public Component {
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
extern EthernetComponent *global_eth_component;
|
||||
extern "C" esp_eth_phy_t *esp_eth_phy_new_jl1101(const eth_phy_config_t *config);
|
||||
|
||||
} // namespace ethernet
|
||||
} // namespace esphome
|
||||
|
||||
158
esphome/components/ld2410/__init__.py
Normal file
158
esphome/components/ld2410/__init__.py
Normal file
@@ -0,0 +1,158 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID, CONF_TIMEOUT
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
CODEOWNERS = ["@sebcaps"]
|
||||
MULTI_CONF = True
|
||||
|
||||
ld2410_ns = cg.esphome_ns.namespace("ld2410")
|
||||
LD2410Component = ld2410_ns.class_("LD2410Component", cg.Component, uart.UARTDevice)
|
||||
LD2410Restart = ld2410_ns.class_("LD2410Restart", automation.Action)
|
||||
CONF_LD2410_ID = "ld2410_id"
|
||||
CONF_MAX_MOVE_DISTANCE = "max_move_distance"
|
||||
CONF_MAX_STILL_DISTANCE = "max_still_distance"
|
||||
CONF_G0_MOVE_THRESHOLD = "g0_move_threshold"
|
||||
CONF_G0_STILL_THRESHOLD = "g0_still_threshold"
|
||||
CONF_G1_MOVE_THRESHOLD = "g1_move_threshold"
|
||||
CONF_G1_STILL_THRESHOLD = "g1_still_threshold"
|
||||
CONF_G2_MOVE_THRESHOLD = "g2_move_threshold"
|
||||
CONF_G2_STILL_THRESHOLD = "g2_still_threshold"
|
||||
CONF_G3_MOVE_THRESHOLD = "g3_move_threshold"
|
||||
CONF_G3_STILL_THRESHOLD = "g3_still_threshold"
|
||||
CONF_G4_MOVE_THRESHOLD = "g4_move_threshold"
|
||||
CONF_G4_STILL_THRESHOLD = "g4_still_threshold"
|
||||
CONF_G5_MOVE_THRESHOLD = "g5_move_threshold"
|
||||
CONF_G5_STILL_THRESHOLD = "g5_still_threshold"
|
||||
CONF_G6_MOVE_THRESHOLD = "g6_move_threshold"
|
||||
CONF_G6_STILL_THRESHOLD = "g6_still_threshold"
|
||||
CONF_G7_MOVE_THRESHOLD = "g7_move_threshold"
|
||||
CONF_G7_STILL_THRESHOLD = "g7_still_threshold"
|
||||
CONF_G8_MOVE_THRESHOLD = "g8_move_threshold"
|
||||
CONF_G8_STILL_THRESHOLD = "g8_still_threshold"
|
||||
|
||||
DISTANCES = [0.75, 1.5, 2.25, 3, 3.75, 4.5, 5.25, 6]
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LD2410Component),
|
||||
cv.Optional(CONF_MAX_MOVE_DISTANCE, default="4.5m"): cv.All(
|
||||
cv.distance, cv.one_of(*DISTANCES, float=True)
|
||||
),
|
||||
cv.Optional(CONF_MAX_STILL_DISTANCE, default="4.5m"): cv.All(
|
||||
cv.distance, cv.one_of(*DISTANCES, float=True)
|
||||
),
|
||||
cv.Optional(CONF_TIMEOUT, default="5s"): cv.All(
|
||||
cv.positive_time_period_seconds,
|
||||
cv.Range(max=cv.TimePeriod(seconds=32767)),
|
||||
),
|
||||
cv.Optional(CONF_G0_MOVE_THRESHOLD, default=50): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G0_STILL_THRESHOLD, default=0): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G1_MOVE_THRESHOLD, default=50): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G1_STILL_THRESHOLD, default=0): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G2_MOVE_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G2_STILL_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G3_MOVE_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G3_STILL_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G4_MOVE_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G4_STILL_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G5_MOVE_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G5_STILL_THRESHOLD, default=40): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G6_MOVE_THRESHOLD, default=30): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G6_STILL_THRESHOLD, default=15): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G7_MOVE_THRESHOLD, default=30): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G7_STILL_THRESHOLD, default=15): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G8_MOVE_THRESHOLD, default=30): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
cv.Optional(CONF_G8_STILL_THRESHOLD, default=15): cv.int_range(
|
||||
min=0, max=100
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"ld2410",
|
||||
baud_rate=256000,
|
||||
require_tx=True,
|
||||
require_rx=True,
|
||||
parity="NONE",
|
||||
stop_bits=1,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
cg.add(var.set_timeout(config[CONF_TIMEOUT]))
|
||||
cg.add(var.set_max_move_distance(int(config[CONF_MAX_MOVE_DISTANCE] / 0.75)))
|
||||
cg.add(var.set_max_still_distance(int(config[CONF_MAX_STILL_DISTANCE] / 0.75)))
|
||||
cg.add(
|
||||
var.set_range_config(
|
||||
config[CONF_G0_MOVE_THRESHOLD],
|
||||
config[CONF_G0_STILL_THRESHOLD],
|
||||
config[CONF_G1_MOVE_THRESHOLD],
|
||||
config[CONF_G1_STILL_THRESHOLD],
|
||||
config[CONF_G2_MOVE_THRESHOLD],
|
||||
config[CONF_G2_STILL_THRESHOLD],
|
||||
config[CONF_G3_MOVE_THRESHOLD],
|
||||
config[CONF_G3_STILL_THRESHOLD],
|
||||
config[CONF_G4_MOVE_THRESHOLD],
|
||||
config[CONF_G4_STILL_THRESHOLD],
|
||||
config[CONF_G5_MOVE_THRESHOLD],
|
||||
config[CONF_G5_STILL_THRESHOLD],
|
||||
config[CONF_G6_MOVE_THRESHOLD],
|
||||
config[CONF_G6_STILL_THRESHOLD],
|
||||
config[CONF_G7_MOVE_THRESHOLD],
|
||||
config[CONF_G7_STILL_THRESHOLD],
|
||||
config[CONF_G8_MOVE_THRESHOLD],
|
||||
config[CONF_G8_STILL_THRESHOLD],
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
CALIBRATION_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(LD2410Component),
|
||||
}
|
||||
)
|
||||
36
esphome/components/ld2410/binary_sensor.py
Normal file
36
esphome/components/ld2410/binary_sensor.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import DEVICE_CLASS_MOTION, DEVICE_CLASS_OCCUPANCY
|
||||
from . import CONF_LD2410_ID, LD2410Component
|
||||
|
||||
DEPENDENCIES = ["ld2410"]
|
||||
CONF_HAS_TARGET = "has_target"
|
||||
CONF_HAS_MOVING_TARGET = "has_moving_target"
|
||||
CONF_HAS_STILL_TARGET = "has_still_target"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY
|
||||
),
|
||||
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_MOTION
|
||||
),
|
||||
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if CONF_HAS_TARGET in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET])
|
||||
cg.add(ld2410_component.set_target_sensor(sens))
|
||||
if CONF_HAS_MOVING_TARGET in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_MOVING_TARGET])
|
||||
cg.add(ld2410_component.set_moving_target_sensor(sens))
|
||||
if CONF_HAS_STILL_TARGET in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_STILL_TARGET])
|
||||
cg.add(ld2410_component.set_still_target_sensor(sens))
|
||||
315
esphome/components/ld2410/ld2410.cpp
Normal file
315
esphome/components/ld2410/ld2410.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
#include "ld2410.h"
|
||||
|
||||
#define highbyte(val) (uint8_t)((val) >> 8)
|
||||
#define lowbyte(val) (uint8_t)((val) &0xff)
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2410 {
|
||||
|
||||
static const char *const TAG = "ld2410";
|
||||
|
||||
void LD2410Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LD2410:");
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "HasTargetSensor", this->target_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "MovingSensor", this->moving_binary_sensor_);
|
||||
LOG_BINARY_SENSOR(" ", "StillSensor", this->still_binary_sensor_);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Moving Distance", this->moving_target_distance_sensor_);
|
||||
LOG_SENSOR(" ", "Still Distance", this->still_target_distance_sensor_);
|
||||
LOG_SENSOR(" ", "Moving Energy", this->moving_target_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Still Energy", this->still_target_energy_sensor_);
|
||||
LOG_SENSOR(" ", "Detection Distance", this->detection_distance_sensor_);
|
||||
#endif
|
||||
this->set_config_mode_(true);
|
||||
this->get_version_();
|
||||
this->set_config_mode_(false);
|
||||
ESP_LOGCONFIG(TAG, " Firmware Version : %u.%u.%u%u%u%u", this->version_[0], this->version_[1], this->version_[2],
|
||||
this->version_[3], this->version_[4], this->version_[5]);
|
||||
}
|
||||
|
||||
void LD2410Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up LD2410...");
|
||||
this->set_config_mode_(true);
|
||||
this->set_max_distances_timeout_(this->max_move_distance_, this->max_still_distance_, this->timeout_);
|
||||
// Configure Gates sensitivity
|
||||
this->set_gate_threshold_(0, this->rg0_move_threshold_, this->rg0_still_threshold_);
|
||||
this->set_gate_threshold_(1, this->rg1_move_threshold_, this->rg1_still_threshold_);
|
||||
this->set_gate_threshold_(2, this->rg2_move_threshold_, this->rg2_still_threshold_);
|
||||
this->set_gate_threshold_(3, this->rg3_move_threshold_, this->rg3_still_threshold_);
|
||||
this->set_gate_threshold_(4, this->rg4_move_threshold_, this->rg4_still_threshold_);
|
||||
this->set_gate_threshold_(5, this->rg5_move_threshold_, this->rg5_still_threshold_);
|
||||
this->set_gate_threshold_(6, this->rg6_move_threshold_, this->rg6_still_threshold_);
|
||||
this->set_gate_threshold_(7, this->rg7_move_threshold_, this->rg7_still_threshold_);
|
||||
this->set_gate_threshold_(8, this->rg8_move_threshold_, this->rg8_still_threshold_);
|
||||
this->get_version_();
|
||||
this->set_config_mode_(false);
|
||||
ESP_LOGCONFIG(TAG, "Firmware Version : %u.%u.%u%u%u%u", this->version_[0], this->version_[1], this->version_[2],
|
||||
this->version_[3], this->version_[4], this->version_[5]);
|
||||
ESP_LOGCONFIG(TAG, "LD2410 setup complete.");
|
||||
}
|
||||
|
||||
void LD2410Component::loop() {
|
||||
const int max_line_length = 80;
|
||||
static uint8_t buffer[max_line_length];
|
||||
|
||||
while (available()) {
|
||||
this->readline_(read(), buffer, max_line_length);
|
||||
}
|
||||
}
|
||||
|
||||
void LD2410Component::send_command_(uint8_t command, uint8_t *command_value, int command_value_len) {
|
||||
// lastCommandSuccess->publish_state(false);
|
||||
|
||||
// frame start bytes
|
||||
this->write_array(CMD_FRAME_HEADER, 4);
|
||||
// length bytes
|
||||
int len = 2;
|
||||
if (command_value != nullptr)
|
||||
len += command_value_len;
|
||||
this->write_byte(lowbyte(len));
|
||||
this->write_byte(highbyte(len));
|
||||
|
||||
// command
|
||||
this->write_byte(lowbyte(command));
|
||||
this->write_byte(highbyte(command));
|
||||
|
||||
// command value bytes
|
||||
if (command_value != nullptr) {
|
||||
for (int i = 0; i < command_value_len; i++) {
|
||||
this->write_byte(command_value[i]);
|
||||
}
|
||||
}
|
||||
// frame end bytes
|
||||
this->write_array(CMD_FRAME_END, 4);
|
||||
// FIXME to remove
|
||||
delay(50); // NOLINT
|
||||
}
|
||||
|
||||
void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) {
|
||||
if (len < 12)
|
||||
return; // 4 frame start bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame end bytes
|
||||
if (buffer[0] != 0xF4 || buffer[1] != 0xF3 || buffer[2] != 0xF2 || buffer[3] != 0xF1) // check 4 frame start bytes
|
||||
return;
|
||||
if (buffer[7] != HEAD || buffer[len - 6] != END || buffer[len - 5] != CHECK) // Check constant values
|
||||
return; // data head=0xAA, data end=0x55, crc=0x00
|
||||
|
||||
/*
|
||||
Data Type: 6th
|
||||
0x01: Engineering mode
|
||||
0x02: Normal mode
|
||||
*/
|
||||
// char data_type = buffer[DATA_TYPES];
|
||||
/*
|
||||
Target states: 9th
|
||||
0x00 = No target
|
||||
0x01 = Moving targets
|
||||
0x02 = Still targets
|
||||
0x03 = Moving+Still targets
|
||||
*/
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
char target_state = buffer[TARGET_STATES];
|
||||
if (this->target_binary_sensor_ != nullptr) {
|
||||
this->target_binary_sensor_->publish_state(target_state != 0x00);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*
|
||||
Reduce data update rate to prevent home assistant database size grow fast
|
||||
*/
|
||||
int32_t current_millis = millis();
|
||||
if (current_millis - last_periodic_millis < 1000)
|
||||
return;
|
||||
last_periodic_millis = current_millis;
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->moving_binary_sensor_ != nullptr) {
|
||||
this->moving_binary_sensor_->publish_state(CHECK_BIT(target_state, 0));
|
||||
}
|
||||
if (this->still_binary_sensor_ != nullptr) {
|
||||
this->still_binary_sensor_->publish_state(CHECK_BIT(target_state, 1));
|
||||
}
|
||||
#endif
|
||||
/*
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
#ifdef USE_SENSOR
|
||||
if (this->moving_target_distance_sensor_ != nullptr) {
|
||||
int new_moving_target_distance = this->two_byte_to_int_(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]);
|
||||
if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance)
|
||||
this->moving_target_distance_sensor_->publish_state(new_moving_target_distance);
|
||||
}
|
||||
if (this->moving_target_energy_sensor_ != nullptr) {
|
||||
int new_moving_target_energy = buffer[MOVING_ENERGY];
|
||||
if (this->moving_target_energy_sensor_->get_state() != new_moving_target_energy)
|
||||
this->moving_target_energy_sensor_->publish_state(new_moving_target_energy);
|
||||
}
|
||||
if (this->still_target_distance_sensor_ != nullptr) {
|
||||
int new_still_target_distance = this->two_byte_to_int_(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]);
|
||||
if (this->still_target_distance_sensor_->get_state() != new_still_target_distance)
|
||||
this->still_target_distance_sensor_->publish_state(new_still_target_distance);
|
||||
}
|
||||
if (this->still_target_energy_sensor_ != nullptr) {
|
||||
int new_still_target_energy = buffer[STILL_ENERGY];
|
||||
if (this->still_target_energy_sensor_->get_state() != new_still_target_energy)
|
||||
this->still_target_energy_sensor_->publish_state(new_still_target_energy);
|
||||
}
|
||||
if (this->detection_distance_sensor_ != nullptr) {
|
||||
int new_detect_distance = this->two_byte_to_int_(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]);
|
||||
if (this->detection_distance_sensor_->get_state() != new_detect_distance)
|
||||
this->detection_distance_sensor_->publish_state(new_detect_distance);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LD2410Component::handle_ack_data_(uint8_t *buffer, int len) {
|
||||
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND");
|
||||
if (len < 10) {
|
||||
ESP_LOGE(TAG, "Error with last command : incorrect length");
|
||||
return;
|
||||
}
|
||||
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes
|
||||
ESP_LOGE(TAG, "Error with last command : incorrect Header");
|
||||
return;
|
||||
}
|
||||
if (buffer[COMMAND_STATUS] != 0x01) {
|
||||
ESP_LOGE(TAG, "Error with last command : status != 0x01");
|
||||
return;
|
||||
}
|
||||
if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) {
|
||||
ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (buffer[COMMAND]) {
|
||||
case lowbyte(CMD_ENABLE_CONF):
|
||||
ESP_LOGV(TAG, "Handled Enable conf command");
|
||||
break;
|
||||
case lowbyte(CMD_DISABLE_CONF):
|
||||
ESP_LOGV(TAG, "Handled Disabled conf command");
|
||||
break;
|
||||
case lowbyte(CMD_VERSION):
|
||||
ESP_LOGV(TAG, "FW Version is: %u.%u.%u%u%u%u", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
|
||||
buffer[14]);
|
||||
this->version_[0] = buffer[13];
|
||||
this->version_[1] = buffer[12];
|
||||
this->version_[2] = buffer[17];
|
||||
this->version_[3] = buffer[16];
|
||||
this->version_[4] = buffer[15];
|
||||
this->version_[5] = buffer[14];
|
||||
|
||||
break;
|
||||
case lowbyte(CMD_GATE_SENS):
|
||||
ESP_LOGV(TAG, "Handled sensitivity command");
|
||||
break;
|
||||
case lowbyte(CMD_QUERY): // Query parameters response
|
||||
{
|
||||
if (buffer[10] != 0xAA)
|
||||
return; // value head=0xAA
|
||||
/*
|
||||
Moving distance range: 13th byte
|
||||
Still distance range: 14th byte
|
||||
*/
|
||||
// TODO
|
||||
// maxMovingDistanceRange->publish_state(buffer[12]);
|
||||
// maxStillDistanceRange->publish_state(buffer[13]);
|
||||
/*
|
||||
Moving Sensitivities: 15~23th bytes
|
||||
Still Sensitivities: 24~32th bytes
|
||||
*/
|
||||
for (int i = 0; i < 9; i++) {
|
||||
moving_sensitivities[i] = buffer[14 + i];
|
||||
}
|
||||
for (int i = 0; i < 9; i++) {
|
||||
still_sensitivities[i] = buffer[23 + i];
|
||||
}
|
||||
/*
|
||||
None Duration: 33~34th bytes
|
||||
*/
|
||||
// noneDuration->publish_state(this->two_byte_to_int_(buffer[32], buffer[33]));
|
||||
} break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void LD2410Component::readline_(int readch, uint8_t *buffer, int len) {
|
||||
static int pos = 0;
|
||||
|
||||
if (readch >= 0) {
|
||||
if (pos < len - 1) {
|
||||
buffer[pos++] = readch;
|
||||
buffer[pos] = 0;
|
||||
} else {
|
||||
pos = 0;
|
||||
}
|
||||
if (pos >= 4) {
|
||||
if (buffer[pos - 4] == 0xF8 && buffer[pos - 3] == 0xF7 && buffer[pos - 2] == 0xF6 && buffer[pos - 1] == 0xF5) {
|
||||
ESP_LOGV(TAG, "Will handle Periodic Data");
|
||||
this->handle_periodic_data_(buffer, pos);
|
||||
pos = 0; // Reset position index ready for next time
|
||||
} else if (buffer[pos - 4] == 0x04 && buffer[pos - 3] == 0x03 && buffer[pos - 2] == 0x02 &&
|
||||
buffer[pos - 1] == 0x01) {
|
||||
ESP_LOGV(TAG, "Will handle ACK Data");
|
||||
this->handle_ack_data_(buffer, pos);
|
||||
pos = 0; // Reset position index ready for next time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LD2410Component::set_config_mode_(bool enable) {
|
||||
uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
|
||||
uint8_t cmd_value[2] = {0x01, 0x00};
|
||||
this->send_command_(cmd, enable ? cmd_value : nullptr, 2);
|
||||
}
|
||||
|
||||
void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); }
|
||||
void LD2410Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); }
|
||||
|
||||
void LD2410Component::set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range,
|
||||
uint16_t timeout) {
|
||||
uint8_t value[18] = {0x00,
|
||||
0x00,
|
||||
lowbyte(max_moving_distance_range),
|
||||
highbyte(max_moving_distance_range),
|
||||
0x00,
|
||||
0x00,
|
||||
0x01,
|
||||
0x00,
|
||||
lowbyte(max_still_distance_range),
|
||||
highbyte(max_still_distance_range),
|
||||
0x00,
|
||||
0x00,
|
||||
0x02,
|
||||
0x00,
|
||||
lowbyte(timeout),
|
||||
highbyte(timeout),
|
||||
0x00,
|
||||
0x00};
|
||||
this->send_command_(CMD_MAXDIST_DURATION, value, 18);
|
||||
this->query_parameters_();
|
||||
}
|
||||
void LD2410Component::set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens) {
|
||||
// reference
|
||||
// https://drive.google.com/drive/folders/1p4dhbEJA3YubyIjIIC7wwVsSo8x29Fq-?spm=a2g0o.detail.1000023.17.93465697yFwVxH
|
||||
// Send data: configure the motion sensitivity of distance gate 3 to 40, and the static sensitivity of 40
|
||||
// 00 00 (gate)
|
||||
// 03 00 00 00 (gate number)
|
||||
// 01 00 (motion sensitivity)
|
||||
// 28 00 00 00 (value)
|
||||
// 02 00 (still sensitivtiy)
|
||||
// 28 00 00 00 (value)
|
||||
uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00,
|
||||
0x01, 0x00, lowbyte(motionsens), highbyte(motionsens), 0x00, 0x00,
|
||||
0x02, 0x00, lowbyte(stillsens), highbyte(stillsens), 0x00, 0x00};
|
||||
this->send_command_(CMD_GATE_SENS, value, 18);
|
||||
}
|
||||
|
||||
} // namespace ld2410
|
||||
} // namespace esphome
|
||||
146
esphome/components/ld2410/ld2410.h
Normal file
146
esphome/components/ld2410/ld2410.h
Normal file
@@ -0,0 +1,146 @@
|
||||
#pragma once
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace ld2410 {
|
||||
|
||||
#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1)
|
||||
|
||||
// Commands
|
||||
static const uint8_t CMD_ENABLE_CONF = 0x00FF;
|
||||
static const uint8_t CMD_DISABLE_CONF = 0x00FE;
|
||||
static const uint8_t CMD_MAXDIST_DURATION = 0x0060;
|
||||
static const uint8_t CMD_QUERY = 0x0061;
|
||||
static const uint8_t CMD_GATE_SENS = 0x0064;
|
||||
static const uint8_t CMD_VERSION = 0x00A0;
|
||||
|
||||
// Commands values
|
||||
static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000;
|
||||
static const uint8_t CMD_MAX_STILL_VALUE = 0x0001;
|
||||
static const uint8_t CMD_DURATION_VALUE = 0x0002;
|
||||
// Command Header & Footer
|
||||
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
|
||||
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
|
||||
// Data Header & Footer
|
||||
static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1};
|
||||
static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5};
|
||||
/*
|
||||
Data Type: 6th byte
|
||||
Target states: 9th byte
|
||||
Moving target distance: 10~11th bytes
|
||||
Moving target energy: 12th byte
|
||||
Still target distance: 13~14th bytes
|
||||
Still target energy: 15th byte
|
||||
Detect distance: 16~17th bytes
|
||||
*/
|
||||
enum PeriodicDataStructure : uint8_t {
|
||||
DATA_TYPES = 5,
|
||||
TARGET_STATES = 8,
|
||||
MOVING_TARGET_LOW = 9,
|
||||
MOVING_TARGET_HIGH = 10,
|
||||
MOVING_ENERGY = 11,
|
||||
STILL_TARGET_LOW = 12,
|
||||
STILL_TARGET_HIGH = 13,
|
||||
STILL_ENERGY = 14,
|
||||
DETECT_DISTANCE_LOW = 15,
|
||||
DETECT_DISTANCE_HIGH = 16,
|
||||
};
|
||||
enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 };
|
||||
|
||||
enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 };
|
||||
|
||||
// char cmd[2] = {enable ? 0xFF : 0xFE, 0x00};
|
||||
class LD2410Component : public Component, public uart::UARTDevice {
|
||||
#ifdef USE_SENSOR
|
||||
SUB_SENSOR(moving_target_distance)
|
||||
SUB_SENSOR(still_target_distance)
|
||||
SUB_SENSOR(moving_target_energy)
|
||||
SUB_SENSOR(still_target_energy)
|
||||
SUB_SENSOR(detection_distance)
|
||||
#endif
|
||||
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void set_target_sensor(binary_sensor::BinarySensor *sens) { this->target_binary_sensor_ = sens; };
|
||||
void set_moving_target_sensor(binary_sensor::BinarySensor *sens) { this->moving_binary_sensor_ = sens; };
|
||||
void set_still_target_sensor(binary_sensor::BinarySensor *sens) { this->still_binary_sensor_ = sens; };
|
||||
#endif
|
||||
|
||||
void set_timeout(uint16_t value) { this->timeout_ = value; };
|
||||
void set_max_move_distance(uint8_t value) { this->max_move_distance_ = value; };
|
||||
void set_max_still_distance(uint8_t value) { this->max_still_distance_ = value; };
|
||||
void set_range_config(int rg0_move, int rg0_still, int rg1_move, int rg1_still, int rg2_move, int rg2_still,
|
||||
int rg3_move, int rg3_still, int rg4_move, int rg4_still, int rg5_move, int rg5_still,
|
||||
int rg6_move, int rg6_still, int rg7_move, int rg7_still, int rg8_move, int rg8_still) {
|
||||
this->rg0_move_threshold_ = rg0_move;
|
||||
this->rg0_still_threshold_ = rg0_still;
|
||||
this->rg1_move_threshold_ = rg1_move;
|
||||
this->rg1_still_threshold_ = rg1_still;
|
||||
this->rg2_move_threshold_ = rg2_move;
|
||||
this->rg2_still_threshold_ = rg2_still;
|
||||
this->rg3_move_threshold_ = rg3_move;
|
||||
this->rg3_still_threshold_ = rg3_still;
|
||||
this->rg4_move_threshold_ = rg4_move;
|
||||
this->rg4_still_threshold_ = rg4_still;
|
||||
this->rg5_move_threshold_ = rg5_move;
|
||||
this->rg5_still_threshold_ = rg5_still;
|
||||
this->rg6_move_threshold_ = rg6_move;
|
||||
this->rg6_still_threshold_ = rg6_still;
|
||||
this->rg7_move_threshold_ = rg7_move;
|
||||
this->rg7_still_threshold_ = rg7_still;
|
||||
this->rg8_move_threshold_ = rg8_move;
|
||||
this->rg8_still_threshold_ = rg8_still;
|
||||
};
|
||||
int moving_sensitivities[9] = {0};
|
||||
int still_sensitivities[9] = {0};
|
||||
|
||||
int32_t last_periodic_millis = millis();
|
||||
|
||||
protected:
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
binary_sensor::BinarySensor *target_binary_sensor_{nullptr};
|
||||
binary_sensor::BinarySensor *moving_binary_sensor_{nullptr};
|
||||
binary_sensor::BinarySensor *still_binary_sensor_{nullptr};
|
||||
#endif
|
||||
|
||||
std::vector<uint8_t> rx_buffer_;
|
||||
int two_byte_to_int_(char firstbyte, char secondbyte) { return (int16_t)(secondbyte << 8) + firstbyte; }
|
||||
void send_command_(uint8_t command_str, uint8_t *command_value, int command_value_len);
|
||||
|
||||
void set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range,
|
||||
uint16_t timeout);
|
||||
void set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens);
|
||||
void set_config_mode_(bool enable);
|
||||
void handle_periodic_data_(uint8_t *buffer, int len);
|
||||
void handle_ack_data_(uint8_t *buffer, int len);
|
||||
void readline_(int readch, uint8_t *buffer, int len);
|
||||
void query_parameters_();
|
||||
void get_version_();
|
||||
|
||||
uint16_t timeout_;
|
||||
uint8_t max_move_distance_;
|
||||
uint8_t max_still_distance_;
|
||||
|
||||
uint8_t version_[6];
|
||||
uint8_t rg0_move_threshold_, rg0_still_threshold_, rg1_move_threshold_, rg1_still_threshold_, rg2_move_threshold_,
|
||||
rg2_still_threshold_, rg3_move_threshold_, rg3_still_threshold_, rg4_move_threshold_, rg4_still_threshold_,
|
||||
rg5_move_threshold_, rg5_still_threshold_, rg6_move_threshold_, rg6_still_threshold_, rg7_move_threshold_,
|
||||
rg7_still_threshold_, rg8_move_threshold_, rg8_still_threshold_;
|
||||
};
|
||||
|
||||
} // namespace ld2410
|
||||
} // namespace esphome
|
||||
55
esphome/components/ld2410/sensor.py
Normal file
55
esphome/components/ld2410/sensor.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
UNIT_CENTIMETER,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
from . import CONF_LD2410_ID, LD2410Component
|
||||
|
||||
DEPENDENCIES = ["ld2410"]
|
||||
CONF_MOVING_DISTANCE = "moving_distance"
|
||||
CONF_STILL_DISTANCE = "still_distance"
|
||||
CONF_MOVING_ENERGY = "moving_energy"
|
||||
CONF_STILL_ENERGY = "still_energy"
|
||||
CONF_DETECTION_DISTANCE = "detection_distance"
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
|
||||
),
|
||||
cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=UNIT_PERCENT
|
||||
),
|
||||
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=UNIT_PERCENT
|
||||
),
|
||||
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
|
||||
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
ld2410_component = await cg.get_variable(config[CONF_LD2410_ID])
|
||||
if CONF_MOVING_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE])
|
||||
cg.add(ld2410_component.set_moving_target_distance_sensor(sens))
|
||||
if CONF_STILL_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_STILL_DISTANCE])
|
||||
cg.add(ld2410_component.set_still_target_distance_sensor(sens))
|
||||
if CONF_MOVING_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_MOVING_ENERGY])
|
||||
cg.add(ld2410_component.set_moving_target_energy_sensor(sens))
|
||||
if CONF_STILL_ENERGY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_STILL_ENERGY])
|
||||
cg.add(ld2410_component.set_still_target_energy_sensor(sens))
|
||||
if CONF_DETECTION_DISTANCE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_DETECTION_DISTANCE])
|
||||
cg.add(ld2410_component.set_detection_distance_sensor(sens))
|
||||
@@ -8,6 +8,20 @@
|
||||
#endif
|
||||
#include <driver/ledc.h>
|
||||
|
||||
#define CLOCK_FREQUENCY 80e6f
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK
|
||||
#undef CLOCK_FREQUENCY
|
||||
// starting with ESP32 Arduino 2.0.2, the 40MHz crystal is used as clock by default if supported
|
||||
#define CLOCK_FREQUENCY 40e6f
|
||||
#endif
|
||||
#else
|
||||
#define DEFAULT_CLK LEDC_USE_APB_CLK
|
||||
#endif
|
||||
|
||||
static const uint8_t SETUP_ATTEMPT_COUNT_MAX = 5;
|
||||
|
||||
namespace esphome {
|
||||
namespace ledc {
|
||||
|
||||
@@ -26,11 +40,11 @@ inline ledc_mode_t get_speed_mode(uint8_t) { return LEDC_LOW_SPEED_MODE; }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return 80e6f / float(1 << bit_depth); }
|
||||
float ledc_max_frequency_for_bit_depth(uint8_t bit_depth) { return CLOCK_FREQUENCY / float(1 << bit_depth); }
|
||||
|
||||
float ledc_min_frequency_for_bit_depth(uint8_t bit_depth, bool low_frequency) {
|
||||
const float max_div_num = ((1 << MAX_RES_BITS) - 1) / (low_frequency ? 32.0f : 256.0f);
|
||||
return 80e6f / (max_div_num * float(1 << bit_depth));
|
||||
return CLOCK_FREQUENCY / (max_div_num * float(1 << bit_depth));
|
||||
}
|
||||
|
||||
optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
||||
@@ -46,6 +60,38 @@ optional<uint8_t> ledc_bit_depth_for_frequency(float frequency) {
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
esp_err_t configure_timer_frequency(ledc_mode_t speed_mode, ledc_timer_t timer_num, ledc_channel_t chan_num,
|
||||
uint8_t channel, uint8_t &bit_depth, float frequency) {
|
||||
bit_depth = *ledc_bit_depth_for_frequency(frequency);
|
||||
if (bit_depth < 1) {
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with any bit depth", frequency);
|
||||
}
|
||||
|
||||
ledc_timer_config_t timer_conf{};
|
||||
timer_conf.speed_mode = speed_mode;
|
||||
timer_conf.duty_resolution = static_cast<ledc_timer_bit_t>(bit_depth);
|
||||
timer_conf.timer_num = timer_num;
|
||||
timer_conf.freq_hz = (uint32_t) frequency;
|
||||
timer_conf.clk_cfg = DEFAULT_CLK;
|
||||
|
||||
// Configure the time with fallback in case of error
|
||||
int attempt_count_max = SETUP_ATTEMPT_COUNT_MAX;
|
||||
esp_err_t init_result = ESP_FAIL;
|
||||
while (attempt_count_max > 0 && init_result != ESP_OK) {
|
||||
init_result = ledc_timer_config(&timer_conf);
|
||||
if (init_result != ESP_OK) {
|
||||
ESP_LOGW(TAG, "Unable to initialize timer with frequency %.1f and bit depth of %u", frequency, bit_depth);
|
||||
// try again with a lower bit depth
|
||||
timer_conf.duty_resolution = static_cast<ledc_timer_bit_t>(--bit_depth);
|
||||
}
|
||||
attempt_count_max--;
|
||||
}
|
||||
|
||||
return init_result;
|
||||
}
|
||||
#endif
|
||||
|
||||
void LEDCOutput::write_state(float state) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
|
||||
@@ -61,6 +107,7 @@ void LEDCOutput::write_state(float state) {
|
||||
auto duty = static_cast<uint32_t>(duty_rounded);
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
ESP_LOGV(TAG, "Setting duty: %u on channel %u", duty, this->channel_);
|
||||
ledcWrite(this->channel_, duty);
|
||||
#endif
|
||||
#ifdef USE_ESP_IDF
|
||||
@@ -72,6 +119,7 @@ void LEDCOutput::write_state(float state) {
|
||||
}
|
||||
|
||||
void LEDCOutput::setup() {
|
||||
ESP_LOGV(TAG, "Entering setup...");
|
||||
#ifdef USE_ARDUINO
|
||||
this->update_frequency(this->frequency_);
|
||||
this->turn_off();
|
||||
@@ -83,19 +131,16 @@ void LEDCOutput::setup() {
|
||||
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
|
||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||
|
||||
bit_depth_ = *ledc_bit_depth_for_frequency(frequency_);
|
||||
if (bit_depth_ < 1) {
|
||||
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency_);
|
||||
this->status_set_warning();
|
||||
esp_err_t timer_init_result =
|
||||
configure_timer_frequency(speed_mode, timer_num, chan_num, this->channel_, this->bit_depth_, this->frequency_);
|
||||
|
||||
if (timer_init_result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with computed bit depth %u", this->frequency_, this->bit_depth_);
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
|
||||
ledc_timer_config_t timer_conf{};
|
||||
timer_conf.speed_mode = speed_mode;
|
||||
timer_conf.duty_resolution = static_cast<ledc_timer_bit_t>(bit_depth_);
|
||||
timer_conf.timer_num = timer_num;
|
||||
timer_conf.freq_hz = (uint32_t) frequency_;
|
||||
timer_conf.clk_cfg = LEDC_AUTO_CLK;
|
||||
ledc_timer_config(&timer_conf);
|
||||
ESP_LOGV(TAG, "Configured frequency %f with a bit depth of %u bits", this->frequency_, this->bit_depth_);
|
||||
|
||||
ledc_channel_config_t chan_conf{};
|
||||
chan_conf.gpio_num = pin_->get_pin();
|
||||
@@ -107,6 +152,7 @@ void LEDCOutput::setup() {
|
||||
chan_conf.hpoint = 0;
|
||||
ledc_channel_config(&chan_conf);
|
||||
initialized_ = true;
|
||||
this->status_clear_error();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -114,36 +160,80 @@ void LEDCOutput::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "LEDC Output:");
|
||||
LOG_PIN(" Pin ", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " LEDC Channel: %u", this->channel_);
|
||||
ESP_LOGCONFIG(TAG, " Frequency: %.1f Hz", this->frequency_);
|
||||
ESP_LOGCONFIG(TAG, " PWM Frequency: %.1f Hz", this->frequency_);
|
||||
ESP_LOGCONFIG(TAG, " Bit depth: %u", this->bit_depth_);
|
||||
ESP_LOGV(TAG, " Max frequency for bit depth: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_));
|
||||
ESP_LOGV(TAG, " Min frequency for bit depth: %f",
|
||||
ledc_min_frequency_for_bit_depth(this->bit_depth_, (this->frequency_ < 100)));
|
||||
ESP_LOGV(TAG, " Max frequency for bit depth-1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ - 1));
|
||||
ESP_LOGV(TAG, " Min frequency for bit depth-1: %f",
|
||||
ledc_min_frequency_for_bit_depth(this->bit_depth_ - 1, (this->frequency_ < 100)));
|
||||
ESP_LOGV(TAG, " Max frequency for bit depth+1: %f", ledc_max_frequency_for_bit_depth(this->bit_depth_ + 1));
|
||||
ESP_LOGV(TAG, " Min frequency for bit depth+1: %f",
|
||||
ledc_min_frequency_for_bit_depth(this->bit_depth_ + 1, (this->frequency_ < 100)));
|
||||
ESP_LOGV(TAG, " Max res bits: %d", MAX_RES_BITS);
|
||||
ESP_LOGV(TAG, " Clock frequency: %f", CLOCK_FREQUENCY);
|
||||
}
|
||||
|
||||
void LEDCOutput::update_frequency(float frequency) {
|
||||
auto bit_depth_opt = ledc_bit_depth_for_frequency(frequency);
|
||||
if (!bit_depth_opt.has_value()) {
|
||||
ESP_LOGW(TAG, "Frequency %f can't be achieved with any bit depth", frequency);
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with any bit depth", this->frequency_);
|
||||
this->status_set_error();
|
||||
}
|
||||
this->bit_depth_ = bit_depth_opt.value_or(8);
|
||||
this->frequency_ = frequency;
|
||||
#ifdef USE_ARDUINO
|
||||
ledcSetup(this->channel_, frequency, this->bit_depth_);
|
||||
initialized_ = true;
|
||||
ESP_LOGV(TAG, "Using Arduino API - Trying to define channel, frequency and bit depth...");
|
||||
u_int32_t configured_frequency = 0;
|
||||
|
||||
// Configure LEDC channel, frequency and bit depth with fallback
|
||||
int attempt_count_max = SETUP_ATTEMPT_COUNT_MAX;
|
||||
while (attempt_count_max > 0 && configured_frequency == 0) {
|
||||
ESP_LOGV(TAG, "Trying initialize channel %u with frequency %.1f and bit depth of %u...", this->channel_,
|
||||
this->frequency_, this->bit_depth_);
|
||||
configured_frequency = ledcSetup(this->channel_, frequency, this->bit_depth_);
|
||||
if (configured_frequency != 0) {
|
||||
initialized_ = true;
|
||||
this->status_clear_error();
|
||||
ESP_LOGV(TAG, "Configured frequency: %u with bit depth: %u", configured_frequency, this->bit_depth_);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unable to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_,
|
||||
this->frequency_, this->bit_depth_);
|
||||
// try again with a lower bit depth
|
||||
this->bit_depth_--;
|
||||
}
|
||||
attempt_count_max--;
|
||||
}
|
||||
|
||||
if (configured_frequency == 0) {
|
||||
ESP_LOGE(TAG, "Permanently failed to initialize channel %u with frequency %.1f and bit depth of %u", this->channel_,
|
||||
this->frequency_, this->bit_depth_);
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
#ifdef USE_ESP_IDF
|
||||
if (!initialized_) {
|
||||
ESP_LOGW(TAG, "LEDC output hasn't been initialized yet!");
|
||||
return;
|
||||
}
|
||||
|
||||
auto speed_mode = get_speed_mode(channel_);
|
||||
auto timer_num = static_cast<ledc_timer_t>((channel_ % 8) / 2);
|
||||
auto chan_num = static_cast<ledc_channel_t>(channel_ % 8);
|
||||
|
||||
ledc_timer_config_t timer_conf{};
|
||||
timer_conf.speed_mode = speed_mode;
|
||||
timer_conf.duty_resolution = static_cast<ledc_timer_bit_t>(bit_depth_);
|
||||
timer_conf.timer_num = timer_num;
|
||||
timer_conf.freq_hz = (uint32_t) frequency_;
|
||||
timer_conf.clk_cfg = LEDC_AUTO_CLK;
|
||||
ledc_timer_config(&timer_conf);
|
||||
esp_err_t timer_init_result =
|
||||
configure_timer_frequency(speed_mode, timer_num, chan_num, this->channel_, this->bit_depth_, this->frequency_);
|
||||
|
||||
if (timer_init_result != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Frequency %f can't be achieved with computed bit depth %u", this->frequency_, this->bit_depth_);
|
||||
this->status_set_error();
|
||||
return;
|
||||
}
|
||||
|
||||
this->status_clear_error();
|
||||
#endif
|
||||
// re-apply duty
|
||||
this->write_state(this->duty_);
|
||||
|
||||
@@ -35,6 +35,8 @@ class MDNSComponent : public Component {
|
||||
|
||||
void add_extra_service(MDNSService service) { services_extra_.push_back(std::move(service)); }
|
||||
|
||||
void on_shutdown() override;
|
||||
|
||||
protected:
|
||||
std::vector<MDNSService> services_extra_{};
|
||||
std::vector<MDNSService> services_{};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#ifdef USE_ESP_IDF
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "mdns_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <mdns.h>
|
||||
#include <cstring>
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
@@ -47,7 +48,12 @@ void MDNSComponent::setup() {
|
||||
}
|
||||
}
|
||||
|
||||
void MDNSComponent::on_shutdown() {
|
||||
mdns_free();
|
||||
delay(40); // Allow the mdns packets announcing service removal to be sent
|
||||
}
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
@@ -1,26 +0,0 @@
|
||||
#ifdef USE_ESP32_FRAMEWORK_ARDUINO
|
||||
|
||||
#include "mdns_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <ESPmDNS.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
|
||||
void MDNSComponent::setup() {
|
||||
this->compile_records_();
|
||||
|
||||
MDNS.begin(this->hostname_.c_str());
|
||||
|
||||
for (const auto &service : this->services_) {
|
||||
MDNS.addService(service.service_type.c_str(), service.proto.c_str(), service.port);
|
||||
for (const auto &record : service.txt_records) {
|
||||
MDNS.addServiceTxt(service.service_type.c_str(), service.proto.c_str(), record.key.c_str(), record.value.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32_FRAMEWORK_ARDUINO
|
||||
@@ -1,10 +1,11 @@
|
||||
#if defined(USE_ESP8266) && defined(USE_ARDUINO)
|
||||
|
||||
#include "mdns_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <ESP8266mDNS.h>
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/components/network/util.h"
|
||||
#include <ESP8266mDNS.h>
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "mdns_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mdns {
|
||||
@@ -37,6 +38,11 @@ void MDNSComponent::setup() {
|
||||
|
||||
void MDNSComponent::loop() { MDNS.update(); }
|
||||
|
||||
void MDNSComponent::on_shutdown() {
|
||||
MDNS.close();
|
||||
delay(10);
|
||||
}
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -38,6 +38,11 @@ void MDNSComponent::setup() {
|
||||
|
||||
void MDNSComponent::loop() { MDNS.update(); }
|
||||
|
||||
void MDNSComponent::on_shutdown() {
|
||||
MDNS.close();
|
||||
delay(40);
|
||||
}
|
||||
|
||||
} // namespace mdns
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ void MICS4514Component::setup() {
|
||||
uint8_t power_mode;
|
||||
this->read_register(POWER_MODE_REGISTER, &power_mode, 1);
|
||||
if (power_mode == 0x00) {
|
||||
ESP_LOGCONFIG(TAG, "Waking up MICS 4514");
|
||||
ESP_LOGCONFIG(TAG, "Waking up MICS 4514, sensors will have data after 3 minutes...");
|
||||
power_mode = 0x01;
|
||||
this->write_register(POWER_MODE_REGISTER, &power_mode, 1);
|
||||
delay(100); // NOLINT
|
||||
|
||||
@@ -84,18 +84,18 @@ ClimateTraits AirConditioner::traits() {
|
||||
traits.set_supported_custom_presets(this->supported_custom_presets_);
|
||||
traits.set_supported_custom_fan_modes(this->supported_custom_fan_modes_);
|
||||
/* + MINIMAL SET OF CAPABILITIES */
|
||||
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF);
|
||||
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_FAN_ONLY);
|
||||
traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_AUTO);
|
||||
traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_LOW);
|
||||
traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_MEDIUM);
|
||||
traits.add_supported_fan_mode(ClimateFanMode::CLIMATE_FAN_HIGH);
|
||||
traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF);
|
||||
traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_VERTICAL);
|
||||
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE);
|
||||
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_SLEEP);
|
||||
if (this->base_.getAutoconfStatus() == dudanov::midea::AUTOCONF_OK)
|
||||
Converters::to_climate_traits(traits, this->base_.getCapabilities());
|
||||
if (!traits.get_supported_modes().empty())
|
||||
traits.add_supported_mode(ClimateMode::CLIMATE_MODE_OFF);
|
||||
if (!traits.get_supported_swing_modes().empty())
|
||||
traits.add_supported_swing_mode(ClimateSwingMode::CLIMATE_SWING_OFF);
|
||||
if (!traits.get_supported_presets().empty())
|
||||
traits.add_supported_preset(ClimatePreset::CLIMATE_PRESET_NONE);
|
||||
return traits;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ bool MopekaProCheck::parse_device(const esp32_ble_tracker::ESPBTDevice &device)
|
||||
// Now parse the data - See Datasheet for definition
|
||||
|
||||
if (static_cast<SensorType>(manu_data.data[0]) != STANDARD_BOTTOM_UP &&
|
||||
static_cast<SensorType>(manu_data.data[0]) != LIPPERT_BOTTOM_UP &&
|
||||
static_cast<SensorType>(manu_data.data[0]) != PLUS_BOTTOM_UP) {
|
||||
ESP_LOGE(TAG, "Unsupported Sensor Type (0x%X)", manu_data.data[0]);
|
||||
return false;
|
||||
|
||||
@@ -15,6 +15,7 @@ enum SensorType {
|
||||
STANDARD_BOTTOM_UP = 0x03,
|
||||
TOP_DOWN_AIR_ABOVE = 0x04,
|
||||
BOTTOM_UP_WATER = 0x05,
|
||||
LIPPERT_BOTTOM_UP = 0x06,
|
||||
PLUS_BOTTOM_UP = 0x08
|
||||
// all other values are reserved
|
||||
};
|
||||
|
||||
@@ -66,18 +66,42 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
|
||||
// temperature units are always coerced to Celsius internally
|
||||
root[MQTT_TEMPERATURE_UNIT] = "C";
|
||||
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
|
||||
// away_mode_command_topic
|
||||
root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic();
|
||||
// away_mode_state_topic
|
||||
root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic();
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
// preset_mode_command_topic
|
||||
root[MQTT_PRESET_MODE_COMMAND_TOPIC] = this->get_preset_command_topic();
|
||||
// preset_mode_state_topic
|
||||
root[MQTT_PRESET_MODE_STATE_TOPIC] = this->get_preset_state_topic();
|
||||
// presets
|
||||
JsonArray presets = root.createNestedArray("presets");
|
||||
if (traits.supports_preset(CLIMATE_PRESET_HOME))
|
||||
presets.add("home");
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
|
||||
// away_mode_command_topic
|
||||
root[MQTT_AWAY_MODE_COMMAND_TOPIC] = this->get_away_command_topic();
|
||||
// away_mode_state_topic
|
||||
root[MQTT_AWAY_MODE_STATE_TOPIC] = this->get_away_state_topic();
|
||||
presets.add("away");
|
||||
}
|
||||
if (traits.supports_preset(CLIMATE_PRESET_BOOST))
|
||||
presets.add("boost");
|
||||
if (traits.supports_preset(CLIMATE_PRESET_COMFORT))
|
||||
presets.add("comfort");
|
||||
if (traits.supports_preset(CLIMATE_PRESET_ECO))
|
||||
presets.add("eco");
|
||||
if (traits.supports_preset(CLIMATE_PRESET_SLEEP))
|
||||
presets.add("sleep");
|
||||
if (traits.supports_preset(CLIMATE_PRESET_ACTIVITY))
|
||||
presets.add("activity");
|
||||
for (const auto &preset : traits.get_supported_custom_presets())
|
||||
presets.add(preset);
|
||||
}
|
||||
|
||||
if (traits.get_supports_action()) {
|
||||
// action_topic
|
||||
root[MQTT_ACTION_TOPIC] = this->get_action_state_topic();
|
||||
}
|
||||
|
||||
if (traits.get_supports_fan_modes() || !traits.get_supported_custom_fan_modes().empty()) {
|
||||
if (traits.get_supports_fan_modes()) {
|
||||
// fan_mode_command_topic
|
||||
root[MQTT_FAN_MODE_COMMAND_TOPIC] = this->get_fan_mode_command_topic();
|
||||
// fan_mode_state_topic
|
||||
@@ -102,6 +126,8 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
|
||||
fan_modes.add("focus");
|
||||
if (traits.supports_fan_mode(CLIMATE_FAN_DIFFUSE))
|
||||
fan_modes.add("diffuse");
|
||||
if (traits.supports_fan_mode(CLIMATE_FAN_QUIET))
|
||||
fan_modes.add("quiet");
|
||||
for (const auto &fan_mode : traits.get_supported_custom_fan_modes())
|
||||
fan_modes.add(fan_mode);
|
||||
}
|
||||
@@ -194,6 +220,14 @@ void MQTTClimateComponent::setup() {
|
||||
});
|
||||
}
|
||||
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
this->subscribe(this->get_preset_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
auto call = this->device_->make_call();
|
||||
call.set_preset(payload);
|
||||
call.perform();
|
||||
});
|
||||
}
|
||||
|
||||
if (traits.get_supports_fan_modes()) {
|
||||
this->subscribe(this->get_fan_mode_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
auto call = this->device_->make_call();
|
||||
@@ -271,6 +305,42 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
if (!this->publish(this->get_away_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
if (traits.get_supports_presets() || !traits.get_supported_custom_presets().empty()) {
|
||||
std::string payload;
|
||||
if (this->device_->preset.has_value()) {
|
||||
switch (this->device_->preset.value()) {
|
||||
case CLIMATE_PRESET_NONE:
|
||||
payload = "none";
|
||||
break;
|
||||
case CLIMATE_PRESET_HOME:
|
||||
payload = "home";
|
||||
break;
|
||||
case CLIMATE_PRESET_AWAY:
|
||||
payload = "away";
|
||||
break;
|
||||
case CLIMATE_PRESET_BOOST:
|
||||
payload = "boost";
|
||||
break;
|
||||
case CLIMATE_PRESET_COMFORT:
|
||||
payload = "comfort";
|
||||
break;
|
||||
case CLIMATE_PRESET_ECO:
|
||||
payload = "eco";
|
||||
break;
|
||||
case CLIMATE_PRESET_SLEEP:
|
||||
payload = "sleep";
|
||||
break;
|
||||
case CLIMATE_PRESET_ACTIVITY:
|
||||
payload = "activity";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this->device_->custom_preset.has_value())
|
||||
payload = this->device_->custom_preset.value();
|
||||
if (!this->publish(this->get_preset_state_topic(), payload))
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (traits.get_supports_action()) {
|
||||
const char *payload = "unknown";
|
||||
switch (this->device_->action) {
|
||||
@@ -328,6 +398,9 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
case CLIMATE_FAN_DIFFUSE:
|
||||
payload = "diffuse";
|
||||
break;
|
||||
case CLIMATE_FAN_QUIET:
|
||||
payload = "quiet";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this->device_->custom_fan_mode.has_value())
|
||||
|
||||
@@ -35,6 +35,8 @@ class MQTTClimateComponent : public mqtt::MQTTComponent {
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(fan_mode, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(swing_mode, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(swing_mode, command)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(preset, state)
|
||||
MQTT_COMPONENT_CUSTOM_TOPIC(preset, command)
|
||||
|
||||
protected:
|
||||
const EntityBase *get_entity() const override;
|
||||
|
||||
@@ -165,7 +165,7 @@ def do_packages_pass(config: dict):
|
||||
f"Packages must be a key to value mapping, got {type(packages)} instead"
|
||||
)
|
||||
|
||||
for package_name, package_config in packages.items():
|
||||
for package_name, package_config in reversed(packages.items()):
|
||||
with cv.prepend_path(package_name):
|
||||
recursive_package = package_config
|
||||
if CONF_URL in package_config:
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#include "pid_autotuner.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifndef M_PI
|
||||
#define M_PI 3.1415926535897932384626433
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace pid {
|
||||
|
||||
@@ -73,7 +77,7 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce
|
||||
}
|
||||
|
||||
if (!std::isnan(this->setpoint_) && this->setpoint_ != setpoint) {
|
||||
ESP_LOGW(TAG, "Setpoint changed during autotune! The result will not be accurate!");
|
||||
ESP_LOGW(TAG, "%s: Setpoint changed during autotune! The result will not be accurate!", this->id_.c_str());
|
||||
}
|
||||
this->setpoint_ = setpoint;
|
||||
|
||||
@@ -87,7 +91,7 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce
|
||||
|
||||
if (!this->frequency_detector_.has_enough_data() || !this->amplitude_detector_.has_enough_data()) {
|
||||
// not enough data for calculation yet
|
||||
ESP_LOGV(TAG, " Not enough data yet for aututuner");
|
||||
ESP_LOGV(TAG, "%s: Not enough data yet for autotuner", this->id_.c_str());
|
||||
return res;
|
||||
}
|
||||
|
||||
@@ -97,12 +101,13 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce
|
||||
// The frequency/amplitude is not fully accurate yet, try to wait
|
||||
// until the fault clears, or terminate after a while anyway
|
||||
if (zc_symmetrical) {
|
||||
ESP_LOGVV(TAG, " ZC is not symmetrical");
|
||||
ESP_LOGVV(TAG, "%s: ZC is not symmetrical", this->id_.c_str());
|
||||
}
|
||||
if (amplitude_convergent) {
|
||||
ESP_LOGVV(TAG, " Amplitude is not convergent");
|
||||
ESP_LOGVV(TAG, "%s: Amplitude is not convergent", this->id_.c_str());
|
||||
}
|
||||
uint32_t phase = this->relay_function_.phase_count;
|
||||
ESP_LOGVV(TAG, "%s: >", this->id_.c_str());
|
||||
ESP_LOGVV(TAG, " Phase %u, enough=%u", phase, enough_data_phase_);
|
||||
|
||||
if (this->enough_data_phase_ == 0) {
|
||||
@@ -116,7 +121,7 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "PID Autotune finished!");
|
||||
ESP_LOGI(TAG, "%s: PID Autotune finished!", this->id_.c_str());
|
||||
|
||||
float osc_ampl = this->amplitude_detector_.get_mean_oscillation_amplitude();
|
||||
float d = (this->relay_function_.output_positive - this->relay_function_.output_negative) / 2.0f;
|
||||
@@ -131,12 +136,12 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce
|
||||
return res;
|
||||
}
|
||||
void PIDAutotuner::dump_config() {
|
||||
ESP_LOGI(TAG, "PID Autotune:");
|
||||
if (this->state_ == AUTOTUNE_SUCCEEDED) {
|
||||
ESP_LOGI(TAG, "%s: PID Autotune:", this->id_.c_str());
|
||||
ESP_LOGI(TAG, " State: Succeeded!");
|
||||
bool has_issue = false;
|
||||
if (!this->amplitude_detector_.is_amplitude_convergent()) {
|
||||
ESP_LOGW(TAG, " Could not reliable determine oscillation amplitude, PID parameters may be inaccurate!");
|
||||
ESP_LOGW(TAG, " Could not reliably determine oscillation amplitude, PID parameters may be inaccurate!");
|
||||
ESP_LOGW(TAG, " Please make sure you eliminate all outside influences on the measured temperature.");
|
||||
has_issue = true;
|
||||
}
|
||||
@@ -173,10 +178,12 @@ void PIDAutotuner::dump_config() {
|
||||
print_rule_("Pessen Integral PID", 0.7f, 1.75f, 0.105f);
|
||||
print_rule_("Some Overshoot PID", 0.333f, 0.667f, 0.111f);
|
||||
print_rule_("No Overshoot PID", 0.2f, 0.4f, 0.0625f);
|
||||
ESP_LOGI(TAG, "%s: Autotune completed", this->id_.c_str());
|
||||
}
|
||||
|
||||
if (this->state_ == AUTOTUNE_RUNNING) {
|
||||
ESP_LOGI(TAG, " Autotune is still running!");
|
||||
ESP_LOGD(TAG, "%s: PID Autotune:", this->id_.c_str());
|
||||
ESP_LOGD(TAG, " Autotune is still running!");
|
||||
ESP_LOGD(TAG, " Status: Trying to reach %.2f °C", setpoint_ - relay_function_.current_target_error());
|
||||
ESP_LOGD(TAG, " Stats so far:");
|
||||
ESP_LOGD(TAG, " Phases: %u", relay_function_.phase_count);
|
||||
@@ -221,7 +228,6 @@ float PIDAutotuner::RelayFunction::update(float error) {
|
||||
float output = state == RELAY_FUNCTION_POSITIVE ? output_positive : output_negative;
|
||||
if (change) {
|
||||
this->phase_count++;
|
||||
ESP_LOGV(TAG, "Autotune: Turning output to %.1f%%", output * 100);
|
||||
}
|
||||
|
||||
return output;
|
||||
@@ -245,10 +251,8 @@ void PIDAutotuner::OscillationFrequencyDetector::update(uint32_t now, float erro
|
||||
|
||||
if (had_crossing) {
|
||||
// Had crossing above hysteresis threshold, record
|
||||
ESP_LOGV(TAG, "Autotune: Detected Zero-Cross at %u", now);
|
||||
if (this->last_zerocross != 0) {
|
||||
uint32_t dt = now - this->last_zerocross;
|
||||
ESP_LOGV(TAG, " dt: %u", dt);
|
||||
this->zerocrossing_intervals.push_back(dt);
|
||||
}
|
||||
this->last_zerocross = now;
|
||||
@@ -297,13 +301,11 @@ void PIDAutotuner::OscillationAmplitudeDetector::update(float error,
|
||||
// The positive error peak must have been in previous segment (180° shifted)
|
||||
// record phase_max
|
||||
this->phase_maxs.push_back(phase_max);
|
||||
ESP_LOGV(TAG, "Autotune: Phase Max: %f", phase_max);
|
||||
} else if (last_relay_state == RelayFunction::RELAY_FUNCTION_NEGATIVE) {
|
||||
// Transitioned from negative error to positive error.
|
||||
// The negative error peak must have been in previous segment (180° shifted)
|
||||
// record phase_min
|
||||
this->phase_mins.push_back(phase_min);
|
||||
ESP_LOGV(TAG, "Autotune: Phase Min: %f", phase_min);
|
||||
}
|
||||
// reset phase values for next phase
|
||||
this->phase_min = error;
|
||||
|
||||
@@ -31,6 +31,8 @@ class PIDAutotuner {
|
||||
|
||||
void dump_config();
|
||||
|
||||
void set_autotuner_id(std::string id) { this->id_ = std::move(id); }
|
||||
|
||||
void set_noiseband(float noiseband) {
|
||||
relay_function_.noiseband = noiseband;
|
||||
// ZC detector uses 1/4 the noiseband of relay function (noise suppression)
|
||||
@@ -106,6 +108,7 @@ class PIDAutotuner {
|
||||
} state_ = AUTOTUNE_RUNNING;
|
||||
float ku_;
|
||||
float pu_;
|
||||
std::string id_;
|
||||
};
|
||||
|
||||
} // namespace pid
|
||||
|
||||
@@ -130,9 +130,6 @@ void PIDClimate::update_pid_() {
|
||||
// keep autotuner instance so that subsequent dump_configs will print the long result message.
|
||||
} else {
|
||||
value = res.output;
|
||||
if (mode != climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
ESP_LOGW(TAG, "For PID autotuner you need to set AUTO (also called heat/cool) mode!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -151,10 +148,24 @@ void PIDClimate::start_autotune(std::unique_ptr<PIDAutotuner> &&autotune) {
|
||||
float min_value = this->supports_cool_() ? -1.0f : 0.0f;
|
||||
float max_value = this->supports_heat_() ? 1.0f : 0.0f;
|
||||
this->autotuner_->config(min_value, max_value);
|
||||
this->autotuner_->set_autotuner_id(this->get_object_id());
|
||||
|
||||
ESP_LOGI(TAG,
|
||||
"%s: Autotune has started. This can take a long time depending on the "
|
||||
"responsiveness of your system. Your system "
|
||||
"output will be altered to deliberately oscillate above and below the setpoint multiple times. "
|
||||
"Until your sensor provides a reading, the autotuner may display \'nan\'",
|
||||
this->get_object_id().c_str());
|
||||
|
||||
this->set_interval("autotune-progress", 10000, [this]() {
|
||||
if (this->autotuner_ != nullptr && !this->autotuner_->is_finished())
|
||||
this->autotuner_->dump_config();
|
||||
});
|
||||
|
||||
if (mode != climate::CLIMATE_MODE_HEAT_COOL) {
|
||||
ESP_LOGW(TAG, "%s: !!! For PID autotuner you need to set AUTO (also called heat/cool) mode!",
|
||||
this->get_object_id().c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void PIDClimate::reset_integral_term() { this->controller_.reset_accumulated_integral(); }
|
||||
|
||||
@@ -237,6 +237,107 @@ async def build_dumpers(config):
|
||||
return dumpers
|
||||
|
||||
|
||||
# CanalSat
|
||||
(
|
||||
CanalSatData,
|
||||
CanalSatBinarySensor,
|
||||
CanalSatTrigger,
|
||||
CanalSatAction,
|
||||
CanalSatDumper,
|
||||
) = declare_protocol("CanalSat")
|
||||
CANALSAT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_DEVICE): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_ADDRESS, default=0): cv.hex_uint8_t,
|
||||
cv.Required(CONF_COMMAND): cv.hex_uint8_t,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("canalsat", CanalSatBinarySensor, CANALSAT_SCHEMA)
|
||||
def canalsat_binary_sensor(var, config):
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
CanalSatData,
|
||||
("device", config[CONF_DEVICE]),
|
||||
("address", config[CONF_ADDRESS]),
|
||||
("command", config[CONF_COMMAND]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register_trigger("canalsat", CanalSatTrigger, CanalSatData)
|
||||
def canalsat_trigger(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_dumper("canalsat", CanalSatDumper)
|
||||
def canalsat_dumper(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_action("canalsat", CanalSatAction, CANALSAT_SCHEMA)
|
||||
async def canalsat_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_DEVICE], args, cg.uint8)
|
||||
cg.add(var.set_device(template_))
|
||||
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
|
||||
cg.add(var.set_address(template_))
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||
cg.add(var.set_command(template_))
|
||||
|
||||
|
||||
(
|
||||
CanalSatLDData,
|
||||
CanalSatLDBinarySensor,
|
||||
CanalSatLDTrigger,
|
||||
CanalSatLDAction,
|
||||
CanalSatLDDumper,
|
||||
) = declare_protocol("CanalSatLD")
|
||||
CANALSATLD_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_DEVICE): cv.hex_uint8_t,
|
||||
cv.Optional(CONF_ADDRESS, default=0): cv.hex_uint8_t,
|
||||
cv.Required(CONF_COMMAND): cv.hex_uint8_t,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("canalsatld", CanalSatLDBinarySensor, CANALSAT_SCHEMA)
|
||||
def canalsatld_binary_sensor(var, config):
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
CanalSatLDData,
|
||||
("device", config[CONF_DEVICE]),
|
||||
("address", config[CONF_ADDRESS]),
|
||||
("command", config[CONF_COMMAND]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register_trigger("canalsatld", CanalSatLDTrigger, CanalSatLDData)
|
||||
def canalsatld_trigger(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_dumper("canalsatld", CanalSatLDDumper)
|
||||
def canalsatld_dumper(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_action("canalsatld", CanalSatLDAction, CANALSATLD_SCHEMA)
|
||||
async def canalsatld_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_DEVICE], args, cg.uint8)
|
||||
cg.add(var.set_device(template_))
|
||||
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint8)
|
||||
cg.add(var.set_address(template_))
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||
cg.add(var.set_command(template_))
|
||||
|
||||
|
||||
# Coolix
|
||||
(
|
||||
CoolixData,
|
||||
|
||||
108
esphome/components/remote_base/canalsat_protocol.cpp
Normal file
108
esphome/components/remote_base/canalsat_protocol.cpp
Normal file
@@ -0,0 +1,108 @@
|
||||
#include "canalsat_protocol.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
static const char *const CANALSAT_TAG = "remote.canalsat";
|
||||
static const char *const CANALSATLD_TAG = "remote.canalsatld";
|
||||
|
||||
static const uint16_t CANALSAT_FREQ = 55500;
|
||||
static const uint16_t CANALSATLD_FREQ = 56000;
|
||||
static const uint16_t CANALSAT_UNIT = 250;
|
||||
static const uint16_t CANALSATLD_UNIT = 320;
|
||||
|
||||
CanalSatProtocol::CanalSatProtocol() {
|
||||
this->frequency_ = CANALSAT_FREQ;
|
||||
this->unit_ = CANALSAT_UNIT;
|
||||
this->tag_ = CANALSAT_TAG;
|
||||
}
|
||||
|
||||
CanalSatLDProtocol::CanalSatLDProtocol() {
|
||||
this->frequency_ = CANALSATLD_FREQ;
|
||||
this->unit_ = CANALSATLD_UNIT;
|
||||
this->tag_ = CANALSATLD_TAG;
|
||||
}
|
||||
|
||||
void CanalSatBaseProtocol::encode(RemoteTransmitData *dst, const CanalSatData &data) {
|
||||
dst->reserve(48);
|
||||
dst->set_carrier_frequency(this->frequency_);
|
||||
|
||||
uint32_t raw{
|
||||
static_cast<uint32_t>((1 << 23) | (data.device << 16) | (data.address << 10) | (0 << 9) | (data.command << 1))};
|
||||
bool was_high{true};
|
||||
|
||||
for (uint32_t mask = 0x800000; mask; mask >>= 1) {
|
||||
if (raw & mask) {
|
||||
if (was_high) {
|
||||
dst->mark(this->unit_);
|
||||
}
|
||||
was_high = true;
|
||||
if (raw & mask >> 1) {
|
||||
dst->space(this->unit_);
|
||||
} else {
|
||||
dst->space(this->unit_ * 2);
|
||||
}
|
||||
} else {
|
||||
if (!was_high) {
|
||||
dst->space(this->unit_);
|
||||
}
|
||||
was_high = false;
|
||||
if (raw & mask >> 1) {
|
||||
dst->mark(this->unit_ * 2);
|
||||
} else {
|
||||
dst->mark(this->unit_);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optional<CanalSatData> CanalSatBaseProtocol::decode(RemoteReceiveData src) {
|
||||
CanalSatData data{
|
||||
.device = 0,
|
||||
.address = 0,
|
||||
.repeat = 0,
|
||||
.command = 0,
|
||||
};
|
||||
|
||||
// Check if initial mark and spaces match
|
||||
if (!src.peek_mark(this->unit_) || !(src.peek_space(this->unit_, 1) || src.peek_space(this->unit_ * 2, 1))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
uint8_t bit{1};
|
||||
uint8_t offset{1};
|
||||
uint32_t buffer{0};
|
||||
|
||||
while (offset < 24) {
|
||||
buffer = buffer | (bit << (24 - offset++));
|
||||
src.advance();
|
||||
if (src.peek_mark(this->unit_) || src.peek_space(this->unit_)) {
|
||||
src.advance();
|
||||
} else if (src.peek_mark(this->unit_ * 2) || src.peek_space(this->unit_ * 2)) {
|
||||
bit = !bit;
|
||||
} else if (offset != 24 && bit != 1) { // If last bit is high, final space is indistinguishable
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
data.device = (0xFF0000 & buffer) >> 16;
|
||||
data.address = (0x00FF00 & buffer) >> 10;
|
||||
data.repeat = (0x00FF00 & buffer) >> 9;
|
||||
data.command = (0x0000FF & buffer) >> 1;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void CanalSatBaseProtocol::dump(const CanalSatData &data) {
|
||||
if (this->tag_ == CANALSATLD_TAG) {
|
||||
ESP_LOGD(this->tag_, "Received CanalSatLD: device=0x%02X, address=0x%02X, command=0x%02X, repeat=0x%X", data.device,
|
||||
data.address, data.command, data.repeat);
|
||||
} else {
|
||||
ESP_LOGD(this->tag_, "Received CanalSat: device=0x%02X, address=0x%02X, command=0x%02X, repeat=0x%X", data.device,
|
||||
data.address, data.command, data.repeat);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
||||
78
esphome/components/remote_base/canalsat_protocol.h
Normal file
78
esphome/components/remote_base/canalsat_protocol.h
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include "remote_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
struct CanalSatData {
|
||||
uint8_t device : 7;
|
||||
uint8_t address : 6;
|
||||
uint8_t repeat : 1;
|
||||
uint8_t command : 7;
|
||||
|
||||
bool operator==(const CanalSatData &rhs) const {
|
||||
return device == rhs.device && address == rhs.address && command == rhs.command;
|
||||
}
|
||||
};
|
||||
|
||||
struct CanalSatLDData : public CanalSatData {};
|
||||
|
||||
class CanalSatBaseProtocol : public RemoteProtocol<CanalSatData> {
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const CanalSatData &data) override;
|
||||
optional<CanalSatData> decode(RemoteReceiveData src) override;
|
||||
void dump(const CanalSatData &data) override;
|
||||
|
||||
protected:
|
||||
uint16_t frequency_;
|
||||
uint16_t unit_;
|
||||
const char *tag_;
|
||||
};
|
||||
|
||||
class CanalSatProtocol : public CanalSatBaseProtocol {
|
||||
public:
|
||||
CanalSatProtocol();
|
||||
};
|
||||
|
||||
class CanalSatLDProtocol : public CanalSatBaseProtocol {
|
||||
public:
|
||||
CanalSatLDProtocol();
|
||||
};
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(CanalSat)
|
||||
|
||||
template<typename... Ts> class CanalSatAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, device)
|
||||
TEMPLATABLE_VALUE(uint8_t, address)
|
||||
TEMPLATABLE_VALUE(uint8_t, command)
|
||||
|
||||
void encode(RemoteTransmitData *dst, Ts... x) {
|
||||
CanalSatData data{};
|
||||
data.device = this->device_.value(x...);
|
||||
data.address = this->address_.value(x...);
|
||||
data.command = this->command_.value(x...);
|
||||
CanalSatProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(CanalSatLD)
|
||||
|
||||
template<typename... Ts> class CanalSatLDAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, device)
|
||||
TEMPLATABLE_VALUE(uint8_t, address)
|
||||
TEMPLATABLE_VALUE(uint8_t, command)
|
||||
|
||||
void encode(RemoteTransmitData *dst, Ts... x) {
|
||||
CanalSatData data{};
|
||||
data.device = this->device_.value(x...);
|
||||
data.address = this->address_.value(x...);
|
||||
data.command = this->command_.value(x...);
|
||||
CanalSatLDProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
||||
23
esphome/components/scd30/automation.h
Normal file
23
esphome/components/scd30/automation.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "scd30.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace scd30 {
|
||||
|
||||
template<typename... Ts> class ForceRecalibrationWithReference : public Action<Ts...>, public Parented<SCD30Component> {
|
||||
public:
|
||||
void play(Ts... x) override {
|
||||
if (this->value_.has_value()) {
|
||||
this->parent_->force_recalibration_with_reference(this->value_.value(x...));
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
TEMPLATABLE_VALUE(uint16_t, value)
|
||||
};
|
||||
|
||||
} // namespace scd30
|
||||
} // namespace esphome
|
||||
@@ -202,5 +202,27 @@ bool SCD30Component::is_data_ready_() {
|
||||
return is_data_ready == 1;
|
||||
}
|
||||
|
||||
bool SCD30Component::force_recalibration_with_reference(uint16_t co2_reference) {
|
||||
ESP_LOGD(TAG, "Performing CO2 force recalibration with reference %dppm.", co2_reference);
|
||||
if (this->write_command(SCD30_CMD_FORCED_CALIBRATION, co2_reference)) {
|
||||
ESP_LOGD(TAG, "Force recalibration complete.");
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to force recalibration with reference.");
|
||||
this->error_code_ = FORCE_RECALIBRATION_FAILED;
|
||||
this->status_set_warning();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t SCD30Component::get_forced_calibration_reference() {
|
||||
uint16_t forced_calibration_reference;
|
||||
// Get current CO2 calibration
|
||||
if (!this->get_register(SCD30_CMD_FORCED_CALIBRATION, forced_calibration_reference)) {
|
||||
ESP_LOGE(TAG, "Unable to read forced calibration reference.");
|
||||
}
|
||||
return forced_calibration_reference;
|
||||
}
|
||||
|
||||
} // namespace scd30
|
||||
} // namespace esphome
|
||||
|
||||
@@ -20,6 +20,8 @@ class SCD30Component : public Component, public sensirion_common::SensirionI2CDe
|
||||
}
|
||||
void set_temperature_offset(float offset) { temperature_offset_ = offset; }
|
||||
void set_update_interval(uint16_t interval) { update_interval_ = interval; }
|
||||
bool force_recalibration_with_reference(uint16_t co2_reference);
|
||||
uint16_t get_forced_calibration_reference();
|
||||
|
||||
void setup() override;
|
||||
void update();
|
||||
@@ -33,6 +35,7 @@ class SCD30Component : public Component, public sensirion_common::SensirionI2CDe
|
||||
COMMUNICATION_FAILED,
|
||||
FIRMWARE_IDENTIFICATION_FAILED,
|
||||
MEASUREMENT_INIT_FAILED,
|
||||
FORCE_RECALIBRATION_FAILED,
|
||||
UNKNOWN
|
||||
} error_code_{UNKNOWN};
|
||||
bool enable_asc_{true};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
from esphome import core
|
||||
from esphome import automation, core
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
CONF_TEMPERATURE,
|
||||
CONF_CO2,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
CONF_VALUE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
@@ -26,6 +27,11 @@ SCD30Component = scd30_ns.class_(
|
||||
"SCD30Component", cg.Component, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
# Actions
|
||||
ForceRecalibrationWithReference = scd30_ns.class_(
|
||||
"ForceRecalibrationWithReference", automation.Action
|
||||
)
|
||||
|
||||
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
||||
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
||||
CONF_AMBIENT_PRESSURE_COMPENSATION = "ambient_pressure_compensation"
|
||||
@@ -106,3 +112,26 @@ async def to_code(config):
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"scd30.force_recalibration_with_reference",
|
||||
ForceRecalibrationWithReference,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(SCD30Component),
|
||||
cv.Required(CONF_VALUE): cv.templatable(
|
||||
cv.int_range(min=400, max=2000, max_included=True)
|
||||
),
|
||||
},
|
||||
key=CONF_VALUE,
|
||||
),
|
||||
)
|
||||
async def scd30_force_recalibration_with_reference_to_code(
|
||||
config, action_id, template_arg, args
|
||||
):
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_ID])
|
||||
template_ = await cg.templatable(config[CONF_VALUE], args, cg.uint16)
|
||||
cg.add(var.set_value(template_))
|
||||
return var
|
||||
|
||||
@@ -6,8 +6,7 @@ from esphome.const import (
|
||||
CONF_STORE_BASELINE,
|
||||
CONF_TEMPERATURE_SOURCE,
|
||||
ICON_RADIATOR,
|
||||
DEVICE_CLASS_NITROUS_OXIDE,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
DEVICE_CLASS_AQI,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
@@ -67,13 +66,13 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_VOC): sensor.sensor_schema(
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
device_class=DEVICE_CLASS_AQI,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(GAS_SENSOR),
|
||||
cv.Optional(CONF_NOX): sensor.sensor_schema(
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_NITROUS_OXIDE,
|
||||
device_class=DEVICE_CLASS_AQI,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(GAS_SENSOR),
|
||||
cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean,
|
||||
|
||||
@@ -22,6 +22,7 @@ from esphome.const import (
|
||||
UNIT_WATT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
)
|
||||
from esphome.core import HexInt, CORE
|
||||
|
||||
@@ -169,7 +170,7 @@ CONFIG_SCHEMA = (
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
accuracy_decimals=2,
|
||||
),
|
||||
# Change the default gamma_correct setting.
|
||||
|
||||
@@ -156,6 +156,7 @@ void Sim800LComponent::parse_cmd_(std::string message) {
|
||||
case STATE_SEND_USSD1:
|
||||
this->send_cmd_("AT+CUSD=1, \"" + this->ussd_ + "\"");
|
||||
this->state_ = STATE_SEND_USSD2;
|
||||
this->expect_ack_ = true;
|
||||
break;
|
||||
case STATE_SEND_USSD2:
|
||||
ESP_LOGD(TAG, "SendUssd2: '%s'", message.c_str());
|
||||
|
||||
@@ -24,6 +24,7 @@ from esphome.const import (
|
||||
CONF_FAN_MODE_MIDDLE_ACTION,
|
||||
CONF_FAN_MODE_FOCUS_ACTION,
|
||||
CONF_FAN_MODE_DIFFUSE_ACTION,
|
||||
CONF_FAN_MODE_QUIET_ACTION,
|
||||
CONF_FAN_ONLY_ACTION,
|
||||
CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER,
|
||||
CONF_FAN_ONLY_COOLING,
|
||||
@@ -273,6 +274,7 @@ def validate_thermostat(config):
|
||||
CONF_FAN_MODE_MIDDLE_ACTION,
|
||||
CONF_FAN_MODE_FOCUS_ACTION,
|
||||
CONF_FAN_MODE_DIFFUSE_ACTION,
|
||||
CONF_FAN_MODE_QUIET_ACTION,
|
||||
],
|
||||
}
|
||||
for req_config_item, config_triggers in requirements.items():
|
||||
@@ -413,6 +415,7 @@ def validate_thermostat(config):
|
||||
"MIDDLE": [CONF_FAN_MODE_MIDDLE_ACTION],
|
||||
"FOCUS": [CONF_FAN_MODE_FOCUS_ACTION],
|
||||
"DIFFUSE": [CONF_FAN_MODE_DIFFUSE_ACTION],
|
||||
"QUIET": [CONF_FAN_MODE_QUIET_ACTION],
|
||||
}
|
||||
|
||||
for preset_config in config[CONF_PRESET]:
|
||||
@@ -500,12 +503,13 @@ def validate_thermostat(config):
|
||||
CONF_FAN_MODE_MIDDLE_ACTION,
|
||||
CONF_FAN_MODE_FOCUS_ACTION,
|
||||
CONF_FAN_MODE_DIFFUSE_ACTION,
|
||||
CONF_FAN_MODE_QUIET_ACTION,
|
||||
]
|
||||
for config_req_action in requirements:
|
||||
if config_req_action in config:
|
||||
return config
|
||||
raise cv.Invalid(
|
||||
f"At least one of {CONF_FAN_MODE_ON_ACTION}, {CONF_FAN_MODE_OFF_ACTION}, {CONF_FAN_MODE_AUTO_ACTION}, {CONF_FAN_MODE_LOW_ACTION}, {CONF_FAN_MODE_MEDIUM_ACTION}, {CONF_FAN_MODE_HIGH_ACTION}, {CONF_FAN_MODE_MIDDLE_ACTION}, {CONF_FAN_MODE_FOCUS_ACTION}, {CONF_FAN_MODE_DIFFUSE_ACTION} must be defined to use {CONF_MIN_FAN_MODE_SWITCHING_TIME}"
|
||||
f"At least one of {CONF_FAN_MODE_ON_ACTION}, {CONF_FAN_MODE_OFF_ACTION}, {CONF_FAN_MODE_AUTO_ACTION}, {CONF_FAN_MODE_LOW_ACTION}, {CONF_FAN_MODE_MEDIUM_ACTION}, {CONF_FAN_MODE_HIGH_ACTION}, {CONF_FAN_MODE_MIDDLE_ACTION}, {CONF_FAN_MODE_FOCUS_ACTION}, {CONF_FAN_MODE_DIFFUSE_ACTION}, {CONF_FAN_MODE_QUIET_ACTION} must be defined to use {CONF_MIN_FAN_MODE_SWITCHING_TIME}"
|
||||
)
|
||||
return config
|
||||
|
||||
@@ -563,6 +567,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_FAN_MODE_DIFFUSE_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_FAN_MODE_QUIET_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_SWING_BOTH_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
@@ -836,6 +843,11 @@ async def to_code(config):
|
||||
var.get_fan_mode_diffuse_trigger(), [], config[CONF_FAN_MODE_DIFFUSE_ACTION]
|
||||
)
|
||||
cg.add(var.set_supports_fan_mode_diffuse(True))
|
||||
if CONF_FAN_MODE_QUIET_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_fan_mode_quiet_trigger(), [], config[CONF_FAN_MODE_QUIET_ACTION]
|
||||
)
|
||||
cg.add(var.set_supports_fan_mode_quiet(True))
|
||||
if CONF_SWING_BOTH_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_swing_mode_both_trigger(), [], config[CONF_SWING_BOTH_ACTION]
|
||||
|
||||
@@ -247,6 +247,8 @@ climate::ClimateTraits ThermostatClimate::traits() {
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_FOCUS);
|
||||
if (supports_fan_mode_diffuse_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_DIFFUSE);
|
||||
if (supports_fan_mode_quiet_)
|
||||
traits.add_supported_fan_mode(climate::CLIMATE_FAN_QUIET);
|
||||
|
||||
if (supports_swing_mode_both_)
|
||||
traits.add_supported_swing_mode(climate::CLIMATE_SWING_BOTH);
|
||||
@@ -594,6 +596,10 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo
|
||||
trig = this->fan_mode_diffuse_trigger_;
|
||||
ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode");
|
||||
break;
|
||||
case climate::CLIMATE_FAN_QUIET:
|
||||
trig = this->fan_mode_quiet_trigger_;
|
||||
ESP_LOGVV(TAG, "Switching to FAN_QUIET mode");
|
||||
break;
|
||||
default:
|
||||
// we cannot report an invalid mode back to HA (even if it asked for one)
|
||||
// and must assume some valid value
|
||||
@@ -1093,6 +1099,7 @@ ThermostatClimate::ThermostatClimate()
|
||||
fan_mode_middle_trigger_(new Trigger<>()),
|
||||
fan_mode_focus_trigger_(new Trigger<>()),
|
||||
fan_mode_diffuse_trigger_(new Trigger<>()),
|
||||
fan_mode_quiet_trigger_(new Trigger<>()),
|
||||
swing_mode_both_trigger_(new Trigger<>()),
|
||||
swing_mode_off_trigger_(new Trigger<>()),
|
||||
swing_mode_horizontal_trigger_(new Trigger<>()),
|
||||
@@ -1208,6 +1215,9 @@ void ThermostatClimate::set_supports_fan_mode_focus(bool supports_fan_mode_focus
|
||||
void ThermostatClimate::set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse) {
|
||||
this->supports_fan_mode_diffuse_ = supports_fan_mode_diffuse;
|
||||
}
|
||||
void ThermostatClimate::set_supports_fan_mode_quiet(bool supports_fan_mode_quiet) {
|
||||
this->supports_fan_mode_quiet_ = supports_fan_mode_quiet;
|
||||
}
|
||||
void ThermostatClimate::set_supports_swing_mode_both(bool supports_swing_mode_both) {
|
||||
this->supports_swing_mode_both_ = supports_swing_mode_both;
|
||||
}
|
||||
@@ -1250,6 +1260,7 @@ Trigger<> *ThermostatClimate::get_fan_mode_high_trigger() const { return this->f
|
||||
Trigger<> *ThermostatClimate::get_fan_mode_middle_trigger() const { return this->fan_mode_middle_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_mode_focus_trigger() const { return this->fan_mode_focus_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_mode_diffuse_trigger() const { return this->fan_mode_diffuse_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_fan_mode_quiet_trigger() const { return this->fan_mode_quiet_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() const { return this->swing_mode_both_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this->swing_mode_off_trigger_; }
|
||||
Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; }
|
||||
@@ -1294,7 +1305,8 @@ void ThermostatClimate::dump_config() {
|
||||
}
|
||||
if (this->supports_fan_mode_on_ || this->supports_fan_mode_off_ || this->supports_fan_mode_auto_ ||
|
||||
this->supports_fan_mode_low_ || this->supports_fan_mode_medium_ || this->supports_fan_mode_high_ ||
|
||||
this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_) {
|
||||
this->supports_fan_mode_middle_ || this->supports_fan_mode_focus_ || this->supports_fan_mode_diffuse_ ||
|
||||
this->supports_fan_mode_quiet_) {
|
||||
ESP_LOGCONFIG(TAG, " Minimum Fan Mode Switching Time: %us",
|
||||
this->timer_duration_(thermostat::TIMER_FAN_MODE) / 1000);
|
||||
}
|
||||
@@ -1323,6 +1335,7 @@ void ThermostatClimate::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, " Supports FAN MODE MIDDLE: %s", YESNO(this->supports_fan_mode_middle_));
|
||||
ESP_LOGCONFIG(TAG, " Supports FAN MODE FOCUS: %s", YESNO(this->supports_fan_mode_focus_));
|
||||
ESP_LOGCONFIG(TAG, " Supports FAN MODE DIFFUSE: %s", YESNO(this->supports_fan_mode_diffuse_));
|
||||
ESP_LOGCONFIG(TAG, " Supports FAN MODE QUIET: %s", YESNO(this->supports_fan_mode_quiet_));
|
||||
ESP_LOGCONFIG(TAG, " Supports SWING MODE BOTH: %s", YESNO(this->supports_swing_mode_both_));
|
||||
ESP_LOGCONFIG(TAG, " Supports SWING MODE OFF: %s", YESNO(this->supports_swing_mode_off_));
|
||||
ESP_LOGCONFIG(TAG, " Supports SWING MODE HORIZONTAL: %s", YESNO(this->supports_swing_mode_horizontal_));
|
||||
|
||||
@@ -101,6 +101,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
void set_supports_fan_mode_middle(bool supports_fan_mode_middle);
|
||||
void set_supports_fan_mode_focus(bool supports_fan_mode_focus);
|
||||
void set_supports_fan_mode_diffuse(bool supports_fan_mode_diffuse);
|
||||
void set_supports_fan_mode_quiet(bool supports_fan_mode_quiet);
|
||||
void set_supports_swing_mode_both(bool supports_swing_mode_both);
|
||||
void set_supports_swing_mode_horizontal(bool supports_swing_mode_horizontal);
|
||||
void set_supports_swing_mode_off(bool supports_swing_mode_off);
|
||||
@@ -132,6 +133,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
Trigger<> *get_fan_mode_middle_trigger() const;
|
||||
Trigger<> *get_fan_mode_focus_trigger() const;
|
||||
Trigger<> *get_fan_mode_diffuse_trigger() const;
|
||||
Trigger<> *get_fan_mode_quiet_trigger() const;
|
||||
Trigger<> *get_swing_mode_both_trigger() const;
|
||||
Trigger<> *get_swing_mode_horizontal_trigger() const;
|
||||
Trigger<> *get_swing_mode_off_trigger() const;
|
||||
@@ -277,6 +279,7 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
bool supports_fan_mode_middle_{false};
|
||||
bool supports_fan_mode_focus_{false};
|
||||
bool supports_fan_mode_diffuse_{false};
|
||||
bool supports_fan_mode_quiet_{false};
|
||||
|
||||
/// Whether the controller supports various swing modes.
|
||||
///
|
||||
@@ -372,6 +375,9 @@ class ThermostatClimate : public climate::Climate, public Component {
|
||||
/// The trigger to call when the controller should switch the fan to "diffuse" position.
|
||||
Trigger<> *fan_mode_diffuse_trigger_{nullptr};
|
||||
|
||||
/// The trigger to call when the controller should switch the fan to "quiet" position.
|
||||
Trigger<> *fan_mode_quiet_trigger_{nullptr};
|
||||
|
||||
/// The trigger to call when the controller should switch the swing mode to "both".
|
||||
Trigger<> *swing_mode_both_trigger_{nullptr};
|
||||
|
||||
|
||||
32
esphome/components/vbus/__init__.py
Normal file
32
esphome/components/vbus/__init__.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
MULTI_CONF = True
|
||||
|
||||
vbus_ns = cg.esphome_ns.namespace("vbus")
|
||||
VBus = vbus_ns.class_("VBus", uart.UARTDevice, cg.Component)
|
||||
|
||||
CONF_VBUS_ID = "vbus_id"
|
||||
|
||||
CONF_DELTASOL_BS_PLUS = "deltasol_bs_plus"
|
||||
CONF_DELTASOL_C = "deltasol_c"
|
||||
CONF_DELTASOL_CS2 = "deltasol_cs2"
|
||||
CONF_DELTASOL_CS_PLUS = "deltasol_cs_plus"
|
||||
|
||||
CONFIG_SCHEMA = uart.UART_DEVICE_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(VBus),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
296
esphome/components/vbus/binary_sensor/__init__.py
Normal file
296
esphome/components/vbus/binary_sensor/__init__.py
Normal file
@@ -0,0 +1,296 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_BINARY_SENSORS,
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
CONF_DEST,
|
||||
CONF_LAMBDA,
|
||||
CONF_MODEL,
|
||||
CONF_SOURCE,
|
||||
DEVICE_CLASS_PROBLEM,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
from .. import (
|
||||
vbus_ns,
|
||||
VBus,
|
||||
CONF_VBUS_ID,
|
||||
CONF_DELTASOL_BS_PLUS,
|
||||
CONF_DELTASOL_C,
|
||||
CONF_DELTASOL_CS2,
|
||||
CONF_DELTASOL_CS_PLUS,
|
||||
)
|
||||
|
||||
DeltaSol_BS_Plus = vbus_ns.class_("DeltaSolBSPlusBSensor", cg.Component)
|
||||
DeltaSol_C = vbus_ns.class_("DeltaSolCBSensor", cg.Component)
|
||||
DeltaSol_CS2 = vbus_ns.class_("DeltaSolCS2BSensor", cg.Component)
|
||||
DeltaSol_CS_Plus = vbus_ns.class_("DeltaSolCSPlusBSensor", cg.Component)
|
||||
VBusCustom = vbus_ns.class_("VBusCustomBSensor", cg.Component)
|
||||
VBusCustomSub = vbus_ns.class_("VBusCustomSubBSensor", cg.Component)
|
||||
|
||||
CONF_RELAY1 = "relay1"
|
||||
CONF_RELAY2 = "relay2"
|
||||
CONF_SENSOR1_ERROR = "sensor1_error"
|
||||
CONF_SENSOR2_ERROR = "sensor2_error"
|
||||
CONF_SENSOR3_ERROR = "sensor3_error"
|
||||
CONF_SENSOR4_ERROR = "sensor4_error"
|
||||
CONF_COLLECTOR_MAX = "collector_max"
|
||||
CONF_COLLECTOR_MIN = "collector_min"
|
||||
CONF_COLLECTOR_FROST = "collector_frost"
|
||||
CONF_TUBE_COLLECTOR = "tube_collector"
|
||||
CONF_RECOOLING = "recooling"
|
||||
CONF_HQM = "hqm"
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
CONF_DELTASOL_BS_PLUS: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeltaSol_BS_Plus),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_RELAY1): binary_sensor.binary_sensor_schema(),
|
||||
cv.Optional(CONF_RELAY2): binary_sensor.binary_sensor_schema(),
|
||||
cv.Optional(CONF_SENSOR1_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR2_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR3_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR4_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_COLLECTOR_MAX): binary_sensor.binary_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_COLLECTOR_MIN): binary_sensor.binary_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_COLLECTOR_FROST): binary_sensor.binary_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_TUBE_COLLECTOR): binary_sensor.binary_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_RECOOLING): binary_sensor.binary_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_HQM): binary_sensor.binary_sensor_schema(
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_DELTASOL_C: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeltaSol_C),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_SENSOR1_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR2_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR3_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR4_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_DELTASOL_CS2: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeltaSol_CS2),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_SENSOR1_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR2_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR3_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR4_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_DELTASOL_CS_PLUS: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeltaSol_CS_Plus),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_SENSOR1_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR2_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR3_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_SENSOR4_ERROR): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_PROBLEM,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_CUSTOM: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(VBusCustom),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_COMMAND): cv.uint16_t,
|
||||
cv.Optional(CONF_SOURCE): cv.uint16_t,
|
||||
cv.Optional(CONF_DEST): cv.uint16_t,
|
||||
cv.Optional(CONF_BINARY_SENSORS): cv.ensure_list(
|
||||
binary_sensor.binary_sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(VBusCustomSub),
|
||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
},
|
||||
key=CONF_MODEL,
|
||||
lower=True,
|
||||
space="_",
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if config[CONF_MODEL] == CONF_DELTASOL_BS_PLUS:
|
||||
cg.add(var.set_command(0x0100))
|
||||
cg.add(var.set_source(0x4221))
|
||||
cg.add(var.set_dest(0x0010))
|
||||
if CONF_RELAY1 in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_RELAY1])
|
||||
cg.add(var.set_relay1_bsensor(sens))
|
||||
if CONF_RELAY2 in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_RELAY2])
|
||||
cg.add(var.set_relay2_bsensor(sens))
|
||||
if CONF_SENSOR1_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR1_ERROR])
|
||||
cg.add(var.set_s1_error_bsensor(sens))
|
||||
if CONF_SENSOR2_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR2_ERROR])
|
||||
cg.add(var.set_s2_error_bsensor(sens))
|
||||
if CONF_SENSOR3_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR3_ERROR])
|
||||
cg.add(var.set_s3_error_bsensor(sens))
|
||||
if CONF_SENSOR4_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR4_ERROR])
|
||||
cg.add(var.set_s4_error_bsensor(sens))
|
||||
if CONF_COLLECTOR_MAX in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_COLLECTOR_MAX])
|
||||
cg.add(var.set_collector_max_bsensor(sens))
|
||||
if CONF_COLLECTOR_MIN in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_COLLECTOR_MIN])
|
||||
cg.add(var.set_collector_min_bsensor(sens))
|
||||
if CONF_COLLECTOR_FROST in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_COLLECTOR_FROST])
|
||||
cg.add(var.set_collector_frost_bsensor(sens))
|
||||
if CONF_TUBE_COLLECTOR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_TUBE_COLLECTOR])
|
||||
cg.add(var.set_tube_collector_bsensor(sens))
|
||||
if CONF_RECOOLING in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_RECOOLING])
|
||||
cg.add(var.set_recooling_bsensor(sens))
|
||||
if CONF_HQM in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_HQM])
|
||||
cg.add(var.set_hqm_bsensor(sens))
|
||||
|
||||
elif config[CONF_MODEL] == CONF_DELTASOL_C:
|
||||
cg.add(var.set_command(0x0100))
|
||||
cg.add(var.set_source(0x4212))
|
||||
cg.add(var.set_dest(0x0010))
|
||||
if CONF_SENSOR1_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR1_ERROR])
|
||||
cg.add(var.set_s1_error_bsensor(sens))
|
||||
if CONF_SENSOR2_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR2_ERROR])
|
||||
cg.add(var.set_s2_error_bsensor(sens))
|
||||
if CONF_SENSOR3_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR3_ERROR])
|
||||
cg.add(var.set_s3_error_bsensor(sens))
|
||||
if CONF_SENSOR4_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR4_ERROR])
|
||||
cg.add(var.set_s4_error_bsensor(sens))
|
||||
|
||||
elif config[CONF_MODEL] == CONF_DELTASOL_CS2:
|
||||
cg.add(var.set_command(0x0100))
|
||||
cg.add(var.set_source(0x1121))
|
||||
cg.add(var.set_dest(0x0010))
|
||||
if CONF_SENSOR1_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR1_ERROR])
|
||||
cg.add(var.set_s1_error_bsensor(sens))
|
||||
if CONF_SENSOR2_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR2_ERROR])
|
||||
cg.add(var.set_s2_error_bsensor(sens))
|
||||
if CONF_SENSOR3_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR3_ERROR])
|
||||
cg.add(var.set_s3_error_bsensor(sens))
|
||||
if CONF_SENSOR4_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR4_ERROR])
|
||||
cg.add(var.set_s4_error_bsensor(sens))
|
||||
|
||||
elif config[CONF_MODEL] == CONF_DELTASOL_CS_PLUS:
|
||||
cg.add(var.set_command(0x0100))
|
||||
cg.add(var.set_source(0x2211))
|
||||
cg.add(var.set_dest(0x0010))
|
||||
if CONF_SENSOR1_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR1_ERROR])
|
||||
cg.add(var.set_s1_error_bsensor(sens))
|
||||
if CONF_SENSOR2_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR2_ERROR])
|
||||
cg.add(var.set_s2_error_bsensor(sens))
|
||||
if CONF_SENSOR3_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR3_ERROR])
|
||||
cg.add(var.set_s3_error_bsensor(sens))
|
||||
if CONF_SENSOR4_ERROR in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_SENSOR4_ERROR])
|
||||
cg.add(var.set_s4_error_bsensor(sens))
|
||||
|
||||
elif config[CONF_MODEL] == CONF_CUSTOM:
|
||||
if CONF_COMMAND in config:
|
||||
cg.add(var.set_command(config[CONF_COMMAND]))
|
||||
if CONF_SOURCE in config:
|
||||
cg.add(var.set_source(config[CONF_SOURCE]))
|
||||
if CONF_DEST in config:
|
||||
cg.add(var.set_dest(config[CONF_DEST]))
|
||||
bsensors = []
|
||||
for conf in config[CONF_BINARY_SENSORS]:
|
||||
bsens = await binary_sensor.new_binary_sensor(conf)
|
||||
lambda_ = await cg.process_lambda(
|
||||
conf[CONF_LAMBDA],
|
||||
[(cg.std_vector.template(cg.uint8), "x")],
|
||||
return_type=cg.bool_,
|
||||
)
|
||||
cg.add(bsens.set_message_parser(lambda_))
|
||||
bsensors.append(bsens)
|
||||
cg.add(var.set_bsensors(bsensors))
|
||||
|
||||
vbus = await cg.get_variable(config[CONF_VBUS_ID])
|
||||
cg.add(vbus.register_listener(var))
|
||||
142
esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp
Normal file
142
esphome/components/vbus/binary_sensor/vbus_binary_sensor.cpp
Normal file
@@ -0,0 +1,142 @@
|
||||
#include "vbus_binary_sensor.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace vbus {
|
||||
|
||||
static const char *const TAG = "vbus.binary_sensor";
|
||||
|
||||
void DeltaSolBSPlusBSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Deltasol BS Plus:");
|
||||
LOG_BINARY_SENSOR(" ", "Relay 1 On", this->relay1_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Relay 2 On", this->relay2_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 2 Error", this->s2_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 3 Error", this->s3_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 4 Error", this->s4_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Option Collector Max", this->collector_max_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Option Collector Min", this->collector_min_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Option Collector Frost", this->collector_frost_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Option Tube Collector", this->tube_collector_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Option Recooling", this->recooling_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Option Heat Quantity Measurement", this->hqm_bsensor_);
|
||||
}
|
||||
|
||||
void DeltaSolBSPlusBSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
if (this->relay1_bsensor_ != nullptr)
|
||||
this->relay1_bsensor_->publish_state(message[10] & 1);
|
||||
if (this->relay2_bsensor_ != nullptr)
|
||||
this->relay2_bsensor_->publish_state(message[10] & 2);
|
||||
if (this->s1_error_bsensor_ != nullptr)
|
||||
this->s1_error_bsensor_->publish_state(message[11] & 1);
|
||||
if (this->s2_error_bsensor_ != nullptr)
|
||||
this->s2_error_bsensor_->publish_state(message[11] & 2);
|
||||
if (this->s3_error_bsensor_ != nullptr)
|
||||
this->s3_error_bsensor_->publish_state(message[11] & 4);
|
||||
if (this->s4_error_bsensor_ != nullptr)
|
||||
this->s4_error_bsensor_->publish_state(message[11] & 8);
|
||||
if (this->collector_max_bsensor_ != nullptr)
|
||||
this->collector_max_bsensor_->publish_state(message[15] & 1);
|
||||
if (this->collector_min_bsensor_ != nullptr)
|
||||
this->collector_min_bsensor_->publish_state(message[15] & 2);
|
||||
if (this->collector_frost_bsensor_ != nullptr)
|
||||
this->collector_frost_bsensor_->publish_state(message[15] & 4);
|
||||
if (this->tube_collector_bsensor_ != nullptr)
|
||||
this->tube_collector_bsensor_->publish_state(message[15] & 8);
|
||||
if (this->recooling_bsensor_ != nullptr)
|
||||
this->recooling_bsensor_->publish_state(message[15] & 0x10);
|
||||
if (this->hqm_bsensor_ != nullptr)
|
||||
this->hqm_bsensor_->publish_state(message[15] & 0x20);
|
||||
}
|
||||
|
||||
void DeltaSolCBSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Deltasol C:");
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 2 Error", this->s2_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 3 Error", this->s3_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 4 Error", this->s4_error_bsensor_);
|
||||
}
|
||||
|
||||
void DeltaSolCBSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
if (this->s1_error_bsensor_ != nullptr)
|
||||
this->s1_error_bsensor_->publish_state(message[10] & 1);
|
||||
if (this->s2_error_bsensor_ != nullptr)
|
||||
this->s2_error_bsensor_->publish_state(message[10] & 2);
|
||||
if (this->s3_error_bsensor_ != nullptr)
|
||||
this->s3_error_bsensor_->publish_state(message[10] & 4);
|
||||
if (this->s4_error_bsensor_ != nullptr)
|
||||
this->s4_error_bsensor_->publish_state(message[10] & 8);
|
||||
}
|
||||
|
||||
void DeltaSolCS2BSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Deltasol CS2:");
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 2 Error", this->s2_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 3 Error", this->s3_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 4 Error", this->s4_error_bsensor_);
|
||||
}
|
||||
|
||||
void DeltaSolCS2BSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
if (this->s1_error_bsensor_ != nullptr)
|
||||
this->s1_error_bsensor_->publish_state(message[18] & 1);
|
||||
if (this->s2_error_bsensor_ != nullptr)
|
||||
this->s2_error_bsensor_->publish_state(message[18] & 2);
|
||||
if (this->s3_error_bsensor_ != nullptr)
|
||||
this->s3_error_bsensor_->publish_state(message[18] & 4);
|
||||
if (this->s4_error_bsensor_ != nullptr)
|
||||
this->s4_error_bsensor_->publish_state(message[18] & 8);
|
||||
}
|
||||
|
||||
void DeltaSolCSPlusBSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Deltasol CS Plus:");
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 1 Error", this->s1_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 2 Error", this->s2_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 3 Error", this->s3_error_bsensor_);
|
||||
LOG_BINARY_SENSOR(" ", "Sensor 4 Error", this->s4_error_bsensor_);
|
||||
}
|
||||
|
||||
void DeltaSolCSPlusBSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
if (this->s1_error_bsensor_ != nullptr)
|
||||
this->s1_error_bsensor_->publish_state(message[20] & 1);
|
||||
if (this->s2_error_bsensor_ != nullptr)
|
||||
this->s2_error_bsensor_->publish_state(message[20] & 2);
|
||||
if (this->s3_error_bsensor_ != nullptr)
|
||||
this->s3_error_bsensor_->publish_state(message[20] & 4);
|
||||
if (this->s4_error_bsensor_ != nullptr)
|
||||
this->s4_error_bsensor_->publish_state(message[20] & 8);
|
||||
}
|
||||
|
||||
void VBusCustomBSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "VBus Custom Binary Sensor:");
|
||||
if (this->source_ == 0xffff) {
|
||||
ESP_LOGCONFIG(TAG, " Source address: ANY");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Source address: 0x%04x", this->source_);
|
||||
}
|
||||
if (this->dest_ == 0xffff) {
|
||||
ESP_LOGCONFIG(TAG, " Dest address: ANY");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Dest address: 0x%04x", this->dest_);
|
||||
}
|
||||
if (this->command_ == 0xffff) {
|
||||
ESP_LOGCONFIG(TAG, " Command: ANY");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Command: 0x%04x", this->command_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Binary Sensors:");
|
||||
for (VBusCustomSubBSensor *bsensor : this->bsensors_)
|
||||
LOG_BINARY_SENSOR(" ", "-", bsensor);
|
||||
}
|
||||
|
||||
void VBusCustomBSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
for (VBusCustomSubBSensor *bsensor : this->bsensors_)
|
||||
bsensor->parse_message(message);
|
||||
}
|
||||
|
||||
void VBusCustomSubBSensor::parse_message(std::vector<uint8_t> &message) {
|
||||
this->publish_state(this->message_parser_(message));
|
||||
}
|
||||
|
||||
} // namespace vbus
|
||||
} // namespace esphome
|
||||
115
esphome/components/vbus/binary_sensor/vbus_binary_sensor.h
Normal file
115
esphome/components/vbus/binary_sensor/vbus_binary_sensor.h
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include "../vbus.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace vbus {
|
||||
|
||||
class DeltaSolBSPlusBSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_relay1_bsensor(binary_sensor::BinarySensor *bsensor) { this->relay1_bsensor_ = bsensor; }
|
||||
void set_relay2_bsensor(binary_sensor::BinarySensor *bsensor) { this->relay2_bsensor_ = bsensor; }
|
||||
void set_s1_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s1_error_bsensor_ = bsensor; }
|
||||
void set_s2_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s2_error_bsensor_ = bsensor; }
|
||||
void set_s3_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s3_error_bsensor_ = bsensor; }
|
||||
void set_s4_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s4_error_bsensor_ = bsensor; }
|
||||
void set_collector_max_bsensor(binary_sensor::BinarySensor *bsensor) { this->collector_max_bsensor_ = bsensor; }
|
||||
void set_collector_min_bsensor(binary_sensor::BinarySensor *bsensor) { this->collector_min_bsensor_ = bsensor; }
|
||||
void set_collector_frost_bsensor(binary_sensor::BinarySensor *bsensor) { this->collector_frost_bsensor_ = bsensor; }
|
||||
void set_tube_collector_bsensor(binary_sensor::BinarySensor *bsensor) { this->tube_collector_bsensor_ = bsensor; }
|
||||
void set_recooling_bsensor(binary_sensor::BinarySensor *bsensor) { this->recooling_bsensor_ = bsensor; }
|
||||
void set_hqm_bsensor(binary_sensor::BinarySensor *bsensor) { this->hqm_bsensor_ = bsensor; }
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *relay1_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *relay2_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s1_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s2_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s3_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s4_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *collector_max_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *collector_min_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *collector_frost_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *tube_collector_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *recooling_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *hqm_bsensor_{nullptr};
|
||||
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class DeltaSolCBSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_s1_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s1_error_bsensor_ = bsensor; }
|
||||
void set_s2_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s2_error_bsensor_ = bsensor; }
|
||||
void set_s3_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s3_error_bsensor_ = bsensor; }
|
||||
void set_s4_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s4_error_bsensor_ = bsensor; }
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *s1_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s2_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s3_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s4_error_bsensor_{nullptr};
|
||||
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class DeltaSolCS2BSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_s1_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s1_error_bsensor_ = bsensor; }
|
||||
void set_s2_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s2_error_bsensor_ = bsensor; }
|
||||
void set_s3_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s3_error_bsensor_ = bsensor; }
|
||||
void set_s4_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s4_error_bsensor_ = bsensor; }
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *s1_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s2_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s3_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s4_error_bsensor_{nullptr};
|
||||
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class DeltaSolCSPlusBSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_s1_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s1_error_bsensor_ = bsensor; }
|
||||
void set_s2_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s2_error_bsensor_ = bsensor; }
|
||||
void set_s3_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s3_error_bsensor_ = bsensor; }
|
||||
void set_s4_error_bsensor(binary_sensor::BinarySensor *bsensor) { this->s4_error_bsensor_ = bsensor; }
|
||||
|
||||
protected:
|
||||
binary_sensor::BinarySensor *s1_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s2_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s3_error_bsensor_{nullptr};
|
||||
binary_sensor::BinarySensor *s4_error_bsensor_{nullptr};
|
||||
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class VBusCustomSubBSensor;
|
||||
|
||||
class VBusCustomBSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_bsensors(std::vector<VBusCustomSubBSensor *> bsensors) { this->bsensors_ = std::move(bsensors); };
|
||||
|
||||
protected:
|
||||
std::vector<VBusCustomSubBSensor *> bsensors_;
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class VBusCustomSubBSensor : public binary_sensor::BinarySensor, public Component {
|
||||
public:
|
||||
void set_message_parser(message_parser_t parser) { this->message_parser_ = std::move(parser); };
|
||||
void parse_message(std::vector<uint8_t> &message);
|
||||
|
||||
protected:
|
||||
message_parser_t message_parser_;
|
||||
};
|
||||
|
||||
} // namespace vbus
|
||||
} // namespace esphome
|
||||
568
esphome/components/vbus/sensor/__init__.py
Normal file
568
esphome/components/vbus/sensor/__init__.py
Normal file
@@ -0,0 +1,568 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_COMMAND,
|
||||
CONF_CUSTOM,
|
||||
CONF_DEST,
|
||||
CONF_LAMBDA,
|
||||
CONF_MODEL,
|
||||
CONF_SENSORS,
|
||||
CONF_SOURCE,
|
||||
CONF_TIME,
|
||||
CONF_VERSION,
|
||||
DEVICE_CLASS_DURATION,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_ENERGY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_PERCENT,
|
||||
ICON_RADIATOR,
|
||||
ICON_THERMOMETER,
|
||||
ICON_TIMER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HOUR,
|
||||
UNIT_MINUTE,
|
||||
UNIT_PERCENT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
from .. import (
|
||||
vbus_ns,
|
||||
VBus,
|
||||
CONF_VBUS_ID,
|
||||
CONF_DELTASOL_BS_PLUS,
|
||||
CONF_DELTASOL_C,
|
||||
CONF_DELTASOL_CS2,
|
||||
CONF_DELTASOL_CS_PLUS,
|
||||
)
|
||||
|
||||
DeltaSol_BS_Plus = vbus_ns.class_("DeltaSolBSPlusSensor", cg.Component)
|
||||
DeltaSol_C = vbus_ns.class_("DeltaSolCSensor", cg.Component)
|
||||
DeltaSol_CS2 = vbus_ns.class_("DeltaSolCS2Sensor", cg.Component)
|
||||
DeltaSol_CS_Plus = vbus_ns.class_("DeltaSolCSPlusSensor", cg.Component)
|
||||
VBusCustom = vbus_ns.class_("VBusCustomSensor", cg.Component)
|
||||
VBusCustomSub = vbus_ns.class_("VBusCustomSubSensor", cg.Component)
|
||||
|
||||
CONF_FLOW_RATE = "flow_rate"
|
||||
CONF_HEAT_QUANTITY = "heat_quantity"
|
||||
CONF_OPERATING_HOURS = "operating_hours"
|
||||
CONF_OPERATING_HOURS_1 = "operating_hours_1"
|
||||
CONF_OPERATING_HOURS_2 = "operating_hours_2"
|
||||
CONF_PUMP_SPEED = "pump_speed"
|
||||
CONF_PUMP_SPEED_1 = "pump_speed_1"
|
||||
CONF_PUMP_SPEED_2 = "pump_speed_2"
|
||||
CONF_TEMPERATURE_1 = "temperature_1"
|
||||
CONF_TEMPERATURE_2 = "temperature_2"
|
||||
CONF_TEMPERATURE_3 = "temperature_3"
|
||||
CONF_TEMPERATURE_4 = "temperature_4"
|
||||
CONF_TEMPERATURE_5 = "temperature_5"
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
CONF_DELTASOL_BS_PLUS: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeltaSol_BS_Plus),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_3): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_4): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_SPEED_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_SPEED_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_OPERATING_HOURS_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HOUR,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_OPERATING_HOURS_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HOUR,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HEAT_QUANTITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TIME): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MINUTE,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_VERSION): sensor.sensor_schema(
|
||||
accuracy_decimals=2,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_DELTASOL_C: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeltaSol_C),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_3): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_4): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_SPEED_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_SPEED_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_OPERATING_HOURS_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HOUR,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_OPERATING_HOURS_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HOUR,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HEAT_QUANTITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TIME): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MINUTE,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_DELTASOL_CS2: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeltaSol_CS2),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_3): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_4): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_SPEED): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_OPERATING_HOURS): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HOUR,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HEAT_QUANTITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_VERSION): sensor.sensor_schema(
|
||||
accuracy_decimals=2,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_DELTASOL_CS_PLUS: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DeltaSol_CS_Plus),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_TEMPERATURE_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_3): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_4): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE_5): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
icon=ICON_THERMOMETER,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_SPEED_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_PUMP_SPEED_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
icon=ICON_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_OPERATING_HOURS_1): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HOUR,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_OPERATING_HOURS_2): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HOUR,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_HEAT_QUANTITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS,
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_ENERGY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TIME): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MINUTE,
|
||||
icon=ICON_TIMER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DURATION,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_VERSION): sensor.sensor_schema(
|
||||
accuracy_decimals=2,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_FLOW_RATE): sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_EMPTY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_CUSTOM: cv.COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(VBusCustom),
|
||||
cv.GenerateID(CONF_VBUS_ID): cv.use_id(VBus),
|
||||
cv.Optional(CONF_COMMAND): cv.uint16_t,
|
||||
cv.Optional(CONF_SOURCE): cv.uint16_t,
|
||||
cv.Optional(CONF_DEST): cv.uint16_t,
|
||||
cv.Optional(CONF_SENSORS): cv.ensure_list(
|
||||
sensor.sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(VBusCustomSub),
|
||||
cv.Required(CONF_LAMBDA): cv.lambda_,
|
||||
}
|
||||
)
|
||||
),
|
||||
}
|
||||
),
|
||||
},
|
||||
key=CONF_MODEL,
|
||||
lower=True,
|
||||
space="_",
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if config[CONF_MODEL] == CONF_DELTASOL_BS_PLUS:
|
||||
cg.add(var.set_command(0x0100))
|
||||
cg.add(var.set_source(0x4221))
|
||||
cg.add(var.set_dest(0x0010))
|
||||
if CONF_TEMPERATURE_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_1])
|
||||
cg.add(var.set_temperature1_sensor(sens))
|
||||
if CONF_TEMPERATURE_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_2])
|
||||
cg.add(var.set_temperature2_sensor(sens))
|
||||
if CONF_TEMPERATURE_3 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_3])
|
||||
cg.add(var.set_temperature3_sensor(sens))
|
||||
if CONF_TEMPERATURE_4 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_4])
|
||||
cg.add(var.set_temperature4_sensor(sens))
|
||||
if CONF_PUMP_SPEED_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_1])
|
||||
cg.add(var.set_pump_speed1_sensor(sens))
|
||||
if CONF_PUMP_SPEED_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_2])
|
||||
cg.add(var.set_pump_speed2_sensor(sens))
|
||||
if CONF_OPERATING_HOURS_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_1])
|
||||
cg.add(var.set_operating_hours1_sensor(sens))
|
||||
if CONF_OPERATING_HOURS_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_2])
|
||||
cg.add(var.set_operating_hours2_sensor(sens))
|
||||
if CONF_HEAT_QUANTITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HEAT_QUANTITY])
|
||||
cg.add(var.set_heat_quantity_sensor(sens))
|
||||
if CONF_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TIME])
|
||||
cg.add(var.set_time_sensor(sens))
|
||||
if CONF_VERSION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_VERSION])
|
||||
cg.add(var.set_version_sensor(sens))
|
||||
|
||||
elif config[CONF_MODEL] == CONF_DELTASOL_C:
|
||||
cg.add(var.set_command(0x0100))
|
||||
cg.add(var.set_source(0x4212))
|
||||
cg.add(var.set_dest(0x0010))
|
||||
if CONF_TEMPERATURE_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_1])
|
||||
cg.add(var.set_temperature1_sensor(sens))
|
||||
if CONF_TEMPERATURE_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_2])
|
||||
cg.add(var.set_temperature2_sensor(sens))
|
||||
if CONF_TEMPERATURE_3 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_3])
|
||||
cg.add(var.set_temperature3_sensor(sens))
|
||||
if CONF_TEMPERATURE_4 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_4])
|
||||
cg.add(var.set_temperature4_sensor(sens))
|
||||
if CONF_PUMP_SPEED_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_1])
|
||||
cg.add(var.set_pump_speed1_sensor(sens))
|
||||
if CONF_PUMP_SPEED_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_2])
|
||||
cg.add(var.set_pump_speed2_sensor(sens))
|
||||
if CONF_OPERATING_HOURS_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_1])
|
||||
cg.add(var.set_operating_hours1_sensor(sens))
|
||||
if CONF_OPERATING_HOURS_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_2])
|
||||
cg.add(var.set_operating_hours2_sensor(sens))
|
||||
if CONF_HEAT_QUANTITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HEAT_QUANTITY])
|
||||
cg.add(var.set_heat_quantity_sensor(sens))
|
||||
if CONF_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TIME])
|
||||
cg.add(var.set_time_sensor(sens))
|
||||
|
||||
elif config[CONF_MODEL] == CONF_DELTASOL_CS2:
|
||||
cg.add(var.set_command(0x0100))
|
||||
cg.add(var.set_source(0x1121))
|
||||
cg.add(var.set_dest(0x0010))
|
||||
if CONF_TEMPERATURE_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_1])
|
||||
cg.add(var.set_temperature1_sensor(sens))
|
||||
if CONF_TEMPERATURE_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_2])
|
||||
cg.add(var.set_temperature2_sensor(sens))
|
||||
if CONF_TEMPERATURE_3 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_3])
|
||||
cg.add(var.set_temperature3_sensor(sens))
|
||||
if CONF_TEMPERATURE_4 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_4])
|
||||
cg.add(var.set_temperature4_sensor(sens))
|
||||
if CONF_PUMP_SPEED in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED])
|
||||
cg.add(var.set_pump_speed_sensor(sens))
|
||||
if CONF_OPERATING_HOURS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS])
|
||||
cg.add(var.set_operating_hours_sensor(sens))
|
||||
if CONF_HEAT_QUANTITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HEAT_QUANTITY])
|
||||
cg.add(var.set_heat_quantity_sensor(sens))
|
||||
if CONF_VERSION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_VERSION])
|
||||
cg.add(var.set_version_sensor(sens))
|
||||
|
||||
if config[CONF_MODEL] == CONF_DELTASOL_CS_PLUS:
|
||||
cg.add(var.set_command(0x0100))
|
||||
cg.add(var.set_source(0x2211))
|
||||
cg.add(var.set_dest(0x0010))
|
||||
if CONF_TEMPERATURE_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_1])
|
||||
cg.add(var.set_temperature1_sensor(sens))
|
||||
if CONF_TEMPERATURE_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_2])
|
||||
cg.add(var.set_temperature2_sensor(sens))
|
||||
if CONF_TEMPERATURE_3 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_3])
|
||||
cg.add(var.set_temperature3_sensor(sens))
|
||||
if CONF_TEMPERATURE_4 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_4])
|
||||
cg.add(var.set_temperature4_sensor(sens))
|
||||
if CONF_TEMPERATURE_5 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE_5])
|
||||
cg.add(var.set_temperature5_sensor(sens))
|
||||
if CONF_PUMP_SPEED_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_1])
|
||||
cg.add(var.set_pump_speed1_sensor(sens))
|
||||
if CONF_PUMP_SPEED_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_PUMP_SPEED_2])
|
||||
cg.add(var.set_pump_speed2_sensor(sens))
|
||||
if CONF_OPERATING_HOURS_1 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_1])
|
||||
cg.add(var.set_operating_hours1_sensor(sens))
|
||||
if CONF_OPERATING_HOURS_2 in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OPERATING_HOURS_2])
|
||||
cg.add(var.set_operating_hours2_sensor(sens))
|
||||
if CONF_HEAT_QUANTITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HEAT_QUANTITY])
|
||||
cg.add(var.set_heat_quantity_sensor(sens))
|
||||
if CONF_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TIME])
|
||||
cg.add(var.set_time_sensor(sens))
|
||||
if CONF_VERSION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_VERSION])
|
||||
cg.add(var.set_version_sensor(sens))
|
||||
if CONF_FLOW_RATE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FLOW_RATE])
|
||||
cg.add(var.set_flow_rate_sensor(sens))
|
||||
|
||||
elif config[CONF_MODEL] == CONF_CUSTOM:
|
||||
if CONF_COMMAND in config:
|
||||
cg.add(var.set_command(config[CONF_COMMAND]))
|
||||
if CONF_SOURCE in config:
|
||||
cg.add(var.set_source(config[CONF_SOURCE]))
|
||||
if CONF_DEST in config:
|
||||
cg.add(var.set_dest(config[CONF_DEST]))
|
||||
sensors = []
|
||||
for conf in config[CONF_SENSORS]:
|
||||
sens = await sensor.new_sensor(conf)
|
||||
lambda_ = await cg.process_lambda(
|
||||
conf[CONF_LAMBDA],
|
||||
[(cg.std_vector.template(cg.uint8), "x")],
|
||||
return_type=cg.float_,
|
||||
)
|
||||
cg.add(sens.set_message_parser(lambda_))
|
||||
sensors.append(sens)
|
||||
cg.add(var.set_sensors(sensors))
|
||||
|
||||
vbus = await cg.get_variable(config[CONF_VBUS_ID])
|
||||
cg.add(vbus.register_listener(var))
|
||||
208
esphome/components/vbus/sensor/vbus_sensor.cpp
Normal file
208
esphome/components/vbus/sensor/vbus_sensor.cpp
Normal file
@@ -0,0 +1,208 @@
|
||||
#include "vbus_sensor.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace vbus {
|
||||
|
||||
static const char *const TAG = "vbus.sensor";
|
||||
|
||||
static inline uint16_t get_u16(std::vector<uint8_t> &message, int start) {
|
||||
return (message[start + 1] << 8) + message[start];
|
||||
}
|
||||
|
||||
static inline int16_t get_i16(std::vector<uint8_t> &message, int start) {
|
||||
return (int16_t)((message[start + 1] << 8) + message[start]);
|
||||
}
|
||||
|
||||
void DeltaSolBSPlusSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Deltasol BS Plus:");
|
||||
LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 2", this->temperature2_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 3", this->temperature3_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 4", this->temperature4_sensor_);
|
||||
LOG_SENSOR(" ", "Pump Speed 1", this->pump_speed1_sensor_);
|
||||
LOG_SENSOR(" ", "Pump Speed 2", this->pump_speed2_sensor_);
|
||||
LOG_SENSOR(" ", "Operating Hours 1", this->operating_hours1_sensor_);
|
||||
LOG_SENSOR(" ", "Operating Hours 2", this->operating_hours2_sensor_);
|
||||
LOG_SENSOR(" ", "Heat Quantity", this->heat_quantity_sensor_);
|
||||
LOG_SENSOR(" ", "System Time", this->time_sensor_);
|
||||
LOG_SENSOR(" ", "FW Version", this->version_sensor_);
|
||||
}
|
||||
|
||||
void DeltaSolBSPlusSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
if (this->temperature1_sensor_ != nullptr)
|
||||
this->temperature1_sensor_->publish_state(get_i16(message, 0) * 0.1f);
|
||||
if (this->temperature2_sensor_ != nullptr)
|
||||
this->temperature2_sensor_->publish_state(get_i16(message, 2) * 0.1f);
|
||||
if (this->temperature3_sensor_ != nullptr)
|
||||
this->temperature3_sensor_->publish_state(get_i16(message, 4) * 0.1f);
|
||||
if (this->temperature4_sensor_ != nullptr)
|
||||
this->temperature4_sensor_->publish_state(get_i16(message, 6) * 0.1f);
|
||||
if (this->pump_speed1_sensor_ != nullptr)
|
||||
this->pump_speed1_sensor_->publish_state(message[8]);
|
||||
if (this->pump_speed2_sensor_ != nullptr)
|
||||
this->pump_speed2_sensor_->publish_state(message[9]);
|
||||
if (this->operating_hours1_sensor_ != nullptr)
|
||||
this->operating_hours1_sensor_->publish_state(get_u16(message, 16));
|
||||
if (this->operating_hours2_sensor_ != nullptr)
|
||||
this->operating_hours2_sensor_->publish_state(get_u16(message, 18));
|
||||
if (this->heat_quantity_sensor_ != nullptr) {
|
||||
this->heat_quantity_sensor_->publish_state(get_u16(message, 20) + get_u16(message, 22) * 1000 +
|
||||
get_u16(message, 24) * 1000000);
|
||||
}
|
||||
if (this->time_sensor_ != nullptr)
|
||||
this->time_sensor_->publish_state(get_u16(message, 12));
|
||||
if (this->version_sensor_ != nullptr)
|
||||
this->version_sensor_->publish_state(get_u16(message, 26) * 0.01f);
|
||||
}
|
||||
|
||||
void DeltaSolCSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Deltasol C:");
|
||||
LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 2", this->temperature2_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 3", this->temperature3_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 4", this->temperature4_sensor_);
|
||||
LOG_SENSOR(" ", "Pump Speed 1", this->pump_speed1_sensor_);
|
||||
LOG_SENSOR(" ", "Pump Speed 2", this->pump_speed2_sensor_);
|
||||
LOG_SENSOR(" ", "Operating Hours 1", this->operating_hours1_sensor_);
|
||||
LOG_SENSOR(" ", "Operating Hours 2", this->operating_hours2_sensor_);
|
||||
LOG_SENSOR(" ", "Heat Quantity", this->heat_quantity_sensor_);
|
||||
LOG_SENSOR(" ", "System Time", this->time_sensor_);
|
||||
}
|
||||
|
||||
void DeltaSolCSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
if (this->temperature1_sensor_ != nullptr)
|
||||
this->temperature1_sensor_->publish_state(get_i16(message, 0) * 0.1f);
|
||||
if (this->temperature2_sensor_ != nullptr)
|
||||
this->temperature2_sensor_->publish_state(get_i16(message, 2) * 0.1f);
|
||||
if (this->temperature3_sensor_ != nullptr)
|
||||
this->temperature3_sensor_->publish_state(get_i16(message, 4) * 0.1f);
|
||||
if (this->temperature4_sensor_ != nullptr)
|
||||
this->temperature4_sensor_->publish_state(get_i16(message, 6) * 0.1f);
|
||||
if (this->pump_speed1_sensor_ != nullptr)
|
||||
this->pump_speed1_sensor_->publish_state(message[8]);
|
||||
if (this->pump_speed2_sensor_ != nullptr)
|
||||
this->pump_speed2_sensor_->publish_state(message[9]);
|
||||
if (this->operating_hours1_sensor_ != nullptr)
|
||||
this->operating_hours1_sensor_->publish_state(get_u16(message, 12));
|
||||
if (this->operating_hours2_sensor_ != nullptr)
|
||||
this->operating_hours2_sensor_->publish_state(get_u16(message, 14));
|
||||
if (this->heat_quantity_sensor_ != nullptr) {
|
||||
this->heat_quantity_sensor_->publish_state(get_u16(message, 16) + get_u16(message, 18) * 1000 +
|
||||
get_u16(message, 20) * 1000000);
|
||||
}
|
||||
if (this->time_sensor_ != nullptr)
|
||||
this->time_sensor_->publish_state(get_u16(message, 22));
|
||||
}
|
||||
|
||||
void DeltaSolCS2Sensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Deltasol CS2:");
|
||||
LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 2", this->temperature2_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 3", this->temperature3_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 4", this->temperature4_sensor_);
|
||||
LOG_SENSOR(" ", "Pump Speed", this->pump_speed_sensor_);
|
||||
LOG_SENSOR(" ", "Operating Hours", this->operating_hours_sensor_);
|
||||
LOG_SENSOR(" ", "Heat Quantity", this->heat_quantity_sensor_);
|
||||
LOG_SENSOR(" ", "FW Version", this->version_sensor_);
|
||||
}
|
||||
|
||||
void DeltaSolCS2Sensor::handle_message(std::vector<uint8_t> &message) {
|
||||
if (this->temperature1_sensor_ != nullptr)
|
||||
this->temperature1_sensor_->publish_state(get_i16(message, 0) * 0.1f);
|
||||
if (this->temperature2_sensor_ != nullptr)
|
||||
this->temperature2_sensor_->publish_state(get_i16(message, 2) * 0.1f);
|
||||
if (this->temperature3_sensor_ != nullptr)
|
||||
this->temperature3_sensor_->publish_state(get_i16(message, 4) * 0.1f);
|
||||
if (this->temperature4_sensor_ != nullptr)
|
||||
this->temperature4_sensor_->publish_state(get_i16(message, 6) * 0.1f);
|
||||
if (this->pump_speed_sensor_ != nullptr)
|
||||
this->pump_speed_sensor_->publish_state(message[12]);
|
||||
if (this->operating_hours_sensor_ != nullptr)
|
||||
this->operating_hours_sensor_->publish_state(get_u16(message, 14));
|
||||
if (this->heat_quantity_sensor_ != nullptr)
|
||||
this->heat_quantity_sensor_->publish_state((get_u16(message, 26) << 16) + get_u16(message, 24));
|
||||
if (this->version_sensor_ != nullptr)
|
||||
this->version_sensor_->publish_state(get_u16(message, 28) * 0.01f);
|
||||
}
|
||||
|
||||
void DeltaSolCSPlusSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Deltasol CS Plus:");
|
||||
LOG_SENSOR(" ", "Temperature 1", this->temperature1_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 2", this->temperature2_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 3", this->temperature3_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 4", this->temperature4_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature 5", this->temperature5_sensor_);
|
||||
LOG_SENSOR(" ", "Pump Speed 1", this->pump_speed1_sensor_);
|
||||
LOG_SENSOR(" ", "Pump Speed 2", this->pump_speed2_sensor_);
|
||||
LOG_SENSOR(" ", "Operating Hours 1", this->operating_hours1_sensor_);
|
||||
LOG_SENSOR(" ", "Operating Hours 2", this->operating_hours2_sensor_);
|
||||
LOG_SENSOR(" ", "Heat Quantity", this->heat_quantity_sensor_);
|
||||
LOG_SENSOR(" ", "System Time", this->time_sensor_);
|
||||
LOG_SENSOR(" ", "FW Version", this->version_sensor_);
|
||||
LOG_SENSOR(" ", "Flow Rate", this->flow_rate_sensor_);
|
||||
}
|
||||
|
||||
void DeltaSolCSPlusSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
if (this->temperature1_sensor_ != nullptr)
|
||||
this->temperature1_sensor_->publish_state(get_i16(message, 0) * 0.1f);
|
||||
if (this->temperature2_sensor_ != nullptr)
|
||||
this->temperature2_sensor_->publish_state(get_i16(message, 2) * 0.1f);
|
||||
if (this->temperature3_sensor_ != nullptr)
|
||||
this->temperature3_sensor_->publish_state(get_i16(message, 4) * 0.1f);
|
||||
if (this->temperature4_sensor_ != nullptr)
|
||||
this->temperature4_sensor_->publish_state(get_i16(message, 6) * 0.1f);
|
||||
if (this->temperature5_sensor_ != nullptr)
|
||||
this->temperature5_sensor_->publish_state(get_i16(message, 36) * 0.1f);
|
||||
if (this->pump_speed1_sensor_ != nullptr)
|
||||
this->pump_speed1_sensor_->publish_state(message[8]);
|
||||
if (this->pump_speed2_sensor_ != nullptr)
|
||||
this->pump_speed2_sensor_->publish_state(message[12]);
|
||||
if (this->operating_hours1_sensor_ != nullptr)
|
||||
this->operating_hours1_sensor_->publish_state(get_u16(message, 10));
|
||||
if (this->operating_hours2_sensor_ != nullptr)
|
||||
this->operating_hours2_sensor_->publish_state(get_u16(message, 14));
|
||||
if (this->heat_quantity_sensor_ != nullptr)
|
||||
this->heat_quantity_sensor_->publish_state((get_u16(message, 30) << 16) + get_u16(message, 28));
|
||||
if (this->time_sensor_ != nullptr)
|
||||
this->time_sensor_->publish_state(get_u16(message, 12));
|
||||
if (this->version_sensor_ != nullptr)
|
||||
this->version_sensor_->publish_state(get_u16(message, 26) * 0.01f);
|
||||
if (this->flow_rate_sensor_ != nullptr)
|
||||
this->flow_rate_sensor_->publish_state(get_u16(message, 38));
|
||||
}
|
||||
|
||||
void VBusCustomSensor::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "VBus Custom Sensor:");
|
||||
if (this->source_ == 0xffff) {
|
||||
ESP_LOGCONFIG(TAG, " Source address: ANY");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Source address: 0x%04x", this->source_);
|
||||
}
|
||||
if (this->dest_ == 0xffff) {
|
||||
ESP_LOGCONFIG(TAG, " Dest address: ANY");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Dest address: 0x%04x", this->dest_);
|
||||
}
|
||||
if (this->command_ == 0xffff) {
|
||||
ESP_LOGCONFIG(TAG, " Command: ANY");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, " Command: 0x%04x", this->command_);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Sensors:");
|
||||
for (VBusCustomSubSensor *sensor : this->sensors_)
|
||||
LOG_SENSOR(" ", "-", sensor);
|
||||
}
|
||||
|
||||
void VBusCustomSensor::handle_message(std::vector<uint8_t> &message) {
|
||||
for (VBusCustomSubSensor *sensor : this->sensors_)
|
||||
sensor->parse_message(message);
|
||||
}
|
||||
|
||||
void VBusCustomSubSensor::parse_message(std::vector<uint8_t> &message) {
|
||||
this->publish_state(this->message_parser_(message));
|
||||
}
|
||||
|
||||
} // namespace vbus
|
||||
} // namespace esphome
|
||||
151
esphome/components/vbus/sensor/vbus_sensor.h
Normal file
151
esphome/components/vbus/sensor/vbus_sensor.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include "../vbus.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace vbus {
|
||||
|
||||
class DeltaSolBSPlusSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_temperature1_sensor(sensor::Sensor *sensor) { this->temperature1_sensor_ = sensor; }
|
||||
void set_temperature2_sensor(sensor::Sensor *sensor) { this->temperature2_sensor_ = sensor; }
|
||||
void set_temperature3_sensor(sensor::Sensor *sensor) { this->temperature3_sensor_ = sensor; }
|
||||
void set_temperature4_sensor(sensor::Sensor *sensor) { this->temperature4_sensor_ = sensor; }
|
||||
void set_pump_speed1_sensor(sensor::Sensor *sensor) { this->pump_speed1_sensor_ = sensor; }
|
||||
void set_pump_speed2_sensor(sensor::Sensor *sensor) { this->pump_speed2_sensor_ = sensor; }
|
||||
void set_operating_hours1_sensor(sensor::Sensor *sensor) { this->operating_hours1_sensor_ = sensor; }
|
||||
void set_operating_hours2_sensor(sensor::Sensor *sensor) { this->operating_hours2_sensor_ = sensor; }
|
||||
void set_heat_quantity_sensor(sensor::Sensor *sensor) { this->heat_quantity_sensor_ = sensor; }
|
||||
void set_time_sensor(sensor::Sensor *sensor) { this->time_sensor_ = sensor; }
|
||||
void set_version_sensor(sensor::Sensor *sensor) { this->version_sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature1_sensor_{nullptr};
|
||||
sensor::Sensor *temperature2_sensor_{nullptr};
|
||||
sensor::Sensor *temperature3_sensor_{nullptr};
|
||||
sensor::Sensor *temperature4_sensor_{nullptr};
|
||||
sensor::Sensor *pump_speed1_sensor_{nullptr};
|
||||
sensor::Sensor *pump_speed2_sensor_{nullptr};
|
||||
sensor::Sensor *operating_hours1_sensor_{nullptr};
|
||||
sensor::Sensor *operating_hours2_sensor_{nullptr};
|
||||
sensor::Sensor *heat_quantity_sensor_{nullptr};
|
||||
sensor::Sensor *time_sensor_{nullptr};
|
||||
sensor::Sensor *version_sensor_{nullptr};
|
||||
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class DeltaSolCSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_temperature1_sensor(sensor::Sensor *sensor) { this->temperature1_sensor_ = sensor; }
|
||||
void set_temperature2_sensor(sensor::Sensor *sensor) { this->temperature2_sensor_ = sensor; }
|
||||
void set_temperature3_sensor(sensor::Sensor *sensor) { this->temperature3_sensor_ = sensor; }
|
||||
void set_temperature4_sensor(sensor::Sensor *sensor) { this->temperature4_sensor_ = sensor; }
|
||||
void set_pump_speed1_sensor(sensor::Sensor *sensor) { this->pump_speed1_sensor_ = sensor; }
|
||||
void set_pump_speed2_sensor(sensor::Sensor *sensor) { this->pump_speed2_sensor_ = sensor; }
|
||||
void set_operating_hours1_sensor(sensor::Sensor *sensor) { this->operating_hours1_sensor_ = sensor; }
|
||||
void set_operating_hours2_sensor(sensor::Sensor *sensor) { this->operating_hours2_sensor_ = sensor; }
|
||||
void set_heat_quantity_sensor(sensor::Sensor *sensor) { this->heat_quantity_sensor_ = sensor; }
|
||||
void set_time_sensor(sensor::Sensor *sensor) { this->time_sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature1_sensor_{nullptr};
|
||||
sensor::Sensor *temperature2_sensor_{nullptr};
|
||||
sensor::Sensor *temperature3_sensor_{nullptr};
|
||||
sensor::Sensor *temperature4_sensor_{nullptr};
|
||||
sensor::Sensor *pump_speed1_sensor_{nullptr};
|
||||
sensor::Sensor *pump_speed2_sensor_{nullptr};
|
||||
sensor::Sensor *operating_hours1_sensor_{nullptr};
|
||||
sensor::Sensor *operating_hours2_sensor_{nullptr};
|
||||
sensor::Sensor *heat_quantity_sensor_{nullptr};
|
||||
sensor::Sensor *time_sensor_{nullptr};
|
||||
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class DeltaSolCS2Sensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_temperature1_sensor(sensor::Sensor *sensor) { this->temperature1_sensor_ = sensor; }
|
||||
void set_temperature2_sensor(sensor::Sensor *sensor) { this->temperature2_sensor_ = sensor; }
|
||||
void set_temperature3_sensor(sensor::Sensor *sensor) { this->temperature3_sensor_ = sensor; }
|
||||
void set_temperature4_sensor(sensor::Sensor *sensor) { this->temperature4_sensor_ = sensor; }
|
||||
void set_pump_speed_sensor(sensor::Sensor *sensor) { this->pump_speed_sensor_ = sensor; }
|
||||
void set_operating_hours_sensor(sensor::Sensor *sensor) { this->operating_hours_sensor_ = sensor; }
|
||||
void set_heat_quantity_sensor(sensor::Sensor *sensor) { this->heat_quantity_sensor_ = sensor; }
|
||||
void set_version_sensor(sensor::Sensor *sensor) { this->version_sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature1_sensor_{nullptr};
|
||||
sensor::Sensor *temperature2_sensor_{nullptr};
|
||||
sensor::Sensor *temperature3_sensor_{nullptr};
|
||||
sensor::Sensor *temperature4_sensor_{nullptr};
|
||||
sensor::Sensor *pump_speed_sensor_{nullptr};
|
||||
sensor::Sensor *operating_hours_sensor_{nullptr};
|
||||
sensor::Sensor *heat_quantity_sensor_{nullptr};
|
||||
sensor::Sensor *version_sensor_{nullptr};
|
||||
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class DeltaSolCSPlusSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_temperature1_sensor(sensor::Sensor *sensor) { this->temperature1_sensor_ = sensor; }
|
||||
void set_temperature2_sensor(sensor::Sensor *sensor) { this->temperature2_sensor_ = sensor; }
|
||||
void set_temperature3_sensor(sensor::Sensor *sensor) { this->temperature3_sensor_ = sensor; }
|
||||
void set_temperature4_sensor(sensor::Sensor *sensor) { this->temperature4_sensor_ = sensor; }
|
||||
void set_temperature5_sensor(sensor::Sensor *sensor) { this->temperature5_sensor_ = sensor; }
|
||||
void set_pump_speed1_sensor(sensor::Sensor *sensor) { this->pump_speed1_sensor_ = sensor; }
|
||||
void set_pump_speed2_sensor(sensor::Sensor *sensor) { this->pump_speed2_sensor_ = sensor; }
|
||||
void set_operating_hours1_sensor(sensor::Sensor *sensor) { this->operating_hours1_sensor_ = sensor; }
|
||||
void set_operating_hours2_sensor(sensor::Sensor *sensor) { this->operating_hours2_sensor_ = sensor; }
|
||||
void set_heat_quantity_sensor(sensor::Sensor *sensor) { this->heat_quantity_sensor_ = sensor; }
|
||||
void set_time_sensor(sensor::Sensor *sensor) { this->time_sensor_ = sensor; }
|
||||
void set_version_sensor(sensor::Sensor *sensor) { this->version_sensor_ = sensor; }
|
||||
void set_flow_rate_sensor(sensor::Sensor *sensor) { this->flow_rate_sensor_ = sensor; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature1_sensor_{nullptr};
|
||||
sensor::Sensor *temperature2_sensor_{nullptr};
|
||||
sensor::Sensor *temperature3_sensor_{nullptr};
|
||||
sensor::Sensor *temperature4_sensor_{nullptr};
|
||||
sensor::Sensor *temperature5_sensor_{nullptr};
|
||||
sensor::Sensor *pump_speed1_sensor_{nullptr};
|
||||
sensor::Sensor *pump_speed2_sensor_{nullptr};
|
||||
sensor::Sensor *operating_hours1_sensor_{nullptr};
|
||||
sensor::Sensor *operating_hours2_sensor_{nullptr};
|
||||
sensor::Sensor *heat_quantity_sensor_{nullptr};
|
||||
sensor::Sensor *time_sensor_{nullptr};
|
||||
sensor::Sensor *version_sensor_{nullptr};
|
||||
sensor::Sensor *flow_rate_sensor_{nullptr};
|
||||
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class VBusCustomSubSensor;
|
||||
|
||||
class VBusCustomSensor : public VBusListener, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void set_sensors(std::vector<VBusCustomSubSensor *> sensors) { this->sensors_ = std::move(sensors); };
|
||||
|
||||
protected:
|
||||
std::vector<VBusCustomSubSensor *> sensors_;
|
||||
void handle_message(std::vector<uint8_t> &message) override;
|
||||
};
|
||||
|
||||
class VBusCustomSubSensor : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void set_message_parser(message_parser_t parser) { this->message_parser_ = std::move(parser); };
|
||||
void parse_message(std::vector<uint8_t> &message);
|
||||
|
||||
protected:
|
||||
message_parser_t message_parser_;
|
||||
};
|
||||
|
||||
} // namespace vbus
|
||||
} // namespace esphome
|
||||
124
esphome/components/vbus/vbus.cpp
Normal file
124
esphome/components/vbus/vbus.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "vbus.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace vbus {
|
||||
|
||||
static const char *const TAG = "vbus";
|
||||
|
||||
void VBus::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "VBus:");
|
||||
check_uart_settings(9600);
|
||||
}
|
||||
|
||||
static void septet_spread(uint8_t *data, int start, int count, uint8_t septet) {
|
||||
for (int i = 0; i < count; i++, septet >>= 1) {
|
||||
if (septet & 1)
|
||||
data[start + i] |= 0x80;
|
||||
}
|
||||
}
|
||||
|
||||
static bool checksum(const uint8_t *data, int start, int count) {
|
||||
uint8_t csum = 0x7f;
|
||||
for (int i = 0; i < count; i++)
|
||||
csum = (csum - data[start + i]) & 0x7f;
|
||||
return csum == 0;
|
||||
}
|
||||
|
||||
void VBus::loop() {
|
||||
if (!available())
|
||||
return;
|
||||
|
||||
while (available()) {
|
||||
uint8_t c;
|
||||
read_byte(&c);
|
||||
|
||||
if (c == 0xaa) {
|
||||
this->state_ = 1;
|
||||
this->buffer_.clear();
|
||||
continue;
|
||||
}
|
||||
if (c & 0x80) {
|
||||
this->state_ = 0;
|
||||
continue;
|
||||
}
|
||||
if (this->state_ == 0)
|
||||
continue;
|
||||
|
||||
if (this->state_ == 1) {
|
||||
this->buffer_.push_back(c);
|
||||
if (this->buffer_.size() == 7) {
|
||||
this->protocol_ = this->buffer_[4];
|
||||
this->source_ = (this->buffer_[3] << 8) + this->buffer_[2];
|
||||
this->dest_ = (this->buffer_[1] << 8) + this->buffer_[0];
|
||||
this->command_ = (this->buffer_[6] << 8) + this->buffer_[5];
|
||||
}
|
||||
if ((this->protocol_ == 0x20) && (this->buffer_.size() == 15)) {
|
||||
this->state_ = 0;
|
||||
if (!checksum(this->buffer_.data(), 0, 15)) {
|
||||
ESP_LOGE(TAG, "P2 checksum failed");
|
||||
continue;
|
||||
}
|
||||
septet_spread(this->buffer_.data(), 7, 6, this->buffer_[13]);
|
||||
uint16_t id = (this->buffer_[8] << 8) + this->buffer_[7];
|
||||
uint32_t value =
|
||||
(this->buffer_[12] << 24) + (this->buffer_[11] << 16) + (this->buffer_[10] << 8) + this->buffer_[9];
|
||||
ESP_LOGV(TAG, "P1 C%04x %04x->%04x: %04x %04x (%d)", this->command_, this->source_, this->dest_, id, value,
|
||||
value);
|
||||
} else if ((this->protocol_ == 0x10) && (this->buffer_.size() == 9)) {
|
||||
if (!checksum(this->buffer_.data(), 0, 9)) {
|
||||
ESP_LOGE(TAG, "P1 checksum failed");
|
||||
this->state_ = 0;
|
||||
continue;
|
||||
}
|
||||
this->frames_ = this->buffer_[7];
|
||||
if (this->frames_) {
|
||||
this->state_ = 2;
|
||||
this->cframe_ = 0;
|
||||
this->fbcount_ = 0;
|
||||
this->buffer_.clear();
|
||||
} else {
|
||||
this->state_ = 0;
|
||||
ESP_LOGD(TAG, "P1 empty message");
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this->state_ == 2) {
|
||||
this->fbytes_[this->fbcount_++] = c;
|
||||
if (this->fbcount_ < 6)
|
||||
continue;
|
||||
this->fbcount_ = 0;
|
||||
if (!checksum(this->fbytes_, 0, 6)) {
|
||||
ESP_LOGE(TAG, "frame checksum failed");
|
||||
continue;
|
||||
}
|
||||
septet_spread(this->fbytes_, 0, 4, this->fbytes_[4]);
|
||||
for (int i = 0; i < 4; i++)
|
||||
this->buffer_.push_back(this->fbytes_[i]);
|
||||
if (++this->cframe_ < this->frames_)
|
||||
continue;
|
||||
ESP_LOGV(TAG, "P2 C%04x %04x->%04x: %s", this->command_, this->source_, this->dest_,
|
||||
format_hex(this->buffer_).c_str());
|
||||
for (auto &listener : this->listeners_)
|
||||
listener->on_message(this->command_, this->source_, this->dest_, this->buffer_);
|
||||
this->state_ = 0;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VBusListener::on_message(uint16_t command, uint16_t source, uint16_t dest, std::vector<uint8_t> &message) {
|
||||
if ((this->command_ != 0xffff) && (this->command_ != command))
|
||||
return;
|
||||
if ((this->source_ != 0xffff) && (this->source_ != source))
|
||||
return;
|
||||
if ((this->dest_ != 0xffff) && (this->dest_ != dest))
|
||||
return;
|
||||
this->handle_message(message);
|
||||
}
|
||||
|
||||
} // namespace vbus
|
||||
} // namespace esphome
|
||||
52
esphome/components/vbus/vbus.h
Normal file
52
esphome/components/vbus/vbus.h
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace vbus {
|
||||
|
||||
using message_parser_t = std::function<float(std::vector<uint8_t> &)>;
|
||||
|
||||
class VBus;
|
||||
|
||||
class VBusListener {
|
||||
public:
|
||||
void set_command(uint16_t command) { this->command_ = command; }
|
||||
void set_source(uint16_t source) { this->source_ = source; }
|
||||
void set_dest(uint16_t dest) { this->dest_ = dest; }
|
||||
|
||||
void on_message(uint16_t command, uint16_t source, uint16_t dest, std::vector<uint8_t> &message);
|
||||
|
||||
protected:
|
||||
uint16_t command_{0xffff};
|
||||
uint16_t source_{0xffff};
|
||||
uint16_t dest_{0xffff};
|
||||
|
||||
virtual void handle_message(std::vector<uint8_t> &message) = 0;
|
||||
};
|
||||
|
||||
class VBus : public uart::UARTDevice, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
void loop() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
void register_listener(VBusListener *listener) { this->listeners_.push_back(listener); }
|
||||
|
||||
protected:
|
||||
int state_{0};
|
||||
std::vector<uint8_t> buffer_;
|
||||
uint8_t protocol_;
|
||||
uint16_t source_;
|
||||
uint16_t dest_;
|
||||
uint16_t command_;
|
||||
uint8_t frames_;
|
||||
uint8_t cframe_;
|
||||
uint8_t fbytes_[6];
|
||||
int fbcount_;
|
||||
std::vector<VBusListener *> listeners_{};
|
||||
};
|
||||
|
||||
} // namespace vbus
|
||||
} // namespace esphome
|
||||
@@ -14,6 +14,7 @@ from esphome import core, yaml_util, loader
|
||||
import esphome.core.config as core_config
|
||||
from esphome.const import (
|
||||
CONF_ESPHOME,
|
||||
CONF_ID,
|
||||
CONF_PLATFORM,
|
||||
CONF_PACKAGES,
|
||||
CONF_SUBSTITUTIONS,
|
||||
@@ -24,6 +25,7 @@ from esphome.core import CORE, EsphomeError
|
||||
from esphome.helpers import indent
|
||||
from esphome.util import safe_print, OrderedDict
|
||||
|
||||
from esphome.config_helpers import Extend
|
||||
from esphome.loader import get_component, get_platform, ComponentManifest
|
||||
from esphome.yaml_util import is_secret, ESPHomeDataBase, ESPForceValue
|
||||
from esphome.voluptuous_schema import ExtraKeysInvalid
|
||||
@@ -334,6 +336,13 @@ class LoadValidationStep(ConfigValidationStep):
|
||||
continue
|
||||
p_name = p_config.get("platform")
|
||||
if p_name is None:
|
||||
p_id = p_config.get(CONF_ID)
|
||||
if isinstance(p_id, Extend):
|
||||
result.add_str_error(
|
||||
f"Source for extension of ID '{p_id.value}' was not found.",
|
||||
path + [CONF_ID],
|
||||
)
|
||||
continue
|
||||
result.add_str_error("No platform specified! See 'platform' key.", path)
|
||||
continue
|
||||
# Remove temp output path and construct new one
|
||||
|
||||
@@ -1,10 +1,27 @@
|
||||
import json
|
||||
import os
|
||||
|
||||
from esphome.const import CONF_ID
|
||||
from esphome.core import CORE
|
||||
from esphome.helpers import read_file
|
||||
|
||||
|
||||
class Extend:
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return f"!extend {self.value}"
|
||||
|
||||
def __eq__(self, b):
|
||||
"""
|
||||
Check if two Extend objects contain the same ID.
|
||||
|
||||
Only used in unit tests.
|
||||
"""
|
||||
return isinstance(b, Extend) and self.value == b.value
|
||||
|
||||
|
||||
def read_config_file(path: str) -> str:
|
||||
if CORE.vscode and (
|
||||
not CORE.ace or os.path.abspath(path) == os.path.abspath(CORE.config_path)
|
||||
@@ -36,7 +53,25 @@ def merge_config(full_old, full_new):
|
||||
if isinstance(new, list):
|
||||
if not isinstance(old, list):
|
||||
return new
|
||||
return old + new
|
||||
res = old.copy()
|
||||
ids = {
|
||||
v[CONF_ID]: i
|
||||
for i, v in enumerate(res)
|
||||
if CONF_ID in v and isinstance(v[CONF_ID], str)
|
||||
}
|
||||
for v in new:
|
||||
if CONF_ID in v:
|
||||
new_id = v[CONF_ID]
|
||||
if isinstance(new_id, Extend):
|
||||
new_id = new_id.value
|
||||
if new_id in ids:
|
||||
v[CONF_ID] = new_id
|
||||
res[ids[new_id]] = merge(res[ids[new_id]], v)
|
||||
continue
|
||||
else:
|
||||
ids[new_id] = len(res)
|
||||
res.append(v)
|
||||
return res
|
||||
if new is None:
|
||||
return old
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import voluptuous as vol
|
||||
|
||||
from esphome import core
|
||||
import esphome.codegen as cg
|
||||
from esphome.config_helpers import Extend
|
||||
from esphome.const import (
|
||||
ALLOWED_NAME_CHARS,
|
||||
CONF_AVAILABILITY,
|
||||
@@ -490,6 +491,8 @@ def declare_id(type):
|
||||
if value is None:
|
||||
return core.ID(None, is_declaration=True, type=type)
|
||||
|
||||
if isinstance(value, Extend):
|
||||
raise Invalid(f"Source for extension of ID '{value.value}' was not found.")
|
||||
return core.ID(validate_id_name(value), is_declaration=True, type=type)
|
||||
|
||||
return validator
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2023.2.0-dev"
|
||||
__version__ = "2023.2.2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
|
||||
@@ -12,7 +12,7 @@ TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_RP2040]
|
||||
|
||||
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
|
||||
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
|
||||
SECRETS_FILES = {"secrets.yaml", "secrets.yml"}
|
||||
SECRETS_FILES = ("secrets.yaml", "secrets.yml")
|
||||
|
||||
|
||||
CONF_ABOVE = "above"
|
||||
@@ -141,6 +141,7 @@ CONF_CURRENT = "current"
|
||||
CONF_CURRENT_OPERATION = "current_operation"
|
||||
CONF_CURRENT_RESISTOR = "current_resistor"
|
||||
CONF_CURRENT_TEMPERATURE_STATE_TOPIC = "current_temperature_state_topic"
|
||||
CONF_CUSTOM = "custom"
|
||||
CONF_CUSTOM_FAN_MODE = "custom_fan_mode"
|
||||
CONF_CUSTOM_FAN_MODES = "custom_fan_modes"
|
||||
CONF_CUSTOM_PRESET = "custom_preset"
|
||||
@@ -167,6 +168,7 @@ CONF_DEFAULT_TRANSITION_LENGTH = "default_transition_length"
|
||||
CONF_DELAY = "delay"
|
||||
CONF_DELIMITER = "delimiter"
|
||||
CONF_DELTA = "delta"
|
||||
CONF_DEST = "dest"
|
||||
CONF_DEVICE = "device"
|
||||
CONF_DEVICE_CLASS = "device_class"
|
||||
CONF_DEVICE_FACTOR = "device_factor"
|
||||
@@ -235,6 +237,7 @@ CONF_FAN_MODE_MEDIUM_ACTION = "fan_mode_medium_action"
|
||||
CONF_FAN_MODE_MIDDLE_ACTION = "fan_mode_middle_action"
|
||||
CONF_FAN_MODE_OFF_ACTION = "fan_mode_off_action"
|
||||
CONF_FAN_MODE_ON_ACTION = "fan_mode_on_action"
|
||||
CONF_FAN_MODE_QUIET_ACTION = "fan_mode_quiet_action"
|
||||
CONF_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic"
|
||||
CONF_FAN_ONLY_ACTION = "fan_only_action"
|
||||
CONF_FAN_ONLY_ACTION_USES_FAN_MODE_TIMER = "fan_only_action_uses_fan_mode_timer"
|
||||
@@ -548,8 +551,10 @@ CONF_POWER_SAVE_MODE = "power_save_mode"
|
||||
CONF_POWER_SUPPLY = "power_supply"
|
||||
CONF_PRESET = "preset"
|
||||
CONF_PRESET_BOOST = "preset_boost"
|
||||
CONF_PRESET_COMMAND_TOPIC = "preset_command_topic"
|
||||
CONF_PRESET_ECO = "preset_eco"
|
||||
CONF_PRESET_SLEEP = "preset_sleep"
|
||||
CONF_PRESET_STATE_TOPIC = "preset_state_topic"
|
||||
CONF_PRESSURE = "pressure"
|
||||
CONF_PRIORITY = "priority"
|
||||
CONF_PROJECT = "project"
|
||||
@@ -862,6 +867,7 @@ UNIT_AMPERE = "A"
|
||||
UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³"
|
||||
UNIT_BYTES = "B"
|
||||
UNIT_CELSIUS = "°C"
|
||||
UNIT_CENTIMETER = "cm"
|
||||
UNIT_COUNT_DECILITRE = "/dL"
|
||||
UNIT_COUNTS_PER_CUBIC_METER = "#/m³"
|
||||
UNIT_CUBIC_METER = "m³"
|
||||
@@ -873,6 +879,7 @@ UNIT_EMPTY = ""
|
||||
UNIT_G = "G"
|
||||
UNIT_HECTOPASCAL = "hPa"
|
||||
UNIT_HERTZ = "Hz"
|
||||
UNIT_HOUR = "h"
|
||||
UNIT_KELVIN = "K"
|
||||
UNIT_KILOGRAM = "kg"
|
||||
UNIT_KILOMETER = "km"
|
||||
|
||||
@@ -55,6 +55,7 @@ class DashboardSettings:
|
||||
self.using_password = False
|
||||
self.on_ha_addon = False
|
||||
self.cookie_secret = None
|
||||
self.absolute_config_dir = None
|
||||
|
||||
def parse_args(self, args):
|
||||
self.on_ha_addon = args.ha_addon
|
||||
@@ -65,6 +66,7 @@ class DashboardSettings:
|
||||
if self.using_password:
|
||||
self.password_hash = password_hash(password)
|
||||
self.config_dir = args.configuration
|
||||
self.absolute_config_dir = Path(self.config_dir).resolve()
|
||||
|
||||
@property
|
||||
def relative_url(self):
|
||||
@@ -94,7 +96,10 @@ class DashboardSettings:
|
||||
return hmac.compare_digest(self.password_hash, password_hash(password))
|
||||
|
||||
def rel_path(self, *args):
|
||||
return os.path.join(self.config_dir, *args)
|
||||
joined_path = os.path.join(self.config_dir, *args)
|
||||
# Raises ValueError if not relative to ESPHome config folder
|
||||
Path(joined_path).resolve().relative_to(self.absolute_config_dir)
|
||||
return joined_path
|
||||
|
||||
def list_yaml_files(self):
|
||||
return util.list_yaml_files([self.config_dir])
|
||||
@@ -433,6 +438,7 @@ class ImportRequestHandler(BaseHandler):
|
||||
try:
|
||||
name = args["name"]
|
||||
friendly_name = args.get("friendly_name")
|
||||
encryption = args.get("encryption", False)
|
||||
|
||||
imported_device = next(
|
||||
(res for res in IMPORT_RESULT.values() if res.device_name == name), None
|
||||
@@ -452,6 +458,7 @@ class ImportRequestHandler(BaseHandler):
|
||||
args["project_name"],
|
||||
args["package_import_url"],
|
||||
network,
|
||||
encryption,
|
||||
)
|
||||
except FileExistsError:
|
||||
self.set_status(500)
|
||||
@@ -538,35 +545,11 @@ class DownloadBinaryRequestHandler(BaseHandler):
|
||||
self.finish()
|
||||
|
||||
|
||||
class ManifestRequestHandler(BaseHandler):
|
||||
class EsphomeVersionHandler(BaseHandler):
|
||||
@authenticated
|
||||
@bind_config
|
||||
def get(self, configuration=None):
|
||||
args = ["esphome", "idedata", settings.rel_path(configuration)]
|
||||
rc, stdout, _ = run_system_command(*args)
|
||||
|
||||
if rc != 0:
|
||||
self.send_error(404 if rc == 2 else 500)
|
||||
return
|
||||
|
||||
idedata = platformio_api.IDEData(json.loads(stdout))
|
||||
|
||||
firmware_offset = "0x10000" if idedata.extra_flash_images else "0x0"
|
||||
flash_images = [
|
||||
{
|
||||
"path": f"./download.bin?configuration={configuration}&type=firmware.bin",
|
||||
"offset": firmware_offset,
|
||||
}
|
||||
] + [
|
||||
{
|
||||
"path": f"./download.bin?configuration={configuration}&type={os.path.basename(image.path)}",
|
||||
"offset": image.offset,
|
||||
}
|
||||
for image in idedata.extra_flash_images
|
||||
]
|
||||
|
||||
def get(self):
|
||||
self.set_header("Content-Type", "application/json")
|
||||
self.write(json.dumps(flash_images))
|
||||
self.write(json.dumps({"version": const.__version__}))
|
||||
self.finish()
|
||||
|
||||
|
||||
@@ -1025,8 +1008,14 @@ class SafeLoaderIgnoreUnknown(yaml.SafeLoader):
|
||||
def ignore_unknown(self, node):
|
||||
return f"{node.tag} {node.value}"
|
||||
|
||||
def construct_yaml_binary(self, node) -> str:
|
||||
return super().construct_yaml_binary(node).decode("ascii")
|
||||
|
||||
|
||||
SafeLoaderIgnoreUnknown.add_constructor(None, SafeLoaderIgnoreUnknown.ignore_unknown)
|
||||
SafeLoaderIgnoreUnknown.add_constructor(
|
||||
"tag:yaml.org,2002:binary", SafeLoaderIgnoreUnknown.construct_yaml_binary
|
||||
)
|
||||
|
||||
|
||||
class JsonConfigRequestHandler(BaseHandler):
|
||||
@@ -1147,7 +1136,6 @@ def make_app(debug=get_bool_env(ENV_DEV)):
|
||||
(f"{rel}info", InfoRequestHandler),
|
||||
(f"{rel}edit", EditRequestHandler),
|
||||
(f"{rel}download.bin", DownloadBinaryRequestHandler),
|
||||
(f"{rel}manifest.json", ManifestRequestHandler),
|
||||
(f"{rel}serial-ports", SerialPortRequestHandler),
|
||||
(f"{rel}ping", PingRequestHandler),
|
||||
(f"{rel}delete", DeleteRequestHandler),
|
||||
@@ -1161,6 +1149,7 @@ def make_app(debug=get_bool_env(ENV_DEV)):
|
||||
(f"{rel}rename", EsphomeRenameHandler),
|
||||
(f"{rel}prometheus-sd", PrometheusServiceDiscoveryHandler),
|
||||
(f"{rel}boards/([a-z0-9]+)", BoardsRequestHandler),
|
||||
(f"{rel}version", EsphomeVersionHandler),
|
||||
],
|
||||
**app_settings,
|
||||
)
|
||||
|
||||
@@ -49,6 +49,11 @@ def _set_mode(value, default_mode):
|
||||
CONF_INPUT: True,
|
||||
CONF_PULLDOWN: True,
|
||||
},
|
||||
"INPUT_OUTPUT_OPEN_DRAIN": {
|
||||
CONF_INPUT: True,
|
||||
CONF_OUTPUT: True,
|
||||
CONF_OPEN_DRAIN: True,
|
||||
},
|
||||
}
|
||||
if mode.upper() not in PIN_MODES:
|
||||
raise cv.Invalid(f"Unknown pin mode {mode}", [CONF_MODE])
|
||||
|
||||
@@ -142,7 +142,10 @@ def get_ini_content():
|
||||
# Sort to avoid changing build flags order
|
||||
CORE.add_platformio_option("build_flags", sorted(CORE.build_flags))
|
||||
|
||||
content = f"[env:{CORE.name}]\n"
|
||||
content = "[platformio]\n"
|
||||
content += f"description = ESPHome {__version__}\n"
|
||||
|
||||
content += f"[env:{CORE.name}]\n"
|
||||
content += format_ini(CORE.platformio_options)
|
||||
|
||||
return content
|
||||
|
||||
@@ -10,7 +10,7 @@ import yaml
|
||||
import yaml.constructor
|
||||
|
||||
from esphome import core
|
||||
from esphome.config_helpers import read_config_file
|
||||
from esphome.config_helpers import read_config_file, Extend
|
||||
from esphome.core import (
|
||||
EsphomeError,
|
||||
IPAddress,
|
||||
@@ -338,6 +338,10 @@ class ESPHomeLoader(yaml.SafeLoader):
|
||||
obj = self.construct_scalar(node)
|
||||
return add_class_to_obj(obj, ESPForceValue)
|
||||
|
||||
@_add_data_ref
|
||||
def construct_extend(self, node):
|
||||
return Extend(str(node.value))
|
||||
|
||||
|
||||
ESPHomeLoader.add_constructor("tag:yaml.org,2002:int", ESPHomeLoader.construct_yaml_int)
|
||||
ESPHomeLoader.add_constructor(
|
||||
@@ -369,6 +373,7 @@ ESPHomeLoader.add_constructor(
|
||||
)
|
||||
ESPHomeLoader.add_constructor("!lambda", ESPHomeLoader.construct_lambda)
|
||||
ESPHomeLoader.add_constructor("!force", ESPHomeLoader.construct_force)
|
||||
ESPHomeLoader.add_constructor("!extend", ESPHomeLoader.construct_extend)
|
||||
|
||||
|
||||
def load_yaml(fname, clear_secrets=True):
|
||||
|
||||
@@ -157,6 +157,11 @@ class DashboardImportDiscovery:
|
||||
return
|
||||
if state_change == ServiceStateChange.Removed:
|
||||
self.import_state.pop(name, None)
|
||||
return
|
||||
|
||||
if state_change == ServiceStateChange.Updated and name not in self.import_state:
|
||||
# Ignore updates for devices that are not in the import state
|
||||
return
|
||||
|
||||
info = zeroconf.get_service_info(service_type, name)
|
||||
_LOGGER.debug("-> resolved info: %s", info)
|
||||
|
||||
@@ -9,7 +9,7 @@ pyserial==3.5
|
||||
platformio==6.1.5 # When updating platformio, also update Dockerfile
|
||||
esptool==4.4
|
||||
click==8.1.3
|
||||
esphome-dashboard==20230120.0
|
||||
esphome-dashboard==20230214.0
|
||||
aioesphomeapi==13.1.0
|
||||
zeroconf==0.47.1
|
||||
|
||||
|
||||
351
tests/component_tests/packages/test_packages.py
Normal file
351
tests/component_tests/packages/test_packages.py
Normal file
@@ -0,0 +1,351 @@
|
||||
"""Tests for the packages component."""
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
from esphome.const import (
|
||||
CONF_DOMAIN,
|
||||
CONF_ESPHOME,
|
||||
CONF_FILTERS,
|
||||
CONF_ID,
|
||||
CONF_MULTIPLY,
|
||||
CONF_NAME,
|
||||
CONF_OFFSET,
|
||||
CONF_PACKAGES,
|
||||
CONF_PASSWORD,
|
||||
CONF_PLATFORM,
|
||||
CONF_SENSOR,
|
||||
CONF_SSID,
|
||||
CONF_UPDATE_INTERVAL,
|
||||
CONF_WIFI,
|
||||
)
|
||||
from esphome.components.packages import do_packages_pass
|
||||
from esphome.config_helpers import Extend
|
||||
import esphome.config_validation as cv
|
||||
|
||||
# Test strings
|
||||
TEST_DEVICE_NAME = "test_device_name"
|
||||
TEST_PLATFORM = "test_platform"
|
||||
TEST_WIFI_SSID = "test_wifi_ssid"
|
||||
TEST_PACKAGE_WIFI_SSID = "test_package_wifi_ssid"
|
||||
TEST_PACKAGE_WIFI_PASSWORD = "test_package_wifi_password"
|
||||
TEST_DOMAIN = "test_domain_name"
|
||||
TEST_SENSOR_PLATFORM_1 = "test_sensor_platform_1"
|
||||
TEST_SENSOR_PLATFORM_2 = "test_sensor_platform_2"
|
||||
TEST_SENSOR_NAME_1 = "test_sensor_name_1"
|
||||
TEST_SENSOR_NAME_2 = "test_sensor_name_2"
|
||||
TEST_SENSOR_ID_1 = "test_sensor_id_1"
|
||||
TEST_SENSOR_ID_2 = "test_sensor_id_2"
|
||||
TEST_SENSOR_UPDATE_INTERVAL = "test_sensor_update_interval"
|
||||
|
||||
|
||||
@pytest.fixture(name="basic_wifi")
|
||||
def fixture_basic_wifi():
|
||||
return {
|
||||
CONF_SSID: TEST_PACKAGE_WIFI_SSID,
|
||||
CONF_PASSWORD: TEST_PACKAGE_WIFI_PASSWORD,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture(name="basic_esphome")
|
||||
def fixture_basic_esphome():
|
||||
return {CONF_NAME: TEST_DEVICE_NAME, CONF_PLATFORM: TEST_PLATFORM}
|
||||
|
||||
|
||||
def test_package_unused(basic_esphome, basic_wifi):
|
||||
"""
|
||||
Ensures do_package_pass does not change a config if packages aren't used.
|
||||
"""
|
||||
config = {CONF_ESPHOME: basic_esphome, CONF_WIFI: basic_wifi}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == config
|
||||
|
||||
|
||||
def test_package_invalid_dict(basic_esphome, basic_wifi):
|
||||
"""
|
||||
Ensures an error is raised if packages is not valid.
|
||||
|
||||
"""
|
||||
config = {CONF_ESPHOME: basic_esphome, CONF_PACKAGES: basic_wifi}
|
||||
|
||||
with pytest.raises(cv.Invalid):
|
||||
do_packages_pass(config)
|
||||
|
||||
|
||||
def test_package_include(basic_wifi, basic_esphome):
|
||||
"""
|
||||
Tests the simple case where an independent config present in a package is added to the top-level config as is.
|
||||
|
||||
In this test, the CONF_WIFI config is expected to be simply added to the top-level config.
|
||||
"""
|
||||
config = {
|
||||
CONF_ESPHOME: basic_esphome,
|
||||
CONF_PACKAGES: {"network": {CONF_WIFI: basic_wifi}},
|
||||
}
|
||||
|
||||
expected = {CONF_ESPHOME: basic_esphome, CONF_WIFI: basic_wifi}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_package_append(basic_wifi, basic_esphome):
|
||||
"""
|
||||
Tests the case where a key is present in both a package and top-level config.
|
||||
|
||||
In this test, CONF_WIFI is defined in a package, and CONF_DOMAIN is added to it at the top level.
|
||||
"""
|
||||
config = {
|
||||
CONF_ESPHOME: basic_esphome,
|
||||
CONF_PACKAGES: {"network": {CONF_WIFI: basic_wifi}},
|
||||
CONF_WIFI: {CONF_DOMAIN: TEST_DOMAIN},
|
||||
}
|
||||
|
||||
expected = {
|
||||
CONF_ESPHOME: basic_esphome,
|
||||
CONF_WIFI: {
|
||||
CONF_SSID: TEST_PACKAGE_WIFI_SSID,
|
||||
CONF_PASSWORD: TEST_PACKAGE_WIFI_PASSWORD,
|
||||
CONF_DOMAIN: TEST_DOMAIN,
|
||||
},
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_package_override(basic_wifi, basic_esphome):
|
||||
"""
|
||||
Ensures that the top-level configuration takes precedence over duplicate keys defined in a package.
|
||||
|
||||
In this test, CONF_SSID should be overwritten by that defined in the top-level config.
|
||||
"""
|
||||
config = {
|
||||
CONF_ESPHOME: basic_esphome,
|
||||
CONF_PACKAGES: {"network": {CONF_WIFI: basic_wifi}},
|
||||
CONF_WIFI: {CONF_SSID: TEST_WIFI_SSID},
|
||||
}
|
||||
|
||||
expected = {
|
||||
CONF_ESPHOME: basic_esphome,
|
||||
CONF_WIFI: {
|
||||
CONF_SSID: TEST_WIFI_SSID,
|
||||
CONF_PASSWORD: TEST_PACKAGE_WIFI_PASSWORD,
|
||||
},
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_multiple_package_order():
|
||||
"""
|
||||
Ensures that mutiple packages are merged in order.
|
||||
"""
|
||||
config = {
|
||||
CONF_PACKAGES: {
|
||||
"package1": {
|
||||
"logger": {
|
||||
"level": "DEBUG",
|
||||
},
|
||||
},
|
||||
"package2": {
|
||||
"logger": {
|
||||
"level": "VERBOSE",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected = {
|
||||
"logger": {
|
||||
"level": "VERBOSE",
|
||||
},
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_package_list_merge():
|
||||
"""
|
||||
Ensures lists defined in both a package and the top-level config are merged correctly
|
||||
"""
|
||||
config = {
|
||||
CONF_PACKAGES: {
|
||||
"package_sensors": {
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_PLATFORM: TEST_SENSOR_PLATFORM_1,
|
||||
CONF_NAME: TEST_SENSOR_NAME_1,
|
||||
},
|
||||
{
|
||||
CONF_PLATFORM: TEST_SENSOR_PLATFORM_1,
|
||||
CONF_NAME: TEST_SENSOR_NAME_2,
|
||||
},
|
||||
]
|
||||
}
|
||||
},
|
||||
CONF_SENSOR: [
|
||||
{CONF_PLATFORM: TEST_SENSOR_PLATFORM_2, CONF_NAME: TEST_SENSOR_NAME_1},
|
||||
{CONF_PLATFORM: TEST_SENSOR_PLATFORM_2, CONF_NAME: TEST_SENSOR_NAME_2},
|
||||
],
|
||||
}
|
||||
|
||||
expected = {
|
||||
CONF_SENSOR: [
|
||||
{CONF_PLATFORM: TEST_SENSOR_PLATFORM_1, CONF_NAME: TEST_SENSOR_NAME_1},
|
||||
{CONF_PLATFORM: TEST_SENSOR_PLATFORM_1, CONF_NAME: TEST_SENSOR_NAME_2},
|
||||
{CONF_PLATFORM: TEST_SENSOR_PLATFORM_2, CONF_NAME: TEST_SENSOR_NAME_1},
|
||||
{CONF_PLATFORM: TEST_SENSOR_PLATFORM_2, CONF_NAME: TEST_SENSOR_NAME_2},
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_package_list_merge_by_id():
|
||||
"""
|
||||
Ensures that components with matching IDs are merged correctly.
|
||||
|
||||
In this test, a sensor is defined in a package, and a CONF_UPDATE_INTERVAL is added at the top level,
|
||||
and a sensor name is overridden in another sensor.
|
||||
"""
|
||||
config = {
|
||||
CONF_PACKAGES: {
|
||||
"package_sensors": {
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
CONF_PLATFORM: TEST_SENSOR_PLATFORM_1,
|
||||
CONF_NAME: TEST_SENSOR_NAME_1,
|
||||
},
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_2,
|
||||
CONF_PLATFORM: TEST_SENSOR_PLATFORM_1,
|
||||
CONF_NAME: TEST_SENSOR_NAME_2,
|
||||
},
|
||||
]
|
||||
},
|
||||
"package2": {
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: Extend(TEST_SENSOR_ID_1),
|
||||
CONF_DOMAIN: "2",
|
||||
}
|
||||
],
|
||||
},
|
||||
"package3": {
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: Extend(TEST_SENSOR_ID_1),
|
||||
CONF_DOMAIN: "3",
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: Extend(TEST_SENSOR_ID_1),
|
||||
CONF_UPDATE_INTERVAL: TEST_SENSOR_UPDATE_INTERVAL,
|
||||
},
|
||||
{CONF_ID: Extend(TEST_SENSOR_ID_2), CONF_NAME: TEST_SENSOR_NAME_1},
|
||||
{CONF_PLATFORM: TEST_SENSOR_PLATFORM_2, CONF_NAME: TEST_SENSOR_NAME_2},
|
||||
],
|
||||
}
|
||||
|
||||
expected = {
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
CONF_PLATFORM: TEST_SENSOR_PLATFORM_1,
|
||||
CONF_NAME: TEST_SENSOR_NAME_1,
|
||||
CONF_UPDATE_INTERVAL: TEST_SENSOR_UPDATE_INTERVAL,
|
||||
CONF_DOMAIN: "3",
|
||||
},
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_2,
|
||||
CONF_PLATFORM: TEST_SENSOR_PLATFORM_1,
|
||||
CONF_NAME: TEST_SENSOR_NAME_1,
|
||||
},
|
||||
{CONF_PLATFORM: TEST_SENSOR_PLATFORM_2, CONF_NAME: TEST_SENSOR_NAME_2},
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_package_merge_by_id_with_list():
|
||||
"""
|
||||
Ensures that components with matching IDs are merged correctly when their configuration contains lists.
|
||||
|
||||
For example, a sensor with filters defined in both a package and the top level config should be merged.
|
||||
"""
|
||||
|
||||
config = {
|
||||
CONF_PACKAGES: {
|
||||
"sensors": {
|
||||
CONF_SENSOR: [
|
||||
{CONF_ID: TEST_SENSOR_ID_1, CONF_FILTERS: [{CONF_MULTIPLY: 42.0}]}
|
||||
]
|
||||
}
|
||||
},
|
||||
CONF_SENSOR: [
|
||||
{CONF_ID: Extend(TEST_SENSOR_ID_1), CONF_FILTERS: [{CONF_OFFSET: 146.0}]}
|
||||
],
|
||||
}
|
||||
|
||||
expected = {
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
CONF_FILTERS: [{CONF_MULTIPLY: 42.0}, {CONF_OFFSET: 146.0}],
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_package_merge_by_missing_id():
|
||||
"""
|
||||
Ensures that components with missing IDs are not merged.
|
||||
"""
|
||||
|
||||
config = {
|
||||
CONF_PACKAGES: {
|
||||
"sensors": {
|
||||
CONF_SENSOR: [
|
||||
{CONF_ID: TEST_SENSOR_ID_1, CONF_FILTERS: [{CONF_MULTIPLY: 42.0}]},
|
||||
]
|
||||
}
|
||||
},
|
||||
CONF_SENSOR: [
|
||||
{CONF_ID: TEST_SENSOR_ID_1, CONF_FILTERS: [{CONF_MULTIPLY: 10.0}]},
|
||||
{CONF_ID: Extend(TEST_SENSOR_ID_2), CONF_FILTERS: [{CONF_OFFSET: 146.0}]},
|
||||
],
|
||||
}
|
||||
|
||||
expected = {
|
||||
CONF_SENSOR: [
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
CONF_FILTERS: [{CONF_MULTIPLY: 42.0}],
|
||||
},
|
||||
{
|
||||
CONF_ID: TEST_SENSOR_ID_1,
|
||||
CONF_FILTERS: [{CONF_MULTIPLY: 10.0}],
|
||||
},
|
||||
{
|
||||
CONF_ID: Extend(TEST_SENSOR_ID_2),
|
||||
CONF_FILTERS: [{CONF_OFFSET: 146.0}],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
actual = do_packages_pass(config)
|
||||
assert actual == expected
|
||||
@@ -222,6 +222,12 @@ uart:
|
||||
rx_pin: GPIO26
|
||||
baud_rate: 115200
|
||||
rx_buffer_size: 1024
|
||||
- id: ld2410_uart
|
||||
tx_pin: 18
|
||||
rx_pin: 23
|
||||
baud_rate: 256000
|
||||
parity: NONE
|
||||
stop_bits: 1
|
||||
|
||||
ota:
|
||||
safe_mode: true
|
||||
@@ -1070,6 +1076,8 @@ sensor:
|
||||
id: ultrasonic_sensor1
|
||||
- platform: uptime
|
||||
name: Uptime Sensor
|
||||
- id: !extend ${devicename}_uptime_pcg
|
||||
unit_of_measurement: s
|
||||
- platform: wifi_signal
|
||||
name: WiFi Signal Sensor
|
||||
update_interval: 15s
|
||||
@@ -1200,6 +1208,17 @@ sensor:
|
||||
pressure:
|
||||
name: "MPL3115A2 Pressure"
|
||||
update_interval: 10s
|
||||
- platform: ld2410
|
||||
moving_distance:
|
||||
name: "Moving distance (cm)"
|
||||
still_distance:
|
||||
name: "Still Distance (cm)"
|
||||
moving_energy:
|
||||
name: "Move Energy"
|
||||
still_energy:
|
||||
name: "Still Energy"
|
||||
detection_distance:
|
||||
name: "Distance Detection"
|
||||
|
||||
esp32_touch:
|
||||
setup_mode: false
|
||||
@@ -1304,6 +1323,11 @@ binary_sensor:
|
||||
number: GPIO9
|
||||
mode: INPUT_PULLUP
|
||||
name: Living Room Window 2
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: GPIO9
|
||||
mode: INPUT_OUTPUT_OPEN_DRAIN
|
||||
name: Living Room Button
|
||||
- platform: status
|
||||
name: Living Room Status
|
||||
- platform: esp32_touch
|
||||
@@ -1461,6 +1485,13 @@ binary_sensor:
|
||||
id: close_sensor
|
||||
- platform: template
|
||||
id: close_obstacle_sensor
|
||||
- platform: ld2410
|
||||
has_target:
|
||||
name: presence
|
||||
has_moving_target:
|
||||
name: movement
|
||||
has_still_target:
|
||||
name: still
|
||||
|
||||
pca9685:
|
||||
frequency: 500
|
||||
@@ -3143,6 +3174,19 @@ button:
|
||||
on_press:
|
||||
midea_ac.power_toggle:
|
||||
|
||||
ld2410:
|
||||
id: my_ld2410
|
||||
uart_id: ld2410_uart
|
||||
timeout: 150s
|
||||
max_move_distance: 6m
|
||||
max_still_distance: 0.75m
|
||||
g0_move_threshold: 10
|
||||
g0_still_threshold: 20
|
||||
g2_move_threshold: 20
|
||||
g2_still_threshold: 21
|
||||
g8_move_threshold: 80
|
||||
g8_still_threshold: 81
|
||||
|
||||
lcd_menu:
|
||||
display_id: my_lcd_gpio
|
||||
mark_back: 0x5e
|
||||
|
||||
@@ -287,6 +287,9 @@ uart:
|
||||
modbus:
|
||||
uart_id: uart1
|
||||
|
||||
vbus:
|
||||
uart_id: uart4
|
||||
|
||||
ota:
|
||||
safe_mode: true
|
||||
port: 3286
|
||||
@@ -799,6 +802,11 @@ sensor:
|
||||
id: adc128s102_channel_0
|
||||
channel: 0
|
||||
|
||||
- platform: vbus
|
||||
model: deltasol c
|
||||
temperature_1:
|
||||
name: Temperature 1
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
|
||||
@@ -904,6 +912,11 @@ binary_sensor:
|
||||
then:
|
||||
- pzemac.reset_energy: pzemac1
|
||||
|
||||
- platform: vbus
|
||||
model: deltasol_bs_plus
|
||||
relay1:
|
||||
name: Relay 1 On
|
||||
|
||||
globals:
|
||||
- id: my_global_string
|
||||
type: std::string
|
||||
@@ -1123,14 +1136,16 @@ climate:
|
||||
- switch.turn_on: gpio_switch1
|
||||
fan_mode_diffuse_action:
|
||||
- switch.turn_on: gpio_switch2
|
||||
fan_mode_quiet_action:
|
||||
- switch.turn_on: gpio_switch1
|
||||
swing_off_action:
|
||||
- switch.turn_on: gpio_switch1
|
||||
- switch.turn_on: gpio_switch2
|
||||
swing_horizontal_action:
|
||||
- switch.turn_on: gpio_switch2
|
||||
swing_vertical_action:
|
||||
- switch.turn_on: gpio_switch1
|
||||
swing_both_action:
|
||||
swing_vertical_action:
|
||||
- switch.turn_on: gpio_switch2
|
||||
swing_both_action:
|
||||
- switch.turn_on: gpio_switch1
|
||||
startup_delay: true
|
||||
supplemental_cooling_delta: 2.0
|
||||
cool_deadband: 0.5
|
||||
|
||||
@@ -66,6 +66,9 @@ mqtt:
|
||||
ESP_LOGD("Mqtt Test", "testing/sensor/testing_sensor/state=[%s]", x.c_str());
|
||||
# yamllint enable rule:line-length
|
||||
|
||||
vbus:
|
||||
- uart_id: uart2
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin: GPIO0
|
||||
@@ -183,6 +186,22 @@ binary_sensor:
|
||||
id: key1
|
||||
key: 1
|
||||
|
||||
- platform: vbus
|
||||
model: deltasol_bs_plus
|
||||
relay2:
|
||||
name: Relay 2 On
|
||||
sensor1_error:
|
||||
name: Sensor 1 Error
|
||||
|
||||
- platform: vbus
|
||||
model: custom
|
||||
command: 0x100
|
||||
source: 0x1234
|
||||
dest: 0x10
|
||||
binary_sensors:
|
||||
- id: vcustom_b
|
||||
name: VBus Custom Binary Sensor
|
||||
lambda: return x[0] & 1;
|
||||
|
||||
tlc5947:
|
||||
data_pin: GPIO12
|
||||
@@ -478,6 +497,27 @@ sensor:
|
||||
max_flow_rate:
|
||||
name: Max Flow Rate
|
||||
|
||||
- platform: vbus
|
||||
model: deltasol c
|
||||
temperature_3:
|
||||
name: Temperature 3
|
||||
operating_hours_1:
|
||||
name: Operating Hours 1
|
||||
heat_quantity:
|
||||
name: Heat Quantity
|
||||
time:
|
||||
name: System Time
|
||||
|
||||
- platform: vbus
|
||||
model: custom
|
||||
command: 0x100
|
||||
source: 0x1234
|
||||
dest: 0x10
|
||||
sensors:
|
||||
- id: vcustom
|
||||
name: VBus Custom Sensor
|
||||
lambda: return x[0] / 10.0;
|
||||
|
||||
script:
|
||||
- id: automation_test
|
||||
then:
|
||||
|
||||
Reference in New Issue
Block a user