Merge branch 'dev' into jesserockz-2024-087

This commit is contained in:
Jesse Hills 2024-04-24 14:39:57 +12:00
commit da154c24a8
No known key found for this signature in database
GPG Key ID: BEAAE804EFD8E83A
1291 changed files with 30855 additions and 238 deletions

View File

@ -20,7 +20,6 @@ permissions:
env:
DEFAULT_PYTHON: "3.9"
PYUPGRADE_TARGET: "--py39-plus"
CLANG_FORMAT_VERSION: "13.0.1"
concurrency:
# yamllint disable-line rule:line-length
@ -239,7 +238,7 @@ jobs:
- name: Install clang-format
run: |
. venv/bin/activate
pip install clang-format==${{ env.CLANG_FORMAT_VERSION }}
pip install clang-format -c requirements_dev.txt
- name: Run clang-format
run: |
. venv/bin/activate
@ -399,7 +398,8 @@ jobs:
- common
if: github.event_name == 'pull_request'
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
components: ${{ steps.list-components.outputs.components }}
count: ${{ steps.list-components.outputs.count }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.1
@ -420,10 +420,18 @@ jobs:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Find changed components
id: set-matrix
id: list-components
run: |
. venv/bin/activate
echo "matrix=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }} | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
components=$(script/list-components.py --changed --branch ${{ steps.target-branch.outputs.branch }})
output_components=$(echo "$components" | jq -R -s -c 'split("\n")[:-1] | map(select(length > 0))')
count=$(echo "$output_components" | jq length)
echo "components=$output_components" >> $GITHUB_OUTPUT
echo "count=$count" >> $GITHUB_OUTPUT
echo "$count Components:"
echo "$output_components" | jq
test-build-components:
name: Component test ${{ matrix.file }}
@ -431,12 +439,12 @@ jobs:
needs:
- common
- list-components
if: ${{ github.event_name == 'pull_request' && needs.list-components.outputs.matrix != '[]' && needs.list-components.outputs.matrix != '' }}
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) > 0 && fromJSON(needs.list-components.outputs.count) < 100
strategy:
fail-fast: false
max-parallel: 2
matrix:
file: ${{ fromJson(needs.list-components.outputs.matrix) }}
file: ${{ fromJson(needs.list-components.outputs.components) }}
steps:
- name: Install libsodium
run: sudo apt-get install libsodium-dev
@ -457,6 +465,64 @@ jobs:
. venv/bin/activate
./script/test_build_components -e compile -c ${{ matrix.file }}
test-build-components-splitter:
name: Split components for testing into 20 groups maximum
runs-on: ubuntu-latest
needs:
- common
- list-components
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100
outputs:
matrix: ${{ steps.split.outputs.components }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.1
- name: Split components into 20 groups
id: split
run: |
components=$(echo '${{ needs.list-components.outputs.components }}' | jq -c '.[]' | shuf | jq -s -c '[_nwise(20) | join(" ")]')
echo "components=$components" >> $GITHUB_OUTPUT
test-build-components-split:
name: Test split components
runs-on: ubuntu-latest
needs:
- common
- list-components
- test-build-components-splitter
if: github.event_name == 'pull_request' && fromJSON(needs.list-components.outputs.count) >= 100
strategy:
fail-fast: false
max-parallel: 4
matrix:
components: ${{ fromJson(needs.test-build-components-splitter.outputs.matrix) }}
steps:
- name: List components
run: echo ${{ matrix.components }}
- name: Install libsodium
run: sudo apt-get install libsodium-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.1
- name: Restore Python
uses: ./.github/actions/restore-python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
cache-key: ${{ needs.common.outputs.cache-key }}
- name: Validate config
run: |
. venv/bin/activate
for component in ${{ matrix.components }}; do
./script/test_build_components -e config -c $component
done
- name: Compile config
run: |
. venv/bin/activate
for component in ${{ matrix.components }}; do
./script/test_build_components -e compile -c $component
done
ci-status:
name: CI Status
runs-on: ubuntu-latest
@ -471,8 +537,10 @@ jobs:
- pyupgrade
- compile-tests
- clang-tidy
- test-build-components
- list-components
- test-build-components
- test-build-components-splitter
- test-build-components-split
if: always()
steps:
- name: Success

View File

@ -119,6 +119,7 @@ esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core
esphome/components/ethernet_info/* @gtjadsonsantos
esphome/components/event/* @nohat
esphome/components/exposure_notifications/* @OttoWinter
esphome/components/ezo/* @ssieb
esphome/components/ezo_pmp/* @carlos-sarmiento
@ -359,6 +360,7 @@ esphome/components/tee501/* @Stock-M
esphome/components/teleinfo/* @0hax
esphome/components/template/alarm_control_panel/* @grahambrown11 @hwstar
esphome/components/template/datetime/* @rfdarter
esphome/components/template/event/* @nohat
esphome/components/template/fan/* @ssieb
esphome/components/text/* @mauritskorse
esphome/components/thermostat/* @kbx81
@ -390,6 +392,7 @@ esphome/components/ufire_ec/* @pvizeli
esphome/components/ufire_ise/* @pvizeli
esphome/components/ultrasonic/* @OttoWinter
esphome/components/uponor_smatrix/* @kroimon
esphome/components/valve/* @esphome/core
esphome/components/vbus/* @ssieb
esphome/components/veml3235/* @kbx81
esphome/components/veml7700/* @latonita
@ -399,10 +402,21 @@ esphome/components/wake_on_lan/* @willwill2will54
esphome/components/waveshare_epaper/* @clydebarrow
esphome/components/web_server_base/* @OttoWinter
esphome/components/web_server_idf/* @dentra
esphome/components/weikai/* @DrCoolZic
esphome/components/weikai_i2c/* @DrCoolZic
esphome/components/weikai_spi/* @DrCoolZic
esphome/components/whirlpool/* @glmnet
esphome/components/whynter/* @aeonsablaze
esphome/components/wiegand/* @ssieb
esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
esphome/components/wk2132_i2c/* @DrCoolZic
esphome/components/wk2132_spi/* @DrCoolZic
esphome/components/wk2168_i2c/* @DrCoolZic
esphome/components/wk2168_spi/* @DrCoolZic
esphome/components/wk2204_i2c/* @DrCoolZic
esphome/components/wk2204_spi/* @DrCoolZic
esphome/components/wk2212_i2c/* @DrCoolZic
esphome/components/wk2212_spi/* @DrCoolZic
esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier

View File

@ -43,6 +43,7 @@ service APIConnection {
rpc select_command (SelectCommandRequest) returns (void) {}
rpc button_command (ButtonCommandRequest) returns (void) {}
rpc lock_command (LockCommandRequest) returns (void) {}
rpc valve_command (ValveCommandRequest) returns (void) {}
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
rpc date_command (DateCommandRequest) returns (void) {}
rpc time_command (TimeCommandRequest) returns (void) {}
@ -1702,6 +1703,82 @@ message TimeCommandRequest {
uint32 second = 4;
}
// ==================== EVENT ====================
message ListEntitiesEventResponse {
option (id) = 107;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
repeated string event_types = 9;
}
message EventResponse {
option (id) = 108;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_EVENT";
fixed32 key = 1;
string event_type = 2;
}
// ==================== VALVE ====================
message ListEntitiesValveResponse {
option (id) = 109;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VALVE";
string object_id = 1;
fixed32 key = 2;
string name = 3;
string unique_id = 4;
string icon = 5;
bool disabled_by_default = 6;
EntityCategory entity_category = 7;
string device_class = 8;
bool assumed_state = 9;
bool supports_position = 10;
bool supports_stop = 11;
}
enum ValveOperation {
VALVE_OPERATION_IDLE = 0;
VALVE_OPERATION_IS_OPENING = 1;
VALVE_OPERATION_IS_CLOSING = 2;
}
message ValveStateResponse {
option (id) = 110;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_VALVE";
option (no_delay) = true;
fixed32 key = 1;
float position = 2;
ValveOperation current_operation = 3;
}
message ValveCommandRequest {
option (id) = 111;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VALVE";
option (no_delay) = true;
fixed32 key = 1;
bool has_position = 2;
float position = 3;
bool stop = 4;
}
// ==================== DATETIME DATETIME ====================
message ListEntitiesDateTimeResponse {
option (id) = 112;

View File

@ -953,6 +953,48 @@ void APIConnection::lock_command(const LockCommandRequest &msg) {
}
#endif
#ifdef USE_VALVE
bool APIConnection::send_valve_state(valve::Valve *valve) {
if (!this->state_subscription_)
return false;
ValveStateResponse resp{};
resp.key = valve->get_object_id_hash();
resp.position = valve->position;
resp.current_operation = static_cast<enums::ValveOperation>(valve->current_operation);
return this->send_valve_state_response(resp);
}
bool APIConnection::send_valve_info(valve::Valve *valve) {
auto traits = valve->get_traits();
ListEntitiesValveResponse msg;
msg.key = valve->get_object_id_hash();
msg.object_id = valve->get_object_id();
if (valve->has_own_name())
msg.name = valve->get_name();
msg.unique_id = get_default_unique_id("valve", valve);
msg.icon = valve->get_icon();
msg.disabled_by_default = valve->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(valve->get_entity_category());
msg.device_class = valve->get_device_class();
msg.assumed_state = traits.get_is_assumed_state();
msg.supports_position = traits.get_supports_position();
msg.supports_stop = traits.get_supports_stop();
return this->send_list_entities_valve_response(msg);
}
void APIConnection::valve_command(const ValveCommandRequest &msg) {
valve::Valve *valve = App.get_valve_by_key(msg.key);
if (valve == nullptr)
return;
auto call = valve->make_call();
if (msg.has_position)
call.set_position(msg.position);
if (msg.stop)
call.set_command_stop();
call.perform();
}
#endif
#ifdef USE_MEDIA_PLAYER
bool APIConnection::send_media_player_state(media_player::MediaPlayer *media_player) {
if (!this->state_subscription_)
@ -1205,6 +1247,30 @@ void APIConnection::alarm_control_panel_command(const AlarmControlPanelCommandRe
}
#endif
#ifdef USE_EVENT
bool APIConnection::send_event(event::Event *event, std::string event_type) {
EventResponse resp{};
resp.key = event->get_object_id_hash();
resp.event_type = std::move(event_type);
return this->send_event_response(resp);
}
bool APIConnection::send_event_info(event::Event *event) {
ListEntitiesEventResponse msg;
msg.key = event->get_object_id_hash();
msg.object_id = event->get_object_id();
if (event->has_own_name())
msg.name = event->get_name();
msg.unique_id = get_default_unique_id("event", event);
msg.icon = event->get_icon();
msg.disabled_by_default = event->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(event->get_entity_category());
msg.device_class = event->get_device_class();
for (const auto &event_type : event->get_event_types())
msg.event_types.push_back(event_type);
return this->send_list_entities_event_response(msg);
}
#endif
bool APIConnection::send_log_message(int level, const char *tag, const char *line) {
if (this->log_subscription_ < level)
return false;

View File

@ -106,6 +106,11 @@ class APIConnection : public APIServerConnection {
bool send_lock_info(lock::Lock *a_lock);
void lock_command(const LockCommandRequest &msg) override;
#endif
#ifdef USE_VALVE
bool send_valve_state(valve::Valve *valve);
bool send_valve_info(valve::Valve *valve);
void valve_command(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
bool send_media_player_state(media_player::MediaPlayer *media_player);
bool send_media_player_info(media_player::MediaPlayer *media_player);
@ -153,6 +158,11 @@ class APIConnection : public APIServerConnection {
void alarm_control_panel_command(const AlarmControlPanelCommandRequest &msg) override;
#endif
#ifdef USE_EVENT
bool send_event(event::Event *event, std::string event_type);
bool send_event_info(event::Event *event);
#endif
void on_disconnect_response(const DisconnectResponse &value) override;
void on_ping_response(const PingResponse &value) override {
// we initiated ping

View File

@ -537,6 +537,20 @@ template<> const char *proto_enum_to_string<enums::TextMode>(enums::TextMode val
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::ValveOperation>(enums::ValveOperation value) {
switch (value) {
case enums::VALVE_OPERATION_IDLE:
return "VALVE_OPERATION_IDLE";
case enums::VALVE_OPERATION_IS_OPENING:
return "VALVE_OPERATION_IS_OPENING";
case enums::VALVE_OPERATION_IS_CLOSING:
return "VALVE_OPERATION_IS_CLOSING";
default:
return "UNKNOWN";
}
}
#endif
bool HelloRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
@ -7695,6 +7709,390 @@ void TimeCommandRequest::dump_to(std::string &out) const {
out.append("}");
}
#endif
bool ListEntitiesEventResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
default:
return false;
}
}
bool ListEntitiesEventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 8: {
this->device_class = value.as_string();
return true;
}
case 9: {
this->event_types.push_back(value.as_string());
return true;
}
default:
return false;
}
}
bool ListEntitiesEventResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesEventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
for (auto &it : this->event_types) {
buffer.encode_string(9, it, true);
}
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesEventResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesEventResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
for (const auto &it : this->event_types) {
out.append(" event_types: ");
out.append("'").append(it).append("'");
out.append("\n");
}
out.append("}");
}
#endif
bool EventResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 2: {
this->event_type = value.as_string();
return true;
}
default:
return false;
}
}
bool EventResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void EventResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_string(2, this->event_type);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void EventResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("EventResponse {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" event_type: ");
out.append("'").append(this->event_type).append("'");
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesValveResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {
this->disabled_by_default = value.as_bool();
return true;
}
case 7: {
this->entity_category = value.as_enum<enums::EntityCategory>();
return true;
}
case 9: {
this->assumed_state = value.as_bool();
return true;
}
case 10: {
this->supports_position = value.as_bool();
return true;
}
case 11: {
this->supports_stop = value.as_bool();
return true;
}
default:
return false;
}
}
bool ListEntitiesValveResponse::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->object_id = value.as_string();
return true;
}
case 3: {
this->name = value.as_string();
return true;
}
case 4: {
this->unique_id = value.as_string();
return true;
}
case 5: {
this->icon = value.as_string();
return true;
}
case 8: {
this->device_class = value.as_string();
return true;
}
default:
return false;
}
}
bool ListEntitiesValveResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesValveResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->object_id);
buffer.encode_fixed32(2, this->key);
buffer.encode_string(3, this->name);
buffer.encode_string(4, this->unique_id);
buffer.encode_string(5, this->icon);
buffer.encode_bool(6, this->disabled_by_default);
buffer.encode_enum<enums::EntityCategory>(7, this->entity_category);
buffer.encode_string(8, this->device_class);
buffer.encode_bool(9, this->assumed_state);
buffer.encode_bool(10, this->supports_position);
buffer.encode_bool(11, this->supports_stop);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesValveResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesValveResponse {\n");
out.append(" object_id: ");
out.append("'").append(this->object_id).append("'");
out.append("\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" name: ");
out.append("'").append(this->name).append("'");
out.append("\n");
out.append(" unique_id: ");
out.append("'").append(this->unique_id).append("'");
out.append("\n");
out.append(" icon: ");
out.append("'").append(this->icon).append("'");
out.append("\n");
out.append(" disabled_by_default: ");
out.append(YESNO(this->disabled_by_default));
out.append("\n");
out.append(" entity_category: ");
out.append(proto_enum_to_string<enums::EntityCategory>(this->entity_category));
out.append("\n");
out.append(" device_class: ");
out.append("'").append(this->device_class).append("'");
out.append("\n");
out.append(" assumed_state: ");
out.append(YESNO(this->assumed_state));
out.append("\n");
out.append(" supports_position: ");
out.append(YESNO(this->supports_position));
out.append("\n");
out.append(" supports_stop: ");
out.append(YESNO(this->supports_stop));
out.append("\n");
out.append("}");
}
#endif
bool ValveStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 3: {
this->current_operation = value.as_enum<enums::ValveOperation>();
return true;
}
default:
return false;
}
}
bool ValveStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
case 2: {
this->position = value.as_float();
return true;
}
default:
return false;
}
}
void ValveStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_float(2, this->position);
buffer.encode_enum<enums::ValveOperation>(3, this->current_operation);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ValveStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ValveStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" position: ");
sprintf(buffer, "%g", this->position);
out.append(buffer);
out.append("\n");
out.append(" current_operation: ");
out.append(proto_enum_to_string<enums::ValveOperation>(this->current_operation));
out.append("\n");
out.append("}");
}
#endif
bool ValveCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->has_position = value.as_bool();
return true;
}
case 4: {
this->stop = value.as_bool();
return true;
}
default:
return false;
}
}
bool ValveCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
case 3: {
this->position = value.as_float();
return true;
}
default:
return false;
}
}
void ValveCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->has_position);
buffer.encode_float(3, this->position);
buffer.encode_bool(4, this->stop);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ValveCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ValveCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" has_position: ");
out.append(YESNO(this->has_position));
out.append("\n");
out.append(" position: ");
sprintf(buffer, "%g", this->position);
out.append(buffer);
out.append("\n");
out.append(" stop: ");
out.append(YESNO(this->stop));
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesDateTimeResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 6: {

View File

@ -216,6 +216,11 @@ enum TextMode : uint32_t {
TEXT_MODE_TEXT = 0,
TEXT_MODE_PASSWORD = 1,
};
enum ValveOperation : uint32_t {
VALVE_OPERATION_IDLE = 0,
VALVE_OPERATION_IS_OPENING = 1,
VALVE_OPERATION_IS_CLOSING = 2,
};
} // namespace enums
@ -1969,6 +1974,92 @@ class TimeCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesEventResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
std::vector<std::string> event_types{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class EventResponse : public ProtoMessage {
public:
uint32_t key{0};
std::string event_type{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
};
class ListEntitiesValveResponse : public ProtoMessage {
public:
std::string object_id{};
uint32_t key{0};
std::string name{};
std::string unique_id{};
std::string icon{};
bool disabled_by_default{false};
enums::EntityCategory entity_category{};
std::string device_class{};
bool assumed_state{false};
bool supports_position{false};
bool supports_stop{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ValveStateResponse : public ProtoMessage {
public:
uint32_t key{0};
float position{0.0f};
enums::ValveOperation current_operation{};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ValveCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
bool has_position{false};
float position{0.0f};
bool stop{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesDateTimeResponse : public ProtoMessage {
public:
std::string object_id{};

View File

@ -557,6 +557,40 @@ bool APIServerConnectionBase::send_time_state_response(const TimeStateResponse &
#endif
#ifdef USE_DATETIME_TIME
#endif
#ifdef USE_EVENT
bool APIServerConnectionBase::send_list_entities_event_response(const ListEntitiesEventResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_event_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesEventResponse>(msg, 107);
}
#endif
#ifdef USE_EVENT
bool APIServerConnectionBase::send_event_response(const EventResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_event_response: %s", msg.dump().c_str());
#endif
return this->send_message_<EventResponse>(msg, 108);
}
#endif
#ifdef USE_VALVE
bool APIServerConnectionBase::send_list_entities_valve_response(const ListEntitiesValveResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_valve_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesValveResponse>(msg, 109);
}
#endif
#ifdef USE_VALVE
bool APIServerConnectionBase::send_valve_state_response(const ValveStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_valve_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ValveStateResponse>(msg, 110);
}
#endif
#ifdef USE_VALVE
#endif
#ifdef USE_DATETIME_DATETIME
bool APIServerConnectionBase::send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
@ -1037,6 +1071,17 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_audio(msg);
#endif
break;
}
case 111: {
#ifdef USE_VALVE
ValveCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_valve_command_request: %s", msg.dump().c_str());
#endif
this->on_valve_command_request(msg);
#endif
break;
}
@ -1311,6 +1356,19 @@ void APIServerConnection::on_lock_command_request(const LockCommandRequest &msg)
this->lock_command(msg);
}
#endif
#ifdef USE_VALVE
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->valve_command(msg);
}
#endif
#ifdef USE_MEDIA_PLAYER
void APIServerConnection::on_media_player_command_request(const MediaPlayerCommandRequest &msg) {
if (!this->is_connection_setup()) {

View File

@ -280,6 +280,21 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_DATETIME_TIME
virtual void on_time_command_request(const TimeCommandRequest &value){};
#endif
#ifdef USE_EVENT
bool send_list_entities_event_response(const ListEntitiesEventResponse &msg);
#endif
#ifdef USE_EVENT
bool send_event_response(const EventResponse &msg);
#endif
#ifdef USE_VALVE
bool send_list_entities_valve_response(const ListEntitiesValveResponse &msg);
#endif
#ifdef USE_VALVE
bool send_valve_state_response(const ValveStateResponse &msg);
#endif
#ifdef USE_VALVE
virtual void on_valve_command_request(const ValveCommandRequest &value){};
#endif
#ifdef USE_DATETIME_DATETIME
bool send_list_entities_date_time_response(const ListEntitiesDateTimeResponse &msg);
#endif
@ -340,6 +355,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_LOCK
virtual void lock_command(const LockCommandRequest &msg) = 0;
#endif
#ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif
#ifdef USE_MEDIA_PLAYER
virtual void media_player_command(const MediaPlayerCommandRequest &msg) = 0;
#endif
@ -435,6 +453,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_LOCK
void on_lock_command_request(const LockCommandRequest &msg) override;
#endif
#ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_command_request(const MediaPlayerCommandRequest &msg) override;
#endif

View File

@ -309,6 +309,15 @@ void APIServer::on_lock_update(lock::Lock *obj) {
}
#endif
#ifdef USE_VALVE
void APIServer::on_valve_update(valve::Valve *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_valve_state(obj);
}
#endif
#ifdef USE_MEDIA_PLAYER
void APIServer::on_media_player_update(media_player::MediaPlayer *obj) {
if (obj->is_internal())
@ -318,6 +327,13 @@ void APIServer::on_media_player_update(media_player::MediaPlayer *obj) {
}
#endif
#ifdef USE_EVENT
void APIServer::on_event(event::Event *obj, const std::string &event_type) {
for (auto &c : this->clients_)
c->send_event(obj, event_type);
}
#endif
float APIServer::get_setup_priority() const { return setup_priority::AFTER_WIFI; }
void APIServer::set_port(uint16_t port) { this->port_ = port; }
APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)

View File

@ -84,6 +84,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_LOCK
void on_lock_update(lock::Lock *obj) override;
#endif
#ifdef USE_VALVE
void on_valve_update(valve::Valve *obj) override;
#endif
#ifdef USE_MEDIA_PLAYER
void on_media_player_update(media_player::MediaPlayer *obj) override;
#endif
@ -96,6 +99,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
#endif
#ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override;
#endif
bool is_connected() const;

View File

@ -38,6 +38,9 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
#ifdef USE_LOCK
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { return this->client_->send_lock_info(a_lock); }
#endif
#ifdef USE_VALVE
bool ListEntitiesIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_info(valve); }
#endif
bool ListEntitiesIterator::on_end() { return this->client_->send_list_info_done(); }
ListEntitiesIterator::ListEntitiesIterator(APIConnection *client) : client_(client) {}
@ -92,6 +95,9 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
return this->client_->send_alarm_control_panel_info(a_alarm_control_panel);
}
#endif
#ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) { return this->client_->send_event_info(event); }
#endif
} // namespace api
} // namespace esphome

View File

@ -64,11 +64,17 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
#ifdef USE_VALVE
bool on_valve(valve::Valve *valve) override;
#endif
#ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override;
#endif
bool on_end() override;

View File

@ -64,6 +64,9 @@ 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
#ifdef USE_VALVE
bool InitialStateIterator::on_valve(valve::Valve *valve) { return this->client_->send_valve_state(valve); }
#endif
#ifdef USE_MEDIA_PLAYER
bool InitialStateIterator::on_media_player(media_player::MediaPlayer *media_player) {
return this->client_->send_media_player_state(media_player);

View File

@ -61,11 +61,17 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
#ifdef USE_VALVE
bool on_valve(valve::Valve *valve) override;
#endif
#ifdef USE_MEDIA_PLAYER
bool on_media_player(media_player::MediaPlayer *media_player) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override { return true; };
#endif
protected:
APIConnection *client_;

View File

@ -5,17 +5,13 @@ namespace cst226 {
void CST226Touchscreen::setup() {
esph_log_config(TAG, "Setting up CST226 Touchscreen...");
if (this->reset_pin_ != nullptr) {
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
this->set_timeout(30, [this] { this->continue_setup_(); });
} else {
this->continue_setup_();
}
this->reset_pin_->setup();
this->reset_pin_->digital_write(true);
delay(5);
this->reset_pin_->digital_write(false);
delay(5);
this->reset_pin_->digital_write(true);
this->set_timeout(30, [this] { this->continue_setup_(); });
}
void CST226Touchscreen::update_touches() {

View File

@ -35,7 +35,7 @@ class CST226Touchscreen : public touchscreen::Touchscreen, public i2c::I2CDevice
void continue_setup_();
InternalGPIOPin *interrupt_pin_{};
GPIOPin *reset_pin_{};
GPIOPin *reset_pin_{NULL_PIN};
uint8_t chip_id_{};
bool setup_complete_{};
};

View File

@ -316,17 +316,26 @@ def _parse_platform_version(value):
def _detect_variant(value):
if CONF_VARIANT not in value:
board = value[CONF_BOARD]
if board not in BOARDS:
board = value[CONF_BOARD]
if board in BOARDS:
variant = BOARDS[board][KEY_VARIANT]
if CONF_VARIANT in value and variant != value[CONF_VARIANT]:
raise cv.Invalid(
"This board is unknown, please set the variant manually",
f"Option '{CONF_VARIANT}' does not match selected board.",
path=[CONF_VARIANT],
)
value = value.copy()
value[CONF_VARIANT] = variant
else:
if CONF_VARIANT not in value:
raise cv.Invalid(
"This board is unknown, if you are sure you want to compile with this board selection, "
f"override with option '{CONF_VARIANT}'",
path=[CONF_BOARD],
)
value = value.copy()
value[CONF_VARIANT] = BOARDS[board][KEY_VARIANT]
_LOGGER.warning(
"This board is unknown. Make sure the chosen chip component is correct.",
)
return value

View File

@ -65,6 +65,8 @@ async def to_code(config):
add_idf_sdkconfig_option("CONFIG_BT_ENABLED", True)
add_idf_sdkconfig_option("CONFIG_BT_BLE_42_FEATURES_SUPPORTED", True)
cg.add_define("USE_ESP32_BLE")
@automation.register_condition("ble.enabled", BLEEnabledCondition, cv.Schema({}))
async def ble_enabled_to_code(config, condition_id, template_arg, args):

View File

@ -364,7 +364,11 @@ void ESP32BLETracker::gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_ga
}
void ESP32BLETracker::gap_scan_set_param_complete_(const esp_ble_gap_cb_param_t::ble_scan_param_cmpl_evt_param &param) {
this->scan_set_param_failed_ = param.status;
if (param.status == ESP_BT_STATUS_DONE) {
this->scan_set_param_failed_ = ESP_BT_STATUS_SUCCESS;
} else {
this->scan_set_param_failed_ = param.status;
}
}
void ESP32BLETracker::gap_scan_start_complete_(const esp_ble_gap_cb_param_t::ble_scan_start_cmpl_evt_param &param) {

View File

@ -0,0 +1,134 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import mqtt
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ENTITY_CATEGORY,
CONF_ICON,
CONF_ID,
CONF_ON_EVENT,
CONF_TRIGGER_ID,
CONF_MQTT_ID,
CONF_EVENT_TYPE,
DEVICE_CLASS_BUTTON,
DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_MOTION,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
from esphome.cpp_generator import MockObjClass
CODEOWNERS = ["@nohat"]
IS_PLATFORM_COMPONENT = True
DEVICE_CLASSES = [
DEVICE_CLASS_BUTTON,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_MOTION,
]
event_ns = cg.esphome_ns.namespace("event")
Event = event_ns.class_("Event", cg.EntityBase)
EventPtr = Event.operator("ptr")
TriggerEventAction = event_ns.class_("TriggerEventAction", automation.Action)
EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template())
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
EVENT_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMPONENT_SCHEMA).extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
cv.GenerateID(): cv.declare_id(Event),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_EVENT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(EventTrigger),
}
),
}
)
_UNDEF = object()
def event_schema(
class_: MockObjClass = _UNDEF,
*,
icon: str = _UNDEF,
entity_category: str = _UNDEF,
device_class: str = _UNDEF,
) -> cv.Schema:
schema = {}
if class_ is not _UNDEF:
schema[cv.GenerateID()] = cv.declare_id(class_)
for key, default, validator in [
(CONF_ICON, icon, cv.icon),
(CONF_ENTITY_CATEGORY, entity_category, cv.entity_category),
(CONF_DEVICE_CLASS, device_class, validate_device_class),
]:
if default is not _UNDEF:
schema[cv.Optional(key, default=default)] = validator
return EVENT_SCHEMA.extend(schema)
async def setup_event_core_(var, config, *, event_types: list[str]):
await setup_entity(var, config)
for conf in config.get(CONF_ON_EVENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.std_string, "event_type")], conf
)
cg.add(var.set_event_types(event_types))
if (device_class := config.get(CONF_DEVICE_CLASS)) is not None:
cg.add(var.set_device_class(device_class))
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_event(var, config, *, event_types: list[str]):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_event(var))
await setup_event_core_(var, config, event_types=event_types)
async def new_event(config, *, event_types: list[str]):
var = cg.new_Pvariable(config[CONF_ID])
await register_event(var, config, event_types=event_types)
return var
TRIGGER_EVENT_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(Event),
cv.Required(CONF_EVENT_TYPE): cv.templatable(cv.string_strict),
}
)
@automation.register_action("event.trigger", TriggerEventAction, TRIGGER_EVENT_SCHEMA)
async def event_fire_to_code(config, action_id, template_arg, args):
var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(var, config[CONF_ID])
templ = await cg.templatable(config[CONF_EVENT_TYPE], args, cg.std_string)
cg.add(var.set_event_type(templ))
return var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_EVENT")
cg.add_global(event_ns.using)

View File

@ -0,0 +1,25 @@
#pragma once
#include "esphome/components/event/event.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
namespace esphome {
namespace event {
template<typename... Ts> class TriggerEventAction : public Action<Ts...>, public Parented<Event> {
public:
TEMPLATABLE_VALUE(std::string, event_type)
void play(Ts... x) override { this->parent_->trigger(this->event_type_.value(x...)); }
};
class EventTrigger : public Trigger<std::string> {
public:
EventTrigger(Event *event) {
event->add_on_event_callback([this](const std::string &event_type) { this->trigger(event_type); });
}
};
} // namespace event
} // namespace esphome

View File

@ -0,0 +1,24 @@
#include "event.h"
#include "esphome/core/log.h"
namespace esphome {
namespace event {
static const char *const TAG = "event";
void Event::trigger(const std::string &event_type) {
if (types_.find(event_type) == types_.end()) {
ESP_LOGE(TAG, "'%s': invalid event type for trigger(): %s", this->get_name().c_str(), event_type.c_str());
return;
}
ESP_LOGD(TAG, "'%s' Triggered event '%s'", this->get_name().c_str(), event_type.c_str());
this->event_callback_.call(event_type);
}
void Event::add_on_event_callback(std::function<void(const std::string &event_type)> &&callback) {
this->event_callback_.add(std::move(callback));
}
} // namespace event
} // namespace esphome

View File

@ -0,0 +1,37 @@
#pragma once
#include <set>
#include <string>
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace event {
#define LOG_EVENT(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
if (!(obj)->get_icon().empty()) { \
ESP_LOGCONFIG(TAG, "%s Icon: '%s'", prefix, (obj)->get_icon().c_str()); \
} \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}
class Event : public EntityBase, public EntityBase_DeviceClass {
public:
void trigger(const std::string &event_type);
void set_event_types(const std::set<std::string> &event_types) { this->types_ = event_types; }
std::set<std::string> get_event_types() const { return this->types_; }
void add_on_event_callback(std::function<void(const std::string &event_type)> &&callback);
protected:
CallbackManager<void(const std::string &event_type)> event_callback_;
std::set<std::string> types_;
};
} // namespace event
} // namespace esphome

View File

@ -38,7 +38,7 @@ CONFIG_SCHEMA = DISPLAY_MENU_BASE_SCHEMA.extend(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(GraphicalDisplayMenu),
cv.Optional(CONF_DISPLAY): cv.use_id(display.DisplayBuffer),
cv.Optional(CONF_DISPLAY): cv.use_id(display.Display),
cv.Required(CONF_FONT): cv.use_id(font.Font),
cv.Optional(CONF_MENU_ITEM_VALUE): cv.templatable(cv.string),
cv.Optional(CONF_FOREGROUND_COLOR): cv.use_id(color.ColorStruct),

View File

@ -229,7 +229,8 @@ inline void GraphicalDisplayMenu::draw_item(display::Display *display, const dis
label.append(this->menu_item_value_.value(&args));
}
display->print(bounds->x, bounds->y, this->font_, foreground_color, display::TextAlign::TOP_LEFT, label.c_str());
display->print(bounds->x, bounds->y, this->font_, foreground_color, display::TextAlign::TOP_LEFT, label.c_str(),
~foreground_color);
}
void GraphicalDisplayMenu::draw_item(const display_menu_base::MenuItem *item, const uint8_t row, const bool selected) {

View File

@ -55,11 +55,13 @@ void InternalTemperatureSensor::update() {
uint32_t raw, result;
result = temp_single_get_current_temperature(&raw);
success = (result == 0);
#ifdef USE_LIBRETINY_VARIANT_BK7231T
#if defined(USE_LIBRETINY_VARIANT_BK7231N)
temperature = raw * -0.38f + 156.0f;
#elif defined(USE_LIBRETINY_VARIANT_BK7231T)
temperature = raw * 0.04f;
#else
#else // USE_LIBRETINY_VARIANT
temperature = raw * 0.128f;
#endif // USE_LIBRETINY_VARIANT_BK7231T
#endif // USE_LIBRETINY_VARIANT
#endif // USE_BK72XX
if (success && std::isfinite(temperature)) {
this->publish_state(temperature);

View File

@ -493,19 +493,16 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
}
int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
uint32_t start_millis = millis();
uint8_t error = 0;
uint8_t ack_buffer[64];
uint8_t cmd_buffer[64];
uint16_t loop_count;
this->cmd_reply_.ack = false;
if (frame.command != CMD_RESTART)
this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
uint8_t retry = 3;
while (retry) {
// TODO setup a dynamic method e.g. millis time count etc. to tune for non ESP32 240Mhz devices
// this is ok for now since the module firmware is changing like the weather atm
frame.length = 0;
loop_count = 1250;
uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size
memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header));
@ -538,12 +535,13 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
}
delay_microseconds_safe(1450);
if (loop_count <= 0) {
// Wait on an Rx from the LD2420 for up to 3 1 second loops, otherwise it could trigger a WDT.
if ((millis() - start_millis) > 1000) {
start_millis = millis();
error = LD2420_ERROR_TIMEOUT;
retry--;
break;
}
loop_count--;
}
if (this->cmd_reply_.ack)
retry = 0;

View File

@ -1,6 +1,10 @@
import json
import logging
from os.path import dirname, isfile, join
from os.path import (
dirname,
isfile,
join,
)
import esphome.codegen as cg
import esphome.config_validation as cv
@ -55,15 +59,25 @@ def _detect_variant(value):
component: LibreTinyComponent = CORE.data[KEY_LIBRETINY][KEY_COMPONENT_DATA]
board = value[CONF_BOARD]
# read board-default family if not specified
if CONF_FAMILY not in value:
if board not in component.boards:
if board not in component.boards:
if CONF_FAMILY not in value:
raise cv.Invalid(
"This board is unknown, please set the family manually. "
"Also, make sure the chosen chip component is correct.",
"This board is unknown, if you are sure you want to compile with this board selection, "
f"override with option '{CONF_FAMILY}'",
path=[CONF_BOARD],
)
_LOGGER.warning(
"This board is unknown. Make sure the chosen chip component is correct.",
)
else:
family = component.boards[board][KEY_FAMILY]
if CONF_FAMILY in value and family != value[CONF_FAMILY]:
raise cv.Invalid(
f"Option '{CONF_FAMILY}' does not match selected board.",
path=[CONF_FAMILY],
)
value = value.copy()
value[CONF_FAMILY] = component.boards[board][KEY_FAMILY]
value[CONF_FAMILY] = family
# read component name matching this family
value[CONF_COMPONENT_ID] = FAMILY_COMPONENT[value[CONF_FAMILY]]
# make sure the chosen component matches the family
@ -72,11 +86,6 @@ def _detect_variant(value):
f"The chosen family doesn't belong to '{component.name}' component. The correct component is '{value[CONF_COMPONENT_ID]}'",
path=[CONF_FAMILY],
)
# warn anyway if the board wasn't found
if board not in component.boards:
_LOGGER.warning(
"This board is unknown. Make sure the chosen chip component is correct.",
)
return value

View File

@ -120,6 +120,8 @@ MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent)
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)
MQTTLockComponent = mqtt_ns.class_("MQTTLockComponent", MQTTComponent)
MQTTEventComponent = mqtt_ns.class_("MQTTEventComponent", MQTTComponent)
MQTTValveComponent = mqtt_ns.class_("MQTTValveComponent", MQTTComponent)
MQTTDiscoveryUniqueIdGenerator = mqtt_ns.enum("MQTTDiscoveryUniqueIdGenerator")
MQTT_DISCOVERY_UNIQUE_ID_GENERATOR_OPTIONS = {

View File

@ -9,8 +9,8 @@ namespace mqtt {
#ifdef USE_MQTT_ABBREVIATIONS
constexpr const char *const MQTT_ACTION_TOPIC = "act_t";
constexpr const char *const MQTT_ACTION_TEMPLATE = "act_tpl";
constexpr const char *const MQTT_ACTION_TOPIC = "act_t";
constexpr const char *const MQTT_AUTOMATION_TYPE = "atype";
constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_cmd_t";
constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_stat_tpl";
@ -21,60 +21,70 @@ constexpr const char *const MQTT_AVAILABILITY_TOPIC = "avty_t";
constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_cmd_t";
constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_stat_tpl";
constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_stat_t";
constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl";
constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t";
constexpr const char *const MQTT_BLUE_TEMPLATE = "b_tpl";
constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "bri_cmd_t";
constexpr const char *const MQTT_BRIGHTNESS_SCALE = "bri_scl";
constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "bri_stat_t";
constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "bri_tpl";
constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "bri_val_tpl";
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl";
constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "bat_lev_t";
constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "bat_lev_tpl";
constexpr const char *const MQTT_CONFIGURATION_URL = "cu";
constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t";
constexpr const char *const MQTT_CHARGING_TEMPLATE = "chrg_tpl";
constexpr const char *const MQTT_CHARGING_TOPIC = "chrg_t";
constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl";
constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t";
constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req";
constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req";
constexpr const char *const MQTT_COLOR_MODE = "clrm";
constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "clrm_stat_t";
constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "clrm_val_tpl";
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "clr_temp_cmd_tpl";
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "clr_temp_cmd_t";
constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "clr_temp_stat_t";
constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "clr_temp_tpl";
constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "clr_temp_val_tpl";
constexpr const char *const MQTT_CLEANING_TOPIC = "cln_t";
constexpr const char *const MQTT_CLEANING_TEMPLATE = "cln_tpl";
constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "cmd_off_tpl";
constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "cmd_on_tpl";
constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t";
constexpr const char *const MQTT_COMMAND_RETAIN = "ret";
constexpr const char *const MQTT_COMMAND_TEMPLATE = "cmd_tpl";
constexpr const char *const MQTT_CODE_ARM_REQUIRED = "cod_arm_req";
constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "cod_dis_req";
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t";
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl";
constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "curr_hum_t";
constexpr const char *const MQTT_COMMAND_TOPIC = "cmd_t";
constexpr const char *const MQTT_CONFIGURATION_URL = "cu";
constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "curr_hum_tpl";
constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "curr_hum_t";
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "curr_temp_tpl";
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "curr_temp_t";
constexpr const char *const MQTT_DEVICE = "dev";
constexpr const char *const MQTT_DEVICE_CLASS = "dev_cla";
constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t";
constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns";
constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids";
constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf";
constexpr const char *const MQTT_DEVICE_MODEL = "mdl";
constexpr const char *const MQTT_DEVICE_NAME = "name";
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa";
constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw";
constexpr const char *const MQTT_DOCKED_TEMPLATE = "dock_tpl";
constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en";
constexpr const char *const MQTT_ERROR_TOPIC = "err_t";
constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl";
constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t";
constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl";
constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst";
constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng";
constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht";
constexpr const char *const MQTT_DOCKED_TOPIC = "dock_t";
constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "fx_cmd_t";
constexpr const char *const MQTT_EFFECT_LIST = "fx_list";
constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "fx_stat_t";
constexpr const char *const MQTT_EFFECT_TEMPLATE = "fx_tpl";
constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "fx_val_tpl";
constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "en";
constexpr const char *const MQTT_ENTITY_CATEGORY = "ent_cat";
constexpr const char *const MQTT_ERROR_TEMPLATE = "err_tpl";
constexpr const char *const MQTT_ERROR_TOPIC = "err_t";
constexpr const char *const MQTT_EVENT_TYPE = "event_type";
constexpr const char *const MQTT_EVENT_TYPES = "evt_typ";
constexpr const char *const MQTT_EXPIRE_AFTER = "exp_aft";
constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_cmd_tpl";
constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_cmd_t";
constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_stat_tpl";
constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_stat_t";
constexpr const char *const MQTT_FAN_SPEED_LIST = "fanspd_lst";
constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fanspd_tpl";
constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fanspd_t";
constexpr const char *const MQTT_FLASH_TIME_LONG = "flsh_tlng";
constexpr const char *const MQTT_FLASH_TIME_SHORT = "flsh_tsht";
constexpr const char *const MQTT_FORCE_UPDATE = "frc_upd";
constexpr const char *const MQTT_GREEN_TEMPLATE = "g_tpl";
constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_cmd_tpl";
@ -86,56 +96,49 @@ constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_stat_t";
constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_val_tpl";
constexpr const char *const MQTT_ICON = "ic";
constexpr const char *const MQTT_INITIAL = "init";
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t";
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl";
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t";
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl";
constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attr";
constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t";
constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attr_tpl";
constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attr_t";
constexpr const char *const MQTT_LAST_RESET_TOPIC = "lrst_t";
constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "lrst_val_tpl";
constexpr const char *const MQTT_MAX = "max";
constexpr const char *const MQTT_MIN = "min";
constexpr const char *const MQTT_MAX_HUMIDITY = "max_hum";
constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum";
constexpr const char *const MQTT_MAX_MIREDS = "max_mirs";
constexpr const char *const MQTT_MIN_MIREDS = "min_mirs";
constexpr const char *const MQTT_MAX_TEMP = "max_temp";
constexpr const char *const MQTT_MIN = "min";
constexpr const char *const MQTT_MIN_HUMIDITY = "min_hum";
constexpr const char *const MQTT_MIN_MIREDS = "min_mirs";
constexpr const char *const MQTT_MIN_TEMP = "min_temp";
constexpr const char *const MQTT_MODE = "mode";
constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_cmd_tpl";
constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_cmd_t";
constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t";
constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_stat_tpl";
constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_stat_t";
constexpr const char *const MQTT_MODES = "modes";
constexpr const char *const MQTT_NAME = "name";
constexpr const char *const MQTT_OBJECT_ID = "obj_id";
constexpr const char *const MQTT_OFF_DELAY = "off_dly";
constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_cmd_type";
constexpr const char *const MQTT_OPTIONS = "ops";
constexpr const char *const MQTT_OPTIMISTIC = "opt";
constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t";
constexpr const char *const MQTT_OPTIONS = "ops";
constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "osc_cmd_tpl";
constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "osc_cmd_t";
constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "osc_stat_t";
constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "osc_val_tpl";
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t";
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl";
constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t";
constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl";
constexpr const char *const MQTT_PAYLOAD = "pl";
constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "pl_arm_away";
constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b";
constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "pl_arm_home";
constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "pl_arm_nite";
constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "pl_arm_vacation";
constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "pl_arm_custom_b";
constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "pl_avail";
constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "pl_cln_sp";
constexpr const char *const MQTT_PAYLOAD_CLOSE = "pl_cls";
constexpr const char *const MQTT_PAYLOAD_DISARM = "pl_disarm";
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "pl_hi_spd";
constexpr const char *const MQTT_PAYLOAD_HOME = "pl_home";
constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock";
constexpr const char *const MQTT_PAYLOAD_LOCATE = "pl_loc";
constexpr const char *const MQTT_PAYLOAD_LOCK = "pl_lock";
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "pl_lo_spd";
constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "pl_med_spd";
constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "pl_not_avail";
@ -152,20 +155,26 @@ constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "pl_rst_hum";
constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "pl_rst_mode";
constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "pl_rst_pct";
constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "pl_rst_pr_mode";
constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop";
constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret";
constexpr const char *const MQTT_PAYLOAD_START = "pl_strt";
constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "pl_stpa";
constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "pl_ret";
constexpr const char *const MQTT_PAYLOAD_STOP = "pl_stop";
constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "pl_toff";
constexpr const char *const MQTT_PAYLOAD_TURN_ON = "pl_ton";
constexpr const char *const MQTT_PAYLOAD_UNLOCK = "pl_unlk";
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "pct_cmd_tpl";
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "pct_cmd_t";
constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "pct_stat_t";
constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "pct_val_tpl";
constexpr const char *const MQTT_POSITION_CLOSED = "pos_clsd";
constexpr const char *const MQTT_POSITION_OPEN = "pos_open";
constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl";
constexpr const char *const MQTT_POSITION_TOPIC = "pos_t";
constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "pow_cmd_t";
constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t";
constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "pow_stat_tpl";
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t";
constexpr const char *const MQTT_POWER_STATE_TOPIC = "pow_stat_t";
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "pr_mode_cmd_tpl";
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "pr_mode_cmd_t";
constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "pr_mode_stat_t";
constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "pr_mode_val_tpl";
constexpr const char *const MQTT_PRESET_MODES = "pr_modes";
@ -188,36 +197,38 @@ constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off";
constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_spd_t";
constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_pos_tpl";
constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_pos_t";
constexpr const char *const MQTT_POSITION_TOPIC = "pos_t";
constexpr const char *const MQTT_POSITION_TEMPLATE = "pos_tpl";
constexpr const char *const MQTT_SOURCE_TYPE = "src_type";
constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "spd_cmd_t";
constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t";
constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min";
constexpr const char *const MQTT_SPEED_RANGE_MAX = "spd_rng_max";
constexpr const char *const MQTT_SPEED_RANGE_MIN = "spd_rng_min";
constexpr const char *const MQTT_SPEED_STATE_TOPIC = "spd_stat_t";
constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "spd_val_tpl";
constexpr const char *const MQTT_SPEEDS = "spds";
constexpr const char *const MQTT_SOURCE_TYPE = "src_type";
constexpr const char *const MQTT_STATE_CLASS = "stat_cla";
constexpr const char *const MQTT_STATE_CLOSED = "stat_clsd";
constexpr const char *const MQTT_STATE_CLOSING = "stat_closing";
constexpr const char *const MQTT_STATE_LOCKED = "stat_locked";
constexpr const char *const MQTT_STATE_OFF = "stat_off";
constexpr const char *const MQTT_STATE_ON = "stat_on";
constexpr const char *const MQTT_STATE_OPEN = "stat_open";
constexpr const char *const MQTT_STATE_OPENING = "stat_opening";
constexpr const char *const MQTT_STATE_STOPPED = "stat_stopped";
constexpr const char *const MQTT_STATE_LOCKED = "stat_locked";
constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked";
constexpr const char *const MQTT_STATE_TOPIC = "stat_t";
constexpr const char *const MQTT_STATE_TEMPLATE = "stat_tpl";
constexpr const char *const MQTT_STATE_TOPIC = "stat_t";
constexpr const char *const MQTT_STATE_UNLOCKED = "stat_unlocked";
constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "stat_val_tpl";
constexpr const char *const MQTT_STEP = "step";
constexpr const char *const MQTT_SUBTYPE = "stype";
constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat";
constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "sup_clrm";
constexpr const char *const MQTT_SUPPORTED_FEATURES = "sup_feat";
constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_cmd_tpl";
constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_cmd_t";
constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_stat_tpl";
constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_stat_t";
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "hum_cmd_tpl";
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "hum_cmd_t";
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "hum_state_tpl";
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "hum_stat_t";
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temp_cmd_tpl";
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temp_cmd_t";
constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temp_hi_cmd_tpl";
@ -232,15 +243,15 @@ constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temp_stat_tpl";
constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temp_stat_t";
constexpr const char *const MQTT_TEMPERATURE_UNIT = "temp_unit";
constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_clsd_val";
constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t";
constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_cmd_tpl";
constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_cmd_t";
constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_inv_stat";
constexpr const char *const MQTT_TILT_MAX = "tilt_max";
constexpr const char *const MQTT_TILT_MIN = "tilt_min";
constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opnd_val";
constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_opt";
constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t";
constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_tpl";
constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_t";
constexpr const char *const MQTT_TOPIC = "t";
constexpr const char *const MQTT_UNIQUE_ID = "uniq_id";
constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_meas";
@ -255,18 +266,10 @@ constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_cmd_t";
constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_stat_t";
constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_val_tpl";
constexpr const char *const MQTT_DEVICE_CONNECTIONS = "cns";
constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "ids";
constexpr const char *const MQTT_DEVICE_NAME = "name";
constexpr const char *const MQTT_DEVICE_MANUFACTURER = "mf";
constexpr const char *const MQTT_DEVICE_MODEL = "mdl";
constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw";
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "sa";
#else
constexpr const char *const MQTT_ACTION_TOPIC = "action_topic";
constexpr const char *const MQTT_ACTION_TEMPLATE = "action_template";
constexpr const char *const MQTT_ACTION_TOPIC = "action_topic";
constexpr const char *const MQTT_AUTOMATION_TYPE = "automation_type";
constexpr const char *const MQTT_AUX_COMMAND_TOPIC = "aux_command_topic";
constexpr const char *const MQTT_AUX_STATE_TEMPLATE = "aux_state_template";
@ -277,60 +280,70 @@ constexpr const char *const MQTT_AVAILABILITY_TOPIC = "availability_topic";
constexpr const char *const MQTT_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic";
constexpr const char *const MQTT_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template";
constexpr const char *const MQTT_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic";
constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template";
constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic";
constexpr const char *const MQTT_BLUE_TEMPLATE = "blue_template";
constexpr const char *const MQTT_BRIGHTNESS_COMMAND_TOPIC = "brightness_command_topic";
constexpr const char *const MQTT_BRIGHTNESS_SCALE = "brightness_scale";
constexpr const char *const MQTT_BRIGHTNESS_STATE_TOPIC = "brightness_state_topic";
constexpr const char *const MQTT_BRIGHTNESS_TEMPLATE = "brightness_template";
constexpr const char *const MQTT_BRIGHTNESS_VALUE_TEMPLATE = "brightness_value_template";
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template";
constexpr const char *const MQTT_BATTERY_LEVEL_TOPIC = "battery_level_topic";
constexpr const char *const MQTT_BATTERY_LEVEL_TEMPLATE = "battery_level_template";
constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url";
constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic";
constexpr const char *const MQTT_CHARGING_TEMPLATE = "charging_template";
constexpr const char *const MQTT_CHARGING_TOPIC = "charging_topic";
constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template";
constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic";
constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required";
constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required";
constexpr const char *const MQTT_COLOR_MODE = "color_mode";
constexpr const char *const MQTT_COLOR_MODE_STATE_TOPIC = "color_mode_state_topic";
constexpr const char *const MQTT_COLOR_MODE_VALUE_TEMPLATE = "color_mode_value_template";
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TEMPLATE = "color_temp_command_template";
constexpr const char *const MQTT_COLOR_TEMP_COMMAND_TOPIC = "color_temp_command_topic";
constexpr const char *const MQTT_COLOR_TEMP_STATE_TOPIC = "color_temp_state_topic";
constexpr const char *const MQTT_COLOR_TEMP_TEMPLATE = "color_temp_template";
constexpr const char *const MQTT_COLOR_TEMP_VALUE_TEMPLATE = "color_temp_value_template";
constexpr const char *const MQTT_CLEANING_TOPIC = "cleaning_topic";
constexpr const char *const MQTT_CLEANING_TEMPLATE = "cleaning_template";
constexpr const char *const MQTT_COMMAND_OFF_TEMPLATE = "command_off_template";
constexpr const char *const MQTT_COMMAND_ON_TEMPLATE = "command_on_template";
constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic";
constexpr const char *const MQTT_COMMAND_RETAIN = "retain";
constexpr const char *const MQTT_COMMAND_TEMPLATE = "command_template";
constexpr const char *const MQTT_CODE_ARM_REQUIRED = "code_arm_required";
constexpr const char *const MQTT_CODE_DISARM_REQUIRED = "code_disarm_required";
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic";
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template";
constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "current_humidity_topic";
constexpr const char *const MQTT_COMMAND_TOPIC = "command_topic";
constexpr const char *const MQTT_CONFIGURATION_URL = "configuration_url";
constexpr const char *const MQTT_CURRENT_HUMIDITY_TEMPLATE = "current_humidity_template";
constexpr const char *const MQTT_CURRENT_HUMIDITY_TOPIC = "current_humidity_topic";
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template";
constexpr const char *const MQTT_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic";
constexpr const char *const MQTT_DEVICE = "device";
constexpr const char *const MQTT_DEVICE_CLASS = "device_class";
constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic";
constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections";
constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers";
constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer";
constexpr const char *const MQTT_DEVICE_MODEL = "model";
constexpr const char *const MQTT_DEVICE_NAME = "name";
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area";
constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version";
constexpr const char *const MQTT_DOCKED_TEMPLATE = "docked_template";
constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default";
constexpr const char *const MQTT_ERROR_TOPIC = "error_topic";
constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template";
constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic";
constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template";
constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list";
constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long";
constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short";
constexpr const char *const MQTT_DOCKED_TOPIC = "docked_topic";
constexpr const char *const MQTT_EFFECT_COMMAND_TOPIC = "effect_command_topic";
constexpr const char *const MQTT_EFFECT_LIST = "effect_list";
constexpr const char *const MQTT_EFFECT_STATE_TOPIC = "effect_state_topic";
constexpr const char *const MQTT_EFFECT_TEMPLATE = "effect_template";
constexpr const char *const MQTT_EFFECT_VALUE_TEMPLATE = "effect_value_template";
constexpr const char *const MQTT_ENABLED_BY_DEFAULT = "enabled_by_default";
constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category";
constexpr const char *const MQTT_ERROR_TEMPLATE = "error_template";
constexpr const char *const MQTT_ERROR_TOPIC = "error_topic";
constexpr const char *const MQTT_EVENT_TYPE = "event_type";
constexpr const char *const MQTT_EVENT_TYPES = "event_types";
constexpr const char *const MQTT_EXPIRE_AFTER = "expire_after";
constexpr const char *const MQTT_FAN_MODE_COMMAND_TEMPLATE = "fan_mode_command_template";
constexpr const char *const MQTT_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic";
constexpr const char *const MQTT_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template";
constexpr const char *const MQTT_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic";
constexpr const char *const MQTT_FAN_SPEED_LIST = "fan_speed_list";
constexpr const char *const MQTT_FAN_SPEED_TEMPLATE = "fan_speed_template";
constexpr const char *const MQTT_FAN_SPEED_TOPIC = "fan_speed_topic";
constexpr const char *const MQTT_FLASH_TIME_LONG = "flash_time_long";
constexpr const char *const MQTT_FLASH_TIME_SHORT = "flash_time_short";
constexpr const char *const MQTT_FORCE_UPDATE = "force_update";
constexpr const char *const MQTT_GREEN_TEMPLATE = "green_template";
constexpr const char *const MQTT_HOLD_COMMAND_TEMPLATE = "hold_command_template";
@ -342,56 +355,49 @@ constexpr const char *const MQTT_HS_STATE_TOPIC = "hs_state_topic";
constexpr const char *const MQTT_HS_VALUE_TEMPLATE = "hs_value_template";
constexpr const char *const MQTT_ICON = "icon";
constexpr const char *const MQTT_INITIAL = "initial";
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic";
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template";
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic";
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template";
constexpr const char *const MQTT_JSON_ATTRIBUTES = "json_attributes";
constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic";
constexpr const char *const MQTT_JSON_ATTRIBUTES_TEMPLATE = "json_attributes_template";
constexpr const char *const MQTT_JSON_ATTRIBUTES_TOPIC = "json_attributes_topic";
constexpr const char *const MQTT_LAST_RESET_TOPIC = "last_reset_topic";
constexpr const char *const MQTT_LAST_RESET_VALUE_TEMPLATE = "last_reset_value_template";
constexpr const char *const MQTT_MAX = "max";
constexpr const char *const MQTT_MIN = "min";
constexpr const char *const MQTT_MAX_HUMIDITY = "max_humidity";
constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity";
constexpr const char *const MQTT_MAX_MIREDS = "max_mireds";
constexpr const char *const MQTT_MIN_MIREDS = "min_mireds";
constexpr const char *const MQTT_MAX_TEMP = "max_temp";
constexpr const char *const MQTT_MIN = "min";
constexpr const char *const MQTT_MIN_HUMIDITY = "min_humidity";
constexpr const char *const MQTT_MIN_MIREDS = "min_mireds";
constexpr const char *const MQTT_MIN_TEMP = "min_temp";
constexpr const char *const MQTT_MODE = "mode";
constexpr const char *const MQTT_MODE_COMMAND_TEMPLATE = "mode_command_template";
constexpr const char *const MQTT_MODE_COMMAND_TOPIC = "mode_command_topic";
constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic";
constexpr const char *const MQTT_MODE_STATE_TEMPLATE = "mode_state_template";
constexpr const char *const MQTT_MODE_STATE_TOPIC = "mode_state_topic";
constexpr const char *const MQTT_MODES = "modes";
constexpr const char *const MQTT_NAME = "name";
constexpr const char *const MQTT_OBJECT_ID = "object_id";
constexpr const char *const MQTT_OFF_DELAY = "off_delay";
constexpr const char *const MQTT_ON_COMMAND_TYPE = "on_command_type";
constexpr const char *const MQTT_OPTIONS = "options";
constexpr const char *const MQTT_OPTIMISTIC = "optimistic";
constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic";
constexpr const char *const MQTT_OPTIONS = "options";
constexpr const char *const MQTT_OSCILLATION_COMMAND_TEMPLATE = "oscillation_command_template";
constexpr const char *const MQTT_OSCILLATION_COMMAND_TOPIC = "oscillation_command_topic";
constexpr const char *const MQTT_OSCILLATION_STATE_TOPIC = "oscillation_state_topic";
constexpr const char *const MQTT_OSCILLATION_VALUE_TEMPLATE = "oscillation_value_template";
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic";
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template";
constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic";
constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template";
constexpr const char *const MQTT_PAYLOAD = "payload";
constexpr const char *const MQTT_PAYLOAD_ARM_AWAY = "payload_arm_away";
constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass";
constexpr const char *const MQTT_PAYLOAD_ARM_HOME = "payload_arm_home";
constexpr const char *const MQTT_PAYLOAD_ARM_NIGHT = "payload_arm_night";
constexpr const char *const MQTT_PAYLOAD_ARM_VACATION = "payload_arm_vacation";
constexpr const char *const MQTT_PAYLOAD_ARM_CUSTOM_BYPASS = "payload_arm_custom_bypass";
constexpr const char *const MQTT_PAYLOAD_AVAILABLE = "payload_available";
constexpr const char *const MQTT_PAYLOAD_CLEAN_SPOT = "payload_clean_spot";
constexpr const char *const MQTT_PAYLOAD_CLOSE = "payload_close";
constexpr const char *const MQTT_PAYLOAD_DISARM = "payload_disarm";
constexpr const char *const MQTT_PAYLOAD_HIGH_SPEED = "payload_high_speed";
constexpr const char *const MQTT_PAYLOAD_HOME = "payload_home";
constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock";
constexpr const char *const MQTT_PAYLOAD_LOCATE = "payload_locate";
constexpr const char *const MQTT_PAYLOAD_LOCK = "payload_lock";
constexpr const char *const MQTT_PAYLOAD_LOW_SPEED = "payload_low_speed";
constexpr const char *const MQTT_PAYLOAD_MEDIUM_SPEED = "payload_medium_speed";
constexpr const char *const MQTT_PAYLOAD_NOT_AVAILABLE = "payload_not_available";
@ -408,20 +414,26 @@ constexpr const char *const MQTT_PAYLOAD_RESET_HUMIDITY = "payload_reset_humidit
constexpr const char *const MQTT_PAYLOAD_RESET_MODE = "payload_reset_mode";
constexpr const char *const MQTT_PAYLOAD_RESET_PERCENTAGE = "payload_reset_percentage";
constexpr const char *const MQTT_PAYLOAD_RESET_PRESET_MODE = "payload_reset_preset_mode";
constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop";
constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base";
constexpr const char *const MQTT_PAYLOAD_START = "payload_start";
constexpr const char *const MQTT_PAYLOAD_START_PAUSE = "payload_start_pause";
constexpr const char *const MQTT_PAYLOAD_RETURN_TO_BASE = "payload_return_to_base";
constexpr const char *const MQTT_PAYLOAD_STOP = "payload_stop";
constexpr const char *const MQTT_PAYLOAD_TURN_OFF = "payload_turn_off";
constexpr const char *const MQTT_PAYLOAD_TURN_ON = "payload_turn_on";
constexpr const char *const MQTT_PAYLOAD_UNLOCK = "payload_unlock";
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TEMPLATE = "percentage_command_template";
constexpr const char *const MQTT_PERCENTAGE_COMMAND_TOPIC = "percentage_command_topic";
constexpr const char *const MQTT_PERCENTAGE_STATE_TOPIC = "percentage_state_topic";
constexpr const char *const MQTT_PERCENTAGE_VALUE_TEMPLATE = "percentage_value_template";
constexpr const char *const MQTT_POSITION_CLOSED = "position_closed";
constexpr const char *const MQTT_POSITION_OPEN = "position_open";
constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template";
constexpr const char *const MQTT_POSITION_TOPIC = "position_topic";
constexpr const char *const MQTT_POWER_COMMAND_TOPIC = "power_command_topic";
constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic";
constexpr const char *const MQTT_POWER_STATE_TEMPLATE = "power_state_template";
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic";
constexpr const char *const MQTT_POWER_STATE_TOPIC = "power_state_topic";
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TEMPLATE = "preset_mode_command_template";
constexpr const char *const MQTT_PRESET_MODE_COMMAND_TOPIC = "preset_mode_command_topic";
constexpr const char *const MQTT_PRESET_MODE_STATE_TOPIC = "preset_mode_state_topic";
constexpr const char *const MQTT_PRESET_MODE_VALUE_TEMPLATE = "preset_mode_value_template";
constexpr const char *const MQTT_PRESET_MODES = "preset_modes";
@ -444,36 +456,38 @@ constexpr const char *const MQTT_SEND_IF_OFF = "send_if_off";
constexpr const char *const MQTT_SET_FAN_SPEED_TOPIC = "set_fan_speed_topic";
constexpr const char *const MQTT_SET_POSITION_TEMPLATE = "set_position_template";
constexpr const char *const MQTT_SET_POSITION_TOPIC = "set_position_topic";
constexpr const char *const MQTT_POSITION_TOPIC = "position_topic";
constexpr const char *const MQTT_POSITION_TEMPLATE = "position_template";
constexpr const char *const MQTT_SOURCE_TYPE = "source_type";
constexpr const char *const MQTT_SPEED_COMMAND_TOPIC = "speed_command_topic";
constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic";
constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min";
constexpr const char *const MQTT_SPEED_RANGE_MAX = "speed_range_max";
constexpr const char *const MQTT_SPEED_RANGE_MIN = "speed_range_min";
constexpr const char *const MQTT_SPEED_STATE_TOPIC = "speed_state_topic";
constexpr const char *const MQTT_SPEED_VALUE_TEMPLATE = "speed_value_template";
constexpr const char *const MQTT_SPEEDS = "speeds";
constexpr const char *const MQTT_SOURCE_TYPE = "source_type";
constexpr const char *const MQTT_STATE_CLASS = "state_class";
constexpr const char *const MQTT_STATE_CLOSED = "state_closed";
constexpr const char *const MQTT_STATE_CLOSING = "state_closing";
constexpr const char *const MQTT_STATE_LOCKED = "state_locked";
constexpr const char *const MQTT_STATE_OFF = "state_off";
constexpr const char *const MQTT_STATE_ON = "state_on";
constexpr const char *const MQTT_STATE_OPEN = "state_open";
constexpr const char *const MQTT_STATE_OPENING = "state_opening";
constexpr const char *const MQTT_STATE_STOPPED = "state_stopped";
constexpr const char *const MQTT_STATE_LOCKED = "state_locked";
constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked";
constexpr const char *const MQTT_STATE_TOPIC = "state_topic";
constexpr const char *const MQTT_STATE_TEMPLATE = "state_template";
constexpr const char *const MQTT_STATE_TOPIC = "state_topic";
constexpr const char *const MQTT_STATE_UNLOCKED = "state_unlocked";
constexpr const char *const MQTT_STATE_VALUE_TEMPLATE = "state_value_template";
constexpr const char *const MQTT_STEP = "step";
constexpr const char *const MQTT_SUBTYPE = "subtype";
constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features";
constexpr const char *const MQTT_SUPPORTED_COLOR_MODES = "supported_color_modes";
constexpr const char *const MQTT_SUPPORTED_FEATURES = "supported_features";
constexpr const char *const MQTT_SWING_MODE_COMMAND_TEMPLATE = "swing_mode_command_template";
constexpr const char *const MQTT_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic";
constexpr const char *const MQTT_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template";
constexpr const char *const MQTT_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic";
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TEMPLATE = "target_humidity_command_template";
constexpr const char *const MQTT_TARGET_HUMIDITY_COMMAND_TOPIC = "target_humidity_command_topic";
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TEMPLATE = "target_humidity_state_template";
constexpr const char *const MQTT_TARGET_HUMIDITY_STATE_TOPIC = "target_humidity_state_topic";
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TEMPLATE = "temperature_command_template";
constexpr const char *const MQTT_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic";
constexpr const char *const MQTT_TEMPERATURE_HIGH_COMMAND_TEMPLATE = "temperature_high_command_template";
@ -488,15 +502,15 @@ constexpr const char *const MQTT_TEMPERATURE_STATE_TEMPLATE = "temperature_state
constexpr const char *const MQTT_TEMPERATURE_STATE_TOPIC = "temperature_state_topic";
constexpr const char *const MQTT_TEMPERATURE_UNIT = "temperature_unit";
constexpr const char *const MQTT_TILT_CLOSED_VALUE = "tilt_closed_value";
constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic";
constexpr const char *const MQTT_TILT_COMMAND_TEMPLATE = "tilt_command_template";
constexpr const char *const MQTT_TILT_COMMAND_TOPIC = "tilt_command_topic";
constexpr const char *const MQTT_TILT_INVERT_STATE = "tilt_invert_state";
constexpr const char *const MQTT_TILT_MAX = "tilt_max";
constexpr const char *const MQTT_TILT_MIN = "tilt_min";
constexpr const char *const MQTT_TILT_OPENED_VALUE = "tilt_opened_value";
constexpr const char *const MQTT_TILT_OPTIMISTIC = "tilt_optimistic";
constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic";
constexpr const char *const MQTT_TILT_STATUS_TEMPLATE = "tilt_status_template";
constexpr const char *const MQTT_TILT_STATUS_TOPIC = "tilt_status_topic";
constexpr const char *const MQTT_TOPIC = "topic";
constexpr const char *const MQTT_UNIQUE_ID = "unique_id";
constexpr const char *const MQTT_UNIT_OF_MEASUREMENT = "unit_of_measurement";
@ -511,19 +525,8 @@ constexpr const char *const MQTT_XY_COMMAND_TOPIC = "xy_command_topic";
constexpr const char *const MQTT_XY_STATE_TOPIC = "xy_state_topic";
constexpr const char *const MQTT_XY_VALUE_TEMPLATE = "xy_value_template";
constexpr const char *const MQTT_DEVICE_CONNECTIONS = "connections";
constexpr const char *const MQTT_DEVICE_IDENTIFIERS = "identifiers";
constexpr const char *const MQTT_DEVICE_NAME = "name";
constexpr const char *const MQTT_DEVICE_MANUFACTURER = "manufacturer";
constexpr const char *const MQTT_DEVICE_MODEL = "model";
constexpr const char *const MQTT_DEVICE_SW_VERSION = "sw_version";
constexpr const char *const MQTT_DEVICE_SUGGESTED_AREA = "suggested_area";
#endif
// Additional MQTT fields where no abbreviation is defined in HA source
constexpr const char *const MQTT_ENTITY_CATEGORY = "entity_category";
constexpr const char *const MQTT_MODE = "mode";
} // namespace mqtt
} // namespace esphome

View File

@ -0,0 +1,54 @@
#include "mqtt_event.h"
#include "esphome/core/log.h"
#include "mqtt_const.h"
#ifdef USE_MQTT
#ifdef USE_EVENT
namespace esphome {
namespace mqtt {
static const char *const TAG = "mqtt.event";
using namespace esphome::event;
MQTTEventComponent::MQTTEventComponent(event::Event *event) : event_(event) {}
void MQTTEventComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
JsonArray event_types = root.createNestedArray(MQTT_EVENT_TYPES);
for (const auto &event_type : this->event_->get_event_types())
event_types.add(event_type);
if (!this->event_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->event_->get_device_class();
config.command_topic = false;
}
void MQTTEventComponent::setup() {
this->event_->add_on_event_callback([this](const std::string &event_type) { this->publish_event_(event_type); });
}
void MQTTEventComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MQTT Event '%s': ", this->event_->get_name().c_str());
ESP_LOGCONFIG(TAG, "Event Types: ");
for (const auto &event_type : this->event_->get_event_types()) {
ESP_LOGCONFIG(TAG, "- %s", event_type.c_str());
}
LOG_MQTT_COMPONENT(true, true);
}
bool MQTTEventComponent::publish_event_(const std::string &event_type) {
return this->publish_json(this->get_state_topic_(),
[event_type](JsonObject root) { root[MQTT_EVENT_TYPE] = event_type; });
}
std::string MQTTEventComponent::component_type() const { return "event"; }
const EntityBase *MQTTEventComponent::get_entity() const { return this->event_; }
} // namespace mqtt
} // namespace esphome
#endif
#endif // USE_MQTT

View File

@ -0,0 +1,39 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_MQTT
#ifdef USE_EVENT
#include "esphome/components/event/event.h"
#include "mqtt_component.h"
namespace esphome {
namespace mqtt {
class MQTTEventComponent : public mqtt::MQTTComponent {
public:
explicit MQTTEventComponent(event::Event *event);
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
void setup() override;
void dump_config() override;
/// Events do not send a state so just return true.
bool send_initial_state() override { return true; }
protected:
bool publish_event_(const std::string &event_type);
std::string component_type() const override;
const EntityBase *get_entity() const override;
event::Event *event_;
};
} // namespace mqtt
} // namespace esphome
#endif
#endif // USE_MQTT

View File

@ -0,0 +1,90 @@
#include "mqtt_valve.h"
#include "esphome/core/log.h"
#include "mqtt_const.h"
#ifdef USE_MQTT
#ifdef USE_VALVE
namespace esphome {
namespace mqtt {
static const char *const TAG = "mqtt.valve";
using namespace esphome::valve;
MQTTValveComponent::MQTTValveComponent(Valve *valve) : valve_(valve) {}
void MQTTValveComponent::setup() {
auto traits = this->valve_->get_traits();
this->valve_->add_on_state_callback([this]() { this->publish_state(); });
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
auto call = this->valve_->make_call();
call.set_command(payload.c_str());
call.perform();
});
if (traits.get_supports_position()) {
this->subscribe(this->get_position_command_topic(), [this](const std::string &topic, const std::string &payload) {
auto value = parse_number<float>(payload);
if (!value.has_value()) {
ESP_LOGW(TAG, "Invalid position value: '%s'", payload.c_str());
return;
}
auto call = this->valve_->make_call();
call.set_position(*value / 100.0f);
call.perform();
});
}
}
void MQTTValveComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MQTT valve '%s':", this->valve_->get_name().c_str());
auto traits = this->valve_->get_traits();
bool has_command_topic = traits.get_supports_position();
LOG_MQTT_COMPONENT(true, has_command_topic)
if (traits.get_supports_position()) {
ESP_LOGCONFIG(TAG, " Position State Topic: '%s'", this->get_position_state_topic().c_str());
ESP_LOGCONFIG(TAG, " Position Command Topic: '%s'", this->get_position_command_topic().c_str());
}
}
void MQTTValveComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
if (!this->valve_->get_device_class().empty())
root[MQTT_DEVICE_CLASS] = this->valve_->get_device_class();
auto traits = this->valve_->get_traits();
if (traits.get_is_assumed_state()) {
root[MQTT_OPTIMISTIC] = true;
}
if (traits.get_supports_position()) {
root[MQTT_POSITION_TOPIC] = this->get_position_state_topic();
root[MQTT_SET_POSITION_TOPIC] = this->get_position_command_topic();
}
}
std::string MQTTValveComponent::component_type() const { return "valve"; }
const EntityBase *MQTTValveComponent::get_entity() const { return this->valve_; }
bool MQTTValveComponent::send_initial_state() { return this->publish_state(); }
bool MQTTValveComponent::publish_state() {
auto traits = this->valve_->get_traits();
bool success = true;
if (traits.get_supports_position()) {
std::string pos = value_accuracy_to_string(roundf(this->valve_->position * 100), 0);
if (!this->publish(this->get_position_state_topic(), pos))
success = false;
}
const char *state_s = this->valve_->current_operation == VALVE_OPERATION_OPENING ? "opening"
: this->valve_->current_operation == VALVE_OPERATION_CLOSING ? "closing"
: this->valve_->position == VALVE_CLOSED ? "closed"
: this->valve_->position == VALVE_OPEN ? "open"
: traits.get_supports_position() ? "open"
: "unknown";
if (!this->publish(this->get_state_topic_(), state_s))
success = false;
return success;
}
} // namespace mqtt
} // namespace esphome
#endif
#endif // USE_MQTT

View File

@ -0,0 +1,41 @@
#pragma once
#include "esphome/core/defines.h"
#include "mqtt_component.h"
#ifdef USE_MQTT
#ifdef USE_VALVE
#include "esphome/components/valve/valve.h"
namespace esphome {
namespace mqtt {
class MQTTValveComponent : public mqtt::MQTTComponent {
public:
explicit MQTTValveComponent(valve::Valve *valve);
void setup() override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
MQTT_COMPONENT_CUSTOM_TOPIC(position, command)
MQTT_COMPONENT_CUSTOM_TOPIC(position, state)
bool send_initial_state() override;
bool publish_state();
void dump_config() override;
protected:
std::string component_type() const override;
const EntityBase *get_entity() const override;
valve::Valve *valve_;
};
} // namespace mqtt
} // namespace esphome
#endif
#endif // USE_MQTT

View File

@ -359,11 +359,15 @@ OrFilter::OrFilter(std::vector<Filter *> filters) : filters_(std::move(filters))
OrFilter::PhiNode::PhiNode(OrFilter *or_parent) : or_parent_(or_parent) {}
optional<float> OrFilter::PhiNode::new_value(float value) {
this->or_parent_->output(value);
if (!this->or_parent_->has_value_) {
this->or_parent_->output(value);
this->or_parent_->has_value_ = true;
}
return {};
}
optional<float> OrFilter::new_value(float value) {
this->has_value_ = false;
for (Filter *filter : this->filters_)
filter->input(value);

View File

@ -388,6 +388,7 @@ class OrFilter : public Filter {
};
std::vector<Filter *> filters_;
bool has_value_{false};
PhiNode phi_;
};

View File

@ -6,9 +6,14 @@ namespace sht3xd {
static const char *const TAG = "sht3xd";
// use read serial number register with clock stretching disabled as per other SHT3XD_COMMAND registers
// which provides support for SHT85 sensor
// SHT85 does not support clock stretching and uses same registers as SHT3xd with clock stretching disabled
// https://sensirion.com/media/documents/E5762713/63D103C2/Sensirion_electronic_identification_code_SHT3x.pdf
// indicates two possible read serial number registers either with clock stretching enabled or disabled.
// Other SHT3XD_COMMAND registers use the clock stretching disabled register.
// To ensure compatibility, reading serial number using the register with clock stretching register enabled
// (used originally in this component) is tried first and if that fails the alternate register address
// with clock stretching disabled is read.
static const uint16_t SHT3XD_COMMAND_READ_SERIAL_NUMBER_CLOCK_STRETCHING = 0x3780;
static const uint16_t SHT3XD_COMMAND_READ_SERIAL_NUMBER = 0x3682;
static const uint16_t SHT3XD_COMMAND_READ_STATUS = 0xF32D;
@ -22,13 +27,19 @@ static const uint16_t SHT3XD_COMMAND_FETCH_DATA = 0xE000;
void SHT3XDComponent::setup() {
ESP_LOGCONFIG(TAG, "Setting up SHT3xD...");
uint16_t raw_serial_number[2];
if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) {
this->mark_failed();
return;
if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER_CLOCK_STRETCHING, raw_serial_number, 2)) {
this->error_code_ = READ_SERIAL_STRETCHED_FAILED;
if (!this->get_register(SHT3XD_COMMAND_READ_SERIAL_NUMBER, raw_serial_number, 2)) {
this->error_code_ = READ_SERIAL_FAILED;
this->mark_failed();
return;
}
}
this->serial_number_ = (uint32_t(raw_serial_number[0]) << 16) | uint32_t(raw_serial_number[1]);
if (!this->write_command(heater_enabled_ ? SHT3XD_COMMAND_HEATER_ENABLE : SHT3XD_COMMAND_HEATER_DISABLE)) {
this->error_code_ = WRITE_HEATER_MODE_FAILED;
this->mark_failed();
return;
}
@ -36,10 +47,21 @@ void SHT3XDComponent::setup() {
void SHT3XDComponent::dump_config() {
ESP_LOGCONFIG(TAG, "SHT3xD:");
switch (this->error_code_) {
case READ_SERIAL_FAILED:
ESP_LOGD(TAG, " Error reading serial number");
break;
case WRITE_HEATER_MODE_FAILED:
ESP_LOGD(TAG, " Error writing heater mode");
break;
default:
break;
}
if (this->is_failed()) {
ESP_LOGE(TAG, " Communication with SHT3xD failed!");
return;
}
ESP_LOGD(TAG, " Setup successful");
ESP_LOGD(TAG, " Serial Number: 0x%08" PRIX32, this->serial_number_);
ESP_LOGD(TAG, " Heater Enabled: %s", this->heater_enabled_ ? "true" : "false");

View File

@ -22,6 +22,13 @@ class SHT3XDComponent : public PollingComponent, public sensirion_common::Sensir
void set_heater_enabled(bool heater_enabled) { heater_enabled_ = heater_enabled; }
protected:
enum ErrorCode {
NONE = 0,
READ_SERIAL_STRETCHED_FAILED,
READ_SERIAL_FAILED,
WRITE_HEATER_MODE_FAILED,
} error_code_{NONE};
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *humidity_sensor_{nullptr};
bool heater_enabled_{true};

View File

@ -4,14 +4,13 @@ from esphome import pins
from esphome.components import spi
from esphome.const import (
CONF_ID,
CONF_SPI_ID,
CONF_NUMBER,
CONF_INVERTED,
CONF_DATA_PIN,
CONF_CLOCK_PIN,
CONF_OUTPUT,
CONF_TYPE,
)
from esphome.core import EsphomeError
MULTI_CONF = True
@ -34,53 +33,53 @@ CONF_LATCH_PIN = "latch_pin"
CONF_OE_PIN = "oe_pin"
CONF_SR_COUNT = "sr_count"
CONFIG_SCHEMA = cv.Any(
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(SN74HC595GPIOComponent),
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_LATCH_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_SR_COUNT, default=1): cv.int_range(min=1, max=256),
}
).extend(cv.COMPONENT_SCHEMA),
cv.Schema(
{
cv.Required(CONF_ID): cv.declare_id(SN74HC595SPIComponent),
cv.Required(CONF_LATCH_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_SR_COUNT, default=1): cv.int_range(min=1, max=256),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(spi.spi_device_schema(cs_pin_required=False))
.extend(
{
cv.Required(CONF_SPI_ID): cv.use_id(spi.SPIComponent),
}
),
msg='Either "data_pin" and "clock_pin" must be set or "spi_id" must be set.',
TYPE_GPIO = "gpio"
TYPE_SPI = "spi"
_COMMON_SCHEMA = cv.Schema(
{
cv.Required(CONF_LATCH_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_OE_PIN): pins.gpio_output_pin_schema,
cv.Optional(CONF_SR_COUNT, default=1): cv.int_range(min=1, max=256),
}
)
CONFIG_SCHEMA = cv.typed_schema(
{
TYPE_GPIO: _COMMON_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(SN74HC595GPIOComponent),
cv.Required(CONF_DATA_PIN): pins.gpio_output_pin_schema,
cv.Required(CONF_CLOCK_PIN): pins.gpio_output_pin_schema,
}
).extend(cv.COMPONENT_SCHEMA),
TYPE_SPI: _COMMON_SCHEMA.extend(
{
cv.Required(CONF_ID): cv.declare_id(SN74HC595SPIComponent),
}
)
.extend(cv.COMPONENT_SCHEMA)
.extend(spi.spi_device_schema(cs_pin_required=False)),
},
default_type=TYPE_GPIO,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_DATA_PIN in config:
if config[CONF_TYPE] == TYPE_GPIO:
data_pin = await cg.gpio_pin_expression(config[CONF_DATA_PIN])
cg.add(var.set_data_pin(data_pin))
clock_pin = await cg.gpio_pin_expression(config[CONF_CLOCK_PIN])
cg.add(var.set_clock_pin(clock_pin))
elif CONF_SPI_ID in config:
await spi.register_spi_device(var, config)
else:
raise EsphomeError("Not supported")
await spi.register_spi_device(var, config)
latch_pin = await cg.gpio_pin_expression(config[CONF_LATCH_PIN])
cg.add(var.set_latch_pin(latch_pin))
if CONF_OE_PIN in config:
oe_pin = await cg.gpio_pin_expression(config[CONF_OE_PIN])
if oe_pin := config.get(CONF_OE_PIN):
oe_pin = await cg.gpio_pin_expression(oe_pin)
cg.add(var.set_oe_pin(oe_pin))
cg.add(var.set_sr_count(config[CONF_SR_COUNT]))

View File

@ -0,0 +1,24 @@
import esphome.config_validation as cv
from esphome.components import event
import esphome.codegen as cg
from esphome.const import CONF_EVENT_TYPES
from .. import template_ns
CODEOWNERS = ["@nohat"]
TemplateEvent = template_ns.class_("TemplateEvent", event.Event, cg.Component)
CONFIG_SCHEMA = event.event_schema(TemplateEvent).extend(
{
cv.Required(CONF_EVENT_TYPES): cv.ensure_list(cv.string_strict),
}
)
async def to_code(config):
var = await event.new_event(config, event_types=config[CONF_EVENT_TYPES])
await cg.register_component(var, config)

View File

@ -0,0 +1,12 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/event/event.h"
namespace esphome {
namespace template_ {
class TemplateEvent : public Component, public event::Event {};
} // namespace template_
} // namespace esphome

View File

@ -0,0 +1,118 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.components import valve
from esphome.const import (
CONF_ASSUMED_STATE,
CONF_CLOSE_ACTION,
CONF_CURRENT_OPERATION,
CONF_ID,
CONF_LAMBDA,
CONF_OPEN_ACTION,
CONF_OPTIMISTIC,
CONF_POSITION,
CONF_POSITION_ACTION,
CONF_RESTORE_MODE,
CONF_STATE,
CONF_STOP_ACTION,
)
from .. import template_ns
TemplateValve = template_ns.class_("TemplateValve", valve.Valve, cg.Component)
TemplateValveRestoreMode = template_ns.enum("TemplateValveRestoreMode")
RESTORE_MODES = {
"NO_RESTORE": TemplateValveRestoreMode.VALVE_NO_RESTORE,
"RESTORE": TemplateValveRestoreMode.VALVE_RESTORE,
"RESTORE_AND_CALL": TemplateValveRestoreMode.VALVE_RESTORE_AND_CALL,
}
CONF_HAS_POSITION = "has_position"
CONF_TOGGLE_ACTION = "toggle_action"
CONFIG_SCHEMA = valve.VALVE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(TemplateValve),
cv.Optional(CONF_LAMBDA): cv.returning_lambda,
cv.Optional(CONF_OPTIMISTIC, default=False): cv.boolean,
cv.Optional(CONF_ASSUMED_STATE, default=False): cv.boolean,
cv.Optional(CONF_HAS_POSITION, default=False): cv.boolean,
cv.Optional(CONF_OPEN_ACTION): automation.validate_automation(single=True),
cv.Optional(CONF_CLOSE_ACTION): automation.validate_automation(single=True),
cv.Optional(CONF_STOP_ACTION): automation.validate_automation(single=True),
cv.Optional(CONF_TOGGLE_ACTION): automation.validate_automation(single=True),
cv.Optional(CONF_POSITION_ACTION): automation.validate_automation(single=True),
cv.Optional(CONF_RESTORE_MODE, default="NO_RESTORE"): cv.enum(
RESTORE_MODES, upper=True
),
}
).extend(cv.COMPONENT_SCHEMA)
async def to_code(config):
var = await valve.new_valve(config)
await cg.register_component(var, config)
if lambda_config := config.get(CONF_LAMBDA):
template_ = await cg.process_lambda(
lambda_config, [], return_type=cg.optional.template(float)
)
cg.add(var.set_state_lambda(template_))
if open_action_config := config.get(CONF_OPEN_ACTION):
await automation.build_automation(
var.get_open_trigger(), [], open_action_config
)
if close_action_config := config.get(CONF_CLOSE_ACTION):
await automation.build_automation(
var.get_close_trigger(), [], close_action_config
)
if stop_action_config := config.get(CONF_STOP_ACTION):
await automation.build_automation(
var.get_stop_trigger(), [], stop_action_config
)
cg.add(var.set_has_stop(True))
if toggle_action_config := config.get(CONF_TOGGLE_ACTION):
await automation.build_automation(
var.get_toggle_trigger(), [], toggle_action_config
)
cg.add(var.set_has_toggle(True))
if position_action_config := config.get(CONF_POSITION_ACTION):
await automation.build_automation(
var.get_position_trigger(), [(float, "pos")], position_action_config
)
cg.add(var.set_has_position(True))
else:
cg.add(var.set_has_position(config[CONF_HAS_POSITION]))
cg.add(var.set_optimistic(config[CONF_OPTIMISTIC]))
cg.add(var.set_assumed_state(config[CONF_ASSUMED_STATE]))
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
@automation.register_action(
"valve.template.publish",
valve.ValvePublishAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(valve.Valve),
cv.Exclusive(CONF_STATE, "pos"): cv.templatable(valve.validate_valve_state),
cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.percentage),
cv.Optional(CONF_CURRENT_OPERATION): cv.templatable(
valve.validate_valve_operation
),
}
),
)
async def valve_template_publish_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if state_config := config.get(CONF_STATE):
template_ = await cg.templatable(state_config, args, float)
cg.add(var.set_position(template_))
if (position_config := config.get(CONF_POSITION)) is not None:
template_ = await cg.templatable(position_config, args, float)
cg.add(var.set_position(template_))
if current_operation_config := config.get(CONF_CURRENT_OPERATION):
template_ = await cg.templatable(
current_operation_config, args, valve.ValveOperation
)
cg.add(var.set_current_operation(template_))
return var

View File

@ -0,0 +1,131 @@
#include "template_valve.h"
#include "esphome/core/log.h"
namespace esphome {
namespace template_ {
using namespace esphome::valve;
static const char *const TAG = "template.valve";
TemplateValve::TemplateValve()
: open_trigger_(new Trigger<>()),
close_trigger_(new Trigger<>),
stop_trigger_(new Trigger<>()),
toggle_trigger_(new Trigger<>()),
position_trigger_(new Trigger<float>()) {}
void TemplateValve::setup() {
ESP_LOGCONFIG(TAG, "Setting up template valve '%s'...", this->name_.c_str());
switch (this->restore_mode_) {
case VALVE_NO_RESTORE:
break;
case VALVE_RESTORE: {
auto restore = this->restore_state_();
if (restore.has_value())
restore->apply(this);
break;
}
case VALVE_RESTORE_AND_CALL: {
auto restore = this->restore_state_();
if (restore.has_value()) {
restore->to_call(this).perform();
}
break;
}
}
}
void TemplateValve::loop() {
bool changed = false;
if (this->state_f_.has_value()) {
auto s = (*this->state_f_)();
if (s.has_value()) {
auto pos = clamp(*s, 0.0f, 1.0f);
if (pos != this->position) {
this->position = pos;
changed = true;
}
}
}
if (changed)
this->publish_state();
}
void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
void TemplateValve::set_state_lambda(std::function<optional<float>()> &&f) { this->state_f_ = f; }
float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; }
Trigger<> *TemplateValve::get_close_trigger() const { return this->close_trigger_; }
Trigger<> *TemplateValve::get_stop_trigger() const { return this->stop_trigger_; }
Trigger<> *TemplateValve::get_toggle_trigger() const { return this->toggle_trigger_; }
void TemplateValve::dump_config() {
LOG_VALVE("", "Template Valve", this);
ESP_LOGCONFIG(TAG, " Has position: %s", YESNO(this->has_position_));
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
}
void TemplateValve::control(const ValveCall &call) {
if (call.get_stop()) {
this->stop_prev_trigger_();
this->stop_trigger_->trigger();
this->prev_command_trigger_ = this->stop_trigger_;
this->publish_state();
}
if (call.get_toggle().has_value()) {
this->stop_prev_trigger_();
this->toggle_trigger_->trigger();
this->prev_command_trigger_ = this->toggle_trigger_;
this->publish_state();
}
if (call.get_position().has_value()) {
auto pos = *call.get_position();
this->stop_prev_trigger_();
if (pos == VALVE_OPEN) {
this->open_trigger_->trigger();
this->prev_command_trigger_ = this->open_trigger_;
} else if (pos == VALVE_CLOSED) {
this->close_trigger_->trigger();
this->prev_command_trigger_ = this->close_trigger_;
} else {
this->position_trigger_->trigger(pos);
}
if (this->optimistic_) {
this->position = pos;
}
}
this->publish_state();
}
ValveTraits TemplateValve::get_traits() {
auto traits = ValveTraits();
traits.set_is_assumed_state(this->assumed_state_);
traits.set_supports_stop(this->has_stop_);
traits.set_supports_toggle(this->has_toggle_);
traits.set_supports_position(this->has_position_);
return traits;
}
Trigger<float> *TemplateValve::get_position_trigger() const { return this->position_trigger_; }
void TemplateValve::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
void TemplateValve::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
void TemplateValve::set_has_position(bool has_position) { this->has_position_ = has_position; }
void TemplateValve::stop_prev_trigger_() {
if (this->prev_command_trigger_ != nullptr) {
this->prev_command_trigger_->stop_action();
this->prev_command_trigger_ = nullptr;
}
}
} // namespace template_
} // namespace esphome

View File

@ -0,0 +1,60 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/valve/valve.h"
namespace esphome {
namespace template_ {
enum TemplateValveRestoreMode {
VALVE_NO_RESTORE,
VALVE_RESTORE,
VALVE_RESTORE_AND_CALL,
};
class TemplateValve : public valve::Valve, public Component {
public:
TemplateValve();
void set_state_lambda(std::function<optional<float>()> &&f);
Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger() const;
Trigger<> *get_stop_trigger() const;
Trigger<> *get_toggle_trigger() const;
Trigger<float> *get_position_trigger() const;
void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state);
void set_has_stop(bool has_stop);
void set_has_position(bool has_position);
void set_has_toggle(bool has_toggle);
void set_restore_mode(TemplateValveRestoreMode restore_mode) { restore_mode_ = restore_mode; }
void setup() override;
void loop() override;
void dump_config() override;
float get_setup_priority() const override;
protected:
void control(const valve::ValveCall &call) override;
valve::ValveTraits get_traits() override;
void stop_prev_trigger_();
TemplateValveRestoreMode restore_mode_{VALVE_NO_RESTORE};
optional<std::function<optional<float>()>> state_f_;
bool assumed_state_{false};
bool optimistic_{false};
Trigger<> *open_trigger_;
Trigger<> *close_trigger_;
bool has_stop_{false};
bool has_toggle_{false};
Trigger<> *stop_trigger_;
Trigger<> *toggle_trigger_;
Trigger<> *prev_command_trigger_{nullptr};
Trigger<float> *position_trigger_;
bool has_position_{false};
};
} // namespace template_
} // namespace esphome

View File

@ -59,6 +59,7 @@ UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action)
UARTDummyReceiver = uart_ns.class_("UARTDummyReceiver", cg.Component)
MULTI_CONF = True
MULTI_CONF_NO_DEFAULT = True
def validate_raw_data(value):

View File

@ -0,0 +1,186 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import automation
from esphome.automation import maybe_simple_id, Condition
from esphome.components import mqtt
from esphome.const import (
CONF_DEVICE_CLASS,
CONF_ID,
CONF_MQTT_ID,
CONF_ON_OPEN,
CONF_POSITION,
CONF_POSITION_COMMAND_TOPIC,
CONF_POSITION_STATE_TOPIC,
CONF_STATE,
CONF_STOP,
CONF_TRIGGER_ID,
)
from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_helpers import setup_entity
IS_PLATFORM_COMPONENT = True
CODEOWNERS = ["@esphome/core"]
valve_ns = cg.esphome_ns.namespace("valve")
Valve = valve_ns.class_("Valve", cg.EntityBase)
VALVE_OPEN = valve_ns.VALVE_OPEN
VALVE_CLOSED = valve_ns.VALVE_CLOSED
VALVE_STATES = {
"OPEN": VALVE_OPEN,
"CLOSED": VALVE_CLOSED,
}
validate_valve_state = cv.enum(VALVE_STATES, upper=True)
ValveOperation = valve_ns.enum("ValveOperation")
VALVE_OPERATIONS = {
"IDLE": ValveOperation.VALVE_OPERATION_IDLE,
"OPENING": ValveOperation.VALVE_OPERATION_OPENING,
"CLOSING": ValveOperation.VALVE_OPERATION_CLOSING,
}
validate_valve_operation = cv.enum(VALVE_OPERATIONS, upper=True)
# Actions
OpenAction = valve_ns.class_("OpenAction", automation.Action)
CloseAction = valve_ns.class_("CloseAction", automation.Action)
StopAction = valve_ns.class_("StopAction", automation.Action)
ToggleAction = valve_ns.class_("ToggleAction", automation.Action)
ControlAction = valve_ns.class_("ControlAction", automation.Action)
ValvePublishAction = valve_ns.class_("ValvePublishAction", automation.Action)
ValveIsOpenCondition = valve_ns.class_("ValveIsOpenCondition", Condition)
ValveIsClosedCondition = valve_ns.class_("ValveIsClosedCondition", Condition)
# Triggers
ValveOpenTrigger = valve_ns.class_("ValveOpenTrigger", automation.Trigger.template())
ValveClosedTrigger = valve_ns.class_(
"ValveClosedTrigger", automation.Trigger.template()
)
CONF_ON_CLOSED = "on_closed"
VALVE_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA).extend(
{
cv.GenerateID(): cv.declare_id(Valve),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTValveComponent),
cv.Optional(CONF_POSITION_COMMAND_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_POSITION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.subscribe_topic
),
cv.Optional(CONF_ON_OPEN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveOpenTrigger),
}
),
cv.Optional(CONF_ON_CLOSED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValveClosedTrigger),
}
),
}
)
async def setup_valve_core_(var, config):
await setup_entity(var, config)
if device_class_config := config.get(CONF_DEVICE_CLASS):
cg.add(var.set_device_class(device_class_config))
for conf in config.get(CONF_ON_OPEN, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_CLOSED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if mqtt_id_config := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id_config, var)
await mqtt.register_mqtt_component(mqtt_, config)
if position_state_topic_config := config.get(CONF_POSITION_STATE_TOPIC):
cg.add(mqtt_.set_custom_position_state_topic(position_state_topic_config))
if position_command_topic_config := config.get(CONF_POSITION_COMMAND_TOPIC):
cg.add(
mqtt_.set_custom_position_command_topic(position_command_topic_config)
)
async def register_valve(var, config):
if not CORE.has_id(config[CONF_ID]):
var = cg.Pvariable(config[CONF_ID], var)
cg.add(cg.App.register_valve(var))
await setup_valve_core_(var, config)
async def new_valve(config, *args):
var = cg.new_Pvariable(config[CONF_ID], *args)
await register_valve(var, config)
return var
VALVE_ACTION_SCHEMA = maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(Valve),
}
)
@automation.register_action("valve.open", OpenAction, VALVE_ACTION_SCHEMA)
async def valve_open_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action("valve.close", CloseAction, VALVE_ACTION_SCHEMA)
async def valve_close_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action("valve.stop", StopAction, VALVE_ACTION_SCHEMA)
async def valve_stop_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
return cg.new_Pvariable(action_id, template_arg, paren)
@automation.register_action("valve.toggle", ToggleAction, VALVE_ACTION_SCHEMA)
def valve_toggle_to_code(config, action_id, template_arg, args):
paren = yield cg.get_variable(config[CONF_ID])
yield cg.new_Pvariable(action_id, template_arg, paren)
VALVE_CONTROL_ACTION_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(Valve),
cv.Optional(CONF_STOP): cv.templatable(cv.boolean),
cv.Exclusive(CONF_STATE, "pos"): cv.templatable(validate_valve_state),
cv.Exclusive(CONF_POSITION, "pos"): cv.templatable(cv.percentage),
}
)
@automation.register_action("valve.control", ControlAction, VALVE_CONTROL_ACTION_SCHEMA)
async def valve_control_to_code(config, action_id, template_arg, args):
paren = await cg.get_variable(config[CONF_ID])
var = cg.new_Pvariable(action_id, template_arg, paren)
if stop_config := config.get(CONF_STOP):
template_ = await cg.templatable(stop_config, args, bool)
cg.add(var.set_stop(template_))
if state_config := config.get(CONF_STATE):
template_ = await cg.templatable(state_config, args, float)
cg.add(var.set_position(template_))
if (position_config := config.get(CONF_POSITION)) is not None:
template_ = await cg.templatable(position_config, args, float)
cg.add(var.set_position(template_))
return var
@coroutine_with_priority(100.0)
async def to_code(config):
cg.add_define("USE_VALVE")
cg.add_global(valve_ns.using)

View File

@ -0,0 +1,129 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "valve.h"
namespace esphome {
namespace valve {
template<typename... Ts> class OpenAction : public Action<Ts...> {
public:
explicit OpenAction(Valve *valve) : valve_(valve) {}
void play(Ts... x) override { this->valve_->make_call().set_command_open().perform(); }
protected:
Valve *valve_;
};
template<typename... Ts> class CloseAction : public Action<Ts...> {
public:
explicit CloseAction(Valve *valve) : valve_(valve) {}
void play(Ts... x) override { this->valve_->make_call().set_command_close().perform(); }
protected:
Valve *valve_;
};
template<typename... Ts> class StopAction : public Action<Ts...> {
public:
explicit StopAction(Valve *valve) : valve_(valve) {}
void play(Ts... x) override { this->valve_->make_call().set_command_stop().perform(); }
protected:
Valve *valve_;
};
template<typename... Ts> class ToggleAction : public Action<Ts...> {
public:
explicit ToggleAction(Valve *valve) : valve_(valve) {}
void play(Ts... x) override { this->valve_->make_call().set_command_toggle().perform(); }
protected:
Valve *valve_;
};
template<typename... Ts> class ControlAction : public Action<Ts...> {
public:
explicit ControlAction(Valve *valve) : valve_(valve) {}
TEMPLATABLE_VALUE(bool, stop)
TEMPLATABLE_VALUE(float, position)
void play(Ts... x) override {
auto call = this->valve_->make_call();
if (this->stop_.has_value())
call.set_stop(this->stop_.value(x...));
if (this->position_.has_value())
call.set_position(this->position_.value(x...));
call.perform();
}
protected:
Valve *valve_;
};
template<typename... Ts> class ValvePublishAction : public Action<Ts...> {
public:
ValvePublishAction(Valve *valve) : valve_(valve) {}
TEMPLATABLE_VALUE(float, position)
TEMPLATABLE_VALUE(ValveOperation, current_operation)
void play(Ts... x) override {
if (this->position_.has_value())
this->valve_->position = this->position_.value(x...);
if (this->current_operation_.has_value())
this->valve_->current_operation = this->current_operation_.value(x...);
this->valve_->publish_state();
}
protected:
Valve *valve_;
};
template<typename... Ts> class ValveIsOpenCondition : public Condition<Ts...> {
public:
ValveIsOpenCondition(Valve *valve) : valve_(valve) {}
bool check(Ts... x) override { return this->valve_->is_fully_open(); }
protected:
Valve *valve_;
};
template<typename... Ts> class ValveIsClosedCondition : public Condition<Ts...> {
public:
ValveIsClosedCondition(Valve *valve) : valve_(valve) {}
bool check(Ts... x) override { return this->valve_->is_fully_closed(); }
protected:
Valve *valve_;
};
class ValveOpenTrigger : public Trigger<> {
public:
ValveOpenTrigger(Valve *a_valve) {
a_valve->add_on_state_callback([this, a_valve]() {
if (a_valve->is_fully_open()) {
this->trigger();
}
});
}
};
class ValveClosedTrigger : public Trigger<> {
public:
ValveClosedTrigger(Valve *a_valve) {
a_valve->add_on_state_callback([this, a_valve]() {
if (a_valve->is_fully_closed()) {
this->trigger();
}
});
}
};
} // namespace valve
} // namespace esphome

View File

@ -0,0 +1,179 @@
#include "valve.h"
#include "esphome/core/log.h"
namespace esphome {
namespace valve {
static const char *const TAG = "valve";
const float VALVE_OPEN = 1.0f;
const float VALVE_CLOSED = 0.0f;
const char *valve_command_to_str(float pos) {
if (pos == VALVE_OPEN) {
return "OPEN";
} else if (pos == VALVE_CLOSED) {
return "CLOSE";
} else {
return "UNKNOWN";
}
}
const char *valve_operation_to_str(ValveOperation op) {
switch (op) {
case VALVE_OPERATION_IDLE:
return "IDLE";
case VALVE_OPERATION_OPENING:
return "OPENING";
case VALVE_OPERATION_CLOSING:
return "CLOSING";
default:
return "UNKNOWN";
}
}
Valve::Valve() : position{VALVE_OPEN} {}
ValveCall::ValveCall(Valve *parent) : parent_(parent) {}
ValveCall &ValveCall::set_command(const char *command) {
if (strcasecmp(command, "OPEN") == 0) {
this->set_command_open();
} else if (strcasecmp(command, "CLOSE") == 0) {
this->set_command_close();
} else if (strcasecmp(command, "STOP") == 0) {
this->set_command_stop();
} else if (strcasecmp(command, "TOGGLE") == 0) {
this->set_command_toggle();
} else {
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command);
}
return *this;
}
ValveCall &ValveCall::set_command_open() {
this->position_ = VALVE_OPEN;
return *this;
}
ValveCall &ValveCall::set_command_close() {
this->position_ = VALVE_CLOSED;
return *this;
}
ValveCall &ValveCall::set_command_stop() {
this->stop_ = true;
return *this;
}
ValveCall &ValveCall::set_command_toggle() {
this->toggle_ = true;
return *this;
}
ValveCall &ValveCall::set_position(float position) {
this->position_ = position;
return *this;
}
void ValveCall::perform() {
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
auto traits = this->parent_->get_traits();
this->validate_();
if (this->stop_) {
ESP_LOGD(TAG, " Command: STOP");
}
if (this->position_.has_value()) {
if (traits.get_supports_position()) {
ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f);
} else {
ESP_LOGD(TAG, " Command: %s", valve_command_to_str(*this->position_));
}
}
if (this->toggle_.has_value()) {
ESP_LOGD(TAG, " Command: TOGGLE");
}
this->parent_->control(*this);
}
const optional<float> &ValveCall::get_position() const { return this->position_; }
const optional<bool> &ValveCall::get_toggle() const { return this->toggle_; }
void ValveCall::validate_() {
auto traits = this->parent_->get_traits();
if (this->position_.has_value()) {
auto pos = *this->position_;
if (!traits.get_supports_position() && pos != VALVE_OPEN && pos != VALVE_CLOSED) {
ESP_LOGW(TAG, "'%s' - This valve device does not support setting position!", this->parent_->get_name().c_str());
this->position_.reset();
} else if (pos < 0.0f || pos > 1.0f) {
ESP_LOGW(TAG, "'%s' - Position %.2f is out of range [0.0 - 1.0]", this->parent_->get_name().c_str(), pos);
this->position_ = clamp(pos, 0.0f, 1.0f);
}
}
if (this->toggle_.has_value()) {
if (!traits.get_supports_toggle()) {
ESP_LOGW(TAG, "'%s' - This valve device does not support toggle!", this->parent_->get_name().c_str());
this->toggle_.reset();
}
}
if (this->stop_) {
if (this->position_.has_value()) {
ESP_LOGW(TAG, "Cannot set position when stopping a valve!");
this->position_.reset();
}
if (this->toggle_.has_value()) {
ESP_LOGW(TAG, "Cannot set toggle when stopping a valve!");
this->toggle_.reset();
}
}
}
ValveCall &ValveCall::set_stop(bool stop) {
this->stop_ = stop;
return *this;
}
bool ValveCall::get_stop() const { return this->stop_; }
ValveCall Valve::make_call() { return {this}; }
void Valve::add_on_state_callback(std::function<void()> &&f) { this->state_callback_.add(std::move(f)); }
void Valve::publish_state(bool save) {
this->position = clamp(this->position, 0.0f, 1.0f);
ESP_LOGD(TAG, "'%s' - Publishing:", this->name_.c_str());
auto traits = this->get_traits();
if (traits.get_supports_position()) {
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
} else {
if (this->position == VALVE_OPEN) {
ESP_LOGD(TAG, " State: OPEN");
} else if (this->position == VALVE_CLOSED) {
ESP_LOGD(TAG, " State: CLOSED");
} else {
ESP_LOGD(TAG, " State: UNKNOWN");
}
}
ESP_LOGD(TAG, " Current Operation: %s", valve_operation_to_str(this->current_operation));
this->state_callback_.call();
if (save) {
ValveRestoreState restore{};
memset(&restore, 0, sizeof(restore));
restore.position = this->position;
this->rtc_.save(&restore);
}
}
optional<ValveRestoreState> Valve::restore_state_() {
this->rtc_ = global_preferences->make_preference<ValveRestoreState>(this->get_object_id_hash());
ValveRestoreState recovered{};
if (!this->rtc_.load(&recovered))
return {};
return recovered;
}
bool Valve::is_fully_open() const { return this->position == VALVE_OPEN; }
bool Valve::is_fully_closed() const { return this->position == VALVE_CLOSED; }
ValveCall ValveRestoreState::to_call(Valve *valve) {
auto call = valve->make_call();
call.set_position(this->position);
return call;
}
void ValveRestoreState::apply(Valve *valve) {
valve->position = this->position;
valve->publish_state();
}
} // namespace valve
} // namespace esphome

View File

@ -0,0 +1,152 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/entity_base.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#include "valve_traits.h"
namespace esphome {
namespace valve {
const extern float VALVE_OPEN;
const extern float VALVE_CLOSED;
#define LOG_VALVE(prefix, type, obj) \
if ((obj) != nullptr) { \
ESP_LOGCONFIG(TAG, "%s%s '%s'", prefix, LOG_STR_LITERAL(type), (obj)->get_name().c_str()); \
auto traits_ = (obj)->get_traits(); \
if (traits_.get_is_assumed_state()) { \
ESP_LOGCONFIG(TAG, "%s Assumed State: YES", prefix); \
} \
if (!(obj)->get_device_class().empty()) { \
ESP_LOGCONFIG(TAG, "%s Device Class: '%s'", prefix, (obj)->get_device_class().c_str()); \
} \
}
class Valve;
class ValveCall {
public:
ValveCall(Valve *parent);
/// Set the command as a string, "STOP", "OPEN", "CLOSE", "TOGGLE".
ValveCall &set_command(const char *command);
/// Set the command to open the valve.
ValveCall &set_command_open();
/// Set the command to close the valve.
ValveCall &set_command_close();
/// Set the command to stop the valve.
ValveCall &set_command_stop();
/// Set the command to toggle the valve.
ValveCall &set_command_toggle();
/// Set the call to a certain target position.
ValveCall &set_position(float position);
/// Set whether this valve call should stop the valve.
ValveCall &set_stop(bool stop);
/// Perform the valve call.
void perform();
const optional<float> &get_position() const;
bool get_stop() const;
const optional<bool> &get_toggle() const;
protected:
void validate_();
Valve *parent_;
bool stop_{false};
optional<float> position_{};
optional<bool> toggle_{};
};
/// Struct used to store the restored state of a valve
struct ValveRestoreState {
float position;
/// Convert this struct to a valve call that can be performed.
ValveCall to_call(Valve *valve);
/// Apply these settings to the valve
void apply(Valve *valve);
} __attribute__((packed));
/// Enum encoding the current operation of a valve.
enum ValveOperation : uint8_t {
/// The valve is currently idle (not moving)
VALVE_OPERATION_IDLE = 0,
/// The valve is currently opening.
VALVE_OPERATION_OPENING,
/// The valve is currently closing.
VALVE_OPERATION_CLOSING,
};
const char *valve_operation_to_str(ValveOperation op);
/** Base class for all valve devices.
*
* Valves currently have three properties:
* - position - The current position of the valve from 0.0 (fully closed) to 1.0 (fully open).
* For valves with only binary OPEN/CLOSED position this will always be either 0.0 or 1.0
* - current_operation - The operation the valve is currently performing, this can
* be one of IDLE, OPENING and CLOSING.
*
* For users: All valve operations must be performed over the .make_call() interface.
* To command a valve, use .make_call() to create a call object, set all properties
* you wish to set, and activate the command with .perform().
* For reading out the current values of the valve, use the public .position, etc.
* properties (these are read-only for users)
*
* For integrations: Integrations must implement two methods: control() and get_traits().
* Control will be called with the arguments supplied by the user and should be used
* to control all values of the valve. Also implement get_traits() to return what operations
* the valve supports.
*/
class Valve : public EntityBase, public EntityBase_DeviceClass {
public:
explicit Valve();
/// The current operation of the valve (idle, opening, closing).
ValveOperation current_operation{VALVE_OPERATION_IDLE};
/** The position of the valve from 0.0 (fully closed) to 1.0 (fully open).
*
* For binary valves this is always equals to 0.0 or 1.0 (see also VALVE_OPEN and
* VALVE_CLOSED constants).
*/
float position;
/// Construct a new valve call used to control the valve.
ValveCall make_call();
void add_on_state_callback(std::function<void()> &&f);
/** Publish the current state of the valve.
*
* First set the .position, etc. values and then call this method
* to publish the state of the valve.
*
* @param save Whether to save the updated values in RTC area.
*/
void publish_state(bool save = true);
virtual ValveTraits get_traits() = 0;
/// Helper method to check if the valve is fully open. Equivalent to comparing .position against 1.0
bool is_fully_open() const;
/// Helper method to check if the valve is fully closed. Equivalent to comparing .position against 0.0
bool is_fully_closed() const;
protected:
friend ValveCall;
virtual void control(const ValveCall &call) = 0;
optional<ValveRestoreState> restore_state_();
CallbackManager<void()> state_callback_{};
ESPPreferenceObject rtc_;
};
} // namespace valve
} // namespace esphome

