Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
Daniël Koek 2024-04-24 16:36:57 +01:00
commit fdf50b0c03
128 changed files with 3249 additions and 419 deletions

View file

@ -17,7 +17,7 @@ runs:
steps: steps:
- name: Set up Python ${{ inputs.python-version }} - name: Set up Python ${{ inputs.python-version }}
id: python id: python
uses: actions/setup-python@v5.0.0 uses: actions/setup-python@v5.1.0
with: with:
python-version: ${{ inputs.python-version }} python-version: ${{ inputs.python-version }}
- name: Restore Python virtual environment - name: Restore Python virtual environment

View file

@ -23,7 +23,7 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4.1.1 uses: actions/checkout@v4.1.1
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.0.0 uses: actions/setup-python@v5.1.0
with: with:
python-version: "3.11" python-version: "3.11"

View file

@ -42,11 +42,11 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.1.1
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.0.0 uses: actions/setup-python@v5.1.0
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.2.0 uses: docker/setup-buildx-action@v3.3.0
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.0.0

View file

@ -41,7 +41,7 @@ jobs:
run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT run: echo key="${{ hashFiles('requirements.txt', 'requirements_optional.txt', 'requirements_test.txt') }}" >> $GITHUB_OUTPUT
- name: Set up Python ${{ env.DEFAULT_PYTHON }} - name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python id: python
uses: actions/setup-python@v5.0.0 uses: actions/setup-python@v5.1.0
with: with:
python-version: ${{ env.DEFAULT_PYTHON }} python-version: ${{ env.DEFAULT_PYTHON }}
- name: Restore Python virtual environment - name: Restore Python virtual environment

View file

@ -44,7 +44,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.1.1
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.0.0 uses: actions/setup-python@v5.1.0
with: with:
python-version: "3.x" python-version: "3.x"
- name: Set up python environment - name: Set up python environment
@ -79,12 +79,12 @@ jobs:
steps: steps:
- uses: actions/checkout@v4.1.1 - uses: actions/checkout@v4.1.1
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v5.0.0 uses: actions/setup-python@v5.1.0
with: with:
python-version: "3.9" python-version: "3.9"
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.2.0 uses: docker/setup-buildx-action@v3.3.0
- name: Set up QEMU - name: Set up QEMU
if: matrix.platform != 'linux/amd64' if: matrix.platform != 'linux/amd64'
uses: docker/setup-qemu-action@v3.0.0 uses: docker/setup-qemu-action@v3.0.0
@ -162,7 +162,7 @@ jobs:
name: digests-${{ matrix.image.target }}-${{ matrix.registry }} name: digests-${{ matrix.image.target }}-${{ matrix.registry }}
path: /tmp/digests path: /tmp/digests
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.2.0 uses: docker/setup-buildx-action@v3.3.0
- name: Log in to docker hub - name: Log in to docker hub
if: matrix.registry == 'dockerhub' if: matrix.registry == 'dockerhub'

View file

@ -22,7 +22,7 @@ jobs:
path: lib/home-assistant path: lib/home-assistant
- name: Setup Python - name: Setup Python
uses: actions/setup-python@v5.0.0 uses: actions/setup-python@v5.1.0
with: with:
python-version: 3.11 python-version: 3.11

View file

