Compare commits
257 Commits
native-mqt
...
2022.2.0b2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc0ed8857f | ||
|
|
bb6b77bd98 | ||
|
|
dcc80f9032 | ||
|
|
dd554bcdf4 | ||
|
|
f376a39e55 | ||
|
|
8dcc9d6b66 | ||
|
|
a576c9f21f | ||
|
|
71a438e2cb | ||
|
|
272d6f2a8b | ||
|
|
5c22065135 | ||
|
|
e7dd6c52ac | ||
|
|
3bf042dce9 | ||
|
|
64f798d4b2 | ||
|
|
f43e04e15a | ||
|
|
88d72f8c9a | ||
|
|
9826726a72 | ||
|
|
c66d0550e8 | ||
|
|
4aeacfd16e | ||
|
|
58fa63ad88 | ||
|
|
94f944dc9c | ||
|
|
116ddbdd01 | ||
|
|
1c0697b5d4 | ||
|
|
434ca47ea0 | ||
|
|
397ef72b16 | ||
|
|
7ca9245735 | ||
|
|
69856286e8 | ||
|
|
ad43d6a5bc | ||
|
|
1e5004f495 | ||
|
|
253161d3d0 | ||
|
|
ab47e201c7 | ||
|
|
42984fa72a | ||
|
|
e7864a28a1 | ||
|
|
21803607e7 | ||
|
|
5dc776e55f | ||
|
|
72d60f30f7 | ||
|
|
869743a742 | ||
|
|
7b03e07908 | ||
|
|
348f880e15 | ||
|
|
ead597d0fb | ||
|
|
afbf989715 | ||
|
|
01b62a16c3 | ||
|
|
c5eba04517 | ||
|
|
282313ab52 | ||
|
|
d274545e77 | ||
|
|
d3fda37615 | ||
|
|
cbe3092404 | ||
|
|
6dfe3039d0 | ||
|
|
d6009453df | ||
|
|
c81323ef91 | ||
|
|
961c27f1c2 | ||
|
|
fe4a14e6cc | ||
|
|
50848c2f4d | ||
|
|
d32633b3c7 | ||
|
|
b37739eec2 | ||
|
|
28f87dc804 | ||
|
|
41879e41e6 | ||
|
|
fc0a6546a2 | ||
|
|
ffd4280d6c | ||
|
|
db3b955b0f | ||
|
|
5516f65971 | ||
|
|
9471df0a1b | ||
|
|
6d39f64be7 | ||
|
|
b89d0a9a73 | ||
|
|
4bb779d9a5 | ||
|
|
386a5b6362 | ||
|
|
e32a999cd0 | ||
|
|
bfbc6a4bad | ||
|
|
8c9e0e552d | ||
|
|
8aaf9fd83f | ||
|
|
08057720b8 | ||
|
|
bfaa648837 | ||
|
|
d504daef91 | ||
|
|
b8d3ef2f49 | ||
|
|
3bf6320030 | ||
|
|
708b928c73 | ||
|
|
649366ff44 | ||
|
|
e5c9e87fad | ||
|
|
f3d9d707b6 | ||
|
|
090e10730c | ||
|
|
fbc84861c7 | ||
|
|
e763469af8 | ||
|
|
3c0c514e44 | ||
|
|
ed5e2dd332 | ||
|
|
09b7c6f550 | ||
|
|
df315a1f51 | ||
|
|
7ee4bb621c | ||
|
|
24874f4c3c | ||
|
|
c128880033 | ||
|
|
a66e94a0b0 | ||
|
|
56870ed4a8 | ||
|
|
3ac720df47 | ||
|
|
1bc757ad06 | ||
|
|
f72abc6f3d | ||
|
|
5ac88de985 | ||
|
|
0826b367d6 | ||
|
|
329bf861d6 | ||
|
|
9dcd3d18a0 | ||
|
|
db66cd88b6 | ||
|
|
86c205fe43 | ||
|
|
c6414138c7 | ||
|
|
36b355eb82 | ||
|
|
7be9291b13 | ||
|
|
ea9e75039b | ||
|
|
a5fb036011 | ||
|
|
e55506f9db | ||
|
|
50ec1d0445 | ||
|
|
3d5e1d8d91 | ||
|
|
db2128a344 | ||
|
|
21db43db06 | ||
|
|
5009b3029f | ||
|
|
57a029189c | ||
|
|
0cb715bb76 | ||
|
|
7d03823afd | ||
|
|
8e1c9f5042 | ||
|
|
980b7cda8f | ||
|
|
3a72dd5cb6 | ||
|
|
3178243811 | ||
|
|
d30e2f2a4f | ||
|
|
6226dae05c | ||
|
|
9c6a475a6e | ||
|
|
8294d10d5b | ||
|
|
67558bec47 | ||
|
|
84873d4074 | ||
|
|
58a0b28a39 | ||
|
|
b37d3a66cc | ||
|
|
7e495a5e27 | ||
|
|
c41547fd4a | ||
|
|
0d47d41c85 | ||
|
|
41a3a17456 | ||
|
|
cbbafbcca2 | ||
|
|
c75566b374 | ||
|
|
7279f1fcc1 | ||
|
|
d7432f7c10 | ||
|
|
b0a0a153f3 | ||
|
|
024632dbd0 | ||
|
|
0a545a28b9 | ||
|
|
0f2df59998 | ||
|
|
29a7d32f77 | ||
|
|
687a7e9b2f | ||
|
|
09e8782318 | ||
|
|
f2aea02210 | ||
|
|
194f922312 | ||
|
|
fea3c48098 | ||
|
|
c2f57baec2 | ||
|
|
f4a140e126 | ||
|
|
ab506b09fe | ||
|
|
87e1cdeedb | ||
|
|
81a36146ef | ||
|
|
7fa4a68a27 | ||
|
|
f1c5e2ef81 | ||
|
|
b526155cce | ||
|
|
62c3f301e7 | ||
|
|
38cb988809 | ||
|
|
b976ac54c8 | ||
|
|
78026e766f | ||
|
|
b4cd8d21a5 | ||
|
|
7552893311 | ||
|
|
21c896d8f8 | ||
|
|
4b7fe202ec | ||
|
|
9f4519210f | ||
|
|
b0506afa5b | ||
|
|
8cbb379898 | ||
|
|
b226215593 | ||
|
|
19970729a9 | ||
|
|
d2ebfd2833 | ||
|
|
bd782fc828 | ||
|
|
23560e608c | ||
|
|
f1377b560e | ||
|
|
72108684ea | ||
|
|
c6adaaea97 | ||
|
|
91999a38ca | ||
|
|
b34eed125d | ||
|
|
2abe09529a | ||
|
|
9aaaf4dd4b | ||
|
|
cbfbcf7f1b | ||
|
|
c7651dc40d | ||
|
|
eda1c471ad | ||
|
|
c7ef18fbc4 | ||
|
|
901ec918b1 | ||
|
|
6bdae55ee1 | ||
|
|
dfb96e4b7f | ||
|
|
ff2c316b18 | ||
|
|
5be52f71f9 | ||
|
|
42873dd37c | ||
|
|
f93e7d4e3a | ||
|
|
bbcd523967 | ||
|
|
68cbe58d00 | ||
|
|
115bca98f1 | ||
|
|
ed0b34b2fe | ||
|
|
ab34401421 | ||
|
|
eed0c18d65 | ||
|
|
e5a38ce748 | ||
|
|
7d9d9fcf36 | ||
|
|
f0aba6ceb2 | ||
|
|
ab07ee57c6 | ||
|
|
eae3d72a4d | ||
|
|
7b8d826704 | ||
|
|
e7baa42e63 | ||
|
|
2f32833a22 | ||
|
|
f6935a4b4b | ||
|
|
332c9e891b | ||
|
|
b91ee4847f | ||
|
|
625463d871 | ||
|
|
6433a01e07 | ||
|
|
56cc31e8e7 | ||
|
|
3af297aa76 | ||
|
|
996ec59d28 | ||
|
|
95593eeeab | ||
|
|
dad244fb7a | ||
|
|
adb5d27d95 | ||
|
|
8456a8cecb | ||
|
|
b9f66373c1 | ||
|
|
9ac365feef | ||
|
|
43bbd58a44 | ||
|
|
7feffa64f3 | ||
|
|
ea0977abb4 | ||
|
|
4c83dc7c28 | ||
|
|
e10ab1da78 | ||
|
|
1b0e60374b | ||
|
|
3a760fbb44 | ||
|
|
6ef57a2973 | ||
|
|
3e9c7f2e9f | ||
|
|
430598b7a1 | ||
|
|
91611b09b4 | ||
|
|
ecd115851f | ||
|
|
4a1e50fed1 | ||
|
|
d6d037047b | ||
|
|
b5734c2b20 | ||
|
|
723fb7eaac | ||
|
|
63a9acaa19 | ||
|
|
0524f8c677 | ||
|
|
70b62f272e | ||
|
|
f0089b7940 | ||
|
|
4b44280d53 | ||
|
|
f045382d20 | ||
|
|
db3fa1ade7 | ||
|
|
f83950fd75 | ||
|
|
4dd1bf920d | ||
|
|
98755f3621 | ||
|
|
c3a8a044b9 | ||
|
|
15b5ea43a7 | ||
|
|
ec683fc227 | ||
|
|
d4e65eb82a | ||
|
|
10c6601b0a | ||
|
|
73940bc1bd | ||
|
|
9b7fb829f9 | ||
|
|
c51d8c9021 | ||
|
|
d8a6dfe5ce | ||
|
|
5f7cef0b06 | ||
|
|
48ff2ffc68 | ||
|
|
b3b9ccd314 | ||
|
|
e63c7b483b | ||
|
|
f57980b069 | ||
|
|
7006aa0d2a | ||
|
|
8051c1ca99 | ||
|
|
a779592414 | ||
|
|
112215848d |
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -137,7 +137,7 @@ jobs:
|
||||
--build-type "${{ matrix.build_type }}" \
|
||||
manifest
|
||||
|
||||
deploy-hassio-repo:
|
||||
deploy-ha-addon-repo:
|
||||
if: github.repository == 'esphome/esphome' && github.event_name == 'release'
|
||||
runs-on: ubuntu-latest
|
||||
needs: [deploy-docker]
|
||||
@@ -150,5 +150,5 @@ jobs:
|
||||
-u ":$TOKEN" \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github.v3+json" \
|
||||
https://api.github.com/repos/esphome/hassio/actions/workflows/bump-version.yml/dispatches \
|
||||
https://api.github.com/repos/esphome/home-assistant-addon/actions/workflows/bump-version.yml/dispatches \
|
||||
-d "{\"ref\":\"main\",\"inputs\":{\"version\":\"$TAG\"}}"
|
||||
|
||||
@@ -89,9 +89,12 @@ esphome/components/json/* @OttoWinter
|
||||
esphome/components/kalman_combinator/* @Cat-Ion
|
||||
esphome/components/ledc/* @OttoWinter
|
||||
esphome/components/light/* @esphome/core
|
||||
esphome/components/lilygo_t5_47/touchscreen/* @jesserockz
|
||||
esphome/components/lock/* @esphome/core
|
||||
esphome/components/logger/* @esphome/core
|
||||
esphome/components/ltr390/* @sjtrny
|
||||
esphome/components/max7219digit/* @rspaargaren
|
||||
esphome/components/max9611/* @mckaymatthew
|
||||
esphome/components/mcp23008/* @jesserockz
|
||||
esphome/components/mcp23017/* @jesserockz
|
||||
esphome/components/mcp23s08/* @SenexCrenshaw @jesserockz
|
||||
@@ -108,10 +111,12 @@ esphome/components/mdns/* @esphome/core
|
||||
esphome/components/midea/* @dudanov
|
||||
esphome/components/midea_ir/* @dudanov
|
||||
esphome/components/mitsubishi/* @RubyBailey
|
||||
esphome/components/mlx90393/* @functionpointer
|
||||
esphome/components/modbus_controller/* @martgras
|
||||
esphome/components/modbus_controller/binary_sensor/* @martgras
|
||||
esphome/components/modbus_controller/number/* @martgras
|
||||
esphome/components/modbus_controller/output/* @martgras
|
||||
esphome/components/modbus_controller/select/* @martgras @stegm
|
||||
esphome/components/modbus_controller/sensor/* @martgras
|
||||
esphome/components/modbus_controller/switch/* @martgras
|
||||
esphome/components/modbus_controller/text_sensor/* @martgras
|
||||
@@ -138,6 +143,8 @@ esphome/components/psram/* @esphome/core
|
||||
esphome/components/pulse_meter/* @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/qr_code/* @wjtje
|
||||
esphome/components/radon_eye_ble/* @jeffeb3
|
||||
esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
|
||||
@@ -102,7 +102,7 @@ RUN \
|
||||
ARG BUILD_VERSION=dev
|
||||
|
||||
# Copy root filesystem
|
||||
COPY docker/hassio-rootfs/ /
|
||||
COPY docker/ha-addon-rootfs/ /
|
||||
|
||||
# First install requirements to leverage caching when requirements don't change
|
||||
COPY requirements.txt requirements_optional.txt docker/platformio_install_deps.py platformio.ini /
|
||||
|
||||
@@ -10,7 +10,7 @@ server {
|
||||
ssl_certificate_key /ssl/%%keyfile%%;
|
||||
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "";
|
||||
proxy_set_header X-HA-Ingress "";
|
||||
|
||||
# Redirect http requests to https on the same port.
|
||||
# https://rageagainstshell.com/2016/11/redirect-http-to-https-on-the-same-port-in-nginx/
|
||||
@@ -4,7 +4,7 @@ server {
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
# Clear Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "";
|
||||
proxy_set_header X-HA-Ingress "";
|
||||
|
||||
location / {
|
||||
proxy_pass http://esphome;
|
||||
@@ -3,8 +3,8 @@ server {
|
||||
|
||||
include /etc/nginx/includes/server_params.conf;
|
||||
include /etc/nginx/includes/proxy_params.conf;
|
||||
# Set Hass.io Ingress header
|
||||
proxy_set_header X-Hassio-Ingress "YES";
|
||||
# Set Home Assistant Ingress header
|
||||
proxy_set_header X-HA-Ingress "YES";
|
||||
|
||||
location / {
|
||||
# Only allow from Hass.io supervisor
|
||||
@@ -4,7 +4,7 @@
|
||||
# Runs the ESPHome dashboard
|
||||
# ==============================================================================
|
||||
|
||||
export ESPHOME_IS_HASSIO=true
|
||||
export ESPHOME_IS_HA_ADDON=true
|
||||
|
||||
if bashio::config.true 'leave_front_door_open'; then
|
||||
export DISABLE_HA_AUTHENTICATION=true
|
||||
@@ -32,4 +32,4 @@ export PLATFORMIO_CACHE_DIR="${pio_cache_base}/cache"
|
||||
export PLATFORMIO_GLOBALLIB_DIR=/piolibs
|
||||
|
||||
bashio::log.info "Starting ESPHome dashboard..."
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --hassio
|
||||
exec esphome dashboard /config/esphome --socket /var/run/esphome.sock --ha-addon
|
||||
@@ -661,7 +661,7 @@ def parse_args(argv):
|
||||
"--open-ui", help="Open the dashboard UI in a browser.", action="store_true"
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--hassio", help=argparse.SUPPRESS, action="store_true"
|
||||
"--ha-addon", help=argparse.SUPPRESS, action="store_true"
|
||||
)
|
||||
parser_dashboard.add_argument(
|
||||
"--socket", help="Make the dashboard serve under a unix socket", type=str
|
||||
|
||||
@@ -63,6 +63,7 @@ from esphome.cpp_types import ( # noqa
|
||||
uint32,
|
||||
uint64,
|
||||
int32,
|
||||
int64,
|
||||
const_char_ptr,
|
||||
NAN,
|
||||
esphome_ns,
|
||||
@@ -81,4 +82,5 @@ from esphome.cpp_types import ( # noqa
|
||||
InternalGPIOPin,
|
||||
gpio_Flags,
|
||||
EntityCategory,
|
||||
Parented,
|
||||
)
|
||||
|
||||
@@ -76,6 +76,8 @@ async def to_code(config):
|
||||
pos = 0
|
||||
for frameIndex in range(frames):
|
||||
image.seek(frameIndex)
|
||||
if CONF_RESIZE in config:
|
||||
image.thumbnail(config[CONF_RESIZE])
|
||||
frame = image.convert("RGB")
|
||||
if CONF_RESIZE in config:
|
||||
frame = frame.resize([width, height])
|
||||
|
||||
@@ -41,6 +41,7 @@ service APIConnection {
|
||||
rpc number_command (NumberCommandRequest) returns (void) {}
|
||||
rpc select_command (SelectCommandRequest) returns (void) {}
|
||||
rpc button_command (ButtonCommandRequest) returns (void) {}
|
||||
rpc lock_command (LockCommandRequest) returns (void) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -528,6 +529,7 @@ message ListEntitiesSwitchResponse {
|
||||
bool assumed_state = 6;
|
||||
bool disabled_by_default = 7;
|
||||
EntityCategory entity_category = 8;
|
||||
string device_class = 9;
|
||||
}
|
||||
message SwitchStateResponse {
|
||||
option (id) = 26;
|
||||
@@ -956,6 +958,63 @@ message SelectCommandRequest {
|
||||
string state = 2;
|
||||
}
|
||||
|
||||
|
||||
// ==================== LOCK ====================
|
||||
enum LockState {
|
||||
LOCK_STATE_NONE = 0;
|
||||
LOCK_STATE_LOCKED = 1;
|
||||
LOCK_STATE_UNLOCKED = 2;
|
||||
LOCK_STATE_JAMMED = 3;
|
||||
LOCK_STATE_LOCKING = 4;
|
||||
LOCK_STATE_UNLOCKING = 5;
|
||||
}
|
||||
enum LockCommand {
|
||||
LOCK_UNLOCK = 0;
|
||||
LOCK_LOCK = 1;
|
||||
LOCK_OPEN = 2;
|
||||
}
|
||||
message ListEntitiesLockResponse {
|
||||
option (id) = 58;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
|
||||
string object_id = 1;
|
||||
fixed32 key = 2;
|
||||
string name = 3;
|
||||
string unique_id = 4;
|
||||
|
||||
string icon = 5;
|
||||
bool disabled_by_default = 6;
|
||||
EntityCategory entity_category = 7;
|
||||
bool assumed_state = 8;
|
||||
|
||||
bool supports_open = 9;
|
||||
bool requires_code = 10;
|
||||
|
||||
# Not yet implemented:
|
||||
string code_format = 11;
|
||||
}
|
||||
message LockStateResponse {
|
||||
option (id) = 59;
|
||||
option (source) = SOURCE_SERVER;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
LockState state = 2;
|
||||
}
|
||||
message LockCommandRequest {
|
||||
option (id) = 60;
|
||||
option (source) = SOURCE_CLIENT;
|
||||
option (ifdef) = "USE_LOCK";
|
||||
option (no_delay) = true;
|
||||
fixed32 key = 1;
|
||||
LockCommand command = 2;
|
||||
|
||||
# Not yet implemented:
|
||||
bool has_code = 3;
|
||||
string code = 4;
|
||||
}
|
||||
|
||||
// ==================== BUTTON ====================
|
||||
message ListEntitiesButtonResponse {
|
||||
option (id) = 61;
|
||||
@@ -980,3 +1039,4 @@ message ButtonCommandRequest {
|
||||
|
||||
fixed32 key = 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -462,6 +462,7 @@ bool APIConnection::send_switch_info(switch_::Switch *a_switch) {
|
||||
msg.assumed_state = a_switch->assumed_state();
|
||||
msg.disabled_by_default = a_switch->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(a_switch->get_entity_category());
|
||||
msg.device_class = a_switch->get_device_class();
|
||||
return this->send_list_entities_switch_response(msg);
|
||||
}
|
||||
void APIConnection::switch_command(const SwitchCommandRequest &msg) {
|
||||
@@ -700,6 +701,49 @@ void APIConnection::button_command(const ButtonCommandRequest &msg) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
bool APIConnection::send_lock_state(lock::Lock *a_lock, lock::LockState state) {
|
||||
if (!this->state_subscription_)
|
||||
return false;
|
||||
|
||||
LockStateResponse resp{};
|
||||
resp.key = a_lock->get_object_id_hash();
|
||||
resp.state = static_cast<enums::LockState>(state);
|
||||
return this->send_lock_state_response(resp);
|
||||
}
|
||||
bool APIConnection::send_lock_info(lock::Lock *a_lock) {
|
||||
ListEntitiesLockResponse msg;
|
||||
msg.key = a_lock->get_object_id_hash();
|
||||
msg.object_id = a_lock->get_object_id();
|
||||
msg.name = a_lock->get_name();
|
||||
msg.unique_id = get_default_unique_id("lock", a_lock);
|
||||
msg.icon = a_lock->get_icon();
|
||||
msg.assumed_state = a_lock->traits.get_assumed_state();
|
||||
msg.disabled_by_default = a_lock->is_disabled_by_default();
|
||||
msg.entity_category = static_cast<enums::EntityCategory>(a_lock->get_entity_category());
|
||||
msg.supports_open = a_lock->traits.get_supports_open();
|
||||
msg.requires_code = a_lock->traits.get_requires_code();
|
||||
return this->send_list_entities_lock_response(msg);
|
||||
}
|
||||
void APIConnection::lock_command(const LockCommandRequest &msg) {
|
||||
lock::Lock *a_lock = App.get_lock_by_key(msg.key);
|
||||
if (a_lock == nullptr)
|
||||
return;
|
||||
|
||||
switch (msg.command) {
|
||||
case enums::LOCK_UNLOCK:
|
||||
a_lock->unlock();
|
||||
break;
|
||||
case enums::LOCK_LOCK:
|
||||
a_lock->lock();
|
||||
break;
|
||||
case enums::LOCK_OPEN:
|
||||
a_lock->open();
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
void APIConnection::send_camera_state(std::shared_ptr<esp32_camera::CameraImage> image) {
|
||||
if (!this->state_subscription_)
|
||||
|
||||
@@ -77,6 +77,11 @@ class APIConnection : public APIServerConnection {
|
||||
#ifdef USE_BUTTON
|
||||
bool send_button_info(button::Button *button);
|
||||
void button_command(const ButtonCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state(lock::Lock *a_lock, lock::LockState state);
|
||||
bool send_lock_info(lock::Lock *a_lock);
|
||||
void lock_command(const LockCommandRequest &msg) override;
|
||||
#endif
|
||||
bool send_log_message(int level, const char *tag, const char *line);
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call) {
|
||||
|
||||
@@ -278,6 +278,36 @@ template<> const char *proto_enum_to_string<enums::NumberMode>(enums::NumberMode
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LockState>(enums::LockState value) {
|
||||
switch (value) {
|
||||
case enums::LOCK_STATE_NONE:
|
||||
return "LOCK_STATE_NONE";
|
||||
case enums::LOCK_STATE_LOCKED:
|
||||
return "LOCK_STATE_LOCKED";
|
||||
case enums::LOCK_STATE_UNLOCKED:
|
||||
return "LOCK_STATE_UNLOCKED";
|
||||
case enums::LOCK_STATE_JAMMED:
|
||||
return "LOCK_STATE_JAMMED";
|
||||
case enums::LOCK_STATE_LOCKING:
|
||||
return "LOCK_STATE_LOCKING";
|
||||
case enums::LOCK_STATE_UNLOCKING:
|
||||
return "LOCK_STATE_UNLOCKING";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
template<> const char *proto_enum_to_string<enums::LockCommand>(enums::LockCommand value) {
|
||||
switch (value) {
|
||||
case enums::LOCK_UNLOCK:
|
||||
return "LOCK_UNLOCK";
|
||||
case enums::LOCK_LOCK:
|
||||
return "LOCK_LOCK";
|
||||
case enums::LOCK_OPEN:
|
||||
return "LOCK_OPEN";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
bool HelloRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
@@ -2147,6 +2177,10 @@ bool ListEntitiesSwitchResponse::decode_length(uint32_t field_id, ProtoLengthDel
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->device_class = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -2170,6 +2204,7 @@ void ListEntitiesSwitchResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_bool(6, this->assumed_state);
|
||||
buffer.encode_bool(7, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(8, this->entity_category);
|
||||
buffer.encode_string(9, this->device_class);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesSwitchResponse::dump_to(std::string &out) const {
|
||||
@@ -2207,6 +2242,10 @@ void ListEntitiesSwitchResponse::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(" device_class: ");
|
||||
out.append("'").append(this->device_class).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
@@ -4186,6 +4225,234 @@ void SelectCommandRequest::dump_to(std::string &out) const {
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesLockResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
this->disabled_by_default = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 7: {
|
||||
this->entity_category = value.as_enum<enums::EntityCategory>();
|
||||
return true;
|
||||
}
|
||||
case 8: {
|
||||
this->assumed_state = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 9: {
|
||||
this->supports_open = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
case 10: {
|
||||
this->requires_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesLockResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->object_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->name = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 4: {
|
||||
this->unique_id = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 5: {
|
||||
this->icon = value.as_string();
|
||||
return true;
|
||||
}
|
||||
case 11: {
|
||||
this->code_format = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool ListEntitiesLockResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void ListEntitiesLockResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_string(1, this->object_id);
|
||||
buffer.encode_fixed32(2, this->key);
|
||||
buffer.encode_string(3, this->name);
|
||||
buffer.encode_string(4, this->unique_id);
|
||||
buffer.encode_string(5, this->icon);
|
||||
buffer.encode_bool(6, this->disabled_by_default);
|
||||
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
|
||||
buffer.encode_bool(8, this->assumed_state);
|
||||
buffer.encode_bool(9, this->supports_open);
|
||||
buffer.encode_bool(10, this->requires_code);
|
||||
buffer.encode_string(11, this->code_format);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesLockResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesLockResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" name: ");
|
||||
out.append("'").append(this->name).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" unique_id: ");
|
||||
out.append("'").append(this->unique_id).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" icon: ");
|
||||
out.append("'").append(this->icon).append("'");
|
||||
out.append("\n");
|
||||
|
||||
out.append(" disabled_by_default: ");
|
||||
out.append(YESNO(this->disabled_by_default));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" entity_category: ");
|
||||
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" assumed_state: ");
|
||||
out.append(YESNO(this->assumed_state));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" supports_open: ");
|
||||
out.append(YESNO(this->supports_open));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" requires_code: ");
|
||||
out.append(YESNO(this->requires_code));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" code_format: ");
|
||||
out.append("'").append(this->code_format).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool LockStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->state = value.as_enum<enums::LockState>();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void LockStateResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::LockState>(2, this->state);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void LockStateResponse::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("LockStateResponse {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" state: ");
|
||||
out.append(proto_enum_to_string<enums::LockState>(this->state));
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool LockCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 2: {
|
||||
this->command = value.as_enum<enums::LockCommand>();
|
||||
return true;
|
||||
}
|
||||
case 3: {
|
||||
this->has_code = value.as_bool();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockCommandRequest::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
|
||||
switch (field_id) {
|
||||
case 4: {
|
||||
this->code = value.as_string();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool LockCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
switch (field_id) {
|
||||
case 1: {
|
||||
this->key = value.as_fixed32();
|
||||
return true;
|
||||
}
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
void LockCommandRequest::encode(ProtoWriteBuffer buffer) const {
|
||||
buffer.encode_fixed32(1, this->key);
|
||||
buffer.encode_enum<enums::LockCommand>(2, this->command);
|
||||
buffer.encode_bool(3, this->has_code);
|
||||
buffer.encode_string(4, this->code);
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void LockCommandRequest::dump_to(std::string &out) const {
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("LockCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
out.append(buffer);
|
||||
out.append("\n");
|
||||
|
||||
out.append(" command: ");
|
||||
out.append(proto_enum_to_string<enums::LockCommand>(this->command));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" has_code: ");
|
||||
out.append(YESNO(this->has_code));
|
||||
out.append("\n");
|
||||
|
||||
out.append(" code: ");
|
||||
out.append("'").append(this->code).append("'");
|
||||
out.append("\n");
|
||||
out.append("}");
|
||||
}
|
||||
#endif
|
||||
bool ListEntitiesButtonResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
|
||||
switch (field_id) {
|
||||
case 6: {
|
||||
@@ -4248,7 +4515,7 @@ void ListEntitiesButtonResponse::encode(ProtoWriteBuffer buffer) const {
|
||||
}
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ListEntitiesButtonResponse::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ListEntitiesButtonResponse {\n");
|
||||
out.append(" object_id: ");
|
||||
out.append("'").append(this->object_id).append("'");
|
||||
@@ -4298,7 +4565,7 @@ bool ButtonCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
|
||||
void ButtonCommandRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_fixed32(1, this->key); }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void ButtonCommandRequest::dump_to(std::string &out) const {
|
||||
char buffer[64];
|
||||
__attribute__((unused)) char buffer[64];
|
||||
out.append("ButtonCommandRequest {\n");
|
||||
out.append(" key: ");
|
||||
sprintf(buffer, "%u", this->key);
|
||||
|
||||
@@ -128,6 +128,19 @@ enum NumberMode : uint32_t {
|
||||
NUMBER_MODE_BOX = 1,
|
||||
NUMBER_MODE_SLIDER = 2,
|
||||
};
|
||||
enum LockState : uint32_t {
|
||||
LOCK_STATE_NONE = 0,
|
||||
LOCK_STATE_LOCKED = 1,
|
||||
LOCK_STATE_UNLOCKED = 2,
|
||||
LOCK_STATE_JAMMED = 3,
|
||||
LOCK_STATE_LOCKING = 4,
|
||||
LOCK_STATE_UNLOCKING = 5,
|
||||
};
|
||||
enum LockCommand : uint32_t {
|
||||
LOCK_UNLOCK = 0,
|
||||
LOCK_LOCK = 1,
|
||||
LOCK_OPEN = 2,
|
||||
};
|
||||
|
||||
} // namespace enums
|
||||
|
||||
@@ -567,6 +580,7 @@ class ListEntitiesSwitchResponse : public ProtoMessage {
|
||||
bool assumed_state{false};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
std::string device_class{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
@@ -1049,6 +1063,58 @@ class SelectCommandRequest : public ProtoMessage {
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
};
|
||||
class ListEntitiesLockResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
uint32_t key{0};
|
||||
std::string name{};
|
||||
std::string unique_id{};
|
||||
std::string icon{};
|
||||
bool disabled_by_default{false};
|
||||
enums::EntityCategory entity_category{};
|
||||
bool assumed_state{false};
|
||||
bool supports_open{false};
|
||||
bool requires_code{false};
|
||||
std::string code_format{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LockStateResponse : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
enums::LockState state{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class LockCommandRequest : public ProtoMessage {
|
||||
public:
|
||||
uint32_t key{0};
|
||||
enums::LockCommand command{};
|
||||
bool has_code{false};
|
||||
std::string code{};
|
||||
void encode(ProtoWriteBuffer buffer) const override;
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void dump_to(std::string &out) const override;
|
||||
#endif
|
||||
|
||||
protected:
|
||||
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
|
||||
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
|
||||
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
|
||||
};
|
||||
class ListEntitiesButtonResponse : public ProtoMessage {
|
||||
public:
|
||||
std::string object_id{};
|
||||
|
||||
@@ -282,6 +282,24 @@ bool APIServerConnectionBase::send_select_state_response(const SelectStateRespon
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool APIServerConnectionBase::send_list_entities_lock_response(const ListEntitiesLockResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_list_entities_lock_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<ListEntitiesLockResponse>(msg, 58);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool APIServerConnectionBase::send_lock_state_response(const LockStateResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "send_lock_state_response: %s", msg.dump().c_str());
|
||||
#endif
|
||||
return this->send_message_<LockStateResponse>(msg, 59);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool APIServerConnectionBase::send_list_entities_button_response(const ListEntitiesButtonResponse &msg) {
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
@@ -523,6 +541,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
|
||||
ESP_LOGVV(TAG, "on_select_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_select_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case 60: {
|
||||
#ifdef USE_LOCK
|
||||
LockCommandRequest msg;
|
||||
msg.decode(msg_data, msg_size);
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
ESP_LOGVV(TAG, "on_lock_command_request: %s", msg.dump().c_str());
|
||||
#endif
|
||||
this->on_lock_command_request(msg);
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
@@ -771,6 +800,19 @@ void APIServerConnection::on_button_command_request(const ButtonCommandRequest &
|
||||
this->button_command(msg);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg) {
|
||||
if (!this->is_connection_setup()) {
|
||||
this->on_no_setup_connection();
|
||||
return;
|
||||
}
|
||||
if (!this->is_authenticated()) {
|
||||
this->on_unauthenticated_access();
|
||||
return;
|
||||
}
|
||||
this->lock_command(msg);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
@@ -130,6 +130,15 @@ class APIServerConnectionBase : public ProtoService {
|
||||
#ifdef USE_SELECT
|
||||
virtual void on_select_command_request(const SelectCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_list_entities_lock_response(const ListEntitiesLockResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool send_lock_state_response(const LockStateResponse &msg);
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual void on_lock_command_request(const LockCommandRequest &value){};
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool send_list_entities_button_response(const ListEntitiesButtonResponse &msg);
|
||||
#endif
|
||||
@@ -180,6 +189,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
virtual void button_command(const ButtonCommandRequest &msg) = 0;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual void lock_command(const LockCommandRequest &msg) = 0;
|
||||
#endif
|
||||
protected:
|
||||
void on_hello_request(const HelloRequest &msg) override;
|
||||
@@ -221,6 +233,9 @@ class APIServerConnection : public APIServerConnectionBase {
|
||||
#ifdef USE_BUTTON
|
||||
void on_button_command_request(const ButtonCommandRequest &msg) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_command_request(const LockCommandRequest &msg) override;
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
@@ -263,6 +263,15 @@ void APIServer::on_select_update(select::Select *obj, const std::string &state)
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void APIServer::on_lock_update(lock::Lock *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
for (auto &c : this->clients_)
|
||||
c->send_lock_state(obj, obj->state);
|
||||
}
|
||||
#endif
|
||||
|
||||
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
|
||||
void APIServer::set_port(uint16_t port) { this->port_ = port; }
|
||||
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -66,6 +66,9 @@ class APIServer : public Component, public Controller {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
void on_select_update(select::Select *obj, const std::string &state) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
#endif
|
||||
void send_homeassistant_service_call(const HomeassistantServiceResponse &call);
|
||||
void register_user_service(UserServiceDescriptor *descriptor) { this->user_services_.push_back(descriptor); }
|
||||
|
||||
@@ -35,6 +35,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
|
||||
return this->client_->send_text_sensor_info(text_sensor);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); }
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||
|
||||
@@ -48,6 +48,9 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool on_lock(lock::Lock *a_lock) override;
|
||||
#endif
|
||||
bool on_end() override;
|
||||
|
||||
|
||||
@@ -47,6 +47,9 @@ bool InitialStateIterator::on_select(select::Select *select) {
|
||||
return this->client_->send_select_state(select, select->state);
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool InitialStateIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_state(a_lock, a_lock->state); }
|
||||
#endif
|
||||
InitialStateIterator::InitialStateIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
|
||||
|
||||
@@ -45,6 +45,9 @@ class InitialStateIterator : public ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
bool on_select(select::Select *select) override;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool on_lock(lock::Lock *a_lock) override;
|
||||
#endif
|
||||
protected:
|
||||
APIConnection *client_;
|
||||
|
||||
@@ -212,6 +212,21 @@ void ComponentIterator::advance() {
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
case IteratorState::LOCK:
|
||||
if (this->at_ >= App.get_locks().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *a_lock = App.get_locks()[this->at_];
|
||||
if (a_lock->is_internal()) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
success = this->on_lock(a_lock);
|
||||
}
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case IteratorState::MAX:
|
||||
if (this->on_end()) {
|
||||
|
||||
@@ -56,6 +56,9 @@ class ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
virtual bool on_select(select::Select *select) = 0;
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual bool on_lock(lock::Lock *a_lock) = 0;
|
||||
#endif
|
||||
virtual bool on_end();
|
||||
|
||||
@@ -99,6 +102,9 @@ class ComponentIterator {
|
||||
#endif
|
||||
#ifdef USE_SELECT
|
||||
SELECT,
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
LOCK,
|
||||
#endif
|
||||
MAX,
|
||||
} state_{IteratorState::NONE};
|
||||
|
||||
@@ -395,15 +395,19 @@ BLEDescriptor *BLECharacteristic::get_descriptor(uint16_t uuid) {
|
||||
return this->get_descriptor(espbt::ESPBTUUID::from_uint16(uuid));
|
||||
}
|
||||
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type) {
|
||||
auto *client = this->service->client;
|
||||
auto status = esp_ble_gattc_write_char(client->gattc_if, client->conn_id, this->handle, new_val_size, new_val,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
write_type, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending write value to BLE gattc server, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void BLECharacteristic::write_value(uint8_t *new_val, int16_t new_val_size) {
|
||||
write_value(new_val, new_val_size, ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -60,6 +60,7 @@ class BLECharacteristic {
|
||||
BLEDescriptor *get_descriptor(espbt::ESPBTUUID uuid);
|
||||
BLEDescriptor *get_descriptor(uint16_t uuid);
|
||||
void write_value(uint8_t *new_val, int16_t new_val_size);
|
||||
void write_value(uint8_t *new_val, int16_t new_val_size, esp_gatt_write_type_t write_type);
|
||||
BLEService *service;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,13 +1,14 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output, ble_client, esp32_ble_tracker
|
||||
from esphome.components import ble_client, esp32_ble_tracker, output
|
||||
from esphome.const import CONF_ID, CONF_SERVICE_UUID
|
||||
from .. import ble_client_ns
|
||||
|
||||
from .. import ble_client_ns
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
CONF_CHARACTERISTIC_UUID = "characteristic_uuid"
|
||||
CONF_REQUIRE_RESPONSE = "require_response"
|
||||
|
||||
BLEBinaryOutput = ble_client_ns.class_(
|
||||
"BLEBinaryOutput", output.BinaryOutput, ble_client.BLEClientNode, cg.Component
|
||||
@@ -19,6 +20,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_ID): cv.declare_id(BLEBinaryOutput),
|
||||
cv.Required(CONF_SERVICE_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Required(CONF_CHARACTERISTIC_UUID): esp32_ble_tracker.bt_uuid,
|
||||
cv.Optional(CONF_REQUIRE_RESPONSE, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -61,7 +63,7 @@ def to_code(config):
|
||||
config[CONF_CHARACTERISTIC_UUID]
|
||||
)
|
||||
cg.add(var.set_char_uuid128(uuid128))
|
||||
|
||||
cg.add(var.set_require_response(config[CONF_REQUIRE_RESPONSE]))
|
||||
yield output.register_output(var, config)
|
||||
yield ble_client.register_ble_node(var, config)
|
||||
yield cg.register_component(var, config)
|
||||
|
||||
@@ -63,7 +63,11 @@ void BLEBinaryOutput::write_state(bool state) {
|
||||
|
||||
uint8_t state_as_uint = (uint8_t) state;
|
||||
ESP_LOGV(TAG, "[%s] Write State: %d", this->char_uuid_.to_string().c_str(), state_as_uint);
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint));
|
||||
if (this->require_response_) {
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_RSP);
|
||||
} else {
|
||||
chr->write_value(&state_as_uint, sizeof(state_as_uint), ESP_GATT_WRITE_TYPE_NO_RSP);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ble_client
|
||||
|
||||
@@ -25,9 +25,11 @@ class BLEBinaryOutput : public output::BinaryOutput, public BLEClientNode, publi
|
||||
void set_char_uuid128(uint8_t *uuid) { this->char_uuid_ = espbt::ESPBTUUID::from_raw(uuid); }
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
void set_require_response(bool response) { this->require_response_ = response; }
|
||||
|
||||
protected:
|
||||
void write_state(bool state) override;
|
||||
bool require_response_;
|
||||
espbt::ESPBTUUID service_uuid_;
|
||||
espbt::ESPBTUUID char_uuid_;
|
||||
espbt::ClientState client_state_;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor, esp32_ble_tracker
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
|
||||
@@ -14,18 +13,13 @@ BLEScanner = ble_scanner_ns.class_(
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BLEScanner),
|
||||
}
|
||||
)
|
||||
text_sensor.text_sensor_schema(klass=BLEScanner)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ID, CONF_ICON
|
||||
from . import BME680BSECComponent, CONF_BME680_BSEC_ID
|
||||
|
||||
DEPENDENCIES = ["bme680_bsec"]
|
||||
@@ -14,11 +13,8 @@ TYPES = [CONF_IAQ_ACCURACY]
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BME680_BSEC_ID): cv.use_id(BME680BSECComponent),
|
||||
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_ACCURACY): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_IAQ_ACCURACY): text_sensor.text_sensor_schema(
|
||||
icon=ICON_ACCURACY
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -27,8 +23,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, conf)
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
|
||||
|
||||
|
||||
|
||||
@@ -14,10 +14,14 @@ CONF_BIT_RATE = "bit_rate"
|
||||
CONF_ON_FRAME = "on_frame"
|
||||
|
||||
|
||||
def validate_id(id_value, id_ext):
|
||||
if not id_ext:
|
||||
if id_value > 0x7FF:
|
||||
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
||||
def validate_id(config):
|
||||
if CONF_CAN_ID in config:
|
||||
id_value = config[CONF_CAN_ID]
|
||||
id_ext = config[CONF_USE_EXTENDED_ID]
|
||||
if not id_ext:
|
||||
if id_value > 0x7FF:
|
||||
raise cv.Invalid("Standard IDs must be 11 Bit (0x000-0x7ff / 0-2047)")
|
||||
return config
|
||||
|
||||
|
||||
def validate_raw_data(value):
|
||||
@@ -67,23 +71,18 @@ CANBUS_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Required(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ON_FRAME): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CanbusTrigger),
|
||||
cv.GenerateID(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
}
|
||||
},
|
||||
validate_id,
|
||||
),
|
||||
}
|
||||
},
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
CANBUS_SCHEMA.add_extra(validate_id)
|
||||
|
||||
|
||||
async def setup_canbus_core_(var, config):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_can_id([config[CONF_CAN_ID]]))
|
||||
cg.add(var.set_use_extended_id([config[CONF_USE_EXTENDED_ID]]))
|
||||
@@ -92,7 +91,6 @@ async def setup_canbus_core_(var, config):
|
||||
for conf in config.get(CONF_ON_FRAME, []):
|
||||
can_id = conf[CONF_CAN_ID]
|
||||
ext_id = conf[CONF_USE_EXTENDED_ID]
|
||||
validate_id(can_id, ext_id)
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var, can_id, ext_id)
|
||||
await cg.register_component(trigger, conf)
|
||||
await automation.build_automation(
|
||||
@@ -117,11 +115,11 @@ async def register_canbus(var, config):
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
},
|
||||
validate_id,
|
||||
key=CONF_DATA,
|
||||
),
|
||||
)
|
||||
async def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
validate_id(config[CONF_CAN_ID], config[CONF_USE_EXTENDED_ID])
|
||||
var = cg.new_Pvariable(action_id, template_arg)
|
||||
await cg.register_parented(var, config[CONF_CANBUS_ID])
|
||||
|
||||
|
||||
@@ -2,7 +2,6 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor, text_sensor
|
||||
from esphome.const import (
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
ICON_RADIATOR,
|
||||
ICON_RESTART,
|
||||
@@ -47,11 +46,8 @@ CONFIG_SCHEMA = (
|
||||
device_class=DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_RESTART): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
|
||||
icon=ICON_RESTART
|
||||
),
|
||||
cv.Optional(CONF_BASELINE): cv.hex_uint16_t,
|
||||
cv.Optional(CONF_TEMPERATURE): cv.use_id(sensor.Sensor),
|
||||
@@ -74,8 +70,7 @@ async def to_code(config):
|
||||
cg.add(var.set_tvoc(sens))
|
||||
|
||||
if CONF_VERSION in config:
|
||||
sens = cg.new_Pvariable(config[CONF_VERSION][CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, config[CONF_VERSION])
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_VERSION])
|
||||
cg.add(var.set_version(sens))
|
||||
|
||||
if CONF_BASELINE in config:
|
||||
|
||||
@@ -7,6 +7,7 @@ from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_STATE,
|
||||
CONF_ON_OPEN,
|
||||
CONF_POSITION,
|
||||
CONF_POSITION_COMMAND_TOPIC,
|
||||
CONF_POSITION_STATE_TOPIC,
|
||||
@@ -74,7 +75,6 @@ CoverClosedTrigger = cover_ns.class_(
|
||||
"CoverClosedTrigger", automation.Trigger.template()
|
||||
)
|
||||
|
||||
CONF_ON_OPEN = "on_open"
|
||||
CONF_ON_CLOSED = "on_closed"
|
||||
|
||||
COVER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
|
||||
@@ -11,11 +11,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(): cv.declare_id(CustomTextSensorConstructor),
|
||||
cv.Required(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Required(CONF_TEXT_SENSORS): cv.ensure_list(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
)
|
||||
text_sensor.text_sensor_schema()
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ICON, CONF_ID, CONF_STATUS
|
||||
from esphome.const import CONF_STATUS
|
||||
from . import DalyBmsComponent, CONF_BMS_DALY_ID
|
||||
|
||||
ICON_CAR_BATTERY = "mdi:car-battery"
|
||||
@@ -14,11 +14,8 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_BMS_DALY_ID): cv.use_id(DalyBmsComponent),
|
||||
cv.Optional(CONF_STATUS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_CAR_BATTERY): cv.icon,
|
||||
}
|
||||
cv.Optional(CONF_STATUS): text_sensor.text_sensor_schema(
|
||||
icon=ICON_CAR_BATTERY
|
||||
),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
@@ -28,8 +25,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def setup_conf(config, key, hub):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
sens = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, conf)
|
||||
sens = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}_text_sensor")(sens))
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import sensor, text_sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_DEVICE,
|
||||
@@ -8,16 +7,12 @@ from esphome.const import (
|
||||
CONF_FRAGMENTATION,
|
||||
CONF_BLOCK,
|
||||
CONF_LOOP_TIME,
|
||||
UNIT_MILLISECOND,
|
||||
UNIT_PERCENT,
|
||||
UNIT_BYTES,
|
||||
ICON_COUNTER,
|
||||
ICON_TIMER,
|
||||
)
|
||||
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
DEPENDENCIES = ["logger"]
|
||||
|
||||
CONF_DEBUG_ID = "debug_id"
|
||||
debug_ns = cg.esphome_ns.namespace("debug")
|
||||
DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
|
||||
|
||||
@@ -25,18 +20,20 @@ DebugComponent = debug_ns.class_("DebugComponent", cg.PollingComponent)
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DebugComponent),
|
||||
cv.Optional(CONF_DEVICE): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{cv.GenerateID(): cv.declare_id(text_sensor.TextSensor)}
|
||||
cv.Optional(CONF_DEVICE): cv.invalid(
|
||||
"The 'device' option has been moved to the 'debug' text_sensor component"
|
||||
),
|
||||
cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
|
||||
cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
|
||||
cv.Optional(CONF_FRAGMENTATION): cv.All(
|
||||
cv.only_on_esp8266,
|
||||
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
|
||||
sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1),
|
||||
cv.Optional(CONF_FREE): cv.invalid(
|
||||
"The 'free' option has been moved to the 'debug' sensor component"
|
||||
),
|
||||
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(
|
||||
UNIT_MILLISECOND, ICON_TIMER, 1
|
||||
cv.Optional(CONF_BLOCK): cv.invalid(
|
||||
"The 'block' option has been moved to the 'debug' sensor component"
|
||||
),
|
||||
cv.Optional(CONF_FRAGMENTATION): cv.invalid(
|
||||
"The 'fragmentation' option has been moved to the 'debug' sensor component"
|
||||
),
|
||||
cv.Optional(CONF_LOOP_TIME): cv.invalid(
|
||||
"The 'loop_time' option has been moved to the 'debug' sensor component"
|
||||
),
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
@@ -45,24 +42,3 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
if CONF_DEVICE in config:
|
||||
sens = cg.new_Pvariable(config[CONF_DEVICE][CONF_ID])
|
||||
await text_sensor.register_text_sensor(sens, config[CONF_DEVICE])
|
||||
cg.add(var.set_device_info_sensor(sens))
|
||||
|
||||
if CONF_FREE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FREE])
|
||||
cg.add(var.set_free_sensor(sens))
|
||||
|
||||
if CONF_BLOCK in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BLOCK])
|
||||
cg.add(var.set_block_sensor(sens))
|
||||
|
||||
if CONF_FRAGMENTATION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FRAGMENTATION])
|
||||
cg.add(var.set_fragmentation_sensor(sens))
|
||||
|
||||
if CONF_LOOP_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_LOOP_TIME])
|
||||
cg.add(var.set_loop_time_sensor(sens))
|
||||
|
||||
@@ -47,12 +47,16 @@ void DebugComponent::dump_config() {
|
||||
#endif
|
||||
|
||||
ESP_LOGCONFIG(TAG, "Debug component:");
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
LOG_TEXT_SENSOR(" ", "Device info", this->device_info_);
|
||||
#endif // USE_TEXT_SENSOR
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Free space on heap", this->free_sensor_);
|
||||
LOG_SENSOR(" ", "Largest free heap block", this->block_sensor_);
|
||||
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
LOG_SENSOR(" ", "Heap fragmentation", this->fragmentation_sensor_);
|
||||
#endif
|
||||
#endif // defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
#endif // USE_SENSOR
|
||||
|
||||
ESP_LOGD(TAG, "ESPHome version %s", ESPHOME_VERSION);
|
||||
device_info += ESPHOME_VERSION;
|
||||
@@ -268,11 +272,13 @@ void DebugComponent::dump_config() {
|
||||
device_info += ESP.getResetInfo().c_str();
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
if (this->device_info_ != nullptr) {
|
||||
if (device_info.length() > 255)
|
||||
device_info.resize(255);
|
||||
this->device_info_->publish_state(device_info);
|
||||
}
|
||||
#endif // USE_TEXT_SENSOR
|
||||
}
|
||||
|
||||
void DebugComponent::loop() {
|
||||
@@ -284,6 +290,7 @@ void DebugComponent::loop() {
|
||||
this->status_momentary_warning("heap", 1000);
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
// calculate loop time - from last call to this one
|
||||
if (this->loop_time_sensor_ != nullptr) {
|
||||
uint32_t now = millis();
|
||||
@@ -291,9 +298,11 @@ void DebugComponent::loop() {
|
||||
this->max_loop_time_ = std::max(this->max_loop_time_, loop_time);
|
||||
this->last_loop_timetag_ = now;
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
}
|
||||
|
||||
void DebugComponent::update() {
|
||||
#ifdef USE_SENSOR
|
||||
if (this->free_sensor_ != nullptr) {
|
||||
this->free_sensor_->publish_state(get_free_heap());
|
||||
}
|
||||
@@ -318,6 +327,7 @@ void DebugComponent::update() {
|
||||
this->loop_time_sensor_->publish_state(this->max_loop_time_);
|
||||
this->max_loop_time_ = 0;
|
||||
}
|
||||
#endif // USE_SENSOR
|
||||
}
|
||||
|
||||
float DebugComponent::get_setup_priority() const { return setup_priority::LATE; }
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/macros.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace debug {
|
||||
@@ -16,27 +22,35 @@ class DebugComponent : public PollingComponent {
|
||||
float get_setup_priority() const override;
|
||||
void dump_config() override;
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void set_device_info_sensor(text_sensor::TextSensor *device_info) { device_info_ = device_info; }
|
||||
#endif // USE_TEXT_SENSOR
|
||||
#ifdef USE_SENSOR
|
||||
void set_free_sensor(sensor::Sensor *free_sensor) { free_sensor_ = free_sensor; }
|
||||
void set_block_sensor(sensor::Sensor *block_sensor) { block_sensor_ = block_sensor; }
|
||||
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
void set_fragmentation_sensor(sensor::Sensor *fragmentation_sensor) { fragmentation_sensor_ = fragmentation_sensor; }
|
||||
#endif
|
||||
void set_loop_time_sensor(sensor::Sensor *loop_time_sensor) { loop_time_sensor_ = loop_time_sensor; }
|
||||
|
||||
#endif // USE_SENSOR
|
||||
protected:
|
||||
uint32_t free_heap_{};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
uint32_t last_loop_timetag_{0};
|
||||
uint32_t max_loop_time_{0};
|
||||
|
||||
text_sensor::TextSensor *device_info_{nullptr};
|
||||
sensor::Sensor *free_sensor_{nullptr};
|
||||
sensor::Sensor *block_sensor_{nullptr};
|
||||
#if defined(USE_ESP8266) && ARDUINO_VERSION_CODE >= VERSION_CODE(2, 5, 2)
|
||||
sensor::Sensor *fragmentation_sensor_{nullptr};
|
||||
#endif
|
||||
sensor::Sensor *loop_time_sensor_{nullptr};
|
||||
#endif // USE_SENSOR
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
text_sensor::TextSensor *device_info_{nullptr};
|
||||
#endif // USE_TEXT_SENSOR
|
||||
};
|
||||
|
||||
} // namespace debug
|
||||
|
||||
49
esphome/components/debug/sensor.py
Normal file
49
esphome/components/debug/sensor.py
Normal file
@@ -0,0 +1,49 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_FREE,
|
||||
CONF_FRAGMENTATION,
|
||||
CONF_BLOCK,
|
||||
CONF_LOOP_TIME,
|
||||
UNIT_MILLISECOND,
|
||||
UNIT_PERCENT,
|
||||
UNIT_BYTES,
|
||||
ICON_COUNTER,
|
||||
ICON_TIMER,
|
||||
)
|
||||
from . import CONF_DEBUG_ID, DebugComponent
|
||||
|
||||
DEPENDENCIES = ["debug"]
|
||||
|
||||
CONFIG_SCHEMA = {
|
||||
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
|
||||
cv.Optional(CONF_FREE): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
|
||||
cv.Optional(CONF_BLOCK): sensor.sensor_schema(UNIT_BYTES, ICON_COUNTER, 0),
|
||||
cv.Optional(CONF_FRAGMENTATION): cv.All(
|
||||
cv.only_on_esp8266,
|
||||
cv.require_framework_version(esp8266_arduino=cv.Version(2, 5, 2)),
|
||||
sensor.sensor_schema(UNIT_PERCENT, ICON_COUNTER, 1),
|
||||
),
|
||||
cv.Optional(CONF_LOOP_TIME): sensor.sensor_schema(UNIT_MILLISECOND, ICON_TIMER, 0),
|
||||
}
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
|
||||
if CONF_FREE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FREE])
|
||||
cg.add(debug_component.set_free_sensor(sens))
|
||||
|
||||
if CONF_BLOCK in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BLOCK])
|
||||
cg.add(debug_component.set_block_sensor(sens))
|
||||
|
||||
if CONF_FRAGMENTATION in config:
|
||||
sens = await sensor.new_sensor(config[CONF_FRAGMENTATION])
|
||||
cg.add(debug_component.set_fragmentation_sensor(sens))
|
||||
|
||||
if CONF_LOOP_TIME in config:
|
||||
sens = await sensor.new_sensor(config[CONF_LOOP_TIME])
|
||||
cg.add(debug_component.set_loop_time_sensor(sens))
|
||||
24
esphome/components/debug/text_sensor.py
Normal file
24
esphome/components/debug/text_sensor.py
Normal file
@@ -0,0 +1,24 @@
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_DEVICE
|
||||
|
||||
from . import CONF_DEBUG_ID, DebugComponent
|
||||
|
||||
DEPENDENCIES = ["debug"]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_DEBUG_ID): cv.use_id(DebugComponent),
|
||||
cv.Optional(CONF_DEVICE): text_sensor.text_sensor_schema(),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
debug_component = await cg.get_variable(config[CONF_DEBUG_ID])
|
||||
|
||||
if CONF_DEVICE in config:
|
||||
sens = await text_sensor.new_text_sensor(config[CONF_DEVICE])
|
||||
cg.add(debug_component.set_device_info_sensor(sens))
|
||||
@@ -37,12 +37,10 @@ from esphome.const import (
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
ICON_BLUETOOTH,
|
||||
ICON_BLUR,
|
||||
ICON_EMPTY,
|
||||
ICON_THERMOMETER,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
STATE_CLASS_TOTAL_INCREASING,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_EMPTY,
|
||||
UNIT_PERCENT,
|
||||
UNIT_WATT_HOURS,
|
||||
)
|
||||
@@ -339,7 +337,7 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
},
|
||||
],
|
||||
): [
|
||||
sensor.sensor_schema(UNIT_EMPTY, ICON_EMPTY, 0)
|
||||
sensor.sensor_schema(accuracy_decimals=0)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(
|
||||
{
|
||||
@@ -378,12 +376,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
},
|
||||
],
|
||||
): [
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
text_sensor.text_sensor_schema(klass=DemoTextSensor).extend(
|
||||
cv.polling_component_schema("60s")
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(DemoTextSensor),
|
||||
}
|
||||
)
|
||||
],
|
||||
}
|
||||
@@ -443,6 +437,5 @@ async def to_code(config):
|
||||
await switch.register_switch(var, conf)
|
||||
|
||||
for conf in config[CONF_TEXT_SENSORS]:
|
||||
var = cg.new_Pvariable(conf[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(conf)
|
||||
await cg.register_component(var, conf)
|
||||
await text_sensor.register_text_sensor(var, conf)
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
)
|
||||
|
||||
from . import Dsmr, CONF_DSMR_ID
|
||||
|
||||
AUTO_LOAD = ["dsmr"]
|
||||
@@ -11,71 +9,19 @@ AUTO_LOAD = ["dsmr"]
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_DSMR_ID): cv.use_id(Dsmr),
|
||||
cv.Optional("identification"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("p1_version"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("p1_version_be"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("timestamp"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("electricity_tariff"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("electricity_failure_log"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("message_short"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("message_long"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("gas_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("thermal_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("water_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("sub_equipment_id"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("gas_delivered_text"): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(text_sensor.TextSensor),
|
||||
}
|
||||
),
|
||||
cv.Optional("identification"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("p1_version"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("p1_version_be"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("timestamp"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("electricity_tariff"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("electricity_failure_log"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("message_short"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("message_long"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("gas_equipment_id"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("thermal_equipment_id"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("water_equipment_id"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("sub_equipment_id"): text_sensor.text_sensor_schema(),
|
||||
cv.Optional("gas_delivered_text"): text_sensor.text_sensor_schema(),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
@@ -89,8 +35,7 @@ async def to_code(config):
|
||||
continue
|
||||
id = conf.get("id")
|
||||
if id and id.type == text_sensor.TextSensor:
|
||||
var = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(var, conf)
|
||||
var = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(getattr(hub, f"set_{key}")(var))
|
||||
text_sensors.append(f"F({key})")
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
# Source https://github.com/letscontrolit/ESPEasy/pull/3845#issuecomment-1005864664
|
||||
|
||||
import esptool
|
||||
from SCons.Script import ARGUMENTS
|
||||
|
||||
# pylint: disable=E0602
|
||||
Import("env") # noqa
|
||||
|
||||
|
||||
def esp32_create_combined_bin(source, target, env):
|
||||
print("Generating combined binary for serial flashing")
|
||||
verbose = bool(int(ARGUMENTS.get("PIOVERBOSE", "0")))
|
||||
if verbose:
|
||||
print("Generating combined binary for serial flashing")
|
||||
app_offset = 0x10000
|
||||
|
||||
new_file_name = env.subst("$BUILD_DIR/${PROGNAME}-factory.bin")
|
||||
@@ -24,18 +27,21 @@ def esp32_create_combined_bin(source, target, env):
|
||||
"--flash_size",
|
||||
flash_size,
|
||||
]
|
||||
print(" Offset | File")
|
||||
if verbose:
|
||||
print(" Offset | File")
|
||||
for section in sections:
|
||||
sect_adr, sect_file = section.split(" ", 1)
|
||||
print(f" - {sect_adr} | {sect_file}")
|
||||
if verbose:
|
||||
print(f" - {sect_adr} | {sect_file}")
|
||||
cmd += [sect_adr, sect_file]
|
||||
|
||||
print(f" - {hex(app_offset)} | {firmware_name}")
|
||||
cmd += [hex(app_offset), firmware_name]
|
||||
|
||||
print()
|
||||
print(f"Using esptool.py arguments: {' '.join(cmd)}")
|
||||
print()
|
||||
if verbose:
|
||||
print(f" - {hex(app_offset)} | {firmware_name}")
|
||||
print()
|
||||
print(f"Using esptool.py arguments: {' '.join(cmd)}")
|
||||
print()
|
||||
esptool.main(cmd)
|
||||
|
||||
|
||||
|
||||
@@ -42,6 +42,11 @@ void BLEAdvertising::remove_service_uuid(ESPBTUUID uuid) {
|
||||
this->advertising_uuids_.end());
|
||||
}
|
||||
|
||||
void BLEAdvertising::set_manufacturer_data(uint8_t *data, uint16_t size) {
|
||||
this->advertising_data_.p_manufacturer_data = data;
|
||||
this->advertising_data_.manufacturer_len = size;
|
||||
}
|
||||
|
||||
void BLEAdvertising::start() {
|
||||
int num_services = this->advertising_uuids_.size();
|
||||
if (num_services == 0) {
|
||||
|
||||
@@ -20,6 +20,7 @@ class BLEAdvertising {
|
||||
void remove_service_uuid(ESPBTUUID uuid);
|
||||
void set_scan_response(bool scan_response) { this->scan_response_ = scan_response; }
|
||||
void set_min_preferred_interval(uint16_t interval) { this->advertising_data_.min_interval = interval; }
|
||||
void set_manufacturer_data(uint8_t *data, uint16_t size);
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
@@ -56,7 +56,7 @@ async def to_code(config):
|
||||
cg.add(ble_server.register_service_component(var))
|
||||
|
||||
cg.add_define("USE_IMPROV")
|
||||
cg.add_library("esphome/Improv", "1.2.0")
|
||||
cg.add_library("esphome/Improv", "1.2.1")
|
||||
|
||||
cg.add(var.set_identify_duration(config[CONF_IDENTIFY_DURATION]))
|
||||
cg.add(var.set_authorized_duration(config[CONF_AUTHORIZED_DURATION]))
|
||||
|
||||
@@ -166,6 +166,7 @@ async def to_code(config):
|
||||
cg.add_platformio_option("framework", "arduino")
|
||||
cg.add_build_flag("-DUSE_ARDUINO")
|
||||
cg.add_build_flag("-DUSE_ESP8266_FRAMEWORK_ARDUINO")
|
||||
cg.add_build_flag("-Wno-nonnull-compare")
|
||||
cg.add_platformio_option("platform", conf[CONF_PLATFORM_VERSION])
|
||||
cg.add_platformio_option(
|
||||
"platform_packages",
|
||||
|
||||
@@ -49,7 +49,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
_validate,
|
||||
cv.only_with_arduino,
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 7, 4),
|
||||
esp32_arduino=cv.Version(99, 0, 0),
|
||||
max_version=True,
|
||||
extra_message="Please see note on documentation for FastLED",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -33,7 +33,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_DATA_RATE): cv.frequency,
|
||||
}
|
||||
),
|
||||
cv.only_with_arduino,
|
||||
cv.require_framework_version(
|
||||
esp8266_arduino=cv.Version(2, 7, 4),
|
||||
esp32_arduino=cv.Version(99, 0, 0),
|
||||
max_version=True,
|
||||
extra_message="Please see note on documentation for FastLED",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_ID
|
||||
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
|
||||
|
||||
from .. import homeassistant_ns
|
||||
|
||||
DEPENDENCIES = ["api"]
|
||||
@@ -10,7 +11,7 @@ HomeassistantTextSensor = homeassistant_ns.class_(
|
||||
"HomeassistantTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HomeassistantTextSensor),
|
||||
cv.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
@@ -20,9 +21,8 @@ CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
|
||||
if CONF_ATTRIBUTE in config:
|
||||
|
||||
@@ -30,4 +30,4 @@ FINAL_VALIDATE_SCHEMA = validate_logger_baud_rate
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
cg.add_library("esphome/Improv", "1.2.0")
|
||||
cg.add_library("esphome/Improv", "1.2.1")
|
||||
|
||||
@@ -47,6 +47,7 @@ InkplateModel = inkplate6_ns.enum("InkplateModel")
|
||||
MODELS = {
|
||||
"inkplate_6": InkplateModel.INKPLATE_6,
|
||||
"inkplate_10": InkplateModel.INKPLATE_10,
|
||||
"inkplate_6_plus": InkplateModel.INKPLATE_6_PLUS,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
|
||||
@@ -13,6 +13,11 @@ namespace inkplate6 {
|
||||
static const char *const TAG = "inkplate";
|
||||
|
||||
void Inkplate6::setup() {
|
||||
for (uint32_t i = 0; i < 256; i++) {
|
||||
this->pin_lut_[i] = ((i & 0b00000011) << 4) | (((i & 0b00001100) >> 2) << 18) | (((i & 0b00010000) >> 4) << 23) |
|
||||
(((i & 0b11100000) >> 5) << 25);
|
||||
}
|
||||
|
||||
this->initialize_();
|
||||
|
||||
this->vcom_pin_->setup();
|
||||
@@ -38,11 +43,21 @@ void Inkplate6::setup() {
|
||||
this->display_data_6_pin_->setup();
|
||||
this->display_data_7_pin_->setup();
|
||||
|
||||
this->clean();
|
||||
this->display();
|
||||
this->wakeup_pin_->digital_write(true);
|
||||
delay(1);
|
||||
this->write_bytes(0x09, {
|
||||
0b00011011, // Power up seq.
|
||||
0b00000000, // Power up delay (3mS per rail)
|
||||
0b00011011, // Power down seq.
|
||||
0b00000000, // Power down delay (6mS per rail)
|
||||
});
|
||||
delay(1);
|
||||
this->wakeup_pin_->digital_write(false);
|
||||
}
|
||||
|
||||
void Inkplate6::initialize_() {
|
||||
ExternalRAMAllocator<uint8_t> allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
|
||||
ExternalRAMAllocator<uint32_t> allocator32(ExternalRAMAllocator<uint32_t>::ALLOW_FAILURE);
|
||||
uint32_t buffer_size = this->get_buffer_length_();
|
||||
if (buffer_size == 0)
|
||||
return;
|
||||
@@ -53,6 +68,10 @@ void Inkplate6::initialize_() {
|
||||
allocator.deallocate(this->partial_buffer_2_, buffer_size * 2);
|
||||
if (this->buffer_ != nullptr)
|
||||
allocator.deallocate(this->buffer_, buffer_size);
|
||||
if (this->glut_ != nullptr)
|
||||
allocator32.deallocate(this->glut_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8));
|
||||
if (this->glut2_ != nullptr)
|
||||
allocator32.deallocate(this->glut2_, 256 * (this->model_ == INKPLATE_6_PLUS ? 9 : 8));
|
||||
|
||||
this->buffer_ = allocator.allocate(buffer_size);
|
||||
if (this->buffer_ == nullptr) {
|
||||
@@ -60,7 +79,34 @@ void Inkplate6::initialize_() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!this->greyscale_) {
|
||||
if (this->greyscale_) {
|
||||
uint8_t glut_size = (this->model_ == INKPLATE_6_PLUS ? 9 : 8);
|
||||
|
||||
this->glut_ = allocator32.allocate(256 * glut_size);
|
||||
if (this->glut_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate glut!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->glut2_ = allocator32.allocate(256 * glut_size);
|
||||
if (this->glut2_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate glut2!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < glut_size; i++) {
|
||||
for (uint32_t j = 0; j < 256; j++) {
|
||||
uint8_t z = (waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i]);
|
||||
this->glut_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) |
|
||||
(((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25);
|
||||
z = ((waveform3Bit[j & 0x07][i] << 2) | (waveform3Bit[(j >> 4) & 0x07][i])) << 4;
|
||||
this->glut2_[i * 256 + j] = ((z & 0b00000011) << 4) | (((z & 0b00001100) >> 2) << 18) |
|
||||
(((z & 0b00010000) >> 4) << 23) | (((z & 0b11100000) >> 5) << 25);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
this->partial_buffer_ = allocator.allocate(buffer_size);
|
||||
if (this->partial_buffer_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate partial buffer for display!");
|
||||
@@ -73,13 +119,16 @@ void Inkplate6::initialize_() {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
memset(this->partial_buffer_, 0, buffer_size);
|
||||
memset(this->partial_buffer_2_, 0, buffer_size * 2);
|
||||
}
|
||||
|
||||
memset(this->buffer_, 0, buffer_size);
|
||||
}
|
||||
|
||||
float Inkplate6::get_setup_priority() const { return setup_priority::PROCESSOR; }
|
||||
|
||||
size_t Inkplate6::get_buffer_length_() {
|
||||
if (this->greyscale_) {
|
||||
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 2u;
|
||||
@@ -87,6 +136,7 @@ size_t Inkplate6::get_buffer_length_() {
|
||||
return size_t(this->get_width_internal()) * size_t(this->get_height_internal()) / 8u;
|
||||
}
|
||||
}
|
||||
|
||||
void Inkplate6::update() {
|
||||
this->do_update_();
|
||||
|
||||
@@ -96,6 +146,7 @@ void Inkplate6::update() {
|
||||
|
||||
this->display();
|
||||
}
|
||||
|
||||
void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
if (x >= this->get_width_internal() || y >= this->get_height_internal() || x < 0 || y < 0)
|
||||
return;
|
||||
@@ -121,6 +172,7 @@ void HOT Inkplate6::draw_absolute_pixel_internal(int x, int y, Color color) {
|
||||
this->partial_buffer_[pos] = (~pixelMaskLUT[x_sub] & current) | (color.is_on() ? 0 : pixelMaskLUT[x_sub]);
|
||||
}
|
||||
}
|
||||
|
||||
void Inkplate6::dump_config() {
|
||||
LOG_DISPLAY("", "Inkplate", this);
|
||||
ESP_LOGCONFIG(TAG, " Greyscale: %s", YESNO(this->greyscale_));
|
||||
@@ -150,44 +202,51 @@ void Inkplate6::dump_config() {
|
||||
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
void Inkplate6::eink_off_() {
|
||||
ESP_LOGV(TAG, "Eink off called");
|
||||
if (panel_on_ == 0)
|
||||
if (!panel_on_)
|
||||
return;
|
||||
panel_on_ = 0;
|
||||
this->gmod_pin_->digital_write(false);
|
||||
panel_on_ = false;
|
||||
|
||||
this->oe_pin_->digital_write(false);
|
||||
this->gmod_pin_->digital_write(false);
|
||||
|
||||
GPIO.out &= ~(get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin()));
|
||||
|
||||
GPIO.out &= ~(this->get_data_pin_mask_() | (1 << this->cl_pin_->get_pin()) | (1 << this->le_pin_->get_pin()));
|
||||
this->ckv_pin_->digital_write(false);
|
||||
this->sph_pin_->digital_write(false);
|
||||
this->spv_pin_->digital_write(false);
|
||||
|
||||
this->powerup_pin_->digital_write(false);
|
||||
this->wakeup_pin_->digital_write(false);
|
||||
this->vcom_pin_->digital_write(false);
|
||||
|
||||
this->write_byte(0x01, 0x6F); // Put TPS65186 into standby mode
|
||||
|
||||
delay(100); // NOLINT
|
||||
|
||||
this->write_byte(0x01, 0x4f); // Disable 3V3 to the panel
|
||||
|
||||
if (this->model_ != INKPLATE_6_PLUS)
|
||||
this->wakeup_pin_->digital_write(false);
|
||||
|
||||
pins_z_state_();
|
||||
}
|
||||
|
||||
void Inkplate6::eink_on_() {
|
||||
ESP_LOGV(TAG, "Eink on called");
|
||||
if (panel_on_ == 1)
|
||||
if (panel_on_)
|
||||
return;
|
||||
panel_on_ = 1;
|
||||
pins_as_outputs_();
|
||||
this->panel_on_ = true;
|
||||
|
||||
this->pins_as_outputs_();
|
||||
this->wakeup_pin_->digital_write(true);
|
||||
this->powerup_pin_->digital_write(true);
|
||||
this->vcom_pin_->digital_write(true);
|
||||
|
||||
this->write_byte(0x01, 0x3F);
|
||||
|
||||
delay(40);
|
||||
|
||||
this->write_byte(0x0D, 0x80);
|
||||
|
||||
delay(2);
|
||||
|
||||
this->read_register(0x00, nullptr, 0);
|
||||
this->write_byte(0x01, 0b00101111); // Enable all rails
|
||||
|
||||
delay(1);
|
||||
|
||||
this->write_byte(0x01, 0b10101111); // Switch TPS65186 into active mode
|
||||
|
||||
this->le_pin_->digital_write(false);
|
||||
this->oe_pin_->digital_write(false);
|
||||
@@ -196,8 +255,33 @@ void Inkplate6::eink_on_() {
|
||||
this->gmod_pin_->digital_write(true);
|
||||
this->spv_pin_->digital_write(true);
|
||||
this->ckv_pin_->digital_write(false);
|
||||
this->oe_pin_->digital_write(false);
|
||||
|
||||
uint32_t timer = millis();
|
||||
do {
|
||||
delay(1);
|
||||
} while (!this->read_power_status_() && ((millis() - timer) < 250));
|
||||
if ((millis() - timer) >= 250) {
|
||||
ESP_LOGW(TAG, "Power supply not detected");
|
||||
this->wakeup_pin_->digital_write(false);
|
||||
this->vcom_pin_->digital_write(false);
|
||||
this->powerup_pin_->digital_write(false);
|
||||
this->panel_on_ = false;
|
||||
return;
|
||||
}
|
||||
|
||||
this->oe_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
bool Inkplate6::read_power_status_() {
|
||||
uint8_t data;
|
||||
auto err = this->read_register(0x0F, &data, 1);
|
||||
if (err == i2c::ERROR_OK) {
|
||||
return data == 0b11111010;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Inkplate6::fill(Color color) {
|
||||
ESP_LOGV(TAG, "Fill called");
|
||||
uint32_t start_time = millis();
|
||||
@@ -212,6 +296,7 @@ void Inkplate6::fill(Color color) {
|
||||
|
||||
ESP_LOGV(TAG, "Fill finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::display() {
|
||||
ESP_LOGV(TAG, "Display called");
|
||||
uint32_t start_time = millis();
|
||||
@@ -227,201 +312,185 @@ void Inkplate6::display() {
|
||||
}
|
||||
ESP_LOGV(TAG, "Display finished (full) (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::display1b_() {
|
||||
ESP_LOGV(TAG, "Display1b called");
|
||||
uint32_t start_time = millis();
|
||||
|
||||
memcpy(this->buffer_, this->partial_buffer_, this->get_buffer_length_());
|
||||
|
||||
uint32_t send;
|
||||
uint8_t data;
|
||||
uint8_t buffer_value;
|
||||
const uint8_t *buffer_ptr;
|
||||
eink_on_();
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 5);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 5);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 12);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 11);
|
||||
if (this->model_ == INKPLATE_6_PLUS) {
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 15);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 5);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 15);
|
||||
} else {
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 21);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 12);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 21);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 12);
|
||||
}
|
||||
|
||||
uint32_t clock = (1 << this->cl_pin_->get_pin());
|
||||
uint32_t data_mask = this->get_data_pin_mask_();
|
||||
ESP_LOGV(TAG, "Display1b start loops (%ums)", millis() - start_time);
|
||||
for (int k = 0; k < 3; k++) {
|
||||
|
||||
for (int k = 0; k < 4; k++) {
|
||||
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
|
||||
vscan_start_();
|
||||
for (int i = 0; i < this->get_height_internal(); i++) {
|
||||
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
|
||||
buffer_value = *(buffer_ptr--);
|
||||
data = LUTB[(buffer_value >> 4) & 0x0F];
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25);
|
||||
hscan_start_(send);
|
||||
data = LUTB[buffer_value & 0x0F];
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F];
|
||||
hscan_start_(this->pin_lut_[data]);
|
||||
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F];
|
||||
GPIO.out_w1ts = this->pin_lut_[data] | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
|
||||
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
|
||||
buffer_value = *(buffer_ptr--);
|
||||
data = LUTB[(buffer_value >> 4) & 0x0F];
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
data = LUTB[buffer_value & 0x0F];
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value >> 4) & 0x0F] : LUTB[(buffer_value >> 4) & 0x0F];
|
||||
GPIO.out_w1ts = this->pin_lut_[data] | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
data = this->model_ == INKPLATE_6_PLUS ? LUTW[(~buffer_value) & 0x0F] : LUTB[buffer_value & 0x0F];
|
||||
GPIO.out_w1ts = this->pin_lut_[data] | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
}
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = get_data_pin_mask_() | clock;
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
}
|
||||
ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 3, millis() - start_time);
|
||||
ESP_LOGV(TAG, "Display1b first loop x %d (%ums)", 4, millis() - start_time);
|
||||
|
||||
buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
|
||||
vscan_start_();
|
||||
for (int i = 0; i < this->get_height_internal(); i++) {
|
||||
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
|
||||
buffer_value = *(buffer_ptr--);
|
||||
data = LUT2[(buffer_value >> 4) & 0x0F];
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25);
|
||||
hscan_start_(send);
|
||||
data = LUT2[buffer_value & 0x0F];
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F];
|
||||
hscan_start_(this->pin_lut_[data] | clock);
|
||||
data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F];
|
||||
GPIO.out_w1ts = this->pin_lut_[data] | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
|
||||
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
|
||||
buffer_value = *(buffer_ptr--);
|
||||
data = LUT2[(buffer_value >> 4) & 0x0F];
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
data = LUT2[buffer_value & 0x0F];
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
data = this->model_ == INKPLATE_6_PLUS ? LUTB[(buffer_value >> 4) & 0x0F] : LUT2[(buffer_value >> 4) & 0x0F];
|
||||
GPIO.out_w1ts = this->pin_lut_[data] | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
data = this->model_ == INKPLATE_6_PLUS ? LUTB[buffer_value & 0x0F] : LUT2[buffer_value & 0x0F];
|
||||
GPIO.out_w1ts = this->pin_lut_[data] | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
}
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = get_data_pin_mask_() | clock;
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
ESP_LOGV(TAG, "Display1b second loop (%ums)", millis() - start_time);
|
||||
|
||||
vscan_start_();
|
||||
for (int i = 0; i < this->get_height_internal(); i++) {
|
||||
data = 0b00000000;
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25);
|
||||
hscan_start_(send);
|
||||
send |= clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
if (this->model_ == INKPLATE_6_PLUS) {
|
||||
clean_fast_(2, 2);
|
||||
clean_fast_(3, 1);
|
||||
} else {
|
||||
uint32_t send = this->pin_lut_[0];
|
||||
vscan_start_();
|
||||
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
|
||||
hscan_start_(send);
|
||||
GPIO.out_w1ts = send | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
|
||||
GPIO.out_w1ts = send | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
GPIO.out_w1ts = send | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
}
|
||||
GPIO.out_w1ts = send | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
vscan_end_();
|
||||
}
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = get_data_pin_mask_() | clock;
|
||||
vscan_end_();
|
||||
delayMicroseconds(230);
|
||||
ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time);
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
ESP_LOGV(TAG, "Display1b third loop (%ums)", millis() - start_time);
|
||||
|
||||
vscan_start_();
|
||||
eink_off_();
|
||||
this->block_partial_ = false;
|
||||
this->partial_updates_ = 0;
|
||||
ESP_LOGV(TAG, "Display1b finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::display3b_() {
|
||||
ESP_LOGV(TAG, "Display3b called");
|
||||
uint32_t start_time = millis();
|
||||
|
||||
eink_on_();
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 12);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 11);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 12);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 11);
|
||||
if (this->model_ == INKPLATE_6_PLUS) {
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 15);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 5);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 15);
|
||||
} else {
|
||||
clean_fast_(0, 1);
|
||||
clean_fast_(1, 21);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 12);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(1, 21);
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(0, 12);
|
||||
}
|
||||
|
||||
uint32_t clock = (1 << this->cl_pin_->get_pin());
|
||||
for (int k = 0; k < 8; k++) {
|
||||
const uint8_t *buffer_ptr = &this->buffer_[this->get_buffer_length_() - 1];
|
||||
uint32_t send;
|
||||
uint8_t pix1;
|
||||
uint8_t pix2;
|
||||
uint8_t pix3;
|
||||
uint8_t pix4;
|
||||
uint8_t pixel;
|
||||
uint8_t pixel2;
|
||||
|
||||
uint32_t data_mask = this->get_data_pin_mask_();
|
||||
uint32_t pos;
|
||||
uint32_t data;
|
||||
uint8_t glut_size = this->model_ == INKPLATE_6_PLUS ? 9 : 8;
|
||||
for (int k = 0; k < glut_size; k++) {
|
||||
pos = this->get_buffer_length_();
|
||||
vscan_start_();
|
||||
for (int i = 0; i < this->get_height_internal(); i++) {
|
||||
pix1 = (*buffer_ptr--);
|
||||
pix2 = (*buffer_ptr--);
|
||||
pix3 = (*buffer_ptr--);
|
||||
pix4 = (*buffer_ptr--);
|
||||
pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) |
|
||||
(waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0);
|
||||
pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) |
|
||||
(waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0);
|
||||
data = this->glut2_[k * 256 + this->buffer_[--pos]];
|
||||
data |= this->glut_[k * 256 + this->buffer_[--pos]];
|
||||
hscan_start_(data);
|
||||
data = this->glut2_[k * 256 + this->buffer_[--pos]];
|
||||
data |= this->glut_[k * 256 + this->buffer_[--pos]];
|
||||
GPIO.out_w1ts = data | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
|
||||
send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) |
|
||||
(((pixel & 0b11100000) >> 5) << 25);
|
||||
hscan_start_(send);
|
||||
send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) |
|
||||
(((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
|
||||
for (int j = 0, jm = (this->get_width_internal() / 8) - 1; j < jm; j++) {
|
||||
pix1 = (*buffer_ptr--);
|
||||
pix2 = (*buffer_ptr--);
|
||||
pix3 = (*buffer_ptr--);
|
||||
pix4 = (*buffer_ptr--);
|
||||
pixel = (waveform3Bit[pix1 & 0x07][k] << 6) | (waveform3Bit[(pix1 >> 4) & 0x07][k] << 4) |
|
||||
(waveform3Bit[pix2 & 0x07][k] << 2) | (waveform3Bit[(pix2 >> 4) & 0x07][k] << 0);
|
||||
pixel2 = (waveform3Bit[pix3 & 0x07][k] << 6) | (waveform3Bit[(pix3 >> 4) & 0x07][k] << 4) |
|
||||
(waveform3Bit[pix4 & 0x07][k] << 2) | (waveform3Bit[(pix4 >> 4) & 0x07][k] << 0);
|
||||
|
||||
send = ((pixel & 0b00000011) << 4) | (((pixel & 0b00001100) >> 2) << 18) | (((pixel & 0b00010000) >> 4) << 23) |
|
||||
(((pixel & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
|
||||
send = ((pixel2 & 0b00000011) << 4) | (((pixel2 & 0b00001100) >> 2) << 18) |
|
||||
(((pixel2 & 0b00010000) >> 4) << 23) | (((pixel2 & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
|
||||
data = this->glut2_[k * 256 + this->buffer_[--pos]];
|
||||
data |= this->glut_[k * 256 + this->buffer_[--pos]];
|
||||
GPIO.out_w1ts = data | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
data = this->glut2_[k * 256 + this->buffer_[--pos]];
|
||||
data |= this->glut_[k * 256 + this->buffer_[--pos]];
|
||||
GPIO.out_w1ts = data | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
}
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = get_data_pin_mask_() | clock;
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
}
|
||||
clean_fast_(2, 1);
|
||||
clean_fast_(3, 1);
|
||||
vscan_start_();
|
||||
eink_off_();
|
||||
ESP_LOGV(TAG, "Display3b finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
bool Inkplate6::partial_update_() {
|
||||
ESP_LOGV(TAG, "Partial update called");
|
||||
uint32_t start_time = millis();
|
||||
@@ -432,16 +501,15 @@ bool Inkplate6::partial_update_() {
|
||||
|
||||
this->partial_updates_++;
|
||||
|
||||
uint16_t pos = this->get_buffer_length_() - 1;
|
||||
uint32_t send;
|
||||
uint32_t pos = this->get_buffer_length_() - 1;
|
||||
uint8_t data;
|
||||
uint8_t diffw, diffb;
|
||||
uint32_t n = (this->get_buffer_length_() * 2) - 1;
|
||||
|
||||
for (int i = 0, im = this->get_height_internal(); i < im; i++) {
|
||||
for (int j = 0, jm = (this->get_width_internal() / 8); j < jm; j++) {
|
||||
diffw = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & ~(this->partial_buffer_[pos]);
|
||||
diffb = (this->buffer_[pos] ^ this->partial_buffer_[pos]) & this->partial_buffer_[pos];
|
||||
diffw = this->buffer_[pos] & ~(this->partial_buffer_[pos]);
|
||||
diffb = ~(this->buffer_[pos]) & this->partial_buffer_[pos];
|
||||
pos--;
|
||||
this->partial_buffer_2_[n--] = LUTW[diffw >> 4] & LUTB[diffb >> 4];
|
||||
this->partial_buffer_2_[n--] = LUTW[diffw & 0x0F] & LUTB[diffb & 0x0F];
|
||||
@@ -451,23 +519,20 @@ bool Inkplate6::partial_update_() {
|
||||
|
||||
eink_on_();
|
||||
uint32_t clock = (1 << this->cl_pin_->get_pin());
|
||||
uint32_t data_mask = this->get_data_pin_mask_();
|
||||
for (int k = 0; k < 5; k++) {
|
||||
vscan_start_();
|
||||
const uint8_t *data_ptr = &this->partial_buffer_2_[(this->get_buffer_length_() * 2) - 1];
|
||||
for (int i = 0; i < this->get_height_internal(); i++) {
|
||||
data = *(data_ptr--);
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25);
|
||||
hscan_start_(send);
|
||||
hscan_start_(this->pin_lut_[data]);
|
||||
for (int j = 0, jm = (this->get_width_internal() / 4) - 1; j < jm; j++) {
|
||||
data = *(data_ptr--);
|
||||
send = ((data & 0b00000011) << 4) | (((data & 0b00001100) >> 2) << 18) | (((data & 0b00010000) >> 4) << 23) |
|
||||
(((data & 0b11100000) >> 5) << 25) | clock;
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = send;
|
||||
GPIO.out_w1ts = this->pin_lut_[data] | clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
}
|
||||
GPIO.out_w1ts = send;
|
||||
GPIO.out_w1tc = get_data_pin_mask_() | clock;
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = data_mask | clock;
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
@@ -482,6 +547,7 @@ bool Inkplate6::partial_update_() {
|
||||
ESP_LOGV(TAG, "Partial update finished (%ums)", millis() - start_time);
|
||||
return true;
|
||||
}
|
||||
|
||||
void Inkplate6::vscan_start_() {
|
||||
this->ckv_pin_->digital_write(true);
|
||||
delayMicroseconds(7);
|
||||
@@ -505,30 +571,23 @@ void Inkplate6::vscan_start_() {
|
||||
delayMicroseconds(0);
|
||||
this->ckv_pin_->digital_write(true);
|
||||
}
|
||||
void Inkplate6::vscan_write_() {
|
||||
this->ckv_pin_->digital_write(false);
|
||||
this->le_pin_->digital_write(true);
|
||||
this->le_pin_->digital_write(false);
|
||||
delayMicroseconds(0);
|
||||
|
||||
void Inkplate6::hscan_start_(uint32_t d) {
|
||||
uint8_t clock = (1 << this->cl_pin_->get_pin());
|
||||
this->sph_pin_->digital_write(false);
|
||||
this->cl_pin_->digital_write(true);
|
||||
this->cl_pin_->digital_write(false);
|
||||
GPIO.out_w1ts = d | clock;
|
||||
GPIO.out_w1tc = this->get_data_pin_mask_() | clock;
|
||||
this->sph_pin_->digital_write(true);
|
||||
this->ckv_pin_->digital_write(true);
|
||||
}
|
||||
void Inkplate6::hscan_start_(uint32_t d) {
|
||||
this->sph_pin_->digital_write(false);
|
||||
GPIO.out_w1ts = (d) | (1 << this->cl_pin_->get_pin());
|
||||
GPIO.out_w1tc = get_data_pin_mask_() | (1 << this->cl_pin_->get_pin());
|
||||
this->sph_pin_->digital_write(true);
|
||||
}
|
||||
|
||||
void Inkplate6::vscan_end_() {
|
||||
this->ckv_pin_->digital_write(false);
|
||||
this->le_pin_->digital_write(true);
|
||||
this->le_pin_->digital_write(false);
|
||||
delayMicroseconds(1);
|
||||
this->ckv_pin_->digital_write(true);
|
||||
delayMicroseconds(0);
|
||||
}
|
||||
|
||||
void Inkplate6::clean() {
|
||||
ESP_LOGV(TAG, "Clean called");
|
||||
uint32_t start_time = millis();
|
||||
@@ -542,6 +601,7 @@ void Inkplate6::clean() {
|
||||
clean_fast_(1, 10); // White to White
|
||||
ESP_LOGV(TAG, "Clean finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
ESP_LOGV(TAG, "Clean fast called with: (%d, %d)", c, rep);
|
||||
uint32_t start_time = millis();
|
||||
@@ -568,14 +628,14 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
hscan_start_(send);
|
||||
GPIO.out_w1ts = send | clock;
|
||||
GPIO.out_w1tc = clock;
|
||||
for (int j = 0, jm = this->get_width_internal() / 8; j < jm; j++) {
|
||||
for (int j = 0; j < (this->get_width_internal() / 8) - 1; j++) {
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = clock;
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = clock;
|
||||
}
|
||||
GPIO.out_w1ts = clock;
|
||||
GPIO.out_w1tc = get_data_pin_mask_() | clock;
|
||||
GPIO.out_w1ts = send | clock;
|
||||
GPIO.out_w1tc = clock;
|
||||
vscan_end_();
|
||||
}
|
||||
delayMicroseconds(230);
|
||||
@@ -583,7 +643,10 @@ void Inkplate6::clean_fast_(uint8_t c, uint8_t rep) {
|
||||
}
|
||||
ESP_LOGV(TAG, "Clean fast finished (%ums)", millis() - start_time);
|
||||
}
|
||||
|
||||
void Inkplate6::pins_z_state_() {
|
||||
this->cl_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->le_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->ckv_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->sph_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
|
||||
@@ -600,7 +663,10 @@ void Inkplate6::pins_z_state_() {
|
||||
this->display_data_6_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->display_data_7_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
}
|
||||
|
||||
void Inkplate6::pins_as_outputs_() {
|
||||
this->cl_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->le_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->ckv_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->sph_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
|
||||
|
||||
@@ -13,32 +13,28 @@ namespace inkplate6 {
|
||||
enum InkplateModel : uint8_t {
|
||||
INKPLATE_6 = 0,
|
||||
INKPLATE_10 = 1,
|
||||
INKPLATE_6_PLUS = 2,
|
||||
};
|
||||
|
||||
class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public i2c::I2CDevice {
|
||||
public:
|
||||
const uint8_t LUT2[16] = {0b10101010, 0b10101001, 0b10100110, 0b10100101, 0b10011010, 0b10011001,
|
||||
0b10010110, 0b10010101, 0b01101010, 0b01101001, 0b01100110, 0b01100101,
|
||||
0b01011010, 0b01011001, 0b01010110, 0b01010101};
|
||||
const uint8_t LUTW[16] = {0b11111111, 0b11111110, 0b11111011, 0b11111010, 0b11101111, 0b11101110,
|
||||
0b11101011, 0b11101010, 0b10111111, 0b10111110, 0b10111011, 0b10111010,
|
||||
0b10101111, 0b10101110, 0b10101011, 0b10101010};
|
||||
const uint8_t LUTB[16] = {0b11111111, 0b11111101, 0b11110111, 0b11110101, 0b11011111, 0b11011101,
|
||||
0b11010111, 0b11010101, 0b01111111, 0b01111101, 0b01110111, 0b01110101,
|
||||
0b01011111, 0b01011101, 0b01010111, 0b01010101};
|
||||
const uint8_t pixelMaskLUT[8] = {0b00000001, 0b00000010, 0b00000100, 0b00001000,
|
||||
0b00010000, 0b00100000, 0b01000000, 0b10000000};
|
||||
const uint8_t pixelMaskGLUT[2] = {0b00001111, 0b11110000};
|
||||
const uint8_t waveform3Bit[8][8] = {{0, 0, 0, 0, 1, 1, 1, 0}, {1, 2, 2, 2, 1, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0},
|
||||
{0, 2, 1, 2, 1, 2, 1, 0}, {0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0},
|
||||
const uint8_t LUT2[16] = {0xAA, 0xA9, 0xA6, 0xA5, 0x9A, 0x99, 0x96, 0x95,
|
||||
0x6A, 0x69, 0x66, 0x65, 0x5A, 0x59, 0x56, 0x55};
|
||||
const uint8_t LUTW[16] = {0xFF, 0xFE, 0xFB, 0xFA, 0xEF, 0xEE, 0xEB, 0xEA,
|
||||
0xBF, 0xBE, 0xBB, 0xBA, 0xAF, 0xAE, 0xAB, 0xAA};
|
||||
const uint8_t LUTB[16] = {0xFF, 0xFD, 0xF7, 0xF5, 0xDF, 0xDD, 0xD7, 0xD5,
|
||||
0x7F, 0x7D, 0x77, 0x75, 0x5F, 0x5D, 0x57, 0x55};
|
||||
|
||||
const uint8_t pixelMaskLUT[8] = {0x1, 0x2, 0x4, 0x8, 0x10, 0x20, 0x40, 0x80};
|
||||
const uint8_t pixelMaskGLUT[2] = {0x0F, 0xF0};
|
||||
|
||||
const uint8_t waveform3Bit[8][8] = {{0, 1, 1, 0, 0, 1, 1, 0}, {0, 1, 2, 1, 1, 2, 1, 0}, {1, 1, 1, 2, 2, 1, 0, 0},
|
||||
{0, 0, 0, 1, 1, 1, 2, 0}, {2, 1, 1, 1, 2, 1, 2, 0}, {2, 2, 1, 1, 2, 1, 2, 0},
|
||||
{1, 1, 1, 2, 1, 2, 2, 0}, {0, 0, 0, 0, 0, 0, 2, 0}};
|
||||
const uint32_t waveform[50] = {
|
||||
0x00000008, 0x00000008, 0x00200408, 0x80281888, 0x60a81898, 0x60a8a8a8, 0x60a8a8a8, 0x6068a868, 0x6868a868,
|
||||
0x6868a868, 0x68686868, 0x6a686868, 0x5a686868, 0x5a686868, 0x5a586a68, 0x5a5a6a68, 0x5a5a6a68, 0x55566a68,
|
||||
0x55565a64, 0x55555654, 0x55555556, 0x55555556, 0x55555556, 0x55555516, 0x55555596, 0x15555595, 0x95955595,
|
||||
0x95959595, 0x95949495, 0x94949495, 0x94949495, 0xa4949494, 0x9494a4a4, 0x84a49494, 0x84948484, 0x84848484,
|
||||
0x84848484, 0x84848484, 0xa5a48484, 0xa9a4a4a8, 0xa9a8a8a8, 0xa5a9a9a4, 0xa5a5a5a4, 0xa1a5a5a1, 0xa9a9a9a9,
|
||||
0xa9a9a9a9, 0xa9a9a9a9, 0xa9a9a9a9, 0x15151515, 0x11111111};
|
||||
const uint8_t waveform3Bit6Plus[8][9] = {{0, 0, 0, 0, 0, 2, 1, 1, 0}, {0, 0, 2, 1, 1, 1, 2, 1, 0},
|
||||
{0, 2, 2, 2, 1, 1, 2, 1, 0}, {0, 0, 2, 2, 2, 1, 2, 1, 0},
|
||||
{0, 0, 0, 0, 2, 2, 2, 1, 0}, {0, 0, 2, 1, 2, 1, 1, 2, 0},
|
||||
{0, 0, 2, 2, 2, 1, 1, 2, 0}, {0, 0, 0, 0, 2, 2, 2, 2, 0}};
|
||||
|
||||
void set_greyscale(bool greyscale) {
|
||||
this->greyscale_ = greyscale;
|
||||
@@ -88,6 +84,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
|
||||
bool get_partial_updating() { return this->partial_updating_; }
|
||||
uint8_t get_temperature() { return this->temperature_; }
|
||||
|
||||
void block_partial() { this->block_partial_ = true; }
|
||||
|
||||
protected:
|
||||
void draw_absolute_pixel_internal(int x, int y, Color color) override;
|
||||
void display1b_();
|
||||
@@ -99,10 +97,10 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
|
||||
void hscan_start_(uint32_t d);
|
||||
void vscan_end_();
|
||||
void vscan_start_();
|
||||
void vscan_write_();
|
||||
|
||||
void eink_off_();
|
||||
void eink_on_();
|
||||
bool read_power_status_();
|
||||
|
||||
void setup_pins_();
|
||||
void pins_z_state_();
|
||||
@@ -113,6 +111,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
|
||||
return 800;
|
||||
} else if (this->model_ == INKPLATE_10) {
|
||||
return 1200;
|
||||
} else if (this->model_ == INKPLATE_6_PLUS) {
|
||||
return 1024;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -122,6 +122,8 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
|
||||
return 600;
|
||||
} else if (this->model_ == INKPLATE_10) {
|
||||
return 825;
|
||||
} else if (this->model_ == INKPLATE_6_PLUS) {
|
||||
return 758;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
@@ -141,16 +143,20 @@ class Inkplate6 : public PollingComponent, public display::DisplayBuffer, public
|
||||
return data;
|
||||
}
|
||||
|
||||
uint8_t panel_on_ = 0;
|
||||
bool panel_on_{false};
|
||||
uint8_t temperature_;
|
||||
|
||||
uint8_t *partial_buffer_{nullptr};
|
||||
uint8_t *partial_buffer_2_{nullptr};
|
||||
|
||||
uint32_t *glut_{nullptr};
|
||||
uint32_t *glut2_{nullptr};
|
||||
uint32_t pin_lut_[256];
|
||||
|
||||
uint32_t full_update_every_;
|
||||
uint32_t partial_updates_{0};
|
||||
|
||||
bool block_partial_;
|
||||
bool block_partial_{true};
|
||||
bool greyscale_;
|
||||
bool partial_updating_;
|
||||
|
||||
|
||||
3
esphome/components/lilygo_t5_47/__init__.py
Normal file
3
esphome/components/lilygo_t5_47/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
lilygo_t5_47_ns = cg.esphome_ns.namespace("lilygo_t5_47")
|
||||
45
esphome/components/lilygo_t5_47/touchscreen/__init__.py
Normal file
45
esphome/components/lilygo_t5_47/touchscreen/__init__.py
Normal file
@@ -0,0 +1,45 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
|
||||
from esphome import pins
|
||||
from esphome.components import i2c, touchscreen
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from .. import lilygo_t5_47_ns
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
LilygoT547Touchscreen = lilygo_t5_47_ns.class_(
|
||||
"LilygoT547Touchscreen",
|
||||
touchscreen.Touchscreen,
|
||||
cg.Component,
|
||||
i2c.I2CDevice,
|
||||
)
|
||||
|
||||
CONF_LILYGO_T5_47_TOUCHSCREEN_ID = "lilygo_t5_47_touchscreen_id"
|
||||
CONF_INTERRUPT_PIN = "interrupt_pin"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = touchscreen.TOUCHSCREEN_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(LilygoT547Touchscreen),
|
||||
cv.Required(CONF_INTERRUPT_PIN): cv.All(
|
||||
pins.internal_gpio_input_pin_schema
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(i2c.i2c_device_schema(0x5A))
|
||||
.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)
|
||||
await touchscreen.register_touchscreen(var, config)
|
||||
|
||||
interrupt_pin = await cg.gpio_pin_expression(config[CONF_INTERRUPT_PIN])
|
||||
cg.add(var.set_interrupt_pin(interrupt_pin))
|
||||
@@ -0,0 +1,141 @@
|
||||
#include "lilygo_t5_47_touchscreen.h"
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lilygo_t5_47 {
|
||||
|
||||
static const char *const TAG = "lilygo_t5_47.touchscreen";
|
||||
|
||||
static const uint8_t POWER_REGISTER = 0xD6;
|
||||
static const uint8_t TOUCH_REGISTER = 0xD0;
|
||||
|
||||
static const uint8_t WAKEUP_CMD[1] = {0x06};
|
||||
static const uint8_t READ_FLAGS[1] = {0x00};
|
||||
static const uint8_t CLEAR_FLAGS[2] = {0x00, 0xAB};
|
||||
static const uint8_t READ_TOUCH[1] = {0x07};
|
||||
|
||||
#define ERROR_CHECK(err) \
|
||||
if ((err) != i2c::ERROR_OK) { \
|
||||
ESP_LOGE(TAG, "Failed to communicate!"); \
|
||||
this->status_set_warning(); \
|
||||
return; \
|
||||
}
|
||||
|
||||
void Store::gpio_intr(Store *store) { store->touch = true; }
|
||||
|
||||
void LilygoT547Touchscreen::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up Lilygo T5 4.7 Touchscreen...");
|
||||
this->interrupt_pin_->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP);
|
||||
this->interrupt_pin_->setup();
|
||||
|
||||
this->store_.pin = this->interrupt_pin_->to_isr();
|
||||
this->interrupt_pin_->attach_interrupt(Store::gpio_intr, &this->store_, gpio::INTERRUPT_FALLING_EDGE);
|
||||
|
||||
if (this->write(nullptr, 0) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to communicate!");
|
||||
this->interrupt_pin_->detach_interrupt();
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->write_register(POWER_REGISTER, WAKEUP_CMD, 1);
|
||||
}
|
||||
|
||||
void LilygoT547Touchscreen::loop() {
|
||||
if (!this->store_.touch) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
}
|
||||
this->store_.touch = false;
|
||||
|
||||
uint8_t point = 0;
|
||||
uint8_t buffer[40] = {0};
|
||||
uint32_t sum_l = 0, sum_h = 0;
|
||||
|
||||
i2c::ErrorCode err;
|
||||
err = this->write_register(TOUCH_REGISTER, READ_FLAGS, 1);
|
||||
ERROR_CHECK(err);
|
||||
|
||||
err = this->read(buffer, 7);
|
||||
ERROR_CHECK(err);
|
||||
|
||||
if (buffer[0] == 0xAB) {
|
||||
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
|
||||
return;
|
||||
}
|
||||
|
||||
point = buffer[5] & 0xF;
|
||||
|
||||
if (point == 0) {
|
||||
for (auto *listener : this->touch_listeners_)
|
||||
listener->release();
|
||||
return;
|
||||
} else if (point == 1) {
|
||||
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(&buffer[5], 2);
|
||||
ERROR_CHECK(err);
|
||||
|
||||
sum_l = buffer[5] << 8 | buffer[6];
|
||||
} else if (point > 1) {
|
||||
err = this->write_register(TOUCH_REGISTER, READ_TOUCH, 1);
|
||||
ERROR_CHECK(err);
|
||||
err = this->read(&buffer[5], 5 * (point - 1) + 3);
|
||||
ERROR_CHECK(err);
|
||||
|
||||
sum_l = buffer[5 * point + 1] << 8 | buffer[5 * point + 2];
|
||||
}
|
||||
|
||||
this->write_register(TOUCH_REGISTER, CLEAR_FLAGS, 2);
|
||||
|
||||
for (int i = 0; i < 5 * point; i++)
|
||||
sum_h += buffer[i];
|
||||
|
||||
if (sum_l != sum_h)
|
||||
point = 0;
|
||||
|
||||
if (point) {
|
||||
uint8_t offset;
|
||||
for (int i = 0; i < point; i++) {
|
||||
if (i == 0) {
|
||||
offset = 0;
|
||||
} else {
|
||||
offset = 4;
|
||||
}
|
||||
|
||||
TouchPoint tp;
|
||||
|
||||
tp.id = (buffer[i * 5 + offset] >> 4) & 0x0F;
|
||||
tp.state = buffer[i * 5 + offset] & 0x0F;
|
||||
if (tp.state == 0x06)
|
||||
tp.state = 0x07;
|
||||
|
||||
tp.y = (uint16_t)((buffer[i * 5 + 1 + offset] << 4) | ((buffer[i * 5 + 3 + offset] >> 4) & 0x0F));
|
||||
tp.x = (uint16_t)((buffer[i * 5 + 2 + offset] << 4) | (buffer[i * 5 + 3 + offset] & 0x0F));
|
||||
|
||||
this->defer([this, tp]() { this->send_touch_(tp); });
|
||||
}
|
||||
} else {
|
||||
TouchPoint tp;
|
||||
tp.id = (buffer[0] >> 4) & 0x0F;
|
||||
tp.state = 0x06;
|
||||
tp.y = (uint16_t)((buffer[0 * 5 + 1] << 4) | ((buffer[0 * 5 + 3] >> 4) & 0x0F));
|
||||
tp.x = (uint16_t)((buffer[0 * 5 + 2] << 4) | (buffer[0 * 5 + 3] & 0x0F));
|
||||
|
||||
this->defer([this, tp]() { this->send_touch_(tp); });
|
||||
}
|
||||
|
||||
this->status_clear_warning();
|
||||
}
|
||||
|
||||
void LilygoT547Touchscreen::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Lilygo T5 47 Touchscreen:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
LOG_PIN(" Interrupt Pin: ", this->interrupt_pin_);
|
||||
}
|
||||
|
||||
} // namespace lilygo_t5_47
|
||||
} // namespace esphome
|
||||
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lilygo_t5_47 {
|
||||
|
||||
struct Store {
|
||||
volatile bool touch;
|
||||
ISRInternalGPIOPin pin;
|
||||
|
||||
static void gpio_intr(Store *store);
|
||||
};
|
||||
|
||||
using namespace touchscreen;
|
||||
|
||||
class LilygoT547Touchscreen : public Touchscreen, public Component, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_interrupt_pin(InternalGPIOPin *pin) { this->interrupt_pin_ = pin; }
|
||||
|
||||
protected:
|
||||
InternalGPIOPin *interrupt_pin_;
|
||||
Store store_;
|
||||
};
|
||||
|
||||
} // namespace lilygo_t5_47
|
||||
} // namespace esphome
|
||||
102
esphome/components/lock/__init__.py
Normal file
102
esphome/components/lock/__init__.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_ON_LOCK,
|
||||
CONF_ON_UNLOCK,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MQTT_ID,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
|
||||
lock_ns = cg.esphome_ns.namespace("lock")
|
||||
Lock = lock_ns.class_("Lock", cg.EntityBase)
|
||||
LockPtr = Lock.operator("ptr")
|
||||
LockCall = lock_ns.class_("LockCall")
|
||||
|
||||
UnlockAction = lock_ns.class_("UnlockAction", automation.Action)
|
||||
LockAction = lock_ns.class_("LockAction", automation.Action)
|
||||
OpenAction = lock_ns.class_("OpenAction", automation.Action)
|
||||
LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action)
|
||||
|
||||
LockCondition = lock_ns.class_("LockCondition", Condition)
|
||||
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
|
||||
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
|
||||
|
||||
LOCK_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
|
||||
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def setup_lock_core_(var, config):
|
||||
await setup_entity(var, config)
|
||||
|
||||
for conf in config.get(CONF_ON_LOCK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
for conf in config.get(CONF_ON_UNLOCK, []):
|
||||
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||
await automation.build_automation(trigger, [], conf)
|
||||
|
||||
if CONF_MQTT_ID in config:
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
|
||||
async def register_lock(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
var = cg.Pvariable(config[CONF_ID], var)
|
||||
cg.add(cg.App.register_lock(var))
|
||||
await setup_lock_core_(var, config)
|
||||
|
||||
|
||||
LOCK_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(Lock),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action("lock.unlock", UnlockAction, LOCK_ACTION_SCHEMA)
|
||||
@automation.register_action("lock.lock", LockAction, LOCK_ACTION_SCHEMA)
|
||||
@automation.register_action("lock.open", OpenAction, LOCK_ACTION_SCHEMA)
|
||||
async def lock_action_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
|
||||
|
||||
@automation.register_condition("lock.is_locked", LockCondition, LOCK_ACTION_SCHEMA)
|
||||
async def lock_is_on_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, True)
|
||||
|
||||
|
||||
@automation.register_condition("lock.is_unlocked", LockCondition, LOCK_ACTION_SCHEMA)
|
||||
async def lock_is_off_to_code(config, condition_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(condition_id, template_arg, paren, False)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
async def to_code(config):
|
||||
cg.add_global(lock_ns.using)
|
||||
cg.add_define("USE_LOCK")
|
||||
87
esphome/components/lock/automation.h
Normal file
87
esphome/components/lock/automation.h
Normal file
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/lock/lock.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lock {
|
||||
|
||||
template<typename... Ts> class LockAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit LockAction(Lock *a_lock) : lock_(a_lock) {}
|
||||
|
||||
void play(Ts... x) override { this->lock_->lock(); }
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class UnlockAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit UnlockAction(Lock *a_lock) : lock_(a_lock) {}
|
||||
|
||||
void play(Ts... x) override { this->lock_->unlock(); }
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class OpenAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit OpenAction(Lock *a_lock) : lock_(a_lock) {}
|
||||
|
||||
void play(Ts... x) override { this->lock_->open(); }
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
template<typename... Ts> class LockCondition : public Condition<Ts...> {
|
||||
public:
|
||||
LockCondition(Lock *parent, bool state) : parent_(parent), state_(state) {}
|
||||
bool check(Ts... x) override {
|
||||
auto check_state = this->state_ ? LockState::LOCK_STATE_LOCKED : LockState::LOCK_STATE_UNLOCKED;
|
||||
return this->parent_->state == check_state;
|
||||
}
|
||||
|
||||
protected:
|
||||
Lock *parent_;
|
||||
bool state_;
|
||||
};
|
||||
|
||||
class LockLockTrigger : public Trigger<> {
|
||||
public:
|
||||
LockLockTrigger(Lock *a_lock) {
|
||||
a_lock->add_on_state_callback([this, a_lock]() {
|
||||
if (a_lock->state == LockState::LOCK_STATE_LOCKED) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
class LockUnlockTrigger : public Trigger<> {
|
||||
public:
|
||||
LockUnlockTrigger(Lock *a_lock) {
|
||||
a_lock->add_on_state_callback([this, a_lock]() {
|
||||
if (a_lock->state == LockState::LOCK_STATE_UNLOCKED) {
|
||||
this->trigger();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template<typename... Ts> class LockPublishAction : public Action<Ts...> {
|
||||
public:
|
||||
LockPublishAction(Lock *a_lock) : lock_(a_lock) {}
|
||||
TEMPLATABLE_VALUE(LockState, state)
|
||||
|
||||
void play(Ts... x) override { this->lock_->publish_state(this->state_.value(x...)); }
|
||||
|
||||
protected:
|
||||
Lock *lock_;
|
||||
};
|
||||
|
||||
} // namespace lock
|
||||
} // namespace esphome
|
||||
109
esphome/components/lock/lock.cpp
Normal file
109
esphome/components/lock/lock.cpp
Normal file
@@ -0,0 +1,109 @@
|
||||
#include "lock.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace lock {
|
||||
|
||||
static const char *const TAG = "lock";
|
||||
|
||||
const char *lock_state_to_string(LockState state) {
|
||||
switch (state) {
|
||||
case LOCK_STATE_LOCKED:
|
||||
return "LOCKED";
|
||||
case LOCK_STATE_UNLOCKED:
|
||||
return "UNLOCKED";
|
||||
case LOCK_STATE_JAMMED:
|
||||
return "JAMMED";
|
||||
case LOCK_STATE_LOCKING:
|
||||
return "LOCKING";
|
||||
case LOCK_STATE_UNLOCKING:
|
||||
return "UNLOCKING";
|
||||
case LOCK_STATE_NONE:
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
Lock::Lock(const std::string &name) : EntityBase(name), state(LOCK_STATE_NONE) {}
|
||||
Lock::Lock() : Lock("") {}
|
||||
LockCall Lock::make_call() { return LockCall(this); }
|
||||
|
||||
void Lock::lock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(LOCK_STATE_LOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
void Lock::unlock() {
|
||||
auto call = this->make_call();
|
||||
call.set_state(LOCK_STATE_UNLOCKED);
|
||||
this->control(call);
|
||||
}
|
||||
void Lock::open() {
|
||||
if (traits.get_supports_open()) {
|
||||
ESP_LOGD(TAG, "'%s' Opening.", this->get_name().c_str());
|
||||
this->open_latch();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' Does not support Open.", this->get_name().c_str());
|
||||
}
|
||||
}
|
||||
void Lock::publish_state(LockState state) {
|
||||
if (!this->publish_dedup_.next(state))
|
||||
return;
|
||||
|
||||
this->state = state;
|
||||
this->rtc_.save(&this->state);
|
||||
ESP_LOGD(TAG, "'%s': Sending state %s", this->name_.c_str(), lock_state_to_string(state));
|
||||
this->state_callback_.call();
|
||||
}
|
||||
|
||||
void Lock::add_on_state_callback(std::function<void()> &&callback) { this->state_callback_.add(std::move(callback)); }
|
||||
uint32_t Lock::hash_base() { return 856245656UL; }
|
||||
|
||||
void LockCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
this->validate_();
|
||||
if (this->state_.has_value()) {
|
||||
const char *state_s = lock_state_to_string(*this->state_);
|
||||
ESP_LOGD(TAG, " State: %s", state_s);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
void LockCall::validate_() {
|
||||
if (this->state_.has_value()) {
|
||||
auto state = *this->state_;
|
||||
if (!this->parent_->traits.supports_state(state)) {
|
||||
ESP_LOGW(TAG, " State %s is not supported by this device!", lock_state_to_string(*this->state_));
|
||||
this->state_.reset();
|
||||
}
|
||||
}
|
||||
}
|
||||
LockCall &LockCall::set_state(LockState state) {
|
||||
this->state_ = state;
|
||||
return *this;
|
||||
}
|
||||
LockCall &LockCall::set_state(optional<LockState> state) {
|
||||
this->state_ = state;
|
||||
return *this;
|
||||
}
|
||||
LockCall &LockCall::set_state(const std::string &state) {
|
||||
if (str_equals_case_insensitive(state, "LOCKED")) {
|
||||
this->set_state(LOCK_STATE_LOCKED);
|
||||
} else if (str_equals_case_insensitive(state, "UNLOCKED")) {
|
||||
this->set_state(LOCK_STATE_UNLOCKED);
|
||||
} else if (str_equals_case_insensitive(state, "JAMMED")) {
|
||||
this->set_state(LOCK_STATE_JAMMED);
|
||||
} else if (str_equals_case_insensitive(state, "LOCKING")) {
|
||||
this->set_state(LOCK_STATE_LOCKING);
|
||||
} else if (str_equals_case_insensitive(state, "UNLOCKING")) {
|
||||
this->set_state(LOCK_STATE_UNLOCKING);
|
||||
} else if (str_equals_case_insensitive(state, "NONE")) {
|
||||
this->set_state(LOCK_STATE_NONE);
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s' - Unrecognized state %s", this->parent_->get_name().c_str(), state.c_str());
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
const optional<LockState> &LockCall::get_state() const { return this->state_; }
|
||||
|
||||
} // namespace lock
|
||||
} // namespace esphome
|
||||
178
esphome/components/lock/lock.h
Normal file
178
esphome/components/lock/lock.h
Normal file
@@ -0,0 +1,178 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <set>
|
||||
|
||||
namespace esphome {
|
||||
namespace lock {
|
||||
|
||||
class Lock;
|
||||
|
||||
#define LOG_LOCK(prefix, type, obj) \
|
||||
if ((obj) != nullptr) { \
|
||||
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
|
||||
if (!(obj)->get_icon().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
|
||||
} \
|
||||
if ((obj)->traits.get_assumed_state()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
|
||||
} \
|
||||
}
|
||||
/// Enum for all states a lock can be in.
|
||||
enum LockState : uint8_t {
|
||||
LOCK_STATE_NONE = 0,
|
||||
LOCK_STATE_LOCKED = 1,
|
||||
LOCK_STATE_UNLOCKED = 2,
|
||||
LOCK_STATE_JAMMED = 3,
|
||||
LOCK_STATE_LOCKING = 4,
|
||||
LOCK_STATE_UNLOCKING = 5
|
||||
};
|
||||
const char *lock_state_to_string(LockState state);
|
||||
|
||||
class LockTraits {
|
||||
public:
|
||||
LockTraits() = default;
|
||||
|
||||
bool get_supports_open() const { return this->supports_open_; }
|
||||
void set_supports_open(bool supports_open) { this->supports_open_ = supports_open; }
|
||||
bool get_requires_code() const { return this->requires_code_; }
|
||||
void set_requires_code(bool requires_code) { this->requires_code_ = requires_code; }
|
||||
bool get_assumed_state() const { return this->assumed_state_; }
|
||||
void set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
|
||||
|
||||
bool supports_state(LockState state) const { return supported_states_.count(state); }
|
||||
std::set<LockState> get_supported_states() const { return supported_states_; }
|
||||
void set_supported_states(std::set<LockState> states) { supported_states_ = std::move(states); }
|
||||
void add_supported_state(LockState state) { supported_states_.insert(state); }
|
||||
|
||||
protected:
|
||||
bool supports_open_{false};
|
||||
bool requires_code_{false};
|
||||
bool assumed_state_{false};
|
||||
std::set<LockState> supported_states_ = {LOCK_STATE_NONE, LOCK_STATE_LOCKED, LOCK_STATE_UNLOCKED};
|
||||
};
|
||||
|
||||
/** This class is used to encode all control actions on a lock device.
|
||||
*
|
||||
* It is supposed to be used by all code that wishes to control a lock device (mqtt, api, lambda etc).
|
||||
* Create an instance of this class by calling `id(lock_device).make_call();`. Then set all attributes
|
||||
* with the `set_x` methods. Finally, to apply the changes call `.perform();`.
|
||||
*
|
||||
* The integration that implements the lock device receives this instance with the `control` method.
|
||||
* It should check all the properties it implements and apply them as needed. It should do so by
|
||||
* getting all properties it controls with the getter methods in this class. If the optional value is
|
||||
* set (check with `.has_value()`) that means the user wants to control this property. Get the value
|
||||
* of the optional with the star operator (`*call.get_state()`) and apply it.
|
||||
*/
|
||||
class LockCall {
|
||||
public:
|
||||
LockCall(Lock *parent) : parent_(parent) {}
|
||||
|
||||
/// Set the state of the lock device.
|
||||
LockCall &set_state(LockState state);
|
||||
/// Set the state of the lock device.
|
||||
LockCall &set_state(optional<LockState> state);
|
||||
/// Set the state of the lock device based on a string.
|
||||
LockCall &set_state(const std::string &state);
|
||||
|
||||
void perform();
|
||||
|
||||
const optional<LockState> &get_state() const;
|
||||
|
||||
protected:
|
||||
void validate_();
|
||||
|
||||
Lock *const parent_;
|
||||
optional<LockState> state_;
|
||||
};
|
||||
|
||||
/** Base class for all locks.
|
||||
*
|
||||
* A lock is basically a switch with a combination of a binary sensor (for reporting lock values)
|
||||
* and a write_state method that writes a state to the hardware. Locks can also have an "open"
|
||||
* method to unlatch.
|
||||
*
|
||||
* For integrations: Integrations must implement the method control().
|
||||
* Control will be called with the arguments supplied by the user and should be used
|
||||
* to control all values of the lock.
|
||||
*/
|
||||
class Lock : public EntityBase {
|
||||
public:
|
||||
explicit Lock();
|
||||
explicit Lock(const std::string &name);
|
||||
|
||||
/** Make a lock device control call, this is used to control the lock device, see the LockCall description
|
||||
* for more info.
|
||||
* @return A new LockCall instance targeting this lock device.
|
||||
*/
|
||||
LockCall make_call();
|
||||
|
||||
/** Publish a state to the front-end from the back-end.
|
||||
*
|
||||
* Then the internal value member is set and finally the callbacks are called.
|
||||
*
|
||||
* @param state The new state.
|
||||
*/
|
||||
void publish_state(LockState state);
|
||||
|
||||
/// The current reported state of the lock.
|
||||
LockState state{LOCK_STATE_NONE};
|
||||
|
||||
LockTraits traits;
|
||||
|
||||
/** Turn this lock on. This is called by the front-end.
|
||||
*
|
||||
* For implementing locks, please override control.
|
||||
*/
|
||||
void lock();
|
||||
/** Turn this lock off. This is called by the front-end.
|
||||
*
|
||||
* For implementing locks, please override control.
|
||||
*/
|
||||
void unlock();
|
||||
/** Open (unlatch) this lock. This is called by the front-end.
|
||||
*
|
||||
* For implementing locks, please override control.
|
||||
*/
|
||||
void open();
|
||||
|
||||
/** Set callback for state changes.
|
||||
*
|
||||
* @param callback The void(bool) callback.
|
||||
*/
|
||||
void add_on_state_callback(std::function<void()> &&callback);
|
||||
|
||||
protected:
|
||||
friend LockCall;
|
||||
|
||||
/** Perform the open latch action with hardware. This method is optional to implement
|
||||
* when creating a new lock.
|
||||
*
|
||||
* In the implementation of this method, it is recommended you also call
|
||||
* publish_state with "unlock" to acknowledge that the state was written to the hardware.
|
||||
*/
|
||||
virtual void open_latch() { unlock(); };
|
||||
|
||||
/** Control the lock device, this is a virtual method that each lock integration must implement.
|
||||
*
|
||||
* See more info in LockCall. The integration should check all of its values in this method and
|
||||
* set them accordingly. At the end of the call, the integration must call `publish_state()` to
|
||||
* notify the frontend of a changed state.
|
||||
*
|
||||
* @param call The LockCall instance encoding all attribute changes.
|
||||
*/
|
||||
virtual void control(const LockCall &call) = 0;
|
||||
|
||||
uint32_t hash_base() override;
|
||||
|
||||
CallbackManager<void()> state_callback_{};
|
||||
Deduplicator<LockState> publish_dedup_;
|
||||
ESPPreferenceObject rtc_;
|
||||
};
|
||||
|
||||
} // namespace lock
|
||||
} // namespace esphome
|
||||
1
esphome/components/max9611/__init__.py
Normal file
1
esphome/components/max9611/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@mckaymatthew"]
|
||||
93
esphome/components/max9611/max9611.cpp
Normal file
93
esphome/components/max9611/max9611.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "max9611.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/i2c/i2c_bus.h"
|
||||
namespace esphome {
|
||||
namespace max9611 {
|
||||
using namespace esphome::i2c;
|
||||
// Sign extend
|
||||
// http://graphics.stanford.edu/~seander/bithacks.html#FixedSignExtend
|
||||
template<typename T, unsigned B> inline T signextend(const T x) {
|
||||
struct {
|
||||
T x : B;
|
||||
} s;
|
||||
return s.x = x;
|
||||
}
|
||||
// Map the gain register to in uV/LSB
|
||||
float gain_to_lsb(MAX9611Multiplexer gain) {
|
||||
float lsb = 0.0;
|
||||
if (gain == MAX9611_MULTIPLEXER_CSA_GAIN1) {
|
||||
lsb = 107.50;
|
||||
} else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN4) {
|
||||
lsb = 26.88;
|
||||
} else if (gain == MAX9611_MULTIPLEXER_CSA_GAIN8) {
|
||||
lsb = 13.44;
|
||||
}
|
||||
return lsb;
|
||||
}
|
||||
static const char *const TAG = "max9611";
|
||||
static const uint8_t SETUP_DELAY = 4; // Wait 2 integration periods.
|
||||
static const float VOUT_LSB = 14.0 / 1000.0; // 14mV/LSB
|
||||
static const float TEMP_LSB = 0.48; // 0.48C/LSB
|
||||
static const float MICRO_VOLTS_PER_VOLT = 1000000.0;
|
||||
void MAX9611Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up max9611...");
|
||||
// Perform dummy-read
|
||||
uint8_t value;
|
||||
this->read(&value, 1);
|
||||
// Configuration Stage.
|
||||
// First send an integration request with the specified gain
|
||||
const uint8_t setup_dat[] = {CONTROL_REGISTER_1_ADRR, static_cast<uint8_t>(gain_)};
|
||||
// Then send a request that samples all channels as fast as possible, using the last provided gain
|
||||
const uint8_t fast_mode_dat[] = {CONTROL_REGISTER_1_ADRR, MAX9611Multiplexer::MAX9611_MULTIPLEXER_FAST_MODE};
|
||||
|
||||
if (this->write(reinterpret_cast<const uint8_t *>(&setup_dat), sizeof(setup_dat)) != ErrorCode::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to setup Max9611 during GAIN SET");
|
||||
return;
|
||||
}
|
||||
delay(SETUP_DELAY);
|
||||
if (this->write(reinterpret_cast<const uint8_t *>(&fast_mode_dat), sizeof(fast_mode_dat)) != ErrorCode::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Failed to setup Max9611 during FAST MODE SET");
|
||||
return;
|
||||
}
|
||||
}
|
||||
void MAX9611Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Dump Config max9611...");
|
||||
ESP_LOGCONFIG(TAG, " CSA Gain Register: %x", gain_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
}
|
||||
void MAX9611Component::update() {
|
||||
// Setup read from 0x0 register base
|
||||
const uint8_t reg_base = 0x0;
|
||||
const ErrorCode write_result = this->write(®_base, 1);
|
||||
// Just read the entire register map in a bulk read, faster than individually querying register.
|
||||
const ErrorCode read_result = this->read(register_map_, sizeof(register_map_));
|
||||
if (write_result != ErrorCode::ERROR_OK || read_result != ErrorCode::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "MAX9611 Update FAILED!");
|
||||
return;
|
||||
}
|
||||
uint16_t csa_register = ((register_map_[CSA_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[CSA_DATA_BYTE_LSB_ADRR])) >> 4;
|
||||
uint16_t rs_register = ((register_map_[RS_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[RS_DATA_BYTE_LSB_ADRR])) >> 4;
|
||||
uint16_t t_register = ((register_map_[TEMP_DATA_BYTE_MSB_ADRR] << 8) | (register_map_[TEMP_DATA_BYTE_LSB_ADRR])) >> 7;
|
||||
float voltage = rs_register * VOUT_LSB;
|
||||
float shunt_voltage = (csa_register * gain_to_lsb(gain_)) / MICRO_VOLTS_PER_VOLT;
|
||||
float temp = signextend<signed int, 9>(t_register) * TEMP_LSB;
|
||||
float amps = shunt_voltage / current_resistor_;
|
||||
float watts = amps * voltage;
|
||||
|
||||
if (voltage_sensor_ != nullptr) {
|
||||
voltage_sensor_->publish_state(voltage);
|
||||
}
|
||||
if (current_sensor_ != nullptr) {
|
||||
current_sensor_->publish_state(amps);
|
||||
}
|
||||
if (watt_sensor_ != nullptr) {
|
||||
watt_sensor_->publish_state(watts);
|
||||
}
|
||||
if (temperature_sensor_ != nullptr) {
|
||||
temperature_sensor_->publish_state(temp);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "V: %f, A: %f, W: %f, Deg C: %f", voltage, amps, watts, temp);
|
||||
}
|
||||
} // namespace max9611
|
||||
} // namespace esphome
|
||||
62
esphome/components/max9611/max9611.h
Normal file
62
esphome/components/max9611/max9611.h
Normal file
@@ -0,0 +1,62 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max9611 {
|
||||
|
||||
enum MAX9611Multiplexer {
|
||||
MAX9611_MULTIPLEXER_CSA_GAIN1 = 0b000,
|
||||
MAX9611_MULTIPLEXER_CSA_GAIN4 = 0b001,
|
||||
MAX9611_MULTIPLEXER_CSA_GAIN8 = 0b010,
|
||||
MAX9611_MULTIPLEXER_RS = 0b011,
|
||||
MAX9611_MULTIPLEXER_OUT = 0b100,
|
||||
MAX9611_MULTIPLEXER_SET = 0b101,
|
||||
MAX9611_MULTIPLEXER_TEMP = 0b110,
|
||||
MAX9611_MULTIPLEXER_FAST_MODE = 0b111,
|
||||
};
|
||||
|
||||
enum MAX9611RegisterMap {
|
||||
CSA_DATA_BYTE_MSB_ADRR = 0x00,
|
||||
CSA_DATA_BYTE_LSB_ADRR = 0x01,
|
||||
RS_DATA_BYTE_MSB_ADRR = 0x02,
|
||||
RS_DATA_BYTE_LSB_ADRR = 0x03,
|
||||
OUT_DATA_BYTE_MSB_ADRR = 0x04, // Unused Op-Amp
|
||||
OUT_DATA_BYTE_LSB_ADRR = 0x05, // Unused Op-Amp
|
||||
SET_DATA_BYTE_MSB_ADRR = 0x06, // Unused Op-Amp
|
||||
SET_DATA_BYTE_LSB_ADRR = 0x07, // Unused Op-Amp
|
||||
TEMP_DATA_BYTE_MSB_ADRR = 0x08,
|
||||
TEMP_DATA_BYTE_LSB_ADRR = 0x09,
|
||||
CONTROL_REGISTER_1_ADRR = 0x0A,
|
||||
CONTROL_REGISTER_2_ADRR = 0x0B,
|
||||
};
|
||||
|
||||
class MAX9611Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void update() override;
|
||||
void set_voltage_sensor(sensor::Sensor *vs) { voltage_sensor_ = vs; }
|
||||
void set_current_sensor(sensor::Sensor *cs) { current_sensor_ = cs; }
|
||||
void set_watt_sensor(sensor::Sensor *ws) { watt_sensor_ = ws; }
|
||||
void set_temp_sensor(sensor::Sensor *ts) { temperature_sensor_ = ts; }
|
||||
|
||||
void set_current_resistor(float r) { current_resistor_ = r; }
|
||||
void set_gain(MAX9611Multiplexer g) { gain_ = g; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *current_sensor_{nullptr};
|
||||
sensor::Sensor *watt_sensor_{nullptr};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
float current_resistor_;
|
||||
uint8_t register_map_[0x0C];
|
||||
MAX9611Multiplexer gain_;
|
||||
};
|
||||
|
||||
} // namespace max9611
|
||||
} // namespace esphome
|
||||
92
esphome/components/max9611/sensor.py
Normal file
92
esphome/components/max9611/sensor.py
Normal file
@@ -0,0 +1,92 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_SHUNT_RESISTANCE,
|
||||
CONF_GAIN,
|
||||
CONF_VOLTAGE,
|
||||
CONF_CURRENT,
|
||||
CONF_POWER,
|
||||
CONF_TEMPERATURE,
|
||||
UNIT_VOLT,
|
||||
UNIT_AMPERE,
|
||||
UNIT_WATT,
|
||||
UNIT_CELSIUS,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
DEVICE_CLASS_CURRENT,
|
||||
DEVICE_CLASS_POWER,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
max9611_ns = cg.esphome_ns.namespace("max9611")
|
||||
max9611Gain = max9611_ns.enum("MAX9611Multiplexer")
|
||||
MAX9611_GAIN = {
|
||||
"8X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN8,
|
||||
"4X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN4,
|
||||
"1X": max9611Gain.MAX9611_MULTIPLEXER_CSA_GAIN1,
|
||||
}
|
||||
MAX9611Component = max9611_ns.class_(
|
||||
"MAX9611Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MAX9611Component),
|
||||
cv.Required(CONF_SHUNT_RESISTANCE): cv.resistance,
|
||||
cv.Required(CONF_GAIN): cv.enum(MAX9611_GAIN, upper=True),
|
||||
cv.Optional(CONF_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_CURRENT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_AMPERE,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_CURRENT,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_POWER): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_POWER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x70))
|
||||
)
|
||||
|
||||
|
||||
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_current_resistor(config[CONF_SHUNT_RESISTANCE]))
|
||||
cg.add(var.set_gain(config[CONF_GAIN]))
|
||||
if CONF_VOLTAGE in config:
|
||||
conf = config[CONF_VOLTAGE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
if CONF_CURRENT in config:
|
||||
conf = config[CONF_CURRENT]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_current_sensor(sens))
|
||||
if CONF_POWER in config:
|
||||
conf = config[CONF_POWER]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_watt_sensor(sens))
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_temp_sensor(sens))
|
||||
@@ -1,7 +1,7 @@
|
||||
from esphome.const import CONF_ID
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.core import CORE
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
DEPENDENCIES = ["network"]
|
||||
@@ -29,6 +29,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(55.0)
|
||||
async def to_code(config):
|
||||
if CORE.using_arduino:
|
||||
if CORE.is_esp32:
|
||||
|
||||
1
esphome/components/mlx90393/__init__.py
Normal file
1
esphome/components/mlx90393/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@functionpointer"]
|
||||
135
esphome/components/mlx90393/sensor.py
Normal file
135
esphome/components/mlx90393/sensor.py
Normal file
@@ -0,0 +1,135 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
UNIT_MICROTESLA,
|
||||
UNIT_CELSIUS,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
ICON_MAGNET,
|
||||
ICON_THERMOMETER,
|
||||
CONF_GAIN,
|
||||
CONF_RESOLUTION,
|
||||
CONF_OVERSAMPLING,
|
||||
CONF_FILTER,
|
||||
CONF_TEMPERATURE,
|
||||
)
|
||||
from esphome import pins
|
||||
|
||||
CODEOWNERS = ["@functionpointer"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
mlx90393_ns = cg.esphome_ns.namespace("mlx90393")
|
||||
|
||||
MLX90393Component = mlx90393_ns.class_(
|
||||
"MLX90393Cls", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
GAIN = {
|
||||
"1X": 7,
|
||||
"1_33X": 6,
|
||||
"1_67X": 5,
|
||||
"2X": 4,
|
||||
"2_5X": 3,
|
||||
"3X": 2,
|
||||
"4X": 1,
|
||||
"5X": 0,
|
||||
}
|
||||
|
||||
RESOLUTION = {
|
||||
"16BIT": 0,
|
||||
"17BIT": 1,
|
||||
"18BIT": 2,
|
||||
"19BIT": 3,
|
||||
}
|
||||
|
||||
CONF_X_AXIS = "x_axis"
|
||||
CONF_Y_AXIS = "y_axis"
|
||||
CONF_Z_AXIS = "z_axis"
|
||||
CONF_DRDY_PIN = "drdy_pin"
|
||||
|
||||
|
||||
def mlx90393_axis_schema(default_resolution: str):
|
||||
return sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MICROTESLA,
|
||||
accuracy_decimals=0,
|
||||
icon=ICON_MAGNET,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_RESOLUTION, default=default_resolution): cv.enum(
|
||||
RESOLUTION, upper=True, space="_"
|
||||
)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MLX90393Component),
|
||||
cv.Optional(CONF_GAIN, default="2_5X"): cv.enum(
|
||||
GAIN, upper=True, space="_"
|
||||
),
|
||||
cv.Optional(CONF_DRDY_PIN): pins.gpio_input_pin_schema,
|
||||
cv.Optional(CONF_OVERSAMPLING, default=2): cv.int_range(min=0, max=3),
|
||||
cv.Optional(CONF_FILTER, default=6): cv.int_range(min=0, max=7),
|
||||
cv.Optional(CONF_X_AXIS): mlx90393_axis_schema("19BIT"),
|
||||
cv.Optional(CONF_Y_AXIS): mlx90393_axis_schema("19BIT"),
|
||||
cv.Optional(CONF_Z_AXIS): mlx90393_axis_schema("16BIT"),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
icon=ICON_THERMOMETER,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default=0): cv.int_range(
|
||||
min=0, max=3
|
||||
),
|
||||
}
|
||||
)
|
||||
),
|
||||
},
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x0C))
|
||||
)
|
||||
|
||||
|
||||
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_DRDY_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN])
|
||||
cg.add(var.set_drdy_pin(pin))
|
||||
cg.add(var.set_gain(GAIN[config[CONF_GAIN]]))
|
||||
cg.add(var.set_oversampling(config[CONF_OVERSAMPLING]))
|
||||
cg.add(var.set_filter(config[CONF_FILTER]))
|
||||
|
||||
if CONF_X_AXIS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_X_AXIS])
|
||||
cg.add(var.set_x_sensor(sens))
|
||||
cg.add(var.set_resolution(0, RESOLUTION[config[CONF_X_AXIS][CONF_RESOLUTION]]))
|
||||
if CONF_Y_AXIS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_Y_AXIS])
|
||||
cg.add(var.set_y_sensor(sens))
|
||||
cg.add(var.set_resolution(1, RESOLUTION[config[CONF_Y_AXIS][CONF_RESOLUTION]]))
|
||||
if CONF_Z_AXIS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_Z_AXIS])
|
||||
cg.add(var.set_z_sensor(sens))
|
||||
cg.add(var.set_resolution(2, RESOLUTION[config[CONF_Z_AXIS][CONF_RESOLUTION]]))
|
||||
if CONF_TEMPERATURE in config:
|
||||
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
|
||||
cg.add(var.set_t_sensor(sens))
|
||||
cg.add(var.set_t_oversampling(config[CONF_TEMPERATURE][CONF_OVERSAMPLING]))
|
||||
if CONF_DRDY_PIN in config:
|
||||
pin = await cg.gpio_pin_expression(config[CONF_DRDY_PIN])
|
||||
cg.add(var.set_drdy_gpio(pin))
|
||||
|
||||
cg.add_library("functionpointer/arduino-MLX90393", "1.0.0")
|
||||
91
esphome/components/mlx90393/sensor_mlx90393.cpp
Normal file
91
esphome/components/mlx90393/sensor_mlx90393.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "sensor_mlx90393.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mlx90393 {
|
||||
|
||||
static const char *const TAG = "mlx90393";
|
||||
|
||||
bool MLX90393Cls::transceive(const uint8_t *request, size_t request_size, uint8_t *response, size_t response_size) {
|
||||
i2c::ErrorCode e = this->write(request, request_size);
|
||||
if (e != i2c::ErrorCode::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
e = this->read(response, response_size);
|
||||
return e == i2c::ErrorCode::ERROR_OK;
|
||||
}
|
||||
|
||||
bool MLX90393Cls::has_drdy_pin() { return this->drdy_pin_ != nullptr; }
|
||||
|
||||
bool MLX90393Cls::read_drdy_pin() {
|
||||
if (this->drdy_pin_ == nullptr) {
|
||||
return false;
|
||||
} else {
|
||||
return this->drdy_pin_->digital_read();
|
||||
}
|
||||
}
|
||||
void MLX90393Cls::sleep_millis(uint32_t millis) { delay(millis); }
|
||||
void MLX90393Cls::sleep_micros(uint32_t micros) { delayMicroseconds(micros); }
|
||||
|
||||
void MLX90393Cls::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MLX90393...");
|
||||
// note the two arguments A0 and A1 which are used to construct an i2c address
|
||||
// we can hard-code these because we never actually use the constructed address
|
||||
// see the transceive function above, which uses the address from I2CComponent
|
||||
this->mlx_.begin_with_hal(this, 0, 0);
|
||||
|
||||
this->mlx_.setGainSel(this->gain_);
|
||||
|
||||
this->mlx_.setResolution(this->resolutions_[0], this->resolutions_[1], this->resolutions_[2]);
|
||||
|
||||
this->mlx_.setOverSampling(this->oversampling_);
|
||||
|
||||
this->mlx_.setDigitalFiltering(this->filter_);
|
||||
|
||||
this->mlx_.setTemperatureOverSampling(this->temperature_oversampling_);
|
||||
}
|
||||
|
||||
void MLX90393Cls::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MLX90393:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with MLX90393 failed!");
|
||||
return;
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "X Axis", this->x_sensor_);
|
||||
LOG_SENSOR(" ", "Y Axis", this->y_sensor_);
|
||||
LOG_SENSOR(" ", "Z Axis", this->z_sensor_);
|
||||
LOG_SENSOR(" ", "Temperature", this->t_sensor_);
|
||||
}
|
||||
|
||||
float MLX90393Cls::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void MLX90393Cls::update() {
|
||||
MLX90393::txyz data;
|
||||
|
||||
if (this->mlx_.readData(data) == MLX90393::STATUS_OK) {
|
||||
ESP_LOGD(TAG, "received %f %f %f", data.x, data.y, data.z);
|
||||
if (this->x_sensor_ != nullptr) {
|
||||
this->x_sensor_->publish_state(data.x);
|
||||
}
|
||||
if (this->y_sensor_ != nullptr) {
|
||||
this->y_sensor_->publish_state(data.y);
|
||||
}
|
||||
if (this->z_sensor_ != nullptr) {
|
||||
this->z_sensor_->publish_state(data.z);
|
||||
}
|
||||
if (this->t_sensor_ != nullptr) {
|
||||
this->t_sensor_->publish_state(data.t);
|
||||
}
|
||||
this->status_clear_warning();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "failed to read data");
|
||||
this->status_set_warning();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mlx90393
|
||||
} // namespace esphome
|
||||
59
esphome/components/mlx90393/sensor_mlx90393.h
Normal file
59
esphome/components/mlx90393/sensor_mlx90393.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <MLX90393.h>
|
||||
#include <MLX90393Hal.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace mlx90393 {
|
||||
|
||||
class MLX90393Cls : public PollingComponent, public i2c::I2CDevice, public MLX90393Hal {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
void set_drdy_gpio(GPIOPin *pin) { drdy_pin_ = pin; }
|
||||
|
||||
void set_x_sensor(sensor::Sensor *x_sensor) { x_sensor_ = x_sensor; }
|
||||
void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; }
|
||||
void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; }
|
||||
void set_t_sensor(sensor::Sensor *t_sensor) { t_sensor_ = t_sensor; }
|
||||
|
||||
void set_oversampling(uint8_t osr) { oversampling_ = osr; }
|
||||
void set_t_oversampling(uint8_t osr2) { temperature_oversampling_ = osr2; }
|
||||
void set_resolution(uint8_t xyz, uint8_t res) { resolutions_[xyz] = res; }
|
||||
void set_filter(uint8_t filter) { filter_ = filter; }
|
||||
void set_gain(uint8_t gain_sel) { gain_ = gain_sel; }
|
||||
|
||||
// overrides for MLX library
|
||||
|
||||
// disable lint because it keeps suggesting const uint8_t *response.
|
||||
// this->read() writes data into response, so it can't be const
|
||||
bool transceive(const uint8_t *request, size_t request_size, uint8_t *response,
|
||||
size_t response_size) override; // NOLINT
|
||||
bool has_drdy_pin() override;
|
||||
bool read_drdy_pin() override;
|
||||
void sleep_millis(uint32_t millis) override;
|
||||
void sleep_micros(uint32_t micros) override;
|
||||
|
||||
protected:
|
||||
MLX90393 mlx_;
|
||||
sensor::Sensor *x_sensor_{nullptr};
|
||||
sensor::Sensor *y_sensor_{nullptr};
|
||||
sensor::Sensor *z_sensor_{nullptr};
|
||||
sensor::Sensor *t_sensor_{nullptr};
|
||||
uint8_t gain_;
|
||||
uint8_t oversampling_;
|
||||
uint8_t temperature_oversampling_ = 0;
|
||||
uint8_t filter_;
|
||||
uint8_t resolutions_[3] = {0};
|
||||
GPIOPin *drdy_pin_ = nullptr;
|
||||
};
|
||||
|
||||
} // namespace mlx90393
|
||||
} // namespace esphome
|
||||
@@ -110,7 +110,8 @@ void ModbusController::queue_command(const ModbusCommandItem &command) {
|
||||
for (auto &item : command_queue_) {
|
||||
if (item->register_address == command.register_address && item->register_count == command.register_count &&
|
||||
item->register_type == command.register_type && item->function_code == command.function_code) {
|
||||
ESP_LOGW(TAG, "Duplicate modbus command found");
|
||||
ESP_LOGW(TAG, "Duplicate modbus command found: type=0x%x address=%u count=%u",
|
||||
static_cast<uint8_t>(command.register_type), command.register_address, command.register_count);
|
||||
// update the payload of the queued command
|
||||
// replaces a previous command
|
||||
item->payload = command.payload;
|
||||
@@ -360,8 +361,9 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_command(ModbusControl
|
||||
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
||||
};
|
||||
for (auto v : values) {
|
||||
cmd.payload.push_back((v / 256) & 0xFF);
|
||||
cmd.payload.push_back(v & 0xFF);
|
||||
auto decoded_value = decode_value(v);
|
||||
cmd.payload.push_back(decoded_value[0]);
|
||||
cmd.payload.push_back(decoded_value[1]);
|
||||
}
|
||||
return cmd;
|
||||
}
|
||||
@@ -416,7 +418,7 @@ ModbusCommandItem ModbusCommandItem::create_write_multiple_coils(ModbusControlle
|
||||
}
|
||||
|
||||
ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusController *modbusdevice, uint16_t start_address,
|
||||
int16_t value) {
|
||||
uint16_t value) {
|
||||
ModbusCommandItem cmd;
|
||||
cmd.modbusdevice = modbusdevice;
|
||||
cmd.register_type = ModbusRegisterType::HOLDING;
|
||||
@@ -427,8 +429,10 @@ ModbusCommandItem ModbusCommandItem::create_write_single_command(ModbusControlle
|
||||
const std::vector<uint8_t> &data) {
|
||||
modbusdevice->on_write_register_response(cmd.register_type, start_address, data);
|
||||
};
|
||||
cmd.payload.push_back((value / 256) & 0xFF);
|
||||
cmd.payload.push_back((value % 256) & 0xFF);
|
||||
|
||||
auto decoded_value = decode_value(value);
|
||||
cmd.payload.push_back(decoded_value[0]);
|
||||
cmd.payload.push_back(decoded_value[1]);
|
||||
return cmd;
|
||||
}
|
||||
|
||||
@@ -463,84 +467,70 @@ bool ModbusCommandItem::send() {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
|
||||
union {
|
||||
float float_value;
|
||||
uint32_t raw;
|
||||
} raw_to_float;
|
||||
|
||||
std::vector<uint16_t> data;
|
||||
int32_t val;
|
||||
|
||||
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type) {
|
||||
switch (value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
case SensorValueType::S_WORD:
|
||||
// cast truncates the float do some rounding here
|
||||
data.push_back(lroundf(value) & 0xFFFF);
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::S_DWORD:
|
||||
val = lroundf(value);
|
||||
data.push_back((val & 0xFFFF0000) >> 16);
|
||||
data.push_back(val & 0xFFFF);
|
||||
case SensorValueType::FP32:
|
||||
case SensorValueType::FP32_R:
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::S_DWORD_R:
|
||||
val = lroundf(value);
|
||||
data.push_back(val & 0xFFFF);
|
||||
data.push_back((val & 0xFFFF0000) >> 16);
|
||||
data.push_back(value & 0xFFFF);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
break;
|
||||
case SensorValueType::FP32:
|
||||
raw_to_float.float_value = value;
|
||||
data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16);
|
||||
data.push_back(raw_to_float.raw & 0xFFFF);
|
||||
case SensorValueType::U_QWORD:
|
||||
case SensorValueType::S_QWORD:
|
||||
data.push_back((value & 0xFFFF000000000000) >> 48);
|
||||
data.push_back((value & 0xFFFF00000000) >> 32);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back(value & 0xFFFF);
|
||||
break;
|
||||
case SensorValueType::FP32_R:
|
||||
raw_to_float.float_value = value;
|
||||
data.push_back(raw_to_float.raw & 0xFFFF);
|
||||
data.push_back((raw_to_float.raw & 0xFFFF0000) >> 16);
|
||||
case SensorValueType::U_QWORD_R:
|
||||
case SensorValueType::S_QWORD_R:
|
||||
data.push_back(value & 0xFFFF);
|
||||
data.push_back((value & 0xFFFF0000) >> 16);
|
||||
data.push_back((value & 0xFFFF00000000) >> 32);
|
||||
data.push_back((value & 0xFFFF000000000000) >> 48);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGE(TAG, "Invalid data type for modbus float to payload conversation");
|
||||
ESP_LOGE(TAG, "Invalid data type for modbus number to payload conversation: %d",
|
||||
static_cast<uint16_t>(value_type));
|
||||
break;
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
|
||||
uint32_t bitmask) {
|
||||
union {
|
||||
float float_value;
|
||||
uint32_t raw;
|
||||
} raw_to_float;
|
||||
|
||||
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
|
||||
uint32_t bitmask) {
|
||||
int64_t value = 0; // int64_t because it can hold signed and unsigned 32 bits
|
||||
float result = NAN;
|
||||
|
||||
switch (sensor_value_type) {
|
||||
case SensorValueType::U_WORD:
|
||||
value = mask_and_shift_by_rightbit(get_data<uint16_t>(data, offset), bitmask); // default is 0xFFFF ;
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
case SensorValueType::U_DWORD:
|
||||
case SensorValueType::FP32:
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
case SensorValueType::U_DWORD_R:
|
||||
case SensorValueType::FP32_R:
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
value = static_cast<uint32_t>(value & 0xFFFF) << 16 | (value & 0xFFFF0000) >> 16;
|
||||
value = mask_and_shift_by_rightbit((uint32_t) value, bitmask);
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
case SensorValueType::S_WORD:
|
||||
value = mask_and_shift_by_rightbit(get_data<int16_t>(data, offset),
|
||||
bitmask); // default is 0xFFFF ;
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
case SensorValueType::S_DWORD:
|
||||
value = mask_and_shift_by_rightbit(get_data<int32_t>(data, offset), bitmask);
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
case SensorValueType::S_DWORD_R: {
|
||||
value = get_data<uint32_t>(data, offset);
|
||||
@@ -549,18 +539,14 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
|
||||
uint32_t sign_bit = (value & 0x8000) << 16;
|
||||
value = mask_and_shift_by_rightbit(
|
||||
static_cast<int32_t>(((value & 0x7FFF) << 16 | (value & 0xFFFF0000) >> 16) | sign_bit), bitmask);
|
||||
result = static_cast<float>(value);
|
||||
} break;
|
||||
case SensorValueType::U_QWORD:
|
||||
// Ignore bitmask for U_QWORD
|
||||
value = get_data<uint64_t>(data, offset);
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
|
||||
case SensorValueType::S_QWORD:
|
||||
// Ignore bitmask for S_QWORD
|
||||
value = get_data<int64_t>(data, offset);
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
case SensorValueType::U_QWORD_R:
|
||||
// Ignore bitmask for U_QWORD
|
||||
@@ -568,32 +554,16 @@ float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_
|
||||
value = static_cast<uint64_t>(value & 0xFFFF) << 48 | (value & 0xFFFF000000000000) >> 48 |
|
||||
static_cast<uint64_t>(value & 0xFFFF0000) << 32 | (value & 0x0000FFFF00000000) >> 32 |
|
||||
static_cast<uint64_t>(value & 0xFFFF00000000) << 16 | (value & 0x00000000FFFF0000) >> 16;
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
|
||||
case SensorValueType::S_QWORD_R:
|
||||
// Ignore bitmask for S_QWORD
|
||||
value = get_data<int64_t>(data, offset);
|
||||
result = static_cast<float>(value);
|
||||
break;
|
||||
case SensorValueType::FP32:
|
||||
raw_to_float.raw = get_data<uint32_t>(data, offset);
|
||||
ESP_LOGD(TAG, "FP32 = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
|
||||
result = raw_to_float.float_value;
|
||||
break;
|
||||
case SensorValueType::FP32_R: {
|
||||
auto tmp = get_data<uint32_t>(data, offset);
|
||||
raw_to_float.raw = static_cast<uint32_t>(tmp & 0xFFFF) << 16 | (tmp & 0xFFFF0000) >> 16;
|
||||
ESP_LOGD(TAG, "FP32_R = 0x%08X => %f", raw_to_float.raw, raw_to_float.float_value);
|
||||
result = raw_to_float.float_value;
|
||||
} break;
|
||||
case SensorValueType::RAW:
|
||||
result = NAN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace modbus_controller
|
||||
|
||||
@@ -195,7 +195,7 @@ inline bool coil_from_vector(int coil, const std::vector<uint8_t> &data) {
|
||||
*/
|
||||
template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
|
||||
auto result = (mask & data);
|
||||
if (result == 0) {
|
||||
if (result == 0 || mask == 0xFFFFFFFF) {
|
||||
return result;
|
||||
}
|
||||
for (size_t pos = 0; pos < sizeof(N) << 3; pos++) {
|
||||
@@ -205,22 +205,23 @@ template<typename N> N mask_and_shift_by_rightbit(N data, uint32_t mask) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** convert float value to vector<uint16_t> suitable for sending
|
||||
* @param value float value to cconvert
|
||||
/** Convert float value to vector<uint16_t> suitable for sending
|
||||
* @param data target for payload
|
||||
* @param value float value to convert
|
||||
* @param value_type defines if 16/32 or FP32 is used
|
||||
* @return vector containing the modbus register words in correct order
|
||||
*/
|
||||
std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type);
|
||||
void number_to_payload(std::vector<uint16_t> &data, int64_t value, SensorValueType value_type);
|
||||
|
||||
/** convert vector<uint8_t> response payload to float
|
||||
* @param value float value to cconvert
|
||||
/** Convert vector<uint8_t> response payload to number.
|
||||
* @param data payload with the data to convert
|
||||
* @param sensor_value_type defines if 16/32/64 bits or FP32 is used
|
||||
* @param offset offset to the data in data
|
||||
* @param bitmask bitmask used for masking and shifting
|
||||
* @return float version of the input
|
||||
* @return 64-bit number of the payload
|
||||
*/
|
||||
float payload_to_float(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
|
||||
uint32_t bitmask);
|
||||
int64_t payload_to_number(const std::vector<uint8_t> &data, SensorValueType sensor_value_type, uint8_t offset,
|
||||
uint32_t bitmask);
|
||||
|
||||
class ModbusController;
|
||||
|
||||
@@ -348,11 +349,11 @@ class ModbusCommandItem {
|
||||
* @param modbusdevice pointer to the device to execute the command
|
||||
* @param start_address modbus address of the first register to read
|
||||
* @param register_count number of registers to read
|
||||
* @param values uint16_t array to be written to the registers
|
||||
* @param value uint16_t single register value to write
|
||||
* @return ModbusCommandItem with the prepared command
|
||||
*/
|
||||
static ModbusCommandItem create_write_single_command(ModbusController *modbusdevice, uint16_t start_address,
|
||||
int16_t value);
|
||||
uint16_t value);
|
||||
/** Create modbus write single registers command
|
||||
* Function 05 (05hex) Write Single Coil
|
||||
* @param modbusdevice pointer to the device to execute the command
|
||||
@@ -446,13 +447,36 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice {
|
||||
uint16_t command_throttle_;
|
||||
};
|
||||
|
||||
/** convert vector<uint8_t> response payload to float
|
||||
* @param value float value to cconvert
|
||||
/** Convert vector<uint8_t> response payload to float.
|
||||
* @param data payload with data
|
||||
* @param item SensorItem object
|
||||
* @return float version of the input
|
||||
* @return float value of data
|
||||
*/
|
||||
inline float payload_to_float(const std::vector<uint8_t> &data, const SensorItem &item) {
|
||||
return payload_to_float(data, item.sensor_value_type, item.offset, item.bitmask);
|
||||
int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask);
|
||||
|
||||
float float_value;
|
||||
if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) {
|
||||
float_value = bit_cast<float>(static_cast<uint32_t>(number));
|
||||
} else {
|
||||
float_value = static_cast<float>(number);
|
||||
}
|
||||
|
||||
return float_value;
|
||||
}
|
||||
|
||||
inline std::vector<uint16_t> float_to_payload(float value, SensorValueType value_type) {
|
||||
int64_t val;
|
||||
|
||||
if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) {
|
||||
val = bit_cast<uint32_t>(value);
|
||||
} else {
|
||||
val = llroundf(value);
|
||||
}
|
||||
|
||||
std::vector<uint16_t> data;
|
||||
number_to_payload(data, val, value_type);
|
||||
return data;
|
||||
}
|
||||
|
||||
} // namespace modbus_controller
|
||||
|
||||
142
esphome/components/modbus_controller/select/__init__.py
Normal file
142
esphome/components/modbus_controller/select/__init__.py
Normal file
@@ -0,0 +1,142 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import select
|
||||
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA
|
||||
from esphome.jsonschema import jschema_composite
|
||||
|
||||
from .. import (
|
||||
SENSOR_VALUE_TYPE,
|
||||
TYPE_REGISTER_MAP,
|
||||
ModbusController,
|
||||
SensorItem,
|
||||
modbus_controller_ns,
|
||||
)
|
||||
from ..const import (
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_COUNT,
|
||||
CONF_SKIP_UPDATES,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
CONF_WRITE_LAMBDA,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["modbus_controller"]
|
||||
CODEOWNERS = ["@martgras", "@stegm"]
|
||||
CONF_OPTIONSMAP = "optionsmap"
|
||||
|
||||
ModbusSelect = modbus_controller_ns.class_(
|
||||
"ModbusSelect", cg.Component, select.Select, SensorItem
|
||||
)
|
||||
|
||||
|
||||
@jschema_composite
|
||||
def ensure_option_map():
|
||||
def validator(value):
|
||||
cv.check_not_templatable(value)
|
||||
option = cv.All(cv.string_strict)
|
||||
mapping = cv.All(cv.int_range(-(2**63), 2**63 - 1))
|
||||
options_map_schema = cv.Schema({option: mapping})
|
||||
value = options_map_schema(value)
|
||||
|
||||
all_values = list(value.values())
|
||||
unique_values = set(value.values())
|
||||
if len(all_values) != len(unique_values):
|
||||
raise cv.Invalid("Mapping values must be unique.")
|
||||
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def register_count_value_type_min(value):
|
||||
reg_count = value.get(CONF_REGISTER_COUNT)
|
||||
if reg_count is not None:
|
||||
value_type = value[CONF_VALUE_TYPE]
|
||||
min_register_count = TYPE_REGISTER_MAP[value_type]
|
||||
if min_register_count > reg_count:
|
||||
raise cv.Invalid(
|
||||
f"Value type {value_type} needs at least {min_register_count} registers"
|
||||
)
|
||||
return value
|
||||
|
||||
|
||||
INTEGER_SENSOR_VALUE_TYPE = {
|
||||
key: value for key, value in SENSOR_VALUE_TYPE.items() if not key.startswith("FP")
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
select.SELECT_SCHEMA.extend(cv.COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusSelect),
|
||||
cv.GenerateID(CONF_MODBUS_CONTROLLER_ID): cv.use_id(ModbusController),
|
||||
cv.Required(CONF_ADDRESS): cv.positive_int,
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(
|
||||
INTEGER_SENSOR_VALUE_TYPE
|
||||
),
|
||||
cv.Optional(CONF_REGISTER_COUNT): cv.positive_int,
|
||||
cv.Optional(CONF_SKIP_UPDATES, default=0): cv.positive_int,
|
||||
cv.Optional(CONF_FORCE_NEW_RANGE, default=False): cv.boolean,
|
||||
cv.Required(CONF_OPTIONSMAP): ensure_option_map(),
|
||||
cv.Optional(CONF_USE_WRITE_MULTIPLE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
},
|
||||
),
|
||||
register_count_value_type_min,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
value_type = config[CONF_VALUE_TYPE]
|
||||
reg_count = config.get(CONF_REGISTER_COUNT)
|
||||
if reg_count is None:
|
||||
reg_count = TYPE_REGISTER_MAP[value_type]
|
||||
|
||||
options_map = config[CONF_OPTIONSMAP]
|
||||
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
value_type,
|
||||
config[CONF_ADDRESS],
|
||||
reg_count,
|
||||
config[CONF_SKIP_UPDATES],
|
||||
config[CONF_FORCE_NEW_RANGE],
|
||||
list(options_map.values()),
|
||||
)
|
||||
|
||||
await cg.register_component(var, config)
|
||||
await select.register_select(var, config, options=list(options_map.keys()))
|
||||
|
||||
parent = await cg.get_variable(config[CONF_MODBUS_CONTROLLER_ID])
|
||||
cg.add(parent.add_sensor_item(var))
|
||||
cg.add(var.set_parent(parent))
|
||||
cg.add(var.set_use_write_mutiple(config[CONF_USE_WRITE_MULTIPLE]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA],
|
||||
[
|
||||
(ModbusSelect.operator("const_ptr"), "item"),
|
||||
(cg.int64, "x"),
|
||||
(
|
||||
cg.std_vector.template(cg.uint8).operator("const").operator("ref"),
|
||||
"data",
|
||||
),
|
||||
],
|
||||
return_type=cg.optional.template(cg.std_string),
|
||||
)
|
||||
cg.add(var.set_template(template_))
|
||||
|
||||
if CONF_WRITE_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_WRITE_LAMBDA],
|
||||
[
|
||||
(ModbusSelect.operator("const_ptr"), "item"),
|
||||
(cg.std_string.operator("const").operator("ref"), "x"),
|
||||
(cg.int64, "value"),
|
||||
(cg.std_vector.template(cg.uint16).operator("ref"), "payload"),
|
||||
],
|
||||
return_type=cg.optional.template(cg.int64),
|
||||
)
|
||||
cg.add(var.set_write_template(template_))
|
||||
@@ -0,0 +1,86 @@
|
||||
#include "modbus_select.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modbus_controller {
|
||||
|
||||
static const char *const TAG = "modbus_controller.select";
|
||||
|
||||
void ModbusSelect::dump_config() { LOG_SELECT(TAG, "Modbus Controller Select", this); }
|
||||
|
||||
void ModbusSelect::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
int64_t value = payload_to_number(data, this->sensor_value_type, this->offset, this->bitmask);
|
||||
|
||||
ESP_LOGD(TAG, "New select value %lld from payload", value);
|
||||
|
||||
optional<std::string> new_state;
|
||||
|
||||
if (this->transform_func_.has_value()) {
|
||||
auto val = (*this->transform_func_)(this, value, data);
|
||||
if (val.has_value()) {
|
||||
new_state = *val;
|
||||
ESP_LOGV(TAG, "lambda returned option %s", new_state->c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (!new_state.has_value()) {
|
||||
auto map_it = std::find(this->mapping_.cbegin(), this->mapping_.cend(), value);
|
||||
|
||||
if (map_it != this->mapping_.cend()) {
|
||||
size_t idx = std::distance(this->mapping_.cbegin(), map_it);
|
||||
new_state = this->traits.get_options()[idx];
|
||||
ESP_LOGV(TAG, "Found option %s for value %lld", new_state->c_str(), value);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "No option found for mapping %lld", value);
|
||||
}
|
||||
}
|
||||
|
||||
if (new_state.has_value()) {
|
||||
this->publish_state(new_state.value());
|
||||
}
|
||||
}
|
||||
|
||||
void ModbusSelect::control(const std::string &value) {
|
||||
auto options = this->traits.get_options();
|
||||
auto opt_it = std::find(options.cbegin(), options.cend(), value);
|
||||
size_t idx = std::distance(options.cbegin(), opt_it);
|
||||
optional<int64_t> mapval = this->mapping_[idx];
|
||||
ESP_LOGD(TAG, "Found value %lld for option '%s'", *mapval, value.c_str());
|
||||
|
||||
std::vector<uint16_t> data;
|
||||
|
||||
if (this->write_transform_func_.has_value()) {
|
||||
auto val = (*this->write_transform_func_)(this, value, *mapval, data);
|
||||
if (val.has_value()) {
|
||||
mapval = *val;
|
||||
ESP_LOGV(TAG, "write_lambda returned mapping value %lld", *mapval);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "Communication handled by write_lambda - exiting control");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.empty()) {
|
||||
number_to_payload(data, *mapval, this->sensor_value_type);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Using payload from write lambda");
|
||||
}
|
||||
|
||||
if (data.empty()) {
|
||||
ESP_LOGW(TAG, "No payload was created for updating select");
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t write_address = this->start_address + this->offset / 2;
|
||||
ModbusCommandItem write_cmd;
|
||||
if ((this->register_count == 1) && (!this->use_write_multiple_)) {
|
||||
write_cmd = ModbusCommandItem::create_write_single_command(parent_, write_address, data[0]);
|
||||
} else {
|
||||
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, write_address, this->register_count, data);
|
||||
}
|
||||
|
||||
parent_->queue_command(write_cmd);
|
||||
}
|
||||
|
||||
} // namespace modbus_controller
|
||||
} // namespace esphome
|
||||
51
esphome/components/modbus_controller/select/modbus_select.h
Normal file
51
esphome/components/modbus_controller/select/modbus_select.h
Normal file
@@ -0,0 +1,51 @@
|
||||
#pragma once
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "esphome/components/modbus_controller/modbus_controller.h"
|
||||
#include "esphome/components/select/select.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace modbus_controller {
|
||||
|
||||
class ModbusSelect : public Component, public select::Select, public SensorItem {
|
||||
public:
|
||||
ModbusSelect(SensorValueType sensor_value_type, uint16_t start_address, uint8_t register_count, uint8_t skip_updates,
|
||||
bool force_new_range, std::vector<int64_t> mapping) {
|
||||
this->register_type = ModbusRegisterType::HOLDING; // not configurable
|
||||
this->sensor_value_type = sensor_value_type;
|
||||
this->start_address = start_address;
|
||||
this->offset = 0; // not configurable
|
||||
this->bitmask = 0xFFFFFFFF; // not configurable
|
||||
this->register_count = register_count;
|
||||
this->response_bytes = 0; // not configurable
|
||||
this->skip_updates = skip_updates;
|
||||
this->force_new_range = force_new_range;
|
||||
this->mapping_ = std::move(mapping);
|
||||
}
|
||||
|
||||
using transform_func_t =
|
||||
std::function<optional<std::string>(ModbusSelect *const, int64_t, const std::vector<uint8_t> &)>;
|
||||
using write_transform_func_t =
|
||||
std::function<optional<int64_t>(ModbusSelect *const, const std::string &, int64_t, std::vector<uint16_t> &)>;
|
||||
|
||||
void set_parent(ModbusController *const parent) { this->parent_ = parent; }
|
||||
void set_use_write_mutiple(bool use_write_multiple) { this->use_write_multiple_ = use_write_multiple; }
|
||||
void set_template(transform_func_t &&f) { this->transform_func_ = f; }
|
||||
void set_write_template(write_transform_func_t &&f) { this->write_transform_func_ = f; }
|
||||
|
||||
void dump_config() override;
|
||||
void parse_and_publish(const std::vector<uint8_t> &data) override;
|
||||
void control(const std::string &value) override;
|
||||
|
||||
protected:
|
||||
std::vector<int64_t> mapping_;
|
||||
ModbusController *parent_;
|
||||
bool use_write_multiple_{false};
|
||||
optional<transform_func_t> transform_func_;
|
||||
optional<write_transform_func_t> write_transform_func_;
|
||||
};
|
||||
|
||||
} // namespace modbus_controller
|
||||
} // namespace esphome
|
||||
@@ -40,7 +40,8 @@ RAW_ENCODING = {
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(cv.COMPONENT_SCHEMA)
|
||||
text_sensor.text_sensor_schema()
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(ModbusItemBaseSchema)
|
||||
.extend(
|
||||
{
|
||||
|
||||
@@ -16,6 +16,7 @@ from esphome.const import (
|
||||
CONF_DISCOVERY_PREFIX,
|
||||
CONF_DISCOVERY_RETAIN,
|
||||
CONF_DISCOVERY_UNIQUE_ID_GENERATOR,
|
||||
CONF_DISCOVERY_OBJECT_ID_GENERATOR,
|
||||
CONF_ID,
|
||||
CONF_KEEPALIVE,
|
||||
CONF_LEVEL,
|
||||
@@ -97,6 +98,7 @@ MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
|
||||
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
|
||||
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
|
||||
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
|
||||
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
|
||||
|
||||
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
|
||||
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
|
||||
@@ -104,6 +106,12 @@ MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {
|
||||
"mac": MQTTDiscoveryUniqueIdGenerator.MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
|
||||
}
|
||||
|
||||
MQTTDiscoveryObjectIdGenerator = mqtt_ns.enum("MQTTDiscoveryObjectIdGenerator")
|
||||
MQTT_DISCOVERY_OBJECT_ID_GENERATOR_OPTIONS = {
|
||||
"none": MQTTDiscoveryObjectIdGenerator.MQTT_NONE_OBJECT_ID_GENERATOR,
|
||||
"device_name": MQTTDiscoveryObjectIdGenerator.MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR,
|
||||
}
|
||||
|
||||
|
||||
def validate_config(value):
|
||||
# Populate default fields
|
||||
@@ -165,6 +173,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_DISCOVERY_UNIQUE_ID_GENERATOR, default="legacy"): cv.enum(
|
||||
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS
|
||||
),
|
||||
cv.Optional(CONF_DISCOVERY_OBJECT_ID_GENERATOR, default="none"): cv.enum(
|
||||
MQTT_DISCOVERY_OBJECT_ID_GENERATOR_OPTIONS
|
||||
),
|
||||
cv.Optional(CONF_USE_ABBREVIATIONS, default=True): cv.boolean,
|
||||
cv.Optional(CONF_BIRTH_MESSAGE): MQTT_MESSAGE_SCHEMA,
|
||||
cv.Optional(CONF_WILL_MESSAGE): MQTT_MESSAGE_SCHEMA,
|
||||
@@ -244,19 +255,27 @@ async def to_code(config):
|
||||
discovery_retain = config[CONF_DISCOVERY_RETAIN]
|
||||
discovery_prefix = config[CONF_DISCOVERY_PREFIX]
|
||||
discovery_unique_id_generator = config[CONF_DISCOVERY_UNIQUE_ID_GENERATOR]
|
||||
discovery_object_id_generator = config[CONF_DISCOVERY_OBJECT_ID_GENERATOR]
|
||||
|
||||
if not discovery:
|
||||
cg.add(var.disable_discovery())
|
||||
elif discovery == "CLEAN":
|
||||
cg.add(
|
||||
var.set_discovery_info(
|
||||
discovery_prefix, discovery_unique_id_generator, discovery_retain, True
|
||||
discovery_prefix,
|
||||
discovery_unique_id_generator,
|
||||
discovery_object_id_generator,
|
||||
discovery_retain,
|
||||
True,
|
||||
)
|
||||
)
|
||||
elif CONF_DISCOVERY_RETAIN in config or CONF_DISCOVERY_PREFIX in config:
|
||||
cg.add(
|
||||
var.set_discovery_info(
|
||||
discovery_prefix, discovery_unique_id_generator, discovery_retain
|
||||
discovery_prefix,
|
||||
discovery_unique_id_generator,
|
||||
discovery_object_id_generator,
|
||||
discovery_retain,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -538,9 +538,11 @@ void MQTTClientComponent::set_birth_message(MQTTMessage &&message) {
|
||||
void MQTTClientComponent::set_shutdown_message(MQTTMessage &&message) { this->shutdown_message_ = std::move(message); }
|
||||
|
||||
void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
|
||||
bool retain, bool clean) {
|
||||
MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain,
|
||||
bool clean) {
|
||||
this->discovery_info_.prefix = std::move(prefix);
|
||||
this->discovery_info_.unique_id_generator = unique_id_generator;
|
||||
this->discovery_info_.object_id_generator = object_id_generator;
|
||||
this->discovery_info_.retain = retain;
|
||||
this->discovery_info_.clean = clean;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,12 @@ enum MQTTDiscoveryUniqueIdGenerator {
|
||||
MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR,
|
||||
};
|
||||
|
||||
/// available discovery object_id generators
|
||||
enum MQTTDiscoveryObjectIdGenerator {
|
||||
MQTT_NONE_OBJECT_ID_GENERATOR = 0,
|
||||
MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR,
|
||||
};
|
||||
|
||||
/** Internal struct for MQTT Home Assistant discovery
|
||||
*
|
||||
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
|
||||
@@ -70,6 +76,7 @@ struct MQTTDiscoveryInfo {
|
||||
bool retain; ///< Whether to retain discovery messages.
|
||||
bool clean;
|
||||
MQTTDiscoveryUniqueIdGenerator unique_id_generator;
|
||||
MQTTDiscoveryObjectIdGenerator object_id_generator;
|
||||
};
|
||||
|
||||
enum MQTTClientState {
|
||||
@@ -106,10 +113,11 @@ class MQTTClientComponent : public Component {
|
||||
* See <a href="https://www.home-assistant.io/docs/mqtt/discovery/">MQTT Discovery</a>.
|
||||
* @param prefix The Home Assistant discovery prefix.
|
||||
* @param unique_id_generator Controls how UniqueId is generated.
|
||||
* @param object_id_generator Controls how ObjectId is generated.
|
||||
* @param retain Whether to retain discovery messages.
|
||||
*/
|
||||
void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator, bool retain,
|
||||
bool clean = false);
|
||||
void set_discovery_info(std::string &&prefix, MQTTDiscoveryUniqueIdGenerator unique_id_generator,
|
||||
MQTTDiscoveryObjectIdGenerator object_id_generator, bool retain, bool clean = false);
|
||||
/// Get Home Assistant discovery info.
|
||||
const MQTTDiscoveryInfo &get_discovery_info() const;
|
||||
/// Globally disable Home Assistant discovery.
|
||||
|
||||
@@ -111,12 +111,11 @@ bool MQTTComponent::send_discovery_() {
|
||||
root[MQTT_PAYLOAD_NOT_AVAILABLE] = this->availability_->payload_not_available;
|
||||
}
|
||||
|
||||
const std::string &node_name = App.get_name();
|
||||
std::string unique_id = this->unique_id();
|
||||
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
||||
if (!unique_id.empty()) {
|
||||
root[MQTT_UNIQUE_ID] = unique_id;
|
||||
} else {
|
||||
const MQTTDiscoveryInfo &discovery_info = global_mqtt_client->get_discovery_info();
|
||||
if (discovery_info.unique_id_generator == MQTT_MAC_ADDRESS_UNIQUE_ID_GENERATOR) {
|
||||
char friendly_name_hash[9];
|
||||
sprintf(friendly_name_hash, "%08x", fnv1_hash(this->friendly_name()));
|
||||
@@ -129,6 +128,10 @@ bool MQTTComponent::send_discovery_() {
|
||||
}
|
||||
}
|
||||
|
||||
const std::string &node_name = App.get_name();
|
||||
if (discovery_info.object_id_generator == MQTT_DEVICE_NAME_OBJECT_ID_GENERATOR)
|
||||
root[MQTT_OBJECT_ID] = node_name + "_" + this->get_default_object_id_();
|
||||
|
||||
JsonObject device_info = root.createNestedObject(MQTT_DEVICE);
|
||||
device_info[MQTT_DEVICE_IDENTIFIERS] = get_mac_address();
|
||||
device_info[MQTT_DEVICE_NAME] = node_name;
|
||||
|
||||
@@ -107,6 +107,7 @@ constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t";
|
||||
constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl";
|
||||
constexpr const char *const MQTT_MODES = "modes";
|
||||
constexpr const char *const MQTT_NAME = "name";
|
||||
constexpr const char *const MQTT_OBJECT_ID = "obj_id";
|
||||
constexpr const char *const MQTT_OFF_DELAY = "off_dly";
|
||||
constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type";
|
||||
constexpr const char *const MQTT_OPTIONS = "ops";
|
||||
@@ -360,6 +361,7 @@ constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic";
|
||||
constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template";
|
||||
constexpr const char *const MQTT_MODES = "modes";
|
||||
constexpr const char *const MQTT_NAME = "name";
|
||||
constexpr const char *const MQTT_OBJECT_ID = "object_id";
|
||||
constexpr const char *const MQTT_OFF_DELAY = "off_delay";
|
||||
constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type";
|
||||
constexpr const char *const MQTT_OPTIONS = "options";
|
||||
|
||||
55
esphome/components/mqtt/mqtt_lock.cpp
Normal file
55
esphome/components/mqtt/mqtt_lock.cpp
Normal file
@@ -0,0 +1,55 @@
|
||||
#include "mqtt_lock.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "mqtt_const.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_LOCK
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.lock";
|
||||
|
||||
using namespace esphome::lock;
|
||||
|
||||
MQTTLockComponent::MQTTLockComponent(lock::Lock *a_lock) : lock_(a_lock) {}
|
||||
|
||||
void MQTTLockComponent::setup() {
|
||||
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
|
||||
if (strcasecmp(payload.c_str(), "LOCK") == 0) {
|
||||
this->lock_->lock();
|
||||
} else if (strcasecmp(payload.c_str(), "UNLOCK") == 0) {
|
||||
this->lock_->unlock();
|
||||
} else if (strcasecmp(payload.c_str(), "OPEN") == 0) {
|
||||
this->lock_->open();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "'%s': Received unknown status payload: %s", this->friendly_name().c_str(), payload.c_str());
|
||||
this->status_momentary_warning("state", 5000);
|
||||
}
|
||||
});
|
||||
this->lock_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); });
|
||||
}
|
||||
void MQTTLockComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MQTT Lock '%s': ", this->lock_->get_name().c_str());
|
||||
LOG_MQTT_COMPONENT(true, true);
|
||||
}
|
||||
|
||||
std::string MQTTLockComponent::component_type() const { return "lock"; }
|
||||
const EntityBase *MQTTLockComponent::get_entity() const { return this->lock_; }
|
||||
void MQTTLockComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
|
||||
if (this->lock_->traits.get_assumed_state())
|
||||
root[MQTT_OPTIMISTIC] = true;
|
||||
}
|
||||
bool MQTTLockComponent::send_initial_state() { return this->publish_state(); }
|
||||
|
||||
bool MQTTLockComponent::publish_state() {
|
||||
std::string payload = lock_state_to_string(this->lock_->state);
|
||||
return this->publish(this->get_state_topic_(), payload);
|
||||
}
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_MQTT
|
||||
41
esphome/components/mqtt/mqtt_lock.h
Normal file
41
esphome/components/mqtt/mqtt_lock.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
#ifdef USE_MQTT
|
||||
#ifdef USE_LOCK
|
||||
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#include "mqtt_component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTLockComponent : public mqtt::MQTTComponent {
|
||||
public:
|
||||
explicit MQTTLockComponent(lock::Lock *a_lock);
|
||||
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
|
||||
|
||||
bool send_initial_state() override;
|
||||
|
||||
bool publish_state();
|
||||
|
||||
protected:
|
||||
/// "lock" component type.
|
||||
std::string component_type() const override;
|
||||
const EntityBase *get_entity() const override;
|
||||
|
||||
lock::Lock *lock_;
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
#endif // USE_MQTT
|
||||
@@ -1,7 +1,8 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor, mqtt
|
||||
from esphome.const import CONF_ID, CONF_QOS, CONF_TOPIC
|
||||
from esphome.const import CONF_QOS, CONF_TOPIC
|
||||
|
||||
from .. import mqtt_subscribe_ns
|
||||
|
||||
DEPENDENCIES = ["mqtt"]
|
||||
@@ -11,20 +12,23 @@ MQTTSubscribeTextSensor = mqtt_subscribe_ns.class_(
|
||||
"MQTTSubscribeTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor),
|
||||
cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent),
|
||||
cv.Required(CONF_TOPIC): cv.subscribe_topic,
|
||||
cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.text_sensor_schema()
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MQTTSubscribeTextSensor),
|
||||
cv.GenerateID(CONF_MQTT_PARENT_ID): cv.use_id(mqtt.MQTTClientComponent),
|
||||
cv.Required(CONF_TOPIC): cv.subscribe_topic,
|
||||
cv.Optional(CONF_QOS, default=0): cv.mqtt_qos,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
parent = await cg.get_variable(config[CONF_MQTT_PARENT_ID])
|
||||
cg.add(var.set_parent(parent))
|
||||
|
||||
@@ -17,11 +17,7 @@ NextionTextSensor = nextion_ns.class_(
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(NextionTextSensor),
|
||||
}
|
||||
)
|
||||
text_sensor.text_sensor_schema(klass=NextionTextSensor)
|
||||
.extend(CONFIG_TEXT_COMPONENT_SCHEMA)
|
||||
.extend(cv.polling_component_schema("never"))
|
||||
)
|
||||
@@ -30,8 +26,8 @@ CONFIG_SCHEMA = (
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_NEXTION_ID])
|
||||
var = cg.new_Pvariable(config[CONF_ID], hub)
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(hub.register_textsensor_component(var))
|
||||
|
||||
|
||||
23
esphome/components/output/lock/__init__.py
Normal file
23
esphome/components/output/lock/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import output, lock
|
||||
from esphome.const import CONF_ID, CONF_OUTPUT
|
||||
from .. import output_ns
|
||||
|
||||
OutputLock = output_ns.class_("OutputLock", lock.Lock, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = lock.LOCK_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(OutputLock),
|
||||
cv.Required(CONF_OUTPUT): cv.use_id(output.BinaryOutput),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await lock.register_lock(var, config)
|
||||
|
||||
output_ = await cg.get_variable(config[CONF_OUTPUT])
|
||||
cg.add(var.set_output(output_))
|
||||
22
esphome/components/output/lock/output_lock.cpp
Normal file
22
esphome/components/output/lock/output_lock.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "output_lock.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace output {
|
||||
|
||||
static const char *const TAG = "output.lock";
|
||||
|
||||
void OutputLock::dump_config() { LOG_LOCK("", "Output Lock", this); }
|
||||
|
||||
void OutputLock::control(const lock::LockCall &call) {
|
||||
auto state = *call.get_state();
|
||||
if (state == lock::LOCK_STATE_LOCKED) {
|
||||
this->output_->turn_on();
|
||||
} else if (state == lock::LOCK_STATE_UNLOCKED) {
|
||||
this->output_->turn_off();
|
||||
}
|
||||
this->publish_state(state);
|
||||
}
|
||||
|
||||
} // namespace output
|
||||
} // namespace esphome
|
||||
24
esphome/components/output/lock/output_lock.h
Normal file
24
esphome/components/output/lock/output_lock.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#include "esphome/components/output/binary_output.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace output {
|
||||
|
||||
class OutputLock : public lock::Lock, public Component {
|
||||
public:
|
||||
void set_output(BinaryOutput *output) { output_ = output; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE - 1.0f; }
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
void control(const lock::LockCall &call) override;
|
||||
|
||||
output::BinaryOutput *output_;
|
||||
};
|
||||
|
||||
} // namespace output
|
||||
} // namespace esphome
|
||||
@@ -13,7 +13,7 @@ CONF_PIPSOLAR_ID = "pipsolar_id"
|
||||
pipsolar_ns = cg.esphome_ns.namespace("pipsolar")
|
||||
PipsolarComponent = pipsolar_ns.class_("Pipsolar", cg.Component)
|
||||
|
||||
PIPSOLAR_COMPONENT_SCHEMA = cv.COMPONENT_SCHEMA.extend(
|
||||
PIPSOLAR_COMPONENT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PIPSOLAR_ID): cv.use_id(PipsolarComponent),
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ID
|
||||
from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA, pipsolar_ns
|
||||
from .. import CONF_PIPSOLAR_ID, PIPSOLAR_COMPONENT_SCHEMA
|
||||
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
@@ -15,10 +14,6 @@ CONF_LAST_QPIWS = "last_qpiws"
|
||||
CONF_LAST_QT = "last_qt"
|
||||
CONF_LAST_QMN = "last_qmn"
|
||||
|
||||
PipsolarTextSensor = pipsolar_ns.class_(
|
||||
"PipsolarTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
TYPES = [
|
||||
CONF_DEVICE_MODE,
|
||||
CONF_LAST_QPIGS,
|
||||
@@ -31,12 +26,7 @@ TYPES = [
|
||||
]
|
||||
|
||||
CONFIG_SCHEMA = PIPSOLAR_COMPONENT_SCHEMA.extend(
|
||||
{
|
||||
cv.Optional(type): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{cv.GenerateID(): cv.declare_id(PipsolarTextSensor)}
|
||||
)
|
||||
for type in TYPES
|
||||
}
|
||||
{cv.Optional(type): text_sensor.text_sensor_schema() for type in TYPES}
|
||||
)
|
||||
|
||||
|
||||
@@ -46,7 +36,5 @@ async def to_code(config):
|
||||
for type in TYPES:
|
||||
if type in config:
|
||||
conf = config[type]
|
||||
var = cg.new_Pvariable(conf[CONF_ID])
|
||||
await text_sensor.register_text_sensor(var, conf)
|
||||
await cg.register_component(var, conf)
|
||||
var = await text_sensor.new_text_sensor(conf)
|
||||
cg.add(getattr(paren, f"set_{type}")(var))
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
#include "pipsolar_textsensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pipsolar {
|
||||
|
||||
static const char *const TAG = "pipsolar.text_sensor";
|
||||
|
||||
void PipsolarTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Pipsolar TextSensor", this); }
|
||||
|
||||
} // namespace pipsolar
|
||||
} // namespace esphome
|
||||
@@ -1,20 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../pipsolar.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pipsolar {
|
||||
class Pipsolar;
|
||||
class PipsolarTextSensor : public Component, public text_sensor::TextSensor {
|
||||
public:
|
||||
void set_parent(Pipsolar *parent) { this->parent_ = parent; };
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
Pipsolar *parent_;
|
||||
};
|
||||
|
||||
} // namespace pipsolar
|
||||
} // namespace esphome
|
||||
@@ -45,6 +45,12 @@ void PrometheusHandler::handleRequest(AsyncWebServerRequest *req) {
|
||||
this->switch_row_(stream, obj);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
this->lock_type_(stream);
|
||||
for (auto *obj : App.get_locks())
|
||||
this->lock_row_(stream, obj);
|
||||
#endif
|
||||
|
||||
req->send(stream);
|
||||
}
|
||||
|
||||
@@ -310,6 +316,30 @@ void PrometheusHandler::switch_row_(AsyncResponseStream *stream, switch_::Switch
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void PrometheusHandler::lock_type_(AsyncResponseStream *stream) {
|
||||
stream->print(F("#TYPE esphome_lock_value GAUGE\n"));
|
||||
stream->print(F("#TYPE esphome_lock_failed GAUGE\n"));
|
||||
}
|
||||
void PrometheusHandler::lock_row_(AsyncResponseStream *stream, lock::Lock *obj) {
|
||||
if (obj->is_internal())
|
||||
return;
|
||||
stream->print(F("esphome_lock_failed{id=\""));
|
||||
stream->print(obj->get_object_id().c_str());
|
||||
stream->print(F("\",name=\""));
|
||||
stream->print(obj->get_name().c_str());
|
||||
stream->print(F("\"} 0\n"));
|
||||
// Data itself
|
||||
stream->print(F("esphome_lock_value{id=\""));
|
||||
stream->print(obj->get_object_id().c_str());
|
||||
stream->print(F("\",name=\""));
|
||||
stream->print(obj->get_name().c_str());
|
||||
stream->print(F("\"} "));
|
||||
stream->print(obj->state);
|
||||
stream->print('\n');
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace prometheus
|
||||
} // namespace esphome
|
||||
|
||||
|
||||
@@ -76,6 +76,13 @@ class PrometheusHandler : public AsyncWebHandler, public Component {
|
||||
void switch_row_(AsyncResponseStream *stream, switch_::Switch *obj);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
/// Return the type for prometheus
|
||||
void lock_type_(AsyncResponseStream *stream);
|
||||
/// Return the lock Values state as prometheus data point
|
||||
void lock_row_(AsyncResponseStream *stream, lock::Lock *obj);
|
||||
#endif
|
||||
|
||||
web_server_base::WebServerBase *base_;
|
||||
};
|
||||
|
||||
|
||||
23
esphome/components/radon_eye_ble/__init__.py
Normal file
23
esphome/components/radon_eye_ble/__init__.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
CODEOWNERS = ["@jeffeb3"]
|
||||
|
||||
radon_eye_ble_ns = cg.esphome_ns.namespace("radon_eye_ble")
|
||||
RadonEyeListener = radon_eye_ble_ns.class_(
|
||||
"RadonEyeListener", esp32_ble_tracker.ESPBTDeviceListener
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RadonEyeListener),
|
||||
}
|
||||
).extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
|
||||
|
||||
def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
yield esp32_ble_tracker.register_ble_device(var, config)
|
||||
25
esphome/components/radon_eye_ble/radon_eye_listener.cpp
Normal file
25
esphome/components/radon_eye_ble/radon_eye_listener.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "radon_eye_listener.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace radon_eye_ble {
|
||||
|
||||
static const char *const TAG = "radon_eye_ble";
|
||||
|
||||
bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (not device.get_name().empty()) {
|
||||
if (device.get_name().rfind("FR:R20:SN", 0) == 0) {
|
||||
// This is an RD200, I think
|
||||
ESP_LOGD(TAG, "Found Radon Eye RD200 device Name: %s (MAC: %s)", device.get_name().c_str(),
|
||||
device.address_str().c_str());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace radon_eye_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
19
esphome/components/radon_eye_ble/radon_eye_listener.h
Normal file
19
esphome/components/radon_eye_ble/radon_eye_listener.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace radon_eye_ble {
|
||||
|
||||
class RadonEyeListener : public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
};
|
||||
|
||||
} // namespace radon_eye_ble
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
1
esphome/components/radon_eye_rd200/__init__.py
Normal file
1
esphome/components/radon_eye_rd200/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@jeffeb3"]
|
||||
179
esphome/components/radon_eye_rd200/radon_eye_rd200.cpp
Normal file
179
esphome/components/radon_eye_rd200/radon_eye_rd200.cpp
Normal file
@@ -0,0 +1,179 @@
|
||||
#include "radon_eye_rd200.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace radon_eye_rd200 {
|
||||
|
||||
static const char *const TAG = "radon_eye_rd200";
|
||||
|
||||
void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
case ESP_GATTC_OPEN_EVT: {
|
||||
if (param->open.status == ESP_GATT_OK) {
|
||||
ESP_LOGI(TAG, "Connected successfully!");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_DISCONNECT_EVT: {
|
||||
ESP_LOGW(TAG, "Disconnected!");
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
this->read_handle_ = 0;
|
||||
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor read characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
|
||||
sensors_read_characteristic_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->read_handle_ = chr->handle;
|
||||
|
||||
// Write a 0x50 to the write characteristic.
|
||||
auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_);
|
||||
if (write_chr == nullptr) {
|
||||
ESP_LOGW(TAG, "No sensor write characteristic found at service %s char %s", service_uuid_.to_string().c_str(),
|
||||
sensors_read_characteristic_uuid_.to_string().c_str());
|
||||
break;
|
||||
}
|
||||
this->write_handle_ = write_chr->handle;
|
||||
|
||||
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
|
||||
|
||||
write_query_message_();
|
||||
|
||||
request_read_values_();
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->conn_id)
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->read_handle_) {
|
||||
read_sensors_(param->read.value, param->read.value_len);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) {
|
||||
if (value_len < 20) {
|
||||
ESP_LOGD(TAG, "Invalid read");
|
||||
return;
|
||||
}
|
||||
|
||||
// Example data
|
||||
// [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000
|
||||
ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X",
|
||||
value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9],
|
||||
value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18],
|
||||
value[19]);
|
||||
|
||||
if (value[0] != 0x50) {
|
||||
// This isn't a sensor reading.
|
||||
return;
|
||||
}
|
||||
|
||||
// Convert from pCi/L to Bq/m³
|
||||
constexpr float convert_to_bwpm3 = 37.0;
|
||||
|
||||
RadonValue radon_value;
|
||||
radon_value.chars[0] = value[2];
|
||||
radon_value.chars[1] = value[3];
|
||||
radon_value.chars[2] = value[4];
|
||||
radon_value.chars[3] = value[5];
|
||||
float radon_now = radon_value.number * convert_to_bwpm3;
|
||||
if (is_valid_radon_value_(radon_now)) {
|
||||
radon_sensor_->publish_state(radon_now);
|
||||
}
|
||||
|
||||
radon_value.chars[0] = value[6];
|
||||
radon_value.chars[1] = value[7];
|
||||
radon_value.chars[2] = value[8];
|
||||
radon_value.chars[3] = value[9];
|
||||
float radon_day = radon_value.number * convert_to_bwpm3;
|
||||
|
||||
radon_value.chars[0] = value[10];
|
||||
radon_value.chars[1] = value[11];
|
||||
radon_value.chars[2] = value[12];
|
||||
radon_value.chars[3] = value[13];
|
||||
float radon_month = radon_value.number * convert_to_bwpm3;
|
||||
|
||||
if (is_valid_radon_value_(radon_month)) {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on month");
|
||||
radon_long_term_sensor_->publish_state(radon_month);
|
||||
} else if (is_valid_radon_value_(radon_day)) {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on day");
|
||||
radon_long_term_sensor_->publish_state(radon_day);
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG, " Measurements (Bq/m³) now: %0.03f, day: %0.03f, month: %0.03f", radon_now, radon_day, radon_month);
|
||||
|
||||
ESP_LOGV(TAG, " Measurements (pCi/L) now: %0.03f, day: %0.03f, month: %0.03f", radon_now / convert_to_bwpm3,
|
||||
radon_day / convert_to_bwpm3, radon_month / convert_to_bwpm3);
|
||||
|
||||
// This instance must not stay connected
|
||||
// so other clients can connect to it (e.g. the
|
||||
// mobile app).
|
||||
parent()->set_enabled(false);
|
||||
}
|
||||
|
||||
bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; }
|
||||
|
||||
void RadonEyeRD200::update() {
|
||||
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
|
||||
if (!parent()->enabled) {
|
||||
ESP_LOGW(TAG, "Reconnecting to device");
|
||||
parent()->set_enabled(true);
|
||||
parent()->connect();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Connection in progress");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::write_query_message_() {
|
||||
ESP_LOGV(TAG, "writing 0x50 to write service");
|
||||
int request = 0x50;
|
||||
auto status = esp_ble_gattc_write_char_descr(this->parent()->gattc_if, this->parent()->conn_id, this->write_handle_,
|
||||
sizeof(request), (uint8_t *) &request, ESP_GATT_WRITE_TYPE_NO_RSP,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::request_read_values_() {
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->gattc_if, this->parent()->conn_id, this->read_handle_,
|
||||
ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::dump_config() {
|
||||
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
|
||||
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
|
||||
}
|
||||
|
||||
RadonEyeRD200::RadonEyeRD200()
|
||||
: PollingComponent(10000),
|
||||
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
|
||||
sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)),
|
||||
sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {}
|
||||
|
||||
} // namespace radon_eye_rd200
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
59
esphome/components/radon_eye_rd200/radon_eye_rd200.h
Normal file
59
esphome/components/radon_eye_rd200/radon_eye_rd200.h
Normal file
@@ -0,0 +1,59 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
#include <esp_gattc_api.h>
|
||||
#include <algorithm>
|
||||
#include <iterator>
|
||||
#include "esphome/components/ble_client/ble_client.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace radon_eye_rd200 {
|
||||
|
||||
static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123";
|
||||
static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123";
|
||||
static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123";
|
||||
|
||||
class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode {
|
||||
public:
|
||||
RadonEyeRD200();
|
||||
|
||||
void dump_config() override;
|
||||
void update() override;
|
||||
|
||||
void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) override;
|
||||
|
||||
void set_radon(sensor::Sensor *radon) { radon_sensor_ = radon; }
|
||||
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
|
||||
|
||||
protected:
|
||||
bool is_valid_radon_value_(float radon);
|
||||
|
||||
void read_sensors_(uint8_t *value, uint16_t value_len);
|
||||
void write_query_message_();
|
||||
void request_read_values_();
|
||||
|
||||
sensor::Sensor *radon_sensor_{nullptr};
|
||||
sensor::Sensor *radon_long_term_sensor_{nullptr};
|
||||
|
||||
uint16_t read_handle_;
|
||||
uint16_t write_handle_;
|
||||
esp32_ble_tracker::ESPBTUUID service_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_;
|
||||
|
||||
union RadonValue {
|
||||
char chars[4];
|
||||
float number;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace radon_eye_rd200
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ESP32
|
||||
55
esphome/components/radon_eye_rd200/sensor.py
Normal file
55
esphome/components/radon_eye_rd200/sensor.py
Normal file
@@ -0,0 +1,55 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor, ble_client
|
||||
|
||||
from esphome.const import (
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
CONF_ID,
|
||||
CONF_RADON,
|
||||
CONF_RADON_LONG_TERM,
|
||||
ICON_RADIOACTIVE,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["ble_client"]
|
||||
|
||||
radon_eye_rd200_ns = cg.esphome_ns.namespace("radon_eye_rd200")
|
||||
RadonEyeRD200 = radon_eye_rd200_ns.class_(
|
||||
"RadonEyeRD200", cg.PollingComponent, ble_client.BLEClientNode
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RadonEyeRD200),
|
||||
cv.Optional(CONF_RADON): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
icon=ICON_RADIOACTIVE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_RADON_LONG_TERM): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_BECQUEREL_PER_CUBIC_METER,
|
||||
icon=ICON_RADIOACTIVE,
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("5min"))
|
||||
.extend(ble_client.BLE_CLIENT_SCHEMA),
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await ble_client.register_ble_node(var, config)
|
||||
|
||||
if CONF_RADON in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON])
|
||||
cg.add(var.set_radon(sens))
|
||||
if CONF_RADON_LONG_TERM in config:
|
||||
sens = await sensor.new_sensor(config[CONF_RADON_LONG_TERM])
|
||||
cg.add(var.set_radon_long_term(sens))
|
||||
@@ -138,6 +138,8 @@ void RotaryEncoderSensor::setup() {
|
||||
initial_value = 0;
|
||||
break;
|
||||
}
|
||||
initial_value = clamp(initial_value, this->store_.min_value, this->store_.max_value);
|
||||
|
||||
this->store_.counter = initial_value;
|
||||
this->store_.last_read = initial_value;
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ from esphome.const import (
|
||||
ICON_WEATHER_SUNSET_DOWN,
|
||||
ICON_WEATHER_SUNSET_UP,
|
||||
CONF_TYPE,
|
||||
CONF_ID,
|
||||
CONF_FORMAT,
|
||||
)
|
||||
from .. import sun_ns, CONF_SUN_ID, Sun, CONF_ELEVATION, elevation, DEFAULT_ELEVATION
|
||||
@@ -33,7 +32,8 @@ def validate_optional_icon(config):
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
text_sensor.text_sensor_schema()
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SunTextSensor),
|
||||
cv.GenerateID(CONF_SUN_ID): cv.use_id(Sun),
|
||||
@@ -41,15 +41,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_ELEVATION, default=DEFAULT_ELEVATION): elevation,
|
||||
cv.Optional(CONF_FORMAT, default="%X"): cv.string_strict,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s")),
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s")),
|
||||
validate_optional_icon,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
paren = await cg.get_variable(config[CONF_SUN_ID])
|
||||
cg.add(var.set_parent(paren))
|
||||
|
||||
@@ -4,18 +4,27 @@ from esphome import automation
|
||||
from esphome.automation import Condition, maybe_simple_id
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_DEVICE_CLASS,
|
||||
CONF_ID,
|
||||
CONF_INVERTED,
|
||||
CONF_MQTT_ID,
|
||||
CONF_ON_TURN_OFF,
|
||||
CONF_ON_TURN_ON,
|
||||
CONF_TRIGGER_ID,
|
||||
CONF_MQTT_ID,
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_OUTLET,
|
||||
DEVICE_CLASS_SWITCH,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
IS_PLATFORM_COMPONENT = True
|
||||
DEVICE_CLASSES = [
|
||||
DEVICE_CLASS_EMPTY,
|
||||
DEVICE_CLASS_OUTLET,
|
||||
DEVICE_CLASS_SWITCH,
|
||||
]
|
||||
|
||||
switch_ns = cg.esphome_ns.namespace("switch_")
|
||||
Switch = switch_ns.class_("Switch", cg.EntityBase)
|
||||
@@ -51,6 +60,7 @@ SWITCH_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SwitchTurnOffTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_DEVICE_CLASS): cv.one_of(*DEVICE_CLASSES, lower=True),
|
||||
}
|
||||
)
|
||||
|
||||
@@ -71,6 +81,9 @@ async def setup_switch_core_(var, config):
|
||||
mqtt_ = cg.new_Pvariable(config[CONF_MQTT_ID], var)
|
||||
await mqtt.register_mqtt_component(mqtt_, config)
|
||||
|
||||
if CONF_DEVICE_CLASS in config:
|
||||
cg.add(var.set_device_class(config[CONF_DEVICE_CLASS]))
|
||||
|
||||
|
||||
async def register_switch(var, config):
|
||||
if not CORE.has_id(config[CONF_ID]):
|
||||
|
||||
@@ -46,5 +46,12 @@ void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; }
|
||||
uint32_t Switch::hash_base() { return 3129890955UL; }
|
||||
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; }
|
||||
|
||||
} // namespace switch_
|
||||
} // namespace esphome
|
||||
|
||||
@@ -20,6 +20,9 @@ namespace switch_ {
|
||||
if ((obj)->is_inverted()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Inverted: YES", prefix); \
|
||||
} \
|
||||
if (!(obj)->get_device_class().empty()) { \
|
||||
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
|
||||
} \
|
||||
}
|
||||
|
||||
/** Base class for all switches.
|
||||
@@ -88,6 +91,11 @@ 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);
|
||||
|
||||
protected:
|
||||
/** Write the given state to hardware. You should implement this
|
||||
* abstract method if you want to create your own switch.
|
||||
@@ -105,6 +113,7 @@ class Switch : public EntityBase {
|
||||
bool inverted_{false};
|
||||
Deduplicator<bool> publish_dedup_;
|
||||
ESPPreferenceObject rtc_;
|
||||
optional<std::string> device_class_;
|
||||
};
|
||||
|
||||
} // namespace switch_
|
||||
|
||||
@@ -7,9 +7,20 @@ CODEOWNERS = ["@0hax"]
|
||||
|
||||
teleinfo_ns = cg.esphome_ns.namespace("teleinfo")
|
||||
TeleInfo = teleinfo_ns.class_("TeleInfo", cg.PollingComponent, uart.UARTDevice)
|
||||
CONF_TELEINFO_ID = "teleinfo_id"
|
||||
|
||||
CONF_TELEINFO_ID = "teleinfo_id"
|
||||
CONF_TAG_NAME = "tag_name"
|
||||
CONF_HISTORICAL_MODE = "historical_mode"
|
||||
|
||||
|
||||
TELEINFO_LISTENER_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo),
|
||||
cv.Required(CONF_TAG_NAME): cv.string,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
|
||||
@@ -3,20 +3,22 @@ import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import CONF_ID, ICON_FLASH, UNIT_WATT_HOURS
|
||||
|
||||
from .. import teleinfo_ns, TeleInfo, CONF_TELEINFO_ID
|
||||
from .. import (
|
||||
CONF_TAG_NAME,
|
||||
TELEINFO_LISTENER_SCHEMA,
|
||||
teleinfo_ns,
|
||||
CONF_TELEINFO_ID,
|
||||
)
|
||||
|
||||
CONF_TAG_NAME = "tag_name"
|
||||
|
||||
TeleInfoSensor = teleinfo_ns.class_("TeleInfoSensor", sensor.Sensor, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0
|
||||
).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TeleInfoSensor),
|
||||
cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo),
|
||||
cv.Required(CONF_TAG_NAME): cv.string,
|
||||
}
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_WATT_HOURS, icon=ICON_FLASH, accuracy_decimals=0
|
||||
)
|
||||
.extend({cv.GenerateID(): cv.declare_id(TeleInfoSensor)})
|
||||
.extend(TELEINFO_LISTENER_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -1,22 +1,15 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import CONF_ID
|
||||
|
||||
from .. import teleinfo_ns, TeleInfo, CONF_TELEINFO_ID
|
||||
|
||||
CONF_TAG_NAME = "tag_name"
|
||||
from .. import CONF_TAG_NAME, TELEINFO_LISTENER_SCHEMA, teleinfo_ns, CONF_TELEINFO_ID
|
||||
|
||||
TeleInfoTextSensor = teleinfo_ns.class_(
|
||||
"TeleInfoTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TeleInfoTextSensor),
|
||||
cv.GenerateID(CONF_TELEINFO_ID): cv.use_id(TeleInfo),
|
||||
cv.Required(CONF_TAG_NAME): cv.string,
|
||||
}
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(klass=TeleInfoTextSensor).extend(
|
||||
TELEINFO_LISTENER_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
|
||||
103
esphome/components/template/lock/__init__.py
Normal file
103
esphome/components/template/lock/__init__.py
Normal file
@@ -0,0 +1,103 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import lock
|
||||
from esphome.const import (
|
||||
CONF_ASSUMED_STATE,
|
||||
CONF_ID,
|
||||
CONF_LAMBDA,
|
||||
CONF_LOCK_ACTION,
|
||||
CONF_OPEN_ACTION,
|
||||
CONF_OPTIMISTIC,
|
||||
CONF_STATE,
|
||||
CONF_UNLOCK_ACTION,
|
||||
)
|
||||
from .. import template_ns
|
||||
|
||||
TemplateLock = template_ns.class_("TemplateLock", lock.Lock, cg.Component)
|
||||
|
||||
LockState = lock.lock_ns.enum("LockState")
|
||||
|
||||
LOCK_STATES = {
|
||||
"LOCKED": LockState.LOCK_STATE_LOCKED,
|
||||
"UNLOCKED": LockState.LOCK_STATE_UNLOCKED,
|
||||
"JAMMED": LockState.LOCK_STATE_JAMMED,
|
||||
"LOCKING": LockState.LOCK_STATE_LOCKING,
|
||||
"UNLOCKING": LockState.LOCK_STATE_UNLOCKING,
|
||||
}
|
||||
|
||||
validate_lock_state = cv.enum(LOCK_STATES, upper=True)
|
||||
|
||||
|
||||
def validate(config):
|
||||
if not config[CONF_OPTIMISTIC] and (
|
||||
CONF_LOCK_ACTION not in config or CONF_UNLOCK_ACTION not in config
|
||||
):
|
||||
raise cv.Invalid(
|
||||
"Either optimistic mode must be enabled, or lock_action and unlock_action must be set, "
|
||||
"to handle the lock being changed."
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
lock.LOCK_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TemplateLock),
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_UNLOCK_ACTION): automation.validate_automation(
|
||||
single=True
|
||||
),
|
||||
cv.Optional(CONF_LOCK_ACTION): automation.validate_automation(single=True),
|
||||
cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await lock.register_lock(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
config[CONF_LAMBDA], [], return_type=cg.optional.template(LockState)
|
||||
)
|
||||
cg.add(var.set_state_lambda(template_))
|
||||
if CONF_UNLOCK_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_unlock_trigger(), [], config[CONF_UNLOCK_ACTION]
|
||||
)
|
||||
if CONF_LOCK_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_lock_trigger(), [], config[CONF_LOCK_ACTION]
|
||||
)
|
||||
if CONF_OPEN_ACTION in config:
|
||||
await automation.build_automation(
|
||||
var.get_open_trigger(), [], config[CONF_OPEN_ACTION]
|
||||
)
|
||||
cg.add(var.traits.set_supports_open(CONF_OPEN_ACTION in config))
|
||||
cg.add(var.traits.set_assumed_state(config[CONF_ASSUMED_STATE]))
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
|
||||
|
||||
@automation.register_action(
|
||||
"lock.template.publish",
|
||||
lock.LockPublishAction,
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(lock.Lock),
|
||||
cv.Required(CONF_STATE): cv.templatable(validate_lock_state),
|
||||
}
|
||||
),
|
||||
)
|
||||
async def lock_template_publish_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_STATE], args, LockState)
|
||||
cg.add(var.set_state(template_))
|
||||
return var
|
||||
59
esphome/components/template/lock/template_lock.cpp
Normal file
59
esphome/components/template/lock/template_lock.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "template_lock.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
using namespace esphome::lock;
|
||||
|
||||
static const char *const TAG = "template.lock";
|
||||
|
||||
TemplateLock::TemplateLock()
|
||||
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
|
||||
|
||||
void TemplateLock::loop() {
|
||||
if (!this->f_.has_value())
|
||||
return;
|
||||
auto val = (*this->f_)();
|
||||
if (!val.has_value())
|
||||
return;
|
||||
|
||||
this->publish_state(*val);
|
||||
}
|
||||
void TemplateLock::control(const lock::LockCall &call) {
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
}
|
||||
|
||||
auto state = *call.get_state();
|
||||
if (state == LOCK_STATE_LOCKED) {
|
||||
this->prev_trigger_ = this->lock_trigger_;
|
||||
this->lock_trigger_->trigger();
|
||||
} else if (state == LOCK_STATE_UNLOCKED) {
|
||||
this->prev_trigger_ = this->unlock_trigger_;
|
||||
this->unlock_trigger_->trigger();
|
||||
}
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(state);
|
||||
}
|
||||
void TemplateLock::open_latch() {
|
||||
if (this->prev_trigger_ != nullptr) {
|
||||
this->prev_trigger_->stop_action();
|
||||
}
|
||||
this->prev_trigger_ = this->open_trigger_;
|
||||
this->open_trigger_->trigger();
|
||||
}
|
||||
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
void TemplateLock::set_state_lambda(std::function<optional<lock::LockState>()> &&f) { this->f_ = f; }
|
||||
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
|
||||
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
|
||||
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
|
||||
Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; }
|
||||
void TemplateLock::dump_config() {
|
||||
LOG_LOCK("", "Template Lock", this);
|
||||
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
|
||||
}
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
||||
38
esphome/components/template/lock/template_lock.h
Normal file
38
esphome/components/template/lock/template_lock.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/lock/lock.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace template_ {
|
||||
|
||||
class TemplateLock : public lock::Lock, public Component {
|
||||
public:
|
||||
TemplateLock();
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
void set_state_lambda(std::function<optional<lock::LockState>()> &&f);
|
||||
Trigger<> *get_lock_trigger() const;
|
||||
Trigger<> *get_unlock_trigger() const;
|
||||
Trigger<> *get_open_trigger() const;
|
||||
void set_optimistic(bool optimistic);
|
||||
void loop() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void control(const lock::LockCall &call) override;
|
||||
void open_latch() override;
|
||||
|
||||
optional<std::function<optional<lock::LockState>()>> f_;
|
||||
bool optimistic_{false};
|
||||
Trigger<> *lock_trigger_;
|
||||
Trigger<> *unlock_trigger_;
|
||||
Trigger<> *open_trigger_;
|
||||
Trigger<> *prev_trigger_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace template_
|
||||
} // namespace esphome
|
||||
@@ -10,18 +10,21 @@ TemplateTextSensor = template_ns.class_(
|
||||
"TemplateTextSensor", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TemplateTextSensor),
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.text_sensor_schema()
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TemplateTextSensor),
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
|
||||
@@ -3,7 +3,9 @@ import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome.components import mqtt
|
||||
from esphome.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_FILTERS,
|
||||
CONF_ICON,
|
||||
CONF_ID,
|
||||
CONF_ON_VALUE,
|
||||
CONF_ON_RAW_VALUE,
|
||||
@@ -14,6 +16,7 @@ from esphome.const import (
|
||||
CONF_TO,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
from esphome.cpp_generator import MockObjClass
|
||||
from esphome.cpp_helpers import setup_entity
|
||||
from esphome.util import Registry
|
||||
|
||||
@@ -110,12 +113,10 @@ async def map_filter_to_code(config, filter_id):
|
||||
)
|
||||
|
||||
|
||||
icon = cv.icon
|
||||
|
||||
|
||||
TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
{
|
||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTextSensor),
|
||||
cv.GenerateID(): cv.declare_id(TextSensor),
|
||||
cv.Optional(CONF_FILTERS): validate_filters,
|
||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
|
||||
{
|
||||
@@ -132,6 +133,29 @@ TEXT_SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).exte
|
||||
}
|
||||
)
|
||||
|
||||
_UNDEF = object()
|
||||
|
||||
|
||||
def text_sensor_schema(
|
||||
klass: MockObjClass = _UNDEF,
|
||||
icon: str = _UNDEF,
|
||||
entity_category: str = _UNDEF,
|
||||
) -> cv.Schema:
|
||||
schema = TEXT_SENSOR_SCHEMA
|
||||
if klass is not _UNDEF:
|
||||
schema = schema.extend({cv.GenerateID(): cv.declare_id(klass)})
|
||||
if icon is not _UNDEF:
|
||||
schema = schema.extend({cv.Optional(CONF_ICON, default=icon): cv.icon})
|
||||
if entity_category is not _UNDEF:
|
||||
schema = schema.extend(
|
||||
{
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=entity_category
|
||||
): cv.entity_category
|
||||
}
|
||||
)
|
||||
return schema
|
||||
|
||||
|
||||
async def build_filters(config):
|
||||
return await cg.build_registry_list(FILTER_REGISTRY, config)
|
||||
@@ -164,6 +188,12 @@ async def register_text_sensor(var, config):
|
||||
await setup_text_sensor_core_(var, config)
|
||||
|
||||
|
||||
async def new_text_sensor(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await register_text_sensor(var, config)
|
||||
return var
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
async def to_code(config):
|
||||
cg.add_define("USE_TEXT_SENSOR")
|
||||
|
||||
@@ -9,7 +9,11 @@ from .. import touchscreen_ns, CONF_TOUCHSCREEN_ID, Touchscreen, TouchListener
|
||||
DEPENDENCIES = ["touchscreen"]
|
||||
|
||||
TouchscreenBinarySensor = touchscreen_ns.class_(
|
||||
"TouchscreenBinarySensor", binary_sensor.BinarySensor, TouchListener
|
||||
"TouchscreenBinarySensor",
|
||||
binary_sensor.BinarySensor,
|
||||
cg.Component,
|
||||
TouchListener,
|
||||
cg.Parented.template(Touchscreen),
|
||||
)
|
||||
|
||||
CONF_X_MIN = "x_min"
|
||||
@@ -39,7 +43,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Required(CONF_Y_MIN): cv.int_range(min=0, max=2000),
|
||||
cv.Required(CONF_Y_MAX): cv.int_range(min=0, max=2000),
|
||||
}
|
||||
),
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
validate_coords,
|
||||
)
|
||||
|
||||
@@ -47,7 +51,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
hub = await cg.get_variable(config[CONF_TOUCHSCREEN_ID])
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_TOUCHSCREEN_ID])
|
||||
|
||||
cg.add(
|
||||
var.set_area(
|
||||
config[CONF_X_MIN],
|
||||
@@ -56,4 +62,3 @@ async def to_code(config):
|
||||
config[CONF_Y_MAX],
|
||||
)
|
||||
)
|
||||
cg.add(hub.register_listener(var))
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/touchscreen/touchscreen.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace touchscreen {
|
||||
|
||||
class TouchscreenBinarySensor : public binary_sensor::BinarySensor, public TouchListener {
|
||||
class TouchscreenBinarySensor : public binary_sensor::BinarySensor,
|
||||
public Component,
|
||||
public TouchListener,
|
||||
public Parented<Touchscreen> {
|
||||
public:
|
||||
void setup() override { this->parent_->register_listener(this); }
|
||||
|
||||
/// Set the touch screen area where the button will detect the touch.
|
||||
void set_area(int16_t x_min, int16_t x_max, int16_t y_min, int16_t y_max) {
|
||||
this->x_min_ = x_min;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from esphome.components import text_sensor
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_ID, CONF_SENSOR_DATAPOINT
|
||||
from esphome.const import CONF_SENSOR_DATAPOINT
|
||||
from .. import tuya_ns, CONF_TUYA_ID, Tuya
|
||||
|
||||
DEPENDENCIES = ["tuya"]
|
||||
@@ -9,19 +9,22 @@ CODEOWNERS = ["@dentra"]
|
||||
|
||||
TuyaTextSensor = tuya_ns.class_("TuyaTextSensor", text_sensor.TextSensor, cg.Component)
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TuyaTextSensor),
|
||||
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
|
||||
cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.text_sensor_schema()
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TuyaTextSensor),
|
||||
cv.GenerateID(CONF_TUYA_ID): cv.use_id(Tuya),
|
||||
cv.Required(CONF_SENSOR_DATAPOINT): cv.uint8_t,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
|
||||
paren = await cg.get_variable(config[CONF_TUYA_ID])
|
||||
cg.add(var.set_tuya_parent(paren))
|
||||
|
||||
@@ -199,7 +199,7 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||
this->set_timeout("datapoint_dump", 1000, [this] { this->dump_config(); });
|
||||
this->initialized_callback_.call();
|
||||
}
|
||||
this->handle_datapoint_(buffer, len);
|
||||
this->handle_datapoints_(buffer, len);
|
||||
break;
|
||||
case TuyaCommandType::DATAPOINT_QUERY:
|
||||
break;
|
||||
@@ -224,105 +224,110 @@ void Tuya::handle_command_(uint8_t command, uint8_t version, const uint8_t *buff
|
||||
}
|
||||
}
|
||||
|
||||
void Tuya::handle_datapoint_(const uint8_t *buffer, size_t len) {
|
||||
if (len < 2)
|
||||
return;
|
||||
void Tuya::handle_datapoints_(const uint8_t *buffer, size_t len) {
|
||||
while (len >= 4) {
|
||||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = buffer[0];
|
||||
datapoint.type = (TuyaDatapointType) buffer[1];
|
||||
datapoint.value_uint = 0;
|
||||
|
||||
TuyaDatapoint datapoint{};
|
||||
datapoint.id = buffer[0];
|
||||
datapoint.type = (TuyaDatapointType) buffer[1];
|
||||
datapoint.value_uint = 0;
|
||||
|
||||
// Drop update if datapoint is in ignore_mcu_datapoint_update list
|
||||
for (uint8_t i : this->ignore_mcu_update_on_datapoints_) {
|
||||
if (datapoint.id == i) {
|
||||
ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
|
||||
size_t data_size = (buffer[2] << 8) + buffer[3];
|
||||
const uint8_t *data = buffer + 4;
|
||||
size_t data_len = len - 4;
|
||||
if (data_size > data_len) {
|
||||
ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu > %zu)", datapoint.id, data_size, data_len);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
size_t data_size = (buffer[2] << 8) + buffer[3];
|
||||
const uint8_t *data = buffer + 4;
|
||||
size_t data_len = len - 4;
|
||||
if (data_size > data_len) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has extra bytes that will be ignored (%zu > %zu)", datapoint.id, data_size, data_len);
|
||||
} else if (data_size < data_len) {
|
||||
ESP_LOGW(TAG, "Datapoint %u is truncated and cannot be parsed (%zu < %zu)", datapoint.id, data_size, data_len);
|
||||
return;
|
||||
}
|
||||
datapoint.len = data_len;
|
||||
datapoint.len = data_size;
|
||||
|
||||
switch (datapoint.type) {
|
||||
case TuyaDatapointType::RAW:
|
||||
datapoint.value_raw = std::vector<uint8_t>(data, data + data_len);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
|
||||
break;
|
||||
case TuyaDatapointType::BOOLEAN:
|
||||
if (data_len != 1) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_len);
|
||||
return;
|
||||
}
|
||||
datapoint.value_bool = data[0];
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
|
||||
break;
|
||||
case TuyaDatapointType::INTEGER:
|
||||
if (data_len != 4) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_len);
|
||||
return;
|
||||
}
|
||||
datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int);
|
||||
break;
|
||||
case TuyaDatapointType::STRING:
|
||||
datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_len);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
|
||||
break;
|
||||
case TuyaDatapointType::ENUM:
|
||||
if (data_len != 1) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_len);
|
||||
return;
|
||||
}
|
||||
datapoint.value_enum = data[0];
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
|
||||
break;
|
||||
case TuyaDatapointType::BITMASK:
|
||||
switch (data_len) {
|
||||
case 1:
|
||||
datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]);
|
||||
break;
|
||||
case 2:
|
||||
datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]);
|
||||
break;
|
||||
case 4:
|
||||
datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_len);
|
||||
switch (datapoint.type) {
|
||||
case TuyaDatapointType::RAW:
|
||||
datapoint.value_raw = std::vector<uint8_t>(data, data + data_size);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, format_hex_pretty(datapoint.value_raw).c_str());
|
||||
break;
|
||||
case TuyaDatapointType::BOOLEAN:
|
||||
if (data_size != 1) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad boolean len %zu", datapoint.id, data_size);
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
|
||||
return;
|
||||
}
|
||||
|
||||
// Update internal datapoints
|
||||
bool found = false;
|
||||
for (auto &other : this->datapoints_) {
|
||||
if (other.id == datapoint.id) {
|
||||
other = datapoint;
|
||||
found = true;
|
||||
}
|
||||
datapoint.value_bool = data[0];
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, ONOFF(datapoint.value_bool));
|
||||
break;
|
||||
case TuyaDatapointType::INTEGER:
|
||||
if (data_size != 4) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad integer len %zu", datapoint.id, data_size);
|
||||
return;
|
||||
}
|
||||
datapoint.value_uint = encode_uint32(data[0], data[1], data[2], data[3]);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_int);
|
||||
break;
|
||||
case TuyaDatapointType::STRING:
|
||||
datapoint.value_string = std::string(reinterpret_cast<const char *>(data), data_size);
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %s", datapoint.id, datapoint.value_string.c_str());
|
||||
break;
|
||||
case TuyaDatapointType::ENUM:
|
||||
if (data_size != 1) {
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad enum len %zu", datapoint.id, data_size);
|
||||
return;
|
||||
}
|
||||
datapoint.value_enum = data[0];
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %d", datapoint.id, datapoint.value_enum);
|
||||
break;
|
||||
case TuyaDatapointType::BITMASK:
|
||||
switch (data_size) {
|
||||
case 1:
|
||||
datapoint.value_bitmask = encode_uint32(0, 0, 0, data[0]);
|
||||
break;
|
||||
case 2:
|
||||
datapoint.value_bitmask = encode_uint32(0, 0, data[0], data[1]);
|
||||
break;
|
||||
case 4:
|
||||
datapoint.value_bitmask = encode_uint32(data[0], data[1], data[2], data[3]);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Datapoint %u has bad bitmask len %zu", datapoint.id, data_size);
|
||||
return;
|
||||
}
|
||||
ESP_LOGD(TAG, "Datapoint %u update to %#08X", datapoint.id, datapoint.value_bitmask);
|
||||
break;
|
||||
default:
|
||||
ESP_LOGW(TAG, "Datapoint %u has unknown type %#02hhX", datapoint.id, static_cast<uint8_t>(datapoint.type));
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
this->datapoints_.push_back(datapoint);
|
||||
}
|
||||
|
||||
// Run through listeners
|
||||
for (auto &listener : this->listeners_) {
|
||||
if (listener.datapoint_id == datapoint.id)
|
||||
listener.on_datapoint(datapoint);
|
||||
len -= data_size + 4;
|
||||
buffer = data + data_size;
|
||||
|
||||
// drop update if datapoint is in ignore_mcu_datapoint_update list
|
||||
bool skip = false;
|
||||
for (auto i : this->ignore_mcu_update_on_datapoints_) {
|
||||
if (datapoint.id == i) {
|
||||
ESP_LOGV(TAG, "Datapoint %u found in ignore_mcu_update_on_datapoints list, dropping MCU update", datapoint.id);
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skip)
|
||||
continue;
|
||||
|
||||
// Update internal datapoints
|
||||
bool found = false;
|
||||
for (auto &other : this->datapoints_) {
|
||||
if (other.id == datapoint.id) {
|
||||
other = datapoint;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
this->datapoints_.push_back(datapoint);
|
||||
}
|
||||
|
||||
// Run through listeners
|
||||
for (auto &listener : this->listeners_) {
|
||||
if (listener.datapoint_id == datapoint.id)
|
||||
listener.on_datapoint(datapoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -101,7 +101,7 @@ class Tuya : public Component, public uart::UARTDevice {
|
||||
|
||||
protected:
|
||||
void handle_char_(uint8_t c);
|
||||
void handle_datapoint_(const uint8_t *buffer, size_t len);
|
||||
void handle_datapoints_(const uint8_t *buffer, size_t len);
|
||||
optional<TuyaDatapoint> get_datapoint_(uint8_t datapoint_id);
|
||||
bool validate_message_();
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@ import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import (
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ID,
|
||||
CONF_ICON,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_NEW_BOX,
|
||||
CONF_HIDE_TIMESTAMP,
|
||||
@@ -15,20 +12,22 @@ VersionTextSensor = version_ns.class_(
|
||||
"VersionTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(VersionTextSensor),
|
||||
cv.Optional(CONF_ICON, default=ICON_NEW_BOX): text_sensor.icon,
|
||||
cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean,
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
): cv.entity_category,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.text_sensor_schema(
|
||||
icon=ICON_NEW_BOX,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(VersionTextSensor),
|
||||
cv.Optional(CONF_HIDE_TIMESTAMP, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await text_sensor.register_text_sensor(var, config)
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
cg.add(var.set_hide_timestamp(config[CONF_HIDE_TIMESTAMP]))
|
||||
|
||||
@@ -152,6 +152,13 @@ void WebServer::setup() {
|
||||
client->send(this->select_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
for (auto *obj : App.get_locks()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->lock_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
});
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
@@ -287,6 +294,20 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
for (auto *obj : App.get_locks()) {
|
||||
if (this->include_internal_ || !obj->is_internal()) {
|
||||
write_row(stream, obj, "lock", "", [](AsyncResponseStream &stream, EntityBase *obj) {
|
||||
lock::Lock *lock = (lock::Lock *) obj;
|
||||
stream.print("<button>Lock</button><button>Unlock</button>");
|
||||
if (lock->traits.get_supports_open()) {
|
||||
stream.print("<button>Open</button>");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
stream->print(F("</tbody></table><p>See <a href=\"https://esphome.io/web-api/index.html\">ESPHome Web API</a> for "
|
||||
"REST API documentation.</p>"));
|
||||
if (this->allow_ota_) {
|
||||
@@ -763,6 +784,43 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||
this->events_.send(this->lock_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "lock-" + obj->get_object_id();
|
||||
root["state"] = lock::lock_state_to_string(value);
|
||||
root["value"] = value;
|
||||
});
|
||||
}
|
||||
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (lock::Lock *obj : App.get_locks()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->lock_json(obj, obj->state);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "lock") {
|
||||
this->defer([obj]() { obj->lock(); });
|
||||
request->send(200);
|
||||
} else if (match.method == "unlock") {
|
||||
this->defer([obj]() { obj->unlock(); });
|
||||
request->send(200);
|
||||
} else if (match.method == "open") {
|
||||
this->defer([obj]() { obj->open(); });
|
||||
request->send(200);
|
||||
} else {
|
||||
request->send(404);
|
||||
}
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
#endif
|
||||
|
||||
bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||
if (request->url() == "/")
|
||||
return true;
|
||||
@@ -830,6 +888,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
|
||||
return true;
|
||||
#endif
|
||||
|
||||
return false;
|
||||
}
|
||||
void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
@@ -922,6 +985,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if (match.domain == "lock") {
|
||||
this->handle_lock_request(request, match);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool WebServer::isRequestHandlerTrivial() { return false; }
|
||||
|
||||
@@ -185,6 +185,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
std::string select_json(select::Select *obj, const std::string &value);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void on_lock_update(lock::Lock *obj) override;
|
||||
|
||||
/// Handle a lock request under '/lock/<id>/</lock/unlock/open>'.
|
||||
void handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the lock state with its value as a JSON string.
|
||||
std::string lock_json(lock::Lock *obj, lock::LockState value);
|
||||
#endif
|
||||
|
||||
/// Override the web handler's canHandle method.
|
||||
bool canHandle(AsyncWebServerRequest *request) override;
|
||||
/// Override the web handler's handleRequest method.
|
||||
|
||||
@@ -3,8 +3,6 @@ import esphome.config_validation as cv
|
||||
from esphome.components import text_sensor
|
||||
from esphome.const import (
|
||||
CONF_BSSID,
|
||||
CONF_ENTITY_CATEGORY,
|
||||
CONF_ID,
|
||||
CONF_IP_ADDRESS,
|
||||
CONF_SCAN_RESULTS,
|
||||
CONF_SSID,
|
||||
@@ -16,14 +14,16 @@ DEPENDENCIES = ["wifi"]
|
||||
|
||||
wifi_info_ns = cg.esphome_ns.namespace("wifi_info")
|
||||
IPAddressWiFiInfo = wifi_info_ns.class_(
|
||||
"IPAddressWiFiInfo", text_sensor.TextSensor, cg.Component
|
||||
"IPAddressWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
ScanResultsWiFiInfo = wifi_info_ns.class_(
|
||||
"ScanResultsWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
SSIDWiFiInfo = wifi_info_ns.class_("SSIDWiFiInfo", text_sensor.TextSensor, cg.Component)
|
||||
SSIDWiFiInfo = wifi_info_ns.class_(
|
||||
"SSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
BSSIDWiFiInfo = wifi_info_ns.class_(
|
||||
"BSSIDWiFiInfo", text_sensor.TextSensor, cg.Component
|
||||
"BSSIDWiFiInfo", text_sensor.TextSensor, cg.PollingComponent
|
||||
)
|
||||
MacAddressWifiInfo = wifi_info_ns.class_(
|
||||
"MacAddressWifiInfo", text_sensor.TextSensor, cg.Component
|
||||
@@ -31,45 +31,20 @@ MacAddressWifiInfo = wifi_info_ns.class_(
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_IP_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(IPAddressWiFiInfo),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
): cv.entity_category,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_SCAN_RESULTS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ScanResultsWiFiInfo),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
): cv.entity_category,
|
||||
}
|
||||
cv.Optional(CONF_IP_ADDRESS): text_sensor.text_sensor_schema(
|
||||
klass=IPAddressWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("1s")),
|
||||
cv.Optional(CONF_SCAN_RESULTS): text_sensor.text_sensor_schema(
|
||||
klass=ScanResultsWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("60s")),
|
||||
cv.Optional(CONF_SSID): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(SSIDWiFiInfo),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
): cv.entity_category,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_BSSID): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(BSSIDWiFiInfo),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
): cv.entity_category,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_MAC_ADDRESS): text_sensor.TEXT_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MacAddressWifiInfo),
|
||||
cv.Optional(
|
||||
CONF_ENTITY_CATEGORY, default=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
): cv.entity_category,
|
||||
}
|
||||
cv.Optional(CONF_SSID): text_sensor.text_sensor_schema(
|
||||
klass=SSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("1s")),
|
||||
cv.Optional(CONF_BSSID): text_sensor.text_sensor_schema(
|
||||
klass=BSSIDWiFiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
).extend(cv.polling_component_schema("1s")),
|
||||
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
|
||||
klass=MacAddressWifiInfo, entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||
),
|
||||
}
|
||||
)
|
||||
@@ -78,9 +53,8 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
async def setup_conf(config, key):
|
||||
if key in config:
|
||||
conf = config[key]
|
||||
var = cg.new_Pvariable(conf[CONF_ID])
|
||||
var = await text_sensor.new_text_sensor(conf)
|
||||
await cg.register_component(var, conf)
|
||||
await text_sensor.register_text_sensor(var, conf)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
namespace esphome {
|
||||
namespace wifi_info {
|
||||
|
||||
class IPAddressWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
class IPAddressWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
public:
|
||||
void loop() override {
|
||||
void update() override {
|
||||
auto ip = wifi::global_wifi_component->wifi_sta_ip();
|
||||
if (ip != this->last_ip_) {
|
||||
this->last_ip_ = ip;
|
||||
@@ -53,9 +53,9 @@ class ScanResultsWiFiInfo : public PollingComponent, public text_sensor::TextSen
|
||||
std::string last_scan_results_;
|
||||
};
|
||||
|
||||
class SSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
class SSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
public:
|
||||
void loop() override {
|
||||
void update() override {
|
||||
std::string ssid = wifi::global_wifi_component->wifi_ssid();
|
||||
if (this->last_ssid_ != ssid) {
|
||||
this->last_ssid_ = ssid;
|
||||
@@ -70,9 +70,9 @@ class SSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
std::string last_ssid_;
|
||||
};
|
||||
|
||||
class BSSIDWiFiInfo : public Component, public text_sensor::TextSensor {
|
||||
class BSSIDWiFiInfo : public PollingComponent, public text_sensor::TextSensor {
|
||||
public:
|
||||
void loop() override {
|
||||
void update() override {
|
||||
wifi::bssid_t bssid = wifi::global_wifi_component->wifi_bssid();
|
||||
if (memcmp(bssid.data(), last_bssid_.data(), 6) != 0) {
|
||||
std::copy(bssid.begin(), bssid.end(), last_bssid_.begin());
|
||||
|
||||
@@ -1713,30 +1713,49 @@ def require_framework_version(
|
||||
esp_idf=None,
|
||||
esp32_arduino=None,
|
||||
esp8266_arduino=None,
|
||||
max_version=False,
|
||||
extra_message=None,
|
||||
):
|
||||
def validator(value):
|
||||
core_data = CORE.data[KEY_CORE]
|
||||
framework = core_data[KEY_TARGET_FRAMEWORK]
|
||||
if framework == "esp-idf":
|
||||
if esp_idf is None:
|
||||
raise Invalid("This feature is incompatible with esp-idf")
|
||||
msg = "This feature is incompatible with esp-idf"
|
||||
if extra_message:
|
||||
msg += f". {extra_message}"
|
||||
raise Invalid(msg)
|
||||
required = esp_idf
|
||||
elif CORE.is_esp32 and framework == "arduino":
|
||||
if esp32_arduino is None:
|
||||
raise Invalid(
|
||||
"This feature is incompatible with ESP32 using arduino framework"
|
||||
)
|
||||
msg = "This feature is incompatible with ESP32 using arduino framework"
|
||||
if extra_message:
|
||||
msg += f". {extra_message}"
|
||||
raise Invalid(msg)
|
||||
required = esp32_arduino
|
||||
elif CORE.is_esp8266 and framework == "arduino":
|
||||
if esp8266_arduino is None:
|
||||
raise Invalid("This feature is incompatible with ESP8266")
|
||||
msg = "This feature is incompatible with ESP8266"
|
||||
if extra_message:
|
||||
msg += f". {extra_message}"
|
||||
raise Invalid(msg)
|
||||
required = esp8266_arduino
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
if max_version:
|
||||
if core_data[KEY_FRAMEWORK_VERSION] > required:
|
||||
msg = f"This feature requires framework version {required} or lower"
|
||||
if extra_message:
|
||||
msg += f". {extra_message}"
|
||||
raise Invalid(msg)
|
||||
return value
|
||||
|
||||
if core_data[KEY_FRAMEWORK_VERSION] < required:
|
||||
raise Invalid(
|
||||
f"This feature requires at least framework version {required}"
|
||||
)
|
||||
msg = f"This feature requires at least framework version {required}"
|
||||
if extra_message:
|
||||
msg += f". {extra_message}"
|
||||
raise Invalid(msg)
|
||||
return value
|
||||
|
||||
return validator
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2022.2.0-dev"
|
||||
__version__ = "2022.2.0b2"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
|
||||
@@ -168,6 +168,7 @@ CONF_DIRECTION = "direction"
|
||||
CONF_DIRECTION_OUTPUT = "direction_output"
|
||||
CONF_DISABLED_BY_DEFAULT = "disabled_by_default"
|
||||
CONF_DISCOVERY = "discovery"
|
||||
CONF_DISCOVERY_OBJECT_ID_GENERATOR = "discovery_object_id_generator"
|
||||
CONF_DISCOVERY_PREFIX = "discovery_prefix"
|
||||
CONF_DISCOVERY_RETAIN = "discovery_retain"
|
||||
CONF_DISCOVERY_UNIQUE_ID_GENERATOR = "discovery_unique_id_generator"
|
||||
@@ -334,6 +335,7 @@ CONF_LINE_THICKNESS = "line_thickness"
|
||||
CONF_LINE_TYPE = "line_type"
|
||||
CONF_LOADED_INTEGRATIONS = "loaded_integrations"
|
||||
CONF_LOCAL = "local"
|
||||
CONF_LOCK_ACTION = "lock_action"
|
||||
CONF_LOG_TOPIC = "log_topic"
|
||||
CONF_LOGGER = "logger"
|
||||
CONF_LOGS = "logs"
|
||||
@@ -426,9 +428,11 @@ CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan"
|
||||
CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched"
|
||||
CONF_ON_FINGER_SCAN_UNMATCHED = "on_finger_scan_unmatched"
|
||||
CONF_ON_JSON_MESSAGE = "on_json_message"
|
||||
CONF_ON_LOCK = "on_lock"
|
||||
CONF_ON_LOOP = "on_loop"
|
||||
CONF_ON_MESSAGE = "on_message"
|
||||
CONF_ON_MULTI_CLICK = "on_multi_click"
|
||||
CONF_ON_OPEN = "on_open"
|
||||
CONF_ON_PRESS = "on_press"
|
||||
CONF_ON_RAW_VALUE = "on_raw_value"
|
||||
CONF_ON_RELEASE = "on_release"
|
||||
@@ -442,6 +446,7 @@ CONF_ON_TIME_SYNC = "on_time_sync"
|
||||
CONF_ON_TOUCH = "on_touch"
|
||||
CONF_ON_TURN_OFF = "on_turn_off"
|
||||
CONF_ON_TURN_ON = "on_turn_on"
|
||||
CONF_ON_UNLOCK = "on_unlock"
|
||||
CONF_ON_VALUE = "on_value"
|
||||
CONF_ON_VALUE_RANGE = "on_value_range"
|
||||
CONF_ONE = "one"
|
||||
@@ -709,6 +714,7 @@ CONF_UART_ID = "uart_id"
|
||||
CONF_UID = "uid"
|
||||
CONF_UNIQUE = "unique"
|
||||
CONF_UNIT_OF_MEASUREMENT = "unit_of_measurement"
|
||||
CONF_UNLOCK_ACTION = "unlock_action"
|
||||
CONF_UPDATE_INTERVAL = "update_interval"
|
||||
CONF_UPDATE_ON_BOOT = "update_on_boot"
|
||||
CONF_URL = "url"
|
||||
@@ -915,6 +921,9 @@ DEVICE_CLASS_VOLTAGE = "voltage"
|
||||
DEVICE_CLASS_UPDATE = "update"
|
||||
# device classes of button component
|
||||
DEVICE_CLASS_RESTART = "restart"
|
||||
# device classes of switch component
|
||||
DEVICE_CLASS_OUTLET = "outlet"
|
||||
DEVICE_CLASS_SWITCH = "switch"
|
||||
|
||||
|
||||
# state classes
|
||||
|
||||
@@ -20,7 +20,7 @@ from esphome.coroutine import FakeEventLoop as _FakeEventLoop
|
||||
|
||||
# pylint: disable=unused-import
|
||||
from esphome.coroutine import coroutine, coroutine_with_priority # noqa
|
||||
from esphome.helpers import ensure_unique_string, is_hassio
|
||||
from esphome.helpers import ensure_unique_string, is_ha_addon
|
||||
from esphome.util import OrderedDict
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -568,12 +568,12 @@ class EsphomeCore:
|
||||
return self.relative_build_path("src", *path)
|
||||
|
||||
def relative_pioenvs_path(self, *path):
|
||||
if is_hassio():
|
||||
if is_ha_addon():
|
||||
return os.path.join("/data", self.name, ".pioenvs", *path)
|
||||
return self.relative_build_path(".pioenvs", *path)
|
||||
|
||||
def relative_piolibdeps_path(self, *path):
|
||||
if is_hassio():
|
||||
if is_ha_addon():
|
||||
return os.path.join("/data", self.name, ".piolibdeps", *path)
|
||||
return self.relative_build_path(".piolibdeps", *path)
|
||||
|
||||
|
||||
@@ -42,6 +42,9 @@
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -104,6 +107,10 @@ class Application {
|
||||
void register_select(select::Select *select) { this->selects_.push_back(select); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); }
|
||||
#endif
|
||||
|
||||
/// Register the component in this Application instance.
|
||||
template<class C> C *register_component(C *c) {
|
||||
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
|
||||
@@ -257,6 +264,15 @@ class Application {
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
const std::vector<lock::Lock *> &get_locks() { return this->locks_; }
|
||||
lock::Lock *get_lock_by_key(uint32_t key, bool include_internal = false) {
|
||||
for (auto *obj : this->locks_)
|
||||
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
|
||||
return obj;
|
||||
return nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
Scheduler scheduler;
|
||||
|
||||
@@ -305,6 +321,9 @@ class Application {
|
||||
#ifdef USE_SELECT
|
||||
std::vector<select::Select *> selects_{};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
std::vector<lock::Lock *> locks_{};
|
||||
#endif
|
||||
|
||||
std::string name_;
|
||||
std::string compilation_time_;
|
||||
|
||||
@@ -65,6 +65,12 @@ void Controller::setup_controller(bool include_internal) {
|
||||
obj->add_on_state_callback([this, obj](const std::string &state) { this->on_select_update(obj, state); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
for (auto *obj : App.get_locks()) {
|
||||
if (include_internal || !obj->is_internal())
|
||||
obj->add_on_state_callback([this, obj]() { this->on_lock_update(obj); });
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -34,6 +34,9 @@
|
||||
#ifdef USE_SELECT
|
||||
#include "esphome/components/select/select.h"
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
#include "esphome/components/lock/lock.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -70,6 +73,9 @@ class Controller {
|
||||
#ifdef USE_SELECT
|
||||
virtual void on_select_update(select::Select *obj, const std::string &state){};
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
virtual void on_lock_update(lock::Lock *obj){};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#define USE_GRAPH
|
||||
#define USE_HOMEASSISTANT_TIME
|
||||
#define USE_LIGHT
|
||||
#define USE_LOCK
|
||||
#define USE_LOGGER
|
||||
#define USE_MDNS
|
||||
#define USE_NUMBER
|
||||
|
||||
@@ -773,9 +773,11 @@ class MockObj(Expression):
|
||||
return MockObj(f"{self.base} &", "")
|
||||
if name == "ptr":
|
||||
return MockObj(f"{self.base} *", "")
|
||||
if name == "const_ptr":
|
||||
return MockObj(f"{self.base} *const", "")
|
||||
if name == "const":
|
||||
return MockObj(f"const {self.base}", "")
|
||||
raise ValueError("Expected one of ref, ptr, const.")
|
||||
raise ValueError("Expected one of ref, ptr, const_ptr, const.")
|
||||
|
||||
@property
|
||||
def using(self) -> "MockObj":
|
||||
|
||||
@@ -15,6 +15,7 @@ uint16 = global_ns.namespace("uint16_t")
|
||||
uint32 = global_ns.namespace("uint32_t")
|
||||
uint64 = global_ns.namespace("uint64_t")
|
||||
int32 = global_ns.namespace("int32_t")
|
||||
int64 = global_ns.namespace("int64_t")
|
||||
const_char_ptr = global_ns.namespace("const char *")
|
||||
NAN = global_ns.namespace("NAN")
|
||||
esphome_ns = global_ns # using namespace esphome;
|
||||
@@ -34,3 +35,4 @@ InternalGPIOPin = esphome_ns.class_("InternalGPIOPin", GPIOPin)
|
||||
gpio_ns = esphome_ns.namespace("gpio")
|
||||
gpio_Flags = gpio_ns.enum("Flags", is_class=True)
|
||||
EntityCategory = esphome_ns.enum("EntityCategory")
|
||||
Parented = esphome_ns.class_("Parented")
|
||||
|
||||
@@ -55,13 +55,13 @@ class DashboardSettings:
|
||||
self.password_hash = ""
|
||||
self.username = ""
|
||||
self.using_password = False
|
||||
self.on_hassio = False
|
||||
self.on_ha_addon = False
|
||||
self.cookie_secret = None
|
||||
|
||||
def parse_args(self, args):
|
||||
self.on_hassio = args.hassio
|
||||
self.on_ha_addon = args.ha_addon
|
||||
password = args.password or os.getenv("PASSWORD", "")
|
||||
if not self.on_hassio:
|
||||
if not self.on_ha_addon:
|
||||
self.username = args.username or os.getenv("USERNAME", "")
|
||||
self.using_password = bool(password)
|
||||
if self.using_password:
|
||||
@@ -77,14 +77,14 @@ class DashboardSettings:
|
||||
return get_bool_env("ESPHOME_DASHBOARD_USE_PING")
|
||||
|
||||
@property
|
||||
def using_hassio_auth(self):
|
||||
if not self.on_hassio:
|
||||
def using_ha_addon_auth(self):
|
||||
if not self.on_ha_addon:
|
||||
return False
|
||||
return not get_bool_env("DISABLE_HA_AUTHENTICATION")
|
||||
|
||||
@property
|
||||
def using_auth(self):
|
||||
return self.using_password or self.using_hassio_auth
|
||||
return self.using_password or self.using_ha_addon_auth
|
||||
|
||||
def check_password(self, username, password):
|
||||
if not self.using_auth:
|
||||
@@ -138,10 +138,10 @@ def authenticated(func):
|
||||
|
||||
|
||||
def is_authenticated(request_handler):
|
||||
if settings.on_hassio:
|
||||
if settings.on_ha_addon:
|
||||
# Handle ingress - disable auth on ingress port
|
||||
# X-Hassio-Ingress is automatically stripped on the non-ingress server in nginx
|
||||
header = request_handler.request.headers.get("X-Hassio-Ingress", "NO")
|
||||
# X-HA-Ingress is automatically stripped on the non-ingress server in nginx
|
||||
header = request_handler.request.headers.get("X-HA-Ingress", "NO")
|
||||
if str(header) == "YES":
|
||||
return True
|
||||
if settings.using_auth:
|
||||
@@ -792,23 +792,23 @@ class LoginHandler(BaseHandler):
|
||||
self.render(
|
||||
"login.template.html",
|
||||
error=error,
|
||||
hassio=settings.using_hassio_auth,
|
||||
ha_addon=settings.using_ha_addon_auth,
|
||||
has_username=bool(settings.username),
|
||||
**template_args(),
|
||||
)
|
||||
|
||||
def post_hassio_login(self):
|
||||
def post_ha_addon_login(self):
|
||||
import requests
|
||||
|
||||
headers = {
|
||||
"X-HASSIO-KEY": os.getenv("HASSIO_TOKEN"),
|
||||
"Authentication": f"Bearer {os.getenv('SUPERVISOR_TOKEN')}",
|
||||
}
|
||||
data = {
|
||||
"username": self.get_argument("username", ""),
|
||||
"password": self.get_argument("password", ""),
|
||||
}
|
||||
try:
|
||||
req = requests.post("http://hassio/auth", headers=headers, data=data)
|
||||
req = requests.post("http://supervisor/auth", headers=headers, data=data)
|
||||
if req.status_code == 200:
|
||||
self.set_secure_cookie("authenticated", cookie_authenticated_yes)
|
||||
self.redirect("/")
|
||||
@@ -835,8 +835,8 @@ class LoginHandler(BaseHandler):
|
||||
self.render_login_page(error=error_str)
|
||||
|
||||
def post(self):
|
||||
if settings.using_hassio_auth:
|
||||
self.post_hassio_login()
|
||||
if settings.using_ha_addon_auth:
|
||||
self.post_ha_addon_login()
|
||||
else:
|
||||
self.post_native_login()
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import codecs
|
||||
from contextlib import suppress
|
||||
|
||||
import logging
|
||||
import os
|
||||
@@ -143,8 +144,8 @@ def get_bool_env(var, default=False):
|
||||
return bool(os.getenv(var, default))
|
||||
|
||||
|
||||
def is_hassio():
|
||||
return get_bool_env("ESPHOME_IS_HASSIO")
|
||||
def is_ha_addon():
|
||||
return get_bool_env("ESPHOME_IS_HA_ADDON")
|
||||
|
||||
|
||||
def walk_files(path):
|
||||
@@ -233,8 +234,20 @@ def copy_file_if_changed(src: os.PathLike, dst: os.PathLike) -> None:
|
||||
return
|
||||
mkdir_p(os.path.dirname(dst))
|
||||
try:
|
||||
shutil.copy(src, dst)
|
||||
shutil.copyfile(src, dst)
|
||||
except OSError as err:
|
||||
if isinstance(err, PermissionError):
|
||||
# Older esphome versions copied over the src file permissions too.
|
||||
# So when the dst file had 444 permissions, the dst file would have those
|
||||
# too and subsequent writes would fail
|
||||
|
||||
# -> delete file (it would be overwritten anyway), and try again
|
||||
# if that fails, use normal error handler
|
||||
with suppress(OSError):
|
||||
os.unlink(dst)
|
||||
shutil.copyfile(src, dst)
|
||||
return
|
||||
|
||||
from esphome.core import EsphomeError
|
||||
|
||||
raise EsphomeError(f"Error copying file {src} to {dst}: {err}") from err
|
||||
|
||||
@@ -35,9 +35,10 @@ build_flags =
|
||||
lib_deps =
|
||||
esphome/noise-c@0.1.4 ; api
|
||||
makuna/NeoPixelBus@2.6.9 ; neopixelbus
|
||||
esphome/Improv@1.2.0 ; improv_serial / esp32_improv
|
||||
esphome/Improv@1.2.1 ; improv_serial / esp32_improv
|
||||
bblanchon/ArduinoJson@6.18.5 ; json
|
||||
wjtje/qr-code-generator-library@1.7.0 ; qr_code
|
||||
functionpointer/arduino-MLX90393@1.0.0 ; mlx90393
|
||||
build_flags =
|
||||
-DESPHOME_LOG_LEVEL=ESPHOME_LOG_LEVEL_VERY_VERBOSE
|
||||
src_filter =
|
||||
@@ -93,6 +94,7 @@ lib_deps =
|
||||
DNSServer ; captive_portal (Arduino built-in)
|
||||
build_flags =
|
||||
${common:arduino.build_flags}
|
||||
-Wno-nonnull-compare
|
||||
-DUSE_ESP8266
|
||||
-DUSE_ESP8266_FRAMEWORK_ARDUINO
|
||||
extra_scripts = post:esphome/components/esp8266/post_build.py.script
|
||||
|
||||
@@ -9,8 +9,8 @@ pyserial==3.5
|
||||
platformio==5.2.4 # When updating platformio, also update Dockerfile
|
||||
esptool==3.2
|
||||
click==8.0.3
|
||||
esphome-dashboard==20220116.0
|
||||
aioesphomeapi==10.8.1
|
||||
esphome-dashboard==20220209.0
|
||||
aioesphomeapi==10.8.2
|
||||
zeroconf==0.37.0
|
||||
|
||||
# esp-idf requires this, but doesn't bundle it by default
|
||||
|
||||
@@ -4,9 +4,9 @@ black==22.1.0
|
||||
pre-commit
|
||||
|
||||
# Unit tests
|
||||
pytest==6.2.5
|
||||
pytest==7.0.0
|
||||
pytest-cov==3.0.0
|
||||
pytest-mock==3.7.0
|
||||
pytest-asyncio==0.17.2
|
||||
pytest-asyncio==0.18.0
|
||||
asyncmock==0.4.2
|
||||
hypothesis==5.49.0
|
||||
|
||||
@@ -222,7 +222,13 @@ def lint_ext_check(fname):
|
||||
|
||||
|
||||
@lint_file_check(
|
||||
exclude=["**.sh", "docker/hassio-rootfs/**", "docker/*.py", "script/*", "setup.py"]
|
||||
exclude=[
|
||||
"**.sh",
|
||||
"docker/ha-addon-rootfs/**",
|
||||
"docker/*.py",
|
||||
"script/*",
|
||||
"setup.py",
|
||||
]
|
||||
)
|
||||
def lint_executable_bit(fname):
|
||||
ex = EXECUTABLE_BIT[fname]
|
||||
@@ -598,6 +604,7 @@ def lint_inclusive_language(fname, match):
|
||||
"esphome/components/display/display_buffer.h",
|
||||
"esphome/components/fan/fan.h",
|
||||
"esphome/components/i2c/i2c.h",
|
||||
"esphome/components/lock/lock.h",
|
||||
"esphome/components/mqtt/mqtt_component.h",
|
||||
"esphome/components/number/number.h",
|
||||
"esphome/components/output/binary_output.h",
|
||||
|
||||
@@ -1077,7 +1077,21 @@ sensor:
|
||||
cs_pin:
|
||||
mcp23xxx: mcp23017_hub
|
||||
number: 14
|
||||
|
||||
- platform: max9611
|
||||
i2c_id: i2c_bus
|
||||
shunt_resistance: 0.2 ohm
|
||||
gain: '1X'
|
||||
voltage:
|
||||
name: Max9611 Voltage
|
||||
current:
|
||||
name: Max9611 Current
|
||||
power:
|
||||
name: Max9611 Watts
|
||||
temperature:
|
||||
name: Max9611 Temp
|
||||
update_interval: 1s
|
||||
|
||||
|
||||
esp32_touch:
|
||||
setup_mode: False
|
||||
iir_filter: 10ms
|
||||
@@ -2037,6 +2051,10 @@ switch:
|
||||
- platform: template
|
||||
id: ble1_status
|
||||
optimistic: true
|
||||
- platform: template
|
||||
id: outlet_switch
|
||||
optimistic: true
|
||||
device_class: outlet
|
||||
|
||||
fan:
|
||||
- platform: binary
|
||||
@@ -2582,3 +2600,28 @@ select:
|
||||
qr_code:
|
||||
- id: homepage_qr
|
||||
value: https://esphome.io/index.html
|
||||
|
||||
lock:
|
||||
- platform: template
|
||||
id: test_lock1
|
||||
name: "Template Switch"
|
||||
lambda: |-
|
||||
if (id(binary_sensor1).state) {
|
||||
return LOCK_STATE_LOCKED;
|
||||
}else{
|
||||
return LOCK_STATE_UNLOCKED;
|
||||
}
|
||||
optimistic: true
|
||||
assumed_state: no
|
||||
on_unlock:
|
||||
- lock.template.publish:
|
||||
id: test_lock1
|
||||
state: !lambda "return LOCK_STATE_UNLOCKED;"
|
||||
on_lock:
|
||||
- lock.template.publish:
|
||||
id: test_lock1
|
||||
state: !lambda "return LOCK_STATE_LOCKED;"
|
||||
- platform: output
|
||||
name: "Generic Output Lock"
|
||||
id: test_lock2
|
||||
output: pca_6
|
||||
|
||||
@@ -324,6 +324,13 @@ sensor:
|
||||
bus_voltage:
|
||||
name: "INA260 Voltage"
|
||||
update_interval: 60s
|
||||
- platform: radon_eye_rd200
|
||||
ble_client_id: radon_eye_ble_id
|
||||
update_interval: 10min
|
||||
radon:
|
||||
name: "RD200 Radon"
|
||||
radon_long_term:
|
||||
name: "RD200 Radon Long Term"
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
@@ -423,10 +430,14 @@ ble_client:
|
||||
id: airthings01
|
||||
- mac_address: 01:02:03:04:05:06
|
||||
id: airthingsmini01
|
||||
- mac_address: 01:02:03:04:05:06
|
||||
id: radon_eye_ble_id
|
||||
|
||||
|
||||
airthings_ble:
|
||||
|
||||
radon_eye_ble:
|
||||
|
||||
ruuvi_ble:
|
||||
|
||||
xiaomi_ble:
|
||||
|
||||
@@ -692,6 +692,20 @@ sensor:
|
||||
name: 'testwave'
|
||||
component_id: 2
|
||||
wave_channel_id: 1
|
||||
- platform: mlx90393
|
||||
oversampling: 1
|
||||
filter: 0
|
||||
gain: "3X"
|
||||
x_axis:
|
||||
name: "mlxxaxis"
|
||||
y_axis:
|
||||
name: "mlxyaxis"
|
||||
z_axis:
|
||||
name: "mlxzaxis"
|
||||
resolution: 17BIT
|
||||
temperature:
|
||||
name: "mlxtemp"
|
||||
oversampling: 2
|
||||
time:
|
||||
- platform: homeassistant
|
||||
|
||||
|
||||
@@ -328,6 +328,7 @@ binary_sensor:
|
||||
number: 3
|
||||
|
||||
- platform: touchscreen
|
||||
touchscreen_id: lilygo_touchscreen
|
||||
id: touch_key1
|
||||
x_min: 0
|
||||
x_max: 100
|
||||
@@ -561,3 +562,12 @@ touchscreen:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: ["touch.x", "touch.y"]
|
||||
|
||||
- platform: lilygo_t5_47
|
||||
id: lilygo_touchscreen
|
||||
interrupt_pin: GPIO36
|
||||
display: inkplate_display
|
||||
on_touch:
|
||||
- logger.log:
|
||||
format: Touch at (%d, %d)
|
||||
args: ["touch.x", "touch.y"]
|
||||
|
||||
@@ -124,13 +124,13 @@ def test_get_bool_env(monkeypatch, var, value, default, expected):
|
||||
|
||||
|
||||
@pytest.mark.parametrize("value, expected", ((None, False), ("Yes", True)))
|
||||
def test_is_hassio(monkeypatch, value, expected):
|
||||
def test_is_ha_addon(monkeypatch, value, expected):
|
||||
if value is None:
|
||||
monkeypatch.delenv("ESPHOME_IS_HASSIO", raising=False)
|
||||
monkeypatch.delenv("ESPHOME_IS_HA_ADDON", raising=False)
|
||||
else:
|
||||
monkeypatch.setenv("ESPHOME_IS_HASSIO", value)
|
||||
monkeypatch.setenv("ESPHOME_IS_HA_ADDON", value)
|
||||
|
||||
actual = helpers.is_hassio()
|
||||
actual = helpers.is_ha_addon()
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
Reference in New Issue
Block a user