View File

@ -0,0 +1,27 @@
#pragma once
namespace esphome {
namespace valve {
class ValveTraits {
public:
ValveTraits() = default;
bool get_is_assumed_state() const { return this->is_assumed_state_; }
void set_is_assumed_state(bool is_assumed_state) { this->is_assumed_state_ = is_assumed_state; }
bool get_supports_position() const { return this->supports_position_; }
void set_supports_position(bool supports_position) { this->supports_position_ = supports_position; }
bool get_supports_toggle() const { return this->supports_toggle_; }
void set_supports_toggle(bool supports_toggle) { this->supports_toggle_ = supports_toggle; }
bool get_supports_stop() const { return this->supports_stop_; }
void set_supports_stop(bool supports_stop) { this->supports_stop_ = supports_stop; }
protected:
bool is_assumed_state_{false};
bool supports_position_{false};
bool supports_toggle_{false};
bool supports_stop_{false};
};
} // namespace valve
} // namespace esphome

View File

@ -86,6 +86,15 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
}
#endif
#ifdef USE_VALVE
bool ListEntitiesIterator::on_valve(valve::Valve *valve) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->valve_json(valve, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_CLIMATE
bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
if (this->web_server_->events_.count() == 0)
@ -159,5 +168,14 @@ bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmCont
}
#endif
#ifdef USE_EVENT
bool ListEntitiesIterator::on_event(event::Event *event) {
// Null event type, since we are just iterating over entities
const std::string null_event_type = "";
this->web_server_->events_.send(this->web_server_->event_json(event, null_event_type, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
} // namespace web_server
} // namespace esphome

View File

@ -59,9 +59,15 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_LOCK
bool on_lock(lock::Lock *a_lock) override;
#endif
#ifdef USE_VALVE
bool on_valve(valve::Valve *valve) override;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) override;
#endif
#ifdef USE_EVENT
bool on_event(event::Event *event) override;
#endif
protected:
WebServer *web_server_;