@ -91,7 +91,7 @@ esphome/components/daikin_arc/* @MagicBear
esphome/components/daikin_brc/* @hagak esphome/components/daikin_brc/* @hagak
esphome/components/daly_bms/* @s1lvi0 esphome/components/daly_bms/* @s1lvi0
esphome/components/dashboard_import/* @esphome/core esphome/components/dashboard_import/* @esphome/core
esphome/components/datetime/* @rfdarter esphome/components/datetime/* @jesserockz @rfdarter
esphome/components/debug/* @OttoWinter esphome/components/debug/* @OttoWinter
esphome/components/delonghi/* @grob6000 esphome/components/delonghi/* @grob6000
esphome/components/dfplayer/* @glmnet esphome/components/dfplayer/* @glmnet
@ -116,6 +116,7 @@ esphome/components/esp32_ble_server/* @Rapsssito @clydebarrow @jesserockz
esphome/components/esp32_camera_web_server/* @ayufan esphome/components/esp32_camera_web_server/* @ayufan
esphome/components/esp32_can/* @Sympatron esphome/components/esp32_can/* @Sympatron
esphome/components/esp32_improv/* @jesserockz esphome/components/esp32_improv/* @jesserockz
esphome/components/esp32_rmt/* @jesserockz
esphome/components/esp32_rmt_led_strip/* @jesserockz esphome/components/esp32_rmt_led_strip/* @jesserockz
esphome/components/esp8266/* @esphome/core esphome/components/esp8266/* @esphome/core
esphome/components/ethernet_info/* @gtjadsonsantos esphome/components/ethernet_info/* @gtjadsonsantos
@ -405,6 +406,7 @@ esphome/components/wireguard/* @droscy @lhoracek @thomas0bernard
esphome/components/wl_134/* @hobbypunk90 esphome/components/wl_134/* @hobbypunk90
esphome/components/x9c/* @EtienneMD esphome/components/x9c/* @EtienneMD
esphome/components/xgzp68xx/* @gcormier esphome/components/xgzp68xx/* @gcormier
esphome/components/xiaomi_hhccjcy10/* @fariouche
esphome/components/xiaomi_lywsd03mmc/* @ahpohl esphome/components/xiaomi_lywsd03mmc/* @ahpohl
esphome/components/xiaomi_mhoc303/* @drug123 esphome/components/xiaomi_mhoc303/* @drug123
esphome/components/xiaomi_mhoc401/* @vevsvevs esphome/components/xiaomi_mhoc401/* @vevsvevs

View file

@ -45,6 +45,7 @@ service APIConnection {
rpc lock_command (LockCommandRequest) returns (void) {} rpc lock_command (LockCommandRequest) returns (void) {}
rpc media_player_command (MediaPlayerCommandRequest) returns (void) {} rpc media_player_command (MediaPlayerCommandRequest) returns (void) {}
rpc date_command (DateCommandRequest) returns (void) {} rpc date_command (DateCommandRequest) returns (void) {}
rpc time_command (TimeCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}
@ -217,7 +218,8 @@ message DeviceInfoResponse {
string friendly_name = 13; string friendly_name = 13;
uint32 voice_assistant_version = 14; uint32 legacy_voice_assistant_version = 14;
uint32 voice_assistant_feature_flags = 17;
string suggested_area = 16; string suggested_area = 16;
} }
@ -1422,12 +1424,18 @@ message BluetoothDeviceClearCacheResponse {
} }
// ==================== PUSH TO TALK ==================== // ==================== PUSH TO TALK ====================
enum VoiceAssistantSubscribeFlag {
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0;
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1;
}
message SubscribeVoiceAssistantRequest { message SubscribeVoiceAssistantRequest {
option (id) = 89; option (id) = 89;
option (source) = SOURCE_CLIENT; option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_VOICE_ASSISTANT"; option (ifdef) = "USE_VOICE_ASSISTANT";
bool subscribe = 1; bool subscribe = 1;
uint32 flags = 2;
} }
enum VoiceAssistantRequestFlag { enum VoiceAssistantRequestFlag {
@ -1495,6 +1503,16 @@ message VoiceAssistantEventResponse {
repeated VoiceAssistantEventData data = 2; repeated VoiceAssistantEventData data = 2;
} }
message VoiceAssistantAudio {
option (id) = 106;
option (source) = SOURCE_BOTH;
option (ifdef) = "USE_VOICE_ASSISTANT";
bytes data = 1;
bool end = 2;
}
// ==================== ALARM CONTROL PANEL ==================== // ==================== ALARM CONTROL PANEL ====================
enum AlarmControlPanelState { enum AlarmControlPanelState {
ALARM_STATE_DISARMED = 0; ALARM_STATE_DISARMED = 0;
@ -1641,3 +1659,44 @@ message DateCommandRequest {
uint32 month = 3; uint32 month = 3;
uint32 day = 4; uint32 day = 4;
} }
// ==================== DATETIME TIME ====================
message ListEntitiesTimeResponse {
option (id) = 103;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_TIME";
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;
}
message TimeStateResponse {
option (id) = 104;
option (source) = SOURCE_SERVER;
option (ifdef) = "USE_DATETIME_TIME";
option (no_delay) = true;
fixed32 key = 1;
// If the time does not have a valid state yet.
// Equivalent to `!obj->has_state()` - inverse logic to make state packets smaller
bool missing_state = 2;
uint32 hour = 3;
uint32 minute = 4;
uint32 second = 5;
}
message TimeCommandRequest {
option (id) = 105;
option (source) = SOURCE_CLIENT;
option (ifdef) = "USE_DATETIME_TIME";
option (no_delay) = true;
fixed32 key = 1;
uint32 hour = 2;
uint32 minute = 3;
uint32 second = 4;
}

View file

@ -735,6 +735,43 @@ void APIConnection::date_command(const DateCommandRequest &msg) {
} }
#endif #endif
#ifdef USE_DATETIME_TIME
bool APIConnection::send_time_state(datetime::TimeEntity *time) {
if (!this->state_subscription_)
return false;
TimeStateResponse resp{};
resp.key = time->get_object_id_hash();
resp.missing_state = !time->has_state();
resp.hour = time->hour;
resp.minute = time->minute;
resp.second = time->second;
return this->send_time_state_response(resp);
}
bool APIConnection::send_time_info(datetime::TimeEntity *time) {
ListEntitiesTimeResponse msg;
msg.key = time->get_object_id_hash();
msg.object_id = time->get_object_id();
if (time->has_own_name())
msg.name = time->get_name();
msg.unique_id = get_default_unique_id("time", time);
msg.icon = time->get_icon();
msg.disabled_by_default = time->is_disabled_by_default();
msg.entity_category = static_cast<enums::EntityCategory>(time->get_entity_category());
return this->send_list_entities_time_response(msg);
}
void APIConnection::time_command(const TimeCommandRequest &msg) {
datetime::TimeEntity *time = App.get_time_by_key(msg.key);
if (time == nullptr)
return;
auto call = time->make_call();
call.set_time(msg.hour, msg.minute, msg.second);
call.perform();
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool APIConnection::send_text_state(text::Text *text, std::string state) { bool APIConnection::send_text_state(text::Text *text, std::string state) {
if (!this->state_subscription_) if (!this->state_subscription_)
@ -1040,10 +1077,15 @@ void APIConnection::on_voice_assistant_response(const VoiceAssistantResponse &ms
voice_assistant::global_voice_assistant->failed_to_start(); voice_assistant::global_voice_assistant->failed_to_start();
return; return;
} }
struct sockaddr_storage storage; if (msg.port == 0) {
socklen_t len = sizeof(storage); // Use API Audio
this->helper_->getpeername((struct sockaddr *) &storage, &len); voice_assistant::global_voice_assistant->start_streaming();
voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port); } else {
struct sockaddr_storage storage;
socklen_t len = sizeof(storage);
this->helper_->getpeername((struct sockaddr *) &storage, &len);
voice_assistant::global_voice_assistant->start_streaming(&storage, msg.port);
}
} }
}; };
void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) { void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) {
@ -1055,6 +1097,15 @@ void APIConnection::on_voice_assistant_event_response(const VoiceAssistantEventR
voice_assistant::global_voice_assistant->on_event(msg); voice_assistant::global_voice_assistant->on_event(msg);
} }
} }
void APIConnection::on_voice_assistant_audio(const VoiceAssistantAudio &msg) {
if (voice_assistant::global_voice_assistant != nullptr) {
if (voice_assistant::global_voice_assistant->get_api_connection() != this) {
return;
}
voice_assistant::global_voice_assistant->on_audio(msg);
}
};
#endif #endif
@ -1142,7 +1193,7 @@ HelloResponse APIConnection::hello(const HelloRequest &msg) {
HelloResponse resp; HelloResponse resp;
resp.api_version_major = 1; resp.api_version_major = 1;
resp.api_version_minor = 9; resp.api_version_minor = 10;
resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")"; resp.server_info = App.get_name() + " (esphome v" ESPHOME_VERSION ")";
resp.name = App.get_name(); resp.name = App.get_name();
@ -1203,7 +1254,8 @@ DeviceInfoResponse APIConnection::device_info(const DeviceInfoRequest &msg) {
resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags(); resp.bluetooth_proxy_feature_flags = bluetooth_proxy::global_bluetooth_proxy->get_feature_flags();
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
resp.voice_assistant_version = voice_assistant::global_voice_assistant->get_version(); resp.legacy_voice_assistant_version = voice_assistant::global_voice_assistant->get_legacy_version();
resp.voice_assistant_feature_flags = voice_assistant::global_voice_assistant->get_feature_flags();
#endif #endif
return resp; return resp;
} }

View file

@ -77,6 +77,11 @@ class APIConnection : public APIServerConnection {
bool send_date_info(datetime::DateEntity *date); bool send_date_info(datetime::DateEntity *date);
void date_command(const DateCommandRequest &msg) override; void date_command(const DateCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_TIME
bool send_time_state(datetime::TimeEntity *time);
bool send_time_info(datetime::TimeEntity *time);
void time_command(const TimeCommandRequest &msg) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool send_text_state(text::Text *text, std::string state); bool send_text_state(text::Text *text, std::string state);
bool send_text_info(text::Text *text); bool send_text_info(text::Text *text);
@ -134,6 +139,7 @@ class APIConnection : public APIServerConnection {
void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override; void subscribe_voice_assistant(const SubscribeVoiceAssistantRequest &msg) override;
void on_voice_assistant_response(const VoiceAssistantResponse &msg) override; void on_voice_assistant_response(const VoiceAssistantResponse &msg) override;
void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override; void on_voice_assistant_event_response(const VoiceAssistantEventResponse &msg) override;
void on_voice_assistant_audio(const VoiceAssistantAudio &msg) override;
#endif #endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL

View file

@ -410,6 +410,19 @@ const char *proto_enum_to_string<enums::BluetoothDeviceRequestType>(enums::Bluet
} }
#endif #endif
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
template<>
const char *proto_enum_to_string<enums::VoiceAssistantSubscribeFlag>(enums::VoiceAssistantSubscribeFlag value) {
switch (value) {
case enums::VOICE_ASSISTANT_SUBSCRIBE_NONE:
return "VOICE_ASSISTANT_SUBSCRIBE_NONE";
case enums::VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO:
return "VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO";
default:
return "UNKNOWN";
}
}
#endif
#ifdef HAS_PROTO_MESSAGE_DUMP
template<> const char *proto_enum_to_string<enums::VoiceAssistantRequestFlag>(enums::VoiceAssistantRequestFlag value) { template<> const char *proto_enum_to_string<enums::VoiceAssistantRequestFlag>(enums::VoiceAssistantRequestFlag value) {
switch (value) { switch (value) {
case enums::VOICE_ASSISTANT_REQUEST_NONE: case enums::VOICE_ASSISTANT_REQUEST_NONE:
@ -716,7 +729,11 @@ bool DeviceInfoResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
return true; return true;
} }
case 14: { case 14: {
this->voice_assistant_version = value.as_uint32(); this->legacy_voice_assistant_version = value.as_uint32();
return true;
}
case 17: {
this->voice_assistant_feature_flags = value.as_uint32();
return true; return true;
} }
default: default:
@ -784,7 +801,8 @@ void DeviceInfoResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags); buffer.encode_uint32(15, this->bluetooth_proxy_feature_flags);
buffer.encode_string(12, this->manufacturer); buffer.encode_string(12, this->manufacturer);
buffer.encode_string(13, this->friendly_name); buffer.encode_string(13, this->friendly_name);
buffer.encode_uint32(14, this->voice_assistant_version); buffer.encode_uint32(14, this->legacy_voice_assistant_version);
buffer.encode_uint32(17, this->voice_assistant_feature_flags);
buffer.encode_string(16, this->suggested_area); buffer.encode_string(16, this->suggested_area);
} }
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -850,8 +868,13 @@ void DeviceInfoResponse::dump_to(std::string &out) const {
out.append("'").append(this->friendly_name).append("'"); out.append("'").append(this->friendly_name).append("'");
out.append("\n"); out.append("\n");
out.append(" voice_assistant_version: "); out.append(" legacy_voice_assistant_version: ");
sprintf(buffer, "%" PRIu32, this->voice_assistant_version); sprintf(buffer, "%" PRIu32, this->legacy_voice_assistant_version);
out.append(buffer);
out.append("\n");
out.append(" voice_assistant_feature_flags: ");
sprintf(buffer, "%" PRIu32, this->voice_assistant_feature_flags);
out.append(buffer); out.append(buffer);
out.append("\n"); out.append("\n");
@ -6514,11 +6537,18 @@ bool SubscribeVoiceAssistantRequest::decode_varint(uint32_t field_id, ProtoVarIn
this->subscribe = value.as_bool(); this->subscribe = value.as_bool();
return true; return true;
} }
case 2: {
this->flags = value.as_uint32();
return true;
}
default: default:
return false; return false;
} }
} }
void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const { buffer.encode_bool(1, this->subscribe); } void SubscribeVoiceAssistantRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_bool(1, this->subscribe);
buffer.encode_uint32(2, this->flags);
}
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const { void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64]; __attribute__((unused)) char buffer[64];
@ -6526,6 +6556,11 @@ void SubscribeVoiceAssistantRequest::dump_to(std::string &out) const {
out.append(" subscribe: "); out.append(" subscribe: ");
out.append(YESNO(this->subscribe)); out.append(YESNO(this->subscribe));
out.append("\n"); out.append("\n");
out.append(" flags: ");
sprintf(buffer, "%" PRIu32, this->flags);
out.append(buffer);
out.append("\n");
out.append("}"); out.append("}");
} }
#endif #endif
@ -6752,6 +6787,44 @@ void VoiceAssistantEventResponse::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool VoiceAssistantAudio::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->end = value.as_bool();
return true;
}
default:
return false;
}
}
bool VoiceAssistantAudio::decode_length(uint32_t field_id, ProtoLengthDelimited value) {
switch (field_id) {
case 1: {
this->data = value.as_string();
return true;
}
default:
return false;
}
}
void VoiceAssistantAudio::encode(ProtoWriteBuffer buffer) const {
buffer.encode_string(1, this->data);
buffer.encode_bool(2, this->end);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void VoiceAssistantAudio::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("VoiceAssistantAudio {\n");
out.append(" data: ");
out.append("'").append(this->data).append("'");
out.append("\n");
out.append(" end: ");
out.append(YESNO(this->end));
out.append("\n");
out.append("}");
}
#endif
bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) { bool ListEntitiesAlarmControlPanelResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) { switch (field_id) {
case 6: { case 6: {
@ -7403,6 +7476,225 @@ void DateCommandRequest::dump_to(std::string &out) const {
out.append("}"); out.append("}");
} }
#endif #endif
bool ListEntitiesTimeResponse::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 ListEntitiesTimeResponse::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;
}
default:
return false;
}
}
bool ListEntitiesTimeResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 2: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void ListEntitiesTimeResponse::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);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void ListEntitiesTimeResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("ListEntitiesTimeResponse {\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("}");
}
#endif
bool TimeStateResponse::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->missing_state = value.as_bool();
return true;
}
case 3: {
this->hour = value.as_uint32();
return true;
}
case 4: {
this->minute = value.as_uint32();
return true;
}
case 5: {
this->second = value.as_uint32();
return true;
}
default:
return false;
}
}
bool TimeStateResponse::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void TimeStateResponse::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_bool(2, this->missing_state);
buffer.encode_uint32(3, this->hour);
buffer.encode_uint32(4, this->minute);
buffer.encode_uint32(5, this->second);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void TimeStateResponse::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("TimeStateResponse {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" missing_state: ");
out.append(YESNO(this->missing_state));
out.append("\n");
out.append(" hour: ");
sprintf(buffer, "%" PRIu32, this->hour);
out.append(buffer);
out.append("\n");
out.append(" minute: ");
sprintf(buffer, "%" PRIu32, this->minute);
out.append(buffer);
out.append("\n");
out.append(" second: ");
sprintf(buffer, "%" PRIu32, this->second);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
bool TimeCommandRequest::decode_varint(uint32_t field_id, ProtoVarInt value) {
switch (field_id) {
case 2: {
this->hour = value.as_uint32();
return true;
}
case 3: {
this->minute = value.as_uint32();
return true;
}
case 4: {
this->second = value.as_uint32();
return true;
}
default:
return false;
}
}
bool TimeCommandRequest::decode_32bit(uint32_t field_id, Proto32Bit value) {
switch (field_id) {
case 1: {
this->key = value.as_fixed32();
return true;
}
default:
return false;
}
}
void TimeCommandRequest::encode(ProtoWriteBuffer buffer) const {
buffer.encode_fixed32(1, this->key);
buffer.encode_uint32(2, this->hour);
buffer.encode_uint32(3, this->minute);
buffer.encode_uint32(4, this->second);
}
#ifdef HAS_PROTO_MESSAGE_DUMP
void TimeCommandRequest::dump_to(std::string &out) const {
__attribute__((unused)) char buffer[64];
out.append("TimeCommandRequest {\n");
out.append(" key: ");
sprintf(buffer, "%" PRIu32, this->key);
out.append(buffer);
out.append("\n");
out.append(" hour: ");
sprintf(buffer, "%" PRIu32, this->hour);
out.append(buffer);
out.append("\n");
out.append(" minute: ");
sprintf(buffer, "%" PRIu32, this->minute);
out.append(buffer);
out.append("\n");
out.append(" second: ");
sprintf(buffer, "%" PRIu32, this->second);
out.append(buffer);
out.append("\n");
out.append("}");
}
#endif
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -165,6 +165,10 @@ enum BluetoothDeviceRequestType : uint32_t {
BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5, BLUETOOTH_DEVICE_REQUEST_TYPE_CONNECT_V3_WITHOUT_CACHE = 5,
BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6, BLUETOOTH_DEVICE_REQUEST_TYPE_CLEAR_CACHE = 6,
}; };
enum VoiceAssistantSubscribeFlag : uint32_t {
VOICE_ASSISTANT_SUBSCRIBE_NONE = 0,
VOICE_ASSISTANT_SUBSCRIBE_API_AUDIO = 1,
};
enum VoiceAssistantRequestFlag : uint32_t { enum VoiceAssistantRequestFlag : uint32_t {
VOICE_ASSISTANT_REQUEST_NONE = 0, VOICE_ASSISTANT_REQUEST_NONE = 0,
VOICE_ASSISTANT_REQUEST_USE_VAD = 1, VOICE_ASSISTANT_REQUEST_USE_VAD = 1,
@ -327,7 +331,8 @@ class DeviceInfoResponse : public ProtoMessage {
uint32_t bluetooth_proxy_feature_flags{0}; uint32_t bluetooth_proxy_feature_flags{0};
std::string manufacturer{}; std::string manufacturer{};
std::string friendly_name{}; std::string friendly_name{};
uint32_t voice_assistant_version{0}; uint32_t legacy_voice_assistant_version{0};
uint32_t voice_assistant_feature_flags{0};
std::string suggested_area{}; std::string suggested_area{};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
@ -1674,6 +1679,7 @@ class BluetoothDeviceClearCacheResponse : public ProtoMessage {
class SubscribeVoiceAssistantRequest : public ProtoMessage { class SubscribeVoiceAssistantRequest : public ProtoMessage {
public: public:
bool subscribe{false}; bool subscribe{false};
uint32_t flags{0};
void encode(ProtoWriteBuffer buffer) const override; void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP #ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override; void dump_to(std::string &out) const override;
@ -1749,6 +1755,19 @@ class VoiceAssistantEventResponse : public ProtoMessage {
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override; bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class VoiceAssistantAudio : public ProtoMessage {
public:
std::string data{};
bool end{false};
void encode(ProtoWriteBuffer buffer) const override;
#ifdef HAS_PROTO_MESSAGE_DUMP
void dump_to(std::string &out) const override;
#endif
protected:
bool decode_length(uint32_t field_id, ProtoLengthDelimited value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
};
class ListEntitiesAlarmControlPanelResponse : public ProtoMessage { class ListEntitiesAlarmControlPanelResponse : public ProtoMessage {
public: public:
std::string object_id{}; std::string object_id{};
@ -1900,6 +1919,56 @@ class DateCommandRequest : public ProtoMessage {
bool decode_32bit(uint32_t field_id, Proto32Bit value) override; bool decode_32bit(uint32_t field_id, Proto32Bit value) override;
bool decode_varint(uint32_t field_id, ProtoVarInt value) override; bool decode_varint(uint32_t field_id, ProtoVarInt value) override;
}; };
class ListEntitiesTimeResponse : 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{};
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 TimeStateResponse : public ProtoMessage {
public:
uint32_t key{0};
bool missing_state{false};
uint32_t hour{0};
uint32_t minute{0};
uint32_t second{0};
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 TimeCommandRequest : public ProtoMessage {
public:
uint32_t key{0};
uint32_t hour{0};
uint32_t minute{0};
uint32_t second{0};
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;
};
} // namespace api } // namespace api
} // namespace esphome } // namespace esphome

View file

@ -476,6 +476,14 @@ bool APIServerConnectionBase::send_voice_assistant_request(const VoiceAssistantR
#endif #endif
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
#endif #endif
#ifdef USE_VOICE_ASSISTANT
bool APIServerConnectionBase::send_voice_assistant_audio(const VoiceAssistantAudio &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_voice_assistant_audio: %s", msg.dump().c_str());
#endif
return this->send_message_<VoiceAssistantAudio>(msg, 106);
}
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response( bool APIServerConnectionBase::send_list_entities_alarm_control_panel_response(
const ListEntitiesAlarmControlPanelResponse &msg) { const ListEntitiesAlarmControlPanelResponse &msg) {
@ -531,6 +539,24 @@ bool APIServerConnectionBase::send_date_state_response(const DateStateResponse &
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
#endif #endif
#ifdef USE_DATETIME_TIME
bool APIServerConnectionBase::send_list_entities_time_response(const ListEntitiesTimeResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_list_entities_time_response: %s", msg.dump().c_str());
#endif
return this->send_message_<ListEntitiesTimeResponse>(msg, 103);
}
#endif
#ifdef USE_DATETIME_TIME
bool APIServerConnectionBase::send_time_state_response(const TimeStateResponse &msg) {
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "send_time_state_response: %s", msg.dump().c_str());
#endif
return this->send_message_<TimeStateResponse>(msg, 104);
}
#endif
#ifdef USE_DATETIME_TIME
#endif
bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) { bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) {
switch (msg_type) { switch (msg_type) {
case 1: { case 1: {
@ -971,6 +997,28 @@ bool APIServerConnectionBase::read_message(uint32_t msg_size, uint32_t msg_type,
ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str()); ESP_LOGVV(TAG, "on_date_command_request: %s", msg.dump().c_str());
#endif #endif
this->on_date_command_request(msg); this->on_date_command_request(msg);
#endif
break;
}
case 105: {
#ifdef USE_DATETIME_TIME
TimeCommandRequest msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_time_command_request: %s", msg.dump().c_str());
#endif
this->on_time_command_request(msg);
#endif
break;
}
case 106: {
#ifdef USE_VOICE_ASSISTANT
VoiceAssistantAudio msg;
msg.decode(msg_data, msg_size);
#ifdef HAS_PROTO_MESSAGE_DUMP
ESP_LOGVV(TAG, "on_voice_assistant_audio: %s", msg.dump().c_str());
#endif
this->on_voice_assistant_audio(msg);
#endif #endif
break; break;
} }
@ -1260,6 +1308,19 @@ void APIServerConnection::on_date_command_request(const DateCommandRequest &msg)
this->date_command(msg); this->date_command(msg);
} }
#endif #endif
#ifdef USE_DATETIME_TIME
void APIServerConnection::on_time_command_request(const TimeCommandRequest &msg) {
if (!this->is_connection_setup()) {
this->on_no_setup_connection();
return;
}
if (!this->is_authenticated()) {
this->on_unauthenticated_access();
return;
}
this->time_command(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) { const SubscribeBluetoothLEAdvertisementsRequest &msg) {

View file

@ -240,6 +240,10 @@ class APIServerConnectionBase : public ProtoService {
#ifdef USE_VOICE_ASSISTANT #ifdef USE_VOICE_ASSISTANT
virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){}; virtual void on_voice_assistant_event_response(const VoiceAssistantEventResponse &value){};
#endif #endif
#ifdef USE_VOICE_ASSISTANT
bool send_voice_assistant_audio(const VoiceAssistantAudio &msg);
virtual void on_voice_assistant_audio(const VoiceAssistantAudio &value){};
#endif
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg); bool send_list_entities_alarm_control_panel_response(const ListEntitiesAlarmControlPanelResponse &msg);
#endif #endif
@ -266,6 +270,15 @@ class APIServerConnectionBase : public ProtoService {
#endif #endif
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
virtual void on_date_command_request(const DateCommandRequest &value){}; virtual void on_date_command_request(const DateCommandRequest &value){};
#endif
#ifdef USE_DATETIME_TIME
bool send_list_entities_time_response(const ListEntitiesTimeResponse &msg);
#endif
#ifdef USE_DATETIME_TIME
bool send_time_state_response(const TimeStateResponse &msg);
#endif
#ifdef USE_DATETIME_TIME
virtual void on_time_command_request(const TimeCommandRequest &value){};
#endif #endif
protected: protected:
bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override; bool read_message(uint32_t msg_size, uint32_t msg_type, uint8_t *msg_data) override;
@ -324,6 +337,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
virtual void date_command(const DateCommandRequest &msg) = 0; virtual void date_command(const DateCommandRequest &msg) = 0;
#endif #endif
#ifdef USE_DATETIME_TIME
virtual void time_command(const TimeCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif #endif
@ -413,6 +429,9 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
void on_date_command_request(const DateCommandRequest &msg) override; void on_date_command_request(const DateCommandRequest &msg) override;
#endif #endif
#ifdef USE_DATETIME_TIME
void on_time_command_request(const TimeCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif #endif

View file

@ -264,6 +264,15 @@ void APIServer::on_date_update(datetime::DateEntity *obj) {
} }
#endif #endif
#ifdef USE_DATETIME_TIME
void APIServer::on_time_update(datetime::TimeEntity *obj) {
if (obj->is_internal())
return;
for (auto &c : this->clients_)
c->send_time_state(obj);
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
void APIServer::on_text_update(text::Text *obj, const std::string &state) { void APIServer::on_text_update(text::Text *obj, const std::string &state) {
if (obj->is_internal()) if (obj->is_internal())

View file

@ -69,6 +69,9 @@ class APIServer : public Component, public Controller {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
void on_date_update(datetime::DateEntity *obj) override; void on_date_update(datetime::DateEntity *obj) override;
#endif #endif
#ifdef USE_DATETIME_TIME
void on_time_update(datetime::TimeEntity *obj) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
void on_text_update(text::Text *obj, const std::string &state) override; void on_text_update(text::Text *obj, const std::string &state) override;
#endif #endif

View file

@ -64,6 +64,10 @@ bool ListEntitiesIterator::on_number(number::Number *number) { return this->clie
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); } bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_info(date); }
#endif #endif
#ifdef USE_DATETIME_TIME
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_info(time); }
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); } bool ListEntitiesIterator::on_text(text::Text *text) { return this->client_->send_text_info(text); }
#endif #endif

View file

@ -49,6 +49,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *date) override; bool on_date(datetime::DateEntity *date) override;
#endif #endif
#ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *time) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool on_text(text::Text *text) override; bool on_text(text::Text *text) override;
#endif #endif

View file

@ -45,6 +45,9 @@ bool InitialStateIterator::on_number(number::Number *number) {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); } bool InitialStateIterator::on_date(datetime::DateEntity *date) { return this->client_->send_date_state(date); }
#endif #endif
#ifdef USE_DATETIME_TIME
bool InitialStateIterator::on_time(datetime::TimeEntity *time) { return this->client_->send_time_state(time); }
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); } bool InitialStateIterator::on_text(text::Text *text) { return this->client_->send_text_state(text, text->state); }
#endif #endif

View file

@ -46,6 +46,9 @@ class InitialStateIterator : public ComponentIterator {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *date) override; bool on_date(datetime::DateEntity *date) override;
#endif #endif
#ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *time) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool on_text(text::Text *text) override; bool on_text(text::Text *text) override;
#endif #endif

View file

@ -73,8 +73,7 @@ class BLEPresenceDevice : public binary_sensor::BinarySensorInitiallyOff,
break; break;
case MATCH_BY_IRK: case MATCH_BY_IRK:
if (resolve_irk_(device.address_uint64(), this->irk_)) { if (resolve_irk_(device.address_uint64(), this->irk_)) {
this->publish_state(true); this->set_found_(true);
this->found_ = true;
return true; return true;
} }
break; break;

View file

@ -6,100 +6,102 @@ namespace esphome {
namespace captive_portal { namespace captive_portal {
const uint8_t INDEX_GZ[] PROGMEM = { const uint8_t INDEX_GZ[] PROGMEM = {
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xdd, 0x58, 0x5b, 0x8f, 0xdb, 0x36, 0x16, 0x7e, 0xef, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0xdd, 0x58, 0x69, 0x6f, 0xdc, 0x36, 0x1a, 0xfe, 0xde,
0xaf, 0xe0, 0x2a, 0x49, 0x2d, 0x37, 0x23, 0xea, 0x66, 0xf9, 0x2a, 0xa9, 0x48, 0xb2, 0x29, 0x5a, 0x20, 0x69, 0x03, 0x5f, 0xc1, 0x2a, 0x49, 0x47, 0xd3, 0x58, 0xd4, 0x35, 0x9a, 0x53, 0x9a, 0xc2, 0xf1, 0xa6, 0x68, 0x81, 0xa4, 0x0d,
0xcc, 0xb4, 0xfb, 0x10, 0x04, 0x18, 0x5a, 0xa2, 0x2c, 0x66, 0x24, 0x4a, 0x15, 0xe9, 0x5b, 0x0c, 0xef, 0x6f, 0xdf, 0x60, 0xb7, 0xfd, 0x10, 0x04, 0x30, 0x47, 0xa2, 0x46, 0x8c, 0x25, 0x4a, 0x2b, 0x72, 0xae, 0x0c, 0x66, 0x7f, 0xfb,
0x43, 0x52, 0xf6, 0x38, 0xb3, 0x99, 0x05, 0x52, 0xec, 0x62, 0xd1, 0x4e, 0x26, 0x1c, 0x92, 0x3a, 0xd7, 0x4f, 0x3c, 0xbe, 0x24, 0x35, 0xe3, 0xb1, 0x37, 0x5e, 0x6c, 0x8a, 0x2d, 0x8a, 0xd6, 0x71, 0x68, 0x1e, 0xef, 0xf9, 0x88, 0xef,
0x17, 0x2a, 0xfe, 0x5b, 0xde, 0x64, 0x72, 0xdf, 0x52, 0x54, 0xca, 0xba, 0x4a, 0x63, 0x35, 0xa2, 0x8a, 0xf0, 0x55, 0x21, 0xc5, 0x5f, 0x67, 0x75, 0x2a, 0x77, 0x0d, 0x45, 0x85, 0xac, 0xca, 0x79, 0xac, 0x46, 0x54, 0x12, 0xbe, 0x4c,
0x42, 0x39, 0xac, 0x28, 0xc9, 0xd3, 0xb8, 0xa6, 0x92, 0xa0, 0xac, 0x24, 0x9d, 0xa0, 0x32, 0xf9, 0xf5, 0xe6, 0x07, 0x28, 0x87, 0x15, 0x25, 0xd9, 0x3c, 0xae, 0xa8, 0x24, 0x28, 0x2d, 0x48, 0x2b, 0xa8, 0x4c, 0x7e, 0xb9, 0xf9, 0xde,
0x67, 0x8a, 0xdc, 0x34, 0xae, 0x18, 0xbf, 0x43, 0x1d, 0xad, 0x12, 0x96, 0x35, 0x1c, 0x95, 0x1d, 0x2d, 0x92, 0x9c, 0x19, 0x23, 0x77, 0x1e, 0x97, 0x8c, 0xdf, 0xa1, 0x96, 0x96, 0x09, 0x4b, 0x6b, 0x8e, 0x8a, 0x96, 0xe6, 0x49, 0x46,
0x48, 0x32, 0x67, 0x35, 0x59, 0x51, 0x45, 0xa0, 0xd9, 0x38, 0xa9, 0x69, 0xb2, 0x61, 0x74, 0xdb, 0x36, 0x9d, 0x44, 0x24, 0x99, 0xb2, 0x8a, 0x2c, 0xa9, 0x22, 0xd0, 0x6c, 0x9c, 0x54, 0x34, 0x59, 0x33, 0xba, 0x69, 0xea, 0x56, 0x22,
0x40, 0x29, 0x29, 0x97, 0x89, 0xb5, 0x65, 0xb9, 0x2c, 0x93, 0x9c, 0x6e, 0x58, 0x46, 0x1d, 0xbd, 0xb8, 0x62, 0x9c, 0xa0, 0x94, 0x94, 0xcb, 0xc4, 0xda, 0xb0, 0x4c, 0x16, 0x49, 0x46, 0xd7, 0x2c, 0xa5, 0x8e, 0x5e, 0x5c, 0x30, 0xce,
0x49, 0x46, 0x2a, 0x47, 0x64, 0xa4, 0xa2, 0x89, 0x7f, 0xb5, 0x16, 0xb4, 0xd3, 0x0b, 0xb2, 0x84, 0x35, 0x6f, 0x2c, 0x24, 0x23, 0xa5, 0x23, 0x52, 0x52, 0xd2, 0xc4, 0xbf, 0x58, 0x09, 0xda, 0xea, 0x05, 0x59, 0xc0, 0x9a, 0xd7, 0x16,
0x10, 0x29, 0xb2, 0x8e, 0xb5, 0x12, 0x29, 0x7b, 0x93, 0xba, 0xc9, 0xd7, 0x15, 0x4d, 0x5d, 0x97, 0x08, 0xb0, 0x4b, 0x88, 0x14, 0x69, 0xcb, 0x1a, 0x89, 0x94, 0xbd, 0x49, 0x55, 0x67, 0xab, 0x92, 0xce, 0x5d, 0x97, 0x08, 0xb0, 0x4b,
0xb8, 0x8c, 0xe7, 0x74, 0x87, 0xa7, 0xb3, 0x68, 0x32, 0x9e, 0xe6, 0x13, 0xfc, 0x51, 0x7c, 0x03, 0x9e, 0xad, 0x6b, 0xb8, 0x8c, 0x67, 0x74, 0x8b, 0x87, 0x61, 0x98, 0x06, 0x64, 0x94, 0xe3, 0x8f, 0xe2, 0x2b, 0xf0, 0x6c, 0x55, 0x81,
0x50, 0x87, 0xab, 0x26, 0x23, 0x92, 0x35, 0x1c, 0x0b, 0x4a, 0xba, 0xac, 0x4c, 0x92, 0xc4, 0xfa, 0x5e, 0x90, 0x0d, 0x3a, 0x5c, 0xd6, 0x29, 0x91, 0xac, 0xe6, 0x58, 0x50, 0xd2, 0xa6, 0x45, 0x92, 0x24, 0xd6, 0x77, 0x82, 0xac, 0xa9,
0xb5, 0xbe, 0xfd, 0xd6, 0x3e, 0x13, 0xad, 0xa8, 0x7c, 0x5d, 0x51, 0x35, 0x15, 0x2f, 0xf7, 0x37, 0x64, 0xf5, 0x33, 0xf5, 0xcd, 0x37, 0xf6, 0x89, 0x68, 0x49, 0xe5, 0xeb, 0x92, 0xaa, 0xa9, 0x78, 0xb5, 0xbb, 0x21, 0xcb, 0x9f, 0xc0,
0x58, 0x6e, 0x5b, 0x44, 0xb0, 0x9c, 0x5a, 0xc3, 0xf7, 0xde, 0x07, 0x2c, 0xe4, 0xbe, 0xa2, 0x38, 0x67, 0xa2, 0xad, 0x72, 0xdb, 0x22, 0x82, 0x65, 0xd4, 0xea, 0xbf, 0xf7, 0x3e, 0x60, 0x21, 0x77, 0x25, 0xc5, 0x19, 0x13, 0x4d, 0x49,
0xc8, 0x3e, 0xb1, 0x96, 0x20, 0xf5, 0xce, 0x1a, 0x2e, 0x8a, 0x35, 0xcf, 0x94, 0x70, 0x24, 0x6c, 0x3a, 0x3c, 0x54, 0x76, 0x89, 0xb5, 0x00, 0xa9, 0x77, 0x56, 0x7f, 0x96, 0xaf, 0x78, 0xaa, 0x84, 0x23, 0x61, 0xd3, 0xfe, 0xbe, 0xa4,
0x14, 0xcc, 0x4b, 0xde, 0x12, 0x59, 0xe2, 0x9a, 0xec, 0x6c, 0x33, 0x61, 0xdc, 0x0e, 0xbe, 0xb3, 0xe9, 0x73, 0xdf, 0x60, 0x5e, 0xf2, 0x96, 0xc8, 0x02, 0x57, 0x64, 0x6b, 0x9b, 0x09, 0xe3, 0x76, 0xf0, 0xad, 0x4d, 0x5f, 0xfa, 0x9e,
0xf3, 0x86, 0x57, 0x7a, 0xf0, 0x86, 0x2e, 0xfc, 0x5d, 0x74, 0x54, 0xae, 0x3b, 0x8e, 0x88, 0x7d, 0x1b, 0xb7, 0x40, 0xd7, 0xbf, 0xd0, 0x83, 0xd7, 0x77, 0xe1, 0xef, 0xac, 0xa5, 0x72, 0xd5, 0x72, 0x44, 0xec, 0xdb, 0xb8, 0x01, 0x4a,
0x89, 0xf2, 0xc4, 0xaa, 0xfd, 0x00, 0x7b, 0xde, 0x14, 0xf9, 0x33, 0x1c, 0x44, 0x8e, 0xef, 0xe3, 0xd0, 0xf1, 0xa3, 0x94, 0x25, 0x56, 0xe5, 0x07, 0xd8, 0xf3, 0xc6, 0xc8, 0x9f, 0xe0, 0x20, 0x72, 0x7c, 0x1f, 0x87, 0x8e, 0x1f, 0xa5,
0x6c, 0xe2, 0x44, 0xc8, 0x1f, 0xc1, 0x10, 0x04, 0x38, 0x42, 0xde, 0x27, 0x0b, 0x15, 0xac, 0xaa, 0x12, 0x8b, 0x37, 0x23, 0x27, 0x42, 0xfe, 0x00, 0x86, 0x20, 0xc0, 0x11, 0xf2, 0x3e, 0x59, 0x28, 0x67, 0x65, 0x99, 0x58, 0xbc, 0xe6,
0x9c, 0x5a, 0x48, 0xc8, 0xae, 0xb9, 0xa3, 0x89, 0x95, 0xad, 0xbb, 0x0e, 0xec, 0x7f, 0xd5, 0x54, 0x4d, 0x07, 0x70, 0xd4, 0x42, 0x42, 0xb6, 0xf5, 0x1d, 0x4d, 0xac, 0x74, 0xd5, 0xb6, 0x60, 0xff, 0x55, 0x5d, 0xd6, 0x2d, 0xc0, 0xf5,
0x7d, 0x83, 0x3e, 0xfb, 0xf9, 0x6a, 0x15, 0xb2, 0x23, 0x5c, 0x14, 0x4d, 0x57, 0x27, 0x96, 0x7e, 0x29, 0xf6, 0xd3, 0x15, 0x7a, 0xf0, 0xf3, 0xc5, 0x2a, 0x64, 0x4b, 0xb8, 0xc8, 0xeb, 0xb6, 0x4a, 0x2c, 0xfd, 0x50, 0xec, 0xe7, 0x7b,
0x83, 0x3c, 0x22, 0x35, 0x0c, 0x2f, 0x1e, 0x3a, 0x4d, 0xc7, 0x56, 0x8c, 0x27, 0x96, 0x1f, 0x20, 0x7f, 0x0a, 0x6a, 0x79, 0x40, 0x6a, 0xe8, 0x9f, 0x1d, 0x3a, 0x75, 0xcb, 0x96, 0x8c, 0x27, 0x96, 0x1f, 0x20, 0x7f, 0x0c, 0x6a, 0x6f,
0x6f, 0x87, 0xc7, 0x33, 0x26, 0x44, 0x61, 0xd2, 0x7b, 0xd9, 0xd8, 0xef, 0x6f, 0x63, 0xb1, 0x59, 0xa1, 0x5d, 0x5d, 0xfb, 0x87, 0x13, 0x26, 0x44, 0x61, 0xd2, 0x79, 0x59, 0xdb, 0xef, 0x6f, 0x63, 0xb1, 0x5e, 0xa2, 0x6d, 0x55, 0x72,
0x71, 0x91, 0x58, 0xa5, 0x94, 0xed, 0xdc, 0x75, 0xb7, 0xdb, 0x2d, 0xde, 0x86, 0xb8, 0xe9, 0x56, 0x6e, 0xe0, 0x79, 0x91, 0x58, 0x85, 0x94, 0xcd, 0xd4, 0x75, 0x37, 0x9b, 0x0d, 0xde, 0x84, 0xb8, 0x6e, 0x97, 0x6e, 0xe0, 0x79, 0x9e,
0x9e, 0x0b, 0x14, 0x16, 0x32, 0xe7, 0xc3, 0x0a, 0x46, 0x16, 0x2a, 0x29, 0x5b, 0x95, 0x52, 0xcf, 0xd3, 0xa7, 0x07, 0x0b, 0x14, 0x16, 0x32, 0xf7, 0xc3, 0x0a, 0x06, 0x16, 0x2a, 0x28, 0x5b, 0x16, 0x52, 0xcf, 0xe7, 0xcf, 0xf7, 0xf4,
0x7a, 0x8c, 0x15, 0x45, 0x7a, 0xfb, 0xe1, 0x42, 0x4b, 0x77, 0xa1, 0x85, 0x7e, 0x7f, 0x81, 0xe6, 0xe0, 0xad, 0x32, 0x10, 0x2b, 0x8a, 0xf9, 0xed, 0x87, 0x33, 0x2d, 0xec, 0x4c, 0x0b, 0xfd, 0xee, 0x0c, 0xcd, 0xde, 0x5b, 0x65, 0xd4,
0x6a, 0x42, 0x02, 0x14, 0x20, 0x4f, 0xff, 0x0b, 0x1c, 0x35, 0xef, 0x57, 0xce, 0x83, 0x15, 0xba, 0x58, 0xc1, 0x5f, 0x88, 0x04, 0x28, 0x40, 0x9e, 0xfe, 0x17, 0x38, 0x6a, 0xde, 0xad, 0x9c, 0x47, 0x2b, 0x74, 0xb6, 0x82, 0xbf, 0x80,
0xc0, 0x2f, 0xa8, 0xc7, 0xce, 0xec, 0xcc, 0xee, 0xab, 0xc7, 0x1b, 0xdf, 0xbb, 0xdf, 0x50, 0x3c, 0x3f, 0x8e, 0x2f, 0x5f, 0x50, 0x0d, 0x9d, 0xc9, 0x89, 0xdd, 0x57, 0xc7, 0x6b, 0xdf, 0xbb, 0xdf, 0x50, 0x3c, 0x3f, 0x0c, 0xcf, 0xd7,
0xd7, 0x4e, 0xf0, 0x9b, 0x22, 0x50, 0xd8, 0x9f, 0x99, 0x9c, 0xa0, 0xf4, 0x7f, 0x1b, 0x93, 0x08, 0x45, 0xfd, 0x4e, 0x4e, 0xf0, 0xab, 0x22, 0x50, 0xd8, 0x9f, 0x98, 0x9c, 0xa0, 0xf0, 0x7f, 0x1d, 0x92, 0x08, 0x45, 0xdd, 0x4e, 0xe4,
0xe4, 0xa8, 0xf9, 0x79, 0xa5, 0x34, 0xa1, 0x68, 0x03, 0x54, 0xb5, 0x33, 0x76, 0x22, 0x12, 0xa2, 0xb0, 0x37, 0x09, 0xa8, 0xf9, 0x69, 0xa5, 0x34, 0xa1, 0x68, 0x0d, 0x54, 0x95, 0x33, 0x74, 0x22, 0x12, 0xa2, 0xb0, 0x33, 0x09, 0x66,
0x66, 0xb0, 0x3d, 0x06, 0xe6, 0x8b, 0x3d, 0x27, 0xfc, 0x34, 0x50, 0x30, 0xcf, 0x2d, 0xeb, 0x1e, 0x83, 0xe6, 0x12, 0xb0, 0x3d, 0x04, 0xe6, 0xb3, 0x3d, 0x27, 0xfc, 0xd4, 0x53, 0x30, 0x4f, 0x2d, 0xeb, 0x1e, 0x83, 0xfa, 0x1c, 0x03,
0x03, 0xfc, 0xb1, 0x81, 0x33, 0x67, 0x59, 0x80, 0x11, 0x95, 0x59, 0x69, 0x5b, 0x2e, 0x44, 0x5e, 0xc1, 0x56, 0x10, 0xfc, 0xb1, 0x86, 0x3b, 0x67, 0x59, 0x80, 0x11, 0x95, 0x69, 0x61, 0x5b, 0x2e, 0x44, 0x5e, 0xce, 0x96, 0x10, 0x15,
0x15, 0x0d, 0xb7, 0x86, 0x58, 0x96, 0x94, 0xdb, 0x27, 0x56, 0xc5, 0x48, 0xf5, 0x13, 0xfb, 0xe1, 0x13, 0x39, 0x3c, 0x35, 0xb7, 0xfa, 0x58, 0x16, 0x94, 0xdb, 0x47, 0x56, 0xc5, 0x48, 0xf5, 0x89, 0xfd, 0xf8, 0x44, 0xf6, 0xf7, 0xa7,
0x9c, 0xe3, 0x43, 0x32, 0x09, 0x71, 0x28, 0xb1, 0x8a, 0xe8, 0xab, 0xf3, 0xee, 0xb2, 0xc9, 0xf7, 0x8f, 0x84, 0x4e, 0xf8, 0x90, 0x4c, 0x42, 0x1c, 0x4a, 0xac, 0x22, 0xfa, 0xe2, 0xb4, 0xbb, 0xa8, 0xb3, 0xdd, 0x13, 0xa1, 0x53, 0xf8,
0xe9, 0x9b, 0xb8, 0x61, 0x9c, 0xd3, 0xee, 0x86, 0xee, 0xe0, 0x1d, 0xfe, 0x83, 0xfd, 0xc0, 0xd0, 0xcf, 0x54, 0x6e, 0x26, 0x6e, 0x18, 0xe7, 0xb4, 0xbd, 0xa1, 0x5b, 0x78, 0x86, 0x6f, 0x2f, 0xaf, 0xd0, 0x65, 0x96, 0xb5, 0x54, 0x88,
0x9b, 0xee, 0x4e, 0xcc, 0x91, 0xf5, 0xdc, 0x88, 0x5b, 0xa8, 0xa8, 0x61, 0x20, 0x9b, 0xb4, 0x02, 0x8b, 0x0a, 0x72, 0x29, 0xb2, 0x5e, 0x4a, 0x88, 0x91, 0xf4, 0x7f, 0x97, 0xe5, 0x3f, 0x90, 0xf5, 0x1b, 0xfb, 0x9e, 0xa1, 0x9f, 0xa8,
0x82, 0xed, 0x0f, 0x21, 0x7e, 0xda, 0x7b, 0x4b, 0xf8, 0xc9, 0xb9, 0xdb, 0x38, 0x67, 0x1b, 0x94, 0x55, 0x10, 0xf5, 0xdc, 0xd4, 0xed, 0x5d, 0x27, 0x4d, 0x99, 0x36, 0x53, 0x11, 0xd8, 0x82, 0x9d, 0xa4, 0x11, 0x58, 0x94, 0x90, 0x5f,
0x70, 0xfc, 0x8d, 0x28, 0x0b, 0xf5, 0x47, 0xbd, 0xe1, 0x19, 0x70, 0xdf, 0x25, 0xd6, 0x17, 0xa2, 0xfa, 0xe5, 0xfe, 0x6c, 0xbf, 0x0f, 0x7a, 0x9a, 0x7b, 0xaf, 0xf8, 0x11, 0xa8, 0xdb, 0x38, 0x63, 0x6b, 0x94, 0x96, 0x90, 0x41, 0x20,
0xa7, 0xdc, 0x1e, 0x08, 0x88, 0xe7, 0xc1, 0x10, 0x6f, 0x48, 0xb5, 0xa6, 0x28, 0x41, 0xb2, 0x64, 0xe2, 0xde, 0xc0, 0x94, 0x8c, 0x28, 0x0b, 0x75, 0x61, 0x53, 0xf3, 0x14, 0xb8, 0xef, 0x12, 0xeb, 0x33, 0x19, 0xe2, 0xd5, 0xee, 0xc7,
0xc5, 0xa3, 0x6c, 0xad, 0xb8, 0x03, 0xae, 0x02, 0x1e, 0x0b, 0x7b, 0x68, 0x9d, 0x22, 0x2b, 0x26, 0x26, 0xef, 0x59, 0xcc, 0xee, 0x09, 0xc8, 0x0d, 0xbd, 0x3e, 0x5e, 0x93, 0x72, 0x45, 0x51, 0x82, 0x64, 0xc1, 0xc4, 0xbd, 0x81, 0xb3,
0x4f, 0xac, 0x07, 0x16, 0x39, 0x15, 0x2d, 0xa4, 0x75, 0x1f, 0x81, 0x4f, 0x0f, 0xc2, 0xe6, 0xb8, 0x03, 0xed, 0xc3, 0x27, 0xd9, 0x1a, 0x71, 0x07, 0x5c, 0x39, 0x1c, 0x0b, 0xbb, 0x6f, 0x1d, 0xa3, 0x34, 0x26, 0x26, 0x87, 0x5a, 0xcf,
0xe3, 0x79, 0x33, 0x16, 0x2d, 0xe1, 0x0f, 0x19, 0x95, 0x81, 0xea, 0xa0, 0x43, 0xb2, 0x82, 0x99, 0x3a, 0xed, 0x40, 0xac, 0x47, 0x16, 0x39, 0x25, 0xcd, 0xa5, 0x75, 0x1f, 0xcd, 0xcf, 0xf7, 0xc2, 0xe6, 0xb8, 0x05, 0xed, 0xfd, 0xc3,
0x74, 0x56, 0xe8, 0x92, 0xd3, 0xf4, 0xe9, 0xa1, 0x03, 0x89, 0x2a, 0x07, 0x9d, 0x25, 0xc6, 0x2e, 0x40, 0x93, 0xde, 0x69, 0x33, 0x16, 0x0d, 0xe1, 0x8f, 0x19, 0x95, 0x81, 0x2a, 0x68, 0x20, 0xf1, 0xc1, 0x4c, 0x45, 0x0e, 0x10, 0x9d,
0x1e, 0x87, 0xf7, 0x7e, 0xfc, 0xbe, 0xa6, 0xdd, 0xfe, 0x9a, 0x56, 0x34, 0x93, 0x4d, 0x67, 0x5b, 0x4f, 0x40, 0x0b, 0x14, 0xba, 0xe4, 0x38, 0x7d, 0xbe, 0x67, 0x20, 0x51, 0xe5, 0xb3, 0x93, 0xc4, 0xd8, 0x05, 0x68, 0xe6, 0xb7, 0x87,
0xbc, 0x7e, 0xed, 0xf0, 0x8f, 0x37, 0x6f, 0xdf, 0x24, 0x8d, 0xcd, 0x86, 0x57, 0x8f, 0x51, 0xab, 0x0c, 0xff, 0x1e, 0xfe, 0xbd, 0x1f, 0xff, 0x5c, 0xd1, 0x76, 0x77, 0x4d, 0x4b, 0x9a, 0xca, 0xba, 0xb5, 0xad, 0x67, 0xa0, 0x05, 0xae,
0x32, 0xfc, 0x3f, 0x93, 0x81, 0xca, 0xf1, 0x83, 0x0f, 0xc0, 0xaa, 0xfd, 0xbd, 0xbd, 0x4f, 0xf4, 0x2a, 0x18, 0x9f, 0x92, 0x76, 0xf8, 0x87, 0x9b, 0xb7, 0x6f, 0x92, 0xda, 0x6e, 0xfb, 0x17, 0x4f, 0x51, 0xab, 0x6a, 0xf1, 0x1e, 0xaa,
0x43, 0x40, 0x5f, 0x29, 0x0f, 0x9d, 0x71, 0x34, 0x3c, 0x82, 0x7e, 0xb0, 0x00, 0xec, 0xd6, 0xb9, 0x1a, 0x72, 0xb6, 0xc5, 0xbf, 0x92, 0x9e, 0xaa, 0x17, 0xbd, 0x0f, 0xc0, 0xaa, 0xfd, 0xbd, 0xbd, 0x2f, 0x1a, 0x2a, 0xb0, 0x5f, 0x42,
0x4a, 0x9b, 0xe9, 0x77, 0x87, 0x65, 0xb3, 0x73, 0x04, 0xfb, 0xc4, 0xf8, 0x6a, 0xce, 0x78, 0x49, 0x3b, 0x26, 0x8f, 0x72, 0xb8, 0x50, 0x1e, 0x3a, 0xc3, 0xa8, 0x7f, 0x00, 0xfd, 0x60, 0x01, 0xd8, 0xad, 0xf3, 0x3e, 0xe4, 0x7f, 0x95,
0x60, 0x2e, 0xa4, 0xfd, 0x76, 0x2d, 0x0f, 0x2d, 0xc9, 0x73, 0xf5, 0x24, 0x6a, 0x77, 0x8b, 0x02, 0x8a, 0x84, 0xa2, 0x82, 0xe7, 0xdf, 0xee, 0x17, 0xf5, 0xd6, 0x11, 0xec, 0x13, 0xe3, 0xcb, 0x29, 0xe3, 0x05, 0x6d, 0x99, 0x3c, 0x80,
0xa4, 0x73, 0x9f, 0xd6, 0x47, 0xf3, 0x5c, 0xe7, 0x83, 0xf9, 0x2c, 0x7a, 0x76, 0x54, 0x07, 0xee, 0x20, 0xe1, 0x65, 0xb9, 0x50, 0x42, 0x9a, 0x95, 0xdc, 0x37, 0x24, 0xcb, 0xd4, 0x49, 0xd4, 0x6c, 0x67, 0x39, 0x14, 0x1c, 0x45, 0x49,
0x39, 0xa4, 0x62, 0x2b, 0x3e, 0xcf, 0xc0, 0x70, 0xda, 0x19, 0xa6, 0x82, 0xd4, 0xac, 0xda, 0xcf, 0x05, 0x64, 0x26, 0xa7, 0x3e, 0xad, 0x0e, 0xe6, 0x5c, 0xe7, 0x96, 0xe9, 0x24, 0x7a, 0x71, 0x50, 0x17, 0x6e, 0x2f, 0xe1, 0x61, 0x39,
0x07, 0xaa, 0x07, 0x2b, 0x8e, 0xcb, 0xb5, 0x94, 0x0d, 0x07, 0xdd, 0x5d, 0x4e, 0xbb, 0xb9, 0xb7, 0x30, 0x13, 0xa7, 0xa4, 0x64, 0x4b, 0x3e, 0x4d, 0xc1, 0x70, 0xda, 0x1a, 0xa6, 0x9c, 0x54, 0xac, 0xdc, 0x4d, 0x05, 0x64, 0x39, 0x07,
0x23, 0x39, 0x5b, 0x8b, 0x39, 0x0e, 0x3b, 0x5a, 0x2f, 0x96, 0x24, 0xbb, 0x5b, 0x75, 0xcd, 0x9a, 0xe7, 0x4e, 0xa6, 0x2a, 0x11, 0xcb, 0x0f, 0x8b, 0x95, 0x94, 0x35, 0x07, 0xdd, 0x6d, 0x46, 0xdb, 0xa9, 0x37, 0x33, 0x13, 0xa7, 0x25,
0x32, 0xe7, 0xfc, 0x89, 0x5f, 0x90, 0x90, 0x66, 0x8b, 0x7e, 0x55, 0x14, 0xc5, 0x02, 0xa0, 0xa0, 0x8e, 0xc9, 0x44, 0x19, 0x5b, 0x89, 0x29, 0x0e, 0x5b, 0x5a, 0xcd, 0x16, 0x24, 0xbd, 0x5b, 0xb6, 0xf5, 0x8a, 0x67, 0x4e, 0xaa, 0xb2,
0xf3, 0x00, 0x8f, 0x14, 0xdb, 0x85, 0x99, 0x38, 0x50, 0x1b, 0xc6, 0x46, 0x48, 0xeb, 0xcf, 0x16, 0x27, 0x77, 0xbc, 0xf0, 0xf4, 0x99, 0x9f, 0x93, 0x90, 0xa6, 0xb3, 0x6e, 0x95, 0xe7, 0xf9, 0x0c, 0xa0, 0xa0, 0x8e, 0xc9, 0x6a, 0xd3,
0x05, 0xa4, 0x64, 0x01, 0x42, 0x5a, 0x88, 0x47, 0x30, 0xf3, 0x58, 0x13, 0xc6, 0x2f, 0xad, 0x57, 0xc7, 0x64, 0xd1, 0x00, 0x0f, 0x14, 0xdb, 0x99, 0x99, 0x38, 0x50, 0x1b, 0xc6, 0x46, 0x28, 0x11, 0x2f, 0x66, 0x47, 0x77, 0xbc, 0x19,
0x97, 0x14, 0x80, 0x45, 0xab, 0xd1, 0x85, 0x65, 0x01, 0x45, 0xc3, 0x14, 0xc6, 0x79, 0x30, 0xf6, 0xda, 0xdd, 0x11, 0xa4, 0x77, 0x01, 0x42, 0x1a, 0x88, 0x6d, 0x30, 0xf3, 0x50, 0x11, 0xc6, 0xcf, 0xad, 0x57, 0xd7, 0x64, 0xd6, 0x95,
0xf7, 0x07, 0xe4, 0x70, 0xa2, 0x2e, 0x2a, 0xba, 0x5b, 0x7c, 0x5c, 0x0b, 0xc9, 0x8a, 0xbd, 0xd3, 0x17, 0xd6, 0x39, 0x27, 0x80, 0x45, 0xab, 0xd1, 0x45, 0x6a, 0x06, 0x05, 0xc8, 0x14, 0xd9, 0x69, 0x30, 0xf4, 0x9a, 0xed, 0x01, 0x77,
0x1c, 0x16, 0x28, 0xa8, 0x4b, 0x20, 0xa5, 0x94, 0x2f, 0xb4, 0x0e, 0x87, 0x49, 0x5a, 0x8b, 0x1e, 0xa7, 0xb3, 0x18, 0x17, 0x64, 0x7f, 0xa4, 0xce, 0x4b, 0xba, 0x9d, 0x7d, 0x5c, 0x09, 0xc9, 0xf2, 0x9d, 0xd3, 0x15, 0xe9, 0x29, 0x5c,
0x7d, 0x40, 0x3f, 0x97, 0xf5, 0x9f, 0xa8, 0xd5, 0x59, 0x3c, 0xd4, 0xa4, 0x83, 0x44, 0xef, 0x2c, 0x1b, 0xc0, 0xb4, 0x16, 0x28, 0xce, 0x0b, 0x20, 0xa5, 0x94, 0xcf, 0xb4, 0x0e, 0x87, 0x49, 0x5a, 0x89, 0x0e, 0xa7, 0x93, 0x18, 0x7d,
0x9e, 0x3b, 0x13, 0x78, 0x57, 0xfd, 0x96, 0x12, 0x06, 0x9e, 0x83, 0x99, 0xba, 0x5e, 0x9e, 0xf0, 0xf6, 0xdb, 0x1d, 0x41, 0x1f, 0xca, 0xfa, 0x6f, 0xd4, 0xea, 0x2e, 0xee, 0x2b, 0xd2, 0x42, 0xd1, 0x70, 0x16, 0x35, 0x60, 0x5a, 0x4d,
0x12, 0x4d, 0xc5, 0xf2, 0x9e, 0x4e, 0x93, 0x20, 0xef, 0x0c, 0x8f, 0x0f, 0xaf, 0x1b, 0xa9, 0xbd, 0x13, 0xd4, 0xa3, 0x9d, 0x11, 0x3c, 0xab, 0x6e, 0x4b, 0x09, 0x03, 0xcf, 0xc1, 0x4c, 0x5d, 0x7b, 0x8f, 0x78, 0xfb, 0xcd, 0x16, 0x89,
0x62, 0x4a, 0x7c, 0xef, 0x0b, 0x6f, 0x24, 0x2f, 0x8a, 0x60, 0x59, 0x9c, 0x91, 0x52, 0x65, 0xef, 0xc8, 0xfa, 0x53, 0xba, 0x64, 0x59, 0x47, 0xa7, 0x49, 0x90, 0x77, 0x82, 0xc7, 0x87, 0xc7, 0x8d, 0xd4, 0xde, 0x11, 0xea, 0x41, 0x3e,
0x11, 0x8c, 0x40, 0xc0, 0xe9, 0xdd, 0xc0, 0xfc, 0xc8, 0x74, 0x58, 0x1c, 0x2e, 0xa4, 0xe8, 0xa3, 0x3a, 0x5f, 0x77, 0x26, 0xbe, 0xf7, 0x99, 0x27, 0x92, 0xe5, 0x79, 0xb0, 0xc8, 0x4f, 0x48, 0xa9, 0x12, 0x7a, 0x60, 0xdd, 0xad, 0x08,
0x95, 0x6d, 0x7d, 0xe1, 0xe8, 0x3e, 0x0b, 0x5f, 0xdd, 0x97, 0xa5, 0xc1, 0xe3, 0x65, 0x69, 0x80, 0x54, 0x23, 0xf3, 0x06, 0x20, 0xe0, 0xf8, 0x6c, 0x60, 0x7e, 0x60, 0x3a, 0x2c, 0xf6, 0x67, 0x52, 0xf4, 0x55, 0x9d, 0xae, 0xda, 0xd2,
0xb2, 0xd9, 0x25, 0x03, 0x5d, 0x20, 0x46, 0xf0, 0x3b, 0x78, 0x16, 0xbe, 0x06, 0xfe, 0xff, 0x4a, 0xbd, 0xf9, 0xc3, 0xb6, 0x3e, 0x73, 0x75, 0x5f, 0x84, 0x57, 0xf7, 0x25, 0xae, 0xf7, 0x74, 0x89, 0xeb, 0x21, 0xd5, 0x14, 0xbd, 0xaa,
0xc5, 0xe6, 0x2b, 0x2a, 0xcd, 0x57, 0x56, 0x19, 0xe3, 0x9d, 0x72, 0x1e, 0x66, 0x50, 0x4e, 0x18, 0x16, 0x6c, 0xe5, 0xb7, 0x49, 0x4f, 0x17, 0x9b, 0x01, 0xfc, 0xf6, 0x5e, 0x84, 0xaf, 0x81, 0xff, 0xff, 0x52, 0xbb, 0x7e, 0x77, 0xe1,
0xff, 0x2f, 0xa0, 0xfd, 0x77, 0x1c, 0xc3, 0x17, 0xfe, 0x14, 0xcf, 0x90, 0x1e, 0x0c, 0x44, 0x38, 0x9c, 0xa2, 0xc9, 0xfa, 0x82, 0xaa, 0xf5, 0x85, 0x15, 0xcb, 0x78, 0xa7, 0x9c, 0x87, 0x19, 0x94, 0x26, 0x86, 0x05, 0x5b, 0xfa, 0x7f,
0xab, 0x11, 0x1e, 0xf9, 0x48, 0xb5, 0x30, 0x63, 0x34, 0x81, 0x7e, 0x0f, 0xf9, 0x63, 0x1c, 0x4e, 0x60, 0x03, 0x05, 0x04, 0xb4, 0xff, 0x89, 0x63, 0x78, 0xe9, 0x8f, 0xf1, 0x04, 0xe9, 0xc1, 0x40, 0x84, 0xc3, 0x31, 0x1a, 0x5d, 0x0d,
0x3e, 0x8e, 0xde, 0x04, 0x21, 0x1e, 0x47, 0x40, 0x15, 0x78, 0x38, 0x0c, 0x90, 0xa1, 0x1d, 0xe3, 0x00, 0xc4, 0x29, 0xf0, 0xc0, 0x47, 0xaa, 0x1d, 0x1a, 0xa2, 0x11, 0x1e, 0x03, 0xc1, 0x10, 0x87, 0x23, 0xd8, 0x40, 0x81, 0x8f, 0xa3,
0x92, 0xb0, 0x06, 0xa0, 0xb3, 0x10, 0x7b, 0x13, 0x10, 0x37, 0xc6, 0xde, 0x0c, 0x4f, 0xc7, 0x68, 0x8a, 0x27, 0x00, 0x37, 0x41, 0x88, 0x87, 0x11, 0x50, 0x05, 0x1e, 0x0e, 0x03, 0x64, 0x68, 0x87, 0x38, 0x00, 0x71, 0x8a, 0x24, 0xac,
0x1d, 0x1e, 0x45, 0x95, 0x13, 0x61, 0x1f, 0xb6, 0xc3, 0x31, 0x99, 0xe2, 0x51, 0x88, 0xf4, 0x60, 0xe0, 0x98, 0x80, 0x00, 0xe8, 0x34, 0xc4, 0xde, 0x08, 0xc4, 0x0d, 0xb1, 0x37, 0xc1, 0xe3, 0x21, 0x1a, 0xe3, 0x11, 0x40, 0x87, 0x07,
0x08, 0x07, 0x7b, 0xfe, 0x9b, 0x10, 0x07, 0x13, 0xd0, 0x3b, 0x1a, 0xbd, 0x00, 0xb1, 0xb3, 0x11, 0x32, 0xa3, 0x81, 0x51, 0xe9, 0x44, 0xd8, 0x87, 0xed, 0x70, 0x48, 0xc6, 0x78, 0x10, 0x22, 0x3d, 0x18, 0x38, 0x46, 0x20, 0xc2, 0xc1,
0x17, 0x14, 0x44, 0x8f, 0x81, 0x16, 0xfc, 0x75, 0x41, 0x03, 0x48, 0x7c, 0x14, 0xe2, 0x19, 0xc4, 0xae, 0xaf, 0xf8, 0x9e, 0xff, 0x26, 0xc4, 0xc1, 0x08, 0xf4, 0x0e, 0x06, 0x97, 0x20, 0x76, 0x32, 0x40, 0x66, 0x34, 0xf0, 0x82, 0x82,
0xcd, 0x68, 0x70, 0xf3, 0x7d, 0xe4, 0xfd, 0x61, 0xcc, 0xc2, 0xbf, 0x2e, 0x66, 0xbe, 0x42, 0x00, 0xa6, 0xa0, 0x1b, 0xe8, 0x29, 0xd0, 0x82, 0xbf, 0x2f, 0x68, 0x00, 0x89, 0x8f, 0x42, 0x3c, 0x81, 0xd8, 0xf5, 0x15, 0xbf, 0x19, 0x0d,
0xe4, 0x20, 0x3d, 0x18, 0xdd, 0xc0, 0x3c, 0x7d, 0x35, 0x43, 0x53, 0xe0, 0x1a, 0x4f, 0xd1, 0x0c, 0x45, 0x0a, 0x5d, 0x6e, 0xbe, 0x8f, 0xbc, 0xdf, 0x8d, 0x59, 0xf8, 0xf7, 0xc5, 0xcc, 0x57, 0x08, 0xc0, 0x14, 0x74, 0x83, 0x1c, 0xa4,
0x60, 0x1f, 0x19, 0x26, 0x07, 0x98, 0xbe, 0x12, 0xc6, 0xd1, 0x9f, 0x18, 0xc6, 0xc7, 0x7c, 0xfa, 0x13, 0xbb, 0xf4, 0x07, 0xa3, 0x1b, 0x98, 0xc7, 0x57, 0x13, 0x34, 0x06, 0xae, 0xe1, 0x18, 0x4d, 0x50, 0xa4, 0xd0, 0x05, 0xf6, 0x81,
0xff, 0x48, 0x41, 0xd0, 0x8e, 0xe9, 0x36, 0x2c, 0x76, 0xcd, 0x95, 0x5e, 0x75, 0x51, 0x70, 0x43, 0x87, 0x6e, 0x04, 0x61, 0x72, 0x80, 0xe9, 0x0b, 0x61, 0x1c, 0xfc, 0x85, 0x61, 0x7c, 0xca, 0xa7, 0xbf, 0xb0, 0x4b, 0x7f, 0x46, 0x0a,
0x2e, 0xf9, 0x3e, 0x62, 0x79, 0x52, 0xfa, 0xe9, 0x67, 0xdd, 0x39, 0x50, 0xfa, 0x69, 0xac, 0xcb, 0x79, 0x7a, 0x53, 0x82, 0x76, 0x4c, 0xb7, 0x61, 0xb1, 0x6b, 0x3e, 0x0f, 0xa8, 0x2e, 0x0a, 0xde, 0xf6, 0xa1, 0x1b, 0x99, 0xc7, 0x85,
0x52, 0xf4, 0xfa, 0xfa, 0x1d, 0xdc, 0xca, 0xaa, 0x0a, 0xf1, 0x66, 0x0b, 0x97, 0xbf, 0x3d, 0x92, 0x8d, 0xba, 0xce, 0x8f, 0x58, 0x96, 0x40, 0x57, 0x3f, 0x3f, 0x6b, 0xf5, 0x81, 0xd0, 0x3f, 0x1e, 0xc1, 0xec, 0x41, 0xe3, 0x6e, 0xce,
0x73, 0xe8, 0x15, 0xd5, 0x14, 0xee, 0x0d, 0xa8, 0x6f, 0x16, 0x30, 0xc6, 0xf1, 0xb2, 0x4b, 0xdf, 0x55, 0x94, 0x08, 0x74, 0xa5, 0x9f, 0xdf, 0x14, 0x14, 0xbd, 0xbe, 0x7e, 0x07, 0x2f, 0x7f, 0x65, 0x89, 0x78, 0xbd, 0x81, 0x77, 0xcc,
0x8a, 0x56, 0x6c, 0x43, 0x11, 0x93, 0xd0, 0x07, 0xd4, 0x14, 0x49, 0xa6, 0x86, 0x33, 0xa3, 0xa6, 0x83, 0x9e, 0x56, 0x1d, 0x92, 0xb5, 0xfa, 0x6a, 0xc0, 0xa1, 0x8d, 0x54, 0x53, 0x78, 0x3d, 0x41, 0x5d, 0x1f, 0x81, 0x31, 0x8e, 0x17,
0x2b, 0x31, 0xdd, 0x30, 0x58, 0x02, 0x62, 0xd2, 0xbe, 0xed, 0x8d, 0xcb, 0xd0, 0x58, 0x75, 0x4d, 0xa5, 0x84, 0x8e, 0xed, 0xfc, 0x5d, 0x49, 0x89, 0xa0, 0x68, 0xc9, 0xd6, 0x14, 0x31, 0x09, 0x2d, 0x42, 0x45, 0x91, 0x64, 0x6a, 0x38,
0x41, 0x59, 0x15, 0xa6, 0xb1, 0xba, 0x76, 0x22, 0xa2, 0x2f, 0x06, 0x89, 0xbb, 0x65, 0x05, 0x53, 0x97, 0xf9, 0x34, 0x31, 0x6a, 0x3a, 0x68, 0x77, 0xb5, 0x12, 0xd3, 0x28, 0x83, 0x25, 0x20, 0x66, 0xde, 0x75, 0xc4, 0x71, 0x11, 0x1a,
0xd6, 0xad, 0xa2, 0x92, 0xa0, 0xba, 0x15, 0xf3, 0xe5, 0x41, 0xcf, 0x2a, 0xca, 0x57, 0x70, 0x9b, 0x84, 0x77, 0x01, 0xab, 0xae, 0xa9, 0x94, 0xd0, 0x4c, 0x28, 0xab, 0xc2, 0x79, 0xac, 0xde, 0x6e, 0x11, 0xd1, 0xef, 0x0c, 0x89, 0xbb,
0xcd, 0x43, 0x46, 0xcb, 0xa6, 0x82, 0xe6, 0x24, 0xb9, 0xbe, 0xfe, 0xe9, 0xef, 0xea, 0x33, 0x85, 0x32, 0xe1, 0xcc, 0x61, 0x39, 0x53, 0xdf, 0x0c, 0xe6, 0xb1, 0xee, 0x22, 0x95, 0x04, 0xd5, 0xc8, 0x98, 0x0f, 0x1c, 0x7a, 0x56, 0x52,
0x09, 0x7d, 0xbe, 0x61, 0x54, 0x93, 0x9e, 0x6f, 0x3c, 0x32, 0x1f, 0x1c, 0x5a, 0xe8, 0xd3, 0xc1, 0xbf, 0xfc, 0x33, 0xbe, 0x84, 0x97, 0x56, 0x78, 0x4c, 0xd0, 0x57, 0xa4, 0xb4, 0xa8, 0x4b, 0xe8, 0x5b, 0x92, 0xeb, 0xeb, 0x1f, 0xff,
0x29, 0xef, 0x4e, 0x9b, 0xbd, 0x24, 0xfd, 0x5f, 0x37, 0x9d, 0x86, 0x49, 0xac, 0x97, 0x35, 0x93, 0xe9, 0x35, 0x18, 0xa1, 0xbe, 0x86, 0x28, 0x13, 0x4e, 0x9c, 0xf0, 0x0a, 0x60, 0x18, 0xd5, 0xa4, 0xe3, 0x1b, 0x0e, 0xcc, 0x77, 0x8d,
0x18, 0xbb, 0xe6, 0x01, 0x38, 0xa7, 0x1c, 0x30, 0xb4, 0x65, 0xcf, 0x03, 0x60, 0xff, 0x72, 0xf3, 0x02, 0xfd, 0xda, 0x06, 0x5a, 0x78, 0xf0, 0x2f, 0x7b, 0x20, 0xe5, 0xdd, 0x71, 0xb3, 0x93, 0xa4, 0xff, 0xeb, 0x7e, 0xd4, 0x30, 0x89,
0xc2, 0x09, 0xa6, 0x06, 0x7b, 0xed, 0x65, 0x4d, 0x65, 0xd9, 0xe4, 0xc9, 0xbb, 0x5f, 0xae, 0x6f, 0xce, 0x1e, 0xaf, 0xd5, 0xa2, 0x62, 0x72, 0x7e, 0x0d, 0x06, 0xc6, 0xae, 0x39, 0x00, 0xe7, 0x94, 0x03, 0x86, 0xb6, 0xe8, 0x78, 0x00,
0x35, 0x11, 0xa2, 0x3c, 0x33, 0x1f, 0x42, 0xd6, 0x95, 0x64, 0x2d, 0xe9, 0xa4, 0x16, 0xeb, 0xa8, 0x10, 0x38, 0x79, 0xec, 0x9f, 0x6f, 0x2e, 0xd1, 0x2f, 0x0d, 0x5c, 0x6e, 0x6a, 0xb0, 0xd7, 0x5e, 0x56, 0x54, 0x16, 0x75, 0x96, 0xbc,
0xa4, 0x9f, 0x17, 0xac, 0xa2, 0xc6, 0xa9, 0x9e, 0xd1, 0x4d, 0xd1, 0x97, 0x6c, 0x3c, 0xe9, 0x7e, 0x60, 0xa5, 0x6b, 0xfb, 0xf9, 0xfa, 0xe6, 0xe4, 0xf1, 0x4a, 0x13, 0x21, 0xca, 0x53, 0xf3, 0xbd, 0x65, 0x55, 0x4a, 0xd6, 0x90, 0x56,
0x4e, 0x89, 0x6b, 0x8e, 0x8c, 0xab, 0x3f, 0x13, 0xfd, 0x0b, 0x65, 0x37, 0xa3, 0x8e, 0x36, 0x12, 0x00, 0x00}; 0x6a, 0xb1, 0x8e, 0x8a, 0x8e, 0xa3, 0x47, 0xfa, 0x3c, 0x67, 0x25, 0x35, 0x4e, 0x75, 0x8c, 0xee, 0x1c, 0x7d, 0xce,
0xc6, 0xa3, 0xee, 0x47, 0x56, 0xba, 0xe6, 0x02, 0xb9, 0xe6, 0x36, 0xb9, 0xfa, 0x6b, 0xd4, 0xbf, 0x01, 0x14, 0xee,
0xbc, 0x64, 0x9d, 0x12, 0x00, 0x00};
} // namespace captive_portal } // namespace captive_portal
} // namespace esphome } // namespace esphome

View file

@ -12,7 +12,7 @@ static const char *const TAG = "captive_portal";
void CaptivePortal::handle_config(AsyncWebServerRequest *request) { void CaptivePortal::handle_config(AsyncWebServerRequest *request) {
AsyncResponseStream *stream = request->beginResponseStream("application/json"); AsyncResponseStream *stream = request->beginResponseStream("application/json");
stream->addHeader("cache-control", "public, max-age=0, must-revalidate"); stream->addHeader("cache-control", "public, max-age=0, must-revalidate");
stream->printf(R"({"name":"%s","aps":[{})", App.get_name().c_str()); stream->printf(R"({"mac":"%s","name":"%s","aps":[{})", get_mac_address_pretty().c_str(), App.get_name().c_str());
for (auto &scan : wifi::global_wifi_component->get_scan_result()) { for (auto &scan : wifi::global_wifi_component->get_scan_result()) {
if (scan.get_is_hidden()) if (scan.get_is_hidden())

View file

@ -3,38 +3,50 @@ import esphome.codegen as cg
# import cpp_generator as cpp # import cpp_generator as cpp
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import automation from esphome import automation
from esphome.components import mqtt from esphome.components import mqtt, time
from esphome.const import ( from esphome.const import (
CONF_ID, CONF_ID,
CONF_ON_TIME,
CONF_ON_VALUE, CONF_ON_VALUE,
CONF_TIME_ID,
CONF_TRIGGER_ID, CONF_TRIGGER_ID,
CONF_TYPE, CONF_TYPE,
CONF_MQTT_ID, CONF_MQTT_ID,
CONF_DATE, CONF_DATE,
CONF_TIME,
CONF_YEAR, CONF_YEAR,
CONF_MONTH, CONF_MONTH,
CONF_DAY, CONF_DAY,
CONF_SECOND,
CONF_HOUR,
CONF_MINUTE,
) )
from esphome.core import CORE, coroutine_with_priority from esphome.core import CORE, coroutine_with_priority
from esphome.cpp_generator import MockObjClass from esphome.cpp_generator import MockObjClass
from esphome.cpp_helpers import setup_entity from esphome.cpp_helpers import setup_entity
CODEOWNERS = ["@rfdarter"] CODEOWNERS = ["@rfdarter", "@jesserockz"]
IS_PLATFORM_COMPONENT = True IS_PLATFORM_COMPONENT = True
datetime_ns = cg.esphome_ns.namespace("datetime") datetime_ns = cg.esphome_ns.namespace("datetime")
DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase) DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase)
DateEntity = datetime_ns.class_("DateEntity", DateTimeBase) DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase)
# Actions # Actions
DateSetAction = datetime_ns.class_("DateSetAction", automation.Action) DateSetAction = datetime_ns.class_("DateSetAction", automation.Action)
TimeSetAction = datetime_ns.class_("TimeSetAction", automation.Action)
DateTimeStateTrigger = datetime_ns.class_( DateTimeStateTrigger = datetime_ns.class_(
"DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime) "DateTimeStateTrigger", automation.Trigger.template(cg.ESPTime)
) )
OnTimeTrigger = datetime_ns.class_(
"OnTimeTrigger", automation.Trigger, cg.Component, cg.Parented.template(TimeEntity)
)
DATETIME_MODES = [ DATETIME_MODES = [
"DATE", "DATE",
"TIME", "TIME",
@ -44,7 +56,6 @@ DATETIME_MODES = [
_DATETIME_SCHEMA = cv.Schema( _DATETIME_SCHEMA = cv.Schema(
{ {
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDatetimeComponent),
cv.Optional(CONF_ON_VALUE): automation.validate_automation( cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{ {
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger), cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DateTimeStateTrigger),
@ -57,6 +68,7 @@ _DATETIME_SCHEMA = cv.Schema(
def date_schema(class_: MockObjClass) -> cv.Schema: def date_schema(class_: MockObjClass) -> cv.Schema:
schema = { schema = {
cv.GenerateID(): cv.declare_id(class_), cv.GenerateID(): cv.declare_id(class_),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDateComponent),
cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True), cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True),
} }
return _DATETIME_SCHEMA.extend(schema) return _DATETIME_SCHEMA.extend(schema)
@ -65,7 +77,20 @@ def date_schema(class_: MockObjClass) -> cv.Schema:
def time_schema(class_: MockObjClass) -> cv.Schema: def time_schema(class_: MockObjClass) -> cv.Schema:
schema = { schema = {
cv.GenerateID(): cv.declare_id(class_), cv.GenerateID(): cv.declare_id(class_),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTimeComponent),
cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True), cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True),
cv.Inclusive(
CONF_ON_TIME,
group_of_inclusion=CONF_ON_TIME,
msg="`on_time` and `time_id` must both be specified",
): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(OnTimeTrigger),
}
),
cv.Inclusive(CONF_TIME_ID, group_of_inclusion=CONF_ON_TIME): cv.use_id(
time.RealTimeClock
),
} }
return _DATETIME_SCHEMA.extend(schema) return _DATETIME_SCHEMA.extend(schema)
@ -88,6 +113,17 @@ async def setup_datetime_core_(var, config):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf) await automation.build_automation(trigger, [(cg.ESPTime, "x")], conf)
rtc_id = config.get(CONF_TIME_ID)
rtc = None
if rtc_id is not None:
rtc = await cg.get_variable(rtc_id)
for conf in config.get(CONF_ON_TIME, []):
assert rtc is not None
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], rtc)
await automation.build_automation(trigger, [], conf)
await cg.register_component(trigger, conf)
await cg.register_parented(trigger, var)
async def register_datetime(var, config): async def register_datetime(var, config):
if not CORE.has_id(config[CONF_ID]): if not CORE.has_id(config[CONF_ID]):
@ -109,18 +145,12 @@ async def to_code(config):
cg.add_global(datetime_ns.using) cg.add_global(datetime_ns.using)
OPERATION_BASE_SCHEMA = cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(DateEntity),
}
)
@automation.register_action( @automation.register_action(
"datetime.date.set", "datetime.date.set",
DateSetAction, DateSetAction,
OPERATION_BASE_SCHEMA.extend( cv.Schema(
{ {
cv.Required(CONF_ID): cv.use_id(DateEntity),
cv.Required(CONF_DATE): cv.Any( cv.Required(CONF_DATE): cv.Any(
cv.returning_lambda, cv.date_time(allowed_time=False) cv.returning_lambda, cv.date_time(allowed_time=False)
), ),
@ -144,3 +174,34 @@ async def datetime_date_set_to_code(config, action_id, template_arg, args):
) )
cg.add(action_var.set_date(date_struct)) cg.add(action_var.set_date(date_struct))
return action_var return action_var
@automation.register_action(
"datetime.time.set",
TimeSetAction,
cv.Schema(
{
cv.Required(CONF_ID): cv.use_id(TimeEntity),
cv.Required(CONF_TIME): cv.Any(
cv.returning_lambda, cv.date_time(allowed_date=False)
),
}
),
)
async def datetime_time_set_to_code(config, action_id, template_arg, args):
action_var = cg.new_Pvariable(action_id, template_arg)
await cg.register_parented(action_var, config[CONF_ID])
time_config = config[CONF_TIME]
if cg.is_template(time_config):
template_ = await cg.templatable(config[CONF_TIME], [], cg.ESPTime)
cg.add(action_var.set_time(template_))
else:
time_struct = cg.StructInitializer(
cg.ESPTime,
("second", time_config[CONF_SECOND]),
("minute", time_config[CONF_MINUTE]),
("hour", time_config[CONF_HOUR]),
)
cg.add(action_var.set_time(time_struct))
return action_var

View file

@ -0,0 +1,156 @@
#include "time_entity.h"
#ifdef USE_DATETIME_TIME
#include "esphome/core/log.h"
namespace esphome {
namespace datetime {
static const char *const TAG = "datetime.time_entity";
void TimeEntity::publish_state() {
if (this->hour_ > 23) {
this->has_state_ = false;
ESP_LOGE(TAG, "Hour must be between 0 and 23");
return;
}
if (this->minute_ > 59) {
this->has_state_ = false;
ESP_LOGE(TAG, "Minute must be between 0 and 59");
return;
}
if (this->second_ > 59) {
this->has_state_ = false;
ESP_LOGE(TAG, "Second must be between 0 and 59");
return;
}
this->has_state_ = true;
ESP_LOGD(TAG, "'%s': Sending time %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_,
this->second_);
this->state_callback_.call();
}
TimeCall TimeEntity::make_call() { return TimeCall(this); }
void TimeCall::validate_() {
if (this->hour_.has_value() && this->hour_ > 23) {
ESP_LOGE(TAG, "Hour must be between 0 and 23");
this->hour_.reset();
}
if (this->minute_.has_value() && this->minute_ > 59) {
ESP_LOGE(TAG, "Minute must be between 0 and 59");
this->minute_.reset();
}
if (this->second_.has_value() && this->second_ > 59) {
ESP_LOGE(TAG, "Second must be between 0 and 59");
this->second_.reset();
}
}
void TimeCall::perform() {
this->validate_();
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
if (this->hour_.has_value()) {
ESP_LOGD(TAG, " Hour: %d", *this->hour_);
}
if (this->minute_.has_value()) {
ESP_LOGD(TAG, " Minute: %d", *this->minute_);
}
if (this->second_.has_value()) {
ESP_LOGD(TAG, " Second: %d", *this->second_);
}
this->parent_->control(*this);
}
TimeCall &TimeCall::set_time(uint8_t hour, uint8_t minute, uint8_t second) {
this->hour_ = hour;
this->minute_ = minute;
this->second_ = second;
return *this;
};
TimeCall &TimeCall::set_time(ESPTime time) { return this->set_time(time.hour, time.minute, time.second); };
TimeCall &TimeCall::set_time(const std::string &time) {
ESPTime val{};
if (!ESPTime::strptime(time, val)) {
ESP_LOGE(TAG, "Could not convert the time string to an ESPTime object");
return *this;
}
return this->set_time(val);
}
TimeCall TimeEntityRestoreState::to_call(TimeEntity *time) {
TimeCall call = time->make_call();
call.set_time(this->hour, this->minute, this->second);
return call;
}
void TimeEntityRestoreState::apply(TimeEntity *time) {
time->hour_ = this->hour;
time->minute_ = this->minute;
time->second_ = this->second;
time->publish_state();
}
#ifdef USE_TIME
static const int MAX_TIMESTAMP_DRIFT = 900; // how far can the clock drift before we consider
// there has been a drastic time synchronization
void OnTimeTrigger::loop() {
if (!this->parent_->has_state()) {
return;
}
ESPTime time = this->rtc_->now();
if (!time.is_valid()) {
return;
}
if (this->last_check_.has_value()) {
if (*this->last_check_ > time && this->last_check_->timestamp - time.timestamp > MAX_TIMESTAMP_DRIFT) {
// We went back in time (a lot), probably caused by time synchronization
ESP_LOGW(TAG, "Time has jumped back!");
} else if (*this->last_check_ >= time) {
// already handled this one
return;
} else if (time > *this->last_check_ && time.timestamp - this->last_check_->timestamp > MAX_TIMESTAMP_DRIFT) {
// We went ahead in time (a lot), probably caused by time synchronization
ESP_LOGW(TAG, "Time has jumped ahead!");
this->last_check_ = time;
return;
}
while (true) {
this->last_check_->increment_second();
if (*this->last_check_ >= time)
break;
if (this->matches_(*this->last_check_)) {
this->trigger();
break;
}
}
}
this->last_check_ = time;
if (!time.fields_in_range()) {
ESP_LOGW(TAG, "Time is out of range!");
ESP_LOGD(TAG, "Second=%02u Minute=%02u Hour=%02u", time.second, time.minute, time.hour);
}
if (this->matches_(time))
this->trigger();
}
bool OnTimeTrigger::matches_(const ESPTime &time) const {
return time.is_valid() && time.hour == this->parent_->hour && time.minute == this->parent_->minute &&
time.second == this->parent_->second;
}
#endif
} // namespace datetime
} // namespace esphome
#endif // USE_DATETIME_TIME

View file

@ -0,0 +1,137 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_DATETIME_TIME
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include "esphome/core/time.h"
#include "datetime_base.h"
#ifdef USE_TIME
#include "esphome/components/time/real_time_clock.h"
#endif
namespace esphome {
namespace datetime {
#define LOG_DATETIME_TIME(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()); \
} \
}
class TimeCall;
class TimeEntity;
struct TimeEntityRestoreState {
uint8_t hour;
uint8_t minute;
uint8_t second;
TimeCall to_call(TimeEntity *time);
void apply(TimeEntity *time);
} __attribute__((packed));
class TimeEntity : public DateTimeBase {
protected:
uint8_t hour_;
uint8_t minute_;
uint8_t second_;
public:
void publish_state();
TimeCall make_call();
ESPTime state_as_esptime() const override {
ESPTime obj;
obj.hour = this->hour_;
obj.minute = this->minute_;
obj.second = this->second_;
return obj;
}
const uint8_t &hour = hour_;
const uint8_t &minute = minute_;
const uint8_t &second = second_;
protected:
friend class TimeCall;
friend struct TimeEntityRestoreState;
virtual void control(const TimeCall &call) = 0;
};
class TimeCall {
public:
explicit TimeCall(TimeEntity *parent) : parent_(parent) {}
void perform();
TimeCall &set_time(uint8_t hour, uint8_t minute, uint8_t second);
TimeCall &set_time(ESPTime time);
TimeCall &set_time(const std::string &time);
TimeCall &set_hour(uint8_t hour) {
this->hour_ = hour;
return *this;
}
TimeCall &set_minute(uint8_t minute) {
this->minute_ = minute;
return *this;
}
TimeCall &set_second(uint8_t second) {
this->second_ = second;
return *this;
}
optional<uint8_t> get_hour() const { return this->hour_; }
optional<uint8_t> get_minute() const { return this->minute_; }
optional<uint8_t> get_second() const { return this->second_; }
protected:
void validate_();
TimeEntity *parent_;
optional<uint8_t> hour_;
optional<uint8_t> minute_;
optional<uint8_t> second_;
};
template<typename... Ts> class TimeSetAction : public Action<Ts...>, public Parented<TimeEntity> {
public:
TEMPLATABLE_VALUE(ESPTime, time)
void play(Ts... x) override {
auto call = this->parent_->make_call();
if (this->time_.has_value()) {
call.set_time(this->time_.value(x...));
}
call.perform();
}
};
#ifdef USE_TIME
class OnTimeTrigger : public Trigger<>, public Component, public Parented<TimeEntity> {
public:
explicit OnTimeTrigger(time::RealTimeClock *rtc) : rtc_(rtc) {}
void loop() override;
protected:
bool matches_(const ESPTime &time) const;
time::RealTimeClock *rtc_;
optional<ESPTime> last_check_;
};
#endif
} // namespace datetime
} // namespace esphome
#endif // USE_DATETIME_TIME

View file

@ -60,6 +60,8 @@ void DisplayMenuComponent::left() {
if (this->editing_) { if (this->editing_) {
this->finish_editing_(); this->finish_editing_();
changed = true; changed = true;
} else {
changed = this->leave_menu_();
} }
break; break;
case MENU_MODE_JOYSTICK: case MENU_MODE_JOYSTICK:

View file

@ -0,0 +1,55 @@
import esphome.config_validation as cv
import esphome.codegen as cg
from esphome.components import esp32
CODEOWNERS = ["@jesserockz"]
RMT_TX_CHANNELS = {
esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7],
esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3],
esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3],
esp32.const.VARIANT_ESP32C3: [0, 1],
esp32.const.VARIANT_ESP32C6: [0, 1],
esp32.const.VARIANT_ESP32H2: [0, 1],
}
RMT_RX_CHANNELS = {
esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7],
esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3],
esp32.const.VARIANT_ESP32S3: [4, 5, 6, 7],
esp32.const.VARIANT_ESP32C3: [2, 3],
esp32.const.VARIANT_ESP32C6: [2, 3],
esp32.const.VARIANT_ESP32H2: [2, 3],
}
rmt_channel_t = cg.global_ns.enum("rmt_channel_t")
RMT_CHANNEL_ENUMS = {
0: rmt_channel_t.RMT_CHANNEL_0,
1: rmt_channel_t.RMT_CHANNEL_1,
2: rmt_channel_t.RMT_CHANNEL_2,
3: rmt_channel_t.RMT_CHANNEL_3,
4: rmt_channel_t.RMT_CHANNEL_4,
5: rmt_channel_t.RMT_CHANNEL_5,
6: rmt_channel_t.RMT_CHANNEL_6,
7: rmt_channel_t.RMT_CHANNEL_7,
}
def validate_rmt_channel(*, tx: bool):
rmt_channels = RMT_TX_CHANNELS if tx else RMT_RX_CHANNELS
def _validator(value):
cv.only_on_esp32(value)
value = cv.int_(value)
variant = esp32.get_esp32_variant()
if variant not in rmt_channels:
raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.")
if value not in rmt_channels[variant]:
raise cv.Invalid(
f"RMT channel {value} does not support {'transmitting' if tx else 'receiving'} for ESP32 variant {variant}."
)
return cv.enum(RMT_CHANNEL_ENUMS)(value)
return _validator

View file

@ -3,7 +3,7 @@ from dataclasses import dataclass
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.components import esp32, light from esphome.components import esp32_rmt, light
from esphome.const import ( from esphome.const import (
CONF_CHIPSET, CONF_CHIPSET,
CONF_MAX_REFRESH_RATE, CONF_MAX_REFRESH_RATE,
@ -11,6 +11,7 @@ from esphome.const import (
CONF_OUTPUT_ID, CONF_OUTPUT_ID,
CONF_PIN, CONF_PIN,
CONF_RGB_ORDER, CONF_RGB_ORDER,
CONF_RMT_CHANNEL,
) )
CODEOWNERS = ["@jesserockz"] CODEOWNERS = ["@jesserockz"]
@ -57,27 +58,6 @@ CONF_BIT0_HIGH = "bit0_high"
CONF_BIT0_LOW = "bit0_low" CONF_BIT0_LOW = "bit0_low"
CONF_BIT1_HIGH = "bit1_high" CONF_BIT1_HIGH = "bit1_high"
CONF_BIT1_LOW = "bit1_low" CONF_BIT1_LOW = "bit1_low"
CONF_RMT_CHANNEL = "rmt_channel"
RMT_CHANNELS = {
esp32.const.VARIANT_ESP32: [0, 1, 2, 3, 4, 5, 6, 7],
esp32.const.VARIANT_ESP32S2: [0, 1, 2, 3],
esp32.const.VARIANT_ESP32S3: [0, 1, 2, 3],
esp32.const.VARIANT_ESP32C3: [0, 1],
esp32.const.VARIANT_ESP32C6: [0, 1],
esp32.const.VARIANT_ESP32H2: [0, 1],
}
def _validate_rmt_channel(value):
variant = esp32.get_esp32_variant()
if variant not in RMT_CHANNELS:
raise cv.Invalid(f"ESP32 variant {variant} does not support RMT.")
if value not in RMT_CHANNELS[variant]:
raise cv.Invalid(
f"RMT channel {value} is not supported for ESP32 variant {variant}."
)
return value
CONFIG_SCHEMA = cv.All( CONFIG_SCHEMA = cv.All(
@ -87,7 +67,7 @@ CONFIG_SCHEMA = cv.All(
cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number, cv.Required(CONF_PIN): pins.internal_gpio_output_pin_number,
cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int, cv.Required(CONF_NUM_LEDS): cv.positive_not_null_int,
cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True), cv.Required(CONF_RGB_ORDER): cv.enum(RGB_ORDERS, upper=True),
cv.Required(CONF_RMT_CHANNEL): _validate_rmt_channel, cv.Required(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True),
cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds, cv.Optional(CONF_MAX_REFRESH_RATE): cv.positive_time_period_microseconds,
cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True), cv.Optional(CONF_CHIPSET): cv.one_of(*CHIPSETS, upper=True),
cv.Optional(CONF_IS_RGBW, default=False): cv.boolean, cv.Optional(CONF_IS_RGBW, default=False): cv.boolean,

View file

@ -4,6 +4,7 @@ import esphome.final_validate as fv
from esphome import pins from esphome import pins
from esphome.const import ( from esphome.const import (
CONF_FREQUENCY, CONF_FREQUENCY,
CONF_TIMEOUT,
CONF_ID, CONF_ID,
CONF_INPUT, CONF_INPUT,
CONF_OUTPUT, CONF_OUTPUT,
@ -59,6 +60,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All( cv.Optional(CONF_FREQUENCY, default="50kHz"): cv.All(
cv.frequency, cv.Range(min=0, min_included=False) cv.frequency, cv.Range(min=0, min_included=False)
), ),
cv.Optional(CONF_TIMEOUT): cv.positive_time_period,
cv.Optional(CONF_SCAN, default=True): cv.boolean, cv.Optional(CONF_SCAN, default=True): cv.boolean,
} }
).extend(cv.COMPONENT_SCHEMA), ).extend(cv.COMPONENT_SCHEMA),
@ -81,6 +83,8 @@ async def to_code(config):
cg.add(var.set_frequency(int(config[CONF_FREQUENCY]))) cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
cg.add(var.set_scan(config[CONF_SCAN])) cg.add(var.set_scan(config[CONF_SCAN]))
if CONF_TIMEOUT in config:
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
if CORE.using_arduino: if CORE.using_arduino:
cg.add_library("Wire", None) cg.add_library("Wire", None)
@ -119,23 +123,56 @@ async def register_i2c_device(var, config):
def final_validate_device_schema( def final_validate_device_schema(
name: str, *, min_frequency: cv.frequency = None, max_frequency: cv.frequency = None name: str,
*,
min_frequency: cv.frequency = None,
max_frequency: cv.frequency = None,
min_timeout: cv.time_period = None,
max_timeout: cv.time_period = None,
): ):
hub_schema = {} hub_schema = {}
if min_frequency is not None: if (min_frequency is not None) and (max_frequency is not None):
hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range(
min=cv.frequency(min_frequency),
min_included=True,
max=cv.frequency(max_frequency),
max_included=True,
msg=f"Component {name} requires a frequency between {min_frequency} and {max_frequency} for the I2C bus",
)
elif min_frequency is not None:
hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range(
min=cv.frequency(min_frequency), min=cv.frequency(min_frequency),
min_included=True, min_included=True,
msg=f"Component {name} requires a minimum frequency of {min_frequency} for the I2C bus", msg=f"Component {name} requires a minimum frequency of {min_frequency} for the I2C bus",
) )
elif max_frequency is not None:
if max_frequency is not None:
hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range( hub_schema[cv.Required(CONF_FREQUENCY)] = cv.Range(
max=cv.frequency(max_frequency), max=cv.frequency(max_frequency),
max_included=True, max_included=True,
msg=f"Component {name} cannot be used with a frequency of over {max_frequency} for the I2C bus", msg=f"Component {name} cannot be used with a frequency of over {max_frequency} for the I2C bus",
) )
if (min_timeout is not None) and (max_timeout is not None):
hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range(
min=cv.time_period(min_timeout),
min_included=True,
max=cv.time_period(max_timeout),
max_included=True,
msg=f"Component {name} requires a timeout between {min_timeout} and {max_timeout} for the I2C bus",
)
elif min_timeout is not None:
hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range(
min=cv.time_period(min_timeout),
min_included=True,
msg=f"Component {name} requires a minimum timeout of {min_timeout} for the I2C bus",
)
elif max_timeout is not None:
hub_schema[cv.Required(CONF_TIMEOUT)] = cv.Range(
max=cv.time_period(max_timeout),
max_included=True,
msg=f"Component {name} cannot be used with a timeout of over {max_timeout} for the I2C bus",
)
return cv.Schema( return cv.Schema(
{cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)}, {cv.Required(CONF_I2C_ID): fv.id_declaration_match_schema(hub_schema)},
extra=cv.ALLOW_EXTRA, extra=cv.ALLOW_EXTRA,

View file

@ -52,6 +52,18 @@ void ArduinoI2CBus::set_pins_and_clock_() {
#else #else
wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_)); wire_->begin(static_cast<int>(sda_pin_), static_cast<int>(scl_pin_));
#endif #endif
if (timeout_ > 0) { // if timeout specified in yaml
#if defined(USE_ESP32)
// https://github.com/espressif/arduino-esp32/blob/master/libraries/Wire/src/Wire.cpp
wire_->setTimeOut(timeout_ / 1000); // unit: ms
#elif defined(USE_ESP8266)
// https://github.com/esp8266/Arduino/blob/master/libraries/Wire/Wire.h
wire_->setClockStretchLimit(timeout_); // unit: us
#elif defined(USE_RP2040)
// https://github.com/earlephilhower/ArduinoCore-API/blob/e37df85425e0ac020bfad226d927f9b00d2e0fb7/api/Stream.h
wire_->setTimeout(timeout_ / 1000); // unit: ms
#endif
}
wire_->setClock(frequency_); wire_->setClock(frequency_);
} }
@ -60,6 +72,15 @@ void ArduinoI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_);
ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_);
ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_); ESP_LOGCONFIG(TAG, " Frequency: %u Hz", this->frequency_);
if (timeout_ > 0) {
#if defined(USE_ESP32)
ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
#elif defined(USE_ESP8266)
ESP_LOGCONFIG(TAG, " Timeout: %u us", this->timeout_);
#elif defined(USE_RP2040)
ESP_LOGCONFIG(TAG, " Timeout: %u ms", this->timeout_ / 1000);
#endif
}
switch (this->recovery_result_) { switch (this->recovery_result_) {
case RECOVERY_COMPLETED: case RECOVERY_COMPLETED:
ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered");

View file

@ -27,6 +27,7 @@ class ArduinoI2CBus : public I2CBus, public Component {
void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; } void set_sda_pin(uint8_t sda_pin) { sda_pin_ = sda_pin; }
void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; }
void set_frequency(uint32_t frequency) { frequency_ = frequency; } void set_frequency(uint32_t frequency) { frequency_ = frequency; }
void set_timeout(uint32_t timeout) { timeout_ = timeout; }
private: private:
void recover_(); void recover_();
@ -38,6 +39,7 @@ class ArduinoI2CBus : public I2CBus, public Component {
uint8_t sda_pin_; uint8_t sda_pin_;
uint8_t scl_pin_; uint8_t scl_pin_;
uint32_t frequency_; uint32_t frequency_;
uint32_t timeout_ = 0;
bool initialized_ = false; bool initialized_ = false;
}; };

View file

@ -1,12 +1,12 @@
#ifdef USE_ESP_IDF #ifdef USE_ESP_IDF
#include "i2c_bus_esp_idf.h" #include "i2c_bus_esp_idf.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#include "esphome/core/application.h"
#include <cstring>
#include <cinttypes> #include <cinttypes>
#include <cstring>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome { namespace esphome {
namespace i2c { namespace i2c {
@ -45,6 +45,20 @@ void IDFI2CBus::setup() {
this->mark_failed(); this->mark_failed();
return; return;
} }
if (timeout_ > 0) { // if timeout specified in yaml:
if (timeout_ > 13000) {
ESP_LOGW(TAG, "i2c timeout of %" PRIu32 "us greater than max of 13ms on esp-idf, setting to max", timeout_);
timeout_ = 13000;
}
err = i2c_set_timeout(port_, timeout_ * 80); // unit: APB 80MHz clock cycle
if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_set_timeout failed: %s", esp_err_to_name(err));
this->mark_failed();
return;
} else {
ESP_LOGV(TAG, "i2c_timeout set to %d ticks (%d us)", timeout_ * 80, timeout_);
}
}
err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_IRAM); err = i2c_driver_install(port_, I2C_MODE_MASTER, 0, 0, ESP_INTR_FLAG_IRAM);
if (err != ESP_OK) { if (err != ESP_OK) {
ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err)); ESP_LOGW(TAG, "i2c_driver_install failed: %s", esp_err_to_name(err));
@ -62,6 +76,9 @@ void IDFI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_); ESP_LOGCONFIG(TAG, " SDA Pin: GPIO%u", this->sda_pin_);
ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_); ESP_LOGCONFIG(TAG, " SCL Pin: GPIO%u", this->scl_pin_);
ESP_LOGCONFIG(TAG, " Frequency: %" PRIu32 " Hz", this->frequency_); ESP_LOGCONFIG(TAG, " Frequency: %" PRIu32 " Hz", this->frequency_);
if (timeout_ > 0) {
ESP_LOGCONFIG(TAG, " Timeout: %" PRIu32 "us", this->timeout_);
}
switch (this->recovery_result_) { switch (this->recovery_result_) {
case RECOVERY_COMPLETED: case RECOVERY_COMPLETED:
ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered"); ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered");
@ -127,6 +144,8 @@ ErrorCode IDFI2CBus::readv(uint8_t address, ReadBuffer *buffers, size_t cnt) {
return ERROR_UNKNOWN; return ERROR_UNKNOWN;
} }
err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS); err = i2c_master_cmd_begin(port_, cmd, 20 / portTICK_PERIOD_MS);
// i2c_master_cmd_begin() will block for a whole second if no ack:
// https://github.com/espressif/esp-idf/issues/4999
i2c_cmd_link_delete(cmd); i2c_cmd_link_delete(cmd);
if (err == ESP_FAIL) { if (err == ESP_FAIL) {
// transfer not acked // transfer not acked

View file

@ -29,6 +29,7 @@ class IDFI2CBus : public I2CBus, public Component {
void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; } void set_scl_pin(uint8_t scl_pin) { scl_pin_ = scl_pin; }
void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; } void set_scl_pullup_enabled(bool scl_pullup_enabled) { scl_pullup_enabled_ = scl_pullup_enabled; }
void set_frequency(uint32_t frequency) { frequency_ = frequency; } void set_frequency(uint32_t frequency) { frequency_ = frequency; }
void set_timeout(uint32_t timeout) { timeout_ = timeout; }
private: private:
void recover_(); void recover_();
@ -41,6 +42,7 @@ class IDFI2CBus : public I2CBus, public Component {
uint8_t scl_pin_; uint8_t scl_pin_;
bool scl_pullup_enabled_; bool scl_pullup_enabled_;
uint32_t frequency_; uint32_t frequency_;
uint32_t timeout_ = 0;
bool initialized_ = false; bool initialized_ = false;
}; };

View file

@ -12,12 +12,12 @@ static const char *const TAG = "audio";
void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) { void I2SAudioMediaPlayer::control(const media_player::MediaPlayerCall &call) {
if (call.get_media_url().has_value()) { if (call.get_media_url().has_value()) {
this->current_url_ = call.get_media_url(); this->current_url_ = call.get_media_url();
if (this->i2s_state_ != I2S_STATE_STOPPED && this->audio_ != nullptr) {
if (this->state == media_player::MEDIA_PLAYER_STATE_PLAYING && this->audio_ != nullptr) {
if (this->audio_->isRunning()) { if (this->audio_->isRunning()) {
this->audio_->stopSong(); this->audio_->stopSong();
} }
this->audio_->connecttohost(this->current_url_.value().c_str()); this->audio_->connecttohost(this->current_url_.value().c_str());
this->state = media_player::MEDIA_PLAYER_STATE_PLAYING;
} else { } else {
this->start(); this->start();
} }

View file

@ -14,6 +14,11 @@ uint8_t temprature_sens_read();
#ifdef USE_RP2040 #ifdef USE_RP2040
#include "Arduino.h" #include "Arduino.h"
#endif // USE_RP2040 #endif // USE_RP2040
#ifdef USE_BK72XX
extern "C" {
uint32_t temp_single_get_current_temperature(uint32_t *temp_value);
}
#endif // USE_BK72XX
namespace esphome { namespace esphome {
namespace internal_temperature { namespace internal_temperature {
@ -46,6 +51,16 @@ void InternalTemperatureSensor::update() {
temperature = analogReadTemp(); temperature = analogReadTemp();
success = (temperature != 0.0f); success = (temperature != 0.0f);
#endif // USE_RP2040 #endif // USE_RP2040
#ifdef USE_BK72XX
uint32_t raw, result;
result = temp_single_get_current_temperature(&raw);
success = (result == 0);
#ifdef USE_LIBRETINY_VARIANT_BK7231T
temperature = raw * 0.04f;
#else
temperature = raw * 0.128f;
#endif // USE_LIBRETINY_VARIANT_BK7231T
#endif // USE_BK72XX
if (success && std::isfinite(temperature)) { if (success && std::isfinite(temperature)) {
this->publish_state(temperature); this->publish_state(temperature);
} else { } else {

View file

@ -14,6 +14,7 @@ from esphome.const import (
KEY_FRAMEWORK_VERSION, KEY_FRAMEWORK_VERSION,
PLATFORM_ESP32, PLATFORM_ESP32,
PLATFORM_RP2040, PLATFORM_RP2040,
PLATFORM_BK72XX,
) )
from esphome.core import CORE from esphome.core import CORE
@ -51,7 +52,7 @@ CONFIG_SCHEMA = cv.All(
state_class=STATE_CLASS_MEASUREMENT, state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
).extend(cv.polling_component_schema("60s")), ).extend(cv.polling_component_schema("60s")),
cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040]), cv.only_on([PLATFORM_ESP32, PLATFORM_RP2040, PLATFORM_BK72XX]),
validate_config, validate_config,
) )

View file

@ -170,7 +170,7 @@ def _notify_old_style(config):
ARDUINO_VERSIONS = { ARDUINO_VERSIONS = {
"dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"), "dev": (cv.Version(0, 0, 0), "https://github.com/libretiny-eu/libretiny.git"),
"latest": (cv.Version(0, 0, 0), None), "latest": (cv.Version(0, 0, 0), None),
"recommended": (cv.Version(1, 4, 1), None), "recommended": (cv.Version(1, 5, 1), None),
} }

View file

@ -23,7 +23,7 @@ class DataTrigger : public Trigger<const std::vector<int16_t> &> {
} }
}; };
template<typename... Ts> class IsCapturingActon : public Condition<Ts...>, public Parented<Microphone> { template<typename... Ts> class IsCapturingCondition : public Condition<Ts...>, public Parented<Microphone> {
public: public:
bool check(Ts... x) override { return this->parent_->is_running(); } bool check(Ts... x) override { return this->parent_->is_running(); }
}; };

View file

@ -113,7 +113,8 @@ MQTTSensorComponent = mqtt_ns.class_("MQTTSensorComponent", MQTTComponent)
MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent) MQTTSwitchComponent = mqtt_ns.class_("MQTTSwitchComponent", MQTTComponent)
MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent) MQTTTextSensor = mqtt_ns.class_("MQTTTextSensor", MQTTComponent)
MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent) MQTTNumberComponent = mqtt_ns.class_("MQTTNumberComponent", MQTTComponent)
MQTTDatetimeComponent = mqtt_ns.class_("MQTTDatetimeComponent", MQTTComponent) MQTTDateComponent = mqtt_ns.class_("MQTTDateComponent", MQTTComponent)
MQTTTimeComponent = mqtt_ns.class_("MQTTTimeComponent", MQTTComponent)
MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent) MQTTTextComponent = mqtt_ns.class_("MQTTTextComponent", MQTTComponent)
MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent) MQTTSelectComponent = mqtt_ns.class_("MQTTSelectComponent", MQTTComponent)
MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent) MQTTButtonComponent = mqtt_ns.class_("MQTTButtonComponent", MQTTComponent)

View file

@ -13,9 +13,9 @@ namespace mqtt {
class MQTTDateComponent : public mqtt::MQTTComponent { class MQTTDateComponent : public mqtt::MQTTComponent {
public: public:
/** Construct this MQTTDatetimeComponent instance with the provided friendly_name and datetime /** Construct this MQTTDateComponent instance with the provided friendly_name and date
* *
* @param datetime The datetime component. * @param date The date component.
*/ */
explicit MQTTDateComponent(datetime::DateEntity *date); explicit MQTTDateComponent(datetime::DateEntity *date);

View file

@ -0,0 +1,68 @@
#include "mqtt_time.h"
#include <utility>
#include "esphome/core/log.h"
#include "mqtt_const.h"
#ifdef USE_MQTT
#ifdef USE_DATETIME_TIME
namespace esphome {
namespace mqtt {
static const char *const TAG = "mqtt.datetime.time";
using namespace esphome::datetime;
MQTTTimeComponent::MQTTTimeComponent(TimeEntity *time) : time_(time) {}
void MQTTTimeComponent::setup() {
this->subscribe_json(this->get_command_topic_(), [this](const std::string &topic, JsonObject root) {
auto call = this->time_->make_call();
if (root.containsKey("hour")) {
call.set_hour(root["hour"]);
}
if (root.containsKey("minute")) {
call.set_minute(root["minute"]);
}
if (root.containsKey("second")) {
call.set_second(root["second"]);
}
call.perform();
});
this->time_->add_on_state_callback(
[this]() { this->publish_state(this->time_->hour, this->time_->minute, this->time_->second); });
}
void MQTTTimeComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MQTT Time '%s':", this->time_->get_name().c_str());
LOG_MQTT_COMPONENT(true, true)
}
std::string MQTTTimeComponent::component_type() const { return "time"; }
const EntityBase *MQTTTimeComponent::get_entity() const { return this->time_; }
void MQTTTimeComponent::send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) {
// Nothing extra to add here
}
bool MQTTTimeComponent::send_initial_state() {
if (this->time_->has_state()) {
return this->publish_state(this->time_->hour, this->time_->minute, this->time_->second);
} else {
return true;
}
}
bool MQTTTimeComponent::publish_state(uint8_t hour, uint8_t minute, uint8_t second) {
return this->publish_json(this->get_state_topic_(), [hour, minute, second](JsonObject root) {
root["hour"] = hour;
root["minute"] = minute;
root["second"] = second;
});
}
} // namespace mqtt
} // namespace esphome
#endif // USE_DATETIME_TIME
#endif // USE_MQTT

