Compare commits
313 Commits
host-targe
...
2022.4.0b1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a82d8ea0c3 | ||
|
|
ad57faa9a9 | ||
|
|
a9b5e8d036 | ||
|
|
8be704e591 | ||
|
|
b622a8fa58 | ||
|
|
a519e5c475 | ||
|
|
d620b6dd5e | ||
|
|
99335d986e | ||
|
|
7895cd92cd | ||
|
|
8b2c032da6 | ||
|
|
da336247eb | ||
|
|
dabd27d4be | ||
|
|
fdda47db6e | ||
|
|
efa6fd03e5 | ||
|
|
9e3e34acf5 | ||
|
|
a2d0c1bf18 | ||
|
|
7663716ae8 | ||
|
|
c2cacb3478 | ||
|
|
84666b54b9 | ||
|
|
2b91c23bf3 | ||
|
|
3297267a16 | ||
|
|
a9e653724c | ||
|
|
5e79a1f500 | ||
|
|
d4ff98680a | ||
|
|
ba8d255cb4 | ||
|
|
06f4ad922c | ||
|
|
bff06e448b | ||
|
|
d48ffa2913 | ||
|
|
d97c3a7e01 | ||
|
|
0b1161f7ef | ||
|
|
061e1a471d | ||
|
|
a39d874600 | ||
|
|
de96376565 | ||
|
|
c54c20ab3c | ||
|
|
70fafa473b | ||
|
|
2e436eae6b | ||
|
|
fd7e861ff5 | ||
|
|
792108686c | ||
|
|
fa1b5117fd | ||
|
|
b0bd9e0a34 | ||
|
|
05dc97099a | ||
|
|
9de61fcf58 | ||
|
|
7f7175b184 | ||
|
|
cf5c640ae4 | ||
|
|
6b9371d105 | ||
|
|
9a82057303 | ||
|
|
48584e94c4 | ||
|
|
d8024a5928 | ||
|
|
2034ab4f6c | ||
|
|
58b70b42dd | ||
|
|
1496bc1b07 | ||
|
|
bfbf88b2ea | ||
|
|
e621b938e3 | ||
|
|
59e6e798dd | ||
|
|
e5c2dbc7ec | ||
|
|
756f71c382 | ||
|
|
b7535693fa | ||
|
|
06a3505698 | ||
|
|
0372d17a11 | ||
|
|
4525588116 | ||
|
|
68e957c147 | ||
|
|
99f5ed1461 | ||
|
|
59f67796dc | ||
|
|
aafdfa933e | ||
|
|
3208c8ed1e | ||
|
|
6bf733e24e | ||
|
|
65d3e8fbfc | ||
|
|
a29d65d47c | ||
|
|
efa8f0730d | ||
|
|
0af1edefff | ||
|
|
023d26f521 | ||
|
|
5068619f1b | ||
|
|
5b2457af0b | ||
|
|
900b4f1af9 | ||
|
|
4c22a98b0b | ||
|
|
3b8ca80900 | ||
|
|
b528f48417 | ||
|
|
ec7a79049a | ||
|
|
6ddad6b299 | ||
|
|
16dc7762f9 | ||
|
|
dc0ed8857f | ||
|
|
bb6b77bd98 | ||
|
|
dcc80f9032 | ||
|
|
dd554bcdf4 | ||
|
|
f376a39e55 | ||
|
|
8dcc9d6b66 | ||
|
|
a576c9f21f | ||
|
|
71a438e2cb | ||
|
|
272d6f2a8b | ||
|
|
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 |
@@ -2,7 +2,7 @@
|
||||
# See https://pre-commit.com/hooks.html for more hooks
|
||||
repos:
|
||||
- repo: https://github.com/ambv/black
|
||||
rev: 22.1.0
|
||||
rev: 22.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
args:
|
||||
@@ -26,7 +26,7 @@ repos:
|
||||
- --branch=release
|
||||
- --branch=beta
|
||||
- repo: https://github.com/asottile/pyupgrade
|
||||
rev: v2.31.0
|
||||
rev: v2.31.1
|
||||
hooks:
|
||||
- id: pyupgrade
|
||||
args: [--py38-plus]
|
||||
|
||||
@@ -82,6 +82,7 @@ esphome/components/hitachi_ac424/* @sourabhjaiswal
|
||||
esphome/components/homeassistant/* @OttoWinter
|
||||
esphome/components/honeywellabp/* @RubyBailey
|
||||
esphome/components/hrxl_maxsonar_wr/* @netmikey
|
||||
esphome/components/hydreon_rgxx/* @functionpointer
|
||||
esphome/components/i2c/* @esphome/core
|
||||
esphome/components/improv_serial/* @esphome/core
|
||||
esphome/components/ina260/* @MrEditor97
|
||||
@@ -151,6 +152,7 @@ esphome/components/preferences/* @esphome/core
|
||||
esphome/components/psram/* @esphome/core
|
||||
esphome/components/pulse_meter/* @cstaahl @stevebaxter
|
||||
esphome/components/pvvx_mithermometer/* @pasiz
|
||||
esphome/components/qmp6988/* @andrewpc
|
||||
esphome/components/qr_code/* @wjtje
|
||||
esphome/components/radon_eye_ble/* @jeffeb3
|
||||
esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
@@ -168,6 +170,7 @@ esphome/components/sdm_meter/* @jesserockz @polyfaces
|
||||
esphome/components/sdp3x/* @Azimath
|
||||
esphome/components/selec_meter/* @sourabhjaiswal
|
||||
esphome/components/select/* @esphome/core
|
||||
esphome/components/sensirion_common/* @martgras
|
||||
esphome/components/sensor/* @esphome/core
|
||||
esphome/components/sgp40/* @SenexCrenshaw
|
||||
esphome/components/sht4x/* @sjtrny
|
||||
@@ -175,6 +178,7 @@ esphome/components/shutdown/* @esphome/core @jsuanet
|
||||
esphome/components/sim800l/* @glmnet
|
||||
esphome/components/sm2135/* @BoukeHaarsma23
|
||||
esphome/components/socket/* @esphome/core
|
||||
esphome/components/sonoff_d1/* @anatoly-savchenkov
|
||||
esphome/components/spi/* @esphome/core
|
||||
esphome/components/ssd1322_base/* @kbx81
|
||||
esphome/components/ssd1322_spi/* @kbx81
|
||||
@@ -222,4 +226,5 @@ esphome/components/whirlpool/* @glmnet
|
||||
esphome/components/xiaomi_lywsd03mmc/* @ahpohl
|
||||
esphome/components/xiaomi_mhoc303/* @drug123
|
||||
esphome/components/xiaomi_mhoc401/* @vevsvevs
|
||||
esphome/components/xiaomi_rtcgq02lm/* @jesserockz
|
||||
esphome/components/xpt2046/* @numo68
|
||||
|
||||
@@ -6,13 +6,13 @@
|
||||
ARG BASEIMGTYPE=docker
|
||||
|
||||
# https://github.com/hassio-addons/addon-debian-base/releases
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.2.3 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.2.3 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.2.3 AS base-hassio-armv7
|
||||
FROM ghcr.io/hassio-addons/debian-base/amd64:5.3.0 AS base-hassio-amd64
|
||||
FROM ghcr.io/hassio-addons/debian-base/aarch64:5.3.0 AS base-hassio-arm64
|
||||
FROM ghcr.io/hassio-addons/debian-base/armv7:5.3.0 AS base-hassio-armv7
|
||||
# https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye
|
||||
FROM debian:bullseye-20220125-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20220125-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20220125-slim AS base-docker-armv7
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-amd64
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-arm64
|
||||
FROM debian:bullseye-20220328-slim AS base-docker-armv7
|
||||
|
||||
# Use TARGETARCH/TARGETVARIANT defined by docker
|
||||
# https://docs.docker.com/engine/reference/builder/#automatic-platform-args-in-the-global-scope
|
||||
@@ -23,7 +23,7 @@ RUN \
|
||||
# Use pinned versions so that we get updates with build caching
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
python3=3.9.2-3 \
|
||||
python3-pip=20.3.4-4 \
|
||||
python3-pip=20.3.4-4+deb11u1 \
|
||||
python3-setuptools=52.0.0-4 \
|
||||
python3-pil=8.1.2+dfsg-0.3+deb11u1 \
|
||||
python3-cryptography=3.3.2-1 \
|
||||
|
||||
@@ -262,21 +262,16 @@ async def repeat_action_to_code(config, action_id, template_arg, args):
|
||||
return var
|
||||
|
||||
|
||||
def validate_wait_until(value):
|
||||
schema = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_TIMEOUT): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
}
|
||||
)
|
||||
if isinstance(value, dict) and CONF_CONDITION in value:
|
||||
return schema(value)
|
||||
return validate_wait_until({CONF_CONDITION: value})
|
||||
_validate_wait_until = cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_CONDITION): validate_potentially_and_condition,
|
||||
cv.Optional(CONF_TIMEOUT): cv.templatable(cv.positive_time_period_milliseconds),
|
||||
},
|
||||
key=CONF_CONDITION,
|
||||
)
|
||||
|
||||
|
||||
@register_action("wait_until", WaitUntilAction, validate_wait_until)
|
||||
@register_action("wait_until", WaitUntilAction, _validate_wait_until)
|
||||
async def wait_until_action_to_code(config, action_id, template_arg, args):
|
||||
conditions = await build_condition(config[CONF_CONDITION], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, conditions)
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -23,7 +23,7 @@ static const char *const TAG = "api.connection";
|
||||
static const int ESP32_CAMERA_STOP_STREAM = 5000;
|
||||
|
||||
APIConnection::APIConnection(std::unique_ptr<socket::Socket> sock, APIServer *parent)
|
||||
: parent_(parent), initial_state_iterator_(parent, this), list_entities_iterator_(parent, this) {
|
||||
: parent_(parent), initial_state_iterator_(this), list_entities_iterator_(this) {
|
||||
this->proto_write_buffer_.reserve(64);
|
||||
|
||||
#if defined(USE_API_PLAINTEXT)
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "esphome/components/socket/socket.h"
|
||||
#include "api_pb2.h"
|
||||
#include "api_pb2_service.h"
|
||||
#include "util.h"
|
||||
#include "list_entities.h"
|
||||
#include "subscribe_state.h"
|
||||
#include "user_services.h"
|
||||
|
||||
@@ -40,8 +40,7 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->s
|
||||
#endif
|
||||
|
||||
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIServer *server, APIConnection *client)
|
||||
: ComponentIterator(server), client_(client) {}
|
||||
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
|
||||
bool ListEntitiesIterator::on_service(UserServiceDescriptor *service) {
|
||||
auto resp = service->encode_list_service_response();
|
||||
return this->client_->send_list_entities_services_response(resp);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/component_iterator.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -11,7 +11,7 @@ class APIConnection;
|
||||
|
||||
class ListEntitiesIterator : public ComponentIterator {
|
||||
public:
|
||||
ListEntitiesIterator(APIServer *server, APIConnection *client);
|
||||
ListEntitiesIterator(APIConnection *client);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
#endif
|
||||
@@ -60,5 +60,3 @@ class ListEntitiesIterator : public ComponentIterator {
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
#include "api_server.h"
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
#include "proto.h"
|
||||
#include "util.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
|
||||
@@ -195,6 +195,20 @@ class ProtoWriteBuffer {
|
||||
this->write((value >> 16) & 0xFF);
|
||||
this->write((value >> 24) & 0xFF);
|
||||
}
|
||||
void encode_fixed64(uint32_t field_id, uint64_t value, bool force = false) {
|
||||
if (value == 0 && !force)
|
||||
return;
|
||||
|
||||
this->encode_field_raw(field_id, 5);
|
||||
this->write((value >> 0) & 0xFF);
|
||||
this->write((value >> 8) & 0xFF);
|
||||
this->write((value >> 16) & 0xFF);
|
||||
this->write((value >> 24) & 0xFF);
|
||||
this->write((value >> 32) & 0xFF);
|
||||
this->write((value >> 40) & 0xFF);
|
||||
this->write((value >> 48) & 0xFF);
|
||||
this->write((value >> 56) & 0xFF);
|
||||
}
|
||||
template<typename T> void encode_enum(uint32_t field_id, T value, bool force = false) {
|
||||
this->encode_uint32(field_id, static_cast<uint32_t>(value), force);
|
||||
}
|
||||
@@ -229,6 +243,15 @@ class ProtoWriteBuffer {
|
||||
}
|
||||
this->encode_uint32(field_id, uvalue, force);
|
||||
}
|
||||
void encode_sint64(uint32_t field_id, int64_t value, bool force = false) {
|
||||
uint64_t uvalue;
|
||||
if (value < 0) {
|
||||
uvalue = ~(value << 1);
|
||||
} else {
|
||||
uvalue = value << 1;
|
||||
}
|
||||
this->encode_uint64(field_id, uvalue, force);
|
||||
}
|
||||
template<class C> void encode_message(uint32_t field_id, const C &value, bool force = false) {
|
||||
this->encode_field_raw(field_id, 2);
|
||||
size_t begin = this->buffer_->size();
|
||||
|
||||
@@ -50,8 +50,7 @@ bool InitialStateIterator::on_select(select::Select *select) {
|
||||
#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) {}
|
||||
InitialStateIterator::InitialStateIterator(APIConnection *client) : client_(client) {}
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/component_iterator.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "util.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
@@ -12,7 +12,7 @@ class APIConnection;
|
||||
|
||||
class InitialStateIterator : public ComponentIterator {
|
||||
public:
|
||||
InitialStateIterator(APIServer *server, APIConnection *client);
|
||||
InitialStateIterator(APIConnection *client);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
#endif
|
||||
@@ -55,5 +55,3 @@ class InitialStateIterator : public ComponentIterator {
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
|
||||
#include "api_server.h"
|
||||
|
||||
@@ -38,7 +38,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
const auto &data = service_data.data;
|
||||
|
||||
const uint8_t protocol_version = data[0] >> 4;
|
||||
if (protocol_version != 1) {
|
||||
if (protocol_version != 1 && protocol_version != 2) {
|
||||
ESP_LOGE(TAG, "Unsupported protocol version: %u", protocol_version);
|
||||
return false;
|
||||
}
|
||||
@@ -57,9 +57,15 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
uint16_t battery_millivolt = data[2] << 8 | data[3];
|
||||
float battery_voltage = battery_millivolt / 1000.0f;
|
||||
|
||||
// Temperature in 1000 * Celsius.
|
||||
uint16_t temp_millicelcius = data[4] << 8 | data[5];
|
||||
float temp_celcius = temp_millicelcius / 1000.0f;
|
||||
// Temperature in 1000 * Celsius (protocol v1) or 100 * Celsius (protocol v2).
|
||||
float temp_celsius;
|
||||
if (protocol_version == 1) {
|
||||
uint16_t temp_millicelsius = data[4] << 8 | data[5];
|
||||
temp_celsius = temp_millicelsius / 1000.0f;
|
||||
} else {
|
||||
int16_t temp_centicelsius = data[4] << 8 | data[5];
|
||||
temp_celsius = temp_centicelsius / 100.0f;
|
||||
}
|
||||
|
||||
// Relative air humidity in the range [0, 2^16).
|
||||
uint16_t humidity = data[6] << 8 | data[7];
|
||||
@@ -76,7 +82,7 @@ bool BParasite::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
battery_voltage_->publish_state(battery_voltage);
|
||||
}
|
||||
if (temperature_ != nullptr) {
|
||||
temperature_->publish_state(temp_celcius);
|
||||
temperature_->publish_state(temp_celsius);
|
||||
}
|
||||
if (humidity_ != nullptr) {
|
||||
humidity_->publish_state(humidity_percent);
|
||||
|
||||
@@ -118,16 +118,21 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
this->conn_id = param->open.conn_id;
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->open.conn_id);
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CONNECT_EVT: {
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_CONNECT_EVT", this->address_str().c_str());
|
||||
this->conn_id = param->connect.conn_id;
|
||||
auto ret = esp_ble_gattc_send_mtu_req(this->gattc_if, param->connect.conn_id);
|
||||
if (ret) {
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%d", ret);
|
||||
ESP_LOGW(TAG, "esp_ble_gattc_send_mtu_req failed, status=%x", ret);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ESP_GATTC_CFG_MTU_EVT: {
|
||||
if (param->cfg_mtu.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "cfg_mtu to %s failed, status %d", this->address_str().c_str(), param->cfg_mtu.status);
|
||||
ESP_LOGW(TAG, "cfg_mtu to %s failed, mtu %d, status %d", this->address_str().c_str(), param->cfg_mtu.mtu,
|
||||
param->cfg_mtu.status);
|
||||
this->set_states_(espbt::ClientState::IDLE);
|
||||
break;
|
||||
}
|
||||
@@ -139,7 +144,7 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
if (memcmp(param->disconnect.remote_bda, this->remote_bda, 6) != 0) {
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT", this->address_str().c_str());
|
||||
ESP_LOGV(TAG, "[%s] ESP_GATTC_DISCONNECT_EVT, reason %d", this->address_str().c_str(), param->disconnect.reason);
|
||||
for (auto &svc : this->services_)
|
||||
delete svc; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->services_.clear();
|
||||
@@ -201,6 +206,32 @@ void BLEClient::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t es
|
||||
}
|
||||
}
|
||||
|
||||
void BLEClient::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) {
|
||||
switch (event) {
|
||||
// This event is sent by the server when it requests security
|
||||
case ESP_GAP_BLE_SEC_REQ_EVT:
|
||||
ESP_LOGV(TAG, "ESP_GAP_BLE_SEC_REQ_EVT %x", event);
|
||||
esp_ble_gap_security_rsp(param->ble_security.ble_req.bd_addr, true);
|
||||
break;
|
||||
// This event is sent once authentication has completed
|
||||
case ESP_GAP_BLE_AUTH_CMPL_EVT:
|
||||
esp_bd_addr_t bd_addr;
|
||||
memcpy(bd_addr, param->ble_security.auth_cmpl.bd_addr, sizeof(esp_bd_addr_t));
|
||||
ESP_LOGI(TAG, "auth complete. remote BD_ADDR: %s", format_hex(bd_addr, 6).c_str());
|
||||
if (!param->ble_security.auth_cmpl.success) {
|
||||
ESP_LOGE(TAG, "auth fail reason = 0x%x", param->ble_security.auth_cmpl.fail_reason);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "auth success. address type = %d auth mode = %d", param->ble_security.auth_cmpl.addr_type,
|
||||
param->ble_security.auth_cmpl.auth_mode);
|
||||
}
|
||||
break;
|
||||
// There are other events we'll want to implement at some point to support things like pass key
|
||||
// https://github.com/espressif/esp-idf/blob/cba69dd088344ed9d26739f04736ae7a37541b3a/examples/bluetooth/bluedroid/ble/gatt_security_client/tutorial/Gatt_Security_Client_Example_Walkthrough.md
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse GATT values into a float for a sensor.
|
||||
// Ref: https://www.bluetooth.com/specifications/assigned-numbers/format-types/
|
||||
float BLEClient::parse_char_value(uint8_t *value, uint16_t length) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <esp_gap_ble_api.h>
|
||||
#include <esp_gattc_api.h>
|
||||
#include <esp_bt_defs.h>
|
||||
#include <esp_gatt_common_api.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace ble_client {
|
||||
@@ -86,6 +87,7 @@ class BLEClient : public espbt::ESPBTClient, public Component {
|
||||
|
||||
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 gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) override;
|
||||
bool parse_device(const espbt::ESPBTDevice &device) override;
|
||||
void on_scan_end() override {}
|
||||
void connect() override;
|
||||
|
||||
@@ -10,6 +10,7 @@ IS_PLATFORM_COMPONENT = True
|
||||
CONF_CAN_ID = "can_id"
|
||||
CONF_CAN_ID_MASK = "can_id_mask"
|
||||
CONF_USE_EXTENDED_ID = "use_extended_id"
|
||||
CONF_REMOTE_TRANSMISSION_REQUEST = "remote_transmission_request"
|
||||
CONF_CANBUS_ID = "canbus_id"
|
||||
CONF_BIT_RATE = "bit_rate"
|
||||
CONF_ON_FRAME = "on_frame"
|
||||
@@ -122,6 +123,7 @@ async def register_canbus(var, config):
|
||||
cv.GenerateID(CONF_CANBUS_ID): cv.use_id(CanbusComponent),
|
||||
cv.Optional(CONF_CAN_ID): cv.int_range(min=0, max=0x1FFFFFFF),
|
||||
cv.Optional(CONF_USE_EXTENDED_ID, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REMOTE_TRANSMISSION_REQUEST, default=False): cv.boolean,
|
||||
cv.Required(CONF_DATA): cv.templatable(validate_raw_data),
|
||||
},
|
||||
validate_id,
|
||||
@@ -140,6 +142,11 @@ async def canbus_action_to_code(config, action_id, template_arg, args):
|
||||
)
|
||||
cg.add(var.set_use_extended_id(use_extended_id))
|
||||
|
||||
remote_transmission_request = await cg.templatable(
|
||||
config[CONF_REMOTE_TRANSMISSION_REQUEST], args, bool
|
||||
)
|
||||
cg.add(var.set_remote_transmission_request(remote_transmission_request))
|
||||
|
||||
data = config[CONF_DATA]
|
||||
if isinstance(data, bytes):
|
||||
data = [int(x) for x in data]
|
||||
|
||||
@@ -22,20 +22,22 @@ void Canbus::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
void Canbus::send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
|
||||
void Canbus::send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||
const std::vector<uint8_t> &data) {
|
||||
struct CanFrame can_message;
|
||||
|
||||
uint8_t size = static_cast<uint8_t>(data.size());
|
||||
if (use_extended_id) {
|
||||
ESP_LOGD(TAG, "send extended id=0x%08x size=%d", can_id, size);
|
||||
ESP_LOGD(TAG, "send extended id=0x%08x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
|
||||
} else {
|
||||
ESP_LOGD(TAG, "send extended id=0x%03x size=%d", can_id, size);
|
||||
ESP_LOGD(TAG, "send extended id=0x%03x rtr=%s size=%d", can_id, TRUEFALSE(remote_transmission_request), size);
|
||||
}
|
||||
if (size > CAN_MAX_DATA_LENGTH)
|
||||
size = CAN_MAX_DATA_LENGTH;
|
||||
can_message.can_data_length_code = size;
|
||||
can_message.can_id = can_id;
|
||||
can_message.use_extended_id = use_extended_id;
|
||||
can_message.remote_transmission_request = remote_transmission_request;
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
can_message.data[i] = data[i];
|
||||
|
||||
@@ -62,7 +62,12 @@ class Canbus : public Component {
|
||||
float get_setup_priority() const override { return setup_priority::HARDWARE; }
|
||||
void loop() override;
|
||||
|
||||
void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data);
|
||||
void send_data(uint32_t can_id, bool use_extended_id, bool remote_transmission_request,
|
||||
const std::vector<uint8_t> &data);
|
||||
void send_data(uint32_t can_id, bool use_extended_id, const std::vector<uint8_t> &data) {
|
||||
// for backwards compatibility only
|
||||
this->send_data(can_id, use_extended_id, false, data);
|
||||
}
|
||||
void set_can_id(uint32_t can_id) { this->can_id_ = can_id; }
|
||||
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
||||
void set_bitrate(CanSpeed bit_rate) { this->bit_rate_ = bit_rate; }
|
||||
@@ -96,21 +101,26 @@ template<typename... Ts> class CanbusSendAction : public Action<Ts...>, public P
|
||||
|
||||
void set_use_extended_id(bool use_extended_id) { this->use_extended_id_ = use_extended_id; }
|
||||
|
||||
void set_remote_transmission_request(bool remote_transmission_request) {
|
||||
this->remote_transmission_request_ = remote_transmission_request;
|
||||
}
|
||||
|
||||
void play(Ts... x) override {
|
||||
auto can_id = this->can_id_.has_value() ? *this->can_id_ : this->parent_->can_id_;
|
||||
auto use_extended_id =
|
||||
this->use_extended_id_.has_value() ? *this->use_extended_id_ : this->parent_->use_extended_id_;
|
||||
if (this->static_) {
|
||||
this->parent_->send_data(can_id, use_extended_id, this->data_static_);
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, this->data_static_);
|
||||
} else {
|
||||
auto val = this->data_func_(x...);
|
||||
this->parent_->send_data(can_id, use_extended_id, val);
|
||||
this->parent_->send_data(can_id, use_extended_id, this->remote_transmission_request_, val);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
optional<uint32_t> can_id_{};
|
||||
optional<bool> use_extended_id_{};
|
||||
bool remote_transmission_request_{false};
|
||||
bool static_{false};
|
||||
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
|
||||
std::vector<uint8_t> data_static_{};
|
||||
|
||||
@@ -76,7 +76,7 @@ enum ClimateSwingMode : uint8_t {
|
||||
CLIMATE_SWING_HORIZONTAL = 3,
|
||||
};
|
||||
|
||||
/// Enum for all modes a climate swing can be in
|
||||
/// Enum for all preset modes
|
||||
enum ClimatePreset : uint8_t {
|
||||
/// No preset is active
|
||||
CLIMATE_PRESET_NONE = 0,
|
||||
@@ -108,7 +108,7 @@ const LogString *climate_fan_mode_to_string(ClimateFanMode mode);
|
||||
/// Convert the given ClimateSwingMode to a human-readable string.
|
||||
const LogString *climate_swing_mode_to_string(ClimateSwingMode mode);
|
||||
|
||||
/// Convert the given ClimateSwingMode to a human-readable string.
|
||||
/// Convert the given PresetMode to a human-readable string.
|
||||
const LogString *climate_preset_to_string(ClimatePreset preset);
|
||||
|
||||
} // namespace climate
|
||||
|
||||
@@ -141,7 +141,7 @@ class ClimateTraits {
|
||||
}
|
||||
bool supports_swing_mode(ClimateSwingMode swing_mode) const { return supported_swing_modes_.count(swing_mode); }
|
||||
bool get_supports_swing_modes() const { return !supported_swing_modes_.empty(); }
|
||||
std::set<ClimateSwingMode> get_supported_swing_modes() { return supported_swing_modes_; }
|
||||
std::set<ClimateSwingMode> get_supported_swing_modes() const { return supported_swing_modes_; }
|
||||
|
||||
float get_visual_min_temperature() const { return visual_min_temperature_; }
|
||||
void set_visual_min_temperature(float visual_min_temperature) { visual_min_temperature_ = visual_min_temperature; }
|
||||
|
||||
@@ -142,7 +142,6 @@ void IRAM_ATTR ESPOneWire::select(uint64_t address) {
|
||||
void IRAM_ATTR ESPOneWire::reset_search() {
|
||||
this->last_discrepancy_ = 0;
|
||||
this->last_device_flag_ = false;
|
||||
this->last_family_discrepancy_ = 0;
|
||||
this->rom_number_ = 0;
|
||||
}
|
||||
uint64_t IRAM_ATTR ESPOneWire::search() {
|
||||
@@ -195,9 +194,6 @@ uint64_t IRAM_ATTR ESPOneWire::search() {
|
||||
|
||||
if (!branch) {
|
||||
last_zero = id_bit_number;
|
||||
if (last_zero < 9) {
|
||||
this->last_discrepancy_ = last_zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,6 @@ class ESPOneWire {
|
||||
|
||||
ISRInternalGPIOPin pin_;
|
||||
uint8_t last_discrepancy_{0};
|
||||
uint8_t last_family_discrepancy_{0};
|
||||
bool last_device_flag_{false};
|
||||
uint64_t rom_number_{0};
|
||||
};
|
||||
|
||||
@@ -1,13 +1,18 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import time
|
||||
import esphome.config_validation as cv
|
||||
from esphome import pins, automation
|
||||
from esphome.const import (
|
||||
CONF_HOUR,
|
||||
CONF_ID,
|
||||
CONF_MINUTE,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_PINS,
|
||||
CONF_RUN_DURATION,
|
||||
CONF_SECOND,
|
||||
CONF_SLEEP_DURATION,
|
||||
CONF_TIME_ID,
|
||||
CONF_WAKEUP_PIN,
|
||||
)
|
||||
|
||||
@@ -15,6 +20,7 @@ from esphome.components.esp32 import get_esp32_variant
|
||||
from esphome.components.esp32.const import (
|
||||
VARIANT_ESP32,
|
||||
VARIANT_ESP32C3,
|
||||
VARIANT_ESP32S2,
|
||||
)
|
||||
|
||||
WAKEUP_PINS = {
|
||||
@@ -39,6 +45,30 @@ WAKEUP_PINS = {
|
||||
39,
|
||||
],
|
||||
VARIANT_ESP32C3: [0, 1, 2, 3, 4, 5],
|
||||
VARIANT_ESP32S2: [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
@@ -87,6 +117,7 @@ CONF_TOUCH_WAKEUP = "touch_wakeup"
|
||||
CONF_DEFAULT = "default"
|
||||
CONF_GPIO_WAKEUP_REASON = "gpio_wakeup_reason"
|
||||
CONF_TOUCH_WAKEUP_REASON = "touch_wakeup_reason"
|
||||
CONF_UNTIL = "until"
|
||||
|
||||
WAKEUP_CAUSES_SCHEMA = cv.Schema(
|
||||
{
|
||||
@@ -177,13 +208,19 @@ async def to_code(config):
|
||||
cg.add_define("USE_DEEP_SLEEP")
|
||||
|
||||
|
||||
DEEP_SLEEP_ENTER_SCHEMA = automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DeepSleepComponent),
|
||||
cv.Optional(CONF_SLEEP_DURATION): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
}
|
||||
DEEP_SLEEP_ENTER_SCHEMA = cv.All(
|
||||
automation.maybe_simple_id(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(DeepSleepComponent),
|
||||
cv.Exclusive(CONF_SLEEP_DURATION, "time"): cv.templatable(
|
||||
cv.positive_time_period_milliseconds
|
||||
),
|
||||
# Only on ESP32 due to how long the RTC on ESP8266 can stay asleep
|
||||
cv.Exclusive(CONF_UNTIL, "time"): cv.All(cv.only_on_esp32, cv.time_of_day),
|
||||
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
|
||||
}
|
||||
),
|
||||
cv.has_none_or_all_keys(CONF_UNTIL, CONF_TIME_ID),
|
||||
)
|
||||
|
||||
|
||||
@@ -203,6 +240,14 @@ async def deep_sleep_enter_to_code(config, action_id, template_arg, args):
|
||||
if CONF_SLEEP_DURATION in config:
|
||||
template_ = await cg.templatable(config[CONF_SLEEP_DURATION], args, cg.int32)
|
||||
cg.add(var.set_sleep_duration(template_))
|
||||
|
||||
if CONF_UNTIL in config:
|
||||
until = config[CONF_UNTIL]
|
||||
cg.add(var.set_until(until[CONF_HOUR], until[CONF_MINUTE], until[CONF_SECOND]))
|
||||
|
||||
time_ = await cg.get_variable(config[CONF_TIME_ID])
|
||||
cg.add(var.set_time(time_))
|
||||
|
||||
return var
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "deep_sleep_component.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cinttypes>
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
#include <Esp.h>
|
||||
@@ -101,6 +102,8 @@ void DeepSleepComponent::begin_sleep(bool manual) {
|
||||
#endif
|
||||
|
||||
ESP_LOGI(TAG, "Beginning Deep Sleep");
|
||||
if (this->sleep_duration_.has_value())
|
||||
ESP_LOGI(TAG, "Sleeping for %" PRId64 "us", *this->sleep_duration_);
|
||||
|
||||
App.run_safe_shutdown_hooks();
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include <esp_sleep.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_TIME
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace deep_sleep {
|
||||
|
||||
@@ -116,15 +120,71 @@ template<typename... Ts> class EnterDeepSleepAction : public Action<Ts...> {
|
||||
EnterDeepSleepAction(DeepSleepComponent *deep_sleep) : deep_sleep_(deep_sleep) {}
|
||||
TEMPLATABLE_VALUE(uint32_t, sleep_duration);
|
||||
|
||||
#ifdef USE_TIME
|
||||
void set_until(uint8_t hour, uint8_t minute, uint8_t second) {
|
||||
this->hour_ = hour;
|
||||
this->minute_ = minute;
|
||||
this->second_ = second;
|
||||
}
|
||||
|
||||
void set_time(time::RealTimeClock *time) { this->time_ = time; }
|
||||
#endif
|
||||
|
||||
void play(Ts... x) override {
|
||||
if (this->sleep_duration_.has_value()) {
|
||||
this->deep_sleep_->set_sleep_duration(this->sleep_duration_.value(x...));
|
||||
}
|
||||
#ifdef USE_TIME
|
||||
|
||||
if (this->hour_.has_value()) {
|
||||
auto time = this->time_->now();
|
||||
const uint32_t timestamp_now = time.timestamp;
|
||||
|
||||
bool after_time = false;
|
||||
if (time.hour > this->hour_) {
|
||||
after_time = true;
|
||||
} else {
|
||||
if (time.hour == this->hour_) {
|
||||
if (time.minute > this->minute_) {
|
||||
after_time = true;
|
||||
} else {
|
||||
if (time.minute == this->minute_) {
|
||||
if (time.second > this->second_) {
|
||||
after_time = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
time.hour = *this->hour_;
|
||||
time.minute = *this->minute_;
|
||||
time.second = *this->second_;
|
||||
time.recalc_timestamp_utc();
|
||||
|
||||
time_t timestamp = time.timestamp; // timestamp in local time zone
|
||||
|
||||
if (after_time)
|
||||
timestamp += 60 * 60 * 24;
|
||||
|
||||
int32_t offset = time::ESPTime::timezone_offset();
|
||||
timestamp -= offset; // Change timestamp to utc
|
||||
const uint32_t ms_left = (timestamp - timestamp_now) * 1000;
|
||||
this->deep_sleep_->set_sleep_duration(ms_left);
|
||||
}
|
||||
#endif
|
||||
this->deep_sleep_->begin_sleep(true);
|
||||
}
|
||||
|
||||
protected:
|
||||
DeepSleepComponent *deep_sleep_;
|
||||
#ifdef USE_TIME
|
||||
optional<uint8_t> hour_;
|
||||
optional<uint8_t> minute_;
|
||||
optional<uint8_t> second_;
|
||||
|
||||
time::RealTimeClock *time_;
|
||||
#endif
|
||||
};
|
||||
|
||||
template<typename... Ts> class PreventDeepSleepAction : public Action<Ts...> {
|
||||
|
||||
@@ -12,6 +12,7 @@ using namespace esphome::cover;
|
||||
CoverTraits EndstopCover::get_traits() {
|
||||
auto traits = CoverTraits();
|
||||
traits.set_supports_position(true);
|
||||
traits.set_supports_toggle(true);
|
||||
traits.set_is_assumed_state(false);
|
||||
return traits;
|
||||
}
|
||||
@@ -20,6 +21,20 @@ void EndstopCover::control(const CoverCall &call) {
|
||||
this->start_direction_(COVER_OPERATION_IDLE);
|
||||
this->publish_state();
|
||||
}
|
||||
if (call.get_toggle().has_value()) {
|
||||
if (this->current_operation != COVER_OPERATION_IDLE) {
|
||||
this->start_direction_(COVER_OPERATION_IDLE);
|
||||
this->publish_state();
|
||||
} else {
|
||||
if (this->position == COVER_CLOSED || this->last_operation_ == COVER_OPERATION_CLOSING) {
|
||||
this->target_position_ = COVER_OPEN;
|
||||
this->start_direction_(COVER_OPERATION_OPENING);
|
||||
} else {
|
||||
this->target_position_ = COVER_CLOSED;
|
||||
this->start_direction_(COVER_OPERATION_CLOSING);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (call.get_position().has_value()) {
|
||||
auto pos = *call.get_position();
|
||||
if (pos == this->position) {
|
||||
@@ -125,9 +140,11 @@ void EndstopCover::start_direction_(CoverOperation dir) {
|
||||
trig = this->stop_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_OPENING:
|
||||
this->last_operation_ = dir;
|
||||
trig = this->open_trigger_;
|
||||
break;
|
||||
case COVER_OPERATION_CLOSING:
|
||||
this->last_operation_ = dir;
|
||||
trig = this->close_trigger_;
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -51,6 +51,7 @@ class EndstopCover : public cover::Cover, public Component {
|
||||
uint32_t start_dir_time_{0};
|
||||
uint32_t last_publish_time_{0};
|
||||
float target_position_{0};
|
||||
cover::CoverOperation last_operation_{cover::COVER_OPERATION_OPENING};
|
||||
};
|
||||
|
||||
} // namespace endstop
|
||||
|
||||
@@ -262,6 +262,9 @@ void ESP32BLETracker::real_gap_event_handler_(esp_gap_ble_cb_event_t event, esp_
|
||||
default:
|
||||
break;
|
||||
}
|
||||
for (auto *client : global_esp32_ble_tracker->clients_) {
|
||||
client->gap_event_handler(event, param);
|
||||
}
|
||||
}
|
||||
|
||||
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param ¶m) {
|
||||
|
||||
@@ -155,6 +155,7 @@ class ESPBTClient : public ESPBTDeviceListener {
|
||||
public:
|
||||
virtual void gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) = 0;
|
||||
virtual void gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param) = 0;
|
||||
virtual void connect() = 0;
|
||||
void set_state(ClientState st) { this->state_ = st; }
|
||||
ClientState state() const { return state_; }
|
||||
|
||||
@@ -1,12 +1,29 @@
|
||||
import functools
|
||||
from pathlib import Path
|
||||
import hashlib
|
||||
import re
|
||||
|
||||
import requests
|
||||
|
||||
from esphome import core
|
||||
from esphome.components import display
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
from esphome.const import CONF_FILE, CONF_GLYPHS, CONF_ID, CONF_RAW_DATA_ID, CONF_SIZE
|
||||
from esphome.const import (
|
||||
CONF_FAMILY,
|
||||
CONF_FILE,
|
||||
CONF_GLYPHS,
|
||||
CONF_ID,
|
||||
CONF_RAW_DATA_ID,
|
||||
CONF_TYPE,
|
||||
CONF_SIZE,
|
||||
CONF_PATH,
|
||||
CONF_WEIGHT,
|
||||
)
|
||||
from esphome.core import CORE, HexInt
|
||||
|
||||
|
||||
DOMAIN = "font"
|
||||
DEPENDENCIES = ["display"]
|
||||
MULTI_CONF = True
|
||||
|
||||
@@ -71,6 +88,128 @@ def validate_truetype_file(value):
|
||||
return cv.file_(value)
|
||||
|
||||
|
||||
def _compute_gfonts_local_path(value) -> Path:
|
||||
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}@{value[CONF_ITALIC]}@v1"
|
||||
base_dir = Path(CORE.config_dir) / ".esphome" / DOMAIN
|
||||
h = hashlib.new("sha256")
|
||||
h.update(name.encode())
|
||||
return base_dir / h.hexdigest()[:8] / "font.ttf"
|
||||
|
||||
|
||||
TYPE_LOCAL = "local"
|
||||
TYPE_GFONTS = "gfonts"
|
||||
LOCAL_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_PATH): validate_truetype_file,
|
||||
}
|
||||
)
|
||||
CONF_ITALIC = "italic"
|
||||
FONT_WEIGHTS = {
|
||||
"thin": 100,
|
||||
"extra-light": 200,
|
||||
"light": 300,
|
||||
"regular": 400,
|
||||
"medium": 500,
|
||||
"semi-bold": 600,
|
||||
"bold": 700,
|
||||
"extra-bold": 800,
|
||||
"black": 900,
|
||||
}
|
||||
|
||||
|
||||
def validate_weight_name(value):
|
||||
return FONT_WEIGHTS[cv.one_of(*FONT_WEIGHTS, lower=True, space="-")(value)]
|
||||
|
||||
|
||||
def download_gfonts(value):
|
||||
wght = value[CONF_WEIGHT]
|
||||
if value[CONF_ITALIC]:
|
||||
wght = f"1,{wght}"
|
||||
name = f"{value[CONF_FAMILY]}@{value[CONF_WEIGHT]}"
|
||||
url = f"https://fonts.googleapis.com/css2?family={value[CONF_FAMILY]}:wght@{wght}"
|
||||
|
||||
path = _compute_gfonts_local_path(value)
|
||||
if path.is_file():
|
||||
return value
|
||||
try:
|
||||
req = requests.get(url)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise cv.Invalid(
|
||||
f"Could not download font for {name}, please check the fonts exists "
|
||||
f"at google fonts ({e})"
|
||||
)
|
||||
match = re.search(r"src:\s+url\((.+)\)\s+format\('truetype'\);", req.text)
|
||||
if match is None:
|
||||
raise cv.Invalid(
|
||||
f"Could not extract ttf file from gfonts response for {name}, "
|
||||
f"please report this."
|
||||
)
|
||||
|
||||
ttf_url = match.group(1)
|
||||
try:
|
||||
req = requests.get(ttf_url)
|
||||
req.raise_for_status()
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise cv.Invalid(f"Could not download ttf file for {name} ({ttf_url}): {e}")
|
||||
|
||||
path.parent.mkdir(exist_ok=True, parents=True)
|
||||
path.write_bytes(req.content)
|
||||
return value
|
||||
|
||||
|
||||
GFONTS_SCHEMA = cv.All(
|
||||
{
|
||||
cv.Required(CONF_FAMILY): cv.string_strict,
|
||||
cv.Optional(CONF_WEIGHT, default="regular"): cv.Any(
|
||||
cv.int_, validate_weight_name
|
||||
),
|
||||
cv.Optional(CONF_ITALIC, default=False): cv.boolean,
|
||||
},
|
||||
download_gfonts,
|
||||
)
|
||||
|
||||
|
||||
def validate_file_shorthand(value):
|
||||
value = cv.string_strict(value)
|
||||
if value.startswith("gfonts://"):
|
||||
match = re.match(r"^gfonts://([^@]+)(@.+)?$", value)
|
||||
if match is None:
|
||||
raise cv.Invalid("Could not parse gfonts shorthand syntax, please check it")
|
||||
family = match.group(1)
|
||||
weight = match.group(2)
|
||||
data = {
|
||||
CONF_TYPE: TYPE_GFONTS,
|
||||
CONF_FAMILY: family,
|
||||
}
|
||||
if weight is not None:
|
||||
data[CONF_WEIGHT] = weight[1:]
|
||||
return FILE_SCHEMA(data)
|
||||
return FILE_SCHEMA(
|
||||
{
|
||||
CONF_TYPE: TYPE_LOCAL,
|
||||
CONF_PATH: value,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
TYPED_FILE_SCHEMA = cv.typed_schema(
|
||||
{
|
||||
TYPE_LOCAL: LOCAL_SCHEMA,
|
||||
TYPE_GFONTS: GFONTS_SCHEMA,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _file_schema(value):
|
||||
if isinstance(value, str):
|
||||
return validate_file_shorthand(value)
|
||||
return TYPED_FILE_SCHEMA(value)
|
||||
|
||||
|
||||
FILE_SCHEMA = cv.Schema(_file_schema)
|
||||
|
||||
|
||||
DEFAULT_GLYPHS = (
|
||||
' !"%()+=,-.:/0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz°'
|
||||
)
|
||||
@@ -79,7 +218,7 @@ CONF_RAW_GLYPH_ID = "raw_glyph_id"
|
||||
FONT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.declare_id(Font),
|
||||
cv.Required(CONF_FILE): validate_truetype_file,
|
||||
cv.Required(CONF_FILE): FILE_SCHEMA,
|
||||
cv.Optional(CONF_GLYPHS, default=DEFAULT_GLYPHS): validate_glyphs,
|
||||
cv.Optional(CONF_SIZE, default=20): cv.int_range(min=1),
|
||||
cv.GenerateID(CONF_RAW_DATA_ID): cv.declare_id(cg.uint8),
|
||||
@@ -93,9 +232,13 @@ CONFIG_SCHEMA = cv.All(validate_pillow_installed, FONT_SCHEMA)
|
||||
async def to_code(config):
|
||||
from PIL import ImageFont
|
||||
|
||||
path = CORE.relative_config_path(config[CONF_FILE])
|
||||
conf = config[CONF_FILE]
|
||||
if conf[CONF_TYPE] == TYPE_LOCAL:
|
||||
path = CORE.relative_config_path(conf[CONF_PATH])
|
||||
elif conf[CONF_TYPE] == TYPE_GFONTS:
|
||||
path = _compute_gfonts_local_path(conf)
|
||||
try:
|
||||
font = ImageFont.truetype(path, config[CONF_SIZE])
|
||||
font = ImageFont.truetype(str(path), config[CONF_SIZE])
|
||||
except Exception as e:
|
||||
raise core.EsphomeError(f"Could not load truetype file {path}: {e}")
|
||||
|
||||
|
||||
@@ -7,9 +7,11 @@ namespace growatt_solar {
|
||||
static const char *const TAG = "growatt_solar";
|
||||
|
||||
static const uint8_t MODBUS_CMD_READ_IN_REGISTERS = 0x04;
|
||||
static const uint8_t MODBUS_REGISTER_COUNT = 33;
|
||||
static const uint8_t MODBUS_REGISTER_COUNT[] = {33, 95}; // indexed with enum GrowattProtocolVersion
|
||||
|
||||
void GrowattSolar::update() { this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT); }
|
||||
void GrowattSolar::update() {
|
||||
this->send(MODBUS_CMD_READ_IN_REGISTERS, 0, MODBUS_REGISTER_COUNT[this->protocol_version_]);
|
||||
}
|
||||
|
||||
void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
auto publish_1_reg_sensor_state = [&](sensor::Sensor *sensor, size_t i, float unit) -> void {
|
||||
@@ -27,37 +29,76 @@ void GrowattSolar::on_modbus_data(const std::vector<uint8_t> &data) {
|
||||
sensor->publish_state(value);
|
||||
};
|
||||
|
||||
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
|
||||
switch (this->protocol_version_) {
|
||||
case RTU: {
|
||||
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
|
||||
|
||||
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
|
||||
|
||||
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 11, 12, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 13, TWO_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 14, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 15, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 16, 17, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 18, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 19, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 20, 21, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 22, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 23, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 24, 25, ONE_DEC_UNIT);
|
||||
|
||||
publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->today_production_, 26, 27, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->total_energy_production_, 28, 29, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->inverter_module_temp_, 32, ONE_DEC_UNIT);
|
||||
break;
|
||||
}
|
||||
case RTU2: {
|
||||
publish_1_reg_sensor_state(this->inverter_status_, 0, 1);
|
||||
|
||||
publish_2_reg_sensor_state(this->pv_active_power_sensor_, 1, 2, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->pvs_[0].voltage_sensor_, 3, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[0].current_sensor_, 4, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[0].active_power_sensor_, 5, 6, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->pvs_[1].voltage_sensor_, 7, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->pvs_[1].current_sensor_, 8, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->pvs_[1].active_power_sensor_, 9, 10, ONE_DEC_UNIT);
|
||||
|
||||
publish_2_reg_sensor_state(this->grid_active_power_sensor_, 35, 36, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->grid_frequency_sensor_, 37, TWO_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[0].voltage_sensor_, 38, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[0].current_sensor_, 39, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[0].active_power_sensor_, 40, 41, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[1].voltage_sensor_, 42, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[1].current_sensor_, 43, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[1].active_power_sensor_, 44, 45, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->phases_[2].voltage_sensor_, 46, ONE_DEC_UNIT);
|
||||
publish_1_reg_sensor_state(this->phases_[2].current_sensor_, 47, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->phases_[2].active_power_sensor_, 48, 49, ONE_DEC_UNIT);
|
||||
|
||||
publish_2_reg_sensor_state(this->today_production_, 53, 54, ONE_DEC_UNIT);
|
||||
publish_2_reg_sensor_state(this->total_energy_production_, 55, 56, ONE_DEC_UNIT);
|
||||
|
||||
publish_1_reg_sensor_state(this->inverter_module_temp_, 93, ONE_DEC_UNIT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GrowattSolar::dump_config() {
|
||||
|
||||
@@ -10,12 +10,19 @@ namespace growatt_solar {
|
||||
static const float TWO_DEC_UNIT = 0.01;
|
||||
static const float ONE_DEC_UNIT = 0.1;
|
||||
|
||||
enum GrowattProtocolVersion {
|
||||
RTU = 0,
|
||||
RTU2,
|
||||
};
|
||||
|
||||
class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
|
||||
public:
|
||||
void update() override;
|
||||
void on_modbus_data(const std::vector<uint8_t> &data) override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_protocol_version(GrowattProtocolVersion protocol_version) { this->protocol_version_ = protocol_version; }
|
||||
|
||||
void set_inverter_status_sensor(sensor::Sensor *sensor) { this->inverter_status_ = sensor; }
|
||||
|
||||
void set_grid_frequency_sensor(sensor::Sensor *sensor) { this->grid_frequency_sensor_ = sensor; }
|
||||
@@ -67,6 +74,7 @@ class GrowattSolar : public PollingComponent, public modbus::ModbusDevice {
|
||||
sensor::Sensor *today_production_{nullptr};
|
||||
sensor::Sensor *total_energy_production_{nullptr};
|
||||
sensor::Sensor *inverter_module_temp_{nullptr};
|
||||
GrowattProtocolVersion protocol_version_;
|
||||
};
|
||||
|
||||
} // namespace growatt_solar
|
||||
|
||||
@@ -39,7 +39,7 @@ UNIT_MILLIAMPERE = "mA"
|
||||
CONF_INVERTER_STATUS = "inverter_status"
|
||||
CONF_PV_ACTIVE_POWER = "pv_active_power"
|
||||
CONF_INVERTER_MODULE_TEMP = "inverter_module_temp"
|
||||
|
||||
CONF_PROTOCOL_VERSION = "protocol_version"
|
||||
|
||||
AUTO_LOAD = ["modbus"]
|
||||
CODEOWNERS = ["@leeuwte"]
|
||||
@@ -95,10 +95,20 @@ PV_SCHEMA = cv.Schema(
|
||||
{cv.Optional(sensor): schema for sensor, schema in PV_SENSORS.items()}
|
||||
)
|
||||
|
||||
GrowattProtocolVersion = growatt_solar_ns.enum("GrowattProtocolVersion")
|
||||
PROTOCOL_VERSIONS = {
|
||||
"RTU": GrowattProtocolVersion.RTU,
|
||||
"RTU2": GrowattProtocolVersion.RTU2,
|
||||
}
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(GrowattSolar),
|
||||
cv.Optional(CONF_PROTOCOL_VERSION, default="RTU"): cv.enum(
|
||||
PROTOCOL_VERSIONS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_PHASE_A): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_B): PHASE_SCHEMA,
|
||||
cv.Optional(CONF_PHASE_C): PHASE_SCHEMA,
|
||||
@@ -152,6 +162,8 @@ async def to_code(config):
|
||||
await cg.register_component(var, config)
|
||||
await modbus.register_modbus_device(var, config)
|
||||
|
||||
cg.add(var.set_protocol_version(config[CONF_PROTOCOL_VERSION]))
|
||||
|
||||
if CONF_INVERTER_STATUS in config:
|
||||
sens = await sensor.new_sensor(config[CONF_INVERTER_STATUS])
|
||||
cg.add(var.set_inverter_status_sensor(sens))
|
||||
|
||||
@@ -25,6 +25,7 @@ PROTOCOLS = {
|
||||
"daikin_arc417": Protocol.PROTOCOL_DAIKIN_ARC417,
|
||||
"daikin_arc480": Protocol.PROTOCOL_DAIKIN_ARC480,
|
||||
"daikin": Protocol.PROTOCOL_DAIKIN,
|
||||
"electroluxyal": Protocol.PROTOCOL_ELECTROLUXYAL,
|
||||
"fuego": Protocol.PROTOCOL_FUEGO,
|
||||
"fujitsu_awyz": Protocol.PROTOCOL_FUJITSU_AWYZ,
|
||||
"gree": Protocol.PROTOCOL_GREE,
|
||||
@@ -112,6 +113,4 @@ def to_code(config):
|
||||
cg.add(var.set_max_temperature(config[CONF_MAX_TEMPERATURE]))
|
||||
cg.add(var.set_min_temperature(config[CONF_MIN_TEMPERATURE]))
|
||||
|
||||
# PIO isn't updating releases, so referencing the release tag directly. See:
|
||||
# https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
|
||||
cg.add_library("", "", "https://github.com/ToniA/arduino-heatpumpir.git#1.0.18")
|
||||
cg.add_library("tonia/HeatpumpIR", "1.0.20")
|
||||
|
||||
@@ -20,6 +20,7 @@ const std::map<Protocol, std::function<HeatpumpIR *()>> PROTOCOL_CONSTRUCTOR_MAP
|
||||
{PROTOCOL_DAIKIN_ARC417, []() { return new DaikinHeatpumpARC417IR(); }}, // NOLINT
|
||||
{PROTOCOL_DAIKIN_ARC480, []() { return new DaikinHeatpumpARC480A14IR(); }}, // NOLINT
|
||||
{PROTOCOL_DAIKIN, []() { return new DaikinHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_ELECTROLUXYAL, []() { return new ElectroluxYALHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_FUEGO, []() { return new FuegoHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_FUJITSU_AWYZ, []() { return new FujitsuHeatpumpIR(); }}, // NOLINT
|
||||
{PROTOCOL_GREE, []() { return new GreeGenericHeatpumpIR(); }}, // NOLINT
|
||||
|
||||
@@ -20,6 +20,7 @@ enum Protocol {
|
||||
PROTOCOL_DAIKIN_ARC417,
|
||||
PROTOCOL_DAIKIN_ARC480,
|
||||
PROTOCOL_DAIKIN,
|
||||
PROTOCOL_ELECTROLUXYAL,
|
||||
PROTOCOL_FUEGO,
|
||||
PROTOCOL_FUJITSU_AWYZ,
|
||||
PROTOCOL_GREE,
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace hm3301 {
|
||||
|
||||
class AbstractAQICalculator {
|
||||
public:
|
||||
virtual uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
|
||||
virtual uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) = 0;
|
||||
};
|
||||
|
||||
} // namespace hm3301
|
||||
|
||||
@@ -7,7 +7,7 @@ namespace hm3301 {
|
||||
|
||||
class AQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
||||
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
||||
int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
|
||||
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace hm3301 {
|
||||
|
||||
class CAQICalculator : public AbstractAQICalculator {
|
||||
public:
|
||||
uint8_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
||||
uint16_t get_aqi(uint16_t pm2_5_value, uint16_t pm10_0_value) override {
|
||||
int pm2_5_index = calculate_index_(pm2_5_value, pm2_5_calculation_grid_);
|
||||
int pm10_0_index = calculate_index_(pm10_0_value, pm10_0_calculation_grid_);
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ void HM3301Component::update() {
|
||||
pm_10_0_value = get_sensor_value_(data_buffer_, PM_10_0_VALUE_INDEX);
|
||||
}
|
||||
|
||||
int8_t aqi_value = -1;
|
||||
int16_t aqi_value = -1;
|
||||
if (this->aqi_sensor_ != nullptr && pm_2_5_value != -1 && pm_10_0_value != -1) {
|
||||
AbstractAQICalculator *calculator = this->aqi_calculator_factory_.get_calculator(this->aqi_calc_type_);
|
||||
aqi_value = calculator->get_aqi(pm_2_5_value, pm_10_0_value);
|
||||
|
||||
@@ -1,4 +1,20 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_INTERNAL
|
||||
|
||||
CODEOWNERS = ["@OttoWinter"]
|
||||
homeassistant_ns = cg.esphome_ns.namespace("homeassistant")
|
||||
|
||||
HOME_ASSISTANT_IMPORT_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
cv.Optional(CONF_ATTRIBUTE): cv.string,
|
||||
cv.Optional(CONF_INTERNAL, default=True): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def setup_home_assistant_entity(var, config):
|
||||
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
|
||||
if CONF_ATTRIBUTE in config:
|
||||
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
|
||||
|
||||
@@ -1,30 +1,24 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ATTRIBUTE, CONF_ENTITY_ID
|
||||
from .. import homeassistant_ns
|
||||
|
||||
from .. import (
|
||||
HOME_ASSISTANT_IMPORT_SCHEMA,
|
||||
homeassistant_ns,
|
||||
setup_home_assistant_entity,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["api"]
|
||||
|
||||
HomeassistantBinarySensor = homeassistant_ns.class_(
|
||||
"HomeassistantBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
binary_sensor.binary_sensor_schema(HomeassistantBinarySensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
cv.Optional(CONF_ATTRIBUTE): cv.string,
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(HomeassistantBinarySensor).extend(
|
||||
HOME_ASSISTANT_IMPORT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
|
||||
if CONF_ATTRIBUTE in config:
|
||||
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
|
||||
setup_home_assistant_entity(var, config)
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_ATTRIBUTE,
|
||||
CONF_ENTITY_ID,
|
||||
CONF_ID,
|
||||
|
||||
from .. import (
|
||||
HOME_ASSISTANT_IMPORT_SCHEMA,
|
||||
homeassistant_ns,
|
||||
setup_home_assistant_entity,
|
||||
)
|
||||
from .. import homeassistant_ns
|
||||
|
||||
DEPENDENCIES = ["api"]
|
||||
|
||||
@@ -14,19 +13,12 @@ HomeassistantSensor = homeassistant_ns.class_(
|
||||
"HomeassistantSensor", sensor.Sensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1,).extend(
|
||||
{
|
||||
cv.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
cv.Optional(CONF_ATTRIBUTE): cv.string,
|
||||
}
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(HomeassistantSensor, accuracy_decimals=1).extend(
|
||||
HOME_ASSISTANT_IMPORT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
var = await sensor.new_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
await sensor.register_sensor(var, config)
|
||||
|
||||
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
|
||||
if CONF_ATTRIBUTE in config:
|
||||
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
|
||||
setup_home_assistant_entity(var, config)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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
|
||||
|
||||
from .. import homeassistant_ns
|
||||
from .. import (
|
||||
HOME_ASSISTANT_IMPORT_SCHEMA,
|
||||
homeassistant_ns,
|
||||
setup_home_assistant_entity,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["api"]
|
||||
|
||||
@@ -11,19 +13,12 @@ HomeassistantTextSensor = homeassistant_ns.class_(
|
||||
"HomeassistantTextSensor", text_sensor.TextSensor, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema().extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HomeassistantTextSensor),
|
||||
cv.Required(CONF_ENTITY_ID): cv.entity_id,
|
||||
cv.Optional(CONF_ATTRIBUTE): cv.string,
|
||||
}
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(HomeassistantTextSensor).extend(
|
||||
HOME_ASSISTANT_IMPORT_SCHEMA
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
cg.add(var.set_entity_id(config[CONF_ENTITY_ID]))
|
||||
if CONF_ATTRIBUTE in config:
|
||||
cg.add(var.set_attribute(config[CONF_ATTRIBUTE]))
|
||||
setup_home_assistant_entity(var, config)
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
from esphome.const import (
|
||||
KEY_CORE,
|
||||
KEY_FRAMEWORK_VERSION,
|
||||
KEY_TARGET_FRAMEWORK,
|
||||
KEY_TARGET_PLATFORM,
|
||||
)
|
||||
from esphome.core import CORE
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from .const import KEY_HOST
|
||||
|
||||
# force import gpio to register pin schema
|
||||
from .gpio import host_pin_to_code # noqa
|
||||
|
||||
|
||||
CODEOWNERS = ["@esphome/core"]
|
||||
AUTO_LOAD = ["network"]
|
||||
|
||||
|
||||
def set_core_data(config):
|
||||
CORE.data[KEY_HOST] = {}
|
||||
CORE.data[KEY_CORE][KEY_TARGET_PLATFORM] = "host"
|
||||
CORE.data[KEY_CORE][KEY_TARGET_FRAMEWORK] = "host"
|
||||
CORE.data[KEY_CORE][KEY_FRAMEWORK_VERSION] = cv.Version(1, 0, 0)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema({}),
|
||||
set_core_data,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
cg.add_build_flag("-DUSE_HOST")
|
||||
cg.add_define("ESPHOME_BOARD", "host")
|
||||
cg.add_platformio_option("platform", "platformio/native")
|
||||
@@ -1,5 +0,0 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
KEY_HOST = "host"
|
||||
|
||||
host_ns = cg.esphome_ns.namespace("host")
|
||||
@@ -1,74 +0,0 @@
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "preferences.h"
|
||||
|
||||
#include <sched.h>
|
||||
#include <time.h>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace esphome {
|
||||
|
||||
void IRAM_ATTR HOT yield() { ::sched_yield(); }
|
||||
uint32_t IRAM_ATTR HOT millis() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
time_t seconds = spec.tv_sec;
|
||||
uint32_t ms = round(spec.tv_nsec / 1e6);
|
||||
return ((uint32_t) seconds) * 1000U + ms;
|
||||
}
|
||||
void IRAM_ATTR HOT delay(uint32_t ms) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = ms / 1000;
|
||||
ts.tv_nsec = (ms % 1000) * 1000000;
|
||||
int res;
|
||||
do {
|
||||
res = nanosleep(&ts, &ts);
|
||||
} while (res != 0 && errno == EINTR);
|
||||
}
|
||||
uint32_t IRAM_ATTR HOT micros() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
time_t seconds = spec.tv_sec;
|
||||
uint32_t us = round(spec.tv_nsec / 1e3);
|
||||
return ((uint32_t) seconds) * 1000000U + us;
|
||||
}
|
||||
void IRAM_ATTR HOT delayMicroseconds(uint32_t us) {
|
||||
struct timespec ts;
|
||||
ts.tv_sec = us / 1000000U;
|
||||
ts.tv_nsec = (us % 1000000U) * 1000U;
|
||||
int res;
|
||||
do {
|
||||
res = nanosleep(&ts, &ts);
|
||||
} while (res != 0 && errno == EINTR);
|
||||
}
|
||||
void arch_restart() { exit(0); }
|
||||
void IRAM_ATTR HOT arch_feed_wdt() {
|
||||
// pass
|
||||
}
|
||||
|
||||
uint8_t progmem_read_byte(const uint8_t *addr) { return *addr; }
|
||||
uint32_t arch_get_cpu_cycle_count() {
|
||||
struct timespec spec;
|
||||
clock_gettime(CLOCK_MONOTONIC, &spec);
|
||||
time_t seconds = spec.tv_sec;
|
||||
uint32_t us = spec.tv_nsec;
|
||||
return ((uint32_t) seconds) * 1000000000U + us;
|
||||
}
|
||||
uint32_t arch_get_cpu_freq_hz() { return 1000000000U; }
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
void setup();
|
||||
void loop();
|
||||
int main() {
|
||||
esphome::host::setup_preferences();
|
||||
setup();
|
||||
while (true) {
|
||||
loop();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // USE_HOST
|
||||
@@ -1,59 +0,0 @@
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "gpio.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
static const char *const TAG = "host";
|
||||
|
||||
struct ISRPinArg {
|
||||
uint8_t pin;
|
||||
bool inverted;
|
||||
};
|
||||
|
||||
ISRInternalGPIOPin HostGPIOPin::to_isr() const {
|
||||
auto *arg = new ISRPinArg{}; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
arg->pin = pin_;
|
||||
arg->inverted = inverted_;
|
||||
return ISRInternalGPIOPin((void *) arg);
|
||||
}
|
||||
|
||||
void HostGPIOPin::attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const {
|
||||
ESP_LOGD(TAG, "Attaching interrupt %p to pin %d and mode %d", func, pin_, (uint32_t) type);
|
||||
}
|
||||
void HostGPIOPin::pin_mode(gpio::Flags flags) { ESP_LOGD(TAG, "Setting pin %d mode to %02X", pin_, (uint32_t) flags); }
|
||||
|
||||
std::string HostGPIOPin::dump_summary() const {
|
||||
char buffer[32];
|
||||
snprintf(buffer, sizeof(buffer), "GPIO%u", pin_);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
bool HostGPIOPin::digital_read() { return inverted_; }
|
||||
void HostGPIOPin::digital_write(bool value) {
|
||||
// pass
|
||||
ESP_LOGD(TAG, "Setting pin %d to %s", pin_, value != inverted_ ? "HIGH" : "LOW");
|
||||
}
|
||||
void HostGPIOPin::detach_interrupt() const {}
|
||||
|
||||
} // namespace host
|
||||
|
||||
using namespace host;
|
||||
|
||||
bool IRAM_ATTR ISRInternalGPIOPin::digital_read() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
return arg->inverted;
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::digital_write(bool value) {
|
||||
// pass
|
||||
}
|
||||
void IRAM_ATTR ISRInternalGPIOPin::clear_interrupt() {
|
||||
auto *arg = reinterpret_cast<ISRPinArg *>(arg_);
|
||||
ESP_LOGD(TAG, "Clearing interrupt for pin %d", arg->pin);
|
||||
}
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
||||
@@ -1,37 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
class HostGPIOPin : public InternalGPIOPin {
|
||||
public:
|
||||
void set_pin(uint8_t pin) { pin_ = pin; }
|
||||
void set_inverted(bool inverted) { inverted_ = inverted; }
|
||||
void set_flags(gpio::Flags flags) { flags_ = flags; }
|
||||
|
||||
void setup() override { pin_mode(flags_); }
|
||||
void pin_mode(gpio::Flags flags) override;
|
||||
bool digital_read() override;
|
||||
void digital_write(bool value) override;
|
||||
std::string dump_summary() const override;
|
||||
void detach_interrupt() const override;
|
||||
ISRInternalGPIOPin to_isr() const override;
|
||||
uint8_t get_pin() const override { return pin_; }
|
||||
bool is_inverted() const override { return inverted_; }
|
||||
|
||||
protected:
|
||||
void attach_interrupt_(void (*func)(void *), void *arg, gpio::InterruptType type) const override;
|
||||
|
||||
uint8_t pin_;
|
||||
bool inverted_;
|
||||
gpio::Flags flags_;
|
||||
};
|
||||
|
||||
} // namespace host
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
||||
@@ -1,73 +0,0 @@
|
||||
import logging
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_INPUT,
|
||||
CONF_INVERTED,
|
||||
CONF_MODE,
|
||||
CONF_NUMBER,
|
||||
CONF_OPEN_DRAIN,
|
||||
CONF_OUTPUT,
|
||||
CONF_PULLDOWN,
|
||||
CONF_PULLUP,
|
||||
)
|
||||
from esphome import pins
|
||||
import esphome.config_validation as cv
|
||||
import esphome.codegen as cg
|
||||
|
||||
from .const import host_ns
|
||||
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
HostGPIOPin = host_ns.class_("HostGPIOPin", cg.InternalGPIOPin)
|
||||
|
||||
|
||||
def _translate_pin(value):
|
||||
if isinstance(value, dict) or value is None:
|
||||
raise cv.Invalid(
|
||||
"This variable only supports pin numbers, not full pin schemas "
|
||||
"(with inverted and mode)."
|
||||
)
|
||||
if isinstance(value, int):
|
||||
return value
|
||||
try:
|
||||
return int(value)
|
||||
except ValueError:
|
||||
pass
|
||||
if value.startswith("GPIO"):
|
||||
return cv.int_(value[len("GPIO") :].strip())
|
||||
return value
|
||||
|
||||
|
||||
def validate_gpio_pin(value):
|
||||
return _translate_pin(value)
|
||||
|
||||
|
||||
HOST_PIN_SCHEMA = cv.All(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HostGPIOPin),
|
||||
cv.Required(CONF_NUMBER): validate_gpio_pin,
|
||||
cv.Optional(CONF_MODE, default={}): cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_INPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OPEN_DRAIN, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLUP, default=False): cv.boolean,
|
||||
cv.Optional(CONF_PULLDOWN, default=False): cv.boolean,
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@pins.PIN_SCHEMA_REGISTRY.register("host", HOST_PIN_SCHEMA)
|
||||
async def host_pin_to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
num = config[CONF_NUMBER]
|
||||
cg.add(var.set_pin(num))
|
||||
cg.add(var.set_inverted(config[CONF_INVERTED]))
|
||||
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
|
||||
return var
|
||||
@@ -1,33 +0,0 @@
|
||||
#ifdef USE_HOST
|
||||
|
||||
#include "preferences.h"
|
||||
#include <cstring>
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
static const char *const TAG = "host.preferences";
|
||||
|
||||
class HostPreferences : public ESPPreferences {
|
||||
public:
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { return {}; }
|
||||
|
||||
ESPPreferenceObject make_preference(size_t length, uint32_t type) override { return {}; }
|
||||
};
|
||||
|
||||
void setup_preferences() {
|
||||
auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
|
||||
global_preferences = pref;
|
||||
}
|
||||
|
||||
} // namespace host
|
||||
|
||||
ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_HOST
|
||||
|
||||
namespace esphome {
|
||||
namespace host {
|
||||
|
||||
void setup_preferences();
|
||||
|
||||
} // namespace host
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_HOST
|
||||
11
esphome/components/hydreon_rgxx/__init__.py
Normal file
11
esphome/components/hydreon_rgxx/__init__.py
Normal file
@@ -0,0 +1,11 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
|
||||
CODEOWNERS = ["@functionpointer"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
|
||||
hydreon_rgxx_ns = cg.esphome_ns.namespace("hydreon_rgxx")
|
||||
RGModel = hydreon_rgxx_ns.enum("RGModel")
|
||||
HydreonRGxxComponent = hydreon_rgxx_ns.class_(
|
||||
"HydreonRGxxComponent", cg.PollingComponent, uart.UARTDevice
|
||||
)
|
||||
36
esphome/components/hydreon_rgxx/binary_sensor.py
Normal file
36
esphome/components/hydreon_rgxx/binary_sensor.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_COLD,
|
||||
)
|
||||
|
||||
from . import hydreon_rgxx_ns, HydreonRGxxComponent
|
||||
|
||||
CONF_HYDREON_RGXX_ID = "hydreon_rgxx_id"
|
||||
CONF_TOO_COLD = "too_cold"
|
||||
|
||||
HydreonRGxxBinarySensor = hydreon_rgxx_ns.class_(
|
||||
"HydreonRGxxBinaryComponent", cg.Component
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HydreonRGxxBinarySensor),
|
||||
cv.GenerateID(CONF_HYDREON_RGXX_ID): cv.use_id(HydreonRGxxComponent),
|
||||
cv.Optional(CONF_TOO_COLD): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_COLD
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
main_sensor = await cg.get_variable(config[CONF_HYDREON_RGXX_ID])
|
||||
bin_component = cg.new_Pvariable(config[CONF_ID], main_sensor)
|
||||
await cg.register_component(bin_component, config)
|
||||
if CONF_TOO_COLD in config:
|
||||
tc = await binary_sensor.new_binary_sensor(config[CONF_TOO_COLD])
|
||||
cg.add(main_sensor.set_too_cold_sensor(tc))
|
||||
211
esphome/components/hydreon_rgxx/hydreon_rgxx.cpp
Normal file
211
esphome/components/hydreon_rgxx/hydreon_rgxx.cpp
Normal file
@@ -0,0 +1,211 @@
|
||||
#include "hydreon_rgxx.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hydreon_rgxx {
|
||||
|
||||
static const char *const TAG = "hydreon_rgxx.sensor";
|
||||
static const int MAX_DATA_LENGTH_BYTES = 80;
|
||||
static const uint8_t ASCII_LF = 0x0A;
|
||||
#define HYDREON_RGXX_COMMA ,
|
||||
static const char *const PROTOCOL_NAMES[] = {HYDREON_RGXX_PROTOCOL_LIST(, HYDREON_RGXX_COMMA)};
|
||||
|
||||
void HydreonRGxxComponent::dump_config() {
|
||||
this->check_uart_settings(9600, 1, esphome::uart::UART_CONFIG_PARITY_NONE, 8);
|
||||
ESP_LOGCONFIG(TAG, "hydreon_rgxx:");
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Connection with hydreon_rgxx failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
int i = 0;
|
||||
#define HYDREON_RGXX_LOG_SENSOR(s) \
|
||||
if (this->sensors_[i++] != nullptr) { \
|
||||
LOG_SENSOR(" ", #s, this->sensors_[i - 1]); \
|
||||
}
|
||||
HYDREON_RGXX_PROTOCOL_LIST(HYDREON_RGXX_LOG_SENSOR, );
|
||||
}
|
||||
|
||||
void HydreonRGxxComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up hydreon_rgxx...");
|
||||
while (this->available() != 0) {
|
||||
this->read();
|
||||
}
|
||||
this->schedule_reboot_();
|
||||
}
|
||||
|
||||
bool HydreonRGxxComponent::sensor_missing_() {
|
||||
if (this->sensors_received_ == -1) {
|
||||
// no request sent yet, don't check
|
||||
return false;
|
||||
} else {
|
||||
if (this->sensors_received_ == 0) {
|
||||
ESP_LOGW(TAG, "No data at all");
|
||||
return true;
|
||||
}
|
||||
for (int i = 0; i < NUM_SENSORS; i++) {
|
||||
if (this->sensors_[i] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if ((this->sensors_received_ >> i & 1) == 0) {
|
||||
ESP_LOGW(TAG, "Missing %s", PROTOCOL_NAMES[i]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void HydreonRGxxComponent::update() {
|
||||
if (this->boot_count_ > 0) {
|
||||
if (this->sensor_missing_()) {
|
||||
this->no_response_count_++;
|
||||
ESP_LOGE(TAG, "data missing %d times", this->no_response_count_);
|
||||
if (this->no_response_count_ > 15) {
|
||||
ESP_LOGE(TAG, "asking sensor to reboot");
|
||||
for (auto &sensor : this->sensors_) {
|
||||
if (sensor != nullptr) {
|
||||
sensor->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
this->schedule_reboot_();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
this->no_response_count_ = 0;
|
||||
}
|
||||
this->write_str("R\n");
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->too_cold_sensor_ != nullptr) {
|
||||
this->too_cold_sensor_->publish_state(this->too_cold_);
|
||||
}
|
||||
#endif
|
||||
this->too_cold_ = false;
|
||||
this->sensors_received_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void HydreonRGxxComponent::loop() {
|
||||
uint8_t data;
|
||||
while (this->available() > 0) {
|
||||
if (this->read_byte(&data)) {
|
||||
buffer_ += (char) data;
|
||||
if (this->buffer_.back() == static_cast<char>(ASCII_LF) || this->buffer_.length() >= MAX_DATA_LENGTH_BYTES) {
|
||||
// complete line received
|
||||
this->process_line_();
|
||||
this->buffer_.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Communication with the sensor is asynchronous.
|
||||
* We send requests and let esphome continue doing its thing.
|
||||
* Once we have received a complete line, we process it.
|
||||
*
|
||||
* Catching communication failures is done in two layers:
|
||||
*
|
||||
* 1. We check if all requested data has been received
|
||||
* before we send out the next request. If data keeps
|
||||
* missing, we escalate.
|
||||
* 2. Request the sensor to reboot. We retry based on
|
||||
* a timeout. If the sensor does not respond after
|
||||
* several boot attempts, we give up.
|
||||
*/
|
||||
void HydreonRGxxComponent::schedule_reboot_() {
|
||||
this->boot_count_ = 0;
|
||||
this->set_interval("reboot", 5000, [this]() {
|
||||
if (this->boot_count_ < 0) {
|
||||
ESP_LOGW(TAG, "hydreon_rgxx failed to boot %d times", -this->boot_count_);
|
||||
}
|
||||
this->boot_count_--;
|
||||
this->write_str("K\n");
|
||||
if (this->boot_count_ < -5) {
|
||||
ESP_LOGE(TAG, "hydreon_rgxx can't boot, giving up");
|
||||
for (auto &sensor : this->sensors_) {
|
||||
if (sensor != nullptr) {
|
||||
sensor->publish_state(NAN);
|
||||
}
|
||||
}
|
||||
this->mark_failed();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool HydreonRGxxComponent::buffer_starts_with_(const std::string &prefix) {
|
||||
return this->buffer_starts_with_(prefix.c_str());
|
||||
}
|
||||
|
||||
bool HydreonRGxxComponent::buffer_starts_with_(const char *prefix) { return buffer_.rfind(prefix, 0) == 0; }
|
||||
|
||||
void HydreonRGxxComponent::process_line_() {
|
||||
ESP_LOGV(TAG, "Read from serial: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
|
||||
|
||||
if (buffer_[0] == ';') {
|
||||
ESP_LOGI(TAG, "Comment: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
|
||||
return;
|
||||
}
|
||||
if (this->buffer_starts_with_("PwrDays")) {
|
||||
if (this->boot_count_ <= 0) {
|
||||
this->boot_count_ = 1;
|
||||
} else {
|
||||
this->boot_count_++;
|
||||
}
|
||||
this->cancel_interval("reboot");
|
||||
this->no_response_count_ = 0;
|
||||
ESP_LOGI(TAG, "Boot detected: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
|
||||
this->write_str("P\nH\nM\n"); // set sensor to polling mode, high res mode, metric mode
|
||||
return;
|
||||
}
|
||||
if (this->buffer_starts_with_("SW")) {
|
||||
std::string::size_type majend = this->buffer_.find('.');
|
||||
std::string::size_type endversion = this->buffer_.find(' ', 3);
|
||||
if (majend == std::string::npos || endversion == std::string::npos || majend > endversion) {
|
||||
ESP_LOGW(TAG, "invalid version string: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
|
||||
}
|
||||
int major = strtol(this->buffer_.substr(3, majend - 3).c_str(), nullptr, 10);
|
||||
int minor = strtol(this->buffer_.substr(majend + 1, endversion - (majend + 1)).c_str(), nullptr, 10);
|
||||
|
||||
if (major > 10 || minor >= 1000 || minor < 0 || major < 0) {
|
||||
ESP_LOGW(TAG, "invalid version: %s", this->buffer_.substr(0, this->buffer_.size() - 2).c_str());
|
||||
}
|
||||
this->sw_version_ = major * 1000 + minor;
|
||||
ESP_LOGI(TAG, "detected sw version %i", this->sw_version_);
|
||||
return;
|
||||
}
|
||||
bool is_data_line = false;
|
||||
for (int i = 0; i < NUM_SENSORS; i++) {
|
||||
if (this->sensors_[i] != nullptr && this->buffer_starts_with_(PROTOCOL_NAMES[i])) {
|
||||
is_data_line = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (is_data_line) {
|
||||
std::string::size_type tc = this->buffer_.find("TooCold");
|
||||
this->too_cold_ |= tc != std::string::npos;
|
||||
if (this->too_cold_) {
|
||||
ESP_LOGD(TAG, "Received TooCold");
|
||||
}
|
||||
for (int i = 0; i < NUM_SENSORS; i++) {
|
||||
if (this->sensors_[i] == nullptr) {
|
||||
continue;
|
||||
}
|
||||
std::string::size_type n = this->buffer_.find(PROTOCOL_NAMES[i]);
|
||||
if (n == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
int data = strtol(this->buffer_.substr(n + strlen(PROTOCOL_NAMES[i])).c_str(), nullptr, 10);
|
||||
this->sensors_[i]->publish_state(data);
|
||||
ESP_LOGD(TAG, "Received %s: %f", PROTOCOL_NAMES[i], this->sensors_[i]->get_raw_state());
|
||||
this->sensors_received_ |= (1 << i);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGI(TAG, "Got unknown line: %s", this->buffer_.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
float HydreonRGxxComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
} // namespace hydreon_rgxx
|
||||
} // namespace esphome
|
||||
76
esphome/components/hydreon_rgxx/hydreon_rgxx.h
Normal file
76
esphome/components/hydreon_rgxx/hydreon_rgxx.h
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#include "esphome/components/uart/uart.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace hydreon_rgxx {
|
||||
|
||||
enum RGModel {
|
||||
RG9 = 1,
|
||||
RG15 = 2,
|
||||
};
|
||||
|
||||
#ifdef HYDREON_RGXX_NUM_SENSORS
|
||||
static const uint8_t NUM_SENSORS = HYDREON_RGXX_NUM_SENSORS;
|
||||
#else
|
||||
static const uint8_t NUM_SENSORS = 1;
|
||||
#endif
|
||||
|
||||
#ifndef HYDREON_RGXX_PROTOCOL_LIST
|
||||
#define HYDREON_RGXX_PROTOCOL_LIST(F, SEP) F("")
|
||||
#endif
|
||||
|
||||
class HydreonRGxxComponent : public PollingComponent, public uart::UARTDevice {
|
||||
public:
|
||||
void set_sensor(sensor::Sensor *sensor, int index) { this->sensors_[index] = sensor; }
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void set_too_cold_sensor(binary_sensor::BinarySensor *sensor) { this->too_cold_sensor_ = sensor; }
|
||||
#endif
|
||||
void set_model(RGModel model) { model_ = model; }
|
||||
|
||||
/// Schedule data readings.
|
||||
void update() override;
|
||||
/// Read data once available
|
||||
void loop() override;
|
||||
/// Setup the sensor and test for a connection.
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
float get_setup_priority() const override;
|
||||
|
||||
protected:
|
||||
void process_line_();
|
||||
void schedule_reboot_();
|
||||
bool buffer_starts_with_(const std::string &prefix);
|
||||
bool buffer_starts_with_(const char *prefix);
|
||||
bool sensor_missing_();
|
||||
|
||||
sensor::Sensor *sensors_[NUM_SENSORS] = {nullptr};
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
binary_sensor::BinarySensor *too_cold_sensor_ = nullptr;
|
||||
#endif
|
||||
|
||||
int16_t boot_count_ = 0;
|
||||
int16_t no_response_count_ = 0;
|
||||
std::string buffer_;
|
||||
RGModel model_ = RG9;
|
||||
int sw_version_ = 0;
|
||||
bool too_cold_ = false;
|
||||
|
||||
// bit field showing which sensors we have received data for
|
||||
int sensors_received_ = -1;
|
||||
};
|
||||
|
||||
class HydreonRGxxBinaryComponent : public Component {
|
||||
public:
|
||||
HydreonRGxxBinaryComponent(HydreonRGxxComponent *parent) {}
|
||||
};
|
||||
|
||||
} // namespace hydreon_rgxx
|
||||
} // namespace esphome
|
||||
119
esphome/components/hydreon_rgxx/sensor.py
Normal file
119
esphome/components/hydreon_rgxx/sensor.py
Normal file
@@ -0,0 +1,119 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_MODEL,
|
||||
CONF_MOISTURE,
|
||||
DEVICE_CLASS_HUMIDITY,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
from . import RGModel, HydreonRGxxComponent
|
||||
|
||||
UNIT_INTENSITY = "intensity"
|
||||
UNIT_MILLIMETERS = "mm"
|
||||
UNIT_MILLIMETERS_PER_HOUR = "mm/h"
|
||||
|
||||
CONF_ACC = "acc"
|
||||
CONF_EVENT_ACC = "event_acc"
|
||||
CONF_TOTAL_ACC = "total_acc"
|
||||
CONF_R_INT = "r_int"
|
||||
|
||||
RG_MODELS = {
|
||||
"RG_9": RGModel.RG9,
|
||||
"RG_15": RGModel.RG15,
|
||||
# https://rainsensors.com/wp-content/uploads/sites/3/2020/07/rg-15_instructions_sw_1.000.pdf
|
||||
# https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2020.08.25-rg-9_instructions.pdf
|
||||
# https://rainsensors.com/wp-content/uploads/sites/3/2021/03/2021.03.11-rg-9_instructions.pdf
|
||||
}
|
||||
SUPPORTED_SENSORS = {
|
||||
CONF_ACC: ["RG_15"],
|
||||
CONF_EVENT_ACC: ["RG_15"],
|
||||
CONF_TOTAL_ACC: ["RG_15"],
|
||||
CONF_R_INT: ["RG_15"],
|
||||
CONF_MOISTURE: ["RG_9"],
|
||||
}
|
||||
PROTOCOL_NAMES = {
|
||||
CONF_MOISTURE: "R",
|
||||
CONF_ACC: "Acc",
|
||||
CONF_R_INT: "Rint",
|
||||
CONF_EVENT_ACC: "EventAcc",
|
||||
CONF_TOTAL_ACC: "TotalAcc",
|
||||
}
|
||||
|
||||
|
||||
def _validate(config):
|
||||
for conf, models in SUPPORTED_SENSORS.items():
|
||||
if conf in config:
|
||||
if config[CONF_MODEL] not in models:
|
||||
raise cv.Invalid(
|
||||
f"{conf} is only available on {' and '.join(models)}, not {config[CONF_MODEL]}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(HydreonRGxxComponent),
|
||||
cv.Required(CONF_MODEL): cv.enum(
|
||||
RG_MODELS,
|
||||
upper=True,
|
||||
space="_",
|
||||
),
|
||||
cv.Optional(CONF_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_EVENT_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_TOTAL_ACC): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_R_INT): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETERS_PER_HOUR,
|
||||
accuracy_decimals=2,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_INTENSITY,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_HUMIDITY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(uart.UART_DEVICE_SCHEMA),
|
||||
_validate,
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
|
||||
cg.add_define(
|
||||
"HYDREON_RGXX_PROTOCOL_LIST(F, sep)",
|
||||
cg.RawExpression(
|
||||
" sep ".join([f'F("{name}")' for name in PROTOCOL_NAMES.values()])
|
||||
),
|
||||
)
|
||||
cg.add_define("HYDREON_RGXX_NUM_SENSORS", len(PROTOCOL_NAMES))
|
||||
|
||||
for i, conf in enumerate(PROTOCOL_NAMES):
|
||||
if conf in config:
|
||||
sens = await sensor.new_sensor(config[conf])
|
||||
cg.add(var.set_sensor(sens, i))
|
||||
@@ -46,21 +46,21 @@ class I2CDevice {
|
||||
I2CRegister reg(uint8_t a_register) { return {this, a_register}; }
|
||||
|
||||
ErrorCode read(uint8_t *data, size_t len) { return bus_->read(address_, data, len); }
|
||||
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len) {
|
||||
ErrorCode err = this->write(&a_register, 1);
|
||||
ErrorCode read_register(uint8_t a_register, uint8_t *data, size_t len, bool stop = true) {
|
||||
ErrorCode err = this->write(&a_register, 1, stop);
|
||||
if (err != ERROR_OK)
|
||||
return err;
|
||||
return this->read(data, len);
|
||||
}
|
||||
|
||||
ErrorCode write(const uint8_t *data, uint8_t len) { return bus_->write(address_, data, len); }
|
||||
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len) {
|
||||
ErrorCode write(const uint8_t *data, uint8_t len, bool stop = true) { return bus_->write(address_, data, len, stop); }
|
||||
ErrorCode write_register(uint8_t a_register, const uint8_t *data, size_t len, bool stop = true) {
|
||||
WriteBuffer buffers[2];
|
||||
buffers[0].data = &a_register;
|
||||
buffers[0].len = 1;
|
||||
buffers[1].data = data;
|
||||
buffers[1].len = len;
|
||||
return bus_->writev(address_, buffers, 2);
|
||||
return bus_->writev(address_, buffers, 2, stop);
|
||||
}
|
||||
|
||||
// Compat APIs
|
||||
@@ -93,7 +93,9 @@ class I2CDevice {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool read_byte(uint8_t a_register, uint8_t *data) { return read_register(a_register, data, 1) == ERROR_OK; }
|
||||
bool read_byte(uint8_t a_register, uint8_t *data, bool stop = true) {
|
||||
return read_register(a_register, data, 1, stop) == ERROR_OK;
|
||||
}
|
||||
|
||||
optional<uint8_t> read_byte(uint8_t a_register) {
|
||||
uint8_t data;
|
||||
@@ -104,8 +106,8 @@ class I2CDevice {
|
||||
|
||||
bool read_byte_16(uint8_t a_register, uint16_t *data) { return read_bytes_16(a_register, data, 1); }
|
||||
|
||||
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len) {
|
||||
return write_register(a_register, data, len) == ERROR_OK;
|
||||
bool write_bytes(uint8_t a_register, const uint8_t *data, uint8_t len, bool stop = true) {
|
||||
return write_register(a_register, data, len, stop) == ERROR_OK;
|
||||
}
|
||||
|
||||
bool write_bytes(uint8_t a_register, const std::vector<uint8_t> &data) {
|
||||
@@ -118,7 +120,9 @@ class I2CDevice {
|
||||
|
||||
bool write_bytes_16(uint8_t a_register, const uint16_t *data, uint8_t len);
|
||||
|
||||
bool write_byte(uint8_t a_register, uint8_t data) { return write_bytes(a_register, &data, 1); }
|
||||
bool write_byte(uint8_t a_register, uint8_t data, bool stop = true) {
|
||||
return write_bytes(a_register, &data, 1, stop);
|
||||
}
|
||||
|
||||
bool write_byte_16(uint8_t a_register, uint16_t data) { return write_bytes_16(a_register, &data, 1); }
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ enum ErrorCode {
|
||||
ERROR_NOT_INITIALIZED = 4,
|
||||
ERROR_TOO_LARGE = 5,
|
||||
ERROR_UNKNOWN = 6,
|
||||
ERROR_CRC = 7,
|
||||
};
|
||||
|
||||
struct ReadBuffer {
|
||||
@@ -36,12 +37,18 @@ class I2CBus {
|
||||
}
|
||||
virtual ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) = 0;
|
||||
virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len) {
|
||||
return write(address, buffer, len, true);
|
||||
}
|
||||
virtual ErrorCode write(uint8_t address, const uint8_t *buffer, size_t len, bool stop) {
|
||||
WriteBuffer buf;
|
||||
buf.data = buffer;
|
||||
buf.len = len;
|
||||
return writev(address, &buf, 1);
|
||||
return writev(address, &buf, 1, stop);
|
||||
}
|
||||
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) = 0;
|
||||
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
|
||||
return writev(address, buffers, cnt, true);
|
||||
}
|
||||
virtual ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) = 0;
|
||||
|
||||
protected:
|
||||
void i2c_scan_() {
|
||||
|
||||
@@ -104,7 +104,7 @@ ErrorCode ArduinoI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt)
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
|
||||
ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
|
||||
// logging is only enabled with vv level, if warnings are shown the caller
|
||||
// should log them
|
||||
if (!initialized_) {
|
||||
@@ -139,7 +139,7 @@ ErrorCode ArduinoI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cn
|
||||
return ERROR_UNKNOWN;
|
||||
}
|
||||
}
|
||||
uint8_t status = wire_->endTransmission(true);
|
||||
uint8_t status = wire_->endTransmission(stop);
|
||||
if (status == 0) {
|
||||
return ERROR_OK;
|
||||
} else if (status == 1) {
|
||||
|
||||
@@ -20,7 +20,7 @@ class ArduinoI2CBus : public I2CBus, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
|
||||
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override;
|
||||
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
void set_scan(bool scan) { scan_ = scan; }
|
||||
|
||||
@@ -142,7 +142,7 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
|
||||
|
||||
return ERROR_OK;
|
||||
}
|
||||
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt) {
|
||||
ErrorCode IDFI2CBus::writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) {
|
||||
// logging is only enabled with vv level, if warnings are shown the caller
|
||||
// should log them
|
||||
if (!initialized_) {
|
||||
|
||||
@@ -20,7 +20,7 @@ class IDFI2CBus : public I2CBus, public Component {
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
ErrorCode readv(uint8_t address, ReadBuffer *buffers, size_t cnt) override;
|
||||
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt) override;
|
||||
ErrorCode writev(uint8_t address, WriteBuffer *buffers, size_t cnt, bool stop) override;
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
void set_scan(bool scan) { scan_ = scan; }
|
||||
|
||||
@@ -16,16 +16,24 @@ static const char *const TAG = "json";
|
||||
static std::vector<char> global_json_build_buffer; // NOLINT
|
||||
|
||||
std::string build_json(const json_build_t &f) {
|
||||
// Here we are allocating as much heap memory as available minus 2kb to be safe
|
||||
// Here we are allocating up to 5kb of memory,
|
||||
// with the heap size minus 2kb to be safe if less than 5kb
|
||||
// as we can not have a true dynamic sized document.
|
||||
// The excess memory is freed below with `shrinkToFit()`
|
||||
#ifdef USE_ESP8266
|
||||
const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance)
|
||||
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP32)
|
||||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048;
|
||||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
|
||||
DynamicJsonDocument json_document(free_heap);
|
||||
const size_t request_size = std::min(free_heap - 2048, (size_t) 5120);
|
||||
|
||||
DynamicJsonDocument json_document(request_size);
|
||||
if (json_document.memoryPool().buffer() == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, largest free heap block: %u bytes",
|
||||
request_size, free_heap);
|
||||
return "{}";
|
||||
}
|
||||
JsonObject root = json_document.to<JsonObject>();
|
||||
f(root);
|
||||
json_document.shrinkToFit();
|
||||
@@ -36,27 +44,45 @@ std::string build_json(const json_build_t &f) {
|
||||
}
|
||||
|
||||
void parse_json(const std::string &data, const json_parse_t &f) {
|
||||
// Here we are allocating as much heap memory as available minus 2kb to be safe
|
||||
// Here we are allocating 1.5 times the data size,
|
||||
// with the heap size minus 2kb to be safe if less than that
|
||||
// as we can not have a true dynamic sized document.
|
||||
// The excess memory is freed below with `shrinkToFit()`
|
||||
#ifdef USE_ESP8266
|
||||
const size_t free_heap = ESP.getMaxFreeBlockSize() - 2048; // NOLINT(readability-static-accessed-through-instance)
|
||||
const size_t free_heap = ESP.getMaxFreeBlockSize(); // NOLINT(readability-static-accessed-through-instance)
|
||||
#elif defined(USE_ESP32)
|
||||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL) - 2048;
|
||||
const size_t free_heap = heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
||||
#endif
|
||||
bool pass = false;
|
||||
size_t request_size = std::min(free_heap - 2048, (size_t)(data.size() * 1.5));
|
||||
do {
|
||||
DynamicJsonDocument json_document(request_size);
|
||||
if (json_document.memoryPool().buffer() == nullptr) {
|
||||
ESP_LOGE(TAG, "Could not allocate memory for JSON document! Requested %u bytes, free heap: %u", request_size,
|
||||
free_heap);
|
||||
return;
|
||||
}
|
||||
DeserializationError err = deserializeJson(json_document, data);
|
||||
json_document.shrinkToFit();
|
||||
|
||||
DynamicJsonDocument json_document(free_heap);
|
||||
DeserializationError err = deserializeJson(json_document, data);
|
||||
json_document.shrinkToFit();
|
||||
JsonObject root = json_document.as<JsonObject>();
|
||||
|
||||
JsonObject root = json_document.as<JsonObject>();
|
||||
|
||||
if (err) {
|
||||
ESP_LOGW(TAG, "Parsing JSON failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
f(root);
|
||||
if (err == DeserializationError::Ok) {
|
||||
pass = true;
|
||||
f(root);
|
||||
} else if (err == DeserializationError::NoMemory) {
|
||||
if (request_size * 2 >= free_heap) {
|
||||
ESP_LOGE(TAG, "Can not allocate more memory for deserialization. Consider making source string smaller");
|
||||
return;
|
||||
}
|
||||
ESP_LOGV(TAG, "Increasing memory allocation.");
|
||||
request_size *= 2;
|
||||
continue;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "JSON parse error: %s", err.c_str());
|
||||
return;
|
||||
}
|
||||
} while (!pass);
|
||||
}
|
||||
|
||||
} // namespace json
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import display
|
||||
from esphome.const import CONF_DIMENSIONS
|
||||
from esphome.const import CONF_DIMENSIONS, CONF_POSITION, CONF_DATA
|
||||
|
||||
CONF_USER_CHARACTERS = "user_characters"
|
||||
|
||||
lcd_base_ns = cg.esphome_ns.namespace("lcd_base")
|
||||
LCDDisplay = lcd_base_ns.class_("LCDDisplay", cg.PollingComponent)
|
||||
@@ -16,9 +18,35 @@ def validate_lcd_dimensions(value):
|
||||
return value
|
||||
|
||||
|
||||
def validate_user_characters(value):
|
||||
positions = set()
|
||||
for conf in value:
|
||||
if conf[CONF_POSITION] in positions:
|
||||
raise cv.Invalid(
|
||||
f"Duplicate user defined character at position {conf[CONF_POSITION]}"
|
||||
)
|
||||
positions.add(conf[CONF_POSITION])
|
||||
return value
|
||||
|
||||
|
||||
LCD_SCHEMA = display.BASIC_DISPLAY_SCHEMA.extend(
|
||||
{
|
||||
cv.Required(CONF_DIMENSIONS): validate_lcd_dimensions,
|
||||
cv.Optional(CONF_USER_CHARACTERS): cv.All(
|
||||
cv.ensure_list(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_POSITION): cv.int_range(min=0, max=7),
|
||||
cv.Required(CONF_DATA): cv.All(
|
||||
cv.ensure_list(cv.int_range(min=0, max=31)),
|
||||
cv.Length(min=8, max=8),
|
||||
),
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.Length(max=8),
|
||||
validate_user_characters,
|
||||
),
|
||||
}
|
||||
).extend(cv.polling_component_schema("1s"))
|
||||
|
||||
@@ -27,3 +55,6 @@ async def setup_lcd_display(var, config):
|
||||
await cg.register_component(var, config)
|
||||
await display.register_display(var, config)
|
||||
cg.add(var.set_dimensions(config[CONF_DIMENSIONS][0], config[CONF_DIMENSIONS][1]))
|
||||
if CONF_USER_CHARACTERS in config:
|
||||
for usr in config[CONF_USER_CHARACTERS]:
|
||||
cg.add(var.set_user_defined_char(usr[CONF_POSITION], usr[CONF_DATA]))
|
||||
|
||||
@@ -65,6 +65,13 @@ void LCDDisplay::setup() {
|
||||
this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function);
|
||||
}
|
||||
|
||||
// store user defined characters
|
||||
for (auto &user_defined_char : this->user_defined_chars_) {
|
||||
this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (user_defined_char.first << 3));
|
||||
for (auto data : user_defined_char.second)
|
||||
this->send(data, true);
|
||||
}
|
||||
|
||||
this->command_(LCD_DISPLAY_COMMAND_FUNCTION_SET | display_function);
|
||||
uint8_t display_control = LCD_DISPLAY_DISPLAY_ON;
|
||||
this->command_(LCD_DISPLAY_COMMAND_DISPLAY_CONTROL | display_control);
|
||||
@@ -160,6 +167,13 @@ void LCDDisplay::strftime(uint8_t column, uint8_t row, const char *format, time:
|
||||
}
|
||||
void LCDDisplay::strftime(const char *format, time::ESPTime time) { this->strftime(0, 0, format, time); }
|
||||
#endif
|
||||
void LCDDisplay::loadchar(uint8_t location, uint8_t charmap[]) {
|
||||
location &= 0x7; // we only have 8 locations 0-7
|
||||
this->command_(LCD_DISPLAY_COMMAND_SET_CGRAM_ADDR | (location << 3));
|
||||
for (int i = 0; i < 8; i++) {
|
||||
this->send(charmap[i], true);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace lcd_base
|
||||
} // namespace esphome
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace esphome {
|
||||
namespace lcd_base {
|
||||
|
||||
@@ -19,6 +21,8 @@ class LCDDisplay : public PollingComponent {
|
||||
this->rows_ = rows;
|
||||
}
|
||||
|
||||
void set_user_defined_char(uint8_t pos, const std::vector<uint8_t> &data) { this->user_defined_chars_[pos] = data; }
|
||||
|
||||
void setup() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
@@ -47,6 +51,9 @@ class LCDDisplay : public PollingComponent {
|
||||
void strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0)));
|
||||
#endif
|
||||
|
||||
/// Load custom char to given location
|
||||
void loadchar(uint8_t location, uint8_t charmap[]);
|
||||
|
||||
protected:
|
||||
virtual bool is_four_bit_mode() = 0;
|
||||
virtual void write_n_bits(uint8_t value, uint8_t n) = 0;
|
||||
@@ -58,6 +65,7 @@ class LCDDisplay : public PollingComponent {
|
||||
uint8_t columns_;
|
||||
uint8_t rows_;
|
||||
uint8_t *buffer_{nullptr};
|
||||
std::map<uint8_t, std::vector<uint8_t> > user_defined_chars_;
|
||||
};
|
||||
|
||||
} // namespace lcd_base
|
||||
|
||||
@@ -113,9 +113,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_BAUD_RATE, default=115200): cv.positive_int,
|
||||
cv.Optional(CONF_TX_BUFFER_SIZE, default=512): cv.validate_bytes,
|
||||
cv.Optional(CONF_DEASSERT_RTS_DTR, default=False): cv.boolean,
|
||||
cv.SplitDefault(CONF_HARDWARE_UART, esp32="UART0", esp8266="UART0"): cv.All(
|
||||
cv.only_on(["esp32", "esp8266"]), uart_selection
|
||||
),
|
||||
cv.Optional(CONF_HARDWARE_UART, default="UART0"): uart_selection,
|
||||
cv.Optional(CONF_LEVEL, default="DEBUG"): is_log_level,
|
||||
cv.Optional(CONF_LOGS, default={}): cv.Schema(
|
||||
{
|
||||
@@ -140,9 +138,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
@coroutine_with_priority(90.0)
|
||||
async def to_code(config):
|
||||
baud_rate = config[CONF_BAUD_RATE]
|
||||
log = cg.new_Pvariable(config[CONF_ID], baud_rate, config[CONF_TX_BUFFER_SIZE])
|
||||
if CONF_HARDWARE_UART in config:
|
||||
cg.add(log.set_uart_selection(config[CONF_HARDWARE_UART]))
|
||||
rhs = Logger.new(
|
||||
baud_rate,
|
||||
config[CONF_TX_BUFFER_SIZE],
|
||||
HARDWARE_UART_TO_UART_SELECTION[config[CONF_HARDWARE_UART]],
|
||||
)
|
||||
log = cg.Pvariable(config[CONF_ID], rhs)
|
||||
cg.add(log.pre_setup())
|
||||
|
||||
for tag, level in config[CONF_LOGS].items():
|
||||
@@ -202,15 +203,6 @@ async def to_code(config):
|
||||
)
|
||||
|
||||
|
||||
def maybe_simple_message(schema):
|
||||
def validator(value):
|
||||
if isinstance(value, dict):
|
||||
return cv.Schema(schema)(value)
|
||||
return cv.Schema(schema)({CONF_FORMAT: value})
|
||||
|
||||
return validator
|
||||
|
||||
|
||||
def validate_printf(value):
|
||||
# https://stackoverflow.com/questions/30011379/how-can-i-parse-a-c-format-string-in-python
|
||||
cfmt = r"""
|
||||
@@ -233,7 +225,7 @@ def validate_printf(value):
|
||||
|
||||
CONF_LOGGER_LOG = "logger.log"
|
||||
LOGGER_LOG_ACTION_SCHEMA = cv.All(
|
||||
maybe_simple_message(
|
||||
cv.maybe_simple_value(
|
||||
{
|
||||
cv.Required(CONF_FORMAT): cv.string,
|
||||
cv.Optional(CONF_ARGS, default=list): cv.ensure_list(cv.lambda_),
|
||||
@@ -241,9 +233,10 @@ LOGGER_LOG_ACTION_SCHEMA = cv.All(
|
||||
*LOG_LEVEL_TO_ESP_LOG, upper=True
|
||||
),
|
||||
cv.Optional(CONF_TAG, default="main"): cv.string,
|
||||
}
|
||||
),
|
||||
validate_printf,
|
||||
},
|
||||
validate_printf,
|
||||
key=CONF_FORMAT,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -130,14 +130,12 @@ void HOT Logger::log_message_(int level, const char *tag, int offset) {
|
||||
if (xPortGetFreeHeapSize() < 2048)
|
||||
return;
|
||||
#endif
|
||||
#ifdef USE_HOST
|
||||
puts(msg);
|
||||
#endif
|
||||
|
||||
this->log_callback_.call(level, tag, msg);
|
||||
}
|
||||
|
||||
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size) : baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size) {
|
||||
Logger::Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart)
|
||||
: baud_rate_(baud_rate), tx_buffer_size_(tx_buffer_size), uart_(uart) {
|
||||
// add 1 to buffer size for null terminator
|
||||
this->tx_buffer_ = new char[this->tx_buffer_size_ + 1]; // NOLINT
|
||||
}
|
||||
@@ -219,11 +217,7 @@ void Logger::set_baud_rate(uint32_t baud_rate) { this->baud_rate_ = baud_rate; }
|
||||
void Logger::set_log_level(const std::string &tag, int log_level) {
|
||||
this->log_levels_.push_back(LogLevelOverride{tag, log_level});
|
||||
}
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
UARTSelection Logger::get_uart() const { return this->uart_; }
|
||||
#endif
|
||||
|
||||
void Logger::add_on_log_callback(std::function<void(int, const char *, const char *)> &&callback) {
|
||||
this->log_callback_.add(std::move(callback));
|
||||
}
|
||||
@@ -239,10 +233,7 @@ void Logger::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Logger:");
|
||||
ESP_LOGCONFIG(TAG, " Level: %s", LOG_LEVELS[ESPHOME_LOG_LEVEL]);
|
||||
ESP_LOGCONFIG(TAG, " Log Baud Rate: %u", this->baud_rate_);
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
ESP_LOGCONFIG(TAG, " Hardware UART: %s", UART_SELECTIONS[this->uart_]);
|
||||
#endif
|
||||
|
||||
for (auto &it : this->log_levels_) {
|
||||
ESP_LOGCONFIG(TAG, " Level for '%s': %s", it.tag.c_str(), LOG_LEVELS[it.level]);
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@ namespace esphome {
|
||||
|
||||
namespace logger {
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
/** Enum for logging UART selection
|
||||
*
|
||||
* Advanced configuration (pin selection, etc) is not supported.
|
||||
@@ -32,11 +31,10 @@ enum UARTSelection {
|
||||
UART_SELECTION_UART0_SWAP,
|
||||
#endif
|
||||
};
|
||||
#endif // USE_ESP32 || USE_ESP8266
|
||||
|
||||
class Logger : public Component {
|
||||
public:
|
||||
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size);
|
||||
explicit Logger(uint32_t baud_rate, size_t tx_buffer_size, UARTSelection uart);
|
||||
|
||||
/// Manually set the baud rate for serial, set to 0 to disable.
|
||||
void set_baud_rate(uint32_t baud_rate);
|
||||
@@ -48,11 +46,8 @@ class Logger : public Component {
|
||||
uart_port_t get_uart_num() const { return uart_num_; }
|
||||
#endif
|
||||
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
void set_uart_selection(UARTSelection uart_selection) { uart_ = uart_selection; }
|
||||
/// Get the UART used by the logger.
|
||||
UARTSelection get_uart() const;
|
||||
#endif
|
||||
|
||||
/// Set the log level of the specified tag.
|
||||
void set_log_level(const std::string &tag, int log_level);
|
||||
@@ -122,9 +117,7 @@ class Logger : public Component {
|
||||
char *tx_buffer_{nullptr};
|
||||
int tx_buffer_at_{0};
|
||||
int tx_buffer_size_{0};
|
||||
#if defined(USE_ESP32) || defined(USE_ESP8266)
|
||||
UARTSelection uart_{UART_SELECTION_UART0};
|
||||
#endif
|
||||
#ifdef USE_ARDUINO
|
||||
HardwareSerial *hw_serial_{nullptr};
|
||||
#endif
|
||||
|
||||
@@ -20,7 +20,7 @@ void MCP3204::dump_config() {
|
||||
}
|
||||
|
||||
float MCP3204::read_data(uint8_t pin) {
|
||||
uint8_t adc_primary_config = 0b00000110 & 0b00000111;
|
||||
uint8_t adc_primary_config = 0b00000110 | (pin >> 2);
|
||||
uint8_t adc_secondary_config = pin << 6;
|
||||
this->enable();
|
||||
this->transfer_byte(adc_primary_config);
|
||||
|
||||
@@ -17,7 +17,7 @@ CONFIG_SCHEMA = sensor.SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MCP3204Sensor),
|
||||
cv.GenerateID(CONF_MCP3204_ID): cv.use_id(MCP3204),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=3),
|
||||
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7),
|
||||
}
|
||||
).extend(cv.polling_component_schema("60s"))
|
||||
|
||||
|
||||
@@ -71,9 +71,9 @@ SENSOR_VALUE_TYPE = {
|
||||
"S_DWORD": SensorValueType.S_DWORD,
|
||||
"S_DWORD_R": SensorValueType.S_DWORD_R,
|
||||
"U_QWORD": SensorValueType.U_QWORD,
|
||||
"U_QWORDU_R": SensorValueType.U_QWORD_R,
|
||||
"U_QWORD_R": SensorValueType.U_QWORD_R,
|
||||
"S_QWORD": SensorValueType.S_QWORD,
|
||||
"U_QWORD_R": SensorValueType.S_QWORD_R,
|
||||
"S_QWORD_R": SensorValueType.S_QWORD_R,
|
||||
"FP32": SensorValueType.FP32,
|
||||
"FP32_R": SensorValueType.FP32_R,
|
||||
}
|
||||
@@ -87,9 +87,9 @@ TYPE_REGISTER_MAP = {
|
||||
"S_DWORD": 2,
|
||||
"S_DWORD_R": 2,
|
||||
"U_QWORD": 4,
|
||||
"U_QWORDU_R": 4,
|
||||
"S_QWORD": 4,
|
||||
"U_QWORD_R": 4,
|
||||
"S_QWORD": 4,
|
||||
"S_QWORD_R": 4,
|
||||
"FP32": 2,
|
||||
"FP32_R": 2,
|
||||
}
|
||||
|
||||
@@ -455,6 +455,28 @@ ModbusCommandItem ModbusCommandItem::create_custom_command(
|
||||
return cmd;
|
||||
}
|
||||
|
||||
ModbusCommandItem ModbusCommandItem::create_custom_command(
|
||||
ModbusController *modbusdevice, const std::vector<uint16_t> &values,
|
||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
||||
&&handler) {
|
||||
ModbusCommandItem cmd = {};
|
||||
cmd.modbusdevice = modbusdevice;
|
||||
cmd.function_code = ModbusFunctionCode::CUSTOM;
|
||||
if (handler == nullptr) {
|
||||
cmd.on_data_func = [](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||
ESP_LOGI(TAG, "Custom Command sent");
|
||||
};
|
||||
} else {
|
||||
cmd.on_data_func = handler;
|
||||
}
|
||||
for (auto v : values) {
|
||||
cmd.payload.push_back((v >> 8) & 0xFF);
|
||||
cmd.payload.push_back(v & 0xFF);
|
||||
}
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
bool ModbusCommandItem::send() {
|
||||
if (this->function_code != ModbusFunctionCode::CUSTOM) {
|
||||
modbusdevice->send(uint8_t(this->function_code), this->register_address, this->register_count, this->payload.size(),
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#include "esphome/core/automation.h"
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
#include <list>
|
||||
#include <set>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <vector>
|
||||
|
||||
namespace esphome {
|
||||
@@ -374,8 +374,8 @@ class ModbusCommandItem {
|
||||
const std::vector<bool> &values);
|
||||
/** Create custom modbus command
|
||||
* @param modbusdevice pointer to the device to execute the command
|
||||
* @param values byte vector of data to be sent to the device. The compplete payload must be provided with the
|
||||
* exception of the crc codess
|
||||
* @param values byte vector of data to be sent to the device. The complete payload must be provided with the
|
||||
* exception of the crc codes
|
||||
* @param handler function called when the response is received. Default is just logging a response
|
||||
* @return ModbusCommandItem with the prepared command
|
||||
*/
|
||||
@@ -383,6 +383,18 @@ class ModbusCommandItem {
|
||||
ModbusController *modbusdevice, const std::vector<uint8_t> &values,
|
||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
||||
&&handler = nullptr);
|
||||
|
||||
/** Create custom modbus command
|
||||
* @param modbusdevice pointer to the device to execute the command
|
||||
* @param values word vector of data to be sent to the device. The complete payload must be provided with the
|
||||
* exception of the crc codes
|
||||
* @param handler function called when the response is received. Default is just logging a response
|
||||
* @return ModbusCommandItem with the prepared command
|
||||
*/
|
||||
static ModbusCommandItem create_custom_command(
|
||||
ModbusController *modbusdevice, const std::vector<uint16_t> &values,
|
||||
std::function<void(ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data)>
|
||||
&&handler = nullptr);
|
||||
};
|
||||
|
||||
/** Modbus controller class.
|
||||
|
||||
@@ -11,6 +11,7 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
from .. import (
|
||||
MODBUS_WRITE_REGISTER_TYPE,
|
||||
add_modbus_base_properties,
|
||||
modbus_controller_ns,
|
||||
modbus_calc_properties,
|
||||
@@ -24,6 +25,7 @@ from ..const import (
|
||||
CONF_CUSTOM_COMMAND,
|
||||
CONF_FORCE_NEW_RANGE,
|
||||
CONF_MODBUS_CONTROLLER_ID,
|
||||
CONF_REGISTER_TYPE,
|
||||
CONF_SKIP_UPDATES,
|
||||
CONF_USE_WRITE_MULTIPLE,
|
||||
CONF_VALUE_TYPE,
|
||||
@@ -61,6 +63,9 @@ CONFIG_SCHEMA = cv.All(
|
||||
number.NUMBER_SCHEMA.extend(ModbusItemBaseSchema).extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(ModbusNumber),
|
||||
cv.Optional(CONF_REGISTER_TYPE, default="holding"): cv.enum(
|
||||
MODBUS_WRITE_REGISTER_TYPE
|
||||
),
|
||||
cv.Optional(CONF_VALUE_TYPE, default="U_WORD"): cv.enum(SENSOR_VALUE_TYPE),
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
# 24 bits are the maximum value for fp32 before precison is lost
|
||||
@@ -81,6 +86,7 @@ async def to_code(config):
|
||||
byte_offset, reg_count = modbus_calc_properties(config)
|
||||
var = cg.new_Pvariable(
|
||||
config[CONF_ID],
|
||||
config[CONF_REGISTER_TYPE],
|
||||
config[CONF_ADDRESS],
|
||||
byte_offset,
|
||||
config[CONF_BITMASK],
|
||||
|
||||
@@ -26,6 +26,7 @@ void ModbusNumber::parse_and_publish(const std::vector<uint8_t> &data) {
|
||||
}
|
||||
|
||||
void ModbusNumber::control(float value) {
|
||||
ModbusCommandItem write_cmd;
|
||||
std::vector<uint16_t> data;
|
||||
float write_value = value;
|
||||
// Is there are lambda configured?
|
||||
@@ -45,33 +46,39 @@ void ModbusNumber::control(float value) {
|
||||
write_value = multiply_by_ * write_value;
|
||||
}
|
||||
|
||||
// lambda didn't set payload
|
||||
if (data.empty()) {
|
||||
data = float_to_payload(write_value, this->sensor_value_type);
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)",
|
||||
this->get_name().c_str(), this->start_address, this->register_count, value, write_value);
|
||||
|
||||
// Create and send the write command
|
||||
ModbusCommandItem write_cmd;
|
||||
if (this->register_count == 1 && !this->use_write_multiple_) {
|
||||
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
|
||||
write_cmd =
|
||||
ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]);
|
||||
if (!data.empty()) {
|
||||
ESP_LOGV(TAG, "Modbus Number write raw: %s", format_hex_pretty(data).c_str());
|
||||
write_cmd = ModbusCommandItem::create_custom_command(
|
||||
this->parent_, data,
|
||||
[this, write_cmd](ModbusRegisterType register_type, uint16_t start_address, const std::vector<uint8_t> &data) {
|
||||
this->parent_->on_write_register_response(write_cmd.register_type, this->start_address, data);
|
||||
});
|
||||
} else {
|
||||
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2,
|
||||
this->register_count, data);
|
||||
data = float_to_payload(write_value, this->sensor_value_type);
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
"Updating register: connected Sensor=%s start address=0x%X register count=%d new value=%.02f (val=%.02f)",
|
||||
this->get_name().c_str(), this->start_address, this->register_count, value, write_value);
|
||||
|
||||
// Create and send the write command
|
||||
if (this->register_count == 1 && !this->use_write_multiple_) {
|
||||
// since offset is in bytes and a register is 16 bits we get the start by adding offset/2
|
||||
write_cmd =
|
||||
ModbusCommandItem::create_write_single_command(parent_, this->start_address + this->offset / 2, data[0]);
|
||||
} else {
|
||||
write_cmd = ModbusCommandItem::create_write_multiple_command(parent_, this->start_address + this->offset / 2,
|
||||
this->register_count, data);
|
||||
}
|
||||
// publish new value
|
||||
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
|
||||
const std::vector<uint8_t> &data) {
|
||||
// gets called when the write command is ack'd from the device
|
||||
parent_->on_write_register_response(write_cmd.register_type, start_address, data);
|
||||
this->publish_state(value);
|
||||
};
|
||||
}
|
||||
// publish new value
|
||||
write_cmd.on_data_func = [this, write_cmd, value](ModbusRegisterType register_type, uint16_t start_address,
|
||||
const std::vector<uint8_t> &data) {
|
||||
// gets called when the write command is ack'd from the device
|
||||
parent_->on_write_register_response(write_cmd.register_type, start_address, data);
|
||||
this->publish_state(value);
|
||||
};
|
||||
parent_->queue_command(write_cmd);
|
||||
this->publish_state(value);
|
||||
}
|
||||
void ModbusNumber::dump_config() { LOG_NUMBER(TAG, "Modbus Number", this); }
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ using value_to_data_t = std::function<float>(float);
|
||||
|
||||
class ModbusNumber : public number::Number, public Component, public SensorItem {
|
||||
public:
|
||||
ModbusNumber(uint16_t start_address, uint8_t offset, uint32_t bitmask, SensorValueType value_type, int register_count,
|
||||
uint8_t skip_updates, bool force_new_range) {
|
||||
this->register_type = ModbusRegisterType::HOLDING;
|
||||
ModbusNumber(ModbusRegisterType register_type, uint16_t start_address, uint8_t offset, uint32_t bitmask,
|
||||
SensorValueType value_type, int register_count, uint8_t skip_updates, bool force_new_range) {
|
||||
this->register_type = register_type;
|
||||
this->start_address = start_address;
|
||||
this->offset = offset;
|
||||
this->bitmask = bitmask;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
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 esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_OPTIMISTIC
|
||||
|
||||
from .. import (
|
||||
SENSOR_VALUE_TYPE,
|
||||
@@ -30,7 +29,6 @@ ModbusSelect = modbus_controller_ns.class_(
|
||||
)
|
||||
|
||||
|
||||
@jschema_composite
|
||||
def ensure_option_map():
|
||||
def validator(value):
|
||||
cv.check_not_templatable(value)
|
||||
@@ -79,6 +77,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
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_OPTIMISTIC, default=False): cv.boolean,
|
||||
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
|
||||
cv.Optional(CONF_WRITE_LAMBDA): cv.returning_lambda,
|
||||
},
|
||||
@@ -112,6 +111,7 @@ async def to_code(config):
|
||||
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]))
|
||||
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
|
||||
|
||||
if CONF_LAMBDA in config:
|
||||
template_ = await cg.process_lambda(
|
||||
|
||||
@@ -80,6 +80,9 @@ void ModbusSelect::control(const std::string &value) {
|
||||
}
|
||||
|
||||
parent_->queue_command(write_cmd);
|
||||
|
||||
if (this->optimistic_)
|
||||
this->publish_state(value);
|
||||
}
|
||||
|
||||
} // namespace modbus_controller
|
||||
|
||||
@@ -32,6 +32,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
|
||||
|
||||
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_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
|
||||
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; }
|
||||
|
||||
@@ -43,6 +44,7 @@ class ModbusSelect : public Component, public select::Select, public SensorItem
|
||||
std::vector<int64_t> mapping_;
|
||||
ModbusController *parent_;
|
||||
bool use_write_multiple_{false};
|
||||
bool optimistic_{false};
|
||||
optional<transform_func_t> transform_func_;
|
||||
optional<write_transform_func_t> write_transform_func_;
|
||||
};
|
||||
|
||||
@@ -9,6 +9,7 @@ from esphome.const import (
|
||||
CONF_AVAILABILITY,
|
||||
CONF_BIRTH_MESSAGE,
|
||||
CONF_BROKER,
|
||||
CONF_CERTIFICATE_AUTHORITY,
|
||||
CONF_CLIENT_ID,
|
||||
CONF_COMMAND_TOPIC,
|
||||
CONF_COMMAND_RETAIN,
|
||||
@@ -42,9 +43,14 @@ from esphome.const import (
|
||||
CONF_WILL_MESSAGE,
|
||||
)
|
||||
from esphome.core import coroutine_with_priority, CORE
|
||||
from esphome.components.esp32 import add_idf_sdkconfig_option
|
||||
|
||||
DEPENDENCIES = ["network"]
|
||||
AUTO_LOAD = ["json", "async_tcp"]
|
||||
|
||||
AUTO_LOAD = ["json"]
|
||||
|
||||
CONF_IDF_SEND_ASYNC = "idf_send_async"
|
||||
CONF_SKIP_CERT_CN_CHECK = "skip_cert_cn_check"
|
||||
|
||||
|
||||
def validate_message_just_topic(value):
|
||||
@@ -163,6 +169,15 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.Optional(CONF_USERNAME, default=""): cv.string,
|
||||
cv.Optional(CONF_PASSWORD, default=""): cv.string,
|
||||
cv.Optional(CONF_CLIENT_ID): cv.string,
|
||||
cv.SplitDefault(CONF_IDF_SEND_ASYNC, esp32_idf=False): cv.All(
|
||||
cv.boolean, cv.only_with_esp_idf
|
||||
),
|
||||
cv.Optional(CONF_CERTIFICATE_AUTHORITY): cv.All(
|
||||
cv.string, cv.only_with_esp_idf
|
||||
),
|
||||
cv.SplitDefault(CONF_SKIP_CERT_CN_CHECK, esp32_idf=False): cv.All(
|
||||
cv.boolean, cv.only_with_esp_idf
|
||||
),
|
||||
cv.Optional(CONF_DISCOVERY, default=True): cv.Any(
|
||||
cv.boolean, cv.one_of("CLEAN", upper=True)
|
||||
),
|
||||
@@ -217,7 +232,6 @@ CONFIG_SCHEMA = cv.All(
|
||||
}
|
||||
),
|
||||
validate_config,
|
||||
cv.only_with_arduino,
|
||||
)
|
||||
|
||||
|
||||
@@ -238,9 +252,11 @@ def exp_mqtt_message(config):
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
# Add required libraries for arduino
|
||||
if CORE.using_arduino:
|
||||
# https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json
|
||||
cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6")
|
||||
|
||||
# https://github.com/OttoWinter/async-mqtt-client/blob/master/library.json
|
||||
cg.add_library("ottowinter/AsyncMqttClient-esphome", "0.8.6")
|
||||
cg.add_define("USE_MQTT")
|
||||
cg.add_global(mqtt_ns.using)
|
||||
|
||||
@@ -321,6 +337,19 @@ async def to_code(config):
|
||||
|
||||
cg.add(var.set_reboot_timeout(config[CONF_REBOOT_TIMEOUT]))
|
||||
|
||||
# esp-idf only
|
||||
if CONF_CERTIFICATE_AUTHORITY in config:
|
||||
cg.add(var.set_ca_certificate(config[CONF_CERTIFICATE_AUTHORITY]))
|
||||
cg.add(var.set_skip_cert_cn_check(config[CONF_SKIP_CERT_CN_CHECK]))
|
||||
|
||||
# prevent error -0x428e
|
||||
# See https://github.com/espressif/esp-idf/issues/139
|
||||
add_idf_sdkconfig_option("CONFIG_MBEDTLS_HARDWARE_MPI", False)
|
||||
|
||||
if CONF_IDF_SEND_ASYNC in config and config[CONF_IDF_SEND_ASYNC]:
|
||||
cg.add_define("USE_MQTT_IDF_ENQUEUE")
|
||||
# end esp-idf
|
||||
|
||||
for conf in config.get(CONF_ON_MESSAGE, []):
|
||||
trig = cg.new_Pvariable(conf[CONF_TRIGGER_ID], conf[CONF_TOPIC])
|
||||
cg.add(trig.set_qos(conf[CONF_QOS]))
|
||||
|
||||
69
esphome/components/mqtt/mqtt_backend.h
Normal file
69
esphome/components/mqtt/mqtt_backend.h
Normal file
@@ -0,0 +1,69 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
enum class MQTTClientDisconnectReason : int8_t {
|
||||
TCP_DISCONNECTED = 0,
|
||||
MQTT_UNACCEPTABLE_PROTOCOL_VERSION = 1,
|
||||
MQTT_IDENTIFIER_REJECTED = 2,
|
||||
MQTT_SERVER_UNAVAILABLE = 3,
|
||||
MQTT_MALFORMED_CREDENTIALS = 4,
|
||||
MQTT_NOT_AUTHORIZED = 5,
|
||||
ESP8266_NOT_ENOUGH_SPACE = 6,
|
||||
TLS_BAD_FINGERPRINT = 7
|
||||
};
|
||||
|
||||
/// internal struct for MQTT messages.
|
||||
struct MQTTMessage {
|
||||
std::string topic;
|
||||
std::string payload;
|
||||
uint8_t qos; ///< QoS. Only for last will testaments.
|
||||
bool retain;
|
||||
};
|
||||
|
||||
class MQTTBackend {
|
||||
public:
|
||||
using on_connect_callback_t = void(bool session_present);
|
||||
using on_disconnect_callback_t = void(MQTTClientDisconnectReason reason);
|
||||
using on_subscribe_callback_t = void(uint16_t packet_id, uint8_t qos);
|
||||
using on_unsubscribe_callback_t = void(uint16_t packet_id);
|
||||
using on_message_callback_t = void(const char *topic, const char *payload, size_t len, size_t index, size_t total);
|
||||
using on_publish_user_callback_t = void(uint16_t packet_id);
|
||||
|
||||
virtual void set_keep_alive(uint16_t keep_alive) = 0;
|
||||
virtual void set_client_id(const char *client_id) = 0;
|
||||
virtual void set_clean_session(bool clean_session) = 0;
|
||||
virtual void set_credentials(const char *username, const char *password) = 0;
|
||||
virtual void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) = 0;
|
||||
virtual void set_server(network::IPAddress ip, uint16_t port) = 0;
|
||||
virtual void set_server(const char *host, uint16_t port) = 0;
|
||||
virtual void set_on_connect(std::function<on_connect_callback_t> &&callback) = 0;
|
||||
virtual void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) = 0;
|
||||
virtual void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) = 0;
|
||||
virtual void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) = 0;
|
||||
virtual void set_on_message(std::function<on_message_callback_t> &&callback) = 0;
|
||||
virtual void set_on_publish(std::function<on_publish_user_callback_t> &&callback) = 0;
|
||||
virtual bool connected() const = 0;
|
||||
virtual void connect() = 0;
|
||||
virtual void disconnect() = 0;
|
||||
virtual bool subscribe(const char *topic, uint8_t qos) = 0;
|
||||
virtual bool unsubscribe(const char *topic) = 0;
|
||||
virtual bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) = 0;
|
||||
|
||||
virtual bool publish(const MQTTMessage &message) {
|
||||
return publish(message.topic.c_str(), message.payload.c_str(), message.payload.length(), message.qos,
|
||||
message.retain);
|
||||
}
|
||||
|
||||
// called from MQTTClient::loop()
|
||||
virtual void loop() {}
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
74
esphome/components/mqtt/mqtt_backend_arduino.h
Normal file
74
esphome/components/mqtt/mqtt_backend_arduino.h
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "mqtt_backend.h"
|
||||
#include <AsyncMqttClient.h>
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTBackendArduino final : public MQTTBackend {
|
||||
public:
|
||||
void set_keep_alive(uint16_t keep_alive) final { mqtt_client_.setKeepAlive(keep_alive); }
|
||||
void set_client_id(const char *client_id) final { mqtt_client_.setClientId(client_id); }
|
||||
void set_clean_session(bool clean_session) final { mqtt_client_.setCleanSession(clean_session); }
|
||||
void set_credentials(const char *username, const char *password) final {
|
||||
mqtt_client_.setCredentials(username, password);
|
||||
}
|
||||
void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final {
|
||||
mqtt_client_.setWill(topic, qos, retain, payload);
|
||||
}
|
||||
void set_server(network::IPAddress ip, uint16_t port) final {
|
||||
mqtt_client_.setServer(IPAddress(static_cast<uint32_t>(ip)), port);
|
||||
}
|
||||
void set_server(const char *host, uint16_t port) final { mqtt_client_.setServer(host, port); }
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void set_secure(bool secure) { mqtt_client.setSecure(secure); }
|
||||
void add_server_fingerprint(const uint8_t *fingerprint) { mqtt_client.addServerFingerprint(fingerprint); }
|
||||
#endif
|
||||
|
||||
void set_on_connect(std::function<on_connect_callback_t> &&callback) final {
|
||||
this->mqtt_client_.onConnect(std::move(callback));
|
||||
}
|
||||
void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) final {
|
||||
auto async_callback = [callback](AsyncMqttClientDisconnectReason reason) {
|
||||
// int based enum so casting isn't a problem
|
||||
callback(static_cast<MQTTClientDisconnectReason>(reason));
|
||||
};
|
||||
this->mqtt_client_.onDisconnect(std::move(async_callback));
|
||||
}
|
||||
void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) final {
|
||||
this->mqtt_client_.onSubscribe(std::move(callback));
|
||||
}
|
||||
void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) final {
|
||||
this->mqtt_client_.onUnsubscribe(std::move(callback));
|
||||
}
|
||||
void set_on_message(std::function<on_message_callback_t> &&callback) final {
|
||||
auto async_callback = [callback](const char *topic, const char *payload,
|
||||
AsyncMqttClientMessageProperties async_properties, size_t len, size_t index,
|
||||
size_t total) { callback(topic, payload, len, index, total); };
|
||||
mqtt_client_.onMessage(std::move(async_callback));
|
||||
}
|
||||
void set_on_publish(std::function<on_publish_user_callback_t> &&callback) final {
|
||||
this->mqtt_client_.onPublish(std::move(callback));
|
||||
}
|
||||
|
||||
bool connected() const final { return mqtt_client_.connected(); }
|
||||
void connect() final { mqtt_client_.connect(); }
|
||||
void disconnect() final { mqtt_client_.disconnect(true); }
|
||||
bool subscribe(const char *topic, uint8_t qos) final { return mqtt_client_.subscribe(topic, qos) != 0; }
|
||||
bool unsubscribe(const char *topic) final { return mqtt_client_.unsubscribe(topic) != 0; }
|
||||
bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final {
|
||||
return mqtt_client_.publish(topic, qos, retain, payload, length, false, 0) != 0;
|
||||
}
|
||||
using MQTTBackend::publish;
|
||||
|
||||
protected:
|
||||
AsyncMqttClient mqtt_client_;
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif // defined(USE_ARDUINO)
|
||||
149
esphome/components/mqtt/mqtt_backend_idf.cpp
Normal file
149
esphome/components/mqtt/mqtt_backend_idf.cpp
Normal file
@@ -0,0 +1,149 @@
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include <string>
|
||||
#include "mqtt_backend_idf.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
static const char *const TAG = "mqtt.idf";
|
||||
|
||||
bool MQTTBackendIDF::initialize_() {
|
||||
mqtt_cfg_.user_context = (void *) this;
|
||||
mqtt_cfg_.buffer_size = MQTT_BUFFER_SIZE;
|
||||
|
||||
mqtt_cfg_.host = this->host_.c_str();
|
||||
mqtt_cfg_.port = this->port_;
|
||||
mqtt_cfg_.keepalive = this->keep_alive_;
|
||||
mqtt_cfg_.disable_clean_session = !this->clean_session_;
|
||||
|
||||
if (!this->username_.empty()) {
|
||||
mqtt_cfg_.username = this->username_.c_str();
|
||||
if (!this->password_.empty()) {
|
||||
mqtt_cfg_.password = this->password_.c_str();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->lwt_topic_.empty()) {
|
||||
mqtt_cfg_.lwt_topic = this->lwt_topic_.c_str();
|
||||
this->mqtt_cfg_.lwt_qos = this->lwt_qos_;
|
||||
this->mqtt_cfg_.lwt_retain = this->lwt_retain_;
|
||||
|
||||
if (!this->lwt_message_.empty()) {
|
||||
mqtt_cfg_.lwt_msg = this->lwt_message_.c_str();
|
||||
mqtt_cfg_.lwt_msg_len = this->lwt_message_.size();
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->client_id_.empty()) {
|
||||
mqtt_cfg_.client_id = this->client_id_.c_str();
|
||||
}
|
||||
if (ca_certificate_.has_value()) {
|
||||
mqtt_cfg_.cert_pem = ca_certificate_.value().c_str();
|
||||
mqtt_cfg_.skip_cert_common_name_check = skip_cert_cn_check_;
|
||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_SSL;
|
||||
} else {
|
||||
mqtt_cfg_.transport = MQTT_TRANSPORT_OVER_TCP;
|
||||
}
|
||||
auto *mqtt_client = esp_mqtt_client_init(&mqtt_cfg_);
|
||||
if (mqtt_client) {
|
||||
handler_.reset(mqtt_client);
|
||||
is_initalized_ = true;
|
||||
esp_mqtt_client_register_event(mqtt_client, MQTT_EVENT_ANY, mqtt_event_handler, this);
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to initialize IDF-MQTT");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTBackendIDF::loop() {
|
||||
// process new events
|
||||
// handle only 1 message per loop iteration
|
||||
if (!mqtt_events_.empty()) {
|
||||
auto &event = mqtt_events_.front();
|
||||
mqtt_event_handler_(event);
|
||||
mqtt_events_.pop();
|
||||
}
|
||||
}
|
||||
|
||||
void MQTTBackendIDF::mqtt_event_handler_(const esp_mqtt_event_t &event) {
|
||||
ESP_LOGV(TAG, "Event dispatched from event loop event_id=%d", event.event_id);
|
||||
switch (event.event_id) {
|
||||
case MQTT_EVENT_BEFORE_CONNECT:
|
||||
ESP_LOGV(TAG, "MQTT_EVENT_BEFORE_CONNECT");
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGV(TAG, "MQTT_EVENT_CONNECTED");
|
||||
// TODO session present check
|
||||
this->is_connected_ = true;
|
||||
this->on_connect_.call(!mqtt_cfg_.disable_clean_session);
|
||||
break;
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGV(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||
// TODO is there a way to get the disconnect reason?
|
||||
this->is_connected_ = false;
|
||||
this->on_disconnect_.call(MQTTClientDisconnectReason::TCP_DISCONNECTED);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBED:
|
||||
ESP_LOGV(TAG, "MQTT_EVENT_SUBSCRIBED, msg_id=%d", event.msg_id);
|
||||
// hardcode QoS to 0. QoS is not used in this context but required to mirror the AsyncMqtt interface
|
||||
this->on_subscribe_.call((int) event.msg_id, 0);
|
||||
break;
|
||||
case MQTT_EVENT_UNSUBSCRIBED:
|
||||
ESP_LOGV(TAG, "MQTT_EVENT_UNSUBSCRIBED, msg_id=%d", event.msg_id);
|
||||
this->on_unsubscribe_.call((int) event.msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_PUBLISHED:
|
||||
ESP_LOGV(TAG, "MQTT_EVENT_PUBLISHED, msg_id=%d", event.msg_id);
|
||||
this->on_publish_.call((int) event.msg_id);
|
||||
break;
|
||||
case MQTT_EVENT_DATA: {
|
||||
static std::string topic;
|
||||
if (event.topic) {
|
||||
// not 0 terminated - create a string from it
|
||||
topic = std::string(event.topic, event.topic_len);
|
||||
}
|
||||
ESP_LOGV(TAG, "MQTT_EVENT_DATA %s", topic.c_str());
|
||||
auto data_len = event.data_len;
|
||||
if (data_len == 0)
|
||||
data_len = strlen(event.data);
|
||||
this->on_message_.call(event.topic ? const_cast<char *>(topic.c_str()) : nullptr, event.data, data_len,
|
||||
event.current_data_offset, event.total_data_len);
|
||||
} break;
|
||||
case MQTT_EVENT_ERROR:
|
||||
ESP_LOGE(TAG, "MQTT_EVENT_ERROR");
|
||||
if (event.error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
|
||||
ESP_LOGE(TAG, "Last error code reported from esp-tls: 0x%x", event.error_handle->esp_tls_last_esp_err);
|
||||
ESP_LOGE(TAG, "Last tls stack error number: 0x%x", event.error_handle->esp_tls_stack_err);
|
||||
ESP_LOGE(TAG, "Last captured errno : %d (%s)", event.error_handle->esp_transport_sock_errno,
|
||||
strerror(event.error_handle->esp_transport_sock_errno));
|
||||
} else if (event.error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
|
||||
ESP_LOGE(TAG, "Connection refused error: 0x%x", event.error_handle->connect_return_code);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Unknown error type: 0x%x", event.error_handle->error_type);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
ESP_LOGV(TAG, "Other event id:%d", event.event_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// static - Dispatch event to instance method
|
||||
void MQTTBackendIDF::mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data) {
|
||||
MQTTBackendIDF *instance = static_cast<MQTTBackendIDF *>(handler_args);
|
||||
// queue event to decouple processing
|
||||
if (instance) {
|
||||
auto event = *static_cast<esp_mqtt_event_t *>(event_data);
|
||||
instance->mqtt_events_.push(event);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
#endif // USE_ESP_IDF
|
||||
143
esphome/components/mqtt/mqtt_backend_idf.h
Normal file
143
esphome/components/mqtt/mqtt_backend_idf.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include <mqtt_client.h>
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "mqtt_backend.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace mqtt {
|
||||
|
||||
class MQTTBackendIDF final : public MQTTBackend {
|
||||
public:
|
||||
static const size_t MQTT_BUFFER_SIZE = 4096;
|
||||
|
||||
void set_keep_alive(uint16_t keep_alive) final { this->keep_alive_ = keep_alive; }
|
||||
void set_client_id(const char *client_id) final { this->client_id_ = client_id; }
|
||||
void set_clean_session(bool clean_session) final { this->clean_session_ = clean_session; }
|
||||
|
||||
void set_credentials(const char *username, const char *password) final {
|
||||
if (username)
|
||||
this->username_ = username;
|
||||
if (password)
|
||||
this->password_ = password;
|
||||
}
|
||||
void set_will(const char *topic, uint8_t qos, bool retain, const char *payload) final {
|
||||
if (topic)
|
||||
this->lwt_topic_ = topic;
|
||||
this->lwt_qos_ = qos;
|
||||
if (payload)
|
||||
this->lwt_message_ = payload;
|
||||
this->lwt_retain_ = retain;
|
||||
}
|
||||
void set_server(network::IPAddress ip, uint16_t port) final {
|
||||
this->host_ = ip.str();
|
||||
this->port_ = port;
|
||||
}
|
||||
void set_server(const char *host, uint16_t port) final {
|
||||
this->host_ = host;
|
||||
this->port_ = port;
|
||||
}
|
||||
void set_on_connect(std::function<on_connect_callback_t> &&callback) final {
|
||||
this->on_connect_.add(std::move(callback));
|
||||
}
|
||||
void set_on_disconnect(std::function<on_disconnect_callback_t> &&callback) final {
|
||||
this->on_disconnect_.add(std::move(callback));
|
||||
}
|
||||
void set_on_subscribe(std::function<on_subscribe_callback_t> &&callback) final {
|
||||
this->on_subscribe_.add(std::move(callback));
|
||||
}
|
||||
void set_on_unsubscribe(std::function<on_unsubscribe_callback_t> &&callback) final {
|
||||
this->on_unsubscribe_.add(std::move(callback));
|
||||
}
|
||||
void set_on_message(std::function<on_message_callback_t> &&callback) final {
|
||||
this->on_message_.add(std::move(callback));
|
||||
}
|
||||
void set_on_publish(std::function<on_publish_user_callback_t> &&callback) final {
|
||||
this->on_publish_.add(std::move(callback));
|
||||
}
|
||||
bool connected() const final { return this->is_connected_; }
|
||||
|
||||
void connect() final {
|
||||
if (!is_initalized_) {
|
||||
if (initialize_()) {
|
||||
esp_mqtt_client_start(handler_.get());
|
||||
}
|
||||
}
|
||||
}
|
||||
void disconnect() final {
|
||||
if (is_initalized_)
|
||||
esp_mqtt_client_disconnect(handler_.get());
|
||||
}
|
||||
|
||||
bool subscribe(const char *topic, uint8_t qos) final {
|
||||
return esp_mqtt_client_subscribe(handler_.get(), topic, qos) != -1;
|
||||
}
|
||||
bool unsubscribe(const char *topic) final { return esp_mqtt_client_unsubscribe(handler_.get(), topic) != -1; }
|
||||
|
||||
bool publish(const char *topic, const char *payload, size_t length, uint8_t qos, bool retain) final {
|
||||
#if defined(USE_MQTT_IDF_ENQUEUE)
|
||||
// use the non-blocking version
|
||||
// it can delay sending a couple of seconds but won't block
|
||||
return esp_mqtt_client_enqueue(handler_.get(), topic, payload, length, qos, retain, true) != -1;
|
||||
#else
|
||||
// might block for several seconds, either due to network timeout (10s)
|
||||
// or if publishing payloads longer than internal buffer (due to message fragmentation)
|
||||
return esp_mqtt_client_publish(handler_.get(), topic, payload, length, qos, retain) != -1;
|
||||
#endif
|
||||
}
|
||||
using MQTTBackend::publish;
|
||||
|
||||
void loop() final;
|
||||
|
||||
void set_ca_certificate(const std::string &cert) { ca_certificate_ = cert; }
|
||||
void set_skip_cert_cn_check(bool skip_check) { skip_cert_cn_check_ = skip_check; }
|
||||
|
||||
protected:
|
||||
bool initialize_();
|
||||
void mqtt_event_handler_(const esp_mqtt_event_t &event);
|
||||
static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_t event_id, void *event_data);
|
||||
|
||||
struct MqttClientDeleter {
|
||||
void operator()(esp_mqtt_client *client_handler) { esp_mqtt_client_destroy(client_handler); }
|
||||
};
|
||||
using ClientHandler_ = std::unique_ptr<esp_mqtt_client, MqttClientDeleter>;
|
||||
ClientHandler_ handler_;
|
||||
|
||||
bool is_connected_{false};
|
||||
bool is_initalized_{false};
|
||||
|
||||
esp_mqtt_client_config_t mqtt_cfg_{};
|
||||
|
||||
std::string host_;
|
||||
uint16_t port_;
|
||||
std::string username_;
|
||||
std::string password_;
|
||||
std::string lwt_topic_;
|
||||
std::string lwt_message_;
|
||||
uint8_t lwt_qos_;
|
||||
bool lwt_retain_;
|
||||
std::string client_id_;
|
||||
uint16_t keep_alive_;
|
||||
bool clean_session_;
|
||||
optional<std::string> ca_certificate_;
|
||||
bool skip_cert_cn_check_{false};
|
||||
|
||||
// callbacks
|
||||
CallbackManager<on_connect_callback_t> on_connect_;
|
||||
CallbackManager<on_disconnect_callback_t> on_disconnect_;
|
||||
CallbackManager<on_subscribe_callback_t> on_subscribe_;
|
||||
CallbackManager<on_unsubscribe_callback_t> on_unsubscribe_;
|
||||
CallbackManager<on_message_callback_t> on_message_;
|
||||
CallbackManager<on_publish_user_callback_t> on_publish_;
|
||||
std::queue<esp_mqtt_event_t> mqtt_events_;
|
||||
};
|
||||
|
||||
} // namespace mqtt
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
@@ -27,21 +27,21 @@ MQTTClientComponent::MQTTClientComponent() {
|
||||
// Connection
|
||||
void MQTTClientComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MQTT...");
|
||||
this->mqtt_client_.onMessage([this](char const *topic, char *payload, AsyncMqttClientMessageProperties properties,
|
||||
size_t len, size_t index, size_t total) {
|
||||
if (index == 0)
|
||||
this->payload_buffer_.reserve(total);
|
||||
this->mqtt_backend_.set_on_message(
|
||||
[this](const char *topic, const char *payload, size_t len, size_t index, size_t total) {
|
||||
if (index == 0)
|
||||
this->payload_buffer_.reserve(total);
|
||||
|
||||
// append new payload, may contain incomplete MQTT message
|
||||
this->payload_buffer_.append(payload, len);
|
||||
// append new payload, may contain incomplete MQTT message
|
||||
this->payload_buffer_.append(payload, len);
|
||||
|
||||
// MQTT fully received
|
||||
if (len + index == total) {
|
||||
this->on_message(topic, this->payload_buffer_);
|
||||
this->payload_buffer_.clear();
|
||||
}
|
||||
});
|
||||
this->mqtt_client_.onDisconnect([this](AsyncMqttClientDisconnectReason reason) {
|
||||
// MQTT fully received
|
||||
if (len + index == total) {
|
||||
this->on_message(topic, this->payload_buffer_);
|
||||
this->payload_buffer_.clear();
|
||||
}
|
||||
});
|
||||
this->mqtt_backend_.set_on_disconnect([this](MQTTClientDisconnectReason reason) {
|
||||
this->state_ = MQTT_CLIENT_DISCONNECTED;
|
||||
this->disconnect_reason_ = reason;
|
||||
});
|
||||
@@ -49,8 +49,10 @@ void MQTTClientComponent::setup() {
|
||||
if (this->is_log_message_enabled() && logger::global_logger != nullptr) {
|
||||
logger::global_logger->add_on_log_callback([this](int level, const char *tag, const char *message) {
|
||||
if (level <= this->log_level_ && this->is_connected()) {
|
||||
this->publish(this->log_message_.topic, message, strlen(message), this->log_message_.qos,
|
||||
this->log_message_.retain);
|
||||
this->publish({.topic = this->log_message_.topic,
|
||||
.payload = message,
|
||||
.qos = this->log_message_.qos,
|
||||
.retain = this->log_message_.retain});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -173,9 +175,9 @@ void MQTTClientComponent::start_connect_() {
|
||||
|
||||
ESP_LOGI(TAG, "Connecting to MQTT...");
|
||||
// Force disconnect first
|
||||
this->mqtt_client_.disconnect(true);
|
||||
this->mqtt_backend_.disconnect();
|
||||
|
||||
this->mqtt_client_.setClientId(this->credentials_.client_id.c_str());
|
||||
this->mqtt_backend_.set_client_id(this->credentials_.client_id.c_str());
|
||||
const char *username = nullptr;
|
||||
if (!this->credentials_.username.empty())
|
||||
username = this->credentials_.username.c_str();
|
||||
@@ -183,24 +185,24 @@ void MQTTClientComponent::start_connect_() {
|
||||
if (!this->credentials_.password.empty())
|
||||
password = this->credentials_.password.c_str();
|
||||
|
||||
this->mqtt_client_.setCredentials(username, password);
|
||||
this->mqtt_backend_.set_credentials(username, password);
|
||||
|
||||
this->mqtt_client_.setServer((uint32_t) this->ip_, this->credentials_.port);
|
||||
this->mqtt_backend_.set_server((uint32_t) this->ip_, this->credentials_.port);
|
||||
if (!this->last_will_.topic.empty()) {
|
||||
this->mqtt_client_.setWill(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain,
|
||||
this->last_will_.payload.c_str(), this->last_will_.payload.length());
|
||||
this->mqtt_backend_.set_will(this->last_will_.topic.c_str(), this->last_will_.qos, this->last_will_.retain,
|
||||
this->last_will_.payload.c_str());
|
||||
}
|
||||
|
||||
this->mqtt_client_.connect();
|
||||
this->mqtt_backend_.connect();
|
||||
this->state_ = MQTT_CLIENT_CONNECTING;
|
||||
this->connect_begin_ = millis();
|
||||
}
|
||||
bool MQTTClientComponent::is_connected() {
|
||||
return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_client_.connected();
|
||||
return this->state_ == MQTT_CLIENT_CONNECTED && this->mqtt_backend_.connected();
|
||||
}
|
||||
|
||||
void MQTTClientComponent::check_connected() {
|
||||
if (!this->mqtt_client_.connected()) {
|
||||
if (!this->mqtt_backend_.connected()) {
|
||||
if (millis() - this->connect_begin_ > 60000) {
|
||||
this->state_ = MQTT_CLIENT_DISCONNECTED;
|
||||
this->start_dnslookup_();
|
||||
@@ -222,31 +224,34 @@ void MQTTClientComponent::check_connected() {
|
||||
}
|
||||
|
||||
void MQTTClientComponent::loop() {
|
||||
// Call the backend loop first
|
||||
mqtt_backend_.loop();
|
||||
|
||||
if (this->disconnect_reason_.has_value()) {
|
||||
const LogString *reason_s;
|
||||
switch (*this->disconnect_reason_) {
|
||||
case AsyncMqttClientDisconnectReason::TCP_DISCONNECTED:
|
||||
case MQTTClientDisconnectReason::TCP_DISCONNECTED:
|
||||
reason_s = LOG_STR("TCP disconnected");
|
||||
break;
|
||||
case AsyncMqttClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
|
||||
case MQTTClientDisconnectReason::MQTT_UNACCEPTABLE_PROTOCOL_VERSION:
|
||||
reason_s = LOG_STR("Unacceptable Protocol Version");
|
||||
break;
|
||||
case AsyncMqttClientDisconnectReason::MQTT_IDENTIFIER_REJECTED:
|
||||
case MQTTClientDisconnectReason::MQTT_IDENTIFIER_REJECTED:
|
||||
reason_s = LOG_STR("Identifier Rejected");
|
||||
break;
|
||||
case AsyncMqttClientDisconnectReason::MQTT_SERVER_UNAVAILABLE:
|
||||
case MQTTClientDisconnectReason::MQTT_SERVER_UNAVAILABLE:
|
||||
reason_s = LOG_STR("Server Unavailable");
|
||||
break;
|
||||
case AsyncMqttClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS:
|
||||
case MQTTClientDisconnectReason::MQTT_MALFORMED_CREDENTIALS:
|
||||
reason_s = LOG_STR("Malformed Credentials");
|
||||
break;
|
||||
case AsyncMqttClientDisconnectReason::MQTT_NOT_AUTHORIZED:
|
||||
case MQTTClientDisconnectReason::MQTT_NOT_AUTHORIZED:
|
||||
reason_s = LOG_STR("Not Authorized");
|
||||
break;
|
||||
case AsyncMqttClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE:
|
||||
case MQTTClientDisconnectReason::ESP8266_NOT_ENOUGH_SPACE:
|
||||
reason_s = LOG_STR("Not Enough Space");
|
||||
break;
|
||||
case AsyncMqttClientDisconnectReason::TLS_BAD_FINGERPRINT:
|
||||
case MQTTClientDisconnectReason::TLS_BAD_FINGERPRINT:
|
||||
reason_s = LOG_STR("TLS Bad Fingerprint");
|
||||
break;
|
||||
default:
|
||||
@@ -275,7 +280,7 @@ void MQTTClientComponent::loop() {
|
||||
this->check_connected();
|
||||
break;
|
||||
case MQTT_CLIENT_CONNECTED:
|
||||
if (!this->mqtt_client_.connected()) {
|
||||
if (!this->mqtt_backend_.connected()) {
|
||||
this->state_ = MQTT_CLIENT_DISCONNECTED;
|
||||
ESP_LOGW(TAG, "Lost MQTT Client connection!");
|
||||
this->start_dnslookup_();
|
||||
@@ -302,10 +307,10 @@ bool MQTTClientComponent::subscribe_(const char *topic, uint8_t qos) {
|
||||
if (!this->is_connected())
|
||||
return false;
|
||||
|
||||
uint16_t ret = this->mqtt_client_.subscribe(topic, qos);
|
||||
bool ret = this->mqtt_backend_.subscribe(topic, qos);
|
||||
yield();
|
||||
|
||||
if (ret != 0) {
|
||||
if (ret) {
|
||||
ESP_LOGV(TAG, "subscribe(topic='%s')", topic);
|
||||
} else {
|
||||
delay(5);
|
||||
@@ -360,9 +365,9 @@ void MQTTClientComponent::subscribe_json(const std::string &topic, const mqtt_js
|
||||
}
|
||||
|
||||
void MQTTClientComponent::unsubscribe(const std::string &topic) {
|
||||
uint16_t ret = this->mqtt_client_.unsubscribe(topic.c_str());
|
||||
bool ret = this->mqtt_backend_.unsubscribe(topic.c_str());
|
||||
yield();
|
||||
if (ret != 0) {
|
||||
if (ret) {
|
||||
ESP_LOGV(TAG, "unsubscribe(topic='%s')", topic.c_str());
|
||||
} else {
|
||||
delay(5);
|
||||
@@ -387,34 +392,35 @@ bool MQTTClientComponent::publish(const std::string &topic, const std::string &p
|
||||
|
||||
bool MQTTClientComponent::publish(const std::string &topic, const char *payload, size_t payload_length, uint8_t qos,
|
||||
bool retain) {
|
||||
return publish({.topic = topic, .payload = payload, .qos = qos, .retain = retain});
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish(const MQTTMessage &message) {
|
||||
if (!this->is_connected()) {
|
||||
// critical components will re-transmit their messages
|
||||
return false;
|
||||
}
|
||||
bool logging_topic = topic == this->log_message_.topic;
|
||||
uint16_t ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length);
|
||||
bool logging_topic = this->log_message_.topic == message.topic;
|
||||
bool ret = this->mqtt_backend_.publish(message);
|
||||
delay(0);
|
||||
if (ret == 0 && !logging_topic && this->is_connected()) {
|
||||
if (!ret && !logging_topic && this->is_connected()) {
|
||||
delay(0);
|
||||
ret = this->mqtt_client_.publish(topic.c_str(), qos, retain, payload, payload_length);
|
||||
ret = this->mqtt_backend_.publish(message);
|
||||
delay(0);
|
||||
}
|
||||
|
||||
if (!logging_topic) {
|
||||
if (ret != 0) {
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", topic.c_str(), payload, retain);
|
||||
if (ret) {
|
||||
ESP_LOGV(TAG, "Publish(topic='%s' payload='%s' retain=%d)", message.topic.c_str(), message.payload.c_str(),
|
||||
message.retain);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", topic.c_str(),
|
||||
payload_length); // NOLINT
|
||||
ESP_LOGV(TAG, "Publish failed for topic='%s' (len=%u). will retry later..", message.topic.c_str(),
|
||||
message.payload.length());
|
||||
this->status_momentary_warning("publish", 1000);
|
||||
}
|
||||
}
|
||||
return ret != 0;
|
||||
}
|
||||
|
||||
bool MQTTClientComponent::publish(const MQTTMessage &message) {
|
||||
return this->publish(message.topic, message.payload, message.qos, message.retain);
|
||||
}
|
||||
bool MQTTClientComponent::publish_json(const std::string &topic, const json::json_build_t &f, uint8_t qos,
|
||||
bool retain) {
|
||||
std::string message = json::build_json(f);
|
||||
@@ -499,10 +505,10 @@ bool MQTTClientComponent::is_log_message_enabled() const { return !this->log_mes
|
||||
void MQTTClientComponent::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeout_ = reboot_timeout; }
|
||||
void MQTTClientComponent::register_mqtt_component(MQTTComponent *component) { this->children_.push_back(component); }
|
||||
void MQTTClientComponent::set_log_level(int level) { this->log_level_ = level; }
|
||||
void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_client_.setKeepAlive(keep_alive_s); }
|
||||
void MQTTClientComponent::set_keep_alive(uint16_t keep_alive_s) { this->mqtt_backend_.set_keep_alive(keep_alive_s); }
|
||||
void MQTTClientComponent::set_log_message_template(MQTTMessage &&message) { this->log_message_ = std::move(message); }
|
||||
const MQTTDiscoveryInfo &MQTTClientComponent::get_discovery_info() const { return this->discovery_info_; }
|
||||
void MQTTClientComponent::set_topic_prefix(std::string topic_prefix) { this->topic_prefix_ = std::move(topic_prefix); }
|
||||
void MQTTClientComponent::set_topic_prefix(const std::string &topic_prefix) { this->topic_prefix_ = topic_prefix; }
|
||||
const std::string &MQTTClientComponent::get_topic_prefix() const { return this->topic_prefix_; }
|
||||
void MQTTClientComponent::disable_birth_message() {
|
||||
this->birth_message_.topic = "";
|
||||
@@ -549,7 +555,8 @@ void MQTTClientComponent::set_discovery_info(std::string &&prefix, MQTTDiscovery
|
||||
void MQTTClientComponent::disable_last_will() { this->last_will_.topic = ""; }
|
||||
|
||||
void MQTTClientComponent::disable_discovery() {
|
||||
this->discovery_info_ = MQTTDiscoveryInfo{.prefix = "", .retain = false};
|
||||
this->discovery_info_ = MQTTDiscoveryInfo{
|
||||
.prefix = "", .retain = false, .clean = false, .unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR};
|
||||
}
|
||||
void MQTTClientComponent::on_shutdown() {
|
||||
if (!this->shutdown_message_.topic.empty()) {
|
||||
@@ -557,13 +564,13 @@ void MQTTClientComponent::on_shutdown() {
|
||||
this->publish(this->shutdown_message_);
|
||||
yield();
|
||||
}
|
||||
this->mqtt_client_.disconnect(true);
|
||||
this->mqtt_backend_.disconnect();
|
||||
}
|
||||
|
||||
#if ASYNC_TCP_SSL_ENABLED
|
||||
void MQTTClientComponent::add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint) {
|
||||
this->mqtt_client_.setSecure(true);
|
||||
this->mqtt_client_.addServerFingerprint(fingerprint.data());
|
||||
this->mqtt_backend_.setSecure(true);
|
||||
this->mqtt_backend_.addServerFingerprint(fingerprint.data());
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -9,7 +9,11 @@
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/components/json/json_util.h"
|
||||
#include "esphome/components/network/ip_address.h"
|
||||
#include <AsyncMqttClient.h>
|
||||
#if defined(USE_ESP_IDF)
|
||||
#include "mqtt_backend_idf.h"
|
||||
#elif defined(USE_ARDUINO)
|
||||
#include "mqtt_backend_arduino.h"
|
||||
#endif
|
||||
#include "lwip/ip_addr.h"
|
||||
|
||||
namespace esphome {
|
||||
@@ -22,14 +26,6 @@ namespace mqtt {
|
||||
using mqtt_callback_t = std::function<void(const std::string &, const std::string &)>;
|
||||
using mqtt_json_callback_t = std::function<void(const std::string &, JsonObject)>;
|
||||
|
||||
/// internal struct for MQTT messages.
|
||||
struct MQTTMessage {
|
||||
std::string topic;
|
||||
std::string payload;
|
||||
uint8_t qos; ///< QoS. Only for last will testaments.
|
||||
bool retain;
|
||||
};
|
||||
|
||||
/// internal struct for MQTT subscriptions.
|
||||
struct MQTTSubscription {
|
||||
std::string topic;
|
||||
@@ -139,7 +135,10 @@ class MQTTClientComponent : public Component {
|
||||
*/
|
||||
void add_ssl_fingerprint(const std::array<uint8_t, SHA1_SIZE> &fingerprint);
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
void set_ca_certificate(const char *cert) { this->mqtt_backend_.set_ca_certificate(cert); }
|
||||
void set_skip_cert_cn_check(bool skip_check) { this->mqtt_backend_.set_skip_cert_cn_check(skip_check); }
|
||||
#endif
|
||||
const Availability &get_availability();
|
||||
|
||||
/** Set the topic prefix that will be prepended to all topics together with "/". This will, in most cases,
|
||||
@@ -150,7 +149,7 @@ class MQTTClientComponent : public Component {
|
||||
*
|
||||
* @param topic_prefix The topic prefix. The last "/" is appended automatically.
|
||||
*/
|
||||
void set_topic_prefix(std::string topic_prefix);
|
||||
void set_topic_prefix(const std::string &topic_prefix);
|
||||
/// Get the topic prefix of this device, using default if necessary
|
||||
const std::string &get_topic_prefix() const;
|
||||
|
||||
@@ -277,6 +276,7 @@ class MQTTClientComponent : public Component {
|
||||
.prefix = "homeassistant",
|
||||
.retain = true,
|
||||
.clean = false,
|
||||
.unique_id_generator = MQTT_LEGACY_UNIQUE_ID_GENERATOR,
|
||||
};
|
||||
std::string topic_prefix_{};
|
||||
MQTTMessage log_message_;
|
||||
@@ -284,7 +284,12 @@ class MQTTClientComponent : public Component {
|
||||
int log_level_{ESPHOME_LOG_LEVEL};
|
||||
|
||||
std::vector<MQTTSubscription> subscriptions_;
|
||||
AsyncMqttClient mqtt_client_;
|
||||
#if defined(USE_ESP_IDF)
|
||||
MQTTBackendIDF mqtt_backend_;
|
||||
#elif defined(USE_ARDUINO)
|
||||
MQTTBackendArduino mqtt_backend_;
|
||||
#endif
|
||||
|
||||
MQTTClientState state_{MQTT_CLIENT_DISCONNECTED};
|
||||
network::IPAddress ip_;
|
||||
bool dns_resolved_{false};
|
||||
@@ -293,7 +298,7 @@ class MQTTClientComponent : public Component {
|
||||
uint32_t reboot_timeout_{300000};
|
||||
uint32_t connect_begin_;
|
||||
uint32_t last_connected_{0};
|
||||
optional<AsyncMqttClientDisconnectReason> disconnect_reason_{};
|
||||
optional<MQTTClientDisconnectReason> disconnect_reason_{};
|
||||
};
|
||||
|
||||
extern MQTTClientComponent *global_mqtt_client; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
from esphome import automation
|
||||
import esphome.codegen as cg
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
@@ -5,3 +6,7 @@ CODEOWNERS = ["@jesserockz"]
|
||||
nfc_ns = cg.esphome_ns.namespace("nfc")
|
||||
|
||||
NfcTag = nfc_ns.class_("NfcTag")
|
||||
|
||||
NfcOnTagTrigger = nfc_ns.class_(
|
||||
"NfcOnTagTrigger", automation.Trigger.template(cg.std_string, NfcTag)
|
||||
)
|
||||
|
||||
9
esphome/components/nfc/automation.cpp
Normal file
9
esphome/components/nfc/automation.cpp
Normal file
@@ -0,0 +1,9 @@
|
||||
#include "automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
void NfcOnTagTrigger::process(const std::unique_ptr<NfcTag> &tag) { this->trigger(format_uid(tag->get_uid()), *tag); }
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
||||
17
esphome/components/nfc/automation.h
Normal file
17
esphome/components/nfc/automation.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include "esphome/core/automation.h"
|
||||
|
||||
#include "nfc.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace nfc {
|
||||
|
||||
class NfcOnTagTrigger : public Trigger<std::string, NfcTag> {
|
||||
public:
|
||||
void process(const std::unique_ptr<NfcTag> &tag);
|
||||
};
|
||||
|
||||
} // namespace nfc
|
||||
} // namespace esphome
|
||||
@@ -63,8 +63,8 @@ NUMBER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).e
|
||||
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
|
||||
cv.Optional(CONF_ABOVE): cv.float_,
|
||||
cv.Optional(CONF_BELOW): cv.float_,
|
||||
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
|
||||
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
|
||||
},
|
||||
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
|
||||
),
|
||||
|
||||
@@ -473,6 +473,8 @@ bool OTAComponent::should_enter_safe_mode(uint8_t num_attempts, uint32_t enable_
|
||||
App.reboot();
|
||||
});
|
||||
|
||||
// Delay here to allow power to stabilise before Wi-Fi/Ethernet is initialised.
|
||||
delay(300); // NOLINT
|
||||
App.setup();
|
||||
|
||||
ESP_LOGI(TAG, "Waiting for OTA attempt.");
|
||||
|
||||
@@ -14,9 +14,6 @@ CONF_ON_FINISHED_WRITE = "on_finished_write"
|
||||
pn532_ns = cg.esphome_ns.namespace("pn532")
|
||||
PN532 = pn532_ns.class_("PN532", cg.PollingComponent)
|
||||
|
||||
PN532OnTagTrigger = pn532_ns.class_(
|
||||
"PN532OnTagTrigger", automation.Trigger.template(cg.std_string, nfc.NfcTag)
|
||||
)
|
||||
PN532OnFinishedWriteTrigger = pn532_ns.class_(
|
||||
"PN532OnFinishedWriteTrigger", automation.Trigger.template()
|
||||
)
|
||||
@@ -30,7 +27,7 @@ PN532_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(): cv.declare_id(PN532),
|
||||
cv.Optional(CONF_ON_TAG): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger),
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
|
||||
@@ -42,7 +39,7 @@ PN532_SCHEMA = cv.Schema(
|
||||
),
|
||||
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PN532OnTagTrigger),
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
|
||||
}
|
||||
),
|
||||
}
|
||||
|
||||
@@ -144,9 +144,9 @@ void PN532::loop() {
|
||||
}
|
||||
|
||||
if (nfcid.size() == this->current_uid_.size()) {
|
||||
bool same_uid = false;
|
||||
bool same_uid = true;
|
||||
for (size_t i = 0; i < nfcid.size(); i++)
|
||||
same_uid |= nfcid[i] == this->current_uid_[i];
|
||||
same_uid &= nfcid[i] == this->current_uid_[i];
|
||||
if (same_uid)
|
||||
return;
|
||||
}
|
||||
@@ -376,9 +376,6 @@ bool PN532BinarySensor::process(std::vector<uint8_t> &data) {
|
||||
this->found_ = true;
|
||||
return true;
|
||||
}
|
||||
void PN532OnTagTrigger::process(const std::unique_ptr<nfc::NfcTag> &tag) {
|
||||
this->trigger(nfc::format_uid(tag->get_uid()), *tag);
|
||||
}
|
||||
|
||||
} // namespace pn532
|
||||
} // namespace esphome
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#include "esphome/components/nfc/nfc_tag.h"
|
||||
#include "esphome/components/nfc/nfc.h"
|
||||
#include "esphome/components/nfc/automation.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace pn532 {
|
||||
@@ -16,7 +17,6 @@ static const uint8_t PN532_COMMAND_INDATAEXCHANGE = 0x40;
|
||||
static const uint8_t PN532_COMMAND_INLISTPASSIVETARGET = 0x4A;
|
||||
|
||||
class PN532BinarySensor;
|
||||
class PN532OnTagTrigger;
|
||||
|
||||
class PN532 : public PollingComponent {
|
||||
public:
|
||||
@@ -30,8 +30,8 @@ class PN532 : public PollingComponent {
|
||||
void loop() override;
|
||||
|
||||
void register_tag(PN532BinarySensor *tag) { this->binary_sensors_.push_back(tag); }
|
||||
void register_ontag_trigger(PN532OnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
void register_ontagremoved_trigger(PN532OnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
|
||||
void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); }
|
||||
void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); }
|
||||
|
||||
void add_on_finished_write_callback(std::function<void()> callback) {
|
||||
this->on_finished_write_callback_.add(std::move(callback));
|
||||
@@ -79,8 +79,8 @@ class PN532 : public PollingComponent {
|
||||
|
||||
bool requested_read_{false};
|
||||
std::vector<PN532BinarySensor *> binary_sensors_;
|
||||
std::vector<PN532OnTagTrigger *> triggers_ontag_;
|
||||
std::vector<PN532OnTagTrigger *> triggers_ontagremoved_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontag_;
|
||||
std::vector<nfc::NfcOnTagTrigger *> triggers_ontagremoved_;
|
||||
std::vector<uint8_t> current_uid_;
|
||||
nfc::NdefMessage *next_task_message_to_write_;
|
||||
enum NfcTask {
|
||||
@@ -115,11 +115,6 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
|
||||
bool found_{false};
|
||||
};
|
||||
|
||||
class PN532OnTagTrigger : public Trigger<std::string, nfc::NfcTag> {
|
||||
public:
|
||||
void process(const std::unique_ptr<nfc::NfcTag> &tag);
|
||||
};
|
||||
|
||||
class PN532OnFinishedWriteTrigger : public Trigger<> {
|
||||
public:
|
||||
explicit PN532OnFinishedWriteTrigger(PN532 *parent) {
|
||||
|
||||
1
esphome/components/qmp6988/__init__.py
Normal file
1
esphome/components/qmp6988/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@andrewpc"]
|
||||
397
esphome/components/qmp6988/qmp6988.cpp
Normal file
397
esphome/components/qmp6988/qmp6988.cpp
Normal file
@@ -0,0 +1,397 @@
|
||||
#include "qmp6988.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome {
|
||||
namespace qmp6988 {
|
||||
|
||||
static const uint8_t QMP6988_CHIP_ID = 0x5C;
|
||||
|
||||
static const uint8_t QMP6988_CHIP_ID_REG = 0xD1; /* Chip ID confirmation Register */
|
||||
static const uint8_t QMP6988_RESET_REG = 0xE0; /* Device reset register */
|
||||
static const uint8_t QMP6988_DEVICE_STAT_REG = 0xF3; /* Device state register */
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG = 0xF4; /* Measurement Condition Control Register */
|
||||
/* data */
|
||||
static const uint8_t QMP6988_PRESSURE_MSB_REG = 0xF7; /* Pressure MSB Register */
|
||||
static const uint8_t QMP6988_TEMPERATURE_MSB_REG = 0xFA; /* Temperature MSB Reg */
|
||||
|
||||
/* compensation calculation */
|
||||
static const uint8_t QMP6988_CALIBRATION_DATA_START = 0xA0; /* QMP6988 compensation coefficients */
|
||||
static const uint8_t QMP6988_CALIBRATION_DATA_LENGTH = 25;
|
||||
|
||||
static const uint8_t SHIFT_RIGHT_4_POSITION = 4;
|
||||
static const uint8_t SHIFT_LEFT_2_POSITION = 2;
|
||||
static const uint8_t SHIFT_LEFT_4_POSITION = 4;
|
||||
static const uint8_t SHIFT_LEFT_5_POSITION = 5;
|
||||
static const uint8_t SHIFT_LEFT_8_POSITION = 8;
|
||||
static const uint8_t SHIFT_LEFT_12_POSITION = 12;
|
||||
static const uint8_t SHIFT_LEFT_16_POSITION = 16;
|
||||
|
||||
/* power mode */
|
||||
static const uint8_t QMP6988_SLEEP_MODE = 0x00;
|
||||
static const uint8_t QMP6988_FORCED_MODE = 0x01;
|
||||
static const uint8_t QMP6988_NORMAL_MODE = 0x03;
|
||||
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_MODE_POS = 0;
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_MODE_MSK = 0x03;
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_MODE_LEN = 2;
|
||||
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_POS = 5;
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_MSK = 0xE0;
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_OSRST_LEN = 3;
|
||||
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_POS = 2;
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_MSK = 0x1C;
|
||||
static const uint8_t QMP6988_CTRLMEAS_REG_OSRSP_LEN = 3;
|
||||
|
||||
static const uint8_t QMP6988_CONFIG_REG = 0xF1; /*IIR filter co-efficient setting Register*/
|
||||
static const uint8_t QMP6988_CONFIG_REG_FILTER_POS = 0;
|
||||
static const uint8_t QMP6988_CONFIG_REG_FILTER_MSK = 0x07;
|
||||
static const uint8_t QMP6988_CONFIG_REG_FILTER_LEN = 3;
|
||||
|
||||
static const uint32_t SUBTRACTOR = 8388608;
|
||||
|
||||
static const char *const TAG = "qmp6988";
|
||||
|
||||
static const char *oversampling_to_str(QMP6988Oversampling oversampling) {
|
||||
switch (oversampling) {
|
||||
case QMP6988_OVERSAMPLING_SKIPPED:
|
||||
return "None";
|
||||
case QMP6988_OVERSAMPLING_1X:
|
||||
return "1x";
|
||||
case QMP6988_OVERSAMPLING_2X:
|
||||
return "2x";
|
||||
case QMP6988_OVERSAMPLING_4X:
|
||||
return "4x";
|
||||
case QMP6988_OVERSAMPLING_8X:
|
||||
return "8x";
|
||||
case QMP6988_OVERSAMPLING_16X:
|
||||
return "16x";
|
||||
case QMP6988_OVERSAMPLING_32X:
|
||||
return "32x";
|
||||
case QMP6988_OVERSAMPLING_64X:
|
||||
return "64x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *iir_filter_to_str(QMP6988IIRFilter filter) {
|
||||
switch (filter) {
|
||||
case QMP6988_IIR_FILTER_OFF:
|
||||
return "OFF";
|
||||
case QMP6988_IIR_FILTER_2X:
|
||||
return "2x";
|
||||
case QMP6988_IIR_FILTER_4X:
|
||||
return "4x";
|
||||
case QMP6988_IIR_FILTER_8X:
|
||||
return "8x";
|
||||
case QMP6988_IIR_FILTER_16X:
|
||||
return "16x";
|
||||
case QMP6988_IIR_FILTER_32X:
|
||||
return "32x";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
|
||||
bool QMP6988Component::device_check_() {
|
||||
uint8_t ret = 0;
|
||||
|
||||
ret = this->read_register(QMP6988_CHIP_ID_REG, &(qmp6988_data_.chip_id), 1);
|
||||
if (ret != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "%s: read chip ID (0xD1) failed", __func__);
|
||||
}
|
||||
ESP_LOGD(TAG, "qmp6988 read chip id = 0x%x", qmp6988_data_.chip_id);
|
||||
|
||||
return qmp6988_data_.chip_id == QMP6988_CHIP_ID;
|
||||
}
|
||||
|
||||
bool QMP6988Component::get_calibration_data_() {
|
||||
uint8_t status = 0;
|
||||
// BITFIELDS temp_COE;
|
||||
uint8_t a_data_uint8_tr[QMP6988_CALIBRATION_DATA_LENGTH] = {0};
|
||||
int len;
|
||||
|
||||
for (len = 0; len < QMP6988_CALIBRATION_DATA_LENGTH; len += 1) {
|
||||
status = this->read_register(QMP6988_CALIBRATION_DATA_START + len, &a_data_uint8_tr[len], 1);
|
||||
if (status != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "qmp6988 read calibration data (0xA0) error!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qmp6988_data_.qmp6988_cali.COE_a0 =
|
||||
(QMP6988_S32_t)(((a_data_uint8_tr[18] << SHIFT_LEFT_12_POSITION) |
|
||||
(a_data_uint8_tr[19] << SHIFT_LEFT_4_POSITION) | (a_data_uint8_tr[24] & 0x0f))
|
||||
<< 12);
|
||||
qmp6988_data_.qmp6988_cali.COE_a0 = qmp6988_data_.qmp6988_cali.COE_a0 >> 12;
|
||||
|
||||
qmp6988_data_.qmp6988_cali.COE_a1 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[20]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[21]);
|
||||
qmp6988_data_.qmp6988_cali.COE_a2 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[22]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[23]);
|
||||
|
||||
qmp6988_data_.qmp6988_cali.COE_b00 =
|
||||
(QMP6988_S32_t)(((a_data_uint8_tr[0] << SHIFT_LEFT_12_POSITION) | (a_data_uint8_tr[1] << SHIFT_LEFT_4_POSITION) |
|
||||
((a_data_uint8_tr[24] & 0xf0) >> SHIFT_RIGHT_4_POSITION))
|
||||
<< 12);
|
||||
qmp6988_data_.qmp6988_cali.COE_b00 = qmp6988_data_.qmp6988_cali.COE_b00 >> 12;
|
||||
|
||||
qmp6988_data_.qmp6988_cali.COE_bt1 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[2]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[3]);
|
||||
qmp6988_data_.qmp6988_cali.COE_bt2 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[4]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[5]);
|
||||
qmp6988_data_.qmp6988_cali.COE_bp1 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[6]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[7]);
|
||||
qmp6988_data_.qmp6988_cali.COE_b11 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[8]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[9]);
|
||||
qmp6988_data_.qmp6988_cali.COE_bp2 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[10]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[11]);
|
||||
qmp6988_data_.qmp6988_cali.COE_b12 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[12]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[13]);
|
||||
qmp6988_data_.qmp6988_cali.COE_b21 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[14]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[15]);
|
||||
qmp6988_data_.qmp6988_cali.COE_bp3 =
|
||||
(QMP6988_S16_t)(((a_data_uint8_tr[16]) << SHIFT_LEFT_8_POSITION) | a_data_uint8_tr[17]);
|
||||
|
||||
ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n");
|
||||
ESP_LOGV(TAG, "COE_a0[%d] COE_a1[%d] COE_a2[%d] COE_b00[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_a0,
|
||||
qmp6988_data_.qmp6988_cali.COE_a1, qmp6988_data_.qmp6988_cali.COE_a2, qmp6988_data_.qmp6988_cali.COE_b00);
|
||||
ESP_LOGV(TAG, "COE_bt1[%d] COE_bt2[%d] COE_bp1[%d] COE_b11[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bt1,
|
||||
qmp6988_data_.qmp6988_cali.COE_bt2, qmp6988_data_.qmp6988_cali.COE_bp1, qmp6988_data_.qmp6988_cali.COE_b11);
|
||||
ESP_LOGV(TAG, "COE_bp2[%d] COE_b12[%d] COE_b21[%d] COE_bp3[%d]\r\n", qmp6988_data_.qmp6988_cali.COE_bp2,
|
||||
qmp6988_data_.qmp6988_cali.COE_b12, qmp6988_data_.qmp6988_cali.COE_b21, qmp6988_data_.qmp6988_cali.COE_bp3);
|
||||
ESP_LOGV(TAG, "<-----------calibration data-------------->\r\n");
|
||||
|
||||
qmp6988_data_.ik.a0 = qmp6988_data_.qmp6988_cali.COE_a0; // 20Q4
|
||||
qmp6988_data_.ik.b00 = qmp6988_data_.qmp6988_cali.COE_b00; // 20Q4
|
||||
|
||||
qmp6988_data_.ik.a1 = 3608L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a1 - 1731677965L; // 31Q23
|
||||
qmp6988_data_.ik.a2 = 16889L * (QMP6988_S32_t) qmp6988_data_.qmp6988_cali.COE_a2 - 87619360L; // 30Q47
|
||||
|
||||
qmp6988_data_.ik.bt1 = 2982L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt1 + 107370906L; // 28Q15
|
||||
qmp6988_data_.ik.bt2 = 329854L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bt2 + 108083093L; // 34Q38
|
||||
qmp6988_data_.ik.bp1 = 19923L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp1 + 1133836764L; // 31Q20
|
||||
qmp6988_data_.ik.b11 = 2406L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b11 + 118215883L; // 28Q34
|
||||
qmp6988_data_.ik.bp2 = 3079L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp2 - 181579595L; // 29Q43
|
||||
qmp6988_data_.ik.b12 = 6846L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b12 + 85590281L; // 29Q53
|
||||
qmp6988_data_.ik.b21 = 13836L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_b21 + 79333336L; // 29Q60
|
||||
qmp6988_data_.ik.bp3 = 2915L * (QMP6988_S64_t) qmp6988_data_.qmp6988_cali.COE_bp3 + 157155561L; // 28Q65
|
||||
ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n");
|
||||
ESP_LOGV(TAG, "a0[%d] a1[%d] a2[%d] b00[%d]\r\n", qmp6988_data_.ik.a0, qmp6988_data_.ik.a1, qmp6988_data_.ik.a2,
|
||||
qmp6988_data_.ik.b00);
|
||||
ESP_LOGV(TAG, "bt1[%lld] bt2[%lld] bp1[%lld] b11[%lld]\r\n", qmp6988_data_.ik.bt1, qmp6988_data_.ik.bt2,
|
||||
qmp6988_data_.ik.bp1, qmp6988_data_.ik.b11);
|
||||
ESP_LOGV(TAG, "bp2[%lld] b12[%lld] b21[%lld] bp3[%lld]\r\n", qmp6988_data_.ik.bp2, qmp6988_data_.ik.b12,
|
||||
qmp6988_data_.ik.b21, qmp6988_data_.ik.bp3);
|
||||
ESP_LOGV(TAG, "<----------- int calibration data -------------->\r\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
QMP6988_S16_t QMP6988Component::get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt) {
|
||||
QMP6988_S16_t ret;
|
||||
QMP6988_S64_t wk1, wk2;
|
||||
|
||||
// wk1: 60Q4 // bit size
|
||||
wk1 = ((QMP6988_S64_t) ik->a1 * (QMP6988_S64_t) dt); // 31Q23+24-1=54 (54Q23)
|
||||
wk2 = ((QMP6988_S64_t) ik->a2 * (QMP6988_S64_t) dt) >> 14; // 30Q47+24-1=53 (39Q33)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) dt) >> 10; // 39Q33+24-1=62 (52Q23)
|
||||
wk2 = ((wk1 + wk2) / 32767) >> 19; // 54,52->55Q23 (20Q04)
|
||||
ret = (QMP6988_S16_t)((ik->a0 + wk2) >> 4); // 21Q4 -> 17Q0
|
||||
return ret;
|
||||
}
|
||||
|
||||
QMP6988_S32_t QMP6988Component::get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx) {
|
||||
QMP6988_S32_t ret;
|
||||
QMP6988_S64_t wk1, wk2, wk3;
|
||||
|
||||
// wk1 = 48Q16 // bit size
|
||||
wk1 = ((QMP6988_S64_t) ik->bt1 * (QMP6988_S64_t) tx); // 28Q15+16-1=43 (43Q15)
|
||||
wk2 = ((QMP6988_S64_t) ik->bp1 * (QMP6988_S64_t) dp) >> 5; // 31Q20+24-1=54 (49Q15)
|
||||
wk1 += wk2; // 43,49->50Q15
|
||||
wk2 = ((QMP6988_S64_t) ik->bt2 * (QMP6988_S64_t) tx) >> 1; // 34Q38+16-1=49 (48Q37)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) tx) >> 8; // 48Q37+16-1=63 (55Q29)
|
||||
wk3 = wk2; // 55Q29
|
||||
wk2 = ((QMP6988_S64_t) ik->b11 * (QMP6988_S64_t) tx) >> 4; // 28Q34+16-1=43 (39Q30)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29)
|
||||
wk3 += wk2; // 55,61->62Q29
|
||||
wk2 = ((QMP6988_S64_t) ik->bp2 * (QMP6988_S64_t) dp) >> 13; // 29Q43+24-1=52 (39Q30)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q30+24-1=62 (61Q29)
|
||||
wk3 += wk2; // 62,61->63Q29
|
||||
wk1 += wk3 >> 14; // Q29 >> 14 -> Q15
|
||||
wk2 = ((QMP6988_S64_t) ik->b12 * (QMP6988_S64_t) tx); // 29Q53+16-1=45 (45Q53)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) tx) >> 22; // 45Q53+16-1=61 (39Q31)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q31+24-1=62 (61Q30)
|
||||
wk3 = wk2; // 61Q30
|
||||
wk2 = ((QMP6988_S64_t) ik->b21 * (QMP6988_S64_t) tx) >> 6; // 29Q60+16-1=45 (39Q54)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23; // 39Q54+24-1=62 (39Q31)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 1; // 39Q31+24-1=62 (61Q20)
|
||||
wk3 += wk2; // 61,61->62Q30
|
||||
wk2 = ((QMP6988_S64_t) ik->bp3 * (QMP6988_S64_t) dp) >> 12; // 28Q65+24-1=51 (39Q53)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) dp) >> 23; // 39Q53+24-1=62 (39Q30)
|
||||
wk2 = (wk2 * (QMP6988_S64_t) dp); // 39Q30+24-1=62 (62Q30)
|
||||
wk3 += wk2; // 62,62->63Q30
|
||||
wk1 += wk3 >> 15; // Q30 >> 15 = Q15
|
||||
wk1 /= 32767L;
|
||||
wk1 >>= 11; // Q15 >> 7 = Q4
|
||||
wk1 += ik->b00; // Q4 + 20Q4
|
||||
// wk1 >>= 4; // 28Q4 -> 24Q0
|
||||
ret = (QMP6988_S32_t) wk1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void QMP6988Component::software_reset_() {
|
||||
uint8_t ret = 0;
|
||||
|
||||
ret = this->write_byte(QMP6988_RESET_REG, 0xe6);
|
||||
if (ret != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Software Reset (0xe6) failed");
|
||||
}
|
||||
delay(10);
|
||||
|
||||
this->write_byte(QMP6988_RESET_REG, 0x00);
|
||||
}
|
||||
|
||||
void QMP6988Component::set_power_mode_(uint8_t power_mode) {
|
||||
uint8_t data;
|
||||
|
||||
ESP_LOGD(TAG, "Setting Power mode to: %d", power_mode);
|
||||
|
||||
qmp6988_data_.power_mode = power_mode;
|
||||
this->read_register(QMP6988_CTRLMEAS_REG, &data, 1);
|
||||
data = data & 0xfc;
|
||||
if (power_mode == QMP6988_SLEEP_MODE) {
|
||||
data |= 0x00;
|
||||
} else if (power_mode == QMP6988_FORCED_MODE) {
|
||||
data |= 0x01;
|
||||
} else if (power_mode == QMP6988_NORMAL_MODE) {
|
||||
data |= 0x03;
|
||||
}
|
||||
this->write_byte(QMP6988_CTRLMEAS_REG, data);
|
||||
|
||||
ESP_LOGD(TAG, "Set Power mode 0xf4=0x%x \r\n", data);
|
||||
|
||||
delay(10);
|
||||
}
|
||||
|
||||
void QMP6988Component::write_filter_(unsigned char filter) {
|
||||
uint8_t data;
|
||||
|
||||
data = (filter & 0x03);
|
||||
this->write_byte(QMP6988_CONFIG_REG, data);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
void QMP6988Component::write_oversampling_pressure_(unsigned char oversampling_p) {
|
||||
uint8_t data;
|
||||
|
||||
this->read_register(QMP6988_CTRLMEAS_REG, &data, 1);
|
||||
data &= 0xe3;
|
||||
data |= (oversampling_p << 2);
|
||||
this->write_byte(QMP6988_CTRLMEAS_REG, data);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
void QMP6988Component::write_oversampling_temperature_(unsigned char oversampling_t) {
|
||||
uint8_t data;
|
||||
|
||||
this->read_register(QMP6988_CTRLMEAS_REG, &data, 1);
|
||||
data &= 0x1f;
|
||||
data |= (oversampling_t << 5);
|
||||
this->write_byte(QMP6988_CTRLMEAS_REG, data);
|
||||
delay(10);
|
||||
}
|
||||
|
||||
void QMP6988Component::set_temperature_oversampling(QMP6988Oversampling oversampling_t) {
|
||||
this->temperature_oversampling_ = oversampling_t;
|
||||
}
|
||||
|
||||
void QMP6988Component::set_pressure_oversampling(QMP6988Oversampling oversampling_p) {
|
||||
this->pressure_oversampling_ = oversampling_p;
|
||||
}
|
||||
|
||||
void QMP6988Component::set_iir_filter(QMP6988IIRFilter iirfilter) { this->iir_filter_ = iirfilter; }
|
||||
|
||||
void QMP6988Component::calculate_altitude_(float pressure, float temp) {
|
||||
float altitude;
|
||||
altitude = (pow((101325 / pressure), 1 / 5.257) - 1) * (temp + 273.15) / 0.0065;
|
||||
this->qmp6988_data_.altitude = altitude;
|
||||
}
|
||||
|
||||
void QMP6988Component::calculate_pressure_() {
|
||||
uint8_t err = 0;
|
||||
QMP6988_U32_t p_read, t_read;
|
||||
QMP6988_S32_t p_raw, t_raw;
|
||||
uint8_t a_data_uint8_tr[6] = {0};
|
||||
QMP6988_S32_t t_int, p_int;
|
||||
this->qmp6988_data_.temperature = 0;
|
||||
this->qmp6988_data_.pressure = 0;
|
||||
|
||||
err = this->read_register(QMP6988_PRESSURE_MSB_REG, a_data_uint8_tr, 6);
|
||||
if (err != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Error reading raw pressure/temp values");
|
||||
return;
|
||||
}
|
||||
p_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[0])) << SHIFT_LEFT_16_POSITION) |
|
||||
(((QMP6988_U16_t)(a_data_uint8_tr[1])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[2]));
|
||||
p_raw = (QMP6988_S32_t)(p_read - SUBTRACTOR);
|
||||
|
||||
t_read = (QMP6988_U32_t)((((QMP6988_U32_t)(a_data_uint8_tr[3])) << SHIFT_LEFT_16_POSITION) |
|
||||
(((QMP6988_U16_t)(a_data_uint8_tr[4])) << SHIFT_LEFT_8_POSITION) | (a_data_uint8_tr[5]));
|
||||
t_raw = (QMP6988_S32_t)(t_read - SUBTRACTOR);
|
||||
|
||||
t_int = this->get_compensated_temperature_(&(qmp6988_data_.ik), t_raw);
|
||||
p_int = this->get_compensated_pressure_(&(qmp6988_data_.ik), p_raw, t_int);
|
||||
|
||||
this->qmp6988_data_.temperature = (float) t_int / 256.0f;
|
||||
this->qmp6988_data_.pressure = (float) p_int / 16.0f;
|
||||
}
|
||||
|
||||
void QMP6988Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up QMP6988");
|
||||
|
||||
bool ret;
|
||||
ret = this->device_check_();
|
||||
if (!ret) {
|
||||
ESP_LOGCONFIG(TAG, "Setup failed - device not found");
|
||||
}
|
||||
|
||||
this->software_reset_();
|
||||
this->get_calibration_data_();
|
||||
this->set_power_mode_(QMP6988_NORMAL_MODE);
|
||||
this->write_filter_(iir_filter_);
|
||||
this->write_oversampling_pressure_(this->pressure_oversampling_);
|
||||
this->write_oversampling_temperature_(this->temperature_oversampling_);
|
||||
}
|
||||
|
||||
void QMP6988Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "QMP6988:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with QMP6988 failed!");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
|
||||
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Temperature Oversampling: %s", oversampling_to_str(this->temperature_oversampling_));
|
||||
LOG_SENSOR(" ", "Pressure", this->pressure_sensor_);
|
||||
ESP_LOGCONFIG(TAG, " Pressure Oversampling: %s", oversampling_to_str(this->pressure_oversampling_));
|
||||
ESP_LOGCONFIG(TAG, " IIR Filter: %s", iir_filter_to_str(this->iir_filter_));
|
||||
}
|
||||
|
||||
float QMP6988Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void QMP6988Component::update() {
|
||||
this->calculate_pressure_();
|
||||
float pressurehectopascals = this->qmp6988_data_.pressure / 100;
|
||||
float temperature = this->qmp6988_data_.temperature;
|
||||
|
||||
ESP_LOGD(TAG, "Temperature=%.2f°C, Pressure=%.2fhPa", temperature, pressurehectopascals);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->pressure_sensor_ != nullptr)
|
||||
this->pressure_sensor_->publish_state(pressurehectopascals);
|
||||
}
|
||||
|
||||
} // namespace qmp6988
|
||||
} // namespace esphome
|
||||
116
esphome/components/qmp6988/qmp6988.h
Normal file
116
esphome/components/qmp6988/qmp6988.h
Normal file
@@ -0,0 +1,116 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace qmp6988 {
|
||||
|
||||
#define QMP6988_U16_t unsigned short
|
||||
#define QMP6988_S16_t short
|
||||
#define QMP6988_U32_t unsigned int
|
||||
#define QMP6988_S32_t int
|
||||
#define QMP6988_U64_t unsigned long long
|
||||
#define QMP6988_S64_t long long
|
||||
|
||||
/* oversampling */
|
||||
enum QMP6988Oversampling {
|
||||
QMP6988_OVERSAMPLING_SKIPPED = 0x00,
|
||||
QMP6988_OVERSAMPLING_1X = 0x01,
|
||||
QMP6988_OVERSAMPLING_2X = 0x02,
|
||||
QMP6988_OVERSAMPLING_4X = 0x03,
|
||||
QMP6988_OVERSAMPLING_8X = 0x04,
|
||||
QMP6988_OVERSAMPLING_16X = 0x05,
|
||||
QMP6988_OVERSAMPLING_32X = 0x06,
|
||||
QMP6988_OVERSAMPLING_64X = 0x07,
|
||||
};
|
||||
|
||||
/* filter */
|
||||
enum QMP6988IIRFilter {
|
||||
QMP6988_IIR_FILTER_OFF = 0x00,
|
||||
QMP6988_IIR_FILTER_2X = 0x01,
|
||||
QMP6988_IIR_FILTER_4X = 0x02,
|
||||
QMP6988_IIR_FILTER_8X = 0x03,
|
||||
QMP6988_IIR_FILTER_16X = 0x04,
|
||||
QMP6988_IIR_FILTER_32X = 0x05,
|
||||
};
|
||||
|
||||
using qmp6988_cali_data_t = struct Qmp6988CaliData {
|
||||
QMP6988_S32_t COE_a0;
|
||||
QMP6988_S16_t COE_a1;
|
||||
QMP6988_S16_t COE_a2;
|
||||
QMP6988_S32_t COE_b00;
|
||||
QMP6988_S16_t COE_bt1;
|
||||
QMP6988_S16_t COE_bt2;
|
||||
QMP6988_S16_t COE_bp1;
|
||||
QMP6988_S16_t COE_b11;
|
||||
QMP6988_S16_t COE_bp2;
|
||||
QMP6988_S16_t COE_b12;
|
||||
QMP6988_S16_t COE_b21;
|
||||
QMP6988_S16_t COE_bp3;
|
||||
};
|
||||
|
||||
using qmp6988_fk_data_t = struct Qmp6988FkData {
|
||||
float a0, b00;
|
||||
float a1, a2, bt1, bt2, bp1, b11, bp2, b12, b21, bp3;
|
||||
};
|
||||
|
||||
using qmp6988_ik_data_t = struct Qmp6988IkData {
|
||||
QMP6988_S32_t a0, b00;
|
||||
QMP6988_S32_t a1, a2;
|
||||
QMP6988_S64_t bt1, bt2, bp1, b11, bp2, b12, b21, bp3;
|
||||
};
|
||||
|
||||
using qmp6988_data_t = struct Qmp6988Data {
|
||||
uint8_t chip_id;
|
||||
uint8_t power_mode;
|
||||
float temperature;
|
||||
float pressure;
|
||||
float altitude;
|
||||
qmp6988_cali_data_t qmp6988_cali;
|
||||
qmp6988_ik_data_t ik;
|
||||
};
|
||||
|
||||
class QMP6988Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_pressure_sensor(sensor::Sensor *pressure_sensor) { pressure_sensor_ = pressure_sensor; }
|
||||
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
void set_iir_filter(QMP6988IIRFilter iirfilter);
|
||||
void set_temperature_oversampling(QMP6988Oversampling oversampling_t);
|
||||
void set_pressure_oversampling(QMP6988Oversampling oversampling_p);
|
||||
|
||||
protected:
|
||||
qmp6988_data_t qmp6988_data_;
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *pressure_sensor_;
|
||||
|
||||
QMP6988Oversampling temperature_oversampling_{QMP6988_OVERSAMPLING_16X};
|
||||
QMP6988Oversampling pressure_oversampling_{QMP6988_OVERSAMPLING_16X};
|
||||
QMP6988IIRFilter iir_filter_{QMP6988_IIR_FILTER_OFF};
|
||||
|
||||
void software_reset_();
|
||||
bool get_calibration_data_();
|
||||
bool device_check_();
|
||||
void set_power_mode_(uint8_t power_mode);
|
||||
void write_oversampling_temperature_(unsigned char oversampling_t);
|
||||
void write_oversampling_pressure_(unsigned char oversampling_p);
|
||||
void write_filter_(unsigned char filter);
|
||||
void calculate_pressure_();
|
||||
void calculate_altitude_(float pressure, float temp);
|
||||
|
||||
QMP6988_S32_t get_compensated_pressure_(qmp6988_ik_data_t *ik, QMP6988_S32_t dp, QMP6988_S16_t tx);
|
||||
QMP6988_S16_t get_compensated_temperature_(qmp6988_ik_data_t *ik, QMP6988_S32_t dt);
|
||||
};
|
||||
|
||||
} // namespace qmp6988
|
||||
} // namespace esphome
|
||||
101
esphome/components/qmp6988/sensor.py
Normal file
101
esphome/components/qmp6988/sensor.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PRESSURE,
|
||||
CONF_TEMPERATURE,
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
DEVICE_CLASS_TEMPERATURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_CELSIUS,
|
||||
UNIT_HECTOPASCAL,
|
||||
CONF_IIR_FILTER,
|
||||
CONF_OVERSAMPLING,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
qmp6988_ns = cg.esphome_ns.namespace("qmp6988")
|
||||
QMP6988Component = qmp6988_ns.class_(
|
||||
"QMP6988Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
QMP6988Oversampling = qmp6988_ns.enum("QMP6988Oversampling")
|
||||
OVERSAMPLING_OPTIONS = {
|
||||
"NONE": QMP6988Oversampling.QMP6988_OVERSAMPLING_SKIPPED,
|
||||
"1X": QMP6988Oversampling.QMP6988_OVERSAMPLING_1X,
|
||||
"2X": QMP6988Oversampling.QMP6988_OVERSAMPLING_2X,
|
||||
"4X": QMP6988Oversampling.QMP6988_OVERSAMPLING_4X,
|
||||
"8X": QMP6988Oversampling.QMP6988_OVERSAMPLING_8X,
|
||||
"16X": QMP6988Oversampling.QMP6988_OVERSAMPLING_16X,
|
||||
"32X": QMP6988Oversampling.QMP6988_OVERSAMPLING_32X,
|
||||
"64X": QMP6988Oversampling.QMP6988_OVERSAMPLING_64X,
|
||||
}
|
||||
|
||||
QMP6988IIRFilter = qmp6988_ns.enum("QMP6988IIRFilter")
|
||||
IIR_FILTER_OPTIONS = {
|
||||
"OFF": QMP6988IIRFilter.QMP6988_IIR_FILTER_OFF,
|
||||
"2X": QMP6988IIRFilter.QMP6988_IIR_FILTER_2X,
|
||||
"4X": QMP6988IIRFilter.QMP6988_IIR_FILTER_4X,
|
||||
"8X": QMP6988IIRFilter.QMP6988_IIR_FILTER_8X,
|
||||
"16X": QMP6988IIRFilter.QMP6988_IIR_FILTER_16X,
|
||||
"32X": QMP6988IIRFilter.QMP6988_IIR_FILTER_32X,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(QMP6988Component),
|
||||
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_CELSIUS,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_TEMPERATURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="8X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_PRESSURE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_HECTOPASCAL,
|
||||
accuracy_decimals=1,
|
||||
device_class=DEVICE_CLASS_PRESSURE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_OVERSAMPLING, default="8X"): cv.enum(
|
||||
OVERSAMPLING_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_IIR_FILTER, default="OFF"): cv.enum(
|
||||
IIR_FILTER_OPTIONS, upper=True
|
||||
),
|
||||
}
|
||||
)
|
||||
.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)
|
||||
|
||||
if CONF_TEMPERATURE in config:
|
||||
conf = config[CONF_TEMPERATURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_temperature_sensor(sens))
|
||||
cg.add(var.set_temperature_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
if CONF_PRESSURE in config:
|
||||
conf = config[CONF_PRESSURE]
|
||||
sens = await sensor.new_sensor(conf)
|
||||
cg.add(var.set_pressure_sensor(sens))
|
||||
cg.add(var.set_pressure_oversampling(conf[CONF_OVERSAMPLING]))
|
||||
|
||||
cg.add(var.set_iir_filter(config[CONF_IIR_FILTER]))
|
||||
@@ -1,3 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/rc522/rc522.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
/**
|
||||
* Library based on https://github.com/miguelbalboa/rfid
|
||||
* and adapted to ESPHome by @glmnet
|
||||
@@ -6,14 +13,6 @@
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/rc522/rc522.h"
|
||||
#include "esphome/components/spi/spi.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace rc522_spi {
|
||||
|
||||
class RC522Spi : public rc522::RC522,
|
||||
|
||||
@@ -40,6 +40,24 @@ namespace remote_base {
|
||||
|
||||
static const char *const TAG = "remote.pronto";
|
||||
|
||||
bool ProntoData::operator==(const ProntoData &rhs) const {
|
||||
std::vector<uint16_t> data1 = encode_pronto(data);
|
||||
std::vector<uint16_t> data2 = encode_pronto(rhs.data);
|
||||
|
||||
uint32_t total_diff = 0;
|
||||
// Don't need to check the last one, it's the large gap at the end.
|
||||
for (std::vector<uint16_t>::size_type i = 0; i < data1.size() - 1; ++i) {
|
||||
int diff = data2[i] - data1[i];
|
||||
diff *= diff;
|
||||
if (diff > 9)
|
||||
return false;
|
||||
|
||||
total_diff += diff;
|
||||
}
|
||||
|
||||
return total_diff <= data1.size() * 3;
|
||||
}
|
||||
|
||||
// DO NOT EXPORT from this file
|
||||
static const uint16_t MICROSECONDS_T_MAX = 0xFFFFU;
|
||||
static const uint16_t LEARNED_TOKEN = 0x0000U;
|
||||
@@ -52,6 +70,7 @@ static const uint32_t REFERENCE_FREQUENCY = 4145146UL;
|
||||
static const uint16_t FALLBACK_FREQUENCY = 64767U; // To use with frequency = 0;
|
||||
static const uint32_t MICROSECONDS_IN_SECONDS = 1000000UL;
|
||||
static const uint16_t PRONTO_DEFAULT_GAP = 45000;
|
||||
static const uint16_t MARK_EXCESS_MICROS = 20;
|
||||
|
||||
static uint16_t to_frequency_k_hz(uint16_t code) {
|
||||
if (code == 0)
|
||||
@@ -107,7 +126,7 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::vector<uin
|
||||
}
|
||||
}
|
||||
|
||||
void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) {
|
||||
std::vector<uint16_t> encode_pronto(const std::string &str) {
|
||||
size_t len = str.length() / (DIGITS_IN_PRONTO_NUMBER + 1) + 1;
|
||||
std::vector<uint16_t> data;
|
||||
const char *p = str.c_str();
|
||||
@@ -122,12 +141,90 @@ void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &st
|
||||
data.push_back(x); // If input is conforming, there can be no overflow!
|
||||
p = *endptr;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void ProntoProtocol::send_pronto_(RemoteTransmitData *dst, const std::string &str) {
|
||||
std::vector<uint16_t> data = encode_pronto(str);
|
||||
send_pronto_(dst, data);
|
||||
}
|
||||
|
||||
void ProntoProtocol::encode(RemoteTransmitData *dst, const ProntoData &data) { send_pronto_(dst, data.data); }
|
||||
|
||||
optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) { return {}; }
|
||||
uint16_t ProntoProtocol::effective_frequency_(uint16_t frequency) {
|
||||
return frequency > 0 ? frequency : FALLBACK_FREQUENCY;
|
||||
}
|
||||
|
||||
uint16_t ProntoProtocol::to_timebase_(uint16_t frequency) {
|
||||
return MICROSECONDS_IN_SECONDS / effective_frequency_(frequency);
|
||||
}
|
||||
|
||||
uint16_t ProntoProtocol::to_frequency_code_(uint16_t frequency) {
|
||||
return REFERENCE_FREQUENCY / effective_frequency_(frequency);
|
||||
}
|
||||
|
||||
std::string ProntoProtocol::dump_digit_(uint8_t x) {
|
||||
return std::string(1, (char) (x <= 9 ? ('0' + x) : ('A' + (x - 10))));
|
||||
}
|
||||
|
||||
std::string ProntoProtocol::dump_number_(uint16_t number, bool end /* = false */) {
|
||||
std::string num;
|
||||
|
||||
for (uint8_t i = 0; i < DIGITS_IN_PRONTO_NUMBER; ++i) {
|
||||
uint8_t shifts = BITS_IN_HEXADECIMAL * (DIGITS_IN_PRONTO_NUMBER - 1 - i);
|
||||
num += dump_digit_((number >> shifts) & HEX_MASK);
|
||||
}
|
||||
|
||||
if (!end)
|
||||
num += ' ';
|
||||
|
||||
return num;
|
||||
}
|
||||
|
||||
std::string ProntoProtocol::dump_duration_(uint32_t duration, uint16_t timebase, bool end /* = false */) {
|
||||
return dump_number_((duration + timebase / 2) / timebase, end);
|
||||
}
|
||||
|
||||
std::string ProntoProtocol::compensate_and_dump_sequence_(std::vector<int32_t> *data, uint16_t timebase) {
|
||||
std::string out;
|
||||
|
||||
for (std::vector<int32_t>::size_type i = 0; i < data->size() - 1; i++) {
|
||||
int32_t t_length = data->at(i);
|
||||
uint32_t t_duration;
|
||||
if (t_length > 0) {
|
||||
// Mark
|
||||
t_duration = t_length - MARK_EXCESS_MICROS;
|
||||
} else {
|
||||
t_duration = -t_length + MARK_EXCESS_MICROS;
|
||||
}
|
||||
out += dump_duration_(t_duration, timebase);
|
||||
}
|
||||
|
||||
// append minimum gap
|
||||
out += dump_duration_(PRONTO_DEFAULT_GAP, timebase, true);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
optional<ProntoData> ProntoProtocol::decode(RemoteReceiveData src) {
|
||||
ProntoData out;
|
||||
|
||||
uint16_t frequency = 38000U;
|
||||
std::vector<int32_t> *data = src.get_raw_data();
|
||||
std::string prontodata;
|
||||
|
||||
prontodata += dump_number_(frequency > 0 ? LEARNED_TOKEN : LEARNED_NON_MODULATED_TOKEN);
|
||||
prontodata += dump_number_(to_frequency_code_(frequency));
|
||||
prontodata += dump_number_((data->size() + 1) / 2);
|
||||
prontodata += dump_number_(0);
|
||||
uint16_t timebase = to_timebase_(frequency);
|
||||
prontodata += compensate_and_dump_sequence_(data, timebase);
|
||||
|
||||
out.data = prontodata;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void ProntoProtocol::dump(const ProntoData &data) { ESP_LOGD(TAG, "Received Pronto: data=%s", data.data.c_str()); }
|
||||
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
namespace esphome {
|
||||
namespace remote_base {
|
||||
|
||||
std::vector<uint16_t> encode_pronto(const std::string &str);
|
||||
|
||||
struct ProntoData {
|
||||
std::string data;
|
||||
|
||||
bool operator==(const ProntoData &rhs) const { return data == rhs.data; }
|
||||
bool operator==(const ProntoData &rhs) const;
|
||||
};
|
||||
|
||||
class ProntoProtocol : public RemoteProtocol<ProntoData> {
|
||||
@@ -17,6 +19,14 @@ class ProntoProtocol : public RemoteProtocol<ProntoData> {
|
||||
void send_pronto_(RemoteTransmitData *dst, const std::vector<uint16_t> &data);
|
||||
void send_pronto_(RemoteTransmitData *dst, const std::string &str);
|
||||
|
||||
uint16_t effective_frequency_(uint16_t frequency);
|
||||
uint16_t to_timebase_(uint16_t frequency);
|
||||
uint16_t to_frequency_code_(uint16_t frequency);
|
||||
std::string dump_digit_(uint8_t x);
|
||||
std::string dump_number_(uint16_t number, bool end = false);
|
||||
std::string dump_duration_(uint32_t duration, uint16_t timebase, bool end = false);
|
||||
std::string compensate_and_dump_sequence_(std::vector<int32_t> *data, uint16_t timebase);
|
||||
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const ProntoData &data) override;
|
||||
optional<ProntoData> decode(RemoteReceiveData src) override;
|
||||
|
||||
@@ -33,14 +33,8 @@ void SCD30Component::setup() {
|
||||
#endif
|
||||
|
||||
/// Firmware version identification
|
||||
if (!this->write_command_(SCD30_CMD_GET_FIRMWARE_VERSION)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t raw_firmware_version[3];
|
||||
|
||||
if (!this->read_data_(raw_firmware_version, 3)) {
|
||||
if (!this->get_register(SCD30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version, 3)) {
|
||||
this->error_code_ = FIRMWARE_IDENTIFICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
@@ -49,7 +43,7 @@ void SCD30Component::setup() {
|
||||
uint16_t(raw_firmware_version[0] & 0xFF));
|
||||
|
||||
if (this->temperature_offset_ != 0) {
|
||||
if (!this->write_command_(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) {
|
||||
if (!this->write_command(SCD30_CMD_TEMPERATURE_OFFSET, (uint16_t)(temperature_offset_ * 100.0))) {
|
||||
ESP_LOGE(TAG, "Sensor SCD30 error setting temperature offset.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -69,7 +63,7 @@ void SCD30Component::setup() {
|
||||
delay(30);
|
||||
#endif
|
||||
|
||||
if (!this->write_command_(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
|
||||
if (!this->write_command(SCD30_CMD_MEASUREMENT_INTERVAL, update_interval_)) {
|
||||
ESP_LOGE(TAG, "Sensor SCD30 error setting update interval.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -81,7 +75,7 @@ void SCD30Component::setup() {
|
||||
|
||||
// The start measurement command disables the altitude compensation, if any, so we only set it if it's turned on
|
||||
if (this->altitude_compensation_ != 0xFFFF) {
|
||||
if (!this->write_command_(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
|
||||
if (!this->write_command(SCD30_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
|
||||
ESP_LOGE(TAG, "Sensor SCD30 error setting altitude compensation.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -92,7 +86,7 @@ void SCD30Component::setup() {
|
||||
delay(30);
|
||||
#endif
|
||||
|
||||
if (!this->write_command_(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
|
||||
if (!this->write_command(SCD30_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
|
||||
ESP_LOGE(TAG, "Sensor SCD30 error setting automatic self calibration.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -103,7 +97,7 @@ void SCD30Component::setup() {
|
||||
#endif
|
||||
|
||||
/// Sensor initialization
|
||||
if (!this->write_command_(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
|
||||
if (!this->write_command(SCD30_CMD_START_CONTINUOUS_MEASUREMENTS, this->ambient_pressure_compensation_)) {
|
||||
ESP_LOGE(TAG, "Sensor SCD30 error starting continuous measurements.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -151,14 +145,14 @@ void SCD30Component::dump_config() {
|
||||
}
|
||||
|
||||
void SCD30Component::update() {
|
||||
uint16_t raw_read_status[1];
|
||||
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
|
||||
uint16_t raw_read_status;
|
||||
if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGW(TAG, "Data not ready yet!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->write_command_(SCD30_CMD_READ_MEASUREMENT)) {
|
||||
if (!this->write_command(SCD30_CMD_READ_MEASUREMENT)) {
|
||||
ESP_LOGW(TAG, "Error reading measurement!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -166,7 +160,7 @@ void SCD30Component::update() {
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[6];
|
||||
if (!this->read_data_(raw_data, 6)) {
|
||||
if (!this->read_data(raw_data, 6)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@@ -197,77 +191,16 @@ void SCD30Component::update() {
|
||||
}
|
||||
|
||||
bool SCD30Component::is_data_ready_() {
|
||||
if (!this->write_command_(SCD30_CMD_GET_DATA_READY_STATUS)) {
|
||||
if (!this->write_command(SCD30_CMD_GET_DATA_READY_STATUS)) {
|
||||
return false;
|
||||
}
|
||||
delay(4);
|
||||
uint16_t is_data_ready;
|
||||
if (!this->read_data_(&is_data_ready, 1)) {
|
||||
if (!this->read_data(&is_data_ready, 1)) {
|
||||
return false;
|
||||
}
|
||||
return is_data_ready == 1;
|
||||
}
|
||||
|
||||
bool SCD30Component::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
bool SCD30Component::write_command_(uint16_t command, uint16_t data) {
|
||||
uint8_t raw[5];
|
||||
raw[0] = command >> 8;
|
||||
raw[1] = command & 0xFF;
|
||||
raw[2] = data >> 8;
|
||||
raw[3] = data & 0xFF;
|
||||
raw[4] = sht_crc_(raw[2], raw[3]);
|
||||
return this->write(raw, 5) == i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
uint8_t SCD30Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SCD30Component::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace scd30
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace scd30 {
|
||||
|
||||
/// This class implements support for the Sensirion scd30 i2c GAS (VOC and CO2eq) sensors.
|
||||
class SCD30Component : public Component, public i2c::I2CDevice {
|
||||
class SCD30Component : public Component, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
void set_co2_sensor(sensor::Sensor *co2) { co2_sensor_ = co2; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||
@@ -27,10 +27,6 @@ class SCD30Component : public Component, public i2c::I2CDevice {
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool write_command_(uint16_t command, uint16_t data);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
||||
bool is_data_ready_();
|
||||
|
||||
enum ErrorCode {
|
||||
|
||||
@@ -2,6 +2,7 @@ from esphome import core
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import sensirion_common
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_HUMIDITY,
|
||||
@@ -18,9 +19,12 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
scd30_ns = cg.esphome_ns.namespace("scd30")
|
||||
SCD30Component = scd30_ns.class_("SCD30Component", cg.Component, i2c.I2CDevice)
|
||||
SCD30Component = scd30_ns.class_(
|
||||
"SCD30Component", cg.Component, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
||||
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
||||
|
||||
@@ -25,15 +25,8 @@ void SCD4XComponent::setup() {
|
||||
|
||||
// the sensor needs 1000 ms to enter the idle state
|
||||
this->set_timeout(1000, [this]() {
|
||||
// Check if measurement is ready before reading the value
|
||||
if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) {
|
||||
ESP_LOGE(TAG, "Failed to write data ready status command");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_read_status[1];
|
||||
if (!this->read_data_(raw_read_status, 1)) {
|
||||
uint16_t raw_read_status;
|
||||
if (!this->get_register(SCD4X_CMD_GET_DATA_READY_STATUS, raw_read_status)) {
|
||||
ESP_LOGE(TAG, "Failed to read data ready status");
|
||||
this->mark_failed();
|
||||
return;
|
||||
@@ -41,9 +34,9 @@ void SCD4XComponent::setup() {
|
||||
|
||||
uint32_t stop_measurement_delay = 0;
|
||||
// In order to query the device periodic measurement must be ceased
|
||||
if (raw_read_status[0]) {
|
||||
if (raw_read_status) {
|
||||
ESP_LOGD(TAG, "Sensor has data available, stopping periodic measurement");
|
||||
if (!this->write_command_(SCD4X_CMD_STOP_MEASUREMENTS)) {
|
||||
if (!this->write_command(SCD4X_CMD_STOP_MEASUREMENTS)) {
|
||||
ESP_LOGE(TAG, "Failed to stop measurements");
|
||||
this->mark_failed();
|
||||
return;
|
||||
@@ -53,15 +46,8 @@ void SCD4XComponent::setup() {
|
||||
stop_measurement_delay = 500;
|
||||
}
|
||||
this->set_timeout(stop_measurement_delay, [this]() {
|
||||
if (!this->write_command_(SCD4X_CMD_GET_SERIAL_NUMBER)) {
|
||||
ESP_LOGE(TAG, "Failed to write get serial command");
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_serial_number[3];
|
||||
if (!this->read_data_(raw_serial_number, 3)) {
|
||||
if (!this->get_register(SCD4X_CMD_GET_SERIAL_NUMBER, raw_serial_number, 3, 1)) {
|
||||
ESP_LOGE(TAG, "Failed to read serial number");
|
||||
this->error_code_ = SERIAL_NUMBER_IDENTIFICATION_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -70,8 +56,8 @@ void SCD4XComponent::setup() {
|
||||
ESP_LOGD(TAG, "Serial number %02d.%02d.%02d", (uint16_t(raw_serial_number[0]) >> 8),
|
||||
uint16_t(raw_serial_number[0] & 0xFF), (uint16_t(raw_serial_number[1]) >> 8));
|
||||
|
||||
if (!this->write_command_(SCD4X_CMD_TEMPERATURE_OFFSET,
|
||||
(uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) {
|
||||
if (!this->write_command(SCD4X_CMD_TEMPERATURE_OFFSET,
|
||||
(uint16_t)(temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) {
|
||||
ESP_LOGE(TAG, "Error setting temperature offset.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -88,7 +74,7 @@ void SCD4XComponent::setup() {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (!this->write_command_(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
|
||||
if (!this->write_command(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) {
|
||||
ESP_LOGE(TAG, "Error setting altitude compensation.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -96,7 +82,7 @@ void SCD4XComponent::setup() {
|
||||
}
|
||||
}
|
||||
|
||||
if (!this->write_command_(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
|
||||
if (!this->write_command(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) {
|
||||
ESP_LOGE(TAG, "Error setting automatic self calibration.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -104,7 +90,7 @@ void SCD4XComponent::setup() {
|
||||
}
|
||||
|
||||
// Finally start sensor measurements
|
||||
if (!this->write_command_(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) {
|
||||
if (!this->write_command(SCD4X_CMD_START_CONTINUOUS_MEASUREMENTS)) {
|
||||
ESP_LOGE(TAG, "Error starting continuous measurements.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -164,19 +150,19 @@ void SCD4XComponent::update() {
|
||||
}
|
||||
|
||||
// Check if data is ready
|
||||
if (!this->write_command_(SCD4X_CMD_GET_DATA_READY_STATUS)) {
|
||||
if (!this->write_command(SCD4X_CMD_GET_DATA_READY_STATUS)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_read_status[1];
|
||||
if (!this->read_data_(raw_read_status, 1) || raw_read_status[0] == 0x00) {
|
||||
uint16_t raw_read_status;
|
||||
if (!this->read_data(raw_read_status) || raw_read_status == 0x00) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGW(TAG, "Data not ready yet!");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->write_command_(SCD4X_CMD_READ_MEASUREMENT)) {
|
||||
if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) {
|
||||
ESP_LOGW(TAG, "Error reading measurement!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -184,7 +170,7 @@ void SCD4XComponent::update() {
|
||||
|
||||
// Read off sensor data
|
||||
uint16_t raw_data[3];
|
||||
if (!this->read_data_(raw_data, 3)) {
|
||||
if (!this->read_data(raw_data, 3)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@@ -218,7 +204,7 @@ void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_bar) {
|
||||
}
|
||||
|
||||
bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) {
|
||||
if (this->write_command_(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) {
|
||||
if (this->write_command(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) {
|
||||
ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa);
|
||||
return true;
|
||||
} else {
|
||||
@@ -227,70 +213,5 @@ bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t SCD4XComponent::sht_crc_(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SCD4XComponent::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SCD4XComponent::write_command_(uint16_t command) {
|
||||
const uint8_t num_bytes = 2;
|
||||
uint8_t buffer[num_bytes];
|
||||
|
||||
buffer[0] = (command >> 8);
|
||||
buffer[1] = command & 0xff;
|
||||
|
||||
return this->write(buffer, num_bytes) == i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
bool SCD4XComponent::write_command_(uint16_t command, uint16_t data) {
|
||||
uint8_t raw[5];
|
||||
raw[0] = command >> 8;
|
||||
raw[1] = command & 0xFF;
|
||||
raw[2] = data >> 8;
|
||||
raw[3] = data & 0xFF;
|
||||
raw[4] = sht_crc_(raw[2], raw[3]);
|
||||
return this->write(raw, 5) == i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
} // namespace scd4x
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace scd4x {
|
||||
|
||||
enum ERRORCODE { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, UNKNOWN };
|
||||
|
||||
class SCD4XComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
class SCD4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void setup() override;
|
||||
@@ -27,10 +27,6 @@ class SCD4XComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||
|
||||
protected:
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
bool write_command_(uint16_t command);
|
||||
bool write_command_(uint16_t command, uint16_t data);
|
||||
bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa);
|
||||
|
||||
ERRORCODE error_code_;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
|
||||
from esphome.components import sensirion_common
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_CO2,
|
||||
@@ -21,9 +21,12 @@ from esphome.const import (
|
||||
|
||||
CODEOWNERS = ["@sjtrny"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
scd4x_ns = cg.esphome_ns.namespace("scd4x")
|
||||
SCD4XComponent = scd4x_ns.class_("SCD4XComponent", cg.PollingComponent, i2c.I2CDevice)
|
||||
SCD4XComponent = scd4x_ns.class_(
|
||||
"SCD4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
CONF_AUTOMATIC_SELF_CALIBRATION = "automatic_self_calibration"
|
||||
CONF_ALTITUDE_COMPENSATION = "altitude_compensation"
|
||||
|
||||
@@ -7,55 +7,50 @@ namespace esphome {
|
||||
namespace sdp3x {
|
||||
|
||||
static const char *const TAG = "sdp3x.sensor";
|
||||
static const uint8_t SDP3X_SOFT_RESET[2] = {0x00, 0x06};
|
||||
static const uint8_t SDP3X_READ_ID1[2] = {0x36, 0x7C};
|
||||
static const uint8_t SDP3X_READ_ID2[2] = {0xE1, 0x02};
|
||||
static const uint8_t SDP3X_START_DP_AVG[2] = {0x36, 0x15};
|
||||
static const uint8_t SDP3X_START_MASS_FLOW_AVG[2] = {0x36, 0x03};
|
||||
static const uint8_t SDP3X_STOP_MEAS[2] = {0x3F, 0xF9};
|
||||
static const uint16_t SDP3X_SOFT_RESET = 0x0006;
|
||||
static const uint16_t SDP3X_READ_ID1 = 0x367C;
|
||||
static const uint16_t SDP3X_READ_ID2 = 0xE102;
|
||||
static const uint16_t SDP3X_START_DP_AVG = 0x3615;
|
||||
static const uint16_t SDP3X_START_MASS_FLOW_AVG = 0x3603;
|
||||
static const uint16_t SDP3X_STOP_MEAS = 0x3FF9;
|
||||
|
||||
void SDP3XComponent::update() { this->read_pressure_(); }
|
||||
|
||||
void SDP3XComponent::setup() {
|
||||
ESP_LOGD(TAG, "Setting up SDP3X...");
|
||||
|
||||
if (this->write(SDP3X_STOP_MEAS, 2) != i2c::ERROR_OK) {
|
||||
if (!this->write_command(SDP3X_STOP_MEAS)) {
|
||||
ESP_LOGW(TAG, "Stop SDP3X failed!"); // This sometimes fails for no good reason
|
||||
}
|
||||
|
||||
if (this->write(SDP3X_SOFT_RESET, 2) != i2c::ERROR_OK) {
|
||||
if (!this->write_command(SDP3X_SOFT_RESET)) {
|
||||
ESP_LOGW(TAG, "Soft Reset SDP3X failed!"); // This sometimes fails for no good reason
|
||||
}
|
||||
|
||||
this->set_timeout(20, [this] {
|
||||
if (this->write(SDP3X_READ_ID1, 2) != i2c::ERROR_OK) {
|
||||
if (!this->write_command(SDP3X_READ_ID1)) {
|
||||
ESP_LOGE(TAG, "Read ID1 SDP3X failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (this->write(SDP3X_READ_ID2, 2) != i2c::ERROR_OK) {
|
||||
if (!this->write_command(SDP3X_READ_ID2)) {
|
||||
ESP_LOGE(TAG, "Read ID2 SDP3X failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t data[18];
|
||||
if (this->read(data, 18) != i2c::ERROR_OK) {
|
||||
uint16_t data[6];
|
||||
if (this->read_data(data, 6) != i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Read ID SDP3X failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]))) {
|
||||
ESP_LOGE(TAG, "CRC ID SDP3X failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// SDP8xx
|
||||
// ref:
|
||||
// https://www.sensirion.com/fileadmin/user_upload/customers/sensirion/Dokumente/8_Differential_Pressure/Datasheets/Sensirion_Differential_Pressure_Datasheet_SDP8xx_Digital.pdf
|
||||
if (data[2] == 0x02) {
|
||||
switch (data[3]) {
|
||||
if (data[1] >> 8 == 0x02) {
|
||||
switch (data[1] & 0xFF) {
|
||||
case 0x01: // SDP800-500Pa
|
||||
ESP_LOGCONFIG(TAG, "Sensor is SDP800-500Pa");
|
||||
break;
|
||||
@@ -75,15 +70,16 @@ void SDP3XComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Sensor is SDP810-125Pa");
|
||||
break;
|
||||
}
|
||||
} else if (data[2] == 0x01) {
|
||||
if (data[3] == 0x01) {
|
||||
} else if (data[1] >> 8 == 0x01) {
|
||||
if ((data[1] & 0xFF) == 0x01) {
|
||||
ESP_LOGCONFIG(TAG, "Sensor is SDP31-500Pa");
|
||||
} else if (data[3] == 0x02) {
|
||||
} else if ((data[1] & 0xFF) == 0x02) {
|
||||
ESP_LOGCONFIG(TAG, "Sensor is SDP32-125Pa");
|
||||
}
|
||||
}
|
||||
|
||||
if (this->write(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG, 2) != i2c::ERROR_OK) {
|
||||
if (this->write_command(measurement_mode_ == DP_AVG ? SDP3X_START_DP_AVG : SDP3X_START_MASS_FLOW_AVG) !=
|
||||
i2c::ERROR_OK) {
|
||||
ESP_LOGE(TAG, "Start Measurements SDP3X failed!");
|
||||
this->mark_failed();
|
||||
return;
|
||||
@@ -101,22 +97,16 @@ void SDP3XComponent::dump_config() {
|
||||
}
|
||||
|
||||
void SDP3XComponent::read_pressure_() {
|
||||
uint8_t data[9];
|
||||
if (this->read(data, 9) != i2c::ERROR_OK) {
|
||||
uint16_t data[3];
|
||||
if (this->read_data(data, 3) != i2c::ERROR_OK) {
|
||||
ESP_LOGW(TAG, "Couldn't read SDP3X data!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(check_crc_(&data[0], 2, data[2]) && check_crc_(&data[3], 2, data[5]) && check_crc_(&data[6], 2, data[8]))) {
|
||||
ESP_LOGW(TAG, "Invalid SDP3X data!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t pressure_raw = encode_uint16(data[0], data[1]);
|
||||
int16_t temperature_raw = encode_uint16(data[3], data[4]);
|
||||
int16_t scale_factor_raw = encode_uint16(data[6], data[7]);
|
||||
int16_t pressure_raw = data[0];
|
||||
int16_t temperature_raw = data[1];
|
||||
int16_t scale_factor_raw = data[2];
|
||||
// scale factor is in Pa - convert to hPa
|
||||
float pressure = pressure_raw / (scale_factor_raw * 100.0f);
|
||||
ESP_LOGV(TAG, "Got raw pressure=%d, raw scale factor =%d, raw temperature=%d ", pressure_raw, scale_factor_raw,
|
||||
@@ -129,26 +119,5 @@ void SDP3XComponent::read_pressure_() {
|
||||
|
||||
float SDP3XComponent::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
// Check CRC function from SDP3X sample code provided by sensirion
|
||||
// Returns true if a checksum is OK
|
||||
bool SDP3XComponent::check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum) {
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
// calculates 8-Bit checksum with given polynomial 0x31 (x^8 + x^5 + x^4 + 1)
|
||||
for (int i = 0; i < size; i++) {
|
||||
crc ^= (data[i]);
|
||||
for (uint8_t bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x31;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// verify checksum
|
||||
return (crc == checksum);
|
||||
}
|
||||
|
||||
} // namespace sdp3x
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sdp3x {
|
||||
|
||||
enum MeasurementMode { MASS_FLOW_AVG, DP_AVG };
|
||||
|
||||
class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public sensor::Sensor {
|
||||
class SDP3XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice, public sensor::Sensor {
|
||||
public:
|
||||
/// Schedule temperature+pressure readings.
|
||||
void update() override;
|
||||
@@ -23,8 +23,6 @@ class SDP3XComponent : public PollingComponent, public i2c::I2CDevice, public se
|
||||
protected:
|
||||
/// Internal method to read the pressure from the component after it has been scheduled.
|
||||
void read_pressure_();
|
||||
|
||||
bool check_crc_(const uint8_t data[], uint8_t size, uint8_t checksum);
|
||||
MeasurementMode measurement_mode_;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import sensirion_common
|
||||
from esphome.const import (
|
||||
DEVICE_CLASS_PRESSURE,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
@@ -8,10 +9,13 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
CODEOWNERS = ["@Azimath"]
|
||||
|
||||
sdp3x_ns = cg.esphome_ns.namespace("sdp3x")
|
||||
SDP3XComponent = sdp3x_ns.class_("SDP3XComponent", cg.PollingComponent, i2c.I2CDevice)
|
||||
SDP3XComponent = sdp3x_ns.class_(
|
||||
"SDP3XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
|
||||
MeasurementMode = sdp3x_ns.enum("MeasurementMode")
|
||||
|
||||
10
esphome/components/sensirion_common/__init__.py
Normal file
10
esphome/components/sensirion_common/__init__.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import esphome.codegen as cg
|
||||
|
||||
from esphome.components import i2c
|
||||
|
||||
|
||||
CODEOWNERS = ["@martgras"]
|
||||
|
||||
sensirion_common_ns = cg.esphome_ns.namespace("sensirion_common")
|
||||
|
||||
SensirionI2CDevice = sensirion_common_ns.class_("SensirionI2CDevice", i2c.I2CDevice)
|
||||
128
esphome/components/sensirion_common/i2c_sensirion.cpp
Normal file
128
esphome/components/sensirion_common/i2c_sensirion.cpp
Normal file
@@ -0,0 +1,128 @@
|
||||
#include "i2c_sensirion.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include <cinttypes>
|
||||
|
||||
namespace esphome {
|
||||
namespace sensirion_common {
|
||||
|
||||
static const char *const TAG = "sensirion_i2c";
|
||||
// To avoid memory allocations for small writes a stack buffer is used
|
||||
static const size_t BUFFER_STACK_SIZE = 16;
|
||||
|
||||
bool SensirionI2CDevice::read_data(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
last_error_ = this->read(buf.data(), num_bytes);
|
||||
if (last_error_ != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid at pos %d! 0x%02X != 0x%02X", i, buf[j + 2], crc);
|
||||
last_error_ = i2c::ERROR_CRC;
|
||||
return false;
|
||||
}
|
||||
data[i] = encode_uint16(buf[j], buf[j + 1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
/***
|
||||
* write command with parameters and insert crc
|
||||
* use stack array for less than 4 paramaters. Most sensirion i2c commands have less parameters
|
||||
*/
|
||||
bool SensirionI2CDevice::write_command_(uint16_t command, CommandLen command_len, const uint16_t *data,
|
||||
uint8_t data_len) {
|
||||
uint8_t temp_stack[BUFFER_STACK_SIZE];
|
||||
std::unique_ptr<uint8_t[]> temp_heap;
|
||||
uint8_t *temp;
|
||||
size_t required_buffer_len = data_len * 3 + 2;
|
||||
|
||||
// Is a dynamic allocation required ?
|
||||
if (required_buffer_len >= BUFFER_STACK_SIZE) {
|
||||
temp_heap = std::unique_ptr<uint8_t[]>(new uint8_t[required_buffer_len]);
|
||||
temp = temp_heap.get();
|
||||
} else {
|
||||
temp = temp_stack;
|
||||
}
|
||||
// First byte or word is the command
|
||||
uint8_t raw_idx = 0;
|
||||
if (command_len == 1) {
|
||||
temp[raw_idx++] = command & 0xFF;
|
||||
} else {
|
||||
// command is 2 bytes
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
temp[raw_idx++] = command >> 8;
|
||||
temp[raw_idx++] = command & 0xFF;
|
||||
#else
|
||||
temp[raw_idx++] = command & 0xFF;
|
||||
temp[raw_idx++] = command >> 8;
|
||||
#endif
|
||||
}
|
||||
// add parameters folllowed by crc
|
||||
// skipped if len == 0
|
||||
for (size_t i = 0; i < data_len; i++) {
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
temp[raw_idx++] = data[i] >> 8;
|
||||
temp[raw_idx++] = data[i] & 0xFF;
|
||||
#else
|
||||
temp[raw_idx++] = data[i] & 0xFF;
|
||||
temp[raw_idx++] = data[i] >> 8;
|
||||
#endif
|
||||
temp[raw_idx++] = sht_crc_(data[i]);
|
||||
}
|
||||
last_error_ = this->write(temp, raw_idx);
|
||||
return last_error_ == i2c::ERROR_OK;
|
||||
}
|
||||
|
||||
bool SensirionI2CDevice::get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len,
|
||||
uint8_t delay_ms) {
|
||||
if (!this->write_command_(reg, command_len, nullptr, 0)) {
|
||||
ESP_LOGE(TAG, "Failed to write i2c register=0x%X (%d) err=%d,", reg, command_len, this->last_error_);
|
||||
return false;
|
||||
}
|
||||
delay(delay_ms);
|
||||
bool result = this->read_data(data, len);
|
||||
if (!result) {
|
||||
ESP_LOGE(TAG, "Failed to read data from register=0x%X err=%d,", reg, this->last_error_);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// The 8-bit CRC checksum is transmitted after each data word
|
||||
uint8_t SensirionI2CDevice::sht_crc_(uint16_t data) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
crc ^= data >> 8;
|
||||
#else
|
||||
crc ^= data & 0xFF;
|
||||
#endif
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ crc_polynomial_;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
crc ^= data & 0xFF;
|
||||
#else
|
||||
crc ^= data >> 8;
|
||||
#endif
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ crc_polynomial_;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
} // namespace sensirion_common
|
||||
} // namespace esphome
|
||||
155
esphome/components/sensirion_common/i2c_sensirion.h
Normal file
155
esphome/components/sensirion_common/i2c_sensirion.h
Normal file
@@ -0,0 +1,155 @@
|
||||
#pragma once
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sensirion_common {
|
||||
|
||||
/**
|
||||
* Implementation of a i2c functions for Sensirion sensors
|
||||
* Sensirion data requires crc checking.
|
||||
* Each 16 bit word is/must be followed 8 bit CRC code
|
||||
* (Applies to read and write - note the i2c command code doesn't need a CRC)
|
||||
* Format:
|
||||
* | 16 Bit Command Code | 16 bit Data word 1 | CRC of DW 1 | 16 bit Data word 1 | CRC of DW 2 | ..
|
||||
*/
|
||||
class SensirionI2CDevice : public i2c::I2CDevice {
|
||||
public:
|
||||
enum CommandLen : uint8_t { ADDR_8_BIT = 1, ADDR_16_BIT = 2 };
|
||||
|
||||
/** Read data words from i2c device.
|
||||
* handles crc check used by Sensirion sensors
|
||||
* @param data pointer to raw result
|
||||
* @param len number of words to read
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
bool read_data(uint16_t *data, uint8_t len);
|
||||
|
||||
/** Read 1 data word from i2c device.
|
||||
* @param data reference to raw result
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
bool read_data(uint16_t &data) { return this->read_data(&data, 1); }
|
||||
|
||||
/** get data words from i2c register.
|
||||
* handles crc check used by Sensirion sensors
|
||||
* @param i2c register
|
||||
* @param data pointer to raw result
|
||||
* @param len number of words to read
|
||||
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
bool get_register(uint16_t command, uint16_t *data, uint8_t len, uint8_t delay = 0) {
|
||||
return get_register_(command, ADDR_16_BIT, data, len, delay);
|
||||
}
|
||||
/** Read 1 data word from 16 bit i2c register.
|
||||
* @param i2c register
|
||||
* @param data reference to raw result
|
||||
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
bool get_register(uint16_t i2c_register, uint16_t &data, uint8_t delay = 0) {
|
||||
return this->get_register_(i2c_register, ADDR_16_BIT, &data, 1, delay);
|
||||
}
|
||||
|
||||
/** get data words from i2c register.
|
||||
* handles crc check used by Sensirion sensors
|
||||
* @param i2c register
|
||||
* @param data pointer to raw result
|
||||
* @param len number of words to read
|
||||
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
bool get_8bit_register(uint8_t i2c_register, uint16_t *data, uint8_t len, uint8_t delay = 0) {
|
||||
return get_register_(i2c_register, ADDR_8_BIT, data, len, delay);
|
||||
}
|
||||
|
||||
/** Read 1 data word from 8 bit i2c register.
|
||||
* @param i2c register
|
||||
* @param data reference to raw result
|
||||
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
bool get_8bit_register(uint8_t i2c_register, uint16_t &data, uint8_t delay = 0) {
|
||||
return this->get_register_(i2c_register, ADDR_8_BIT, &data, 1, delay);
|
||||
}
|
||||
|
||||
/** Write a command to the i2c device.
|
||||
* @param command i2c command to send
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
template<class T> bool write_command(T i2c_register) { return write_command(i2c_register, nullptr, 0); }
|
||||
|
||||
/** Write a command and one data word to the i2c device .
|
||||
* @param command i2c command to send
|
||||
* @param data argument for the i2c command
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
template<class T> bool write_command(T i2c_register, uint16_t data) { return write_command(i2c_register, &data, 1); }
|
||||
|
||||
/** Write a command with arguments as words
|
||||
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
|
||||
* @param data vector<uint16> arguments for the i2c command
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
template<class T> bool write_command(T i2c_register, const std::vector<uint16_t> &data) {
|
||||
return write_command_(i2c_register, sizeof(T), data.data(), data.size());
|
||||
}
|
||||
|
||||
/** Write a command with arguments as words
|
||||
* @param i2c_register i2c command to send - an be uint8_t or uint16_t
|
||||
* @param data arguments for the i2c command
|
||||
* @param len number of arguments (words)
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
template<class T> bool write_command(T i2c_register, const uint16_t *data, uint8_t len) {
|
||||
// limit to 8 or 16 bit only
|
||||
static_assert(sizeof(i2c_register) == 1 || sizeof(i2c_register) == 2,
|
||||
"only 8 or 16 bit command types are supported.");
|
||||
return write_command_(i2c_register, CommandLen(sizeof(T)), data, len);
|
||||
}
|
||||
|
||||
protected:
|
||||
uint8_t crc_polynomial_{0x31u}; // default for sensirion
|
||||
/** Write a command with arguments as words
|
||||
* @param command i2c command to send can be uint8_t or uint16_t
|
||||
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
|
||||
* @param data arguments for the i2c command
|
||||
* @param data_len number of arguments (words)
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
bool write_command_(uint16_t command, CommandLen command_len, const uint16_t *data, uint8_t data_len);
|
||||
|
||||
/** get data words from i2c register.
|
||||
* handles crc check used by Sensirion sensors
|
||||
* @param i2c register
|
||||
* @param command_len either 1 for short 8 bit command or 2 for 16 bit command codes
|
||||
* @param data pointer to raw result
|
||||
* @param len number of words to read
|
||||
* @param delay milliseconds to to wait between sending the i2c commmand and reading the result
|
||||
* @return true if reading succeded
|
||||
*/
|
||||
bool get_register_(uint16_t reg, CommandLen command_len, uint16_t *data, uint8_t len, uint8_t delay);
|
||||
|
||||
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
|
||||
* @param command i2c command to send
|
||||
* @param data data word for which the crc8 checksum is calculated
|
||||
* @param len number of arguments (words)
|
||||
* @return 8 Bit CRC
|
||||
*/
|
||||
uint8_t sht_crc_(uint16_t data);
|
||||
|
||||
/** 8-bit CRC checksum that is transmitted after each data word for read and write operation
|
||||
* @param command i2c command to send
|
||||
* @param data1 high byte of data word
|
||||
* @param data2 low byte of data word
|
||||
* @return 8 Bit CRC
|
||||
*/
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2) { return sht_crc_(encode_uint16(data1, data2)); }
|
||||
|
||||
/** last error code from i2c operation
|
||||
*/
|
||||
i2c::ErrorCode last_error_;
|
||||
};
|
||||
|
||||
} // namespace sensirion_common
|
||||
} // namespace esphome
|
||||
@@ -212,8 +212,8 @@ SENSOR_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
|
||||
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
|
||||
{
|
||||
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
|
||||
cv.Optional(CONF_ABOVE): cv.float_,
|
||||
cv.Optional(CONF_BELOW): cv.float_,
|
||||
cv.Optional(CONF_ABOVE): cv.templatable(cv.float_),
|
||||
cv.Optional(CONF_BELOW): cv.templatable(cv.float_),
|
||||
},
|
||||
cv.has_at_least_one_key(CONF_ABOVE, CONF_BELOW),
|
||||
),
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import i2c, sensor, sensirion_common
|
||||
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_BASELINE,
|
||||
CONF_ECO2,
|
||||
CONF_STORE_BASELINE,
|
||||
CONF_TEMPERATURE_SOURCE,
|
||||
CONF_TVOC,
|
||||
ICON_RADIATOR,
|
||||
DEVICE_CLASS_CARBON_DIOXIDE,
|
||||
@@ -13,20 +16,23 @@ from esphome.const import (
|
||||
UNIT_PARTS_PER_MILLION,
|
||||
UNIT_PARTS_PER_BILLION,
|
||||
ICON_MOLECULE_CO2,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
sgp30_ns = cg.esphome_ns.namespace("sgp30")
|
||||
SGP30Component = sgp30_ns.class_("SGP30Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
SGP30Component = sgp30_ns.class_(
|
||||
"SGP30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
CONF_ECO2_BASELINE = "eco2_baseline"
|
||||
CONF_TVOC_BASELINE = "tvoc_baseline"
|
||||
CONF_STORE_BASELINE = "store_baseline"
|
||||
CONF_UPTIME = "uptime"
|
||||
CONF_COMPENSATION = "compensation"
|
||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||
CONF_TEMPERATURE_SOURCE = "temperature_source"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
@@ -49,10 +55,12 @@ CONFIG_SCHEMA = (
|
||||
cv.Optional(CONF_ECO2_BASELINE): sensor.sensor_schema(
|
||||
icon=ICON_MOLECULE_CO2,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_TVOC_BASELINE): sensor.sensor_schema(
|
||||
icon=ICON_RADIATOR,
|
||||
accuracy_decimals=0,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_STORE_BASELINE, default=True): cv.boolean,
|
||||
cv.Optional(CONF_BASELINE): cv.Schema(
|
||||
|
||||
@@ -36,14 +36,8 @@ void SGP30Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SGP30...");
|
||||
|
||||
// Serial Number identification
|
||||
if (!this->write_command_(SGP30_CMD_GET_SERIAL_ID)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t raw_serial_number[3];
|
||||
|
||||
if (!this->read_data_(raw_serial_number, 3)) {
|
||||
if (!this->get_register(SGP30_CMD_GET_SERIAL_ID, raw_serial_number, 3)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -52,16 +46,12 @@ void SGP30Component::setup() {
|
||||
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
|
||||
|
||||
// Featureset identification for future use
|
||||
if (!this->write_command_(SGP30_CMD_GET_FEATURESET)) {
|
||||
uint16_t raw_featureset;
|
||||
if (!this->get_register(SGP30_CMD_GET_FEATURESET, raw_featureset)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t raw_featureset[1];
|
||||
if (!this->read_data_(raw_featureset, 1)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
this->featureset_ = raw_featureset[0];
|
||||
this->featureset_ = raw_featureset;
|
||||
if (uint16_t(this->featureset_ >> 12) != 0x0) {
|
||||
if (uint16_t(this->featureset_ >> 12) == 0x1) {
|
||||
// ID matching a different sensor: SGPC3
|
||||
@@ -76,7 +66,7 @@ void SGP30Component::setup() {
|
||||
ESP_LOGD(TAG, "Product version: 0x%0X", uint16_t(this->featureset_ & 0x1FF));
|
||||
|
||||
// Sensor initialization
|
||||
if (!this->write_command_(SGP30_CMD_IAQ_INIT)) {
|
||||
if (!this->write_command(SGP30_CMD_IAQ_INIT)) {
|
||||
ESP_LOGE(TAG, "Sensor sgp30_iaq_init failed.");
|
||||
this->error_code_ = MEASUREMENT_INIT_FAILED;
|
||||
this->mark_failed();
|
||||
@@ -119,14 +109,14 @@ bool SGP30Component::is_sensor_baseline_reliable_() {
|
||||
|
||||
void SGP30Component::read_iaq_baseline_() {
|
||||
if (this->is_sensor_baseline_reliable_()) {
|
||||
if (!this->write_command_(SGP30_CMD_GET_IAQ_BASELINE)) {
|
||||
if (!this->write_command(SGP30_CMD_GET_IAQ_BASELINE)) {
|
||||
ESP_LOGD(TAG, "Error getting baseline");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[2];
|
||||
if (!this->read_data_(raw_data, 2)) {
|
||||
if (!this->read_data(raw_data, 2)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@@ -274,14 +264,14 @@ void SGP30Component::dump_config() {
|
||||
}
|
||||
|
||||
void SGP30Component::update() {
|
||||
if (!this->write_command_(SGP30_CMD_MEASURE_IAQ)) {
|
||||
if (!this->write_command(SGP30_CMD_MEASURE_IAQ)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
this->seconds_since_last_store_ += this->update_interval_ / 1000;
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[2];
|
||||
if (!this->read_data_(raw_data, 2)) {
|
||||
if (!this->read_data(raw_data, 2)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@@ -305,56 +295,5 @@ void SGP30Component::update() {
|
||||
});
|
||||
}
|
||||
|
||||
bool SGP30Component::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t SGP30Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SGP30Component::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sgp30
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include <cmath>
|
||||
|
||||
@@ -15,7 +15,7 @@ struct SGP30Baselines {
|
||||
} PACKED;
|
||||
|
||||
/// This class implements support for the Sensirion SGP30 i2c GAS (VOC and CO2eq) sensors.
|
||||
class SGP30Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class SGP30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
void set_eco2_sensor(sensor::Sensor *eco2) { eco2_sensor_ = eco2; }
|
||||
void set_tvoc_sensor(sensor::Sensor *tvoc) { tvoc_sensor_ = tvoc; }
|
||||
@@ -33,13 +33,10 @@ class SGP30Component : public PollingComponent, public i2c::I2CDevice {
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
void send_env_data_();
|
||||
void read_iaq_baseline_();
|
||||
bool is_sensor_baseline_reliable_();
|
||||
void write_iaq_baseline_(uint16_t eco2_baseline, uint16_t tvoc_baseline);
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
||||
uint64_t serial_number_;
|
||||
uint16_t featureset_;
|
||||
uint32_t required_warm_up_time_;
|
||||
|
||||
@@ -1,25 +1,30 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import i2c, sensor, sensirion_common
|
||||
|
||||
from esphome.const import (
|
||||
CONF_STORE_BASELINE,
|
||||
CONF_TEMPERATURE_SOURCE,
|
||||
ICON_RADIATOR,
|
||||
DEVICE_CLASS_VOLATILE_ORGANIC_COMPOUNDS,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
CODEOWNERS = ["@SenexCrenshaw"]
|
||||
|
||||
sgp40_ns = cg.esphome_ns.namespace("sgp40")
|
||||
SGP40Component = sgp40_ns.class_(
|
||||
"SGP40Component", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
|
||||
"SGP40Component",
|
||||
sensor.Sensor,
|
||||
cg.PollingComponent,
|
||||
sensirion_common.SensirionI2CDevice,
|
||||
)
|
||||
|
||||
CONF_COMPENSATION = "compensation"
|
||||
CONF_HUMIDITY_SOURCE = "humidity_source"
|
||||
CONF_TEMPERATURE_SOURCE = "temperature_source"
|
||||
CONF_STORE_BASELINE = "store_baseline"
|
||||
CONF_VOC_BASELINE = "voc_baseline"
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
|
||||
@@ -12,14 +12,14 @@ void SGP40Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SGP40...");
|
||||
|
||||
// Serial Number identification
|
||||
if (!this->write_command_(SGP40_CMD_GET_SERIAL_ID)) {
|
||||
if (!this->write_command(SGP40_CMD_GET_SERIAL_ID)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t raw_serial_number[3];
|
||||
|
||||
if (!this->read_data_(raw_serial_number, 3)) {
|
||||
if (!this->read_data(raw_serial_number, 3)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -28,19 +28,19 @@ void SGP40Component::setup() {
|
||||
ESP_LOGD(TAG, "Serial Number: %" PRIu64, this->serial_number_);
|
||||
|
||||
// Featureset identification for future use
|
||||
if (!this->write_command_(SGP40_CMD_GET_FEATURESET)) {
|
||||
if (!this->write_command(SGP40_CMD_GET_FEATURESET)) {
|
||||
ESP_LOGD(TAG, "raw_featureset write_command_ failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
uint16_t raw_featureset[1];
|
||||
if (!this->read_data_(raw_featureset, 1)) {
|
||||
uint16_t raw_featureset;
|
||||
if (!this->read_data(raw_featureset)) {
|
||||
ESP_LOGD(TAG, "raw_featureset read_data_ failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
this->featureset_ = raw_featureset[0];
|
||||
this->featureset_ = raw_featureset;
|
||||
if ((this->featureset_ & 0x1FF) != SGP40_FEATURESET) {
|
||||
ESP_LOGD(TAG, "Product feature set failed 0x%0X , expecting 0x%0X", uint16_t(this->featureset_ & 0x1FF),
|
||||
SGP40_FEATURESET);
|
||||
@@ -95,21 +95,21 @@ void SGP40Component::setup() {
|
||||
|
||||
void SGP40Component::self_test_() {
|
||||
ESP_LOGD(TAG, "Self-test started");
|
||||
if (!this->write_command_(SGP40_CMD_SELF_TEST)) {
|
||||
if (!this->write_command(SGP40_CMD_SELF_TEST)) {
|
||||
this->error_code_ = COMMUNICATION_FAILED;
|
||||
ESP_LOGD(TAG, "Self-test communication failed");
|
||||
this->mark_failed();
|
||||
}
|
||||
|
||||
this->set_timeout(250, [this]() {
|
||||
uint16_t reply[1];
|
||||
if (!this->read_data_(reply, 1)) {
|
||||
uint16_t reply;
|
||||
if (!this->read_data(reply)) {
|
||||
ESP_LOGD(TAG, "Self-test read_data_ failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (reply[0] == 0xD400) {
|
||||
if (reply == 0xD400) {
|
||||
this->self_test_complete_ = true;
|
||||
ESP_LOGD(TAG, "Self-test completed");
|
||||
return;
|
||||
@@ -192,51 +192,28 @@ uint16_t SGP40Component::measure_raw_() {
|
||||
temperature = 25;
|
||||
}
|
||||
|
||||
uint8_t command[8];
|
||||
|
||||
command[0] = 0x26;
|
||||
command[1] = 0x0F;
|
||||
|
||||
uint16_t data[2];
|
||||
uint16_t rhticks = llround((uint16_t)((humidity * 65535) / 100));
|
||||
command[2] = rhticks >> 8;
|
||||
command[3] = rhticks & 0xFF;
|
||||
command[4] = generate_crc_(command + 2, 2);
|
||||
uint16_t tempticks = (uint16_t)(((temperature + 45) * 65535) / 175);
|
||||
command[5] = tempticks >> 8;
|
||||
command[6] = tempticks & 0xFF;
|
||||
command[7] = generate_crc_(command + 5, 2);
|
||||
// first paramater is the relative humidity ticks
|
||||
data[0] = rhticks;
|
||||
// second paramater is the temperature ticks
|
||||
data[1] = tempticks;
|
||||
|
||||
if (this->write(command, 8) != i2c::ERROR_OK) {
|
||||
if (!this->write_command(SGP40_CMD_MEASURE_RAW, data, 2)) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGD(TAG, "write error");
|
||||
return UINT16_MAX;
|
||||
ESP_LOGD(TAG, "write error (%d)", this->last_error_);
|
||||
return false;
|
||||
}
|
||||
delay(30);
|
||||
uint16_t raw_data[1];
|
||||
|
||||
if (!this->read_data_(raw_data, 1)) {
|
||||
uint16_t raw_data;
|
||||
if (!this->read_data(raw_data)) {
|
||||
this->status_set_warning();
|
||||
ESP_LOGD(TAG, "read_data_ error");
|
||||
return UINT16_MAX;
|
||||
}
|
||||
return raw_data[0];
|
||||
}
|
||||
|
||||
uint8_t SGP40Component::generate_crc_(const uint8_t *data, uint8_t datalen) {
|
||||
// calculates 8-Bit checksum with given polynomial
|
||||
uint8_t crc = SGP40_CRC8_INIT;
|
||||
|
||||
for (uint8_t i = 0; i < datalen; i++) {
|
||||
crc ^= data[i];
|
||||
for (uint8_t b = 0; b < 8; b++) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ SGP40_CRC8_POLYNOMIAL;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
return raw_data;
|
||||
}
|
||||
|
||||
void SGP40Component::update_voc_index() {
|
||||
@@ -293,56 +270,5 @@ void SGP40Component::dump_config() {
|
||||
}
|
||||
}
|
||||
|
||||
bool SGP40Component::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t SGP40Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SGP40Component::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sgp40
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/preferences.h"
|
||||
#include "sensirion_voc_algorithm.h"
|
||||
@@ -28,6 +28,7 @@ static const uint8_t SGP40_WORD_LEN = 2; ///< 2 bytes per word
|
||||
static const uint16_t SGP40_CMD_GET_SERIAL_ID = 0x3682;
|
||||
static const uint16_t SGP40_CMD_GET_FEATURESET = 0x202f;
|
||||
static const uint16_t SGP40_CMD_SELF_TEST = 0x280e;
|
||||
static const uint16_t SGP40_CMD_MEASURE_RAW = 0x260F;
|
||||
|
||||
// Shortest time interval of 3H for storing baseline values.
|
||||
// Prevents wear of the flash because of too many write operations
|
||||
@@ -39,7 +40,7 @@ const uint32_t MAXIMUM_STORAGE_DIFF = 50;
|
||||
class SGP40Component;
|
||||
|
||||
/// This class implements support for the Sensirion sgp40 i2c GAS (VOC) sensors.
|
||||
class SGP40Component : public PollingComponent, public sensor::Sensor, public i2c::I2CDevice {
|
||||
class SGP40Component : public PollingComponent, public sensor::Sensor, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
void set_humidity_sensor(sensor::Sensor *humidity) { humidity_sensor_ = humidity; }
|
||||
void set_temperature_sensor(sensor::Sensor *temperature) { temperature_sensor_ = temperature; }
|
||||
@@ -55,11 +56,8 @@ class SGP40Component : public PollingComponent, public sensor::Sensor, public i2
|
||||
/// Input sensor for humidity and temperature compensation.
|
||||
sensor::Sensor *humidity_sensor_{nullptr};
|
||||
sensor::Sensor *temperature_sensor_{nullptr};
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
int16_t sensirion_init_sensors_();
|
||||
int16_t sgp40_probe_();
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
||||
uint64_t serial_number_;
|
||||
uint16_t featureset_;
|
||||
int32_t measure_voc_index_();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import i2c, sensor, sensirion_common
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
@@ -13,10 +13,11 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
sht3xd_ns = cg.esphome_ns.namespace("sht3xd")
|
||||
SHT3XDComponent = sht3xd_ns.class_(
|
||||
"SHT3XDComponent", cg.PollingComponent, i2c.I2CDevice
|
||||
"SHT3XDComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
|
||||
@@ -17,13 +17,8 @@ static const uint16_t SHT3XD_COMMAND_FETCH_DATA = 0xE000;
|
||||
|
||||
void SHT3XDComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up SHT3xD...");
|
||||
if (!this->write_command_(SHT3XD_COMMAND_READ_SERIAL_NUMBER)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_serial_number[2];
|
||||
if (!this->read_data_(raw_serial_number, 2)) {
|
||||
if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -45,16 +40,16 @@ float SHT3XDComponent::get_setup_priority() const { return setup_priority::DATA;
|
||||
void SHT3XDComponent::update() {
|
||||
if (this->status_has_warning()) {
|
||||
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
|
||||
this->write_command_(SHT3XD_COMMAND_SOFT_RESET);
|
||||
this->write_command(SHT3XD_COMMAND_SOFT_RESET);
|
||||
}
|
||||
if (!this->write_command_(SHT3XD_COMMAND_POLLING_H)) {
|
||||
if (!this->write_command(SHT3XD_COMMAND_POLLING_H)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[2];
|
||||
if (!this->read_data_(raw_data, 2)) {
|
||||
if (!this->read_data(raw_data, 2)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@@ -71,56 +66,5 @@ void SHT3XDComponent::update() {
|
||||
});
|
||||
}
|
||||
|
||||
bool SHT3XDComponent::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t sht_crc(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SHT3XDComponent::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sht3xd
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sht3xd {
|
||||
|
||||
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
|
||||
class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
class SHT3XDComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
@@ -19,9 +19,6 @@ class SHT3XDComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import i2c, sensor, sensirion_common
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_TEMPERATURE,
|
||||
@@ -16,10 +16,13 @@ from esphome.const import (
|
||||
|
||||
CODEOWNERS = ["@sjtrny"]
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
sht4x_ns = cg.esphome_ns.namespace("sht4x")
|
||||
|
||||
SHT4XComponent = sht4x_ns.class_("SHT4XComponent", cg.PollingComponent, i2c.I2CDevice)
|
||||
SHT4XComponent = sht4x_ns.class_(
|
||||
"SHT4XComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
CONF_PRECISION = "precision"
|
||||
SHT4XPRECISION = sht4x_ns.enum("SHT4XPRECISION")
|
||||
|
||||
@@ -50,31 +50,28 @@ void SHT4XComponent::setup() {
|
||||
void SHT4XComponent::dump_config() { LOG_I2C_DEVICE(this); }
|
||||
|
||||
void SHT4XComponent::update() {
|
||||
uint8_t cmd[] = {MEASURECOMMANDS[this->precision_]};
|
||||
|
||||
// Send command
|
||||
this->write(cmd, 1);
|
||||
this->write_command(MEASURECOMMANDS[this->precision_]);
|
||||
|
||||
this->set_timeout(10, [this]() {
|
||||
const uint8_t num_bytes = 6;
|
||||
uint8_t buffer[num_bytes];
|
||||
uint16_t buffer[2];
|
||||
|
||||
// Read measurement
|
||||
bool read_status = this->read_bytes_raw(buffer, num_bytes);
|
||||
bool read_status = this->read_data(buffer, 2);
|
||||
|
||||
if (read_status) {
|
||||
// Evaluate and publish measurements
|
||||
if (this->temp_sensor_ != nullptr) {
|
||||
// Temp is contained in the first 16 bits
|
||||
float sensor_value_temp = (buffer[0] << 8) + buffer[1];
|
||||
// Temp is contained in the first result word
|
||||
float sensor_value_temp = buffer[0];
|
||||
float temp = -45 + 175 * sensor_value_temp / 65535;
|
||||
|
||||
this->temp_sensor_->publish_state(temp);
|
||||
}
|
||||
|
||||
if (this->humidity_sensor_ != nullptr) {
|
||||
// Relative humidity is in the last 16 bits
|
||||
float sensor_value_rh = (buffer[3] << 8) + buffer[4];
|
||||
// Relative humidity is in the second result word
|
||||
float sensor_value_rh = buffer[1];
|
||||
float rh = -6 + 125 * sensor_value_rh / 65535;
|
||||
|
||||
this->humidity_sensor_->publish_state(rh);
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sht4x {
|
||||
@@ -13,7 +13,7 @@ enum SHT4XHEATERPOWER { SHT4X_HEATERPOWER_HIGH, SHT4X_HEATERPOWER_MED, SHT4X_HEA
|
||||
|
||||
enum SHT4XHEATERTIME { SHT4X_HEATERTIME_LONG = 1100, SHT4X_HEATERTIME_SHORT = 110 };
|
||||
|
||||
class SHT4XComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
class SHT4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
void setup() override;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import i2c, sensor, sensirion_common
|
||||
from esphome.const import (
|
||||
CONF_HUMIDITY,
|
||||
CONF_ID,
|
||||
@@ -13,9 +13,12 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
shtcx_ns = cg.esphome_ns.namespace("shtcx")
|
||||
SHTCXComponent = shtcx_ns.class_("SHTCXComponent", cg.PollingComponent, i2c.I2CDevice)
|
||||
SHTCXComponent = shtcx_ns.class_(
|
||||
"SHTCXComponent", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
SHTCXType = shtcx_ns.enum("SHTCXType")
|
||||
|
||||
|
||||
@@ -29,21 +29,23 @@ void SHTCXComponent::setup() {
|
||||
this->wake_up();
|
||||
this->soft_reset();
|
||||
|
||||
if (!this->write_command_(SHTCX_COMMAND_READ_ID_REGISTER)) {
|
||||
if (!this->write_command(SHTCX_COMMAND_READ_ID_REGISTER)) {
|
||||
ESP_LOGE(TAG, "Error requesting Device ID");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t device_id_register[1];
|
||||
if (!this->read_data_(device_id_register, 1)) {
|
||||
uint16_t device_id_register;
|
||||
if (!this->read_data(&device_id_register, 1)) {
|
||||
ESP_LOGE(TAG, "Error reading Device ID");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (((device_id_register[0] << 2) & 0x1C) == 0x1C) {
|
||||
if ((device_id_register[0] & 0x847) == 0x847) {
|
||||
this->sensor_id_ = device_id_register;
|
||||
|
||||
if ((device_id_register & 0x3F) == 0x07) {
|
||||
if (device_id_register & 0x800) {
|
||||
this->type_ = SHTCX_TYPE_SHTC3;
|
||||
} else {
|
||||
this->type_ = SHTCX_TYPE_SHTC1;
|
||||
@@ -51,11 +53,11 @@ void SHTCXComponent::setup() {
|
||||
} else {
|
||||
this->type_ = SHTCX_TYPE_UNKNOWN;
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Device identified: %s", to_string(this->type_));
|
||||
ESP_LOGCONFIG(TAG, " Device identified: %s (%04x)", to_string(this->type_), device_id_register);
|
||||
}
|
||||
void SHTCXComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "SHTCx:");
|
||||
ESP_LOGCONFIG(TAG, " Model: %s", to_string(this->type_));
|
||||
ESP_LOGCONFIG(TAG, " Model: %s (%04x)", to_string(this->type_), this->sensor_id_);
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with SHTCx failed!");
|
||||
@@ -74,22 +76,29 @@ void SHTCXComponent::update() {
|
||||
if (this->type_ != SHTCX_TYPE_SHTC1) {
|
||||
this->wake_up();
|
||||
}
|
||||
if (!this->write_command_(SHTCX_COMMAND_POLLING_H)) {
|
||||
if (!this->write_command(SHTCX_COMMAND_POLLING_H)) {
|
||||
ESP_LOGE(TAG, "sensor polling failed");
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(NAN);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
this->humidity_sensor_->publish_state(NAN);
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
float temperature = NAN;
|
||||
float humidity = NAN;
|
||||
uint16_t raw_data[2];
|
||||
if (!this->read_data_(raw_data, 2)) {
|
||||
if (!this->read_data(raw_data, 2)) {
|
||||
ESP_LOGE(TAG, "sensor read failed");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
} else {
|
||||
temperature = 175.0f * float(raw_data[0]) / 65536.0f - 45.0f;
|
||||
humidity = 100.0f * float(raw_data[1]) / 65536.0f;
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity);
|
||||
}
|
||||
|
||||
float temperature = 175.0f * float(raw_data[0]) / 65536.0f - 45.0f;
|
||||
float humidity = 100.0f * float(raw_data[1]) / 65536.0f;
|
||||
|
||||
ESP_LOGD(TAG, "Got temperature=%.2f°C humidity=%.2f%%", temperature, humidity);
|
||||
if (this->temperature_sensor_ != nullptr)
|
||||
this->temperature_sensor_->publish_state(temperature);
|
||||
if (this->humidity_sensor_ != nullptr)
|
||||
@@ -101,65 +110,14 @@ void SHTCXComponent::update() {
|
||||
});
|
||||
}
|
||||
|
||||
bool SHTCXComponent::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t sht_crc(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SHTCXComponent::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SHTCXComponent::soft_reset() {
|
||||
this->write_command_(SHTCX_COMMAND_SOFT_RESET);
|
||||
this->write_command(SHTCX_COMMAND_SOFT_RESET);
|
||||
delayMicroseconds(200);
|
||||
}
|
||||
void SHTCXComponent::sleep() { this->write_command_(SHTCX_COMMAND_SLEEP); }
|
||||
void SHTCXComponent::sleep() { this->write_command(SHTCX_COMMAND_SLEEP); }
|
||||
|
||||
void SHTCXComponent::wake_up() {
|
||||
this->write_command_(SHTCX_COMMAND_WAKEUP);
|
||||
this->write_command(SHTCX_COMMAND_WAKEUP);
|
||||
delayMicroseconds(200);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace shtcx {
|
||||
@@ -10,7 +10,7 @@ namespace shtcx {
|
||||
enum SHTCXType { SHTCX_TYPE_SHTC3 = 0, SHTCX_TYPE_SHTC1, SHTCX_TYPE_UNKNOWN };
|
||||
|
||||
/// This class implements support for the SHT3x-DIS family of temperature+humidity i2c sensors.
|
||||
class SHTCXComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
class SHTCXComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
|
||||
void set_humidity_sensor(sensor::Sensor *humidity_sensor) { humidity_sensor_ = humidity_sensor; }
|
||||
@@ -24,9 +24,8 @@ class SHTCXComponent : public PollingComponent, public i2c::I2CDevice {
|
||||
void wake_up();
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
SHTCXType type_;
|
||||
uint16_t sensor_id_;
|
||||
sensor::Sensor *temperature_sensor_;
|
||||
sensor::Sensor *humidity_sensor_;
|
||||
};
|
||||
|
||||
@@ -13,7 +13,6 @@ CONFIG_SCHEMA = cv.Schema(
|
||||
CONF_IMPLEMENTATION,
|
||||
esp8266=IMPLEMENTATION_LWIP_TCP,
|
||||
esp32=IMPLEMENTATION_BSD_SOCKETS,
|
||||
host=IMPLEMENTATION_BSD_SOCKETS,
|
||||
): cv.one_of(
|
||||
IMPLEMENTATION_LWIP_TCP, IMPLEMENTATION_BSD_SOCKETS, lower=True, space="_"
|
||||
),
|
||||
|
||||
@@ -119,12 +119,6 @@ struct iovec {
|
||||
#include <sys/uio.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifdef USE_HOST
|
||||
#include <netinet/in.h>
|
||||
#include <netinet/ip.h>
|
||||
#include <arpa/inet.h>
|
||||
#endif // USE_HOST
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
// arduino-esp32 declares a global var called INADDR_NONE which is replaced
|
||||
// by the define
|
||||
|
||||
1
esphome/components/sonoff_d1/__init__.py
Normal file
1
esphome/components/sonoff_d1/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
CODEOWNERS = ["@anatoly-savchenkov"]
|
||||
43
esphome/components/sonoff_d1/light.py
Normal file
43
esphome/components/sonoff_d1/light.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import uart, light
|
||||
from esphome.const import (
|
||||
CONF_OUTPUT_ID,
|
||||
CONF_MIN_VALUE,
|
||||
CONF_MAX_VALUE,
|
||||
)
|
||||
|
||||
CONF_USE_RM433_REMOTE = "use_rm433_remote"
|
||||
|
||||
DEPENDENCIES = ["uart", "light"]
|
||||
|
||||
sonoff_d1_ns = cg.esphome_ns.namespace("sonoff_d1")
|
||||
SonoffD1Output = sonoff_d1_ns.class_(
|
||||
"SonoffD1Output", cg.Component, uart.UARTDevice, light.LightOutput
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
light.BRIGHTNESS_ONLY_LIGHT_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(CONF_OUTPUT_ID): cv.declare_id(SonoffD1Output),
|
||||
cv.Optional(CONF_USE_RM433_REMOTE, default=False): cv.boolean,
|
||||
cv.Optional(CONF_MIN_VALUE, default=0): cv.int_range(min=0, max=100),
|
||||
cv.Optional(CONF_MAX_VALUE, default=100): cv.int_range(min=0, max=100),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
)
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"sonoff_d1", baud_rate=9600, require_tx=True, require_rx=True
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_OUTPUT_ID])
|
||||
await cg.register_component(var, config)
|
||||
await uart.register_uart_device(var, config)
|
||||
cg.add(var.set_use_rm433_remote(config[CONF_USE_RM433_REMOTE]))
|
||||
cg.add(var.set_min_value(config[CONF_MIN_VALUE]))
|
||||
cg.add(var.set_max_value(config[CONF_MAX_VALUE]))
|
||||
await light.register_light(var, config)
|
||||
308
esphome/components/sonoff_d1/sonoff_d1.cpp
Normal file
308
esphome/components/sonoff_d1/sonoff_d1.cpp
Normal file
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
sonoff_d1.cpp - Sonoff D1 Dimmer support for ESPHome
|
||||
|
||||
Copyright © 2021 Anatoly Savchenkov
|
||||
Copyright © 2020 Jeff Rescignano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the “Software”), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-----
|
||||
|
||||
If modifying this file, in addition to the license above, please ensure to include links back to the original code:
|
||||
https://jeffresc.dev/blog/2020-10-10
|
||||
https://github.com/JeffResc/Sonoff-D1-Dimmer
|
||||
https://github.com/arendst/Tasmota/blob/2d4a6a29ebc7153dbe2717e3615574ac1c84ba1d/tasmota/xdrv_37_sonoff_d1.ino#L119-L131
|
||||
|
||||
-----
|
||||
*/
|
||||
|
||||
/*********************************************************************************************\
|
||||
* Sonoff D1 dimmer 433
|
||||
* Mandatory/Optional
|
||||
* ^ 0 1 2 3 4 5 6 7 8 9 A B C D E F 10
|
||||
* M AA 55 - Header
|
||||
* M 01 04 - Version?
|
||||
* M 00 0A - Following data length (10 bytes)
|
||||
* O 01 - Power state (00 = off, 01 = on, FF = ignore)
|
||||
* O 64 - Dimmer percentage (01 to 64 = 1 to 100%, 0 - ignore)
|
||||
* O FF FF FF FF FF FF FF FF - Not used
|
||||
* M 6C - CRC over bytes 2 to F (Addition)
|
||||
\*********************************************************************************************/
|
||||
#include <cmath>
|
||||
#include "sonoff_d1.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sonoff_d1 {
|
||||
|
||||
static const char *const TAG = "sonoff_d1";
|
||||
|
||||
uint8_t SonoffD1Output::calc_checksum_(const uint8_t *cmd, const size_t len) {
|
||||
uint8_t crc = 0;
|
||||
for (int i = 2; i < len - 1; i++) {
|
||||
crc += cmd[i];
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void SonoffD1Output::populate_checksum_(uint8_t *cmd, const size_t len) {
|
||||
// Update the checksum
|
||||
cmd[len - 1] = this->calc_checksum_(cmd, len);
|
||||
}
|
||||
|
||||
void SonoffD1Output::skip_command_() {
|
||||
size_t garbage = 0;
|
||||
// Read out everything from the UART FIFO
|
||||
while (this->available()) {
|
||||
uint8_t value = this->read();
|
||||
ESP_LOGW(TAG, "[%04d] Skip %02d: 0x%02x from the dimmer", this->write_count_, garbage, value);
|
||||
garbage++;
|
||||
}
|
||||
|
||||
// Warn about unexpected bytes in the protocol with UART dimmer
|
||||
if (garbage)
|
||||
ESP_LOGW(TAG, "[%04d] Skip %d bytes from the dimmer", this->write_count_, garbage);
|
||||
}
|
||||
|
||||
// This assumes some data is already available
|
||||
bool SonoffD1Output::read_command_(uint8_t *cmd, size_t &len) {
|
||||
// Do consistency check
|
||||
if (cmd == nullptr || len < 7) {
|
||||
ESP_LOGW(TAG, "[%04d] Too short command buffer (actual len is %d bytes, minimal is 7)", this->write_count_, len);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read a minimal packet
|
||||
if (this->read_array(cmd, 6)) {
|
||||
ESP_LOGV(TAG, "[%04d] Reading from dimmer:", this->write_count_);
|
||||
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, 6).c_str());
|
||||
|
||||
if (cmd[0] != 0xAA || cmd[1] != 0x55) {
|
||||
ESP_LOGW(TAG, "[%04d] RX: wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]);
|
||||
this->skip_command_();
|
||||
return false;
|
||||
}
|
||||
if ((cmd[5] + 7 /*mandatory header + crc suffix length*/) > len) {
|
||||
ESP_LOGW(TAG, "[%04d] RX: Payload length is unexpected (%d, max expected %d)", this->write_count_, cmd[5],
|
||||
len - 7);
|
||||
this->skip_command_();
|
||||
return false;
|
||||
}
|
||||
if (this->read_array(&cmd[6], cmd[5] + 1 /*checksum suffix*/)) {
|
||||
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(&cmd[6], cmd[5] + 1).c_str());
|
||||
|
||||
// Check the checksum
|
||||
uint8_t valid_checksum = this->calc_checksum_(cmd, cmd[5] + 7);
|
||||
if (valid_checksum != cmd[cmd[5] + 7 - 1]) {
|
||||
ESP_LOGW(TAG, "[%04d] RX: checksum mismatch (%d, expected %d)", this->write_count_, cmd[cmd[5] + 7 - 1],
|
||||
valid_checksum);
|
||||
this->skip_command_();
|
||||
return false;
|
||||
}
|
||||
len = cmd[5] + 7 /*mandatory header + suffix length*/;
|
||||
|
||||
// Read remaining gardbled data (just in case, I don't see where this can appear now)
|
||||
this->skip_command_();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%04d] RX: feedback timeout", this->write_count_);
|
||||
this->skip_command_();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SonoffD1Output::read_ack_(const uint8_t *cmd, const size_t len) {
|
||||
// Expected acknowledgement from rf chip
|
||||
uint8_t ref_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00};
|
||||
uint8_t buffer[sizeof(ref_buffer)] = {0};
|
||||
uint32_t pos = 0, buf_len = sizeof(ref_buffer);
|
||||
|
||||
// Update the reference checksum
|
||||
this->populate_checksum_(ref_buffer, sizeof(ref_buffer));
|
||||
|
||||
// Read ack code, this either reads 7 bytes or exits with a timeout
|
||||
this->read_command_(buffer, buf_len);
|
||||
|
||||
// Compare response with expected response
|
||||
while (pos < sizeof(ref_buffer) && ref_buffer[pos] == buffer[pos]) {
|
||||
pos++;
|
||||
}
|
||||
if (pos == sizeof(ref_buffer)) {
|
||||
ESP_LOGD(TAG, "[%04d] Acknowledge received", this->write_count_);
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%04d] Unexpected acknowledge received (possible clash of RF/HA commands), expected ack was:",
|
||||
this->write_count_);
|
||||
ESP_LOGW(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(ref_buffer, sizeof(ref_buffer)).c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SonoffD1Output::write_command_(uint8_t *cmd, const size_t len, bool needs_ack) {
|
||||
// Do some consistency checks
|
||||
if (len < 7) {
|
||||
ESP_LOGW(TAG, "[%04d] Too short command (actual len is %d bytes, minimal is 7)", this->write_count_, len);
|
||||
return false;
|
||||
}
|
||||
if (cmd[0] != 0xAA || cmd[1] != 0x55) {
|
||||
ESP_LOGW(TAG, "[%04d] Wrong header (%x%x, must be AA55)", this->write_count_, cmd[0], cmd[1]);
|
||||
return false;
|
||||
}
|
||||
if ((cmd[5] + 7 /*mandatory header + suffix length*/) != len) {
|
||||
ESP_LOGW(TAG, "[%04d] Payload length field does not match packet lenght (%d, expected %d)", this->write_count_,
|
||||
cmd[5], len - 7);
|
||||
return false;
|
||||
}
|
||||
this->populate_checksum_(cmd, len);
|
||||
|
||||
// Need retries here to handle the following cases:
|
||||
// 1. On power up companion MCU starts to respond with a delay, so few first commands are ignored
|
||||
// 2. UART command initiated by this component can clash with a command initiated by RF
|
||||
uint32_t retries = 10;
|
||||
do {
|
||||
ESP_LOGV(TAG, "[%04d] Writing to the dimmer:", this->write_count_);
|
||||
ESP_LOGV(TAG, "[%04d] %s", this->write_count_, format_hex_pretty(cmd, len).c_str());
|
||||
this->write_array(cmd, len);
|
||||
this->write_count_++;
|
||||
if (!needs_ack)
|
||||
return true;
|
||||
retries--;
|
||||
} while (!this->read_ack_(cmd, len) && retries > 0);
|
||||
|
||||
if (retries) {
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "[%04d] Unable to write to the dimmer", this->write_count_);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool SonoffD1Output::control_dimmer_(const bool binary, const uint8_t brightness) {
|
||||
// Include our basic code from the Tasmota project, thank you again!
|
||||
// 0 1 2 3 4 5 6 7 8
|
||||
uint8_t cmd[17] = {0xAA, 0x55, 0x01, 0x04, 0x00, 0x0A, 0x00, 0x00, 0xFF,
|
||||
// 9 10 11 12 13 14 15 16
|
||||
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00};
|
||||
|
||||
cmd[6] = binary;
|
||||
cmd[7] = remap<uint8_t, uint8_t>(brightness, 0, 100, this->min_value_, this->max_value_);
|
||||
ESP_LOGI(TAG, "[%04d] Setting dimmer state to %s, raw brightness=%d", this->write_count_, ONOFF(binary), cmd[7]);
|
||||
return this->write_command_(cmd, sizeof(cmd));
|
||||
}
|
||||
|
||||
void SonoffD1Output::process_command_(const uint8_t *cmd, const size_t len) {
|
||||
if (cmd[2] == 0x01 && cmd[3] == 0x04 && cmd[4] == 0x00 && cmd[5] == 0x0A) {
|
||||
uint8_t ack_buffer[7] = {0xAA, 0x55, cmd[2], cmd[3], 0x00, 0x00, 0x00};
|
||||
// Ack a command from RF to ESP to prevent repeating commands
|
||||
this->write_command_(ack_buffer, sizeof(ack_buffer), false);
|
||||
ESP_LOGI(TAG, "[%04d] RF sets dimmer state to %s, raw brightness=%d", this->write_count_, ONOFF(cmd[6]), cmd[7]);
|
||||
const uint8_t new_brightness = remap<uint8_t, uint8_t>(cmd[7], this->min_value_, this->max_value_, 0, 100);
|
||||
const bool new_state = cmd[6];
|
||||
|
||||
// Got light change state command. In all cases we revert the command immediately
|
||||
// since we want to rely on ESP controlled transitions
|
||||
if (new_state != this->last_binary_ || new_brightness != this->last_brightness_) {
|
||||
this->control_dimmer_(this->last_binary_, this->last_brightness_);
|
||||
}
|
||||
|
||||
if (!this->use_rm433_remote_) {
|
||||
// If RF remote is not used, this is a known ghost RF command
|
||||
ESP_LOGI(TAG, "[%04d] Ghost command from RF detected, reverted", this->write_count_);
|
||||
} else {
|
||||
// If remote is used, initiate transition to the new state
|
||||
this->publish_state_(new_state, new_brightness);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "[%04d] Unexpected command received", this->write_count_);
|
||||
}
|
||||
}
|
||||
|
||||
void SonoffD1Output::publish_state_(const bool is_on, const uint8_t brightness) {
|
||||
if (light_state_) {
|
||||
ESP_LOGV(TAG, "Publishing new state: %s, brightness=%d", ONOFF(is_on), brightness);
|
||||
auto call = light_state_->make_call();
|
||||
call.set_state(is_on);
|
||||
if (brightness != 0) {
|
||||
// Brightness equal to 0 has a special meaning.
|
||||
// D1 uses 0 as "previously set brightness".
|
||||
// Usually zero brightness comes inside light ON command triggered by RF remote.
|
||||
// Since we unconditionally override commands coming from RF remote in process_command_(),
|
||||
// here we mimic the original behavior but with LightCall functionality
|
||||
call.set_brightness((float) brightness / 100.0f);
|
||||
}
|
||||
call.perform();
|
||||
}
|
||||
}
|
||||
|
||||
// Set the device's traits
|
||||
light::LightTraits SonoffD1Output::get_traits() {
|
||||
auto traits = light::LightTraits();
|
||||
traits.set_supported_color_modes({light::ColorMode::BRIGHTNESS});
|
||||
return traits;
|
||||
}
|
||||
|
||||
void SonoffD1Output::write_state(light::LightState *state) {
|
||||
bool binary;
|
||||
float brightness;
|
||||
|
||||
// Fill our variables with the device's current state
|
||||
state->current_values_as_binary(&binary);
|
||||
state->current_values_as_brightness(&brightness);
|
||||
|
||||
// Convert ESPHome's brightness (0-1) to the device's internal brightness (0-100)
|
||||
const uint8_t calculated_brightness = std::round(brightness * 100);
|
||||
|
||||
if (calculated_brightness == 0) {
|
||||
// if(binary) ESP_LOGD(TAG, "current_values_as_binary() returns true for zero brightness");
|
||||
binary = false;
|
||||
}
|
||||
|
||||
// If a new value, write to the dimmer
|
||||
if (binary != this->last_binary_ || calculated_brightness != this->last_brightness_) {
|
||||
if (this->control_dimmer_(binary, calculated_brightness)) {
|
||||
this->last_brightness_ = calculated_brightness;
|
||||
this->last_binary_ = binary;
|
||||
} else {
|
||||
// Return to original value if failed to write to the dimmer
|
||||
// TODO: Test me, can be tested if high-voltage part is not connected
|
||||
ESP_LOGW(TAG, "Failed to update the dimmer, publishing the previous state");
|
||||
this->publish_state_(this->last_binary_, this->last_brightness_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SonoffD1Output::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Sonoff D1 Dimmer: '%s'", this->light_state_ ? this->light_state_->get_name().c_str() : "");
|
||||
ESP_LOGCONFIG(TAG, " Use RM433 Remote: %s", ONOFF(this->use_rm433_remote_));
|
||||
ESP_LOGCONFIG(TAG, " Minimal brightness: %d", this->min_value_);
|
||||
ESP_LOGCONFIG(TAG, " Maximal brightness: %d", this->max_value_);
|
||||
}
|
||||
|
||||
void SonoffD1Output::loop() {
|
||||
// Read commands from the dimmer
|
||||
// RF chip notifies ESP about remotely changed state with the same commands as we send
|
||||
if (this->available()) {
|
||||
ESP_LOGV(TAG, "Have some UART data in loop()");
|
||||
uint8_t buffer[17] = {0};
|
||||
size_t len = sizeof(buffer);
|
||||
if (this->read_command_(buffer, len)) {
|
||||
this->process_command_(buffer, len);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sonoff_d1
|
||||
} // namespace esphome
|
||||
85
esphome/components/sonoff_d1/sonoff_d1.h
Normal file
85
esphome/components/sonoff_d1/sonoff_d1.h
Normal file
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
sonoff_d1.h - Sonoff D1 Dimmer support for ESPHome
|
||||
|
||||
Copyright © 2021 Anatoly Savchenkov
|
||||
Copyright © 2020 Jeff Rescignano
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
|
||||
and associated documentation files (the “Software”), to deal in the Software without
|
||||
restriction, including without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom
|
||||
the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or
|
||||
substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
|
||||
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
-----
|
||||
|
||||
If modifying this file, in addition to the license above, please ensure to include links back to the original code:
|
||||
https://jeffresc.dev/blog/2020-10-10
|
||||
https://github.com/JeffResc/Sonoff-D1-Dimmer
|
||||
https://github.com/arendst/Tasmota/blob/2d4a6a29ebc7153dbe2717e3615574ac1c84ba1d/tasmota/xdrv_37_sonoff_d1.ino#L119-L131
|
||||
|
||||
-----
|
||||
*/
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#include "esphome/components/light/light_output.h"
|
||||
#include "esphome/components/light/light_state.h"
|
||||
#include "esphome/components/light/light_traits.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sonoff_d1 {
|
||||
|
||||
class SonoffD1Output : public light::LightOutput, public uart::UARTDevice, public Component {
|
||||
public:
|
||||
// LightOutput methods
|
||||
light::LightTraits get_traits() override;
|
||||
void setup_state(light::LightState *state) override { this->light_state_ = state; }
|
||||
void write_state(light::LightState *state) override;
|
||||
|
||||
// Component methods
|
||||
void setup() override{};
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return esphome::setup_priority::DATA; }
|
||||
|
||||
// Custom methods
|
||||
void set_use_rm433_remote(const bool use_rm433_remote) { this->use_rm433_remote_ = use_rm433_remote; }
|
||||
void set_min_value(const uint8_t min_value) { this->min_value_ = min_value; }
|
||||
void set_max_value(const uint8_t max_value) { this->max_value_ = max_value; }
|
||||
|
||||
protected:
|
||||
uint8_t min_value_{0};
|
||||
uint8_t max_value_{100};
|
||||
bool use_rm433_remote_{false};
|
||||
bool last_binary_{false};
|
||||
uint8_t last_brightness_{0};
|
||||
int write_count_{0};
|
||||
int read_count_{0};
|
||||
light::LightState *light_state_{nullptr};
|
||||
|
||||
uint8_t calc_checksum_(const uint8_t *cmd, size_t len);
|
||||
void populate_checksum_(uint8_t *cmd, size_t len);
|
||||
void skip_command_();
|
||||
bool read_command_(uint8_t *cmd, size_t &len);
|
||||
bool read_ack_(const uint8_t *cmd, size_t len);
|
||||
bool write_command_(uint8_t *cmd, size_t len, bool needs_ack = true);
|
||||
bool control_dimmer_(bool binary, uint8_t brightness);
|
||||
void process_command_(const uint8_t *cmd, size_t len);
|
||||
void publish_state_(bool is_on, uint8_t brightness);
|
||||
};
|
||||
|
||||
} // namespace sonoff_d1
|
||||
} // namespace esphome
|
||||
@@ -1,6 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import i2c, sensor
|
||||
from esphome.components import i2c, sensor, sensirion_common
|
||||
from esphome.const import (
|
||||
CONF_ID,
|
||||
CONF_PM_1_0,
|
||||
@@ -26,9 +26,12 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
sps30_ns = cg.esphome_ns.namespace("sps30")
|
||||
SPS30Component = sps30_ns.class_("SPS30Component", cg.PollingComponent, i2c.I2CDevice)
|
||||
SPS30Component = sps30_ns.class_(
|
||||
"SPS30Component", cg.PollingComponent, sensirion_common.SensirionI2CDevice
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
|
||||
@@ -22,30 +22,18 @@ static const uint8_t MAX_SKIPPED_DATA_CYCLES_BEFORE_ERROR = 5;
|
||||
|
||||
void SPS30Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up sps30...");
|
||||
this->write_command_(SPS30_CMD_SOFT_RESET);
|
||||
this->write_command(SPS30_CMD_SOFT_RESET);
|
||||
/// Deferred Sensor initialization
|
||||
this->set_timeout(500, [this]() {
|
||||
/// Firmware version identification
|
||||
if (!this->write_command_(SPS30_CMD_GET_FIRMWARE_VERSION)) {
|
||||
this->error_code_ = FIRMWARE_VERSION_REQUEST_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->read_data_(&raw_firmware_version_, 1)) {
|
||||
if (!this->get_register(SPS30_CMD_GET_FIRMWARE_VERSION, raw_firmware_version_, 1)) {
|
||||
this->error_code_ = FIRMWARE_VERSION_READ_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
/// Serial number identification
|
||||
if (!this->write_command_(SPS30_CMD_GET_SERIAL_NUMBER)) {
|
||||
this->error_code_ = SERIAL_NUMBER_REQUEST_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_serial_number[8];
|
||||
if (!this->read_data_(raw_serial_number, 8)) {
|
||||
if (!this->get_register(SPS30_CMD_GET_SERIAL_NUMBER, raw_serial_number, 8, 1)) {
|
||||
this->error_code_ = SERIAL_NUMBER_READ_FAILED;
|
||||
this->mark_failed();
|
||||
return;
|
||||
@@ -109,7 +97,7 @@ void SPS30Component::update() {
|
||||
/// Check if warning flag active (sensor reconnected?)
|
||||
if (this->status_has_warning()) {
|
||||
ESP_LOGD(TAG, "Trying to reconnect the sensor...");
|
||||
if (this->write_command_(SPS30_CMD_SOFT_RESET)) {
|
||||
if (this->write_command(SPS30_CMD_SOFT_RESET)) {
|
||||
ESP_LOGD(TAG, "Sensor has soft-reset successfully. Waiting for reconnection in 500ms...");
|
||||
this->set_timeout(500, [this]() {
|
||||
this->start_continuous_measurement_();
|
||||
@@ -124,13 +112,13 @@ void SPS30Component::update() {
|
||||
return;
|
||||
}
|
||||
/// Check if measurement is ready before reading the value
|
||||
if (!this->write_command_(SPS30_CMD_GET_DATA_READY_STATUS)) {
|
||||
if (!this->write_command(SPS30_CMD_GET_DATA_READY_STATUS)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_read_status;
|
||||
if (!this->read_data_(&raw_read_status, 1) || raw_read_status == 0x00) {
|
||||
if (!this->read_data(&raw_read_status, 1) || raw_read_status == 0x00) {
|
||||
ESP_LOGD(TAG, "Sensor measurement not ready yet.");
|
||||
this->skipped_data_read_cycles_++;
|
||||
/// The following logic is required to address the cases when a sensor is quickly replaced before it's marked
|
||||
@@ -142,7 +130,7 @@ void SPS30Component::update() {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this->write_command_(SPS30_CMD_READ_MEASUREMENT)) {
|
||||
if (!this->write_command(SPS30_CMD_READ_MEASUREMENT)) {
|
||||
ESP_LOGW(TAG, "Error reading measurement status!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -150,7 +138,7 @@ void SPS30Component::update() {
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[20];
|
||||
if (!this->read_data_(raw_data, 20)) {
|
||||
if (!this->read_data(raw_data, 20)) {
|
||||
ESP_LOGW(TAG, "Error reading measurement data!");
|
||||
this->status_set_warning();
|
||||
return;
|
||||
@@ -205,69 +193,18 @@ void SPS30Component::update() {
|
||||
});
|
||||
}
|
||||
|
||||
bool SPS30Component::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t SPS30Component::sht_crc_(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool SPS30Component::start_continuous_measurement_() {
|
||||
uint8_t data[4];
|
||||
data[0] = SPS30_CMD_START_CONTINUOUS_MEASUREMENTS & 0xFF;
|
||||
data[1] = 0x03;
|
||||
data[2] = 0x00;
|
||||
data[3] = sht_crc_(0x03, 0x00);
|
||||
if (!this->write_bytes(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS >> 8, data, 4)) {
|
||||
if (!this->write_command(SPS30_CMD_START_CONTINUOUS_MEASUREMENTS, SPS30_CMD_START_CONTINUOUS_MEASUREMENTS_ARG)) {
|
||||
ESP_LOGE(TAG, "Error initiating measurements");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SPS30Component::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sht_crc_(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sps30
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sps30 {
|
||||
|
||||
/// This class implements support for the Sensirion SPS30 i2c/UART Particulate Matter
|
||||
/// PM1.0, PM2.5, PM4, PM10 Air Quality sensors.
|
||||
class SPS30Component : public PollingComponent, public i2c::I2CDevice {
|
||||
class SPS30Component : public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
void set_pm_1_0_sensor(sensor::Sensor *pm_1_0) { pm_1_0_sensor_ = pm_1_0; }
|
||||
void set_pm_2_5_sensor(sensor::Sensor *pm_2_5) { pm_2_5_sensor_ = pm_2_5; }
|
||||
@@ -29,9 +29,6 @@ class SPS30Component : public PollingComponent, public i2c::I2CDevice {
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
uint8_t sht_crc_(uint8_t data1, uint8_t data2);
|
||||
char serial_number_[17] = {0}; /// Terminating NULL character
|
||||
uint16_t raw_firmware_version_;
|
||||
bool start_continuous_measurement_();
|
||||
|
||||
@@ -8,6 +8,7 @@ from esphome.const import (
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
AUTO_LOAD = ["sensirion_common"]
|
||||
|
||||
sts3x_ns = cg.esphome_ns.namespace("sts3x")
|
||||
|
||||
|
||||
@@ -19,13 +19,13 @@ static const uint16_t STS3X_COMMAND_FETCH_DATA = 0xE000;
|
||||
|
||||
void STS3XComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up STS3x...");
|
||||
if (!this->write_command_(STS3X_COMMAND_READ_SERIAL_NUMBER)) {
|
||||
if (!this->write_command(STS3X_COMMAND_READ_SERIAL_NUMBER)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t raw_serial_number[2];
|
||||
if (!this->read_data_(raw_serial_number, 1)) {
|
||||
if (!this->read_data(raw_serial_number, 1)) {
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
@@ -46,16 +46,16 @@ float STS3XComponent::get_setup_priority() const { return setup_priority::DATA;
|
||||
void STS3XComponent::update() {
|
||||
if (this->status_has_warning()) {
|
||||
ESP_LOGD(TAG, "Retrying to reconnect the sensor.");
|
||||
this->write_command_(STS3X_COMMAND_SOFT_RESET);
|
||||
this->write_command(STS3X_COMMAND_SOFT_RESET);
|
||||
}
|
||||
if (!this->write_command_(STS3X_COMMAND_POLLING_H)) {
|
||||
if (!this->write_command(STS3X_COMMAND_POLLING_H)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
this->set_timeout(50, [this]() {
|
||||
uint16_t raw_data[1];
|
||||
if (!this->read_data_(raw_data, 1)) {
|
||||
if (!this->read_data(raw_data, 1)) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
@@ -67,56 +67,5 @@ void STS3XComponent::update() {
|
||||
});
|
||||
}
|
||||
|
||||
bool STS3XComponent::write_command_(uint16_t command) {
|
||||
// Warning ugly, trick the I2Ccomponent base by setting register to the first 8 bit.
|
||||
return this->write_byte(command >> 8, command & 0xFF);
|
||||
}
|
||||
|
||||
uint8_t sts3x_crc(uint8_t data1, uint8_t data2) {
|
||||
uint8_t bit;
|
||||
uint8_t crc = 0xFF;
|
||||
|
||||
crc ^= data1;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
crc ^= data2;
|
||||
for (bit = 8; bit > 0; --bit) {
|
||||
if (crc & 0x80) {
|
||||
crc = (crc << 1) ^ 0x131;
|
||||
} else {
|
||||
crc = (crc << 1);
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
bool STS3XComponent::read_data_(uint16_t *data, uint8_t len) {
|
||||
const uint8_t num_bytes = len * 3;
|
||||
std::vector<uint8_t> buf(num_bytes);
|
||||
|
||||
if (this->read(buf.data(), num_bytes) != i2c::ERROR_OK) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint8_t i = 0; i < len; i++) {
|
||||
const uint8_t j = 3 * i;
|
||||
uint8_t crc = sts3x_crc(buf[j], buf[j + 1]);
|
||||
if (crc != buf[j + 2]) {
|
||||
ESP_LOGE(TAG, "CRC8 Checksum invalid! 0x%02X != 0x%02X", buf[j + 2], crc);
|
||||
return false;
|
||||
}
|
||||
data[i] = (buf[j] << 8) | buf[j + 1];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace sts3x
|
||||
} // namespace esphome
|
||||
|
||||
@@ -2,22 +2,18 @@
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
#include "esphome/components/sensirion_common/i2c_sensirion.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace sts3x {
|
||||
|
||||
/// This class implements support for the ST3x-DIS family of temperature i2c sensors.
|
||||
class STS3XComponent : public sensor::Sensor, public PollingComponent, public i2c::I2CDevice {
|
||||
class STS3XComponent : public sensor::Sensor, public PollingComponent, public sensirion_common::SensirionI2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
|
||||
protected:
|
||||
bool write_command_(uint16_t command);
|
||||
bool read_data_(uint16_t *data, uint8_t len);
|
||||
};
|
||||
|
||||
} // namespace sts3x
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
/******************************************************************************
|
||||
/*
|
||||
sx1509_registers.h
|
||||
Register definitions for SX1509.
|
||||
Jim Lindblom @ SparkFun Electronics
|
||||
Original Creation Date: September 21, 2015
|
||||
https://github.com/sparkfun/SparkFun_SX1509_Arduino_Library
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
Here you'll find the Arduino code used to interface with the SX1509 I2C
|
||||
namespace esphome {
|
||||
/**
|
||||
Here you'll find the Arduino code used to interface with the SX1509 I2C
|
||||
16 I/O expander. There are functions to take advantage of everything the
|
||||
SX1509 provides - input/output setting, writing pins high/low, reading
|
||||
the input value of pins, LED driver utilities (blink, breath, pwm), and
|
||||
@@ -20,10 +24,7 @@ This code is beerware; if you see me (or any other SparkFun employee) at the
|
||||
local, and you've found our code helpful, please buy us a round!
|
||||
|
||||
Distributed as-is; no warranty is given.
|
||||
******************************************************************************/
|
||||
#pragma once
|
||||
|
||||
namespace esphome {
|
||||
*/
|
||||
namespace sx1509 {
|
||||
|
||||
const uint8_t REG_INPUT_DISABLE_B =
|
||||
|
||||
@@ -12,11 +12,11 @@ i2c::ErrorCode TCA9548AChannel::readv(uint8_t address, i2c::ReadBuffer *buffers,
|
||||
return err;
|
||||
return parent_->bus_->readv(address, buffers, cnt);
|
||||
}
|
||||
i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) {
|
||||
i2c::ErrorCode TCA9548AChannel::writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) {
|
||||
auto err = parent_->switch_to_channel(channel_);
|
||||
if (err != i2c::ERROR_OK)
|
||||
return err;
|
||||
return parent_->bus_->writev(address, buffers, cnt);
|
||||
return parent_->bus_->writev(address, buffers, cnt, stop);
|
||||
}
|
||||
|
||||
void TCA9548AComponent::setup() {
|
||||
|
||||
@@ -13,7 +13,7 @@ class TCA9548AChannel : public i2c::I2CBus {
|
||||
void set_parent(TCA9548AComponent *parent) { parent_ = parent; }
|
||||
|
||||
i2c::ErrorCode readv(uint8_t address, i2c::ReadBuffer *buffers, size_t cnt) override;
|
||||
i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt) override;
|
||||
i2c::ErrorCode writev(uint8_t address, i2c::WriteBuffer *buffers, size_t cnt, bool stop) override;
|
||||
|
||||
protected:
|
||||
uint8_t channel_;
|
||||
|
||||
@@ -176,6 +176,31 @@ void ESPTime::recalc_timestamp_utc(bool use_day_of_year) {
|
||||
res += this->second;
|
||||
this->timestamp = res;
|
||||
}
|
||||
|
||||
int32_t ESPTime::timezone_offset() {
|
||||
int32_t offset = 0;
|
||||
time_t now = ::time(nullptr);
|
||||
auto local = ESPTime::from_epoch_local(now);
|
||||
auto utc = ESPTime::from_epoch_utc(now);
|
||||
bool negative = utc.hour > local.hour && local.day_of_year <= utc.day_of_year;
|
||||
|
||||
if (utc.minute > local.minute) {
|
||||
local.minute += 60;
|
||||
local.hour -= 1;
|
||||
}
|
||||
offset += (local.minute - utc.minute) * 60;
|
||||
|
||||
if (negative) {
|
||||
offset -= (utc.hour - local.hour) * 3600;
|
||||
} else {
|
||||
if (utc.hour > local.hour) {
|
||||
local.hour += 24;
|
||||
}
|
||||
offset += (local.hour - utc.hour) * 3600;
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
bool ESPTime::operator<(ESPTime other) { return this->timestamp < other.timestamp; }
|
||||
bool ESPTime::operator<=(ESPTime other) { return this->timestamp <= other.timestamp; }
|
||||
bool ESPTime::operator==(ESPTime other) { return this->timestamp == other.timestamp; }
|
||||
|
||||
@@ -88,6 +88,8 @@ struct ESPTime {
|
||||
/// Convert this ESPTime instance back to a tm struct.
|
||||
struct tm to_c_tm();
|
||||
|
||||
static int32_t timezone_offset();
|
||||
|
||||
/// Increment this clock instance by one second.
|
||||
void increment_second();
|
||||
/// Increment this clock instance by one day.
|
||||
|
||||
26
esphome/components/tm1637/binary_sensor.py
Normal file
26
esphome/components/tm1637/binary_sensor.py
Normal file
@@ -0,0 +1,26 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import CONF_ID, CONF_KEY
|
||||
|
||||
CONF_TM1637_ID = "tm1637_id"
|
||||
|
||||
tm1637_ns = cg.esphome_ns.namespace("tm1637")
|
||||
TM1637Display = tm1637_ns.class_("TM1637Display", cg.PollingComponent)
|
||||
TM1637Key = tm1637_ns.class_("TM1637Key", binary_sensor.BinarySensor)
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.BINARY_SENSOR_SCHEMA.extend(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(TM1637Key),
|
||||
cv.GenerateID(CONF_TM1637_ID): cv.use_id(TM1637Display),
|
||||
cv.Required(CONF_KEY): cv.int_range(min=0, max=15),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await binary_sensor.register_binary_sensor(var, config)
|
||||
cg.add(var.set_keycode(config[CONF_KEY]))
|
||||
hub = await cg.get_variable(config[CONF_TM1637_ID])
|
||||
cg.add(hub.add_tm1637_key(var))
|
||||
@@ -7,11 +7,17 @@ namespace esphome {
|
||||
namespace tm1637 {
|
||||
|
||||
static const char *const TAG = "display.tm1637";
|
||||
const uint8_t TM1637_I2C_COMM1 = 0x40;
|
||||
const uint8_t TM1637_I2C_COMM2 = 0xC0;
|
||||
const uint8_t TM1637_I2C_COMM3 = 0x80;
|
||||
const uint8_t TM1637_CMD_DATA = 0x40; //!< Display data command
|
||||
const uint8_t TM1637_CMD_CTRL = 0x80; //!< Display control command
|
||||
const uint8_t TM1637_CMD_ADDR = 0xc0; //!< Display address command
|
||||
const uint8_t TM1637_UNKNOWN_CHAR = 0b11111111;
|
||||
|
||||
// Data command bits
|
||||
const uint8_t TM1637_DATA_WRITE = 0x00; //!< Write data
|
||||
const uint8_t TM1637_DATA_READ_KEYS = 0x02; //!< Read keys
|
||||
const uint8_t TM1637_DATA_AUTO_INC_ADDR = 0x00; //!< Auto increment address
|
||||
const uint8_t TM1637_DATA_FIXED_ADDR = 0x04; //!< Fixed address
|
||||
|
||||
//
|
||||
// A
|
||||
// ---
|
||||
@@ -138,6 +144,36 @@ void TM1637Display::dump_config() {
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void TM1637Display::loop() {
|
||||
uint8_t val = this->get_keys();
|
||||
for (auto *tm1637_key : this->tm1637_keys_)
|
||||
tm1637_key->process(val);
|
||||
}
|
||||
|
||||
uint8_t TM1637Display::get_keys() {
|
||||
this->start_();
|
||||
this->send_byte_(TM1637_CMD_DATA | TM1637_DATA_READ_KEYS);
|
||||
this->start_();
|
||||
uint8_t key_code = read_byte_();
|
||||
this->stop_();
|
||||
if (key_code != 0xFF) {
|
||||
// Invert key_code:
|
||||
// Bit | 7 6 5 4 3 2 1 0
|
||||
// ------+-------------------------
|
||||
// From | S0 S1 S2 K1 K2 1 1 1
|
||||
// To | S0 S1 S2 K1 K2 0 0 0
|
||||
key_code = ~key_code;
|
||||
// Shift bits to:
|
||||
// Bit | 7 6 5 4 3 2 1 0
|
||||
// ------+------------------------
|
||||
// To | 0 0 0 0 K2 S2 S1 S0
|
||||
key_code = (uint8_t)((key_code & 0x80) >> 7 | (key_code & 0x40) >> 5 | (key_code & 0x20) >> 3 | (key_code & 0x08));
|
||||
}
|
||||
return key_code;
|
||||
}
|
||||
#endif
|
||||
|
||||
void TM1637Display::update() {
|
||||
for (uint8_t &i : this->buffer_)
|
||||
i = 0;
|
||||
@@ -165,14 +201,14 @@ void TM1637Display::stop_() {
|
||||
void TM1637Display::display() {
|
||||
ESP_LOGVV(TAG, "Display %02X%02X%02X%02X", buffer_[0], buffer_[1], buffer_[2], buffer_[3]);
|
||||
|
||||
// Write COMM1
|
||||
// Write DATA CMND
|
||||
this->start_();
|
||||
this->send_byte_(TM1637_I2C_COMM1);
|
||||
this->send_byte_(TM1637_CMD_DATA);
|
||||
this->stop_();
|
||||
|
||||
// Write COMM2 + first digit address
|
||||
// Write ADDR CMD + first digit address
|
||||
this->start_();
|
||||
this->send_byte_(TM1637_I2C_COMM2);
|
||||
this->send_byte_(TM1637_CMD_ADDR);
|
||||
|
||||
// Write the data bytes
|
||||
if (this->inverted_) {
|
||||
@@ -187,20 +223,17 @@ void TM1637Display::display() {
|
||||
|
||||
this->stop_();
|
||||
|
||||
// Write COMM3 + brightness
|
||||
// Write display CTRL CMND + brightness
|
||||
this->start_();
|
||||
this->send_byte_(TM1637_I2C_COMM3 + ((this->intensity_ & 0x7) | 0x08));
|
||||
this->send_byte_(TM1637_CMD_CTRL + ((this->intensity_ & 0x7) | 0x08));
|
||||
this->stop_();
|
||||
}
|
||||
bool TM1637Display::send_byte_(uint8_t b) {
|
||||
uint8_t data = b;
|
||||
|
||||
// 8 Data Bits
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
// CLK low
|
||||
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->bit_delay_();
|
||||
|
||||
// Set data bit
|
||||
if (data & 0x01) {
|
||||
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
@@ -209,19 +242,16 @@ bool TM1637Display::send_byte_(uint8_t b) {
|
||||
}
|
||||
|
||||
this->bit_delay_();
|
||||
|
||||
// CLK high
|
||||
this->clk_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->bit_delay_();
|
||||
data = data >> 1;
|
||||
}
|
||||
|
||||
// Wait for acknowledge
|
||||
// CLK to zero
|
||||
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->bit_delay_();
|
||||
|
||||
// CLK to high
|
||||
this->clk_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->bit_delay_();
|
||||
@@ -237,8 +267,38 @@ bool TM1637Display::send_byte_(uint8_t b) {
|
||||
return ack;
|
||||
}
|
||||
|
||||
uint8_t TM1637Display::read_byte_() {
|
||||
uint8_t retval = 0;
|
||||
// Prepare DIO to read data
|
||||
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->bit_delay_();
|
||||
// Data is shifted out by the TM1637 on the CLK falling edge
|
||||
for (uint8_t bit = 0; bit < 8; bit++) {
|
||||
this->clk_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->bit_delay_();
|
||||
// Read next bit
|
||||
retval <<= 1;
|
||||
if (this->dio_pin_->digital_read()) {
|
||||
retval |= 0x01;
|
||||
}
|
||||
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->bit_delay_();
|
||||
}
|
||||
// Return DIO to output mode
|
||||
// Dummy ACK
|
||||
this->dio_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->bit_delay_();
|
||||
this->clk_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->bit_delay_();
|
||||
this->clk_pin_->pin_mode(gpio::FLAG_OUTPUT);
|
||||
this->bit_delay_();
|
||||
this->dio_pin_->pin_mode(gpio::FLAG_INPUT);
|
||||
this->bit_delay_();
|
||||
return retval;
|
||||
}
|
||||
|
||||
uint8_t TM1637Display::print(uint8_t start_pos, const char *str) {
|
||||
ESP_LOGV(TAG, "Print at %d: %s", start_pos, str);
|
||||
// ESP_LOGV(TAG, "Print at %d: %s", start_pos, str);
|
||||
uint8_t pos = start_pos;
|
||||
for (; *str != '\0'; str++) {
|
||||
uint8_t data = TM1637_UNKNOWN_CHAR;
|
||||
|
||||
@@ -8,10 +8,17 @@
|
||||
#include "esphome/components/time/real_time_clock.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace tm1637 {
|
||||
|
||||
class TM1637Display;
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
class TM1637Key;
|
||||
#endif
|
||||
|
||||
using tm1637_writer_t = std::function<void(TM1637Display &)>;
|
||||
|
||||
@@ -46,10 +53,15 @@ class TM1637Display : public PollingComponent {
|
||||
|
||||
void display();
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void loop() override;
|
||||
uint8_t get_keys();
|
||||
void add_tm1637_key(TM1637Key *tm1637_key) { this->tm1637_keys_.push_back(tm1637_key); }
|
||||
#endif
|
||||
|
||||
#ifdef USE_TIME
|
||||
/// Evaluate the strftime-format and print the result at the given position.
|
||||
uint8_t strftime(uint8_t pos, const char *format, time::ESPTime time) __attribute__((format(strftime, 3, 0)));
|
||||
|
||||
/// Evaluate the strftime-format and print the result at position 0.
|
||||
uint8_t strftime(const char *format, time::ESPTime time) __attribute__((format(strftime, 2, 0)));
|
||||
#endif
|
||||
@@ -58,6 +70,7 @@ class TM1637Display : public PollingComponent {
|
||||
void bit_delay_();
|
||||
void setup_pins_();
|
||||
bool send_byte_(uint8_t b);
|
||||
uint8_t read_byte_();
|
||||
void start_();
|
||||
void stop_();
|
||||
|
||||
@@ -68,7 +81,23 @@ class TM1637Display : public PollingComponent {
|
||||
bool inverted_;
|
||||
optional<tm1637_writer_t> writer_{};
|
||||
uint8_t buffer_[6] = {0};
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
std::vector<TM1637Key *> tm1637_keys_{};
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
class TM1637Key : public binary_sensor::BinarySensor {
|
||||
friend class TM1637Display;
|
||||
|
||||
public:
|
||||
void set_keycode(uint8_t key_code) { key_code_ = key_code; }
|
||||
void process(uint8_t data) { this->publish_state(static_cast<bool>(data == this->key_code_)); }
|
||||
|
||||
protected:
|
||||
uint8_t key_code_{0};
|
||||
};
|
||||
#endif
|
||||
|
||||
} // namespace tm1637
|
||||
} // namespace esphome
|
||||
|
||||
@@ -4,6 +4,7 @@ import esphome.codegen as cg
|
||||
from esphome.components import display
|
||||
from esphome import automation
|
||||
from esphome.const import CONF_ON_TOUCH
|
||||
from esphome.core import coroutine_with_priority
|
||||
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["display"]
|
||||
@@ -39,3 +40,9 @@ async def register_touchscreen(var, config):
|
||||
[(TouchPoint, "touch")],
|
||||
config[CONF_ON_TOUCH],
|
||||
)
|
||||
|
||||
|
||||
@coroutine_with_priority(100.0)
|
||||
async def to_code(config):
|
||||
cg.add_global(touchscreen_ns.using)
|
||||
cg.add_define("USE_TOUCHSCREEN")
|
||||
|
||||
@@ -14,6 +14,8 @@ from esphome.const import (
|
||||
CONF_PASSWORD,
|
||||
CONF_INCLUDE_INTERNAL,
|
||||
CONF_OTA,
|
||||
CONF_VERSION,
|
||||
CONF_LOCAL,
|
||||
)
|
||||
from esphome.core import CORE, coroutine_with_priority
|
||||
|
||||
@@ -22,18 +24,37 @@ AUTO_LOAD = ["json", "web_server_base"]
|
||||
web_server_ns = cg.esphome_ns.namespace("web_server")
|
||||
WebServer = web_server_ns.class_("WebServer", cg.Component, cg.Controller)
|
||||
|
||||
|
||||
def default_url(config):
|
||||
config = config.copy()
|
||||
if config[CONF_VERSION] == 1:
|
||||
if not (CONF_CSS_URL in config):
|
||||
config[CONF_CSS_URL] = "https://esphome.io/_static/webserver-v1.min.css"
|
||||
if not (CONF_JS_URL in config):
|
||||
config[CONF_JS_URL] = "https://esphome.io/_static/webserver-v1.min.js"
|
||||
if config[CONF_VERSION] == 2:
|
||||
if not (CONF_CSS_URL in config):
|
||||
config[CONF_CSS_URL] = ""
|
||||
if not (CONF_JS_URL in config):
|
||||
config[CONF_JS_URL] = "https://oi.esphome.io/v2/www.js"
|
||||
return config
|
||||
|
||||
|
||||
def validate_local(config):
|
||||
if CONF_LOCAL in config and config[CONF_VERSION] == 1:
|
||||
raise cv.Invalid("'local' is not supported in version 1")
|
||||
return config
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(WebServer),
|
||||
cv.Optional(CONF_PORT, default=80): cv.port,
|
||||
cv.Optional(
|
||||
CONF_CSS_URL, default="https://esphome.io/_static/webserver-v1.min.css"
|
||||
): cv.string,
|
||||
cv.Optional(CONF_VERSION, default=2): cv.one_of(1, 2),
|
||||
cv.Optional(CONF_CSS_URL): cv.string,
|
||||
cv.Optional(CONF_CSS_INCLUDE): cv.file_,
|
||||
cv.Optional(
|
||||
CONF_JS_URL, default="https://esphome.io/_static/webserver-v1.min.js"
|
||||
): cv.string,
|
||||
cv.Optional(CONF_JS_URL): cv.string,
|
||||
cv.Optional(CONF_JS_INCLUDE): cv.file_,
|
||||
cv.Optional(CONF_AUTH): cv.Schema(
|
||||
{
|
||||
@@ -50,9 +71,12 @@ CONFIG_SCHEMA = cv.All(
|
||||
),
|
||||
cv.Optional(CONF_INCLUDE_INTERNAL, default=False): cv.boolean,
|
||||
cv.Optional(CONF_OTA, default=True): cv.boolean,
|
||||
},
|
||||
cv.Optional(CONF_LOCAL): cv.boolean,
|
||||
}
|
||||
).extend(cv.COMPONENT_SCHEMA),
|
||||
cv.only_with_arduino,
|
||||
default_url,
|
||||
validate_local,
|
||||
)
|
||||
|
||||
|
||||
@@ -68,6 +92,7 @@ async def to_code(config):
|
||||
cg.add(paren.set_port(config[CONF_PORT]))
|
||||
cg.add_define("USE_WEBSERVER")
|
||||
cg.add_define("USE_WEBSERVER_PORT", config[CONF_PORT])
|
||||
cg.add_define("USE_WEBSERVER_VERSION", config[CONF_VERSION])
|
||||
cg.add(var.set_css_url(config[CONF_CSS_URL]))
|
||||
cg.add(var.set_js_url(config[CONF_JS_URL]))
|
||||
cg.add(var.set_allow_ota(config[CONF_OTA]))
|
||||
@@ -75,13 +100,15 @@ async def to_code(config):
|
||||
cg.add(paren.set_auth_username(config[CONF_AUTH][CONF_USERNAME]))
|
||||
cg.add(paren.set_auth_password(config[CONF_AUTH][CONF_PASSWORD]))
|
||||
if CONF_CSS_INCLUDE in config:
|
||||
cg.add_define("WEBSERVER_CSS_INCLUDE")
|
||||
cg.add_define("USE_WEBSERVER_CSS_INCLUDE")
|
||||
path = CORE.relative_config_path(config[CONF_CSS_INCLUDE])
|
||||
with open(file=path, encoding="utf-8") as myfile:
|
||||
cg.add(var.set_css_include(myfile.read()))
|
||||
if CONF_JS_INCLUDE in config:
|
||||
cg.add_define("WEBSERVER_JS_INCLUDE")
|
||||
cg.add_define("USE_WEBSERVER_JS_INCLUDE")
|
||||
path = CORE.relative_config_path(config[CONF_JS_INCLUDE])
|
||||
with open(file=path, encoding="utf-8") as myfile:
|
||||
cg.add(var.set_js_include(myfile.read()))
|
||||
cg.add(var.set_include_internal(config[CONF_INCLUDE_INTERNAL]))
|
||||
if CONF_LOCAL in config and config[CONF_LOCAL]:
|
||||
cg.add_define("USE_WEBSERVER_LOCAL")
|
||||
|
||||
97
esphome/components/web_server/list_entities.cpp
Normal file
97
esphome/components/web_server/list_entities.cpp
Normal file
@@ -0,0 +1,97 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "list_entities.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/util.h"
|
||||
|
||||
#include "web_server.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server {
|
||||
|
||||
ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_(web_server) {}
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
|
||||
this->web_server_->events_.send(
|
||||
this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
|
||||
this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
|
||||
this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool ListEntitiesIterator::on_light(light::LightState *light) {
|
||||
this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
|
||||
this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
|
||||
this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(),
|
||||
"state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool ListEntitiesIterator::on_button(button::Button *button) {
|
||||
this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
|
||||
this->web_server_->events_.send(
|
||||
this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_LOCK
|
||||
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
|
||||
this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
|
||||
this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
bool ListEntitiesIterator::on_number(number::Number *number) {
|
||||
this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
bool ListEntitiesIterator::on_select(select::Select *select) {
|
||||
this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state");
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
60
esphome/components/web_server/list_entities.h
Normal file
60
esphome/components/web_server/list_entities.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/component_iterator.h"
|
||||
#include "esphome/core/defines.h"
|
||||
namespace esphome {
|
||||
namespace web_server {
|
||||
|
||||
class WebServer;
|
||||
|
||||
class ListEntitiesIterator : public ComponentIterator {
|
||||
public:
|
||||
ListEntitiesIterator(WebServer *web_server);
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
bool on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) override;
|
||||
#endif
|
||||
#ifdef USE_COVER
|
||||
bool on_cover(cover::Cover *cover) override;
|
||||
#endif
|
||||
#ifdef USE_FAN
|
||||
bool on_fan(fan::Fan *fan) override;
|
||||
#endif
|
||||
#ifdef USE_LIGHT
|
||||
bool on_light(light::LightState *light) override;
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
bool on_sensor(sensor::Sensor *sensor) override;
|
||||
#endif
|
||||
#ifdef USE_SWITCH
|
||||
bool on_switch(switch_::Switch *a_switch) override;
|
||||
#endif
|
||||
#ifdef USE_BUTTON
|
||||
bool on_button(button::Button *button) override;
|
||||
#endif
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
bool on_text_sensor(text_sensor::TextSensor *text_sensor) override;
|
||||
#endif
|
||||
#ifdef USE_CLIMATE
|
||||
bool on_climate(climate::Climate *climate) override;
|
||||
#endif
|
||||
#ifdef USE_NUMBER
|
||||
bool on_number(number::Number *number) override;
|
||||
#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:
|
||||
WebServer *web_server_;
|
||||
};
|
||||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
|
||||
#endif // USE_ARDUINO
|
||||
573
esphome/components/web_server/server_index.h
Normal file
573
esphome/components/web_server/server_index.h
Normal file
@@ -0,0 +1,573 @@
|
||||
#pragma once
|
||||
// Generated from https://github.com/esphome/esphome-webserver
|
||||
#include "esphome/core/hal.h"
|
||||
namespace esphome {
|
||||
|
||||
namespace web_server {
|
||||
|
||||
const uint8_t INDEX_GZ[] PROGMEM = {
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xbd, 0x7d, 0xd9, 0x76, 0xe3, 0xc6, 0x92, 0xe0, 0xf3,
|
||||
0x9c, 0x33, 0x7f, 0x30, 0x2f, 0x10, 0xac, 0x56, 0x01, 0x16, 0x08, 0x91, 0x54, 0x6d, 0x06, 0x05, 0xf2, 0xca, 0x55,
|
||||
0xe5, 0x5b, 0x65, 0xd7, 0xe6, 0x92, 0xaa, 0xbc, 0xc8, 0x74, 0x09, 0x22, 0x93, 0x22, 0x5c, 0x20, 0x40, 0x03, 0x49,
|
||||
0x2d, 0xa6, 0xd0, 0xa7, 0x9f, 0xfa, 0x69, 0xce, 0x99, 0xf5, 0xa1, 0x5f, 0xe6, 0xf4, 0xcb, 0x7c, 0xc4, 0x7c, 0xcf,
|
||||
0xfd, 0x81, 0xe9, 0x4f, 0x98, 0x88, 0xc8, 0x05, 0x09, 0x90, 0x5a, 0xec, 0xf6, 0xdc, 0x53, 0x8b, 0x80, 0x5c, 0x23,
|
||||
0x23, 0x23, 0x63, 0x4f, 0x68, 0x6f, 0x63, 0x9c, 0x8d, 0xf8, 0xe5, 0x9c, 0x59, 0x53, 0x3e, 0x4b, 0xfa, 0x7b, 0xf2,
|
||||
0x7f, 0x16, 0x8d, 0xfb, 0x7b, 0x49, 0x9c, 0x7e, 0xb2, 0x72, 0x96, 0x84, 0xf1, 0x28, 0x4b, 0xad, 0x69, 0xce, 0x26,
|
||||
0xe1, 0x38, 0xe2, 0x51, 0x10, 0xcf, 0xa2, 0x53, 0x66, 0xed, 0xf4, 0xf7, 0x66, 0x8c, 0x47, 0xd6, 0x68, 0x1a, 0xe5,
|
||||
0x05, 0xe3, 0xe1, 0xfb, 0xc3, 0xaf, 0x5a, 0x8f, 0xfb, 0x7b, 0xc5, 0x28, 0x8f, 0xe7, 0xdc, 0xc2, 0x21, 0xc3, 0x59,
|
||||
0x36, 0x5e, 0x24, 0xac, 0x7f, 0x16, 0xe5, 0xd6, 0x3e, 0x0b, 0xdf, 0x9c, 0xfc, 0xc2, 0x46, 0xdc, 0x1f, 0xb3, 0x49,
|
||||
0x9c, 0xb2, 0xb7, 0x79, 0x36, 0x67, 0x39, 0xbf, 0xf4, 0x2e, 0xd6, 0x57, 0xc4, 0xac, 0xf0, 0x9e, 0xe9, 0xaa, 0x53,
|
||||
0xc6, 0xdf, 0x9c, 0xa7, 0xaa, 0xcf, 0x53, 0x26, 0x26, 0xc9, 0xf2, 0xc2, 0xe3, 0xd7, 0xb4, 0x39, 0xb8, 0x9c, 0x9d,
|
||||
0x64, 0x49, 0xe1, 0x1d, 0xe8, 0xfa, 0x79, 0x9e, 0xf1, 0x0c, 0xc1, 0xf2, 0xa7, 0x51, 0x61, 0xb4, 0xf4, 0x3e, 0xad,
|
||||
0x69, 0x32, 0x97, 0x95, 0x2f, 0x8a, 0x67, 0xe9, 0x62, 0xc6, 0xf2, 0xe8, 0x24, 0x61, 0x5e, 0xc1, 0x42, 0x87, 0x79,
|
||||
0xdc, 0x8b, 0xdd, 0xb0, 0xcf, 0xad, 0x38, 0xb5, 0xd8, 0x60, 0x9f, 0x51, 0xc9, 0x92, 0xe9, 0x56, 0xc1, 0x46, 0xdb,
|
||||
0x03, 0x74, 0x4d, 0xe2, 0xd3, 0x85, 0x7e, 0x3f, 0xcf, 0x63, 0xae, 0x9e, 0xcf, 0xa2, 0x64, 0xc1, 0x82, 0xb8, 0x74,
|
||||
0x03, 0x76, 0xc4, 0x87, 0x61, 0xec, 0x3d, 0xa1, 0x41, 0x61, 0xc8, 0xe5, 0x24, 0xcb, 0x1d, 0xc4, 0x55, 0x8c, 0x63,
|
||||
0xf3, 0xab, 0x2b, 0x87, 0x87, 0xcb, 0xd2, 0x75, 0x0f, 0x98, 0x3f, 0x8a, 0x92, 0xc4, 0xc1, 0x89, 0xb7, 0xb6, 0x0a,
|
||||
0x9c, 0x31, 0xf6, 0xf8, 0x51, 0x3c, 0x74, 0x7b, 0xf1, 0xc4, 0xe1, 0xcc, 0xad, 0xfa, 0x65, 0x13, 0x8b, 0x33, 0x87,
|
||||
0xbb, 0xee, 0xa7, 0xeb, 0xfb, 0xe4, 0x8c, 0x2f, 0x72, 0x80, 0xbd, 0xf4, 0xde, 0xa8, 0x99, 0x2f, 0xb0, 0xfe, 0x19,
|
||||
0x75, 0xec, 0x01, 0xec, 0x05, 0xb7, 0x3e, 0x84, 0xe7, 0x71, 0x3a, 0xce, 0xce, 0xfd, 0x83, 0x69, 0x04, 0x3f, 0xde,
|
||||
0x65, 0x19, 0xdf, 0xda, 0x72, 0xce, 0xb2, 0x78, 0x6c, 0xb5, 0xc3, 0xd0, 0xac, 0xbc, 0x7c, 0x72, 0x70, 0x70, 0x75,
|
||||
0xd5, 0x28, 0xf0, 0xd3, 0x88, 0xc7, 0x67, 0x4c, 0x74, 0x06, 0x00, 0x6c, 0xf8, 0x39, 0xe7, 0x6c, 0x7c, 0xc0, 0x2f,
|
||||
0x13, 0x28, 0x65, 0x8c, 0x17, 0x36, 0xac, 0xf1, 0x69, 0x36, 0x02, 0xb4, 0xa5, 0x06, 0xe2, 0xa1, 0x69, 0xce, 0xe6,
|
||||
0x49, 0x34, 0x62, 0x58, 0x0f, 0x23, 0x55, 0x3d, 0xaa, 0x46, 0xde, 0x57, 0xa1, 0xd8, 0x5e, 0xc7, 0xf5, 0x62, 0x16,
|
||||
0xa6, 0xec, 0xdc, 0x7a, 0x15, 0xcd, 0x7b, 0xa3, 0x24, 0x2a, 0x0a, 0xa0, 0xd7, 0x25, 0x2d, 0x21, 0x5f, 0x8c, 0x80,
|
||||
0x40, 0x68, 0x81, 0x4b, 0x44, 0xd3, 0x34, 0x2e, 0xfc, 0x8f, 0x9b, 0xa3, 0xa2, 0x78, 0xc7, 0x8a, 0x45, 0xc2, 0x37,
|
||||
0x43, 0xd8, 0x0b, 0xbe, 0x11, 0x86, 0x5f, 0xb9, 0x7c, 0x9a, 0x67, 0xe7, 0xd6, 0xb3, 0x3c, 0x87, 0xe6, 0x36, 0x4c,
|
||||
0x29, 0x1a, 0x58, 0x71, 0x61, 0xa5, 0x19, 0xb7, 0xf4, 0x60, 0xb8, 0x81, 0xbe, 0xf5, 0xbe, 0x60, 0xd6, 0xf1, 0x22,
|
||||
0x2d, 0xa2, 0x09, 0x83, 0xa6, 0xc7, 0x56, 0x96, 0x5b, 0xc7, 0x30, 0xe8, 0x31, 0x6c, 0x59, 0xc1, 0xe1, 0xd4, 0xf8,
|
||||
0xb6, 0xdb, 0xa3, 0xb9, 0xa0, 0xf0, 0x90, 0x5d, 0xf0, 0x90, 0x95, 0x40, 0x98, 0x56, 0xa1, 0x97, 0xe1, 0xb8, 0xcb,
|
||||
0x04, 0x0a, 0x58, 0x18, 0x33, 0x24, 0x59, 0xc7, 0x6c, 0xac, 0x37, 0xe7, 0xc3, 0xd6, 0x96, 0xc6, 0x35, 0xe0, 0xc4,
|
||||
0x81, 0xb6, 0x45, 0xa3, 0xad, 0x27, 0x16, 0x5e, 0x43, 0x91, 0xeb, 0x31, 0x5f, 0xa2, 0xef, 0xe0, 0x32, 0x1d, 0xd5,
|
||||
0xc7, 0x86, 0xca, 0x92, 0x67, 0x07, 0x3c, 0x8f, 0xd3, 0x53, 0x00, 0x42, 0xce, 0x64, 0x36, 0x29, 0x4b, 0xb1, 0xf9,
|
||||
0x4f, 0x58, 0xc8, 0xc2, 0x3e, 0x8e, 0x9e, 0x33, 0xc7, 0x2e, 0xa8, 0x87, 0x1d, 0x86, 0x88, 0x7a, 0x20, 0x30, 0x36,
|
||||
0x60, 0x01, 0xdb, 0xb6, 0x6d, 0xef, 0x2b, 0xd7, 0x3b, 0x47, 0x0a, 0xf2, 0x7d, 0x9f, 0xc8, 0x57, 0x74, 0x8e, 0xc3,
|
||||
0x0e, 0x02, 0xed, 0x27, 0x2c, 0x3d, 0xe5, 0xd3, 0x01, 0x3b, 0x6a, 0x0f, 0x03, 0x0e, 0x50, 0x8d, 0x17, 0x23, 0xe6,
|
||||
0x20, 0x3d, 0x7a, 0x05, 0x1e, 0x9f, 0x6d, 0x07, 0xa6, 0xc0, 0x8d, 0xd9, 0xa0, 0x35, 0xd6, 0xb6, 0xc6, 0x55, 0x24,
|
||||
0xaa, 0x00, 0x43, 0x3a, 0xb7, 0xe1, 0x84, 0x9d, 0xb0, 0xdc, 0x80, 0x43, 0x37, 0xeb, 0xd5, 0x76, 0x70, 0x01, 0x3b,
|
||||
0x04, 0xfd, 0xac, 0xc9, 0x22, 0x1d, 0xf1, 0x18, 0x18, 0x97, 0xbd, 0x0d, 0xe0, 0x8a, 0x9d, 0xd3, 0x1b, 0x67, 0xbb,
|
||||
0xa5, 0xeb, 0xc4, 0xee, 0x36, 0x3b, 0x2a, 0xb6, 0x3b, 0x43, 0x0f, 0xa1, 0xd4, 0xc8, 0x97, 0x0b, 0x8f, 0x61, 0x81,
|
||||
0x70, 0x46, 0x98, 0x3e, 0x9e, 0x1f, 0x06, 0xcc, 0x5f, 0xa5, 0xe3, 0x90, 0xfb, 0xb3, 0x68, 0x8e, 0xab, 0x61, 0x44,
|
||||
0x03, 0x51, 0x3a, 0x42, 0xe8, 0x6a, 0xfb, 0x82, 0x18, 0xf3, 0x2b, 0x12, 0x70, 0x01, 0x21, 0x70, 0x66, 0x9f, 0x45,
|
||||
0xa3, 0x29, 0x1c, 0xf1, 0x0a, 0x71, 0x63, 0x75, 0x1c, 0x46, 0x39, 0x8b, 0x38, 0x7b, 0x96, 0x30, 0x7c, 0xc3, 0x1d,
|
||||
0x80, 0x9e, 0xb6, 0xeb, 0x15, 0xea, 0xdc, 0x25, 0x31, 0x7f, 0x9d, 0xc1, 0x3c, 0x3d, 0x41, 0x24, 0x40, 0xc5, 0xc5,
|
||||
0xd6, 0x56, 0x8c, 0x24, 0xb2, 0xcf, 0x61, 0xb7, 0x4e, 0x16, 0xc0, 0x04, 0xec, 0x14, 0x5b, 0xd8, 0x80, 0x6d, 0x2f,
|
||||
0xf6, 0x39, 0x20, 0xf1, 0x49, 0x96, 0x72, 0x18, 0x0e, 0xe0, 0xd5, 0x14, 0xe4, 0x47, 0xf3, 0x39, 0x4b, 0xc7, 0x4f,
|
||||
0xa6, 0x71, 0x32, 0x06, 0x6c, 0x94, 0xb0, 0xde, 0x8c, 0x85, 0xb0, 0x4e, 0x58, 0x4c, 0x70, 0xf3, 0x8a, 0x68, 0xfb,
|
||||
0x90, 0x90, 0x79, 0x68, 0xdb, 0x3d, 0xe4, 0x40, 0x72, 0x15, 0xc8, 0x83, 0x68, 0xe3, 0xde, 0x01, 0xeb, 0x2f, 0x5c,
|
||||
0xbe, 0x1d, 0xc6, 0x7a, 0x1b, 0x25, 0x82, 0x9f, 0x20, 0xa7, 0x01, 0xfc, 0x33, 0xe0, 0x81, 0x3d, 0x64, 0x5c, 0xdf,
|
||||
0x49, 0xae, 0x93, 0x32, 0xb5, 0x42, 0x40, 0xc0, 0x08, 0x39, 0x88, 0xc4, 0xc1, 0xdb, 0x2c, 0xb9, 0x9c, 0xc4, 0x49,
|
||||
0x72, 0xb0, 0x98, 0xcf, 0xb3, 0x9c, 0x7b, 0x5f, 0x87, 0x4b, 0x9e, 0x55, 0x6b, 0xa5, 0x43, 0x5e, 0x9c, 0xc7, 0x1c,
|
||||
0x11, 0xea, 0x2e, 0x47, 0x11, 0x6c, 0xf5, 0x97, 0x59, 0x96, 0xb0, 0x28, 0x85, 0x65, 0xb0, 0x81, 0x6d, 0x07, 0xe9,
|
||||
0x22, 0x49, 0x7a, 0x27, 0x30, 0xec, 0xa7, 0x1e, 0x55, 0x0b, 0x8e, 0x1f, 0xd0, 0xf3, 0x7e, 0x9e, 0x47, 0x97, 0xd0,
|
||||
0x10, 0xdb, 0x00, 0x2d, 0xc2, 0x6e, 0x7d, 0x7d, 0xf0, 0xe6, 0xb5, 0x2f, 0x08, 0x3f, 0x9e, 0x5c, 0x02, 0xa0, 0x65,
|
||||
0xc5, 0x35, 0x27, 0x79, 0x36, 0x6b, 0x4c, 0x8d, 0x78, 0x88, 0x43, 0xd6, 0xbb, 0x06, 0x84, 0x98, 0x46, 0x86, 0x5d,
|
||||
0x62, 0x26, 0x04, 0xaf, 0x89, 0x9e, 0x65, 0x25, 0x9e, 0x81, 0x01, 0x3e, 0x04, 0xa2, 0x18, 0xa6, 0xbc, 0x19, 0x5a,
|
||||
0x9e, 0x5f, 0x2e, 0xe3, 0x90, 0xe0, 0x9c, 0xa3, 0xfc, 0x45, 0x18, 0x47, 0x11, 0xcc, 0xbe, 0x14, 0x03, 0x96, 0x0a,
|
||||
0xe2, 0xb8, 0x2c, 0xbd, 0x44, 0x13, 0x31, 0x72, 0x3c, 0x64, 0x28, 0x1c, 0x8e, 0xd1, 0xd5, 0x15, 0x83, 0x17, 0xd7,
|
||||
0xfb, 0x26, 0x5c, 0x46, 0x6a, 0x3d, 0x28, 0xa1, 0xf0, 0x7c, 0x05, 0x82, 0x4f, 0xa0, 0x24, 0x3b, 0x03, 0x39, 0x08,
|
||||
0x70, 0x7e, 0xed, 0x81, 0xfc, 0x4f, 0x10, 0x8a, 0x8d, 0x8e, 0x07, 0x12, 0xf4, 0xc9, 0x34, 0x4a, 0x4f, 0xd9, 0x38,
|
||||
0x48, 0x58, 0x29, 0x39, 0xef, 0xbe, 0x05, 0x7b, 0x0c, 0xe4, 0x54, 0x58, 0xcf, 0x0f, 0x5f, 0xbd, 0x94, 0x3b, 0x57,
|
||||
0x63, 0xc6, 0xb0, 0x49, 0x0b, 0x10, 0xab, 0xc0, 0xb6, 0x25, 0x3b, 0x7e, 0xc6, 0x15, 0xf7, 0x16, 0x25, 0x71, 0xf1,
|
||||
0x7e, 0x0e, 0x2a, 0x06, 0x7b, 0x0b, 0xc3, 0xc0, 0xf4, 0x21, 0x4c, 0x45, 0xe5, 0x30, 0x9f, 0xa8, 0x18, 0xeb, 0x22,
|
||||
0xe8, 0x2c, 0x56, 0x2a, 0x5e, 0x33, 0xc7, 0x2d, 0x81, 0x54, 0x79, 0x3c, 0xb2, 0xa2, 0xf1, 0xf8, 0x45, 0x1a, 0xf3,
|
||||
0x38, 0x4a, 0xe2, 0xdf, 0x08, 0x93, 0x4b, 0xa4, 0x31, 0xde, 0x93, 0x9b, 0x00, 0x6b, 0xa7, 0x1e, 0x89, 0xab, 0x98,
|
||||
0xec, 0x06, 0x21, 0x43, 0x70, 0xcb, 0x24, 0x3c, 0x1a, 0x4a, 0xf0, 0x12, 0x7f, 0xbe, 0x28, 0xa6, 0x88, 0x58, 0x39,
|
||||
0x30, 0x32, 0xf2, 0xec, 0xa4, 0x60, 0xf9, 0x19, 0x1b, 0x6b, 0x0a, 0x28, 0x60, 0x55, 0xd4, 0x1c, 0x94, 0x17, 0x9a,
|
||||
0xd1, 0x51, 0x32, 0x94, 0xc1, 0x50, 0x3d, 0x93, 0xcd, 0x32, 0x49, 0xcc, 0x5a, 0xc3, 0xd1, 0x5c, 0xc0, 0x11, 0x4a,
|
||||
0x85, 0xe4, 0x04, 0x45, 0xa8, 0x56, 0x38, 0x05, 0x2e, 0x04, 0x52, 0xc1, 0x3c, 0xe6, 0x4a, 0x92, 0x3d, 0x5b, 0x90,
|
||||
0x48, 0x28, 0xa0, 0x23, 0x1c, 0x64, 0x82, 0xb4, 0x70, 0xe1, 0x54, 0x01, 0x97, 0x97, 0xe0, 0x0a, 0x2e, 0xa2, 0xd4,
|
||||
0x1c, 0x24, 0x80, 0xf0, 0x1b, 0x21, 0x0b, 0x7d, 0x6c, 0x41, 0x64, 0xe0, 0xeb, 0x9d, 0x07, 0xc4, 0xca, 0x75, 0x57,
|
||||
0x0b, 0xf1, 0xae, 0x01, 0x1b, 0x27, 0x46, 0x7a, 0xf2, 0x36, 0xb8, 0x9f, 0x66, 0xfb, 0xa3, 0x11, 0x2b, 0x8a, 0x2c,
|
||||
0xdf, 0xda, 0xda, 0xa0, 0xf6, 0xd7, 0x29, 0x5a, 0x80, 0x49, 0x57, 0xf3, 0x3a, 0xbb, 0x20, 0x09, 0x6e, 0x8a, 0x15,
|
||||
0x25, 0xd3, 0x03, 0xfb, 0xe3, 0x47, 0xe0, 0xd9, 0x9e, 0x44, 0x03, 0x60, 0x7d, 0x55, 0xf1, 0x13, 0xfa, 0x4c, 0x1d,
|
||||
0x33, 0x6b, 0xf5, 0x4b, 0xa7, 0x0e, 0x92, 0x07, 0xc3, 0xba, 0xa5, 0xb1, 0xa1, 0x6b, 0x87, 0xc6, 0xdd, 0x90, 0x02,
|
||||
0x72, 0x79, 0x4a, 0x22, 0xdb, 0xd8, 0x46, 0xd0, 0xda, 0x4a, 0x8f, 0x50, 0xaf, 0x56, 0x93, 0x13, 0xa0, 0x47, 0x6c,
|
||||
0xd8, 0x93, 0xf5, 0x61, 0x21, 0x30, 0x97, 0xb3, 0x5f, 0x17, 0xac, 0xe0, 0x82, 0x74, 0x61, 0xdc, 0x1c, 0xc6, 0x2d,
|
||||
0x57, 0xb4, 0xc3, 0x9a, 0xee, 0xb8, 0x0e, 0xb6, 0x37, 0x73, 0x94, 0x63, 0x05, 0x52, 0xf2, 0xcd, 0xe4, 0x84, 0xb0,
|
||||
0x32, 0xf7, 0xea, 0xea, 0x1b, 0x35, 0x48, 0xb5, 0x95, 0x5a, 0x07, 0x6a, 0xec, 0x89, 0xad, 0x9a, 0x8c, 0x6d, 0x57,
|
||||
0x0a, 0xd4, 0x8d, 0x4e, 0xaf, 0x46, 0x07, 0x70, 0xe6, 0xda, 0x9a, 0xa4, 0x2b, 0x65, 0xfb, 0xad, 0xc2, 0xe9, 0x1b,
|
||||
0x31, 0x32, 0x69, 0xa3, 0xec, 0x76, 0xea, 0x51, 0x27, 0x1e, 0xda, 0xae, 0xd4, 0x55, 0x8c, 0x61, 0x51, 0x67, 0x0c,
|
||||
0x4d, 0xa8, 0xe7, 0xba, 0x8b, 0xad, 0x89, 0x8a, 0x85, 0x6a, 0xaf, 0x95, 0x01, 0xc1, 0xc3, 0x23, 0x50, 0x4e, 0xd6,
|
||||
0xda, 0x07, 0xaf, 0xa3, 0x19, 0x43, 0x8c, 0x7a, 0xd7, 0x35, 0x90, 0x06, 0x04, 0x34, 0x19, 0x36, 0xc5, 0x1b, 0x77,
|
||||
0x85, 0xd6, 0x54, 0x3f, 0x5f, 0x31, 0x68, 0x11, 0xa0, 0x5f, 0x97, 0x6b, 0xb6, 0x88, 0xe4, 0xa6, 0x24, 0x67, 0x85,
|
||||
0x1f, 0x51, 0x26, 0xf6, 0x84, 0x04, 0x3c, 0x2c, 0x1e, 0xb6, 0xbf, 0xb1, 0x71, 0xb2, 0x15, 0x53, 0x6b, 0xe4, 0xc8,
|
||||
0x53, 0x00, 0xcf, 0x24, 0x04, 0x80, 0x5d, 0xd2, 0xcf, 0xda, 0xc1, 0x42, 0xb4, 0x1d, 0x20, 0x1d, 0xf8, 0x93, 0x24,
|
||||
0xe2, 0x4e, 0x67, 0xa7, 0xed, 0x02, 0x1d, 0x02, 0x13, 0x07, 0x19, 0x01, 0xea, 0x7d, 0xb5, 0x14, 0x86, 0x4b, 0x89,
|
||||
0x5d, 0xee, 0x83, 0x52, 0x34, 0x8d, 0x27, 0xdc, 0xc9, 0x50, 0x88, 0xb8, 0x25, 0x4b, 0x40, 0xc8, 0xe8, 0x73, 0x05,
|
||||
0x5c, 0x82, 0x0b, 0xee, 0x22, 0xaa, 0x35, 0x43, 0x53, 0x90, 0x12, 0x97, 0x22, 0x29, 0xa8, 0x20, 0x30, 0x98, 0x4a,
|
||||
0x4f, 0x51, 0x14, 0xc8, 0xb7, 0x78, 0x20, 0x06, 0x0d, 0x56, 0x34, 0xca, 0x78, 0x10, 0xaf, 0x16, 0x82, 0x18, 0xf6,
|
||||
0x79, 0xf6, 0x32, 0x3b, 0x67, 0xf9, 0x93, 0x08, 0x61, 0x0f, 0x44, 0xf7, 0x12, 0x38, 0x3d, 0x31, 0x74, 0xd6, 0x53,
|
||||
0xb4, 0x72, 0x46, 0x8b, 0x86, 0x8d, 0x98, 0xc5, 0x28, 0x08, 0x41, 0xca, 0x11, 0xee, 0x53, 0x3c, 0x52, 0x74, 0xf6,
|
||||
0x50, 0x94, 0x30, 0x4d, 0x5b, 0xfb, 0x2f, 0xeb, 0xb4, 0x05, 0x23, 0xcc, 0x15, 0xb5, 0xd6, 0x4f, 0xac, 0xeb, 0x49,
|
||||
0xd9, 0xec, 0x48, 0xda, 0x32, 0x84, 0x19, 0xc8, 0x8f, 0xab, 0xab, 0x4a, 0x49, 0x07, 0x61, 0xaa, 0xb9, 0x39, 0x6a,
|
||||
0x4e, 0xe2, 0x48, 0xb8, 0x25, 0x08, 0x23, 0x54, 0xbc, 0xf2, 0x2c, 0x49, 0x0c, 0x59, 0xe4, 0xc5, 0x3d, 0xa7, 0x21,
|
||||
0x8e, 0x00, 0x8a, 0x59, 0x4d, 0x22, 0x0d, 0x78, 0xa0, 0x2b, 0x50, 0x28, 0x29, 0x69, 0xe4, 0x55, 0x4d, 0x04, 0xc4,
|
||||
0xe9, 0x98, 0xe5, 0xc2, 0x40, 0x93, 0x32, 0x14, 0x26, 0x4c, 0x81, 0xa0, 0xd9, 0x18, 0x38, 0xbc, 0x5a, 0x00, 0xa8,
|
||||
0x27, 0xfe, 0x34, 0x2b, 0xb8, 0xae, 0x33, 0xa1, 0x8f, 0xaf, 0xae, 0x62, 0x61, 0x2f, 0x22, 0x01, 0xe4, 0x6c, 0x96,
|
||||
0x9d, 0xb1, 0x35, 0x50, 0xf7, 0xd4, 0x60, 0x26, 0xc8, 0xc6, 0x30, 0x20, 0x44, 0x41, 0xb4, 0xcc, 0x93, 0x78, 0xc4,
|
||||
0xb4, 0x94, 0x9a, 0xf9, 0xa0, 0xd0, 0xb1, 0x0b, 0xe0, 0x11, 0xcc, 0xed, 0xf7, 0xfb, 0x6d, 0xaf, 0xe3, 0x96, 0x02,
|
||||
0xe1, 0xcb, 0x15, 0x8c, 0xde, 0x20, 0x1f, 0xa5, 0x0a, 0xbe, 0x8e, 0x17, 0x70, 0xd7, 0x10, 0x8a, 0x5c, 0xd8, 0x49,
|
||||
0x9e, 0x64, 0xc4, 0xae, 0x37, 0x86, 0x41, 0x39, 0x53, 0x8c, 0x1b, 0x55, 0x5c, 0x71, 0x6c, 0xdf, 0x69, 0xb4, 0x69,
|
||||
0x72, 0x52, 0x27, 0x4c, 0x6d, 0x8c, 0xdc, 0xf3, 0x42, 0x5b, 0xc0, 0xe6, 0xf6, 0xa0, 0x96, 0x48, 0xd5, 0x40, 0xeb,
|
||||
0x00, 0xa1, 0xb0, 0x74, 0x9d, 0x95, 0x25, 0x55, 0x9d, 0x25, 0x13, 0xd7, 0x07, 0xe8, 0x0d, 0x93, 0x60, 0xae, 0x43,
|
||||
0xc1, 0x81, 0x64, 0x08, 0x1c, 0x2d, 0x32, 0xb1, 0x5f, 0x4f, 0x60, 0x7b, 0x4e, 0xa2, 0xd1, 0x27, 0x0d, 0x6e, 0x85,
|
||||
0xf6, 0x26, 0x19, 0x38, 0x8d, 0x92, 0xd0, 0x60, 0x57, 0xe6, 0xba, 0x15, 0x87, 0xae, 0x1d, 0x14, 0x30, 0xc8, 0x56,
|
||||
0xc8, 0xbe, 0xb9, 0xd1, 0x4d, 0x6a, 0x97, 0xe4, 0xa1, 0xec, 0x27, 0x4d, 0x25, 0x37, 0x90, 0x1c, 0x57, 0xdc, 0x80,
|
||||
0x2b, 0xc2, 0x83, 0xad, 0x69, 0x40, 0x02, 0x74, 0x57, 0x8e, 0xe3, 0xe2, 0x7a, 0x14, 0xfc, 0xa9, 0x60, 0x3e, 0x35,
|
||||
0x66, 0xba, 0x15, 0x52, 0xcd, 0xe1, 0xa4, 0x1a, 0xac, 0x41, 0x93, 0xca, 0x83, 0x62, 0x35, 0xdf, 0xa0, 0xa2, 0x42,
|
||||
0x14, 0x7f, 0x2a, 0xaa, 0x50, 0x05, 0x43, 0x30, 0x0a, 0x2f, 0x97, 0x04, 0x97, 0xad, 0xb2, 0x16, 0xc9, 0x53, 0x63,
|
||||
0x12, 0xa9, 0x9a, 0xe4, 0x32, 0x50, 0xb0, 0xe8, 0xb4, 0xfa, 0x52, 0x13, 0x57, 0x2c, 0x37, 0x0d, 0x35, 0x33, 0xc9,
|
||||
0x95, 0x35, 0xe1, 0x14, 0x68, 0x77, 0x29, 0xed, 0xdd, 0x5c, 0x4f, 0xa1, 0xd6, 0x53, 0xf8, 0x86, 0x0d, 0x65, 0xd2,
|
||||
0x76, 0x3e, 0x00, 0x75, 0xbf, 0x56, 0x89, 0xfa, 0xa9, 0x8f, 0x8c, 0xd9, 0xd5, 0x4c, 0x17, 0x18, 0x8a, 0x24, 0x93,
|
||||
0x74, 0x20, 0xe9, 0x0d, 0xd9, 0x46, 0x65, 0x19, 0x65, 0xae, 0x38, 0x20, 0x35, 0xab, 0x34, 0xf3, 0x52, 0xb7, 0xa1,
|
||||
0xbf, 0x97, 0xa5, 0xc4, 0x13, 0x17, 0x98, 0x89, 0xbd, 0x9b, 0x70, 0xe3, 0xa5, 0x61, 0x26, 0xb4, 0x5f, 0xa1, 0xec,
|
||||
0xd4, 0x30, 0x94, 0x4a, 0x16, 0x88, 0x63, 0xe3, 0x6b, 0xa5, 0x19, 0x64, 0xfe, 0x1a, 0x7d, 0x0a, 0x40, 0x49, 0x60,
|
||||
0xf3, 0x35, 0x96, 0xbc, 0x28, 0xac, 0xe3, 0x71, 0x83, 0xf0, 0x58, 0xb1, 0xd0, 0x1a, 0xcb, 0xd7, 0xf2, 0x2c, 0xf6,
|
||||
0x6b, 0x26, 0xa1, 0x89, 0xc9, 0x62, 0x50, 0x04, 0xb6, 0x72, 0x44, 0x54, 0xb2, 0x2d, 0x19, 0x24, 0x64, 0x90, 0xae,
|
||||
0x22, 0xbd, 0x36, 0x92, 0x81, 0xeb, 0x54, 0x70, 0xb4, 0x74, 0x18, 0x46, 0x0e, 0x1a, 0xee, 0xb4, 0x17, 0x2b, 0x88,
|
||||
0x6c, 0xea, 0x9b, 0x44, 0x8a, 0x68, 0x9c, 0x16, 0xa8, 0xc2, 0x99, 0x32, 0xdd, 0x71, 0x60, 0x39, 0xc0, 0xf6, 0x57,
|
||||
0x48, 0x6f, 0xad, 0xda, 0xe9, 0xfa, 0x95, 0xc1, 0x77, 0x75, 0x95, 0x20, 0x3d, 0x08, 0x85, 0x17, 0xf6, 0x6c, 0xa0,
|
||||
0x78, 0xef, 0xfe, 0x4b, 0x6c, 0x45, 0xfa, 0x67, 0x55, 0x52, 0x59, 0x0a, 0x35, 0xca, 0xad, 0xef, 0x13, 0x33, 0x5d,
|
||||
0x8b, 0xaa, 0xe2, 0xc0, 0xe0, 0xea, 0x07, 0x4a, 0x60, 0x57, 0x4b, 0x3e, 0x90, 0x43, 0xc7, 0xae, 0xeb, 0x06, 0x05,
|
||||
0x19, 0x2f, 0x1b, 0xeb, 0x4c, 0xc8, 0xad, 0x2d, 0xd3, 0x66, 0x3a, 0xd3, 0xc3, 0x3f, 0x71, 0x50, 0x38, 0x17, 0x97,
|
||||
0x29, 0x69, 0x30, 0x4f, 0x94, 0x38, 0x5a, 0x31, 0x40, 0xdb, 0x3d, 0xb4, 0xb4, 0xa3, 0xf3, 0x28, 0xe6, 0x96, 0x1e,
|
||||
0x45, 0x58, 0xda, 0xc8, 0x9f, 0xa4, 0xd2, 0x01, 0xeb, 0x42, 0x15, 0x92, 0x8c, 0x70, 0x53, 0x17, 0x2d, 0x46, 0x53,
|
||||
0x86, 0x2e, 0x70, 0xa5, 0x4f, 0x98, 0xbc, 0x67, 0x03, 0xd7, 0x2d, 0x06, 0x66, 0xeb, 0x61, 0x2f, 0x9b, 0xdd, 0x6b,
|
||||
0xea, 0x3f, 0xec, 0x11, 0xf0, 0xb6, 0x99, 0xaa, 0x2b, 0x1b, 0xef, 0x92, 0x45, 0xa2, 0x87, 0x6d, 0xdd, 0xd8, 0x52,
|
||||
0xd7, 0xef, 0x35, 0xcc, 0xeb, 0xca, 0x30, 0xaf, 0x09, 0xd5, 0x86, 0x1c, 0x56, 0x66, 0x0e, 0x33, 0x0d, 0x79, 0xb1,
|
||||
0x83, 0x6e, 0x4f, 0x38, 0x85, 0xc0, 0x88, 0xd0, 0xfa, 0xa0, 0xa2, 0x06, 0x42, 0x25, 0x57, 0x52, 0x35, 0x5b, 0x24,
|
||||
0x63, 0x09, 0x2c, 0x98, 0xb0, 0x5c, 0xd2, 0xd1, 0x79, 0x9c, 0x24, 0x55, 0xe9, 0x9f, 0xca, 0xe0, 0xc5, 0xb0, 0xb7,
|
||||
0xb1, 0x76, 0xb1, 0xa2, 0x85, 0x02, 0xc1, 0xd5, 0x4a, 0xd8, 0x7b, 0xc7, 0xad, 0xf6, 0x5d, 0x78, 0x1c, 0xb9, 0xe9,
|
||||
0x8d, 0x80, 0x7a, 0xf4, 0xb0, 0x6a, 0xd2, 0xde, 0x7f, 0x86, 0x2e, 0x35, 0x63, 0x3d, 0x28, 0xce, 0xa8, 0xf8, 0x77,
|
||||
0xe9, 0x53, 0xbf, 0x73, 0x79, 0xb7, 0x8a, 0xae, 0xa6, 0x43, 0x45, 0x39, 0x3e, 0x4c, 0x17, 0x4b, 0x5b, 0x39, 0x02,
|
||||
0x72, 0x3d, 0x2c, 0x72, 0x01, 0x13, 0x35, 0x58, 0x50, 0x8a, 0x55, 0x6b, 0x61, 0xf7, 0xf2, 0x36, 0x67, 0x0e, 0xb9,
|
||||
0xc2, 0x45, 0xff, 0x27, 0xd9, 0x6c, 0x8e, 0x9a, 0x59, 0x83, 0xa8, 0xa1, 0xc1, 0xfb, 0x46, 0x7d, 0xb9, 0xa6, 0xac,
|
||||
0xd6, 0x87, 0x4e, 0x64, 0x8d, 0x9e, 0xb4, 0xa1, 0x0c, 0x06, 0xd5, 0x42, 0x17, 0xd5, 0xf5, 0xe6, 0x26, 0x8b, 0x59,
|
||||
0x47, 0xe3, 0x3e, 0xc9, 0x6d, 0xad, 0x4d, 0x7a, 0x1a, 0x07, 0xc4, 0x93, 0x24, 0xc1, 0x9b, 0x04, 0x50, 0x56, 0xc8,
|
||||
0x59, 0x96, 0x0d, 0xf4, 0x2d, 0xcb, 0x12, 0xf7, 0xef, 0xdb, 0xde, 0x7e, 0xcd, 0xb2, 0xf6, 0xf6, 0xaf, 0x37, 0x91,
|
||||
0xab, 0x3a, 0x69, 0x41, 0x1e, 0x0d, 0xa1, 0x68, 0x45, 0xa7, 0x0c, 0x97, 0xb3, 0x6c, 0xcc, 0x02, 0x1b, 0xba, 0xa7,
|
||||
0x76, 0xa9, 0xa4, 0x32, 0x1c, 0x8e, 0x94, 0x39, 0xcb, 0x77, 0x75, 0x4f, 0x6a, 0xb0, 0x0f, 0x24, 0xa0, 0xd5, 0x85,
|
||||
0xef, 0xc2, 0xd3, 0x24, 0x3b, 0x89, 0x92, 0x43, 0x21, 0xc0, 0x6b, 0x2d, 0x3f, 0x80, 0xc9, 0x48, 0x1a, 0xab, 0x21,
|
||||
0xa4, 0xbe, 0x1b, 0x7c, 0x17, 0xdc, 0xde, 0xa3, 0xb2, 0x56, 0xec, 0x8e, 0xdf, 0xf6, 0x3b, 0xb6, 0xf2, 0x88, 0xbd,
|
||||
0x34, 0xa7, 0x03, 0x89, 0x53, 0x00, 0x66, 0x0e, 0x41, 0x92, 0x15, 0x5e, 0xc4, 0xc2, 0x97, 0x83, 0x97, 0xca, 0xa4,
|
||||
0xce, 0xc0, 0x84, 0x00, 0x23, 0x3f, 0x89, 0x79, 0x0b, 0xe3, 0x91, 0xb6, 0xb7, 0x14, 0x15, 0xe8, 0x57, 0x24, 0xbf,
|
||||
0x74, 0xa9, 0xac, 0x41, 0xef, 0x63, 0x78, 0x0c, 0xcd, 0x36, 0x37, 0x97, 0xce, 0xab, 0x88, 0x4f, 0xfd, 0x3c, 0x4a,
|
||||
0xc7, 0xd9, 0xcc, 0x71, 0xb7, 0x6d, 0xdb, 0xf5, 0x0b, 0xb2, 0x44, 0xbe, 0x70, 0xcb, 0xcd, 0x63, 0x6f, 0xca, 0x42,
|
||||
0x7b, 0x60, 0x6f, 0x7f, 0xf4, 0xde, 0xb2, 0xf0, 0x78, 0x6f, 0x73, 0x39, 0x65, 0x65, 0xff, 0xd8, 0xbb, 0xd0, 0x3e,
|
||||
0x77, 0xef, 0x2d, 0x72, 0x19, 0xe8, 0x15, 0xf6, 0x2f, 0x24, 0x18, 0x40, 0x6e, 0xe4, 0x7f, 0x07, 0x2e, 0xf7, 0x9e,
|
||||
0x02, 0x22, 0xd2, 0x4f, 0x7b, 0x75, 0x65, 0x67, 0xe4, 0x31, 0xb0, 0x37, 0xb4, 0xb1, 0xba, 0xb5, 0x55, 0x89, 0xf9,
|
||||
0xaa, 0xd4, 0x1b, 0xb1, 0xb0, 0x66, 0xa9, 0x7b, 0xef, 0x29, 0xb4, 0x52, 0x3f, 0xc8, 0x23, 0x46, 0x42, 0x73, 0x55,
|
||||
0x4f, 0x70, 0x8c, 0x23, 0xbe, 0xfe, 0x58, 0x1f, 0x09, 0x2f, 0x85, 0x1f, 0x83, 0xf6, 0x12, 0x81, 0xf8, 0x06, 0x03,
|
||||
0xc7, 0x3b, 0x0c, 0x77, 0xf6, 0x9c, 0x41, 0xe0, 0x6c, 0xb4, 0x5a, 0x57, 0x3f, 0xed, 0x1c, 0xfd, 0x1c, 0xb5, 0x7e,
|
||||
0xdb, 0x6f, 0xfd, 0x38, 0x74, 0xaf, 0x9c, 0x9f, 0x76, 0x06, 0x47, 0xf2, 0xed, 0xe8, 0xe7, 0xfe, 0x4f, 0xc5, 0xf0,
|
||||
0x73, 0x51, 0xb8, 0xe9, 0xba, 0x3b, 0xa7, 0x60, 0x29, 0x85, 0x3b, 0xad, 0x56, 0x1f, 0x9e, 0x16, 0xf0, 0x84, 0x3f,
|
||||
0x2f, 0xe1, 0xc7, 0xd5, 0x91, 0xf5, 0x1f, 0x7e, 0x4a, 0xff, 0xe3, 0x4f, 0xf9, 0x10, 0xc7, 0x3c, 0xfa, 0xf9, 0xa7,
|
||||
0xc2, 0xbe, 0xd7, 0x0f, 0x77, 0x86, 0xdb, 0xae, 0xa3, 0x6b, 0x3e, 0x0f, 0xab, 0x47, 0x68, 0x75, 0xf4, 0xb3, 0x7c,
|
||||
0xb3, 0xef, 0x1d, 0xef, 0xf5, 0xc3, 0xe1, 0x95, 0x63, 0x5f, 0xdd, 0x73, 0xaf, 0x5c, 0xf7, 0x6a, 0x13, 0xe7, 0x99,
|
||||
0xc3, 0xe8, 0xf7, 0xe0, 0xe7, 0x19, 0xfc, 0xb4, 0xe1, 0xe7, 0x29, 0xfc, 0xfc, 0x19, 0xba, 0x09, 0xff, 0xdb, 0x15,
|
||||
0xf9, 0x42, 0xae, 0x30, 0x60, 0x11, 0xc1, 0x2e, 0xb8, 0x9b, 0x3b, 0xb1, 0x37, 0x21, 0xa4, 0xc1, 0x39, 0xf4, 0x7d,
|
||||
0x1f, 0xdd, 0xa4, 0xce, 0xf2, 0xe3, 0x26, 0x6c, 0x3a, 0x52, 0xce, 0x66, 0xc0, 0x3c, 0xe1, 0x39, 0x28, 0x02, 0x2e,
|
||||
0x62, 0xab, 0x05, 0x06, 0x57, 0xbd, 0x45, 0x38, 0x61, 0x0e, 0x28, 0x05, 0x87, 0x0c, 0x1f, 0xba, 0xae, 0xf7, 0x4c,
|
||||
0xc6, 0x0c, 0xf1, 0x9c, 0x0b, 0xd2, 0x4a, 0x33, 0xa1, 0xd2, 0xd8, 0xae, 0x37, 0x5f, 0x53, 0x09, 0xc7, 0x3a, 0x3d,
|
||||
0x85, 0xba, 0x4d, 0x11, 0x68, 0xfb, 0x8e, 0x45, 0x9f, 0xf0, 0x48, 0x3e, 0x37, 0x82, 0xc0, 0x2b, 0x9a, 0x7c, 0x53,
|
||||
0x69, 0x34, 0x74, 0x44, 0x61, 0x8e, 0x7d, 0xc9, 0x60, 0x86, 0x15, 0x15, 0x91, 0x93, 0xd0, 0x14, 0x9a, 0x2d, 0x4c,
|
||||
0xfe, 0x36, 0xca, 0xf9, 0x66, 0xa5, 0xd8, 0x86, 0x35, 0x4d, 0xb6, 0xa9, 0xe9, 0xdf, 0x61, 0x0a, 0x54, 0x2d, 0x29,
|
||||
0xfe, 0x61, 0x8e, 0x1f, 0xa6, 0xb4, 0xac, 0xd7, 0x0e, 0x07, 0x0b, 0xbd, 0x00, 0xbe, 0x23, 0xfa, 0x39, 0x6f, 0x51,
|
||||
0x8c, 0xc1, 0x5f, 0xe9, 0x66, 0xf0, 0xc4, 0x7c, 0xe8, 0xa2, 0x59, 0x96, 0xda, 0xb9, 0x95, 0x22, 0xbb, 0x7f, 0x81,
|
||||
0x27, 0x23, 0x2d, 0xbd, 0x83, 0x50, 0x9d, 0x98, 0xc3, 0x9c, 0xb1, 0xef, 0xa2, 0xe4, 0x13, 0xcb, 0x9d, 0x0b, 0xaf,
|
||||
0xd3, 0xfd, 0x82, 0x3a, 0x7b, 0xa8, 0x9b, 0xbd, 0xae, 0xc2, 0x68, 0x4a, 0x2d, 0x50, 0x21, 0xc2, 0x56, 0xc7, 0x43,
|
||||
0x8e, 0x41, 0x28, 0xc8, 0xbd, 0x2c, 0xec, 0x12, 0x85, 0xdb, 0x7b, 0xc5, 0xd9, 0x69, 0xdf, 0x0e, 0x6c, 0x1b, 0x34,
|
||||
0xfe, 0x43, 0x72, 0x5b, 0x09, 0xc5, 0x02, 0x14, 0xb2, 0xbd, 0xb8, 0xc7, 0xb7, 0xb7, 0x2b, 0x87, 0x13, 0x06, 0xd2,
|
||||
0xa9, 0x7b, 0xe2, 0x45, 0xde, 0x34, 0x84, 0x01, 0x47, 0xd0, 0x0c, 0xbb, 0xf4, 0x46, 0x7b, 0xb1, 0x9c, 0x06, 0x7d,
|
||||
0x21, 0x7e, 0x12, 0x15, 0xfc, 0x05, 0xfa, 0x23, 0xc2, 0x11, 0x2a, 0xfb, 0x3e, 0xbb, 0x60, 0x23, 0xa5, 0x67, 0x00,
|
||||
0xa2, 0x22, 0xb7, 0xe7, 0x8e, 0x42, 0xa3, 0x19, 0xcc, 0x1d, 0x86, 0x87, 0x03, 0x1b, 0xce, 0x12, 0x9c, 0xca, 0x30,
|
||||
0x3a, 0xea, 0x0c, 0x07, 0x69, 0x08, 0xbc, 0x56, 0xe3, 0x56, 0x16, 0x2d, 0x6a, 0x45, 0xdd, 0xe1, 0xc0, 0x39, 0x05,
|
||||
0x25, 0x1d, 0x74, 0x71, 0x07, 0xdf, 0xd0, 0x43, 0x91, 0x87, 0xef, 0xd8, 0xe9, 0xb3, 0x8b, 0xb9, 0x63, 0xef, 0xed,
|
||||
0xd8, 0xdb, 0x58, 0xea, 0xd9, 0x40, 0x5e, 0x30, 0x77, 0x78, 0xe9, 0x9a, 0x9d, 0x77, 0x87, 0x08, 0x2a, 0x16, 0xe2,
|
||||
0xe4, 0x97, 0x03, 0xbb, 0x2f, 0xa6, 0x6e, 0xc3, 0xa0, 0xa9, 0xdc, 0x7e, 0xdc, 0xd1, 0x43, 0x5a, 0xaa, 0xea, 0xaa,
|
||||
0xa0, 0x83, 0xb2, 0x6e, 0xe0, 0x4c, 0xcd, 0x45, 0xb4, 0x70, 0x32, 0x89, 0x05, 0x30, 0x78, 0xb0, 0x19, 0x4c, 0x6a,
|
||||
0x74, 0xdb, 0x1d, 0x0e, 0x2e, 0x83, 0x7b, 0xf6, 0x3d, 0xf5, 0x72, 0xc6, 0x02, 0xb0, 0x2e, 0x68, 0xfa, 0x33, 0x94,
|
||||
0x22, 0xf0, 0x73, 0xce, 0x60, 0x91, 0x97, 0x54, 0x34, 0x96, 0x45, 0x0b, 0x2c, 0x3a, 0x0c, 0x10, 0x54, 0x2f, 0xd7,
|
||||
0xda, 0x9f, 0xd8, 0x93, 0x71, 0x48, 0xb0, 0x6f, 0x6d, 0xc1, 0xd6, 0x6c, 0x77, 0x86, 0x18, 0x6f, 0xc8, 0x79, 0xf1,
|
||||
0x5d, 0xcc, 0x41, 0x24, 0xec, 0xf4, 0x6d, 0x77, 0x60, 0x5b, 0xb8, 0xb5, 0xbd, 0x6c, 0x3b, 0x14, 0x18, 0x8e, 0xb7,
|
||||
0xdf, 0xb2, 0x60, 0xda, 0x0f, 0xdb, 0x03, 0xa7, 0x10, 0xa2, 0x23, 0xc1, 0xb8, 0xa5, 0xe0, 0xe0, 0x6d, 0x6f, 0x0a,
|
||||
0x0c, 0x1d, 0x29, 0x77, 0xd3, 0xde, 0x56, 0x85, 0x50, 0xf4, 0x71, 0x7b, 0xec, 0x06, 0x31, 0xfc, 0x70, 0x5a, 0x48,
|
||||
0x34, 0x53, 0xdd, 0x57, 0x4b, 0x66, 0x37, 0x18, 0x2b, 0x8d, 0x3c, 0x09, 0xb3, 0x6d, 0x07, 0x3d, 0xb4, 0xc0, 0x69,
|
||||
0xf7, 0x06, 0x00, 0xc3, 0xb6, 0xa3, 0x28, 0x6d, 0x47, 0x91, 0x9a, 0xd2, 0xcf, 0x8f, 0xaa, 0xed, 0x60, 0x83, 0x88,
|
||||
0xf9, 0x95, 0xf4, 0x01, 0xb0, 0x82, 0xc4, 0x2b, 0x86, 0x2a, 0xe6, 0xf5, 0xbc, 0x16, 0xdf, 0x5a, 0x2a, 0x56, 0xc4,
|
||||
0x3c, 0x83, 0x43, 0xf1, 0x52, 0x9b, 0x61, 0x42, 0xdd, 0x9e, 0x23, 0x32, 0x34, 0xc9, 0x87, 0x6d, 0x20, 0x7a, 0xe5,
|
||||
0x60, 0x4f, 0xcd, 0x63, 0x91, 0x84, 0x55, 0x73, 0xef, 0x08, 0x48, 0x7b, 0x18, 0xbe, 0x16, 0x11, 0xc7, 0x9e, 0xf2,
|
||||
0xe6, 0xb3, 0x24, 0x7c, 0xde, 0x08, 0x17, 0x47, 0x18, 0x11, 0x3a, 0xf0, 0x47, 0x8b, 0x1c, 0xf8, 0x01, 0x7f, 0x0d,
|
||||
0x9a, 0x41, 0x28, 0x9b, 0xa2, 0xa1, 0x87, 0x21, 0x60, 0x8f, 0x16, 0xde, 0x70, 0x9b, 0x1b, 0xd5, 0xa8, 0x51, 0x92,
|
||||
0xf2, 0x42, 0x81, 0xe1, 0x1e, 0x97, 0xa6, 0x3d, 0x32, 0x06, 0x19, 0x31, 0x76, 0x30, 0xe6, 0xef, 0x8f, 0xb0, 0x1a,
|
||||
0x27, 0x28, 0xdc, 0x92, 0x4e, 0x5b, 0xc5, 0xfe, 0x0e, 0xfc, 0x14, 0x38, 0x38, 0xd6, 0x81, 0x9d, 0xb5, 0xb5, 0x95,
|
||||
0xc8, 0x45, 0xed, 0xa5, 0x3d, 0x8a, 0x44, 0xa0, 0x3f, 0xb8, 0xf0, 0x53, 0xa8, 0x46, 0x14, 0x51, 0x11, 0x69, 0xa0,
|
||||
0x66, 0x54, 0xad, 0x82, 0xef, 0xc8, 0xf4, 0xc0, 0x73, 0x74, 0x5b, 0x93, 0xa2, 0xa8, 0x1b, 0x0b, 0x5f, 0xbe, 0xeb,
|
||||
0x52, 0x68, 0x0b, 0x03, 0x90, 0x82, 0xd0, 0x04, 0xc1, 0xb8, 0xe4, 0x94, 0xac, 0xe8, 0xef, 0xa3, 0xe1, 0x2b, 0x9f,
|
||||
0x1e, 0x65, 0xdb, 0xdb, 0x43, 0x11, 0xb7, 0x20, 0xc2, 0xe1, 0x86, 0x77, 0x35, 0xae, 0x00, 0xa8, 0x4f, 0xe7, 0xc4,
|
||||
0x75, 0xc7, 0xb4, 0x22, 0x4d, 0x97, 0x7c, 0x9f, 0x1c, 0x66, 0x00, 0x0c, 0xee, 0x38, 0x47, 0xfe, 0xe0, 0x2f, 0x43,
|
||||
0x30, 0x8f, 0xfd, 0xcf, 0xdd, 0x1d, 0xc5, 0x68, 0x7a, 0x32, 0xa6, 0xb8, 0xa4, 0x18, 0x6b, 0xc7, 0x23, 0xdf, 0x68,
|
||||
0x90, 0x7b, 0x29, 0xac, 0x00, 0xa4, 0x39, 0xf0, 0x84, 0x8a, 0x82, 0x90, 0xa2, 0x02, 0xdb, 0xc7, 0xc3, 0xcf, 0xf1,
|
||||
0x64, 0xbf, 0x03, 0x0d, 0x6f, 0xa0, 0xdf, 0x9e, 0xc2, 0xdb, 0x5f, 0xf4, 0xdb, 0x97, 0x2c, 0xf8, 0xa5, 0x94, 0xae,
|
||||
0xfb, 0xda, 0x14, 0x0f, 0xd5, 0x14, 0xa5, 0xd8, 0x22, 0x03, 0x87, 0xcc, 0x5d, 0xf5, 0xd9, 0x70, 0xb7, 0x04, 0x64,
|
||||
0x28, 0xd6, 0x05, 0x3a, 0x5a, 0x74, 0x8a, 0xc8, 0x75, 0x4d, 0x54, 0x18, 0xb9, 0x04, 0xe6, 0x82, 0x2b, 0xba, 0x25,
|
||||
0xe2, 0xec, 0xb7, 0xdd, 0x65, 0xad, 0x2d, 0xe9, 0x77, 0x6c, 0x36, 0xe7, 0x97, 0x07, 0x24, 0xe8, 0x03, 0x99, 0x36,
|
||||
0x20, 0x62, 0xe7, 0xed, 0x5e, 0xbc, 0xc7, 0x7b, 0x31, 0x70, 0xf5, 0x42, 0x91, 0x18, 0x9e, 0x55, 0xef, 0x2d, 0x7a,
|
||||
0x29, 0x4d, 0x62, 0xf2, 0x6a, 0xcb, 0xeb, 0xca, 0xe5, 0x6d, 0x6f, 0xc3, 0x02, 0x7b, 0x46, 0x57, 0x2e, 0xba, 0x96,
|
||||
0xa5, 0xc0, 0x09, 0x40, 0xf4, 0xb8, 0x4e, 0x72, 0x44, 0x71, 0x98, 0xcd, 0x86, 0x8c, 0x83, 0xb9, 0x6b, 0x47, 0xc5,
|
||||
0x31, 0xb1, 0xbb, 0x4c, 0xd8, 0x81, 0x95, 0x11, 0x95, 0xb7, 0x3a, 0xc2, 0x3b, 0x2c, 0xfa, 0x6b, 0xff, 0xf6, 0x47,
|
||||
0x8f, 0x6d, 0x77, 0x5c, 0x90, 0x20, 0xb5, 0xb1, 0x1e, 0x55, 0x63, 0x41, 0x7d, 0xf8, 0x51, 0x63, 0xa9, 0xcc, 0xb7,
|
||||
0xb7, 0xcb, 0x7a, 0xa8, 0x56, 0x9d, 0xe0, 0x5a, 0x34, 0xe5, 0xa2, 0x99, 0x0d, 0xc2, 0x01, 0x89, 0x09, 0x14, 0x68,
|
||||
0x6e, 0x65, 0xc5, 0x00, 0x43, 0xca, 0x72, 0xe4, 0x4f, 0x21, 0xf3, 0xe2, 0xb2, 0xd4, 0xa9, 0x2f, 0xd2, 0x1f, 0x19,
|
||||
0x62, 0xd4, 0x93, 0x94, 0x15, 0x10, 0xb0, 0x5e, 0xea, 0x25, 0xb4, 0x45, 0xb0, 0xf2, 0x67, 0x2a, 0x87, 0x46, 0x68,
|
||||
0x20, 0x51, 0x68, 0xa8, 0x25, 0x4a, 0xf9, 0xcc, 0xc3, 0x18, 0xa4, 0xfd, 0x93, 0x9a, 0xef, 0x2b, 0x57, 0x4a, 0x47,
|
||||
0x7e, 0x54, 0x0c, 0x03, 0xaa, 0x5f, 0x48, 0x0e, 0x36, 0x0d, 0xdf, 0x03, 0x19, 0x55, 0x86, 0x27, 0x31, 0xc2, 0xa7,
|
||||
0x71, 0xce, 0xc8, 0x52, 0xd8, 0x94, 0x30, 0x4b, 0xd5, 0x36, 0x52, 0xed, 0x22, 0xd3, 0x09, 0xe5, 0xc2, 0xfc, 0x53,
|
||||
0x23, 0x76, 0x91, 0x85, 0x2b, 0xad, 0x41, 0xfd, 0x78, 0x63, 0x02, 0x94, 0x5d, 0x5d, 0x65, 0xc2, 0xc6, 0x8d, 0x48,
|
||||
0xdf, 0xd0, 0x15, 0xd3, 0x81, 0x5a, 0x54, 0xe0, 0x44, 0xa4, 0xf1, 0x50, 0x0c, 0x85, 0x46, 0x38, 0xa4, 0x28, 0x72,
|
||||
0xe1, 0x1a, 0x87, 0xbe, 0x18, 0x68, 0xdb, 0x28, 0x0d, 0x9d, 0x04, 0x98, 0x80, 0x58, 0xbb, 0xa1, 0x4d, 0xa5, 0x83,
|
||||
0x34, 0x48, 0xa8, 0x14, 0xed, 0x1c, 0x58, 0x7f, 0x18, 0x49, 0x0c, 0x80, 0xfe, 0x50, 0x8d, 0x14, 0x51, 0x96, 0x05,
|
||||
0x6e, 0x00, 0xcd, 0x75, 0x80, 0x3b, 0xe1, 0x0b, 0x05, 0x15, 0xa6, 0xa7, 0x59, 0x79, 0x29, 0x84, 0xc8, 0xab, 0x35,
|
||||
0x29, 0x6b, 0xc4, 0x93, 0xcf, 0xd0, 0xe0, 0x53, 0xd6, 0xf5, 0x6b, 0xb9, 0x0e, 0x5d, 0xf0, 0x14, 0xb6, 0x55, 0x3d,
|
||||
0xbf, 0x0a, 0x39, 0x19, 0xd7, 0x20, 0x2b, 0x24, 0xd3, 0x5f, 0x31, 0x92, 0xf7, 0x5f, 0xf9, 0x55, 0x2d, 0x35, 0x86,
|
||||
0xb2, 0xf7, 0xeb, 0x9a, 0x61, 0x79, 0x39, 0xaf, 0xdc, 0x14, 0x04, 0xdc, 0x92, 0x25, 0xc1, 0x52, 0x4a, 0x08, 0xd0,
|
||||
0xb0, 0x3d, 0x92, 0x4a, 0x41, 0x51, 0x6a, 0xf7, 0xce, 0x53, 0xd0, 0x02, 0x8c, 0xa0, 0x96, 0x4a, 0xa6, 0x91, 0xc8,
|
||||
0x97, 0x42, 0x14, 0x88, 0xf2, 0x60, 0x04, 0x76, 0x6a, 0x33, 0xd2, 0x75, 0xe1, 0xfa, 0xf1, 0x0c, 0x53, 0x7b, 0x08,
|
||||
0xf4, 0xd8, 0xdb, 0x00, 0x55, 0xa2, 0x2e, 0xc3, 0x72, 0xa2, 0xd0, 0xac, 0x26, 0x59, 0x40, 0x8d, 0x69, 0x83, 0x94,
|
||||
0x6c, 0x83, 0x2e, 0x57, 0x80, 0x7e, 0x24, 0x8e, 0x67, 0xb5, 0x03, 0x42, 0xd6, 0xa0, 0x82, 0x21, 0x4f, 0xa9, 0x90,
|
||||
0xc2, 0xbc, 0xd7, 0xa5, 0x22, 0x3c, 0x9f, 0x03, 0x2e, 0xb5, 0xe0, 0xcc, 0xcb, 0x68, 0xe0, 0x83, 0xf8, 0x24, 0xc1,
|
||||
0xc4, 0x17, 0x5c, 0x15, 0xe8, 0xc1, 0x41, 0xa7, 0xd9, 0x14, 0x28, 0x15, 0x37, 0x29, 0x83, 0x6d, 0x45, 0xae, 0x0d,
|
||||
0x3f, 0x24, 0xcb, 0xd6, 0x5d, 0x1e, 0xea, 0x2e, 0x44, 0x02, 0xd8, 0xe9, 0x25, 0x7a, 0xbe, 0x65, 0xbd, 0x74, 0x18,
|
||||
0x9c, 0x69, 0x89, 0x83, 0xc0, 0x6f, 0x6f, 0x27, 0xc3, 0x32, 0x25, 0xb2, 0x6b, 0x92, 0xba, 0x80, 0x1c, 0x86, 0x6a,
|
||||
0xae, 0x1d, 0x98, 0xa5, 0xd2, 0xc7, 0xf3, 0x72, 0x86, 0xdb, 0xa5, 0x34, 0xe4, 0x66, 0xbc, 0x9a, 0xe6, 0x73, 0x2b,
|
||||
0xc9, 0xa6, 0xfd, 0xad, 0xf8, 0xa2, 0xe0, 0x1f, 0x38, 0xb1, 0xd4, 0xea, 0x29, 0xb5, 0xc2, 0xa3, 0xcc, 0x2d, 0x59,
|
||||
0xa7, 0xb8, 0x56, 0xd7, 0x0d, 0x54, 0x23, 0x8c, 0xa6, 0x61, 0x23, 0x60, 0x62, 0x82, 0x8a, 0x5f, 0x37, 0x89, 0x98,
|
||||
0xce, 0x96, 0xe0, 0x3a, 0x42, 0xef, 0xa1, 0x9c, 0xe0, 0xae, 0xa6, 0xd9, 0xe7, 0xe1, 0xfc, 0x7a, 0xe2, 0xde, 0x37,
|
||||
0x88, 0xfb, 0xcb, 0x90, 0x1b, 0x84, 0x1e, 0xcb, 0x84, 0x1f, 0xe9, 0xfb, 0x28, 0x54, 0xd5, 0x93, 0xd3, 0xb0, 0x62,
|
||||
0x59, 0xe2, 0xc9, 0x08, 0x75, 0x18, 0x51, 0xd1, 0x1a, 0x23, 0xbb, 0xba, 0xca, 0xcd, 0xb3, 0x40, 0x4e, 0x53, 0x8f,
|
||||
0xd7, 0xfd, 0xb4, 0x15, 0x39, 0x1b, 0x9e, 0xc8, 0xfd, 0x57, 0x35, 0x4f, 0x64, 0x45, 0xe7, 0x38, 0xd2, 0x35, 0x81,
|
||||
0xdc, 0x27, 0xa7, 0xab, 0x87, 0x54, 0xc8, 0x16, 0xbd, 0x6c, 0xe3, 0x8c, 0xea, 0x80, 0xa4, 0x9e, 0x51, 0x81, 0x55,
|
||||
0x8d, 0xbd, 0xb5, 0xd5, 0x11, 0xe9, 0x96, 0x4a, 0xb0, 0xc1, 0xd6, 0xc2, 0x68, 0xc6, 0x28, 0xe8, 0x94, 0x14, 0x19,
|
||||
0xa8, 0x51, 0x7e, 0x0d, 0x63, 0xd8, 0xa7, 0x06, 0x20, 0x38, 0xd7, 0x57, 0x7f, 0x59, 0x4a, 0xb2, 0x10, 0x90, 0xb8,
|
||||
0x4b, 0x06, 0x6c, 0x4d, 0x10, 0x33, 0xd2, 0xc9, 0x7b, 0xa0, 0xbc, 0x01, 0x43, 0x1b, 0x01, 0xec, 0x02, 0x71, 0xe8,
|
||||
0x41, 0xc5, 0xb6, 0x09, 0x29, 0x3a, 0x36, 0xf0, 0x1c, 0x80, 0x9d, 0x57, 0xae, 0xd1, 0x77, 0x55, 0x0a, 0x18, 0x92,
|
||||
0x81, 0x1b, 0xb0, 0xca, 0x2d, 0xb7, 0xff, 0x1c, 0xcc, 0x06, 0x78, 0x7d, 0x26, 0x9b, 0x6f, 0x62, 0x9e, 0x60, 0x15,
|
||||
0xbb, 0xf0, 0x2b, 0xcd, 0x5a, 0xc4, 0x9d, 0x0e, 0x1b, 0xf5, 0x0a, 0x13, 0xa2, 0xf6, 0x00, 0x6b, 0xdf, 0xa3, 0x87,
|
||||
0x45, 0xbc, 0xbf, 0xc2, 0x77, 0x3d, 0x6e, 0xb9, 0xaf, 0x97, 0x45, 0x2b, 0x5d, 0x45, 0x8d, 0x81, 0xc9, 0xba, 0x9d,
|
||||
0x8c, 0x6b, 0x2f, 0x0f, 0x84, 0x2f, 0xb8, 0x5a, 0x23, 0xab, 0x5c, 0x8a, 0x8d, 0x45, 0xd2, 0xd3, 0x3e, 0x05, 0xd8,
|
||||
0x37, 0x9b, 0xbd, 0x00, 0x33, 0xef, 0x2b, 0x54, 0x49, 0x48, 0x69, 0x76, 0x83, 0x25, 0x09, 0x6d, 0x45, 0x46, 0x9d,
|
||||
0x0f, 0x1c, 0x6d, 0x73, 0x2b, 0x8e, 0x60, 0x38, 0x27, 0x61, 0x3a, 0x56, 0x1e, 0x36, 0x19, 0xb8, 0xf2, 0x8e, 0x98,
|
||||
0xb6, 0x09, 0xf0, 0x6f, 0x06, 0x7c, 0x7b, 0x25, 0xb9, 0xb6, 0xd0, 0x30, 0x3c, 0x41, 0x84, 0x55, 0x9e, 0x08, 0x34,
|
||||
0x14, 0x60, 0x8d, 0x6b, 0x2d, 0x0f, 0x50, 0xe1, 0x6b, 0x67, 0x13, 0x00, 0x12, 0x59, 0x41, 0xce, 0x8a, 0xa3, 0x1b,
|
||||
0x56, 0xb9, 0xde, 0x4f, 0x8d, 0x82, 0xc4, 0xc5, 0x83, 0xe9, 0xea, 0x96, 0xfe, 0x0c, 0x35, 0x67, 0x52, 0xc4, 0xb4,
|
||||
0x13, 0x04, 0xfd, 0xa3, 0xcc, 0xc9, 0x69, 0x3a, 0xa1, 0x7d, 0xce, 0x9d, 0xda, 0xd4, 0x3d, 0x46, 0xdd, 0x3c, 0x89,
|
||||
0x2d, 0x5e, 0xc7, 0x4d, 0x29, 0x17, 0x26, 0x39, 0xe6, 0xa6, 0x48, 0xc5, 0x66, 0x8a, 0xdd, 0xb9, 0xf5, 0x83, 0x16,
|
||||
0xd2, 0x41, 0xdb, 0x14, 0x39, 0xd8, 0xac, 0xe2, 0xf7, 0x04, 0xc6, 0x73, 0x81, 0xf8, 0xf2, 0x15, 0x25, 0xe9, 0x30,
|
||||
0xc7, 0x5c, 0x60, 0xf5, 0x62, 0x0a, 0xf2, 0x77, 0x8e, 0x4e, 0xb3, 0x37, 0xf0, 0x41, 0xe2, 0x0d, 0x38, 0x66, 0x8d,
|
||||
0x7d, 0xe7, 0x52, 0x51, 0x47, 0x08, 0x54, 0x46, 0xb5, 0x4c, 0xc7, 0x89, 0x95, 0xfb, 0x46, 0xd0, 0xd5, 0x5b, 0x1d,
|
||||
0xce, 0x37, 0x9e, 0x1b, 0xbb, 0x11, 0xc4, 0x60, 0x2d, 0x14, 0x43, 0x4f, 0xb2, 0xf0, 0x1c, 0xb6, 0x67, 0x7b, 0xbb,
|
||||
0x57, 0xec, 0xf1, 0xca, 0x45, 0x52, 0xc1, 0x18, 0x63, 0x46, 0x31, 0x9e, 0x89, 0x9a, 0x58, 0x44, 0x64, 0xcb, 0xd6,
|
||||
0x61, 0x81, 0x01, 0x00, 0x68, 0x69, 0x72, 0xaf, 0x9a, 0x08, 0x95, 0xf1, 0x5c, 0x5a, 0x4f, 0x15, 0x44, 0x55, 0x8d,
|
||||
0xdf, 0xae, 0xcf, 0x40, 0x21, 0xb8, 0x37, 0x3a, 0x1e, 0x06, 0x21, 0x60, 0x17, 0x05, 0x2f, 0xd0, 0x07, 0xb4, 0x57,
|
||||
0x25, 0x42, 0x31, 0x73, 0xb2, 0x1e, 0x33, 0x8c, 0x54, 0xd0, 0x85, 0x4a, 0xd8, 0x2a, 0xcd, 0xf0, 0xab, 0x83, 0xd0,
|
||||
0x8c, 0x32, 0xee, 0xbf, 0xaa, 0xd6, 0x0c, 0xf2, 0x83, 0x79, 0xab, 0x84, 0xfa, 0x76, 0x25, 0x22, 0x53, 0x81, 0x89,
|
||||
0x87, 0x59, 0x4a, 0xbf, 0x5f, 0xd6, 0x49, 0x3f, 0x2f, 0x97, 0xe7, 0x9c, 0x24, 0x5f, 0xe7, 0x0e, 0x92, 0x4f, 0xba,
|
||||
0xfb, 0x95, 0xf0, 0x43, 0x0d, 0xa3, 0x26, 0xfc, 0xea, 0x5b, 0x1a, 0xe6, 0x9e, 0x72, 0x6f, 0xf5, 0xbb, 0xc8, 0x74,
|
||||
0x51, 0x9e, 0x83, 0x22, 0xa4, 0x1f, 0xc1, 0x34, 0x34, 0x68, 0x50, 0x24, 0x8b, 0xc5, 0xda, 0x04, 0x71, 0x7d, 0xcc,
|
||||
0xa9, 0x76, 0x28, 0x63, 0x8c, 0x68, 0x5a, 0x52, 0x90, 0x24, 0x70, 0x50, 0x7e, 0x03, 0x03, 0x62, 0x12, 0x12, 0xd2,
|
||||
0x20, 0x74, 0xd6, 0x66, 0x22, 0x2a, 0x73, 0xf1, 0x76, 0xe5, 0xb2, 0x26, 0x50, 0x84, 0x9e, 0x60, 0xa6, 0x52, 0x2a,
|
||||
0x08, 0xa4, 0xca, 0xb7, 0xd1, 0xa9, 0x39, 0x43, 0x73, 0xd7, 0x14, 0x40, 0x5e, 0xdb, 0xf5, 0xa0, 0xc9, 0x7b, 0xf2,
|
||||
0xa1, 0xaf, 0x13, 0x23, 0x5e, 0x66, 0xd0, 0x35, 0x1c, 0xfe, 0x1a, 0x2b, 0x29, 0x42, 0x26, 0x7c, 0xaf, 0x60, 0x13,
|
||||
0x21, 0x99, 0x82, 0x9e, 0x09, 0xf8, 0x43, 0xbd, 0xb2, 0x97, 0xee, 0xe5, 0x95, 0x49, 0x8b, 0xca, 0x56, 0xa2, 0x66,
|
||||
0x2d, 0x8e, 0xe2, 0xed, 0x14, 0xce, 0xb3, 0x47, 0x09, 0x04, 0x24, 0xa9, 0x9c, 0xa4, 0x9a, 0xf7, 0x28, 0x1d, 0x02,
|
||||
0x48, 0x70, 0xfa, 0x09, 0x2c, 0xb4, 0x9b, 0x12, 0x13, 0x2c, 0xaa, 0xc6, 0x6e, 0x73, 0x90, 0x9a, 0x73, 0x92, 0x7c,
|
||||
0x73, 0x94, 0xda, 0xdb, 0x4a, 0x7b, 0xc6, 0xec, 0x00, 0xdb, 0x76, 0xb7, 0xf3, 0xa3, 0x74, 0xbb, 0x33, 0x34, 0x18,
|
||||
0x17, 0x86, 0xff, 0x93, 0x12, 0xd3, 0x40, 0x0a, 0x29, 0x1b, 0x3f, 0xa1, 0x0c, 0xc3, 0xff, 0x96, 0x24, 0x80, 0x07,
|
||||
0xb5, 0xdd, 0x58, 0x31, 0xee, 0x15, 0x45, 0xc9, 0x6d, 0x55, 0xed, 0x6a, 0x09, 0x1a, 0xba, 0x91, 0xf4, 0x89, 0x62,
|
||||
0x9e, 0x13, 0x00, 0xa3, 0xc8, 0xfc, 0x1d, 0xd3, 0x49, 0xde, 0xbf, 0xac, 0x4d, 0xed, 0xf6, 0x7d, 0x3f, 0xca, 0x4f,
|
||||
0x29, 0xa4, 0xa2, 0xb2, 0x39, 0x89, 0xf8, 0x77, 0x05, 0x98, 0xe6, 0xc4, 0x47, 0x7a, 0xae, 0x61, 0x28, 0xc0, 0x57,
|
||||
0x3a, 0x94, 0x9a, 0xed, 0xe9, 0x1f, 0x9d, 0xed, 0xbe, 0x44, 0x8a, 0x20, 0x81, 0x06, 0x5e, 0xae, 0x59, 0x2f, 0xac,
|
||||
0x32, 0xb8, 0x23, 0xfe, 0x14, 0x7c, 0x5f, 0x5e, 0x07, 0x9f, 0x71, 0xfe, 0x05, 0xa0, 0x55, 0x81, 0x01, 0xe5, 0x83,
|
||||
0xa6, 0x62, 0x25, 0xd8, 0x25, 0x0a, 0xcc, 0xca, 0xcf, 0x1f, 0xd7, 0x69, 0xdd, 0xd4, 0x2c, 0xd1, 0x29, 0x3f, 0x77,
|
||||
0x0d, 0x33, 0xbe, 0xd7, 0xc8, 0x1f, 0xdf, 0x7f, 0x0e, 0xb2, 0x9d, 0x50, 0xbb, 0xb5, 0x55, 0x6c, 0x90, 0x86, 0x86,
|
||||
0xf7, 0xc2, 0xe6, 0xd0, 0x16, 0xf1, 0x52, 0xa8, 0x67, 0x2c, 0xc6, 0xdb, 0x22, 0x54, 0x86, 0x0f, 0x58, 0x30, 0x87,
|
||||
0x15, 0x82, 0xc5, 0x4e, 0x65, 0xf2, 0x19, 0x0e, 0x9a, 0x22, 0xd7, 0x42, 0x28, 0x7c, 0x39, 0x88, 0x4a, 0x49, 0x8b,
|
||||
0x75, 0xb4, 0x3d, 0x3b, 0x83, 0xe7, 0x97, 0x71, 0x01, 0xd8, 0x81, 0xe5, 0x57, 0x58, 0x16, 0x07, 0xc8, 0xc5, 0x43,
|
||||
0x59, 0xeb, 0x15, 0x8d, 0xc7, 0x37, 0x76, 0x61, 0x75, 0x01, 0x3e, 0x8d, 0xd2, 0x71, 0x22, 0x26, 0x31, 0x93, 0x2a,
|
||||
0xd7, 0xe4, 0xda, 0xe8, 0x5e, 0x5a, 0xa3, 0x79, 0x2e, 0x38, 0x78, 0x85, 0xe0, 0x06, 0xd3, 0x57, 0xf2, 0x72, 0xbd,
|
||||
0x82, 0x82, 0xa1, 0xf6, 0xe6, 0x26, 0x98, 0x2b, 0xf1, 0x98, 0xc1, 0x35, 0xfd, 0x3a, 0x9c, 0x8a, 0x6e, 0x5e, 0xae,
|
||||
0x18, 0xfc, 0x3a, 0x67, 0xac, 0x21, 0x00, 0x88, 0x4e, 0x1e, 0x5e, 0x6f, 0x26, 0xbd, 0x52, 0xd2, 0x41, 0x49, 0x84,
|
||||
0xf8, 0xae, 0xcc, 0xd7, 0x5d, 0x2a, 0xba, 0x72, 0xd5, 0xbd, 0xaf, 0x19, 0x33, 0x2e, 0x18, 0x3d, 0xe7, 0xb3, 0xa4,
|
||||
0x71, 0xed, 0x86, 0xee, 0xea, 0xfc, 0xe8, 0xfd, 0x20, 0xf3, 0x16, 0x66, 0x40, 0x26, 0x20, 0x0a, 0x9e, 0x7b, 0xaf,
|
||||
0x8d, 0x88, 0xf2, 0xb7, 0x66, 0x88, 0x57, 0x0e, 0xb3, 0x2e, 0x92, 0xfc, 0xed, 0xe0, 0xdb, 0xe0, 0xfa, 0x96, 0x46,
|
||||
0x04, 0xb9, 0xab, 0x22, 0xc8, 0x84, 0xb9, 0x99, 0x3e, 0x70, 0xfb, 0x77, 0x65, 0x08, 0x22, 0x2a, 0xa6, 0x43, 0xe5,
|
||||
0xb8, 0x7f, 0xb4, 0x41, 0xa5, 0x42, 0xe2, 0x53, 0x95, 0xbb, 0x72, 0x6d, 0x6a, 0xa8, 0xc7, 0x75, 0x32, 0x0b, 0x4d,
|
||||
0xb3, 0x26, 0x97, 0xb2, 0x69, 0x31, 0x32, 0x4d, 0x4e, 0xb5, 0xf9, 0xdd, 0x6b, 0x83, 0x74, 0x0c, 0xd5, 0xc5, 0x5a,
|
||||
0x2d, 0x98, 0xdf, 0x95, 0x17, 0xde, 0xf5, 0x62, 0x23, 0x95, 0xa1, 0xa6, 0x3d, 0x8a, 0x3e, 0x8e, 0xdb, 0xcc, 0xe5,
|
||||
0x51, 0xfa, 0x67, 0x0d, 0x00, 0xd3, 0x10, 0x16, 0xdd, 0x4d, 0xcb, 0xd8, 0x13, 0xcb, 0xd3, 0x13, 0x19, 0x28, 0x7a,
|
||||
0xae, 0xf3, 0x55, 0xab, 0xc4, 0xd2, 0x35, 0x08, 0x76, 0x6f, 0xc8, 0x58, 0x95, 0xb8, 0x5b, 0xad, 0x5f, 0xcd, 0xf3,
|
||||
0x79, 0xca, 0x57, 0xf2, 0x7c, 0x6a, 0x1a, 0xdd, 0x46, 0xdb, 0xbd, 0x39, 0x35, 0x54, 0xcc, 0xb5, 0xbe, 0xc9, 0x1f,
|
||||
0x98, 0xae, 0x83, 0xae, 0x16, 0x81, 0x66, 0x75, 0xaa, 0x9e, 0x95, 0xe5, 0xac, 0x9e, 0xc9, 0x31, 0x13, 0xb6, 0xa9,
|
||||
0x34, 0x87, 0xe8, 0x86, 0xa9, 0x9a, 0xe9, 0xc7, 0xc6, 0xb1, 0x90, 0x6d, 0x9e, 0x5f, 0x8e, 0x73, 0xc0, 0xb4, 0x3c,
|
||||
0x5f, 0x26, 0x0c, 0x3f, 0x5e, 0x5d, 0xfd, 0x28, 0xf8, 0x54, 0xd5, 0xd1, 0x5b, 0xbe, 0xd4, 0x3d, 0x83, 0x59, 0xa9,
|
||||
0x8c, 0x88, 0x13, 0xb6, 0x7e, 0xf0, 0xe6, 0xe9, 0x15, 0xb0, 0x9c, 0xc0, 0xea, 0x4e, 0x98, 0xd3, 0x18, 0xaa, 0x3a,
|
||||
0xc0, 0x3f, 0xac, 0x1f, 0x6c, 0xdd, 0x19, 0xfe, 0x61, 0xf0, 0x43, 0x70, 0x63, 0x63, 0xe3, 0x18, 0xef, 0xd6, 0x12,
|
||||
0x41, 0x5e, 0x61, 0x40, 0x1f, 0xaf, 0x3e, 0x0a, 0x5c, 0xae, 0x63, 0xdb, 0x03, 0x87, 0xdc, 0xd6, 0xc0, 0xdf, 0x24,
|
||||
0x4f, 0x1a, 0x2d, 0x0a, 0x9e, 0xcd, 0xe4, 0x0c, 0x85, 0xbc, 0xe6, 0xe3, 0xa0, 0xee, 0x08, 0x7f, 0x03, 0xa7, 0x16,
|
||||
0x5e, 0x5e, 0x7e, 0x82, 0x3e, 0x60, 0xe9, 0x4a, 0x6e, 0x2a, 0xfc, 0x94, 0xf2, 0x88, 0xae, 0xd6, 0x79, 0x30, 0x52,
|
||||
0x5c, 0x4c, 0x51, 0xe8, 0xb8, 0xcb, 0x1b, 0x67, 0x23, 0xa3, 0xbf, 0xc4, 0xab, 0x8b, 0x74, 0xf9, 0x48, 0x64, 0xab,
|
||||
0x96, 0xde, 0x2f, 0x3a, 0xba, 0x6d, 0xcf, 0x18, 0x9f, 0x66, 0x63, 0x0a, 0xcc, 0xf8, 0x38, 0x11, 0x5e, 0x9f, 0x18,
|
||||
0xeb, 0xbb, 0x45, 0xa0, 0xba, 0x39, 0x36, 0xd9, 0xe1, 0x78, 0xbd, 0xd9, 0xac, 0x71, 0x07, 0x6f, 0x9c, 0x27, 0xce,
|
||||
0xb2, 0x44, 0x8f, 0xca, 0x52, 0xc3, 0x03, 0x52, 0x21, 0x6e, 0xde, 0x33, 0x81, 0x71, 0xd9, 0x25, 0x71, 0x6d, 0x37,
|
||||
0x10, 0x6b, 0xb1, 0x27, 0x31, 0x4b, 0xc6, 0xb6, 0x07, 0xe5, 0x81, 0xbe, 0x18, 0x4d, 0xb7, 0x80, 0x69, 0x7b, 0xed,
|
||||
0xec, 0x3c, 0xb5, 0xbd, 0x6a, 0xaa, 0x00, 0x66, 0xc9, 0xf2, 0xf8, 0x14, 0x49, 0xf7, 0x1b, 0xe8, 0x22, 0x06, 0x8c,
|
||||
0x8d, 0x2b, 0x73, 0xee, 0x72, 0xdd, 0x8e, 0xf8, 0x46, 0x13, 0xa9, 0x52, 0x1f, 0x51, 0xdf, 0x61, 0x58, 0xab, 0xab,
|
||||
0x0c, 0x24, 0x30, 0x8f, 0xbc, 0x3b, 0xae, 0xa5, 0xa7, 0x63, 0x16, 0x93, 0x2a, 0x7d, 0x4b, 0x5d, 0x8b, 0x6b, 0xba,
|
||||
0xbd, 0xe2, 0x01, 0xe8, 0x1f, 0xe8, 0xb7, 0x88, 0x85, 0xbf, 0x9d, 0xd7, 0x52, 0x58, 0x1b, 0x73, 0xe4, 0xe8, 0x6b,
|
||||
0x0f, 0x7e, 0x61, 0xd5, 0x9e, 0x81, 0x1a, 0x66, 0xc4, 0x48, 0x7e, 0x33, 0xee, 0x55, 0x4d, 0x1c, 0xb9, 0x0b, 0xc0,
|
||||
0xfa, 0x96, 0x74, 0x49, 0x0e, 0xaf, 0x64, 0xb9, 0x2a, 0x86, 0xfc, 0x1b, 0xec, 0xb3, 0xde, 0x9c, 0x80, 0x99, 0x38,
|
||||
0xe5, 0x25, 0x26, 0xa6, 0x88, 0xcb, 0xcd, 0xd2, 0xe7, 0x69, 0xda, 0x2c, 0xda, 0xc0, 0x29, 0x8c, 0x04, 0x8e, 0xd8,
|
||||
0x37, 0xb6, 0xa1, 0x99, 0xb0, 0x11, 0x13, 0x6a, 0x54, 0x4a, 0x09, 0x1f, 0xc8, 0xad, 0x96, 0xf4, 0x65, 0x6e, 0xaf,
|
||||
0xbe, 0xdc, 0x26, 0x28, 0xa0, 0xa8, 0x81, 0xe5, 0xd0, 0x38, 0x6e, 0x19, 0xc8, 0x85, 0xc5, 0xb0, 0x30, 0x6a, 0x55,
|
||||
0xae, 0x26, 0xa3, 0x3a, 0x99, 0xaf, 0x16, 0x17, 0x2a, 0xf4, 0xe0, 0x91, 0x40, 0xce, 0x5f, 0x60, 0xea, 0x60, 0x56,
|
||||
0x6a, 0x33, 0x2d, 0x36, 0x51, 0xde, 0x33, 0x1d, 0x92, 0xeb, 0xaf, 0xe1, 0xa1, 0xf2, 0x8b, 0x57, 0xe6, 0x14, 0xf3,
|
||||
0x45, 0x1e, 0x4b, 0x5b, 0x63, 0x6e, 0xfd, 0xaf, 0xf2, 0x3e, 0xad, 0x04, 0xec, 0x37, 0x60, 0x53, 0xc6, 0x5a, 0x62,
|
||||
0xe3, 0x82, 0xa4, 0xbc, 0x96, 0xa7, 0xf4, 0xbe, 0x86, 0xf0, 0x5d, 0x51, 0xe9, 0x2a, 0x91, 0x75, 0x8d, 0x56, 0xf7,
|
||||
0xeb, 0x82, 0xe5, 0x97, 0x07, 0x0c, 0x73, 0x93, 0x51, 0x21, 0x5b, 0x51, 0xb3, 0x29, 0xbf, 0xda, 0xbb, 0xf1, 0x2b,
|
||||
0x0f, 0x25, 0x05, 0xd5, 0x2a, 0xd9, 0xbc, 0x72, 0xc3, 0x31, 0x6e, 0xdc, 0x70, 0x8c, 0x7b, 0x14, 0x57, 0xae, 0x50,
|
||||
0xad, 0xf3, 0xdf, 0x57, 0xdd, 0x4f, 0x74, 0xd6, 0x86, 0xfa, 0xd4, 0x0d, 0xd7, 0xa6, 0xa7, 0xdf, 0xb0, 0x54, 0x23,
|
||||
0x4b, 0xe8, 0xa6, 0xa5, 0x62, 0x32, 0x12, 0x25, 0xa6, 0xab, 0x94, 0x47, 0x7d, 0x8d, 0xb8, 0x00, 0x76, 0x43, 0xf9,
|
||||
0x8b, 0x7f, 0x0d, 0xcf, 0x8f, 0x03, 0x54, 0xa2, 0x96, 0x93, 0x2c, 0xe5, 0xad, 0x49, 0x34, 0x8b, 0x93, 0xcb, 0x60,
|
||||
0x11, 0xb7, 0x66, 0x59, 0x9a, 0x15, 0x73, 0xa0, 0x4a, 0xaf, 0xb8, 0x04, 0x1d, 0x7e, 0xd6, 0x5a, 0xc4, 0xde, 0x73,
|
||||
0x96, 0x9c, 0x31, 0x1e, 0x8f, 0x22, 0xcf, 0xde, 0xcf, 0x81, 0x3d, 0x58, 0xaf, 0xa3, 0x3c, 0xcf, 0xce, 0x6d, 0xef,
|
||||
0x5d, 0x76, 0x02, 0x44, 0xeb, 0xbd, 0xb9, 0xb8, 0x3c, 0x65, 0xa9, 0xf7, 0xfe, 0x64, 0x91, 0xf2, 0x85, 0x57, 0x44,
|
||||
0x69, 0xd1, 0x2a, 0x58, 0x1e, 0x4f, 0x40, 0x4c, 0x24, 0x59, 0xde, 0xc2, 0xfc, 0xe7, 0x19, 0x0b, 0x92, 0xf8, 0x74,
|
||||
0xca, 0xad, 0x71, 0x94, 0x7f, 0xea, 0xb5, 0x5a, 0xf3, 0x3c, 0x9e, 0x45, 0xf9, 0x65, 0x8b, 0x5a, 0x04, 0x9f, 0xb5,
|
||||
0x77, 0xa3, 0x2f, 0x26, 0xf7, 0x7b, 0x3c, 0x87, 0xbe, 0x31, 0x62, 0x31, 0x00, 0xe6, 0x63, 0xed, 0x3e, 0x68, 0xcf,
|
||||
0x8a, 0x0d, 0x11, 0x51, 0x8a, 0x52, 0x5e, 0x1e, 0x7b, 0x1f, 0x41, 0xb7, 0x3d, 0xf6, 0x4f, 0x78, 0xea, 0x81, 0x2d,
|
||||
0xc7, 0xb3, 0x74, 0x39, 0x5a, 0xe4, 0x05, 0x0c, 0x30, 0xcf, 0xe2, 0x94, 0xb3, 0xbc, 0x77, 0x92, 0xe5, 0x80, 0xb6,
|
||||
0x56, 0x1e, 0x8d, 0xe3, 0x45, 0x11, 0xdc, 0x9f, 0x5f, 0xf4, 0x50, 0x57, 0x38, 0xcd, 0xb3, 0x45, 0x3a, 0x96, 0x73,
|
||||
0xc5, 0x29, 0x1c, 0x8c, 0x98, 0x9b, 0x15, 0xf4, 0x25, 0x14, 0x80, 0x2f, 0x65, 0x51, 0xde, 0x3a, 0xc5, 0xce, 0xa8,
|
||||
0xe8, 0xb7, 0xc7, 0xec, 0xd4, 0xcb, 0x4f, 0x4f, 0x22, 0xa7, 0xd3, 0x7d, 0xe4, 0xa9, 0x7f, 0xfe, 0x03, 0x17, 0x14,
|
||||
0xf7, 0xb5, 0xc5, 0x9d, 0x76, 0xfb, 0x1f, 0xdc, 0x5e, 0x63, 0x16, 0x02, 0x28, 0xe8, 0xcc, 0x2f, 0xac, 0x22, 0x4b,
|
||||
0x60, 0x7f, 0xd6, 0xf5, 0xec, 0xcd, 0xc1, 0x6e, 0x8a, 0xd3, 0xd3, 0xa0, 0x3b, 0xbf, 0x28, 0x71, 0x75, 0x81, 0x48,
|
||||
0xc8, 0x94, 0x8b, 0x94, 0x6f, 0xcb, 0x3f, 0x0a, 0xf1, 0xe3, 0xf5, 0x10, 0x77, 0x15, 0xc4, 0x15, 0xd6, 0x5b, 0x63,
|
||||
0x38, 0x07, 0x84, 0xfe, 0x4e, 0x21, 0x00, 0x99, 0x82, 0x11, 0x98, 0x2b, 0x38, 0xe8, 0xe5, 0x0f, 0x83, 0xd1, 0x5d,
|
||||
0x0f, 0xc6, 0xe3, 0xdb, 0xc0, 0xc8, 0xd3, 0xf1, 0xb2, 0xbe, 0xaf, 0x1d, 0x30, 0x4e, 0x7b, 0x53, 0x86, 0xf4, 0x14,
|
||||
0x74, 0xf1, 0xf9, 0x3c, 0x1e, 0xf3, 0xa9, 0x78, 0x24, 0x72, 0x3e, 0x17, 0x75, 0x0f, 0xda, 0x6d, 0xf1, 0x5e, 0x80,
|
||||
0x40, 0x0b, 0x3a, 0x3e, 0x36, 0x00, 0x22, 0x7a, 0x71, 0xdd, 0x47, 0x6c, 0x3e, 0xdc, 0xfa, 0xa5, 0x1a, 0xef, 0x52,
|
||||
0xe5, 0x0d, 0x0a, 0x11, 0xa1, 0xbe, 0xd9, 0x82, 0x19, 0x6f, 0x45, 0xbf, 0xa3, 0x03, 0x55, 0x83, 0x0f, 0x8c, 0xa4,
|
||||
0x5e, 0xc0, 0x3d, 0x33, 0x17, 0xa8, 0x97, 0xf6, 0xd1, 0x25, 0xd5, 0x6a, 0xb9, 0x20, 0x37, 0x18, 0xba, 0x90, 0x28,
|
||||
0x20, 0xe8, 0x14, 0x83, 0x9c, 0xbe, 0xa9, 0x91, 0xb9, 0x41, 0xee, 0x64, 0x2e, 0x1c, 0xf9, 0x4c, 0xf3, 0xf5, 0x62,
|
||||
0x6b, 0x0b, 0xac, 0xec, 0x17, 0x4c, 0x36, 0x00, 0xee, 0x4d, 0xae, 0xae, 0xef, 0x43, 0x61, 0x4a, 0x29, 0x43, 0x6a,
|
||||
0x76, 0xd3, 0x15, 0x7d, 0xd8, 0x95, 0x98, 0x32, 0x92, 0x8f, 0x86, 0xff, 0x0e, 0xc5, 0xde, 0xd1, 0x86, 0x65, 0x91,
|
||||
0x2d, 0xf2, 0x11, 0x79, 0xea, 0x56, 0x2d, 0x7e, 0x9b, 0x04, 0xae, 0xed, 0x31, 0xcd, 0xe7, 0xd1, 0x0c, 0xae, 0x7d,
|
||||
0xe4, 0x80, 0x53, 0x10, 0x44, 0xdc, 0x31, 0x90, 0x5e, 0x0e, 0x05, 0x21, 0x8a, 0xae, 0x31, 0xe5, 0xbb, 0xd1, 0xfd,
|
||||
0x4b, 0x7f, 0x91, 0xc6, 0xc0, 0xe9, 0x3e, 0xc6, 0x63, 0xba, 0x77, 0x12, 0x8f, 0x29, 0x10, 0xd1, 0xa2, 0xc4, 0x23,
|
||||
0xf4, 0x6c, 0x43, 0x81, 0xfa, 0x0e, 0x0b, 0x3c, 0xcb, 0x44, 0x16, 0xbb, 0x65, 0x63, 0x30, 0xc1, 0x10, 0x95, 0xe3,
|
||||
0x6c, 0x16, 0xc5, 0x69, 0x80, 0xdf, 0x07, 0xf1, 0xf4, 0x88, 0x01, 0x76, 0xf1, 0xe0, 0x27, 0x93, 0xb9, 0x68, 0x1d,
|
||||
0xd7, 0xff, 0x05, 0xf8, 0x08, 0xf5, 0x2f, 0xa5, 0x1d, 0xa6, 0xe1, 0x52, 0x61, 0xde, 0x7a, 0x29, 0xf0, 0x1e, 0xae,
|
||||
0x74, 0x56, 0x46, 0x7e, 0x8e, 0x3d, 0x4e, 0x3f, 0x06, 0xad, 0x4e, 0xd0, 0xd1, 0xa6, 0x6b, 0xed, 0x36, 0xaa, 0xc8,
|
||||
0x65, 0x91, 0x37, 0x1a, 0x09, 0x06, 0xfd, 0x2c, 0xe0, 0xac, 0xde, 0x35, 0xac, 0x9e, 0xa4, 0x4b, 0x74, 0xe0, 0x9c,
|
||||
0xa6, 0x4e, 0x0d, 0x08, 0x8a, 0x05, 0x5c, 0x33, 0x95, 0x5b, 0x46, 0x24, 0x94, 0xbe, 0xa4, 0x03, 0x5c, 0xbf, 0x4b,
|
||||
0x84, 0xf7, 0x86, 0xea, 0x29, 0x50, 0x8a, 0xe4, 0x16, 0xc7, 0x7b, 0xe2, 0xc4, 0x5b, 0x44, 0x63, 0xa1, 0x0d, 0x47,
|
||||
0xd0, 0xb6, 0xfe, 0x32, 0x02, 0x2c, 0x7d, 0x0a, 0xed, 0xcd, 0xa5, 0xa3, 0x12, 0xeb, 0x73, 0x98, 0x6b, 0x5f, 0x48,
|
||||
0x3d, 0xba, 0x91, 0x6f, 0xf7, 0x37, 0x97, 0xbc, 0xdc, 0xdb, 0x11, 0xbd, 0xfb, 0xc7, 0x65, 0x41, 0x02, 0xca, 0x74,
|
||||
0xa4, 0x55, 0x53, 0x88, 0x3a, 0x18, 0x96, 0xd2, 0x77, 0x71, 0xdc, 0x42, 0x2b, 0x5d, 0xc2, 0x63, 0x2c, 0xc9, 0x2e,
|
||||
0xc7, 0x74, 0xa5, 0x28, 0x87, 0x33, 0xa9, 0x13, 0x52, 0x72, 0x91, 0x83, 0xd1, 0x5b, 0x85, 0xe2, 0x18, 0x21, 0x18,
|
||||
0x6c, 0x2e, 0xe3, 0x32, 0xdc, 0x5c, 0x66, 0xe5, 0x31, 0x68, 0x26, 0x08, 0x55, 0xa1, 0x3e, 0xef, 0x02, 0x13, 0x0b,
|
||||
0x27, 0x8b, 0x45, 0x23, 0xe0, 0xb4, 0xac, 0xb4, 0xad, 0x81, 0x80, 0x06, 0x2c, 0x40, 0x2c, 0x00, 0xdd, 0x8d, 0x7a,
|
||||
0x31, 0x58, 0x8b, 0x68, 0xdd, 0x87, 0x81, 0xf6, 0x76, 0x44, 0x23, 0x58, 0x57, 0x8e, 0x20, 0x57, 0xcb, 0xc2, 0x74,
|
||||
0x1c, 0x73, 0x69, 0x49, 0x74, 0xc2, 0x12, 0xe8, 0x9f, 0x5f, 0x5d, 0xb5, 0xa1, 0x9b, 0x78, 0xb5, 0xf6, 0xe2, 0x74,
|
||||
0xbe, 0x90, 0xdf, 0xd4, 0x82, 0x59, 0x3a, 0x18, 0xe6, 0xc4, 0x94, 0xff, 0x81, 0x8a, 0xdb, 0x05, 0x36, 0x8d, 0x6b,
|
||||
0x03, 0x3c, 0x14, 0x32, 0x40, 0x50, 0x2a, 0x1a, 0x80, 0xd2, 0x78, 0xbc, 0x5a, 0xa6, 0x97, 0x51, 0xc0, 0x0b, 0x9c,
|
||||
0xc1, 0x39, 0x3e, 0xa7, 0xf0, 0x3c, 0x8b, 0x53, 0x7c, 0xcc, 0xf1, 0x31, 0xba, 0xc0, 0xc7, 0xac, 0xb4, 0xff, 0x2e,
|
||||
0xe8, 0xb6, 0x34, 0x02, 0xb2, 0xab, 0x2b, 0x60, 0xee, 0x1a, 0x05, 0x40, 0x10, 0xe2, 0xdb, 0x2a, 0xcc, 0xc4, 0x16,
|
||||
0x2b, 0xe6, 0x2d, 0x51, 0x6e, 0x91, 0xf0, 0x0c, 0xc1, 0xb6, 0xca, 0x9d, 0x86, 0x8e, 0xe0, 0xc9, 0x2c, 0x92, 0x27,
|
||||
0xf8, 0xe2, 0xda, 0x96, 0xf8, 0xf8, 0x85, 0x40, 0x07, 0x3d, 0xe2, 0xda, 0x74, 0x19, 0x97, 0x9f, 0xb5, 0x89, 0x43,
|
||||
0x1b, 0x67, 0x01, 0x35, 0x0d, 0x99, 0x3d, 0x8f, 0xe2, 0x44, 0x34, 0x5e, 0xb3, 0x92, 0x46, 0x3a, 0x20, 0x2d, 0x64,
|
||||
0x6f, 0xa7, 0x82, 0x0d, 0x80, 0x1f, 0x89, 0xcb, 0xd4, 0x15, 0xf4, 0xb6, 0xa8, 0xa2, 0x28, 0xb9, 0x3c, 0xbc, 0x03,
|
||||
0xe1, 0x0f, 0xd7, 0xeb, 0x1c, 0x82, 0x5d, 0x17, 0xa5, 0xf5, 0x16, 0x00, 0xf1, 0x9c, 0xb1, 0xb1, 0x67, 0x5b, 0xc0,
|
||||
0x26, 0xc5, 0xf3, 0xc7, 0x84, 0x9d, 0x31, 0xf9, 0x11, 0x14, 0xdd, 0x57, 0x57, 0x8e, 0x40, 0xda, 0x72, 0x79, 0x3f,
|
||||
0x53, 0x52, 0x9e, 0x5a, 0x97, 0x5c, 0x7d, 0x1d, 0x78, 0xcf, 0x36, 0x06, 0x6d, 0xce, 0xd1, 0xae, 0x0f, 0xeb, 0x75,
|
||||
0x40, 0x91, 0xb5, 0x01, 0x4c, 0xd2, 0xcf, 0x6e, 0x5a, 0x0a, 0xf4, 0x63, 0x93, 0x09, 0x1c, 0x00, 0x15, 0xb7, 0xd0,
|
||||
0xa7, 0x5b, 0x00, 0x03, 0x66, 0xa6, 0x67, 0x8b, 0x16, 0x76, 0xd5, 0x56, 0x3f, 0x21, 0x2a, 0x92, 0x6c, 0xf4, 0xa9,
|
||||
0x36, 0xc5, 0x02, 0x09, 0x08, 0xc7, 0x6a, 0xf0, 0x29, 0xfb, 0xdf, 0xfe, 0xf5, 0x7f, 0xfe, 0x57, 0x18, 0x8e, 0x3a,
|
||||
0xb8, 0xa5, 0x75, 0x7d, 0xab, 0xff, 0x01, 0xad, 0x16, 0xe9, 0x2d, 0xed, 0xfe, 0xf6, 0xcf, 0xff, 0x0d, 0x9a, 0xd1,
|
||||
0xcd, 0x1a, 0xb7, 0x3c, 0x0e, 0xec, 0x11, 0x6a, 0x32, 0x77, 0x03, 0xa4, 0xd6, 0xf5, 0xda, 0xf1, 0xff, 0x05, 0x81,
|
||||
0x2d, 0x78, 0x36, 0xbf, 0x11, 0x08, 0x84, 0x75, 0x94, 0x64, 0x05, 0x13, 0x50, 0x08, 0x36, 0x79, 0x47, 0x30, 0x68,
|
||||
0x86, 0x39, 0x90, 0x6c, 0x61, 0x89, 0xde, 0x02, 0xfb, 0xb5, 0xde, 0x8d, 0x5d, 0x29, 0x18, 0x27, 0xd0, 0xc9, 0x03,
|
||||
0x00, 0xfb, 0x20, 0x9e, 0xe0, 0x81, 0x4e, 0x33, 0x6c, 0xbb, 0xce, 0x17, 0x68, 0x0c, 0xa1, 0x89, 0x4c, 0x8c, 0x20,
|
||||
0x5c, 0x1d, 0xaa, 0x1f, 0xfc, 0x04, 0xd6, 0xf2, 0x51, 0x3f, 0x47, 0x17, 0xfa, 0x19, 0xd9, 0x0f, 0x0c, 0x0b, 0x82,
|
||||
0x62, 0x86, 0x3a, 0x40, 0x73, 0x61, 0xea, 0xa4, 0x56, 0xfc, 0x81, 0xa9, 0xe4, 0xb0, 0x8f, 0x98, 0x0f, 0x89, 0xb7,
|
||||
0x5f, 0x16, 0x39, 0xab, 0x38, 0x26, 0x36, 0x10, 0xac, 0xc8, 0xac, 0xff, 0x98, 0x64, 0xe7, 0xe5, 0x75, 0x75, 0x53,
|
||||
0xa0, 0xe2, 0x72, 0x6f, 0x1c, 0x9f, 0xf5, 0x25, 0x22, 0x1b, 0x6b, 0x59, 0xed, 0xd2, 0x5c, 0x18, 0x56, 0xc9, 0x75,
|
||||
0xc9, 0x47, 0x5c, 0x96, 0xd7, 0x46, 0x01, 0x80, 0xe3, 0xee, 0x9d, 0xe4, 0x7d, 0xb9, 0x80, 0x57, 0x78, 0x61, 0x8b,
|
||||
0x20, 0x41, 0x3e, 0x2e, 0x64, 0x0c, 0x27, 0x19, 0x63, 0xb2, 0x7a, 0xd4, 0x5a, 0x33, 0xc5, 0xd2, 0xb1, 0x61, 0x8d,
|
||||
0x0b, 0x73, 0xc9, 0x85, 0x63, 0xa9, 0x0e, 0x49, 0x2e, 0x8c, 0x1f, 0xe0, 0x68, 0x70, 0xe1, 0xf8, 0x5a, 0x2e, 0x8c,
|
||||
0x6b, 0x1b, 0xe0, 0xc8, 0xa1, 0xbd, 0x8d, 0xb6, 0xb8, 0x21, 0x15, 0x38, 0x0a, 0x37, 0x30, 0xc0, 0x46, 0x9f, 0xa4,
|
||||
0x6c, 0x23, 0x50, 0xd1, 0x18, 0x95, 0xd2, 0x9a, 0x04, 0x9b, 0x64, 0xcf, 0xc1, 0xe2, 0x18, 0x64, 0x9b, 0x39, 0x32,
|
||||
0x58, 0xc2, 0x13, 0x86, 0xc7, 0xff, 0x78, 0x07, 0xfb, 0x8a, 0xcd, 0x2c, 0xe9, 0x19, 0xa4, 0xcf, 0x0e, 0x0d, 0xe0,
|
||||
0x2d, 0x85, 0x3b, 0x23, 0xb0, 0xdf, 0xbe, 0x39, 0x38, 0xb4, 0xbd, 0x93, 0x6c, 0x7c, 0x19, 0xd8, 0xa0, 0x8a, 0x82,
|
||||
0x24, 0x73, 0x7d, 0x3e, 0x65, 0xa9, 0xa3, 0x94, 0xc1, 0x2c, 0x01, 0x65, 0x38, 0x3b, 0x15, 0xb7, 0xef, 0x9b, 0xae,
|
||||
0x58, 0x40, 0x1b, 0x7d, 0x9e, 0xaf, 0xbf, 0xc7, 0xc5, 0x97, 0x2b, 0x79, 0x8e, 0x8f, 0x7d, 0x0c, 0x46, 0xef, 0xed,
|
||||
0xc0, 0x03, 0xbe, 0x1c, 0x20, 0x05, 0xe9, 0x37, 0x01, 0x67, 0x21, 0xde, 0x77, 0xb0, 0xfd, 0x8e, 0xea, 0x8b, 0x50,
|
||||
0x28, 0x1a, 0xd0, 0xfa, 0x5a, 0xa5, 0x04, 0xd0, 0xd8, 0x63, 0x22, 0x41, 0xdc, 0x18, 0xc0, 0x01, 0x1f, 0xeb, 0x12,
|
||||
0x41, 0xa6, 0x46, 0x11, 0x8d, 0x52, 0xb1, 0x7f, 0x59, 0x85, 0x13, 0x12, 0xfa, 0xc4, 0x64, 0xf0, 0x93, 0xc0, 0x3f,
|
||||
0x36, 0xbf, 0x34, 0x25, 0x3e, 0x0a, 0xa3, 0x17, 0x79, 0xf4, 0x57, 0xb0, 0x61, 0xbd, 0xf3, 0x63, 0x6a, 0xa9, 0xcc,
|
||||
0x1a, 0xb4, 0xb7, 0xd1, 0xfc, 0x6b, 0x2b, 0xfb, 0x15, 0x24, 0x5e, 0x12, 0xcd, 0x0b, 0x16, 0xa8, 0x07, 0x69, 0xe1,
|
||||
0xa0, 0xa1, 0xb4, 0x6a, 0x52, 0x9a, 0x92, 0xb1, 0xe4, 0xd3, 0xa5, 0x69, 0x02, 0x3d, 0x04, 0x13, 0x08, 0xd3, 0xb7,
|
||||
0x5b, 0x11, 0xb0, 0xf7, 0x34, 0x48, 0xd8, 0x84, 0x97, 0x1c, 0xef, 0x07, 0x2f, 0x95, 0xcd, 0xe9, 0x77, 0x1f, 0x80,
|
||||
0x59, 0x64, 0xf9, 0xf8, 0xff, 0x6d, 0x63, 0x8f, 0x83, 0x14, 0xcc, 0x18, 0xba, 0x30, 0x80, 0x97, 0xb1, 0x00, 0x22,
|
||||
0xf3, 0x7d, 0x69, 0x4c, 0x34, 0x62, 0x68, 0x8f, 0x97, 0x3c, 0xb7, 0xf8, 0xd4, 0xe3, 0xb9, 0xd9, 0x0e, 0x34, 0xa5,
|
||||
0x15, 0xa3, 0x7c, 0xd5, 0x2c, 0xdc, 0x75, 0xa5, 0xf2, 0xb8, 0xda, 0x58, 0xd9, 0xd6, 0xf5, 0xb7, 0x15, 0x0c, 0x19,
|
||||
0x5e, 0x80, 0x52, 0x70, 0xbe, 0xa5, 0xe8, 0x61, 0xae, 0x69, 0xd5, 0x3f, 0x70, 0xab, 0xee, 0x51, 0xd2, 0xd9, 0x3e,
|
||||
0xa2, 0xb3, 0x4d, 0xcc, 0x65, 0xb8, 0x14, 0x73, 0x8f, 0xa2, 0x64, 0xe4, 0x20, 0x00, 0x56, 0xcb, 0xba, 0x0f, 0xd8,
|
||||
0x04, 0x2e, 0x3d, 0x2c, 0xcb, 0xde, 0x25, 0x73, 0x8e, 0x7e, 0x93, 0x79, 0xe4, 0xe2, 0xfa, 0xa0, 0xfe, 0x04, 0x5b,
|
||||
0xbb, 0x74, 0x87, 0xde, 0xf7, 0xc6, 0x77, 0xad, 0x6c, 0x45, 0xa9, 0xb6, 0x07, 0xf8, 0xfd, 0x3e, 0xc4, 0xbe, 0xaf,
|
||||
0x1c, 0x1b, 0xb5, 0x10, 0xaa, 0xb9, 0x6c, 0x11, 0xe1, 0xd8, 0xd8, 0x4d, 0x78, 0x41, 0xbf, 0xba, 0xce, 0x98, 0xfd,
|
||||
0xee, 0x76, 0x63, 0x96, 0xdd, 0xd1, 0x98, 0xfd, 0xee, 0x4f, 0x36, 0x66, 0xbf, 0x6a, 0x1a, 0xb3, 0xbf, 0xfe, 0x1e,
|
||||
0x63, 0x36, 0xcf, 0xce, 0x8b, 0xb0, 0x23, 0x83, 0xa7, 0xc0, 0x4c, 0xfe, 0x3e, 0x56, 0x2d, 0x4c, 0xd4, 0xb0, 0x69,
|
||||
0xc9, 0x88, 0x15, 0xf9, 0x5e, 0xc0, 0xab, 0xa5, 0x09, 0xd9, 0xd6, 0x89, 0x55, 0xad, 0xfb, 0xea, 0x26, 0x09, 0xe8,
|
||||
0xf5, 0xae, 0xbe, 0x03, 0xd5, 0x55, 0x46, 0x66, 0x40, 0x9f, 0x82, 0xd4, 0x1d, 0xbb, 0xdb, 0x2a, 0xa3, 0xc7, 0x1c,
|
||||
0xa1, 0xa7, 0x1c, 0xb5, 0x82, 0x7c, 0x96, 0xf6, 0x7f, 0x3a, 0xea, 0xf4, 0x76, 0x3b, 0x33, 0xe8, 0x0d, 0x72, 0x0b,
|
||||
0xde, 0xda, 0xbd, 0xdd, 0x5d, 0x7c, 0x3b, 0x57, 0x6f, 0x5d, 0x7c, 0x8b, 0xd5, 0xdb, 0x03, 0x7c, 0x1b, 0xa9, 0xb7,
|
||||
0x87, 0xf8, 0x36, 0x56, 0x6f, 0x8f, 0xf0, 0xed, 0xcc, 0x2e, 0x8f, 0xb8, 0x06, 0xee, 0x11, 0xd0, 0x15, 0x29, 0x89,
|
||||
0x81, 0x2a, 0x83, 0xd3, 0x88, 0x37, 0xb0, 0xa2, 0xd3, 0x20, 0xf6, 0x84, 0x02, 0x1d, 0x14, 0xde, 0x39, 0xb0, 0xf4,
|
||||
0x80, 0x12, 0x8e, 0x9e, 0xe2, 0x55, 0x7c, 0xd0, 0x3d, 0x0f, 0xe3, 0x19, 0x53, 0xdf, 0x24, 0x55, 0xab, 0x06, 0x35,
|
||||
0x05, 0xec, 0xed, 0xb2, 0xa7, 0xf7, 0x49, 0xd8, 0xd0, 0x2a, 0x77, 0x82, 0x76, 0xae, 0xaa, 0x13, 0xd3, 0xb5, 0xf4,
|
||||
0x0e, 0x5f, 0x23, 0x20, 0x40, 0x00, 0x2b, 0xa3, 0x74, 0x02, 0x6a, 0x40, 0xeb, 0x02, 0x94, 0xf4, 0xb5, 0x42, 0x03,
|
||||
0x21, 0xd2, 0x62, 0x82, 0xd6, 0xa4, 0xdf, 0x0e, 0xa3, 0x53, 0xfd, 0xfc, 0x0a, 0xf4, 0xa9, 0xe8, 0x94, 0xdd, 0x26,
|
||||
0x40, 0x08, 0x44, 0x53, 0x78, 0x28, 0x20, 0x48, 0x0b, 0x81, 0xad, 0x41, 0x63, 0x41, 0x0a, 0x0f, 0xc4, 0x4e, 0x5d,
|
||||
0x9c, 0xd0, 0xf4, 0xf5, 0x22, 0xc0, 0x68, 0x55, 0xb0, 0x07, 0x6a, 0x1d, 0x95, 0x0a, 0x0c, 0x43, 0x05, 0x16, 0xdc,
|
||||
0x28, 0x63, 0x84, 0x2a, 0x72, 0x93, 0xa4, 0xb1, 0x94, 0x90, 0x31, 0x1d, 0xbc, 0xda, 0xbb, 0xbb, 0xca, 0xf7, 0x3e,
|
||||
0xeb, 0x8c, 0xf0, 0x8f, 0xe4, 0xaa, 0x9f, 0x4d, 0x26, 0x93, 0x1b, 0x85, 0xce, 0x67, 0xe3, 0x09, 0xeb, 0xb2, 0x07,
|
||||
0x3d, 0x74, 0xfe, 0xb5, 0xa4, 0x2f, 0xae, 0x53, 0x12, 0xee, 0x96, 0x77, 0x6b, 0x8c, 0xce, 0x38, 0x90, 0x43, 0x77,
|
||||
0x97, 0x4e, 0x25, 0x60, 0x65, 0x09, 0x5c, 0xf9, 0x34, 0x4e, 0x83, 0x76, 0xe9, 0x9f, 0x49, 0x76, 0xfe, 0xd9, 0xe3,
|
||||
0xc7, 0x8f, 0x4b, 0x7f, 0xac, 0xde, 0xda, 0xe3, 0x71, 0xe9, 0x8f, 0x96, 0x7a, 0x19, 0xed, 0xf6, 0x64, 0x52, 0xfa,
|
||||
0xb1, 0x2a, 0xd8, 0xed, 0x8e, 0xc6, 0xbb, 0xdd, 0xd2, 0x3f, 0x37, 0x5a, 0x94, 0x3e, 0x93, 0x6f, 0x39, 0x1b, 0xd7,
|
||||
0x3c, 0x88, 0x8f, 0xc0, 0x78, 0xf5, 0x05, 0xa1, 0x2d, 0xd1, 0x64, 0x10, 0x8f, 0x41, 0xb4, 0xe0, 0x60, 0xeb, 0x02,
|
||||
0x6f, 0x67, 0xc0, 0x9f, 0x27, 0x92, 0xb7, 0x8b, 0x4f, 0x7e, 0x22, 0x47, 0xff, 0xd5, 0xe4, 0xe8, 0x48, 0xcc, 0xc4,
|
||||
0xcd, 0x19, 0xc9, 0x81, 0x66, 0x35, 0x52, 0x16, 0x55, 0xff, 0x1a, 0xb2, 0x8a, 0xd9, 0x23, 0xb7, 0xc1, 0x96, 0x82,
|
||||
0xc7, 0x7f, 0x7d, 0x1d, 0x8f, 0xff, 0xe6, 0x76, 0x1e, 0x7f, 0x72, 0x37, 0x16, 0xff, 0xcd, 0x9f, 0xcc, 0xe2, 0xbf,
|
||||
0x6e, 0xb2, 0xf8, 0xcd, 0x3b, 0xb1, 0xf8, 0x35, 0x89, 0x1f, 0xa4, 0x9a, 0xbe, 0x49, 0x43, 0xfb, 0x0d, 0xd8, 0x30,
|
||||
0x46, 0xc9, 0x64, 0x02, 0x45, 0x93, 0x89, 0xad, 0x92, 0x1d, 0x81, 0x13, 0x51, 0xab, 0xd7, 0xb5, 0x12, 0x6a, 0xf5,
|
||||
0xd5, 0x57, 0x66, 0x99, 0x59, 0x20, 0xfd, 0x0d, 0xa6, 0x7c, 0x57, 0x35, 0x52, 0x65, 0x56, 0x9f, 0x06, 0x19, 0xc7,
|
||||
0x05, 0x9e, 0x26, 0x2c, 0x28, 0x79, 0x76, 0x7a, 0x9a, 0x30, 0xfd, 0xed, 0x33, 0xd5, 0xd2, 0x7c, 0x33, 0xe7, 0x33,
|
||||
0xcb, 0x07, 0x26, 0xb4, 0x41, 0x0d, 0xd0, 0x9e, 0x70, 0x64, 0xd2, 0xe7, 0xa0, 0x45, 0xd8, 0xfa, 0x4c, 0x7e, 0x37,
|
||||
0x98, 0xfc, 0xa9, 0x4b, 0xc9, 0x7e, 0x65, 0x40, 0xb3, 0xea, 0x8a, 0x2e, 0x4c, 0x91, 0x02, 0x32, 0x2e, 0x95, 0xdb,
|
||||
0x12, 0xa0, 0x9d, 0xe3, 0x47, 0x4e, 0x74, 0xca, 0xd2, 0xca, 0x37, 0x85, 0x34, 0x9b, 0xc0, 0x8f, 0x1e, 0x88, 0x29,
|
||||
0xc4, 0x67, 0x02, 0xf5, 0xb8, 0x22, 0x0e, 0xe8, 0xd4, 0xd6, 0x68, 0xac, 0x2a, 0x0c, 0xcd, 0xa5, 0xa8, 0x9c, 0x93,
|
||||
0xd5, 0x79, 0xd6, 0x8a, 0xe6, 0xeb, 0x85, 0xf2, 0xdd, 0xa6, 0xbb, 0x45, 0x34, 0x14, 0xe7, 0x76, 0x5f, 0xdb, 0x98,
|
||||
0x35, 0x9a, 0x29, 0xeb, 0x5e, 0x38, 0x9a, 0xe8, 0x24, 0xbb, 0xa8, 0xdb, 0x48, 0x26, 0x0c, 0x68, 0x3e, 0xe9, 0xbd,
|
||||
0x57, 0x75, 0xaa, 0xa0, 0x34, 0xbd, 0xa2, 0x22, 0xd3, 0x8b, 0x48, 0x83, 0x7c, 0x60, 0xb0, 0x03, 0xa9, 0x60, 0xca,
|
||||
0x30, 0x0f, 0x71, 0x17, 0x6d, 0x47, 0xa0, 0x32, 0x6d, 0x2b, 0x60, 0x51, 0x3a, 0xe4, 0xe8, 0x6b, 0xc2, 0x0e, 0x7d,
|
||||
0xab, 0x06, 0x70, 0xaa, 0x6d, 0xb3, 0xdb, 0x19, 0x3e, 0x98, 0x16, 0xe7, 0xc7, 0x7e, 0x71, 0xee, 0xc1, 0x3f, 0xeb,
|
||||
0xf3, 0x25, 0xb0, 0xb0, 0x93, 0x4f, 0x31, 0x07, 0x85, 0x71, 0xde, 0x42, 0xa3, 0x98, 0xdc, 0x3b, 0x92, 0xd7, 0x53,
|
||||
0xa8, 0x45, 0x5c, 0x89, 0xe8, 0x2d, 0x0a, 0xb4, 0x40, 0x48, 0xd5, 0x0e, 0xd2, 0x2c, 0x65, 0xbd, 0x7a, 0x48, 0xcd,
|
||||
0xd4, 0x76, 0x15, 0xb6, 0x86, 0xcb, 0x0c, 0x2d, 0x16, 0x7e, 0x09, 0x16, 0x8b, 0x90, 0x11, 0x6d, 0x15, 0x8e, 0x69,
|
||||
0xaf, 0x6d, 0x1f, 0x48, 0x64, 0x6e, 0x93, 0x28, 0xcc, 0x57, 0x55, 0xfa, 0xeb, 0x54, 0xf2, 0xdb, 0x02, 0x4c, 0xdd,
|
||||
0x07, 0x0f, 0x3c, 0xf5, 0xcf, 0x88, 0xcc, 0x35, 0x8b, 0x29, 0xc0, 0x74, 0x17, 0xc8, 0x82, 0x68, 0x82, 0x5f, 0x10,
|
||||
0xbb, 0x4b, 0xcb, 0x13, 0xca, 0xee, 0x5a, 0xa2, 0xcc, 0x0a, 0x3a, 0x8f, 0xc1, 0xc6, 0xb8, 0xf3, 0xf0, 0x37, 0x2f,
|
||||
0xbf, 0x94, 0x38, 0x52, 0x97, 0xf4, 0x6c, 0xbb, 0x87, 0xa7, 0x39, 0x89, 0x2e, 0xc1, 0xd4, 0x21, 0x01, 0x7a, 0x82,
|
||||
0xce, 0xae, 0xde, 0x3c, 0x93, 0x91, 0xd2, 0x9c, 0x25, 0xf4, 0x99, 0x7e, 0xb9, 0x15, 0xbb, 0x0f, 0xe7, 0x17, 0x6a,
|
||||
0x37, 0x3a, 0x8d, 0x08, 0xe8, 0x9f, 0x1a, 0xe8, 0xbc, 0x3e, 0xb2, 0x5a, 0x0f, 0xd6, 0x3d, 0x00, 0x18, 0x84, 0xd4,
|
||||
0x6e, 0xe5, 0x02, 0xaa, 0x36, 0x94, 0x18, 0xa1, 0xde, 0x6a, 0x20, 0xcb, 0xdf, 0x05, 0x09, 0x11, 0x81, 0xbd, 0x8b,
|
||||
0x9f, 0x72, 0x8b, 0xc1, 0xa0, 0x92, 0x9a, 0xc1, 0x2c, 0x1e, 0x8f, 0x13, 0xd6, 0x53, 0xc2, 0xdf, 0xea, 0x3c, 0xc4,
|
||||
0x48, 0xa9, 0xb9, 0x65, 0xf5, 0x5d, 0x31, 0x90, 0xa7, 0xf1, 0x14, 0x9d, 0x80, 0x32, 0x82, 0xdf, 0x63, 0x5b, 0x8b,
|
||||
0x4e, 0x19, 0x42, 0x6c, 0x57, 0xc8, 0xa3, 0xe7, 0xfa, 0x5a, 0x1e, 0x80, 0x26, 0x44, 0x1b, 0x0e, 0x46, 0x75, 0x36,
|
||||
0x0f, 0x5a, 0xbb, 0xf5, 0x85, 0x60, 0x95, 0x5e, 0x82, 0xb7, 0x66, 0x59, 0x1e, 0xd0, 0x44, 0x4b, 0x7c, 0xf8, 0xc7,
|
||||
0xf2, 0x3b, 0xb2, 0x8c, 0x06, 0xc0, 0x6f, 0x7e, 0xe9, 0xa2, 0xb2, 0xbe, 0x98, 0xff, 0x3f, 0xa7, 0xe5, 0x8b, 0xf5,
|
||||
0xa7, 0xe5, 0x0b, 0x75, 0x5a, 0x6e, 0xa6, 0xd8, 0xcf, 0x26, 0x1d, 0xfc, 0xd3, 0xab, 0x16, 0x04, 0xbb, 0x02, 0xe8,
|
||||
0xb0, 0x50, 0xe9, 0x6b, 0x75, 0xe1, 0x3f, 0x1a, 0xba, 0xed, 0xe1, 0x1f, 0x1f, 0xd4, 0x9b, 0xb6, 0x85, 0x85, 0xf8,
|
||||
0xaf, 0x5d, 0xab, 0xea, 0xdc, 0xc7, 0x3a, 0xec, 0xf5, 0x60, 0xb5, 0xae, 0x7b, 0xf3, 0xa1, 0x05, 0x7e, 0xc5, 0x9d,
|
||||
0x40, 0x31, 0x63, 0xb0, 0x43, 0xa2, 0x93, 0x13, 0x28, 0x9d, 0x64, 0xa3, 0x45, 0xf1, 0x8f, 0x12, 0x7e, 0x89, 0xc4,
|
||||
0x1b, 0x8f, 0x74, 0x63, 0x1c, 0xd5, 0x55, 0x84, 0xdd, 0xd5, 0x08, 0x4b, 0xbd, 0x4f, 0x41, 0x01, 0x84, 0xc9, 0x9c,
|
||||
0xae, 0x7f, 0x7f, 0xcd, 0x21, 0xf8, 0xbb, 0xec, 0xcd, 0xda, 0xc5, 0xfc, 0x7b, 0x91, 0x71, 0x23, 0x12, 0x7e, 0x17,
|
||||
0x0e, 0xcc, 0x3d, 0x6c, 0x3f, 0x5e, 0x0f, 0xee, 0x91, 0x9a, 0x69, 0xa8, 0x84, 0x82, 0x94, 0x3b, 0xa0, 0xe2, 0x46,
|
||||
0x8b, 0x84, 0xdf, 0x3c, 0xea, 0x75, 0x94, 0xb1, 0x32, 0xea, 0x0d, 0x0c, 0xbd, 0x6a, 0x7b, 0x47, 0x2e, 0xfd, 0xd9,
|
||||
0x17, 0xf7, 0xf1, 0x8f, 0xf0, 0xea, 0x9c, 0x54, 0x8a, 0xbf, 0x30, 0x7c, 0x51, 0xf1, 0xdf, 0xac, 0x69, 0xf6, 0x42,
|
||||
0x82, 0x93, 0x72, 0x7f, 0xd7, 0xd6, 0xa8, 0xcf, 0xde, 0xa9, 0xb9, 0xd4, 0x83, 0x7e, 0x57, 0xeb, 0xdf, 0x37, 0xf8,
|
||||
0x1d, 0xdb, 0x8e, 0x84, 0xce, 0x5c, 0x6f, 0x2b, 0x7f, 0x65, 0xc2, 0x6a, 0x63, 0x81, 0xe7, 0xbb, 0x36, 0x57, 0x1b,
|
||||
0x44, 0xed, 0x37, 0xc3, 0x13, 0x6d, 0x1e, 0xc9, 0xb0, 0x1b, 0xb6, 0x17, 0x16, 0xd2, 0xb7, 0x2c, 0xbc, 0x87, 0x9f,
|
||||
0x1a, 0xb2, 0x2e, 0x66, 0x49, 0x0a, 0x3a, 0xd5, 0x94, 0xf3, 0x79, 0xb0, 0xb3, 0x73, 0x7e, 0x7e, 0xee, 0x9f, 0xef,
|
||||
0xfa, 0x59, 0x7e, 0xba, 0xd3, 0x6d, 0xb7, 0xdb, 0xf8, 0x85, 0x18, 0xdb, 0x3a, 0x8b, 0xd9, 0xf9, 0x97, 0xd9, 0x45,
|
||||
0x68, 0x3f, 0xb2, 0x1e, 0x5b, 0x8f, 0x76, 0xad, 0x07, 0x0f, 0x6d, 0x8b, 0xb8, 0x3f, 0x94, 0xec, 0xda, 0x96, 0xe0,
|
||||
0xfe, 0xa1, 0x0d, 0xc5, 0xfd, 0xbd, 0x53, 0xa5, 0xc0, 0x61, 0x06, 0xae, 0x50, 0x8f, 0xc0, 0x66, 0xc9, 0x3e, 0xb1,
|
||||
0xfa, 0x39, 0x17, 0x65, 0x2d, 0x29, 0x43, 0xd4, 0x2b, 0x1e, 0xf6, 0x51, 0x34, 0x0f, 0x88, 0x86, 0xcc, 0x42, 0x74,
|
||||
0x00, 0x89, 0x52, 0x9a, 0x02, 0xa3, 0xba, 0x27, 0xf0, 0x04, 0x1a, 0xfb, 0xd4, 0x82, 0xe7, 0x57, 0xdd, 0x47, 0x20,
|
||||
0xe0, 0xce, 0x5a, 0xf7, 0x47, 0xed, 0x56, 0xc7, 0xea, 0xb4, 0xba, 0xfe, 0x23, 0xab, 0x2b, 0xfe, 0x07, 0x06, 0xb9,
|
||||
0x6b, 0x75, 0xe0, 0x69, 0xd7, 0x82, 0xf7, 0xb3, 0xfb, 0x22, 0x24, 0x1c, 0xd9, 0x3b, 0xfd, 0x3d, 0xfc, 0x85, 0x29,
|
||||
0xb0, 0xa8, 0x2f, 0x6c, 0xf1, 0x2b, 0x9e, 0xec, 0xcf, 0xcc, 0xd2, 0xce, 0xe3, 0xb5, 0xc5, 0xdd, 0x47, 0x6b, 0x8b,
|
||||
0x77, 0x1f, 0xae, 0x2d, 0xbe, 0xff, 0xa0, 0x5e, 0xbc, 0x73, 0x2a, 0xaa, 0x34, 0x53, 0x08, 0xed, 0x59, 0x04, 0x54,
|
||||
0x72, 0xe1, 0x74, 0x00, 0xce, 0xb6, 0xd5, 0xc2, 0x1f, 0x8f, 0xba, 0xae, 0xee, 0x75, 0x82, 0xbd, 0xf4, 0x2a, 0x1f,
|
||||
0x3d, 0x86, 0x55, 0x3e, 0xef, 0x3e, 0x1c, 0x61, 0x3b, 0x5a, 0x28, 0xfc, 0x3b, 0xdb, 0x7d, 0x3c, 0x02, 0x71, 0x60,
|
||||
0xe1, 0x3f, 0xf8, 0x33, 0x7d, 0xd0, 0x1d, 0x89, 0x97, 0x36, 0xd6, 0x7f, 0xe8, 0x3c, 0x2a, 0xa0, 0x29, 0xfe, 0xf9,
|
||||
0x4d, 0xeb, 0xcf, 0xa8, 0xbe, 0x9b, 0xe3, 0xde, 0x07, 0x1c, 0x3d, 0x9e, 0x76, 0xfd, 0x2f, 0xce, 0x1e, 0xf9, 0x8f,
|
||||
0xa7, 0x9d, 0x47, 0x1f, 0xc4, 0x5b, 0x02, 0x18, 0xfc, 0x02, 0xff, 0x7d, 0xd8, 0x6d, 0x83, 0x69, 0xeb, 0x3f, 0x3e,
|
||||
0xdb, 0xf5, 0x77, 0x93, 0xd6, 0x43, 0xff, 0x31, 0xfe, 0xab, 0x86, 0x9b, 0x66, 0x33, 0x66, 0x5b, 0xb8, 0xdf, 0x0d,
|
||||
0xbb, 0xd0, 0x9c, 0xa3, 0x7b, 0xdf, 0x7a, 0x70, 0xff, 0xf9, 0x63, 0xd8, 0xa3, 0x69, 0xa7, 0x0b, 0xff, 0x5f, 0xf7,
|
||||
0xf8, 0x01, 0x11, 0x2f, 0x07, 0x8e, 0x18, 0xe6, 0xce, 0x29, 0xc4, 0xd1, 0xd7, 0x8a, 0xee, 0x79, 0x3f, 0x5e, 0x67,
|
||||
0xda, 0xff, 0x70, 0xbb, 0x69, 0xff, 0xd7, 0x3b, 0xba, 0x6f, 0x7f, 0xf8, 0x93, 0x6d, 0xfb, 0x1f, 0x9b, 0xb6, 0xfd,
|
||||
0x39, 0x5b, 0x31, 0xee, 0x9b, 0xf6, 0xd9, 0x21, 0x73, 0x8e, 0xbe, 0x65, 0x43, 0xcc, 0x13, 0x85, 0xd6, 0x7f, 0xad,
|
||||
0x79, 0x3a, 0x32, 0x3c, 0xc8, 0xe7, 0x4c, 0x9c, 0xe4, 0xef, 0xaf, 0x43, 0x08, 0xe3, 0xb7, 0x22, 0xe4, 0xc5, 0xdd,
|
||||
0xf0, 0x41, 0x9f, 0x16, 0xff, 0x13, 0xf1, 0xf1, 0xbd, 0x89, 0x8f, 0x9a, 0x2f, 0x99, 0x8c, 0x79, 0xb2, 0xc1, 0x0f,
|
||||
0xe8, 0xdd, 0xb1, 0x77, 0x18, 0xbe, 0x15, 0xb6, 0x48, 0x7e, 0x7a, 0xf7, 0x7b, 0xfc, 0xde, 0x22, 0x8d, 0x32, 0xb4,
|
||||
0xa5, 0x83, 0x62, 0x8e, 0x1f, 0xe3, 0x54, 0x2f, 0x67, 0x22, 0x55, 0x3f, 0xa4, 0x7b, 0x36, 0xf7, 0xb5, 0x73, 0x03,
|
||||
0x33, 0x5b, 0xc3, 0x65, 0xc6, 0x23, 0xfc, 0x6d, 0x2f, 0x3c, 0xe6, 0x09, 0xde, 0x02, 0x94, 0x37, 0x66, 0x30, 0x11,
|
||||
0xf3, 0x5b, 0x4c, 0x22, 0x55, 0xee, 0xef, 0x19, 0x3a, 0x0c, 0x5e, 0xb1, 0x71, 0x1c, 0x39, 0xb6, 0x33, 0x87, 0x13,
|
||||
0x0b, 0x63, 0xb6, 0x6a, 0x19, 0x9c, 0x94, 0xbc, 0xe9, 0xda, 0xea, 0x17, 0x8c, 0xe4, 0xf8, 0xc1, 0xa6, 0xf0, 0x48,
|
||||
0xba, 0xce, 0x6c, 0xa9, 0xfe, 0xc3, 0xf8, 0xaa, 0x24, 0x47, 0xd6, 0x5d, 0xa9, 0x0c, 0xb6, 0xd0, 0x19, 0x3a, 0x7e,
|
||||
0x17, 0x6c, 0x08, 0x2a, 0xc6, 0x0f, 0xe0, 0xfc, 0xe0, 0xb4, 0x76, 0x41, 0xa7, 0x31, 0xba, 0xe9, 0x81, 0x86, 0x2b,
|
||||
0x1f, 0xdf, 0x14, 0x7e, 0x83, 0x46, 0xa9, 0xa7, 0x7f, 0xe3, 0x12, 0x50, 0x86, 0xca, 0xf5, 0xff, 0xf2, 0xf2, 0x50,
|
||||
0x5e, 0x72, 0xb5, 0xd1, 0x27, 0x49, 0xbe, 0xe8, 0xea, 0x03, 0x3b, 0xdb, 0x20, 0x2e, 0xe8, 0xd7, 0xde, 0x51, 0x50,
|
||||
0x16, 0x25, 0x02, 0xe6, 0x98, 0x5a, 0xd2, 0x6c, 0x08, 0x6d, 0x21, 0x0f, 0xc6, 0xec, 0x2c, 0x1e, 0x49, 0xb6, 0xee,
|
||||
0x59, 0x32, 0x37, 0xbe, 0x45, 0xab, 0x08, 0x3b, 0x9e, 0x30, 0x9c, 0xe1, 0x05, 0x65, 0x54, 0x98, 0x66, 0x76, 0xff,
|
||||
0x5e, 0x4f, 0x43, 0x52, 0x4f, 0xcf, 0xb5, 0xf1, 0x77, 0xf0, 0x1d, 0x81, 0xa1, 0xf6, 0x8f, 0xe1, 0x3d, 0xfc, 0x2d,
|
||||
0x7c, 0xf7, 0x86, 0xb6, 0xeb, 0x13, 0x53, 0xbc, 0x57, 0xfd, 0x2a, 0x3e, 0xe4, 0x08, 0xdb, 0x20, 0xbf, 0xbc, 0xbb,
|
||||
0x0a, 0x32, 0x29, 0xb4, 0xba, 0x0f, 0x2a, 0xa1, 0x05, 0xcf, 0x06, 0x97, 0x02, 0x06, 0xda, 0xf5, 0x1f, 0x18, 0xac,
|
||||
0xf0, 0xac, 0x85, 0x3f, 0x6b, 0xcc, 0xf0, 0x3e, 0x34, 0x50, 0xdc, 0xf0, 0x25, 0x34, 0xdf, 0x15, 0x8c, 0x17, 0xfa,
|
||||
0xfd, 0x48, 0xac, 0x4a, 0xb0, 0xa9, 0x3a, 0xc5, 0xac, 0x09, 0x8f, 0x88, 0x78, 0xb6, 0xed, 0x39, 0xfa, 0xfb, 0xfe,
|
||||
0x92, 0x5c, 0xe5, 0xe5, 0xa4, 0xa7, 0xd0, 0xd7, 0xd1, 0xdf, 0xad, 0x5d, 0x57, 0xe7, 0xd5, 0x4e, 0xce, 0x9a, 0x29,
|
||||
0x90, 0xe0, 0x1b, 0x21, 0x18, 0xca, 0xd5, 0x16, 0xdf, 0x6f, 0x12, 0xc7, 0xb8, 0xfa, 0xc2, 0xd5, 0x9a, 0x74, 0x43,
|
||||
0xf3, 0x50, 0xb0, 0x8a, 0x68, 0xe8, 0x5c, 0x00, 0x23, 0xa0, 0x9f, 0x55, 0xb1, 0x7a, 0x90, 0x04, 0xe5, 0x27, 0x11,
|
||||
0xfe, 0xfa, 0x09, 0xfa, 0x51, 0x56, 0x07, 0x90, 0xd3, 0x07, 0xfa, 0x08, 0xd2, 0x17, 0xe3, 0xb2, 0xb9, 0x08, 0xd0,
|
||||
0x17, 0xf0, 0xb7, 0x99, 0x55, 0xb9, 0xe1, 0xf2, 0xd2, 0x17, 0x86, 0xc1, 0xc7, 0x71, 0x4e, 0x77, 0x09, 0xd5, 0xfa,
|
||||
0x6b, 0xd7, 0xfc, 0x2a, 0x54, 0xd3, 0xa9, 0x64, 0xc5, 0xc0, 0xc6, 0x22, 0x5b, 0x65, 0xe9, 0x98, 0x5f, 0xa8, 0x35,
|
||||
0x2f, 0x7b, 0x8d, 0x45, 0x9a, 0x0e, 0x7e, 0xc1, 0xdb, 0x16, 0x48, 0xb6, 0x81, 0x8d, 0x5d, 0xbb, 0x26, 0x52, 0x6e,
|
||||
0xf0, 0x8e, 0x54, 0xf5, 0x2b, 0x59, 0xcc, 0x03, 0x6f, 0x9b, 0xbb, 0xa5, 0xc7, 0xa5, 0x7d, 0x70, 0xa5, 0xa7, 0xf0,
|
||||
0x84, 0x45, 0xdc, 0x8f, 0x52, 0xca, 0xf7, 0x70, 0x0c, 0xb6, 0xe0, 0x75, 0xd8, 0xae, 0x5b, 0x02, 0xe7, 0x31, 0x7e,
|
||||
0x67, 0x8d, 0x40, 0xbd, 0x0f, 0x85, 0x6e, 0xe5, 0xb5, 0x9b, 0x76, 0xfb, 0x6f, 0x0e, 0xf7, 0x2d, 0x71, 0x9a, 0xf7,
|
||||
0x76, 0xe0, 0x75, 0x8f, 0x6c, 0x61, 0x91, 0x52, 0x10, 0x8a, 0x94, 0x02, 0x4b, 0x64, 0xc3, 0x84, 0xf6, 0x8e, 0x58,
|
||||
0xa6, 0x6d, 0xb1, 0x74, 0x24, 0x3c, 0x78, 0x33, 0xb0, 0x15, 0x62, 0xfc, 0x8a, 0xd1, 0x0e, 0x76, 0x6b, 0xe1, 0x4e,
|
||||
0xc3, 0x11, 0x10, 0x3e, 0x3e, 0xa5, 0x20, 0xf0, 0xd4, 0x96, 0xfe, 0x3e, 0x10, 0xeb, 0x4c, 0x65, 0x62, 0xc8, 0xa1,
|
||||
0x74, 0x5e, 0xde, 0x6a, 0xeb, 0x62, 0x71, 0x32, 0x03, 0x3e, 0xa4, 0x92, 0x29, 0xde, 0xcb, 0x0e, 0x7b, 0x34, 0x15,
|
||||
0x66, 0x01, 0xae, 0x3a, 0x21, 0xa7, 0x9d, 0xfe, 0x5e, 0x24, 0xf5, 0x1d, 0x3c, 0xbb, 0x05, 0x1c, 0x5e, 0x10, 0x73,
|
||||
0xa8, 0x54, 0xf8, 0x71, 0xb6, 0x73, 0xce, 0x4e, 0x5a, 0xd1, 0x3c, 0xae, 0x7c, 0x7f, 0x28, 0xfd, 0xfa, 0x7b, 0x4a,
|
||||
0x10, 0xca, 0x84, 0x33, 0xf9, 0x18, 0x19, 0x89, 0x07, 0x88, 0x38, 0x22, 0xd0, 0x52, 0x3a, 0x16, 0x49, 0x69, 0x04,
|
||||
0xe4, 0x03, 0xac, 0x44, 0xbf, 0xca, 0x01, 0x29, 0x25, 0x41, 0x69, 0xf7, 0xff, 0xf6, 0xbf, 0xfe, 0xb7, 0xf4, 0x29,
|
||||
0x02, 0x5a, 0x01, 0x2c, 0xcc, 0xdc, 0xa8, 0x62, 0x67, 0xec, 0x02, 0xac, 0xd0, 0x78, 0xdc, 0x9a, 0x46, 0xc9, 0x04,
|
||||
0x20, 0x28, 0x98, 0xb8, 0xbb, 0x21, 0xeb, 0x81, 0x0a, 0x24, 0x58, 0x66, 0xd8, 0x59, 0x82, 0x57, 0x2f, 0xc2, 0x1d,
|
||||
0xfb, 0x43, 0x19, 0x7c, 0x2a, 0xb7, 0x94, 0x08, 0xda, 0xc8, 0xe7, 0x33, 0x68, 0xae, 0x96, 0xd3, 0xa7, 0x7e, 0x23,
|
||||
0x8c, 0x64, 0x1e, 0xac, 0x96, 0xd0, 0x07, 0x2d, 0x75, 0xa0, 0xe0, 0xdf, 0xfe, 0xf5, 0x3f, 0xff, 0x77, 0xf5, 0x8a,
|
||||
0xfe, 0xff, 0xbf, 0xfd, 0xcb, 0x3f, 0xfd, 0xdf, 0xff, 0xf3, 0x5f, 0x30, 0x39, 0x52, 0xc6, 0x08, 0xe8, 0x28, 0x59,
|
||||
0x55, 0x80, 0x40, 0x9c, 0xa9, 0x7a, 0xb6, 0xdf, 0x01, 0xcd, 0x42, 0x04, 0x29, 0x41, 0x22, 0x62, 0xa6, 0x24, 0x50,
|
||||
0x42, 0xd5, 0x0d, 0x38, 0x83, 0xfd, 0xb3, 0x28, 0x4a, 0x6d, 0x3f, 0x68, 0xdb, 0xd5, 0x9e, 0xf6, 0x8d, 0xbe, 0x3b,
|
||||
0xb8, 0x1b, 0x77, 0xca, 0x14, 0xf1, 0xf5, 0x5e, 0x2d, 0x95, 0xe3, 0x0a, 0x4b, 0xca, 0xaa, 0xdc, 0x42, 0x8f, 0xf2,
|
||||
0x12, 0x5f, 0x83, 0xae, 0x51, 0x4c, 0x5b, 0x5b, 0xeb, 0xd3, 0xfb, 0x65, 0x51, 0xf0, 0x78, 0x82, 0xfb, 0x21, 0xdc,
|
||||
0x63, 0x14, 0x0a, 0x6c, 0xa1, 0x4a, 0x92, 0x5c, 0x96, 0x34, 0x8a, 0x30, 0x61, 0xee, 0x3f, 0xfe, 0x87, 0xf2, 0x2f,
|
||||
0x33, 0x54, 0x05, 0x2c, 0x67, 0x16, 0x5d, 0x48, 0xc3, 0xe6, 0x61, 0xbb, 0x3d, 0xbf, 0x70, 0x97, 0xd5, 0x0c, 0xde,
|
||||
0x75, 0x93, 0x91, 0x4b, 0xcd, 0x1c, 0x90, 0x62, 0x88, 0xda, 0x7b, 0x07, 0xba, 0x7c, 0x1b, 0x9d, 0x3d, 0x65, 0xf9,
|
||||
0xf9, 0x92, 0x1c, 0x48, 0xf1, 0x6f, 0x18, 0xeb, 0x93, 0xbe, 0x36, 0x28, 0x31, 0x56, 0xb1, 0x34, 0x7a, 0x75, 0x45,
|
||||
0xaf, 0x69, 0x67, 0x35, 0xd3, 0xc4, 0x8c, 0x55, 0x9a, 0x51, 0x46, 0xcc, 0xc3, 0x80, 0x0e, 0xde, 0xb4, 0xbb, 0xd4,
|
||||
0xc3, 0x73, 0x9e, 0xcd, 0xcc, 0xe0, 0x24, 0x8b, 0xd8, 0x88, 0x4d, 0x94, 0x8f, 0x52, 0xd6, 0x8b, 0xc0, 0x63, 0xf9,
|
||||
0x19, 0x9e, 0x31, 0xc0, 0x6d, 0x16, 0xf1, 0x80, 0x28, 0xb5, 0x67, 0x86, 0x2f, 0x23, 0x0c, 0x0c, 0x67, 0x4b, 0x63,
|
||||
0xae, 0x9e, 0x68, 0x8a, 0x9e, 0xc0, 0x7a, 0x7e, 0x4a, 0xe9, 0x53, 0x77, 0x73, 0x28, 0xe1, 0x48, 0x78, 0x51, 0x65,
|
||||
0x87, 0x54, 0x26, 0xf6, 0xbb, 0x9a, 0x39, 0x2e, 0x99, 0x31, 0x18, 0xc1, 0xb7, 0x37, 0x16, 0x52, 0x52, 0x34, 0xfd,
|
||||
0x15, 0x94, 0x1f, 0x5a, 0x80, 0xdd, 0x6c, 0x45, 0x85, 0xd8, 0xea, 0x5d, 0xf8, 0x42, 0xab, 0xe2, 0xd1, 0x7c, 0x4e,
|
||||
0x0d, 0x5d, 0xa0, 0x53, 0x52, 0xa9, 0x91, 0x71, 0x50, 0x2c, 0x5c, 0x84, 0x9e, 0x65, 0x1b, 0x49, 0xd0, 0xe2, 0x49,
|
||||
0x06, 0xa5, 0xe9, 0xf7, 0x0d, 0xff, 0x3f, 0xdf, 0x8d, 0x21, 0x2b, 0x85, 0x78, 0x00, 0x00};
|
||||
|
||||
} // namespace web_server
|
||||
} // namespace esphome
|
||||
@@ -1,6 +1,7 @@
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "web_server.h"
|
||||
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/entity_base.h"
|
||||
@@ -17,18 +18,27 @@
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
#include <esphome/components/logger/logger.h>
|
||||
#include "esphome/components/logger/logger.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
#include "esphome/components/fan/fan_helpers.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
#include "esphome/components/climate/climate.h"
|
||||
#endif
|
||||
|
||||
#ifdef USE_WEBSERVER_LOCAL
|
||||
#include "server_index.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace web_server {
|
||||
|
||||
static const char *const TAG = "web_server";
|
||||
|
||||
#if USE_WEBSERVER_VERSION == 1
|
||||
void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &klass, const std::string &action,
|
||||
const std::function<void(AsyncResponseStream &stream, EntityBase *obj)> &action_func = nullptr) {
|
||||
stream->print("<tr class=\"");
|
||||
@@ -49,6 +59,7 @@ void write_row(AsyncResponseStream *stream, EntityBase *obj, const std::string &
|
||||
stream->print("</td>");
|
||||
stream->print("</tr>");
|
||||
}
|
||||
#endif
|
||||
|
||||
UrlMatch match_url(const std::string &url, bool only_domain = false) {
|
||||
UrlMatch match;
|
||||
@@ -87,78 +98,16 @@ void WebServer::setup() {
|
||||
this->base_->init();
|
||||
|
||||
this->events_.onConnect([this](AsyncEventSourceClient *client) {
|
||||
// Configure reconnect timeout
|
||||
client->send("", "ping", millis(), 30000);
|
||||
// Configure reconnect timeout and send config
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
for (auto *obj : App.get_sensors()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->sensor_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
client->send(json::build_json([this](JsonObject root) {
|
||||
root["title"] = App.get_name();
|
||||
root["ota"] = this->allow_ota_;
|
||||
root["lang"] = "en";
|
||||
}).c_str(),
|
||||
"ping", millis(), 30000);
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
for (auto *obj : App.get_switches()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->switch_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
for (auto *obj : App.get_binary_sensors()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->binary_sensor_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
for (auto *obj : App.get_fans()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->fan_json(obj).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
for (auto *obj : App.get_lights()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->light_json(obj).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
for (auto *obj : App.get_text_sensors()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->text_sensor_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
for (auto *obj : App.get_covers()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->cover_json(obj).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
for (auto *obj : App.get_numbers()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
client->send(this->number_json(obj, obj->state).c_str(), "state");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
for (auto *obj : App.get_selects()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
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
|
||||
this->entities_iterator_.begin(this->include_internal_);
|
||||
});
|
||||
|
||||
#ifdef USE_LOGGER
|
||||
@@ -175,21 +124,34 @@ void WebServer::setup() {
|
||||
|
||||
this->set_interval(10000, [this]() { this->events_.send("", "ping", millis(), 30000); });
|
||||
}
|
||||
void WebServer::loop() { this->entities_iterator_.advance(); }
|
||||
void WebServer::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Web Server:");
|
||||
ESP_LOGCONFIG(TAG, " Address: %s:%u", network::get_use_address().c_str(), this->base_->get_port());
|
||||
}
|
||||
float WebServer::get_setup_priority() const { return setup_priority::WIFI - 1.0f; }
|
||||
|
||||
#ifdef USE_WEBSERVER_LOCAL
|
||||
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
AsyncWebServerResponse *response = request->beginResponse_P(200, "text/html", INDEX_GZ, sizeof(INDEX_GZ));
|
||||
response->addHeader("Content-Encoding", "gzip");
|
||||
request->send(response);
|
||||
}
|
||||
#else
|
||||
void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *stream = request->beginResponseStream("text/html");
|
||||
std::string title = App.get_name() + " Web Server";
|
||||
stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8>"
|
||||
"<meta name=\"viewport\" content=\"width=device-width, "
|
||||
"initial-scale=1.0\"><title>"));
|
||||
// All content is controlled and created by user - so allowing all origins is fine here.
|
||||
stream->addHeader("Access-Control-Allow-Origin", "*");
|
||||
#if USE_WEBSERVER_VERSION == 1
|
||||
const std::string &title = App.get_name();
|
||||
stream->print(F("<!DOCTYPE html><html lang=\"en\"><head><meta charset=UTF-8><meta "
|
||||
"name=viewport content=\"width=device-width, initial-scale=1,user-scalable=no\"><title>"));
|
||||
stream->print(title.c_str());
|
||||
stream->print(F("</title>"));
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#else
|
||||
stream->print(F("<!DOCTYPE html><html><head><meta charset=UTF-8><link rel=icon href=data:>"));
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
stream->print(F("<link rel=\"stylesheet\" href=\"/0.css\">"));
|
||||
#endif
|
||||
if (strlen(this->css_url_) > 0) {
|
||||
@@ -197,11 +159,12 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
stream->print(this->css_url_);
|
||||
stream->print(F("\">"));
|
||||
}
|
||||
stream->print(F("</head><body><article class=\"markdown-body\"><h1>"));
|
||||
stream->print(F("</head><body>"));
|
||||
#if USE_WEBSERVER_VERSION == 1
|
||||
stream->print(F("<article class=\"markdown-body\"><h1>"));
|
||||
stream->print(title.c_str());
|
||||
stream->print(F("</h1><h2>States</h2><table id=\"states\"><thead><tr><th>Name<th>State<th>Actions<tbody>"));
|
||||
// All content is controlled and created by user - so allowing all origins is fine here.
|
||||
stream->addHeader("Access-Control-Allow-Origin", "*");
|
||||
stream->print(F("</h1>"));
|
||||
stream->print(F("<h2>States</h2><table id=\"states\"><thead><tr><th>Name<th>State<th>Actions<tbody>"));
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
for (auto *obj : App.get_sensors()) {
|
||||
@@ -308,6 +271,13 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
for (auto *obj : App.get_climates()) {
|
||||
if (this->include_internal_ || !obj->is_internal())
|
||||
write_row(stream, obj, "climate", "");
|
||||
}
|
||||
#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_) {
|
||||
@@ -316,23 +286,30 @@ void WebServer::handle_index_request(AsyncWebServerRequest *request) {
|
||||
"type=\"file\" name=\"update\"><input type=\"submit\" value=\"Update\"></form>"));
|
||||
}
|
||||
stream->print(F("<h2>Debug Log</h2><pre id=\"log\"></pre>"));
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
if (this->js_include_ != nullptr) {
|
||||
stream->print(F("<script src=\"/0.js\"></script>"));
|
||||
stream->print(F("<script type=\"module\" src=\"/0.js\"></script>"));
|
||||
}
|
||||
#endif
|
||||
#if USE_WEBSERVER_VERSION == 2
|
||||
stream->print(F("<esp-app></esp-app>"));
|
||||
#endif
|
||||
if (strlen(this->js_url_) > 0) {
|
||||
stream->print(F("<script src=\""));
|
||||
stream->print(this->js_url_);
|
||||
stream->print(F("\"></script>"));
|
||||
}
|
||||
#if USE_WEBSERVER_VERSION == 1
|
||||
stream->print(F("</article></body></html>"));
|
||||
#else
|
||||
stream->print(F("</body></html>"));
|
||||
#endif
|
||||
|
||||
request->send(stream);
|
||||
}
|
||||
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#endif
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
void WebServer::handle_css_request(AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *stream = request->beginResponseStream("text/css");
|
||||
if (this->css_include_ != nullptr) {
|
||||
@@ -343,10 +320,11 @@ void WebServer::handle_css_request(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
void WebServer::handle_js_request(AsyncWebServerRequest *request) {
|
||||
AsyncResponseStream *stream = request->beginResponseStream("text/javascript");
|
||||
if (this->js_include_ != nullptr) {
|
||||
stream->addHeader("Access-Control-Allow-Origin", "*");
|
||||
stream->print(this->js_include_);
|
||||
}
|
||||
|
||||
@@ -354,64 +332,75 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#define set_json_id(root, obj, sensor, start_config) \
|
||||
(root)["id"] = sensor; \
|
||||
if (((start_config) == DETAIL_ALL)) \
|
||||
(root)["name"] = (obj)->get_name();
|
||||
|
||||
#define set_json_value(root, obj, sensor, value, start_config) \
|
||||
set_json_id((root), (obj), sensor, start_config)(root)["value"] = value;
|
||||
|
||||
#define set_json_state_value(root, obj, sensor, state, value, start_config) \
|
||||
set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state;
|
||||
|
||||
#define set_json_icon_state_value(root, obj, sensor, state, value, start_config) \
|
||||
set_json_value(root, obj, sensor, value, start_config)(root)["state"] = state; \
|
||||
if (((start_config) == DETAIL_ALL)) \
|
||||
(root)["icon"] = (obj)->get_icon();
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
|
||||
this->events_.send(this->sensor_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (sensor::Sensor *obj : App.get_sensors()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
std::string data = this->sensor_json(obj, obj->state);
|
||||
std::string data = this->sensor_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::sensor_json(sensor::Sensor *obj, float value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "sensor-" + obj->get_object_id();
|
||||
std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
std::string state = value_accuracy_to_string(value, obj->get_accuracy_decimals());
|
||||
if (!obj->get_unit_of_measurement().empty())
|
||||
state += " " + obj->get_unit_of_measurement();
|
||||
root["state"] = state;
|
||||
root["value"] = value;
|
||||
set_json_icon_state_value(root, obj, "sensor-" + obj->get_object_id(), state, value, start_config);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
|
||||
this->events_.send(this->text_sensor_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (text_sensor::TextSensor *obj : App.get_text_sensors()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
std::string data = this->text_sensor_json(obj, obj->state);
|
||||
std::string data = this->text_sensor_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "text_sensor-" + obj->get_object_id();
|
||||
root["state"] = value;
|
||||
root["value"] = value;
|
||||
std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std::string &value,
|
||||
JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_icon_state_value(root, obj, "text_sensor-" + obj->get_object_id(), value, value, start_config);
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
|
||||
this->events_.send(this->switch_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
std::string WebServer::switch_json(switch_::Switch *obj, bool value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "switch-" + obj->get_object_id();
|
||||
root["state"] = value ? "ON" : "OFF";
|
||||
root["value"] = value;
|
||||
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_icon_state_value(root, obj, "switch-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
|
||||
});
|
||||
}
|
||||
void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -420,7 +409,7 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->switch_json(obj, obj->state);
|
||||
std::string data = this->switch_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "toggle") {
|
||||
this->defer([obj]() { obj->toggle(); });
|
||||
@@ -441,14 +430,19 @@ void WebServer::handle_switch_request(AsyncWebServerRequest *request, const UrlM
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
std::string WebServer::button_json(button::Button *obj, JsonDetail start_config) {
|
||||
return json::build_json(
|
||||
[obj, start_config](JsonObject root) { set_json_id(root, obj, "button-" + obj->get_object_id(), start_config); });
|
||||
}
|
||||
|
||||
void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (button::Button *obj : App.get_buttons()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_POST && match.method == "press") {
|
||||
this->defer([obj]() { obj->press(); });
|
||||
request->send(200);
|
||||
return;
|
||||
} else {
|
||||
request->send(404);
|
||||
}
|
||||
@@ -460,20 +454,18 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
|
||||
this->events_.send(this->binary_sensor_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "binary_sensor-" + obj->get_object_id();
|
||||
root["state"] = value ? "ON" : "OFF";
|
||||
root["value"] = value;
|
||||
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_state_value(root, obj, "binary_sensor-" + obj->get_object_id(), value ? "ON" : "OFF", value, start_config);
|
||||
});
|
||||
}
|
||||
void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (binary_sensor::BinarySensor *obj : App.get_binary_sensors()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
std::string data = this->binary_sensor_json(obj, obj->state);
|
||||
std::string data = this->binary_sensor_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
@@ -482,15 +474,15 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj).c_str(), "state"); }
|
||||
std::string WebServer::fan_json(fan::Fan *obj) {
|
||||
return json::build_json([obj](JsonObject root) {
|
||||
root["id"] = "fan-" + obj->get_object_id();
|
||||
root["state"] = obj->state ? "ON" : "OFF";
|
||||
root["value"] = obj->state;
|
||||
void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); }
|
||||
std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, start_config);
|
||||
const auto traits = obj->get_traits();
|
||||
if (traits.supports_speed()) {
|
||||
root["speed_level"] = obj->speed;
|
||||
root["speed_count"] = traits.supported_speed_count();
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||
// NOLINTNEXTLINE(clang-diagnostic-deprecated-declarations)
|
||||
@@ -517,7 +509,7 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->fan_json(obj);
|
||||
std::string data = this->fan_json(obj, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "toggle") {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
@@ -573,14 +565,16 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
void WebServer::on_light_update(light::LightState *obj) { this->events_.send(this->light_json(obj).c_str(), "state"); }
|
||||
void WebServer::on_light_update(light::LightState *obj) {
|
||||
this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (light::LightState *obj : App.get_lights()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->light_json(obj);
|
||||
std::string data = this->light_json(obj, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "toggle") {
|
||||
this->defer([obj]() { obj->toggle().perform(); });
|
||||
@@ -632,24 +626,34 @@ void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMa
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::light_json(light::LightState *obj) {
|
||||
return json::build_json([obj](JsonObject root) {
|
||||
root["id"] = "light-" + obj->get_object_id();
|
||||
std::string WebServer::light_json(light::LightState *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_id(root, obj, "light-" + obj->get_object_id(), start_config);
|
||||
root["state"] = obj->remote_values.is_on() ? "ON" : "OFF";
|
||||
|
||||
light::LightJSONSchema::dump_json(*obj, root);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
JsonArray opt = root.createNestedArray("effects");
|
||||
opt.add("None");
|
||||
for (auto const &option : obj->get_effects()) {
|
||||
opt.add(option->get_name());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
void WebServer::on_cover_update(cover::Cover *obj) { this->events_.send(this->cover_json(obj).c_str(), "state"); }
|
||||
void WebServer::on_cover_update(cover::Cover *obj) {
|
||||
this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (cover::Cover *obj : App.get_covers()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->cover_json(obj);
|
||||
std::string data = this->cover_json(obj, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
continue;
|
||||
}
|
||||
@@ -684,11 +688,10 @@ void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMa
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::cover_json(cover::Cover *obj) {
|
||||
return json::build_json([obj](JsonObject root) {
|
||||
root["id"] = "cover-" + obj->get_object_id();
|
||||
root["state"] = obj->is_fully_closed() ? "CLOSED" : "OPEN";
|
||||
root["value"] = obj->position;
|
||||
std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_state_value(root, obj, "cover-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
|
||||
obj->position, start_config);
|
||||
root["current_operation"] = cover::cover_operation_to_str(obj->current_operation);
|
||||
|
||||
if (obj->get_traits().get_supports_tilt())
|
||||
@@ -699,7 +702,7 @@ std::string WebServer::cover_json(cover::Cover *obj) {
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
void WebServer::on_number_update(number::Number *obj, float state) {
|
||||
this->events_.send(this->number_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (auto *obj : App.get_numbers()) {
|
||||
@@ -707,18 +710,16 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->number_json(obj, obj->state);
|
||||
std::string data = this->number_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.method != "set") {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
auto call = obj->make_call();
|
||||
|
||||
if (request->hasParam("value")) {
|
||||
String value = request->getParam("value")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
@@ -732,19 +733,30 @@ void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlM
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::number_json(number::Number *obj, float value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "number-" + obj->get_object_id();
|
||||
|
||||
std::string WebServer::number_json(number::Number *obj, float value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_id(root, obj, "number-" + obj->get_object_id(), start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
root["min_value"] = obj->traits.get_min_value();
|
||||
root["max_value"] = obj->traits.get_max_value();
|
||||
root["step"] = obj->traits.get_step();
|
||||
root["mode"] = (int) obj->traits.get_mode();
|
||||
}
|
||||
std::string state = str_sprintf("%f", value);
|
||||
root["state"] = state;
|
||||
root["value"] = value;
|
||||
if (isnan(value)) {
|
||||
root["value"] = "\"NaN\"";
|
||||
} else {
|
||||
root["value"] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
void WebServer::on_select_update(select::Select *obj, const std::string &state) {
|
||||
this->events_.send(this->select_json(obj, state).c_str(), "state");
|
||||
this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (auto *obj : App.get_selects()) {
|
||||
@@ -752,7 +764,7 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->select_json(obj, obj->state);
|
||||
std::string data = this->select_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
@@ -775,24 +787,160 @@ void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlM
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
std::string WebServer::select_json(select::Select *obj, const std::string &value) {
|
||||
return json::build_json([obj, value](JsonObject root) {
|
||||
root["id"] = "select-" + obj->get_object_id();
|
||||
root["state"] = value;
|
||||
root["value"] = value;
|
||||
std::string WebServer::select_json(select::Select *obj, const std::string &value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_state_value(root, obj, "select-" + obj->get_object_id(), value, value, start_config);
|
||||
if (start_config == DETAIL_ALL) {
|
||||
JsonArray opt = root.createNestedArray("option");
|
||||
for (auto &option : obj->traits.get_options()) {
|
||||
opt.add(option);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void WebServer::on_climate_update(climate::Climate *obj) {
|
||||
this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
|
||||
}
|
||||
|
||||
void WebServer::handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
for (auto *obj : App.get_climates()) {
|
||||
if (obj->get_object_id() != match.id)
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->climate_json(obj, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
if (match.method != "set") {
|
||||
request->send(404);
|
||||
return;
|
||||
}
|
||||
|
||||
auto call = obj->make_call();
|
||||
|
||||
if (request->hasParam("mode")) {
|
||||
String mode = request->getParam("mode")->value();
|
||||
call.set_mode(mode.c_str());
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature_high")) {
|
||||
String value = request->getParam("target_temperature_high")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature_high(*value_f);
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature_low")) {
|
||||
String value = request->getParam("target_temperature_low")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature_low(*value_f);
|
||||
}
|
||||
|
||||
if (request->hasParam("target_temperature")) {
|
||||
String value = request->getParam("target_temperature")->value();
|
||||
optional<float> value_f = parse_number<float>(value.c_str());
|
||||
if (value_f.has_value())
|
||||
call.set_target_temperature(*value_f);
|
||||
}
|
||||
|
||||
this->defer([call]() mutable { call.perform(); });
|
||||
request->send(200);
|
||||
return;
|
||||
}
|
||||
request->send(404);
|
||||
}
|
||||
|
||||
// Longest: HORIZONTAL
|
||||
#define PSTR_LOCAL(mode_s) strncpy_P(__buf, (PGM_P)((mode_s)), 15)
|
||||
|
||||
std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_config) {
|
||||
return json::build_json([obj, start_config](JsonObject root) {
|
||||
set_json_id(root, obj, "climate-" + obj->get_object_id(), start_config);
|
||||
const auto traits = obj->get_traits();
|
||||
char __buf[16];
|
||||
|
||||
if (start_config == DETAIL_ALL) {
|
||||
JsonArray opt = root.createNestedArray("modes");
|
||||
for (climate::ClimateMode m : traits.get_supported_modes())
|
||||
opt.add(PSTR_LOCAL(climate::climate_mode_to_string(m)));
|
||||
if (!traits.get_supported_custom_fan_modes().empty()) {
|
||||
JsonArray opt = root.createNestedArray("fan_modes");
|
||||
for (climate::ClimateFanMode m : traits.get_supported_fan_modes())
|
||||
opt.add(PSTR_LOCAL(climate::climate_fan_mode_to_string(m)));
|
||||
}
|
||||
|
||||
if (!traits.get_supported_custom_fan_modes().empty()) {
|
||||
JsonArray opt = root.createNestedArray("custom_fan_modes");
|
||||
for (auto const &custom_fan_mode : traits.get_supported_custom_fan_modes())
|
||||
opt.add(custom_fan_mode);
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
JsonArray opt = root.createNestedArray("swing_modes");
|
||||
for (auto swing_mode : traits.get_supported_swing_modes())
|
||||
opt.add(PSTR_LOCAL(climate::climate_swing_mode_to_string(swing_mode)));
|
||||
}
|
||||
if (traits.get_supports_presets() && obj->preset.has_value()) {
|
||||
JsonArray opt = root.createNestedArray("presets");
|
||||
for (climate::ClimatePreset m : traits.get_supported_presets())
|
||||
opt.add(PSTR_LOCAL(climate::climate_preset_to_string(m)));
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
|
||||
JsonArray opt = root.createNestedArray("custom_presets");
|
||||
for (auto const &custom_preset : traits.get_supported_custom_presets())
|
||||
opt.add(custom_preset);
|
||||
}
|
||||
}
|
||||
|
||||
root["mode"] = PSTR_LOCAL(climate_mode_to_string(obj->mode));
|
||||
root["max_temp"] = traits.get_visual_max_temperature();
|
||||
root["min_temp"] = traits.get_visual_min_temperature();
|
||||
root["step"] = traits.get_visual_temperature_step();
|
||||
if (traits.get_supports_action()) {
|
||||
root["action"] = PSTR_LOCAL(climate_action_to_string(obj->action));
|
||||
}
|
||||
if (traits.get_supports_fan_modes() && obj->fan_mode.has_value()) {
|
||||
root["fan_mode"] = PSTR_LOCAL(climate_fan_mode_to_string(obj->fan_mode.value()));
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && obj->custom_fan_mode.has_value()) {
|
||||
root["custom_fan_mode"] = obj->custom_fan_mode.value().c_str();
|
||||
}
|
||||
if (traits.get_supports_presets() && obj->preset.has_value()) {
|
||||
root["preset"] = PSTR_LOCAL(climate_preset_to_string(obj->preset.value()));
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && obj->custom_preset.has_value()) {
|
||||
root["custom_preset"] = obj->custom_preset.value().c_str();
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
root["swing_mode"] = PSTR_LOCAL(climate_swing_mode_to_string(obj->swing_mode));
|
||||
}
|
||||
if (traits.get_supports_current_temperature()) {
|
||||
root["current_temperature"] = obj->current_temperature;
|
||||
}
|
||||
if (traits.get_supports_two_point_target_temperature()) {
|
||||
root["current_temperature_low"] = obj->target_temperature_low;
|
||||
root["current_temperature_high"] = obj->target_temperature_low;
|
||||
} else {
|
||||
root["target_temperature"] = obj->target_temperature;
|
||||
root["state"] = obj->target_temperature;
|
||||
}
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
void WebServer::on_lock_update(lock::Lock *obj) {
|
||||
this->events_.send(this->lock_json(obj, obj->state).c_str(), "state");
|
||||
this->events_.send(this->lock_json(obj, obj->state, DETAIL_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;
|
||||
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
|
||||
return json::build_json([obj, value, start_config](JsonObject root) {
|
||||
set_json_icon_state_value(root, obj, "lock-" + obj->get_object_id(), lock::lock_state_to_string(value), value,
|
||||
start_config);
|
||||
});
|
||||
}
|
||||
void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMatch &match) {
|
||||
@@ -801,7 +949,7 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
|
||||
continue;
|
||||
|
||||
if (request->method() == HTTP_GET) {
|
||||
std::string data = this->lock_json(obj, obj->state);
|
||||
std::string data = this->lock_json(obj, obj->state, DETAIL_STATE);
|
||||
request->send(200, "text/json", data.c_str());
|
||||
} else if (match.method == "lock") {
|
||||
this->defer([obj]() { obj->lock(); });
|
||||
@@ -825,12 +973,12 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||
if (request->url() == "/")
|
||||
return true;
|
||||
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
if (request->url() == "/0.css")
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
if (request->url() == "/0.js")
|
||||
return true;
|
||||
#endif
|
||||
@@ -888,6 +1036,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "climate")
|
||||
return true;
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "lock")
|
||||
return true;
|
||||
@@ -901,14 +1054,14 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
if (request->url() == "/0.css") {
|
||||
this->handle_css_request(request);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
if (request->url() == "/0.js") {
|
||||
this->handle_js_request(request);
|
||||
return;
|
||||
@@ -986,9 +1139,17 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
if (match.domain == "climate") {
|
||||
this->handle_climate_request(request, match);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
if (match.domain == "lock") {
|
||||
this->handle_lock_request(request, match);
|
||||
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -2,9 +2,11 @@
|
||||
|
||||
#ifdef USE_ARDUINO
|
||||
|
||||
#include "list_entities.h"
|
||||
|
||||
#include "esphome/components/web_server_base/web_server_base.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/components/web_server_base/web_server_base.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
@@ -19,6 +21,8 @@ struct UrlMatch {
|
||||
bool valid; ///< Whether this match is valid
|
||||
};
|
||||
|
||||
enum JsonDetail { DETAIL_ALL, DETAIL_STATE };
|
||||
|
||||
/** This class allows users to create a web server with their ESP nodes.
|
||||
*
|
||||
* Behind the scenes it's using AsyncWebServer to set up the server. It exposes 3 things:
|
||||
@@ -30,7 +34,7 @@ struct UrlMatch {
|
||||
*/
|
||||
class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
public:
|
||||
WebServer(web_server_base::WebServerBase *base) : base_(base) {}
|
||||
WebServer(web_server_base::WebServerBase *base) : base_(base), entities_iterator_(ListEntitiesIterator(this)) {}
|
||||
|
||||
/** Set the URL to the CSS <link> that's sent to each client. Defaults to
|
||||
* https://esphome.io/_static/webserver-v1.min.css
|
||||
@@ -74,6 +78,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
// (In most use cases you won't need these)
|
||||
/// Setup the internal web server and register handlers.
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
|
||||
void dump_config() override;
|
||||
|
||||
@@ -83,12 +88,12 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
/// Handle an index request under '/'.
|
||||
void handle_index_request(AsyncWebServerRequest *request);
|
||||
|
||||
#ifdef WEBSERVER_CSS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_CSS_INCLUDE
|
||||
/// Handle included css request under '/0.css'.
|
||||
void handle_css_request(AsyncWebServerRequest *request);
|
||||
#endif
|
||||
|
||||
#ifdef WEBSERVER_JS_INCLUDE
|
||||
#ifdef USE_WEBSERVER_JS_INCLUDE
|
||||
/// Handle included js request under '/0.js'.
|
||||
void handle_js_request(AsyncWebServerRequest *request);
|
||||
#endif
|
||||
@@ -99,7 +104,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the sensor state with its value as a JSON string.
|
||||
std::string sensor_json(sensor::Sensor *obj, float value);
|
||||
std::string sensor_json(sensor::Sensor *obj, float value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SWITCH
|
||||
@@ -109,12 +114,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_switch_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the switch state with its value as a JSON string.
|
||||
std::string switch_json(switch_::Switch *obj, bool value);
|
||||
std::string switch_json(switch_::Switch *obj, bool value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BUTTON
|
||||
/// Handle a button request under '/button/<id>/press'.
|
||||
void handle_button_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the button details with its value as a JSON string.
|
||||
std::string button_json(button::Button *obj, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -124,7 +132,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_binary_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the binary sensor state with its value as a JSON string.
|
||||
std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value);
|
||||
std::string binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_FAN
|
||||
@@ -134,7 +142,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_fan_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the fan state as a JSON string.
|
||||
std::string fan_json(fan::Fan *obj);
|
||||
std::string fan_json(fan::Fan *obj, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIGHT
|
||||
@@ -144,7 +152,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the light state as a JSON string.
|
||||
std::string light_json(light::LightState *obj);
|
||||
std::string light_json(light::LightState *obj, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
@@ -154,7 +162,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the text sensor state with its value as a JSON string.
|
||||
std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value);
|
||||
std::string text_sensor_json(text_sensor::TextSensor *obj, const std::string &value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_COVER
|
||||
@@ -164,7 +172,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the cover state as a JSON string.
|
||||
std::string cover_json(cover::Cover *obj);
|
||||
std::string cover_json(cover::Cover *obj, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_NUMBER
|
||||
@@ -173,7 +181,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
void handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the number state with its value as a JSON string.
|
||||
std::string number_json(number::Number *obj, float value);
|
||||
std::string number_json(number::Number *obj, float value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SELECT
|
||||
@@ -181,8 +189,17 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
/// Handle a select request under '/select/<id>'.
|
||||
void handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the number state with its value as a JSON string.
|
||||
std::string select_json(select::Select *obj, const std::string &value);
|
||||
/// Dump the select state with its value as a JSON string.
|
||||
std::string select_json(select::Select *obj, const std::string &value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_CLIMATE
|
||||
void on_climate_update(climate::Climate *obj) override;
|
||||
/// Handle a climate request under '/climate/<id>'.
|
||||
void handle_climate_request(AsyncWebServerRequest *request, const UrlMatch &match);
|
||||
|
||||
/// Dump the climate details
|
||||
std::string climate_json(climate::Climate *obj, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
#ifdef USE_LOCK
|
||||
@@ -192,7 +209,7 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
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);
|
||||
std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config);
|
||||
#endif
|
||||
|
||||
/// Override the web handler's canHandle method.
|
||||
@@ -203,8 +220,10 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
|
||||
bool isRequestHandlerTrivial() override;
|
||||
|
||||
protected:
|
||||
friend ListEntitiesIterator;
|
||||
web_server_base::WebServerBase *base_;
|
||||
AsyncEventSource events_{"/events"};
|
||||
ListEntitiesIterator entities_iterator_;
|
||||
const char *css_url_{nullptr};
|
||||
const char *css_include_{nullptr};
|
||||
const char *js_url_{nullptr};
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "xiaomi_ble.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -12,67 +12,74 @@ namespace xiaomi_ble {
|
||||
|
||||
static const char *const TAG = "xiaomi_ble";
|
||||
|
||||
bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) {
|
||||
bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result) {
|
||||
// button pressed, 3 bytes, only byte 3 is used for supported devices so far
|
||||
if ((value_type == 0x1001) && (value_length == 3)) {
|
||||
result.button_press = data[2] == 0;
|
||||
return true;
|
||||
}
|
||||
// motion detection, 1 byte, 8-bit unsigned integer
|
||||
if ((value_type == 0x03) && (value_length == 1)) {
|
||||
else if ((value_type == 0x0003) && (value_length == 1)) {
|
||||
result.has_motion = data[0];
|
||||
}
|
||||
// temperature, 2 bytes, 16-bit signed integer (LE), 0.1 °C
|
||||
else if ((value_type == 0x04) && (value_length == 2)) {
|
||||
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
|
||||
else if ((value_type == 0x1004) && (value_length == 2)) {
|
||||
const int16_t temperature = encode_uint16(data[1], data[0]);
|
||||
result.temperature = temperature / 10.0f;
|
||||
}
|
||||
// humidity, 2 bytes, 16-bit signed integer (LE), 0.1 %
|
||||
else if ((value_type == 0x06) && (value_length == 2)) {
|
||||
const int16_t humidity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
|
||||
else if ((value_type == 0x1006) && (value_length == 2)) {
|
||||
const int16_t humidity = encode_uint16(data[1], data[0]);
|
||||
result.humidity = humidity / 10.0f;
|
||||
}
|
||||
// illuminance (+ motion), 3 bytes, 24-bit unsigned integer (LE), 1 lx
|
||||
else if (((value_type == 0x07) || (value_type == 0x0F)) && (value_length == 3)) {
|
||||
const uint32_t illuminance = uint32_t(data[0]) | (uint32_t(data[1]) << 8) | (uint32_t(data[2]) << 16);
|
||||
else if (((value_type == 0x1007) || (value_type == 0x000F)) && (value_length == 3)) {
|
||||
const uint32_t illuminance = encode_uint24(data[2], data[1], data[0]);
|
||||
result.illuminance = illuminance;
|
||||
result.is_light = illuminance == 100;
|
||||
result.is_light = illuminance >= 100;
|
||||
if (value_type == 0x0F)
|
||||
result.has_motion = true;
|
||||
}
|
||||
// soil moisture, 1 byte, 8-bit unsigned integer, 1 %
|
||||
else if ((value_type == 0x08) && (value_length == 1)) {
|
||||
else if ((value_type == 0x1008) && (value_length == 1)) {
|
||||
result.moisture = data[0];
|
||||
}
|
||||
// conductivity, 2 bytes, 16-bit unsigned integer (LE), 1 µS/cm
|
||||
else if ((value_type == 0x09) && (value_length == 2)) {
|
||||
const uint16_t conductivity = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
|
||||
else if ((value_type == 0x1009) && (value_length == 2)) {
|
||||
const uint16_t conductivity = encode_uint16(data[1], data[0]);
|
||||
result.conductivity = conductivity;
|
||||
}
|
||||
// battery, 1 byte, 8-bit unsigned integer, 1 %
|
||||
else if ((value_type == 0x0A) && (value_length == 1)) {
|
||||
else if ((value_type == 0x100A) && (value_length == 1)) {
|
||||
result.battery_level = data[0];
|
||||
}
|
||||
// temperature + humidity, 4 bytes, 16-bit signed integer (LE) each, 0.1 °C, 0.1 %
|
||||
else if ((value_type == 0x0D) && (value_length == 4)) {
|
||||
const int16_t temperature = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
|
||||
const int16_t humidity = uint16_t(data[2]) | (uint16_t(data[3]) << 8);
|
||||
else if ((value_type == 0x100D) && (value_length == 4)) {
|
||||
const int16_t temperature = encode_uint16(data[1], data[0]);
|
||||
const int16_t humidity = encode_uint16(data[3], data[2]);
|
||||
result.temperature = temperature / 10.0f;
|
||||
result.humidity = humidity / 10.0f;
|
||||
}
|
||||
// formaldehyde, 2 bytes, 16-bit unsigned integer (LE), 0.01 mg / m3
|
||||
else if ((value_type == 0x10) && (value_length == 2)) {
|
||||
const uint16_t formaldehyde = uint16_t(data[0]) | (uint16_t(data[1]) << 8);
|
||||
else if ((value_type == 0x1010) && (value_length == 2)) {
|
||||
const uint16_t formaldehyde = encode_uint16(data[1], data[0]);
|
||||
result.formaldehyde = formaldehyde / 100.0f;
|
||||
}
|
||||
// on/off state, 1 byte, 8-bit unsigned integer
|
||||
else if ((value_type == 0x12) && (value_length == 1)) {
|
||||
else if ((value_type == 0x1012) && (value_length == 1)) {
|
||||
result.is_active = data[0];
|
||||
}
|
||||
// mosquito tablet, 1 byte, 8-bit unsigned integer, 1 %
|
||||
else if ((value_type == 0x13) && (value_length == 1)) {
|
||||
else if ((value_type == 0x1013) && (value_length == 1)) {
|
||||
result.tablet = data[0];
|
||||
}
|
||||
// idle time since last motion, 4 byte, 32-bit unsigned integer, 1 min
|
||||
else if ((value_type == 0x17) && (value_length == 4)) {
|
||||
else if ((value_type == 0x1017) && (value_length == 4)) {
|
||||
const uint32_t idle_time = encode_uint32(data[3], data[2], data[1], data[0]);
|
||||
result.idle_time = idle_time / 60.0f;
|
||||
result.has_motion = !idle_time;
|
||||
} else if ((value_type == 0x1018) && (value_length == 1)) {
|
||||
result.is_light = data[0];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -115,7 +122,7 @@ bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t value_type = payload[payload_offset + 0];
|
||||
const uint16_t value_type = encode_uint16(payload[payload_offset + 1], payload[payload_offset + 0]);
|
||||
const uint8_t *data = &payload[payload_offset + 3];
|
||||
|
||||
if (parse_xiaomi_value(value_type, data, value_length, result))
|
||||
@@ -155,60 +162,67 @@ optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::Service
|
||||
result.is_duplicate = false;
|
||||
result.raw_offset = result.has_capability ? 12 : 11;
|
||||
|
||||
if ((raw[2] == 0x98) && (raw[3] == 0x00)) { // MiFlora
|
||||
const uint16_t device_uuid = encode_uint16(raw[3], raw[2]);
|
||||
|
||||
if (device_uuid == 0x0098) { // MiFlora
|
||||
result.type = XiaomiParseResult::TYPE_HHCCJCY01;
|
||||
result.name = "HHCCJCY01";
|
||||
} else if ((raw[2] == 0xaa) && (raw[3] == 0x01)) { // round body, segment LCD
|
||||
} else if (device_uuid == 0x01aa) { // round body, segment LCD
|
||||
result.type = XiaomiParseResult::TYPE_LYWSDCGQ;
|
||||
result.name = "LYWSDCGQ";
|
||||
} else if ((raw[2] == 0x5d) && (raw[3] == 0x01)) { // FlowerPot, RoPot
|
||||
} else if (device_uuid == 0x015d) { // FlowerPot, RoPot
|
||||
result.type = XiaomiParseResult::TYPE_HHCCPOT002;
|
||||
result.name = "HHCCPOT002";
|
||||
} else if ((raw[2] == 0xdf) && (raw[3] == 0x02)) { // Xiaomi (Honeywell) formaldehyde sensor, OLED display
|
||||
} else if (device_uuid == 0x02df) { // Xiaomi (Honeywell) formaldehyde sensor, OLED display
|
||||
result.type = XiaomiParseResult::TYPE_JQJCY01YM;
|
||||
result.name = "JQJCY01YM";
|
||||
} else if ((raw[2] == 0xdd) && (raw[3] == 0x03)) { // Philips/Xiaomi BLE nightlight
|
||||
} else if (device_uuid == 0x03dd) { // Philips/Xiaomi BLE nightlight
|
||||
result.type = XiaomiParseResult::TYPE_MUE4094RT;
|
||||
result.name = "MUE4094RT";
|
||||
result.raw_offset -= 6;
|
||||
} else if ((raw[2] == 0x47 && raw[3] == 0x03) || // ClearGrass-branded, round body, e-ink display
|
||||
(raw[2] == 0x48 && raw[3] == 0x0B)) { // Qingping-branded, round body, e-ink display — with bindkeys
|
||||
} else if (device_uuid == 0x0347 || // ClearGrass-branded, round body, e-ink display
|
||||
device_uuid == 0x0B48) { // Qingping-branded, round body, e-ink display — with bindkeys
|
||||
result.type = XiaomiParseResult::TYPE_CGG1;
|
||||
result.name = "CGG1";
|
||||
} else if ((raw[2] == 0xbc) && (raw[3] == 0x03)) { // VegTrug Grow Care Garden
|
||||
} else if (device_uuid == 0x03bc) { // VegTrug Grow Care Garden
|
||||
result.type = XiaomiParseResult::TYPE_GCLS002;
|
||||
result.name = "GCLS002";
|
||||
} else if ((raw[2] == 0x5b) && (raw[3] == 0x04)) { // rectangular body, e-ink display
|
||||
} else if (device_uuid == 0x045b) { // rectangular body, e-ink display
|
||||
result.type = XiaomiParseResult::TYPE_LYWSD02;
|
||||
result.name = "LYWSD02";
|
||||
} else if ((raw[2] == 0x0a) && (raw[3] == 0x04)) { // Mosquito Repellent Smart Version
|
||||
} else if (device_uuid == 0x040a) { // Mosquito Repellent Smart Version
|
||||
result.type = XiaomiParseResult::TYPE_WX08ZM;
|
||||
result.name = "WX08ZM";
|
||||
} else if ((raw[2] == 0x76) && (raw[3] == 0x05)) { // Cleargrass (Qingping) alarm clock, segment LCD
|
||||
} else if (device_uuid == 0x0576) { // Cleargrass (Qingping) alarm clock, segment LCD
|
||||
result.type = XiaomiParseResult::TYPE_CGD1;
|
||||
result.name = "CGD1";
|
||||
} else if ((raw[2] == 0x6F) && (raw[3] == 0x06)) { // Cleargrass (Qingping) Temp & RH Lite
|
||||
} else if (device_uuid == 0x066F) { // Cleargrass (Qingping) Temp & RH Lite
|
||||
result.type = XiaomiParseResult::TYPE_CGDK2;
|
||||
result.name = "CGDK2";
|
||||
} else if ((raw[2] == 0x5b) && (raw[3] == 0x05)) { // small square body, segment LCD, encrypted
|
||||
} else if (device_uuid == 0x055b) { // small square body, segment LCD, encrypted
|
||||
result.type = XiaomiParseResult::TYPE_LYWSD03MMC;
|
||||
result.name = "LYWSD03MMC";
|
||||
} else if ((raw[2] == 0xf6) && (raw[3] == 0x07)) { // Xiaomi-Yeelight BLE nightlight
|
||||
} else if (device_uuid == 0x07f6) { // Xiaomi-Yeelight BLE nightlight
|
||||
result.type = XiaomiParseResult::TYPE_MJYD02YLA;
|
||||
result.name = "MJYD02YLA";
|
||||
if (raw.size() == 19)
|
||||
result.raw_offset -= 6;
|
||||
} else if ((raw[2] == 0xd3) && (raw[3] == 0x06)) { // rectangular body, e-ink display with alarm
|
||||
} else if (device_uuid == 0x06d3) { // rectangular body, e-ink display with alarm
|
||||
result.type = XiaomiParseResult::TYPE_MHOC303;
|
||||
result.name = "MHOC303";
|
||||
} else if ((raw[2] == 0x87) && (raw[3] == 0x03)) { // square body, e-ink display
|
||||
} else if (device_uuid == 0x0387) { // square body, e-ink display
|
||||
result.type = XiaomiParseResult::TYPE_MHOC401;
|
||||
result.name = "MHOC401";
|
||||
} else if ((raw[2] == 0x83) && (raw[3] == 0x0A)) { // Qingping-branded, motion & ambient light sensor
|
||||
} else if (device_uuid == 0x0A83) { // Qingping-branded, motion & ambient light sensor
|
||||
result.type = XiaomiParseResult::TYPE_CGPR1;
|
||||
result.name = "CGPR1";
|
||||
if (raw.size() == 19)
|
||||
result.raw_offset -= 6;
|
||||
} else if (device_uuid == 0x0A8D) { // Xiaomi Mi Motion Sensor 2
|
||||
result.type = XiaomiParseResult::TYPE_RTCGQ02LM;
|
||||
result.name = "RTCGQ02LM";
|
||||
if (raw.size() == 19)
|
||||
result.raw_offset -= 6;
|
||||
} else {
|
||||
ESP_LOGVV(TAG, "parse_xiaomi_header(): unknown device, no magic bytes.");
|
||||
return {};
|
||||
@@ -343,6 +357,9 @@ bool report_xiaomi_results(const optional<XiaomiParseResult> &result, const std:
|
||||
if (result->is_light.has_value()) {
|
||||
ESP_LOGD(TAG, " Light: %s", (*result->is_light) ? "on" : "off");
|
||||
}
|
||||
if (result->button_press.has_value()) {
|
||||
ESP_LOGD(TAG, " Button: %s", (*result->button_press) ? "pressed" : "");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -25,7 +25,8 @@ struct XiaomiParseResult {
|
||||
TYPE_MJYD02YLA,
|
||||
TYPE_MHOC303,
|
||||
TYPE_MHOC401,
|
||||
TYPE_CGPR1
|
||||
TYPE_CGPR1,
|
||||
TYPE_RTCGQ02LM,
|
||||
} type;
|
||||
std::string name;
|
||||
optional<float> temperature;
|
||||
@@ -40,6 +41,7 @@ struct XiaomiParseResult {
|
||||
optional<bool> is_active;
|
||||
optional<bool> has_motion;
|
||||
optional<bool> is_light;
|
||||
optional<bool> button_press;
|
||||
bool has_data; // 0x40
|
||||
bool has_capability; // 0x20
|
||||
bool has_encryption; // 0x08
|
||||
@@ -61,7 +63,7 @@ struct XiaomiAESVector {
|
||||
size_t ivsize;
|
||||
};
|
||||
|
||||
bool parse_xiaomi_value(uint8_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result);
|
||||
bool parse_xiaomi_value(uint16_t value_type, const uint8_t *data, uint8_t value_length, XiaomiParseResult &result);
|
||||
bool parse_xiaomi_message(const std::vector<uint8_t> &message, XiaomiParseResult &result);
|
||||
optional<XiaomiParseResult> parse_xiaomi_header(const esp32_ble_tracker::ServiceData &service_data);
|
||||
bool decrypt_xiaomi_payload(std::vector<uint8_t> &raw, const uint8_t *bindkey, const uint64_t &address);
|
||||
|
||||
36
esphome/components/xiaomi_rtcgq02lm/__init__.py
Normal file
36
esphome/components/xiaomi_rtcgq02lm/__init__.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import esp32_ble_tracker
|
||||
from esphome.const import CONF_MAC_ADDRESS, CONF_ID, CONF_BINDKEY
|
||||
|
||||
|
||||
AUTO_LOAD = ["xiaomi_ble"]
|
||||
CODEOWNERS = ["@jesserockz"]
|
||||
DEPENDENCIES = ["esp32_ble_tracker"]
|
||||
MULTI_CONF = True
|
||||
|
||||
xiaomi_rtcgq02lm_ns = cg.esphome_ns.namespace("xiaomi_rtcgq02lm")
|
||||
XiaomiRTCGQ02LM = xiaomi_rtcgq02lm_ns.class_(
|
||||
"XiaomiRTCGQ02LM", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(XiaomiRTCGQ02LM),
|
||||
cv.Required(CONF_BINDKEY): cv.bind_key,
|
||||
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
|
||||
}
|
||||
)
|
||||
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await esp32_ble_tracker.register_ble_device(var, config)
|
||||
|
||||
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
|
||||
cg.add(var.set_bindkey(config[CONF_BINDKEY]))
|
||||
64
esphome/components/xiaomi_rtcgq02lm/binary_sensor.py
Normal file
64
esphome/components/xiaomi_rtcgq02lm/binary_sensor.py
Normal file
@@ -0,0 +1,64 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import binary_sensor
|
||||
from esphome.const import (
|
||||
CONF_LIGHT,
|
||||
CONF_MOTION,
|
||||
CONF_TIMEOUT,
|
||||
DEVICE_CLASS_LIGHT,
|
||||
DEVICE_CLASS_MOTION,
|
||||
CONF_ID,
|
||||
)
|
||||
from esphome.core import TimePeriod
|
||||
|
||||
from . import XiaomiRTCGQ02LM
|
||||
|
||||
DEPENDENCIES = ["xiaomi_rtcgq02lm"]
|
||||
|
||||
CONF_BUTTON = "button"
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(XiaomiRTCGQ02LM),
|
||||
cv.Optional(CONF_MOTION): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_MOTION
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(CONF_TIMEOUT, default="5s"): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=TimePeriod(milliseconds=65535)),
|
||||
),
|
||||
}
|
||||
),
|
||||
cv.Optional(CONF_LIGHT): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_LIGHT
|
||||
),
|
||||
cv.Optional(CONF_BUTTON): binary_sensor.binary_sensor_schema().extend(
|
||||
{
|
||||
cv.Optional(CONF_TIMEOUT, default="200ms"): cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(max=TimePeriod(milliseconds=65535)),
|
||||
),
|
||||
}
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_MOTION in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_MOTION])
|
||||
cg.add(parent.set_motion(sens))
|
||||
cg.add(parent.set_motion_timeout(config[CONF_MOTION][CONF_TIMEOUT]))
|
||||
|
||||
if CONF_LIGHT in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_LIGHT])
|
||||
cg.add(parent.set_light(sens))
|
||||
|
||||
if CONF_BUTTON in config:
|
||||
sens = await binary_sensor.new_binary_sensor(config[CONF_BUTTON])
|
||||
cg.add(parent.set_button(sens))
|
||||
cg.add(parent.set_button_timeout(config[CONF_BUTTON][CONF_TIMEOUT]))
|
||||
37
esphome/components/xiaomi_rtcgq02lm/sensor.py
Normal file
37
esphome/components/xiaomi_rtcgq02lm/sensor.py
Normal file
@@ -0,0 +1,37 @@
|
||||
import esphome.codegen as cg
|
||||
import esphome.config_validation as cv
|
||||
from esphome.components import sensor
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_LEVEL,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
)
|
||||
|
||||
from . import XiaomiRTCGQ02LM
|
||||
|
||||
DEPENDENCIES = ["xiaomi_rtcgq02lm"]
|
||||
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.use_id(XiaomiRTCGQ02LM),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
parent = await cg.get_variable(config[CONF_ID])
|
||||
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(parent.set_battery_level(sens))
|
||||
91
esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp
Normal file
91
esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.cpp
Normal file
@@ -0,0 +1,91 @@
|
||||
#include "xiaomi_rtcgq02lm.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace xiaomi_rtcgq02lm {
|
||||
|
||||
static const char *const TAG = "xiaomi_rtcgq02lm";
|
||||
|
||||
void XiaomiRTCGQ02LM::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "Xiaomi RTCGQ02LM");
|
||||
ESP_LOGCONFIG(TAG, " Bindkey: %s", format_hex_pretty(this->bindkey_, 16).c_str());
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Motion", this->motion_);
|
||||
LOG_BINARY_SENSOR(" ", "Light", this->light_);
|
||||
LOG_BINARY_SENSOR(" ", "Button", this->button_);
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
|
||||
#endif
|
||||
}
|
||||
|
||||
bool XiaomiRTCGQ02LM::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (device.address_uint64() != this->address_) {
|
||||
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
|
||||
return false;
|
||||
}
|
||||
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
|
||||
|
||||
bool success = false;
|
||||
for (auto &service_data : device.get_service_datas()) {
|
||||
auto res = xiaomi_ble::parse_xiaomi_header(service_data);
|
||||
if (!res.has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (res->is_duplicate) {
|
||||
continue;
|
||||
}
|
||||
if (res->has_encryption &&
|
||||
(!(xiaomi_ble::decrypt_xiaomi_payload(const_cast<std::vector<uint8_t> &>(service_data.data), this->bindkey_,
|
||||
this->address_)))) {
|
||||
continue;
|
||||
}
|
||||
if (!(xiaomi_ble::parse_xiaomi_message(service_data.data, *res))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!(xiaomi_ble::report_xiaomi_results(res, device.address_str()))) {
|
||||
continue;
|
||||
}
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (res->has_motion.has_value() && this->motion_ != nullptr) {
|
||||
this->motion_->publish_state(*res->has_motion);
|
||||
this->set_timeout("motion_timeout", this->motion_timeout_,
|
||||
[this, res]() { this->motion_->publish_state(false); });
|
||||
}
|
||||
if (res->is_light.has_value() && this->light_ != nullptr)
|
||||
this->light_->publish_state(*res->is_light);
|
||||
if (res->button_press.has_value() && this->button_ != nullptr) {
|
||||
this->button_->publish_state(*res->button_press);
|
||||
this->set_timeout("button_timeout", this->button_timeout_,
|
||||
[this, res]() { this->button_->publish_state(false); });
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
if (res->battery_level.has_value() && this->battery_level_ != nullptr)
|
||||
this->battery_level_->publish_state(*res->battery_level);
|
||||
#endif
|
||||
success = true;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void XiaomiRTCGQ02LM::set_bindkey(const std::string &bindkey) {
|
||||
memset(bindkey_, 0, 16);
|
||||
if (bindkey.size() != 32) {
|
||||
return;
|
||||
}
|
||||
char temp[3] = {0};
|
||||
for (int i = 0; i < 16; i++) {
|
||||
strncpy(temp, &(bindkey.c_str()[i * 2]), 2);
|
||||
bindkey_[i] = std::strtoul(temp, nullptr, 16);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace xiaomi_rtcgq02lm
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
61
esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h
Normal file
61
esphome/components/xiaomi_rtcgq02lm/xiaomi_rtcgq02lm.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
|
||||
#include "esphome/core/defines.h"
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#include "esphome/components/xiaomi_ble/xiaomi_ble.h"
|
||||
#include "esphome/core/component.h"
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
namespace xiaomi_rtcgq02lm {
|
||||
|
||||
class XiaomiRTCGQ02LM : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
|
||||
public:
|
||||
void set_address(uint64_t address) { address_ = address; };
|
||||
void set_bindkey(const std::string &bindkey);
|
||||
|
||||
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void set_motion(binary_sensor::BinarySensor *motion) { this->motion_ = motion; }
|
||||
void set_motion_timeout(uint16_t timeout) { this->motion_timeout_ = timeout; }
|
||||
|
||||
void set_light(binary_sensor::BinarySensor *light) { this->light_ = light; }
|
||||
void set_button(binary_sensor::BinarySensor *button) { this->button_ = button; }
|
||||
void set_button_timeout(uint16_t timeout) { this->button_timeout_ = timeout; }
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void set_battery_level(sensor::Sensor *battery_level) { battery_level_ = battery_level; }
|
||||
#endif
|
||||
|
||||
protected:
|
||||
uint64_t address_;
|
||||
uint8_t bindkey_[16];
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
uint16_t motion_timeout_;
|
||||
uint16_t button_timeout_;
|
||||
|
||||
binary_sensor::BinarySensor *motion_{nullptr};
|
||||
binary_sensor::BinarySensor *light_{nullptr};
|
||||
binary_sensor::BinarySensor *button_{nullptr};
|
||||
#endif
|
||||
#ifdef USE_SENSOR
|
||||
sensor::Sensor *battery_level_{nullptr};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace xiaomi_rtcgq02lm
|
||||
} // namespace esphome
|
||||
|
||||
#endif
|
||||
@@ -161,6 +161,7 @@ class Config(OrderedDict, fv.FinalValidateConfig):
|
||||
# type: (ConfigPath) -> Optional[vol.Invalid]
|
||||
for err in self.errors:
|
||||
if self.get_deepest_path(err.path) == path:
|
||||
self.errors.remove(err)
|
||||
return err
|
||||
return None
|
||||
|
||||
@@ -647,7 +648,7 @@ class FinalValidateValidationStep(ConfigValidationStep):
|
||||
fv.full_config.reset(token)
|
||||
|
||||
|
||||
def validate_config(config, command_line_substitutions):
|
||||
def validate_config(config, command_line_substitutions) -> Config:
|
||||
result = Config()
|
||||
|
||||
loader.clear_component_meta_finders()
|
||||
@@ -734,9 +735,6 @@ def validate_config(config, command_line_substitutions):
|
||||
result.add_validation_step(LoadValidationStep(key, config[key]))
|
||||
result.run_validation_steps()
|
||||
|
||||
if result.errors:
|
||||
return result
|
||||
|
||||
for domain, conf in config.items():
|
||||
result.add_validation_step(LoadValidationStep(domain, conf))
|
||||
result.add_validation_step(IDPassValidationStep())
|
||||
@@ -991,5 +989,10 @@ def read_config(command_line_substitutions):
|
||||
errstr += f" {errline}"
|
||||
safe_print(errstr)
|
||||
safe_print(indent(dump_dict(res, path)[0]))
|
||||
|
||||
for err in res.errors:
|
||||
safe_print(color(Fore.BOLD_RED, err.msg))
|
||||
safe_print("")
|
||||
|
||||
return None
|
||||
return OrderedDict(res)
|
||||
|
||||
@@ -58,7 +58,7 @@ from esphome.core import (
|
||||
)
|
||||
from esphome.helpers import list_starts_with, add_class_to_obj
|
||||
from esphome.jsonschema import (
|
||||
jschema_composite,
|
||||
jschema_list,
|
||||
jschema_extractor,
|
||||
jschema_registry,
|
||||
jschema_typed,
|
||||
@@ -327,7 +327,7 @@ def boolean(value):
|
||||
)
|
||||
|
||||
|
||||
@jschema_composite
|
||||
@jschema_list
|
||||
def ensure_list(*validators):
|
||||
"""Validate this configuration option to be a list.
|
||||
|
||||
@@ -494,7 +494,11 @@ def templatable(other_validators):
|
||||
"""
|
||||
schema = Schema(other_validators)
|
||||
|
||||
@jschema_extractor("templatable")
|
||||
def validator(value):
|
||||
# pylint: disable=comparison-with-callable
|
||||
if value == jschema_extractor:
|
||||
return other_validators
|
||||
if isinstance(value, Lambda):
|
||||
return returning_lambda(value)
|
||||
if isinstance(other_validators, dict):
|
||||
@@ -1426,7 +1430,6 @@ class SplitDefault(Optional):
|
||||
esp32=vol.UNDEFINED,
|
||||
esp32_arduino=vol.UNDEFINED,
|
||||
esp32_idf=vol.UNDEFINED,
|
||||
host=vol.UNDEFINED,
|
||||
):
|
||||
super().__init__(key)
|
||||
self._esp8266_default = vol.default_factory(esp8266)
|
||||
@@ -1436,7 +1439,6 @@ class SplitDefault(Optional):
|
||||
self._esp32_idf_default = vol.default_factory(
|
||||
esp32_idf if esp32 is vol.UNDEFINED else esp32
|
||||
)
|
||||
self._host_default = vol.default_factory(host)
|
||||
|
||||
@property
|
||||
def default(self):
|
||||
@@ -1446,8 +1448,6 @@ class SplitDefault(Optional):
|
||||
return self._esp32_arduino_default
|
||||
if CORE.is_esp32 and CORE.using_esp_idf:
|
||||
return self._esp32_idf_default
|
||||
if CORE.is_host:
|
||||
return self._host_default
|
||||
raise NotImplementedError
|
||||
|
||||
@default.setter
|
||||
@@ -1550,7 +1550,7 @@ def validate_registry(name, registry):
|
||||
return ensure_list(validate_registry_entry(name, registry))
|
||||
|
||||
|
||||
@jschema_composite
|
||||
@jschema_list
|
||||
def maybe_simple_value(*validators, **kwargs):
|
||||
key = kwargs.pop("key", CONF_VALUE)
|
||||
validator = All(*validators)
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
"""Constants used by esphome."""
|
||||
|
||||
__version__ = "2022.3.0-dev"
|
||||
__version__ = "2022.4.0b1"
|
||||
|
||||
ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_"
|
||||
|
||||
TARGET_FRAMEWORKS = ["arduino", "esp-idf"]
|
||||
|
||||
PLATFORM_ESP32 = "esp32"
|
||||
PLATFORM_ESP8266 = "esp8266"
|
||||
PLATFORM_HOST = "host"
|
||||
|
||||
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266, PLATFORM_HOST]
|
||||
TARGET_PLATFORMS = [PLATFORM_ESP32, PLATFORM_ESP8266]
|
||||
|
||||
SOURCE_FILE_EXTENSIONS = {".cpp", ".hpp", ".h", ".c", ".tcc", ".ino"}
|
||||
HEADER_FILE_EXTENSIONS = {".h", ".hpp", ".tcc"}
|
||||
@@ -647,6 +644,7 @@ CONF_STEP_MODE = "step_mode"
|
||||
CONF_STEP_PIN = "step_pin"
|
||||
CONF_STOP = "stop"
|
||||
CONF_STOP_ACTION = "stop_action"
|
||||
CONF_STORE_BASELINE = "store_baseline"
|
||||
CONF_SUBNET = "subnet"
|
||||
CONF_SUBSTITUTIONS = "substitutions"
|
||||
CONF_SUPPLEMENTAL_COOLING_ACTION = "supplemental_cooling_action"
|
||||
@@ -683,6 +681,7 @@ CONF_TARGET_TEMPERATURE_LOW_COMMAND_TOPIC = "target_temperature_low_command_topi
|
||||
CONF_TARGET_TEMPERATURE_LOW_STATE_TOPIC = "target_temperature_low_state_topic"
|
||||
CONF_TARGET_TEMPERATURE_STATE_TOPIC = "target_temperature_state_topic"
|
||||
CONF_TEMPERATURE = "temperature"
|
||||
CONF_TEMPERATURE_SOURCE = "temperature_source"
|
||||
CONF_TEMPERATURE_STEP = "temperature_step"
|
||||
CONF_TEXT_SENSORS = "text_sensors"
|
||||
CONF_THEN = "then"
|
||||
|
||||
@@ -593,10 +593,6 @@ class EsphomeCore:
|
||||
def is_esp32(self):
|
||||
return self.target_platform == "esp32"
|
||||
|
||||
@property
|
||||
def is_host(self):
|
||||
return self.target_platform == "host"
|
||||
|
||||
@property
|
||||
def target_framework(self):
|
||||
return self.data[KEY_CORE][KEY_TARGET_FRAMEWORK]
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
#include "util.h"
|
||||
#include "api_server.h"
|
||||
#include "user_services.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "component_iterator.h"
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
#ifdef USE_API
|
||||
#include "esphome/components/api/api_server.h"
|
||||
#include "esphome/components/api/user_services.h"
|
||||
#endif
|
||||
|
||||
ComponentIterator::ComponentIterator(APIServer *server) : server_(server) {}
|
||||
void ComponentIterator::begin() {
|
||||
namespace esphome {
|
||||
|
||||
void ComponentIterator::begin(bool include_internal) {
|
||||
this->state_ = IteratorState::BEGIN;
|
||||
this->at_ = 0;
|
||||
this->include_internal_ = include_internal;
|
||||
}
|
||||
void ComponentIterator::advance() {
|
||||
bool advance_platform = false;
|
||||
@@ -32,7 +34,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *binary_sensor = App.get_binary_sensors()[this->at_];
|
||||
if (binary_sensor->is_internal()) {
|
||||
if (binary_sensor->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -47,7 +49,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *cover = App.get_covers()[this->at_];
|
||||
if (cover->is_internal()) {
|
||||
if (cover->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -62,7 +64,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *fan = App.get_fans()[this->at_];
|
||||
if (fan->is_internal()) {
|
||||
if (fan->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -77,7 +79,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *light = App.get_lights()[this->at_];
|
||||
if (light->is_internal()) {
|
||||
if (light->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -92,7 +94,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *sensor = App.get_sensors()[this->at_];
|
||||
if (sensor->is_internal()) {
|
||||
if (sensor->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -107,7 +109,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *a_switch = App.get_switches()[this->at_];
|
||||
if (a_switch->is_internal()) {
|
||||
if (a_switch->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -122,7 +124,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *button = App.get_buttons()[this->at_];
|
||||
if (button->is_internal()) {
|
||||
if (button->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -137,7 +139,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *text_sensor = App.get_text_sensors()[this->at_];
|
||||
if (text_sensor->is_internal()) {
|
||||
if (text_sensor->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -146,20 +148,22 @@ void ComponentIterator::advance() {
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_API
|
||||
case IteratorState ::SERVICE:
|
||||
if (this->at_ >= this->server_->get_user_services().size()) {
|
||||
if (this->at_ >= api::global_api_server->get_user_services().size()) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *service = this->server_->get_user_services()[this->at_];
|
||||
auto *service = api::global_api_server->get_user_services()[this->at_];
|
||||
success = this->on_service(service);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
case IteratorState::CAMERA:
|
||||
if (esp32_camera::global_esp32_camera == nullptr) {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
if (esp32_camera::global_esp32_camera->is_internal()) {
|
||||
if (esp32_camera::global_esp32_camera->is_internal() && !this->include_internal_) {
|
||||
advance_platform = success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -174,7 +178,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *climate = App.get_climates()[this->at_];
|
||||
if (climate->is_internal()) {
|
||||
if (climate->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -189,7 +193,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *number = App.get_numbers()[this->at_];
|
||||
if (number->is_internal()) {
|
||||
if (number->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -204,7 +208,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *select = App.get_selects()[this->at_];
|
||||
if (select->is_internal()) {
|
||||
if (select->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -219,7 +223,7 @@ void ComponentIterator::advance() {
|
||||
advance_platform = true;
|
||||
} else {
|
||||
auto *a_lock = App.get_locks()[this->at_];
|
||||
if (a_lock->is_internal()) {
|
||||
if (a_lock->is_internal() && !this->include_internal_) {
|
||||
success = true;
|
||||
break;
|
||||
} else {
|
||||
@@ -244,10 +248,10 @@ void ComponentIterator::advance() {
|
||||
}
|
||||
bool ComponentIterator::on_end() { return true; }
|
||||
bool ComponentIterator::on_begin() { return true; }
|
||||
bool ComponentIterator::on_service(UserServiceDescriptor *service) { return true; }
|
||||
#ifdef USE_API
|
||||
bool ComponentIterator::on_service(api::UserServiceDescriptor *service) { return true; }
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
bool ComponentIterator::on_camera(esp32_camera::ESP32Camera *camera) { return true; }
|
||||
#endif
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -1,23 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/controller.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
#include "esphome/components/esp32_camera/esp32_camera.h"
|
||||
#endif
|
||||
|
||||
namespace esphome {
|
||||
namespace api {
|
||||
|
||||
class APIServer;
|
||||
#ifdef USE_API
|
||||
namespace api {
|
||||
class UserServiceDescriptor;
|
||||
} // namespace api
|
||||
#endif
|
||||
|
||||
class ComponentIterator {
|
||||
public:
|
||||
ComponentIterator(APIServer *server);
|
||||
|
||||
void begin();
|
||||
void begin(bool include_internal = false);
|
||||
void advance();
|
||||
virtual bool on_begin();
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
@@ -44,7 +45,9 @@ class ComponentIterator {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
virtual bool on_text_sensor(text_sensor::TextSensor *text_sensor) = 0;
|
||||
#endif
|
||||
virtual bool on_service(UserServiceDescriptor *service);
|
||||
#ifdef USE_API
|
||||
virtual bool on_service(api::UserServiceDescriptor *service);
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
virtual bool on_camera(esp32_camera::ESP32Camera *camera);
|
||||
#endif
|
||||
@@ -90,7 +93,9 @@ class ComponentIterator {
|
||||
#ifdef USE_TEXT_SENSOR
|
||||
TEXT_SENSOR,
|
||||
#endif
|
||||
#ifdef USE_API
|
||||
SERVICE,
|
||||
#endif
|
||||
#ifdef USE_ESP32_CAMERA
|
||||
CAMERA,
|
||||
#endif
|
||||
@@ -109,9 +114,7 @@ class ComponentIterator {
|
||||
MAX,
|
||||
} state_{IteratorState::NONE};
|
||||
size_t at_{0};
|
||||
|
||||
APIServer *server_;
|
||||
bool include_internal_{false};
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
} // namespace esphome
|
||||
@@ -29,6 +29,7 @@
|
||||
#define USE_LOCK
|
||||
#define USE_LOGGER
|
||||
#define USE_MDNS
|
||||
#define USE_MQTT
|
||||
#define USE_NUMBER
|
||||
#define USE_OTA_PASSWORD
|
||||
#define USE_OTA_STATE_CALLBACK
|
||||
@@ -40,6 +41,7 @@
|
||||
#define USE_SWITCH
|
||||
#define USE_TEXT_SENSOR
|
||||
#define USE_TIME
|
||||
#define USE_TOUCHSCREEN
|
||||
#define USE_UART_DEBUGGER
|
||||
#define USE_WIFI
|
||||
|
||||
@@ -48,13 +50,17 @@
|
||||
#define USE_CAPTIVE_PORTAL
|
||||
#define USE_JSON
|
||||
#define USE_NEXTION_TFT_UPLOAD
|
||||
#define USE_MQTT
|
||||
#define USE_PROMETHEUS
|
||||
#define USE_WEBSERVER
|
||||
#define USE_WEBSERVER_PORT 80 // NOLINT
|
||||
#define USE_WIFI_WPA2_EAP
|
||||
#endif
|
||||
|
||||
// IDF-specific feature flags
|
||||
#ifdef USE_ESP_IDF
|
||||
#define USE_MQTT_IDF_ENQUEUE
|
||||
#endif
|
||||
|
||||
// ESP32-specific feature flags
|
||||
#ifdef USE_ESP32
|
||||
#define USE_ESP32_BLE_CLIENT
|
||||
@@ -83,10 +89,6 @@
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
#endif
|
||||
|
||||
#ifdef USE_HOST
|
||||
#define USE_SOCKET_IMPL_BSD_SOCKETS
|
||||
#endif
|
||||
|
||||
// Disabled feature flags
|
||||
//#define USE_BSEC // Requires a library with proprietary license.
|
||||
|
||||
|
||||
@@ -21,10 +21,6 @@
|
||||
#include "esp_system.h"
|
||||
#include <freertos/FreeRTOS.h>
|
||||
#include <freertos/portmacro.h>
|
||||
#elif defined(USE_HOST)
|
||||
#include <cstdio>
|
||||
#include <random>
|
||||
#include <limits>
|
||||
#endif
|
||||
|
||||
#ifdef USE_ESP32_IGNORE_EFUSE_MAC_CRC
|
||||
@@ -80,11 +76,6 @@ uint32_t random_uint32() {
|
||||
return esp_random();
|
||||
#elif defined(USE_ESP8266)
|
||||
return os_random();
|
||||
#elif defined(USE_HOST)
|
||||
std::random_device dev;
|
||||
std::mt19937 rng(dev());
|
||||
std::uniform_int_distribution<uint32_t> dist(0, std::numeric_limits<uint32_t>::max());
|
||||
return dist(rng);
|
||||
#else
|
||||
#error "No random source available for this configuration."
|
||||
#endif
|
||||
@@ -96,19 +87,6 @@ bool random_bytes(uint8_t *data, size_t len) {
|
||||
return true;
|
||||
#elif defined(USE_ESP8266)
|
||||
return os_get_random(data, len) == 0;
|
||||
#elif defined(USE_HOST)
|
||||
FILE *fp = fopen("/dev/urandom", "r");
|
||||
if (fp == nullptr) {
|
||||
ESP_LOGW(TAG, "Could not open /dev/urandom, errno=%d", errno);
|
||||
exit(1);
|
||||
}
|
||||
size_t read = fread(data, 1, len, fp);
|
||||
if (read != len) {
|
||||
ESP_LOGW(TAG, "Not enough data from /dev/urandom");
|
||||
exit(1);
|
||||
}
|
||||
fclose(fp);
|
||||
return true
|
||||
#else
|
||||
#error "No random source available for this configuration."
|
||||
#endif
|
||||
@@ -235,6 +213,25 @@ std::string format_hex_pretty(const uint8_t *data, size_t length) {
|
||||
}
|
||||
std::string format_hex_pretty(const std::vector<uint8_t> &data) { return format_hex_pretty(data.data(), data.size()); }
|
||||
|
||||
std::string format_hex_pretty(const uint16_t *data, size_t length) {
|
||||
if (length == 0)
|
||||
return "";
|
||||
std::string ret;
|
||||
ret.resize(5 * length - 1);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
ret[5 * i] = format_hex_pretty_char((data[i] & 0xF000) >> 12);
|
||||
ret[5 * i + 1] = format_hex_pretty_char((data[i] & 0x0F00) >> 8);
|
||||
ret[5 * i + 2] = format_hex_pretty_char((data[i] & 0x00F0) >> 4);
|
||||
ret[5 * i + 3] = format_hex_pretty_char(data[i] & 0x000F);
|
||||
if (i != length - 1)
|
||||
ret[5 * i + 2] = '.';
|
||||
}
|
||||
if (length > 4)
|
||||
return ret + " (" + to_string(length) + ")";
|
||||
return ret;
|
||||
}
|
||||
std::string format_hex_pretty(const std::vector<uint16_t> &data) { return format_hex_pretty(data.data(), data.size()); }
|
||||
|
||||
ParseOnOffState parse_on_off(const char *str, const char *on, const char *off) {
|
||||
if (on == nullptr && strcasecmp(str, "on") == 0)
|
||||
return PARSE_ON;
|
||||
|
||||
@@ -173,6 +173,10 @@ constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, ui
|
||||
return (static_cast<uint32_t>(byte1) << 24) | (static_cast<uint32_t>(byte2) << 16) |
|
||||
(static_cast<uint32_t>(byte3) << 8) | (static_cast<uint32_t>(byte4));
|
||||
}
|
||||
/// Encode a 24-bit value given three bytes in most to least significant byte order.
|
||||
constexpr uint32_t encode_uint24(uint8_t byte1, uint8_t byte2, uint8_t byte3) {
|
||||
return ((static_cast<uint32_t>(byte1) << 16) | (static_cast<uint32_t>(byte2) << 8) | (static_cast<uint32_t>(byte3)));
|
||||
}
|
||||
|
||||
/// Encode a value from its constituent bytes (from most to least significant) in an array with length sizeof(T).
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0>
|
||||
@@ -386,8 +390,12 @@ template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::stri
|
||||
|
||||
/// Format the byte array \p data of length \p len in pretty-printed, human-readable hex.
|
||||
std::string format_hex_pretty(const uint8_t *data, size_t length);
|
||||
/// Format the word array \p data of length \p len in pretty-printed, human-readable hex.
|
||||
std::string format_hex_pretty(const uint16_t *data, size_t length);
|
||||
/// Format the vector \p data in pretty-printed, human-readable hex.
|
||||
std::string format_hex_pretty(const std::vector<uint8_t> &data);
|
||||
/// Format the vector \p data in pretty-printed, human-readable hex.
|
||||
std::string format_hex_pretty(const std::vector<uint16_t> &data);
|
||||
/// Format an unsigned integer in pretty-printed, human-readable hex, starting with the most significant byte.
|
||||
template<typename T, enable_if_t<std::is_unsigned<T>::value, int> = 0> std::string format_hex_pretty(T val) {
|
||||
val = convert_big_endian(val);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Helpers to retrieve schema from voluptuous validators.
|
||||
|
||||
These are a helper decorators to help get schema from some
|
||||
components which uses volutuous in a way where validation
|
||||
components which uses voluptuous in a way where validation
|
||||
is hidden in local functions
|
||||
These decorators should not modify at all what the functions
|
||||
originally do.
|
||||
@@ -24,7 +24,7 @@ def jschema_extractor(validator_name):
|
||||
if EnableJsonSchemaCollect:
|
||||
|
||||
def decorator(func):
|
||||
hidden_schemas[str(func)] = validator_name
|
||||
hidden_schemas[repr(func)] = validator_name
|
||||
return func
|
||||
|
||||
return decorator
|
||||
@@ -41,7 +41,7 @@ def jschema_extended(func):
|
||||
def decorate(*args, **kwargs):
|
||||
ret = func(*args, **kwargs)
|
||||
assert len(args) == 2
|
||||
extended_schemas[str(ret)] = args
|
||||
extended_schemas[repr(ret)] = args
|
||||
return ret
|
||||
|
||||
return decorate
|
||||
@@ -49,13 +49,13 @@ def jschema_extended(func):
|
||||
return func
|
||||
|
||||
|
||||
def jschema_composite(func):
|
||||
def jschema_list(func):
|
||||
if EnableJsonSchemaCollect:
|
||||
|
||||
def decorate(*args, **kwargs):
|
||||
ret = func(*args, **kwargs)
|
||||
# args length might be 2, but 2nd is always validator
|
||||
list_schemas[str(ret)] = args
|
||||
list_schemas[repr(ret)] = args
|
||||
return ret
|
||||
|
||||
return decorate
|
||||
@@ -67,7 +67,7 @@ def jschema_registry(registry):
|
||||
if EnableJsonSchemaCollect:
|
||||
|
||||
def decorator(func):
|
||||
registry_schemas[str(func)] = registry
|
||||
registry_schemas[repr(func)] = registry
|
||||
return func
|
||||
|
||||
return decorator
|
||||
@@ -83,7 +83,7 @@ def jschema_typed(func):
|
||||
|
||||
def decorate(*args, **kwargs):
|
||||
ret = func(*args, **kwargs)
|
||||
typed_schemas[str(ret)] = (args, kwargs)
|
||||
typed_schemas[repr(ret)] = (args, kwargs)
|
||||
return ret
|
||||
|
||||
return decorate
|
||||
|
||||
@@ -62,9 +62,7 @@ lib_deps =
|
||||
glmnet/Dsmr@0.5 ; dsmr
|
||||
rweather/Crypto@0.2.0 ; dsmr
|
||||
dudanov/MideaUART@1.1.8 ; midea
|
||||
; PIO isn't update releases correctly, see:
|
||||
; https://github.com/ToniA/arduino-heatpumpir/commit/0948c619d86407a4e50e8db2f3c193e0576c86fd
|
||||
https://github.com/ToniA/arduino-heatpumpir.git#1.0.18 ; heatpumpir
|
||||
tonia/HeatpumpIR@1.0.20 ; heatpumpir
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-DUSE_ARDUINO
|
||||
@@ -220,13 +218,3 @@ board_build.esp-idf.sdkconfig_path = .temp/sdkconfig-esp32s2-idf-tidy
|
||||
build_flags =
|
||||
${common:esp32-idf.build_flags}
|
||||
${flags:clangtidy.build_flags}
|
||||
|
||||
[env:host]
|
||||
extends = common
|
||||
platform = platformio/native
|
||||
lib_deps =
|
||||
esphome/noise-c@0.1.1 ; used by api
|
||||
build_flags =
|
||||
${common.build_flags}
|
||||
-DUSE_HOST
|
||||
${flags:runtime.build_flags}
|
||||
@@ -1,17 +1,17 @@
|
||||
voluptuous==0.12.2
|
||||
voluptuous==0.13.1
|
||||
PyYAML==6.0
|
||||
paho-mqtt==1.6.1
|
||||
colorama==0.4.4
|
||||
tornado==6.1
|
||||
tzlocal==4.1 # from time
|
||||
tzlocal==4.2 # from time
|
||||
tzdata>=2021.1 # from time
|
||||
pyserial==3.5
|
||||
platformio==5.2.5 # When updating platformio, also update Dockerfile
|
||||
esptool==3.2
|
||||
click==8.0.3
|
||||
esphome-dashboard==20220219.0
|
||||
esptool==3.3
|
||||
click==8.1.2
|
||||
esphome-dashboard==20220309.0
|
||||
aioesphomeapi==10.8.2
|
||||
zeroconf==0.38.3
|
||||
zeroconf==0.38.4
|
||||
|
||||
# esp-idf requires this, but doesn't bundle it by default
|
||||
# https://github.com/espressif/esp-idf/blob/220590d599e134d7a5e7f1e683cc4550349ffbf8/requirements.txt#L24
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
pylint==2.12.2
|
||||
pylint==2.13.5
|
||||
flake8==4.0.1
|
||||
black==22.1.0
|
||||
pyupgrade==2.31.0
|
||||
black==22.3.0
|
||||
pyupgrade==2.32.0
|
||||
pre-commit
|
||||
|
||||
# Unit tests
|
||||
pytest==7.0.1
|
||||
pytest==7.1.1
|
||||
pytest-cov==3.0.0
|
||||
pytest-mock==3.7.0
|
||||
pytest-asyncio==0.18.1
|
||||
pytest-asyncio==0.18.3
|
||||
asyncmock==0.4.2
|
||||
hypothesis==5.49.0
|
||||
|
||||
@@ -236,7 +236,7 @@ class Int64Type(TypeInfo):
|
||||
encode_func = "encode_int64"
|
||||
|
||||
def dump(self, name):
|
||||
o = f'sprintf(buffer, "%ll", {name});\n'
|
||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||
o += f"out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -249,7 +249,7 @@ class UInt64Type(TypeInfo):
|
||||
encode_func = "encode_uint64"
|
||||
|
||||
def dump(self, name):
|
||||
o = f'sprintf(buffer, "%ull", {name});\n'
|
||||
o = f'sprintf(buffer, "%llu", {name});\n'
|
||||
o += f"out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -275,7 +275,7 @@ class Fixed64Type(TypeInfo):
|
||||
encode_func = "encode_fixed64"
|
||||
|
||||
def dump(self, name):
|
||||
o = f'sprintf(buffer, "%ull", {name});\n'
|
||||
o = f'sprintf(buffer, "%llu", {name});\n'
|
||||
o += f"out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -417,7 +417,7 @@ class SFixed64Type(TypeInfo):
|
||||
encode_func = "encode_sfixed64"
|
||||
|
||||
def dump(self, name):
|
||||
o = f'sprintf(buffer, "%ll", {name});\n'
|
||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||
o += f"out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -440,10 +440,10 @@ class SInt64Type(TypeInfo):
|
||||
cpp_type = "int64_t"
|
||||
default_value = "0"
|
||||
decode_varint = "value.as_sint64()"
|
||||
encode_func = "encode_sin64"
|
||||
encode_func = "encode_sint64"
|
||||
|
||||
def dump(self, name):
|
||||
o = f'sprintf(buffer, "%ll", {name});\n'
|
||||
o = f'sprintf(buffer, "%lld", {name});\n'
|
||||
o += f"out.append(buffer);"
|
||||
return o
|
||||
|
||||
@@ -622,13 +622,13 @@ def build_message_type(desc):
|
||||
protected_content.insert(0, prot)
|
||||
if decode_64bit:
|
||||
decode_64bit.append("default:\n return false;")
|
||||
o = f"bool {desc.name}::decode_64bit(uint32_t field_id, Proto64bit value) {{\n"
|
||||
o = f"bool {desc.name}::decode_64bit(uint32_t field_id, Proto64Bit value) {{\n"
|
||||
o += " switch (field_id) {\n"
|
||||
o += indent("\n".join(decode_64bit), " ") + "\n"
|
||||
o += " }\n"
|
||||
o += "}\n"
|
||||
cpp += o
|
||||
prot = "bool decode_64bit(uint32_t field_id, Proto64bit value) override;"
|
||||
prot = "bool decode_64bit(uint32_t field_id, Proto64Bit value) override;"
|
||||
protected_content.insert(0, prot)
|
||||
|
||||
o = f"void {desc.name}::encode(ProtoWriteBuffer buffer) const {{"
|
||||
|
||||
@@ -70,7 +70,7 @@ def add_definition_array_or_single_object(ref):
|
||||
def add_core():
|
||||
from esphome.core.config import CONFIG_SCHEMA
|
||||
|
||||
base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA.schema)
|
||||
base_props["esphome"] = get_jschema("esphome", CONFIG_SCHEMA)
|
||||
|
||||
|
||||
def add_buses():
|
||||
@@ -216,7 +216,7 @@ def add_components():
|
||||
add_module_registries(domain, c.module)
|
||||
add_module_schemas(domain, c.module)
|
||||
|
||||
# need first to iterate all platforms then iteate components
|
||||
# need first to iterate all platforms then iterate components
|
||||
# a platform component can have other components as properties,
|
||||
# e.g. climate components usually have a temperature sensor
|
||||
|
||||
@@ -325,7 +325,9 @@ def get_entry(parent_key, vschema):
|
||||
if DUMP_COMMENTS:
|
||||
entry[JSC_COMMENT] = "entry: " + parent_key + "/" + str(vschema)
|
||||
|
||||
if isinstance(vschema, list):
|
||||
if isinstance(vschema, dict):
|
||||
entry = {"what": "is_this"}
|
||||
elif isinstance(vschema, list):
|
||||
ref = get_jschema(parent_key + "[]", vschema[0])
|
||||
entry = {"type": "array", "items": ref}
|
||||
elif isinstance(vschema, schema_type) and hasattr(vschema, "schema"):
|
||||
@@ -387,8 +389,10 @@ def get_entry(parent_key, vschema):
|
||||
|
||||
v = vschema(None)
|
||||
if isinstance(v, ID):
|
||||
if v.type.base != "script::Script" and (
|
||||
v.type.inherits_from(Trigger) or v.type == Automation
|
||||
if (
|
||||
v.type.base != "script::Script"
|
||||
and v.type.base != "switch_::Switch"
|
||||
and (v.type.inherits_from(Trigger) or v.type == Automation)
|
||||
):
|
||||
return None
|
||||
entry = {"type": "string", "id_type": v.type.base}
|
||||
@@ -410,6 +414,8 @@ def default_schema():
|
||||
|
||||
|
||||
def is_default_schema(jschema):
|
||||
if jschema is None:
|
||||
return False
|
||||
if is_ref(jschema):
|
||||
jschema = unref(jschema)
|
||||
if not jschema:
|
||||
@@ -425,6 +431,9 @@ def get_jschema(path, vschema, create_return_ref=True):
|
||||
|
||||
jschema = convert_schema(path, vschema)
|
||||
|
||||
if jschema is None:
|
||||
return None
|
||||
|
||||
if is_ref(jschema):
|
||||
# this can happen when returned extended
|
||||
# schemas where all properties found in previous extended schema
|
||||
@@ -450,6 +459,9 @@ def get_schema_str(vschema):
|
||||
|
||||
|
||||
def create_ref(name, vschema, jschema):
|
||||
if jschema is None:
|
||||
raise ValueError("Cannot create a ref with null jschema for " + name)
|
||||
|
||||
if name in schema_names:
|
||||
raise ValueError("Not supported")
|
||||
|
||||
@@ -523,6 +535,15 @@ def convert_schema(path, vschema, un_extend=True):
|
||||
extended = ejs.extended_schemas.get(str(vschema))
|
||||
if extended:
|
||||
lhs = get_jschema(path, extended[0], False)
|
||||
|
||||
# The midea actions are extending an empty schema (resulted in the templatize not templatizing anything)
|
||||
# this causes a recursion in that this extended looks the same in extended schema as the extended[1]
|
||||
if ejs.extended_schemas.get(str(vschema)) == ejs.extended_schemas.get(
|
||||
str(extended[1])
|
||||
):
|
||||
assert path.startswith("midea_ac")
|
||||
return convert_schema(path, extended[1], False)
|
||||
|
||||
rhs = get_jschema(path, extended[1], False)
|
||||
|
||||
# check if we are not merging properties which are already in base component
|
||||
@@ -567,6 +588,8 @@ def convert_schema(path, vschema, un_extend=True):
|
||||
# we should take the valid schema,
|
||||
# commonly all is used to validate a schema, and then a function which
|
||||
# is not a schema es also given, get_schema will then return a default_schema()
|
||||
if v == dict:
|
||||
continue # this is a dict in the SCHEMA of packages
|
||||
val_schema = get_jschema(path, v, False)
|
||||
if is_default_schema(val_schema):
|
||||
if not output:
|
||||
@@ -673,6 +696,11 @@ def add_pin_registry():
|
||||
|
||||
for mode in ("INPUT", "OUTPUT"):
|
||||
schema_name = f"PIN.GPIO_FULL_{mode}_PIN_SCHEMA"
|
||||
|
||||
# TODO: get pin definitions properly
|
||||
if schema_name not in definitions:
|
||||
definitions[schema_name] = {"type": ["object", "null"], JSC_PROPERTIES: {}}
|
||||
|
||||
internal = definitions[schema_name]
|
||||
definitions[schema_name]["additionalItems"] = False
|
||||
definitions[f"PIN.{mode}_INTERNAL"] = internal
|
||||
@@ -683,12 +711,11 @@ def add_pin_registry():
|
||||
definitions[schema_name] = {"oneOf": schemas, "type": ["string", "object"]}
|
||||
|
||||
for k, v in pin_registry.items():
|
||||
pin_jschema = get_jschema(
|
||||
f"PIN.{mode}_" + k, v[1][0 if mode == "OUTPUT" else 1]
|
||||
)
|
||||
if unref(pin_jschema):
|
||||
pin_jschema["required"] = [k]
|
||||
schemas.append(pin_jschema)
|
||||
if isinstance(v[1], vol.validators.All):
|
||||
pin_jschema = get_jschema(f"PIN.{mode}_" + k, v[1])
|
||||
if unref(pin_jschema):
|
||||
pin_jschema["required"] = [k]
|
||||
schemas.append(pin_jschema)
|
||||
|
||||
|
||||
def dump_schema():
|
||||
@@ -730,9 +757,9 @@ def dump_schema():
|
||||
cv.valid_name,
|
||||
cv.hex_int,
|
||||
cv.hex_int_range,
|
||||
pins.output_pin,
|
||||
pins.input_pin,
|
||||
pins.input_pullup_pin,
|
||||
pins.gpio_output_pin_schema,
|
||||
pins.gpio_input_pin_schema,
|
||||
pins.gpio_input_pullup_pin_schema,
|
||||
cv.float_with_unit,
|
||||
cv.subscribe_topic,
|
||||
cv.publish_topic,
|
||||
@@ -753,12 +780,12 @@ def dump_schema():
|
||||
|
||||
for v in [pins.gpio_input_pin_schema, pins.gpio_input_pullup_pin_schema]:
|
||||
schema_registry[v] = get_ref("PIN.GPIO_FULL_INPUT_PIN_SCHEMA")
|
||||
for v in [pins.internal_gpio_input_pin_schema, pins.input_pin]:
|
||||
for v in [pins.internal_gpio_input_pin_schema, pins.gpio_input_pin_schema]:
|
||||
schema_registry[v] = get_ref("PIN.INPUT_INTERNAL")
|
||||
|
||||
for v in [pins.gpio_output_pin_schema, pins.internal_gpio_output_pin_schema]:
|
||||
schema_registry[v] = get_ref("PIN.GPIO_FULL_OUTPUT_PIN_SCHEMA")
|
||||
for v in [pins.internal_gpio_output_pin_schema, pins.output_pin]:
|
||||
for v in [pins.internal_gpio_output_pin_schema, pins.gpio_output_pin_schema]:
|
||||
schema_registry[v] = get_ref("PIN.OUTPUT_INTERNAL")
|
||||
|
||||
add_module_schemas("CONFIG", cv)
|
||||
|
||||
813
script/build_language_schema.py
Normal file
813
script/build_language_schema.py
Normal file
@@ -0,0 +1,813 @@
|
||||
import inspect
|
||||
import json
|
||||
import argparse
|
||||
from operator import truediv
|
||||
import os
|
||||
import voluptuous as vol
|
||||
|
||||
# NOTE: Cannot import other esphome components globally as a modification in jsonschema
|
||||
# is needed before modules are loaded
|
||||
import esphome.jsonschema as ejs
|
||||
|
||||
ejs.EnableJsonSchemaCollect = True
|
||||
|
||||
# schema format:
|
||||
# Schemas are splitted in several files in json format, one for core stuff, one for each platform (sensor, binary_sensor, etc) and
|
||||
# one for each component (dallas, sim800l, etc.) component can have schema for root component/hub and also for platform component,
|
||||
# e.g. dallas has hub component which has pin and then has the sensor platform which has sensor name, index, etc.
|
||||
# When files are loaded they are merged in a single object.
|
||||
# The root format is
|
||||
|
||||
S_CONFIG_VAR = "config_var"
|
||||
S_CONFIG_VARS = "config_vars"
|
||||
S_CONFIG_SCHEMA = "CONFIG_SCHEMA"
|
||||
S_COMPONENT = "component"
|
||||
S_COMPONENTS = "components"
|
||||
S_PLATFORMS = "platforms"
|
||||
S_SCHEMA = "schema"
|
||||
S_SCHEMAS = "schemas"
|
||||
S_EXTENDS = "extends"
|
||||
S_TYPE = "type"
|
||||
S_NAME = "name"
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"--output-path", default=".", help="Output path", type=os.path.abspath
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
DUMP_RAW = False
|
||||
DUMP_UNKNOWN = False
|
||||
DUMP_PATH = False
|
||||
JSON_DUMP_PRETTY = True
|
||||
|
||||
# store here dynamic load of esphome components
|
||||
components = {}
|
||||
|
||||
schema_core = {}
|
||||
|
||||
# output is where all is built
|
||||
output = {"core": schema_core}
|
||||
# The full generated output is here here
|
||||
schema_full = {"components": output}
|
||||
|
||||
# A string, string map, key is the str(schema) and value is
|
||||
# a tuple, first element is the schema reference and second is the schema path given, the schema reference is needed to test as different schemas have same key
|
||||
known_schemas = {}
|
||||
|
||||
solve_registry = []
|
||||
|
||||
|
||||
def get_component_names():
|
||||
# return [
|
||||
# "esphome",
|
||||
# "esp32",
|
||||
# "esp8266",
|
||||
# "logger",
|
||||
# "sensor",
|
||||
# "remote_receiver",
|
||||
# "binary_sensor",
|
||||
# ]
|
||||
from esphome.loader import CORE_COMPONENTS_PATH
|
||||
|
||||
component_names = ["esphome", "sensor"]
|
||||
|
||||
for d in os.listdir(CORE_COMPONENTS_PATH):
|
||||
if not d.startswith("__") and os.path.isdir(
|
||||
os.path.join(CORE_COMPONENTS_PATH, d)
|
||||
):
|
||||
if d not in component_names:
|
||||
component_names.append(d)
|
||||
|
||||
return component_names
|
||||
|
||||
|
||||
def load_components():
|
||||
from esphome.config import get_component
|
||||
|
||||
for domain in get_component_names():
|
||||
components[domain] = get_component(domain)
|
||||
|
||||
|
||||
load_components()
|
||||
|
||||
# Import esphome after loading components (so schema is tracked)
|
||||
# pylint: disable=wrong-import-position
|
||||
import esphome.core as esphome_core
|
||||
import esphome.config_validation as cv
|
||||
from esphome import automation
|
||||
from esphome import pins
|
||||
from esphome.components import remote_base
|
||||
from esphome.const import CONF_TYPE
|
||||
from esphome.loader import get_platform
|
||||
from esphome.helpers import write_file_if_changed
|
||||
from esphome.util import Registry
|
||||
|
||||
# pylint: enable=wrong-import-position
|
||||
|
||||
|
||||
def write_file(name, obj):
|
||||
full_path = os.path.join(args.output_path, name + ".json")
|
||||
if JSON_DUMP_PRETTY:
|
||||
json_str = json.dumps(obj, indent=2)
|
||||
else:
|
||||
json_str = json.dumps(obj, separators=(",", ":"))
|
||||
write_file_if_changed(full_path, json_str)
|
||||
print(f"Wrote {full_path}")
|
||||
|
||||
|
||||
def register_module_schemas(key, module, manifest=None):
|
||||
for name, schema in module_schemas(module):
|
||||
register_known_schema(key, name, schema)
|
||||
if (
|
||||
manifest and manifest.multi_conf and S_CONFIG_SCHEMA in output[key][S_SCHEMAS]
|
||||
): # not sure about 2nd part of the if, might be useless config (e.g. as3935)
|
||||
output[key][S_SCHEMAS][S_CONFIG_SCHEMA]["is_list"] = True
|
||||
|
||||
|
||||
def register_known_schema(module, name, schema):
|
||||
if module not in output:
|
||||
output[module] = {S_SCHEMAS: {}}
|
||||
config = convert_config(schema, f"{module}/{name}")
|
||||
if S_TYPE not in config:
|
||||
print(f"Config var without type: {module}.{name}")
|
||||
|
||||
output[module][S_SCHEMAS][name] = config
|
||||
repr_schema = repr(schema)
|
||||
if repr_schema in known_schemas:
|
||||
schema_info = known_schemas[repr_schema]
|
||||
schema_info.append((schema, f"{module}.{name}"))
|
||||
else:
|
||||
known_schemas[repr_schema] = [(schema, f"{module}.{name}")]
|
||||
|
||||
|
||||
def module_schemas(module):
|
||||
# This should yield elements in order so extended schemas are resolved properly
|
||||
# To do this we check on the source code where the symbol is seen first. Seems to work.
|
||||
try:
|
||||
module_str = inspect.getsource(module)
|
||||
except TypeError:
|
||||
# improv
|
||||
module_str = ""
|
||||
except OSError:
|
||||
# some empty __init__ files
|
||||
module_str = ""
|
||||
schemas = {}
|
||||
for m_attr_name in dir(module):
|
||||
m_attr_obj = getattr(module, m_attr_name)
|
||||
if isConvertibleSchema(m_attr_obj):
|
||||
schemas[module_str.find(m_attr_name)] = [m_attr_name, m_attr_obj]
|
||||
|
||||
for pos in sorted(schemas.keys()):
|
||||
yield schemas[pos]
|
||||
|
||||
|
||||
found_registries = {}
|
||||
|
||||
# Pin validators keys are the functions in pin which validate the pins
|
||||
pin_validators = {}
|
||||
|
||||
|
||||
def add_pin_validators():
|
||||
for m_attr_name in dir(pins):
|
||||
if "gpio" in m_attr_name:
|
||||
s = pin_validators[repr(getattr(pins, m_attr_name))] = {}
|
||||
if "schema" in m_attr_name:
|
||||
s["schema"] = True # else is just number
|
||||
if "internal" in m_attr_name:
|
||||
s["internal"] = True
|
||||
if "input" in m_attr_name:
|
||||
s["modes"] = ["input"]
|
||||
elif "output" in m_attr_name:
|
||||
s["modes"] = ["output"]
|
||||
else:
|
||||
s["modes"] = []
|
||||
if "pullup" in m_attr_name:
|
||||
s["modes"].append("pullup")
|
||||
from esphome.components.adc import sensor as adc_sensor
|
||||
|
||||
pin_validators[repr(adc_sensor.validate_adc_pin)] = {
|
||||
"internal": True,
|
||||
"modes": ["input"],
|
||||
}
|
||||
|
||||
|
||||
def add_module_registries(domain, module):
|
||||
for attr_name in dir(module):
|
||||
attr_obj = getattr(module, attr_name)
|
||||
if isinstance(attr_obj, Registry):
|
||||
if attr_obj == automation.ACTION_REGISTRY:
|
||||
reg_type = "action"
|
||||
reg_domain = "core"
|
||||
found_registries[repr(attr_obj)] = reg_type
|
||||
elif attr_obj == automation.CONDITION_REGISTRY:
|
||||
reg_type = "condition"
|
||||
reg_domain = "core"
|
||||
found_registries[repr(attr_obj)] = reg_type
|
||||
else: # attr_name == "FILTER_REGISTRY":
|
||||
reg_domain = domain
|
||||
reg_type = attr_name.partition("_")[0].lower()
|
||||
found_registries[repr(attr_obj)] = f"{domain}.{reg_type}"
|
||||
|
||||
for name in attr_obj.keys():
|
||||
if "." not in name:
|
||||
reg_entry_name = name
|
||||
else:
|
||||
parts = name.split(".")
|
||||
if len(parts) == 2:
|
||||
reg_domain = parts[0]
|
||||
reg_entry_name = parts[1]
|
||||
else:
|
||||
reg_domain = ".".join([parts[1], parts[0]])
|
||||
reg_entry_name = parts[2]
|
||||
|
||||
if reg_domain not in output:
|
||||
output[reg_domain] = {}
|
||||
if reg_type not in output[reg_domain]:
|
||||
output[reg_domain][reg_type] = {}
|
||||
output[reg_domain][reg_type][reg_entry_name] = convert_config(
|
||||
attr_obj[name].schema, f"{reg_domain}/{reg_type}/{reg_entry_name}"
|
||||
)
|
||||
|
||||
# print(f"{domain} - {attr_name} - {name}")
|
||||
|
||||
|
||||
def do_pins():
|
||||
# do pin registries
|
||||
pins_providers = schema_core["pins"] = []
|
||||
for pin_registry in pins.PIN_SCHEMA_REGISTRY:
|
||||
s = convert_config(
|
||||
pins.PIN_SCHEMA_REGISTRY[pin_registry][1], f"pins/{pin_registry}"
|
||||
)
|
||||
if pin_registry not in output:
|
||||
output[pin_registry] = {} # mcp23xxx does not create a component yet
|
||||
output[pin_registry]["pin"] = s
|
||||
pins_providers.append(pin_registry)
|
||||
|
||||
|
||||
def do_esp32():
|
||||
import esphome.components.esp32.boards as esp32_boards
|
||||
|
||||
setEnum(
|
||||
output["esp32"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"],
|
||||
list(esp32_boards.BOARD_TO_VARIANT.keys()),
|
||||
)
|
||||
|
||||
|
||||
def do_esp8266():
|
||||
import esphome.components.esp8266.boards as esp8266_boards
|
||||
|
||||
setEnum(
|
||||
output["esp8266"]["schemas"]["CONFIG_SCHEMA"]["schema"]["config_vars"]["board"],
|
||||
list(esp8266_boards.ESP8266_BOARD_PINS.keys()),
|
||||
)
|
||||
|
||||
|
||||
def fix_remote_receiver():
|
||||
output["remote_receiver.binary_sensor"]["schemas"]["CONFIG_SCHEMA"] = {
|
||||
"type": "schema",
|
||||
"schema": {
|
||||
"extends": ["binary_sensor.BINARY_SENSOR_SCHEMA", "core.COMPONENT_SCHEMA"],
|
||||
"config_vars": output["remote_base"]["binary"],
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def add_referenced_recursive(referenced_schemas, config_var, path, eat_schema=False):
|
||||
assert (
|
||||
S_CONFIG_VARS not in config_var and S_EXTENDS not in config_var
|
||||
) # S_TYPE in cv or "key" in cv or len(cv) == 0
|
||||
if (
|
||||
config_var.get(S_TYPE) in ["schema", "trigger", "maybe"]
|
||||
and S_SCHEMA in config_var
|
||||
):
|
||||
schema = config_var[S_SCHEMA]
|
||||
for k, v in schema.get(S_CONFIG_VARS, {}).items():
|
||||
if eat_schema:
|
||||
new_path = path + [S_CONFIG_VARS, k]
|
||||
else:
|
||||
new_path = path + ["schema", S_CONFIG_VARS, k]
|
||||
add_referenced_recursive(referenced_schemas, v, new_path)
|
||||
for k in schema.get(S_EXTENDS, []):
|
||||
if k not in referenced_schemas:
|
||||
referenced_schemas[k] = [path]
|
||||
else:
|
||||
if path not in referenced_schemas[k]:
|
||||
referenced_schemas[k].append(path)
|
||||
|
||||
s1 = get_str_path_schema(k)
|
||||
p = k.split(".")
|
||||
if len(p) == 3 and path[0] == f"{p[0]}.{p[1]}":
|
||||
# special case for schema inside platforms
|
||||
add_referenced_recursive(
|
||||
referenced_schemas, s1, [path[0], "schemas", p[2]]
|
||||
)
|
||||
else:
|
||||
add_referenced_recursive(
|
||||
referenced_schemas, s1, [p[0], "schemas", p[1]]
|
||||
)
|
||||
elif config_var.get(S_TYPE) == "typed":
|
||||
for tk, tv in config_var.get("types").items():
|
||||
add_referenced_recursive(
|
||||
referenced_schemas,
|
||||
{
|
||||
S_TYPE: S_SCHEMA,
|
||||
S_SCHEMA: tv,
|
||||
},
|
||||
path + ["types", tk],
|
||||
eat_schema=True,
|
||||
)
|
||||
|
||||
|
||||
def get_str_path_schema(strPath):
|
||||
parts = strPath.split(".")
|
||||
if len(parts) > 2:
|
||||
parts[0] += "." + parts[1]
|
||||
parts[1] = parts[2]
|
||||
s1 = output.get(parts[0], {}).get(S_SCHEMAS, {}).get(parts[1], {})
|
||||
return s1
|
||||
|
||||
|
||||
def pop_str_path_schema(strPath):
|
||||
parts = strPath.split(".")
|
||||
if len(parts) > 2:
|
||||
parts[0] += "." + parts[1]
|
||||
parts[1] = parts[2]
|
||||
output.get(parts[0], {}).get(S_SCHEMAS, {}).pop(parts[1])
|
||||
|
||||
|
||||
def get_arr_path_schema(path):
|
||||
s = output
|
||||
for x in path:
|
||||
s = s[x]
|
||||
return s
|
||||
|
||||
|
||||
def merge(source, destination):
|
||||
"""
|
||||
run me with nosetests --with-doctest file.py
|
||||
|
||||
>>> a = { 'first' : { 'all_rows' : { 'pass' : 'dog', 'number' : '1' } } }
|
||||
>>> b = { 'first' : { 'all_rows' : { 'fail' : 'cat', 'number' : '5' } } }
|
||||
>>> merge(b, a) == { 'first' : { 'all_rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } }
|
||||
True
|
||||
"""
|
||||
for key, value in source.items():
|
||||
if isinstance(value, dict):
|
||||
# get node or create one
|
||||
node = destination.setdefault(key, {})
|
||||
merge(value, node)
|
||||
else:
|
||||
destination[key] = value
|
||||
|
||||
return destination
|
||||
|
||||
|
||||
def shrink():
|
||||
"""Shrink the extending schemas which has just an end type, e.g. at this point
|
||||
ota / port is type schema with extended pointing to core.port, this should instead be
|
||||
type number. core.port is number
|
||||
|
||||
This also fixes enums, as they are another schema and they are instead put in the same cv
|
||||
"""
|
||||
|
||||
# referenced_schemas contains a dict, keys are all that are shown in extends: [] arrays, values are lists of paths that are pointing to that extend
|
||||
# e.g. key: core.COMPONENT_SCHEMA has a lot of paths of config vars which extends this schema
|
||||
|
||||
pass_again = True
|
||||
|
||||
while pass_again:
|
||||
pass_again = False
|
||||
|
||||
referenced_schemas = {}
|
||||
|
||||
for k, v in output.items():
|
||||
for kv, vv in v.items():
|
||||
if kv != "pin" and isinstance(vv, dict):
|
||||
for kvv, vvv in vv.items():
|
||||
add_referenced_recursive(referenced_schemas, vvv, [k, kv, kvv])
|
||||
|
||||
for x, paths in referenced_schemas.items():
|
||||
if len(paths) == 1:
|
||||
key_s = get_str_path_schema(x)
|
||||
arr_s = get_arr_path_schema(paths[0])
|
||||
# key_s |= arr_s
|
||||
# key_s.pop(S_EXTENDS)
|
||||
pass_again = True
|
||||
if S_SCHEMA in arr_s:
|
||||
if S_EXTENDS in arr_s[S_SCHEMA]:
|
||||
arr_s[S_SCHEMA].pop(S_EXTENDS)
|
||||
else:
|
||||
print("expected extends here!" + x)
|
||||
arr_s = merge(key_s, arr_s)
|
||||
if arr_s[S_TYPE] == "enum":
|
||||
arr_s.pop(S_SCHEMA)
|
||||
else:
|
||||
arr_s.pop(S_EXTENDS)
|
||||
arr_s |= key_s[S_SCHEMA]
|
||||
print(x)
|
||||
|
||||
# simple types should be spread on each component,
|
||||
# for enums so far these are logger.is_log_level, cover.validate_cover_state and pulse_counter.sensor.COUNT_MODE_SCHEMA
|
||||
# then for some reasons sensor filter registry falls here
|
||||
# then are all simple types, integer and strings
|
||||
for x, paths in referenced_schemas.items():
|
||||
key_s = get_str_path_schema(x)
|
||||
if key_s and key_s[S_TYPE] in ["enum", "registry", "integer", "string"]:
|
||||
if key_s[S_TYPE] == "registry":
|
||||
print("Spreading registry: " + x)
|
||||
for target in paths:
|
||||
target_s = get_arr_path_schema(target)
|
||||
assert target_s[S_SCHEMA][S_EXTENDS] == [x]
|
||||
target_s.pop(S_SCHEMA)
|
||||
target_s |= key_s
|
||||
if key_s[S_TYPE] in ["integer", "string"]:
|
||||
target_s["data_type"] = x.split(".")[1]
|
||||
# remove this dangling again
|
||||
pop_str_path_schema(x)
|
||||
elif not key_s:
|
||||
for target in paths:
|
||||
target_s = get_arr_path_schema(target)
|
||||
assert target_s[S_SCHEMA][S_EXTENDS] == [x]
|
||||
target_s.pop(S_SCHEMA)
|
||||
target_s.pop(S_TYPE) # undefined
|
||||
target_s["data_type"] = x.split(".")[1]
|
||||
# remove this dangling again
|
||||
pop_str_path_schema(x)
|
||||
|
||||
# remove dangling items (unreachable schemas)
|
||||
for domain, domain_schemas in output.items():
|
||||
for schema_name in list(domain_schemas.get(S_SCHEMAS, {}).keys()):
|
||||
s = f"{domain}.{schema_name}"
|
||||
if (
|
||||
not s.endswith("." + S_CONFIG_SCHEMA)
|
||||
and s not in referenced_schemas.keys()
|
||||
):
|
||||
print(f"Removing {s}")
|
||||
output[domain][S_SCHEMAS].pop(schema_name)
|
||||
|
||||
|
||||
def build_schema():
|
||||
print("Building schema")
|
||||
|
||||
# check esphome was not loaded globally (IDE auto imports)
|
||||
if len(ejs.extended_schemas) == 0:
|
||||
raise Exception(
|
||||
"no data collected. Did you globally import an ESPHome component?"
|
||||
)
|
||||
|
||||
# Core schema
|
||||
schema_core[S_SCHEMAS] = {}
|
||||
register_module_schemas("core", cv)
|
||||
|
||||
platforms = {}
|
||||
schema_core[S_PLATFORMS] = platforms
|
||||
core_components = {}
|
||||
schema_core[S_COMPONENTS] = core_components
|
||||
|
||||
add_pin_validators()
|
||||
|
||||
# Load a preview of each component
|
||||
for domain, manifest in components.items():
|
||||
if manifest.is_platform_component:
|
||||
# e.g. sensor, binary sensor, add S_COMPONENTS
|
||||
# note: S_COMPONENTS is not filled until loaded, e.g.
|
||||
# if lock: is not used, then we don't need to know about their
|
||||
# platforms yet.
|
||||
output[domain] = {S_COMPONENTS: {}, S_SCHEMAS: {}}
|
||||
platforms[domain] = {}
|
||||
elif manifest.config_schema is not None:
|
||||
# e.g. dallas
|
||||
output[domain] = {S_SCHEMAS: {S_CONFIG_SCHEMA: {}}}
|
||||
|
||||
# Generate platforms (e.g. sensor, binary_sensor, climate )
|
||||
for domain in platforms:
|
||||
c = components[domain]
|
||||
register_module_schemas(domain, c.module)
|
||||
|
||||
# Generate components
|
||||
for domain, manifest in components.items():
|
||||
if domain not in platforms:
|
||||
if manifest.config_schema is not None:
|
||||
core_components[domain] = {}
|
||||
register_module_schemas(domain, manifest.module, manifest)
|
||||
|
||||
for platform in platforms:
|
||||
platform_manifest = get_platform(domain=platform, platform=domain)
|
||||
if platform_manifest is not None:
|
||||
output[platform][S_COMPONENTS][domain] = {}
|
||||
register_module_schemas(
|
||||
f"{domain}.{platform}", platform_manifest.module
|
||||
)
|
||||
|
||||
# Do registries
|
||||
add_module_registries("core", automation)
|
||||
for domain, manifest in components.items():
|
||||
add_module_registries(domain, manifest.module)
|
||||
add_module_registries("remote_base", remote_base)
|
||||
|
||||
# update props pointing to registries
|
||||
for reg_config_var in solve_registry:
|
||||
(registry, config_var) = reg_config_var
|
||||
config_var[S_TYPE] = "registry"
|
||||
config_var["registry"] = found_registries[repr(registry)]
|
||||
|
||||
do_pins()
|
||||
do_esp8266()
|
||||
do_esp32()
|
||||
fix_remote_receiver()
|
||||
shrink()
|
||||
|
||||
# aggregate components, so all component info is in same file, otherwise we have dallas.json, dallas.sensor.json, etc.
|
||||
data = {}
|
||||
for component, component_schemas in output.items():
|
||||
if "." in component:
|
||||
key = component.partition(".")[0]
|
||||
if key not in data:
|
||||
data[key] = {}
|
||||
data[key][component] = component_schemas
|
||||
else:
|
||||
if component not in data:
|
||||
data[component] = {}
|
||||
data[component] |= {component: component_schemas}
|
||||
|
||||
# bundle core inside esphome
|
||||
data["esphome"]["core"] = data.pop("core")["core"]
|
||||
|
||||
for c, s in data.items():
|
||||
write_file(c, s)
|
||||
|
||||
|
||||
def setEnum(obj, items):
|
||||
obj[S_TYPE] = "enum"
|
||||
obj["values"] = items
|
||||
|
||||
|
||||
def isConvertibleSchema(schema):
|
||||
if schema is None:
|
||||
return False
|
||||
if isinstance(schema, (cv.Schema, cv.All)):
|
||||
return True
|
||||
if repr(schema) in ejs.hidden_schemas:
|
||||
return True
|
||||
if repr(schema) in ejs.typed_schemas:
|
||||
return True
|
||||
if repr(schema) in ejs.list_schemas:
|
||||
return True
|
||||
if repr(schema) in ejs.registry_schemas:
|
||||
return True
|
||||
if isinstance(schema, dict):
|
||||
for k in schema.keys():
|
||||
if isinstance(k, (cv.Required, cv.Optional)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def convert_config(schema, path):
|
||||
converted = {}
|
||||
convert_1(schema, converted, path)
|
||||
return converted
|
||||
|
||||
|
||||
def convert_1(schema, config_var, path):
|
||||
"""config_var can be a config_var or a schema: both are dicts
|
||||
config_var has a S_TYPE property, if this is S_SCHEMA, then it has a S_SCHEMA property
|
||||
schema does not have a type property, schema can have optionally both S_CONFIG_VARS and S_EXTENDS
|
||||
"""
|
||||
repr_schema = repr(schema)
|
||||
|
||||
if repr_schema in known_schemas:
|
||||
schema_info = known_schemas[(repr_schema)]
|
||||
for (schema_instance, name) in schema_info:
|
||||
if schema_instance is schema:
|
||||
assert S_CONFIG_VARS not in config_var
|
||||
assert S_EXTENDS not in config_var
|
||||
if not S_TYPE in config_var:
|
||||
config_var[S_TYPE] = S_SCHEMA
|
||||
assert config_var[S_TYPE] == S_SCHEMA
|
||||
|
||||
if S_SCHEMA not in config_var:
|
||||
config_var[S_SCHEMA] = {}
|
||||
if S_EXTENDS not in config_var[S_SCHEMA]:
|
||||
config_var[S_SCHEMA][S_EXTENDS] = [name]
|
||||
else:
|
||||
config_var[S_SCHEMA][S_EXTENDS].append(name)
|
||||
return
|
||||
|
||||
# Extended schemas are tracked when the .extend() is used in a schema
|
||||
if repr_schema in ejs.extended_schemas:
|
||||
extended = ejs.extended_schemas.get(repr_schema)
|
||||
# The midea actions are extending an empty schema (resulted in the templatize not templatizing anything)
|
||||
# this causes a recursion in that this extended looks the same in extended schema as the extended[1]
|
||||
if repr_schema == repr(extended[1]):
|
||||
assert path.startswith("midea_ac/")
|
||||
return
|
||||
|
||||
assert len(extended) == 2
|
||||
convert_1(extended[0], config_var, path + "/extL")
|
||||
convert_1(extended[1], config_var, path + "/extR")
|
||||
return
|
||||
|
||||
if isinstance(schema, cv.All):
|
||||
i = 0
|
||||
for inner in schema.validators:
|
||||
i = i + 1
|
||||
convert_1(inner, config_var, path + f"/val {i}")
|
||||
return
|
||||
|
||||
if hasattr(schema, "validators"):
|
||||
i = 0
|
||||
for inner in schema.validators:
|
||||
i = i + 1
|
||||
convert_1(inner, config_var, path + f"/val {i}")
|
||||
|
||||
if isinstance(schema, cv.Schema):
|
||||
convert_1(schema.schema, config_var, path + "/all")
|
||||
return
|
||||
|
||||
if isinstance(schema, dict):
|
||||
convert_keys(config_var, schema, path)
|
||||
return
|
||||
|
||||
if repr_schema in ejs.list_schemas:
|
||||
config_var["is_list"] = True
|
||||
items_schema = ejs.list_schemas[repr_schema][0]
|
||||
convert_1(items_schema, config_var, path + "/list")
|
||||
return
|
||||
|
||||
if DUMP_RAW:
|
||||
config_var["raw"] = repr_schema
|
||||
|
||||
# pylint: disable=comparison-with-callable
|
||||
if schema == cv.boolean:
|
||||
config_var[S_TYPE] = "boolean"
|
||||
elif schema == automation.validate_potentially_and_condition:
|
||||
config_var[S_TYPE] = "registry"
|
||||
config_var["registry"] = "condition"
|
||||
elif schema == cv.int_ or schema == cv.int_range:
|
||||
config_var[S_TYPE] = "integer"
|
||||
elif schema == cv.string or schema == cv.string_strict or schema == cv.valid_name:
|
||||
config_var[S_TYPE] = "string"
|
||||
|
||||
elif isinstance(schema, vol.Schema):
|
||||
# test: esphome/project
|
||||
config_var[S_TYPE] = "schema"
|
||||
config_var["schema"] = convert_config(schema.schema, path + "/s")["schema"]
|
||||
|
||||
elif repr_schema in pin_validators:
|
||||
config_var |= pin_validators[repr_schema]
|
||||
config_var[S_TYPE] = "pin"
|
||||
|
||||
elif repr_schema in ejs.hidden_schemas:
|
||||
schema_type = ejs.hidden_schemas[repr_schema]
|
||||
|
||||
data = schema(ejs.jschema_extractor)
|
||||
|
||||
# enums, e.g. esp32/variant
|
||||
if schema_type == "one_of":
|
||||
config_var[S_TYPE] = "enum"
|
||||
config_var["values"] = list(data)
|
||||
elif schema_type == "enum":
|
||||
config_var[S_TYPE] = "enum"
|
||||
config_var["values"] = list(data.keys())
|
||||
elif schema_type == "maybe":
|
||||
config_var[S_TYPE] = "maybe"
|
||||
config_var["schema"] = convert_config(data, path + "/maybe")["schema"]
|
||||
# esphome/on_boot
|
||||
elif schema_type == "automation":
|
||||
extra_schema = None
|
||||
config_var[S_TYPE] = "trigger"
|
||||
if automation.AUTOMATION_SCHEMA == ejs.extended_schemas[repr(data)][0]:
|
||||
extra_schema = ejs.extended_schemas[repr(data)][1]
|
||||
if (
|
||||
extra_schema is not None and len(extra_schema) > 1
|
||||
): # usually only trigger_id here
|
||||
config = convert_config(extra_schema, path + "/extra")
|
||||
if "schema" in config:
|
||||
automation_schema = config["schema"]
|
||||
if not (
|
||||
len(automation_schema["config_vars"]) == 1
|
||||
and "trigger_id" in automation_schema["config_vars"]
|
||||
):
|
||||
automation_schema["config_vars"]["then"] = {S_TYPE: "trigger"}
|
||||
if "trigger_id" in automation_schema["config_vars"]:
|
||||
automation_schema["config_vars"].pop("trigger_id")
|
||||
|
||||
config_var[S_TYPE] = "trigger"
|
||||
config_var["schema"] = automation_schema
|
||||
# some triggers can have a list of actions directly, while others needs to have some other configuration,
|
||||
# e.g. sensor.on_value_rang, and the list of actions is only accepted under "then" property.
|
||||
try:
|
||||
schema({"delay": "1s"})
|
||||
except cv.Invalid:
|
||||
config_var["has_required_var"] = True
|
||||
else:
|
||||
print("figure out " + path)
|
||||
elif schema_type == "effects":
|
||||
config_var[S_TYPE] = "registry"
|
||||
config_var["registry"] = "light.effects"
|
||||
config_var["filter"] = data[0]
|
||||
elif schema_type == "templatable":
|
||||
config_var["templatable"] = True
|
||||
convert_1(data, config_var, path + "/templat")
|
||||
elif schema_type == "triggers":
|
||||
# remote base
|
||||
convert_1(data, config_var, path + "/trigger")
|
||||
elif schema_type == "sensor":
|
||||
schema = data
|
||||
convert_1(data, config_var, path + "/trigger")
|
||||
else:
|
||||
raise Exception("Unknown extracted schema type")
|
||||
|
||||
elif repr_schema in ejs.registry_schemas:
|
||||
solve_registry.append((ejs.registry_schemas[repr_schema], config_var))
|
||||
|
||||
elif repr_schema in ejs.typed_schemas:
|
||||
config_var[S_TYPE] = "typed"
|
||||
types = config_var["types"] = {}
|
||||
typed_schema = ejs.typed_schemas[repr_schema]
|
||||
if len(typed_schema) > 1:
|
||||
config_var["typed_key"] = typed_schema[1].get("key", CONF_TYPE)
|
||||
for schema_key, schema_type in typed_schema[0][0].items():
|
||||
config = convert_config(schema_type, path + "/type_" + schema_key)
|
||||
types[schema_key] = config["schema"]
|
||||
|
||||
elif DUMP_UNKNOWN:
|
||||
if S_TYPE not in config_var:
|
||||
config_var["unknown"] = repr_schema
|
||||
|
||||
if DUMP_PATH:
|
||||
config_var["path"] = path
|
||||
|
||||
|
||||
def get_overridden_config(key, converted):
|
||||
# check if the key is in any extended schema in this converted schema, i.e.
|
||||
# if we see a on_value_range in a dallas sensor, then this is overridden because
|
||||
# it is already defined in sensor
|
||||
assert S_CONFIG_VARS not in converted and S_EXTENDS not in converted
|
||||
config = converted.get(S_SCHEMA, {})
|
||||
|
||||
return get_overridden_key_inner(key, config, {})
|
||||
|
||||
|
||||
def get_overridden_key_inner(key, config, ret):
|
||||
if S_EXTENDS not in config:
|
||||
return ret
|
||||
for s in config[S_EXTENDS]:
|
||||
p = s.partition(".")
|
||||
s1 = output.get(p[0], {}).get(S_SCHEMAS, {}).get(p[2], {}).get(S_SCHEMA)
|
||||
if s1:
|
||||
if key in s1.get(S_CONFIG_VARS, {}):
|
||||
for k, v in s1.get(S_CONFIG_VARS)[key].items():
|
||||
if k not in ret: # keep most overridden
|
||||
ret[k] = v
|
||||
get_overridden_key_inner(key, s1, ret)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def convert_keys(converted, schema, path):
|
||||
for k, v in schema.items():
|
||||
# deprecated stuff
|
||||
if repr(v).startswith("<function invalid"):
|
||||
continue
|
||||
|
||||
result = {}
|
||||
|
||||
if isinstance(k, cv.GenerateID):
|
||||
result["key"] = "GeneratedID"
|
||||
elif isinstance(k, cv.Required):
|
||||
result["key"] = "Required"
|
||||
elif (
|
||||
isinstance(k, cv.Optional)
|
||||
or isinstance(k, cv.Inclusive)
|
||||
or isinstance(k, cv.Exclusive)
|
||||
):
|
||||
result["key"] = "Optional"
|
||||
else:
|
||||
converted["key"] = "String"
|
||||
converted["key_dump"] = str(k)
|
||||
|
||||
esphome_core.CORE.data = {
|
||||
esphome_core.KEY_CORE: {esphome_core.KEY_TARGET_PLATFORM: "esp8266"}
|
||||
}
|
||||
if hasattr(k, "default") and str(k.default) != "...":
|
||||
default_value = k.default()
|
||||
if default_value is not None:
|
||||
result["default"] = str(default_value)
|
||||
|
||||
# Do value
|
||||
convert_1(v, result, path + f"/{str(k)}")
|
||||
if "schema" not in converted:
|
||||
converted[S_TYPE] = "schema"
|
||||
converted["schema"] = {S_CONFIG_VARS: {}}
|
||||
if S_CONFIG_VARS not in converted["schema"]:
|
||||
converted["schema"][S_CONFIG_VARS] = {}
|
||||
for base_k, base_v in get_overridden_config(k, converted).items():
|
||||
if base_k in result and base_v == result[base_k]:
|
||||
result.pop(base_k)
|
||||
converted["schema"][S_CONFIG_VARS][str(k)] = result
|
||||
|
||||
|
||||
build_schema()
|
||||
@@ -247,9 +247,7 @@ logger:
|
||||
|
||||
web_server:
|
||||
port: 8080
|
||||
ota: true
|
||||
css_url: https://esphome.io/_static/webserver-v1.min.css
|
||||
js_url: https://esphome.io/_static/webserver-v1.min.js
|
||||
version: 2
|
||||
|
||||
power_supply:
|
||||
id: "atx_power_supply"
|
||||
@@ -800,6 +798,17 @@ sensor:
|
||||
value: 12345
|
||||
total:
|
||||
name: "Pulse Meter Total"
|
||||
- platform: qmp6988
|
||||
temperature:
|
||||
name: "Living Temperature QMP"
|
||||
oversampling: 32x
|
||||
pressure:
|
||||
name: "Living Pressure QMP"
|
||||
oversampling: 2x
|
||||
address: 0x70
|
||||
update_interval: 30s
|
||||
iir_filter: 16x
|
||||
i2c_id: i2c_bus
|
||||
- platform: rotary_encoder
|
||||
name: "Rotary Encoder"
|
||||
id: rotary_encoder1
|
||||
@@ -2209,6 +2218,17 @@ display:
|
||||
- platform: lcd_pcf8574
|
||||
dimensions: 18x4
|
||||
address: 0x3F
|
||||
user_characters:
|
||||
- position: 0
|
||||
data:
|
||||
- 0b00000
|
||||
- 0b01010
|
||||
- 0b00000
|
||||
- 0b00100
|
||||
- 0b00100
|
||||
- 0b10001
|
||||
- 0b01110
|
||||
- 0b00000
|
||||
lambda: |-
|
||||
it.print("Hello World!");
|
||||
i2c_id: i2c_bus
|
||||
@@ -2596,6 +2616,16 @@ text_sensor:
|
||||
canbus_id: esp32_internal_can
|
||||
can_id: 23
|
||||
data: [0x10, 0x20, 0x30]
|
||||
- canbus.send:
|
||||
canbus_id: mcp2515_can
|
||||
can_id: 24
|
||||
remote_transmission_request: true
|
||||
data: []
|
||||
- canbus.send:
|
||||
canbus_id: esp32_internal_can
|
||||
can_id: 24
|
||||
remote_transmission_request: true
|
||||
data: []
|
||||
- platform: template
|
||||
name: Template Text Sensor
|
||||
id: ${textname}_text
|
||||
|
||||
@@ -263,6 +263,10 @@ sensor:
|
||||
name: 'Inkbird IBS-TH1 Humidity'
|
||||
battery_level:
|
||||
name: 'Inkbird IBS-TH1 Battery Level'
|
||||
- platform: xiaomi_rtcgq02lm
|
||||
id: motion_rtcgq02lm
|
||||
battery_level:
|
||||
name: 'Mi Motion Sensor 2 Battery level'
|
||||
- platform: ltr390
|
||||
uv:
|
||||
name: "LTR390 UV"
|
||||
@@ -417,6 +421,14 @@ binary_sensor:
|
||||
name: 'CGPR1 Idle Time'
|
||||
illuminance:
|
||||
name: 'CGPR1 Illuminance'
|
||||
- platform: xiaomi_rtcgq02lm
|
||||
id: motion_rtcgq02lm
|
||||
motion:
|
||||
name: 'Mi Motion Sensor 2'
|
||||
light:
|
||||
name: 'Mi Motion Sensor 2 Light'
|
||||
button:
|
||||
name: 'Mi Motion Sensor 2 Button'
|
||||
|
||||
esp32_ble_tracker:
|
||||
on_ble_advertise:
|
||||
@@ -457,6 +469,11 @@ xiaomi_ble:
|
||||
|
||||
mopeka_ble:
|
||||
|
||||
xiaomi_rtcgq02lm:
|
||||
- id: motion_rtcgq02lm
|
||||
mac_address: 01:02:03:04:05:06
|
||||
bindkey: '48403ebe2d385db8d0c187f81e62cb64'
|
||||
|
||||
#esp32_ble_beacon:
|
||||
# type: iBeacon
|
||||
# uuid: 'c29ce823-e67a-4e71-bff2-abaa32e77a98'
|
||||
|
||||
@@ -349,6 +349,24 @@ sensor:
|
||||
name: 'Temperature'
|
||||
humidity:
|
||||
name: 'Humidity'
|
||||
- platform: hydreon_rgxx
|
||||
model: "RG 9"
|
||||
uart_id: uart6
|
||||
id: "hydreon_rg9"
|
||||
moisture:
|
||||
name: "hydreon_rain"
|
||||
id: hydreon_rain
|
||||
- platform: hydreon_rgxx
|
||||
model: "RG_15"
|
||||
uart_id: uart6
|
||||
acc:
|
||||
name: "hydreon_acc"
|
||||
event_acc:
|
||||
name: "hydreon_event_acc"
|
||||
total_acc:
|
||||
name: "hydreon_total_acc"
|
||||
r_int:
|
||||
name: "hydreon_r_int"
|
||||
- platform: adc
|
||||
pin: VCC
|
||||
id: my_sensor
|
||||
@@ -795,6 +813,11 @@ binary_sensor:
|
||||
on_press:
|
||||
then:
|
||||
- cover.toggle: time_based_cover
|
||||
- cover.toggle: endstop_cover
|
||||
- platform: hydreon_rgxx
|
||||
hydreon_rgxx_id: "hydreon_rg9"
|
||||
too_cold:
|
||||
name: "rg9_toocold"
|
||||
- platform: template
|
||||
id: 'pzemac_reset_energy'
|
||||
on_press:
|
||||
@@ -1060,6 +1083,7 @@ climate:
|
||||
cover:
|
||||
- platform: endstop
|
||||
name: Endstop Cover
|
||||
id: endstop_cover
|
||||
stop_action:
|
||||
- switch.turn_on: gpio_switch1
|
||||
open_endstop: my_binary_sensor
|
||||
@@ -1205,6 +1229,12 @@ light:
|
||||
name: Icicle Lights
|
||||
pin_a: out
|
||||
pin_b: out2
|
||||
- platform: sonoff_d1
|
||||
uart_id: uart2
|
||||
use_rm433_remote: False
|
||||
name: Sonoff D1 Dimmer
|
||||
id: d1_light
|
||||
restore_mode: RESTORE_DEFAULT_OFF
|
||||
|
||||
servo:
|
||||
id: my_servo
|
||||
|
||||
@@ -49,6 +49,19 @@ modbus_controller:
|
||||
address: 0x2
|
||||
modbus_id: mod_bus1
|
||||
|
||||
mqtt:
|
||||
broker: test.mosquitto.org
|
||||
port: 1883
|
||||
discovery: true
|
||||
discovery_prefix: homeassistant
|
||||
idf_send_async: false
|
||||
on_message:
|
||||
topic: testing/sensor/testing_sensor/state
|
||||
qos: 0
|
||||
then:
|
||||
- lambda: |-
|
||||
ESP_LOGD("Mqtt Test","testing/sensor/testing_sensor/state=[%s]",x.c_str());
|
||||
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin: GPIO0
|
||||
|
||||
Reference in New Issue
Block a user