View File

@ -1311,6 +1311,68 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
}
#endif
#ifdef USE_VALVE
void WebServer::on_valve_update(valve::Valve *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->valve_json(obj, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (valve::Valve *obj : App.get_valves()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->valve_json(obj, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
continue;
}
auto call = obj->make_call();
if (match.method == "open") {
call.set_command_open();
} else if (match.method == "close") {
call.set_command_close();
} else if (match.method == "stop") {
call.set_command_stop();
} else if (match.method == "toggle") {
call.set_command_toggle();
} else if (match.method != "set") {
request->send(404);
return;
}
auto traits = obj->get_traits();
if (request->hasParam("position") && !traits.get_supports_position()) {
request->send(409);
return;
}
if (request->hasParam("position")) {
auto position = parse_number<float>(request->getParam("position")->value().c_str());
if (position.has_value()) {
call.set_position(*position);
}
}
this->schedule_([call]() mutable { call.perform(); });
request->send(200);
return;
}
request->send(404);
}
std::string WebServer::valve_json(valve::Valve *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "valve-" + obj->get_object_id(), obj->is_fully_closed() ? "CLOSED" : "OPEN",
obj->position, start_config);
root["current_operation"] = valve::valve_operation_to_str(obj->current_operation);
if (obj->get_traits().get_supports_position())
root["position"] = obj->position;
});
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (this->events_.count() == 0)
@ -1341,6 +1403,28 @@ void WebServer::handle_alarm_control_panel_request(AsyncWebServerRequest *reques
}
#endif
#ifdef USE_EVENT
void WebServer::on_event(event::Event *obj, const std::string &event_type) {
this->events_.send(this->event_json(obj, event_type, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config) {
return json::build_json([obj, event_type, start_config](JsonObject root) {
set_json_id(root, obj, "event-" + obj->get_object_id(), start_config);
if (!event_type.empty()) {
root["event_type"] = event_type;
}
if (start_config == DETAIL_ALL) {
JsonArray event_types = root.createNestedArray("event_types");
for (auto const &event_type : obj->get_event_types()) {
event_types.add(event_type);
}
root["device_class"] = obj->get_device_class();
}
});
}
#endif
bool WebServer::canHandle(AsyncWebServerRequest *request) {
if (request->url() == "/")
return true;
@ -1450,6 +1534,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
return true;
#endif
#ifdef USE_VALVE
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "valve")
return true;
#endif
#ifdef USE_ALARM_CONTROL_PANEL
if (request->method() == HTTP_GET && match.domain == "alarm_control_panel")
return true;
@ -1598,6 +1687,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
}
#endif
#ifdef USE_VALVE
if (match.domain == "valve") {
this->handle_valve_request(request, match);
return;
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL
if (match.domain == "alarm_control_panel") {
this->handle_alarm_control_panel_request(request, match);

View File

@ -285,6 +285,16 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
std::string lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config);
#endif
#ifdef USE_VALVE
void on_valve_update(valve::Valve *obj) override;
/// Handle a valve request under '/valve/<id>/<open/close/stop/set>'.
void handle_valve_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the valve state as a JSON string.
std::string valve_json(valve::Valve *obj, JsonDetail start_config);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) override;
@ -296,6 +306,13 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
alarm_control_panel::AlarmControlPanelState value, JsonDetail start_config);
#endif
#ifdef USE_EVENT
void on_event(event::Event *obj, const std::string &event_type) override;
/// Dump the event details with its value as a JSON string.
std::string event_json(event::Event *obj, const std::string &event_type, JsonDetail start_config);
#endif
/// Override the web handler's canHandle method.
bool canHandle(AsyncWebServerRequest *request) override;
/// Override the web handler's handleRequest method.

View File

@ -0,0 +1,108 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import (
CONF_BAUD_RATE,
CONF_CHANNEL,
CONF_ID,
CONF_INPUT,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
CONF_OUTPUT,
)
CODEOWNERS = ["@DrCoolZic"]
AUTO_LOAD = ["uart"]
MULTI_CONF = True
CONF_STOP_BITS = "stop_bits"
CONF_PARITY = "parity"
CONF_CRYSTAL = "crystal"
CONF_UART = "uart"
CONF_TEST_MODE = "test_mode"
weikai_ns = cg.esphome_ns.namespace("weikai")
WeikaiComponent = weikai_ns.class_("WeikaiComponent", cg.Component)
WeikaiChannel = weikai_ns.class_("WeikaiChannel", uart.UARTComponent)
def check_channel_max(value, max):
channel_uniq = []
channel_dup = []
for x in value[CONF_UART]:
if x[CONF_CHANNEL] > max - 1:
raise cv.Invalid(f"Invalid channel number: {x[CONF_CHANNEL]}")
if x[CONF_CHANNEL] not in channel_uniq:
channel_uniq.append(x[CONF_CHANNEL])
else:
channel_dup.append(x[CONF_CHANNEL])
if len(channel_dup) > 0:
raise cv.Invalid(f"Duplicate channel list: {channel_dup}")
return value
def check_channel_max_4(value):
return check_channel_max(value, 4)
def check_channel_max_2(value):
return check_channel_max(value, 2)
WKBASE_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(WeikaiComponent),
cv.Optional(CONF_CRYSTAL, default=14745600): cv.int_,
cv.Optional(CONF_TEST_MODE, default=0): cv.int_,
cv.Required(CONF_UART): cv.ensure_list(
{
cv.Required(CONF_ID): cv.declare_id(WeikaiChannel),
cv.Optional(CONF_CHANNEL, default=0): cv.int_range(min=0, max=3),
cv.Required(CONF_BAUD_RATE): cv.int_range(min=1),
cv.Optional(CONF_STOP_BITS, default=1): cv.one_of(1, 2, int=True),
cv.Optional(CONF_PARITY, default="NONE"): cv.enum(
uart.UART_PARITY_OPTIONS, upper=True
),
}
),
}
).extend(cv.COMPONENT_SCHEMA)
async def register_weikai(var, config):
"""Register an weikai device with the given config."""
cg.add(var.set_crystal(config[CONF_CRYSTAL]))
cg.add(var.set_test_mode(config[CONF_TEST_MODE]))
await cg.register_component(var, config)
for uart_elem in config[CONF_UART]:
chan = cg.new_Pvariable(uart_elem[CONF_ID])
cg.add(chan.set_channel_name(str(uart_elem[CONF_ID])))
cg.add(chan.set_parent(var))
cg.add(chan.set_channel(uart_elem[CONF_CHANNEL]))
cg.add(chan.set_baud_rate(uart_elem[CONF_BAUD_RATE]))
cg.add(chan.set_stop_bits(uart_elem[CONF_STOP_BITS]))
cg.add(chan.set_parity(uart_elem[CONF_PARITY]))
def validate_pin_mode(value):
"""Checks input/output mode inconsistency"""
if not (value[CONF_MODE][CONF_INPUT] or value[CONF_MODE][CONF_OUTPUT]):
raise cv.Invalid("Mode must be either input or output")
if value[CONF_MODE][CONF_INPUT] and value[CONF_MODE][CONF_OUTPUT]:
raise cv.Invalid("Mode must be either input or output")
return value
WEIKAI_PIN_SCHEMA = cv.Schema(
{
cv.Required(CONF_NUMBER): cv.int_range(min=0, max=7),
cv.Optional(CONF_MODE, default={}): cv.All(
{
cv.Optional(CONF_INPUT, default=False): cv.boolean,
cv.Optional(CONF_OUTPUT, default=False): cv.boolean,
},
),
cv.Optional(CONF_INVERTED, default=False): cv.boolean,
}
)

