1
0
mirror of https://github.com/esphome/esphome.git synced 2025-11-01 15:41:52 +00:00

Compare commits

...

48 Commits

Author SHA1 Message Date
Jesse Hills
d83324c4dc Merge pull request #4404 from esphome/bump-2023.2.0b2
2023.2.0b2
2023-02-09 16:55:48 +13:00
Jesse Hills
ecde4c1d2d Bump version to 2023.2.0b2 2023-02-09 16:27:25 +13:00
Jesse Hills
bd8e470726 Bump curl version in docker (#4403) 2023-02-09 16:27:24 +13:00
Jesse Hills
d2913fe627 Merge pull request #4402 from esphome/bump-2023.2.0b1
2023.2.0b1
2023-02-09 15:59:06 +13:00
Jesse Hills
43acc7dc2c Bump version to 2023.2.0b1 2023-02-09 15:33:02 +13:00
Jesse Hills
e2a16d758b Merge branch 'dev' into bump-2023.2.0b1 2023-02-09 15:33:01 +13:00
Paulus Schoutsen
17ea0efb08 Verify rel_path output is relative (#4247) 2023-02-08 20:20:30 -05:00
Paulus Schoutsen
2fbd33267e Convert secrets constant to a tuple (#4245) 2023-02-09 14:00:58 +13:00
Jesse Hills
cf3977f088 Use the github-script action to call the workflow (#4400) 2023-02-08 19:51:24 -05:00
Paulus Schoutsen
d20d4947ac Remove unused manifest handler (#4169)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-02-09 13:41:59 +13:00
Eric van Blokland
7810ad40d7 Added CanalSat and CanalSatLD protocol support (#3513)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-02-09 13:22:05 +13:00
jmichiel
7e1e799b3a add MQTT preset support for Climate components (#4379)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Michiel, Jeroen <jeroen.michiel@teledyneflir.com>
2023-02-09 12:46:01 +13:00
Michael Muré
dfafc41ce6 climate: add support for quiet fan mode (#3609) 2023-02-09 11:28:16 +13:00
Bob Perciaccante
e460792c43 Add support for Lippert LP sensors in mopeka_pro_check component (#4118)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
fixes https://github.com/esphome/feature-requests/issues/1988
2023-02-09 11:07:41 +13:00
Jesse Hills
a9dc491a54 Dont keep logging on improv start (#4401) 2023-02-09 10:25:57 +13:00
Jesse Hills
ac6693f177 mDNS updates (#4399) 2023-02-09 10:25:44 +13:00
Jesse Hills
c6742117d3 Update log for mics4514 to state 3 minute start time. (#4396) 2023-02-09 07:30:19 +13:00
Jesse Hills
b5c47b9669 Update ld2410 logging (#4395) 2023-02-09 07:30:00 +13:00
Quentin Smith
40df3aa55e Merge components in packages (#3555)
Co-authored-by: Paul Monigatti <pm@paul.pm>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-02-07 14:08:40 +13:00
tomaszduda23
393ca64d70 adds gpio INPUT_OUTPUT_OPEN_DRAIN (#4360)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-02-07 12:55:35 +13:00
Michał Obrembski
d3627f0972 Added Ethernet Component for ESP IDF with JL1101 PHY driver (#4009)
Co-authored-by: Michał Obrembski <michal@obrembski.com>
2023-02-07 12:54:59 +13:00
Trevor North
124ab31f22 Fix shelly dimmer current sensor device class (#4385)
fixes https://github.com/esphome/issues/issues/4086
2023-02-07 12:46:06 +13:00
Jesse Hills
1b66fa5004 Remove unneeded validation for esp32 gpio pins (#4394) 2023-02-07 12:43:30 +13:00
alexd321
9494c27ad8 modify SGP4X integration to report device_class as air quality index (#4327) 2023-02-07 12:35:09 +13:00
Jesse Hills
3facfa5c21 Allow dashboard import to specify if api encryption key should be generated (#4393) 2023-02-07 12:27:07 +13:00
Samuel Sieb
93ddce2e79 add Resol VBus support (#3976)
Co-authored-by: Samuel Sieb <samuel@sieb.net>
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
fixes https://github.com/esphome/feature-requests/issues/1949
2023-02-07 12:17:17 +13:00
sebcaps
0bf6e21e1a Add Ld2410 Support (#3919)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-02-07 11:47:50 +13:00
Florian Trück
6b7b076875 SCD30 Added support for manual calibration (#4362)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
2023-02-03 21:13:27 +13:00
Florian idB
8d6ffb9169 Update sim800l.cpp (#4223) 2023-02-03 07:53:46 +13:00
Jesse Hills
cb520c00a5 Merge pull request #4185 from esphome/bump-2022.12.0b6
2022.12.0b6
2022-12-14 12:24:25 +13:00
Jesse Hills
2f24138345 Bump version to 2022.12.0b6 2022-12-14 12:03:03 +13:00
Jesse Hills
96512b80cc Revert camera config change for esp-idf (#4182) 2022-12-14 12:03:03 +13:00
Jesse Hills
fcb9b51978 Remove warnings when falling through switch cases on purpose (#4181) 2022-12-14 12:03:03 +13:00
Jesse Hills
f408f1a368 Merge pull request #4180 from esphome/bump-2022.12.0b5
2022.12.0b5
2022-12-13 19:42:18 +13:00
Jesse Hills
7d8d563c62 Bump version to 2022.12.0b5 2022-12-13 13:58:44 +13:00
J. Nick Koston
0a1f705fda Speed up bluetooth proxy connections when using esp-idf (#4171) 2022-12-13 13:58:44 +13:00
Jesse Hills
1952c1880b Remove internal pin restriction from cd74hc4067 (#4179) 2022-12-13 13:58:44 +13:00
Jesse Hills
b03967dac1 Merge pull request #4178 from esphome/bump-2022.12.0b4
2022.12.0b4
2022-12-13 10:14:08 +13:00
Jesse Hills
bcae2596a6 Bump version to 2022.12.0b4 2022-12-13 09:13:28 +13:00
Jesse Hills
fc0347c86c Bump esphome-dashboard to 20221213.0 (#4176) 2022-12-13 09:13:28 +13:00
Jesse Hills
d9563d4de1 Merge pull request #4174 from esphome/bump-2022.12.0b3
2022.12.0b3
2022-12-12 17:31:44 +13:00
Jesse Hills
cc7e2bf8db Bump version to 2022.12.0b3 2022-12-12 17:19:12 +13:00
Jesse Hills
5d98e2923b Increase watchdog timeout when starting OTA (#4172) 2022-12-12 17:19:12 +13:00
Jesse Hills
07197d12f6 Merge pull request #4163 from esphome/bump-2022.12.0b2
2022.12.0b2
2022-12-08 14:04:20 +13:00
Jesse Hills
7b0a298497 Bump version to 2022.12.0b2 2022-12-08 13:42:25 +13:00
Jesse Hills
21679cf2ba Fix ble parsing with zero padded advertisements (#4162) 2022-12-08 13:42:24 +13:00
Jesse Hills
4be7cd12a1 Merge pull request #4157 from esphome/bump-2022.12.0b1
2022.12.0b1
2022-12-07 17:25:12 +13:00
Jesse Hills
dee4d0ccb7 Bump version to 2022.12.0b1 2022-12-07 17:00:10 +13:00
70 changed files with 3863 additions and 138 deletions

View File

@@ -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) }}"
}
})

View File

@@ -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

View File

@@ -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/* \

View File

@@ -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;

View File

@@ -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";
}

View File

@@ -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,

View File

@@ -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(

View File

@@ -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;

View File

@@ -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");
}

View File

@@ -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

View File

@@ -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
*

View File

@@ -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,21 @@ def import_config(
raise FileExistsError
if project_name == "esphome.web":
kwargs = {
"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",
}
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 +124,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:

View File

@@ -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({

View File

@@ -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"

View File

@@ -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

View File

@@ -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");

View File

@@ -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")

View 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 */

View File

@@ -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;

View File

@@ -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

View 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),
}
)

View 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))

View 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

View 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

View 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))

View File

@@ -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_{};

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -38,6 +38,11 @@ void MDNSComponent::setup() {
void MDNSComponent::loop() { MDNS.update(); }
void MDNSComponent::on_shutdown() {
MDNS.close();
delay(40);
}
} // namespace mdns
} // namespace esphome

View File

@@ -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

View File

@@ -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;

View File

@@ -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
};

View File

@@ -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())

View File

@@ -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;

View File

@@ -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:

View File

@@ -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,

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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};

View File

@@ -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

View File

@@ -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,

View File

@@ -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.

View File

@@ -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());

View File

@@ -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]

View File

@@ -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_));

View File

@@ -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};

View 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)

View 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))

View 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

View 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

View 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))

View 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

View 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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -1,6 +1,6 @@
"""Constants used by esphome."""
__version__ = "2023.2.0-dev"
__version__ = "2023.2.0b2"
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 = ""
@@ -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"

View File

@@ -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,38 +545,6 @@ class DownloadBinaryRequestHandler(BaseHandler):
self.finish()
class ManifestRequestHandler(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
]
self.set_header("Content-Type", "application/json")
self.write(json.dumps(flash_images))
self.finish()
def _list_dashboard_entries():
files = settings.list_yaml_files()
return [DashboardEntry(file) for file in files]
@@ -1147,7 +1122,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),

View File

@@ -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])

View File

@@ -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):

View File

@@ -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)

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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: