Compare commits
64 Commits
2023.4.2
...
jesserockz
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fcf0761ba3 | ||
|
|
4b9732629e | ||
|
|
fb094fca0f | ||
|
|
de10b356cf | ||
|
|
bd6d6caa8a | ||
|
|
1c4af08ed3 | ||
|
|
c97d361b6c | ||
|
|
379b1d84dd | ||
|
|
c13e20643b | ||
|
|
76b6fcf554 | ||
|
|
57e909e790 | ||
|
|
d6f7876e68 | ||
|
|
56e0923c22 | ||
|
|
f4b98f5e32 | ||
|
|
2d56b70a36 | ||
|
|
980cfaf295 | ||
|
|
c2a43c733a | ||
|
|
568e65a6ab | ||
|
|
5cef11acf0 | ||
|
|
b1ad48c824 | ||
|
|
35b1b5fae6 | ||
|
|
ce20b64712 | ||
|
|
1720b2a37e | ||
|
|
c974fbbea8 | ||
|
|
00c5233bf4 | ||
|
|
7efb138933 | ||
|
|
f73435f820 | ||
|
|
bfc55beedc | ||
|
|
9fa057eae8 | ||
|
|
59d6b3afa0 | ||
|
|
70aa38f5bd | ||
|
|
55ec082628 | ||
|
|
6f27126c8d | ||
|
|
ee21a91313 | ||
|
|
c5efaa1c00 | ||
|
|
6476357596 | ||
|
|
77f71acbc8 | ||
|
|
4a08a5413d | ||
|
|
e3d89cc6b6 | ||
|
|
64afb07e91 | ||
|
|
f639f7c280 | ||
|
|
986dd2ddd2 | ||
|
|
7abdb5d046 | ||
|
|
4a177e3931 | ||
|
|
bef5b38d49 | ||
|
|
0a95f116fc | ||
|
|
327cd662b4 | ||
|
|
bb05ba3d00 | ||
|
|
c0ad5d1d16 | ||
|
|
4c39631428 | ||
|
|
afc2b3b74f | ||
|
|
0f7e34e7ec | ||
|
|
2be703b329 | ||
|
|
4cea74ef3b | ||
|
|
3a587ea0d4 | ||
|
|
8a60919e1f | ||
|
|
382dcddf12 | ||
|
|
6b67acbeb5 | ||
|
|
7b0fca6824 | ||
|
|
47555d314a | ||
|
|
0643b71908 | ||
|
|
afc848bf22 | ||
|
|
cc1eb648f9 | ||
|
|
04a139fe3d |
2
.github/workflows/ci-docker.yml
vendored
2
.github/workflows/ci-docker.yml
vendored
@@ -11,6 +11,7 @@ on:
|
||||
- ".github/workflows/**"
|
||||
- "requirements*.txt"
|
||||
- "platformio.ini"
|
||||
- "script/platformio_install_deps.py"
|
||||
|
||||
pull_request:
|
||||
paths:
|
||||
@@ -18,6 +19,7 @@ on:
|
||||
- ".github/workflows/**"
|
||||
- "requirements*.txt"
|
||||
- "platformio.ini"
|
||||
- "script/platformio_install_deps.py"
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
1
.github/workflows/ci.yml
vendored
1
.github/workflows/ci.yml
vendored
@@ -23,6 +23,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
max-parallel: 5
|
||||
matrix:
|
||||
include:
|
||||
- id: ci-custom
|
||||
|
||||
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -117,7 +117,7 @@ jobs:
|
||||
--suffix "${{ matrix.image.suffix }}"
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
context: .
|
||||
file: ./docker/Dockerfile
|
||||
|
||||
2
.github/workflows/sync-device-classes.yml
vendored
2
.github/workflows/sync-device-classes.yml
vendored
@@ -48,7 +48,7 @@ jobs:
|
||||
echo "$delimiter" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Commit changes
|
||||
uses: peter-evans/create-pull-request@v4
|
||||
uses: peter-evans/create-pull-request@v5
|
||||
with:
|
||||
commit-message: "Synchronise Device Classes from Home Assistant"
|
||||
committer: esphomebot <esphome@nabucasa.com>
|
||||
|
||||
@@ -27,7 +27,7 @@ repos:
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v3.3.1
|
||||
rev: v3.3.2
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py39-plus]
|
||||
|
||||
@@ -21,6 +21,7 @@ esphome/components/airthings_wave_mini/* @ncareau
|
||||
esphome/components/airthings_wave_plus/* @jeromelaban
|
||||
esphome/components/am43/* @buxtronix
|
||||
esphome/components/am43/cover/* @buxtronix
|
||||
esphome/components/am43/sensor/* @buxtronix
|
||||
esphome/components/analog_threshold/* @ianchi
|
||||
esphome/components/animation/* @syndlex
|
||||
esphome/components/anova/* @buxtronix
|
||||
@@ -109,6 +110,7 @@ esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hte501/* @Stock-M
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/hyt271/* @Philippe12
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/i2s_audio/* @jesserockz
|
||||
esphome/components/i2s_audio/media_player/* @jesserockz
|
||||
@@ -138,6 +140,7 @@ esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/matrix_keypad/* @ssieb
|
||||
esphome/components/max31865/* @DAVe3283
|
||||
esphome/components/max44009/* @berfenger
|
||||
esphome/components/max6956/* @looping40
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/max9611/* @mckaymatthew
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
@@ -162,6 +165,7 @@ esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mlx90393/* @functionpointer
|
||||
esphome/components/mlx90614/* @jesserockz
|
||||
esphome/components/mmc5603/* @benhoff
|
||||
esphome/components/modbus_controller/* @martgras
|
||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||
@@ -186,6 +190,7 @@ esphome/components/nfc/* @jesserockz
|
||||
esphome/components/number/* @esphome/core
|
||||
esphome/components/ota/* @esphome/core
|
||||
esphome/components/output/* @esphome/core
|
||||
esphome/components/pca6416a/* @Mat931
|
||||
esphome/components/pca9554/* @hwstar
|
||||
esphome/components/pcf85063/* @brogon
|
||||
esphome/components/pid/* @OttoWinter
|
||||
|
||||
@@ -24,8 +24,9 @@ RUN \
|
||||
python3-setuptools=52.0.0-4 \
|
||||
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
python3-venv=3.9.2-3 \
|
||||
iputils-ping=3:20210202-1 \
|
||||
git=1:2.30.2-1 \
|
||||
git=1:2.30.2-1+deb11u2 \
|
||||
curl=7.74.0-1.3+deb11u7 \
|
||||
openssh-client=1:8.4p1-5+deb11u1 \
|
||||
&& rm -rf \
|
||||
@@ -59,10 +60,10 @@ RUN \
|
||||
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
COPY requirements.txt requirements_optional.txt script/platformio_install_deps.py platformio.ini /
|
||||
RUN \
|
||||
pip3 install --no-cache-dir -r /requirements.txt -r /requirements_optional.txt \
|
||||
&& /platformio_install_deps.py /platformio.ini
|
||||
&& /platformio_install_deps.py /platformio.ini --libraries
|
||||
|
||||
|
||||
# ======================= docker-type image =======================
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# This script is used in the docker containers to preinstall
|
||||
# all platformio libraries in the global storage
|
||||
|
||||
import configparser
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
config = configparser.ConfigParser(inline_comment_prefixes=(';', ))
|
||||
config.read(sys.argv[1])
|
||||
|
||||
libs = []
|
||||
# Extract from every lib_deps key in all sections
|
||||
for section in config.sections():
|
||||
conf = config[section]
|
||||
if "lib_deps" not in conf:
|
||||
continue
|
||||
for lib_dep in conf["lib_deps"].splitlines():
|
||||
if not lib_dep:
|
||||
# Empty line or comment
|
||||
continue
|
||||
if lib_dep.startswith("${"):
|
||||
# Extending from another section
|
||||
continue
|
||||
if "@" not in lib_dep:
|
||||
# No version pinned, this is an internal lib
|
||||
continue
|
||||
libs.append(lib_dep)
|
||||
|
||||
subprocess.check_call(['platformio', 'lib', '-g', 'install', *libs])
|
||||
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
|
||||
@@ -5,7 +5,7 @@ from esphome.const import CONF_ID, CONF_PIN
|
||||
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
AUTO_LOAD = ["am43", "sensor"]
|
||||
AUTO_LOAD = ["am43"]
|
||||
|
||||
CONF_INVERT_POSITION = "invert_position"
|
||||
|
||||
@@ -27,10 +27,10 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
cg.add(var.set_invert_position(config[CONF_INVERT_POSITION]))
|
||||
yield cg.register_component(var, config)
|
||||
yield cover.register_cover(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await cover.register_cover(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
@@ -40,6 +40,7 @@ void Am43Component::loop() {
|
||||
|
||||
CoverTraits Am43Component::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_stop(true);
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_tilt(false);
|
||||
traits.set_is_assumed_state(false);
|
||||
|
||||
@@ -11,6 +11,7 @@ from esphome.const import (
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
AUTO_LOAD = ["am43"]
|
||||
CODEOWNERS = ["@buxtronix"]
|
||||
|
||||
am43_ns = cg.esphome_ns.namespace("am43")
|
||||
@@ -38,15 +39,15 @@ CONFIG_SCHEMA = (
|
||||
)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield cg.register_component(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
await cg.register_component(var, config)
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery(sens))
|
||||
|
||||
if CONF_ILLUMINANCE in config:
|
||||
sens = yield sensor.new_sensor(config[CONF_ILLUMINANCE])
|
||||
sens = await sensor.new_sensor(config[CONF_ILLUMINANCE])
|
||||
cg.add(var.set_illuminance(sens))
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "am43.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "am43_sensor.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -288,6 +288,7 @@ message ListEntitiesCoverResponse {
|
||||
bool disabled_by_default = 9;
|
||||
string icon = 10;
|
||||
EntityCategory entity_category = 11;
|
||||
bool supports_stop = 12;
|
||||
}
|
||||
|
||||
enum LegacyCoverState {
|
||||
@@ -861,8 +862,7 @@ message ClimateStateResponse {
|
||||
float target_temperature = 4;
|
||||
float target_temperature_low = 5;
|
||||
float target_temperature_high = 6;
|
||||
// For older peers, equal to preset == CLIMATE_PRESET_AWAY
|
||||
bool legacy_away = 7;
|
||||
bool unused_legacy_away = 7;
|
||||
ClimateAction action = 8;
|
||||
ClimateFanMode fan_mode = 9;
|
||||
ClimateSwingMode swing_mode = 10;
|
||||
@@ -885,9 +885,8 @@ message ClimateCommandRequest {
|
||||
float target_temperature_low = 7;
|
||||
bool has_target_temperature_high = 8;
|
||||
float target_temperature_high = 9;
|
||||
// legacy, for older peers, newer ones should use CLIMATE_PRESET_AWAY in preset
|
||||
bool has_legacy_away = 10;
|
||||
bool legacy_away = 11;
|
||||
bool unused_has_legacy_away = 10;
|
||||
bool unused_legacy_away = 11;
|
||||
bool has_fan_mode = 12;
|
||||
ClimateFanMode fan_mode = 13;
|
||||
bool has_swing_mode = 14;
|
||||
|
||||
@@ -530,7 +530,6 @@ bool APIConnection::send_climate_state(climate::Climate *climate) {
|
||||
resp.custom_fan_mode = climate->custom_fan_mode.value();
|
||||
if (traits.get_supports_presets() && climate->preset.has_value()) {
|
||||
resp.preset = static_cast<enums::ClimatePreset>(climate->preset.value());
|
||||
resp.legacy_away = resp.preset == enums::CLIMATE_PRESET_AWAY;
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && climate->custom_preset.has_value())
|
||||
resp.custom_preset = climate->custom_preset.value();
|
||||
@@ -591,8 +590,6 @@ void APIConnection::climate_command(const ClimateCommandRequest &msg) {
|
||||
call.set_target_temperature_low(msg.target_temperature_low);
|
||||
if (msg.has_target_temperature_high)
|
||||
call.set_target_temperature_high(msg.target_temperature_high);
|
||||
if (msg.has_legacy_away)
|
||||
call.set_preset(msg.legacy_away ? climate::CLIMATE_PRESET_AWAY : climate::CLIMATE_PRESET_HOME);
|
||||
if (msg.has_fan_mode)
|
||||
call.set_fan_mode(static_cast<climate::ClimateFanMode>(msg.fan_mode));
|
||||
if (msg.has_custom_fan_mode)
|
||||
@@ -944,7 +941,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
|
||||
|
||||
HelloResponse resp;
|
||||
resp.api_version_major = 1;
|
||||
resp.api_version_minor = 7;
|
||||
resp.api_version_minor = 8;
|
||||
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
|
||||
resp.name = App.get_name();
|
||||
|
||||
|
||||
@@ -941,6 +941,10 @@ bool ListEntitiesCoverResponse::decode_varint(uint32_t field_id, ProtoVarInt val
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
this->supports_stop = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -993,6 +997,7 @@ void ListEntitiesCoverResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(9, this->disabled_by_default);
|
||||
buffer.encode_string(10, this->icon);
|
||||
buffer.encode_enum<enums::EntityCategory>(11, this->entity_category);
|
||||
buffer.encode_bool(12, this->supports_stop);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
@@ -1042,6 +1047,10 @@ void ListEntitiesCoverResponse::dump_to(std::string &out) const {
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_stop: ");
|
||||
out.append(YESNO(this->supports_stop));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -3649,7 +3658,7 @@ bool ClimateStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->legacy_away = value.as_bool();
|
||||
this->unused_legacy_away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
@@ -3719,7 +3728,7 @@ void ClimateStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(4, this->target_temperature);
|
||||
buffer.encode_float(5, this->target_temperature_low);
|
||||
buffer.encode_float(6, this->target_temperature_high);
|
||||
buffer.encode_bool(7, this->legacy_away);
|
||||
buffer.encode_bool(7, this->unused_legacy_away);
|
||||
buffer.encode_enum<enums::ClimateAction>(8, this->action);
|
||||
buffer.encode_enum<enums::ClimateFanMode>(9, this->fan_mode);
|
||||
buffer.encode_enum<enums::ClimateSwingMode>(10, this->swing_mode);
|
||||
@@ -3760,8 +3769,8 @@ void ClimateStateResponse::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_away: ");
|
||||
out.append(YESNO(this->legacy_away));
|
||||
out.append(" unused_legacy_away: ");
|
||||
out.append(YESNO(this->unused_legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" action: ");
|
||||
@@ -3813,11 +3822,11 @@ bool ClimateCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value)
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->has_legacy_away = value.as_bool();
|
||||
this->unused_has_legacy_away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->legacy_away = value.as_bool();
|
||||
this->unused_legacy_away = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 12: {
|
||||
@@ -3902,8 +3911,8 @@ void ClimateCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_float(7, this->target_temperature_low);
|
||||
buffer.encode_bool(8, this->has_target_temperature_high);
|
||||
buffer.encode_float(9, this->target_temperature_high);
|
||||
buffer.encode_bool(10, this->has_legacy_away);
|
||||
buffer.encode_bool(11, this->legacy_away);
|
||||
buffer.encode_bool(10, this->unused_has_legacy_away);
|
||||
buffer.encode_bool(11, this->unused_legacy_away);
|
||||
buffer.encode_bool(12, this->has_fan_mode);
|
||||
buffer.encode_enum<enums::ClimateFanMode>(13, this->fan_mode);
|
||||
buffer.encode_bool(14, this->has_swing_mode);
|
||||
@@ -3959,12 +3968,12 @@ void ClimateCommandRequest::dump_to(std::string &out) const {
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_legacy_away: ");
|
||||
out.append(YESNO(this->has_legacy_away));
|
||||
out.append(" unused_has_legacy_away: ");
|
||||
out.append(YESNO(this->unused_has_legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" legacy_away: ");
|
||||
out.append(YESNO(this->legacy_away));
|
||||
out.append(" unused_legacy_away: ");
|
||||
out.append(YESNO(this->unused_legacy_away));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_fan_mode: ");
|
||||
|
||||
@@ -375,6 +375,7 @@ class ListEntitiesCoverResponse : public ProtoMessage {
|
||||
bool disabled_by_default{false};
|
||||
std::string icon{};
|
||||
enums::EntityCategory entity_category{};
|
||||
bool supports_stop{false};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -958,7 +959,7 @@ class ClimateStateResponse : public ProtoMessage {
|
||||
float target_temperature{0.0f};
|
||||
float target_temperature_low{0.0f};
|
||||
float target_temperature_high{0.0f};
|
||||
bool legacy_away{false};
|
||||
bool unused_legacy_away{false};
|
||||
enums::ClimateAction action{};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
enums::ClimateSwingMode swing_mode{};
|
||||
@@ -986,8 +987,8 @@ class ClimateCommandRequest : public ProtoMessage {
|
||||
float target_temperature_low{0.0f};
|
||||
bool has_target_temperature_high{false};
|
||||
float target_temperature_high{0.0f};
|
||||
bool has_legacy_away{false};
|
||||
bool legacy_away{false};
|
||||
bool unused_has_legacy_away{false};
|
||||
bool unused_legacy_away{false};
|
||||
bool has_fan_mode{false};
|
||||
enums::ClimateFanMode fan_mode{};
|
||||
bool has_swing_mode{false};
|
||||
|
||||
@@ -18,5 +18,5 @@ async def to_code(config):
|
||||
# https://github.com/esphome/AsyncTCP/blob/master/library.json
|
||||
cg.add_library("esphome/AsyncTCP-esphome", "1.2.2")
|
||||
elif CORE.is_esp8266:
|
||||
# https://github.com/OttoWinter/ESPAsyncTCP
|
||||
cg.add_library("ottowinter/ESPAsyncTCP-esphome", "1.2.3")
|
||||
# https://github.com/esphome/ESPAsyncTCP
|
||||
cg.add_library("esphome/ESPAsyncTCP-esphome", "1.2.3")
|
||||
|
||||
@@ -43,12 +43,7 @@ void BinarySensor::send_state_internal(bool state, bool is_initial) {
|
||||
}
|
||||
|
||||
BinarySensor::BinarySensor() : state(false) {}
|
||||
void BinarySensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
std::string BinarySensor::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
return "";
|
||||
}
|
||||
|
||||
void BinarySensor::add_filter(Filter *filter) {
|
||||
filter->parent_ = this;
|
||||
if (this->filter_list_ == nullptr) {
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace binary_sensor {
|
||||
* The sub classes should notify the front-end of new states via the publish_state() method which
|
||||
* handles inverted inputs for you.
|
||||
*/
|
||||
class BinarySensor : public EntityBase {
|
||||
class BinarySensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
explicit BinarySensor();
|
||||
|
||||
@@ -60,12 +60,6 @@ class BinarySensor : public EntityBase {
|
||||
/// The current reported state of the binary sensor.
|
||||
bool state;
|
||||
|
||||
/// Manually set the Home Assistant device class (see binary_sensor::device_class)
|
||||
void set_device_class(const std::string &device_class);
|
||||
|
||||
/// Get the device class for this binary sensor, using the manual override if specified.
|
||||
std::string get_device_class();
|
||||
|
||||
void add_filter(Filter *filter);
|
||||
void add_filters(const std::vector<Filter *> &filters);
|
||||
|
||||
@@ -82,7 +76,6 @@ class BinarySensor : public EntityBase {
|
||||
|
||||
protected:
|
||||
CallbackManager<void(bool)> state_callback_{};
|
||||
optional<std::string> device_class_{}; ///< Stores the override of the device class
|
||||
Filter *filter_list_{nullptr};
|
||||
bool has_state_{false};
|
||||
bool publish_initial_state_{false};
|
||||
|
||||
@@ -16,6 +16,9 @@ void BinarySensorMap::loop() {
|
||||
case BINARY_SENSOR_MAP_TYPE_SUM:
|
||||
this->process_sum_();
|
||||
break;
|
||||
case BINARY_SENSOR_MAP_TYPE_BAYESIAN:
|
||||
this->process_bayesian_();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,46 +26,51 @@ void BinarySensorMap::process_group_() {
|
||||
float total_current_value = 0.0;
|
||||
uint8_t num_active_sensors = 0;
|
||||
uint64_t mask = 0x00;
|
||||
// check all binary_sensors for its state. when active add its value to total_current_value.
|
||||
// create a bitmask for the binary_sensor status on all channels
|
||||
|
||||
// - check all binary_sensors for its state
|
||||
// - if active, add its value to total_current_value.
|
||||
// - creates a bitmask for the binary_sensor states on all channels
|
||||
for (size_t i = 0; i < this->channels_.size(); i++) {
|
||||
auto bs = this->channels_[i];
|
||||
if (bs.binary_sensor->state) {
|
||||
num_active_sensors++;
|
||||
total_current_value += bs.sensor_value;
|
||||
total_current_value += bs.parameters.sensor_value;
|
||||
mask |= 1ULL << i;
|
||||
}
|
||||
}
|
||||
// check if the sensor map was touched
|
||||
|
||||
// potentially update state only if a binary_sensor is active
|
||||
if (mask != 0ULL) {
|
||||
// did the bit_mask change or is it a new sensor touch
|
||||
// publish the average if the bitmask has changed
|
||||
if (this->last_mask_ != mask) {
|
||||
float publish_value = total_current_value / num_active_sensors;
|
||||
this->publish_state(publish_value);
|
||||
}
|
||||
} else if (this->last_mask_ != 0ULL) {
|
||||
// is this a new sensor release
|
||||
// no buttons are pressed and the states have changed since last run, so publish NAN
|
||||
ESP_LOGV(TAG, "'%s' - No binary sensor active, publishing NAN", this->name_.c_str());
|
||||
this->publish_state(NAN);
|
||||
}
|
||||
|
||||
this->last_mask_ = mask;
|
||||
}
|
||||
|
||||
void BinarySensorMap::process_sum_() {
|
||||
float total_current_value = 0.0;
|
||||
uint64_t mask = 0x00;
|
||||
|
||||
// - check all binary_sensor states
|
||||
// - if active, add its value to total_current_value
|
||||
// - creates a bitmask for the binary_sensor status on all channels
|
||||
// - creates a bitmask for the binary_sensor states on all channels
|
||||
for (size_t i = 0; i < this->channels_.size(); i++) {
|
||||
auto bs = this->channels_[i];
|
||||
if (bs.binary_sensor->state) {
|
||||
total_current_value += bs.sensor_value;
|
||||
total_current_value += bs.parameters.sensor_value;
|
||||
mask |= 1ULL << i;
|
||||
}
|
||||
}
|
||||
|
||||
// update state only if the binary sensor states have changed or if no state has ever been sent on boot
|
||||
// update state only if any binary_sensor states have changed or if no state has ever been sent on boot
|
||||
if ((this->last_mask_ != mask) || (!this->has_state())) {
|
||||
this->publish_state(total_current_value);
|
||||
}
|
||||
@@ -70,15 +78,65 @@ void BinarySensorMap::process_sum_() {
|
||||
this->last_mask_ = mask;
|
||||
}
|
||||
|
||||
void BinarySensorMap::process_bayesian_() {
|
||||
float posterior_probability = this->bayesian_prior_;
|
||||
uint64_t mask = 0x00;
|
||||
|
||||
// - compute the posterior probability by taking the product of the predicate probablities for each observation
|
||||
// - create a bitmask for the binary_sensor states on all channels/observations
|
||||
for (size_t i = 0; i < this->channels_.size(); i++) {
|
||||
auto bs = this->channels_[i];
|
||||
|
||||
posterior_probability *=
|
||||
this->bayesian_predicate_(bs.binary_sensor->state, posterior_probability,
|
||||
bs.parameters.probabilities.given_true, bs.parameters.probabilities.given_false);
|
||||
|
||||
mask |= ((uint64_t) (bs.binary_sensor->state)) << i;
|
||||
}
|
||||
|
||||
// update state only if any binary_sensor states have changed or if no state has ever been sent on boot
|
||||
if ((this->last_mask_ != mask) || (!this->has_state())) {
|
||||
this->publish_state(posterior_probability);
|
||||
}
|
||||
|
||||
this->last_mask_ = mask;
|
||||
}
|
||||
|
||||
float BinarySensorMap::bayesian_predicate_(bool sensor_state, float prior, float prob_given_true,
|
||||
float prob_given_false) {
|
||||
float prob_state_source_true = prob_given_true;
|
||||
float prob_state_source_false = prob_given_false;
|
||||
|
||||
// if sensor is off, then we use the probabilities for the observation's complement
|
||||
if (!sensor_state) {
|
||||
prob_state_source_true = 1 - prob_given_true;
|
||||
prob_state_source_false = 1 - prob_given_false;
|
||||
}
|
||||
|
||||
return prob_state_source_true / (prior * prob_state_source_true + (1.0 - prior) * prob_state_source_false);
|
||||
}
|
||||
|
||||
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float value) {
|
||||
BinarySensorMapChannel sensor_channel{
|
||||
.binary_sensor = sensor,
|
||||
.sensor_value = value,
|
||||
.parameters{
|
||||
.sensor_value = value,
|
||||
},
|
||||
};
|
||||
this->channels_.push_back(sensor_channel);
|
||||
}
|
||||
|
||||
void BinarySensorMap::set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
|
||||
|
||||
void BinarySensorMap::add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false) {
|
||||
BinarySensorMapChannel sensor_channel{
|
||||
.binary_sensor = sensor,
|
||||
.parameters{
|
||||
.probabilities{
|
||||
.given_true = prob_given_true,
|
||||
.given_false = prob_given_false,
|
||||
},
|
||||
},
|
||||
};
|
||||
this->channels_.push_back(sensor_channel);
|
||||
}
|
||||
} // namespace binary_sensor_map
|
||||
} // namespace esphome
|
||||
|
||||
@@ -12,51 +12,88 @@ namespace binary_sensor_map {
|
||||
enum BinarySensorMapType {
|
||||
BINARY_SENSOR_MAP_TYPE_GROUP,
|
||||
BINARY_SENSOR_MAP_TYPE_SUM,
|
||||
BINARY_SENSOR_MAP_TYPE_BAYESIAN,
|
||||
};
|
||||
|
||||
struct BinarySensorMapChannel {
|
||||
binary_sensor::BinarySensor *binary_sensor;
|
||||
float sensor_value;
|
||||
union {
|
||||
float sensor_value;
|
||||
struct {
|
||||
float given_true;
|
||||
float given_false;
|
||||
} probabilities;
|
||||
} parameters;
|
||||
};
|
||||
|
||||
/** Class to group binary_sensors to one Sensor.
|
||||
/** Class to map one or more binary_sensors to one Sensor.
|
||||
*
|
||||
* Each binary sensor represents a float value in the group.
|
||||
* Each binary sensor has configured parameters that each mapping type uses to compute the single numerical result
|
||||
*/
|
||||
class BinarySensorMap : public sensor::Sensor, public Component {
|
||||
public:
|
||||
void dump_config() override;
|
||||
|
||||
/**
|
||||
* The loop checks all binary_sensor states
|
||||
* When the binary_sensor reports a true value for its state, then the float value it represents is added to the
|
||||
* total_current_value
|
||||
* The loop calls the configured type processing method
|
||||
*
|
||||
* Only when the total_current_value changed and at least one sensor reports an active state we publish the sensors
|
||||
* average value. When the value changed and no sensors ar active we publish NAN.
|
||||
* */
|
||||
* The processing method loops through all sensors and calculates the numerical result
|
||||
* The result is only published if a binary sensor state has changed or, for some types, on initial boot
|
||||
*/
|
||||
void loop() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
/** Add binary_sensors to the group.
|
||||
* Each binary_sensor represents a float value when its state is true
|
||||
|
||||
/**
|
||||
* Add binary_sensors to the group when only one parameter is needed for the configured mapping type.
|
||||
*
|
||||
* @param *sensor The binary sensor.
|
||||
* @param value The value this binary_sensor represents
|
||||
*/
|
||||
void add_channel(binary_sensor::BinarySensor *sensor, float value);
|
||||
void set_sensor_type(BinarySensorMapType sensor_type);
|
||||
|
||||
/**
|
||||
* Add binary_sensors to the group when two parameters are needed for the Bayesian mapping type.
|
||||
*
|
||||
* @param *sensor The binary sensor.
|
||||
* @param prob_given_true Probability this observation is on when the Bayesian event is true
|
||||
* @param prob_given_false Probability this observation is on when the Bayesian event is false
|
||||
*/
|
||||
void add_channel(binary_sensor::BinarySensor *sensor, float prob_given_true, float prob_given_false);
|
||||
|
||||
void set_sensor_type(BinarySensorMapType sensor_type) { this->sensor_type_ = sensor_type; }
|
||||
|
||||
void set_bayesian_prior(float prior) { this->bayesian_prior_ = prior; };
|
||||
|
||||
protected:
|
||||
std::vector<BinarySensorMapChannel> channels_{};
|
||||
BinarySensorMapType sensor_type_{BINARY_SENSOR_MAP_TYPE_GROUP};
|
||||
// this gives max 64 channels per binary_sensor_map
|
||||
|
||||
// this allows a max of 64 channels/observations in order to keep track of binary_sensor states
|
||||
uint64_t last_mask_{0x00};
|
||||
|
||||
// Bayesian event prior probability before taking into account any observations
|
||||
float bayesian_prior_{};
|
||||
|
||||
/**
|
||||
* methods to process the types of binary_sensor_maps
|
||||
* GROUP: process_group_() just map to a value
|
||||
* Methods to process the binary_sensor_maps types
|
||||
*
|
||||
* GROUP: process_group_() averages all the values
|
||||
* ADD: process_add_() adds all the values
|
||||
* BAYESIAN: process_bayesian_() computes the predicate probability
|
||||
* */
|
||||
void process_group_();
|
||||
void process_sum_();
|
||||
void process_bayesian_();
|
||||
|
||||
/**
|
||||
* Computes the Bayesian predicate for a specific observation
|
||||
* If the sensor state is false, then we use the parameters' probabilities for the observatiosn complement
|
||||
*
|
||||
* @param sensor_state State of observation
|
||||
* @param prior Prior probability before accounting for this observation
|
||||
* @param prob_given_true Probability this observation is on when the Bayesian event is true
|
||||
* @param prob_given_false Probability this observation is on when the Bayesian event is false
|
||||
* */
|
||||
float bayesian_predicate_(bool sensor_state, float prior, float prob_given_true, float prob_given_false);
|
||||
};
|
||||
|
||||
} // namespace binary_sensor_map
|
||||
|
||||
@@ -20,16 +20,29 @@ BinarySensorMap = binary_sensor_map_ns.class_(
|
||||
)
|
||||
SensorMapType = binary_sensor_map_ns.enum("SensorMapType")
|
||||
|
||||
CONF_BAYESIAN = "bayesian"
|
||||
CONF_PRIOR = "prior"
|
||||
CONF_PROB_GIVEN_TRUE = "prob_given_true"
|
||||
CONF_PROB_GIVEN_FALSE = "prob_given_false"
|
||||
CONF_OBSERVATIONS = "observations"
|
||||
|
||||
SENSOR_MAP_TYPES = {
|
||||
CONF_GROUP: SensorMapType.BINARY_SENSOR_MAP_TYPE_GROUP,
|
||||
CONF_SUM: SensorMapType.BINARY_SENSOR_MAP_TYPE_SUM,
|
||||
CONF_BAYESIAN: SensorMapType.BINARY_SENSOR_MAP_TYPE_BAYESIAN,
|
||||
}
|
||||
|
||||
entry = {
|
||||
entry_one_parameter = {
|
||||
cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Required(CONF_VALUE): cv.float_,
|
||||
}
|
||||
|
||||
entry_bayesian_parameters = {
|
||||
cv.Required(CONF_BINARY_SENSOR): cv.use_id(binary_sensor.BinarySensor),
|
||||
cv.Required(CONF_PROB_GIVEN_TRUE): cv.float_range(min=0, max=1),
|
||||
cv.Required(CONF_PROB_GIVEN_FALSE): cv.float_range(min=0, max=1),
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
CONF_GROUP: sensor.sensor_schema(
|
||||
@@ -39,7 +52,7 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_CHANNELS): cv.All(
|
||||
cv.ensure_list(entry), cv.Length(min=1, max=64)
|
||||
cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
|
||||
),
|
||||
}
|
||||
),
|
||||
@@ -50,7 +63,18 @@ CONFIG_SCHEMA = cv.typed_schema(
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_CHANNELS): cv.All(
|
||||
cv.ensure_list(entry), cv.Length(min=1, max=64)
|
||||
cv.ensure_list(entry_one_parameter), cv.Length(min=1, max=64)
|
||||
),
|
||||
}
|
||||
),
|
||||
CONF_BAYESIAN: sensor.sensor_schema(
|
||||
BinarySensorMap,
|
||||
accuracy_decimals=2,
|
||||
).extend(
|
||||
{
|
||||
cv.Required(CONF_PRIOR): cv.float_range(min=0, max=1),
|
||||
cv.Required(CONF_OBSERVATIONS): cv.All(
|
||||
cv.ensure_list(entry_bayesian_parameters), cv.Length(min=1, max=64)
|
||||
),
|
||||
}
|
||||
),
|
||||
@@ -66,6 +90,17 @@ async def to_code(config):
|
||||
constant = SENSOR_MAP_TYPES[config[CONF_TYPE]]
|
||||
cg.add(var.set_sensor_type(constant))
|
||||
|
||||
for ch in config[CONF_CHANNELS]:
|
||||
input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
|
||||
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
|
||||
if config[CONF_TYPE] == CONF_BAYESIAN:
|
||||
cg.add(var.set_bayesian_prior(config[CONF_PRIOR]))
|
||||
|
||||
for obs in config[CONF_OBSERVATIONS]:
|
||||
input_var = await cg.get_variable(obs[CONF_BINARY_SENSOR])
|
||||
cg.add(
|
||||
var.add_channel(
|
||||
input_var, obs[CONF_PROB_GIVEN_TRUE], obs[CONF_PROB_GIVEN_FALSE]
|
||||
)
|
||||
)
|
||||
else:
|
||||
for ch in config[CONF_CHANNELS]:
|
||||
input_var = await cg.get_variable(ch[CONF_BINARY_SENSOR])
|
||||
cg.add(var.add_channel(input_var, ch[CONF_VALUE]))
|
||||
|
||||
@@ -29,8 +29,35 @@ BLEClientConnectTrigger = ble_client_ns.class_(
|
||||
BLEClientDisconnectTrigger = ble_client_ns.class_(
|
||||
"BLEClientDisconnectTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
BLEClientPasskeyRequestTrigger = ble_client_ns.class_(
|
||||
"BLEClientPasskeyRequestTrigger", automation.Trigger.template(BLEClientNodeConstRef)
|
||||
)
|
||||
BLEClientPasskeyNotificationTrigger = ble_client_ns.class_(
|
||||
"BLEClientPasskeyNotificationTrigger",
|
||||
automation.Trigger.template(BLEClientNodeConstRef, cg.uint32),
|
||||
)
|
||||
BLEClientNumericComparisonRequestTrigger = ble_client_ns.class_(
|
||||
"BLEClientNumericComparisonRequestTrigger",
|
||||
automation.Trigger.template(BLEClientNodeConstRef, cg.uint32),
|
||||
)
|
||||
|
||||
# Actions
|
||||
BLEWriteAction = ble_client_ns.class_("BLEClientWriteAction", automation.Action)
|
||||
BLEPasskeyReplyAction = ble_client_ns.class_(
|
||||
"BLEClientPasskeyReplyAction", automation.Action
|
||||
)
|
||||
BLENumericComparisonReplyAction = ble_client_ns.class_(
|
||||
"BLEClientNumericComparisonReplyAction", automation.Action
|
||||
)
|
||||
BLERemoveBondAction = ble_client_ns.class_(
|
||||
"BLEClientRemoveBondAction", automation.Action
|
||||
)
|
||||
|
||||
CONF_PASSKEY = "passkey"
|
||||
CONF_ACCEPT = "accept"
|
||||
CONF_ON_PASSKEY_REQUEST = "on_passkey_request"
|
||||
CONF_ON_PASSKEY_NOTIFICATION = "on_passkey_notification"
|
||||
CONF_ON_NUMERIC_COMPARISON_REQUEST = "on_numeric_comparison_request"
|
||||
|
||||
# Espressif platformio framework is built with MAX_BLE_CONN to 3, so
|
||||
# enforce this in yaml checks.
|
||||
@@ -56,6 +83,29 @@ CONFIG_SCHEMA = (
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PASSKEY_REQUEST): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientPasskeyRequestTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_PASSKEY_NOTIFICATION): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientPasskeyNotificationTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(
|
||||
CONF_ON_NUMERIC_COMPARISON_REQUEST
|
||||
): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
|
||||
BLEClientNumericComparisonRequestTrigger
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -85,13 +135,34 @@ BLE_WRITE_ACTION_SCHEMA = cv.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
cv.Required(CONF_ACCEPT): cv.templatable(cv.boolean),
|
||||
}
|
||||
)
|
||||
|
||||
BLE_PASSKEY_REPLY_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
cv.Required(CONF_PASSKEY): cv.templatable(cv.int_range(min=0, max=999999)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
BLE_REMOVE_BOND_ACTION_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(BLEClient),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.ble_write", BLEWriteAction, BLE_WRITE_ACTION_SCHEMA
|
||||
)
|
||||
async def ble_write_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
value = config[CONF_VALUE]
|
||||
if cg.is_template(value):
|
||||
@@ -137,6 +208,54 @@ async def ble_write_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.numeric_comparison_reply",
|
||||
BLENumericComparisonReplyAction,
|
||||
BLE_NUMERIC_COMPARISON_REPLY_ACTION_SCHEMA,
|
||||
)
|
||||
async def numeric_comparison_reply_to_code(config, action_id, template_arg, args):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
accept = config[CONF_ACCEPT]
|
||||
if cg.is_template(accept):
|
||||
templ = await cg.templatable(accept, args, cg.bool_)
|
||||
cg.add(var.set_value_template(templ))
|
||||
else:
|
||||
cg.add(var.set_value_simple(accept))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.passkey_reply", BLEPasskeyReplyAction, BLE_PASSKEY_REPLY_ACTION_SCHEMA
|
||||
)
|
||||
async def passkey_reply_to_code(config, action_id, template_arg, args):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
passkey = config[CONF_PASSKEY]
|
||||
if cg.is_template(passkey):
|
||||
templ = await cg.templatable(passkey, args, cg.uint32)
|
||||
cg.add(var.set_value_template(templ))
|
||||
else:
|
||||
cg.add(var.set_value_simple(passkey))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"ble_client.remove_bond",
|
||||
BLERemoveBondAction,
|
||||
BLE_REMOVE_BOND_ACTION_SCHEMA,
|
||||
)
|
||||
async def remove_bond_to_code(config, action_id, template_arg, args):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, parent)
|
||||
|
||||
return var
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
@@ -148,3 +267,12 @@ async def to_code(config):
|
||||
for conf in config.get(CONF_ON_DISCONNECT, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PASSKEY_REQUEST, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_PASSKEY_NOTIFICATION, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf)
|
||||
for conf in config.get(CONF_ON_NUMERIC_COMPARISON_REQUEST, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [(cg.uint32, "passkey")], conf)
|
||||
|
||||
@@ -37,6 +37,44 @@ class BLEClientDisconnectTrigger : public Trigger<>, public BLEClientNode {
|
||||
}
|
||||
};
|
||||
|
||||
class BLEClientPasskeyRequestTrigger : public Trigger<>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientPasskeyRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
|
||||
if (event == ESP_GAP_BLE_PASSKEY_REQ_EVT &&
|
||||
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
|
||||
this->trigger();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BLEClientPasskeyNotificationTrigger : public Trigger<uint32_t>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientPasskeyNotificationTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
|
||||
if (event == ESP_GAP_BLE_PASSKEY_NOTIF_EVT &&
|
||||
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
|
||||
uint32_t passkey = param->ble_security.key_notif.passkey;
|
||||
this->trigger(passkey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BLEClientNumericComparisonRequestTrigger : public Trigger<uint32_t>, public BLEClientNode {
|
||||
public:
|
||||
explicit BLEClientNumericComparisonRequestTrigger(BLEClient *parent) { parent->register_ble_node(this); }
|
||||
void loop() override {}
|
||||
void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override {
|
||||
if (event == ESP_GAP_BLE_NC_REQ_EVT &&
|
||||
memcmp(param->ble_security.auth_cmpl.bd_addr, this->parent_->get_remote_bda(), 6) == 0) {
|
||||
uint32_t passkey = param->ble_security.key_notif.passkey;
|
||||
this->trigger(passkey);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class BLEWriterClientNode : public BLEClientNode {
|
||||
public:
|
||||
BLEWriterClientNode(BLEClient *ble_client) {
|
||||
@@ -94,6 +132,86 @@ template<typename... Ts> class BLEClientWriteAction : public Action<Ts...>, publ
|
||||
std::function<std::vector<uint8_t>(Ts...)> value_template_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientPasskeyReplyAction : public Action<Ts...> {
|
||||
public:
|
||||
BLEClientPasskeyReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||
|
||||
void play(Ts... x) override {
|
||||
uint32_t passkey;
|
||||
if (has_simple_value_) {
|
||||
passkey = this->value_simple_;
|
||||
} else {
|
||||
passkey = this->value_template_(x...);
|
||||
}
|
||||
if (passkey > 999999)
|
||||
return;
|
||||
esp_bd_addr_t remote_bda;
|
||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||
esp_ble_passkey_reply(remote_bda, true, passkey);
|
||||
}
|
||||
|
||||
void set_value_template(std::function<uint32_t(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const uint32_t &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
BLEClient *parent_{nullptr};
|
||||
bool has_simple_value_ = true;
|
||||
uint32_t value_simple_{0};
|
||||
std::function<uint32_t(Ts...)> value_template_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientNumericComparisonReplyAction : public Action<Ts...> {
|
||||
public:
|
||||
BLEClientNumericComparisonReplyAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||
|
||||
void play(Ts... x) override {
|
||||
esp_bd_addr_t remote_bda;
|
||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||
if (has_simple_value_) {
|
||||
esp_ble_confirm_reply(remote_bda, this->value_simple_);
|
||||
} else {
|
||||
esp_ble_confirm_reply(remote_bda, this->value_template_(x...));
|
||||
}
|
||||
}
|
||||
|
||||
void set_value_template(std::function<bool(Ts...)> func) {
|
||||
this->value_template_ = std::move(func);
|
||||
has_simple_value_ = false;
|
||||
}
|
||||
|
||||
void set_value_simple(const bool &value) {
|
||||
this->value_simple_ = value;
|
||||
has_simple_value_ = true;
|
||||
}
|
||||
|
||||
private:
|
||||
BLEClient *parent_{nullptr};
|
||||
bool has_simple_value_ = true;
|
||||
bool value_simple_{false};
|
||||
std::function<bool(Ts...)> value_template_{};
|
||||
};
|
||||
|
||||
template<typename... Ts> class BLEClientRemoveBondAction : public Action<Ts...> {
|
||||
public:
|
||||
BLEClientRemoveBondAction(BLEClient *ble_client) { parent_ = ble_client; }
|
||||
|
||||
void play(Ts... x) override {
|
||||
esp_bd_addr_t remote_bda;
|
||||
memcpy(remote_bda, parent_->get_remote_bda(), sizeof(esp_bd_addr_t));
|
||||
esp_ble_remove_bond_device(remote_bda);
|
||||
}
|
||||
|
||||
private:
|
||||
BLEClient *parent_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ class BLEClient;
|
||||
class BLEClientNode {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
esp_ble_gattc_cb_param_t *param){};
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {}
|
||||
virtual void loop() {}
|
||||
void set_address(uint64_t address) { address_ = address; }
|
||||
|
||||
@@ -13,8 +13,5 @@ void Button::press() {
|
||||
}
|
||||
void Button::add_on_press_callback(std::function<void()> &&callback) { this->press_callback_.add(std::move(callback)); }
|
||||
|
||||
void Button::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
std::string Button::get_device_class() { return this->device_class_; }
|
||||
|
||||
} // namespace button
|
||||
} // namespace esphome
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace button {
|
||||
*
|
||||
* A button is just a momentary switch that does not have a state, only a trigger.
|
||||
*/
|
||||
class Button : public EntityBase {
|
||||
class Button : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
/** Press this button. This is called by the front-end.
|
||||
*
|
||||
@@ -40,19 +40,12 @@ class Button : public EntityBase {
|
||||
*/
|
||||
void add_on_press_callback(std::function<void()> &&callback);
|
||||
|
||||
/// Set the Home Assistant device class (see button::device_class).
|
||||
void set_device_class(const std::string &device_class);
|
||||
|
||||
/// Get the device class for this button.
|
||||
std::string get_device_class();
|
||||
|
||||
protected:
|
||||
/** You should implement this virtual method if you want to create your own button.
|
||||
*/
|
||||
virtual void press_action() = 0;
|
||||
|
||||
CallbackManager<void()> press_callback_{};
|
||||
std::string device_class_{};
|
||||
};
|
||||
|
||||
} // namespace button
|
||||
|
||||
@@ -343,7 +343,7 @@ CLIMATE_CONTROL_ACTION_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_LOW): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_TARGET_TEMPERATURE_HIGH): cv.templatable(cv.temperature),
|
||||
cv.Optional(CONF_AWAY): cv.templatable(cv.boolean),
|
||||
cv.Optional(CONF_AWAY): cv.invalid("Use preset instead"),
|
||||
cv.Exclusive(CONF_FAN_MODE, "fan_mode"): cv.templatable(
|
||||
validate_climate_fan_mode
|
||||
),
|
||||
@@ -379,9 +379,6 @@ async def climate_control_to_code(config, action_id, template_arg, args):
|
||||
config[CONF_TARGET_TEMPERATURE_HIGH], args, float
|
||||
)
|
||||
cg.add(var.set_target_temperature_high(template_))
|
||||
if CONF_AWAY in config:
|
||||
template_ = await cg.templatable(config[CONF_AWAY], args, bool)
|
||||
cg.add(var.set_away(template_))
|
||||
if CONF_FAN_MODE in config:
|
||||
template_ = await cg.templatable(config[CONF_FAN_MODE], args, ClimateFanMode)
|
||||
cg.add(var.set_fan_mode(template_))
|
||||
|
||||
@@ -264,25 +264,11 @@ const optional<ClimateMode> &ClimateCall::get_mode() const { return this->mode_;
|
||||
const optional<float> &ClimateCall::get_target_temperature() const { return this->target_temperature_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature_low() const { return this->target_temperature_low_; }
|
||||
const optional<float> &ClimateCall::get_target_temperature_high() const { return this->target_temperature_high_; }
|
||||
optional<bool> ClimateCall::get_away() const {
|
||||
if (!this->preset_.has_value())
|
||||
return {};
|
||||
return *this->preset_ == ClimatePreset::CLIMATE_PRESET_AWAY;
|
||||
}
|
||||
const optional<ClimateFanMode> &ClimateCall::get_fan_mode() const { return this->fan_mode_; }
|
||||
const optional<std::string> &ClimateCall::get_custom_fan_mode() const { return this->custom_fan_mode_; }
|
||||
const optional<ClimatePreset> &ClimateCall::get_preset() const { return this->preset_; }
|
||||
const optional<std::string> &ClimateCall::get_custom_preset() const { return this->custom_preset_; }
|
||||
const optional<ClimateSwingMode> &ClimateCall::get_swing_mode() const { return this->swing_mode_; }
|
||||
ClimateCall &ClimateCall::set_away(bool away) {
|
||||
this->preset_ = away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_away(optional<bool> away) {
|
||||
if (away.has_value())
|
||||
this->preset_ = *away ? CLIMATE_PRESET_AWAY : CLIMATE_PRESET_HOME;
|
||||
return *this;
|
||||
}
|
||||
ClimateCall &ClimateCall::set_target_temperature_high(optional<float> target_temperature_high) {
|
||||
this->target_temperature_high_ = target_temperature_high;
|
||||
return *this;
|
||||
|
||||
@@ -64,10 +64,6 @@ class ClimateCall {
|
||||
* For climate devices with two point target temperature control
|
||||
*/
|
||||
ClimateCall &set_target_temperature_high(optional<float> target_temperature_high);
|
||||
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
|
||||
ClimateCall &set_away(bool away);
|
||||
ESPDEPRECATED("set_away() is deprecated, please use .set_preset(CLIMATE_PRESET_AWAY) instead", "v1.20")
|
||||
ClimateCall &set_away(optional<bool> away);
|
||||
/// Set the fan mode of the climate device.
|
||||
ClimateCall &set_fan_mode(ClimateFanMode fan_mode);
|
||||
/// Set the fan mode of the climate device.
|
||||
@@ -97,8 +93,6 @@ class ClimateCall {
|
||||
const optional<float> &get_target_temperature() const;
|
||||
const optional<float> &get_target_temperature_low() const;
|
||||
const optional<float> &get_target_temperature_high() const;
|
||||
ESPDEPRECATED("get_away() is deprecated, please use .get_preset() instead", "v1.20")
|
||||
optional<bool> get_away() const;
|
||||
const optional<ClimateFanMode> &get_fan_mode() const;
|
||||
const optional<ClimateSwingMode> &get_swing_mode() const;
|
||||
const optional<std::string> &get_custom_fan_mode() const;
|
||||
@@ -184,14 +178,6 @@ class Climate : public EntityBase {
|
||||
};
|
||||
};
|
||||
|
||||
/** Whether the climate device is in away mode.
|
||||
*
|
||||
* Away allows climate devices to have two different target temperature configs:
|
||||
* one for normal mode and one for away mode.
|
||||
*/
|
||||
ESPDEPRECATED("away is deprecated, use preset instead", "v1.20")
|
||||
bool away{false};
|
||||
|
||||
/// The active fan mode of the climate device.
|
||||
optional<ClimateFanMode> fan_mode;
|
||||
|
||||
|
||||
@@ -117,15 +117,6 @@ class ClimateTraits {
|
||||
bool supports_custom_preset(const std::string &custom_preset) const {
|
||||
return supported_custom_presets_.count(custom_preset);
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use set_supported_presets() instead", "v1.20")
|
||||
void set_supports_away(bool supports) {
|
||||
if (supports) {
|
||||
supported_presets_.insert(CLIMATE_PRESET_AWAY);
|
||||
supported_presets_.insert(CLIMATE_PRESET_HOME);
|
||||
}
|
||||
}
|
||||
ESPDEPRECATED("This method is deprecated, use supports_preset() instead", "v1.20")
|
||||
bool get_supports_away() const { return supports_preset(CLIMATE_PRESET_AWAY); }
|
||||
|
||||
void set_supported_swing_modes(std::set<ClimateSwingMode> modes) { supported_swing_modes_ = std::move(modes); }
|
||||
void add_supported_swing_mode(ClimateSwingMode mode) { supported_swing_modes_.insert(mode); }
|
||||
|
||||
@@ -28,6 +28,7 @@ cover::CoverTraits CopyCover::get_traits() {
|
||||
// copy traits manually so it doesn't break when new options are added
|
||||
// but the control() method hasn't implemented them yet.
|
||||
traits.set_is_assumed_state(base.get_is_assumed_state());
|
||||
traits.set_supports_stop(base.get_supports_stop());
|
||||
traits.set_supports_position(base.get_supports_position());
|
||||
traits.set_supports_tilt(base.get_supports_tilt());
|
||||
traits.set_supports_toggle(base.get_supports_toggle());
|
||||
|
||||
@@ -145,7 +145,7 @@ CoverCall &CoverCall::set_stop(bool stop) {
|
||||
return *this;
|
||||
}
|
||||
bool CoverCall::get_stop() const { return this->stop_; }
|
||||
void Cover::set_device_class(const std::string &device_class) { this->device_class_override_ = device_class; }
|
||||
|
||||
CoverCall Cover::make_call() { return {this}; }
|
||||
void Cover::open() {
|
||||
auto call = this->make_call();
|
||||
@@ -204,11 +204,7 @@ optional<CoverRestoreState> Cover::restore_state_() {
|
||||
return {};
|
||||
return recovered;
|
||||
}
|
||||
std::string Cover::get_device_class() {
|
||||
if (this->device_class_override_.has_value())
|
||||
return *this->device_class_override_;
|
||||
return "";
|
||||
}
|
||||
|
||||
bool Cover::is_fully_open() const { return this->position == COVER_OPEN; }
|
||||
bool Cover::is_fully_closed() const { return this->position == COVER_CLOSED; }
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ const char *cover_operation_to_str(CoverOperation op);
|
||||
* to control all values of the cover. Also implement get_traits() to return what operations
|
||||
* the cover supports.
|
||||
*/
|
||||
class Cover : public EntityBase {
|
||||
class Cover : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
explicit Cover();
|
||||
|
||||
@@ -156,8 +156,6 @@ class Cover : public EntityBase {
|
||||
void publish_state(bool save = true);
|
||||
|
||||
virtual CoverTraits get_traits() = 0;
|
||||
void set_device_class(const std::string &device_class);
|
||||
std::string get_device_class();
|
||||
|
||||
/// Helper method to check if the cover is fully open. Equivalent to comparing .position against 1.0
|
||||
bool is_fully_open() const;
|
||||
@@ -172,7 +170,6 @@ class Cover : public EntityBase {
|
||||
optional<CoverRestoreState> restore_state_();
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
optional<std::string> device_class_override_{};
|
||||
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
@@ -15,12 +15,15 @@ class CoverTraits {
|
||||
void set_supports_tilt(bool supports_tilt) { this->supports_tilt_ = supports_tilt; }
|
||||
bool get_supports_toggle() const { return this->supports_toggle_; }
|
||||
void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; }
|
||||
bool get_supports_stop() const { return this->supports_stop_; }
|
||||
void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; }
|
||||
|
||||
protected:
|
||||
bool is_assumed_state_{false};
|
||||
bool supports_position_{false};
|
||||
bool supports_tilt_{false};
|
||||
bool supports_toggle_{false};
|
||||
bool supports_stop_{false};
|
||||
};
|
||||
|
||||
} // namespace cover
|
||||
|
||||
@@ -12,6 +12,7 @@ using namespace esphome::cover;
|
||||
|
||||
CoverTraits CurrentBasedCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_stop(true);
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_toggle(true);
|
||||
traits.set_is_assumed_state(false);
|
||||
|
||||
@@ -72,6 +72,7 @@ class DemoCover : public cover::Cover, public Component {
|
||||
traits.set_supports_tilt(true);
|
||||
break;
|
||||
case DemoCoverType::TYPE_4:
|
||||
traits.set_supports_stop(true);
|
||||
traits.set_is_assumed_state(true);
|
||||
traits.set_supports_tilt(true);
|
||||
break;
|
||||
|
||||
@@ -40,6 +40,7 @@ DEVICE = {
|
||||
|
||||
NextAction = dfplayer_ns.class_("NextAction", automation.Action)
|
||||
PreviousAction = dfplayer_ns.class_("PreviousAction", automation.Action)
|
||||
PlayMp3Action = dfplayer_ns.class_("PlayMp3Action", automation.Action)
|
||||
PlayFileAction = dfplayer_ns.class_("PlayFileAction", automation.Action)
|
||||
PlayFolderAction = dfplayer_ns.class_("PlayFolderAction", automation.Action)
|
||||
SetVolumeAction = dfplayer_ns.class_("SetVolumeAction", automation.Action)
|
||||
@@ -113,6 +114,25 @@ async def dfplayer_previous_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play_mp3",
|
||||
PlayMp3Action,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DFPlayer),
|
||||
cv.Required(CONF_FILE): cv.templatable(cv.int_),
|
||||
},
|
||||
key=CONF_FILE,
|
||||
),
|
||||
)
|
||||
async def dfplayer_play_mp3_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_FILE], args, float)
|
||||
cg.add(var.set_file(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"dfplayer.play",
|
||||
PlayFileAction,
|
||||
|
||||
@@ -7,10 +7,10 @@ namespace dfplayer {
|
||||
static const char *const TAG = "dfplayer";
|
||||
|
||||
void DFPlayer::play_folder(uint16_t folder, uint16_t file) {
|
||||
if (folder < 100 && file < 256) {
|
||||
if (folder <= 10 && file <= 1000) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x0F, (uint8_t) folder, (uint8_t) file);
|
||||
} else if (folder <= 10 && file <= 1000) {
|
||||
} else if (folder < 100 && file < 256) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x14, (((uint16_t) folder) << 12) | file);
|
||||
} else {
|
||||
|
||||
@@ -35,6 +35,10 @@ class DFPlayer : public uart::UARTDevice, public Component {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x02);
|
||||
}
|
||||
void play_mp3(uint16_t file) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x12, file);
|
||||
}
|
||||
void play_file(uint16_t file) {
|
||||
this->ack_set_is_playing_ = true;
|
||||
this->send_cmd_(0x03, file);
|
||||
@@ -113,6 +117,16 @@ class DFPlayer : public uart::UARTDevice, public Component {
|
||||
DFPLAYER_SIMPLE_ACTION(NextAction, next)
|
||||
DFPLAYER_SIMPLE_ACTION(PreviousAction, previous)
|
||||
|
||||
template<typename... Ts> class PlayMp3Action : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto file = this->file_.value(x...);
|
||||
this->parent_->play_mp3(file);
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class PlayFileAction : public Action<Ts...>, public Parented<DFPlayer> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, file)
|
||||
|
||||
@@ -11,6 +11,7 @@ using namespace esphome::cover;
|
||||
|
||||
CoverTraits EndstopCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_stop(true);
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_toggle(true);
|
||||
traits.set_is_assumed_state(false);
|
||||
|
||||
21
esphome/components/es8388/__init__.py
Normal file
21
esphome/components/es8388/__init__.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
es8388_ns = cg.esphome_ns.namespace("es8388")
|
||||
ES8388Component = es8388_ns.class_("ES8388Component", cg.Component, i2c.I2CDevice)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.GenerateID(): cv.declare_id(ES8388Component)})
|
||||
.extend(i2c.i2c_device_schema(0x10))
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
75
esphome/components/es8388/es8388_component.cpp
Normal file
75
esphome/components/es8388/es8388_component.cpp
Normal file
@@ -0,0 +1,75 @@
|
||||
#include "es8388_component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
#include <soc/io_mux_reg.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace es8388 {
|
||||
|
||||
void ES8388Component::setup() {
|
||||
PIN_FUNC_SELECT(PERIPHS_IO_MUX_GPIO0_U, FUNC_GPIO0_CLK_OUT1);
|
||||
WRITE_PERI_REG(PIN_CTRL, READ_PERI_REG(PIN_CTRL) & 0xFFFFFFF0);
|
||||
|
||||
// mute
|
||||
this->write_byte(0x19, 0x04);
|
||||
// powerup
|
||||
this->write_byte(0x01, 0x50);
|
||||
this->write_byte(0x02, 0x00);
|
||||
// worker mode
|
||||
this->write_byte(0x08, 0x00);
|
||||
// DAC powerdown
|
||||
this->write_byte(0x04, 0xC0);
|
||||
// vmidsel/500k ADC/DAC idem
|
||||
this->write_byte(0x00, 0x12);
|
||||
|
||||
// i2s 16 bits
|
||||
this->write_byte(0x17, 0x18);
|
||||
// sample freq 256
|
||||
this->write_byte(0x18, 0x02);
|
||||
// LIN2/RIN2 for mixer
|
||||
this->write_byte(0x26, 0x00);
|
||||
// left DAC to left mixer
|
||||
this->write_byte(0x27, 0x90);
|
||||
// right DAC to right mixer
|
||||
this->write_byte(0x2A, 0x90);
|
||||
// DACLRC ADCLRC idem
|
||||
this->write_byte(0x2B, 0x80);
|
||||
this->write_byte(0x2D, 0x00);
|
||||
// DAC volume max
|
||||
this->write_byte(0x1B, 0x00);
|
||||
this->write_byte(0x1A, 0x00);
|
||||
|
||||
// ADC poweroff
|
||||
this->write_byte(0x03, 0xFF);
|
||||
// ADC amp 24dB
|
||||
this->write_byte(0x09, 0x88);
|
||||
// LINPUT1/RINPUT1
|
||||
this->write_byte(0x0A, 0x00);
|
||||
// ADC mono left
|
||||
this->write_byte(0x0B, 0x02);
|
||||
// i2S 16b
|
||||
this->write_byte(0x0C, 0x0C);
|
||||
// MCLK 256
|
||||
this->write_byte(0x0D, 0x02);
|
||||
// ADC Volume
|
||||
this->write_byte(0x10, 0x00);
|
||||
this->write_byte(0x11, 0x00);
|
||||
// ALC OFF
|
||||
this->write_byte(0x03, 0x09);
|
||||
this->write_byte(0x2B, 0x80);
|
||||
|
||||
this->write_byte(0x02, 0xF0);
|
||||
delay(1);
|
||||
this->write_byte(0x02, 0x00);
|
||||
// DAC power-up LOUT1/ROUT1 enabled
|
||||
this->write_byte(0x04, 0x30);
|
||||
this->write_byte(0x03, 0x00);
|
||||
// DAC volume max
|
||||
this->write_byte(0x2E, 0x1C);
|
||||
this->write_byte(0x2F, 0x1C);
|
||||
// unmute
|
||||
this->write_byte(0x19, 0x00);
|
||||
}
|
||||
|
||||
} // namespace es8388
|
||||
} // namespace esphome
|
||||
17
esphome/components/es8388/es8388_component.h
Normal file
17
esphome/components/es8388/es8388_component.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace es8388 {
|
||||
|
||||
class ES8388Component : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::LATE - 1; }
|
||||
};
|
||||
|
||||
} // namespace es8388
|
||||
} // namespace esphome
|
||||
@@ -252,7 +252,7 @@ def _parse_platform_version(value):
|
||||
try:
|
||||
# if platform version is a valid version constraint, prefix the default package
|
||||
cv.platformio_version_constraint(value)
|
||||
return f"platformio/espressif32 @ {value}"
|
||||
return f"platformio/espressif32@{value}"
|
||||
except cv.Invalid:
|
||||
return value
|
||||
|
||||
@@ -367,12 +367,12 @@ async def to_code(config):
|
||||
cg.add_build_flag("-Wno-nonnull-compare")
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-espidf @ {conf[CONF_SOURCE]}"],
|
||||
[f"platformio/framework-espidf@{conf[CONF_SOURCE]}"],
|
||||
)
|
||||
# platformio/toolchain-esp32ulp does not support linux_aarch64 yet and has not been updated for over 2 years
|
||||
# This is espressif's own published version which is more up to date.
|
||||
cg.add_platformio_option(
|
||||
"platform_packages", ["espressif/toolchain-esp32ulp @ 2.35.0-20220830"]
|
||||
"platform_packages", ["espressif/toolchain-esp32ulp@2.35.0-20220830"]
|
||||
)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
|
||||
@@ -433,7 +433,7 @@ async def to_code(config):
|
||||
cg.add_build_flag("-DUSE_ESP32_FRAMEWORK_ARDUINO")
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-arduinoespressif32 @ {conf[CONF_SOURCE]}"],
|
||||
[f"platformio/framework-arduinoespressif32@{conf[CONF_SOURCE]}"],
|
||||
)
|
||||
|
||||
cg.add_platformio_option("board_build.partitions", "partitions.csv")
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <freertos/task.h>
|
||||
#include <esp_idf_version.h>
|
||||
#include <esp_task_wdt.h>
|
||||
#include <esp_timer.h>
|
||||
#include <soc/rtc.h>
|
||||
|
||||
#if ESP_IDF_VERSION_MAJOR >= 4
|
||||
|
||||
@@ -9,10 +9,9 @@ CODEOWNERS = ["@jesserockz"]
|
||||
CONFLICTS_WITH = ["esp32_ble_beacon"]
|
||||
|
||||
CONF_BLE_ID = "ble_id"
|
||||
CONF_IO_CAPABILITY = "io_capability"
|
||||
|
||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
NO_BLUTOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
NO_BLUETOOTH_VARIANTS = [const.VARIANT_ESP32S2]
|
||||
|
||||
esp32_ble_ns = cg.esphome_ns.namespace("esp32_ble")
|
||||
ESP32BLE = esp32_ble_ns.class_("ESP32BLE", cg.Component)
|
||||
@@ -21,17 +20,28 @@ GAPEventHandler = esp32_ble_ns.class_("GAPEventHandler")
|
||||
GATTcEventHandler = esp32_ble_ns.class_("GATTcEventHandler")
|
||||
GATTsEventHandler = esp32_ble_ns.class_("GATTsEventHandler")
|
||||
|
||||
IoCapability = esp32_ble_ns.enum("IoCapability")
|
||||
IO_CAPABILITY = {
|
||||
"none": IoCapability.IO_CAP_NONE,
|
||||
"keyboard_only": IoCapability.IO_CAP_IN,
|
||||
"keyboard_display": IoCapability.IO_CAP_KBDISP,
|
||||
"display_only": IoCapability.IO_CAP_OUT,
|
||||
"display_yes_no": IoCapability.IO_CAP_IO,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ESP32BLE),
|
||||
cv.Optional(CONF_IO_CAPABILITY, default="none"): cv.enum(
|
||||
IO_CAPABILITY, lower=True
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
def validate_variant(_):
|
||||
variant = get_esp32_variant()
|
||||
if variant in NO_BLUTOOTH_VARIANTS:
|
||||
if variant in NO_BLUETOOTH_VARIANTS:
|
||||
raise cv.Invalid(f"{variant} does not support Bluetooth")
|
||||
|
||||
|
||||
@@ -41,6 +51,7 @@ FINAL_VALIDATE_SCHEMA = validate_variant
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_io_capability(config[CONF_IO_CAPABILITY]))
|
||||
|
||||
if CORE.using_esp_idf:
|
||||
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
|
||||
|
||||
@@ -134,8 +134,7 @@ bool ESP32BLE::ble_setup_() {
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_ble_io_cap_t iocap = ESP_IO_CAP_NONE;
|
||||
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &iocap, sizeof(uint8_t));
|
||||
err = esp_ble_gap_set_security_param(ESP_BLE_SM_IOCAP_MODE, &(this->io_cap_), sizeof(uint8_t));
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "esp_ble_gap_set_security_param failed: %d", err);
|
||||
return false;
|
||||
@@ -215,9 +214,31 @@ float ESP32BLE::get_setup_priority() const { return setup_priority::BLUETOOTH; }
|
||||
void ESP32BLE::dump_config() {
|
||||
const uint8_t *mac_address = esp_bt_dev_get_address();
|
||||
if (mac_address) {
|
||||
const char *io_capability_s;
|
||||
switch (this->io_cap_) {
|
||||
case ESP_IO_CAP_OUT:
|
||||
io_capability_s = "display_only";
|
||||
break;
|
||||
case ESP_IO_CAP_IO:
|
||||
io_capability_s = "display_yes_no";
|
||||
break;
|
||||
case ESP_IO_CAP_IN:
|
||||
io_capability_s = "keyboard_only";
|
||||
break;
|
||||
case ESP_IO_CAP_NONE:
|
||||
io_capability_s = "none";
|
||||
break;
|
||||
case ESP_IO_CAP_KBDISP:
|
||||
io_capability_s = "keyboard_display";
|
||||
break;
|
||||
default:
|
||||
io_capability_s = "invalid";
|
||||
break;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, "ESP32 BLE:");
|
||||
ESP_LOGCONFIG(TAG, " MAC address: %02X:%02X:%02X:%02X:%02X:%02X", mac_address[0], mac_address[1], mac_address[2],
|
||||
mac_address[3], mac_address[4], mac_address[5]);
|
||||
ESP_LOGCONFIG(TAG, " IO Capability: %s", io_capability_s);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled");
|
||||
}
|
||||
|
||||
@@ -25,6 +25,14 @@ typedef struct {
|
||||
uint16_t mtu;
|
||||
} conn_status_t;
|
||||
|
||||
enum IoCapability {
|
||||
IO_CAP_OUT = ESP_IO_CAP_OUT,
|
||||
IO_CAP_IO = ESP_IO_CAP_IO,
|
||||
IO_CAP_IN = ESP_IO_CAP_IN,
|
||||
IO_CAP_NONE = ESP_IO_CAP_NONE,
|
||||
IO_CAP_KBDISP = ESP_IO_CAP_KBDISP,
|
||||
};
|
||||
|
||||
class GAPEventHandler {
|
||||
public:
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||
@@ -44,6 +52,8 @@ class GATTsEventHandler {
|
||||
|
||||
class ESP32BLE : public Component {
|
||||
public:
|
||||
void set_io_capability(IoCapability io_capability) { this->io_cap_ = (esp_ble_io_cap_t) io_capability; }
|
||||
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
@@ -72,6 +82,7 @@ class ESP32BLE : public Component {
|
||||
|
||||
Queue<BLEEvent> ble_events_;
|
||||
BLEAdvertising *advertising_;
|
||||
esp_ble_io_cap_t io_cap_{ESP_IO_CAP_NONE};
|
||||
};
|
||||
|
||||
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -125,7 +125,7 @@ def _parse_platform_version(value):
|
||||
try:
|
||||
# if platform version is a valid version constraint, prefix the default package
|
||||
cv.platformio_version_constraint(value)
|
||||
return f"platformio/espressif8266 @ {value}"
|
||||
return f"platformio/espressif8266@{value}"
|
||||
except cv.Invalid:
|
||||
return value
|
||||
|
||||
@@ -181,7 +181,7 @@ async def to_code(config):
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"platformio/framework-arduinoespressif8266 @ {conf[CONF_SOURCE]}"],
|
||||
[f"platformio/framework-arduinoespressif8266@{conf[CONF_SOURCE]}"],
|
||||
)
|
||||
|
||||
# Default for platformio is LWIP2_LOW_MEMORY with:
|
||||
|
||||
@@ -41,6 +41,7 @@ void FeedbackCover::setup() {
|
||||
|
||||
CoverTraits FeedbackCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_stop(true);
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_toggle(true);
|
||||
traits.set_is_assumed_state(this->assumed_state_);
|
||||
|
||||
1
esphome/components/hyt271/__init__.py
Normal file
1
esphome/components/hyt271/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@Philippe12"]
|
||||
52
esphome/components/hyt271/hyt271.cpp
Normal file
52
esphome/components/hyt271/hyt271.cpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#include "hyt271.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hyt271 {
|
||||
|
||||
static const char *const TAG = "hyt271";
|
||||
|
||||
static const uint8_t HYT271_ADDRESS = 0x28;
|
||||
|
||||
void HYT271Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "HYT271:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_);
|
||||
LOG_SENSOR(" ", "Humidity", this->humidity_);
|
||||
}
|
||||
void HYT271Component::update() {
|
||||
uint8_t raw_data[4];
|
||||
|
||||
if (this->write(&raw_data[0], 0) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Communication with HYT271 failed! => Ask new values");
|
||||
return;
|
||||
}
|
||||
this->set_timeout("wait_convert", 50, [this]() {
|
||||
uint8_t raw_data[4];
|
||||
if (this->read(raw_data, 4) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "Communication with HYT271 failed! => Read values");
|
||||
return;
|
||||
}
|
||||
uint16_t raw_temperature = ((raw_data[2] << 8) | raw_data[3]) >> 2;
|
||||
uint16_t raw_humidity = ((raw_data[0] & 0x3F) << 8) | raw_data[1];
|
||||
|
||||
float temperature = ((float(raw_temperature)) * (165.0f / 16383.0f)) - 40.0f;
|
||||
float humidity = (float(raw_humidity)) * (100.0f / 16383.0f);
|
||||
|
||||
ESP_LOGD(TAG, "Got Temperature=%.1f°C Humidity=%.1f%%", temperature, humidity);
|
||||
|
||||
if (this->temperature_ != nullptr)
|
||||
this->temperature_->publish_state(temperature);
|
||||
if (this->humidity_ != nullptr)
|
||||
this->humidity_->publish_state(humidity);
|
||||
this->status_clear_warning();
|
||||
});
|
||||
}
|
||||
float HYT271Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace hyt271
|
||||
} // namespace esphome
|
||||
27
esphome/components/hyt271/hyt271.h
Normal file
27
esphome/components/hyt271/hyt271.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hyt271 {
|
||||
|
||||
class HYT271Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature(sensor::Sensor *temperature) { temperature_ = temperature; }
|
||||
void set_humidity(sensor::Sensor *humidity) { humidity_ = humidity; }
|
||||
|
||||
void dump_config() override;
|
||||
/// Update the sensor values (temperature+humidity).
|
||||
void update() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
sensor::Sensor *temperature_{nullptr};
|
||||
sensor::Sensor *humidity_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace hyt271
|
||||
} // namespace esphome
|
||||
56
esphome/components/hyt271/sensor.py
Normal file
56
esphome/components/hyt271/sensor.py
Normal file
@@ -0,0 +1,56 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_PERCENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
hyt271_ns = cg.esphome_ns.namespace("hyt271")
|
||||
HYT271Component = hyt271_ns.class_(
|
||||
"HYT271Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HYT271Component),
|
||||
cv.Required(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Required(CONF_HUMIDITY): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x28))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_temperature(sens))
|
||||
|
||||
if CONF_HUMIDITY in config:
|
||||
sens = await sensor.new_sensor(config[CONF_HUMIDITY])
|
||||
cg.add(var.set_humidity(sens))
|
||||
148
esphome/components/max6956/__init__.py
Normal file
148
esphome/components/max6956/__init__.py
Normal file
@@ -0,0 +1,148 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins, automation
|
||||
from esphome.components import i2c
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_NUMBER,
|
||||
CONF_MODE,
|
||||
CONF_INVERTED,
|
||||
CONF_INPUT,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@looping40"]
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_BRIGHTNESS_MODE = "brightness_mode"
|
||||
CONF_BRIGHTNESS_GLOBAL = "brightness_global"
|
||||
|
||||
|
||||
max6956_ns = cg.esphome_ns.namespace("max6956")
|
||||
|
||||
MAX6956 = max6956_ns.class_("MAX6956", cg.Component, i2c.I2CDevice)
|
||||
MAX6956GPIOPin = max6956_ns.class_("MAX6956GPIOPin", cg.GPIOPin)
|
||||
|
||||
# Actions
|
||||
SetCurrentGlobalAction = max6956_ns.class_("SetCurrentGlobalAction", automation.Action)
|
||||
SetCurrentModeAction = max6956_ns.class_("SetCurrentModeAction", automation.Action)
|
||||
|
||||
MAX6956_CURRENTMODE = max6956_ns.enum("MAX6956CURRENTMODE")
|
||||
CURRENT_MODES = {
|
||||
"global": MAX6956_CURRENTMODE.GLOBAL,
|
||||
"segment": MAX6956_CURRENTMODE.SEGMENT,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(MAX6956),
|
||||
cv.Optional(CONF_BRIGHTNESS_GLOBAL, default="0"): cv.int_range(
|
||||
min=0, max=15
|
||||
),
|
||||
cv.Optional(CONF_BRIGHTNESS_MODE, default="global"): cv.enum(
|
||||
CURRENT_MODES, lower=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x40))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
cg.add(var.set_brightness_mode(config[CONF_BRIGHTNESS_MODE]))
|
||||
cg.add(var.set_brightness_global(config[CONF_BRIGHTNESS_GLOBAL]))
|
||||
|
||||
|
||||
def validate_mode(value):
|
||||
if not (value[CONF_INPUT] or value[CONF_OUTPUT]):
|
||||
raise cv.Invalid("Mode must be either input or output")
|
||||
if value[CONF_INPUT] and value[CONF_OUTPUT]:
|
||||
raise cv.Invalid("Mode must be either input or output")
|
||||
if value[CONF_PULLUP] and not value[CONF_INPUT]:
|
||||
raise cv.Invalid("Pullup only available with input")
|
||||
return value
|
||||
|
||||
|
||||
CONF_MAX6956 = "max6956"
|
||||
|
||||
MAX6956_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MAX6956GPIOPin),
|
||||
cv.Required(CONF_MAX6956): cv.use_id(MAX6956),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(min=4, max=31),
|
||||
cv.Optional(CONF_MODE, default={}): cv.All(
|
||||
{
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
},
|
||||
validate_mode,
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register(CONF_MAX6956, MAX6956_PIN_SCHEMA)
|
||||
async def max6956_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
parent = await cg.get_variable(config[CONF_MAX6956])
|
||||
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max6956.set_brightness_global",
|
||||
SetCurrentGlobalAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.GenerateID(CONF_ID): cv.use_id(MAX6956),
|
||||
cv.Required(CONF_BRIGHTNESS_GLOBAL): cv.templatable(
|
||||
cv.int_range(min=0, max=15)
|
||||
),
|
||||
},
|
||||
key=CONF_BRIGHTNESS_GLOBAL,
|
||||
),
|
||||
)
|
||||
async def max6956_set_brightness_global_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_BRIGHTNESS_GLOBAL], args, float)
|
||||
cg.add(var.set_brightness_global(template_))
|
||||
return var
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"max6956.set_brightness_mode",
|
||||
SetCurrentModeAction,
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(MAX6956),
|
||||
cv.Required(CONF_BRIGHTNESS_MODE): cv.templatable(
|
||||
cv.enum(CURRENT_MODES, lower=True)
|
||||
),
|
||||
},
|
||||
key=CONF_BRIGHTNESS_MODE,
|
||||
),
|
||||
)
|
||||
async def max6956_set_brightness_mode_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg, paren)
|
||||
template_ = await cg.templatable(config[CONF_BRIGHTNESS_MODE], args, float)
|
||||
cg.add(var.set_brightness_mode(template_))
|
||||
return var
|
||||
40
esphome/components/max6956/automation.h
Normal file
40
esphome/components/max6956/automation.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/max6956/max6956.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
template<typename... Ts> class SetCurrentGlobalAction : public Action<Ts...> {
|
||||
public:
|
||||
SetCurrentGlobalAction(MAX6956 *max6956) : max6956_(max6956) {}
|
||||
|
||||
TEMPLATABLE_VALUE(uint8_t, brightness_global)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->max6956_->set_brightness_global(this->brightness_global_.value(x...));
|
||||
this->max6956_->write_brightness_global();
|
||||
}
|
||||
|
||||
protected:
|
||||
MAX6956 *max6956_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class SetCurrentModeAction : public Action<Ts...> {
|
||||
public:
|
||||
SetCurrentModeAction(MAX6956 *max6956) : max6956_(max6956) {}
|
||||
|
||||
TEMPLATABLE_VALUE(max6956::MAX6956CURRENTMODE, brightness_mode)
|
||||
|
||||
void play(Ts... x) override {
|
||||
this->max6956_->set_brightness_mode(this->brightness_mode_.value(x...));
|
||||
this->max6956_->write_brightness_mode();
|
||||
}
|
||||
|
||||
protected:
|
||||
MAX6956 *max6956_;
|
||||
};
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
||||
170
esphome/components/max6956/max6956.cpp
Normal file
170
esphome/components/max6956/max6956.cpp
Normal file
@@ -0,0 +1,170 @@
|
||||
#include "max6956.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
static const char *const TAG = "max6956";
|
||||
|
||||
/// Masks for MAX6956 Configuration register
|
||||
const uint32_t MASK_TRANSITION_DETECTION = 0x80;
|
||||
const uint32_t MASK_INDIVIDUAL_CURRENT = 0x40;
|
||||
const uint32_t MASK_NORMAL_OPERATION = 0x01;
|
||||
|
||||
const uint32_t MASK_1PORT_VALUE = 0x03;
|
||||
const uint32_t MASK_PORT_CONFIG = 0x03;
|
||||
const uint8_t MASK_CONFIG_CURRENT = 0x40;
|
||||
const uint8_t MASK_CURRENT_PIN = 0x0F;
|
||||
|
||||
/**************************************
|
||||
* MAX6956 *
|
||||
**************************************/
|
||||
void MAX6956::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MAX6956...");
|
||||
uint8_t configuration;
|
||||
if (!this->read_reg_(MAX6956_CONFIGURATION, &configuration)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
write_brightness_global();
|
||||
write_brightness_mode();
|
||||
|
||||
/** TO DO : read transition detection in yaml
|
||||
TO DO : read indivdual current in yaml **/
|
||||
this->read_reg_(MAX6956_CONFIGURATION, &configuration);
|
||||
ESP_LOGD(TAG, "Initial reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
|
||||
configuration = configuration | MASK_NORMAL_OPERATION;
|
||||
this->write_reg_(MAX6956_CONFIGURATION, configuration);
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Enabling normal operation");
|
||||
ESP_LOGD(TAG, "setup reg[0x%.2X]=0x%.2X", MAX6956_CONFIGURATION, configuration);
|
||||
}
|
||||
|
||||
bool MAX6956::digital_read(uint8_t pin) {
|
||||
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
|
||||
uint8_t value = 0;
|
||||
this->read_reg_(reg_addr, &value);
|
||||
return (value & MASK_1PORT_VALUE);
|
||||
}
|
||||
|
||||
void MAX6956::digital_write(uint8_t pin, bool value) {
|
||||
uint8_t reg_addr = MAX6956_1PORT_VALUE_START + pin;
|
||||
this->write_reg_(reg_addr, value);
|
||||
}
|
||||
|
||||
void MAX6956::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
|
||||
uint8_t config = 0;
|
||||
uint8_t shift = 2 * (pin % 4);
|
||||
MAX6956GPIOMode mode = MAX6956_INPUT;
|
||||
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
mode = MAX6956GPIOMode::MAX6956_INPUT;
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
mode = MAX6956GPIOMode::MAX6956_INPUT_PULLUP;
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
mode = MAX6956GPIOMode::MAX6956_OUTPUT;
|
||||
}
|
||||
|
||||
this->read_reg_(reg_addr, &config);
|
||||
config &= ~(MASK_PORT_CONFIG << shift);
|
||||
config |= (mode << shift);
|
||||
this->write_reg_(reg_addr, config);
|
||||
}
|
||||
|
||||
void MAX6956::pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags) {
|
||||
uint8_t reg_addr = MAX6956_PORT_CONFIG_START + (pin - MAX6956_MIN) / 4;
|
||||
uint8_t config = 0;
|
||||
uint8_t shift = 2 * (pin % 4);
|
||||
MAX6956GPIOMode mode = MAX6956GPIOMode::MAX6956_LED;
|
||||
|
||||
if (flags == max6956::FLAG_LED) {
|
||||
mode = MAX6956GPIOMode::MAX6956_LED;
|
||||
}
|
||||
|
||||
this->read_reg_(reg_addr, &config);
|
||||
config &= ~(MASK_PORT_CONFIG << shift);
|
||||
config |= (mode << shift);
|
||||
this->write_reg_(reg_addr, config);
|
||||
}
|
||||
|
||||
void MAX6956::set_brightness_global(uint8_t current) {
|
||||
if (current > 15) {
|
||||
ESP_LOGE(TAG, "Global brightness out off range (%u)", current);
|
||||
return;
|
||||
}
|
||||
global_brightness_ = current;
|
||||
}
|
||||
|
||||
void MAX6956::write_brightness_global() { this->write_reg_(MAX6956_GLOBAL_CURRENT, global_brightness_); }
|
||||
|
||||
void MAX6956::set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode) { brightness_mode_ = brightness_mode; };
|
||||
|
||||
void MAX6956::write_brightness_mode() {
|
||||
uint8_t reg_addr = MAX6956_CONFIGURATION;
|
||||
uint8_t config = 0;
|
||||
|
||||
this->read_reg_(reg_addr, &config);
|
||||
config &= ~MASK_CONFIG_CURRENT;
|
||||
config |= brightness_mode_ << 6;
|
||||
this->write_reg_(reg_addr, config);
|
||||
}
|
||||
|
||||
void MAX6956::set_pin_brightness(uint8_t pin, float brightness) {
|
||||
uint8_t reg_addr = MAX6956_CURRENT_START + (pin - MAX6956_MIN) / 2;
|
||||
uint8_t config = 0;
|
||||
uint8_t shift = 4 * (pin % 2);
|
||||
uint8_t bright = roundf(brightness * 15);
|
||||
|
||||
if (prev_bright_[pin - MAX6956_MIN] == bright)
|
||||
return;
|
||||
|
||||
prev_bright_[pin - MAX6956_MIN] = bright;
|
||||
|
||||
this->read_reg_(reg_addr, &config);
|
||||
config &= ~(MASK_CURRENT_PIN << shift);
|
||||
config |= (bright << shift);
|
||||
this->write_reg_(reg_addr, config);
|
||||
}
|
||||
|
||||
bool MAX6956::read_reg_(uint8_t reg, uint8_t *value) {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
return this->read_byte(reg, value);
|
||||
}
|
||||
|
||||
bool MAX6956::write_reg_(uint8_t reg, uint8_t value) {
|
||||
if (this->is_failed())
|
||||
return false;
|
||||
|
||||
return this->write_byte(reg, value);
|
||||
}
|
||||
|
||||
void MAX6956::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MAX6956");
|
||||
|
||||
if (brightness_mode_ == MAX6956CURRENTMODE::GLOBAL) {
|
||||
ESP_LOGCONFIG(TAG, "current mode: global");
|
||||
ESP_LOGCONFIG(TAG, "global brightness: %u", global_brightness_);
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "current mode: segment");
|
||||
}
|
||||
}
|
||||
|
||||
/**************************************
|
||||
* MAX6956GPIOPin *
|
||||
**************************************/
|
||||
void MAX6956GPIOPin::setup() { pin_mode(flags_); }
|
||||
void MAX6956GPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool MAX6956GPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
void MAX6956GPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||
std::string MAX6956GPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "%u via Max6956", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
||||
94
esphome/components/max6956/max6956.h
Normal file
94
esphome/components/max6956/max6956.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
/// Modes for MAX6956 pins
|
||||
enum MAX6956GPIOMode : uint8_t {
|
||||
MAX6956_LED = 0x00,
|
||||
MAX6956_OUTPUT = 0x01,
|
||||
MAX6956_INPUT = 0x02,
|
||||
MAX6956_INPUT_PULLUP = 0x03
|
||||
};
|
||||
|
||||
/// Range for MAX6956 pins
|
||||
enum MAX6956GPIORange : uint8_t {
|
||||
MAX6956_MIN = 4,
|
||||
MAX6956_MAX = 31,
|
||||
};
|
||||
|
||||
enum MAX6956GPIORegisters {
|
||||
MAX6956_GLOBAL_CURRENT = 0x02,
|
||||
MAX6956_CONFIGURATION = 0x04,
|
||||
MAX6956_TRANSITION_DETECT_MASK = 0x06,
|
||||
MAX6956_DISPLAY_TEST = 0x07,
|
||||
MAX6956_PORT_CONFIG_START = 0x09, // Port Configuration P7, P6, P5, P4
|
||||
MAX6956_CURRENT_START = 0x12, // Current054
|
||||
MAX6956_1PORT_VALUE_START = 0x20, // Port 0 only (virtual port, no action)
|
||||
MAX6956_8PORTS_VALUE_START = 0x44, // 8 ports 4–11 (data bits D0–D7)
|
||||
};
|
||||
|
||||
enum MAX6956GPIOFlag { FLAG_LED = 0x20 };
|
||||
|
||||
enum MAX6956CURRENTMODE { GLOBAL = 0x00, SEGMENT = 0x01 };
|
||||
|
||||
class MAX6956 : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
MAX6956() = default;
|
||||
|
||||
void setup() override;
|
||||
|
||||
bool digital_read(uint8_t pin);
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
void pin_mode(uint8_t pin, max6956::MAX6956GPIOFlag flags);
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
void set_brightness_global(uint8_t current);
|
||||
void set_brightness_mode(max6956::MAX6956CURRENTMODE brightness_mode);
|
||||
void set_pin_brightness(uint8_t pin, float brightness);
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void write_brightness_global();
|
||||
void write_brightness_mode();
|
||||
|
||||
protected:
|
||||
// read a given register
|
||||
bool read_reg_(uint8_t reg, uint8_t *value);
|
||||
// write a value to a given register
|
||||
bool write_reg_(uint8_t reg, uint8_t value);
|
||||
max6956::MAX6956CURRENTMODE brightness_mode_;
|
||||
uint8_t global_brightness_;
|
||||
|
||||
private:
|
||||
int8_t prev_bright_[28] = {0};
|
||||
};
|
||||
|
||||
class MAX6956GPIOPin : public GPIOPin {
|
||||
public:
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
|
||||
void set_parent(MAX6956 *parent) { parent_ = parent; }
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
protected:
|
||||
MAX6956 *parent_;
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
||||
28
esphome/components/max6956/output/__init__.py
Normal file
28
esphome/components/max6956/output/__init__.py
Normal file
@@ -0,0 +1,28 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output
|
||||
from esphome.const import CONF_PIN, CONF_ID
|
||||
from .. import MAX6956, max6956_ns, CONF_MAX6956
|
||||
|
||||
DEPENDENCIES = ["max6956"]
|
||||
|
||||
MAX6956LedChannel = max6956_ns.class_(
|
||||
"MAX6956LedChannel", output.FloatOutput, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(MAX6956LedChannel),
|
||||
cv.GenerateID(CONF_MAX6956): cv.use_id(MAX6956),
|
||||
cv.Required(CONF_PIN): cv.int_range(min=4, max=31),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_MAX6956])
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await output.register_output(var, config)
|
||||
cg.add(var.set_pin(config[CONF_PIN]))
|
||||
cg.add(var.set_parent(parent))
|
||||
26
esphome/components/max6956/output/max6956_led_output.cpp
Normal file
26
esphome/components/max6956/output/max6956_led_output.cpp
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "max6956_led_output.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
static const char *const TAG = "max6956_led_channel";
|
||||
|
||||
void MAX6956LedChannel::write_state(float state) { this->parent_->set_pin_brightness(this->pin_, state); }
|
||||
|
||||
void MAX6956LedChannel::write_state(bool state) { this->parent_->digital_write(this->pin_, state); }
|
||||
|
||||
void MAX6956LedChannel::setup() {
|
||||
this->parent_->pin_mode(this->pin_, max6956::FLAG_LED);
|
||||
this->turn_off();
|
||||
}
|
||||
|
||||
void MAX6956LedChannel::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MAX6956 current:");
|
||||
ESP_LOGCONFIG(TAG, " MAX6956 pin: %d", this->pin_);
|
||||
LOG_FLOAT_OUTPUT(this);
|
||||
}
|
||||
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
||||
28
esphome/components/max6956/output/max6956_led_output.h
Normal file
28
esphome/components/max6956/output/max6956_led_output.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/max6956/max6956.h"
|
||||
#include "esphome/components/output/float_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max6956 {
|
||||
|
||||
class MAX6956;
|
||||
|
||||
class MAX6956LedChannel : public output::FloatOutput, public Component {
|
||||
public:
|
||||
void set_parent(MAX6956 *parent) { this->parent_ = parent; }
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
|
||||
protected:
|
||||
void write_state(float state) override;
|
||||
void write_state(bool state) override;
|
||||
|
||||
MAX6956 *parent_;
|
||||
uint8_t pin_;
|
||||
};
|
||||
|
||||
} // namespace max6956
|
||||
} // namespace esphome
|
||||
@@ -4,10 +4,13 @@ from esphome.const import (
|
||||
CONF_PROTOCOL,
|
||||
CONF_SERVICES,
|
||||
CONF_SERVICE,
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
)
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.components.esp32 import add_idf_component
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["network"]
|
||||
@@ -79,6 +82,16 @@ async def to_code(config):
|
||||
elif CORE.is_rp2040:
|
||||
cg.add_library("LEAmDNS", None)
|
||||
|
||||
if CORE.using_esp_idf and CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] >= cv.Version(
|
||||
5, 0, 0
|
||||
):
|
||||
add_idf_component(
|
||||
"mdns",
|
||||
"https://github.com/espressif/esp-protocols.git",
|
||||
"mdns-v1.0.9",
|
||||
"components/mdns",
|
||||
)
|
||||
|
||||
if config[CONF_DISABLED]:
|
||||
return
|
||||
|
||||
|
||||
0
esphome/components/mlx90614/__init__.py
Normal file
0
esphome/components/mlx90614/__init__.py
Normal file
122
esphome/components/mlx90614/mlx90614.cpp
Normal file
122
esphome/components/mlx90614/mlx90614.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#include "mlx90614.h"
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mlx90614 {
|
||||
|
||||
static const uint8_t MLX90614_RAW_IR_1 = 0x04;
|
||||
static const uint8_t MLX90614_RAW_IR_2 = 0x05;
|
||||
static const uint8_t MLX90614_TEMPERATURE_AMBIENT = 0x06;
|
||||
static const uint8_t MLX90614_TEMPERATURE_OBJECT_1 = 0x07;
|
||||
static const uint8_t MLX90614_TEMPERATURE_OBJECT_2 = 0x08;
|
||||
|
||||
static const uint8_t MLX90614_TOMAX = 0x20;
|
||||
static const uint8_t MLX90614_TOMIN = 0x21;
|
||||
static const uint8_t MLX90614_PWMCTRL = 0x22;
|
||||
static const uint8_t MLX90614_TARANGE = 0x23;
|
||||
static const uint8_t MLX90614_EMISSIVITY = 0x24;
|
||||
static const uint8_t MLX90614_CONFIG = 0x25;
|
||||
static const uint8_t MLX90614_ADDR = 0x2E;
|
||||
static const uint8_t MLX90614_ID1 = 0x3C;
|
||||
static const uint8_t MLX90614_ID2 = 0x3D;
|
||||
static const uint8_t MLX90614_ID3 = 0x3E;
|
||||
static const uint8_t MLX90614_ID4 = 0x3F;
|
||||
|
||||
static const char *const TAG = "mlx90614";
|
||||
|
||||
void MLX90614Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MLX90614...");
|
||||
if (!this->write_emissivity_()) {
|
||||
ESP_LOGE(TAG, "Communication with MLX90614 failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool MLX90614Component::write_emissivity_() {
|
||||
if (std::isnan(this->emissivity_))
|
||||
return true;
|
||||
uint16_t value = (uint16_t) (this->emissivity_ * 65535);
|
||||
if (!this->write_bytes_(MLX90614_EMISSIVITY, 0)) {
|
||||
return false;
|
||||
}
|
||||
delay(10);
|
||||
if (!this->write_bytes_(MLX90614_EMISSIVITY, value)) {
|
||||
return false;
|
||||
}
|
||||
delay(10);
|
||||
return true;
|
||||
}
|
||||
|
||||
uint8_t MLX90614Component::crc8_pec_(const uint8_t *data, uint8_t len) {
|
||||
uint8_t crc = 0;
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
uint8_t in = data[i];
|
||||
for (uint8_t j = 0; j < 8; j++) {
|
||||
bool carry = (crc ^ in) & 0x80;
|
||||
crc <<= 1;
|
||||
if (carry)
|
||||
crc ^= 0x07;
|
||||
in <<= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool MLX90614Component::write_bytes_(uint8_t reg, uint16_t data) {
|
||||
uint8_t buf[5];
|
||||
buf[0] = this->address_ << 1;
|
||||
buf[1] = reg;
|
||||
buf[2] = data & 0xFF;
|
||||
buf[3] = data >> 8;
|
||||
buf[4] = this->crc8_pec_(buf, 4);
|
||||
return this->write_bytes(reg, buf + 2, 3);
|
||||
}
|
||||
|
||||
void MLX90614Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MLX90614:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with MLX90614 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Ambient", this->ambient_sensor_);
|
||||
LOG_SENSOR(" ", "Object", this->object_sensor_);
|
||||
}
|
||||
|
||||
float MLX90614Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void MLX90614Component::update() {
|
||||
uint8_t emissivity[3];
|
||||
if (this->read_register(MLX90614_EMISSIVITY, emissivity, 3, false) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
uint8_t raw_object[3];
|
||||
if (this->read_register(MLX90614_TEMPERATURE_OBJECT_1, raw_object, 3, false) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t raw_ambient[3];
|
||||
if (this->read_register(MLX90614_TEMPERATURE_AMBIENT, raw_ambient, 3, false) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
float ambient = raw_ambient[1] & 0x80 ? NAN : encode_uint16(raw_ambient[1], raw_ambient[0]) * 0.02f - 273.15f;
|
||||
float object = raw_object[1] & 0x80 ? NAN : encode_uint16(raw_object[1], raw_object[0]) * 0.02f - 273.15f;
|
||||
|
||||
ESP_LOGD(TAG, "Got Temperature=%.1f°C Ambient=%.1f°C", object, ambient);
|
||||
|
||||
if (this->ambient_sensor_ != nullptr && !std::isnan(ambient))
|
||||
this->ambient_sensor_->publish_state(ambient);
|
||||
if (this->object_sensor_ != nullptr && !std::isnan(object))
|
||||
this->object_sensor_->publish_state(object);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
} // namespace mlx90614
|
||||
} // namespace esphome
|
||||
34
esphome/components/mlx90614/mlx90614.h
Normal file
34
esphome/components/mlx90614/mlx90614.h
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mlx90614 {
|
||||
|
||||
class MLX90614Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void set_ambient_sensor(sensor::Sensor *ambient_sensor) { ambient_sensor_ = ambient_sensor; }
|
||||
void set_object_sensor(sensor::Sensor *object_sensor) { object_sensor_ = object_sensor; }
|
||||
|
||||
void set_emissivity(float emissivity) { emissivity_ = emissivity; }
|
||||
|
||||
protected:
|
||||
bool write_emissivity_();
|
||||
|
||||
uint8_t crc8_pec_(const uint8_t *data, uint8_t len);
|
||||
bool write_bytes_(uint8_t reg, uint16_t data);
|
||||
|
||||
sensor::Sensor *ambient_sensor_{nullptr};
|
||||
sensor::Sensor *object_sensor_{nullptr};
|
||||
|
||||
float emissivity_{NAN};
|
||||
};
|
||||
} // namespace mlx90614
|
||||
} // namespace esphome
|
||||
63
esphome/components/mlx90614/sensor.py
Normal file
63
esphome/components/mlx90614/sensor.py
Normal file
@@ -0,0 +1,63 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
CONF_AMBIENT = "ambient"
|
||||
CONF_EMISSIVITY = "emissivity"
|
||||
CONF_OBJECT = "object"
|
||||
|
||||
mlx90614_ns = cg.esphome_ns.namespace("mlx90614")
|
||||
MLX90614Component = mlx90614_ns.class_(
|
||||
"MLX90614Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MLX90614Component),
|
||||
cv.Optional(CONF_AMBIENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_OBJECT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_EMISSIVITY, default=1.0): cv.percentage,
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x5A))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if CONF_AMBIENT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_AMBIENT])
|
||||
cg.add(var.set_ambient_sensor(sens))
|
||||
|
||||
if CONF_OBJECT in config:
|
||||
sens = await sensor.new_sensor(config[CONF_OBJECT])
|
||||
cg.add(var.set_object_sensor(sens))
|
||||
|
||||
cg.add(var.set_emissivity(config[CONF_OBJECT][CONF_EMISSIVITY]))
|
||||
@@ -75,13 +75,8 @@ void MQTTClimateComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryCo
|
||||
JsonArray presets = root.createNestedArray("preset_modes");
|
||||
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();
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY))
|
||||
presets.add("away");
|
||||
}
|
||||
if (traits.supports_preset(CLIMATE_PRESET_BOOST))
|
||||
presets.add("boost");
|
||||
if (traits.supports_preset(CLIMATE_PRESET_COMFORT))
|
||||
@@ -197,29 +192,6 @@ void MQTTClimateComponent::setup() {
|
||||
});
|
||||
}
|
||||
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
|
||||
this->subscribe(this->get_away_command_topic(), [this](const std::string &topic, const std::string &payload) {
|
||||
auto onoff = parse_on_off(payload.c_str());
|
||||
auto call = this->device_->make_call();
|
||||
switch (onoff) {
|
||||
case PARSE_ON:
|
||||
call.set_preset(CLIMATE_PRESET_AWAY);
|
||||
break;
|
||||
case PARSE_OFF:
|
||||
call.set_preset(CLIMATE_PRESET_HOME);
|
||||
break;
|
||||
case PARSE_TOGGLE:
|
||||
call.set_preset(this->device_->preset == CLIMATE_PRESET_AWAY ? CLIMATE_PRESET_HOME : CLIMATE_PRESET_AWAY);
|
||||
break;
|
||||
case PARSE_NONE:
|
||||
default:
|
||||
ESP_LOGW(TAG, "Unknown payload '%s'", payload.c_str());
|
||||
return;
|
||||
}
|
||||
call.perform();
|
||||
});
|
||||
}
|
||||
|
||||
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();
|
||||
@@ -301,11 +273,6 @@ bool MQTTClimateComponent::publish_state_() {
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (traits.supports_preset(CLIMATE_PRESET_AWAY)) {
|
||||
std::string payload = ONOFF(this->device_->preset == CLIMATE_PRESET_AWAY);
|
||||
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()) {
|
||||
|
||||
@@ -16,13 +16,5 @@ std::string NumberTraits::get_unit_of_measurement() {
|
||||
return "";
|
||||
}
|
||||
|
||||
void NumberTraits::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
|
||||
std::string NumberTraits::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace number
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -11,7 +12,7 @@ enum NumberMode : uint8_t {
|
||||
NUMBER_MODE_SLIDER = 2,
|
||||
};
|
||||
|
||||
class NumberTraits {
|
||||
class NumberTraits : public EntityBase_DeviceClass {
|
||||
public:
|
||||
// Set/get the number value boundaries.
|
||||
void set_min_value(float min_value) { min_value_ = min_value; }
|
||||
@@ -32,17 +33,12 @@ class NumberTraits {
|
||||
void set_mode(NumberMode mode) { this->mode_ = mode; }
|
||||
NumberMode get_mode() const { return this->mode_; }
|
||||
|
||||
// Set/get the device class.
|
||||
void set_device_class(const std::string &device_class);
|
||||
std::string get_device_class();
|
||||
|
||||
protected:
|
||||
float min_value_ = NAN;
|
||||
float max_value_ = NAN;
|
||||
float step_ = NAN;
|
||||
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
|
||||
NumberMode mode_{NUMBER_MODE_AUTO};
|
||||
optional<std::string> device_class_;
|
||||
};
|
||||
|
||||
} // namespace number
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
#include <esp_ota_ops.h>
|
||||
#include "esphome/components/md5/md5.h"
|
||||
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
#include <spi_flash_mmap.h>
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace ota {
|
||||
|
||||
@@ -16,9 +20,28 @@ OTAResponseTypes IDFOTABackend::begin(size_t image_size) {
|
||||
if (this->partition_ == nullptr) {
|
||||
return OTA_RESPONSE_ERROR_NO_UPDATE_PARTITION;
|
||||
}
|
||||
esp_task_wdt_init(15, false); // The following function takes longer than the 5 seconds timeout of WDT
|
||||
|
||||
// The following function takes longer than the 5 seconds timeout of WDT
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
esp_task_wdt_config_t wdtc;
|
||||
wdtc.timeout_ms = 15000;
|
||||
wdtc.idle_core_mask = 0;
|
||||
wdtc.trigger_panic = false;
|
||||
esp_task_wdt_reconfigure(&wdtc);
|
||||
#else
|
||||
esp_task_wdt_init(15, false);
|
||||
#endif
|
||||
|
||||
esp_err_t err = esp_ota_begin(this->partition_, image_size, &this->update_handle_);
|
||||
esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false); // Set the WDT back to the configured timeout
|
||||
|
||||
// Set the WDT back to the configured timeout
|
||||
#if ESP_IDF_VERSION_MAJOR >= 5
|
||||
wdtc.timeout_ms = CONFIG_ESP_TASK_WDT_TIMEOUT_S;
|
||||
esp_task_wdt_reconfigure(&wdtc);
|
||||
#else
|
||||
esp_task_wdt_init(CONFIG_ESP_TASK_WDT_TIMEOUT_S, false);
|
||||
#endif
|
||||
|
||||
if (err != ESP_OK) {
|
||||
esp_ota_abort(this->update_handle_);
|
||||
this->update_handle_ = 0;
|
||||
|
||||
78
esphome/components/pca6416a/__init__.py
Normal file
78
esphome/components/pca6416a/__init__.py
Normal file
@@ -0,0 +1,78 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins
|
||||
from esphome.components import i2c
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_NUMBER,
|
||||
CONF_MODE,
|
||||
CONF_INVERTED,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@Mat931"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
MULTI_CONF = True
|
||||
pca6416a_ns = cg.esphome_ns.namespace("pca6416a")
|
||||
|
||||
PCA6416AComponent = pca6416a_ns.class_("PCA6416AComponent", cg.Component, i2c.I2CDevice)
|
||||
PCA6416AGPIOPin = pca6416a_ns.class_(
|
||||
"PCA6416AGPIOPin", cg.GPIOPin, cg.Parented.template(PCA6416AComponent)
|
||||
)
|
||||
|
||||
CONF_PCA6416A = "pca6416a"
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema({cv.Required(CONF_ID): cv.declare_id(PCA6416AComponent)})
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(i2c.i2c_device_schema(0x21))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
|
||||
def validate_mode(value):
|
||||
if not (value[CONF_INPUT] or value[CONF_OUTPUT]):
|
||||
raise cv.Invalid("Mode must be either input or output")
|
||||
if value[CONF_INPUT] and value[CONF_OUTPUT]:
|
||||
raise cv.Invalid("Mode must be either input or output")
|
||||
if value[CONF_PULLUP] and not value[CONF_INPUT]:
|
||||
raise cv.Invalid("Pullup only available with input")
|
||||
return value
|
||||
|
||||
|
||||
PCA6416A_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(PCA6416AGPIOPin),
|
||||
cv.Required(CONF_PCA6416A): cv.use_id(PCA6416AComponent),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=16),
|
||||
cv.Optional(CONF_MODE, default={}): cv.All(
|
||||
{
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
},
|
||||
validate_mode,
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("pca6416a", PCA6416A_PIN_SCHEMA)
|
||||
async def pca6416a_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
parent = await cg.get_variable(config[CONF_PCA6416A])
|
||||
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
174
esphome/components/pca6416a/pca6416a.cpp
Normal file
174
esphome/components/pca6416a/pca6416a.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "pca6416a.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pca6416a {
|
||||
|
||||
enum PCA6416AGPIORegisters {
|
||||
// 0 side
|
||||
PCA6416A_INPUT0 = 0x00,
|
||||
PCA6416A_OUTPUT0 = 0x02,
|
||||
PCA6416A_INVERT0 = 0x04,
|
||||
PCA6416A_CONFIG0 = 0x06,
|
||||
PCAL6416A_PULL_EN0 = 0x46,
|
||||
PCAL6416A_PULL_DIR0 = 0x48,
|
||||
// 1 side
|
||||
PCA6416A_INPUT1 = 0x01,
|
||||
PCA6416A_OUTPUT1 = 0x03,
|
||||
PCA6416A_INVERT1 = 0x05,
|
||||
PCA6416A_CONFIG1 = 0x07,
|
||||
PCAL6416A_PULL_EN1 = 0x47,
|
||||
PCAL6416A_PULL_DIR1 = 0x49,
|
||||
};
|
||||
|
||||
static const char *const TAG = "pca6416a";
|
||||
|
||||
void PCA6416AComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up PCA6416A...");
|
||||
// Test to see if device exists
|
||||
uint8_t value;
|
||||
if (!this->read_register_(PCA6416A_INPUT0, &value)) {
|
||||
ESP_LOGE(TAG, "PCA6416A not available under 0x%02X", this->address_);
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Test to see if the device supports pull-up resistors
|
||||
if (this->read_register(PCAL6416A_PULL_EN0, &value, 1, true) == esphome::i2c::ERROR_OK) {
|
||||
this->has_pullup_ = true;
|
||||
}
|
||||
|
||||
// No polarity inversion
|
||||
this->write_register_(PCA6416A_INVERT0, 0);
|
||||
this->write_register_(PCA6416A_INVERT1, 0);
|
||||
// Set all pins to input
|
||||
this->write_register_(PCA6416A_CONFIG0, 0xff);
|
||||
this->write_register_(PCA6416A_CONFIG1, 0xff);
|
||||
// Read current output register state
|
||||
this->read_register_(PCA6416A_OUTPUT0, &this->output_0_);
|
||||
this->read_register_(PCA6416A_OUTPUT1, &this->output_1_);
|
||||
|
||||
ESP_LOGD(TAG, "Initialization complete. Warning: %d, Error: %d", this->status_has_warning(),
|
||||
this->status_has_error());
|
||||
}
|
||||
|
||||
void PCA6416AComponent::dump_config() {
|
||||
if (this->has_pullup_) {
|
||||
ESP_LOGCONFIG(TAG, "PCAL6416A:");
|
||||
} else {
|
||||
ESP_LOGCONFIG(TAG, "PCA6416A:");
|
||||
}
|
||||
LOG_I2C_DEVICE(this)
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with PCA6416A failed!");
|
||||
}
|
||||
}
|
||||
|
||||
bool PCA6416AComponent::digital_read(uint8_t pin) {
|
||||
uint8_t bit = pin % 8;
|
||||
uint8_t reg_addr = pin < 8 ? PCA6416A_INPUT0 : PCA6416A_INPUT1;
|
||||
uint8_t value = 0;
|
||||
this->read_register_(reg_addr, &value);
|
||||
return value & (1 << bit);
|
||||
}
|
||||
|
||||
void PCA6416AComponent::digital_write(uint8_t pin, bool value) {
|
||||
uint8_t reg_addr = pin < 8 ? PCA6416A_OUTPUT0 : PCA6416A_OUTPUT1;
|
||||
this->update_register_(pin, value, reg_addr);
|
||||
}
|
||||
|
||||
void PCA6416AComponent::pin_mode(uint8_t pin, gpio::Flags flags) {
|
||||
uint8_t io_dir = pin < 8 ? PCA6416A_CONFIG0 : PCA6416A_CONFIG1;
|
||||
uint8_t pull_en = pin < 8 ? PCAL6416A_PULL_EN0 : PCAL6416A_PULL_EN1;
|
||||
uint8_t pull_dir = pin < 8 ? PCAL6416A_PULL_DIR0 : PCAL6416A_PULL_DIR1;
|
||||
if (flags == gpio::FLAG_INPUT) {
|
||||
this->update_register_(pin, true, io_dir);
|
||||
if (has_pullup_) {
|
||||
this->update_register_(pin, true, pull_dir);
|
||||
this->update_register_(pin, false, pull_en);
|
||||
}
|
||||
} else if (flags == (gpio::FLAG_INPUT | gpio::FLAG_PULLUP)) {
|
||||
this->update_register_(pin, true, io_dir);
|
||||
if (has_pullup_) {
|
||||
this->update_register_(pin, true, pull_dir);
|
||||
this->update_register_(pin, true, pull_en);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Your PCA6416A does not support pull-up resistors");
|
||||
}
|
||||
} else if (flags == gpio::FLAG_OUTPUT) {
|
||||
this->update_register_(pin, false, io_dir);
|
||||
}
|
||||
}
|
||||
|
||||
bool PCA6416AComponent::read_register_(uint8_t reg, uint8_t *value) {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGD(TAG, "Device marked failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this->last_error_ = this->read_register(reg, value, 1, true)) != esphome::i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "read_register_(): I2C I/O error: %d", (int) this->last_error_);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PCA6416AComponent::write_register_(uint8_t reg, uint8_t value) {
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGD(TAG, "Device marked failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((this->last_error_ = this->write_register(reg, &value, 1, true)) != esphome::i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGE(TAG, "write_register_(): I2C I/O error: %d", (int) this->last_error_);
|
||||
return false;
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
return true;
|
||||
}
|
||||
|
||||
void PCA6416AComponent::update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr) {
|
||||
uint8_t bit = pin % 8;
|
||||
uint8_t reg_value = 0;
|
||||
if (reg_addr == PCA6416A_OUTPUT0) {
|
||||
reg_value = this->output_0_;
|
||||
} else if (reg_addr == PCA6416A_OUTPUT1) {
|
||||
reg_value = this->output_1_;
|
||||
} else {
|
||||
this->read_register_(reg_addr, ®_value);
|
||||
}
|
||||
|
||||
if (pin_value) {
|
||||
reg_value |= 1 << bit;
|
||||
} else {
|
||||
reg_value &= ~(1 << bit);
|
||||
}
|
||||
|
||||
this->write_register_(reg_addr, reg_value);
|
||||
|
||||
if (reg_addr == PCA6416A_OUTPUT0) {
|
||||
this->output_0_ = reg_value;
|
||||
} else if (reg_addr == PCA6416A_OUTPUT1) {
|
||||
this->output_1_ = reg_value;
|
||||
}
|
||||
}
|
||||
|
||||
float PCA6416AComponent::get_setup_priority() const { return setup_priority::IO; }
|
||||
|
||||
void PCA6416AGPIOPin::setup() { pin_mode(flags_); }
|
||||
void PCA6416AGPIOPin::pin_mode(gpio::Flags flags) { this->parent_->pin_mode(this->pin_, flags); }
|
||||
bool PCA6416AGPIOPin::digital_read() { return this->parent_->digital_read(this->pin_) != this->inverted_; }
|
||||
void PCA6416AGPIOPin::digital_write(bool value) { this->parent_->digital_write(this->pin_, value != this->inverted_); }
|
||||
std::string PCA6416AGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "%u via PCA6416A", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
} // namespace pca6416a
|
||||
} // namespace esphome
|
||||
63
esphome/components/pca6416a/pca6416a.h
Normal file
63
esphome/components/pca6416a/pca6416a.h
Normal file
@@ -0,0 +1,63 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pca6416a {
|
||||
|
||||
class PCA6416AComponent : public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
PCA6416AComponent() = default;
|
||||
|
||||
/// Check i2c availability and setup masks
|
||||
void setup() override;
|
||||
/// Helper function to read the value of a pin.
|
||||
bool digital_read(uint8_t pin);
|
||||
/// Helper function to write the value of a pin.
|
||||
void digital_write(uint8_t pin, bool value);
|
||||
/// Helper function to set the pin mode of a pin.
|
||||
void pin_mode(uint8_t pin, gpio::Flags flags);
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
bool read_register_(uint8_t reg, uint8_t *value);
|
||||
bool write_register_(uint8_t reg, uint8_t value);
|
||||
void update_register_(uint8_t pin, bool pin_value, uint8_t reg_addr);
|
||||
|
||||
/// The mask to write as output state - 1 means HIGH, 0 means LOW
|
||||
uint8_t output_0_{0x00};
|
||||
uint8_t output_1_{0x00};
|
||||
/// Storage for last I2C error seen
|
||||
esphome::i2c::ErrorCode last_error_;
|
||||
/// Only the PCAL6416A has pull-up resistors
|
||||
bool has_pullup_{false};
|
||||
};
|
||||
|
||||
/// Helper class to expose a PCA6416A pin as an internal input GPIO pin.
|
||||
class PCA6416AGPIOPin : public GPIOPin {
|
||||
public:
|
||||
void setup() override;
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
|
||||
void set_parent(PCA6416AComponent *parent) { parent_ = parent; }
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
protected:
|
||||
PCA6416AComponent *parent_;
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace pca6416a
|
||||
} // namespace esphome
|
||||
@@ -81,7 +81,32 @@ void PN532::setup() {
|
||||
this->turn_off_rf_();
|
||||
}
|
||||
|
||||
bool PN532::powerdown() {
|
||||
updates_enabled_ = false;
|
||||
requested_read_ = false;
|
||||
ESP_LOGI(TAG, "Powering down PN532");
|
||||
if (!this->write_command_({PN532_COMMAND_POWERDOWN, 0b10100000})) { // enable i2c,spi wakeup
|
||||
ESP_LOGE(TAG, "Error writing powerdown command to PN532");
|
||||
return false;
|
||||
}
|
||||
std::vector<uint8_t> response;
|
||||
if (!this->read_response(PN532_COMMAND_POWERDOWN, response)) {
|
||||
ESP_LOGE(TAG, "Error reading PN532 powerdown response");
|
||||
return false;
|
||||
}
|
||||
if (response[0] != 0x00) {
|
||||
ESP_LOGE(TAG, "Error on PN532 powerdown: %02x", response[0]);
|
||||
return false;
|
||||
}
|
||||
ESP_LOGV(TAG, "Powerdown successful");
|
||||
delay(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void PN532::update() {
|
||||
if (!updates_enabled_)
|
||||
return;
|
||||
|
||||
for (auto *obj : this->binary_sensors_)
|
||||
obj->on_scan_end();
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ static const uint8_t PN532_COMMAND_SAMCONFIGURATION = 0x14;
|
||||
static const uint8_t PN532_COMMAND_RFCONFIGURATION = 0x32;
|
||||
static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
|
||||
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
|
||||
static const uint8_t PN532_COMMAND_POWERDOWN = 0x16;
|
||||
|
||||
class PN532BinarySensor;
|
||||
|
||||
@@ -30,6 +31,7 @@ class PN532 : public PollingComponent {
|
||||
float get_setup_priority() const override;
|
||||
|
||||
void loop() override;
|
||||
void on_shutdown() override { powerdown(); }
|
||||
|
||||
void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
|
||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
@@ -45,6 +47,7 @@ class PN532 : public PollingComponent {
|
||||
void clean_mode();
|
||||
void format_mode();
|
||||
void write_mode(nfc::NdefMessage *message);
|
||||
bool powerdown();
|
||||
|
||||
protected:
|
||||
void turn_off_rf_();
|
||||
@@ -79,6 +82,7 @@ class PN532 : public PollingComponent {
|
||||
bool write_mifare_ultralight_tag_(std::vector<uint8_t> &uid, nfc::NdefMessage *message);
|
||||
bool clean_mifare_ultralight_();
|
||||
|
||||
bool updates_enabled_{true};
|
||||
bool requested_read_{false};
|
||||
std::vector<PN532BinarySensor *> binary_sensors_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
|
||||
@@ -2,7 +2,12 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation, pins
|
||||
from esphome.components import i2c
|
||||
from esphome.const import CONF_ON_TAG, CONF_TRIGGER_ID, CONF_RESET_PIN
|
||||
from esphome.const import (
|
||||
CONF_ON_TAG,
|
||||
CONF_ON_TAG_REMOVED,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_RESET_PIN,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@glmnet"]
|
||||
AUTO_LOAD = ["binary_sensor"]
|
||||
@@ -24,6 +29,11 @@ RC522_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(RC522Trigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
|
||||
@@ -37,5 +47,10 @@ async def setup_rc522(var, config):
|
||||
|
||||
for conf in config.get(CONF_ON_TAG, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_trigger(trigger))
|
||||
cg.add(var.register_ontag_trigger(trigger))
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
for conf in config.get(CONF_ON_TAG_REMOVED, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID])
|
||||
cg.add(var.register_ontagremoved_trigger(trigger))
|
||||
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
|
||||
|
||||
@@ -256,7 +256,7 @@ void RC522::loop() {
|
||||
|
||||
this->current_uid_ = rfid_uid;
|
||||
|
||||
for (auto *trigger : this->triggers_)
|
||||
for (auto *trigger : this->triggers_ontag_)
|
||||
trigger->process(rfid_uid);
|
||||
|
||||
if (report) {
|
||||
@@ -265,6 +265,11 @@ void RC522::loop() {
|
||||
break;
|
||||
}
|
||||
case STATE_DONE: {
|
||||
if (!this->current_uid_.empty()) {
|
||||
ESP_LOGV(TAG, "Tag '%s' removed", format_uid(this->current_uid_).c_str());
|
||||
for (auto *trigger : this->triggers_ontagremoved_)
|
||||
trigger->process(this->current_uid_);
|
||||
}
|
||||
this->current_uid_ = {};
|
||||
state_ = STATE_INIT;
|
||||
break;
|
||||
|
||||
@@ -24,7 +24,8 @@ class RC522 : public PollingComponent {
|
||||
void loop() override;
|
||||
|
||||
void register_tag(RC522BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
|
||||
void register_trigger(RC522Trigger *trig) { this->triggers_.push_back(trig); }
|
||||
void register_ontag_trigger(RC522Trigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
void register_ontagremoved_trigger(RC522Trigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
|
||||
|
||||
void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; }
|
||||
|
||||
@@ -242,7 +243,8 @@ class RC522 : public PollingComponent {
|
||||
uint8_t reset_count_{0};
|
||||
uint32_t reset_timeout_{0};
|
||||
std::vector<RC522BinarySensor *> binary_sensors_;
|
||||
std::vector<RC522Trigger *> triggers_;
|
||||
std::vector<RC522Trigger *> triggers_ontag_;
|
||||
std::vector<RC522Trigger *> triggers_ontagremoved_;
|
||||
std::vector<uint8_t> current_uid_;
|
||||
|
||||
enum RC522Error {
|
||||
|
||||
@@ -791,6 +791,57 @@ async def raw_action(var, config, args):
|
||||
cg.add(var.set_carrier_frequency(templ))
|
||||
|
||||
|
||||
# Drayton
|
||||
(
|
||||
DraytonData,
|
||||
DraytonBinarySensor,
|
||||
DraytonTrigger,
|
||||
DraytonAction,
|
||||
DraytonDumper,
|
||||
) = declare_protocol("Drayton")
|
||||
DRAYTON_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ADDRESS): cv.All(cv.hex_int, cv.Range(min=0, max=0xFFFF)),
|
||||
cv.Required(CONF_CHANNEL): cv.All(cv.hex_int, cv.Range(min=0, max=0x1F)),
|
||||
cv.Required(CONF_COMMAND): cv.All(cv.hex_int, cv.Range(min=0, max=0x7F)),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("drayton", DraytonBinarySensor, DRAYTON_SCHEMA)
|
||||
def drayton_binary_sensor(var, config):
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
DraytonData,
|
||||
("address", config[CONF_ADDRESS]),
|
||||
("channel", config[CONF_CHANNEL]),
|
||||
("command", config[CONF_COMMAND]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register_trigger("drayton", DraytonTrigger, DraytonData)
|
||||
def drayton_trigger(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_dumper("drayton", DraytonDumper)
|
||||
def drayton_dumper(var, config):
|
||||
pass
|
||||
|
||||
|
||||
@register_action("drayton", DraytonAction, DRAYTON_SCHEMA)
|
||||
async def drayton_action(var, config, args):
|
||||
template_ = await cg.templatable(config[CONF_ADDRESS], args, cg.uint16)
|
||||
cg.add(var.set_address(template_))
|
||||
template_ = await cg.templatable(config[CONF_CHANNEL], args, cg.uint8)
|
||||
cg.add(var.set_channel(template_))
|
||||
template_ = await cg.templatable(config[CONF_COMMAND], args, cg.uint8)
|
||||
cg.add(var.set_command(template_))
|
||||
|
||||
|
||||
# RC5
|
||||
RC5Data, RC5BinarySensor, RC5Trigger, RC5Action, RC5Dumper = declare_protocol("RC5")
|
||||
RC5_SCHEMA = cv.Schema(
|
||||
|
||||
213
esphome/components/remote_base/drayton_protocol.cpp
Normal file
213
esphome/components/remote_base/drayton_protocol.cpp
Normal file
@@ -0,0 +1,213 @@
|
||||
#include "drayton_protocol.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
static const char *const TAG = "remote.drayton";
|
||||
|
||||
static const uint32_t BIT_TIME_US = 500;
|
||||
static const uint8_t CARRIER_KHZ = 2;
|
||||
static const uint8_t NBITS_PREAMBLE = 12;
|
||||
static const uint8_t NBITS_SYNC = 4;
|
||||
static const uint8_t NBITS_ADDRESS = 16;
|
||||
static const uint8_t NBITS_CHANNEL = 5;
|
||||
static const uint8_t NBITS_COMMAND = 7;
|
||||
static const uint8_t NBITS = NBITS_ADDRESS + NBITS_CHANNEL + NBITS_COMMAND;
|
||||
|
||||
static const uint8_t CMD_ON = 0x41;
|
||||
static const uint8_t CMD_OFF = 0x02;
|
||||
|
||||
/*
|
||||
Drayton Protocol
|
||||
Using an oscilloscope to capture the data transmitted by the Digistat two
|
||||
distinct packets for 'On' and 'Off' are transmitted. Each transmitted bit
|
||||
has a period of 500us, a bit rate of 2000 baud.
|
||||
|
||||
Each packet consists of an initial 1010 pattern to set up the receiver bias.
|
||||
The number of these bits seen at the receiver varies depending on the state
|
||||
of the bias when the packet transmission starts. The receiver algoritmn takes
|
||||
account of this.
|
||||
|
||||
The packet appears to be Manchester encoded, with a '10' tranmitted pair
|
||||
representing a '1' bit and a '01' pair representing a '0' bit. Each packet is
|
||||
begun with a '1100' syncronisation symbol which breaks this rule. Following
|
||||
the sync are 28 '01' or '10' pairs.
|
||||
|
||||
--------------------
|
||||
|
||||
Boiler On Command as received:
|
||||
101010101010110001101001010101101001010101010101100101010101101001011001
|
||||
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-1-0-0-0-0-0-1-1-0-0-1-0
|
||||
|
||||
(Where pppp represents the preamble bits and SSSS represents the sync symbol)
|
||||
|
||||
28 bits of data received 01100001100000001000001 10010 (bin) or 6180832 (hex)
|
||||
|
||||
Boiler Off Command as received:
|
||||
101010101010110001101001010101101001010101010101010101010110011001011001
|
||||
ppppppppppppSSSS-0-1-1-0-0-0-0-1-1-0-0-0-0-0-0-0-0-0-0-0-0-1-0-1-0-0-1-0
|
||||
|
||||
28 bits of data received 0110000110000000000001010010 (bin) or 6180052 (hex)
|
||||
|
||||
--------------------
|
||||
|
||||
I have used 'RFLink' software (RLink Firmware Version: 1.1 Revision: 48) to
|
||||
capture and retransmit the Digistat packets. RFLink splits each packet into an
|
||||
ID, SWITCH, and CMD field.
|
||||
|
||||
0;17;Drayton;ID=c300;SWITCH=12;CMD=ON;
|
||||
20;18;Drayton;ID=c300;SWITCH=12;CMD=OFF;
|
||||
|
||||
--------------------
|
||||
|
||||
Spliting my received data into three parts of 16, 7 and 5 bits gives address,
|
||||
channel and Command values of:
|
||||
|
||||
On 6180832 0110000110000000 1000001 10010
|
||||
address: '0x6180' channel: '0x12' command: '0x41'
|
||||
|
||||
Off 6180052 0110000110000000 0000010 10010
|
||||
address: '0x6180' channel: '0x12' command: '0x02'
|
||||
|
||||
These values are slightly different to those used by RFLink (the RFLink
|
||||
ID/Adress value is rotated/manipulated), and I don't know who's interpretation
|
||||
is correct. A larger data sample would help (I have only found five different
|
||||
packet captures online) or definitive information from Drayton.
|
||||
|
||||
Splitting each packet in this way works well for me with esphome. Any
|
||||
corrections or additional data samples would be gratefully received.
|
||||
|
||||
marshn
|
||||
|
||||
*/
|
||||
|
||||
void DraytonProtocol::encode(RemoteTransmitData *dst, const DraytonData &data) {
|
||||
uint16_t khz = CARRIER_KHZ;
|
||||
dst->set_carrier_frequency(khz * 1000);
|
||||
|
||||
// Preamble = 101010101010
|
||||
uint32_t out_data = 0x0AAA;
|
||||
for (uint32_t mask = 1UL << (NBITS_PREAMBLE - 1); mask != 0; mask >>= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(BIT_TIME_US);
|
||||
} else {
|
||||
dst->space(BIT_TIME_US);
|
||||
}
|
||||
}
|
||||
|
||||
// Sync = 1100
|
||||
out_data = 0x000C;
|
||||
for (uint32_t mask = 1UL << (NBITS_SYNC - 1); mask != 0; mask >>= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(BIT_TIME_US);
|
||||
} else {
|
||||
dst->space(BIT_TIME_US);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Send Drayton: address=%04x channel=%03x cmd=%02x", data.address, data.channel, data.command);
|
||||
|
||||
out_data = data.address;
|
||||
out_data <<= NBITS_COMMAND;
|
||||
out_data |= data.command;
|
||||
out_data <<= NBITS_CHANNEL;
|
||||
out_data |= data.channel;
|
||||
|
||||
ESP_LOGV(TAG, "Send Drayton: out_data %08x", out_data);
|
||||
|
||||
for (uint32_t mask = 1UL << (NBITS - 1); mask != 0; mask >>= 1) {
|
||||
if (out_data & mask) {
|
||||
dst->mark(BIT_TIME_US);
|
||||
dst->space(BIT_TIME_US);
|
||||
} else {
|
||||
dst->space(BIT_TIME_US);
|
||||
dst->mark(BIT_TIME_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
optional<DraytonData> DraytonProtocol::decode(RemoteReceiveData src) {
|
||||
DraytonData out{
|
||||
.address = 0,
|
||||
.channel = 0,
|
||||
.command = 0,
|
||||
};
|
||||
|
||||
if (src.size() < 45) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ESP_LOGVV(TAG, "Decode Drayton: %d, %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d %d", src.size(),
|
||||
src.peek(0), src.peek(1), src.peek(2), src.peek(3), src.peek(4), src.peek(5), src.peek(6), src.peek(7),
|
||||
src.peek(8), src.peek(9), src.peek(10), src.peek(11), src.peek(12), src.peek(13), src.peek(14),
|
||||
src.peek(15), src.peek(16), src.peek(17), src.peek(18), src.peek(19));
|
||||
|
||||
// If first preamble item is a space, skip it
|
||||
if (src.peek_space_at_least(1)) {
|
||||
src.advance(1);
|
||||
}
|
||||
|
||||
// Look for sync pulse, after. If sucessful index points to space of sync symbol
|
||||
for (uint16_t preamble = 0; preamble <= NBITS_PREAMBLE * 2; preamble += 2) {
|
||||
ESP_LOGVV(TAG, "Decode Drayton: preamble %d %d %d", preamble, src.peek(preamble), src.peek(preamble + 1));
|
||||
if (src.peek_mark(2 * BIT_TIME_US, preamble) &&
|
||||
(src.peek_space(2 * BIT_TIME_US, preamble + 1) || src.peek_space(3 * BIT_TIME_US, preamble + 1))) {
|
||||
src.advance(preamble + 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read data. Index points to space of sync symbol
|
||||
// Extract first bit
|
||||
// Checks next bit to leave index pointing correctly
|
||||
uint32_t out_data = 0;
|
||||
uint8_t bit = NBITS_ADDRESS + NBITS_COMMAND + NBITS_CHANNEL - 1;
|
||||
if (src.expect_space(3 * BIT_TIME_US) && (src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
||||
out_data |= 0 << bit;
|
||||
} else if (src.expect_space(2 * BIT_TIME_US) && src.expect_mark(BIT_TIME_US) &&
|
||||
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
||||
out_data |= 1 << bit;
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Decode Drayton: Fail 1, - %d", src.get_index());
|
||||
return {};
|
||||
}
|
||||
|
||||
// Before/after each bit is read the index points to the transition at the start of the bit period or,
|
||||
// if there is no transition at the start of the bit period, then the transition in the middle of
|
||||
// the previous bit period.
|
||||
while (--bit >= 1) {
|
||||
ESP_LOGVV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
||||
if ((src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) &&
|
||||
(src.expect_mark(BIT_TIME_US) || src.peek_mark(2 * BIT_TIME_US))) {
|
||||
out_data |= 0 << bit;
|
||||
} else if ((src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) &&
|
||||
(src.expect_space(BIT_TIME_US) || src.peek_space(2 * BIT_TIME_US))) {
|
||||
out_data |= 1 << bit;
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "Decode Drayton: Fail 2, %2d %08x", bit, out_data);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
if (src.expect_space(BIT_TIME_US) || src.expect_space(2 * BIT_TIME_US)) {
|
||||
out_data |= 0;
|
||||
} else if (src.expect_mark(BIT_TIME_US) || src.expect_mark(2 * BIT_TIME_US)) {
|
||||
out_data |= 1;
|
||||
}
|
||||
ESP_LOGV(TAG, "Decode Drayton: Data, %2d %08x", bit, out_data);
|
||||
|
||||
out.channel = (uint8_t) (out_data & 0x1F);
|
||||
out_data >>= NBITS_CHANNEL;
|
||||
out.command = (uint8_t) (out_data & 0x7F);
|
||||
out_data >>= NBITS_COMMAND;
|
||||
out.address = (uint16_t) (out_data & 0xFFFF);
|
||||
|
||||
return out;
|
||||
}
|
||||
void DraytonProtocol::dump(const DraytonData &data) {
|
||||
ESP_LOGD(TAG, "Received Drayton: address=0x%04X (0x%04x), channel=0x%03x command=0x%03X", data.address,
|
||||
((data.address << 1) & 0xffff), data.channel, data.command);
|
||||
}
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
||||
44
esphome/components/remote_base/drayton_protocol.h
Normal file
44
esphome/components/remote_base/drayton_protocol.h
Normal file
@@ -0,0 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "remote_base.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
struct DraytonData {
|
||||
uint16_t address;
|
||||
uint8_t channel;
|
||||
uint8_t command;
|
||||
|
||||
bool operator==(const DraytonData &rhs) const {
|
||||
return address == rhs.address && channel == rhs.channel && command == rhs.command;
|
||||
}
|
||||
};
|
||||
|
||||
class DraytonProtocol : public RemoteProtocol<DraytonData> {
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const DraytonData &data) override;
|
||||
optional<DraytonData> decode(RemoteReceiveData src) override;
|
||||
void dump(const DraytonData &data) override;
|
||||
};
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(Drayton)
|
||||
|
||||
template<typename... Ts> class DraytonAction : public RemoteTransmitterActionBase<Ts...> {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint16_t, address)
|
||||
TEMPLATABLE_VALUE(uint8_t, channel)
|
||||
TEMPLATABLE_VALUE(uint8_t, command)
|
||||
|
||||
void encode(RemoteTransmitData *dst, Ts... x) override {
|
||||
DraytonData data{};
|
||||
data.address = this->address_.value(x...);
|
||||
data.channel = this->channel_.value(x...);
|
||||
data.command = this->command_.value(x...);
|
||||
DraytonProtocol().encode(dst, data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace remote_base
|
||||
} // namespace esphome
|
||||
@@ -102,7 +102,7 @@ def _parse_platform_version(value):
|
||||
try:
|
||||
# if platform version is a valid version constraint, prefix the default package
|
||||
cv.platformio_version_constraint(value)
|
||||
return f"platformio/raspberrypi @ {value}"
|
||||
return f"platformio/raspberrypi@{value}"
|
||||
except cv.Invalid:
|
||||
return value
|
||||
|
||||
@@ -148,7 +148,7 @@ async def to_code(config):
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
[f"earlephilhower/framework-arduinopico @ {conf[CONF_SOURCE]}"],
|
||||
[f"earlephilhower/framework-arduinopico@{conf[CONF_SOURCE]}"],
|
||||
)
|
||||
|
||||
cg.add_platformio_option("board_build.core", "earlephilhower")
|
||||
|
||||
@@ -38,13 +38,6 @@ int8_t Sensor::get_accuracy_decimals() {
|
||||
}
|
||||
void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; }
|
||||
|
||||
std::string Sensor::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
return "";
|
||||
}
|
||||
void Sensor::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
|
||||
void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; }
|
||||
StateClass Sensor::get_state_class() {
|
||||
if (this->state_class_.has_value())
|
||||
|
||||
@@ -54,7 +54,7 @@ std::string state_class_to_string(StateClass state_class);
|
||||
*
|
||||
* A sensor has unit of measurement and can use publish_state to send out a new value with the specified accuracy.
|
||||
*/
|
||||
class Sensor : public EntityBase {
|
||||
class Sensor : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
explicit Sensor();
|
||||
|
||||
@@ -68,11 +68,6 @@ class Sensor : public EntityBase {
|
||||
/// Manually set the accuracy in decimals.
|
||||
void set_accuracy_decimals(int8_t accuracy_decimals);
|
||||
|
||||
/// Get the device class, using the manual override if set.
|
||||
std::string get_device_class();
|
||||
/// Manually set the device class.
|
||||
void set_device_class(const std::string &device_class);
|
||||
|
||||
/// Get the state class, using the manual override if set.
|
||||
StateClass get_state_class();
|
||||
/// Manually set the state class.
|
||||
@@ -165,7 +160,6 @@ class Sensor : public EntityBase {
|
||||
|
||||
optional<std::string> unit_of_measurement_; ///< Unit of measurement override
|
||||
optional<int8_t> accuracy_decimals_; ///< Accuracy in decimals override
|
||||
optional<std::string> device_class_; ///< Device class override
|
||||
optional<StateClass> state_class_{STATE_CLASS_NONE}; ///< State class override
|
||||
bool force_update_{false}; ///< Force update mode
|
||||
bool has_state_{false};
|
||||
|
||||
@@ -286,7 +286,9 @@ SPRINKLER_VALVE_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_ENABLE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
@@ -333,7 +335,9 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_NAME): cv.string,
|
||||
cv.Optional(CONF_AUTO_ADVANCE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
@@ -343,19 +347,25 @@ SPRINKLER_CONTROLLER_SCHEMA = cv.Schema(
|
||||
),
|
||||
cv.Optional(CONF_QUEUE_ENABLE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_REVERSE_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
cv.Optional(CONF_STANDBY_SWITCH): cv.maybe_simple_value(
|
||||
switch.switch_schema(
|
||||
SprinklerControllerSwitch, entity_category=ENTITY_CATEGORY_CONFIG
|
||||
SprinklerControllerSwitch,
|
||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||
default_restore_mode="RESTORE_DEFAULT_OFF",
|
||||
),
|
||||
key=CONF_NAME,
|
||||
),
|
||||
|
||||
@@ -1176,6 +1176,21 @@ optional<uint32_t> Sprinkler::time_remaining_current_operation() {
|
||||
return nullopt;
|
||||
}
|
||||
|
||||
bool Sprinkler::any_controller_is_active() {
|
||||
if (this->state_ != IDLE) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (auto &controller : this->other_controllers_) {
|
||||
if (controller != this) { // dummy check
|
||||
if (controller->controller_state() != IDLE) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
SprinklerControllerSwitch *Sprinkler::control_switch(size_t valve_number) {
|
||||
if (this->is_a_valid_valve(valve_number)) {
|
||||
return this->valve_[valve_number].controller_switch;
|
||||
|
||||
@@ -406,6 +406,12 @@ class Sprinkler : public Component {
|
||||
/// returns the amount of time remaining in seconds for all valves remaining, including the active valve, if any
|
||||
optional<uint32_t> time_remaining_current_operation();
|
||||
|
||||
/// returns true if this or any sprinkler controller this controller knows about is active
|
||||
bool any_controller_is_active();
|
||||
|
||||
/// returns the current state of the sprinkler controller
|
||||
SprinklerState controller_state() { return this->state_; };
|
||||
|
||||
/// returns a pointer to a valve's control switch object
|
||||
SprinklerControllerSwitch *control_switch(size_t valve_number);
|
||||
|
||||
@@ -503,7 +509,6 @@ class Sprinkler : public Component {
|
||||
/// callback functions for timers
|
||||
void valve_selection_callback_();
|
||||
void sm_timer_callback_();
|
||||
void pump_stop_delay_callback_();
|
||||
|
||||
/// Maximum allowed queue size
|
||||
const uint8_t max_queue_size_{100};
|
||||
|
||||
@@ -287,18 +287,17 @@ HorizontalCoordinate Sun::calc_coords_() {
|
||||
*/
|
||||
return sun.true_coordinate(m);
|
||||
}
|
||||
optional<time::ESPTime> Sun::calc_event_(bool rising, double zenith) {
|
||||
optional<time::ESPTime> Sun::calc_event_(time::ESPTime date, bool rising, double zenith) {
|
||||
SunAtLocation sun{location_};
|
||||
auto now = this->time_->utcnow();
|
||||
if (!now.is_valid())
|
||||
if (!date.is_valid())
|
||||
return {};
|
||||
// Calculate UT1 timestamp at 0h
|
||||
auto today = now;
|
||||
auto today = date;
|
||||
today.hour = today.minute = today.second = 0;
|
||||
today.recalc_timestamp_utc();
|
||||
|
||||
auto it = sun.event(rising, today, zenith);
|
||||
if (it.has_value() && it->timestamp < now.timestamp) {
|
||||
if (it.has_value() && it->timestamp < date.timestamp) {
|
||||
// We're calculating *next* sunrise/sunset, but calculated event
|
||||
// is today, so try again tomorrow
|
||||
time_t new_timestamp = today.timestamp + 24 * 60 * 60;
|
||||
@@ -307,9 +306,19 @@ optional<time::ESPTime> Sun::calc_event_(bool rising, double zenith) {
|
||||
}
|
||||
return it;
|
||||
}
|
||||
optional<time::ESPTime> Sun::calc_event_(bool rising, double zenith) {
|
||||
auto it = Sun::calc_event_(this->time_->utcnow(), rising, zenith);
|
||||
return it;
|
||||
}
|
||||
|
||||
optional<time::ESPTime> Sun::sunrise(double elevation) { return this->calc_event_(true, 90 - elevation); }
|
||||
optional<time::ESPTime> Sun::sunset(double elevation) { return this->calc_event_(false, 90 - elevation); }
|
||||
optional<time::ESPTime> Sun::sunrise(time::ESPTime date, double elevation) {
|
||||
return this->calc_event_(date, true, 90 - elevation);
|
||||
}
|
||||
optional<time::ESPTime> Sun::sunset(time::ESPTime date, double elevation) {
|
||||
return this->calc_event_(date, false, 90 - elevation);
|
||||
}
|
||||
double Sun::elevation() { return this->calc_coords_().elevation; }
|
||||
double Sun::azimuth() { return this->calc_coords_().azimuth; }
|
||||
|
||||
|
||||
@@ -59,6 +59,8 @@ class Sun {
|
||||
|
||||
optional<time::ESPTime> sunrise(double elevation);
|
||||
optional<time::ESPTime> sunset(double elevation);
|
||||
optional<time::ESPTime> sunrise(time::ESPTime date, double elevation);
|
||||
optional<time::ESPTime> sunset(time::ESPTime date, double elevation);
|
||||
|
||||
double elevation();
|
||||
double azimuth();
|
||||
@@ -66,6 +68,7 @@ class Sun {
|
||||
protected:
|
||||
internal::HorizontalCoordinate calc_coords_();
|
||||
optional<time::ESPTime> calc_event_(bool rising, double zenith);
|
||||
optional<time::ESPTime> calc_event_(time::ESPTime date, bool rising, double zenith);
|
||||
|
||||
time::RealTimeClock *time_;
|
||||
internal::GeoLocation location_;
|
||||
|
||||
@@ -63,13 +63,6 @@ void Switch::add_on_state_callback(std::function<void(bool)> &&callback) {
|
||||
void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; }
|
||||
bool Switch::is_inverted() const { return this->inverted_; }
|
||||
|
||||
std::string Switch::get_device_class() {
|
||||
if (this->device_class_.has_value())
|
||||
return *this->device_class_;
|
||||
return "";
|
||||
}
|
||||
void Switch::set_device_class(const std::string &device_class) { this->device_class_ = device_class; }
|
||||
|
||||
void log_switch(const char *tag, const char *prefix, const char *type, Switch *obj) {
|
||||
if (obj != nullptr) {
|
||||
ESP_LOGCONFIG(tag, "%s%s '%s'", prefix, type, obj->get_name().c_str());
|
||||
|
||||
@@ -29,7 +29,7 @@ enum SwitchRestoreMode {
|
||||
* A switch is basically just a combination of a binary sensor (for reporting switch values)
|
||||
* and a write_state method that writes a state to the hardware.
|
||||
*/
|
||||
class Switch : public EntityBase {
|
||||
class Switch : public EntityBase, public EntityBase_DeviceClass {
|
||||
public:
|
||||
explicit Switch();
|
||||
|
||||
@@ -103,10 +103,6 @@ class Switch : public EntityBase {
|
||||
|
||||
bool is_inverted() const;
|
||||
|
||||
/// Get the device class for this switch.
|
||||
std::string get_device_class();
|
||||
/// Set the Home Assistant device class for this switch.
|
||||
void set_device_class(const std::string &device_class);
|
||||
void set_restore_mode(SwitchRestoreMode restore_mode) { this->restore_mode = restore_mode; }
|
||||
|
||||
protected:
|
||||
@@ -124,7 +120,6 @@ class Switch : public EntityBase {
|
||||
bool inverted_{false};
|
||||
Deduplicator<bool> publish_dedup_;
|
||||
ESPPreferenceObject rtc_;
|
||||
optional<std::string> device_class_;
|
||||
};
|
||||
|
||||
#define LOG_SWITCH(prefix, type, obj) log_switch((TAG), (prefix), LOG_STR_LITERAL(type), (obj))
|
||||
|
||||
@@ -73,6 +73,7 @@ async def to_code(config):
|
||||
await automation.build_automation(
|
||||
var.get_stop_trigger(), [], config[CONF_STOP_ACTION]
|
||||
)
|
||||
cg.add(var.set_has_stop(True))
|
||||
if CONF_TILT_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_tilt_trigger(), [(float, "tilt")], config[CONF_TILT_ACTION]
|
||||
|
||||
@@ -109,6 +109,7 @@ void TemplateCover::control(const CoverCall &call) {
|
||||
CoverTraits TemplateCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_is_assumed_state(this->assumed_state_);
|
||||
traits.set_supports_stop(this->has_stop_);
|
||||
traits.set_supports_position(this->has_position_);
|
||||
traits.set_supports_tilt(this->has_tilt_);
|
||||
return traits;
|
||||
@@ -116,6 +117,7 @@ CoverTraits TemplateCover::get_traits() {
|
||||
Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
|
||||
Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
|
||||
void TemplateCover::set_tilt_lambda(std::function<optional<float>()> &&tilt_f) { this->tilt_f_ = tilt_f; }
|
||||
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
|
||||
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }
|
||||
void TemplateCover::set_has_tilt(bool has_tilt) { this->has_tilt_ = has_tilt; }
|
||||
void TemplateCover::stop_prev_trigger_() {
|
||||
|
||||
@@ -26,6 +26,7 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
void set_optimistic(bool optimistic);
|
||||
void set_assumed_state(bool assumed_state);
|
||||
void set_tilt_lambda(std::function<optional<float>()> &&tilt_f);
|
||||
void set_has_stop(bool has_stop);
|
||||
void set_has_position(bool has_position);
|
||||
void set_has_tilt(bool has_tilt);
|
||||
void set_restore_mode(TemplateCoverRestoreMode restore_mode) { restore_mode_ = restore_mode; }
|
||||
@@ -48,6 +49,7 @@ class TemplateCover : public cover::Cover, public Component {
|
||||
bool optimistic_{false};
|
||||
Trigger<> *open_trigger_;
|
||||
Trigger<> *close_trigger_;
|
||||
bool has_stop_{false};
|
||||
Trigger<> *stop_trigger_;
|
||||
Trigger<> *prev_command_trigger_{nullptr};
|
||||
Trigger<float> *position_trigger_;
|
||||
|
||||
@@ -51,6 +51,7 @@ void TimeBasedCover::loop() {
|
||||
float TimeBasedCover::get_setup_priority() const { return setup_priority::DATA; }
|
||||
CoverTraits TimeBasedCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_stop(true);
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_toggle(true);
|
||||
traits.set_is_assumed_state(this->assumed_state_);
|
||||
|
||||
@@ -128,6 +128,7 @@ void TuyaCover::dump_config() {
|
||||
|
||||
cover::CoverTraits TuyaCover::get_traits() {
|
||||
auto traits = cover::CoverTraits();
|
||||
traits.set_supports_stop(true);
|
||||
traits.set_supports_position(true);
|
||||
return traits;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,9 @@ WaveshareEPaper4P2InBV2 = waveshare_epaper_ns.class_(
|
||||
WaveshareEPaper5P8In = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper5P8In", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper5P8InV2 = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper5P8InV2", WaveshareEPaper
|
||||
)
|
||||
WaveshareEPaper7P5In = waveshare_epaper_ns.class_(
|
||||
"WaveshareEPaper7P5In", WaveshareEPaper
|
||||
)
|
||||
@@ -80,6 +83,7 @@ MODELS = {
|
||||
"4.20in": ("b", WaveshareEPaper4P2In),
|
||||
"4.20in-bv2": ("b", WaveshareEPaper4P2InBV2),
|
||||
"5.83in": ("b", WaveshareEPaper5P8In),
|
||||
"5.83inv2": ("b", WaveshareEPaper5P8InV2),
|
||||
"7.50in": ("b", WaveshareEPaper7P5In),
|
||||
"7.50in-bv2": ("b", WaveshareEPaper7P5InBV2),
|
||||
"7.50in-bc": ("b", WaveshareEPaper7P5InBC),
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user