Merge branch 'dev' into optolink

This commit is contained in:
j0ta29 2024-02-25 16:27:35 +01:00 committed by GitHub
commit 8f57f428f0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
178 changed files with 2749 additions and 153 deletions

View File

@ -7,8 +7,21 @@
"PIP_BREAK_SYSTEM_PACKAGES": "1",
"PIP_ROOT_USER_ACTION": "ignore"
},
"runArgs": ["--privileged", "-e", "ESPHOME_DASHBOARD_USE_PING=1"],
"runArgs": [
"--privileged",
"-e",
"ESPHOME_DASHBOARD_USE_PING=1"
// uncomment and edit the path in order to pass though local USB serial to the conatiner
// , "--device=/dev/ttyACM0"
],
"appPort": 6052,
// if you are using avahi in the host device, uncomment these to allow the
// devcontainer to find devices via mdns
//"mounts": [
// "type=bind,source=/dev/bus/usb,target=/dev/bus/usb",
// "type=bind,source=/var/run/dbus,target=/var/run/dbus",
// "type=bind,source=/var/run/avahi-daemon/socket,target=/var/run/avahi-daemon/socket"
//],
"customizations": {
"vscode": {
"extensions": [

View File

@ -11,6 +11,7 @@ on:
- "**"
- "!.github/workflows/*.yml"
- ".github/workflows/ci.yml"
- "!.yamllint"
merge_group:
permissions:
@ -218,7 +219,7 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native tests
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}

View File

@ -1,5 +1,6 @@
name: Needs Docs
# yamllint disable-line rule:truthy
on:
pull_request:
types: [labeled, unlabeled]

View File

@ -1,6 +1,7 @@
---
name: Synchronise Device Classes from Home Assistant
# yamllint disable-line rule:truthy
on:
workflow_dispatch:
schedule:
@ -36,7 +37,7 @@ jobs:
python ./script/sync-device_class.py
- name: Commit changes
uses: peter-evans/create-pull-request@v5.0.2
uses: peter-evans/create-pull-request@v6.0.0
with:
commit-message: "Synchronise Device Classes from Home Assistant"
committer: esphomebot <esphome@nabucasa.com>

View File

@ -1,5 +1,7 @@
---
name: YAML lint
# yamllint disable-line rule:truthy
on:
push:
branches: [dev, beta, release]
@ -19,4 +21,6 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.1
- name: Run yamllint
uses: frenck/action-yamllint@v1.4.2
uses: frenck/action-yamllint@v1.5.0
with:
strict: true

View File

@ -3,7 +3,7 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/psf/black-pre-commit-mirror
rev: 23.12.1
rev: 24.2.0
hooks:
- id: black
args:
@ -27,7 +27,7 @@ repos:
- --branch=release
- --branch=beta
- repo: https://github.com/asottile/pyupgrade
rev: v3.15.0
rev: v3.15.1
hooks:
- id: pyupgrade
args: [--py39-plus]

View File

@ -1,3 +1,18 @@
---
ignore: |
venv/
extends: default
ignore-from-file: .gitignore
rules:
document-start: disable
empty-lines:
level: error
max: 1
max-start: 0
max-end: 1
indentation:
level: error
spaces: 2
indent-sequences: true
check-multi-line-strings: false
line-length: disable

View File

@ -372,6 +372,7 @@ esphome/components/uart/button/* @ssieb
esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter
esphome/components/uponor_smatrix/* @kroimon
esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81
esphome/components/version/* @esphome/core

View File

@ -4,6 +4,7 @@ from esphome.components import sensor, uart
from esphome.const import (
CONF_CURRENT,
CONF_ENERGY,
CONF_EXTERNAL_TEMPERATURE,
CONF_ID,
CONF_POWER,
CONF_VOLTAGE,
@ -24,7 +25,6 @@ from esphome.const import (
DEPENDENCIES = ["uart"]
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
bl0940_ns = cg.esphome_ns.namespace("bl0940")
BL0940 = bl0940_ns.class_("BL0940", cg.PollingComponent, uart.UARTDevice)

View File

@ -7,6 +7,8 @@ CODEOWNERS = ["@ellull"]
DEPENDENCIES = ["i2c"]
MULTI_CONF = True
CONF_PWM = "pwm"
CONF_DIVIDER = "divider"
CONF_DAC = "dac"

View File

@ -2,6 +2,7 @@ import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_EXTERNAL_TEMPERATURE,
CONF_ID,
CONF_SPEED,
DEVICE_CLASS_TEMPERATURE,
@ -16,7 +17,6 @@ from .. import EMC2101_COMPONENT_SCHEMA, CONF_EMC2101_ID, emc2101_ns
DEPENDENCIES = ["emc2101"]
CONF_INTERNAL_TEMPERATURE = "internal_temperature"
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
CONF_DUTY_CYCLE = "duty_cycle"
EMC2101Sensor = emc2101_ns.class_("EMC2101Sensor", cg.PollingComponent)

View File

@ -336,6 +336,8 @@ void FingerprintGrowComponent::aura_led_control(uint8_t state, uint8_t speed, ui
}
uint8_t FingerprintGrowComponent::send_command_() {
while (this->available())
this->read();
this->write((uint8_t) (START_CODE >> 8));
this->write((uint8_t) (START_CODE & 0xFF));
this->write(this->address_[0]);

View File

@ -1,2 +1,3 @@
"""Support for Honeywell HumidIcon HIH"""
CODEOWNERS = ["@Benichou34"]

View File

@ -1,2 +1,3 @@
"""Support for Honeywell ABP2"""
CODEOWNERS = ["@jpfaff"]

View File

@ -24,7 +24,7 @@ void ArduinoI2CBus::setup() {
}
next_bus_num++;
#elif defined(USE_ESP8266)
wire_ = &Wire; // NOLINT(cppcoreguidelines-prefer-member-initializer)
wire_ = new TwoWire(); // NOLINT(cppcoreguidelines-owning-memory)
#elif defined(USE_RP2040)
static bool first = true;
if (first) {
@ -35,6 +35,16 @@ void ArduinoI2CBus::setup() {
}
#endif
this->set_pins_and_clock_();
this->initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
this->i2c_scan_();
}
}
void ArduinoI2CBus::set_pins_and_clock_() {
#ifdef USE_RP2040
wire_->setSDA(this->sda_pin_);
wire_->setSCL(this->scl_pin_);
@ -43,12 +53,8 @@ void ArduinoI2CBus::setup() {
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
#endif
wire_->setClock(frequency_);
initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning i2c bus for active devices...");
this->i2c_scan_();
}
}
void ArduinoI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, "I2C Bus:");
ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_);
@ -82,6 +88,10 @@ void ArduinoI2CBus::dump_config() {
}
ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
#if defined(USE_ESP8266)
this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances
#endif
// logging is only enabled with vv level, if warnings are shown the caller
// should log them
if (!initialized_) {
@ -120,6 +130,10 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt)
return ERROR_OK;
}
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
#if defined(USE_ESP8266)
this->set_pins_and_clock_(); // reconfigure Wire global state in case there are multiple instances
#endif
// logging is only enabled with vv level, if warnings are shown the caller
// should log them
if (!initialized_) {
@ -164,7 +178,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn
return ERROR_UNKNOWN;
case 2:
case 3:
ESP_LOGVV(TAG, "TX failed: not acknowledged");
ESP_LOGVV(TAG, "TX failed: not acknowledged: %d", status);
return ERROR_NOT_ACKNOWLEDGED;
case 5:
ESP_LOGVV(TAG, "TX failed: timeout");

View File

@ -30,6 +30,7 @@ class ArduinoI2CBus : public I2CBus, public Component {
private:
void recover_();
void set_pins_and_clock_();
RecoveryCode recovery_result_;
protected:

View File

@ -3,6 +3,7 @@ import esphome.config_validation as cv
from esphome.components import sensor, esp32_ble_tracker
from esphome.const import (
CONF_BATTERY_LEVEL,
CONF_EXTERNAL_TEMPERATURE,
CONF_HUMIDITY,
CONF_MAC_ADDRESS,
CONF_TEMPERATURE,
@ -19,8 +20,6 @@ from esphome.const import (
CODEOWNERS = ["@fkirill"]
DEPENDENCIES = ["esp32_ble_tracker"]
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
inkbird_ibsth1_mini_ns = cg.esphome_ns.namespace("inkbird_ibsth1_mini")
InkbirdIbstH1Mini = inkbird_ibsth1_mini_ns.class_(
"InkbirdIbstH1Mini", esp32_ble_tracker.ESPBTDeviceListener, cg.Component

View File

@ -8,10 +8,23 @@ namespace ltr390 {
static const char *const TAG = "ltr390";
static const uint8_t LTR390_MAIN_CTRL = 0x00;
static const uint8_t LTR390_MEAS_RATE = 0x04;
static const uint8_t LTR390_GAIN = 0x05;
static const uint8_t LTR390_PART_ID = 0x06;
static const uint8_t LTR390_MAIN_STATUS = 0x07;
static const float GAINVALUES[5] = {1.0, 3.0, 6.0, 9.0, 18.0};
static const float RESOLUTIONVALUE[6] = {4.0, 2.0, 1.0, 0.5, 0.25, 0.125};
// Request fastest measurement rate - will be slowed by device if conversion rate is slower.
static const float RESOLUTION_SETTING[6] = {0x00, 0x10, 0x20, 0x30, 0x40, 0x50};
static const uint32_t MODEADDRESSES[2] = {0x0D, 0x10};
static const float SENSITIVITY_MAX = 2300;
static const float INTG_MAX = RESOLUTIONVALUE[0] * 100;
static const int GAIN_MAX = GAINVALUES[4];
uint32_t little_endian_bytes_to_int(const uint8_t *buffer, uint8_t num_bytes) {
uint32_t value = 0;
@ -58,7 +71,7 @@ void LTR390Component::read_als_() {
uint32_t als = *val;
if (this->light_sensor_ != nullptr) {
float lux = (0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_]) * this->wfac_;
float lux = ((0.6 * als) / (GAINVALUES[this->gain_] * RESOLUTIONVALUE[this->res_])) * this->wfac_;
this->light_sensor_->publish_state(lux);
}
@ -74,7 +87,7 @@ void LTR390Component::read_uvs_() {
uint32_t uv = *val;
if (this->uvi_sensor_ != nullptr) {
this->uvi_sensor_->publish_state(uv / LTR390_SENSITIVITY * this->wfac_);
this->uvi_sensor_->publish_state((uv / this->sensitivity_) * this->wfac_);
}
if (this->uv_sensor_ != nullptr) {
@ -132,12 +145,13 @@ void LTR390Component::setup() {
// Set gain
this->reg(LTR390_GAIN) = gain_;
// Set resolution
uint8_t res = this->reg(LTR390_MEAS_RATE).get();
// resolution is in bits 5-7
res &= ~0b01110000;
res |= res << 4;
this->reg(LTR390_MEAS_RATE) = res;
// Set resolution and measurement rate
this->reg(LTR390_MEAS_RATE) = RESOLUTION_SETTING[this->res_];
// Set sensitivity by linearly scaling against known value in the datasheet
float gain_scale = GAINVALUES[this->gain_] / GAIN_MAX;
float intg_scale = (RESOLUTIONVALUE[this->res_] * 100) / INTG_MAX;
this->sensitivity_ = SENSITIVITY_MAX * gain_scale * intg_scale;
// Set sensor read state
this->reading_ = false;

View File

@ -17,14 +17,6 @@ enum LTR390CTRL {
};
// enums from https://github.com/adafruit/Adafruit_LTR390/
static const uint8_t LTR390_MAIN_CTRL = 0x00;
static const uint8_t LTR390_MEAS_RATE = 0x04;
static const uint8_t LTR390_GAIN = 0x05;
static const uint8_t LTR390_PART_ID = 0x06;
static const uint8_t LTR390_MAIN_STATUS = 0x07;
static const float LTR390_SENSITIVITY = 2300.0;
// Sensing modes
enum LTR390MODE {
LTR390_MODE_ALS,
@ -81,6 +73,7 @@ class LTR390Component : public PollingComponent, public i2c::I2CDevice {
LTR390GAIN gain_;
LTR390RESOLUTION res_;
float sensitivity_;
float wfac_;
sensor::Sensor *light_sensor_{nullptr};

View File

@ -8,6 +8,7 @@ from esphome.const import (
CONF_RESOLUTION,
UNIT_LUX,
ICON_BRIGHTNESS_5,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_ILLUMINANCE,
)
@ -61,22 +62,22 @@ CONFIG_SCHEMA = cv.All(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
device_class=DEVICE_CLASS_EMPTY,
),
cv.Optional(CONF_UV_INDEX): sensor.sensor_schema(
unit_of_measurement=UNIT_UVI,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=5,
device_class=DEVICE_CLASS_ILLUMINANCE,
device_class=DEVICE_CLASS_EMPTY,
),
cv.Optional(CONF_UV): sensor.sensor_schema(
unit_of_measurement=UNIT_COUNTS,
icon=ICON_BRIGHTNESS_5,
accuracy_decimals=1,
device_class=DEVICE_CLASS_ILLUMINANCE,
device_class=DEVICE_CLASS_EMPTY,
),
cv.Optional(CONF_GAIN, default="X3"): cv.enum(GAIN_OPTIONS),
cv.Optional(CONF_RESOLUTION, default=18): cv.enum(RES_OPTIONS),
cv.Optional(CONF_GAIN, default="X18"): cv.enum(GAIN_OPTIONS),
cv.Optional(CONF_RESOLUTION, default=20): cv.enum(RES_OPTIONS),
cv.Optional(CONF_WINDOW_CORRECTION_FACTOR, default=1.0): cv.float_range(
min=1.0
),

View File

@ -74,7 +74,8 @@ CONF_FORCE_SW = "force_sw"
CONF_INTERFACE = "interface"
CONF_INTERFACE_INDEX = "interface_index"
# RP2040 SPI pin assignments are complicated. Refer to https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
# RP2040 SPI pin assignments are complicated;
# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
RP_SPI_PINSETS = [
{
@ -85,7 +86,7 @@ RP_SPI_PINSETS = [
{
CONF_MISO_PIN: [8, 12, 24, 28, -1],
CONF_CLK_PIN: [10, 14, 26],
CONF_MOSI_PIN: [11, 23, 27, -1],
CONF_MOSI_PIN: [11, 15, 27, -1],
},
]

View File

@ -13,6 +13,7 @@ from esphome.const import (
CODEOWNERS = ["@freekode"]
tm1651_ns = cg.esphome_ns.namespace("tm1651")
TM1651Brightness = tm1651_ns.enum("TM1651Brightness")
TM1651Display = tm1651_ns.class_("TM1651Display", cg.Component)
SetLevelPercentAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
@ -24,9 +25,9 @@ TurnOffAction = tm1651_ns.class_("SetLevelPercentAction", automation.Action)
CONF_LEVEL_PERCENT = "level_percent"
TM1651_BRIGHTNESS_OPTIONS = {
1: TM1651Display.TM1651_BRIGHTNESS_LOW,
2: TM1651Display.TM1651_BRIGHTNESS_MEDIUM,
3: TM1651Display.TM1651_BRIGHTNESS_HIGH,
1: TM1651Brightness.TM1651_BRIGHTNESS_LOW,
2: TM1651Brightness.TM1651_BRIGHTNESS_MEDIUM,
3: TM1651Brightness.TM1651_BRIGHTNESS_HIGH,
}
CONFIG_SCHEMA = cv.All(

View File

@ -12,9 +12,9 @@ static const char *const TAG = "tm1651.display";
static const uint8_t MAX_INPUT_LEVEL_PERCENT = 100;
static const uint8_t TM1651_MAX_LEVEL = 7;
static const uint8_t TM1651_BRIGHTNESS_LOW = 0;
static const uint8_t TM1651_BRIGHTNESS_MEDIUM = 2;
static const uint8_t TM1651_BRIGHTNESS_HIGH = 7;
static const uint8_t TM1651_BRIGHTNESS_LOW_HW = 0;
static const uint8_t TM1651_BRIGHTNESS_MEDIUM_HW = 2;
static const uint8_t TM1651_BRIGHTNESS_HIGH_HW = 7;
void TM1651Display::setup() {
ESP_LOGCONFIG(TAG, "Setting up TM1651...");
@ -78,14 +78,14 @@ uint8_t TM1651Display::calculate_level_(uint8_t new_level) {
uint8_t TM1651Display::calculate_brightness_(uint8_t new_brightness) {
if (new_brightness <= 1) {
return TM1651_BRIGHTNESS_LOW;
return TM1651_BRIGHTNESS_LOW_HW;
} else if (new_brightness == 2) {
return TM1651_BRIGHTNESS_MEDIUM;
return TM1651_BRIGHTNESS_MEDIUM_HW;
} else if (new_brightness >= 3) {
return TM1651_BRIGHTNESS_HIGH;
return TM1651_BRIGHTNESS_HIGH_HW;
}
return TM1651_BRIGHTNESS_LOW;
return TM1651_BRIGHTNESS_LOW_HW;
}
} // namespace tm1651

View File

@ -13,6 +13,12 @@
namespace esphome {
namespace tm1651 {
enum TM1651Brightness : uint8_t {
TM1651_BRIGHTNESS_LOW = 1,
TM1651_BRIGHTNESS_MEDIUM = 2,
TM1651_BRIGHTNESS_HIGH = 3,
};
class TM1651Display : public Component {
public:
void set_clk_pin(InternalGPIOPin *pin) { clk_pin_ = pin; }
@ -24,6 +30,7 @@ class TM1651Display : public Component {
void set_level_percent(uint8_t new_level);
void set_level(uint8_t new_level);
void set_brightness(uint8_t new_brightness);
void set_brightness(TM1651Brightness new_brightness) { this->set_brightness(static_cast<uint8_t>(new_brightness)); }
void turn_on();
void turn_off();

View File

@ -7,6 +7,7 @@ reading temperatures to a resolution of 0.0625°C.
https://www.sparkfun.com/datasheets/Sensors/Temperature/tmp102.pdf
"""
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, sensor

View File

@ -34,9 +34,13 @@ void TuyaFan::setup() {
}
if (this->oscillation_id_.has_value()) {
this->parent_->register_listener(*this->oscillation_id_, [this](const TuyaDatapoint &datapoint) {
// Whether data type is BOOL or ENUM, it will still be a 1 or a 0, so the functions below are valid in both
// scenarios
ESP_LOGV(TAG, "MCU reported oscillation is: %s", ONOFF(datapoint.value_bool));
this->oscillating = datapoint.value_bool;
this->publish_state();
this->oscillation_type_ = datapoint.type;
});
}
if (this->direction_id_.has_value()) {
@ -80,7 +84,11 @@ void TuyaFan::control(const fan::FanCall &call) {
this->parent_->set_boolean_datapoint_value(*this->switch_id_, *call.get_state());
}
if (this->oscillation_id_.has_value() && call.get_oscillating().has_value()) {
this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
if (this->oscillation_type_ == TuyaDatapointType::ENUM) {
this->parent_->set_enum_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
} else if (this->speed_type_ == TuyaDatapointType::BOOLEAN) {
this->parent_->set_boolean_datapoint_value(*this->oscillation_id_, *call.get_oscillating());
}
}
if (this->direction_id_.has_value() && call.get_direction().has_value()) {
bool enable = *call.get_direction() == fan::FanDirection::REVERSE;

View File

@ -29,6 +29,7 @@ class TuyaFan : public Component, public fan::Fan {
optional<uint8_t> direction_id_{};
int speed_count_{};
TuyaDatapointType speed_type_{};
TuyaDatapointType oscillation_type_{};
};
} // namespace tuya

View File

@ -0,0 +1,78 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart, time
from esphome.const import (
CONF_ADDRESS,
CONF_ID,
CONF_TIME_ID,
)
CODEOWNERS = ["@kroimon"]
DEPENDENCIES = ["uart"]
MULTI_CONF = True
uponor_smatrix_ns = cg.esphome_ns.namespace("uponor_smatrix")
UponorSmatrixComponent = uponor_smatrix_ns.class_(
"UponorSmatrixComponent", cg.Component, uart.UARTDevice
)
UponorSmatrixDevice = uponor_smatrix_ns.class_(
"UponorSmatrixDevice", cg.Parented.template(UponorSmatrixComponent)
)
CONF_UPONOR_SMATRIX_ID = "uponor_smatrix_id"
CONF_TIME_DEVICE_ADDRESS = "time_device_address"
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(UponorSmatrixComponent),
cv.Optional(CONF_ADDRESS): cv.hex_uint16_t,
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
cv.Optional(CONF_TIME_DEVICE_ADDRESS): cv.hex_uint16_t,
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(uart.UART_DEVICE_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"uponor_smatrix",
baud_rate=19200,
require_tx=True,
require_rx=True,
data_bits=8,
parity=None,
stop_bits=1,
)
# A schema to use for all Uponor Smatrix devices
UPONOR_SMATRIX_DEVICE_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_UPONOR_SMATRIX_ID): cv.use_id(UponorSmatrixComponent),
cv.Required(CONF_ADDRESS): cv.hex_uint16_t,
}
)
async def to_code(config):
cg.add_global(uponor_smatrix_ns.using)
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
if address := config.get(CONF_ADDRESS):
cg.add(var.set_system_address(address))
if time_id := config.get(CONF_TIME_ID):
time_ = await cg.get_variable(time_id)
cg.add(var.set_time_id(time_))
if time_device_address := config.get(CONF_TIME_DEVICE_ADDRESS):
cg.add(var.set_time_device_address(time_device_address))
async def register_uponor_smatrix_device(var, config):
parent = await cg.get_variable(config[CONF_UPONOR_SMATRIX_ID])
cg.add(var.set_parent(parent))
cg.add(var.set_device_address(config[CONF_ADDRESS]))
cg.add(parent.register_device(var))

View File

@ -0,0 +1,33 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import climate
from esphome.const import CONF_ID
from .. import (
uponor_smatrix_ns,
UponorSmatrixDevice,
UPONOR_SMATRIX_DEVICE_SCHEMA,
register_uponor_smatrix_device,
)
DEPENDENCIES = ["uponor_smatrix"]
UponorSmatrixClimate = uponor_smatrix_ns.class_(
"UponorSmatrixClimate",
climate.Climate,
cg.Component,
UponorSmatrixDevice,
)
CONFIG_SCHEMA = climate.CLIMATE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(UponorSmatrixClimate),
}
).extend(UPONOR_SMATRIX_DEVICE_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await climate.register_climate(var, config)
await register_uponor_smatrix_device(var, config)

View File

@ -0,0 +1,101 @@
#include "uponor_smatrix_climate.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace uponor_smatrix {
static const char *const TAG = "uponor_smatrix.climate";
void UponorSmatrixClimate::dump_config() {
LOG_CLIMATE("", "Uponor Smatrix Climate", this);
ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_);
}
void UponorSmatrixClimate::loop() {
const uint32_t now = millis();
// Publish state after all update packets are processed
if (this->last_data_ != 0 && (now - this->last_data_ > 100) && this->target_temperature_raw_ != 0) {
float temp = raw_to_celsius((this->preset == climate::CLIMATE_PRESET_ECO)
? (this->target_temperature_raw_ - this->eco_setback_value_raw_)
: this->target_temperature_raw_);
float step = this->get_traits().get_visual_target_temperature_step();
this->target_temperature = roundf(temp / step) * step;
this->publish_state();
this->last_data_ = 0;
}
}
climate::ClimateTraits UponorSmatrixClimate::traits() {
auto traits = climate::ClimateTraits();
traits.set_supports_current_temperature(true);
traits.set_supports_current_humidity(true);
traits.set_supported_modes({climate::CLIMATE_MODE_HEAT});
traits.set_supports_action(true);
traits.set_supported_presets({climate::CLIMATE_PRESET_ECO});
traits.set_visual_min_temperature(this->min_temperature_);
traits.set_visual_max_temperature(this->max_temperature_);
traits.set_visual_current_temperature_step(0.1f);
traits.set_visual_target_temperature_step(0.5f);
return traits;
}
void UponorSmatrixClimate::control(const climate::ClimateCall &call) {
if (call.get_target_temperature().has_value()) {
uint16_t temp = celsius_to_raw(*call.get_target_temperature());
if (this->preset == climate::CLIMATE_PRESET_ECO) {
// During ECO mode, the thermostat automatically substracts the setback value from the setpoint,
// so we need to add it here first
temp += this->eco_setback_value_raw_;
}
// For unknown reasons, we need to send a null setpoint first for the thermostat to react
UponorSmatrixData data[] = {{UPONOR_ID_TARGET_TEMP, 0}, {UPONOR_ID_TARGET_TEMP, temp}};
this->send(data, sizeof(data) / sizeof(data[0]));
}
}
void UponorSmatrixClimate::on_device_data(const UponorSmatrixData *data, size_t data_len) {
for (int i = 0; i < data_len; i++) {
switch (data[i].id) {
case UPONOR_ID_TARGET_TEMP_MIN:
this->min_temperature_ = raw_to_celsius(data[i].value);
break;
case UPONOR_ID_TARGET_TEMP_MAX:
this->max_temperature_ = raw_to_celsius(data[i].value);
break;
case UPONOR_ID_TARGET_TEMP:
// Ignore invalid values here as they are used by the controller to explicitely request the setpoint from a
// thermostat
if (data[i].value != UPONOR_INVALID_VALUE)
this->target_temperature_raw_ = data[i].value;
break;
case UPONOR_ID_ECO_SETBACK:
this->eco_setback_value_raw_ = data[i].value;
break;
case UPONOR_ID_DEMAND:
if (data[i].value & 0x1000) {
this->mode = climate::CLIMATE_MODE_COOL;
this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_COOLING : climate::CLIMATE_ACTION_IDLE;
} else {
this->mode = climate::CLIMATE_MODE_HEAT;
this->action = (data[i].value & 0x0040) ? climate::CLIMATE_ACTION_HEATING : climate::CLIMATE_ACTION_IDLE;
}
break;
case UPONOR_ID_MODE1:
this->set_preset_((data[i].value & 0x0008) ? climate::CLIMATE_PRESET_ECO : climate::CLIMATE_PRESET_NONE);
break;
case UPONOR_ID_ROOM_TEMP:
this->current_temperature = raw_to_celsius(data[i].value);
break;
case UPONOR_ID_HUMIDITY:
this->current_humidity = data[i].value & 0x00FF;
}
}
this->last_data_ = millis();
}
} // namespace uponor_smatrix
} // namespace esphome

View File

@ -0,0 +1,28 @@
#pragma once
#include "esphome/components/climate/climate.h"
#include "esphome/components/uponor_smatrix/uponor_smatrix.h"
#include "esphome/core/component.h"
namespace esphome {
namespace uponor_smatrix {
class UponorSmatrixClimate : public climate::Climate, public Component, public UponorSmatrixDevice {
public:
void dump_config() override;
void loop() override;
protected:
climate::ClimateTraits traits() override;
void control(const climate::ClimateCall &call) override;
void on_device_data(const UponorSmatrixData *data, size_t data_len) override;
uint32_t last_data_;
float min_temperature_{5.0f};
float max_temperature_{35.0f};
uint16_t eco_setback_value_raw_{0x0048};
uint16_t target_temperature_raw_;
};
} // namespace uponor_smatrix
} // namespace esphome

View File

@ -0,0 +1,70 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import (
CONF_EXTERNAL_TEMPERATURE,
CONF_HUMIDITY,
CONF_TEMPERATURE,
CONF_ID,
DEVICE_CLASS_HUMIDITY,
DEVICE_CLASS_TEMPERATURE,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
)
from .. import (
uponor_smatrix_ns,
UponorSmatrixDevice,
UPONOR_SMATRIX_DEVICE_SCHEMA,
register_uponor_smatrix_device,
)
DEPENDENCIES = ["uponor_smatrix"]
UponorSmatrixSensor = uponor_smatrix_ns.class_(
"UponorSmatrixSensor",
sensor.Sensor,
cg.Component,
UponorSmatrixDevice,
)
CONFIG_SCHEMA = cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(UponorSmatrixSensor),
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_EXTERNAL_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_HUMIDITY): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_HUMIDITY,
state_class=STATE_CLASS_MEASUREMENT,
),
}
).extend(UPONOR_SMATRIX_DEVICE_SCHEMA)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await register_uponor_smatrix_device(var, config)
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature_sensor(sens))
if external_temperature_config := config.get(CONF_EXTERNAL_TEMPERATURE):
sens = await sensor.new_sensor(external_temperature_config)
cg.add(var.set_external_temperature_sensor(sens))
if humidity_config := config.get(CONF_HUMIDITY):
sens = await sensor.new_sensor(humidity_config)
cg.add(var.set_humidity_sensor(sens))

View File

@ -0,0 +1,37 @@
#include "uponor_smatrix_sensor.h"
#include "esphome/core/log.h"
namespace esphome {
namespace uponor_smatrix {
static const char *const TAG = "uponor_smatrix.sensor";
void UponorSmatrixSensor::dump_config() {
ESP_LOGCONFIG(TAG, "Uponor Smatrix Sensor");
ESP_LOGCONFIG(TAG, " Device address: 0x%04X", this->address_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
LOG_SENSOR(" ", "External Temperature", this->external_temperature_sensor_);
LOG_SENSOR(" ", "Humidity", this->humidity_sensor_);
}
void UponorSmatrixSensor::on_device_data(const UponorSmatrixData *data, size_t data_len) {
for (int i = 0; i < data_len; i++) {
switch (data[i].id) {
case UPONOR_ID_ROOM_TEMP:
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(raw_to_celsius(data[i].value));
break;
case UPONOR_ID_EXTERNAL_TEMP:
if (this->external_temperature_sensor_ != nullptr)
this->external_temperature_sensor_->publish_state(raw_to_celsius(data[i].value));
break;
case UPONOR_ID_HUMIDITY:
if (this->humidity_sensor_ != nullptr)
this->humidity_sensor_->publish_state(data[i].value & 0x00FF);
break;
}
}
}
} // namespace uponor_smatrix
} // namespace esphome

View File

@ -0,0 +1,23 @@
#pragma once
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uponor_smatrix/uponor_smatrix.h"
#include "esphome/core/component.h"
namespace esphome {
namespace uponor_smatrix {
class UponorSmatrixSensor : public sensor::Sensor, public Component, public UponorSmatrixDevice {
SUB_SENSOR(temperature)
SUB_SENSOR(external_temperature)
SUB_SENSOR(humidity)
public:
void dump_config() override;
protected:
void on_device_data(const UponorSmatrixData *data, size_t data_len) override;
};
} // namespace uponor_smatrix
} // namespace esphome

View File

@ -0,0 +1,225 @@
#include "uponor_smatrix.h"
#include "esphome/core/log.h"
namespace esphome {
namespace uponor_smatrix {
static const char *const TAG = "uponor_smatrix";
void UponorSmatrixComponent::setup() {
#ifdef USE_TIME
if (this->time_id_ != nullptr) {
this->time_id_->add_on_time_sync_callback([this] { this->send_time(); });
}
#endif
}
void UponorSmatrixComponent::dump_config() {
ESP_LOGCONFIG(TAG, "Uponor Smatrix");
ESP_LOGCONFIG(TAG, " System address: 0x%04X", this->address_);
#ifdef USE_TIME
if (this->time_id_ != nullptr) {
ESP_LOGCONFIG(TAG, " Time synchronization: YES");
ESP_LOGCONFIG(TAG, " Time master device address: 0x%04X", this->time_device_address_);
}
#endif
this->check_uart_settings(19200);
if (!this->unknown_devices_.empty()) {
ESP_LOGCONFIG(TAG, " Detected unknown device addresses:");
for (auto device_address : this->unknown_devices_) {
ESP_LOGCONFIG(TAG, " 0x%04X", device_address);
}
}
}
void UponorSmatrixComponent::loop() {
const uint32_t now = millis();
// Discard stale data
if (!this->rx_buffer_.empty() && (now - this->last_rx_ > 50)) {
ESP_LOGD(TAG, "Discarding %d bytes of unparsed data", this->rx_buffer_.size());
this->rx_buffer_.clear();
}
// Read incoming data
while (this->available()) {
// The controller polls devices every 10 seconds, with around 200 ms between devices.
// Remember timestamps so we can send our own packets when the bus is expected to be silent.
if (now - this->last_rx_ > 500) {
this->last_poll_start_ = now;
}
this->last_rx_ = now;
uint8_t byte;
this->read_byte(&byte);
if (this->parse_byte_(byte)) {
this->rx_buffer_.clear();
}
}
// Send packets during bus silence
if ((now - this->last_rx_ > 300) && (now - this->last_poll_start_ < 9500) && (now - this->last_tx_ > 200)) {
// Only build time packet when bus is silent and queue is empty to make sure we can send it right away
if (this->send_time_requested_ && this->tx_queue_.empty() && this->do_send_time_())
this->send_time_requested_ = false;
// Send the next packet in the queue
if (!this->tx_queue_.empty()) {
auto packet = std::move(this->tx_queue_.front());
this->tx_queue_.pop();
this->write_array(packet);
this->flush();
this->last_tx_ = now;
}
}
}
bool UponorSmatrixComponent::parse_byte_(uint8_t byte) {
this->rx_buffer_.push_back(byte);
const uint8_t *packet = this->rx_buffer_.data();
size_t packet_len = this->rx_buffer_.size();
if (packet_len < 7) {
// Minimum packet size is 7 bytes, wait for more
return false;
}
uint16_t system_address = encode_uint16(packet[0], packet[1]);
uint16_t device_address = encode_uint16(packet[2], packet[3]);
uint16_t crc = encode_uint16(packet[packet_len - 1], packet[packet_len - 2]);
uint16_t computed_crc = crc16(packet, packet_len - 2);
if (crc != computed_crc) {
// CRC did not match, more data might be coming
return false;
}
ESP_LOGV(TAG, "Received packet: sys=%04X, dev=%04X, data=%s, crc=%04X", system_address, device_address,
format_hex(&packet[4], packet_len - 6).c_str(), crc);
// Detect or check system address
if (this->address_ == 0) {
ESP_LOGI(TAG, "Using detected system address 0x%04X", system_address);
this->address_ = system_address;
} else if (this->address_ != system_address) {
// This should never happen except if the system address was set or detected incorrectly, so warn the user.
ESP_LOGW(TAG, "Received packet from unknown system address 0x%04X", system_address);
return true;
}
// Handle packet
size_t data_len = (packet_len - 6) / 3;
if (data_len == 0) {
if (packet[4] == UPONOR_ID_REQUEST)
ESP_LOGVV(TAG, "Ignoring request packet for device 0x%04X", device_address);
return true;
}
// Decode packet payload data for easy access
UponorSmatrixData data[data_len];
for (int i = 0; i < data_len; i++) {
data[i].id = packet[(i * 3) + 4];
data[i].value = encode_uint16(packet[(i * 3) + 5], packet[(i * 3) + 6]);
}
#ifdef USE_TIME
// Detect device that acts as time master if not set explicitely
if (this->time_device_address_ == 0 && data_len >= 2) {
// The first thermostat paired to the controller will act as the time master. Time can only be manually adjusted at
// this first thermostat. To synchronize time, we need to know its address, so we search for packets coming from a
// thermostat sending both room temperature and time information.
bool found_temperature = false;
bool found_time = false;
for (int i = 0; i < data_len; i++) {
if (data[i].id == UPONOR_ID_ROOM_TEMP)
found_temperature = true;
if (data[i].id == UPONOR_ID_DATETIME1)
found_time = true;
if (found_temperature && found_time) {
ESP_LOGI(TAG, "Using detected time device address 0x%04X", device_address);
this->time_device_address_ = device_address;
break;
}
}
}
#endif
// Forward data to device components
bool found = false;
for (auto *device : this->devices_) {
if (device->address_ == device_address) {
found = true;
device->on_device_data(data, data_len);
}
}
// Log unknown device addresses
if (!found && !this->unknown_devices_.count(device_address)) {
ESP_LOGI(TAG, "Received packet for unknown device address 0x%04X ", device_address);
this->unknown_devices_.insert(device_address);
}
// Return true to reset buffer
return true;
}
bool UponorSmatrixComponent::send(uint16_t device_address, const UponorSmatrixData *data, size_t data_len) {
if (this->address_ == 0 || device_address == 0 || data == nullptr || data_len == 0)
return false;
// Assemble packet for send queue. All fields are big-endian except for the little-endian checksum.
std::vector<uint8_t> packet(6 + 3 * data_len);
packet.push_back(this->address_ >> 8);
packet.push_back(this->address_ >> 0);
packet.push_back(device_address >> 8);
packet.push_back(device_address >> 0);
for (int i = 0; i < data_len; i++) {
packet.push_back(data[i].id);
packet.push_back(data[i].value >> 8);
packet.push_back(data[i].value >> 0);
}
auto crc = crc16(packet.data(), packet.size());
packet.push_back(crc >> 0);
packet.push_back(crc >> 8);
this->tx_queue_.push(packet);
return true;
}
#ifdef USE_TIME
bool UponorSmatrixComponent::do_send_time_() {
if (this->time_device_address_ == 0 || this->time_id_ == nullptr)
return false;
ESPTime now = this->time_id_->now();
if (!now.is_valid())
return false;
uint8_t year = now.year - 2000;
uint8_t month = now.month;
// ESPHome days are [1-7] starting with Sunday, Uponor days are [0-6] starting with Monday
uint8_t day_of_week = (now.day_of_week == 1) ? 6 : (now.day_of_week - 2);
uint8_t day_of_month = now.day_of_month;
uint8_t hour = now.hour;
uint8_t minute = now.minute;
uint8_t second = now.second;
uint16_t time1 = (year & 0x7F) << 7 | (month & 0x0F) << 3 | (day_of_week & 0x07);
uint16_t time2 = (day_of_month & 0x1F) << 11 | (hour & 0x1F) << 6 | (minute & 0x3F);
uint16_t time3 = second;
ESP_LOGI(TAG, "Sending local time: %04d-%02d-%02d %02d:%02d:%02d", now.year, now.month, now.day_of_month, now.hour,
now.minute, now.second);
UponorSmatrixData data[] = {{UPONOR_ID_DATETIME1, time1}, {UPONOR_ID_DATETIME2, time2}, {UPONOR_ID_DATETIME3, time3}};
return this->send(this->time_device_address_, data, sizeof(data) / sizeof(data[0]));
}
#endif
} // namespace uponor_smatrix
} // namespace esphome

View File

@ -0,0 +1,128 @@
#pragma once
#include "esphome/components/uart/uart.h"
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#include "esphome/core/time.h"
#endif
#include <queue>
#include <set>
#include <vector>
namespace esphome {
namespace uponor_smatrix {
/// Date/Time Part 1 (year, month, day of week)
static const uint8_t UPONOR_ID_DATETIME1 = 0x08;
/// Date/Time Part 2 (day of month, hour, minute)
static const uint8_t UPONOR_ID_DATETIME2 = 0x09;
/// Date/Time Part 3 (seconds)
static const uint8_t UPONOR_ID_DATETIME3 = 0x0A;
/// Unknown (observed values: 0x0342, 0x0024)
static const uint8_t UPONOR_ID_UNKNOWN1 = 0x0C;
/// Outdoor Temperature? (sent by controller)
static const uint8_t UPONOR_ID_OUTDOOR_TEMP = 0x2D;
/// Unknown (observed values: 0x8000)
static const uint8_t UPONOR_ID_UNKNOWN2 = 0x35;
/// Room Temperature Setpoint Minimum
static const uint8_t UPONOR_ID_TARGET_TEMP_MIN = 0x37;
/// Room Temperature Setpoint Maximum
static const uint8_t UPONOR_ID_TARGET_TEMP_MAX = 0x38;
/// Room Temperature Setpoint
static const uint8_t UPONOR_ID_TARGET_TEMP = 0x3B;
/// Room Temperature Setpoint Setback for ECO Mode
static const uint8_t UPONOR_ID_ECO_SETBACK = 0x3C;
/// Heating/Cooling Demand
static const uint8_t UPONOR_ID_DEMAND = 0x3D;
/// Thermostat Operating Mode 1 (ECO state, program schedule state)
static const uint8_t UPONOR_ID_MODE1 = 0x3E;
/// Thermostat Operating Mode 2 (sensor configuration, heating/cooling allowed)
static const uint8_t UPONOR_ID_MODE2 = 0x3F;
/// Current Room Temperature
static const uint8_t UPONOR_ID_ROOM_TEMP = 0x40;
/// Current External (Floor/Outdoor) Sensor Temperature
static const uint8_t UPONOR_ID_EXTERNAL_TEMP = 0x41;
/// Current Room Humidity
static const uint8_t UPONOR_ID_HUMIDITY = 0x42;
/// Data Request (sent by controller)
static const uint8_t UPONOR_ID_REQUEST = 0xFF;
/// Indicating an invalid/missing value
static const uint16_t UPONOR_INVALID_VALUE = 0x7FFF;
struct UponorSmatrixData {
uint8_t id;
uint16_t value;
};
class UponorSmatrixDevice;
class UponorSmatrixComponent : public uart::UARTDevice, public Component {
public:
UponorSmatrixComponent() = default;
void setup() override;
void dump_config() override;
void loop() override;
void set_system_address(uint16_t address) { this->address_ = address; }
void register_device(UponorSmatrixDevice *device) { this->devices_.push_back(device); }
bool send(uint16_t device_address, const UponorSmatrixData *data, size_t data_len);
#ifdef USE_TIME
void set_time_id(time::RealTimeClock *time_id) { this->time_id_ = time_id; }
void set_time_device_address(uint16_t address) { this->time_device_address_ = address; }
void send_time() { this->send_time_requested_ = true; }
#endif
protected:
bool parse_byte_(uint8_t byte);
uint16_t address_;
std::vector<UponorSmatrixDevice *> devices_;
std::set<uint16_t> unknown_devices_;
std::vector<uint8_t> rx_buffer_;
std::queue<std::vector<uint8_t>> tx_queue_;
uint32_t last_rx_;
uint32_t last_tx_;
uint32_t last_poll_start_;
#ifdef USE_TIME
time::RealTimeClock *time_id_{nullptr};
uint16_t time_device_address_;
bool send_time_requested_;
bool do_send_time_();
#endif
};
class UponorSmatrixDevice : public Parented<UponorSmatrixComponent> {
public:
void set_device_address(uint16_t address) { this->address_ = address; }
virtual void on_device_data(const UponorSmatrixData *data, size_t data_len) = 0;
bool send(const UponorSmatrixData *data, size_t data_len) {
return this->parent_->send(this->address_, data, data_len);
}
protected:
friend UponorSmatrixComponent;
uint16_t address_;
};
inline float raw_to_celsius(uint16_t raw) {
return (raw == UPONOR_INVALID_VALUE) ? NAN : fahrenheit_to_celsius(raw / 10.0f);
}
inline uint16_t celsius_to_raw(float celsius) {
return std::isnan(celsius) ? UPONOR_INVALID_VALUE
: static_cast<uint16_t>(lroundf(celsius_to_fahrenheit(celsius) * 10.0f));
}
} // namespace uponor_smatrix
} // namespace esphome

View File

@ -32,6 +32,7 @@ CONF_ON_TTS_START = "on_tts_start"
CONF_ON_TTS_STREAM_START = "on_tts_stream_start"
CONF_ON_TTS_STREAM_END = "on_tts_stream_end"
CONF_ON_WAKE_WORD_DETECTED = "on_wake_word_detected"
CONF_ON_IDLE = "on_idle"
CONF_SILENCE_DETECTION = "silence_detection"
CONF_USE_WAKE_WORD = "use_wake_word"
@ -127,6 +128,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_ON_TTS_STREAM_END): automation.validate_automation(
single=True
),
cv.Optional(CONF_ON_IDLE): automation.validate_automation(single=True),
}
).extend(cv.COMPONENT_SCHEMA),
tts_stream_validate,
@ -259,6 +261,13 @@ async def to_code(config):
config[CONF_ON_TTS_STREAM_END],
)
if CONF_ON_IDLE in config:
await automation.build_automation(
var.get_idle_trigger(),
[],
config[CONF_ON_IDLE],
)
cg.add_define("USE_VOICE_ASSISTANT")

View File

@ -135,6 +135,8 @@ void VoiceAssistant::loop() {
switch (this->state_) {
case State::IDLE: {
if (this->continuous_ && this->desired_state_ == State::IDLE) {
this->idle_trigger_->trigger();
this->ring_buffer_->reset();
#ifdef USE_ESP_ADF
if (this->use_wake_word_) {
@ -618,6 +620,9 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
{
this->set_state_(State::IDLE, State::IDLE);
}
} else if (this->state_ == State::AWAITING_RESPONSE) {
// No TTS start event ("nevermind")
this->set_state_(State::IDLE, State::IDLE);
}
this->defer([this]() { this->end_trigger_->trigger(); });
break;

View File

@ -116,6 +116,7 @@ class VoiceAssistant : public Component {
Trigger<std::string> *get_tts_end_trigger() const { return this->tts_end_trigger_; }
Trigger<std::string> *get_tts_start_trigger() const { return this->tts_start_trigger_; }
Trigger<std::string, std::string> *get_error_trigger() const { return this->error_trigger_; }
Trigger<> *get_idle_trigger() const { return this->idle_trigger_; }
Trigger<> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
Trigger<> *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; }
@ -148,6 +149,7 @@ class VoiceAssistant : public Component {
Trigger<std::string> *tts_end_trigger_ = new Trigger<std::string>();
Trigger<std::string> *tts_start_trigger_ = new Trigger<std::string>();
Trigger<std::string, std::string> *error_trigger_ = new Trigger<std::string, std::string>();
Trigger<> *idle_trigger_ = new Trigger<>();
Trigger<> *client_connected_trigger_ = new Trigger<>();
Trigger<> *client_disconnected_trigger_ = new Trigger<>();

View File

@ -785,6 +785,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
obj->position, start_config);
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
if (obj->get_traits().get_supports_position())
root["position"] = obj->position;
if (obj->get_traits().get_supports_tilt())
root["tilt"] = obj->tilt;
});

View File

@ -3,6 +3,7 @@
The cryptography package is loaded lazily in the functions
so that it doesn't crash if it's not installed.
"""
import logging
from pathlib import Path

View File

@ -292,8 +292,7 @@ class ConfigValidationStep(abc.ABC):
priority: float = 0.0
@abc.abstractmethod
def run(self, result: Config) -> None:
...
def run(self, result: Config) -> None: ... # noqa: E704
class LoadValidationStep(ConfigValidationStep):

View File

@ -251,6 +251,7 @@ CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy"
CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy"
CONF_EXTERNAL_CLOCK_INPUT = "external_clock_input"
CONF_EXTERNAL_COMPONENTS = "external_components"
CONF_EXTERNAL_TEMPERATURE = "external_temperature"
CONF_EXTERNAL_VCC = "external_vcc"
CONF_FALLING_EDGE = "falling_edge"
CONF_FAMILY = "family"

View File

@ -8,3 +8,5 @@ MAX_EXECUTOR_WORKERS = 48
SENTINEL = object()
DASHBOARD_COMMAND = ["esphome", "--dashboard"]

View File

@ -1,11 +1,13 @@
from __future__ import annotations
import asyncio
import contextlib
import logging
import threading
from dataclasses import dataclass
from functools import partial
from typing import TYPE_CHECKING, Any, Callable
from collections.abc import Coroutine
from ..zeroconf import DiscoveredImport
from .dns import DNSCache
@ -71,6 +73,7 @@ class ESPHomeDashboard:
"mdns_status",
"settings",
"dns_cache",
"_background_tasks",
)
def __init__(self) -> None:
@ -85,6 +88,7 @@ class ESPHomeDashboard:
self.mdns_status: MDNSStatus | None = None
self.settings = DashboardSettings()
self.dns_cache = DNSCache()
self._background_tasks: set[asyncio.Task] = set()
async def async_setup(self) -> None:
"""Setup the dashboard."""
@ -132,7 +136,19 @@ class ESPHomeDashboard:
if settings.status_use_mqtt:
status_thread_mqtt.join()
self.mqtt_ping_request.set()
for task in self._background_tasks:
task.cancel()
with contextlib.suppress(asyncio.CancelledError):
await task
await asyncio.sleep(0)
def async_create_background_task(
self, coro: Coroutine[Any, Any, Any]
) -> asyncio.Task:
"""Create a background task."""
task = self.loop.create_task(coro)
task.add_done_callback(self._background_tasks.discard)
return task
DASHBOARD = ESPHomeDashboard()

View File

@ -10,12 +10,14 @@ from esphome import const, util
from esphome.storage_json import StorageJSON, ext_storage_path
from .const import (
DASHBOARD_COMMAND,
EVENT_ENTRY_ADDED,
EVENT_ENTRY_REMOVED,
EVENT_ENTRY_STATE_CHANGED,
EVENT_ENTRY_UPDATED,
)
from .enum import StrEnum
from .util.subprocess import async_run_system_command
if TYPE_CHECKING:
from .core import ESPHomeDashboard
@ -235,6 +237,14 @@ class DashboardEntries:
)
return path_to_cache_key
def async_schedule_storage_json_update(self, filename: str) -> None:
"""Schedule a task to update the storage JSON file."""
self._dashboard.async_create_background_task(
async_run_system_command(
[*DASHBOARD_COMMAND, "compile", "--only-generate", filename]
)
)
class DashboardEntry:
"""Represents a single dashboard entry.

View File

@ -1,4 +1,5 @@
"""Enum backports from standard lib."""
from __future__ import annotations
from enum import Enum

View File

@ -9,11 +9,11 @@ import hashlib
import json
import logging
import os
import time
import secrets
import shutil
import subprocess
import threading
import time
from collections.abc import Iterable
from pathlib import Path
from typing import TYPE_CHECKING, Any, Callable, TypeVar
@ -40,6 +40,7 @@ from esphome.storage_json import StorageJSON, ext_storage_path, trash_storage_pa
from esphome.util import get_serial_ports, shlex_quote
from esphome.yaml_util import FastestAvailableSafeLoader
from .const import DASHBOARD_COMMAND
from .core import DASHBOARD
from .entries import EntryState, entry_state_to_bool
from .util.file import write_file
@ -286,9 +287,6 @@ class EsphomeCommandWebSocket(tornado.websocket.WebSocketHandler):
raise NotImplementedError
DASHBOARD_COMMAND = ["esphome", "--dashboard"]
class EsphomePortCommandWebSocket(EsphomeCommandWebSocket):
"""Base class for commands that require a port."""
@ -808,8 +806,16 @@ class EditRequestHandler(BaseHandler):
@bind_config
async def get(self, configuration: str | None = None) -> None:
"""Get the content of a file."""
loop = asyncio.get_running_loop()
if not configuration.endswith((".yaml", ".yml")):
self.send_error(404)
return
filename = settings.rel_path(configuration)
if Path(filename).resolve().parent != settings.absolute_config_dir:
self.send_error(404)
return
loop = asyncio.get_running_loop()
content = await loop.run_in_executor(
None, self._read_file, filename, configuration
)
@ -835,15 +841,19 @@ class EditRequestHandler(BaseHandler):
@bind_config
async def post(self, configuration: str | None = None) -> None:
"""Write the content of a file."""
if not configuration.endswith((".yaml", ".yml")):
self.send_error(404)
return
filename = settings.rel_path(configuration)
if Path(filename).resolve().parent != settings.absolute_config_dir:
self.send_error(404)
return
loop = asyncio.get_running_loop()
config_file = settings.rel_path(configuration)
await loop.run_in_executor(
None, self._write_file, config_file, self.request.body
)
await loop.run_in_executor(None, self._write_file, filename, self.request.body)
# Ensure the StorageJSON is updated as well
await async_run_system_command(
[*DASHBOARD_COMMAND, "compile", "--only-generate", config_file]
)
DASHBOARD.entries.async_schedule_storage_json_update(filename)
self.set_status(200)

View File

@ -206,8 +206,11 @@ def perform_ota(
_, version = receive_exactly(sock, 2, "version", RESPONSE_OK)
_LOGGER.debug("Device support OTA version: %s", version)
if version not in (OTA_VERSION_1_0, OTA_VERSION_2_0):
raise OTAError(f"Unsupported OTA version {version}")
supported_versions = (OTA_VERSION_1_0, OTA_VERSION_2_0)
if version not in supported_versions:
raise OTAError(
f"Device uses unsupported OTA version {version}, this ESPHome supports {supported_versions}"
)
# Features
send_check(sock, FEATURE_SUPPORTS_COMPRESSION, "features")

View File

@ -8,7 +8,6 @@ originally do.
However there is a property to further disable decorator
impact."""
# This is set to true by script/build_language_schema.py
# only, so data is collected (again functionality is not modified)
EnableSchemaExtraction = False

View File

@ -1,4 +1,5 @@
"""This helper module tracks commonly used types in the esphome python codebase."""
from typing import Union
from esphome.core import ID, Lambda, EsphomeCore

View File

@ -64,7 +64,7 @@ class _Schema(vol.Schema):
# Recursively compile schema
_compiled_schema = {}
for skey, svalue in vol.iteritems(schema):
for skey, svalue in schema.items():
new_key = self._compile(skey)
new_value = self._compile(svalue)
_compiled_schema[skey] = (new_key, new_value)

View File

@ -110,7 +110,7 @@ class DashboardImportDiscovery:
self, zeroconf: Zeroconf, info: AsyncServiceInfo, service_type: str, name: str
) -> None:
"""Process a service info."""
if await info.async_request(zeroconf):
if await info.async_request(zeroconf, timeout=3000):
self._process_service_info(name, info)
def _process_service_info(self, name: str, info: ServiceInfo) -> None:

View File

@ -1,6 +1,6 @@
async_timeout==4.0.3; python_version <= "3.10"
cryptography==42.0.2
voluptuous==0.14.1
voluptuous==0.14.2
PyYAML==6.0.1
paho-mqtt==1.6.1
colorama==0.4.6
@ -13,7 +13,7 @@ platformio==6.1.13 # When updating platformio, also update Dockerfile
esptool==4.7.0
click==8.1.7
esphome-dashboard==20231107.0
aioesphomeapi==21.0.2
aioesphomeapi==22.0.0
zeroconf==0.131.0
python-magic==0.4.27

View File

@ -1,11 +1,11 @@
pylint==3.0.3
flake8==7.0.0 # also change in .pre-commit-config.yaml when updating
black==23.12.1 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.15.0 # also change in .pre-commit-config.yaml when updating
black==24.2.0 # also change in .pre-commit-config.yaml when updating
pyupgrade==3.15.1 # also change in .pre-commit-config.yaml when updating
pre-commit
# Unit tests
pytest==7.4.4
pytest==8.0.1
pytest-cov==4.1.0
pytest-mock==3.12.0
pytest-asyncio==0.23.5

View File

@ -849,9 +849,11 @@ def convert(schema, config_var, path):
config_var["id_type"] = {
"class": str(data.base),
"parents": [str(x.base) for x in parents]
if isinstance(parents, list)
else None,
"parents": (
[str(x.base) for x in parents]
if isinstance(parents, list)
else None
),
}
elif schema_type == "use_id":
if inspect.ismodule(data):

View File

@ -3,6 +3,9 @@
set -e
# set -x
apt update
apt-get install avahi-utils -y
mkdir -p config
script/setup

View File

@ -20,4 +20,4 @@ animation:
- id: rgb565_animation
file: ../../pnglogo.png
type: RGB565
use_transparency: no
use_transparency: false

View File

@ -20,4 +20,4 @@ animation:
- id: rgb565_animation
file: ../../pnglogo.png
type: RGB565
use_transparency: no
use_transparency: false

View File

@ -20,4 +20,4 @@ animation:
- id: rgb565_animation
file: ../../pnglogo.png
type: RGB565
use_transparency: no
use_transparency: false

View File

@ -20,4 +20,4 @@ animation:
- id: rgb565_animation
file: ../../pnglogo.png
type: RGB565
use_transparency: no
use_transparency: false

View File

@ -20,4 +20,4 @@ animation:
- id: rgb565_animation
file: ../../pnglogo.png
type: RGB565
use_transparency: no
use_transparency: false

View File

@ -20,4 +20,4 @@ animation:
- id: rgb565_animation
file: ../../pnglogo.png
type: RGB565
use_transparency: no
use_transparency: false

View File

@ -0,0 +1,43 @@
i2c:
- id: i2c_dac7678
scl: 5
sda: 4
dac7678:
address: 0x4A
id: dac7678_hub
internal_reference: true
output:
- platform: dac7678
dac7678_id: dac7678_hub
channel: 0
id: dac7678_1_ch0
- platform: dac7678
dac7678_id: dac7678_hub
channel: 1
id: dac7678_1_ch1
- platform: dac7678
dac7678_id: dac7678_hub
channel: 2
id: dac7678_1_ch2
- platform: dac7678
dac7678_id: dac7678_hub
channel: 3
id: dac7678_1_ch3
- platform: dac7678
dac7678_id: dac7678_hub
channel: 4
id: dac7678_1_ch4
- platform: dac7678
dac7678_id: dac7678_hub
channel: 5
id: dac7678_1_ch5
- platform: dac7678
dac7678_id: dac7678_hub
channel: 6
id: dac7678_1_ch6
- platform: dac7678
dac7678_id: dac7678_hub
channel: 7
id: dac7678_1_ch7

View File

@ -0,0 +1,43 @@
i2c:
- id: i2c_dac7678
scl: 5
sda: 4
dac7678:
address: 0x4A
id: dac7678_hub
internal_reference: true
output:
- platform: dac7678
dac7678_id: dac7678_hub
channel: 0
id: dac7678_1_ch0
- platform: dac7678
dac7678_id: dac7678_hub
channel: 1
id: dac7678_1_ch1
- platform: dac7678
dac7678_id: dac7678_hub
channel: 2
id: dac7678_1_ch2
- platform: dac7678
dac7678_id: dac7678_hub
channel: 3
id: dac7678_1_ch3
- platform: dac7678
dac7678_id: dac7678_hub
channel: 4
id: dac7678_1_ch4
- platform: dac7678
dac7678_id: dac7678_hub
channel: 5
id: dac7678_1_ch5
- platform: dac7678
dac7678_id: dac7678_hub
channel: 6
id: dac7678_1_ch6
- platform: dac7678
dac7678_id: dac7678_hub
channel: 7
id: dac7678_1_ch7

View File

@ -0,0 +1,43 @@
i2c:
- id: i2c_dac7678
scl: 16
sda: 17
dac7678:
address: 0x4A
id: dac7678_hub
internal_reference: true
output:
- platform: dac7678
dac7678_id: dac7678_hub
channel: 0
id: dac7678_1_ch0
- platform: dac7678
dac7678_id: dac7678_hub
channel: 1
id: dac7678_1_ch1
- platform: dac7678
dac7678_id: dac7678_hub
channel: 2
id: dac7678_1_ch2
- platform: dac7678
dac7678_id: dac7678_hub
channel: 3
id: dac7678_1_ch3
- platform: dac7678
dac7678_id: dac7678_hub
channel: 4
id: dac7678_1_ch4
- platform: dac7678
dac7678_id: dac7678_hub
channel: 5
id: dac7678_1_ch5
- platform: dac7678
dac7678_id: dac7678_hub
channel: 6
id: dac7678_1_ch6
- platform: dac7678
dac7678_id: dac7678_hub
channel: 7
id: dac7678_1_ch7

View File

@ -0,0 +1,43 @@
i2c:
- id: i2c_dac7678
scl: 16
sda: 17
dac7678:
address: 0x4A
id: dac7678_hub
internal_reference: true
output:
- platform: dac7678
dac7678_id: dac7678_hub
channel: 0
id: dac7678_1_ch0
- platform: dac7678
dac7678_id: dac7678_hub
channel: 1
id: dac7678_1_ch1
- platform: dac7678
dac7678_id: dac7678_hub
channel: 2
id: dac7678_1_ch2
- platform: dac7678
dac7678_id: dac7678_hub
channel: 3
id: dac7678_1_ch3
- platform: dac7678
dac7678_id: dac7678_hub
channel: 4
id: dac7678_1_ch4
- platform: dac7678
dac7678_id: dac7678_hub
channel: 5
id: dac7678_1_ch5
- platform: dac7678
dac7678_id: dac7678_hub
channel: 6
id: dac7678_1_ch6
- platform: dac7678
dac7678_id: dac7678_hub
channel: 7
id: dac7678_1_ch7

View File

@ -0,0 +1,43 @@
i2c:
- id: i2c_dac7678
scl: 5
sda: 4
dac7678:
address: 0x4A
id: dac7678_hub
internal_reference: true
output:
- platform: dac7678
dac7678_id: dac7678_hub
channel: 0
id: dac7678_1_ch0
- platform: dac7678
dac7678_id: dac7678_hub
channel: 1
id: dac7678_1_ch1
- platform: dac7678
dac7678_id: dac7678_hub
channel: 2
id: dac7678_1_ch2
- platform: dac7678
dac7678_id: dac7678_hub
channel: 3
id: dac7678_1_ch3
- platform: dac7678
dac7678_id: dac7678_hub
channel: 4
id: dac7678_1_ch4
- platform: dac7678
dac7678_id: dac7678_hub
channel: 5
id: dac7678_1_ch5
- platform: dac7678
dac7678_id: dac7678_hub
channel: 6
id: dac7678_1_ch6
- platform: dac7678
dac7678_id: dac7678_hub
channel: 7
id: dac7678_1_ch7

View File

@ -0,0 +1,43 @@
i2c:
- id: i2c_dac7678
scl: 5
sda: 4
dac7678:
address: 0x4A
id: dac7678_hub
internal_reference: true
output:
- platform: dac7678
dac7678_id: dac7678_hub
channel: 0
id: dac7678_1_ch0
- platform: dac7678
dac7678_id: dac7678_hub
channel: 1
id: dac7678_1_ch1
- platform: dac7678
dac7678_id: dac7678_hub
channel: 2
id: dac7678_1_ch2
- platform: dac7678
dac7678_id: dac7678_hub
channel: 3
id: dac7678_1_ch3
- platform: dac7678
dac7678_id: dac7678_hub
channel: 4
id: dac7678_1_ch4
- platform: dac7678
dac7678_id: dac7678_hub
channel: 5
id: dac7678_1_ch5
- platform: dac7678
dac7678_id: dac7678_hub
channel: 6
id: dac7678_1_ch6
- platform: dac7678
dac7678_id: dac7678_hub
channel: 7
id: dac7678_1_ch7

View File

@ -0,0 +1,12 @@
remote_transmitter:
pin: 2
carrier_duty_percent: 50%
climate:
- platform: heatpumpir
protocol: daikin
horizontal_default: middle
vertical_default: middle
name: HeatpumpIR Climate
min_temperature: 18
max_temperature: 30

View File

@ -0,0 +1,12 @@
remote_transmitter:
pin: 5
carrier_duty_percent: 50%
climate:
- platform: heatpumpir
protocol: daikin
horizontal_default: middle
vertical_default: middle
name: HeatpumpIR Climate
min_temperature: 18
max_temperature: 30

View File

@ -0,0 +1,7 @@
remote_transmitter:
pin: 2
carrier_duty_percent: 50%
climate:
- platform: daikin_brc
name: Daikin_brc Climate

View File

@ -0,0 +1,7 @@
remote_transmitter:
pin: 2
carrier_duty_percent: 50%
climate:
- platform: daikin_brc
name: Daikin_brc Climate

View File

@ -0,0 +1,7 @@
remote_transmitter:
pin: 2
carrier_duty_percent: 50%
climate:
- platform: daikin_brc
name: Daikin_brc Climate

View File

@ -0,0 +1,7 @@
remote_transmitter:
pin: 2
carrier_duty_percent: 50%
climate:
- platform: daikin_brc
name: Daikin_brc Climate

View File

@ -0,0 +1,7 @@
remote_transmitter:
pin: 5
carrier_duty_percent: 50%
climate:
- platform: daikin_brc
name: Daikin_brc Climate

View File

@ -0,0 +1,11 @@
dallas:
pin: 4
sensor:
- platform: dallas
address: 0x1C0000031EDD2A28
name: Dallas Temperature
resolution: 9
- platform: dallas
index: 1
name: Dallas Temperature

View File

@ -0,0 +1,11 @@
dallas:
pin: 4
sensor:
- platform: dallas
address: 0x1C0000031EDD2A28
name: Dallas Temperature
resolution: 9
- platform: dallas
index: 1
name: Dallas Temperature

View File

@ -0,0 +1,11 @@
dallas:
pin: 4
sensor:
- platform: dallas
address: 0x1C0000031EDD2A28
name: Dallas Temperature
resolution: 9
- platform: dallas
index: 1
name: Dallas Temperature

View File

@ -0,0 +1,11 @@
dallas:
pin: 4
sensor:
- platform: dallas
address: 0x1C0000031EDD2A28
name: Dallas Temperature
resolution: 9
- platform: dallas
index: 1
name: Dallas Temperature

View File

@ -0,0 +1,11 @@
dallas:
pin: 4
sensor:
- platform: dallas
address: 0x1C0000031EDD2A28
name: Dallas Temperature
resolution: 9
- platform: dallas
index: 1
name: Dallas Temperature

View File

@ -0,0 +1,11 @@
dallas:
pin: 4
sensor:
- platform: dallas
address: 0x1C0000031EDD2A28
name: Dallas Temperature
resolution: 9
- platform: dallas
index: 1
name: Dallas Temperature

View File

@ -0,0 +1,55 @@
uart:
- id: uart_daly_bms
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 4800
daly_bms:
update_interval: 20s
binary_sensor:
- platform: daly_bms
charging_mos_enabled:
name: Charging MOS
discharging_mos_enabled:
name: Discharging MOS
sensor:
- platform: daly_bms
voltage:
name: Battery Voltage
current:
name: Battery Current
battery_level:
name: Battery Level
max_cell_voltage:
name: Max Cell Voltage
max_cell_voltage_number:
name: Max Cell Voltage Number
min_cell_voltage:
name: Min Cell Voltage
min_cell_voltage_number:
name: Min Cell Voltage Number
max_temperature:
name: Max Temperature
max_temperature_probe_number:
name: Max Temperature Probe Number
min_temperature:
name: Min Temperature
min_temperature_probe_number:
name: Min Temperature Probe Number
remaining_capacity:
name: Remaining Capacity
cells_number:
name: Cells Number
temperature_1:
name: Temperature 1
temperature_2:
name: Temperature 2
text_sensor:
- platform: daly_bms
status:
name: BMS Status

View File

@ -0,0 +1,55 @@
uart:
- id: uart_daly_bms
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 4800
daly_bms:
update_interval: 20s
binary_sensor:
- platform: daly_bms
charging_mos_enabled:
name: Charging MOS
discharging_mos_enabled:
name: Discharging MOS
sensor:
- platform: daly_bms
voltage:
name: Battery Voltage
current:
name: Battery Current
battery_level:
name: Battery Level
max_cell_voltage:
name: Max Cell Voltage
max_cell_voltage_number:
name: Max Cell Voltage Number
min_cell_voltage:
name: Min Cell Voltage
min_cell_voltage_number:
name: Min Cell Voltage Number
max_temperature:
name: Max Temperature
max_temperature_probe_number:
name: Max Temperature Probe Number
min_temperature:
name: Min Temperature
min_temperature_probe_number:
name: Min Temperature Probe Number
remaining_capacity:
name: Remaining Capacity
cells_number:
name: Cells Number
temperature_1:
name: Temperature 1
temperature_2:
name: Temperature 2
text_sensor:
- platform: daly_bms
status:
name: BMS Status

View File

@ -0,0 +1,55 @@
uart:
- id: uart_daly_bms
tx_pin:
number: 17
rx_pin:
number: 16
baud_rate: 4800
daly_bms:
update_interval: 20s
binary_sensor:
- platform: daly_bms
charging_mos_enabled:
name: Charging MOS
discharging_mos_enabled:
name: Discharging MOS
sensor:
- platform: daly_bms
voltage:
name: Battery Voltage
current:
name: Battery Current
battery_level:
name: Battery Level
max_cell_voltage:
name: Max Cell Voltage
max_cell_voltage_number:
name: Max Cell Voltage Number
min_cell_voltage:
name: Min Cell Voltage
min_cell_voltage_number:
name: Min Cell Voltage Number
max_temperature:
name: Max Temperature
max_temperature_probe_number:
name: Max Temperature Probe Number
min_temperature:
name: Min Temperature
min_temperature_probe_number:
name: Min Temperature Probe Number
remaining_capacity:
name: Remaining Capacity
cells_number:
name: Cells Number
temperature_1:
name: Temperature 1
temperature_2:
name: Temperature 2
text_sensor:
- platform: daly_bms
status:
name: BMS Status

View File

@ -0,0 +1,55 @@
uart:
- id: uart_daly_bms
tx_pin:
number: 17
rx_pin:
number: 16
baud_rate: 4800
daly_bms:
update_interval: 20s
binary_sensor:
- platform: daly_bms
charging_mos_enabled:
name: Charging MOS
discharging_mos_enabled:
name: Discharging MOS
sensor:
- platform: daly_bms
voltage:
name: Battery Voltage
current:
name: Battery Current
battery_level:
name: Battery Level
max_cell_voltage:
name: Max Cell Voltage
max_cell_voltage_number:
name: Max Cell Voltage Number
min_cell_voltage:
name: Min Cell Voltage
min_cell_voltage_number:
name: Min Cell Voltage Number
max_temperature:
name: Max Temperature
max_temperature_probe_number:
name: Max Temperature Probe Number
min_temperature:
name: Min Temperature
min_temperature_probe_number:
name: Min Temperature Probe Number
remaining_capacity:
name: Remaining Capacity
cells_number:
name: Cells Number
temperature_1:
name: Temperature 1
temperature_2:
name: Temperature 2
text_sensor:
- platform: daly_bms
status:
name: BMS Status

View File

@ -0,0 +1,55 @@
uart:
- id: uart_daly_bms
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 4800
daly_bms:
update_interval: 20s
binary_sensor:
- platform: daly_bms
charging_mos_enabled:
name: Charging MOS
discharging_mos_enabled:
name: Discharging MOS
sensor:
- platform: daly_bms
voltage:
name: Battery Voltage
current:
name: Battery Current
battery_level:
name: Battery Level
max_cell_voltage:
name: Max Cell Voltage
max_cell_voltage_number:
name: Max Cell Voltage Number
min_cell_voltage:
name: Min Cell Voltage
min_cell_voltage_number:
name: Min Cell Voltage Number
max_temperature:
name: Max Temperature
max_temperature_probe_number:
name: Max Temperature Probe Number
min_temperature:
name: Min Temperature
min_temperature_probe_number:
name: Min Temperature Probe Number
remaining_capacity:
name: Remaining Capacity
cells_number:
name: Cells Number
temperature_1:
name: Temperature 1
temperature_2:
name: Temperature 2
text_sensor:
- platform: daly_bms
status:
name: BMS Status

View File

@ -0,0 +1,55 @@
uart:
- id: uart_daly_bms
tx_pin:
number: 4
rx_pin:
number: 5
baud_rate: 4800
daly_bms:
update_interval: 20s
binary_sensor:
- platform: daly_bms
charging_mos_enabled:
name: Charging MOS
discharging_mos_enabled:
name: Discharging MOS
sensor:
- platform: daly_bms
voltage:
name: Battery Voltage
current:
name: Battery Current
battery_level:
name: Battery Level
max_cell_voltage:
name: Max Cell Voltage
max_cell_voltage_number:
name: Max Cell Voltage Number
min_cell_voltage:
name: Min Cell Voltage
min_cell_voltage_number:
name: Min Cell Voltage Number
max_temperature:
name: Max Temperature
max_temperature_probe_number:
name: Max Temperature Probe Number
min_temperature:
name: Min Temperature
min_temperature_probe_number:
name: Min Temperature Probe Number
remaining_capacity:
name: Remaining Capacity
cells_number:
name: Cells Number
temperature_1:
name: Temperature 1
temperature_2:
name: Temperature 2
text_sensor:
- platform: daly_bms
status:
name: BMS Status

View File

@ -0,0 +1 @@
debug:

View File

@ -0,0 +1 @@
debug:

View File

@ -0,0 +1 @@
debug:

View File

@ -0,0 +1 @@
debug:

View File

@ -0,0 +1 @@
debug:

View File

@ -0,0 +1 @@
debug:

View File

@ -0,0 +1,14 @@
esphome:
on_boot:
then:
- deep_sleep.prevent
- delay: 1s
- deep_sleep.allow
deep_sleep:
run_duration:
default: 10s
gpio_wakeup_reason: 30s
sleep_duration: 50s
wakeup_pin: 4
wakeup_pin_mode: INVERT_WAKEUP

View File

@ -0,0 +1,14 @@
esphome:
on_boot:
then:
- deep_sleep.prevent
- delay: 1s
- deep_sleep.allow
deep_sleep:
run_duration:
default: 10s
gpio_wakeup_reason: 30s
sleep_duration: 50s
wakeup_pin: 4
wakeup_pin_mode: INVERT_WAKEUP

View File

@ -0,0 +1,14 @@
esphome:
on_boot:
then:
- deep_sleep.prevent
- delay: 1s
- deep_sleep.allow
deep_sleep:
run_duration:
default: 10s
gpio_wakeup_reason: 30s
sleep_duration: 50s
wakeup_pin: 4
wakeup_pin_mode: INVERT_WAKEUP

View File

@ -0,0 +1,14 @@
esphome:
on_boot:
then:
- deep_sleep.prevent
- delay: 1s
- deep_sleep.allow
deep_sleep:
run_duration:
default: 10s
gpio_wakeup_reason: 30s
sleep_duration: 50s
wakeup_pin: 4
wakeup_pin_mode: INVERT_WAKEUP

View File

@ -0,0 +1,10 @@
esphome:
on_boot:
then:
- deep_sleep.prevent
- delay: 1s
- deep_sleep.allow
deep_sleep:
run_duration: 10s
sleep_duration: 50s

View File

@ -0,0 +1,7 @@
remote_transmitter:
pin: 2
carrier_duty_percent: 50%
climate:
- platform: delonghi
name: Delonghi Climate

Some files were not shown because too many files have changed in this diff Show More