View file

@ -0,0 +1,45 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_MQTT
#ifdef USE_DATETIME_TIME
#include "esphome/components/datetime/time_entity.h"
#include "mqtt_component.h"
namespace esphome {
namespace mqtt {
class MQTTTimeComponent : public mqtt::MQTTComponent {
public:
/** Construct this MQTTTimeComponent instance with the provided friendly_name and time
*
* @param time The time entity.
*/
explicit MQTTTimeComponent(datetime::TimeEntity *time);
// ========== INTERNAL METHODS ==========
// (In most use cases you won't need these)
/// Override setup.
void setup() override;
void dump_config() override;
void send_discovery(JsonObject root, mqtt::SendDiscoveryConfig &config) override;
bool send_initial_state() override;
bool publish_state(uint8_t hour, uint8_t minute, uint8_t second);
protected:
std::string component_type() const override;
const EntityBase *get_entity() const override;
datetime::TimeEntity *time_;
};
} // namespace mqtt
} // namespace esphome
#endif // USE_DATETIME_DATE
#endif // USE_MQTT

View file

@ -4,6 +4,7 @@
#include <cstdio> #include <cstdio>
#include <array> #include <array>
#include "esphome/core/macros.h" #include "esphome/core/macros.h"
#include "esphome/core/helpers.h"
#if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0) #if defined(USE_ESP_IDF) || defined(USE_LIBRETINY) || USE_ARDUINO_VERSION_CODE > VERSION_CODE(3, 0, 0)
#include <lwip/ip_addr.h> #include <lwip/ip_addr.h>
@ -116,7 +117,7 @@ struct IPAddress {
bool is_set() { return !ip_addr_isany(&ip_addr_); } bool is_set() { return !ip_addr_isany(&ip_addr_); }
bool is_ip4() { return IP_IS_V4(&ip_addr_); } bool is_ip4() { return IP_IS_V4(&ip_addr_); }
bool is_ip6() { return IP_IS_V6(&ip_addr_); } bool is_ip6() { return IP_IS_V6(&ip_addr_); }
std::string str() const { return ipaddr_ntoa(&ip_addr_); } std::string str() const { return str_lower_case(ipaddr_ntoa(&ip_addr_)); }
bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); } bool operator==(const IPAddress &other) const { return ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); } bool operator!=(const IPAddress &other) const { return !ip_addr_cmp(&ip_addr_, &other.ip_addr_); }
IPAddress &operator+=(uint8_t increase) { IPAddress &operator+=(uint8_t increase) {

View file

@ -1,16 +1,17 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h" #include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h" #include "esphome/components/uart/uart.h"
#include "esphome/core/component.h"
namespace esphome { namespace esphome {
namespace pmsx003 { namespace pmsx003 {
// known command bytes // known command bytes
#define PMS_CMD_AUTO_MANUAL 0xE1 // data=0: perform measurement manually, data=1: perform measurement automatically static const uint8_t PMS_CMD_AUTO_MANUAL =
#define PMS_CMD_TRIG_MANUAL 0xE2 // trigger a manual measurement 0xE1; // data=0: perform measurement manually, data=1: perform measurement automatically
#define PMS_CMD_ON_STANDBY 0xE4 // data=0: go to standby mode, data=1: go to normal mode static const uint8_t PMS_CMD_TRIG_MANUAL = 0xE2; // trigger a manual measurement
static const uint8_t PMS_CMD_ON_STANDBY = 0xE4; // data=0: go to standby mode, data=1: go to normal mode
static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on

View file

@ -72,19 +72,13 @@ void QMC5883LComponent::dump_config() {
LOG_SENSOR(" ", "Y Axis", this->y_sensor_); LOG_SENSOR(" ", "Y Axis", this->y_sensor_);
LOG_SENSOR(" ", "Z Axis", this->z_sensor_); LOG_SENSOR(" ", "Z Axis", this->z_sensor_);
LOG_SENSOR(" ", "Heading", this->heading_sensor_); LOG_SENSOR(" ", "Heading", this->heading_sensor_);
LOG_SENSOR(" ", "Temperature", this->temperature_sensor_);
} }
float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; } float QMC5883LComponent::get_setup_priority() const { return setup_priority::DATA; }
void QMC5883LComponent::update() { void QMC5883LComponent::update() {
uint8_t status = false; uint8_t status = false;
this->read_byte(QMC5883L_REGISTER_STATUS, &status); if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG)
this->read_byte(QMC5883L_REGISTER_STATUS, &status);
uint16_t raw_x, raw_y, raw_z;
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x) ||
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y) ||
!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) {
this->status_set_warning();
return;
}
float mg_per_bit; float mg_per_bit;
switch (this->range_) { switch (this->range_) {
@ -99,12 +93,49 @@ void QMC5883LComponent::update() {
} }
// in µT // in µT
const float x = int16_t(raw_x) * mg_per_bit * 0.1f; float x = NAN, y = NAN, z = NAN;
const float y = int16_t(raw_y) * mg_per_bit * 0.1f; if (this->x_sensor_ != nullptr || this->heading_sensor_ != nullptr) {
const float z = int16_t(raw_z) * mg_per_bit * 0.1f; uint16_t raw_x;
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_X_LSB, &raw_x)) {
this->status_set_warning();
return;
}
x = int16_t(raw_x) * mg_per_bit * 0.1f;
}
if (this->y_sensor_ != nullptr || this->heading_sensor_ != nullptr) {
uint16_t raw_y;
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_Y_LSB, &raw_y)) {
this->status_set_warning();
return;
}
y = int16_t(raw_y) * mg_per_bit * 0.1f;
}
if (this->z_sensor_ != nullptr) {
uint16_t raw_z;
if (!this->read_byte_16_(QMC5883L_REGISTER_DATA_Z_LSB, &raw_z)) {
this->status_set_warning();
return;
}
z = int16_t(raw_z) * mg_per_bit * 0.1f;
}
float heading = atan2f(0.0f - x, y) * 180.0f / M_PI; float heading = NAN;
ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° status=%u", x, y, z, heading, status); if (this->heading_sensor_ != nullptr) {
heading = atan2f(0.0f - x, y) * 180.0f / M_PI;
}
float temp = NAN;
if (this->temperature_sensor_ != nullptr) {
uint16_t raw_temp;
if (!this->read_byte_16_(QMC5883L_REGISTER_TEMPERATURE_LSB, &raw_temp)) {
this->status_set_warning();
return;
}
temp = int16_t(raw_temp) * 0.01f;
}
ESP_LOGD(TAG, "Got x=%0.02fµT y=%0.02fµT z=%0.02fµT heading=%0.01f° temperature=%0.01f°C status=%u", x, y, z, heading,
temp, status);
if (this->x_sensor_ != nullptr) if (this->x_sensor_ != nullptr)
this->x_sensor_->publish_state(x); this->x_sensor_->publish_state(x);
@ -114,6 +145,8 @@ void QMC5883LComponent::update() {
this->z_sensor_->publish_state(z); this->z_sensor_->publish_state(z);
if (this->heading_sensor_ != nullptr) if (this->heading_sensor_ != nullptr)
this->heading_sensor_->publish_state(heading); this->heading_sensor_->publish_state(heading);
if (this->temperature_sensor_ != nullptr)
this->temperature_sensor_->publish_state(temp);
} }
bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) { bool QMC5883LComponent::read_byte_16_(uint8_t a_register, uint16_t *data) {

View file

@ -40,6 +40,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice {
void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; } void set_y_sensor(sensor::Sensor *y_sensor) { y_sensor_ = y_sensor; }
void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; } void set_z_sensor(sensor::Sensor *z_sensor) { z_sensor_ = z_sensor; }
void set_heading_sensor(sensor::Sensor *heading_sensor) { heading_sensor_ = heading_sensor; } void set_heading_sensor(sensor::Sensor *heading_sensor) { heading_sensor_ = heading_sensor; }
void set_temperature_sensor(sensor::Sensor *temperature_sensor) { temperature_sensor_ = temperature_sensor; }
protected: protected:
QMC5883LDatarate datarate_{QMC5883L_DATARATE_10_HZ}; QMC5883LDatarate datarate_{QMC5883L_DATARATE_10_HZ};
@ -49,6 +50,7 @@ class QMC5883LComponent : public PollingComponent, public i2c::I2CDevice {
sensor::Sensor *y_sensor_{nullptr}; sensor::Sensor *y_sensor_{nullptr};
sensor::Sensor *z_sensor_{nullptr}; sensor::Sensor *z_sensor_{nullptr};
sensor::Sensor *heading_sensor_{nullptr}; sensor::Sensor *heading_sensor_{nullptr};
sensor::Sensor *temperature_sensor_{nullptr};
enum ErrorCode { enum ErrorCode {
NONE = 0, NONE = 0,
COMMUNICATION_FAILED, COMMUNICATION_FAILED,

View file

@ -6,12 +6,15 @@ from esphome.const import (
CONF_FIELD_STRENGTH_X, CONF_FIELD_STRENGTH_X,
CONF_FIELD_STRENGTH_Y, CONF_FIELD_STRENGTH_Y,
CONF_FIELD_STRENGTH_Z, CONF_FIELD_STRENGTH_Z,
CONF_TEMPERATURE,
CONF_ID, CONF_ID,
CONF_OVERSAMPLING, CONF_OVERSAMPLING,
CONF_RANGE, CONF_RANGE,
DEVICE_CLASS_TEMPERATURE,
ICON_MAGNET, ICON_MAGNET,
STATE_CLASS_MEASUREMENT, STATE_CLASS_MEASUREMENT,
UNIT_MICROTESLA, UNIT_MICROTESLA,
UNIT_CELSIUS,
UNIT_DEGREES, UNIT_DEGREES,
ICON_SCREEN_ROTATION, ICON_SCREEN_ROTATION,
CONF_UPDATE_INTERVAL, CONF_UPDATE_INTERVAL,
@ -79,6 +82,12 @@ heading_schema = sensor.sensor_schema(
icon=ICON_SCREEN_ROTATION, icon=ICON_SCREEN_ROTATION,
accuracy_decimals=1, accuracy_decimals=1,
) )
temperature_schema = sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
)
CONFIG_SCHEMA = ( CONFIG_SCHEMA = (
cv.Schema( cv.Schema(
@ -95,6 +104,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_Y): field_strength_schema,
cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema, cv.Optional(CONF_FIELD_STRENGTH_Z): field_strength_schema,
cv.Optional(CONF_HEADING): heading_schema, cv.Optional(CONF_HEADING): heading_schema,
cv.Optional(CONF_TEMPERATURE): temperature_schema,
} }
) )
.extend(cv.polling_component_schema("60s")) .extend(cv.polling_component_schema("60s"))
@ -131,3 +141,6 @@ async def to_code(config):
if CONF_HEADING in config: if CONF_HEADING in config:
sens = await sensor.new_sensor(config[CONF_HEADING]) sens = await sensor.new_sensor(config[CONF_HEADING])
cg.add(var.set_heading_sensor(sens)) cg.add(var.set_heading_sensor(sens))
if CONF_TEMPERATURE in config:
sens = await sensor.new_sensor(config[CONF_TEMPERATURE])
cg.add(var.set_temperature_sensor(sens))

View file

@ -1718,3 +1718,105 @@ async def haier_action(var, config, args):
vec_ = cg.std_vector.template(cg.uint8) vec_ = cg.std_vector.template(cg.uint8)
template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_) template_ = await cg.templatable(config[CONF_CODE], args, vec_, vec_)
cg.add(var.set_code(template_)) cg.add(var.set_code(template_))
# ABBWelcome
(
ABBWelcomeData,
ABBWelcomeBinarySensor,
ABBWelcomeTrigger,
ABBWelcomeAction,
ABBWelcomeDumper,
) = declare_protocol("ABBWelcome")
CONF_SOURCE_ADDRESS = "source_address"
CONF_DESTINATION_ADDRESS = "destination_address"
CONF_THREE_BYTE_ADDRESS = "three_byte_address"
CONF_MESSAGE_TYPE = "message_type"
CONF_MESSAGE_ID = "message_id"
CONF_RETRANSMISSION = "retransmission"
ABB_WELCOME_SCHEMA = cv.Schema(
{
cv.Required(CONF_SOURCE_ADDRESS): cv.hex_uint32_t,
cv.Required(CONF_DESTINATION_ADDRESS): cv.hex_uint32_t,
cv.Optional(CONF_RETRANSMISSION, default=False): cv.boolean,
cv.Optional(CONF_THREE_BYTE_ADDRESS, default=False): cv.boolean,
cv.Required(CONF_MESSAGE_TYPE): cv.Any(cv.hex_uint8_t, cv.uint8_t),
cv.Optional(CONF_MESSAGE_ID): cv.Any(cv.hex_uint8_t, cv.uint8_t),
cv.Optional(CONF_DATA): cv.All(
[cv.Any(cv.hex_uint8_t, cv.uint8_t)],
cv.Length(min=0, max=7),
),
}
)
@register_binary_sensor("abbwelcome", ABBWelcomeBinarySensor, ABB_WELCOME_SCHEMA)
def abbwelcome_binary_sensor(var, config):
cg.add(var.set_three_byte_address(config[CONF_THREE_BYTE_ADDRESS]))
cg.add(var.set_source_address(config[CONF_SOURCE_ADDRESS]))
cg.add(var.set_destination_address(config[CONF_DESTINATION_ADDRESS]))
cg.add(var.set_retransmission(config[CONF_RETRANSMISSION]))
cg.add(var.set_message_type(config[CONF_MESSAGE_TYPE]))
cg.add(var.set_auto_message_id(CONF_MESSAGE_ID not in config))
if CONF_MESSAGE_ID in config:
cg.add(var.set_message_id(config[CONF_MESSAGE_ID]))
if CONF_DATA in config:
cg.add(var.set_data(config[CONF_DATA]))
cg.add(var.finalize())
@register_trigger("abbwelcome", ABBWelcomeTrigger, ABBWelcomeData)
def abbwelcome_trigger(var, config):
pass
@register_dumper("abbwelcome", ABBWelcomeDumper)
def abbwelcome_dumper(var, config):
pass
@register_action("abbwelcome", ABBWelcomeAction, ABB_WELCOME_SCHEMA)
async def abbwelcome_action(var, config, args):
cg.add(
var.set_three_byte_address(
await cg.templatable(config[CONF_THREE_BYTE_ADDRESS], args, cg.bool_)
)
)
cg.add(
var.set_source_address(
await cg.templatable(config[CONF_SOURCE_ADDRESS], args, cg.uint16)
)
)
cg.add(
var.set_destination_address(
await cg.templatable(config[CONF_DESTINATION_ADDRESS], args, cg.uint16)
)
)
cg.add(
var.set_retransmission(
await cg.templatable(config[CONF_RETRANSMISSION], args, cg.bool_)
)
)
cg.add(
var.set_message_type(
await cg.templatable(config[CONF_MESSAGE_TYPE], args, cg.uint8)
)
)
cg.add(var.set_auto_message_id(CONF_MESSAGE_ID not in config))
if CONF_MESSAGE_ID in config:
cg.add(
var.set_message_id(
await cg.templatable(config[CONF_MESSAGE_ID], args, cg.uint8)
)
)
if CONF_DATA in config:
data_ = config[CONF_DATA]
if cg.is_template(data_):
template_ = await cg.templatable(
data_, args, cg.std_vector.template(cg.uint8)
)
cg.add(var.set_data_template(template_))
else:
cg.add(var.set_data_static(data_))

View file

@ -0,0 +1,123 @@
#include "abbwelcome_protocol.h"
#include "esphome/core/log.h"
namespace esphome {
namespace remote_base {
static const char *const TAG = "remote.abbwelcome";
static const uint32_t BIT_ONE_SPACE_US = 102;
static const uint32_t BIT_ZERO_MARK_US = 32; // 18-44
static const uint32_t BIT_ZERO_SPACE_US = BIT_ONE_SPACE_US - BIT_ZERO_MARK_US;
static const uint16_t BYTE_SPACE_US = 210;
uint8_t ABBWelcomeData::calc_cs_() const {
uint8_t checksum = 0;
for (uint8_t i = 0; i < this->size() - 1; i++) {
uint16_t temp = checksum ^ (this->data_[i]);
temp = temp ^ (uint16_t) (((uint32_t) temp << 0x11) >> 0x10) ^ (uint16_t) (((uint32_t) temp << 0x12) >> 0x10) ^
(uint16_t) (((uint32_t) temp << 0x13) >> 0x10) ^ (uint16_t) (((uint32_t) temp << 0x14) >> 0x10) ^
(uint16_t) (((uint32_t) temp << 0x15) >> 0x10) ^ (uint16_t) (((uint32_t) temp << 0x16) >> 0x10) ^
(uint16_t) (((uint32_t) temp << 0x17) >> 0x10);
checksum = (temp & 0xfe) ^ ((temp >> 8) & 1);
}
return ~checksum;
}
void ABBWelcomeProtocol::encode_byte_(RemoteTransmitData *dst, uint8_t data) const {
// space = bus high, mark = activate bus pulldown
dst->mark(BIT_ZERO_MARK_US);
uint32_t next_space = BIT_ZERO_SPACE_US;
for (uint8_t mask = 1 << 7; mask; mask >>= 1) {
if (data & mask) {
next_space += BIT_ONE_SPACE_US;
} else {
dst->space(next_space);
dst->mark(BIT_ZERO_MARK_US);
next_space = BIT_ZERO_SPACE_US;
}
}
next_space += BYTE_SPACE_US;
dst->space(next_space);
}
void ABBWelcomeProtocol::encode(RemoteTransmitData *dst, const ABBWelcomeData &src) {
dst->set_carrier_frequency(0);
uint32_t reserve_count = 0;
for (size_t i = 0; i < src.size(); i++) {
reserve_count += 2 * (9 - (src[i] & 1) - ((src[i] >> 1) & 1) - ((src[i] >> 2) & 1) - ((src[i] >> 3) & 1) -
((src[i] >> 4) & 1) - ((src[i] >> 5) & 1) - ((src[i] >> 6) & 1) - ((src[i] >> 7) & 1));
}
dst->reserve(reserve_count);
for (size_t i = 0; i < src.size(); i++)
this->encode_byte_(dst, src[i]);
ESP_LOGD(TAG, "Transmitting: %s", src.to_string().c_str());
}
bool ABBWelcomeProtocol::decode_byte_(RemoteReceiveData &src, bool &done, uint8_t &data) {
if (!src.expect_mark(BIT_ZERO_MARK_US))
return false;
uint32_t next_space = BIT_ZERO_SPACE_US;
for (uint8_t mask = 1 << 7; mask; mask >>= 1) {
// if (!src.peek_space_at_least(next_space, 0))
// return false;
if (src.expect_space(next_space)) {
if (!src.expect_mark(BIT_ZERO_MARK_US))
return false;
next_space = BIT_ZERO_SPACE_US;
} else {
data |= mask;
next_space += BIT_ONE_SPACE_US;
}
}
next_space += BYTE_SPACE_US;
// if (!src.peek_space_at_least(next_space, 0))
// return false;
done = !(src.expect_space(next_space));
return true;
}
optional<ABBWelcomeData> ABBWelcomeProtocol::decode(RemoteReceiveData src) {
if (src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US) &&
src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + BIT_ONE_SPACE_US) &&
src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + BIT_ONE_SPACE_US) &&
src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + BIT_ONE_SPACE_US) &&
src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + BIT_ONE_SPACE_US + BYTE_SPACE_US) &&
src.expect_item(BIT_ZERO_MARK_US, BIT_ZERO_SPACE_US + 8 * BIT_ONE_SPACE_US + BYTE_SPACE_US)) {
ESP_LOGVV(TAG, "Received Header: 0x55FF");
ABBWelcomeData out;
out[0] = 0x55;
out[1] = 0xff;
bool done = false;
uint8_t length = 10;
uint8_t received_bytes = 2;
for (; (received_bytes < length) && !done; received_bytes++) {
uint8_t data = 0;
if (!this->decode_byte_(src, done, data)) {
ESP_LOGW(TAG, "Received incomplete packet: %s", out.to_string(received_bytes).c_str());
return {};
}
if (received_bytes == 2) {
length += std::min(static_cast<uint8_t>(data & DATA_LENGTH_MASK), MAX_DATA_LENGTH);
if (data & 0x40) {
length += 2;
}
}
ESP_LOGVV(TAG, "Received Byte: 0x%02X", data);
out[received_bytes] = data;
}
if (out.is_valid()) {
ESP_LOGI(TAG, "Received: %s", out.to_string().c_str());
return out;
}
ESP_LOGW(TAG, "Received malformed packet: %s", out.to_string(received_bytes).c_str());
}
return {};
}
void ABBWelcomeProtocol::dump(const ABBWelcomeData &data) {
ESP_LOGD(TAG, "Received ABBWelcome: %s", data.to_string().c_str());
}
} // namespace remote_base
} // namespace esphome