View File

@ -0,0 +1,615 @@
/// @file weikai.cpp
/// @brief WeiKai component family - classes implementation
/// @date Last Modified: 2024/04/06 15:13:11
/// @details The classes declared in this file can be used by the Weikai family
#include "weikai.h"
namespace esphome {
namespace weikai {
/*! @mainpage Weikai source code documentation
This documentation provides information about the implementation of the family of WeiKai Components in ESPHome.
Here is the class diagram related to Weikai family of components:
@image html weikai_class.png
@section WKRingBuffer_ The WKRingBuffer template class
The WKRingBuffer template class has it names implies implement a simple ring buffer helper class. This straightforward
container implements FIFO functionality, enabling bytes to be pushed into one side and popped from the other in the
order of entry. Implementation is classic and therefore not described in any details.
@section WeikaiRegister_ The WeikaiRegister class
The WeikaiRegister helper class creates objects that act as proxies to the device registers.
@details This is an abstract virtual class (interface) that provides all the necessary access to registers while hiding
the actual implementation. The access to the registers can be made through an I²C bus in for example for wk2168_i2c
component or through a SPI bus for example in the case of the wk2168_spi component. Derived classes will actually
performs the specific bus operations.
@section WeikaiRegisterI2C_ WeikaiRegisterI2C
The weikai_i2c::WeikaiRegisterI2C class implements the virtual methods of the WeikaiRegister class for an I2C bus.
@section WeikaiRegisterSPI_ WeikaiRegisterSPI
The weikai_spi::WeikaiRegisterSPI class implements the virtual methods of the WeikaiRegister class for an SPI bus.
@section WeikaiComponent_ The WeikaiComponent class
The WeikaiComponent class stores the information global to a WeiKai family component and provides methods to set/access
this information. It also serves as a container for WeikaiChannel instances. This is done by maintaining an array of
references these WeikaiChannel instances. This class derives from the esphome::Component classes. This class override
esphome::Component::loop() method to facilitate the seamless transfer of accumulated bytes from the receive
FIFO into the ring buffer. This process ensures quick access to the stored bytes, enhancing the overall efficiency of
the component.
@section WeikaiComponentI2C_ WeikaiComponentI2C
The weikai_i2c::WeikaiComponentI2C class implements the virtual methods of the WeikaiComponent class for an I2C bus.
@section WeikaiComponentSPI_ WeikaiComponentSPI
The weikai_spi::WeikaiComponentSPI class implements the virtual methods of the WeikaiComponent class for an SPI bus.
@section WeikaiGPIOPin_ WeikaiGPIOPin class
The WeikaiGPIOPin class is an helper class to expose the GPIO pins of WK family components as if they were internal
GPIO pins. It also provides the setup() and dump_summary() methods.
@section WeikaiChannel_ The WeikaiChannel class
The WeikaiChannel class is used to implement all the virtual methods of the ESPHome uart::UARTComponent class. An
individual instance of this class is created for each UART channel. It has a link back to the WeikaiComponent object it
belongs to. This class derives from the uart::UARTComponent class. It collaborates through an aggregation with
WeikaiComponent. This implies that WeikaiComponent acts as a container, housing several WeikaiChannel instances.
Furthermore, the WeikaiChannel class derives from the ESPHome uart::UARTComponent class, it also has an association
relationship with the WKRingBuffer and WeikaiRegister helper classes. Consequently, when a WeikaiChannel instance is
destroyed, the associated WKRingBuffer instance is also destroyed.
*/
static const char *const TAG = "weikai";
/// @brief convert an int to binary representation as C++ std::string
/// @param val integer to convert
/// @return a std::string
inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); }
/// Convert std::string to C string
#define I2S2CS(val) (i2s(val).c_str())
/// @brief measure the time elapsed between two calls
/// @param last_time time of the previous call
/// @return the elapsed time in milliseconds
uint32_t elapsed_ms(uint32_t &last_time) {
uint32_t e = millis() - last_time;
last_time = millis();
return e;
};
/// @brief Converts the parity enum value to a C string
/// @param parity enum
/// @return the string
const char *p2s(uart::UARTParityOptions parity) {
using namespace uart;
switch (parity) {
case UART_CONFIG_PARITY_NONE:
return "NONE";
case UART_CONFIG_PARITY_EVEN:
return "EVEN";
case UART_CONFIG_PARITY_ODD:
return "ODD";
default:
return "UNKNOWN";
}
}
/// @brief Display a buffer in hexadecimal format (32 hex values / line) for debug
void print_buffer(const uint8_t *data, size_t length) {
char hex_buffer[100];
hex_buffer[(3 * 32) + 1] = 0;
for (size_t i = 0; i < length; i++) {
snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]);
if (i % 32 == 31) {
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
if (length % 32) {
// null terminate if incomplete line
hex_buffer[3 * (length % 32) + 2] = 0;
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER",
"SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"};
static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL",
"TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"};
// method to print a register value as text: used in the log messages ...
const char *reg_to_str(int reg, bool page1) {
if (reg == WKREG_GPDAT) {
return "GPDAT";
} else if (reg == WKREG_GPDIR) {
return "GPDIR";
} else {
return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F];
}
}
enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO
///////////////////////////////////////////////////////////////////////////////
// The WeikaiRegister methods
///////////////////////////////////////////////////////////////////////////////
WeikaiRegister &WeikaiRegister::operator=(uint8_t value) {
write_reg(value);
return *this;
}
WeikaiRegister &WeikaiRegister::operator&=(uint8_t value) {
value &= read_reg();
write_reg(value);
return *this;
}
WeikaiRegister &WeikaiRegister::operator|=(uint8_t value) {
value |= read_reg();
write_reg(value);
return *this;
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiComponent methods
///////////////////////////////////////////////////////////////////////////////
void WeikaiComponent::loop() {
if ((this->component_state_ & COMPONENT_STATE_MASK) != COMPONENT_STATE_LOOP)
return;
// If there are some bytes in the receive FIFO we transfers them to the ring buffers
size_t transferred = 0;
for (auto *child : this->children_) {
// we look if some characters has been received in the fifo
transferred += child->xfer_fifo_to_buffer_();
}
if (transferred > 0) {
ESP_LOGV(TAG, "we transferred %d bytes from fifo to buffer...", transferred);
}
#ifdef TEST_COMPONENT
static uint32_t loop_time = 0;
static uint32_t loop_count = 0;
uint32_t time = 0;
if (test_mode_ == 1) { // test component in loopback
ESP_LOGI(TAG, "Component loop %" PRIu32 " for %s : %" PRIu32 " ms since last call ...", loop_count++,
this->get_name(), millis() - loop_time);
loop_time = millis();
char message[64];
elapsed_ms(time); // set time to now
for (int i = 0; i < this->children_.size(); i++) {
if (i != ((loop_count - 1) % this->children_.size())) // we do only one per loop
continue;
snprintf(message, sizeof(message), "%s:%s", this->get_name(), children_[i]->get_channel_name());
children_[i]->uart_send_test_(message);
uint32_t const start_time = millis();
while (children_[i]->tx_fifo_is_not_empty_()) { // wait until buffer empty
if (millis() - start_time > 1500) {
ESP_LOGE(TAG, "timeout while flushing - %d bytes left in buffer...", children_[i]->tx_in_fifo_());
break;
}
yield(); // reschedule our thread to avoid blocking
}
bool status = children_[i]->uart_receive_test_(message);
ESP_LOGI(TAG, "Test %s => send/received %u bytes %s - execution time %" PRIu32 " ms...", message,
RING_BUFFER_SIZE, status ? "correctly" : "with error", elapsed_ms(time));
}
}
if (this->test_mode_ == 2) { // test component in echo mode
for (auto *child : this->children_) {
uint8_t data = 0;
if (child->available()) {
child->read_byte(&data);
ESP_LOGI(TAG, "echo mode: read -> send %02X", data);
child->write_byte(data);
}
}
}
if (test_mode_ == 3) {
test_gpio_input_();
}
if (test_mode_ == 4) {
test_gpio_output_();
}
#endif
}
#if defined(TEST_COMPONENT)
void WeikaiComponent::test_gpio_input_() {
static bool init_input{false};
static uint8_t state{0};
uint8_t value;
if (!init_input) {
init_input = true;
// set all pins in input mode
this->reg(WKREG_GPDIR, 0) = 0x00;
ESP_LOGI(TAG, "initializing all pins to input mode");
state = this->reg(WKREG_GPDAT, 0);
ESP_LOGI(TAG, "initial input data state = %02X (%s)", state, I2S2CS(state));
}
value = this->reg(WKREG_GPDAT, 0);
if (value != state) {
ESP_LOGI(TAG, "Input data changed from %02X to %02X (%s)", state, value, I2S2CS(value));
state = value;
}
}
void WeikaiComponent::test_gpio_output_() {
static bool init_output{false};
static uint8_t state{0};
if (!init_output) {
init_output = true;
// set all pins in output mode
this->reg(WKREG_GPDIR, 0) = 0xFF;
ESP_LOGI(TAG, "initializing all pins to output mode");
this->reg(WKREG_GPDAT, 0) = state;
ESP_LOGI(TAG, "setting all outputs to 0");
}
state = ~state;
this->reg(WKREG_GPDAT, 0) = state;
ESP_LOGI(TAG, "Flipping all outputs to %02X (%s)", state, I2S2CS(state));
delay(100); // NOLINT
}
#endif
///////////////////////////////////////////////////////////////////////////////
// The WeikaiGPIOPin methods
///////////////////////////////////////////////////////////////////////////////
bool WeikaiComponent::read_pin_val_(uint8_t pin) {
this->input_state_ = this->reg(WKREG_GPDAT, 0);
ESP_LOGVV(TAG, "reading input pin %u = %u in_state %s", pin, this->input_state_ & (1 << pin), I2S2CS(input_state_));
return this->input_state_ & (1 << pin);
}
void WeikaiComponent::write_pin_val_(uint8_t pin, bool value) {
if (value) {
this->output_state_ |= (1 << pin);
} else {
this->output_state_ &= ~(1 << pin);
}
ESP_LOGVV(TAG, "writing output pin %d with %d out_state %s", pin, uint8_t(value), I2S2CS(this->output_state_));
this->reg(WKREG_GPDAT, 0) = this->output_state_;
}
void WeikaiComponent::set_pin_direction_(uint8_t pin, gpio::Flags flags) {
if (flags == gpio::FLAG_INPUT) {
this->pin_config_ &= ~(1 << pin); // clear bit (input mode)
} else {
if (flags == gpio::FLAG_OUTPUT) {
this->pin_config_ |= 1 << pin; // set bit (output mode)
} else {
ESP_LOGE(TAG, "pin %d direction invalid", pin);
}
}
ESP_LOGVV(TAG, "setting pin %d direction to %d pin_config=%s", pin, flags, I2S2CS(this->pin_config_));
this->reg(WKREG_GPDIR, 0) = this->pin_config_; // TODO check ~
}
void WeikaiGPIOPin::setup() {
ESP_LOGCONFIG(TAG, "Setting GPIO pin %d mode to %s", this->pin_,
flags_ == gpio::FLAG_INPUT ? "Input"
: this->flags_ == gpio::FLAG_OUTPUT ? "Output"
: "NOT SPECIFIED");
// ESP_LOGCONFIG(TAG, "Setting GPIO pins mode to '%s' %02X", I2S2CS(this->flags_), this->flags_);
this->pin_mode(this->flags_);
}
std::string WeikaiGPIOPin::dump_summary() const {
char buffer[32];
snprintf(buffer, sizeof(buffer), "%u via WeiKai %s", this->pin_, this->parent_->get_name());
return buffer;
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiChannel methods
///////////////////////////////////////////////////////////////////////////////
void WeikaiChannel::setup_channel() {
ESP_LOGCONFIG(TAG, " Setting up UART %s:%s ...", this->parent_->get_name(), this->get_channel_name());
// we enable transmit and receive on this channel
if (this->check_channel_down()) {
ESP_LOGCONFIG(TAG, " Error channel %s not working...", this->get_channel_name());
}
this->reset_fifo_();
this->receive_buffer_.clear();
this->set_line_param_();
this->set_baudrate_();
}
void WeikaiChannel::dump_channel() {
ESP_LOGCONFIG(TAG, " UART %s ...", this->get_channel_name());
ESP_LOGCONFIG(TAG, " Baud rate: %" PRIu32 " Bd", this->baud_rate_);
ESP_LOGCONFIG(TAG, " Data bits: %u", this->data_bits_);
ESP_LOGCONFIG(TAG, " Stop bits: %u", this->stop_bits_);
ESP_LOGCONFIG(TAG, " Parity: %s", p2s(this->parity_));
}
void WeikaiChannel::reset_fifo_() {
// enable transmission and reception
this->reg(WKREG_SCR) = SCR_RXEN | SCR_TXEN;
// we reset and enable transmit and receive FIFO
this->reg(WKREG_FCR) = FCR_TFEN | FCR_RFEN | FCR_TFRST | FCR_RFRST;
}
void WeikaiChannel::set_line_param_() {
this->data_bits_ = 8; // always equal to 8 for WeiKai (cant be changed)
uint8_t lcr = 0;
if (this->stop_bits_ == 2)
lcr |= LCR_STPL;
switch (this->parity_) { // parity selection settings
case uart::UART_CONFIG_PARITY_ODD:
lcr |= (LCR_PAEN | LCR_PAR_ODD);
break;
case uart::UART_CONFIG_PARITY_EVEN:
lcr |= (LCR_PAEN | LCR_PAR_EVEN);
break;
default:
break; // no parity 000x
}
this->reg(WKREG_LCR) = lcr; // write LCR
ESP_LOGV(TAG, " line config: %d data_bits, %d stop_bits, parity %s register [%s]", this->data_bits_,
this->stop_bits_, p2s(this->parity_), I2S2CS(lcr));
}
void WeikaiChannel::set_baudrate_() {
if (this->baud_rate_ > this->parent_->crystal_ / 16) {
baud_rate_ = this->parent_->crystal_ / 16;
ESP_LOGE(TAG, " Requested baudrate too high for crystal=%" PRIu32 " Hz. Has been reduced to %" PRIu32 " Bd",
this->parent_->crystal_, this->baud_rate_);
};
uint16_t const val_int = this->parent_->crystal_ / (this->baud_rate_ * 16) - 1;
uint16_t val_dec = (this->parent_->crystal_ % (this->baud_rate_ * 16)) / (this->baud_rate_ * 16);
uint8_t const baud_high = (uint8_t) (val_int >> 8);
uint8_t const baud_low = (uint8_t) (val_int & 0xFF);
while (val_dec > 0x0A)
val_dec /= 0x0A;
uint8_t const baud_dec = (uint8_t) (val_dec);
this->parent_->page1_ = true; // switch to page 1
this->reg(WKREG_SPAGE) = 1;
this->reg(WKREG_BRH) = baud_high;
this->reg(WKREG_BRL) = baud_low;
this->reg(WKREG_BRD) = baud_dec;
this->parent_->page1_ = false; // switch back to page 0
this->reg(WKREG_SPAGE) = 0;
ESP_LOGV(TAG, " Crystal=%d baudrate=%d => registers [%d %d %d]", this->parent_->crystal_, this->baud_rate_,
baud_high, baud_low, baud_dec);
}
inline bool WeikaiChannel::tx_fifo_is_not_empty_() { return this->reg(WKREG_FSR) & FSR_TFDAT; }
size_t WeikaiChannel::tx_in_fifo_() {
size_t tfcnt = this->reg(WKREG_TFCNT);
if (tfcnt == 0) {
uint8_t const fsr = this->reg(WKREG_FSR);
if (fsr & FSR_TFFULL) {
ESP_LOGVV(TAG, "tx FIFO full FSR=%s", I2S2CS(fsr));
tfcnt = FIFO_SIZE;
}
}
ESP_LOGVV(TAG, "tx FIFO contains %d bytes", tfcnt);
return tfcnt;
}
size_t WeikaiChannel::rx_in_fifo_() {
size_t available = this->reg(WKREG_RFCNT);
uint8_t const fsr = this->reg(WKREG_FSR);
if (fsr & (FSR_RFOE | FSR_RFLB | FSR_RFFE | FSR_RFPE)) {
if (fsr & FSR_RFOE)
ESP_LOGE(TAG, "Receive data overflow FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFLB)
ESP_LOGE(TAG, "Receive line break FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFFE)
ESP_LOGE(TAG, "Receive frame error FSR=%s", I2S2CS(fsr));
if (fsr & FSR_RFPE)
ESP_LOGE(TAG, "Receive parity error FSR=%s", I2S2CS(fsr));
}
if ((available == 0) && (fsr & FSR_RFDAT)) {
// here we should be very careful because we can have something like this:
// - at time t0 we read RFCNT=0 because nothing yet received
// - at time t0+delta we might read FIFO not empty because one byte has just been received
// - so to be sure we need to do another read of RFCNT and if it is still zero -> buffer full
available = this->reg(WKREG_RFCNT);
if (available == 0) { // still zero ?
ESP_LOGV(TAG, "rx FIFO is full FSR=%s", I2S2CS(fsr));
available = FIFO_SIZE;
}
}
ESP_LOGVV(TAG, "rx FIFO contain %d bytes - FSR status=%s", available, I2S2CS(fsr));
return available;
}
bool WeikaiChannel::check_channel_down() {
// to check if we channel is up we write to the LCR W/R register
// note that this will put a break on the tx line for few ms
WeikaiRegister &lcr = this->reg(WKREG_LCR);
lcr = 0x3F;
uint8_t val = lcr;
if (val != 0x3F) {
ESP_LOGE(TAG, "R/W of register failed expected 0x3F received 0x%02X", val);
return true;
}
lcr = 0;
val = lcr;
if (val != 0x00) {
ESP_LOGE(TAG, "R/W of register failed expected 0x00 received 0x%02X", val);
return true;
}
return false;
}
bool WeikaiChannel::peek_byte(uint8_t *buffer) {
auto available = this->receive_buffer_.count();
if (!available)
xfer_fifo_to_buffer_();
return this->receive_buffer_.peek(*buffer);
}
int WeikaiChannel::available() {
size_t available = this->receive_buffer_.count();
if (!available)
available = xfer_fifo_to_buffer_();
return available;
}
bool WeikaiChannel::read_array(uint8_t *buffer, size_t length) {
bool status = true;
auto available = this->receive_buffer_.count();
if (length > available) {
ESP_LOGW(TAG, "read_array: buffer underflow requested %d bytes only %d bytes available...", length, available);
length = available;
status = false;
}
// retrieve the bytes from ring buffer
for (size_t i = 0; i < length; i++) {
this->receive_buffer_.pop(buffer[i]);
}
ESP_LOGVV(TAG, "read_array(ch=%d buffer[0]=%02X, length=%d): status %s", this->channel_, *buffer, length,
status ? "OK" : "ERROR");
return status;
}
void WeikaiChannel::write_array(const uint8_t *buffer, size_t length) {
if (length > XFER_MAX_SIZE) {
ESP_LOGE(TAG, "Write_array: invalid call - requested %d bytes but max size %d ...", length, XFER_MAX_SIZE);
length = XFER_MAX_SIZE;
}
this->reg(0).write_fifo(const_cast<uint8_t *>(buffer), length);
}
void WeikaiChannel::flush() {
uint32_t const start_time = millis();
while (this->tx_fifo_is_not_empty_()) { // wait until buffer empty
if (millis() - start_time > 200) {
ESP_LOGW(TAG, "WARNING flush timeout - still %d bytes not sent after 200 ms...", this->tx_in_fifo_());
return;
}
yield(); // reschedule our thread to avoid blocking
}
}
size_t WeikaiChannel::xfer_fifo_to_buffer_() {
size_t to_transfer;
size_t free;
while ((to_transfer = this->rx_in_fifo_()) && (free = this->receive_buffer_.free())) {
// while bytes in fifo and some room in the buffer we transfer
if (to_transfer > XFER_MAX_SIZE)
to_transfer = XFER_MAX_SIZE; // we can only do so much
if (to_transfer > free)
to_transfer = free; // we'll do the rest next time
if (to_transfer) {
uint8_t data[to_transfer];
this->reg(0).read_fifo(data, to_transfer);
for (size_t i = 0; i < to_transfer; i++)
this->receive_buffer_.push(data[i]);
}
} // while work to do
return to_transfer;
}
///
// TEST COMPONENT
//
#ifdef TEST_COMPONENT
/// @addtogroup test_ Test component information
/// @{
/// @brief An increment "Functor" (i.e. a class object that acts like a method with state!)
///
/// Functors are objects that can be treated as though they are a function or function pointer.
class Increment {
public:
/// @brief constructor: initialize current value to 0
Increment() : i_(0) {}
/// @brief overload of the parenthesis operator.
/// Returns the current value and auto increment it
/// @return the current value.
uint8_t operator()() { return i_++; }
private:
uint8_t i_;
};
/// @brief Hex converter to print/display a buffer in hexadecimal format (32 hex values / line).
/// @param buffer contains the values to display
void print_buffer(std::vector<uint8_t> buffer) {
char hex_buffer[100];
hex_buffer[(3 * 32) + 1] = 0;
for (size_t i = 0; i < buffer.size(); i++) {
snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", buffer[i]);
if (i % 32 == 31)
ESP_LOGI(TAG, " %s", hex_buffer);
}
if (buffer.size() % 32) {
// null terminate if incomplete line
hex_buffer[3 * (buffer.size() % 32) + 1] = 0;
ESP_LOGI(TAG, " %s", hex_buffer);
}
}
/// @brief test the write_array method
void WeikaiChannel::uart_send_test_(char *message) {
auto start_exec = micros();
std::vector<uint8_t> output_buffer(XFER_MAX_SIZE);
generate(output_buffer.begin(), output_buffer.end(), Increment()); // fill with incrementing number
size_t to_send = RING_BUFFER_SIZE;
while (to_send) {
this->write_array(&output_buffer[0], XFER_MAX_SIZE); // we send the buffer
this->flush();
to_send -= XFER_MAX_SIZE;
}
ESP_LOGV(TAG, "%s => sent %d bytes - exec time %d µs ...", message, RING_BUFFER_SIZE, micros() - start_exec);
}
/// @brief test read_array method
bool WeikaiChannel::uart_receive_test_(char *message) {
auto start_exec = micros();
bool status = true;
size_t received = 0;
std::vector<uint8_t> buffer(RING_BUFFER_SIZE);
// we wait until we have received all the bytes
uint32_t const start_time = millis();
status = true;
while (received < RING_BUFFER_SIZE) {
while (XFER_MAX_SIZE > this->available()) {
this->xfer_fifo_to_buffer_();
if (millis() - start_time > 1500) {
ESP_LOGE(TAG, "uart_receive_test_() timeout: only %d bytes received...", this->available());
break;
}
yield(); // reschedule our thread to avoid blocking
}
status = this->read_array(&buffer[received], XFER_MAX_SIZE) && status;
received += XFER_MAX_SIZE;
}
uint8_t peek_value = 0;
this->peek_byte(&peek_value);
if (peek_value != 0) {
ESP_LOGE(TAG, "Peek first byte value error...");
status = false;
}
for (size_t i = 0; i < RING_BUFFER_SIZE; i++) {
if (buffer[i] != i % XFER_MAX_SIZE) {
ESP_LOGE(TAG, "Read buffer contains error...b=%x i=%x", buffer[i], i % XFER_MAX_SIZE);
print_buffer(buffer);
status = false;
break;
}
}
ESP_LOGV(TAG, "%s => received %d bytes status %s - exec time %d µs ...", message, received, status ? "OK" : "ERROR",
micros() - start_exec);
return status;
}
/// @}
#endif
} // namespace weikai
} // namespace esphome

View File

@ -0,0 +1,443 @@
/// @file weikai.h
/// @author DrCoolZic
/// @brief WeiKai component family - classes declaration
/// @date Last Modified: 2024/04/06 14:44:17
/// @details The classes declared in this file can be used by the Weikai family
/// of UART and GPIO expander components. As of today it provides support for
/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi,
/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c
#pragma once
#include <bitset>
#include <memory>
#include <cinttypes>
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "wk_reg_def.h"
/// When the TEST_COMPONENT flag is defined we include some auto-test methods. Used to test the software during
/// development but can also be used in situ to test if the component is working correctly. For release we do
/// not set it by default but you can set it by using the following lines in you configuration file:
/// @code
/// esphome:
/// platformio_options:
/// build_flags:
/// - -DTEST_COMPONENT
/// @endcode
// #define TEST_COMPONENT
namespace esphome {
namespace weikai {
/// @brief XFER_MAX_SIZE defines the maximum number of bytes allowed during one transfer.
#if defined(I2C_BUFFER_LENGTH)
constexpr size_t XFER_MAX_SIZE = I2C_BUFFER_LENGTH;
#else
constexpr size_t XFER_MAX_SIZE = 128;
#endif
/// @brief size of the internal WeiKai FIFO
constexpr size_t FIFO_SIZE = 256;
/// @brief size of the ring buffer set to size of the FIFO
constexpr size_t RING_BUFFER_SIZE = FIFO_SIZE;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief This is an helper class that provides a simple ring buffers that works as a FIFO
/// @details This ring buffer is used to buffer the bytes received in the FIFO of the Weika device. The best way to read
/// characters from the device FIFO, is to first check how many bytes were received and then read them all at once.
/// Unfortunately in all the code I have reviewed the characters are read one by one in a while loop by checking if
/// bytes are available then reading the byte until no more byte available. This is pretty inefficient for two reasons:
/// - Fist you need to perform a test for each byte to read
/// - and second you call the read byte method for each character.
/// .
/// Assuming you need to read 100 bytes that results into 200 calls. This is to compare to 2 calls (one to find the
/// number of bytes available plus one to read all the bytes) in the best case! If the registers you read are located on
/// the micro-controller this is acceptable because the registers can be accessed fast. But when the registers are
/// located on a remote device accessing them requires several cycles on a slow bus. As it it not possible to fix this
/// problem by asking users to rewrite their code, I have implemented this ring buffer that store the bytes received
/// locally.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template<typename T, size_t SIZE> class WKRingBuffer {
public:
/// @brief pushes an item at the tail of the fifo
/// @param item item to push
/// @return true if item has been pushed, false il item could not pushed (buffer full)
bool push(const T item) {
if (is_full())
return false;
this->rb_[this->head_] = item;
this->head_ = (this->head_ + 1) % SIZE;
this->count_++;
return true;
}
/// @brief return and remove the item at head of the fifo
/// @param item item read
/// @return true if an item has been retrieved, false il no item available (buffer empty)
bool pop(T &item) {
if (is_empty())
return false;
item = this->rb_[this->tail_];
this->tail_ = (this->tail_ + 1) % SIZE;
this->count_--;
return true;
}
/// @brief return the value of the item at fifo's head without removing it
/// @param item pointer to item to return
/// @return true if item has been retrieved, false il no item available (buffer empty)
bool peek(T &item) {
if (is_empty())
return false;
item = this->rb_[this->tail_];
return true;
}
/// @brief test is the Ring Buffer is empty ?
/// @return true if empty
inline bool is_empty() { return (this->count_ == 0); }
/// @brief test is the ring buffer is full ?
/// @return true if full
inline bool is_full() { return (this->count_ == SIZE); }
/// @brief return the number of item in the ring buffer
/// @return the number of items
inline size_t count() { return this->count_; }
/// @brief returns the number of free positions in the buffer
/// @return how many items can be added
inline size_t free() { return SIZE - this->count_; }
/// @brief clear the buffer content
inline void clear() { this->head_ = this->tail_ = this->count_ = 0; }
protected:
std::array<T, SIZE> rb_{0}; ///< the ring buffer
int tail_{0}; ///< position of the next element to read
int head_{0}; ///< position of the next element to write
size_t count_{0}; ///< count number of element in the buffer
};
class WeikaiComponent;
// class WeikaiComponentSPI;
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief WeikaiRegister objects acts as proxies to access remote register independently of the bus type.
/// @details This is an abstract interface class that provides many operations to access to registers while hiding
/// the actual implementation. This allow to accesses the registers in the Weikai component abstract class independently
/// of the actual bus (I2C, SPI). The derived classes will actually implements the specific bus operations dependant of
/// the bus used.
/// @n typical usage of WeikaiRegister:
/// @code
/// WeikaiRegister reg_X {&WeikaiComponent, ADDR_REGISTER_X, CHANNEL_NUM} // declaration
/// reg_X |= 0x01; // set bit 0 of the weikai register
/// reg_X &= ~0x01; // reset bit 0 of the weikai register
/// reg_X = 10; // Set the value of weikai register
/// uint val = reg_X; // get the value of weikai register
/// @endcode
class WeikaiRegister {
public:
/// @brief WeikaiRegister constructor.
/// @param comp our parent WeikaiComponent
/// @param reg address of the register
/// @param channel the channel of this register
WeikaiRegister(WeikaiComponent *const comp, uint8_t reg, uint8_t channel)
: comp_(comp), register_(reg), channel_(channel) {}
virtual ~WeikaiRegister() {}
/// @brief overloads the = operator. This is used to set a value into the weikai register
/// @param value to be set
/// @return this object
WeikaiRegister &operator=(uint8_t value);
/// @brief overloads the compound &= operator. This is often used to reset bits in the weikai register
/// @param value performs an & operation with value and store the result
/// @return this object
WeikaiRegister &operator&=(uint8_t value);
/// @brief overloads the compound |= operator. This is often used to set bits in the weikai register
/// @param value performs an | operation with value and store the result
/// @return this object
WeikaiRegister &operator|=(uint8_t value);
/// @brief cast operator that returns the content of the weikai register
operator uint8_t() const { return read_reg(); }
/// @brief reads the register
/// @return the value read from the register
virtual uint8_t read_reg() const = 0;
/// @brief writes the register
/// @param value to write in the register
virtual void write_reg(uint8_t value) = 0;
/// @brief read an array of bytes from the receiver fifo
/// @param data pointer to data buffer
/// @param length number of bytes to read
virtual void read_fifo(uint8_t *data, size_t length) const = 0;
/// @brief write an array of bytes to the transmitter fifo
/// @param data pointer to data buffer
/// @param length number of bytes to write
virtual void write_fifo(uint8_t *data, size_t length) = 0;
WeikaiComponent *const comp_; ///< pointer to our parent (aggregation)
uint8_t register_; ///< address of the register
uint8_t channel_; ///< channel for this register
};
class WeikaiChannel; // forward declaration
////////////////////////////////////////////////////////////////////////////////////
/// @brief The WeikaiComponent class stores the information global to the WeiKai component
/// and provides methods to set/access this information. It is also the container of
/// the WeikaiChannel children objects. This class is derived from esphome::Component
/// class.
////////////////////////////////////////////////////////////////////////////////////
class WeikaiComponent : public Component {
public:
/// @brief virtual destructor
virtual ~WeikaiComponent() {}
/// @brief store crystal frequency
/// @param crystal frequency
void set_crystal(uint32_t crystal) { this->crystal_ = crystal; }
/// @brief store if the component is in test mode
/// @param test_mode 0=normal mode any other values mean component in test mode
void set_test_mode(int test_mode) { this->test_mode_ = test_mode; }
/// @brief store the name for the component
/// @param name the name as defined by the python code generator
void set_name(std::string name) { this->name_ = std::move(name); }
/// @brief Get the name of the component
/// @return the name
const char *get_name() { return this->name_.c_str(); }
/// @brief override the Component loop()
void loop() override;
bool page1() { return page1_; }
/// @brief Factory method to create a Register object
/// @param reg address of the register
/// @param channel channel associated with this register
/// @return a reference to WeikaiRegister
virtual WeikaiRegister &reg(uint8_t reg, uint8_t channel) = 0;
protected:
friend class WeikaiChannel;
/// @brief Get the priority of the component
/// @return the priority
/// @details The priority is set below setup_priority::BUS because we use
/// the spi/i2c busses (which has a priority of BUS) to communicate and the WeiKai
/// therefore it is seen by our client almost as if it was a bus.
float get_setup_priority() const override { return setup_priority::BUS - 0.1F; }
friend class WeikaiGPIOPin;
/// Helper method to read the value of a pin.
bool read_pin_val_(uint8_t pin);
/// Helper method to write the value of a pin.
void write_pin_val_(uint8_t pin, bool value);
/// Helper method to set the pin mode of a pin.
void set_pin_direction_(uint8_t pin, gpio::Flags flags);
#ifdef TEST_COMPONENT
/// @defgroup test_ Test component information
/// @brief Contains information about the auto-tests of the component
/// @{
void test_gpio_input_();
void test_gpio_output_();
/// @}
#endif
uint8_t pin_config_{0x00}; ///< pin config mask: 1 means OUTPUT, 0 means INPUT
uint8_t output_state_{0x00}; ///< output state: 1 means HIGH, 0 means LOW
uint8_t input_state_{0x00}; ///< input pin states: 1 means HIGH, 0 means LOW
uint32_t crystal_; ///< crystal value;
int test_mode_; ///< test mode value (0 -> no tests)
bool page1_{false}; ///< set to true when in "page1 mode"
std::vector<WeikaiChannel *> children_{}; ///< the list of WeikaiChannel UART children
std::string name_; ///< name of entity
};
///////////////////////////////////////////////////////////////////////////////
/// @brief Helper class to expose a WeiKai family IO pin as an internal GPIO pin.
///////////////////////////////////////////////////////////////////////////////
class WeikaiGPIOPin : public GPIOPin {
public:
void set_parent(WeikaiComponent *parent) { this->parent_ = parent; }
void set_pin(uint8_t pin) { this->pin_ = pin; }
void set_inverted(bool inverted) { this->inverted_ = inverted; }
void set_flags(gpio::Flags flags) { this->flags_ = flags; }
void setup() override;
std::string dump_summary() const override;
void pin_mode(gpio::Flags flags) override { this->parent_->set_pin_direction_(this->pin_, flags); }
bool digital_read() override { return this->parent_->read_pin_val_(this->pin_) != this->inverted_; }
void digital_write(bool value) override { this->parent_->write_pin_val_(this->pin_, value != this->inverted_); }
protected:
WeikaiComponent *parent_{nullptr};
uint8_t pin_;
bool inverted_;
gpio::Flags flags_;
};
///////////////////////////////////////////////////////////////////////////////////////////////////
/// @brief The WeikaiChannel class is used to implement all the virtual methods of the ESPHome
/// uart::UARTComponent virtual class. This class is common to the different members of the Weikai
/// components family and therefore avoid code duplication.
///////////////////////////////////////////////////////////////////////////////////////////////////
class WeikaiChannel : public uart::UARTComponent {
public:
/// @brief We belongs to this WeikaiComponent
/// @param parent pointer to the component we belongs to
void set_parent(WeikaiComponent *parent) {
this->parent_ = parent;
this->parent_->children_.push_back(this); // add ourself to the list (vector)
}
/// @brief Sets the channel number
/// @param channel number
void set_channel(uint8_t channel) { this->channel_ = channel; }
/// @brief The name as generated by the Python code generator
/// @param name of the channel
void set_channel_name(std::string name) { this->name_ = std::move(name); }
/// @brief Get the channel name
/// @return the name
const char *get_channel_name() { return this->name_.c_str(); }
/// @brief Setup the channel
void virtual setup_channel();
/// @brief dump channel information
void virtual dump_channel();
/// @brief Factory method to create a WeikaiRegister proxy object
/// @param reg address of the register
/// @return a reference to WeikaiRegister
WeikaiRegister &reg(uint8_t reg) { return this->parent_->reg(reg, channel_); }
//
// we implements/overrides the virtual class from UARTComponent
//
/// @brief Writes a specified number of bytes to a serial port
/// @param buffer pointer to the buffer
/// @param length number of bytes to write
/// @details This method sends 'length' characters from the buffer to the serial line. Unfortunately (unlike the
/// Arduino equivalent) this method does not return any flag and therefore it is not possible to know if any/all bytes
/// have been transmitted correctly. Another problem is that it is not possible to know ahead of time how many bytes
/// we can safely send as there is no tx_available() method provided! To avoid overrun when using the write method you
/// can use the flush() method to wait until the transmit fifo is empty.
/// @n Typical usage could be:
/// @code
/// // ...
/// uint8_t buffer[128];
/// // ...
/// write_array(&buffer, length);
/// flush();
/// // ...
/// @endcode
void write_array(const uint8_t *buffer, size_t length) override;
/// @brief Reads a specified number of bytes from a serial port
/// @param buffer buffer to store the bytes
/// @param length number of bytes to read
/// @return true if succeed, false otherwise
/// @details Typical usage:
/// @code
/// // ...
/// auto length = available();
/// uint8_t buffer[128];
/// if (length > 0) {
/// auto status = read_array(&buffer, length)
/// // test status ...
/// }
/// @endcode
bool read_array(uint8_t *buffer, size_t length) override;
/// @brief Reads the first byte in FIFO without removing it
/// @param buffer pointer to the byte
/// @return true if succeed reading one byte, false if no character available
/// @details This method returns the next byte from receiving buffer without removing it from the internal fifo. It
/// returns true if a character is available and has been read, false otherwise.\n
bool peek_byte(uint8_t *buffer) override;
/// @brief Returns the number of bytes in the receive buffer
/// @return the number of bytes available in the receiver fifo
int available() override;
/// @brief Flush the output fifo.
/// @details If we refer to Serial.flush() in Arduino it says: ** Waits for the transmission of outgoing serial data
/// to complete. (Prior to Arduino 1.0, this the method was removing any buffered incoming serial data.). ** Therefore
/// we wait until all bytes are gone with a timeout of 100 ms
void flush() override;
protected:
friend class WeikaiComponent;
/// @brief this cannot happen with external uart therefore we do nothing
void check_logger_conflict() override {}
/// @brief reset the weikai internal FIFO
void reset_fifo_();
/// @brief set the line parameters
void set_line_param_();
/// @brief set the baud rate
void set_baudrate_();
/// @brief Returns the number of bytes in the receive fifo
/// @return the number of bytes in the fifo
size_t rx_in_fifo_();
/// @brief Returns the number of bytes in the transmit fifo
/// @return the number of bytes in the fifo
size_t tx_in_fifo_();
/// @brief test if transmit buffer is not empty in the status register (optimization)
/// @return true if not emptygroup test_
bool tx_fifo_is_not_empty_();
/// @brief transfer bytes from the weikai internal FIFO to the buffer (if any)
/// @return number of bytes transferred
size_t xfer_fifo_to_buffer_();
/// @brief check if channel is alive
/// @return true if OK
bool virtual check_channel_down();
#ifdef TEST_COMPONENT
/// @ingroup test_
/// @{
/// @brief Test the write_array() method
/// @param message to display
void uart_send_test_(char *message);
/// @brief Test the read_array() method
/// @param message to display
/// @return true if success
bool uart_receive_test_(char *message);
/// @}
#endif
/// @brief the buffer where we store temporarily the bytes received
WKRingBuffer<uint8_t, RING_BUFFER_SIZE> receive_buffer_;
WeikaiComponent *parent_; ///< our WK2168component parent
uint8_t channel_; ///< our Channel number
uint8_t data_; ///< a one byte buffer for register read storage
std::string name_; ///< name of the entity
};
} // namespace weikai
} // namespace esphome

View File

@ -0,0 +1,304 @@
/// @file wk_reg_def.h
/// @author DrCoolZic
/// @brief WeiKai component family - registers' definition
/// @date Last Modified: 2024/02/18 15:49:18
#pragma once
namespace esphome {
namespace weikai {
////////////////////////////////////////////////////////////////////////////////////////
/// Definition of the WeiKai registers
////////////////////////////////////////////////////////////////////////////////////////
/// @defgroup wk2168_gr_ WeiKai Global Registers
/// This section groups all **Global Registers**: these registers are global to the
/// the WeiKai chip (i.e. independent of the UART channel used)
/// @note only registers and parameters used have been fully documented
/// @{
/// @brief Global Control Register - 00 0000
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | M0 | M1 | RSV | C4EN | C3EN | C2EN | C1EN | name
/// -------------------------------------------------------------------------
/// | R | R | R | R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_GENA = 0x00;
/// @brief Channel 4 enable clock (0: disable, 1: enable)
constexpr uint8_t GENA_C4EN = 1 << 3;
/// @brief Channel 3 enable clock (0: disable, 1: enable)
constexpr uint8_t GENA_C3EN = 1 << 2;
/// @brief Channel 2 enable clock (0: disable, 1: enable)
constexpr uint8_t GENA_C2EN = 1 << 1;
/// @brief Channel 1 enable clock (0: disable, 1: enable)
constexpr uint8_t GENA_C1EN = 1 << 0;
/// @brief Global Reset Register - 00 0001
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | C4SLEEP| C3SLEEP| C2SLEEP| C1SLEEP| C4RST | C3RST | C2RST | C1RST | name
/// -------------------------------------------------------------------------
/// | R | R | R | R | W1/R0 | W1/R0 | W1/R0 | W1/R0 | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_GRST = 0x01;
/// @brief Channel 4 soft reset (0: not reset, 1: reset)
constexpr uint8_t GRST_C4RST = 1 << 3;
/// @brief Channel 3 soft reset (0: not reset, 1: reset)
constexpr uint8_t GRST_C3RST = 1 << 2;
/// @brief Channel 2 soft reset (0: not reset, 1: reset)
constexpr uint8_t GRST_C2RST = 1 << 1;
/// @brief Channel 1 soft reset (0: not reset, 1: reset)
constexpr uint8_t GRST_C1RST = 1 << 0;
/// @brief Global Master channel control register (not used) - 000010
constexpr uint8_t WKREG_GMUT = 0x02;
/// Global interrupt register (not used) - 01 0000
constexpr uint8_t WKREG_GIER = 0x10;
/// Global interrupt flag register (not used) 01 0001
constexpr uint8_t WKREG_GIFR = 0x11;
/// @brief Global GPIO direction register - 10 0001
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | PD7 | PD6 | PD5 | PD4 | PD3 | PD2 | PD1 | PD0 | name
/// -------------------------------------------------------------------------
/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_GPDIR = 0x21;
/// @brief Global GPIO data register - 11 0001
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | PV7 | PV6 | PV5 | PV4 | PV3 | PV2 | PV1 | PV0 | name
/// -------------------------------------------------------------------------
/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_GPDAT = 0x31;
/// @}
/// @defgroup WeiKai_cr_ WeiKai Channel Registers
/// @brief Definition of the register linked to a particular channel
/// @details This topic groups all the **Channel Registers**: these registers are specific
/// to the a specific channel i.e. each channel has its own set of registers
/// @note only registers and parameters used have been documented
/// @{
/// @defgroup cr_p0 Channel registers when SPAGE=0
/// @brief Definition of the register linked to a particular channel when SPAGE=0
/// @details The channel registers are further splitted into two groups.
/// This first group is defined when the Global register WKREG_SPAGE is 0
/// @{
/// @brief Global Page register c0/c1 0011
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | RSV | PAGE | name
/// -------------------------------------------------------------------------
/// | R | R | R | R | R | R | R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_SPAGE = 0x03;
/// @brief Serial Control Register - c0/c1 0100
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | RSV | SLPEN | TXEN | RXEN | name
/// -------------------------------------------------------------------------
/// | R | R | R | R | R | R/W | R/W | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_SCR = 0x04;
/// @brief transmission control (0: enable, 1: disable)
constexpr uint8_t SCR_TXEN = 1 << 1;
/// @brief receiving control (0: enable, 1: disable)
constexpr uint8_t SCR_RXEN = 1 << 0;
/// @brief Line Configuration Register - c0/c1 0101
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | RSV | BREAK | IREN | PAEN | PARITY | STPL | name
/// -------------------------------------------------------------------------
/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_LCR = 0x05;
/// @brief Parity enable (0: no check, 1: check)
constexpr uint8_t LCR_PAEN = 1 << 3;
/// @brief Parity force 0
constexpr uint8_t LCR_PAR_F0 = 0 << 1;
/// @brief Parity odd
constexpr uint8_t LCR_PAR_ODD = 1 << 1;
/// @brief Parity even
constexpr uint8_t LCR_PAR_EVEN = 2 << 1;
/// @brief Parity force 1
constexpr uint8_t LCR_PAR_F1 = 3 << 1;
/// @brief Stop length (0: 1 bit, 1: 2 bits)
constexpr uint8_t LCR_STPL = 1 << 0;
/// @brief FIFO Control Register - c0/c1 0110
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | TFTRIG | RFTRIG | TFEN | RFEN | TFRST | RFRST | name
/// -------------------------------------------------------------------------
/// | W/R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_FCR = 0x06;
/// @brief Transmitter FIFO enable
constexpr uint8_t FCR_TFEN = 1 << 3;
/// @brief Receiver FIFO enable
constexpr uint8_t FCR_RFEN = 1 << 2;
/// @brief Transmitter FIFO reset
constexpr uint8_t FCR_TFRST = 1 << 1;
/// @brief Receiver FIFO reset
constexpr uint8_t FCR_RFRST = 1 << 0;
/// @brief Serial Interrupt Enable Register (not used) - c0/c1 0111
constexpr uint8_t WKREG_SIER = 0x07;
/// @brief Serial Interrupt Flag Register (not used) - c0/c1 1000
constexpr uint8_t WKREG_SIFR = 0x08;
/// @brief Transmitter FIFO Count - c0/c1 1001
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
/// -------------------------------------------------------------------------
/// | NUMBER OF DATA IN TRANSMITTER FIFO |
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_TFCNT = 0x09;
/// @brief Receiver FIFO count - c0/c1 1010
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
/// -------------------------------------------------------------------------
/// | NUMBER OF DATA IN RECEIVER FIFO |
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_RFCNT = 0x0A;
/// @brief FIFO Status Register - c0/c1 1011
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 | bit
/// -------------------------------------------------------------------------
/// | RFOE | RFLB | RFFE | RFPE | RFDAT | TFDAT | TFFULL | TBUSY | name
/// -------------------------------------------------------------------------
/// | R | W/R | W/R | W/R | W/R | W/R | W/R | W/R | type
/// -------------------------------------------------------------------------
/// | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | reset
/// -------------------------------------------------------------------------
/// @endcode
/// @warning The received buffer can hold 256 bytes. However, as the RFCNT reg
/// is 8 bits, if we have 256 byte in the register this is reported as 0 ! Therefore
/// RFCNT=0 can indicate that there are 0 **or** 256 bytes in the buffer. If we
/// have RFDAT = 1 and RFCNT = 0 it should be interpreted as 256 bytes in the FIFO.
/// @note Note that in case of overflow the RFOE goes to one **but** as soon as you read
/// the FSR this bit is cleared. Therefore Overflow can be read only once.
/// @n The same problem applies to the transmit buffer but here we have to check the
/// TFFULL flag. So if TFFULL is set and TFCNT is 0 this should be interpreted as 256
constexpr uint8_t WKREG_FSR = 0x0B;
/// @brief Receiver FIFO Overflow Error (0: no OE, 1: OE)
constexpr uint8_t FSR_RFOE = 1 << 7;
/// @brief Receiver FIFO Line Break (0: no LB, 1: LB)
constexpr uint8_t FSR_RFLB = 1 << 6;
/// @brief Receiver FIFO Frame Error (0: no FE, 1: FE)
constexpr uint8_t FSR_RFFE = 1 << 5;
/// @brief Receiver Parity Error (0: no PE, 1: PE)
constexpr uint8_t FSR_RFPE = 1 << 4;
/// @brief Receiver FIFO count (0: empty, 1: not empty)
constexpr uint8_t FSR_RFDAT = 1 << 3;
/// @brief Transmitter FIFO count (0: empty, 1: not empty)
constexpr uint8_t FSR_TFDAT = 1 << 2;
/// @brief Transmitter FIFO full (0: not full, 1: full)
constexpr uint8_t FSR_TFFULL = 1 << 1;
/// @brief Transmitter busy (0 nothing to transmit, 1: transmitter busy sending)
constexpr uint8_t FSR_TBUSY = 1 << 0;
/// @brief Line Status Register (not used - using FIFO)
constexpr uint8_t WKREG_LSR = 0x0C;
/// @brief FIFO Data Register (not used - does not seems to work)
constexpr uint8_t WKREG_FDAT = 0x0D;
/// @}
/// @defgroup cr_p1 Channel registers for SPAGE=1
/// @brief Definition of the register linked to a particular channel when SPAGE=1
/// @details The channel registers are further splitted into two groups.
/// This second group is defined when the Global register WKREG_SPAGE is 1
/// @{
/// @brief Baud rate configuration register: high byte - c0/c1 0100
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
/// -------------------------------------------------------------------------
/// | High byte of the baud rate |
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_BRH = 0x04;
/// @brief Baud rate configuration register: low byte - c0/c1 0101
/// @details @code
/// -------------------------------------------------------------------------
/// | b7 | b6 | b5 | b4 | b3 | b2 | b1 | b0 |
/// -------------------------------------------------------------------------
/// | Low byte of the baud rate |
/// -------------------------------------------------------------------------
/// @endcode
constexpr uint8_t WKREG_BRL = 0x05;
/// @brief Baud rate configuration register decimal part - c0/c1 0110
constexpr uint8_t WKREG_BRD = 0x06;
/// @brief Receive FIFO Interrupt trigger configuration (not used) - c0/c1 0111
constexpr uint8_t WKREG_RFI = 0x07;
/// @brief Transmit FIFO interrupt trigger configuration (not used) - c0/c1 1000
constexpr uint8_t WKREG_TFI = 0x08;
/// @}
/// @}
} // namespace weikai
} // namespace esphome

View File

@ -0,0 +1 @@
CODEOWNERS = ["@DrCoolZic"]

View File

@ -0,0 +1,177 @@
/// @file weikai_i2c.cpp
/// @brief WeiKai component family - classes implementation
/// @date Last Modified: 2024/04/06 14:43:31
/// @details The classes declared in this file can be used by the Weikai family
#include "weikai_i2c.h"
namespace esphome {
namespace weikai_i2c {
static const char *const TAG = "weikai_i2c";
/// @brief Display a buffer in hexadecimal format (32 hex values / line).
void print_buffer(const uint8_t *data, size_t length) {
char hex_buffer[100];
hex_buffer[(3 * 32) + 1] = 0;
for (size_t i = 0; i < length; i++) {
snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]);
if (i % 32 == 31) {
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
if (length % 32) {
// null terminate if incomplete line
hex_buffer[3 * (length % 32) + 2] = 0;
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER",
"SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"};
static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL",
"TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"};
using namespace weikai;
// method to print a register value as text: used in the log messages ...
const char *reg_to_str(int reg, bool page1) {
if (reg == WKREG_GPDAT) {
return "GPDAT";
} else if (reg == WKREG_GPDIR) {
return "GPDIR";
} else {
return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F];
}
}
enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO
/// @brief Computes the I²C bus's address used to access the component
/// @param base_address the base address of the component - set by the A1 A0 pins
/// @param channel (0-3) the UART channel
/// @param fifo (0-1) 0 = access to internal register, 1 = direct access to fifo
/// @return the i2c address to use
inline uint8_t i2c_address(uint8_t base_address, uint8_t channel, RegType fifo) {
// the address of the device is:
// +----+----+----+----+----+----+----+----+
// | 0 | A1 | A0 | 1 | 0 | C1 | C0 | F |
// +----+----+----+----+----+----+----+----+
// where:
// - A1,A0 is the address read from A1,A0 switch
// - C1,C0 is the channel number (in practice only 00 or 01)
// - F is: 0 when accessing register, one when accessing FIFO
uint8_t const addr = base_address | channel << 1 | fifo << 0;
return addr;
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiRegisterI2C methods
///////////////////////////////////////////////////////////////////////////////
uint8_t WeikaiRegisterI2C::read_reg() const {
uint8_t value = 0x00;
WeikaiComponentI2C *comp_i2c = static_cast<WeikaiComponentI2C *>(this->comp_);
uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, REG);
comp_i2c->set_i2c_address(address);
auto error = comp_i2c->read_register(this->register_, &value, 1);
if (error == i2c::NO_ERROR) {
this->comp_->status_clear_warning();
ESP_LOGVV(TAG, "WeikaiRegisterI2C::read_reg() @%02X reg=%s ch=%u I2C_code:%d, buf=%02X", address,
reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value);
} else { // error
this->comp_->status_set_warning();
ESP_LOGE(TAG, "WeikaiRegisterI2C::read_reg() @%02X reg=%s ch=%u I2C_code:%d, buf=%02X", address,
reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value);
}
return value;
}
void WeikaiRegisterI2C::read_fifo(uint8_t *data, size_t length) const {
WeikaiComponentI2C *comp_i2c = static_cast<WeikaiComponentI2C *>(this->comp_);
uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, FIFO);
comp_i2c->set_i2c_address(address);
auto error = comp_i2c->read(data, length);
if (error == i2c::NO_ERROR) {
this->comp_->status_clear_warning();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "WeikaiRegisterI2C::read_fifo() @%02X ch=%d I2C_code:%d len=%d buffer", address, this->channel_,
(int) error, length);
print_buffer(data, length);
#endif
} else { // error
this->comp_->status_set_warning();
ESP_LOGE(TAG, "WeikaiRegisterI2C::read_fifo() @%02X reg=N/A ch=%d I2C_code:%d len=%d buf=%02X...", address,
this->channel_, (int) error, length, data[0]);
}
}
void WeikaiRegisterI2C::write_reg(uint8_t value) {
WeikaiComponentI2C *comp_i2c = static_cast<WeikaiComponentI2C *>(this->comp_);
uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, REG); // update the i2c bus
comp_i2c->set_i2c_address(address);
auto error = comp_i2c->write_register(this->register_, &value, 1);
if (error == i2c::NO_ERROR) {
this->comp_->status_clear_warning();
ESP_LOGVV(TAG, "WK2168Reg::write_reg() @%02X reg=%s ch=%d I2C_code:%d buf=%02X", address,
reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value);
} else { // error
this->comp_->status_set_warning();
ESP_LOGE(TAG, "WK2168Reg::write_reg() @%02X reg=%s ch=%d I2C_code:%d buf=%d", address,
reg_to_str(this->register_, comp_i2c->page1()), this->channel_, (int) error, value);
}
}
void WeikaiRegisterI2C::write_fifo(uint8_t *data, size_t length) {
WeikaiComponentI2C *comp_i2c = static_cast<WeikaiComponentI2C *>(this->comp_);
uint8_t address = i2c_address(comp_i2c->base_address_, this->channel_, FIFO); // set fifo flag
comp_i2c->set_i2c_address(address);
auto error = comp_i2c->write(data, length);
if (error == i2c::NO_ERROR) {
this->comp_->status_clear_warning();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "WK2168Reg::write_fifo() @%02X ch=%d I2C_code:%d len=%d buffer", address, this->channel_,
(int) error, length);
print_buffer(data, length);
#endif
} else { // error
this->comp_->status_set_warning();
ESP_LOGE(TAG, "WK2168Reg::write_fifo() @%02X reg=N/A, ch=%d I2C_code:%d len=%d, buf=%02X...", address,
this->channel_, (int) error, length, data[0]);
}
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiComponentI2C methods
///////////////////////////////////////////////////////////////////////////////
void WeikaiComponentI2C::setup() {
// before any manipulation we store the address to base_address_ for future use
this->base_address_ = this->address_;
ESP_LOGCONFIG(TAG, "Setting up wk2168_i2c: %s with %d UARTs at @%02X ...", this->get_name(), this->children_.size(),
this->base_address_);
// enable all channels
this->reg(WKREG_GENA, 0) = GENA_C1EN | GENA_C2EN | GENA_C3EN | GENA_C4EN;
// reset all channels
this->reg(WKREG_GRST, 0) = GRST_C1RST | GRST_C2RST | GRST_C3RST | GRST_C4RST;
// initialize the spage register to page 0
this->reg(WKREG_SPAGE, 0) = 0;
this->page1_ = false;
// we setup our children channels
for (auto *child : this->children_) {
child->setup_channel();
}
}
void WeikaiComponentI2C::dump_config() {
ESP_LOGCONFIG(TAG, "Initialization of %s with %d UARTs completed", this->get_name(), this->children_.size());
ESP_LOGCONFIG(TAG, " Crystal: %" PRIu32, this->crystal_);
if (test_mode_)
ESP_LOGCONFIG(TAG, " Test mode: %d", test_mode_);
ESP_LOGCONFIG(TAG, " Transfer buffer size: %d", XFER_MAX_SIZE);
this->address_ = this->base_address_; // we restore the base_address before display (less confusing)
LOG_I2C_DEVICE(this);
for (auto *child : this->children_) {
child->dump_channel();
}
}
} // namespace weikai_i2c
} // namespace esphome