View file

@ -0,0 +1,251 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "remote_base.h"
#include <array>
#include <utility>
#include <vector>
namespace esphome {
namespace remote_base {
static const uint8_t MAX_DATA_LENGTH = 15;
static const uint8_t DATA_LENGTH_MASK = 0x3f;
/*
Message Format:
2 bytes: Sync (0x55FF)
1 bit: Retransmission flag (High means retransmission)
1 bit: Address length flag (Low means 2 bytes, High means 3 bytes)
2 bits: Unknown
4 bits: Data length (in bytes)
1 bit: Reply flag (High means this is a reply to a previous message with the same message type)
7 bits: Message type
2-3 bytes: Destination address
2-3 bytes: Source address
1 byte: Message ID (randomized, does not change for retransmissions)
0-? bytes: Data
1 byte: Checksum
*/
class ABBWelcomeData {
public:
// Make default
ABBWelcomeData() {
std::fill(std::begin(this->data_), std::end(this->data_), 0);
this->data_[0] = 0x55;
this->data_[1] = 0xff;
}
// Make from initializer_list
ABBWelcomeData(std::initializer_list<uint8_t> data) {
std::fill(std::begin(this->data_), std::end(this->data_), 0);
std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin());
}
// Make from vector
ABBWelcomeData(const std::vector<uint8_t> &data) {
std::fill(std::begin(this->data_), std::end(this->data_), 0);
std::copy_n(data.begin(), std::min(data.size(), this->data_.size()), this->data_.begin());
}
// Default copy constructor
ABBWelcomeData(const ABBWelcomeData &) = default;
bool auto_message_id{false};
uint8_t *data() { return this->data_.data(); }
const uint8_t *data() const { return this->data_.data(); }
uint8_t size() const {
return std::min(static_cast<uint8_t>(6 + (2 * this->get_address_length()) + (this->data_[2] & DATA_LENGTH_MASK)),
static_cast<uint8_t>(this->data_.size()));
}
bool is_valid() const {
return this->data_[0] == 0x55 && this->data_[1] == 0xff &&
((this->data_[2] & DATA_LENGTH_MASK) <= MAX_DATA_LENGTH) &&
(this->data_[this->size() - 1] == this->calc_cs_());
}
void set_retransmission(bool retransmission) {
if (retransmission) {
this->data_[2] |= 0x80;
} else {
this->data_[2] &= 0x7f;
}
}
bool get_retransmission() const { return this->data_[2] & 0x80; }
// set_three_byte_address must be called before set_source_address, set_destination_address, set_message_id and
// set_data!
void set_three_byte_address(bool three_byte_address) {
if (three_byte_address) {
this->data_[2] |= 0x40;
} else {
this->data_[2] &= 0xbf;
}
}
uint8_t get_three_byte_address() const { return (this->data_[2] & 0x40); }
uint8_t get_address_length() const { return this->get_three_byte_address() ? 3 : 2; }
void set_message_type(uint8_t message_type) { this->data_[3] = message_type; }
uint8_t get_message_type() const { return this->data_[3]; }
void set_destination_address(uint32_t address) {
if (this->get_address_length() == 2) {
this->data_[4] = (address >> 8) & 0xff;
this->data_[5] = address & 0xff;
} else {
this->data_[4] = (address >> 16) & 0xff;
this->data_[5] = (address >> 8) & 0xff;
this->data_[6] = address & 0xff;
}
}
uint32_t get_destination_address() const {
if (this->get_address_length() == 2) {
return (this->data_[4] << 8) + this->data_[5];
}
return (this->data_[4] << 16) + (this->data_[5] << 8) + this->data_[6];
}
void set_source_address(uint32_t address) {
if (this->get_address_length() == 2) {
this->data_[6] = (address >> 8) & 0xff;
this->data_[7] = address & 0xff;
} else {
this->data_[7] = (address >> 16) & 0xff;
this->data_[8] = (address >> 8) & 0xff;
this->data_[9] = address & 0xff;
}
}
uint32_t get_source_address() const {
if (this->get_address_length() == 2) {
return (this->data_[6] << 8) + this->data_[7];
}
return (this->data_[7] << 16) + (this->data_[8] << 8) + this->data_[9];
}
void set_message_id(uint8_t message_id) { this->data_[4 + 2 * this->get_address_length()] = message_id; }
uint8_t get_message_id() const { return this->data_[4 + 2 * this->get_address_length()]; }
void set_data(std::vector<uint8_t> data) {
uint8_t size = std::min(MAX_DATA_LENGTH, static_cast<uint8_t>(data.size()));
this->data_[2] &= (0xff ^ DATA_LENGTH_MASK);
this->data_[2] |= (size & DATA_LENGTH_MASK);
if (size)
std::copy_n(data.begin(), size, this->data_.begin() + 5 + 2 * this->get_address_length());
}
std::vector<uint8_t> get_data() const {
std::vector<uint8_t> data(this->data_.begin() + 5 + 2 * this->get_address_length(),
this->data_.begin() + 5 + 2 * this->get_address_length() + this->get_data_size());
return data;
}
uint8_t get_data_size() const {
return std::min(MAX_DATA_LENGTH, static_cast<uint8_t>(this->data_[2] & DATA_LENGTH_MASK));
}
void finalize() {
if (this->auto_message_id && !this->get_retransmission() && !(this->data_[3] & 0x80)) {
this->set_message_id(static_cast<uint8_t>(random_uint32()));
}
this->data_[0] = 0x55;
this->data_[1] = 0xff;
this->data_[this->size() - 1] = this->calc_cs_();
}
std::string to_string(uint8_t max_print_bytes = 255) const {
std::string info;
if (this->is_valid()) {
info = str_sprintf(this->get_three_byte_address() ? "[%06X %s %06X] Type: %02X" : "[%04X %s %04X] Type: %02X",
this->get_source_address(), this->get_retransmission() ? "»" : ">",
this->get_destination_address(), this->get_message_type());
if (this->get_data_size())
info += str_sprintf(", Data: %s", format_hex_pretty(this->get_data()).c_str());
} else {
info = "[Invalid]";
}
uint8_t print_bytes = std::min(this->size(), max_print_bytes);
if (print_bytes)
info = str_sprintf("%s %s", format_hex_pretty(this->data_.data(), print_bytes).c_str(), info.c_str());
return info;
}
bool operator==(const ABBWelcomeData &rhs) const {
if (std::equal(this->data_.begin(), this->data_.begin() + this->size(), rhs.data_.begin()))
return true;
return (this->auto_message_id || rhs.auto_message_id) && this->is_valid() && rhs.is_valid() &&
(this->get_message_type() == rhs.get_message_type()) &&
(this->get_source_address() == rhs.get_source_address()) &&
(this->get_destination_address() == rhs.get_destination_address()) && (this->get_data() == rhs.get_data());
}
uint8_t &operator[](size_t idx) { return this->data_[idx]; }
const uint8_t &operator[](size_t idx) const { return this->data_[idx]; }
protected:
std::array<uint8_t, 12 + MAX_DATA_LENGTH> data_;
// Calculate checksum
uint8_t calc_cs_() const;
};
class ABBWelcomeProtocol : public RemoteProtocol<ABBWelcomeData> {
public:
void encode(RemoteTransmitData *dst, const ABBWelcomeData &src) override;
optional<ABBWelcomeData> decode(RemoteReceiveData src) override;
void dump(const ABBWelcomeData &data) override;
protected:
void encode_byte_(RemoteTransmitData *dst, uint8_t data) const;
bool decode_byte_(RemoteReceiveData &src, bool &done, uint8_t &data);
};
class ABBWelcomeBinarySensor : public RemoteReceiverBinarySensorBase {
public:
bool matches(RemoteReceiveData src) override {
auto data = ABBWelcomeProtocol().decode(src);
return data.has_value() && data.value() == this->data_;
}
void set_source_address(const uint32_t source_address) { this->data_.set_source_address(source_address); }
void set_destination_address(const uint32_t destination_address) {
this->data_.set_destination_address(destination_address);
}
void set_retransmission(const bool retransmission) { this->data_.set_retransmission(retransmission); }
void set_three_byte_address(const bool three_byte_address) { this->data_.set_three_byte_address(three_byte_address); }
void set_message_type(const uint8_t message_type) { this->data_.set_message_type(message_type); }
void set_message_id(const uint8_t message_id) { this->data_.set_message_id(message_id); }
void set_auto_message_id(const bool auto_message_id) { this->data_.auto_message_id = auto_message_id; }
void set_data(const std::vector<uint8_t> &data) { this->data_.set_data(data); }
void finalize() { this->data_.finalize(); }
protected:
ABBWelcomeData data_;
};
using ABBWelcomeTrigger = RemoteReceiverTrigger<ABBWelcomeProtocol>;
using ABBWelcomeDumper = RemoteReceiverDumper<ABBWelcomeProtocol>;
template<typename... Ts> class ABBWelcomeAction : public RemoteTransmitterActionBase<Ts...> {
TEMPLATABLE_VALUE(uint32_t, source_address)
TEMPLATABLE_VALUE(uint32_t, destination_address)
TEMPLATABLE_VALUE(bool, retransmission)
TEMPLATABLE_VALUE(bool, three_byte_address)
TEMPLATABLE_VALUE(uint8_t, message_type)
TEMPLATABLE_VALUE(uint8_t, message_id)
TEMPLATABLE_VALUE(bool, auto_message_id)
void set_data_static(std::vector<uint8_t> data) { data_static_ = std::move(data); }
void set_data_template(std::function<std::vector<uint8_t>(Ts...)> func) {
this->data_func_ = func;
has_data_func_ = true;
}
void encode(RemoteTransmitData *dst, Ts... x) override {
ABBWelcomeData data;
data.set_three_byte_address(this->three_byte_address_.value(x...));
data.set_source_address(this->source_address_.value(x...));
data.set_destination_address(this->destination_address_.value(x...));
data.set_retransmission(this->retransmission_.value(x...));
data.set_message_type(this->message_type_.value(x...));
data.set_message_id(this->message_id_.value(x...));
data.auto_message_id = this->auto_message_id_.value(x...);
if (has_data_func_) {
data.set_data(this->data_func_(x...));
} else {
data.set_data(this->data_static_);
}
data.finalize();
ABBWelcomeProtocol().encode(dst, data);
}
protected:
std::function<std::vector<uint8_t>(Ts...)> data_func_{};
std::vector<uint8_t> data_static_{};
bool has_data_func_{false};
};
} // namespace remote_base
} // namespace esphome

View file

@ -15,6 +15,9 @@ RemoteRMTChannel::RemoteRMTChannel(uint8_t mem_block_num) : mem_block_num_(mem_b
next_rmt_channel = rmt_channel_t(int(next_rmt_channel) + mem_block_num); next_rmt_channel = rmt_channel_t(int(next_rmt_channel) + mem_block_num);
} }
RemoteRMTChannel::RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num)
: channel_(channel), mem_block_num_(mem_block_num) {}
void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) { void RemoteRMTChannel::config_rmt(rmt_config_t &rmt) {
if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_MAX) { if (rmt_channel_t(int(this->channel_) + this->mem_block_num_) > RMT_CHANNEL_MAX) {
this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_); this->mem_block_num_ = int(RMT_CHANNEL_MAX) - int(this->channel_);

View file

@ -3,10 +3,10 @@
#pragma once #pragma once
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/automation.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <driver/rmt.h> #include <driver/rmt.h>
@ -86,6 +86,7 @@ class RemoteComponentBase {
class RemoteRMTChannel { class RemoteRMTChannel {
public: public:
explicit RemoteRMTChannel(uint8_t mem_block_num = 1); explicit RemoteRMTChannel(uint8_t mem_block_num = 1);
explicit RemoteRMTChannel(rmt_channel_t channel, uint8_t mem_block_num = 1);
void config_rmt(rmt_config_t &rmt); void config_rmt(rmt_config_t &rmt);
void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; } void set_clock_divider(uint8_t clock_divider) { this->clock_divider_ = clock_divider; }

View file

@ -1,7 +1,7 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.components import remote_base from esphome.components import remote_base, esp32_rmt
from esphome.const import ( from esphome.const import (
CONF_BUFFER_SIZE, CONF_BUFFER_SIZE,
CONF_DUMP, CONF_DUMP,
@ -11,6 +11,7 @@ from esphome.const import (
CONF_PIN, CONF_PIN,
CONF_TOLERANCE, CONF_TOLERANCE,
CONF_MEMORY_BLOCKS, CONF_MEMORY_BLOCKS,
CONF_RMT_CHANNEL,
) )
from esphome.core import CORE, TimePeriod from esphome.core import CORE, TimePeriod
@ -45,6 +46,7 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
CONF_IDLE, default="10ms" CONF_IDLE, default="10ms"
): cv.positive_time_period_microseconds, ): cv.positive_time_period_microseconds,
cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8), cv.Optional(CONF_MEMORY_BLOCKS, default=3): cv.Range(min=1, max=8),
cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=False),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
) )
@ -53,7 +55,12 @@ CONFIG_SCHEMA = remote_base.validate_triggers(
async def to_code(config): async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
if CORE.is_esp32: if CORE.is_esp32:
var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS]) if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None:
var = cg.new_Pvariable(
config[CONF_ID], pin, rmt_channel, config[CONF_MEMORY_BLOCKS]
)
else:
var = cg.new_Pvariable(config[CONF_ID], pin, config[CONF_MEMORY_BLOCKS])
else: else:
var = cg.new_Pvariable(config[CONF_ID], pin) var = cg.new_Pvariable(config[CONF_ID], pin)

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/components/remote_base/remote_base.h" #include "esphome/components/remote_base/remote_base.h"
#include "esphome/core/component.h"
#include <cinttypes> #include <cinttypes>
@ -38,6 +38,9 @@ class RemoteReceiverComponent : public remote_base::RemoteReceiverBase,
#ifdef USE_ESP32 #ifdef USE_ESP32
RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1) RemoteReceiverComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1)
: RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {} : RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {}
RemoteReceiverComponent(InternalGPIOPin *pin, rmt_channel_t channel, uint8_t mem_block_num = 1)
: RemoteReceiverBase(pin), remote_base::RemoteRMTChannel(channel, mem_block_num) {}
#else #else
RemoteReceiverComponent(InternalGPIOPin *pin) : RemoteReceiverBase(pin) {} RemoteReceiverComponent(InternalGPIOPin *pin) : RemoteReceiverBase(pin) {}
#endif #endif

View file

@ -1,8 +1,8 @@
import esphome.codegen as cg import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome import pins from esphome import pins
from esphome.components import remote_base from esphome.components import remote_base, esp32_rmt
from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_ID, CONF_PIN, CONF_RMT_CHANNEL
AUTO_LOAD = ["remote_base"] AUTO_LOAD = ["remote_base"]
remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter") remote_transmitter_ns = cg.esphome_ns.namespace("remote_transmitter")
@ -18,13 +18,17 @@ CONFIG_SCHEMA = cv.Schema(
cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All( cv.Required(CONF_CARRIER_DUTY_PERCENT): cv.All(
cv.percentage_int, cv.Range(min=1, max=100) cv.percentage_int, cv.Range(min=1, max=100)
), ),
cv.Optional(CONF_RMT_CHANNEL): esp32_rmt.validate_rmt_channel(tx=True),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
async def to_code(config): async def to_code(config):
pin = await cg.gpio_pin_expression(config[CONF_PIN]) pin = await cg.gpio_pin_expression(config[CONF_PIN])
var = cg.new_Pvariable(config[CONF_ID], pin) if (rmt_channel := config.get(CONF_RMT_CHANNEL, None)) is not None:
var = cg.new_Pvariable(config[CONF_ID], pin, rmt_channel)
else:
var = cg.new_Pvariable(config[CONF_ID], pin)
await cg.register_component(var, config) await cg.register_component(var, config)
cg.add(var.set_carrier_duty_percent(config[CONF_CARRIER_DUTY_PERCENT])) cg.add(var.set_carrier_duty_percent(config[CONF_CARRIER_DUTY_PERCENT]))

View file

@ -1,7 +1,7 @@
#pragma once #pragma once
#include "esphome/core/component.h"
#include "esphome/components/remote_base/remote_base.h" #include "esphome/components/remote_base/remote_base.h"
#include "esphome/core/component.h"
#include <vector> #include <vector>
@ -16,8 +16,15 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
#endif #endif
{ {
public: public:
explicit RemoteTransmitterComponent(InternalGPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {} #ifdef USE_ESP32
RemoteTransmitterComponent(InternalGPIOPin *pin, uint8_t mem_block_num = 1)
: remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(mem_block_num) {}
RemoteTransmitterComponent(InternalGPIOPin *pin, rmt_channel_t channel, uint8_t mem_block_num = 1)
: remote_base::RemoteTransmitterBase(pin), remote_base::RemoteRMTChannel(channel, mem_block_num) {}
#else
explicit RemoteTransmitterComponent(InternalGPIOPin *pin) : remote_base::RemoteTransmitterBase(pin) {}
#endif
void setup() override; void setup() override;
void dump_config() override; void dump_config() override;

View file

@ -74,12 +74,12 @@ def _format_framework_arduino_version(ver: cv.Version) -> str:
# The default/recommended arduino framework version # The default/recommended arduino framework version
# - https://github.com/earlephilhower/arduino-pico/releases # - https://github.com/earlephilhower/arduino-pico/releases
# - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico # - https://api.registry.platformio.org/v3/packages/earlephilhower/tool/framework-arduinopico
RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 6, 0) RECOMMENDED_ARDUINO_FRAMEWORK_VERSION = cv.Version(3, 7, 2)
# The platformio/raspberrypi version to use for arduino frameworks # The platformio/raspberrypi version to use for arduino frameworks
# - https://github.com/platformio/platform-raspberrypi/releases # - https://github.com/platformio/platform-raspberrypi/releases
# - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi # - https://api.registry.platformio.org/v3/packages/platformio/platform/raspberrypi
ARDUINO_PLATFORM_VERSION = cv.Version(1, 10, 0) ARDUINO_PLATFORM_VERSION = cv.Version(1, 12, 0)
def _arduino_check_versions(value): def _arduino_check_versions(value):

View file

@ -36,6 +36,10 @@ RTL87XX_BOARDS = {
"name": "T103_V1.0", "name": "T103_V1.0",
"family": FAMILY_RTL8710B, "family": FAMILY_RTL8710B,
}, },
"t112-v1.1": {
"name": "T112_V1.1",
"family": FAMILY_RTL8710B,
},
"wr1": { "wr1": {
"name": "WR1 Wi-Fi Module", "name": "WR1 Wi-Fi Module",
"family": FAMILY_RTL8710B, "family": FAMILY_RTL8710B,
@ -125,7 +129,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -136,9 +139,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 22,
"SCL1": 18, "SCL1": 18,
"SDA0": 30,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -180,11 +181,9 @@ RTL87XX_BOARD_PINS = {
"SERIAL2_RTS": 20, "SERIAL2_RTS": 20,
"SERIAL2_RX": 15, "SERIAL2_RX": 15,
"SERIAL2_TX": 16, "SERIAL2_TX": 16,
"CS0": 15,
"CTS1": 4, "CTS1": 4,
"CTS2": 19, "CTS2": 19,
"MISO0": 20, "MISO0": 20,
"MOSI0": 19,
"PA00": 0, "PA00": 0,
"PA0": 0, "PA0": 0,
"PA01": 1, "PA01": 1,
@ -203,23 +202,15 @@ RTL87XX_BOARD_PINS = {
"PA18": 18, "PA18": 18,
"PA19": 19, "PA19": 19,
"PA20": 20, "PA20": 20,
"PWM0": 0,
"PWM1": 1, "PWM1": 1,
"PWM2": 14,
"PWM3": 3,
"PWM4": 16,
"PWM5": 17, "PWM5": 17,
"PWM6": 18, "PWM6": 18,
"PWM7": 13,
"RTS2": 20, "RTS2": 20,
"RX0": 13, "RX0": 13,
"RX1": 0,
"RX2": 15, "RX2": 15,
"SCK0": 3,
"SCL0": 19, "SCL0": 19,
"SDA0": 3, "SDA0": 3,
"TX0": 14, "TX0": 14,
"TX1": 1,
"TX2": 16, "TX2": 16,
"D0": 17, "D0": 17,
"D1": 18, "D1": 18,
@ -294,7 +285,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -305,9 +295,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 29,
"SCL1": 18, "SCL1": 18,
"SDA0": 30,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -390,7 +378,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -401,9 +388,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 29,
"SCL1": 18, "SCL1": 18,
"SDA0": 30,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -485,7 +470,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -496,9 +480,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 29,
"SCL1": 18, "SCL1": 18,
"SDA0": 30,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -560,7 +542,6 @@ RTL87XX_BOARD_PINS = {
"CTS0": 10, "CTS0": 10,
"CTS1": 4, "CTS1": 4,
"CTS2": 19, "CTS2": 19,
"MISO0": 20,
"MOSI0": 19, "MOSI0": 19,
"PA00": 0, "PA00": 0,
"PA0": 0, "PA0": 0,
@ -591,23 +572,13 @@ RTL87XX_BOARD_PINS = {
"PA20": 20, "PA20": 20,
"PA23": 23, "PA23": 23,
"PWM0": 20, "PWM0": 20,
"PWM1": 12,
"PWM2": 14,
"PWM3": 15,
"PWM4": 16,
"PWM5": 17, "PWM5": 17,
"PWM6": 18, "PWM6": 18,
"PWM7": 23, "PWM7": 23,
"RTS0": 9, "RTS0": 9,
"RTS2": 20, "RTS2": 20,
"RX0": 13,
"RX1": 2,
"RX2": 15, "RX2": 15,
"SCK0": 16, "SCK0": 16,
"SCL0": 19,
"SDA0": 20,
"TX0": 14,
"TX1": 3,
"TX2": 16, "TX2": 16,
"D0": 0, "D0": 0,
"D1": 1, "D1": 1,
@ -652,7 +623,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 14,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -720,7 +690,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -731,9 +700,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 29,
"SCL1": 18, "SCL1": 18,
"SDA0": 30,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -751,6 +718,75 @@ RTL87XX_BOARD_PINS = {
"A0": 19, "A0": 19,
"A1": 41, "A1": 41,
}, },
"t112-v1.1": {
"SPI0_CS": 19,
"SPI0_MISO": 22,
"SPI0_MOSI": 23,
"SPI0_SCK": 18,
"SPI1_CS": 19,
"SPI1_MISO": 22,
"SPI1_MOSI": 23,
"SPI1_SCK": 18,
"WIRE0_SCL_0": 29,
"WIRE0_SCL_1": 22,
"WIRE0_SDA_0": 19,
"WIRE0_SDA_1": 30,
"WIRE1_SCL": 18,
"WIRE1_SDA": 23,
"SERIAL0_CTS": 19,
"SERIAL0_RTS": 22,
"SERIAL0_RX": 18,
"SERIAL0_TX": 23,
"SERIAL2_RX": 29,
"SERIAL2_TX": 30,
"ADC1": 19,
"CS0": 19,
"CS1": 19,
"CTS0": 19,
"MISO0": 22,
"MISO1": 22,
"MOSI0": 23,
"MOSI1": 23,
"PA00": 0,
"PA0": 0,
"PA05": 5,
"PA5": 5,
"PA12": 12,
"PA14": 14,
"PA15": 15,
"PA18": 18,
"PA19": 19,
"PA22": 22,
"PA23": 23,
"PA29": 29,
"PA30": 30,
"PWM1": 15,
"PWM2": 0,
"PWM3": 12,
"PWM4": 30,
"PWM5": 22,
"RTS0": 22,
"RX0": 18,
"RX2": 29,
"SCK0": 18,
"SCK1": 18,
"SCL1": 18,
"SDA1": 23,
"TX0": 23,
"TX2": 30,
"D0": 29,
"D1": 19,
"D2": 15,
"D3": 14,
"D4": 0,
"D5": 5,
"D6": 18,
"D7": 12,
"D8": 23,
"D9": 22,
"D10": 30,
"A0": 19,
},
"wr1": { "wr1": {
"SPI0_CS": 19, "SPI0_CS": 19,
"SPI0_MISO": 22, "SPI0_MISO": 22,
@ -793,7 +829,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 14,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM4": 29, "PWM4": 29,
@ -803,9 +838,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 22,
"SCL1": 18, "SCL1": 18,
"SDA0": 19,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -863,7 +896,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 14,
"PWM1": 15, "PWM1": 15,
"PWM3": 12, "PWM3": 12,
"PWM4": 29, "PWM4": 29,
@ -873,9 +905,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 22,
"SCL1": 18, "SCL1": 18,
"SDA0": 19,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -915,7 +945,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 14,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -969,7 +998,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 14,
"PWM1": 15, "PWM1": 15,
"PWM3": 12, "PWM3": 12,
"PWM4": 29, "PWM4": 29,
@ -979,7 +1007,6 @@ RTL87XX_BOARD_PINS = {
"SCK1": 18, "SCK1": 18,
"SCL0": 29, "SCL0": 29,
"SCL1": 18, "SCL1": 18,
"SDA0": 30,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -1083,7 +1110,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -1094,9 +1120,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 29,
"SCL1": 18, "SCL1": 18,
"SDA0": 30,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -1157,7 +1181,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -1168,9 +1191,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 22,
"SCL1": 18, "SCL1": 18,
"SDA0": 19,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -1231,7 +1252,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -1242,9 +1262,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 29,
"SCL1": 18, "SCL1": 18,
"SDA0": 30,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -1305,7 +1323,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,
@ -1316,9 +1333,7 @@ RTL87XX_BOARD_PINS = {
"RX2": 29, "RX2": 29,
"SCK0": 18, "SCK0": 18,
"SCK1": 18, "SCK1": 18,
"SCL0": 22,
"SCL1": 18, "SCL1": 18,
"SDA0": 19,
"SDA1": 23, "SDA1": 23,
"TX0": 23, "TX0": 23,
"TX2": 30, "TX2": 30,
@ -1359,7 +1374,6 @@ RTL87XX_BOARD_PINS = {
"PA23": 23, "PA23": 23,
"PA29": 29, "PA29": 29,
"PA30": 30, "PA30": 30,
"PWM0": 23,
"PWM1": 15, "PWM1": 15,
"PWM2": 0, "PWM2": 0,
"PWM3": 12, "PWM3": 12,

View file

@ -8,6 +8,13 @@ from esphome.const import (
CONF_OPTIMISTIC, CONF_OPTIMISTIC,
CONF_RESTORE_VALUE, CONF_RESTORE_VALUE,
CONF_SET_ACTION, CONF_SET_ACTION,
CONF_DAY,
CONF_HOUR,
CONF_MINUTE,
CONF_MONTH,
CONF_SECOND,
CONF_TYPE,
CONF_YEAR,
) )
from esphome.core import coroutine_with_priority from esphome.core import coroutine_with_priority
@ -20,6 +27,10 @@ TemplateDate = template_ns.class_(
"TemplateDate", datetime.DateEntity, cg.PollingComponent "TemplateDate", datetime.DateEntity, cg.PollingComponent
) )
TemplateTime = template_ns.class_(
"TemplateTime", datetime.TimeEntity, cg.PollingComponent
)
def validate(config): def validate(config):
config = config.copy() config = config.copy()
@ -60,6 +71,13 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_INITIAL_VALUE): cv.date_time(allowed_time=False), cv.Optional(CONF_INITIAL_VALUE): cv.date_time(allowed_time=False),
} }
), ),
"TIME": datetime.time_schema(TemplateTime)
.extend(_BASE_SCHEMA)
.extend(
{
cv.Optional(CONF_INITIAL_VALUE): cv.date_time(allowed_date=False),
}
),
}, },
upper=True, upper=True,
), ),
@ -82,7 +100,22 @@ async def to_code(config):
cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE])) cg.add(var.set_restore_value(config[CONF_RESTORE_VALUE]))
if initial_value := config.get(CONF_INITIAL_VALUE): if initial_value := config.get(CONF_INITIAL_VALUE):
cg.add(var.set_initial_value(initial_value)) if config[CONF_TYPE] == "DATE":
date_struct = cg.StructInitializer(
cg.ESPTime,
("day_of_month", initial_value[CONF_DAY]),
("month", initial_value[CONF_MONTH]),
("year", initial_value[CONF_YEAR]),
)
cg.add(var.set_initial_value(date_struct))
elif config[CONF_TYPE] == "TIME":
time_struct = cg.StructInitializer(
cg.ESPTime,
("second", initial_value[CONF_SECOND]),
("minute", initial_value[CONF_MINUTE]),
("hour", initial_value[CONF_HOUR]),
)
cg.add(var.set_initial_value(time_struct))
if CONF_SET_ACTION in config: if CONF_SET_ACTION in config:
await automation.build_automation( await automation.build_automation(

View file

@ -0,0 +1,111 @@
#include "template_time.h"
#ifdef USE_DATETIME_TIME
#include "esphome/core/log.h"
namespace esphome {
namespace template_ {
static const char *const TAG = "template.time";
void TemplateTime::setup() {
if (this->f_.has_value())
return;
ESPTime state{};
if (!this->restore_value_) {
state = this->initial_value_;
} else {
datetime::TimeEntityRestoreState temp;
this->pref_ =
global_preferences->make_preference<datetime::TimeEntityRestoreState>(194434060U ^ this->get_object_id_hash());
if (this->pref_.load(&temp)) {
temp.apply(this);
return;
} else {
// set to inital value if loading from pref failed
state = this->initial_value_;
}
}
this->hour_ = state.hour;
this->minute_ = state.minute;
this->second_ = state.second;
this->publish_state();
}
void TemplateTime::update() {
if (!this->f_.has_value())
return;
auto val = (*this->f_)();
if (!val.has_value())
return;
this->hour_ = val->hour;
this->minute_ = val->minute;
this->second_ = val->second;
this->publish_state();
}
void TemplateTime::control(const datetime::TimeCall &call) {
bool has_hour = call.get_hour().has_value();
bool has_minute = call.get_minute().has_value();
bool has_second = call.get_second().has_value();
ESPTime value = {};
if (has_hour)
value.hour = *call.get_hour();
if (has_minute)
value.minute = *call.get_minute();
if (has_second)
value.second = *call.get_second();
this->set_trigger_->trigger(value);
if (this->optimistic_) {
if (has_hour)
this->hour_ = *call.get_hour();
if (has_minute)
this->minute_ = *call.get_minute();
if (has_second)
this->second_ = *call.get_second();
this->publish_state();
}
if (this->restore_value_) {
datetime::TimeEntityRestoreState temp = {};
if (has_hour) {
temp.hour = *call.get_hour();
} else {
temp.hour = this->hour_;
}
if (has_minute) {
temp.minute = *call.get_minute();
} else {
temp.minute = this->minute_;
}
if (has_second) {
temp.second = *call.get_second();
} else {
temp.second = this->second_;
}
this->pref_.save(&temp);
}
}
void TemplateTime::dump_config() {
LOG_DATETIME_TIME("", "Template Time", this);
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));
LOG_UPDATE_INTERVAL(this);
}
} // namespace template_
} // namespace esphome
#endif // USE_DATETIME_TIME

View file

@ -0,0 +1,46 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_DATETIME_TIME
#include "esphome/components/datetime/time_entity.h"
#include "esphome/core/automation.h"
#include "esphome/core/component.h"
#include "esphome/core/preferences.h"
#include "esphome/core/time.h"
namespace esphome {
namespace template_ {
class TemplateTime : public datetime::TimeEntity, public PollingComponent {
public:
void set_template(std::function<optional<ESPTime>()> &&f) { this->f_ = f; }
void setup() override;
void update() override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
protected:
void control(const datetime::TimeCall &call) override;
bool optimistic_{false};
ESPTime initial_value_{};
bool restore_value_{false};
Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
optional<std::function<optional<ESPTime>()>> f_;
ESPPreferenceObject pref_;
};
} // namespace template_
} // namespace esphome
#endif // USE_DATETIME_TIME

View file

@ -14,7 +14,6 @@ from esphome.const import (
CONF_LAT_PIN = "lat_pin" CONF_LAT_PIN = "lat_pin"
CONF_OE_PIN = "oe_pin" CONF_OE_PIN = "oe_pin"
AUTO_LOAD = ["output"]
CODEOWNERS = ["@rnauber"] CODEOWNERS = ["@rnauber"]
tlc5947_ns = cg.esphome_ns.namespace("tlc5947") tlc5947_ns = cg.esphome_ns.namespace("tlc5947")

View file

@ -2,18 +2,19 @@ import esphome.codegen as cg
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.components import output from esphome.components import output
from esphome.const import CONF_CHANNEL, CONF_ID from esphome.const import CONF_CHANNEL, CONF_ID
from . import TLC5947 from .. import TLC5947, tlc5947_ns
DEPENDENCIES = ["tlc5947"] DEPENDENCIES = ["tlc5947"]
CODEOWNERS = ["@rnauber"]
Channel = TLC5947.class_("Channel", output.FloatOutput) TLC5947Channel = tlc5947_ns.class_(
"TLC5947Channel", output.FloatOutput, cg.Parented.template(TLC5947)
)
CONF_TLC5947_ID = "tlc5947_id" CONF_TLC5947_ID = "tlc5947_id"
CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend( CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
{ {
cv.GenerateID(CONF_TLC5947_ID): cv.use_id(TLC5947), cv.GenerateID(CONF_TLC5947_ID): cv.use_id(TLC5947),
cv.Required(CONF_ID): cv.declare_id(Channel), cv.Required(CONF_ID): cv.declare_id(TLC5947Channel),
cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535), cv.Required(CONF_CHANNEL): cv.int_range(min=0, max=65535),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@ -22,7 +23,5 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
async def to_code(config): async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await output.register_output(var, config) await output.register_output(var, config)
await cg.register_parented(var, config[CONF_TLC5947_ID])
parent = await cg.get_variable(config[CONF_TLC5947_ID])
cg.add(var.set_parent(parent))
cg.add(var.set_channel(config[CONF_CHANNEL])) cg.add(var.set_channel(config[CONF_CHANNEL]))

View file

@ -0,0 +1,12 @@
#include "tlc5947_output.h"
namespace esphome {
namespace tlc5947 {
void TLC5947Channel::write_state(float state) {
auto amount = static_cast<uint16_t>(state * 0xfff);
this->parent_->set_channel_value(this->channel_, amount);
}
} // namespace tlc5947
} // namespace esphome

View file

@ -0,0 +1,22 @@
#pragma once
#include "esphome/core/helpers.h"
#include "esphome/components/output/float_output.h"
#include "../tlc5947.h"
namespace esphome {
namespace tlc5947 {
class TLC5947Channel : public output::FloatOutput, public Parented<TLC5947> {
public:
void set_channel(uint8_t channel) { this->channel_ = channel; }
protected:
void write_state(float state) override;
uint8_t channel_;
};
} // namespace tlc5947
} // namespace esphome

View file

@ -60,5 +60,14 @@ void TLC5947::loop() {
this->update_ = false; this->update_ = false;
} }
void TLC5947::set_channel_value(uint16_t channel, uint16_t value) {
if (channel >= this->num_chips_ * N_CHANNELS_PER_CHIP)
return;
if (this->pwm_amounts_[channel] != value) {
this->update_ = true;
}
this->pwm_amounts_[channel] = value;
}
} // namespace tlc5947 } // namespace tlc5947
} // namespace esphome } // namespace esphome

View file