View File

@ -0,0 +1,61 @@
/// @file weikai_i2c.h
/// @author DrCoolZic
/// @brief WeiKai component family - classes declaration
/// @date Last Modified: 2024/03/01 13:31:57
/// @details The classes declared in this file can be used by the Weikai family
/// wk2132_i2c, wk2168_i2c, wk2204_i2c, wk2212_i2c
#pragma once
#include <bitset>
#include <memory>
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/i2c/i2c.h"
#include "esphome/components/weikai/weikai.h"
namespace esphome {
namespace weikai_i2c {
class WeikaiComponentI2C;
// using namespace weikai;
////////////////////////////////////////////////////////////////////////////////////
/// @brief WeikaiRegisterI2C objects acts as proxies to access remote register through an I2C Bus
////////////////////////////////////////////////////////////////////////////////////
class WeikaiRegisterI2C : public weikai::WeikaiRegister {
public:
uint8_t read_reg() const override;
void write_reg(uint8_t value) override;
void read_fifo(uint8_t *data, size_t length) const override;
void write_fifo(uint8_t *data, size_t length) override;
protected:
friend WeikaiComponentI2C;
WeikaiRegisterI2C(weikai::WeikaiComponent *const comp, uint8_t reg, uint8_t channel)
: weikai::WeikaiRegister(comp, reg, channel) {}
};
////////////////////////////////////////////////////////////////////////////////////
/// @brief The WeikaiComponentI2C class stores the information to the WeiKai component
/// connected through an I2C bus.
////////////////////////////////////////////////////////////////////////////////////
class WeikaiComponentI2C : public weikai::WeikaiComponent, public i2c::I2CDevice {
public:
weikai::WeikaiRegister &reg(uint8_t reg, uint8_t channel) override {
reg_i2c_.register_ = reg;
reg_i2c_.channel_ = channel;
return reg_i2c_;
}
//
// override Component methods
//
void setup() override;
void dump_config() override;
uint8_t base_address_; ///< base address of I2C device
WeikaiRegisterI2C reg_i2c_{this, 0, 0}; ///< init to this component
};
} // namespace weikai_i2c
} // namespace esphome