@ -2,18 +2,16 @@
// TLC5947 24-Channel, 12-Bit PWM LED Driver // TLC5947 24-Channel, 12-Bit PWM LED Driver
// https://www.ti.com/lit/ds/symlink/tlc5947.pdf // https://www.ti.com/lit/ds/symlink/tlc5947.pdf
#include <vector>
#include "esphome/components/output/float_output.h"
#include "esphome/core/component.h" #include "esphome/core/component.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/components/output/float_output.h"
#include <vector>
namespace esphome { namespace esphome {
namespace tlc5947 { namespace tlc5947 {
class TLC5947 : public Component { class TLC5947 : public Component {
public: public:
class Channel;
const uint8_t N_CHANNELS_PER_CHIP = 24; const uint8_t N_CHANNELS_PER_CHIP = 24;
void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; } void set_data_pin(GPIOPin *data_pin) { data_pin_ = data_pin; }
@ -31,31 +29,9 @@ class TLC5947 : public Component {
/// Send new values if they were updated. /// Send new values if they were updated.
void loop() override; void loop() override;
class Channel : public output::FloatOutput { void set_channel_value(uint16_t channel, uint16_t value);
public:
void set_parent(TLC5947 *parent) { parent_ = parent; }
void set_channel(uint8_t channel) { channel_ = channel; }
protected:
void write_state(float state) override {
auto amount = static_cast<uint16_t>(state * 0xfff);
this->parent_->set_channel_value_(this->channel_, amount);
}
TLC5947 *parent_;
uint8_t channel_;
};
protected: protected:
void set_channel_value_(uint16_t channel, uint16_t value) {
if (channel >= this->num_chips_ * N_CHANNELS_PER_CHIP)
return;
if (this->pwm_amounts_[channel] != value) {
this->update_ = true;
}
this->pwm_amounts_[channel] = value;
}
GPIOPin *data_pin_; GPIOPin *data_pin_;
GPIOPin *clock_pin_; GPIOPin *clock_pin_;
GPIOPin *lat_pin_; GPIOPin *lat_pin_;

View file

@ -30,37 +30,37 @@ CONFIG_SCHEMA = cv.All(
def determine_config_register(polling_period): def determine_config_register(polling_period):
if polling_period >= 16.0: if polling_period >= 16000:
# 64 averaged conversions, max conversion time # 64 averaged conversions, max conversion time
# 0000 00 111 11 00000 # 0000 00 111 11 00000
# 0000 0011 1110 0000 # 0000 0011 1110 0000
return 0x03E0 return 0x03E0
if polling_period >= 8.0: if polling_period >= 8000:
# 64 averaged conversions, high conversion time # 64 averaged conversions, high conversion time
# 0000 00 110 11 00000 # 0000 00 110 11 00000
# 0000 0011 0110 0000 # 0000 0011 0110 0000
return 0x0360 return 0x0360
if polling_period >= 4.0: if polling_period >= 4000:
# 64 averaged conversions, mid conversion time # 64 averaged conversions, mid conversion time
# 0000 00 101 11 00000 # 0000 00 101 11 00000
# 0000 0010 1110 0000 # 0000 0010 1110 0000
return 0x02E0 return 0x02E0
if polling_period >= 1.0: if polling_period >= 1000:
# 64 averaged conversions, min conversion time # 64 averaged conversions, min conversion time
# 0000 00 000 11 00000 # 0000 00 000 11 00000
# 0000 0000 0110 0000 # 0000 0000 0110 0000
return 0x0060 return 0x0060
if polling_period >= 0.5: if polling_period >= 500:
# 32 averaged conversions, min conversion time # 32 averaged conversions, min conversion time
# 0000 00 000 10 00000 # 0000 00 000 10 00000
# 0000 0000 0100 0000 # 0000 0000 0100 0000
return 0x0040 return 0x0040
if polling_period >= 0.25: if polling_period >= 250:
# 8 averaged conversions, mid conversion time # 8 averaged conversions, mid conversion time
# 0000 00 010 01 00000 # 0000 00 010 01 00000
# 0000 0001 0010 0000 # 0000 0001 0010 0000
return 0x0120 return 0x0120
if polling_period >= 0.125: if polling_period >= 125:
# 8 averaged conversions, min conversion time # 8 averaged conversions, min conversion time
# 0000 00 000 01 00000 # 0000 00 000 01 00000
# 0000 0000 0010 0000 # 0000 0000 0010 0000
@ -76,5 +76,5 @@ async def to_code(config):
await cg.register_component(var, config) await cg.register_component(var, config)
await i2c.register_i2c_device(var, config) await i2c.register_i2c_device(var, config)
update_period = config[CONF_UPDATE_INTERVAL].total_seconds update_period = config[CONF_UPDATE_INTERVAL].total_milliseconds
cg.add(var.set_config(determine_config_register(update_period))) cg.add(var.set_config(determine_config_register(update_period)))

View file

@ -46,6 +46,14 @@ LibreTinyUARTComponent = uart_ns.class_(
"LibreTinyUARTComponent", UARTComponent, cg.Component "LibreTinyUARTComponent", UARTComponent, cg.Component
) )
NATIVE_UART_CLASSES = (
str(IDFUARTComponent),
str(ESP32ArduinoUARTComponent),
str(ESP8266UartComponent),
str(RP2040UartComponent),
str(LibreTinyUARTComponent),
)
UARTDevice = uart_ns.class_("UARTDevice") UARTDevice = uart_ns.class_("UARTDevice")
UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action) UARTWriteAction = uart_ns.class_("UARTWriteAction", automation.Action)
UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action) UARTDebugger = uart_ns.class_("UARTDebugger", cg.Component, automation.Action)
@ -299,17 +307,18 @@ def final_validate_device_schema(
def validate_hub(hub_config): def validate_hub(hub_config):
hub_schema = {} hub_schema = {}
uart_id = hub_config[CONF_ID] uart_id = hub_config[CONF_ID]
uart_id_type_str = str(uart_id.type)
devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {}) devices = fv.full_config.get().data.setdefault(KEY_UART_DEVICES, {})
device = devices.setdefault(uart_id, {}) device = devices.setdefault(uart_id, {})
if require_tx: if require_tx and uart_id_type_str in NATIVE_UART_CLASSES:
hub_schema[ hub_schema[
cv.Required( cv.Required(
CONF_TX_PIN, CONF_TX_PIN,
msg=f"Component {name} requires this uart bus to declare a tx_pin", msg=f"Component {name} requires this uart bus to declare a tx_pin",
) )
] = validate_pin(CONF_TX_PIN, device) ] = validate_pin(CONF_TX_PIN, device)
if require_rx: if require_rx and uart_id_type_str in NATIVE_UART_CLASSES:
hub_schema[ hub_schema[
cv.Required( cv.Required(
CONF_RX_PIN, CONF_RX_PIN,

View file

@ -24,28 +24,24 @@ static const size_t SPEAKER_BUFFER_SIZE = 16 * RECEIVE_SIZE;
float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; } float VoiceAssistant::get_setup_priority() const { return setup_priority::AFTER_CONNECTION; }
void VoiceAssistant::setup() { bool VoiceAssistant::start_udp_socket_() {
ESP_LOGCONFIG(TAG, "Setting up Voice Assistant...");
global_voice_assistant = this;
this->socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); this->socket_ = socket::socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
if (socket_ == nullptr) { if (this->socket_ == nullptr) {
ESP_LOGW(TAG, "Could not create socket"); ESP_LOGE(TAG, "Could not create socket");
this->mark_failed(); this->mark_failed();
return; return false;
} }
int enable = 1; int enable = 1;
int err = socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); int err = this->socket_->setsockopt(SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int));
if (err != 0) { if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err); ESP_LOGW(TAG, "Socket unable to set reuseaddr: errno %d", err);
// we can still continue // we can still continue
} }
err = socket_->setblocking(false); err = this->socket_->setblocking(false);
if (err != 0) { if (err != 0) {
ESP_LOGW(TAG, "Socket unable to set nonblocking mode: errno %d", err); ESP_LOGE(TAG, "Socket unable to set nonblocking mode: errno %d", err);
this->mark_failed(); this->mark_failed();
return; return false;
} }
#ifdef USE_SPEAKER #ifdef USE_SPEAKER
@ -54,18 +50,30 @@ void VoiceAssistant::setup() {
socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), 6055); socklen_t sl = socket::set_sockaddr_any((struct sockaddr *) &server, sizeof(server), 6055);
if (sl == 0) { if (sl == 0) {
ESP_LOGW(TAG, "Socket unable to set sockaddr: errno %d", errno); ESP_LOGE(TAG, "Socket unable to set sockaddr: errno %d", errno);
this->mark_failed(); this->mark_failed();
return; return false;
} }
err = socket_->bind((struct sockaddr *) &server, sizeof(server)); err = this->socket_->bind((struct sockaddr *) &server, sizeof(server));
if (err != 0) { if (err != 0) {
ESP_LOGW(TAG, "Socket unable to bind: errno %d", errno); ESP_LOGE(TAG, "Socket unable to bind: errno %d", errno);
this->mark_failed(); this->mark_failed();
return; return false;
} }
}
#endif
this->udp_socket_running_ = true;
return true;
}
void VoiceAssistant::setup() {
ESP_LOGCONFIG(TAG, "Setting up Voice Assistant...");
global_voice_assistant = this;
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
ExternalRAMAllocator<uint8_t> speaker_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE); ExternalRAMAllocator<uint8_t> speaker_allocator(ExternalRAMAllocator<uint8_t>::ALLOW_FAILURE);
this->speaker_buffer_ = speaker_allocator.allocate(SPEAKER_BUFFER_SIZE); this->speaker_buffer_ = speaker_allocator.allocate(SPEAKER_BUFFER_SIZE);
if (this->speaker_buffer_ == nullptr) { if (this->speaker_buffer_ == nullptr) {
@ -238,8 +246,20 @@ void VoiceAssistant::loop() {
size_t available = this->ring_buffer_->available(); size_t available = this->ring_buffer_->available();
while (available >= SEND_BUFFER_SIZE) { while (available >= SEND_BUFFER_SIZE) {
size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0); size_t read_bytes = this->ring_buffer_->read((void *) this->send_buffer_, SEND_BUFFER_SIZE, 0);
this->socket_->sendto(this->send_buffer_, read_bytes, 0, (struct sockaddr *) &this->dest_addr_, if (this->audio_mode_ == AUDIO_MODE_API) {
sizeof(this->dest_addr_)); api::VoiceAssistantAudio msg;
msg.data.assign((char *) this->send_buffer_, read_bytes);
this->api_client_->send_voice_assistant_audio(msg);
} else {
if (!this->udp_socket_running_) {
if (!this->start_udp_socket_()) {
this->set_state_(State::STOP_MICROPHONE, State::IDLE);
break;
}
}
this->socket_->sendto(this->send_buffer_, read_bytes, 0, (struct sockaddr *) &this->dest_addr_,
sizeof(this->dest_addr_));
}
available = this->ring_buffer_->available(); available = this->ring_buffer_->available();
} }
@ -268,22 +288,25 @@ void VoiceAssistant::loop() {
#ifdef USE_SPEAKER #ifdef USE_SPEAKER
if (this->speaker_ != nullptr) { if (this->speaker_ != nullptr) {
ssize_t received_len = 0; ssize_t received_len = 0;
if (this->speaker_buffer_index_ + RECEIVE_SIZE < SPEAKER_BUFFER_SIZE) { if (this->audio_mode_ == AUDIO_MODE_UDP) {
received_len = this->socket_->read(this->speaker_buffer_ + this->speaker_buffer_index_, RECEIVE_SIZE); if (this->speaker_buffer_index_ + RECEIVE_SIZE < SPEAKER_BUFFER_SIZE) {
if (received_len > 0) { received_len = this->socket_->read(this->speaker_buffer_ + this->speaker_buffer_index_, RECEIVE_SIZE);
this->speaker_buffer_index_ += received_len; if (received_len > 0) {
this->speaker_buffer_size_ += received_len; this->speaker_buffer_index_ += received_len;
this->speaker_bytes_received_ += received_len; this->speaker_buffer_size_ += received_len;
this->speaker_bytes_received_ += received_len;
}
} else {
ESP_LOGD(TAG, "Receive buffer full");
} }
} else {
ESP_LOGD(TAG, "Receive buffer full");
} }
// Build a small buffer of audio before sending to the speaker // Build a small buffer of audio before sending to the speaker
if (this->speaker_bytes_received_ > RECEIVE_SIZE * 4) bool end_of_stream = this->stream_ended_ && (this->audio_mode_ == AUDIO_MODE_API || received_len < 0);
if (this->speaker_bytes_received_ > RECEIVE_SIZE * 4 || end_of_stream)
this->write_speaker_(); this->write_speaker_();
if (this->wait_for_stream_end_) { if (this->wait_for_stream_end_) {
this->cancel_timeout("playing"); this->cancel_timeout("playing");
if (this->stream_ended_ && received_len < 0) { if (end_of_stream) {
ESP_LOGD(TAG, "End of audio stream received"); ESP_LOGD(TAG, "End of audio stream received");
this->cancel_timeout("speaker-timeout"); this->cancel_timeout("speaker-timeout");
this->set_state_(State::RESPONSE_FINISHED, State::RESPONSE_FINISHED); this->set_state_(State::RESPONSE_FINISHED, State::RESPONSE_FINISHED);
@ -428,6 +451,22 @@ void VoiceAssistant::failed_to_start() {
this->set_state_(State::STOP_MICROPHONE, State::IDLE); this->set_state_(State::STOP_MICROPHONE, State::IDLE);
} }
void VoiceAssistant::start_streaming() {
if (this->state_ != State::STARTING_PIPELINE) {
this->signal_stop_();
return;
}
ESP_LOGD(TAG, "Client started, streaming microphone");
this->audio_mode_ = AUDIO_MODE_API;
if (this->mic_->is_running()) {
this->set_state_(State::STREAMING_MICROPHONE, State::STREAMING_MICROPHONE);
} else {
this->set_state_(State::START_MICROPHONE, State::STREAMING_MICROPHONE);
}
}
void VoiceAssistant::start_streaming(struct sockaddr_storage *addr, uint16_t port) { void VoiceAssistant::start_streaming(struct sockaddr_storage *addr, uint16_t port) {
if (this->state_ != State::STARTING_PIPELINE) { if (this->state_ != State::STARTING_PIPELINE) {
this->signal_stop_(); this->signal_stop_();
@ -435,6 +474,7 @@ void VoiceAssistant::start_streaming(struct sockaddr_storage *addr, uint16_t por
} }
ESP_LOGD(TAG, "Client started, streaming microphone"); ESP_LOGD(TAG, "Client started, streaming microphone");
this->audio_mode_ = AUDIO_MODE_UDP;
memcpy(&this->dest_addr_, addr, sizeof(this->dest_addr_)); memcpy(&this->dest_addr_, addr, sizeof(this->dest_addr_));
if (this->dest_addr_.ss_family == AF_INET) { if (this->dest_addr_.ss_family == AF_INET) {
@ -688,6 +728,17 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) {
} }
} }
void VoiceAssistant::on_audio(const api::VoiceAssistantAudio &msg) {
if (this->speaker_buffer_index_ + msg.data.length() < SPEAKER_BUFFER_SIZE) {
memcpy(this->speaker_buffer_ + this->speaker_buffer_index_, msg.data.data(), msg.data.length());
this->speaker_buffer_index_ += msg.data.length();
this->speaker_buffer_size_ += msg.data.length();
this->speaker_bytes_received_ += msg.data.length();
} else {
ESP_LOGE(TAG, "Cannot receive audio, buffer is full");
}
}
VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) VoiceAssistant *global_voice_assistant = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
} // namespace voice_assistant } // namespace voice_assistant

View file

@ -29,9 +29,14 @@ namespace voice_assistant {
// Version 1: Initial version // Version 1: Initial version
// Version 2: Adds raw speaker support // Version 2: Adds raw speaker support
// Version 3: Unused/skip static const uint32_t LEGACY_INITIAL_VERSION = 1;
static const uint32_t INITIAL_VERSION = 1; static const uint32_t LEGACY_SPEAKER_SUPPORT = 2;
static const uint32_t SPEAKER_SUPPORT = 2;
enum VoiceAssistantFeature : uint32_t {
FEATURE_VOICE_ASSISTANT = 1 << 0,
FEATURE_SPEAKER = 1 << 1,
FEATURE_API_AUDIO = 1 << 2,
};
enum class State { enum class State {
IDLE, IDLE,
@ -49,11 +54,17 @@ enum class State {
RESPONSE_FINISHED, RESPONSE_FINISHED,
}; };
enum AudioMode : uint8_t {
AUDIO_MODE_UDP,
AUDIO_MODE_API,
};
class VoiceAssistant : public Component { class VoiceAssistant : public Component {
public: public:
void setup() override; void setup() override;
void loop() override; void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
void start_streaming();
void start_streaming(struct sockaddr_storage *addr, uint16_t port); void start_streaming(struct sockaddr_storage *addr, uint16_t port);
void failed_to_start(); void failed_to_start();
@ -71,19 +82,32 @@ class VoiceAssistant : public Component {
} }
#endif #endif
uint32_t get_version() const { uint32_t get_legacy_version() const {
#ifdef USE_SPEAKER #ifdef USE_SPEAKER
if (this->speaker_ != nullptr) { if (this->speaker_ != nullptr) {
return SPEAKER_SUPPORT; return LEGACY_SPEAKER_SUPPORT;
} }
#endif #endif
return INITIAL_VERSION; return LEGACY_INITIAL_VERSION;
}
uint32_t get_feature_flags() const {
uint32_t flags = 0;
flags |= VoiceAssistantFeature::FEATURE_VOICE_ASSISTANT;
#ifdef USE_SPEAKER
if (this->speaker_ != nullptr) {
flags |= VoiceAssistantFeature::FEATURE_SPEAKER;
flags |= VoiceAssistantFeature::FEATURE_API_AUDIO;
}
#endif
return flags;
} }
void request_start(bool continuous, bool silence_detection); void request_start(bool continuous, bool silence_detection);
void request_stop(); void request_stop();
void on_event(const api::VoiceAssistantEventResponse &msg); void on_event(const api::VoiceAssistantEventResponse &msg);
void on_audio(const api::VoiceAssistantAudio &msg);
bool is_running() const { return this->state_ != State::IDLE; } bool is_running() const { return this->state_ != State::IDLE; }
void set_continuous(bool continuous) { this->continuous_ = continuous; } void set_continuous(bool continuous) { this->continuous_ = continuous; }
@ -201,6 +225,10 @@ class VoiceAssistant : public Component {
State state_{State::IDLE}; State state_{State::IDLE};
State desired_state_{State::IDLE}; State desired_state_{State::IDLE};
AudioMode audio_mode_{AUDIO_MODE_UDP};
bool udp_socket_running_{false};
bool start_udp_socket_();
}; };
template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<VoiceAssistant> { template<typename... Ts> class StartAction : public Action<Ts...>, public Parented<VoiceAssistant> {

View file

@ -12,6 +12,8 @@ ListEntitiesIterator::ListEntitiesIterator(WebServer *web_server) : web_server_(
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) { bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_sensor) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send( this->web_server_->events_.send(
this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state"); this->web_server_->binary_sensor_json(binary_sensor, binary_sensor->state, DETAIL_ALL).c_str(), "state");
return true; return true;
@ -19,30 +21,40 @@ bool ListEntitiesIterator::on_binary_sensor(binary_sensor::BinarySensor *binary_
#endif #endif
#ifdef USE_COVER #ifdef USE_COVER
bool ListEntitiesIterator::on_cover(cover::Cover *cover) { bool ListEntitiesIterator::on_cover(cover::Cover *cover) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->cover_json(cover, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
bool ListEntitiesIterator::on_fan(fan::Fan *fan) { bool ListEntitiesIterator::on_fan(fan::Fan *fan) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->fan_json(fan, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
#endif #endif
#ifdef USE_LIGHT #ifdef USE_LIGHT
bool ListEntitiesIterator::on_light(light::LightState *light) { bool ListEntitiesIterator::on_light(light::LightState *light) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->light_json(light, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) { bool ListEntitiesIterator::on_sensor(sensor::Sensor *sensor) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->sensor_json(sensor, sensor->state, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) { bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(), this->web_server_->events_.send(this->web_server_->switch_json(a_switch, a_switch->state, DETAIL_ALL).c_str(),
"state"); "state");
return true; return true;
@ -50,12 +62,16 @@ bool ListEntitiesIterator::on_switch(switch_::Switch *a_switch) {
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
bool ListEntitiesIterator::on_button(button::Button *button) { bool ListEntitiesIterator::on_button(button::Button *button) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->button_json(button, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) { bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send( this->web_server_->events_.send(
this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state"); this->web_server_->text_sensor_json(text_sensor, text_sensor->state, DETAIL_ALL).c_str(), "state");
return true; return true;
@ -63,6 +79,8 @@ bool ListEntitiesIterator::on_text_sensor(text_sensor::TextSensor *text_sensor)
#endif #endif
#ifdef USE_LOCK #ifdef USE_LOCK
bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) { bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->lock_json(a_lock, a_lock->state, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
@ -70,6 +88,8 @@ bool ListEntitiesIterator::on_lock(lock::Lock *a_lock) {
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
bool ListEntitiesIterator::on_climate(climate::Climate *climate) { bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->climate_json(climate, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
@ -77,6 +97,8 @@ bool ListEntitiesIterator::on_climate(climate::Climate *climate) {
#ifdef USE_NUMBER #ifdef USE_NUMBER
bool ListEntitiesIterator::on_number(number::Number *number) { bool ListEntitiesIterator::on_number(number::Number *number) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->number_json(number, number->state, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
@ -84,13 +106,24 @@ bool ListEntitiesIterator::on_number(number::Number *number) {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool ListEntitiesIterator::on_date(datetime::DateEntity *date) { bool ListEntitiesIterator::on_date(datetime::DateEntity *date) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->date_json(date, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->date_json(date, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
#endif #endif
#ifdef USE_DATETIME_TIME
bool ListEntitiesIterator::on_time(datetime::TimeEntity *time) {
this->web_server_->events_.send(this->web_server_->time_json(time, DETAIL_ALL).c_str(), "state");
return true;
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool ListEntitiesIterator::on_text(text::Text *text) { bool ListEntitiesIterator::on_text(text::Text *text) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->text_json(text, text->state, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->text_json(text, text->state, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
@ -98,6 +131,8 @@ bool ListEntitiesIterator::on_text(text::Text *text) {
#ifdef USE_SELECT #ifdef USE_SELECT
bool ListEntitiesIterator::on_select(select::Select *select) { bool ListEntitiesIterator::on_select(select::Select *select) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state"); this->web_server_->events_.send(this->web_server_->select_json(select, select->state, DETAIL_ALL).c_str(), "state");
return true; return true;
} }
@ -105,6 +140,8 @@ bool ListEntitiesIterator::on_select(select::Select *select) {
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) { bool ListEntitiesIterator::on_alarm_control_panel(alarm_control_panel::AlarmControlPanel *a_alarm_control_panel) {
if (this->web_server_->events_.count() == 0)
return true;
this->web_server_->events_.send( this->web_server_->events_.send(
this->web_server_->alarm_control_panel_json(a_alarm_control_panel, a_alarm_control_panel->get_state(), DETAIL_ALL) this->web_server_->alarm_control_panel_json(a_alarm_control_panel, a_alarm_control_panel->get_state(), DETAIL_ALL)
.c_str(), .c_str(),

View file

@ -44,6 +44,9 @@ class ListEntitiesIterator : public ComponentIterator {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
bool on_date(datetime::DateEntity *date) override; bool on_date(datetime::DateEntity *date) override;
#endif #endif
#ifdef USE_DATETIME_TIME
bool on_time(datetime::TimeEntity *time) override;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
bool on_text(text::Text *text) override; bool on_text(text::Text *text) override;
#endif #endif

View file

@ -416,6 +416,8 @@ void WebServer::handle_js_request(AsyncWebServerRequest *request) {
#ifdef USE_SENSOR #ifdef USE_SENSOR
void WebServer::on_sensor_update(sensor::Sensor *obj, float state) { void WebServer::on_sensor_update(sensor::Sensor *obj, float state) {
if (this->events_.count() == 0)
return;
this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); this->events_.send(this->sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@ -449,6 +451,8 @@ std::string WebServer::sensor_json(sensor::Sensor *obj, float value, JsonDetail
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) { void WebServer::on_text_sensor_update(text_sensor::TextSensor *obj, const std::string &state) {
if (this->events_.count() == 0)
return;
this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); this->events_.send(this->text_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_text_sensor_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@ -471,6 +475,8 @@ std::string WebServer::text_sensor_json(text_sensor::TextSensor *obj, const std:
#ifdef USE_SWITCH #ifdef USE_SWITCH
void WebServer::on_switch_update(switch_::Switch *obj, bool state) { void WebServer::on_switch_update(switch_::Switch *obj, bool state) {
if (this->events_.count() == 0)
return;
this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state"); this->events_.send(this->switch_json(obj, state, DETAIL_STATE).c_str(), "state");
} }
std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) { std::string WebServer::switch_json(switch_::Switch *obj, bool value, JsonDetail start_config) {
@ -532,6 +538,8 @@ void WebServer::handle_button_request(AsyncWebServerRequest *request, const UrlM
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) { void WebServer::on_binary_sensor_update(binary_sensor::BinarySensor *obj, bool state) {
if (this->events_.count() == 0)
return;
this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state"); this->events_.send(this->binary_sensor_json(obj, state, DETAIL_STATE).c_str(), "state");
} }
std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) { std::string WebServer::binary_sensor_json(binary_sensor::BinarySensor *obj, bool value, JsonDetail start_config) {
@ -553,7 +561,11 @@ void WebServer::handle_binary_sensor_request(AsyncWebServerRequest *request, con
#endif #endif
#ifdef USE_FAN #ifdef USE_FAN
void WebServer::on_fan_update(fan::Fan *obj) { this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state"); } void WebServer::on_fan_update(fan::Fan *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->fan_json(obj, DETAIL_STATE).c_str(), "state");
}
std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) { std::string WebServer::fan_json(fan::Fan *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) { return json::build_json([obj, start_config](JsonObject root) {
set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state, set_json_icon_state_value(root, obj, "fan-" + obj->get_object_id(), obj->state ? "ON" : "OFF", obj->state,
@ -623,6 +635,8 @@ void WebServer::handle_fan_request(AsyncWebServerRequest *request, const UrlMatc
#ifdef USE_LIGHT #ifdef USE_LIGHT
void WebServer::on_light_update(light::LightState *obj) { void WebServer::on_light_update(light::LightState *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state"); this->events_.send(this->light_json(obj, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_light_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@ -729,6 +743,8 @@ std::string WebServer::light_json(light::LightState *obj, JsonDetail start_confi
#ifdef USE_COVER #ifdef USE_COVER
void WebServer::on_cover_update(cover::Cover *obj) { void WebServer::on_cover_update(cover::Cover *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state"); this->events_.send(this->cover_json(obj, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_cover_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@ -798,6 +814,8 @@ std::string WebServer::cover_json(cover::Cover *obj, JsonDetail start_config) {
#ifdef USE_NUMBER #ifdef USE_NUMBER
void WebServer::on_number_update(number::Number *obj, float state) { void WebServer::on_number_update(number::Number *obj, float state) {
if (this->events_.count() == 0)
return;
this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state"); this->events_.send(this->number_json(obj, state, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_number_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@ -856,6 +874,8 @@ std::string WebServer::number_json(number::Number *obj, float value, JsonDetail
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
void WebServer::on_date_update(datetime::DateEntity *obj) { void WebServer::on_date_update(datetime::DateEntity *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->date_json(obj, DETAIL_STATE).c_str(), "state"); this->events_.send(this->date_json(obj, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@ -894,15 +914,63 @@ void WebServer::handle_date_request(AsyncWebServerRequest *request, const UrlMat
std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) { std::string WebServer::date_json(datetime::DateEntity *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) { return json::build_json([obj, start_config](JsonObject root) {
set_json_id(root, obj, "date-" + obj->get_object_id(), start_config); set_json_id(root, obj, "date-" + obj->get_object_id(), start_config);
std::string value = str_sprintf("%d-%d-%d", obj->year, obj->month, obj->day); std::string value = str_sprintf("%d-%02d-%02d", obj->year, obj->month, obj->day);
root["value"] = value; root["value"] = value;
root["state"] = value; root["state"] = value;
}); });
} }
#endif // USE_DATETIME_DATE #endif // USE_DATETIME_DATE
#ifdef USE_DATETIME_TIME
void WebServer::on_time_update(datetime::TimeEntity *obj) {
this->events_.send(this->time_json(obj, DETAIL_STATE).c_str(), "state");
}
void WebServer::handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match) {
for (auto *obj : App.get_times()) {
if (obj->get_object_id() != match.id)
continue;
if (request->method() == HTTP_GET && match.method.empty()) {
std::string data = this->time_json(obj, DETAIL_STATE);
request->send(200, "application/json", data.c_str());
return;
}
if (match.method != "set") {
request->send(404);
return;
}
auto call = obj->make_call();
if (!request->hasParam("value")) {
request->send(409);
return;
}
if (request->hasParam("value")) {
std::string value = request->getParam("value")->value().c_str();
call.set_time(value);
}
this->schedule_([call]() mutable { call.perform(); });
request->send(200);
return;
}
request->send(404);
}
std::string WebServer::time_json(datetime::TimeEntity *obj, JsonDetail start_config) {
return json::build_json([obj, start_config](JsonObject root) {
set_json_id(root, obj, "time-" + obj->get_object_id(), start_config);
std::string value = str_sprintf("%02d:%02d:%02d", obj->hour, obj->minute, obj->second);
root["value"] = value;
root["state"] = value;
});
}
#endif // USE_DATETIME_TIME
#ifdef USE_TEXT #ifdef USE_TEXT
void WebServer::on_text_update(text::Text *obj, const std::string &state) { void WebServer::on_text_update(text::Text *obj, const std::string &state) {
if (this->events_.count() == 0)
return;
this->events_.send(this->text_json(obj, state, DETAIL_STATE).c_str(), "state"); this->events_.send(this->text_json(obj, state, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_text_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@ -954,6 +1022,8 @@ std::string WebServer::text_json(text::Text *obj, const std::string &value, Json
#ifdef USE_SELECT #ifdef USE_SELECT
void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) { void WebServer::on_select_update(select::Select *obj, const std::string &state, size_t index) {
if (this->events_.count() == 0)
return;
this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state"); this->events_.send(this->select_json(obj, state, DETAIL_STATE).c_str(), "state");
} }
void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) { void WebServer::handle_select_request(AsyncWebServerRequest *request, const UrlMatch &match) {
@ -1008,6 +1078,8 @@ std::string WebServer::select_json(select::Select *obj, const std::string &value
#ifdef USE_CLIMATE #ifdef USE_CLIMATE
void WebServer::on_climate_update(climate::Climate *obj) { void WebServer::on_climate_update(climate::Climate *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state"); this->events_.send(this->climate_json(obj, DETAIL_STATE).c_str(), "state");
} }
@ -1149,6 +1221,8 @@ std::string WebServer::climate_json(climate::Climate *obj, JsonDetail start_conf
#ifdef USE_LOCK #ifdef USE_LOCK
void WebServer::on_lock_update(lock::Lock *obj) { void WebServer::on_lock_update(lock::Lock *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state"); this->events_.send(this->lock_json(obj, obj->state, DETAIL_STATE).c_str(), "state");
} }
std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) { std::string WebServer::lock_json(lock::Lock *obj, lock::LockState value, JsonDetail start_config) {
@ -1185,6 +1259,8 @@ void WebServer::handle_lock_request(AsyncWebServerRequest *request, const UrlMat
#ifdef USE_ALARM_CONTROL_PANEL #ifdef USE_ALARM_CONTROL_PANEL
void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) { void WebServer::on_alarm_control_panel_update(alarm_control_panel::AlarmControlPanel *obj) {
if (this->events_.count() == 0)
return;
this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state"); this->events_.send(this->alarm_control_panel_json(obj, obj->get_state(), DETAIL_STATE).c_str(), "state");
} }
std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj, std::string WebServer::alarm_control_panel_json(alarm_control_panel::AlarmControlPanel *obj,
@ -1290,6 +1366,11 @@ bool WebServer::canHandle(AsyncWebServerRequest *request) {
return true; return true;
#endif #endif
#ifdef USE_DATETIME_TIME
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "time")
return true;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text") if ((request->method() == HTTP_POST || request->method() == HTTP_GET) && match.domain == "text")
return true; return true;
@ -1415,6 +1496,13 @@ void WebServer::handleRequest(AsyncWebServerRequest *request) {
} }
#endif #endif
#ifdef USE_DATETIME_TIME
if (match.domain == "time") {
this->handle_time_request(request, match);
return;
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
if (match.domain == "text") { if (match.domain == "text") {
this->handle_text_request(request, match); this->handle_text_request(request, match);

View file

@ -8,9 +8,9 @@
#include <vector> #include <vector>
#ifdef USE_ESP32 #ifdef USE_ESP32
#include <deque>
#include <freertos/FreeRTOS.h> #include <freertos/FreeRTOS.h>
#include <freertos/semphr.h> #include <freertos/semphr.h>
#include <deque>
#endif #endif
#if USE_WEBSERVER_VERSION >= 2 #if USE_WEBSERVER_VERSION >= 2
@ -230,6 +230,15 @@ class WebServer : public Controller, public Component, public AsyncWebHandler {
std::string date_json(datetime::DateEntity *obj, JsonDetail start_config); std::string date_json(datetime::DateEntity *obj, JsonDetail start_config);
#endif #endif
#ifdef USE_DATETIME_TIME
void on_time_update(datetime::TimeEntity *obj) override;
/// Handle a time request under '/time/<id>'.
void handle_time_request(AsyncWebServerRequest *request, const UrlMatch &match);
/// Dump the time state with its value as a JSON string.
std::string time_json(datetime::TimeEntity *obj, JsonDetail start_config);
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
void on_text_update(text::Text *obj, const std::string &state) override; void on_text_update(text::Text *obj, const std::string &state) override;
/// Handle a text input request under '/text/<id>'. /// Handle a text input request under '/text/<id>'.

View file

@ -3,11 +3,11 @@
#include <esp_http_server.h> #include <esp_http_server.h>
#include <string>
#include <functional> #include <functional>
#include <vector>
#include <map> #include <map>
#include <set> #include <set>
#include <string>
#include <vector>
namespace esphome { namespace esphome {
namespace web_server_idf { namespace web_server_idf {
@ -251,6 +251,8 @@ class AsyncEventSource : public AsyncWebHandler {
void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0); void send(const char *message, const char *event = nullptr, uint32_t id = 0, uint32_t reconnect = 0);
size_t count() const { return this->sessions_.size(); }
protected: protected:
std::string url_; std::string url_;
std::set<AsyncEventSourceResponse *> sessions_; std::set<AsyncEventSourceResponse *> sessions_;

View file

@ -582,14 +582,14 @@ void WiFiComponent::wifi_pre_setup_() {
} }
WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() { WiFiSTAConnectStatus WiFiComponent::wifi_sta_connect_status_() {
auto status = WiFiClass::status(); auto status = WiFiClass::status();
if (status == WL_CONNECTED) { if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) {
return WiFiSTAConnectStatus::CONNECTED;
} else if (status == WL_CONNECT_FAILED || status == WL_CONNECTION_LOST) {
return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED; return WiFiSTAConnectStatus::ERROR_CONNECT_FAILED;
} else if (status == WL_NO_SSID_AVAIL) { } else if (status == WL_NO_SSID_AVAIL) {
return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND; return WiFiSTAConnectStatus::ERROR_NETWORK_NOT_FOUND;
} else if (s_sta_connecting) { } else if (s_sta_connecting) {
return WiFiSTAConnectStatus::CONNECTING; return WiFiSTAConnectStatus::CONNECTING;
} else if (status == WL_CONNECTED) {
return WiFiSTAConnectStatus::CONNECTED;
} }
return WiFiSTAConnectStatus::IDLE; return WiFiSTAConnectStatus::IDLE;
} }
@ -707,7 +707,7 @@ bool WiFiComponent::wifi_start_ap_(const WiFiAP &ap) {
*conf.ap.password = 0; *conf.ap.password = 0;
} else { } else {
conf.ap.authmode = WIFI_AUTH_WPA2_PSK; conf.ap.authmode = WIFI_AUTH_WPA2_PSK;
strncpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.ssid)); strncpy(reinterpret_cast<char *>(conf.ap.password), ap.get_password().c_str(), sizeof(conf.ap.password));
} }
conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP; conf.ap.pairwise_cipher = WIFI_CIPHER_TYPE_CCMP;

View file

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

View file

@ -0,0 +1,96 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor, esp32_ble_tracker
from esphome.const import (
CONF_MAC_ADDRESS,
CONF_TEMPERATURE,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_TEMPERATURE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_WATER_PERCENT,
STATE_CLASS_MEASUREMENT,
UNIT_CELSIUS,
UNIT_PERCENT,
CONF_ID,
CONF_MOISTURE,
CONF_ILLUMINANCE,
UNIT_LUX,
CONF_CONDUCTIVITY,
UNIT_MICROSIEMENS_PER_CENTIMETER,
ICON_FLOWER,
DEVICE_CLASS_BATTERY,
CONF_BATTERY_LEVEL,
)
DEPENDENCIES = ["esp32_ble_tracker"]
xiaomi_hhccjcy10_ns = cg.esphome_ns.namespace("xiaomi_hhccjcy10")
XiaomiHHCCJCY10 = xiaomi_hhccjcy10_ns.class_(
"XiaomiHHCCJCY10", esp32_ble_tracker.ESPBTDeviceListener, cg.Component
)
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(XiaomiHHCCJCY10),
cv.Required(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_TEMPERATURE): sensor.sensor_schema(
unit_of_measurement=UNIT_CELSIUS,
accuracy_decimals=1,
device_class=DEVICE_CLASS_TEMPERATURE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_MOISTURE): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_WATER_PERCENT,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_ILLUMINANCE): sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
accuracy_decimals=0,
device_class=DEVICE_CLASS_ILLUMINANCE,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_CONDUCTIVITY): sensor.sensor_schema(
unit_of_measurement=UNIT_MICROSIEMENS_PER_CENTIMETER,
icon=ICON_FLOWER,
accuracy_decimals=0,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
accuracy_decimals=0,
device_class=DEVICE_CLASS_BATTERY,
state_class=STATE_CLASS_MEASUREMENT,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
),
}
)
.extend(esp32_ble_tracker.ESP_BLE_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await esp32_ble_tracker.register_ble_device(var, config)
cg.add(var.set_address(config[CONF_MAC_ADDRESS].as_hex))
if temperature_config := config.get(CONF_TEMPERATURE):
sens = await sensor.new_sensor(temperature_config)
cg.add(var.set_temperature(sens))
if moisture_config := config.get(CONF_MOISTURE):
sens = await sensor.new_sensor(moisture_config)
cg.add(var.set_moisture(sens))
if illuminance_config := config.get(CONF_ILLUMINANCE):
sens = await sensor.new_sensor(illuminance_config)
cg.add(var.set_illuminance(sens))
if conductivity_config := config.get(CONF_CONDUCTIVITY):
sens = await sensor.new_sensor(conductivity_config)
cg.add(var.set_conductivity(sens))
if battery_level_config := config.get(CONF_BATTERY_LEVEL):
sens = await sensor.new_sensor(battery_level_config)
cg.add(var.set_battery_level(sens))

View file

@ -0,0 +1,68 @@
#include "xiaomi_hhccjcy10.h"
#include "esphome/core/log.h"
#include "esphome/core/helpers.h"
#ifdef USE_ESP32
namespace esphome {
namespace xiaomi_hhccjcy10 {
static const char *const TAG = "xiaomi_hhccjcy10";
void XiaomiHHCCJCY10::dump_config() {
ESP_LOGCONFIG(TAG, "Xiaomi HHCCJCY10");
LOG_SENSOR(" ", "Temperature", this->temperature_);
LOG_SENSOR(" ", "Moisture", this->moisture_);
LOG_SENSOR(" ", "Conductivity", this->conductivity_);
LOG_SENSOR(" ", "Illuminance", this->illuminance_);
LOG_SENSOR(" ", "Battery Level", this->battery_level_);
}
bool XiaomiHHCCJCY10::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
if (device.address_uint64() != this->address_) {
ESP_LOGVV(TAG, "parse_device(): unknown MAC address.");
return false;
}
ESP_LOGVV(TAG, "parse_device(): MAC address %s found.", device.address_str().c_str());
bool success = false;
for (auto &service_data : device.get_service_datas()) {
if (!service_data.uuid.contains(0x50, 0xFD)) {
ESP_LOGVV(TAG, "no tuya service data UUID.");
continue;
}
if (service_data.data.size() != 9) { // tuya alternate between two service data
continue;
}
const uint8_t *data = service_data.data.data();
if (this->temperature_ != nullptr) {
const int16_t temperature = encode_uint16(data[1], data[2]);
this->temperature_->publish_state((float) temperature / 10.0f);
}
if (this->moisture_ != nullptr)
this->moisture_->publish_state(data[0]);
if (this->conductivity_ != nullptr) {
const uint16_t conductivity = encode_uint16(data[7], data[8]);
this->conductivity_->publish_state((float) conductivity);
}
if (this->illuminance_ != nullptr) {
const uint32_t illuminance = encode_uint24(data[3], data[4], data[5]);
this->illuminance_->publish_state((float) illuminance);
}
if (this->battery_level_ != nullptr)
this->battery_level_->publish_state(data[6]);
success = true;
}
return success;
}
} // namespace xiaomi_hhccjcy10
} // namespace esphome
#endif

View file

@ -0,0 +1,38 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/esp32_ble_tracker/esp32_ble_tracker.h"
#ifdef USE_ESP32
namespace esphome {
namespace xiaomi_hhccjcy10 {
class XiaomiHHCCJCY10 : public Component, public esp32_ble_tracker::ESPBTDeviceListener {
public:
void set_address(uint64_t address) { this->address_ = address; }
bool parse_device(const esp32_ble_tracker::ESPBTDevice &device) override;
void dump_config() override;
float get_setup_priority() const override { return setup_priority::DATA; }
void set_temperature(sensor::Sensor *temperature) { this->temperature_ = temperature; }
void set_moisture(sensor::Sensor *moisture) { this->moisture_ = moisture; }
void set_conductivity(sensor::Sensor *conductivity) { this->conductivity_ = conductivity; }
void set_illuminance(sensor::Sensor *illuminance) { this->illuminance_ = illuminance; }
void set_battery_level(sensor::Sensor *battery_level) { this->battery_level_ = battery_level; }
protected:
uint64_t address_;
sensor::Sensor *temperature_{nullptr};
sensor::Sensor *moisture_{nullptr};
sensor::Sensor *conductivity_{nullptr};
sensor::Sensor *illuminance_{nullptr};
sensor::Sensor *battery_level_{nullptr};
};
} // namespace xiaomi_hhccjcy10
} // namespace esphome
#endif

View file

@ -676,6 +676,7 @@ CONF_REVERSED = "reversed"
CONF_RGB_ORDER = "rgb_order" CONF_RGB_ORDER = "rgb_order"
CONF_RGBW = "rgbw" CONF_RGBW = "rgbw"
CONF_RISING_EDGE = "rising_edge" CONF_RISING_EDGE = "rising_edge"
CONF_RMT_CHANNEL = "rmt_channel"
CONF_ROTATION = "rotation" CONF_ROTATION = "rotation"
CONF_RS_PIN = "rs_pin" CONF_RS_PIN = "rs_pin"
CONF_RTD_NOMINAL_RESISTANCE = "rtd_nominal_resistance" CONF_RTD_NOMINAL_RESISTANCE = "rtd_nominal_resistance"

View file

@ -81,13 +81,11 @@ void Application::loop() {
const uint32_t now = millis(); const uint32_t now = millis();
if (HighFrequencyLoopRequester::is_high_frequency()) { auto elapsed = now - this->last_loop_;
if (elapsed >= this->loop_interval_ || HighFrequencyLoopRequester::is_high_frequency()) {
yield(); yield();
} else { } else {
uint32_t delay_time = this->loop_interval_; uint32_t delay_time = this->loop_interval_ - elapsed;
if (now - this->last_loop_ < this->loop_interval_)
delay_time = this->loop_interval_ - (now - this->last_loop_);
uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time); uint32_t next_schedule = this->scheduler.next_schedule_in().value_or(delay_time);
// next_schedule is max 0.5*delay_time // next_schedule is max 0.5*delay_time
// otherwise interval=0 schedules result in constant looping with almost no sleep // otherwise interval=0 schedules result in constant looping with almost no sleep

View file

@ -42,6 +42,9 @@
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
#include "esphome/components/datetime/date_entity.h" #include "esphome/components/datetime/date_entity.h"
#endif #endif
#ifdef USE_DATETIME_TIME
#include "esphome/components/datetime/time_entity.h"
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
#include "esphome/components/text/text.h" #include "esphome/components/text/text.h"
#endif #endif
@ -128,6 +131,10 @@ class Application {
void register_date(datetime::DateEntity *date) { this->dates_.push_back(date); } void register_date(datetime::DateEntity *date) { this->dates_.push_back(date); }
#endif #endif
#ifdef USE_DATETIME_TIME
void register_time(datetime::TimeEntity *time) { this->times_.push_back(time); }
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
void register_text(text::Text *text) { this->texts_.push_back(text); } void register_text(text::Text *text) { this->texts_.push_back(text); }
#endif #endif
@ -305,6 +312,15 @@ class Application {
return nullptr; return nullptr;
} }
#endif #endif
#ifdef USE_DATETIME_TIME
const std::vector<datetime::TimeEntity *> &get_times() { return this->times_; }
datetime::TimeEntity *get_time_by_key(uint32_t key, bool include_internal = false) {
for (auto *obj : this->times_)
if (obj->get_object_id_hash() == key && (include_internal || !obj->is_internal()))
return obj;
return nullptr;
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
const std::vector<text::Text *> &get_texts() { return this->texts_; } const std::vector<text::Text *> &get_texts() { return this->texts_; }
text::Text *get_text_by_key(uint32_t key, bool include_internal = false) { text::Text *get_text_by_key(uint32_t key, bool include_internal = false) {
@ -401,6 +417,9 @@ class Application {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
std::vector<datetime::DateEntity *> dates_{}; std::vector<datetime::DateEntity *> dates_{};
#endif #endif
#ifdef USE_DATETIME_TIME
std::vector<datetime::TimeEntity *> times_{};
#endif
#ifdef USE_SELECT #ifdef USE_SELECT
std::vector<select::Select *> selects_{}; std::vector<select::Select *> selects_{};
#endif #endif

View file

@ -76,7 +76,12 @@ bool Component::cancel_timeout(const std::string &name) { // NOLINT
void Component::call_loop() { this->loop(); } void Component::call_loop() { this->loop(); }
void Component::call_setup() { this->setup(); } void Component::call_setup() { this->setup(); }
void Component::call_dump_config() { this->dump_config(); } void Component::call_dump_config() {
this->dump_config();
if (this->is_failed()) {
ESP_LOGE(TAG, " Component %s is marked FAILED", this->get_component_source());
}
}
uint32_t Component::get_component_state() const { return this->component_state_; } uint32_t Component::get_component_state() const { return this->component_state_; }
void Component::call() { void Component::call() {
@ -149,26 +154,26 @@ void Component::status_set_warning(const char *message) {
return; return;
this->component_state_ |= STATUS_LED_WARNING; this->component_state_ |= STATUS_LED_WARNING;
App.app_state_ |= STATUS_LED_WARNING; App.app_state_ |= STATUS_LED_WARNING;
ESP_LOGW(this->get_component_source(), "Warning set: %s", message); ESP_LOGW(TAG, "Component %s set Warning flag: %s", this->get_component_source(), message);
} }
void Component::status_set_error(const char *message) { void Component::status_set_error(const char *message) {
if ((this->component_state_ & STATUS_LED_ERROR) != 0) if ((this->component_state_ & STATUS_LED_ERROR) != 0)
return; return;
this->component_state_ |= STATUS_LED_ERROR; this->component_state_ |= STATUS_LED_ERROR;
App.app_state_ |= STATUS_LED_ERROR; App.app_state_ |= STATUS_LED_ERROR;
ESP_LOGE(this->get_component_source(), "Error set: %s", message); ESP_LOGE(TAG, "Component %s set Error flag: %s", this->get_component_source(), message);
} }
void Component::status_clear_warning() { void Component::status_clear_warning() {
if ((this->component_state_ & STATUS_LED_WARNING) == 0) if ((this->component_state_ & STATUS_LED_WARNING) == 0)
return; return;
this->component_state_ &= ~STATUS_LED_WARNING; this->component_state_ &= ~STATUS_LED_WARNING;
ESP_LOGW(this->get_component_source(), "Warning cleared"); ESP_LOGW(TAG, "Component %s cleared Warning flag", this->get_component_source());
} }
void Component::status_clear_error() { void Component::status_clear_error() {
if ((this->component_state_ & STATUS_LED_ERROR) == 0) if ((this->component_state_ & STATUS_LED_ERROR) == 0)
return; return;
this->component_state_ &= ~STATUS_LED_ERROR; this->component_state_ &= ~STATUS_LED_ERROR;
ESP_LOGE(this->get_component_source(), "Error cleared"); ESP_LOGE(TAG, "Component %s cleared Error flag", this->get_component_source());
} }
void Component::status_momentary_warning(const std::string &name, uint32_t length) { void Component::status_momentary_warning(const std::string &name, uint32_t length) {
this->status_set_warning(); this->status_set_warning();

View file

@ -217,6 +217,21 @@ void ComponentIterator::advance() {
} }
break; break;
#endif #endif
#ifdef USE_DATETIME_TIME
case IteratorState::DATETIME_TIME:
if (this->at_ >= App.get_times().size()) {
advance_platform = true;
} else {
auto *time = App.get_times()[this->at_];
if (time->is_internal() && !this->include_internal_) {
success = true;
break;
} else {
success = this->on_time(time);
}
}
break;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
case IteratorState::TEXT: case IteratorState::TEXT:
if (this->at_ >= App.get_texts().size()) { if (this->at_ >= App.get_texts().size()) {

View file

@ -60,6 +60,9 @@ class ComponentIterator {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
virtual bool on_date(datetime::DateEntity *date) = 0; virtual bool on_date(datetime::DateEntity *date) = 0;
#endif #endif
#ifdef USE_DATETIME_TIME
virtual bool on_time(datetime::TimeEntity *time) = 0;
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
virtual bool on_text(text::Text *text) = 0; virtual bool on_text(text::Text *text) = 0;
#endif #endif
@ -120,6 +123,9 @@ class ComponentIterator {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
DATETIME_DATE, DATETIME_DATE,
#endif #endif
#ifdef USE_DATETIME_TIME
DATETIME_TIME,
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
TEXT, TEXT,
#endif #endif

View file

@ -65,6 +65,12 @@ void Controller::setup_controller(bool include_internal) {
obj->add_on_state_callback([this, obj]() { this->on_date_update(obj); }); obj->add_on_state_callback([this, obj]() { this->on_date_update(obj); });
} }
#endif #endif
#ifdef USE_DATETIME_TIME
for (auto *obj : App.get_times()) {
if (include_internal || !obj->is_internal())
obj->add_on_state_callback([this, obj]() { this->on_time_update(obj); });
}
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
for (auto *obj : App.get_texts()) { for (auto *obj : App.get_texts()) {
if (include_internal || !obj->is_internal()) if (include_internal || !obj->is_internal())

View file

@ -34,6 +34,9 @@
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
#include "esphome/components/datetime/date_entity.h" #include "esphome/components/datetime/date_entity.h"
#endif #endif
#ifdef USE_DATETIME_TIME
#include "esphome/components/datetime/time_entity.h"
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
#include "esphome/components/text/text.h" #include "esphome/components/text/text.h"
#endif #endif
@ -85,6 +88,9 @@ class Controller {
#ifdef USE_DATETIME_DATE #ifdef USE_DATETIME_DATE
virtual void on_date_update(datetime::DateEntity *obj){}; virtual void on_date_update(datetime::DateEntity *obj){};
#endif #endif
#ifdef USE_DATETIME_TIME
virtual void on_time_update(datetime::TimeEntity *obj){};
#endif
#ifdef USE_TEXT #ifdef USE_TEXT
virtual void on_text_update(text::Text *obj, const std::string &state){}; virtual void on_text_update(text::Text *obj, const std::string &state){};
#endif #endif

View file

@ -36,6 +36,7 @@
#define USE_NUMBER #define USE_NUMBER
#define USE_DATETIME #define USE_DATETIME
#define USE_DATETIME_DATE #define USE_DATETIME_DATE
#define USE_DATETIME_TIME
#define USE_OTA #define USE_OTA
#define USE_OTA_PASSWORD #define USE_OTA_PASSWORD
#define USE_OTA_STATE_CALLBACK #define USE_OTA_STATE_CALLBACK

View file

@ -1,9 +1,7 @@
#ifdef USE_DATETIME
#include <regex>
#endif
#include "helpers.h"
#include "time.h" // NOLINT #include "time.h" // NOLINT
#include "helpers.h"
#include <cinttypes>
namespace esphome { namespace esphome {
@ -66,48 +64,47 @@ std::string ESPTime::strftime(const std::string &format) {
return timestr; return timestr;
} }
#ifdef USE_DATETIME
bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) { bool ESPTime::strptime(const std::string &time_to_parse, ESPTime &esp_time) {
// clang-format off uint16_t year;
std::regex dt_regex(R"(^ uint8_t month;
( uint8_t day;
(\d{4})-(\d{1,2})-(\d{1,2}) uint8_t hour;
(?:\s(?=.+)) uint8_t minute;
)? uint8_t second;
( int num;
(\d{1,2}):(\d{2})
(?::(\d{2}))?
)?
$)");
// clang-format on
std::smatch match; if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %02hhu:%02hhu:%02hhu %n", &year, &month, &day, // NOLINT
if (std::regex_match(time_to_parse, match, dt_regex) == 0) &hour, // NOLINT
&minute, // NOLINT
&second, &num) == 6 && // NOLINT
num == time_to_parse.size()) {
esp_time.year = year;
esp_time.month = month;
esp_time.day_of_month = day;
esp_time.hour = hour;
esp_time.minute = minute;
esp_time.second = second;
} else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu:%02hhu %n", &hour, &minute, &second, &num) == 3 && // NOLINT
num == time_to_parse.size()) {
esp_time.hour = hour;
esp_time.minute = minute;
esp_time.second = second;
} else if (sscanf(time_to_parse.c_str(), "%02hhu:%02hhu %n", &hour, &minute, &num) == 2 && // NOLINT
num == time_to_parse.size()) {
esp_time.hour = hour;
esp_time.minute = minute;
esp_time.second = 0;
} else if (sscanf(time_to_parse.c_str(), "%04hu-%02hhu-%02hhu %n", &year, &month, &day, &num) == 3 && // NOLINT
num == time_to_parse.size()) {
esp_time.year = year;
esp_time.month = month;
esp_time.day_of_month = day;
} else {
return false; return false;
if (match[1].matched) { // Has date parts
esp_time.year = parse_number<uint16_t>(match[2].str()).value_or(0);
esp_time.month = parse_number<uint8_t>(match[3].str()).value_or(0);
esp_time.day_of_month = parse_number<uint8_t>(match[4].str()).value_or(0);
} }
if (match[5].matched) { // Has time parts
esp_time.hour = parse_number<uint8_t>(match[6].str()).value_or(0);
esp_time.minute = parse_number<uint8_t>(match[7].str()).value_or(0);
if (match[8].matched) {
esp_time.second = parse_number<uint8_t>(match[8].str()).value_or(0);
} else {
esp_time.second = 0;
}
}
return true; return true;
} }
#endif
void ESPTime::increment_second() { void ESPTime::increment_second() {
this->timestamp++; this->timestamp++;
if (!increment_time_value(this->second, 0, 60)) if (!increment_time_value(this->second, 0, 60))

View file

@ -67,8 +67,6 @@ struct ESPTime {
this->day_of_year < 367 && this->month > 0 && this->month < 13; this->day_of_year < 367 && this->month > 0 && this->month < 13;
} }
#ifdef USE_DATETIME
/** Convert a string to ESPTime struct as specified by the format argument. /** Convert a string to ESPTime struct as specified by the format argument.
* @param time_to_parse null-terminated c string formatet like this: 2020-08-25 05:30:00. * @param time_to_parse null-terminated c string formatet like this: 2020-08-25 05:30:00.
* @param esp_time an instance of a ESPTime struct * @param esp_time an instance of a ESPTime struct
@ -76,8 +74,6 @@ struct ESPTime {
*/ */
static bool strptime(const std::string &time_to_parse, ESPTime &esp_time); static bool strptime(const std::string &time_to_parse, ESPTime &esp_time);
#endif
/// Convert a C tm struct instance with a C unix epoch timestamp to an ESPTime instance. /// Convert a C tm struct instance with a C unix epoch timestamp to an ESPTime instance.
static ESPTime from_c_tm(struct tm *c_tm, time_t c_time); static ESPTime from_c_tm(struct tm *c_tm, time_t c_time);

View file

@ -59,17 +59,14 @@ def clone_or_update(
) )
repo_dir = _compute_destination_path(key, domain) repo_dir = _compute_destination_path(key, domain)
fetch_pr_branch = ref is not None and ref.startswith("pull/")
if not repo_dir.is_dir(): if not repo_dir.is_dir():
_LOGGER.info("Cloning %s", key) _LOGGER.info("Cloning %s", key)
_LOGGER.debug("Location: %s", repo_dir) _LOGGER.debug("Location: %s", repo_dir)
cmd = ["git", "clone", "--depth=1"] cmd = ["git", "clone", "--depth=1"]
if ref is not None and not fetch_pr_branch:
cmd += ["--branch", ref]
cmd += ["--", url, str(repo_dir)] cmd += ["--", url, str(repo_dir)]
run_git_command(cmd) run_git_command(cmd)
if fetch_pr_branch: if ref is not None:
# We need to fetch the PR branch first, otherwise git will complain # We need to fetch the PR branch first, otherwise git will complain
# about missing objects # about missing objects
_LOGGER.info("Fetching %s", ref) _LOGGER.info("Fetching %s", ref)

View file

@ -154,13 +154,12 @@ extra_scripts = post:esphome/components/esp32/post_build.py.script
; These are common settings for the RP2040 using Arduino. ; These are common settings for the RP2040 using Arduino.
[common:rp2040-arduino] [common:rp2040-arduino]
extends = common:arduino extends = common:arduino
board_build.core = earlephilhower
board_build.filesystem_size = 0.5m board_build.filesystem_size = 0.5m
platform = https://github.com/maxgerhardt/platform-raspberrypi.git platform = https://github.com/maxgerhardt/platform-raspberrypi.git
platform_packages = platform_packages =
; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted ; earlephilhower/framework-arduinopico@~1.20602.0 ; Cannot use the platformio package until old releases stop getting deleted
earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.6.0/rp2040-3.6.0.zip earlephilhower/framework-arduinopico@https://github.com/earlephilhower/arduino-pico/releases/download/3.7.2/rp2040-3.7.2.zip
framework = arduino framework = arduino
lib_deps = lib_deps =

View file

@ -1,3 +1,4 @@
# Useful stuff when working in a development environment # Useful stuff when working in a development environment
clang-format==13.0.1 clang-format==13.0.1
clang-tidy==14.0.6 clang-tidy==14.0.6
yamllint==1.35.1

View file

@ -57,6 +57,7 @@ file_types = (
"", "",
) )
cpp_include = ("*.h", "*.c", "*.cpp", "*.tcc") cpp_include = ("*.h", "*.c", "*.cpp", "*.tcc")
py_include = ("*.py",)
ignore_types = (".ico", ".png", ".woff", ".woff2", "") ignore_types = (".ico", ".png", ".woff", ".woff2", "")
LINT_FILE_CHECKS = [] LINT_FILE_CHECKS = []
@ -265,7 +266,8 @@ def lint_end_newline(fname, content):
return None return None
CPP_RE_EOL = r"\s*?(?://.*?)?$" CPP_RE_EOL = r".*?(?://.*?)?$"
PY_RE_EOL = r".*?(?:#.*?)?$"
def highlight(s): def highlight(s):
@ -273,7 +275,7 @@ def highlight(s):
@lint_re_check( @lint_re_check(
r"^#define\s+([a-zA-Z0-9_]+)\s+([0-9bx]+)" + CPP_RE_EOL, r"^#define\s+([a-zA-Z0-9_]+)\s+(0b[10]+|0x[0-9a-fA-F]+|\d+)\s*?(?:\/\/.*?)?$",
include=cpp_include, include=cpp_include,
exclude=[ exclude=[
"esphome/core/log.h", "esphome/core/log.h",
@ -574,11 +576,6 @@ def lint_pragma_once(fname, content):
return None return None
@lint_re_check(
r"(whitelist|blacklist|slave)",
exclude=["script/ci-custom.py"],
flags=re.IGNORECASE | re.MULTILINE,
)
def lint_inclusive_language(fname, match): def lint_inclusive_language(fname, match):
# From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=49decddd39e5f6132ccd7d9fdc3d7c470b0061bb # From https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=49decddd39e5f6132ccd7d9fdc3d7c470b0061bb
return ( return (
@ -596,6 +593,21 @@ def lint_inclusive_language(fname, match):
) )
lint_re_check(
r"(whitelist|blacklist|slave)" + PY_RE_EOL,
include=py_include,
exclude=["script/ci-custom.py"],
flags=re.IGNORECASE | re.MULTILINE,
)(lint_inclusive_language)
lint_re_check(
r"(whitelist|blacklist|slave)" + CPP_RE_EOL,
include=cpp_include,
flags=re.IGNORECASE | re.MULTILINE,
)(lint_inclusive_language)
@lint_re_check(r"[\t\r\f\v ]+$") @lint_re_check(r"[\t\r\f\v ]+$")
def lint_trailing_whitespace(fname, match): def lint_trailing_whitespace(fname, match):
return "Trailing whitespace detected" return "Trailing whitespace detected"
@ -610,6 +622,7 @@ def lint_trailing_whitespace(fname, match):
"esphome/components/climate/climate.h", "esphome/components/climate/climate.h",
"esphome/components/cover/cover.h", "esphome/components/cover/cover.h",
"esphome/components/datetime/date_entity.h", "esphome/components/datetime/date_entity.h",
"esphome/components/datetime/time_entity.h",
"esphome/components/display/display.h", "esphome/components/display/display.h",
"esphome/components/fan/fan.h", "esphome/components/fan/fan.h",
"esphome/components/i2c/i2c.h", "esphome/components/i2c/i2c.h",

View file

@ -14,6 +14,8 @@ sensor:
name: QMC5883L Field Strength Z name: QMC5883L Field Strength Z
heading: heading:
name: QMC5883L Heading name: QMC5883L Heading
temperature:
name: QMC5883L Temperature
range: 800uT range: 800uT
oversampling: 256x oversampling: 256x
update_interval: 15s update_interval: 15s

View file

@ -14,6 +14,8 @@ sensor:
name: QMC5883L Field Strength Z name: QMC5883L Field Strength Z
heading: heading:
name: QMC5883L Heading name: QMC5883L Heading
temperature:
name: QMC5883L Temperature
range: 800uT range: 800uT
oversampling: 256x oversampling: 256x
update_interval: 15s update_interval: 15s

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