View File

@ -0,0 +1 @@
CODEOWNERS = ["@DrCoolZic"]

View File

@ -0,0 +1,189 @@
/// @file weikai_spi.cpp
/// @brief WeiKai component family - classes implementation
/// @date Last Modified: 2024/04/06 14:46:09
/// @details The classes declared in this file can be used by the Weikai family
#include "weikai_spi.h"
namespace esphome {
namespace weikai_spi {
using namespace weikai;
static const char *const TAG = "weikai_spi";
/// @brief convert an int to binary representation as C++ std::string
/// @param val integer to convert
/// @return a std::string
inline std::string i2s(uint8_t val) { return std::bitset<8>(val).to_string(); }
/// Convert std::string to C string
#define I2S2CS(val) (i2s(val).c_str())
/// @brief measure the time elapsed between two calls
/// @param last_time time of the previous call
/// @return the elapsed time in microseconds
uint32_t elapsed_ms(uint32_t &last_time) {
uint32_t e = millis() - last_time;
last_time = millis();
return e;
};
/// @brief Converts the parity enum value to a C string
/// @param parity enum
/// @return the string
const char *p2s(uart::UARTParityOptions parity) {
using namespace uart;
switch (parity) {
case UART_CONFIG_PARITY_NONE:
return "NONE";
case UART_CONFIG_PARITY_EVEN:
return "EVEN";
case UART_CONFIG_PARITY_ODD:
return "ODD";
default:
return "UNKNOWN";
}
}
/// @brief Display a buffer in hexadecimal format (32 hex values / line).
void print_buffer(const uint8_t *data, size_t length) {
char hex_buffer[100];
hex_buffer[(3 * 32) + 1] = 0;
for (size_t i = 0; i < length; i++) {
snprintf(&hex_buffer[3 * (i % 32)], sizeof(hex_buffer), "%02X ", data[i]);
if (i % 32 == 31) {
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
if (length % 32) {
// null terminate if incomplete line
hex_buffer[3 * (length % 32) + 2] = 0;
ESP_LOGVV(TAG, " %s", hex_buffer);
}
}
static const char *const REG_TO_STR_P0[16] = {"GENA", "GRST", "GMUT", "SPAGE", "SCR", "LCR", "FCR", "SIER",
"SIFR", "TFCNT", "RFCNT", "FSR", "LSR", "FDAT", "FWCR", "RS485"};
static const char *const REG_TO_STR_P1[16] = {"GENA", "GRST", "GMUT", "SPAGE", "BAUD1", "BAUD0", "PRES", "RFTL",
"TFTL", "FWTH", "FWTL", "XON1", "XOFF1", "SADR", "SAEN", "RTSDLY"};
// method to print a register value as text: used in the log messages ...
const char *reg_to_str(int reg, bool page1) {
if (reg == WKREG_GPDAT) {
return "GPDAT";
} else if (reg == WKREG_GPDIR) {
return "GPDIR";
} else {
return page1 ? REG_TO_STR_P1[reg & 0x0F] : REG_TO_STR_P0[reg & 0x0F];
}
}
enum RegType { REG = 0, FIFO = 1 }; ///< Register or FIFO
enum CmdType { WRITE_CMD = 0, READ_CMD = 1 }; ///< Read or Write transfer
/// @brief Computes the SPI command byte
/// @param transfer_type read or write command
/// @param reg (0-15) the address of the register
/// @param channel (0-3) the UART channel
/// @param fifo (0-1) 0 = access to internal register, 1 = direct access to fifo
/// @return the spi command byte
/// @details
/// +------+------+------+------+------+------+------+------+
/// | FIFO | R/W | C1-C0 | A3-A0 |
/// +------+------+-------------+---------------------------+
/// FIFO: 0 = register, 1 = FIFO
/// R/W: 0 = write, 1 = read
/// C1-C0: Channel (0-1)
/// A3-A0: Address (0-F)
inline static uint8_t cmd_byte(RegType fifo, CmdType transfer_type, uint8_t channel, uint8_t reg) {
return (fifo << 7 | transfer_type << 6 | channel << 4 | reg << 0);
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiRegisterSPI methods
///////////////////////////////////////////////////////////////////////////////
uint8_t WeikaiRegisterSPI::read_reg() const {
auto *spi_comp = static_cast<WeikaiComponentSPI *>(this->comp_);
uint8_t cmd = cmd_byte(REG, READ_CMD, this->channel_, this->register_);
spi_comp->enable();
spi_comp->write_byte(cmd);
uint8_t val = spi_comp->read_byte();
spi_comp->disable();
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(cmd), cmd,
reg_to_str(this->register_, this->comp_->page1()), this->channel_, val);
return val;
}
void WeikaiRegisterSPI::read_fifo(uint8_t *data, size_t length) const {
auto *spi_comp = static_cast<WeikaiComponentSPI *>(this->comp_);
uint8_t cmd = cmd_byte(FIFO, READ_CMD, this->channel_, this->register_);
spi_comp->enable();
spi_comp->write_byte(cmd);
spi_comp->read_array(data, length);
spi_comp->disable();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "WeikaiRegisterSPI::read_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_,
length);
print_buffer(data, length);
#endif
}
void WeikaiRegisterSPI::write_reg(uint8_t value) {
auto *spi_comp = static_cast<WeikaiComponentSPI *>(this->comp_);
uint8_t buf[2]{cmd_byte(REG, WRITE_CMD, this->channel_, this->register_), value};
spi_comp->enable();
spi_comp->write_array(buf, 2);
spi_comp->disable();
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_reg() cmd=%s(%02X) reg=%s ch=%d buf=%02X", I2S2CS(buf[0]), buf[0],
reg_to_str(this->register_, this->comp_->page1()), this->channel_, buf[1]);
}
void WeikaiRegisterSPI::write_fifo(uint8_t *data, size_t length) {
auto *spi_comp = static_cast<WeikaiComponentSPI *>(this->comp_);
uint8_t cmd = cmd_byte(FIFO, WRITE_CMD, this->channel_, this->register_);
spi_comp->enable();
spi_comp->write_byte(cmd);
spi_comp->write_array(data, length);
spi_comp->disable();
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
ESP_LOGVV(TAG, "WeikaiRegisterSPI::write_fifo() cmd=%s(%02X) ch=%d len=%d buffer", I2S2CS(cmd), cmd, this->channel_,
length);
print_buffer(data, length);
#endif
}
///////////////////////////////////////////////////////////////////////////////
// The WeikaiComponentSPI methods
///////////////////////////////////////////////////////////////////////////////
void WeikaiComponentSPI::setup() {
using namespace weikai;
ESP_LOGCONFIG(TAG, "Setting up wk2168_spi: %s with %d UARTs...", this->get_name(), this->children_.size());
this->spi_setup();
// enable all channels
this->reg(WKREG_GENA, 0) = GENA_C1EN | GENA_C2EN | GENA_C3EN | GENA_C4EN;
// reset all channels
this->reg(WKREG_GRST, 0) = GRST_C1RST | GRST_C2RST | GRST_C3RST | GRST_C4RST;
// initialize the spage register to page 0
this->reg(WKREG_SPAGE, 0) = 0;
this->page1_ = false;
// we setup our children channels
for (auto *child : this->children_) {
child->setup_channel();
}
}
void WeikaiComponentSPI::dump_config() {
ESP_LOGCONFIG(TAG, "Initialization of %s with %d UARTs completed", this->get_name(), this->children_.size());
ESP_LOGCONFIG(TAG, " Crystal: %" PRIu32 "", this->crystal_);
if (test_mode_)
ESP_LOGCONFIG(TAG, " Test mode: %d", test_mode_);
ESP_LOGCONFIG(TAG, " Transfer buffer size: %d", XFER_MAX_SIZE);
LOG_PIN(" CS Pin: ", this->cs_);
for (auto *child : this->children_) {
child->dump_channel();
}
}
} // namespace weikai_spi
} // namespace esphome

View File

@ -0,0 +1,54 @@
/// @file weikai.h
/// @author DrCoolZic
/// @brief WeiKai component family - classes declaration
/// @date Last Modified: 2024/02/29 17:20:32
/// @details The classes declared in this file can be used by the Weikai family
/// wk2124_spi, wk2132_spi, wk2168_spi, wk2204_spi, wk2212_spi,
#pragma once
#include <bitset>
#include <memory>
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/components/spi/spi.h"
#include "esphome/components/weikai/weikai.h"
namespace esphome {
namespace weikai_spi {
////////////////////////////////////////////////////////////////////////////////////
/// @brief WeikaiRegisterSPI objects acts as proxies to access remote register through an SPI Bus
////////////////////////////////////////////////////////////////////////////////////
class WeikaiRegisterSPI : public weikai::WeikaiRegister {
public:
WeikaiRegisterSPI(weikai::WeikaiComponent *const comp, uint8_t reg, uint8_t channel)
: weikai::WeikaiRegister(comp, reg, channel) {}
uint8_t read_reg() const override;
void write_reg(uint8_t value) override;
void read_fifo(uint8_t *data, size_t length) const override;
void write_fifo(uint8_t *data, size_t length) override;
};
////////////////////////////////////////////////////////////////////////////////////
/// @brief The WeikaiComponentSPI class stores the information to the WeiKai component
/// connected through an SPI bus.
////////////////////////////////////////////////////////////////////////////////////
class WeikaiComponentSPI : public weikai::WeikaiComponent,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW,
spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> {
public:
weikai::WeikaiRegister &reg(uint8_t reg, uint8_t channel) override {
reg_spi_.register_ = reg;
reg_spi_.channel_ = channel;
return reg_spi_;
}
void setup() override;
void dump_config() override;
protected:
WeikaiRegisterSPI reg_spi_{this, 0, 0}; ///< init to this component
};
} // namespace weikai_spi
} // namespace esphome

View File

@ -128,7 +128,7 @@ void WiFiComponent::loop() {
case WIFI_COMPONENT_STATE_COOLDOWN: {
this->status_set_warning();
if (millis() - this->action_started_ > 5000) {
if (this->fast_connect_) {
if (this->fast_connect_ || this->retry_hidden_) {
this->start_connecting(this->sta_[0], false);
} else {
this->start_scanning();
@ -591,6 +591,9 @@ void WiFiComponent::check_connecting_finished() {
return;
}
// We won't retry hidden networks unless a reconnect fails more than three times again
this->retry_hidden_ = false;
ESP_LOGI(TAG, "WiFi Connected!");
this->print_connect_params_();
@ -668,10 +671,11 @@ void WiFiComponent::retry_connect() {
this->wifi_mode_(false, {});
delay(100); // NOLINT
this->num_retried_ = 0;
this->retry_hidden_ = false;
} else {
// Try hidden networks after 3 failed retries
ESP_LOGD(TAG, "Retrying with hidden networks...");
this->fast_connect_ = true;
this->retry_hidden_ = true;
this->num_retried_++;
}
} else {

View File

@ -371,6 +371,7 @@ class WiFiComponent : public Component {
std::vector<WiFiSTAPriority> sta_priorities_;
WiFiAP selected_ap_;
bool fast_connect_{false};
bool retry_hidden_{false};
bool has_ap_{false};
WiFiAP ap_;

View File

@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, weikai
from esphome.const import CONF_ID
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["weikai", "weikai_i2c"]
MULTI_CONF = True
weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c")
WeikaiComponentI2C = weikai_i2c_ns.class_(
"WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentI2C),
}
).extend(i2c.i2c_device_schema(0x2C)),
weikai.check_channel_max_2,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await i2c.register_i2c_device(var, config)

View File

@ -0,0 +1,4 @@
/* compiling with esp-idf framework requires a .cpp file for some reason ? */
namespace esphome {
namespace wk2132_i2c {}
} // namespace esphome

View File

@ -0,0 +1,31 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi, weikai
from esphome.const import CONF_ID
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["weikai", "weikai_spi"]
MULTI_CONF = True
weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi")
WeikaiComponentSPI = weikai_spi_ns.class_(
"WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentSPI),
}
).extend(spi.spi_device_schema()),
weikai.check_channel_max_2,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await spi.register_spi_device(var, config)

View File

@ -0,0 +1,64 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c, weikai
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
)
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["weikai", "weikai_i2c"]
MULTI_CONF = True
CONF_WK2168_I2C = "wk2168_i2c"
weikai_ns = cg.esphome_ns.namespace("weikai")
weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c")
WeikaiComponentI2C = weikai_i2c_ns.class_(
"WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice
)
WeikaiGPIOPin = weikai_ns.class_(
"WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentI2C)
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentI2C),
}
).extend(i2c.i2c_device_schema(0x2C)),
weikai.check_channel_max_4,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await i2c.register_i2c_device(var, config)
WK2168_PIN_SCHEMA = cv.All(
weikai.WEIKAI_PIN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiGPIOPin),
cv.Required(CONF_WK2168_I2C): cv.use_id(WeikaiComponentI2C),
}
),
weikai.validate_pin_mode,
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2168_I2C, WK2168_PIN_SCHEMA)
async def sc16is75x_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_WK2168_I2C])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@ -0,0 +1,62 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, weikai
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
)
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["weikai", "weikai_spi"]
MULTI_CONF = True
CONF_WK2168_SPI = "wk2168_spi"
weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi")
weikai_ns = cg.esphome_ns.namespace("weikai")
WeikaiComponentSPI = weikai_spi_ns.class_(
"WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice
)
WeikaiGPIOPin = weikai_ns.class_(
"WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentSPI)
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(WeikaiComponentSPI)}
).extend(spi.spi_device_schema()),
weikai.check_channel_max_4,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await spi.register_spi_device(var, config)
WK2168_PIN_SCHEMA = cv.All(
weikai.WEIKAI_PIN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiGPIOPin),
cv.Required(CONF_WK2168_SPI): cv.use_id(WeikaiComponentSPI),
},
),
weikai.validate_pin_mode,
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2168_SPI, WK2168_PIN_SCHEMA)
async def sc16is75x_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_WK2168_SPI])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import i2c, weikai
from esphome.const import CONF_ID
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["weikai", "weikai_i2c"]
MULTI_CONF = True
weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c")
WeikaiComponentI2C = weikai_i2c_ns.class_(
"WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentI2C),
}
).extend(i2c.i2c_device_schema(0x2C)),
weikai.check_channel_max_4,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await i2c.register_i2c_device(var, config)

View File

@ -0,0 +1,30 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import spi, weikai
from esphome.const import CONF_ID
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["weikai", "weikai_spi"]
MULTI_CONF = True
weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi")
WeikaiComponentSPI = weikai_spi_ns.class_(
"WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentSPI),
}
).extend(spi.spi_device_schema()),
weikai.check_channel_max_4,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await spi.register_spi_device(var, config)

View File

@ -0,0 +1,64 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import i2c, weikai
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
)
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["i2c"]
AUTO_LOAD = ["weikai", "weikai_i2c"]
MULTI_CONF = True
CONF_WK2212_I2C = "wk2212_i2c"
weikai_ns = cg.esphome_ns.namespace("weikai")
weikai_i2c_ns = cg.esphome_ns.namespace("weikai_i2c")
WeikaiComponentI2C = weikai_i2c_ns.class_(
"WeikaiComponentI2C", weikai.WeikaiComponent, i2c.I2CDevice
)
WeikaiGPIOPin = weikai_ns.class_(
"WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentI2C)
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiComponentI2C),
}
).extend(i2c.i2c_device_schema(0x2C)),
weikai.check_channel_max_2,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await i2c.register_i2c_device(var, config)
WK2212_PIN_SCHEMA = cv.All(
weikai.WEIKAI_PIN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiGPIOPin),
cv.Required(CONF_WK2212_I2C): cv.use_id(WeikaiComponentI2C),
}
),
weikai.validate_pin_mode,
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2212_I2C, WK2212_PIN_SCHEMA)
async def sc16is75x_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_WK2212_I2C])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@ -0,0 +1,62 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome import pins
from esphome.components import spi, weikai
from esphome.const import (
CONF_ID,
CONF_INVERTED,
CONF_MODE,
CONF_NUMBER,
)
CODEOWNERS = ["@DrCoolZic"]
DEPENDENCIES = ["spi"]
AUTO_LOAD = ["weikai", "weikai_spi"]
MULTI_CONF = True
CONF_WK2212_SPI = "wk2212_spi"
weikai_ns = cg.esphome_ns.namespace("weikai")
weikai_spi_ns = cg.esphome_ns.namespace("weikai_spi")
WeikaiComponentSPI = weikai_spi_ns.class_(
"WeikaiComponentSPI", weikai.WeikaiComponent, spi.SPIDevice
)
WeikaiGPIOPin = weikai_ns.class_(
"WeikaiGPIOPin", cg.GPIOPin, cg.Parented.template(WeikaiComponentSPI)
)
CONFIG_SCHEMA = cv.All(
weikai.WKBASE_SCHEMA.extend(
{cv.GenerateID(): cv.declare_id(WeikaiComponentSPI)}
).extend(spi.spi_device_schema()),
weikai.check_channel_max_2,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
cg.add(var.set_name(str(config[CONF_ID])))
await weikai.register_weikai(var, config)
await spi.register_spi_device(var, config)
WK2212_PIN_SCHEMA = cv.All(
weikai.WEIKAI_PIN_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(WeikaiGPIOPin),
cv.Required(CONF_WK2212_SPI): cv.use_id(WeikaiComponentSPI),
},
),
weikai.validate_pin_mode,
)
@pins.PIN_SCHEMA_REGISTRY.register(CONF_WK2212_SPI, WK2212_PIN_SCHEMA)
async def sc16is75x_pin_to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
parent = await cg.get_variable(config[CONF_WK2212_SPI])
cg.add(var.set_parent(parent))
num = config[CONF_NUMBER]
cg.add(var.set_pin(num))
cg.add(var.set_inverted(config[CONF_INVERTED]))
cg.add(var.set_flags(pins.gpio_flags_expr(config[CONF_MODE])))
return var

View File

@ -252,6 +252,8 @@ CONF_ESP8266_DISABLE_SSL_SUPPORT = "esp8266_disable_ssl_support"
CONF_ESPHOME = "esphome"
CONF_ETHERNET = "ethernet"
CONF_EVENT = "event"
CONF_EVENT_TYPE = "event_type"
CONF_EVENT_TYPES = "event_types"
CONF_EXPIRE_AFTER = "expire_after"
CONF_EXPORT_ACTIVE_ENERGY = "export_active_energy"
CONF_EXPORT_REACTIVE_ENERGY = "export_reactive_energy"
@ -518,6 +520,7 @@ CONF_ON_DOUBLE_CLICK = "on_double_click"
CONF_ON_ENROLLMENT_DONE = "on_enrollment_done"
CONF_ON_ENROLLMENT_FAILED = "on_enrollment_failed"
CONF_ON_ENROLLMENT_SCAN = "on_enrollment_scan"
CONF_ON_EVENT = "on_event"
CONF_ON_FINGER_SCAN_INVALID = "on_finger_scan_invalid"
CONF_ON_FINGER_SCAN_MATCHED = "on_finger_scan_matched"
CONF_ON_FINGER_SCAN_MISPLACED = "on_finger_scan_misplaced"
@ -1025,6 +1028,7 @@ DEVICE_CLASS_AWNING = "awning"
DEVICE_CLASS_BATTERY = "battery"
DEVICE_CLASS_BATTERY_CHARGING = "battery_charging"
DEVICE_CLASS_BLIND = "blind"
DEVICE_CLASS_BUTTON = "button"
DEVICE_CLASS_CARBON_DIOXIDE = "carbon_dioxide"
DEVICE_CLASS_CARBON_MONOXIDE = "carbon_monoxide"
DEVICE_CLASS_COLD = "cold"
@ -1037,6 +1041,7 @@ DEVICE_CLASS_DATA_SIZE = "data_size"
DEVICE_CLASS_DATE = "date"
DEVICE_CLASS_DISTANCE = "distance"
DEVICE_CLASS_DOOR = "door"
DEVICE_CLASS_DOORBELL = "doorbell"
DEVICE_CLASS_DURATION = "duration"
DEVICE_CLASS_EMPTY = ""
DEVICE_CLASS_ENERGY = "energy"

View File

@ -57,12 +57,18 @@
#ifdef USE_LOCK
#include "esphome/components/lock/lock.h"
#endif
#ifdef USE_VALVE
#include "esphome/components/valve/valve.h"
#endif
#ifdef USE_MEDIA_PLAYER
#include "esphome/components/media_player/media_player.h"
#endif
#ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif
#ifdef USE_EVENT
#include "esphome/components/event/event.h"
#endif
namespace esphome {
@ -154,6 +160,10 @@ class Application {
void register_lock(lock::Lock *a_lock) { this->locks_.push_back(a_lock); }
#endif
#ifdef USE_VALVE
void register_valve(valve::Valve *valve) { this->valves_.push_back(valve); }
#endif
#ifdef USE_MEDIA_PLAYER
void register_media_player(media_player::MediaPlayer *media_player) { this->media_players_.push_back(media_player); }
#endif
@ -164,6 +174,10 @@ class Application {
}
#endif
#ifdef USE_EVENT
void register_event(event::Event *event) { this->events_.push_back(event); }
#endif
/// Register the component in this Application instance.
template<class C> C *register_component(C *c) {
static_assert(std::is_base_of<Component, C>::value, "Only Component subclasses can be registered");
@ -364,6 +378,15 @@ class Application {
return nullptr;
}
#endif
#ifdef USE_VALVE
const std::vector<valve::Valve *> &get_valves() { return this->valves_; }
valve::Valve *get_valve_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->valves_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_MEDIA_PLAYER
const std::vector<media_player::MediaPlayer *> &get_media_players() { return this->media_players_; }
media_player::MediaPlayer *get_media_player_by_key(uint32_t key, bool include_internal = false) {
@ -386,6 +409,16 @@ class Application {
}
#endif
#ifdef USE_EVENT
const std::vector<event::Event *> &get_events() { return this->events_; }
event::Event *get_event_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->events_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
Scheduler scheduler;
protected:
@ -409,6 +442,9 @@ class Application {
#ifdef USE_BUTTON
std::vector<button::Button *> buttons_{};
#endif
#ifdef USE_EVENT
std::vector<event::Event *> events_{};
#endif
#ifdef USE_SENSOR
std::vector<sensor::Sensor *> sensors_{};
#endif
@ -448,6 +484,9 @@ class Application {
#ifdef USE_LOCK
std::vector<lock::Lock *> locks_{};
#endif
#ifdef USE_VALVE
std::vector<valve::Valve *> valves_{};
#endif
#ifdef USE_MEDIA_PLAYER
std::vector<media_player::MediaPlayer *> media_players_{};
#endif

View File

@ -62,6 +62,7 @@ struct Color {
return Color(esp_scale8(this->red, scale), esp_scale8(this->green, scale), esp_scale8(this->blue, scale),
esp_scale8(this->white, scale));
}
inline Color operator~() const ALWAYS_INLINE { return Color(255 - this->red, 255 - this->green, 255 - this->blue); }
inline Color &operator*=(uint8_t scale) ALWAYS_INLINE {
this->red = esp_scale8(this->red, scale);
this->green = esp_scale8(this->green, scale);

View File

@ -292,6 +292,21 @@ void ComponentIterator::advance() {
}
break;
#endif
#ifdef USE_VALVE
case IteratorState::VALVE:
if (this->at_ >= App.get_valves().size()) {
advance_platform = true;
} else {
auto *valve = App.get_valves()[this->at_];
if (valve->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
success = this->on_valve(valve);
}
}
break;
#endif
#ifdef USE_MEDIA_PLAYER
case IteratorState::MEDIA_PLAYER:
if (this->at_ >= App.get_media_players().size()) {
@ -321,6 +336,21 @@ void ComponentIterator::advance() {
}
}
break;
#endif
#ifdef USE_EVENT
case IteratorState::EVENT:
if (this->at_ >= App.get_events().size()) {
advance_platform = true;
} else {
auto *event = App.get_events()[this->at_];
if (event->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
success = this->on_event(event);
}
}
break;
#endif
case IteratorState::MAX:
if (this->on_end()) {

View File

@ -75,11 +75,17 @@ class ComponentIterator {
#ifdef USE_LOCK
virtual bool on_lock(lock::Lock *a_lock) = 0;
#endif
#ifdef USE_VALVE
virtual bool on_valve(valve::Valve *valve) = 0;
#endif
#ifdef USE_MEDIA_PLAYER
virtual bool on_media_player(media_player::MediaPlayer *media_player);
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual bool on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) = 0;
#endif
#ifdef USE_EVENT
virtual bool on_event(event::Event *event) = 0;
#endif
virtual bool on_end();
@ -141,11 +147,17 @@ class ComponentIterator {
#ifdef USE_LOCK
LOCK,
#endif
#ifdef USE_VALVE
VALVE,
#endif
#ifdef USE_MEDIA_PLAYER
MEDIA_PLAYER,
#endif
#ifdef USE_ALARM_CONTROL_PANEL
ALARM_CONTROL_PANEL,
#endif
#ifdef USE_EVENT
EVENT,
#endif
MAX,
} state_{IteratorState::NONE};

View File

@ -97,6 +97,12 @@ void Controller::setup_controller(bool include_internal) {
obj->add_on_state_callback([this, obj]() { this->on_lock_update(obj); });
}
#endif
#ifdef USE_VALVE
for (auto *obj : App.get_valves()) {
if (include_internal || !obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_valve_update(obj); });
}
#endif
#ifdef USE_MEDIA_PLAYER
for (auto *obj : App.get_media_players()) {
if (include_internal || !obj->is_internal())
@ -109,6 +115,12 @@ void Controller::setup_controller(bool include_internal) {
obj->add_on_state_callback([this, obj]() { this->on_alarm_control_panel_update(obj); });
}
#endif
#ifdef USE_EVENT
for (auto *obj : App.get_events()) {
if (include_internal || !obj->is_internal())
obj->add_on_event_callback([this, obj](const std::string &event_type) { this->on_event(obj, event_type); });
}
#endif
}
} // namespace esphome

View File

@ -49,12 +49,18 @@
#ifdef USE_LOCK
#include "esphome/components/lock/lock.h"
#endif
#ifdef USE_VALVE
#include "esphome/components/valve/valve.h"
#endif
#ifdef USE_MEDIA_PLAYER
#include "esphome/components/media_player/media_player.h"
#endif
#ifdef USE_ALARM_CONTROL_PANEL
#include "esphome/components/alarm_control_panel/alarm_control_panel.h"
#endif
#ifdef USE_EVENT
#include "esphome/components/event/event.h"
#endif
namespace esphome {
@ -106,12 +112,18 @@ class Controller {
#ifdef USE_LOCK
virtual void on_lock_update(lock::Lock *obj){};
#endif
#ifdef USE_VALVE
virtual void on_valve_update(valve::Valve *obj){};
#endif
#ifdef USE_MEDIA_PLAYER
virtual void on_media_player_update(media_player::MediaPlayer *obj){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL
virtual void on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj){};
#endif
#ifdef USE_EVENT
virtual void on_event(event::Event *obj, const std::string &event_type){};
#endif
};
} // namespace esphome

View File

@ -24,6 +24,7 @@
#define USE_CLIMATE
#define USE_COVER
#define USE_DEEP_SLEEP
#define USE_EVENT
#define USE_FAN
#define USE_GRAPH
#define USE_HOMEASSISTANT_TIME
@ -55,6 +56,7 @@
#define USE_TIME
#define USE_TOUCHSCREEN
#define USE_UART_DEBUGGER
#define USE_VALVE
#define USE_WIFI
#define USE_WIFI_AP
#define USE_GRAPHICAL_DISPLAY_MENU

View File

@ -62,6 +62,24 @@ class GPIOPin {
virtual bool is_internal() { return false; }
};
/**
* A pin to replace those that don't exist.
*/
class NullPin : public GPIOPin {
public:
void setup() override {}
void pin_mode(gpio::Flags _) override {}
bool digital_read() override { return false; }
void digital_write(bool _) override {}
std::string dump_summary() const override { return {"Not used"}; }
};
static GPIOPin *const NULL_PIN = new NullPin();
/// Copy of GPIOPin that is safe to use from ISRs (with no virtual functions)
class ISRInternalGPIOPin {
public:

View File

@ -10,6 +10,7 @@ import paho.mqtt.client as mqtt
from esphome.const import (
CONF_BROKER,
CONF_CERTIFICATE_AUTHORITY,
CONF_DISCOVERY_PREFIX,
CONF_ESPHOME,
CONF_LOG_TOPIC,
@ -99,7 +100,9 @@ def prepare(
elif username:
client.username_pw_set(username, password)
if config[CONF_MQTT].get(CONF_SSL_FINGERPRINTS):
if config[CONF_MQTT].get(CONF_SSL_FINGERPRINTS) or config[CONF_MQTT].get(
CONF_CERTIFICATE_AUTHORITY
):
if sys.version_info >= (2, 7, 13):
tls_version = ssl.PROTOCOL_TLS # pylint: disable=no-member
else:

View File

@ -321,8 +321,9 @@ class ESPHomeLoaderMixin:
file, vars = node.value, None
result = _load_yaml_internal(self._rel_path(file))
if vars:
result = substitute_vars(result, vars)
if not vars:
vars = {}
result = substitute_vars(result, vars)
return result
@_add_data_ref

View File

@ -13,7 +13,7 @@ platformio==6.1.13 # When updating platformio, also update Dockerfile
esptool==4.7.0
click==8.1.7
esphome-dashboard==20240412.0
aioesphomeapi==24.0.0
aioesphomeapi==24.3.0
zeroconf==0.132.2
python-magic==0.4.27
ruamel.yaml==0.18.6 # dashboard_import

View File

@ -1,4 +1,4 @@
# Useful stuff when working in a development environment
clang-format==13.0.1
clang-tidy==14.0.6
yamllint==1.35.1
clang-format==13.0.1 # also change in .pre-commit-config.yaml and Dockerfile when updating
clang-tidy==14.0.6 # When updating clang-tidy, also update Dockerfile
yamllint==1.35.1 # also change in .pre-commit-config.yaml when updating

View File

@ -625,6 +625,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/datetime/time_entity.h",
"esphome/components/datetime/datetime_entity.h",
"esphome/components/display/display.h",
"esphome/components/event/event.h",
"esphome/components/fan/fan.h",
"esphome/components/i2c/i2c.h",
"esphome/components/lock/lock.h",
@ -639,6 +640,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/stepper/stepper.h",
"esphome/components/switch/switch.h",
"esphome/components/text_sensor/text_sensor.h",
"esphome/components/valve/valve.h",
"esphome/core/component.h",
"esphome/core/gpio.h",
"esphome/core/log.h",

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,9 @@
event:
- platform: template
name: Event
id: some_event
event_types:
- template_event_type1
- template_event_type2
on_event:
- logger.log: Event fired

View File

@ -0,0 +1,4 @@
i2c:
- id: i2c_i2c
scl: 5
sda: 4

View File

@ -0,0 +1,4 @@
i2c:
- id: i2c_i2c
scl: 5
sda: 4

View File

@ -0,0 +1,4 @@
i2c:
- id: i2c_i2c
scl: 16
sda: 17

View File

@ -0,0 +1,4 @@
i2c:
- id: i2c_i2c
scl: 16
sda: 17

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